feat: add Convex project backend with generic storage and org-scoped authz
Schema:
- Cut projects table to {organizationId, name, description, createdAt, updatedAt}
- Add projectSources table with git source metadata and normalized URL index
- Add projectContextDocuments table with six canonical kinds, origin, revision
- Add by_organizationId index for project enumeration
- Remove all GitHub-specific fields (ownerId, provider, repoOwner, etc.)
Backend:
- Add requireCurrentOrganization and requireProjectMember authz helpers
- Migrate signals.ts authorizeProject to organizationId invariant check
- Update artifactModel: 8 operational artifacts (removed project/business/design.md)
- Add projectStore.ts with query/mutation/action store adapters
- Rewrite projects.ts to thin generic application calls
- Update projectIssues/projectArtifacts to use requireProjectMember
- Update projectEvents.test.ts and signals.test.ts for new project creation
All 33 backend tests pass.
This commit is contained in:
@@ -63,25 +63,57 @@ export default defineSchema({
|
||||
.index("by_daemonId_and_createdAt", ["daemonId", "createdAt"])
|
||||
.index("by_commandId_and_createdAt", ["commandId", "createdAt"]),
|
||||
projects: defineTable({
|
||||
ownerId: v.string(),
|
||||
provider: v.literal("github"),
|
||||
providerRepoId: v.number(),
|
||||
organizationId: v.id("organizations"),
|
||||
name: v.string(),
|
||||
description: v.optional(v.string()),
|
||||
repoOwner: v.string(),
|
||||
repoName: v.string(),
|
||||
repoUrl: v.string(),
|
||||
defaultBranch: v.string(),
|
||||
createdAt: v.number(),
|
||||
updatedAt: v.number(),
|
||||
}).index("by_organization_and_createdAt", ["organizationId", "createdAt"]),
|
||||
projectSources: defineTable({
|
||||
organizationId: v.id("organizations"),
|
||||
projectId: v.id("projects"),
|
||||
kind: v.literal("git"),
|
||||
url: v.string(),
|
||||
normalizedUrl: v.string(),
|
||||
host: v.string(),
|
||||
repositoryPath: v.string(),
|
||||
defaultBranch: v.optional(v.string()),
|
||||
createdAt: v.number(),
|
||||
updatedAt: v.number(),
|
||||
})
|
||||
.index("by_owner_and_createdAt", ["ownerId", "createdAt"])
|
||||
.index("by_owner_and_repository", [
|
||||
"ownerId",
|
||||
"provider",
|
||||
"repoOwner",
|
||||
"repoName",
|
||||
]),
|
||||
.index("by_project", ["projectId"])
|
||||
.index("by_organization_and_normalizedUrl", [
|
||||
"organizationId",
|
||||
"normalizedUrl",
|
||||
])
|
||||
.index("by_organization_and_createdAt", ["organizationId", "createdAt"]),
|
||||
projectContextDocuments: defineTable({
|
||||
organizationId: v.id("organizations"),
|
||||
projectId: v.id("projects"),
|
||||
kind: v.union(
|
||||
v.literal("readme"),
|
||||
v.literal("agents"),
|
||||
v.literal("product"),
|
||||
v.literal("business"),
|
||||
v.literal("design"),
|
||||
v.literal("tech")
|
||||
),
|
||||
path: v.string(),
|
||||
content: v.string(),
|
||||
revision: v.number(),
|
||||
origin: v.union(
|
||||
v.literal("repository"),
|
||||
v.literal("paste"),
|
||||
v.literal("upload"),
|
||||
v.literal("public-url")
|
||||
),
|
||||
sourceUrl: v.optional(v.string()),
|
||||
createdAt: v.number(),
|
||||
updatedAt: v.number(),
|
||||
})
|
||||
.index("by_project", ["projectId"])
|
||||
.index("by_project_and_kind", ["projectId", "kind"])
|
||||
.index("by_project_and_path", ["projectId", "path"]),
|
||||
projectArtifacts: defineTable({
|
||||
projectId: v.id("projects"),
|
||||
path: v.string(),
|
||||
@@ -165,6 +197,7 @@ export default defineSchema({
|
||||
createdAt: v.number(),
|
||||
})
|
||||
.index("by_userId", ["userId"])
|
||||
.index("by_organizationId", ["organizationId"])
|
||||
.index("by_organizationId_and_userId", ["organizationId", "userId"]),
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user