Commit Graph

105 Commits

Author SHA1 Message Date
16c21c17d2 feat(partner-portal): Sprint 2 — partner-me events CRUD endpoints
Add partner-scoped event endpoints under /api/v1/partners/me/events/:
- GET/POST  /partners/me/events/            → list + create
- GET/PATCH/DELETE /partners/me/events/{pk}/ → detail + update + delete
- POST /partners/me/events/{pk}/duplicate/   → clone as draft

All endpoints enforce partner ownership via _require_owned_event().
Create auto-sets partner FK + source='partner'. Duplicate always
resets status to 'created' (draft).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 11:20:37 +05:30
761b702e57 feat(partner-portal): Sprint 1 — partner-me settings endpoints
Add 4 self-service endpoints under /api/v1/partners/me/:
- GET/PUT  /partners/me/profile/      → name, email, phone, website, bio
- GET/PUT  /partners/me/notifications/ → 4 boolean notification prefs
- GET/PUT  /partners/me/payout/        → bank account + payout schedule
- POST     /partners/me/change-password/ → current+new password change

Model changes (partner/models.py + migration 0002):
- Partner.bio TextField
- Partner.payout_* fields (holder name, account number, IFSC, bank name, schedule)
- Partner.notif_* boolean fields (new_booking, event_status, payout_update, weekly_report)

Auth: simplejwt Bearer token (same as all admin_api views).
Role guard: _require_partner() enforces partner/partner_manager/partner_staff
and verifies user.partner FK is non-null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 11:02:34 +05:30
46b391bd51 fix: allow partner portal SSR to reach admin_api (/me/) for impersonation
ALLOWED_HOSTS was missing partner.eventifyplus.com + docker internal
hostnames (eventify-backend, eventify-django). Partner Next.js
server-side authorize() fetch to /api/v1/auth/me/ was rejected with
HTTP 400 DisallowedHost, so admin "Login as Partner" redirected to
/login?error=ImpersonationFailed instead of /dashboard.

Also added `partner` FK to UserSerializer so the /me/ response exposes
the partner id the portal needs to set session.user.partnerId.

Deployed to both eventify-backend and eventify-django containers via
docker cp + HUP.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 10:30:58 +05:30
d9a2af7168 fix(reviews): expose profile_photo in /api/reviews/list payload
_serialize_review() was not returning the reviewer's profile_picture URL,
so the consumer app had no field to key off and always rendered DiceBear
cartoons for every reviewer.

- Resolves r.reviewer.profile_picture.url when non-empty
- Treats default.png placeholder as no-photo (returns empty string)
- Defensive try/except around FK dereference, same pattern as user.py

Paired with mvnew consumer v1.7.8 which consumes the new field.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 00:32:00 +05:30
f75d4f2915 fix: add missing get_object_or_404 import in PartnerImpersonateView 2026-04-21 23:18:37 +05:30
05de552820 feat(partners): add PartnerImpersonateView for admin Login-as-Partner
POST /api/v1/partners/<pk>/impersonate/ mints a short-lived JWT for the
partner's primary partner_manager user. Returns access + refresh tokens
so the partner portal can create a session without requiring a password.
Writes a partner.impersonated audit log row with admin username, partner
name, and impersonated user for traceability.

Closes: admin Login-as-Partner showing "Partner not found" (mock data)
2026-04-21 22:55:08 +05:30
f85188ca6b revert: remove partner role login block from AdminLoginView
Partner accounts must be able to log into admin.eventifyplus.com.
ProtectedRoute empty-module redirect (frontend) handles the access
boundary — no backend login gate needed.
2026-04-21 18:38:10 +05:30
64ff08b2b2 security: block non-admin roles from AdminLoginView
AdminLoginView previously accepted any valid credential regardless of
role. partner_manager / partner / partner_staff / partner_customer /
customer accounts could obtain admin JWTs and land on admin.eventifyplus.com,
where protected pages would render generic "not found" empty states.

Now returns 403 for those roles unless the user is a superuser or has an
attached StaffProfile. Writes an auth.admin_login_failed audit row with
reason=non_admin_role.

Closes gap reported for novakopro@gmail.com on /partners/3.
2026-04-21 18:35:16 +05:30
4a9f754fda feat(rbac): add Reviews/Contributions/Leads/Audit scope defs + fix reviews module mapping (v1.14.0)
- SCOPE_DEFINITIONS extended with 13 new scopes across 4 categories so the
  admin Roles & Permissions grid and new Base Permissions tab can grant
  module-level access
