@apiagex/database 0.6.3 → 0.8.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/README.md +10 -6
- package/dist/admin-permission-repository.d.ts +4 -4
- package/dist/admin-permission-repository.d.ts.map +1 -1
- package/dist/admin-permission-repository.js +19 -17
- package/dist/api-token-repository.d.ts +5 -5
- package/dist/api-token-repository.d.ts.map +1 -1
- package/dist/api-token-repository.js +19 -20
- package/dist/database-adapter.type.d.ts +18 -0
- package/dist/database-adapter.type.d.ts.map +1 -0
- package/dist/database-adapter.type.js +1 -0
- package/dist/entry-query.d.ts +2 -2
- package/dist/entry-query.d.ts.map +1 -1
- package/dist/entry-query.js +8 -7
- package/dist/entry-repository.d.ts +6 -6
- package/dist/entry-repository.d.ts.map +1 -1
- package/dist/entry-repository.js +46 -56
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/mysql-adapter.d.ts +17 -0
- package/dist/mysql-adapter.d.ts.map +1 -0
- package/dist/mysql-adapter.js +63 -0
- package/dist/permission-repository.d.ts +4 -4
- package/dist/permission-repository.d.ts.map +1 -1
- package/dist/permission-repository.js +24 -38
- package/dist/postgres-adapter.d.ts +17 -0
- package/dist/postgres-adapter.d.ts.map +1 -0
- package/dist/postgres-adapter.js +61 -0
- package/dist/provider-migrations.d.ts +14 -0
- package/dist/provider-migrations.d.ts.map +1 -0
- package/dist/provider-migrations.js +198 -0
- package/dist/realtime-repository.d.ts +11 -11
- package/dist/realtime-repository.d.ts.map +1 -1
- package/dist/realtime-repository.js +37 -27
- package/dist/realtime-session-repository.d.ts +3 -3
- package/dist/realtime-session-repository.d.ts.map +1 -1
- package/dist/realtime-session-repository.js +11 -11
- package/dist/relation-helpers.d.ts +3 -3
- package/dist/relation-helpers.d.ts.map +1 -1
- package/dist/relation-helpers.js +4 -6
- package/dist/role-repository.d.ts +6 -6
- package/dist/role-repository.d.ts.map +1 -1
- package/dist/role-repository.js +17 -24
- package/dist/schema-repository.d.ts +7 -7
- package/dist/schema-repository.d.ts.map +1 -1
- package/dist/schema-repository.js +63 -88
- package/dist/sqlite-adapter.d.ts +15 -0
- package/dist/sqlite-adapter.d.ts.map +1 -0
- package/dist/sqlite-adapter.js +48 -0
- package/dist/user-repository.d.ts +8 -6
- package/dist/user-repository.d.ts.map +1 -1
- package/dist/user-repository.js +17 -27
- package/dist/webhook-repository.d.ts +13 -13
- package/dist/webhook-repository.d.ts.map +1 -1
- package/dist/webhook-repository.js +47 -43
- package/package.json +6 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiagexDatabase } from "./database-adapter.type.js";
|
|
2
2
|
import type { CreatedRealtimeSession, CreateRealtimeSessionInput, RealtimeSessionRecord } from "./realtime-session-repository.type.js";
|
|
3
|
-
export declare function createRealtimeSession(db:
|
|
4
|
-
export declare function consumeRealtimeSession(db:
|
|
3
|
+
export declare function createRealtimeSession(db: ApiagexDatabase, input: CreateRealtimeSessionInput): Promise<CreatedRealtimeSession>;
|
|
4
|
+
export declare function consumeRealtimeSession(db: ApiagexDatabase, token: string, schemaSlug: string, now?: Date): Promise<RealtimeSessionRecord | undefined>;
|
|
5
5
|
//# sourceMappingURL=realtime-session-repository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime-session-repository.d.ts","sourceRoot":"","sources":["../src/realtime-session-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"realtime-session-repository.d.ts","sourceRoot":"","sources":["../src/realtime-session-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EACV,sBAAsB,EACtB,0BAA0B,EAC1B,qBAAqB,EACtB,MAAM,uCAAuC,CAAC;AAI/C,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,sBAAsB,CAAC,CAoBjC;AAED,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,OAAa,GACf,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAS5C"}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
-
export function createRealtimeSession(db, input) {
|
|
2
|
+
export async function createRealtimeSession(db, input) {
|
|
3
3
|
const now = new Date();
|
|
4
4
|
const ttlSeconds = Math.max(30, Math.min(input.ttlSeconds ?? 300, 900));
|
|
5
5
|
const token = `rt_${randomBytes(32).toString("base64url")}`;
|
|
6
6
|
const id = randomUUID();
|
|
7
|
-
db.prepare(`INSERT INTO realtime_sessions
|
|
7
|
+
await db.prepare(`INSERT INTO realtime_sessions
|
|
8
8
|
(id, token_hash, token_prefix, role_id, schema_id, schema_slug, created_at, expires_at, used_at)
|
|
9
9
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, NULL)`).run(id, hashToken(token), token.slice(0, 12), input.roleId, input.schemaId, input.schemaSlug, now.toISOString(), new Date(now.getTime() + ttlSeconds * 1000).toISOString());
|
|
10
|
-
return { token, session: requireRealtimeSession(db, id) };
|
|
10
|
+
return { token, session: await requireRealtimeSession(db, id) };
|
|
11
11
|
}
|
|
12
|
-
export function consumeRealtimeSession(db, token, schemaSlug, now = new Date()) {
|
|
13
|
-
const row = db
|
|
12
|
+
export async function consumeRealtimeSession(db, token, schemaSlug, now = new Date()) {
|
|
13
|
+
const row = await db
|
|
14
|
+
.prepare(sessionSelectSql("WHERE token_hash = ? AND schema_slug = ?"))
|
|
14
15
|
.get(hashToken(token), schemaSlug);
|
|
15
16
|
if (!row || row.usedAt || Date.parse(row.expiresAt) <= now.getTime())
|
|
16
17
|
return undefined;
|
|
17
|
-
const result = db.prepare("UPDATE realtime_sessions SET used_at = ? WHERE id = ? AND used_at IS NULL")
|
|
18
|
+
const result = await db.prepare("UPDATE realtime_sessions SET used_at = ? WHERE id = ? AND used_at IS NULL")
|
|
18
19
|
.run(now.toISOString(), row.id);
|
|
19
20
|
if (result.changes !== 1)
|
|
20
21
|
return undefined;
|
|
21
22
|
return getRealtimeSessionById(db, row.id);
|
|
22
23
|
}
|
|
23
|
-
function getRealtimeSessionById(db, id) {
|
|
24
|
-
|
|
25
|
-
return row;
|
|
24
|
+
async function getRealtimeSessionById(db, id) {
|
|
25
|
+
return db.prepare(sessionSelectSql("WHERE id = ?")).get(id);
|
|
26
26
|
}
|
|
27
|
-
function requireRealtimeSession(db, id) {
|
|
28
|
-
const session = getRealtimeSessionById(db, id);
|
|
27
|
+
async function requireRealtimeSession(db, id) {
|
|
28
|
+
const session = await getRealtimeSessionById(db, id);
|
|
29
29
|
if (!session)
|
|
30
30
|
throw new Error("REALTIME_SESSION_NOT_FOUND");
|
|
31
31
|
return session;
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
+
import type { ApiagexDatabase, DatabaseQueryParam } from "./database-adapter.type.js";
|
|
1
2
|
import type { EntryData } from "./entry-repository.type.js";
|
|
2
3
|
import type { FieldRecord, RelationType } from "./schema-repository.type.js";
|
|
3
|
-
import type { SqliteDatabase } from "./sqlite.js";
|
|
4
4
|
type EntryDataRow = {
|
|
5
5
|
dataJson: string;
|
|
6
6
|
id: string;
|
|
7
7
|
};
|
|
8
8
|
export declare function relationTypeOf(field: FieldRecord): RelationType;
|
|
9
9
|
export declare function entryDataReferences(data: EntryData, entryId: string): boolean;
|
|
10
|
-
export declare function listEntryDataRows(db:
|
|
10
|
+
export declare function listEntryDataRows(db: ApiagexDatabase, where?: string, params?: DatabaseQueryParam[]): Promise<EntryDataRow[]>;
|
|
11
11
|
export declare function parseEntryData(dataJson: string): EntryData;
|
|
12
|
-
export declare function schemaEntriesUseField(db:
|
|
12
|
+
export declare function schemaEntriesUseField(db: ApiagexDatabase, schemaId: string, fieldSlug: string): Promise<boolean>;
|
|
13
13
|
export {};
|
|
14
14
|
//# sourceMappingURL=relation-helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relation-helpers.d.ts","sourceRoot":"","sources":["../src/relation-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"relation-helpers.d.ts","sourceRoot":"","sources":["../src/relation-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AACtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAE7E,KAAK,YAAY,GAAG;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY,CAE/D;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAI7E;AAED,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,eAAe,EACnB,KAAK,SAAK,EACV,MAAM,GAAE,kBAAkB,EAAO,GAChC,OAAO,CAAC,YAAY,EAAE,CAAC,CAEzB;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAE1D;AAED,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,eAAe,EACnB,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAMlB"}
|
package/dist/relation-helpers.js
CHANGED
|
@@ -4,16 +4,14 @@ export function relationTypeOf(field) {
|
|
|
4
4
|
export function entryDataReferences(data, entryId) {
|
|
5
5
|
return Object.values(data).some((value) => value === entryId || (Array.isArray(value) && value.includes(entryId)));
|
|
6
6
|
}
|
|
7
|
-
export function listEntryDataRows(db, where = "", params = []) {
|
|
8
|
-
return db
|
|
9
|
-
.prepare(`SELECT id, data_json as dataJson FROM entries ${where}`)
|
|
10
|
-
.all(...params);
|
|
7
|
+
export async function listEntryDataRows(db, where = "", params = []) {
|
|
8
|
+
return db.prepare(`SELECT id, data_json as dataJson FROM entries ${where}`).all(...params);
|
|
11
9
|
}
|
|
12
10
|
export function parseEntryData(dataJson) {
|
|
13
11
|
return JSON.parse(dataJson);
|
|
14
12
|
}
|
|
15
|
-
export function schemaEntriesUseField(db, schemaId, fieldSlug) {
|
|
16
|
-
const rows = listEntryDataRows(db, "WHERE schema_id = ?", [schemaId]);
|
|
13
|
+
export async function schemaEntriesUseField(db, schemaId, fieldSlug) {
|
|
14
|
+
const rows = await listEntryDataRows(db, "WHERE schema_id = ?", [schemaId]);
|
|
17
15
|
return rows.some((row) => {
|
|
18
16
|
const value = parseEntryData(row.dataJson)[fieldSlug];
|
|
19
17
|
return value !== undefined && value !== null && value !== "";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiagexDatabase } from "./database-adapter.type.js";
|
|
2
2
|
import type { CreateRoleInput, RoleRecord } from "./role-repository.type.js";
|
|
3
|
-
export declare function createRole(db:
|
|
4
|
-
export declare function createAdminRole(db:
|
|
5
|
-
export declare function listRoles(db:
|
|
6
|
-
export declare function listAdminRoles(db:
|
|
7
|
-
export declare function getRoleById(db:
|
|
3
|
+
export declare function createRole(db: ApiagexDatabase, input: CreateRoleInput): Promise<RoleRecord>;
|
|
4
|
+
export declare function createAdminRole(db: ApiagexDatabase, input: CreateRoleInput): Promise<RoleRecord>;
|
|
5
|
+
export declare function listRoles(db: ApiagexDatabase): Promise<RoleRecord[]>;
|
|
6
|
+
export declare function listAdminRoles(db: ApiagexDatabase): Promise<RoleRecord[]>;
|
|
7
|
+
export declare function getRoleById(db: ApiagexDatabase, id: string): Promise<RoleRecord | undefined>;
|
|
8
8
|
//# sourceMappingURL=role-repository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role-repository.d.ts","sourceRoot":"","sources":["../src/role-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"role-repository.d.ts","sourceRoot":"","sources":["../src/role-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAO7E,wBAAsB,UAAU,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAQjG;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CAQtG;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAG1E;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAK/E;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAGlG"}
|
package/dist/role-repository.js
CHANGED
|
@@ -1,61 +1,54 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
const roleNamePattern = /^[a-z][a-z0-9-]*$/;
|
|
3
3
|
const adminRoleNames = new Set(["owner", "admin", "schema-manager", "user-manager"]);
|
|
4
|
-
export function createRole(db, input) {
|
|
4
|
+
export async function createRole(db, input) {
|
|
5
5
|
validateApiRole(input);
|
|
6
6
|
const id = randomUUID();
|
|
7
7
|
const now = new Date().toISOString();
|
|
8
|
-
db.prepare("INSERT INTO roles (id, name, description, is_owner, role_kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, input.name, input.description ?? "", 0, "api", now, now);
|
|
8
|
+
await db.prepare("INSERT INTO roles (id, name, description, is_owner, role_kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, input.name, input.description ?? "", 0, "api", now, now);
|
|
9
9
|
return requireRole(db, id);
|
|
10
10
|
}
|
|
11
|
-
export function createAdminRole(db, input) {
|
|
11
|
+
export async function createAdminRole(db, input) {
|
|
12
12
|
validateAdminRole(input);
|
|
13
13
|
const id = randomUUID();
|
|
14
14
|
const now = new Date().toISOString();
|
|
15
|
-
db.prepare("INSERT INTO roles (id, name, description, is_owner, role_kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, input.name, input.description ?? "", 0, "admin", now, now);
|
|
15
|
+
await db.prepare("INSERT INTO roles (id, name, description, is_owner, role_kind, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)").run(id, input.name, input.description ?? "", 0, "admin", now, now);
|
|
16
16
|
return requireRole(db, id);
|
|
17
17
|
}
|
|
18
|
-
export function listRoles(db) {
|
|
19
|
-
const rows = db
|
|
20
|
-
.prepare(roleSelectSql("WHERE role_kind = 'api' ORDER BY created_at ASC"))
|
|
21
|
-
.all();
|
|
18
|
+
export async function listRoles(db) {
|
|
19
|
+
const rows = await db.prepare(roleSelectSql("WHERE role_kind = 'api' ORDER BY created_at ASC")).all();
|
|
22
20
|
return rows.map(rowToRole);
|
|
23
21
|
}
|
|
24
|
-
export function listAdminRoles(db) {
|
|
25
|
-
const rows = db
|
|
22
|
+
export async function listAdminRoles(db) {
|
|
23
|
+
const rows = await db
|
|
26
24
|
.prepare(roleSelectSql("WHERE role_kind = 'admin' ORDER BY is_owner DESC, created_at ASC"))
|
|
27
25
|
.all();
|
|
28
26
|
return rows.map(rowToRole);
|
|
29
27
|
}
|
|
30
|
-
export function getRoleById(db, id) {
|
|
31
|
-
const row = db.prepare(roleSelectSql("WHERE id = ?")).get(id);
|
|
28
|
+
export async function getRoleById(db, id) {
|
|
29
|
+
const row = await db.prepare(roleSelectSql("WHERE id = ?")).get(id);
|
|
32
30
|
return row ? rowToRole(row) : undefined;
|
|
33
31
|
}
|
|
34
32
|
function validateApiRole(input) {
|
|
35
33
|
validateRoleName(input);
|
|
36
|
-
if (input.name === "owner")
|
|
34
|
+
if (input.name === "owner")
|
|
37
35
|
throw new Error("ROLE_OWNER_RESERVED");
|
|
38
|
-
|
|
39
|
-
if (adminRoleNames.has(input.name)) {
|
|
36
|
+
if (adminRoleNames.has(input.name))
|
|
40
37
|
throw new Error("ROLE_ADMIN_RESERVED");
|
|
41
|
-
}
|
|
42
38
|
}
|
|
43
39
|
function validateAdminRole(input) {
|
|
44
40
|
validateRoleName(input);
|
|
45
|
-
if (input.name === "owner")
|
|
41
|
+
if (input.name === "owner")
|
|
46
42
|
throw new Error("ROLE_OWNER_RESERVED");
|
|
47
|
-
}
|
|
48
43
|
}
|
|
49
44
|
function validateRoleName(input) {
|
|
50
|
-
if (!roleNamePattern.test(input.name))
|
|
45
|
+
if (!roleNamePattern.test(input.name))
|
|
51
46
|
throw new Error("ROLE_NAME_INVALID");
|
|
52
|
-
}
|
|
53
47
|
}
|
|
54
|
-
function requireRole(db, id) {
|
|
55
|
-
const role = getRoleById(db, id);
|
|
56
|
-
if (!role)
|
|
48
|
+
async function requireRole(db, id) {
|
|
49
|
+
const role = await getRoleById(db, id);
|
|
50
|
+
if (!role)
|
|
57
51
|
throw new Error("ROLE_NOT_FOUND");
|
|
58
|
-
}
|
|
59
52
|
return role;
|
|
60
53
|
}
|
|
61
54
|
function rowToRole(row) {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiagexDatabase } from "./database-adapter.type.js";
|
|
2
2
|
import type { CreateSchemaInput, SchemaRecord, UpdateSchemaInput } from "./schema-repository.type.js";
|
|
3
|
-
export declare function createSchema(db:
|
|
4
|
-
export declare function listSchemas(db:
|
|
5
|
-
export declare function getSchemaById(db:
|
|
6
|
-
export declare function getSchemaBySlug(db:
|
|
7
|
-
export declare function updateSchema(db:
|
|
8
|
-
export declare function deleteSchema(db:
|
|
3
|
+
export declare function createSchema(db: ApiagexDatabase, input: CreateSchemaInput): Promise<SchemaRecord>;
|
|
4
|
+
export declare function listSchemas(db: ApiagexDatabase): Promise<SchemaRecord[]>;
|
|
5
|
+
export declare function getSchemaById(db: ApiagexDatabase, id: string): Promise<SchemaRecord | undefined>;
|
|
6
|
+
export declare function getSchemaBySlug(db: ApiagexDatabase, slug: string): Promise<SchemaRecord | undefined>;
|
|
7
|
+
export declare function updateSchema(db: ApiagexDatabase, id: string, input: UpdateSchemaInput): Promise<SchemaRecord>;
|
|
8
|
+
export declare function deleteSchema(db: ApiagexDatabase, id: string): Promise<void>;
|
|
9
9
|
//# sourceMappingURL=schema-repository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-repository.d.ts","sourceRoot":"","sources":["../src/schema-repository.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema-repository.d.ts","sourceRoot":"","sources":["../src/schema-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAOlE,OAAO,KAAK,EAEV,iBAAiB,EAIjB,YAAY,EACZ,iBAAiB,EAClB,MAAM,6BAA6B,CAAC;AAMrC,wBAAsB,YAAY,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkBvG;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAI9E;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAKtG;AAED,wBAAsB,eAAe,CAAC,EAAE,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,SAAS,CAAC,CAG1G;AAED,wBAAsB,YAAY,CAChC,EAAE,EAAE,eAAe,EACnB,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,YAAY,CAAC,CAmBvB;AAED,wBAAsB,YAAY,CAAC,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIjF"}
|
|
@@ -2,153 +2,128 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { relationErrors, relationFieldUpdateUnsafe, relationSchemaReferenced, } from "./relation-errors.js";
|
|
3
3
|
import { schemaEntriesUseField } from "./relation-helpers.js";
|
|
4
4
|
const slugPattern = /^[a-z][a-z0-9-]*$/;
|
|
5
|
-
const fieldTypes = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"boolean",
|
|
10
|
-
"date",
|
|
11
|
-
"json",
|
|
12
|
-
"media",
|
|
13
|
-
"relation",
|
|
14
|
-
];
|
|
15
|
-
const relationTypes = [
|
|
16
|
-
"oneToOne",
|
|
17
|
-
"oneToMany",
|
|
18
|
-
"manyToOne",
|
|
19
|
-
"manyToMany",
|
|
20
|
-
];
|
|
21
|
-
export function createSchema(db, input) {
|
|
22
|
-
validateSchemaInput(db, input);
|
|
5
|
+
const fieldTypes = ["text", "longText", "number", "boolean", "date", "json", "media", "relation"];
|
|
6
|
+
const relationTypes = ["oneToOne", "oneToMany", "manyToOne", "manyToMany"];
|
|
7
|
+
export async function createSchema(db, input) {
|
|
8
|
+
await validateSchemaInput(db, input);
|
|
23
9
|
const schemaId = randomUUID();
|
|
24
10
|
const now = new Date().toISOString();
|
|
25
11
|
const description = input.description ?? "";
|
|
26
|
-
|
|
27
|
-
db.prepare("INSERT INTO schemas (id, name, slug, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(schemaId, input.name, input.slug, description, now, now);
|
|
28
|
-
|
|
12
|
+
await db.transaction(async () => {
|
|
13
|
+
await db.prepare("INSERT INTO schemas (id, name, slug, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(schemaId, input.name, input.slug, description, now, now);
|
|
14
|
+
for (const [index, field] of input.fields.entries()) {
|
|
15
|
+
await insertField(db, schemaId, field, index);
|
|
16
|
+
}
|
|
29
17
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (!created) {
|
|
18
|
+
const created = await getSchemaById(db, schemaId);
|
|
19
|
+
if (!created)
|
|
33
20
|
throw new Error("SCHEMA_CREATE_FAILED");
|
|
34
|
-
}
|
|
35
21
|
return created;
|
|
36
22
|
}
|
|
37
|
-
export function listSchemas(db) {
|
|
38
|
-
const rows = db.prepare("SELECT id FROM schemas ORDER BY created_at ASC").all();
|
|
39
|
-
|
|
23
|
+
export async function listSchemas(db) {
|
|
24
|
+
const rows = await db.prepare("SELECT id FROM schemas ORDER BY created_at ASC").all();
|
|
25
|
+
const schemas = await Promise.all(rows.map((row) => getSchemaById(db, row.id)));
|
|
26
|
+
return schemas.filter(isSchemaRecord);
|
|
40
27
|
}
|
|
41
|
-
export function getSchemaById(db, id) {
|
|
42
|
-
const schema = db
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
return { ...schema, fields: listFields(db, schema.id) };
|
|
28
|
+
export async function getSchemaById(db, id) {
|
|
29
|
+
const schema = await db
|
|
30
|
+
.prepare("SELECT id, name, slug, description FROM schemas WHERE id = ?")
|
|
31
|
+
.get(id);
|
|
32
|
+
return schema ? { ...schema, fields: await listFields(db, schema.id) } : undefined;
|
|
47
33
|
}
|
|
48
|
-
export function getSchemaBySlug(db, slug) {
|
|
49
|
-
const row = db.prepare("SELECT id FROM schemas WHERE slug = ?").get(slug);
|
|
34
|
+
export async function getSchemaBySlug(db, slug) {
|
|
35
|
+
const row = await db.prepare("SELECT id FROM schemas WHERE slug = ?").get(slug);
|
|
50
36
|
return row ? getSchemaById(db, row.id) : undefined;
|
|
51
37
|
}
|
|
52
|
-
export function updateSchema(db, id, input) {
|
|
53
|
-
if (!getSchemaById(db, id))
|
|
38
|
+
export async function updateSchema(db, id, input) {
|
|
39
|
+
if (!(await getSchemaById(db, id)))
|
|
54
40
|
throw new Error("SCHEMA_NOT_FOUND");
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
validateSchemaInput(db, input);
|
|
41
|
+
await assertSafeRelationUpdate(db, id, input);
|
|
42
|
+
await validateSchemaInput(db, input);
|
|
58
43
|
const now = new Date().toISOString();
|
|
59
44
|
const description = input.description ?? "";
|
|
60
|
-
|
|
61
|
-
db.prepare("UPDATE schemas SET name = ?, slug = ?, description = ?, updated_at = ? WHERE id = ?")
|
|
62
|
-
|
|
63
|
-
|
|
45
|
+
await db.transaction(async () => {
|
|
46
|
+
await db.prepare("UPDATE schemas SET name = ?, slug = ?, description = ?, updated_at = ? WHERE id = ?")
|
|
47
|
+
.run(input.name, input.slug, description, now, id);
|
|
48
|
+
await db.prepare("DELETE FROM fields WHERE schema_id = ?").run(id);
|
|
49
|
+
for (const [index, field] of input.fields.entries()) {
|
|
50
|
+
await insertField(db, id, field, index);
|
|
51
|
+
}
|
|
64
52
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (!updated) {
|
|
53
|
+
const updated = await getSchemaById(db, id);
|
|
54
|
+
if (!updated)
|
|
68
55
|
throw new Error("SCHEMA_UPDATE_FAILED");
|
|
69
|
-
}
|
|
70
56
|
return updated;
|
|
71
57
|
}
|
|
72
|
-
function
|
|
73
|
-
|
|
58
|
+
export async function deleteSchema(db, id) {
|
|
59
|
+
await assertSchemaNotReferenced(db, id);
|
|
60
|
+
const result = await db.prepare("DELETE FROM schemas WHERE id = ?").run(id);
|
|
61
|
+
if (result.changes === 0)
|
|
62
|
+
throw new Error("SCHEMA_NOT_FOUND");
|
|
63
|
+
}
|
|
64
|
+
async function assertSafeRelationUpdate(db, schemaId, input) {
|
|
65
|
+
const current = await getSchemaById(db, schemaId);
|
|
74
66
|
if (!current)
|
|
75
67
|
return;
|
|
76
68
|
const nextBySlug = new Map(input.fields.map((field) => [field.slug, field]));
|
|
77
69
|
for (const currentField of current.fields) {
|
|
78
70
|
if (currentField.type !== "relation")
|
|
79
71
|
continue;
|
|
80
|
-
if (!schemaEntriesUseField(db, schemaId, currentField.slug))
|
|
72
|
+
if (!(await schemaEntriesUseField(db, schemaId, currentField.slug)))
|
|
81
73
|
continue;
|
|
82
74
|
const nextField = nextBySlug.get(currentField.slug);
|
|
83
|
-
if (!nextField || nextField.type !== "relation")
|
|
75
|
+
if (!nextField || nextField.type !== "relation")
|
|
84
76
|
throw new Error(relationFieldUpdateUnsafe(currentField.slug));
|
|
85
|
-
}
|
|
86
77
|
const currentRelationType = currentField.relationType ?? "manyToOne";
|
|
87
78
|
const nextRelationType = nextField.relationType ?? "manyToOne";
|
|
88
|
-
if (currentField.relationSchemaId !== nextField.relationSchemaId ||
|
|
89
|
-
currentRelationType !== nextRelationType) {
|
|
79
|
+
if (currentField.relationSchemaId !== nextField.relationSchemaId || currentRelationType !== nextRelationType) {
|
|
90
80
|
throw new Error(relationFieldUpdateUnsafe(currentField.slug));
|
|
91
81
|
}
|
|
92
82
|
}
|
|
93
83
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const result = db.prepare("DELETE FROM schemas WHERE id = ?").run(id);
|
|
97
|
-
if (result.changes === 0) {
|
|
98
|
-
throw new Error("SCHEMA_NOT_FOUND");
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function assertSchemaNotReferenced(db, id) {
|
|
102
|
-
const dependent = db
|
|
84
|
+
async function assertSchemaNotReferenced(db, id) {
|
|
85
|
+
const dependent = await db
|
|
103
86
|
.prepare("SELECT schema_id as schemaId FROM fields WHERE relation_schema_id = ? AND schema_id != ? LIMIT 1")
|
|
104
87
|
.get(id, id);
|
|
105
|
-
if (dependent)
|
|
88
|
+
if (dependent)
|
|
106
89
|
throw new Error(relationSchemaReferenced(id));
|
|
107
|
-
}
|
|
108
90
|
}
|
|
109
|
-
function insertField(db, schemaId, field, position) {
|
|
110
|
-
db.prepare("INSERT INTO fields (id, schema_id, name, slug, type, relation_schema_id, relation_type, required, position) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(randomUUID(), schemaId, field.name, field.slug, field.type, field.relationSchemaId ?? null, field.relationType ?? null, field.required ? 1 : 0, position);
|
|
91
|
+
async function insertField(db, schemaId, field, position) {
|
|
92
|
+
await db.prepare("INSERT INTO fields (id, schema_id, name, slug, type, relation_schema_id, relation_type, required, position) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)").run(randomUUID(), schemaId, field.name, field.slug, field.type, field.relationSchemaId ?? null, field.relationType ?? null, field.required ? 1 : 0, position);
|
|
111
93
|
}
|
|
112
|
-
function listFields(db, schemaId) {
|
|
113
|
-
const rows = db
|
|
94
|
+
async function listFields(db, schemaId) {
|
|
95
|
+
const rows = await db
|
|
114
96
|
.prepare("SELECT id, schema_id as schemaId, name, slug, type, relation_schema_id as relationSchemaId, relation_type as relationType, required, position FROM fields WHERE schema_id = ? ORDER BY position ASC")
|
|
115
97
|
.all(schemaId);
|
|
116
98
|
return rows.map((row) => ({ ...row, required: Boolean(row.required) }));
|
|
117
99
|
}
|
|
118
|
-
function validateSchemaInput(db, input) {
|
|
100
|
+
async function validateSchemaInput(db, input) {
|
|
119
101
|
validateSlug(input.slug, "SCHEMA_SLUG_INVALID");
|
|
120
|
-
if (input.fields.length === 0)
|
|
102
|
+
if (input.fields.length === 0)
|
|
121
103
|
throw new Error("SCHEMA_FIELDS_REQUIRED");
|
|
122
|
-
}
|
|
123
104
|
for (const field of input.fields) {
|
|
124
105
|
validateSlug(field.slug, "FIELD_SLUG_INVALID");
|
|
125
|
-
if (!fieldTypes.includes(field.type))
|
|
106
|
+
if (!fieldTypes.includes(field.type))
|
|
126
107
|
throw new Error("FIELD_TYPE_INVALID");
|
|
127
|
-
|
|
128
|
-
validateRelationMetadata(db, field);
|
|
108
|
+
await validateRelationMetadata(db, field);
|
|
129
109
|
}
|
|
130
110
|
}
|
|
131
|
-
function validateRelationMetadata(db, field) {
|
|
111
|
+
async function validateRelationMetadata(db, field) {
|
|
132
112
|
if (field.type !== "relation") {
|
|
133
|
-
if (field.relationSchemaId || field.relationType)
|
|
113
|
+
if (field.relationSchemaId || field.relationType)
|
|
134
114
|
throw new Error(relationErrors.metadataForNonRelationField);
|
|
135
|
-
}
|
|
136
115
|
return;
|
|
137
116
|
}
|
|
138
|
-
if (!field.relationSchemaId)
|
|
117
|
+
if (!field.relationSchemaId)
|
|
139
118
|
throw new Error(relationErrors.targetRequired);
|
|
140
|
-
|
|
141
|
-
if (!getSchemaById(db, field.relationSchemaId)) {
|
|
119
|
+
if (!(await getSchemaById(db, field.relationSchemaId)))
|
|
142
120
|
throw new Error(relationErrors.targetMissing);
|
|
143
|
-
|
|
144
|
-
if (field.relationType && !relationTypes.includes(field.relationType)) {
|
|
121
|
+
if (field.relationType && !relationTypes.includes(field.relationType))
|
|
145
122
|
throw new Error(relationErrors.typeInvalid);
|
|
146
|
-
}
|
|
147
123
|
}
|
|
148
124
|
function validateSlug(slug, error) {
|
|
149
|
-
if (!slugPattern.test(slug))
|
|
125
|
+
if (!slugPattern.test(slug))
|
|
150
126
|
throw new Error(error);
|
|
151
|
-
}
|
|
152
127
|
}
|
|
153
128
|
function isSchemaRecord(value) {
|
|
154
129
|
return Boolean(value);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ApiagexDatabase, DatabaseStatement } from "./database-adapter.type.js";
|
|
2
|
+
import { type SqliteDatabase } from "./sqlite.js";
|
|
3
|
+
export declare class SqliteApiagexDatabase implements ApiagexDatabase {
|
|
4
|
+
private readonly db;
|
|
5
|
+
readonly provider = "sqlite";
|
|
6
|
+
constructor(db: SqliteDatabase);
|
|
7
|
+
exec(sql: string): Promise<void>;
|
|
8
|
+
prepare(sql: string): DatabaseStatement;
|
|
9
|
+
transaction<TResult>(callback: () => Promise<TResult>): Promise<TResult>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function openSqliteAdapter(path?: string): ApiagexDatabase;
|
|
13
|
+
export declare function openMigratedSqliteAdapter(path?: string): ApiagexDatabase;
|
|
14
|
+
export declare function wrapSqliteDatabase(db: SqliteDatabase): ApiagexDatabase;
|
|
15
|
+
//# sourceMappingURL=sqlite-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-adapter.d.ts","sourceRoot":"","sources":["../src/sqlite-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAyC,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC5H,OAAO,EAA0C,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAE1F,qBAAa,qBAAsB,YAAW,eAAe;IAG/C,OAAO,CAAC,QAAQ,CAAC,EAAE;IAF/B,QAAQ,CAAC,QAAQ,YAAY;gBAEA,EAAE,EAAE,cAAc;IAEzC,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAItC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB;IASjC,WAAW,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAYxE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B;AAED,wBAAgB,iBAAiB,CAAC,IAAI,SAAa,GAAG,eAAe,CAEpE;AAED,wBAAgB,yBAAyB,CAAC,IAAI,SAAa,GAAG,eAAe,CAI5E;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,GAAG,eAAe,CAEtE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { migrateMvpDatabase, openSqliteDatabase } from "./sqlite.js";
|
|
2
|
+
export class SqliteApiagexDatabase {
|
|
3
|
+
db;
|
|
4
|
+
provider = "sqlite";
|
|
5
|
+
constructor(db) {
|
|
6
|
+
this.db = db;
|
|
7
|
+
}
|
|
8
|
+
async exec(sql) {
|
|
9
|
+
this.db.exec(sql);
|
|
10
|
+
}
|
|
11
|
+
prepare(sql) {
|
|
12
|
+
const statement = this.db.prepare(sql);
|
|
13
|
+
return {
|
|
14
|
+
get: async (...params) => statement.get(...params),
|
|
15
|
+
all: async (...params) => statement.all(...params),
|
|
16
|
+
run: async (...params) => toRunResult(statement.run(...params)),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
async transaction(callback) {
|
|
20
|
+
this.db.exec("BEGIN");
|
|
21
|
+
try {
|
|
22
|
+
const result = await callback();
|
|
23
|
+
this.db.exec("COMMIT");
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
this.db.exec("ROLLBACK");
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async close() {
|
|
32
|
+
this.db.close();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function openSqliteAdapter(path = ":memory:") {
|
|
36
|
+
return new SqliteApiagexDatabase(openSqliteDatabase(path));
|
|
37
|
+
}
|
|
38
|
+
export function openMigratedSqliteAdapter(path = ":memory:") {
|
|
39
|
+
const db = openSqliteDatabase(path);
|
|
40
|
+
migrateMvpDatabase(db);
|
|
41
|
+
return new SqliteApiagexDatabase(db);
|
|
42
|
+
}
|
|
43
|
+
export function wrapSqliteDatabase(db) {
|
|
44
|
+
return new SqliteApiagexDatabase(db);
|
|
45
|
+
}
|
|
46
|
+
function toRunResult(result) {
|
|
47
|
+
return { changes: result.changes };
|
|
48
|
+
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiagexDatabase } from "./database-adapter.type.js";
|
|
2
2
|
import type { CreateUserInput, UserRecord } from "./user-repository.type.js";
|
|
3
|
-
|
|
4
|
-
export declare function listUsers(db: SqliteDatabase): UserRecord[];
|
|
5
|
-
export declare function getUserById(db: SqliteDatabase, id: string): UserRecord | undefined;
|
|
6
|
-
export declare function getUserPasswordHashByEmail(db: SqliteDatabase, email: string): {
|
|
3
|
+
type UserPasswordRecord = {
|
|
7
4
|
id: string;
|
|
8
5
|
passwordHash: string;
|
|
9
6
|
roleId: string;
|
|
10
|
-
}
|
|
7
|
+
};
|
|
8
|
+
export declare function createUser(db: ApiagexDatabase, input: CreateUserInput): Promise<UserRecord>;
|
|
9
|
+
export declare function listUsers(db: ApiagexDatabase): Promise<UserRecord[]>;
|
|
10
|
+
export declare function getUserById(db: ApiagexDatabase, id: string): Promise<UserRecord | undefined>;
|
|
11
|
+
export declare function getUserPasswordHashByEmail(db: ApiagexDatabase, email: string): Promise<UserPasswordRecord | undefined>;
|
|
12
|
+
export {};
|
|
11
13
|
//# sourceMappingURL=user-repository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-repository.d.ts","sourceRoot":"","sources":["../src/user-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"user-repository.d.ts","sourceRoot":"","sources":["../src/user-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,OAAO,KAAK,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE7E,KAAK,kBAAkB,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAG/E,wBAAsB,UAAU,CAAC,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,CAAC,CASjG;AAED,wBAAsB,SAAS,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAE1E;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,eAAe,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAElG;AAED,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,eAAe,EACnB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAQzC"}
|
package/dist/user-repository.js
CHANGED
|
@@ -1,51 +1,41 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { getRoleById } from "./role-repository.js";
|
|
3
|
-
export function createUser(db, input) {
|
|
3
|
+
export async function createUser(db, input) {
|
|
4
4
|
const email = normalizeEmail(input.email);
|
|
5
|
-
validateUser(db, { ...input, email });
|
|
5
|
+
await validateUser(db, { ...input, email });
|
|
6
6
|
const id = randomUUID();
|
|
7
7
|
const now = new Date().toISOString();
|
|
8
|
-
db.prepare("INSERT INTO users (id, email, password_hash, role_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(id, email, input.passwordHash, input.roleId, now, now);
|
|
8
|
+
await db.prepare("INSERT INTO users (id, email, password_hash, role_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)").run(id, email, input.passwordHash, input.roleId, now, now);
|
|
9
9
|
return requireUser(db, id);
|
|
10
10
|
}
|
|
11
|
-
export function listUsers(db) {
|
|
12
|
-
|
|
13
|
-
.prepare(userSelectSql("WHERE roles.role_kind = 'api' ORDER BY users.created_at ASC"))
|
|
14
|
-
.all();
|
|
15
|
-
return rows;
|
|
11
|
+
export async function listUsers(db) {
|
|
12
|
+
return db.prepare(userSelectSql("WHERE roles.role_kind = 'api' ORDER BY users.created_at ASC")).all();
|
|
16
13
|
}
|
|
17
|
-
export function getUserById(db, id) {
|
|
18
|
-
return db
|
|
19
|
-
.prepare(userSelectSql("WHERE users.id = ? AND roles.role_kind = 'api'"))
|
|
20
|
-
.get(id);
|
|
14
|
+
export async function getUserById(db, id) {
|
|
15
|
+
return db.prepare(userSelectSql("WHERE users.id = ? AND roles.role_kind = 'api'")).get(id);
|
|
21
16
|
}
|
|
22
|
-
export function getUserPasswordHashByEmail(db, email) {
|
|
17
|
+
export async function getUserPasswordHashByEmail(db, email) {
|
|
23
18
|
return db
|
|
24
19
|
.prepare(`SELECT users.id, users.password_hash as passwordHash, users.role_id as roleId
|
|
25
20
|
FROM users JOIN roles ON roles.id = users.role_id
|
|
26
21
|
WHERE users.email = ? AND roles.role_kind = 'api'`)
|
|
27
22
|
.get(normalizeEmail(email));
|
|
28
23
|
}
|
|
29
|
-
function validateUser(db, input) {
|
|
30
|
-
if (!input.email.includes("@"))
|
|
24
|
+
async function validateUser(db, input) {
|
|
25
|
+
if (!input.email.includes("@"))
|
|
31
26
|
throw new Error("USER_EMAIL_INVALID");
|
|
32
|
-
|
|
33
|
-
if (!input.passwordHash) {
|
|
27
|
+
if (!input.passwordHash)
|
|
34
28
|
throw new Error("USER_PASSWORD_HASH_REQUIRED");
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (!role) {
|
|
29
|
+
const role = await getRoleById(db, input.roleId);
|
|
30
|
+
if (!role)
|
|
38
31
|
throw new Error("ROLE_NOT_FOUND");
|
|
39
|
-
|
|
40
|
-
if (role.roleKind !== "api") {
|
|
32
|
+
if (role.roleKind !== "api")
|
|
41
33
|
throw new Error("ROLE_API_REQUIRED");
|
|
42
|
-
}
|
|
43
34
|
}
|
|
44
|
-
function requireUser(db, id) {
|
|
45
|
-
const user = getUserById(db, id);
|
|
46
|
-
if (!user)
|
|
35
|
+
async function requireUser(db, id) {
|
|
36
|
+
const user = await getUserById(db, id);
|
|
37
|
+
if (!user)
|
|
47
38
|
throw new Error("USER_NOT_FOUND");
|
|
48
|
-
}
|
|
49
39
|
return user;
|
|
50
40
|
}
|
|
51
41
|
function normalizeEmail(email) {
|