Phase 0 of the MinIO upload migration (FUNCTIONAL_AUDIT §4 item 2).
Schema + config only — Phase 1 will wire TrackService.UploadTrack()
to actually route writes to S3 when the flag is flipped.
Schema (migration 985):
- tracks.storage_backend VARCHAR(16) NOT NULL DEFAULT 'local'
CHECK in ('local', 's3')
- tracks.storage_key VARCHAR(512) NULL (S3 object key when backend=s3)
- Partial index on storage_backend = 's3' (migration progress queries)
- Rollback drops both columns + index; safe only while all rows are
still 'local' (guard query in the rollback comment)
Go model (internal/models/track.go):
- StorageBackend string (default 'local', not null)
- StorageKey *string (nullable)
- Both tagged json:"-" — internal plumbing, never exposed publicly
Config (internal/config/config.go):
- New field Config.TrackStorageBackend
- Read from TRACK_STORAGE_BACKEND env var (default 'local')
- Production validation rule #11 (ValidateForEnvironment):
- Must be 'local' or 's3' (reject typos like 'S3' or 'minio')
- If 's3', requires AWS_S3_ENABLED=true (fail fast, do not boot with
TrackStorageBackend=s3 while S3StorageService is nil)
- Dev/staging warns and falls back to 'local' instead of fail — keeps
iteration fast while still flagging misconfig.
Docs:
- docs/ENV_VARIABLES.md §13 restructured as "HLS + track storage backend"
with a migration playbook (local → s3 → migrate-storage CLI)
- docs/ENV_VARIABLES.md §28 validation rules: +2 entries for new rules
- docs/ENV_VARIABLES.md §29 drift findings: TRACK_STORAGE_BACKEND added
to "missing from template" list before it was fixed
- veza-backend-api/.env.template: TRACK_STORAGE_BACKEND=local with
comment pointing at Phase 1/2/3 plans
No behavior change yet — TrackService.UploadTrack() still hardcodes the
local path via copyFileAsync(). Phase 1 wires it.
Refs:
- AUDIT_REPORT.md §9 item (deferrals v1.0.8)
- FUNCTIONAL_AUDIT.md §4 item 2 "Stockage local disque only"
- /home/senke/.claude/plans/audit-fonctionnel-wild-hickey.md Item 3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First item of the v1.0.6 backlog surfaced by the v1.0.5 smoke test: a
brand-new account could register, verify email, and log in — but
attempting to upload hit a 403 because `role='user'` doesn't pass the
`RequireContentCreatorRole` middleware. The only way to get past that
gate was an admin DB update.
This commit wires the self-service path decided in the v1.0.6
specification:
* One-way flip from `role='user'` to `role='creator'`, gated strictly
on `is_verified=true` (the verification-email flow we restored in
Fix 2 of the hardening sprint).
* No KYC, no cooldown, no admin validation. The conscious click
already requires ownership of the email address.
* Downgrade is out of scope — a creator who wants back to `user`
opens a support ticket. Avoids the "my uploads orphaned" edge case.
Backend
* Migration `977_users_promoted_to_creator_at.sql`: nullable
`TIMESTAMPTZ` column, partial index for non-null values. NULL
preserves the semantic for users who never self-promoted
(out-of-band admin assignments stay distinguishable from organic
creators for audit/analytics).
* `models.User`: new `PromotedToCreatorAt *time.Time` field.
* `handlers.UpgradeToCreator(db, auditService, logger)`:
- 401 if no `user_id` in context (belt-and-braces — middleware
should catch this first)
- 404 if the user row is missing
- 403 `EMAIL_NOT_VERIFIED` when `is_verified=false`
- 200 idempotent with `already_elevated=true` when the caller is
already creator / premium / moderator / admin / artist /
producer / label (same set accepted by
`RequireContentCreatorRole`)
- 200 with the new role + `promoted_to_creator_at` on the happy
path. The UPDATE is scoped `WHERE role='user'` so a concurrent
admin assignment can't be silently overwritten; the zero-rows
case reloads and returns `already_elevated=true`.
- audit logs a `user.upgrade_creator` action with IP, UA, and
the role transition metadata. Non-fatal on failure — the
upgrade itself already committed.
* Route: `POST /api/v1/users/me/upgrade-creator` under the existing
protected users group (RequireAuth + CSRF).
Frontend
* `AccountSettingsCreatorCard`: new card in the Account tab of
`/settings`. Completely hidden for users already on a creator-tier
role (no "you're already a creator" clutter). Unverified users see
a disabled-but-explanatory state with a "Resend verification"
CTA to `/verify-email/resend`. Verified users see the "Become an
artist" button, which POSTs to `/users/me/upgrade-creator` and
refetches the user on success.
* `upgradeToCreator()` service in `features/settings/services/`.
* Copy is deliberately explicit that the change is one-way.
Tests
* 6 Go unit tests covering: happy path (role + timestamp), unverified
refused, already-creator idempotent (timestamp preserved),
admin-assigned idempotent (no timestamp overwrite), user-not-found,
no-auth-context.
* 7 Vitest tests covering: verified button visible, unverified state
shown, card hidden for creator, card hidden for admin, success +
refetch, idempotent message, server error via toast.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
backend-ci.yml's `test -z "$(gofmt -l .)"` strict gate (added in
13c21ac11) failed on a backlog of unformatted files. None of the
85 files in this commit had been edited since the gate was added
because no push touched veza-backend-api/** in between, so the
gate never fired until today's CI fixes triggered it.
The diff is exclusively whitespace alignment in struct literals
and trailing-space comments. `go build ./...` and the full test
suite (with VEZA_SKIP_INTEGRATION=1 -short) pass identically.
Update RabbitMQ config and eventbus. Improve secret filter logging.
Refine presence, cloud, and social services. Update announcement and
feature flag handlers. Add track_likes updated_at migration. Rebuild
seed binary.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add daily_track_stats, geographic_play_stats, track_discovery_sources tables.
Add source and country_code columns to track_plays.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Export: table data_exports, POST /me/export (202), GET /me/exports, messages+playback_history
- Notification email quand ZIP prêt, rate limit 3/jour
- Suppression: keep_public_tracks, anonymisation PII complète (users, user_profiles)
- HardDeleteWorker: final anonymization après 30 jours
- Frontend: POST export, checkbox keep_public_tracks
- MSW handlers pour Storybook
- Ajout de type:user_role dans le tag GORM du champ Role
- Amélioration de la détection d'erreurs ENUM dans le service Register
- L'endpoint /auth/register retourne maintenant 201 OK avec tokens
- Score production: 52/70 → 58/70
- PROD-010 marqué comme fixed (P0 blocker résolu)
- Added APIKey field to Webhook model with unique index
- Implemented GenerateAPIKey() method using crypto/rand for secure key generation
- Implemented ValidateAPIKey() method to authenticate webhook requests
- Implemented RegenerateAPIKey() method to rotate API keys
- Created WebhookAPIKeyMiddleware for validating API keys in requests
- Middleware supports X-API-Key header and Authorization: Bearer format
- Added endpoint POST /api/v1/webhooks/:id/regenerate-key
- API keys are prefixed with 'whk_' for identification
- Comprehensive unit tests for all API key functionality
- Inactive webhooks cannot authenticate with their API keys
Phase: PHASE-4
Priority: P2
Progress: 119/267 (44.57%)
- Created Notification model for GORM with proper relationships
- Enhanced NotificationService with GORM-based implementation
- Features: pagination, filtering by type/read status, batch creation
- Mark as read (single and all), deletion (single and all read)
- Unread count and notification types listing
- Comprehensive unit tests for all operations
- Better error handling and logging