Add moderation endpoints for author profiles#150
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
api | 21872cf | Commit Preview URL Branch Preview URL |
Jul 24 2026, 01:42 PM |
There was a problem hiding this comment.
3 issues found and verified against the latest diff
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/services/extensions/v2/index.ts">
<violation number="1" location="src/services/extensions/v2/index.ts:469">
P2: Concurrent first-time `PUT /authors/me` requests with different ids can both succeed, leaving one user with multiple profiles despite the route's singular ownership model; subsequent updates select an arbitrary profile. Enforce unique non-null `owner_user_id` in the schema and map the resulting constraint conflict to 409.</violation>
</file>
<file name="test/services/extensions/v2/index.test.ts">
<violation number="1" location="test/services/extensions/v2/index.test.ts:594">
P3: This test does not establish that the profile was approved before editing, so it can pass without covering the approval-clearing behavior. Checking the approval response (and its `approved: true` result) before the edit would make the regression test meaningful.</violation>
</file>
<file name="src/services/extensions/v2/authors-database.ts">
<violation number="1" location="src/services/extensions/v2/authors-database.ts:22">
P3: `getOwn()` is never called anywhere in the repository, so this new method adds an unused API surface and untested profile-reading logic. Removing it, or wiring it to an intended GET-own-profile endpoint, would keep the author database surface aligned with the actual routes.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| const platform = getPlatform(c); | ||
| const db = new AuthorsDatabase(platform.getDatabase("DB_EXTENSIONS")); | ||
|
|
||
| const { data, error } = await db.upsertOwn(auth.userId, body); |
There was a problem hiding this comment.
P2: Concurrent first-time PUT /authors/me requests with different ids can both succeed, leaving one user with multiple profiles despite the route's singular ownership model; subsequent updates select an arbitrary profile. Enforce unique non-null owner_user_id in the schema and map the resulting constraint conflict to 409.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/services/extensions/v2/index.ts, line 469:
<comment>Concurrent first-time `PUT /authors/me` requests with different ids can both succeed, leaving one user with multiple profiles despite the route's singular ownership model; subsequent updates select an arbitrary profile. Enforce unique non-null `owner_user_id` in the schema and map the resulting constraint conflict to 409.</comment>
<file context>
@@ -416,6 +419,185 @@ extensionsV2.openapi(rejectRoute, async (c) => {
+ const platform = getPlatform(c);
+ const db = new AuthorsDatabase(platform.getDatabase("DB_EXTENSIONS"));
+
+ const { data, error } = await db.upsertOwn(auth.userId, body);
+ if (error || !data) {
+ const status = error?.code === "CONFLICT" ? 409 : 500;
</file context>
| ); | ||
| tables.users.set("mod-1", { id: "mod-1", is_moderator: 1 }); | ||
| await post( | ||
| "/extensions/v2/authors/dev-author/approve", |
There was a problem hiding this comment.
P3: This test does not establish that the profile was approved before editing, so it can pass without covering the approval-clearing behavior. Checking the approval response (and its approved: true result) before the edit would make the regression test meaningful.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/services/extensions/v2/index.test.ts, line 594:
<comment>This test does not establish that the profile was approved before editing, so it can pass without covering the approval-clearing behavior. Checking the approval response (and its `approved: true` result) before the edit would make the regression test meaningful.</comment>
<file context>
@@ -448,6 +512,166 @@ describe("Extensions API v2", () => {
+ );
+ tables.users.set("mod-1", { id: "mod-1", is_moderator: 1 });
+ await post(
+ "/extensions/v2/authors/dev-author/approve",
+ await authHeaders("mod-1")
+ );
</file context>
| this.db = db; | ||
| } | ||
|
|
||
| async getOwn(userId: string): Promise<DatabaseResult<AuthorProfile | null>> { |
There was a problem hiding this comment.
P3: getOwn() is never called anywhere in the repository, so this new method adds an unused API surface and untested profile-reading logic. Removing it, or wiring it to an intended GET-own-profile endpoint, would keep the author database surface aligned with the actual routes.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/services/extensions/v2/authors-database.ts, line 22:
<comment>`getOwn()` is never called anywhere in the repository, so this new method adds an unused API surface and untested profile-reading logic. Removing it, or wiring it to an intended GET-own-profile endpoint, would keep the author database surface aligned with the actual routes.</comment>
<file context>
@@ -0,0 +1,184 @@
+ this.db = db;
+ }
+
+ async getOwn(userId: string): Promise<DatabaseResult<AuthorProfile | null>> {
+ try {
+ const row = await this.db
</file context>
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/services/extensions/v2/db/migrations/0002_add_author_approval.sql">
<violation number="1" location="src/services/extensions/v2/db/migrations/0002_add_author_approval.sql:11">
P2: Authors created while approving a submission retain the 1970 timestamp, because that insert path omits both new columns. They will sort ahead of every real submission in `/authors/unapproved`; set timestamps in that insert path (or use a trigger) so the migration's placeholder cannot persist.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| -- outside of this migration. | ||
|
|
||
| ALTER TABLE authors ADD COLUMN approved_at TEXT; | ||
| ALTER TABLE authors ADD COLUMN created_at TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z'; |
There was a problem hiding this comment.
P2: Authors created while approving a submission retain the 1970 timestamp, because that insert path omits both new columns. They will sort ahead of every real submission in /authors/unapproved; set timestamps in that insert path (or use a trigger) so the migration's placeholder cannot persist.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/services/extensions/v2/db/migrations/0002_add_author_approval.sql, line 11:
<comment>Authors created while approving a submission retain the 1970 timestamp, because that insert path omits both new columns. They will sort ahead of every real submission in `/authors/unapproved`; set timestamps in that insert path (or use a trigger) so the migration's placeholder cannot persist.</comment>
<file context>
@@ -1,8 +1,16 @@
ALTER TABLE authors ADD COLUMN approved_at TEXT;
-ALTER TABLE authors ADD COLUMN created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP;
-ALTER TABLE authors ADD COLUMN updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP;
+ALTER TABLE authors ADD COLUMN created_at TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z';
+ALTER TABLE authors ADD COLUMN updated_at TEXT NOT NULL DEFAULT '1970-01-01T00:00:00.000Z';
+
</file context>
There was a problem hiding this comment.
2 issues found across 5 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="test/services/extensions/v2/index.test.ts">
<violation number="1" location="test/services/extensions/v2/index.test.ts:609">
P3: Updates to an existing author are not covered for `bio`, `avatar_url`, or `contact_email`; the new test exercises only insertion, so the `UPDATE authors SET ...` path could drop these fields without failing tests. A second PUT with the profile fields followed by response and stored-row assertions would cover the update workflow.</violation>
</file>
<file name="src/services/extensions/v2/interfaces.ts">
<violation number="1" location="src/services/extensions/v2/interfaces.ts:38">
P2: Author profile fields sent with an extension submission are silently discarded when that submission is approved. Persist these fields in the approval UPSERT, or use a submission-specific author schema that rejects profile fields until `/authors/me` owns them.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| name: z.string().min(1), | ||
| URL: httpUrl().optional() | ||
| URL: httpUrl().optional(), | ||
| bio: z.string().max(500).optional(), |
There was a problem hiding this comment.
P2: Author profile fields sent with an extension submission are silently discarded when that submission is approved. Persist these fields in the approval UPSERT, or use a submission-specific author schema that rejects profile fields until /authors/me owns them.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/services/extensions/v2/interfaces.ts, line 38:
<comment>Author profile fields sent with an extension submission are silently discarded when that submission is approved. Persist these fields in the approval UPSERT, or use a submission-specific author schema that rejects profile fields until `/authors/me` owns them.</comment>
<file context>
@@ -34,7 +34,10 @@ export const AuthorSchema = z
name: z.string().min(1),
- URL: httpUrl().optional()
+ URL: httpUrl().optional(),
+ bio: z.string().max(500).optional(),
+ avatar_url: httpUrl().optional(),
+ contact_email: z.string().email().optional()
</file context>
| expect(data.result.approved).toBe(false); | ||
| }); | ||
|
|
||
| it("round-trips bio, avatar_url, and contact_email", async () => { |
There was a problem hiding this comment.
P3: Updates to an existing author are not covered for bio, avatar_url, or contact_email; the new test exercises only insertion, so the UPDATE authors SET ... path could drop these fields without failing tests. A second PUT with the profile fields followed by response and stored-row assertions would cover the update workflow.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At test/services/extensions/v2/index.test.ts, line 609:
<comment>Updates to an existing author are not covered for `bio`, `avatar_url`, or `contact_email`; the new test exercises only insertion, so the `UPDATE authors SET ...` path could drop these fields without failing tests. A second PUT with the profile fields followed by response and stored-row assertions would cover the update workflow.</comment>
<file context>
@@ -605,6 +605,53 @@ describe("Extensions API v2", () => {
expect(data.result.approved).toBe(false);
});
+
+ it("round-trips bio, avatar_url, and contact_email", async () => {
+ const headers = await authHeaders("user-1");
+ const res = await put("/extensions/v2/authors/me", headers, {
</file context>
Summary by cubic
Adds developer profile endpoints with a moderator approval flow and new profile fields (bio, avatar, contact email) to the
extensions/v2API. Users can manage their author profile; moderators can review and approve; submissions now require an existing, owned author.New Features
PUT /authors/me: create or update the caller’s author profile; returnsapproved: falseuntil reviewed.GET /authors/unapproved: list unapproved profiles (moderators only).POST /authors/{id}/approve: approve a profile (moderators only).bio(max 500 chars),avatar_url,contact_emailwith validation.Migration
0002_add_author_approval.sql(addsapproved_at, timestamps, index) and0003_add_author_profile_fields.sql(addsbio,avatar_url,contact_email). Update clients/workflows to create an author profile before submitting extensions.Written for commit 21872cf. Summary will update on new commits.