Feat/ai jobs twitter drafts flags apikey audit - #576
Merged
Akatenvictor merged 4 commits intoAug 30, 2026
Merged
Conversation
npm ci failed with "Missing: zod@3.25.76 from lock file" — the lock file had drifted from package.json after recent merges. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uqn6VeNfzGEJsCDDxEJR7P
Auditing the API key surface (creation, revocation, scoping) surfaced several bugs from a bad merge between competing ApiKey designs: - ApiKey was never registered in AppDataSource's entities array, so every ApiKeyService call throws "No metadata for ApiKey was found" outside tests (which mask it via synchronize:true). - The entity had isRevoked (never migrated, never read) while the service read/wrote revokedAt (never on the entity). Replaced isRevoked with revokedAt and added the missing keyPrefix column. - scopes and rateLimitTier existed on the entity but were never migrated, so production (migrations-only, no synchronize) would fail to persist them. Added a migration for both. - ApiKeyController passed the request's `permissions` into the `scopes` parameter slot, silently dropping requested scopes. - keyHasScope treated an empty scope list as unrestricted access (fail-open); a key with no scopes now holds none (fail-closed). - keyHasPermission never re-checked the owning user's role, so a permission survived a role downgrade as long as the key still listed it. It now requires both the key to list the permission and the current role to still hold it (matches the documented "never exceed the owner's role" invariant). - createApiKey didn't enforce that invariant at issue time either; it now rejects unknown or role-exceeding permissions up front. Rewrote the stale apiKeyScopes.test.ts (asserted behavior no test subject implements), fixed apiKeyRateLimitTier.test.ts's call signature, and added ApiKeyService.test.ts covering revoked-key rejection, ownership checks, and scope/permission enforcement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uqn6VeNfzGEJsCDDxEJR7P
Adds the provider abstraction ADR-007 calls for ("all third-party AI
calls go through a thin internal service... a null/no-op provider is
always available") and routes the two AI operations flagged as
too-slow-for-sync — cover art and description generation — through
JobQueueService instead of the request/response cycle:
- src/services/ai/AiProvider.ts: vendor-neutral interface
(generateCoverArt/generateDescription/draftTweet).
- NoopAiProvider: the always-available default. No network call is
made; outputs are deterministic templates, same spirit as the other
"AI-adjacent" features in docs/AI_FEATURES.md. AI_PROVIDER selects
the vendor; anything but "noop" logs a warning and falls back to it,
since no real vendor is implemented yet.
- AiGenerationService.requestGeneration enqueues a job via
JobQueueService and returns a pending AiGenerationRecord immediately;
AiJobHandlers.ts processes it on the existing job worker and only
marks the record failed (and fires a webhook) once JobQueueService
has exhausted retries, not on every transient attempt.
- Completion is announced via the existing webhook system as
ai.generation.completed (WebhookService — already implements Phase 3
of docs/WEBHOOK_IMPLEMENTATION_PLAN.md, whose status header was
stale; corrected it).
- POST /api/ai/songs/:songId/{cover-art,description} to request a
generation, GET /api/ai/generations/:id to poll it.
Replaces the single-global-AI_ENABLED-flag anti-pattern with
per-feature flags (src/config/aiFeatureFlags.ts) checked at each call
site — tags, descriptions, coverArt, moderationTriage, search,
playlists, tweetDrafts — each independently toggleable and
default-off. Only coverArt/descriptions have a wired call site today;
the rest are reserved for when those features are built.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uqn6VeNfzGEJsCDDxEJR7P
twitterRoutes.ts supported connect/disconnect but had no drafting assistance for announcing a release. Adds: - TweetDraftService.createDraft: uses the AiProvider abstraction (draftTweet) to draft announcement text for a song (or a generic release), gated by the AI_FEATURE_TWEET_DRAFTS_ENABLED flag. - POST /api/auth/twitter/draft, GET .../drafts, POST .../draft/:id/approve, DELETE .../draft/:id. Approving a draft only marks it reviewed — it does not post to Twitter. twitterRoutes.ts's OAuth callback deliberately never persists an access/refresh token (documented there: Twitter is used for profile enrichment only), so there is no credential to post with. Wiring actual posting would mean persisting tokens and implementing the OAuth2 refresh flow, which is a separate, larger security-relevant change than "drafting assistance" calls for. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uqn6VeNfzGEJsCDDxEJR7P
|
@DooseWayo Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Please include a summary of the change and which issue is fixed.
Fixes # (issue)
Type of change
Checklist:
Closes #281
Closes #286
Closes #297
Closes #320