- StaffProfile.SCOPE_TO_MODULE was missing 'reviews': 'reviews' — staff with
  reviews.* scopes could not resolve the Reviews module in their sidebar
- NotificationSchedule CRUD views now emit AuditLog rows
  (notification.schedule.created / .updated / .deleted) matching the
  v1.13.0 audit coverage pattern

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-21 17:11:01 +05:30
66e41ba647 feat(audit): extend audit coverage to all admin interactions (v1.13.0)
- _audit_log helper: optional user= kwarg for login-time calls
- AdminLoginView: auth.admin_login / auth.admin_login_failed
- PartnerStatusView: partner.status_changed (atomic)
- PartnerOnboardView: partner.onboarded
- PartnerStaffCreateView: partner.staff.created
- EventCreateView/UpdateView/DeleteView: event.created/updated/deleted (atomic)
- EventPrimaryImageView: event.primary_image_changed
- SettlementReleaseView: settlement.released (atomic)
- ReviewDeleteView: review.deleted (atomic)
- LeadUpdateView: lead.updated
- PaymentGatewaySettingsView: gateway.created/updated/deleted
- tests: AuthAuditEmissionTests + EventCrudAuditTests (16 total, all green)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 13:42:02 +05:30
2c60a82704 feat(audit): add Audit Log module — coverage, metrics endpoint, indexes
- UserStatusView, EventModerationView, ReviewModerationView,
  PartnerKYCReviewView: each state change now emits _audit_log()
  inside the same transaction.atomic() block so the log stays
  consistent with DB state on partial failure
- AuditLogMetricsView: GET /api/v1/rbac/audit-log/metrics/ returns
  total/today/week/distinct_users/by_action_group; 60 s cache with
  ?nocache=1 bypass
- AuditLogListView: free-text search (Q over action/target/user),
  page_size bounded to [1, 200]
- accounts.User.ALL_MODULES += 'audit-log';
  StaffProfile.SCOPE_TO_MODULE['audit'] = 'audit-log'
- Migration 0005: composite indexes (action,-created_at) and
  (target_type,target_id) on AuditLog
