@baseworks/organization 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-5UCSEIJS.js +64 -0
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +534 -0
- package/dist/index.d.ts +199 -0
- package/dist/index.js +335 -0
- package/dist/schema/pg/index.d.ts +562 -0
- package/dist/schema/pg/index.js +62 -0
- package/dist/schema/sqlite/index.d.ts +604 -0
- package/dist/schema/sqlite/index.js +12 -0
- package/package.json +37 -0
- package/src/__tests__/cli-env.test.ts +158 -0
- package/src/__tests__/cli-org.test.ts +154 -0
- package/src/__tests__/cli-proj.test.ts +157 -0
- package/src/__tests__/cli-ws.test.ts +156 -0
- package/src/__tests__/helpers.ts +29 -0
- package/src/cli.ts +682 -0
- package/src/index.ts +5 -0
- package/src/operations/bootstrap.ts +50 -0
- package/src/repo/environments.ts +82 -0
- package/src/repo/index.ts +9 -0
- package/src/repo/organizations.ts +96 -0
- package/src/repo/projects.ts +106 -0
- package/src/repo/workspaces.ts +87 -0
- package/src/schema/environments.ts +14 -0
- package/src/schema/index.ts +5 -0
- package/src/schema/organizations.ts +11 -0
- package/src/schema/pg/environments.ts +14 -0
- package/src/schema/pg/index.ts +4 -0
- package/src/schema/pg/organizations.ts +11 -0
- package/src/schema/pg/projects.ts +16 -0
- package/src/schema/pg/workspaces.ts +15 -0
- package/src/schema/projects.ts +16 -0
- package/src/schema/sqlite/environments.ts +14 -0
- package/src/schema/sqlite/index.ts +4 -0
- package/src/schema/sqlite/organizations.ts +11 -0
- package/src/schema/sqlite/projects.ts +16 -0
- package/src/schema/sqlite/workspaces.ts +15 -0
- package/src/schema/workspaces.ts +15 -0
- package/src/types.ts +88 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createOrganizationRepo } from '../repo/organizations.js'
|
|
2
|
+
import { createWorkspaceRepo } from '../repo/workspaces.js'
|
|
3
|
+
import { createProjectRepo } from '../repo/projects.js'
|
|
4
|
+
import type { Organization, Workspace, Project } from '../types.js'
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
type AnyDB = any
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
type AnySchema = any
|
|
10
|
+
|
|
11
|
+
export interface OrganizationBootstrapResult {
|
|
12
|
+
organization: Organization
|
|
13
|
+
defaultWorkspace: Workspace
|
|
14
|
+
defaultProject: Project
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface OrganizationBootstrapInput {
|
|
18
|
+
name: string
|
|
19
|
+
slug?: string
|
|
20
|
+
workspaceName?: string
|
|
21
|
+
projectName?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Creates org + default workspace + default project in sequence.
|
|
25
|
+
// Wrap in a db.transaction() at the call site if atomicity is required.
|
|
26
|
+
export async function createOrganizationWithDefaults(
|
|
27
|
+
db: AnyDB,
|
|
28
|
+
schema: AnySchema,
|
|
29
|
+
input: OrganizationBootstrapInput,
|
|
30
|
+
): Promise<OrganizationBootstrapResult> {
|
|
31
|
+
const orgRepo = createOrganizationRepo(db, schema)
|
|
32
|
+
const workspaceRepo = createWorkspaceRepo(db, schema)
|
|
33
|
+
const projectRepo = createProjectRepo(db, schema)
|
|
34
|
+
|
|
35
|
+
const organization = await orgRepo.create({ name: input.name, slug: input.slug })
|
|
36
|
+
|
|
37
|
+
const defaultWorkspace = await workspaceRepo.create({
|
|
38
|
+
organizationId: organization.id,
|
|
39
|
+
name: input.workspaceName ?? 'Default',
|
|
40
|
+
isDefault: true,
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
const defaultProject = await projectRepo.create({
|
|
44
|
+
workspaceId: defaultWorkspace.id,
|
|
45
|
+
name: input.projectName ?? 'Default',
|
|
46
|
+
isDefault: true,
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
return { organization, defaultWorkspace, defaultProject }
|
|
50
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { and, eq, or } from 'drizzle-orm'
|
|
2
|
+
import { generateId, generateShortId, generateSlug } from '@baseworks/core'
|
|
3
|
+
import type { Environment } from '../types.js'
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
type AnyDB = any
|
|
7
|
+
|
|
8
|
+
async function resolveSlug(db: AnyDB, envsTable: any, projectId: string, base: string): Promise<string> {
|
|
9
|
+
let slug = base
|
|
10
|
+
let i = 2
|
|
11
|
+
while (true) {
|
|
12
|
+
const rows = await db
|
|
13
|
+
.select({ id: envsTable.id })
|
|
14
|
+
.from(envsTable)
|
|
15
|
+
.where(and(eq(envsTable.projectId, projectId), eq(envsTable.slug, slug)))
|
|
16
|
+
.limit(1)
|
|
17
|
+
if (rows.length === 0) return slug
|
|
18
|
+
slug = `${base}-${i++}`
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createEnvironmentRepo(db: AnyDB, schema: { environments: any }) {
|
|
23
|
+
const { environments } = schema
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
async create(data: { projectId: string; name: string; slug?: string; id?: string }): Promise<Environment> {
|
|
27
|
+
const now = Date.now()
|
|
28
|
+
const base = data.slug ?? generateSlug(data.name)
|
|
29
|
+
const slug = await resolveSlug(db, environments, data.projectId, base)
|
|
30
|
+
const row = {
|
|
31
|
+
id: data.id ?? generateId(),
|
|
32
|
+
projectId: data.projectId,
|
|
33
|
+
shortId: generateShortId(),
|
|
34
|
+
slug,
|
|
35
|
+
name: data.name,
|
|
36
|
+
createdAt: now,
|
|
37
|
+
updatedAt: now,
|
|
38
|
+
}
|
|
39
|
+
await db.insert(environments).values(row)
|
|
40
|
+
return row
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async findById(id: string): Promise<Environment | undefined> {
|
|
44
|
+
const rows = await db.select().from(environments).where(eq(environments.id, id)).limit(1)
|
|
45
|
+
return rows[0]
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
async findByShortId(shortId: string): Promise<Environment | undefined> {
|
|
49
|
+
const rows = await db.select().from(environments).where(eq(environments.shortId, shortId)).limit(1)
|
|
50
|
+
return rows[0]
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async findByIdentifier(projectId: string, identifier: string): Promise<Environment | undefined> {
|
|
54
|
+
const rows = await db
|
|
55
|
+
.select()
|
|
56
|
+
.from(environments)
|
|
57
|
+
.where(and(eq(environments.projectId, projectId), or(eq(environments.shortId, identifier), eq(environments.slug, identifier))))
|
|
58
|
+
.limit(1)
|
|
59
|
+
return rows[0]
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async findByRef(projectId: string, ref: string): Promise<Environment | undefined> {
|
|
63
|
+
return this.findByIdentifier(projectId, ref)
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async listByProject(projectId: string): Promise<Environment[]> {
|
|
67
|
+
return db.select().from(environments).where(eq(environments.projectId, projectId))
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async update(id: string, data: { name?: string; slug?: string }): Promise<Environment | undefined> {
|
|
71
|
+
await db.update(environments).set({ ...data, updatedAt: Date.now() }).where(eq(environments.id, id))
|
|
72
|
+
const rows = await db.select().from(environments).where(eq(environments.id, id)).limit(1)
|
|
73
|
+
return rows[0]
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async delete(id: string): Promise<void> {
|
|
77
|
+
await db.delete(environments).where(eq(environments.id, id))
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type EnvironmentRepo = ReturnType<typeof createEnvironmentRepo>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createOrganizationRepo } from './organizations.js'
|
|
2
|
+
export { createWorkspaceRepo } from './workspaces.js'
|
|
3
|
+
export { createProjectRepo } from './projects.js'
|
|
4
|
+
export { createEnvironmentRepo } from './environments.js'
|
|
5
|
+
|
|
6
|
+
export type { OrganizationRepo } from './organizations.js'
|
|
7
|
+
export type { WorkspaceRepo } from './workspaces.js'
|
|
8
|
+
export type { ProjectRepo } from './projects.js'
|
|
9
|
+
export type { EnvironmentRepo } from './environments.js'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { eq, desc } from 'drizzle-orm'
|
|
2
|
+
import { generateId, generateShortId, generateSlug } from '@baseworks/core'
|
|
3
|
+
import type { Organization } from '../types.js'
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
type AnyDB = any
|
|
7
|
+
|
|
8
|
+
export function createOrganizationRepo(db: AnyDB, schema: { organizations: any }) {
|
|
9
|
+
const { organizations } = schema
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
async create(data: { name: string; slug?: string; id?: string }): Promise<Organization> {
|
|
13
|
+
const now = Date.now()
|
|
14
|
+
const shortId = generateShortId()
|
|
15
|
+
const row = {
|
|
16
|
+
id: data.id ?? generateId(),
|
|
17
|
+
shortId,
|
|
18
|
+
slug: data.slug ?? generateSlug(data.name),
|
|
19
|
+
name: data.name,
|
|
20
|
+
metadata: null,
|
|
21
|
+
createdAt: now,
|
|
22
|
+
updatedAt: now,
|
|
23
|
+
}
|
|
24
|
+
await db.insert(organizations).values(row)
|
|
25
|
+
return row
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
async findById(id: string): Promise<Organization | undefined> {
|
|
29
|
+
const rows = await db.select().from(organizations).where(eq(organizations.id, id)).limit(1)
|
|
30
|
+
return rows[0]
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
async findByShortId(shortId: string): Promise<Organization | undefined> {
|
|
34
|
+
const rows = await db.select().from(organizations).where(eq(organizations.shortId, shortId)).limit(1)
|
|
35
|
+
return rows[0]
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
async findBySlug(slug: string): Promise<Organization | undefined> {
|
|
39
|
+
const rows = await db.select().from(organizations).where(eq(organizations.slug, slug)).limit(1)
|
|
40
|
+
return rows[0]
|
|
41
|
+
},
|
|
42
|
+
|
|
43
|
+
async findByRef(ref: string): Promise<Organization | undefined> {
|
|
44
|
+
const bySlug = await db.select().from(organizations).where(eq(organizations.slug, ref)).limit(1)
|
|
45
|
+
if (bySlug[0]) return bySlug[0]
|
|
46
|
+
const byShortId = await db.select().from(organizations).where(eq(organizations.shortId, ref)).limit(1)
|
|
47
|
+
return byShortId[0]
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async isSlugAvailable(slug: string): Promise<boolean> {
|
|
51
|
+
const rows = await db.select({ id: organizations.id }).from(organizations).where(eq(organizations.slug, slug)).limit(1)
|
|
52
|
+
return rows.length === 0
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
async claimSlug(id: string, slug: string): Promise<void> {
|
|
56
|
+
await db.update(organizations).set({ slug, updatedAt: Date.now() }).where(eq(organizations.id, id))
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async update(id: string, data: { name?: string; slug?: string; metadata?: string | null }): Promise<Organization | undefined> {
|
|
60
|
+
await db.update(organizations).set({ ...data, updatedAt: Date.now() }).where(eq(organizations.id, id))
|
|
61
|
+
const rows = await db.select().from(organizations).where(eq(organizations.id, id)).limit(1)
|
|
62
|
+
return rows[0]
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async patchMetadata(id: string, patch: Record<string, unknown>): Promise<Organization | undefined> {
|
|
66
|
+
const rows = await db.select().from(organizations).where(eq(organizations.id, id)).limit(1)
|
|
67
|
+
const org = rows[0]
|
|
68
|
+
if (!org) return undefined
|
|
69
|
+
|
|
70
|
+
const current: Record<string, unknown> = org.metadata ? JSON.parse(org.metadata) : {}
|
|
71
|
+
const merged = { ...current }
|
|
72
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
73
|
+
if (v === null) delete merged[k]
|
|
74
|
+
else merged[k] = v
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
await db.update(organizations).set({ metadata: JSON.stringify(merged), updatedAt: Date.now() }).where(eq(organizations.id, id))
|
|
78
|
+
const updated = await db.select().from(organizations).where(eq(organizations.id, id)).limit(1)
|
|
79
|
+
return updated[0]
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async delete(id: string): Promise<void> {
|
|
83
|
+
await db.delete(organizations).where(eq(organizations.id, id))
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async listAll(): Promise<Organization[]> {
|
|
87
|
+
return db.select().from(organizations).orderBy(desc(organizations.createdAt))
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
async hardDelete(id: string): Promise<void> {
|
|
91
|
+
await db.delete(organizations).where(eq(organizations.id, id))
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type OrganizationRepo = ReturnType<typeof createOrganizationRepo>
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm'
|
|
2
|
+
import { generateId, generateShortId, generateSlug } from '@baseworks/core'
|
|
3
|
+
import type { Project } from '../types.js'
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
type AnyDB = any
|
|
7
|
+
|
|
8
|
+
async function resolveSlug(db: AnyDB, projectsTable: any, workspaceId: string, base: string): Promise<string> {
|
|
9
|
+
let slug = base
|
|
10
|
+
let i = 2
|
|
11
|
+
while (true) {
|
|
12
|
+
const rows = await db
|
|
13
|
+
.select({ id: projectsTable.id })
|
|
14
|
+
.from(projectsTable)
|
|
15
|
+
.where(and(eq(projectsTable.workspaceId, workspaceId), eq(projectsTable.slug, slug)))
|
|
16
|
+
.limit(1)
|
|
17
|
+
if (rows.length === 0) return slug
|
|
18
|
+
slug = `${base}-${i++}`
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createProjectRepo(db: AnyDB, schema: { projects: any }) {
|
|
23
|
+
const { projects } = schema
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
async create(data: { workspaceId: string; name: string; slug?: string; isDefault?: boolean; id?: string }): Promise<Project> {
|
|
27
|
+
const now = Date.now()
|
|
28
|
+
const base = data.slug ?? generateSlug(data.name)
|
|
29
|
+
const slug = await resolveSlug(db, projects, data.workspaceId, base)
|
|
30
|
+
const row = {
|
|
31
|
+
id: data.id ?? generateId(),
|
|
32
|
+
workspaceId: data.workspaceId,
|
|
33
|
+
shortId: generateShortId(),
|
|
34
|
+
slug,
|
|
35
|
+
name: data.name,
|
|
36
|
+
isDefault: data.isDefault ?? false,
|
|
37
|
+
metadata: null,
|
|
38
|
+
createdAt: now,
|
|
39
|
+
updatedAt: now,
|
|
40
|
+
}
|
|
41
|
+
await db.insert(projects).values(row)
|
|
42
|
+
return row
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
async findById(id: string): Promise<Project | undefined> {
|
|
46
|
+
const rows = await db.select().from(projects).where(eq(projects.id, id)).limit(1)
|
|
47
|
+
return rows[0]
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async findByShortId(shortId: string): Promise<Project | undefined> {
|
|
51
|
+
const rows = await db.select().from(projects).where(eq(projects.shortId, shortId)).limit(1)
|
|
52
|
+
return rows[0]
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
async findBySlug(workspaceId: string, slug: string): Promise<Project | undefined> {
|
|
56
|
+
const rows = await db.select().from(projects).where(and(eq(projects.workspaceId, workspaceId), eq(projects.slug, slug))).limit(1)
|
|
57
|
+
return rows[0]
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
async findByRef(workspaceId: string, ref: string): Promise<Project | undefined> {
|
|
61
|
+
const bySlug = await db.select().from(projects).where(and(eq(projects.workspaceId, workspaceId), eq(projects.slug, ref))).limit(1)
|
|
62
|
+
if (bySlug[0]) return bySlug[0]
|
|
63
|
+
const byShortId = await db.select().from(projects).where(and(eq(projects.workspaceId, workspaceId), eq(projects.shortId, ref))).limit(1)
|
|
64
|
+
return byShortId[0]
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
async listByWorkspace(workspaceId: string): Promise<Project[]> {
|
|
68
|
+
return db.select().from(projects).where(eq(projects.workspaceId, workspaceId))
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
async findDefault(workspaceId: string): Promise<Project | undefined> {
|
|
72
|
+
const rows = await db.select().from(projects).where(and(eq(projects.workspaceId, workspaceId), eq(projects.isDefault, true))).limit(1)
|
|
73
|
+
return rows[0]
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async update(id: string, data: { name?: string }): Promise<Project | undefined> {
|
|
77
|
+
await db.update(projects).set({ ...data, updatedAt: Date.now() }).where(eq(projects.id, id))
|
|
78
|
+
const rows = await db.select().from(projects).where(eq(projects.id, id)).limit(1)
|
|
79
|
+
return rows[0]
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async delete(id: string): Promise<void> {
|
|
83
|
+
await db.delete(projects).where(eq(projects.id, id))
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async getMetadata(id: string): Promise<Record<string, unknown>> {
|
|
87
|
+
const rows = await db.select({ metadata: projects.metadata }).from(projects).where(eq(projects.id, id)).limit(1)
|
|
88
|
+
const row = rows[0]
|
|
89
|
+
if (!row?.metadata) return {}
|
|
90
|
+
try { return JSON.parse(row.metadata) as Record<string, unknown> } catch { return {} }
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async patchMetadata(id: string, patch: Record<string, unknown>): Promise<Record<string, unknown>> {
|
|
94
|
+
const existing = await this.getMetadata(id)
|
|
95
|
+
const next: Record<string, unknown> = { ...existing }
|
|
96
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
97
|
+
if (v === null) delete next[k]
|
|
98
|
+
else next[k] = v
|
|
99
|
+
}
|
|
100
|
+
await db.update(projects).set({ metadata: JSON.stringify(next), updatedAt: Date.now() }).where(eq(projects.id, id))
|
|
101
|
+
return next
|
|
102
|
+
},
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export type ProjectRepo = ReturnType<typeof createProjectRepo>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { and, eq } from 'drizzle-orm'
|
|
2
|
+
import { generateId, generateShortId, generateSlug } from '@baseworks/core'
|
|
3
|
+
import type { Workspace } from '../types.js'
|
|
4
|
+
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
6
|
+
type AnyDB = any
|
|
7
|
+
|
|
8
|
+
async function resolveSlug(db: AnyDB, workspacesTable: any, organizationId: string, base: string): Promise<string> {
|
|
9
|
+
let slug = base
|
|
10
|
+
let i = 2
|
|
11
|
+
while (true) {
|
|
12
|
+
const rows = await db
|
|
13
|
+
.select({ id: workspacesTable.id })
|
|
14
|
+
.from(workspacesTable)
|
|
15
|
+
.where(and(eq(workspacesTable.organizationId, organizationId), eq(workspacesTable.slug, slug)))
|
|
16
|
+
.limit(1)
|
|
17
|
+
if (rows.length === 0) return slug
|
|
18
|
+
slug = `${base}-${i++}`
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createWorkspaceRepo(db: AnyDB, schema: { workspaces: any }) {
|
|
23
|
+
const { workspaces } = schema
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
async create(data: { organizationId: string; name: string; slug?: string; isDefault?: boolean; id?: string }): Promise<Workspace> {
|
|
27
|
+
const now = Date.now()
|
|
28
|
+
const base = data.slug ?? generateSlug(data.name)
|
|
29
|
+
const slug = await resolveSlug(db, workspaces, data.organizationId, base)
|
|
30
|
+
const row = {
|
|
31
|
+
id: data.id ?? generateId(),
|
|
32
|
+
organizationId: data.organizationId,
|
|
33
|
+
shortId: generateShortId(),
|
|
34
|
+
slug,
|
|
35
|
+
name: data.name,
|
|
36
|
+
isDefault: data.isDefault ?? false,
|
|
37
|
+
createdAt: now,
|
|
38
|
+
updatedAt: now,
|
|
39
|
+
}
|
|
40
|
+
await db.insert(workspaces).values(row)
|
|
41
|
+
return row
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
async findById(id: string): Promise<Workspace | undefined> {
|
|
45
|
+
const rows = await db.select().from(workspaces).where(eq(workspaces.id, id)).limit(1)
|
|
46
|
+
return rows[0]
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
async findByShortId(shortId: string): Promise<Workspace | undefined> {
|
|
50
|
+
const rows = await db.select().from(workspaces).where(eq(workspaces.shortId, shortId)).limit(1)
|
|
51
|
+
return rows[0]
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async findBySlug(organizationId: string, slug: string): Promise<Workspace | undefined> {
|
|
55
|
+
const rows = await db.select().from(workspaces).where(and(eq(workspaces.organizationId, organizationId), eq(workspaces.slug, slug))).limit(1)
|
|
56
|
+
return rows[0]
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async findByRef(organizationId: string, ref: string): Promise<Workspace | undefined> {
|
|
60
|
+
const bySlug = await db.select().from(workspaces).where(and(eq(workspaces.organizationId, organizationId), eq(workspaces.slug, ref))).limit(1)
|
|
61
|
+
if (bySlug[0]) return bySlug[0]
|
|
62
|
+
const byShortId = await db.select().from(workspaces).where(and(eq(workspaces.organizationId, organizationId), eq(workspaces.shortId, ref))).limit(1)
|
|
63
|
+
return byShortId[0]
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async listByOrg(organizationId: string): Promise<Workspace[]> {
|
|
67
|
+
return db.select().from(workspaces).where(eq(workspaces.organizationId, organizationId))
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
async findDefault(organizationId: string): Promise<Workspace | undefined> {
|
|
71
|
+
const rows = await db.select().from(workspaces).where(and(eq(workspaces.organizationId, organizationId), eq(workspaces.isDefault, true))).limit(1)
|
|
72
|
+
return rows[0]
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
async update(id: string, data: { name?: string }): Promise<Workspace | undefined> {
|
|
76
|
+
await db.update(workspaces).set({ ...data, updatedAt: Date.now() }).where(eq(workspaces.id, id))
|
|
77
|
+
const rows = await db.select().from(workspaces).where(eq(workspaces.id, id)).limit(1)
|
|
78
|
+
return rows[0]
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async delete(id: string): Promise<void> {
|
|
82
|
+
await db.delete(workspaces).where(eq(workspaces.id, id))
|
|
83
|
+
},
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export type WorkspaceRepo = ReturnType<typeof createWorkspaceRepo>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { projects } from './projects';
|
|
3
|
+
|
|
4
|
+
export const environments = sqliteTable('environments', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
|
7
|
+
shortId: text('short_id').notNull().unique(),
|
|
8
|
+
slug: text('slug').notNull(),
|
|
9
|
+
name: text('name').notNull(),
|
|
10
|
+
createdAt: integer('created_at').notNull(),
|
|
11
|
+
updatedAt: integer('updated_at').notNull(),
|
|
12
|
+
}, (t) => ({
|
|
13
|
+
projectSlugUniq: uniqueIndex('environments_project_slug_uniq').on(t.projectId, t.slug),
|
|
14
|
+
}));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Default export: SQLite (backward compat for existing D1/Turso projects)
|
|
2
|
+
export { organizations } from './sqlite/organizations.js'
|
|
3
|
+
export { workspaces } from './sqlite/workspaces.js'
|
|
4
|
+
export { projects } from './sqlite/projects.js'
|
|
5
|
+
export { environments } from './sqlite/environments.js'
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
|
|
3
|
+
export const organizations = sqliteTable('organizations', {
|
|
4
|
+
id: text('id').primaryKey(),
|
|
5
|
+
shortId: text('short_id').notNull().unique(),
|
|
6
|
+
slug: text('slug').notNull().unique(),
|
|
7
|
+
name: text('name').notNull(),
|
|
8
|
+
metadata: text('metadata'), // JSON: { logo_url, website, ... }
|
|
9
|
+
createdAt: integer('created_at').notNull(),
|
|
10
|
+
updatedAt: integer('updated_at').notNull(),
|
|
11
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { bigint, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core'
|
|
2
|
+
import { projects } from './projects.js'
|
|
3
|
+
|
|
4
|
+
export const environments = pgTable('environments', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
|
7
|
+
shortId: text('short_id').notNull().unique(),
|
|
8
|
+
slug: text('slug').notNull(),
|
|
9
|
+
name: text('name').notNull(),
|
|
10
|
+
createdAt: bigint('created_at', { mode: 'number' }).notNull(),
|
|
11
|
+
updatedAt: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
12
|
+
}, (t) => [
|
|
13
|
+
uniqueIndex('environments_project_slug_uniq').on(t.projectId, t.slug),
|
|
14
|
+
])
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { bigint, pgTable, text } from 'drizzle-orm/pg-core'
|
|
2
|
+
|
|
3
|
+
export const organizations = pgTable('organizations', {
|
|
4
|
+
id: text('id').primaryKey(),
|
|
5
|
+
shortId: text('short_id').notNull().unique(),
|
|
6
|
+
slug: text('slug').notNull().unique(),
|
|
7
|
+
name: text('name').notNull(),
|
|
8
|
+
metadata: text('metadata'),
|
|
9
|
+
createdAt: bigint('created_at', { mode: 'number' }).notNull(),
|
|
10
|
+
updatedAt: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
11
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { bigint, boolean, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core'
|
|
2
|
+
import { workspaces } from './workspaces.js'
|
|
3
|
+
|
|
4
|
+
export const projects = pgTable('projects', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
workspaceId: text('workspace_id').notNull().references(() => workspaces.id, { onDelete: 'cascade' }),
|
|
7
|
+
shortId: text('short_id').notNull().unique(),
|
|
8
|
+
slug: text('slug').notNull(),
|
|
9
|
+
name: text('name').notNull(),
|
|
10
|
+
isDefault: boolean('is_default').notNull().default(false),
|
|
11
|
+
metadata: text('metadata'),
|
|
12
|
+
createdAt: bigint('created_at', { mode: 'number' }).notNull(),
|
|
13
|
+
updatedAt: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
14
|
+
}, (t) => [
|
|
15
|
+
uniqueIndex('projects_workspace_slug_uniq').on(t.workspaceId, t.slug),
|
|
16
|
+
])
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { bigint, boolean, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core'
|
|
2
|
+
import { organizations } from './organizations.js'
|
|
3
|
+
|
|
4
|
+
export const workspaces = pgTable('workspaces', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
organizationId: text('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
|
|
7
|
+
shortId: text('short_id').notNull().unique(),
|
|
8
|
+
slug: text('slug').notNull(),
|
|
9
|
+
name: text('name').notNull(),
|
|
10
|
+
isDefault: boolean('is_default').notNull().default(false),
|
|
11
|
+
createdAt: bigint('created_at', { mode: 'number' }).notNull(),
|
|
12
|
+
updatedAt: bigint('updated_at', { mode: 'number' }).notNull(),
|
|
13
|
+
}, (t) => [
|
|
14
|
+
uniqueIndex('workspaces_org_slug_uniq').on(t.organizationId, t.slug),
|
|
15
|
+
])
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { workspaces } from './workspaces';
|
|
3
|
+
|
|
4
|
+
export const projects = sqliteTable('projects', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
workspaceId: text('workspace_id').notNull().references(() => workspaces.id, { onDelete: 'cascade' }),
|
|
7
|
+
shortId: text('short_id').notNull().unique(),
|
|
8
|
+
slug: text('slug').notNull(),
|
|
9
|
+
name: text('name').notNull(),
|
|
10
|
+
isDefault: integer('is_default', { mode: 'boolean' }).notNull().default(false),
|
|
11
|
+
metadata: text('metadata'),
|
|
12
|
+
createdAt: integer('created_at').notNull(),
|
|
13
|
+
updatedAt: integer('updated_at').notNull(),
|
|
14
|
+
}, (t) => ({
|
|
15
|
+
workspaceSlugUniq: uniqueIndex('projects_workspace_slug_uniq').on(t.workspaceId, t.slug),
|
|
16
|
+
}));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { projects } from './projects.js';
|
|
3
|
+
|
|
4
|
+
export const environments = sqliteTable('environments', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
projectId: text('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
|
7
|
+
shortId: text('short_id').notNull().unique(),
|
|
8
|
+
slug: text('slug').notNull(),
|
|
9
|
+
name: text('name').notNull(),
|
|
10
|
+
createdAt: integer('created_at').notNull(),
|
|
11
|
+
updatedAt: integer('updated_at').notNull(),
|
|
12
|
+
}, (t) => ({
|
|
13
|
+
projectSlugUniq: uniqueIndex('environments_project_slug_uniq').on(t.projectId, t.slug),
|
|
14
|
+
}));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
|
|
3
|
+
export const organizations = sqliteTable('organizations', {
|
|
4
|
+
id: text('id').primaryKey(),
|
|
5
|
+
shortId: text('short_id').notNull().unique(),
|
|
6
|
+
slug: text('slug').notNull().unique(),
|
|
7
|
+
name: text('name').notNull(),
|
|
8
|
+
metadata: text('metadata'), // JSON: { logo_url, website, ... }
|
|
9
|
+
createdAt: integer('created_at').notNull(),
|
|
10
|
+
updatedAt: integer('updated_at').notNull(),
|
|
11
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { workspaces } from './workspaces.js';
|
|
3
|
+
|
|
4
|
+
export const projects = sqliteTable('projects', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
workspaceId: text('workspace_id').notNull().references(() => workspaces.id, { onDelete: 'cascade' }),
|
|
7
|
+
shortId: text('short_id').notNull().unique(),
|
|
8
|
+
slug: text('slug').notNull(),
|
|
9
|
+
name: text('name').notNull(),
|
|
10
|
+
isDefault: integer('is_default', { mode: 'boolean' }).notNull().default(false),
|
|
11
|
+
metadata: text('metadata'),
|
|
12
|
+
createdAt: integer('created_at').notNull(),
|
|
13
|
+
updatedAt: integer('updated_at').notNull(),
|
|
14
|
+
}, (t) => ({
|
|
15
|
+
workspaceSlugUniq: uniqueIndex('projects_workspace_slug_uniq').on(t.workspaceId, t.slug),
|
|
16
|
+
}));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { organizations } from './organizations.js';
|
|
3
|
+
|
|
4
|
+
export const workspaces = sqliteTable('workspaces', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
organizationId: text('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
|
|
7
|
+
shortId: text('short_id').notNull().unique(),
|
|
8
|
+
slug: text('slug').notNull(),
|
|
9
|
+
name: text('name').notNull(),
|
|
10
|
+
isDefault: integer('is_default', { mode: 'boolean' }).notNull().default(false),
|
|
11
|
+
createdAt: integer('created_at').notNull(),
|
|
12
|
+
updatedAt: integer('updated_at').notNull(),
|
|
13
|
+
}, (t) => ({
|
|
14
|
+
orgSlugUniq: uniqueIndex('workspaces_org_slug_uniq').on(t.organizationId, t.slug),
|
|
15
|
+
}));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { integer, sqliteTable, text, uniqueIndex } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
import { organizations } from './organizations';
|
|
3
|
+
|
|
4
|
+
export const workspaces = sqliteTable('workspaces', {
|
|
5
|
+
id: text('id').primaryKey(),
|
|
6
|
+
organizationId: text('organization_id').notNull().references(() => organizations.id, { onDelete: 'cascade' }),
|
|
7
|
+
shortId: text('short_id').notNull().unique(),
|
|
8
|
+
slug: text('slug').notNull(),
|
|
9
|
+
name: text('name').notNull(),
|
|
10
|
+
isDefault: integer('is_default', { mode: 'boolean' }).notNull().default(false),
|
|
11
|
+
createdAt: integer('created_at').notNull(),
|
|
12
|
+
updatedAt: integer('updated_at').notNull(),
|
|
13
|
+
}, (t) => ({
|
|
14
|
+
orgSlugUniq: uniqueIndex('workspaces_org_slug_uniq').on(t.organizationId, t.slug),
|
|
15
|
+
}));
|