初始提交:DSH 多租户平台(dshs)

This commit is contained in:
admin committed 2026-09-13 16:18:10 +08:00
commit 43976fea6a
167 files changed
+24456

No files matched your search

+295
View File
@@ -0,0 +1,295 @@
/**
* SQLite backend for {@link DbAdapter}. Wraps the synchronous better-sqlite3
* repo in async methods and maps constraint errors onto the shared hierarchy.
* Used when `deployMode=local` (no `DSHS_DB_URL`).
*
* NOTE: better-sqlite3 is synchronous; the `async` here only matches the
* interface — the underlying calls still block the event loop. Fine for the
* small single-machine local mode this backend targets (see docs/k8s.md §5.1).
* @module dshs/db/sqlite
*/
import type { DbAdapter } from './adapter.js'
import { openDatabase, type Database } from './connection.js'
import { mapSqliteError } from './errors.js'
import {
audit as auditSync,
countAdmins as countAdminsSync,
createSession as createSessionSync,
createUser as createUserSync,
deleteBusinessPlugin as deleteBusinessPluginSync,
deleteCredentialKey as deleteCredentialKeySync,
deleteInstance as deleteInstanceSync,
deleteSession as deleteSessionSync,
deleteUser as deleteUserSync,
deleteUserInstances as deleteUserInstancesSync,
deleteUserSessions as deleteUserSessionsSync,
findBusinessPlugin as findBusinessPluginSync,
findDomainById as findDomainByIdSync,
findDomainByUser as findDomainByUserSync,
findInstance as findInstanceSync,
findSession as findSessionSync,
findSessionWithUser as findSessionWithUserSync,
hasActiveSession as hasActiveSessionSync,
findUserById as findUserByIdSync,
findUserBySlug as findUserBySlugSync,
findUserByUsername as findUserByUsernameSync,
findUserInstance as findUserInstanceSync,
findWorkspaceByPath as findWorkspaceByPathSync,
getEnabledCredentialKeyRef as getEnabledCredentialKeyRefSync,
getEnabledPluginIds as getEnabledPluginIdsSync,
getOrCreateWorkspace as getOrCreateWorkspaceSync,
listBusinessPlugins as listBusinessPluginsSync,
listCredentialKeys as listCredentialKeysSync,
listDomains as listDomainsSync,
listInstancesByRole as listInstancesByRoleSync,
listPublicUsers as listPublicUsersSync,
listUsersWithoutUid as listUsersWithoutUidSync,
selectCredentialKey as selectCredentialKeySync,
setCredentialKey as setCredentialKeySync,
setDomainVerified as setDomainVerifiedSync,
setFolderPlugins as setFolderPluginsSync,
setInstanceStatus as setInstanceStatusSync,
setUserRole as setUserRoleSync,
setUserUid as setUserUidSync,
upsertBusinessPlugin as upsertBusinessPluginSync,
upsertDomain as upsertDomainSync,
upsertInstance as upsertInstanceSync,
} from './repo.js'
import type {
BusinessPlugin,
CredentialKey,
CreateSessionInput,
CreateUserInput,
Domain,
DshInstance,
DshInstanceRole,
DshInstanceStatus,
PublicUser,
SessionRow,
SessionUser,
UpsertBusinessPluginInput,
UpsertDshInstanceInput,
User,
UserRole,
Workspace,
} from './types.js'
export class SqliteAdapter implements DbAdapter {
private readonly db: Database
constructor(path: string, private readonly baseUid: number) {
this.db = openDatabase(path)
}
async createUser(input: CreateUserInput): Promise<User> {
try {
return createUserSync(this.db, input, this.baseUid)
} catch (e) {
mapSqliteError(e)
}
}
async findUserByUsername(username: string): Promise<User | undefined> {
return findUserByUsernameSync(this.db, username)
}
async findUserBySlug(slug: string): Promise<User | undefined> {
return findUserBySlugSync(this.db, slug)
}
async findUserById(id: string): Promise<User | undefined> {
return findUserByIdSync(this.db, id)
}
async listPublicUsers(): Promise<PublicUser[]> {
return listPublicUsersSync(this.db)
}
async countAdmins(): Promise<number> {
return countAdminsSync(this.db)
}
async setUserRole(id: string, role: UserRole, approvedBy?: string): Promise<boolean> {
return setUserRoleSync(this.db, id, role, approvedBy)
}
async setUserUid(userId: string, uid: number): Promise<void> {
setUserUidSync(this.db, userId, uid)
}
async listUsersWithoutUid(): Promise<string[]> {
return listUsersWithoutUidSync(this.db)
}
async createSession(input: CreateSessionInput): Promise<void> {
try {
createSessionSync(this.db, input)
} catch (e) {
mapSqliteError(e)
}
}
async findSession(tokenHash: string): Promise<SessionRow | undefined> {
return findSessionSync(this.db, tokenHash)
}
async deleteSession(tokenHash: string): Promise<void> {
deleteSessionSync(this.db, tokenHash)
}
async deleteUserSessions(userId: string): Promise<void> {
deleteUserSessionsSync(this.db, userId)
}
async findSessionWithUser(tokenHash: string): Promise<SessionUser | undefined> {
return findSessionWithUserSync(this.db, tokenHash)
}
async hasActiveSession(userId: string): Promise<boolean> {
return hasActiveSessionSync(this.db, userId)
}
async audit(actor: string | null, action: string, detail?: string | null): Promise<void> {
auditSync(this.db, actor, action, detail)
}
async findWorkspaceByPath(userId: string, relPath: string): Promise<Workspace | undefined> {
return findWorkspaceByPathSync(this.db, userId, relPath)
}
async getOrCreateWorkspace(userId: string, relPath: string): Promise<Workspace> {
try {
return getOrCreateWorkspaceSync(this.db, userId, relPath)
} catch (e) {
mapSqliteError(e)
}
}
async setFolderPlugins(
workspaceId: string,
selections: ReadonlyArray<{ id: string; enabled: boolean }>,
): Promise<void> {
try {
setFolderPluginsSync(this.db, workspaceId, selections)
} catch (e) {
mapSqliteError(e)
}
}
async getEnabledPluginIds(workspaceId: string): Promise<string[]> {
return getEnabledPluginIdsSync(this.db, workspaceId)
}
async listBusinessPlugins(): Promise<BusinessPlugin[]> {
return listBusinessPluginsSync(this.db)
}
async findBusinessPlugin(id: string): Promise<BusinessPlugin | undefined> {
return findBusinessPluginSync(this.db, id)
}
async upsertBusinessPlugin(input: UpsertBusinessPluginInput): Promise<BusinessPlugin> {
try {
return upsertBusinessPluginSync(this.db, input)
} catch (e) {
mapSqliteError(e)
}
}
async deleteBusinessPlugin(id: string): Promise<boolean> {
return deleteBusinessPluginSync(this.db, id)
}
async findDomainByUser(userId: string): Promise<Domain | undefined> {
return findDomainByUserSync(this.db, userId)
}
async findDomainById(id: string): Promise<Domain | undefined> {
return findDomainByIdSync(this.db, id)
}
async listDomains(): Promise<Domain[]> {
return listDomainsSync(this.db)
}
async upsertDomain(userId: string, domain: string, nginxConfig: string): Promise<Domain> {
try {
return upsertDomainSync(this.db, userId, domain, nginxConfig)
} catch (e) {
mapSqliteError(e)
}
}
async setDomainVerified(id: string, verified: boolean): Promise<boolean> {
return setDomainVerifiedSync(this.db, id, verified)
}
async listCredentialKeys(userId: string): Promise<CredentialKey[]> {
return listCredentialKeysSync(this.db, userId)
}
async getEnabledCredentialKeyRef(userId: string): Promise<string | null> {
return getEnabledCredentialKeyRefSync(this.db, userId)
}
async setCredentialKey(userId: string, name: string, encryptedRef: string): Promise<CredentialKey> {
try {
return setCredentialKeySync(this.db, userId, name, encryptedRef)
} catch (e) {
mapSqliteError(e)
}
}
async selectCredentialKey(userId: string, id: string): Promise<boolean> {
return selectCredentialKeySync(this.db, userId, id)
}
async deleteCredentialKey(userId: string, id: string): Promise<boolean> {
return deleteCredentialKeySync(this.db, userId, id)
}
async deleteUser(userId: string): Promise<boolean> {
return deleteUserSync(this.db, userId)
}
async upsertInstance(input: UpsertDshInstanceInput): Promise<void> {
try {
upsertInstanceSync(this.db, input)
} catch (e) {
mapSqliteError(e)
}
}
async findInstance(id: string): Promise<DshInstance | undefined> {
return findInstanceSync(this.db, id)
}
async findUserInstance(userId: string, role: DshInstanceRole): Promise<DshInstance | undefined> {
return findUserInstanceSync(this.db, userId, role)
}
async listInstancesByRole(role: DshInstanceRole): Promise<DshInstance[]> {
return listInstancesByRoleSync(this.db, role)
}
async setInstanceStatus(
id: string,
status: DshInstanceStatus,
outcome?: { exitCode?: number; lastError?: string },
): Promise<boolean> {
return setInstanceStatusSync(this.db, id, status, outcome)
}
async deleteInstance(id: string): Promise<boolean> {
return deleteInstanceSync(this.db, id)
}
async deleteUserInstances(userId: string): Promise<void> {
deleteUserInstancesSync(this.db, userId)
}
async close(): Promise<void> {
this.db.close()
}
}