- admin_api/tests.py: 11 tests covering list shape, search,
  page bounds, metrics shape+nocache, suspend/ban/reinstate
  audit emission

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 12:39:38 +05:30
9cde886bd4 feat(notifications): add test-send endpoint for single-address preview
POST /api/v1/notifications/schedules/<pk>/test-send/ accepts {"email": "..."},
renders the schedule's email, delivers to that address only with [TEST] prefix.
Does not touch last_run_at or last_status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 11:53:33 +05:30
a8751b5183 feat(notifications): add scheduled email notification system
- NotificationSchedule + NotificationRecipient models with initial migration
- emails.py BUILDERS registry + events_expiring_this_week HTML email builder (IST week bounds)
- send_scheduled_notifications management command (croniter due-check + select_for_update(skip_locked))
- 6 admin API endpoints under /api/v1/notifications/ (types, schedules CRUD, recipients CRUD, send-now)
- date_from/date_to filters on EventListView for dashboard card
- croniter>=2.0.0 added to requirements

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 11:41:46 +05:30
170208d3e5 fix(search): include name field in EventListAPI full-text search
title__icontains only searched the optional title column; most events
are stored in the required name field, so Thrissur Pooram and similar
events were invisible to the q= search filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 12:03:05 +05:30
ca24a4cb23 fix: restore requests package for google-auth transport
google.auth.transport.requests requires requests — removed it incorrectly
during Clerk cleanup since requests is also a google-auth runtime dependency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 01:35:28 +05:30
e0a491e8cb security: fix GoogleLoginView audience check + replace Clerk with direct GIS flow
- verify_oauth2_token now passes GOOGLE_CLIENT_ID as third arg (audience check)
- fail-closed: returns 503 if GOOGLE_CLIENT_ID env var is not set
- add GOOGLE_CLIENT_ID = os.environ.get('GOOGLE_CLIENT_ID', '') to settings
- replace ClerkLoginViewTests with GoogleLoginViewTests (4 cases)
- update requirements-docker.txt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 01:31:18 +05:30
aa2846b884 fix(admin): include eventifyId in _serialize_user for users table
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 06:51:14 +05:30
086bbbf546 fix(registration): seed gamification profile with eventify_id on account creation
Added _seed_gamification_profile() helper that inserts a row into
user_gamification_profiles immediately after user.save(), so every new
account has their eventify_id in the Node.js gamification DB from day one.
Non-fatal: failures are logged as warnings without blocking registration.
Called in both RegisterView (mobile) and WebRegisterView (web).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 06:47:58 +05:30
60d98f1ae8 feat: add profile_photo to StatusView response
Expose profile_photo in /user/status/ so the Flutter app can
hydrate the profile picture for existing sessions without requiring
a re-login.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 16:12:27 +05:30
9aa7c01efe feat(favorites): add EventLike model, favorites API, and notifications module
- EventLike model (user × event unique constraint, indexed)
- contributed_by field on Event (EVT ID or email of community contributor)
- Favorites API endpoints: toggle-like, my-likes, my-liked-events
- Notifications app wired into main urls.py at /api/notifications/
- accounts migration 0014_merge_0013 (resolves split 0013 branches)
- requirements.txt updated
2026-04-07 12:56:25 +05:30
d04891c064 feat(leads): link consumer account to lead on submission
- user_account FK on Lead model (SET_NULL, related_name='submitted_leads')
- Migration 0004_lead_user_account
- ScheduleCallView auto-matches consumer account by email on create
- _serialize_lead now returns userAccount: {id, name, email, phone, eventifyId, profilePicture}
2026-04-07 11:52:41 +05:30
9142b8fedb feat(leads): add Lead Manager module with full admin and consumer endpoints
- Lead model in admin_api with status/priority/source/assigned_to fields
- Admin API: metrics, list, detail, update views at /api/v1/leads/
- Consumer API: public ScheduleCallView at /api/leads/schedule-call/
- RBAC: 'leads' module registered in ALL_MODULES and StaffProfile scopes
- Migration 0003_lead with indexes on status, priority, created_at, email
2026-04-07 10:48:04 +05:30
14c474ea87 docs: changelog v1.8.3 — TopEventsAPI fix 2026-04-06 22:21:32 +05:30
8d0e801d86 fix(top-events): remove token gate, add event_status filter and event_type_name
TopEventsAPI had AllowAny permission but still called
validate_token_and_get_user(), blocking unauthenticated carousel fetches.
Also added event_status='published' filter and event_type_name resolution
(model_to_dict only returns the FK integer, not the string name).
2026-04-06 22:16:41 +05:30
a29e8d2892 fix(featured-events): resolve event_type FK to name string in API response
model_to_dict() returns event_type as an integer PK; the DHS frontend
reads ev.event_type_name to show the category badge. Added
event_type_name resolution so the carousel displays e.g. "Festivals".
2026-04-06 21:44:11 +05:30
2fefdd16c9 docs: changelog v1.8.1 — FeaturedEventsAPI token gate fix 2026-04-06 19:45:46 +05:30
8ae97dcdc7 fix(featured-events): remove token gate from FeaturedEventsAPI
FeaturedEventsAPI had AllowAny permission but still called
validate_token_and_get_user(), causing it to return a token-required
error for unauthenticated requests from the desktop hero slider.

Removed the token check entirely — the endpoint is public by design.
Also tightened the queryset to event_status='published' to match
ConsumerFeaturedEventsView behaviour.
2026-04-06 19:41:25 +05:30
05770d6d21 feat(carousel): wire is_featured flag to consumer featured events API
ConsumerFeaturedEventsView now includes events with is_featured=True
alongside ad placement results. Placement events retain priority;
is_featured events are appended, deduped, and capped at 10 total.
2026-04-06 17:25:19 +05:30
b8a69ceae2 fix(accounts): add merge migration to resolve conflicting eventify_id migrations
0011_user_eventify_id and 0012_user_eventify_id both added eventify_id field
from different base migrations. Created 0013 merge node to unify the graph.
2026-04-06 12:30:49 +05:30
b2a2cbad5f feat(ad_control): new AdSurface + AdPlacement module for placement-based featured/top events
- New ad_control Django app: AdSurface + AdPlacement models with GLOBAL/LOCAL scope
- Admin CRUD API at /api/v1/ad-control/ (JWT-protected): surfaces, placements, picker events
- Placement lifecycle: DRAFT → ACTIVE|SCHEDULED → EXPIRED|DISABLED
- LOCAL scope: Haversine ≤ 50km from event lat/lng (fixed radius, no config needed)
- Consumer APIs: /api/events/featured-events/ and /api/events/top-events/ rewritten
  to use placement-based queries (same URL paths + response shape — no breaking changes)
