Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ IDV_DOMAIN=account.hackclub.com
IDV_CLIENT_ID=changeme
IDV_CLIENT_SECRET=changeme

SENTRY_AUTH_TOKEN=changeme
SENTRY_AUTH_TOKEN=changeme

AI_API_KEY=sk-...
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ You can also run this to get a GUI for the database:
npm run db:studio
```

### AI prereview (devlogs)

- The review panel now shows an AI pre-check on shipped projects’ devlogs. It only flags obvious over-reporting of time.
- Set `AI_API_KEY` to your Hack Club AI key (it uses the https://ai.hackclub.com proxy under the hood). No key = no AI hints in review.

## Deploying

Create a `.env` file containing all the required credentials, look at `.env.example` for an example.
Expand Down
32 changes: 32 additions & 0 deletions drizzle/0004_ai_prechecks.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
CREATE TABLE "ai_devlog_review" (
"id" serial PRIMARY KEY NOT NULL,
"devlogId" integer NOT NULL,
"projectId" integer NOT NULL,
"approved" boolean NOT NULL,
"rationale" text NOT NULL,
"prompt" text NOT NULL,
"model" text NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "ai_devlog_review_devlogId_unique" UNIQUE ("devlogId")
);
--> statement-breakpoint

ALTER TABLE "ai_devlog_review" ADD CONSTRAINT "ai_devlog_review_devlogId_devlog_id_fk" FOREIGN KEY ("devlogId") REFERENCES "devlog"("id") ON DELETE no action ON UPDATE no action;
--> statement-breakpoint
ALTER TABLE "ai_devlog_review" ADD CONSTRAINT "ai_devlog_review_projectId_project_id_fk" FOREIGN KEY ("projectId") REFERENCES "project"("id") ON DELETE no action ON UPDATE no action;
--> statement-breakpoint

CREATE TABLE "ai_project_review" (
"id" serial PRIMARY KEY NOT NULL,
"projectId" integer NOT NULL,
"overallApproved" boolean NOT NULL,
"summary" text NOT NULL,
"prompt" text NOT NULL,
"model" text NOT NULL,
"createdAt" timestamp DEFAULT now() NOT NULL,
"updatedAt" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "ai_project_review_projectId_unique" UNIQUE ("projectId")
);
--> statement-breakpoint
ALTER TABLE "ai_project_review" ADD CONSTRAINT "ai_project_review_projectId_project_id_fk" FOREIGN KEY ("projectId") REFERENCES "project"("id") ON DELETE no action ON UPDATE no action;
Copy link

Copilot AI Dec 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The foreign key constraint uses ON DELETE no action, which means if a project is deleted, the deletion will fail if there are AI reviews referencing it. Consider using ON DELETE CASCADE to automatically delete AI reviews when their associated project is deleted. Given that projects use a soft-delete pattern (with a deleted flag), this might not be an immediate issue, but it's worth considering for data consistency.

Suggested change
ALTER TABLE "ai_project_review" ADD CONSTRAINT "ai_project_review_projectId_project_id_fk" FOREIGN KEY ("projectId") REFERENCES "project"("id") ON DELETE no action ON UPDATE no action;
ALTER TABLE "ai_project_review" ADD CONSTRAINT "ai_project_review_projectId_project_id_fk" FOREIGN KEY ("projectId") REFERENCES "project"("id") ON DELETE CASCADE ON UPDATE no action;

Copilot uses AI. Check for mistakes.
277 changes: 277 additions & 0 deletions drizzle/meta/0004_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
{
"id": "5c6f3ce1-9e64-4e8c-93ab-9fd1d3ae1b70",
"prevId": "dd1faf34-f215-4e5a-95a9-66c21e151248",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.ai_devlog_review": {
"name": "ai_devlog_review",
"schema": "",
"columns": {
"id": { "name": "id", "type": "serial", "primaryKey": true, "notNull": true },
"devlogId": { "name": "devlogId", "type": "integer", "primaryKey": false, "notNull": true },
"projectId": { "name": "projectId", "type": "integer", "primaryKey": false, "notNull": true },
"approved": { "name": "approved", "type": "boolean", "primaryKey": false, "notNull": true },
"rationale": { "name": "rationale", "type": "text", "primaryKey": false, "notNull": true },
"prompt": { "name": "prompt", "type": "text", "primaryKey": false, "notNull": true },
"model": { "name": "model", "type": "text", "primaryKey": false, "notNull": true },
"createdAt": { "name": "createdAt", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" },
"updatedAt": { "name": "updatedAt", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" }
},
"indexes": {},
"foreignKeys": {
"ai_devlog_review_devlogId_devlog_id_fk": {
"name": "ai_devlog_review_devlogId_devlog_id_fk",
"tableFrom": "ai_devlog_review",
"tableTo": "devlog",
"columnsFrom": ["devlogId"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
},
"ai_devlog_review_projectId_project_id_fk": {
"name": "ai_devlog_review_projectId_project_id_fk",
"tableFrom": "ai_devlog_review",
"tableTo": "project",
"columnsFrom": ["projectId"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"ai_devlog_review_devlogId_unique": {
"name": "ai_devlog_review_devlogId_unique",
"nullsNotDistinct": false,
"columns": ["devlogId"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.ai_project_review": {
"name": "ai_project_review",
"schema": "",
"columns": {
"id": { "name": "id", "type": "serial", "primaryKey": true, "notNull": true },
"projectId": { "name": "projectId", "type": "integer", "primaryKey": false, "notNull": true },
"overallApproved": { "name": "overallApproved", "type": "boolean", "primaryKey": false, "notNull": true },
"summary": { "name": "summary", "type": "text", "primaryKey": false, "notNull": true },
"prompt": { "name": "prompt", "type": "text", "primaryKey": false, "notNull": true },
"model": { "name": "model", "type": "text", "primaryKey": false, "notNull": true },
"createdAt": { "name": "createdAt", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" },
"updatedAt": { "name": "updatedAt", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" }
},
"indexes": {},
"foreignKeys": {
"ai_project_review_projectId_project_id_fk": {
"name": "ai_project_review_projectId_project_id_fk",
"tableFrom": "ai_project_review",
"tableTo": "project",
"columnsFrom": ["projectId"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"ai_project_review_projectId_unique": {
"name": "ai_project_review_projectId_unique",
"nullsNotDistinct": false,
"columns": ["projectId"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.devlog": {
"name": "devlog",
"schema": "",
"columns": {
"id": { "name": "id", "type": "serial", "primaryKey": true, "notNull": true },
"userId": { "name": "userId", "type": "integer", "primaryKey": false, "notNull": false },
"projectId": { "name": "projectId", "type": "integer", "primaryKey": false, "notNull": true },
"description": { "name": "description", "type": "text", "primaryKey": false, "notNull": true },
"timeSpent": { "name": "timeSpent", "type": "integer", "primaryKey": false, "notNull": true },
"image": { "name": "image", "type": "text", "primaryKey": false, "notNull": true },
"model": { "name": "model", "type": "text", "primaryKey": false, "notNull": true },
"deleted": { "name": "deleted", "type": "boolean", "primaryKey": false, "notNull": true, "default": false },
"createdAt": { "name": "createdAt", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" },
"updatedAt": { "name": "updatedAt", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" }
},
"indexes": {},
"foreignKeys": {
"devlog_userId_user_id_fk": {
"name": "devlog_userId_user_id_fk",
"tableFrom": "devlog",
"tableTo": "user",
"columnsFrom": ["userId"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
},
"devlog_projectId_project_id_fk": {
"name": "devlog_projectId_project_id_fk",
"tableFrom": "devlog",
"tableTo": "project",
"columnsFrom": ["projectId"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.project": {
"name": "project",
"schema": "",
"columns": {
"id": { "name": "id", "type": "serial", "primaryKey": true, "notNull": true },
"userId": { "name": "userId", "type": "integer", "primaryKey": false, "notNull": true },
"name": { "name": "name", "type": "text", "primaryKey": false, "notNull": false },
"description": { "name": "description", "type": "text", "primaryKey": false, "notNull": false },
"url": { "name": "url", "type": "text", "primaryKey": false, "notNull": false },
"status": { "name": "status", "type": "status", "typeSchema": "public", "primaryKey": false, "notNull": true, "default": "'building'" },
"deleted": { "name": "deleted", "type": "boolean", "primaryKey": false, "notNull": true, "default": false },
"createdAt": { "name": "createdAt", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" },
"updatedAt": { "name": "updatedAt", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" }
},
"indexes": {},
"foreignKeys": {
"project_userId_user_id_fk": {
"name": "project_userId_user_id_fk",
"tableFrom": "project",
"tableTo": "user",
"columnsFrom": ["userId"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"id": { "name": "id", "type": "serial", "primaryKey": true, "notNull": true },
"token": { "name": "token", "type": "text", "primaryKey": false, "notNull": true },
"userId": { "name": "userId", "type": "integer", "primaryKey": false, "notNull": true },
"expiresAt": { "name": "expiresAt", "type": "timestamp", "primaryKey": false, "notNull": true }
},
"indexes": {},
"foreignKeys": {
"session_userId_user_id_fk": {
"name": "session_userId_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": ["userId"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.t1_review": {
"name": "t1_review",
"schema": "",
"columns": {
"id": { "name": "id", "type": "serial", "primaryKey": true, "notNull": true },
"userId": { "name": "userId", "type": "integer", "primaryKey": false, "notNull": true },
"projectId": { "name": "projectId", "type": "integer", "primaryKey": false, "notNull": true },
"feedback": { "name": "feedback", "type": "text", "primaryKey": false, "notNull": false },
"notes": { "name": "notes", "type": "text", "primaryKey": false, "notNull": false },
"action": { "name": "action", "type": "t1_review_action", "typeSchema": "public", "primaryKey": false, "notNull": true },
"timestamp": { "name": "timestamp", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" }
},
"indexes": {},
"foreignKeys": {
"t1_review_userId_user_id_fk": {
"name": "t1_review_userId_user_id_fk",
"tableFrom": "t1_review",
"tableTo": "user",
"columnsFrom": ["userId"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
},
"t1_review_projectId_project_id_fk": {
"name": "t1_review_projectId_project_id_fk",
"tableFrom": "t1_review",
"tableTo": "project",
"columnsFrom": ["projectId"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": { "name": "id", "type": "serial", "primaryKey": true, "notNull": true },
"idvId": { "name": "idvId", "type": "text", "primaryKey": false, "notNull": true },
"slackId": { "name": "slackId", "type": "text", "primaryKey": false, "notNull": true },
"profilePicture": { "name": "profilePicture", "type": "text", "primaryKey": false, "notNull": true },
"name": { "name": "name", "type": "text", "primaryKey": false, "notNull": true },
"hackatimeTrust": { "name": "hackatimeTrust", "type": "hackatime_trust", "typeSchema": "public", "primaryKey": false, "notNull": true },
"trust": { "name": "trust", "type": "trust", "typeSchema": "public", "primaryKey": false, "notNull": true, "default": "'blue'" },
"clay": { "name": "clay", "type": "real", "primaryKey": false, "notNull": true, "default": 0 },
"brick": { "name": "brick", "type": "real", "primaryKey": false, "notNull": true, "default": 0 },
"shopScore": { "name": "shopScore", "type": "real", "primaryKey": false, "notNull": true, "default": 0 },
"hasBasePrinter": { "name": "hasBasePrinter", "type": "boolean", "primaryKey": false, "notNull": true, "default": false },
"hasT1Review": { "name": "hasT1Review", "type": "boolean", "primaryKey": false, "notNull": true, "default": false },
"hasT2Review": { "name": "hasT2Review", "type": "boolean", "primaryKey": false, "notNull": true, "default": false },
"hasAdmin": { "name": "hasAdmin", "type": "boolean", "primaryKey": false, "notNull": true, "default": false },
"createdAt": { "name": "createdAt", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" },
"lastLoginAt": { "name": "lastLoginAt", "type": "timestamp", "primaryKey": false, "notNull": true, "default": "now()" }
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_idvId_unique": { "name": "user_idvId_unique", "nullsNotDistinct": false, "columns": ["idvId"] },
"user_slackId_unique": { "name": "user_slackId_unique", "nullsNotDistinct": false, "columns": ["slackId"] }
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.hackatime_trust": { "name": "hackatime_trust", "schema": "public", "values": ["green", "blue", "yellow", "red"] },
"public.project_audit_log_type": { "name": "project_audit_log_type", "schema": "public", "values": ["create", "update", "delete"] },
"public.status": { "name": "status", "schema": "public", "values": ["building", "submitted", "t1_approved", "printing", "printed", "t2_approved", "finalized", "rejected", "rejected_locked"] },
"public.t1_review_action": { "name": "t1_review_action", "schema": "public", "values": ["approve", "approve_no_print", "add_comment", "reject", "reject_lock"] },
"public.trust": { "name": "trust", "schema": "public", "values": ["green", "blue", "yellow", "red"] }
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": { "columns": {}, "schemas": {}, "tables": {} }
}
7 changes: 7 additions & 0 deletions drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
"when": 1765151231107,
"tag": "0003_confused_captain_marvel",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1765478400000,
"tag": "0004_ai_prechecks",
"breakpoints": true
}
]
}
Loading