|
Some checks failed
Veza CI / Backend (Go) (push) Failing after 0s
Veza CI / Frontend (Web) (push) Failing after 0s
Veza CI / Rust (Stream Server) (push) Failing after 0s
Frontend CI / test (push) Failing after 0s
Security Scan / Secret Scanning (gitleaks) (push) Failing after 0s
Veza CI / Notify on failure (push) Failing after 0s
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>
|
||
|---|---|---|
| .. | ||
| admin.go | ||
| announcement.go | ||
| api_key.go | ||
| bitrate_adaptation.go | ||
| bitrate_adaptation_test.go | ||
| chat_message.go | ||
| cloud_file_share.go | ||
| cloud_file_version.go | ||
| co_listening_session.go | ||
| custom_claims.go | ||
| daily_track_stats.go | ||
| data_export.go | ||
| delivered_status.go | ||
| feature_flag.go | ||
| federated_identity.go | ||
| gear.go | ||
| gear_document.go | ||
| gear_image.go | ||
| gear_repair.go | ||
| genre.go | ||
| hardware.go | ||
| hls_stream.go | ||
| hls_stream_test.go | ||
| hls_transcode_queue.go | ||
| hls_transcode_queue_test.go | ||
| live_stream.go | ||
| message.go | ||
| message_reaction.go | ||
| mfa_config.go | ||
| notification.go | ||
| playback_analytics.go | ||
| playback_analytics_test.go | ||
| playlist.go | ||
| playlist_collaborator.go | ||
| playlist_collaborator_test.go | ||
| playlist_follow.go | ||
| playlist_share_link.go | ||
| playlist_test.go | ||
| playlist_version.go | ||
| queue.go | ||
| queue_session.go | ||
| read_receipt.go | ||
| recovery_code.go | ||
| refresh_token.go | ||
| report.go | ||
| requests.go | ||
| responses.go | ||
| role.go | ||
| role_test.go | ||
| room.go | ||
| room_invitation.go | ||
| royalty.go | ||
| seller_stripe_account.go | ||
| session.go | ||
| storage_quota.go | ||
| tag.go | ||
| track.go | ||
| track_comment.go | ||
| track_comment_test.go | ||
| track_history.go | ||
| track_history_test.go | ||
| track_like.go | ||
| track_like_test.go | ||
| track_lyrics.go | ||
| track_play.go | ||
| track_play_test.go | ||
| track_repost.go | ||
| track_share.go | ||
| track_share_test.go | ||
| track_status.go | ||
| track_stem.go | ||
| track_version.go | ||
| track_version_test.go | ||
| user.go | ||
| user_file.go | ||
| user_folder.go | ||
| user_genre_tag_follow.go | ||
| user_presence.go | ||
| user_settings.go | ||
| webauthn_credential.go | ||
| webhook.go | ||