- Seed command: seed_surfaces --migrate converts existing is_featured/is_top_event booleans
- mount: admin_api/urls.py → ad-control/, mobile_api/urls.py → replaced consumer views
- settings.py: added ad_control to INSTALLED_APPS
2026-04-06 12:10:06 +05:30
635a1224cd fix: add localhost:8080 to CORS_ALLOWED_ORIGINS for Flutter web preview 2026-04-04 18:56:47 +05:30
3a3f6d4179 feat: HOME-007 — server-side event title/description search (q param) 2026-04-04 17:33:56 +05:30
c9afbcf3cc feat(accounts): home district with 6-month cooldown
- accounts/models.py: add district_changed_at DateTimeField + VALID_DISTRICTS constant (14 Kerala districts)
- migration 0013_user_district_changed_at: nullable DateTimeField, no backfill
- WebRegisterForm: accept optional district during signup, stamp district_changed_at
- UpdateProfileView: enforce 183-day cooldown with human-readable error
- LoginView/WebRegisterView/StatusView: include district_changed_at in responses

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-04 10:42:44 +05:30
ac2b2ba242 fix(users): add include_all param to UserListView for contributor search
Superusers (admins) were excluded by the is_superuser=False filter,
making them unsearchable in the contributor picker. Pass include_all=1
to bypass this filter when searching for event contributors.
2026-04-03 17:41:45 +05:30
a208ddf1f7 fix(users): add eventify_id__icontains to UserListView search filter
EVT-XXXXXXXX searches were returning no results because the Q filter
only covered first_name, last_name, email, username, phone_number.
2026-04-03 17:37:09 +05:30
4a24e9cdca feat(events): add EventDeleteView for permanent event deletion
- Add EventDeleteView with DELETE /api/v1/events/<pk>/delete/
- Register delete URL in admin_api/urls.py
2026-04-03 17:21:26 +05:30
bae9ac9e23 docs: add v1.6.1 and v1.6.2 CHANGELOG entries
Documents StatusView eventify_id addition and the security fix
that stops internal Python exceptions from reaching API callers.
2026-04-03 09:27:15 +05:30
a5bdde278d security: never expose internal exceptions to API callers
All except blocks in user.py and events.py now log the real
error server-side (via eventify_logger) and return a generic
"An unexpected server error occurred." message to the client.
Python tracebacks, model field names, and ORM errors are no
longer visible in API responses.
2026-04-03 09:23:26 +05:30
fc5aa555e5 feat(api): return eventify_id in StatusView response
Adds `eventify_id` to the `/api/user/status/` endpoint so that
`initProfileTickets` can fetch the EVT-XXXXXXXX badge for users
whose localStorage session pre-dates the eventify_id login field.
2026-04-03 09:14:37 +05:30
9d61967350 feat: add Haversine radius-based location filtering to EventListAPI
- Add _haversine_km() great-circle distance function (pure Python, no PostGIS)
- EventListAPI now accepts optional latitude, longitude, radius_km params
- Bounding-box SQL pre-filter narrows candidates, Haversine filters precisely
- Progressive radius expansion: 10km → 25km → 50km → 100km if <6 results
- Backward compatible: falls back to pincode filtering when no coords provided
- Response includes radius_km field showing effective search radius used
- Guard radius_km float conversion against malformed input
- Use `is not None` checks for lat/lng (handles 0.0 edge case)
- Expansion list filters to only try radii larger than requested
2026-04-03 08:56:00 +05:30
99f376506d docs: add CHANGELOG.md and update README version to 1.6.0
- CHANGELOG.md: full history from 1.0.0 → 1.6.0 (Keep a Changelog format)
- README.md: bump version badge 1.5.0 → 1.6.0, add changelog summary table
2026-04-02 11:03:18 +05:30
384797551f feat: add Eventify ID (EVT-XXXXXXXX) to User model and all APIs
- Add eventify_id CharField (unique, indexed, editable=False) to User
- Auto-generate on save() with charset excluding I/O/0/1 for clarity
- Migration 0012: add field nullable, backfill all existing users, make non-null
- Sync migration 0011 (allowed_modules) pulled from server
- Expose eventify_id in accounts/api.py, partner/api.py serializers
- Expose eventify_id in mobile_api login response (populates localStorage)
2026-04-02 10:26:08 +05:30
255519473b feat: add RBAC migrations, user modules, admin API updates, and utility scripts 2026-04-02 04:06:02 +00:00
1b6185c758 security: fix SMTP credential exposure and auth bypass
- C-1: Move EMAIL_HOST_PASSWORD to os.environ (was hardcoded plaintext)
- C-2: Enable token-user cross-validation in validate_token_and_get_user()
  (compares token.user_id with user.id to prevent impersonation)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 19:29:42 +00:00
