_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>
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)
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.
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.
- 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>
- 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>
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>
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>
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>
- 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>
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>
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>
- 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
- 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}
- 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
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).
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".
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.
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.
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.
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.
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.
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.
- 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
- 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)
- 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>
- 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>
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.
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.
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
- UserListView and UserMetricsView now filter is_superuser=False so only
end-user accounts appear in the admin Users page (not admin/staff)
- _serialize_user now returns avatarUrl from profile_picture field so the
grid view renders profile images instead of broken img tags
- RegisterForm and WebRegisterForm now set is_customer=True and
role='customer' on save so future registrants are correctly classified