[DEVIN: Konsti] Add userCount property to Project table with automatic update trigger (#506)

Co-authored-by: Konstantin Wohlwend <n2d4xc@gmail.com>
This commit is contained in:
devin-ai-integration[bot] 2025-03-05 09:27:40 -08:00 committed by GitHub
parent 0e17833609
commit 271ea9b175
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 117 additions and 6 deletions

View File

@ -0,0 +1,51 @@
-- AlterTable
ALTER TABLE "Project" ADD COLUMN "userCount" INTEGER NOT NULL DEFAULT 0;
-- Initialize userCount for existing projects
UPDATE "Project" SET "userCount" = (
SELECT COUNT(*) FROM "ProjectUser"
WHERE "ProjectUser"."mirroredProjectId" = "Project"."id"
);
-- Create function to update userCount
CREATE OR REPLACE FUNCTION update_project_user_count()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
-- Increment userCount when a new ProjectUser is added
UPDATE "Project" SET "userCount" = "userCount" + 1
WHERE "id" = NEW."mirroredProjectId";
ELSIF TG_OP = 'DELETE' THEN
-- Decrement userCount when a ProjectUser is deleted
UPDATE "Project" SET "userCount" = "userCount" - 1
WHERE "id" = OLD."mirroredProjectId";
ELSIF TG_OP = 'UPDATE' AND OLD."mirroredProjectId" <> NEW."mirroredProjectId" THEN
-- If mirroredProjectId changed, decrement count for old project and increment for new project
UPDATE "Project" SET "userCount" = "userCount" - 1
WHERE "id" = OLD."mirroredProjectId";
UPDATE "Project" SET "userCount" = "userCount" + 1
WHERE "id" = NEW."mirroredProjectId";
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- Create triggers
DROP TRIGGER IF EXISTS project_user_insert_trigger ON "ProjectUser";
CREATE TRIGGER project_user_insert_trigger
AFTER INSERT ON "ProjectUser"
FOR EACH ROW
EXECUTE FUNCTION update_project_user_count();
DROP TRIGGER IF EXISTS project_user_update_trigger ON "ProjectUser";
CREATE TRIGGER project_user_update_trigger
AFTER UPDATE ON "ProjectUser"
FOR EACH ROW
EXECUTE FUNCTION update_project_user_count();
DROP TRIGGER IF EXISTS project_user_delete_trigger ON "ProjectUser";
CREATE TRIGGER project_user_delete_trigger
AFTER DELETE ON "ProjectUser"
FOR EACH ROW
EXECUTE FUNCTION update_project_user_count();

View File

@ -21,6 +21,7 @@ model Project {
configId String @db.Uuid
config ProjectConfig @relation(fields: [configId], references: [id], onDelete: Cascade)
isProductionMode Boolean
userCount Int @default(0)
apiKeySets ApiKeySet[]
projectUsers ProjectUser[]

View File

@ -351,11 +351,6 @@ export function getProjectQuery(projectId: string): RawQuery<ProjectsCrud["Admin
)
FROM "ProjectConfig"
WHERE "ProjectConfig"."id" = "Project"."configId"
),
'userCount', (
SELECT count(*)
FROM "ProjectUser"
WHERE "ProjectUser"."mirroredProjectId" = "Project"."id"
)
)
)

View File

@ -1272,3 +1272,67 @@ it("has a correctly formatted JWKS endpoint", async ({ expect }) => {
],
});
});
it("should increment and decrement userCount when a user is added to a project", async ({ expect }) => {
const { adminAccessToken } = await Project.createAndSwitch({
config: {
magic_link_enabled: true,
}
});
const initialProjectResponse = await niceBackendFetch("/api/v1/projects/current", { accessType: "admin" });
expect(initialProjectResponse.status).toBe(200);
expect(initialProjectResponse.body.user_count).toBe(0);
// Create a new user in the project
await Auth.Password.signUpWithEmail();
// Check that the userCount has been incremented
const updatedProjectResponse = await niceBackendFetch("/api/v1/projects/current", { accessType: "admin" });
expect(updatedProjectResponse.status).toBe(200);
expect(updatedProjectResponse).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"config": {
"allow_localhost": true,
"client_team_creation_enabled": false,
"client_user_deletion_enabled": false,
"create_team_on_sign_up": false,
"credential_enabled": true,
"domains": [],
"email_config": { "type": "shared" },
"enabled_oauth_providers": [],
"id": "<stripped UUID>",
"magic_link_enabled": true,
"oauth_providers": [],
"passkey_enabled": false,
"sign_up_enabled": true,
"team_creator_default_permissions": [{ "id": "admin" }],
"team_member_default_permissions": [{ "id": "member" }],
},
"created_at_millis": <stripped field 'created_at_millis'>,
"description": "",
"display_name": "New Project",
"id": "<stripped UUID>",
"is_production_mode": false,
"user_count": 1,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
expect(updatedProjectResponse.body.user_count).toBe(1);
// Delete the user
const deleteRes = await niceBackendFetch("/api/v1/users/me", {
accessType: "admin",
method: "DELETE",
});
expect(deleteRes.status).toBe(200);
// Check that the userCount has been decremented
const finalProjectResponse = await niceBackendFetch("/api/v1/projects/current", { accessType: "admin" });
expect(finalProjectResponse.status).toBe(200);
expect(finalProjectResponse.body.user_count).toBe(0);
});

View File

@ -23,7 +23,7 @@
"codegen": "pnpm pre && turbo run codegen && pnpm run generate-sdks",
"deps-compose": "docker compose -p stack-dependencies -f docker/dependencies/docker.compose.yaml",
"stop-deps": "POSTGRES_DELAY_MS=0 pnpm run deps-compose kill && POSTGRES_DELAY_MS=0 pnpm run deps-compose down -v",
"init-db": "pnpm pre && pnpm run prisma db push && pnpm run prisma db seed",
"init-db": "pnpm pre && pnpm run prisma migrate deploy && pnpm run prisma db seed",
"wait-until-postgres-is-ready:pg_isready": "until pg_isready -h localhost -p 5432; do sleep 1; done",
"wait-until-postgres-is-ready": "command -v pg_isready >/dev/null 2>&1 && pnpm run wait-until-postgres-is-ready:pg_isready || sleep 10 # not everyone has pg_isready installed, so we fallback to sleeping",
"start-deps:no-delay": "pnpm pre && pnpm run deps-compose up --detach --build && pnpm run wait-until-postgres-is-ready && pnpm run init-db && echo \"\\nDependencies started in the background as Docker containers. 'pnpm run stop-deps' to stop them\"n",