43123d0ff1 feat: add source field with 3 options, fix EventListAPI fallback, add is_eventify_event to API response
- Event.source field updated: eventify, community, partner (radio select in form)
- EventListAPI: fallback to all events when pincode returns < 6
- EventListAPI: include is_eventify_event and source in serializer
- Admin API: add source to list serializer
- Django admin: source in list_display, list_filter, list_editable
- Event form template: proper radio button rendering for source field

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 11:23:03 +00:00
388057b641 feat: add user search/filter, banned metric, mobile review API, event detail improvements
- admin_api/views.py: Add banned count to UserMetrics, fix server-side search/filter in UserListView
- admin_api/models.py: Add ReviewInteraction model, display_name/is_verified/helpful_count/flag_count to Review
- mobile_api/views/reviews.py: Customer-facing review submit/list/helpful/flag endpoints
- mobile_api/urls.py: Wire review API routes
- mobile_api/views/events.py: Event detail and listing improvements
- Security hardening across API modules
2026-03-26 09:50:03 +00:00
5a2752a2de fix: security audit remediation — Django settings + payment gateway API
- ALLOWED_HOSTS: wildcard replaced with explicit domain list (#15)
- CORS_ALLOWED_ORIGINS: added app.eventifyplus.com (#16)
- CSRF_TRUSTED_ORIGINS: added app.eventifyplus.com (#18)
- JWT ACCESS_TOKEN_LIFETIME: 1 day reduced to 30 minutes (#19)
- ROTATE_REFRESH_TOKENS enabled
- SECRET_KEY: removed unsafe fallback, crash on missing env var
- Added ActivePaymentGatewayView for dynamic gateway config (#1, #5, #20)
- Added PaymentGatewaySettingsView CRUD for admin panel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 12:25:11 +00:00
b12f4952b3 fix: update admin_api migration dependency to existing events migration
0001_initial was referencing events.0011_dashboard_indexes which no
longer exists as a file on disk (the DB has it applied but the file
was removed). Updated dependency to 0010_merge_20260324_1443 which
is the latest events migration file present, resolving the
NodeNotFoundError on management commands.
2026-03-25 11:49:15 +05:30
ea378f19b1 refactor: migrate users to PostgreSQL, remove SQLite secondary DB
Users have been migrated from eventify-django SQLite to eventify-backend
PostgreSQL. The temporary users_db workaround is no longer needed:

- settings.py: removed users_db SQLite secondary database config
- views.py: removed _user_db()/_user_qs() helpers; user views now query
  the default PostgreSQL directly with plain User.objects.filter()
- docker-compose.yml: SQLite read-only volume mount removed

All 27 users (25 non-superuser customers) now live in PostgreSQL.
2026-03-25 11:43:12 +05:30
54aa7ce06e fix: read real users from eventify-django SQLite via secondary database
The admin_api was querying eventify-backend's empty PostgreSQL. Real users
live in eventify-django's SQLite (db.sqlite3 on host). Fix:

- settings.py: auto-adds 'users_db' database config when users_db.sqlite3
  is mounted into the container (read-only volume in docker-compose)
- views.py: _user_db() helper selects the correct database alias;
  _user_qs() defers 'partner' field (absent from older SQLite schema)
- UserMetricsView, UserListView, UserDetailView, UserStatusView all use
  _user_qs() so they query the 25 real registered customers
2026-03-25 11:38:03 +05:30