@stacksjs/database 0.70.87 → 0.70.90
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/auth-tables.js +220 -0
- package/dist/class-seeder.js +116 -0
- package/dist/column.d.ts +17 -0
- package/dist/column.js +26 -0
- package/dist/custom/audits.js +57 -0
- package/dist/custom/errors.js +48 -0
- package/dist/custom/index.js +3 -0
- package/dist/custom/jobs.js +449 -0
- package/dist/database.js +178 -0
- package/dist/defaults.js +48 -0
- package/dist/driver-config.js +144 -0
- package/dist/drivers/defaults/index.js +2 -0
- package/dist/drivers/defaults/passwords.js +106 -0
- package/dist/drivers/defaults/traits.js +1125 -0
- package/dist/drivers/dynamodb.js +607 -0
- package/dist/drivers/helpers.js +206 -0
- package/dist/drivers/index.js +9 -0
- package/dist/drivers/mysql.js +322 -0
- package/dist/drivers/postgres.js +411 -0
- package/dist/drivers/sqlite.js +397 -0
- package/dist/factory.js +51 -0
- package/dist/fk-audit.js +181 -0
- package/dist/index.js +55 -1263
- package/dist/migration-lock.js +143 -0
- package/dist/migrations.js +528 -0
- package/dist/notification-tables.js +54 -0
- package/dist/query-logger.js +213 -0
- package/dist/query-parser.js +93 -0
- package/dist/rbac-tables.js +84 -0
- package/dist/safe-migrations.js +59 -0
- package/dist/schema.d.ts +4 -0
- package/dist/schema.js +10 -0
- package/dist/seed-scaffold.js +144 -0
- package/dist/seeder.js +363 -0
- package/dist/sql-helpers.js +24 -0
- package/dist/table.d.ts +7 -0
- package/dist/table.js +26 -0
- package/dist/tools/setup.d.ts +1 -0
- package/dist/tools/setup.js +6 -0
- package/dist/transaction-context.js +62 -0
- package/dist/types.js +23 -0
- package/dist/unique-audit.js +174 -0
- package/dist/utils.js +163 -0
- package/dist/uuid-columns.js +68 -0
- package/dist/validators.js +122 -0
- package/package.json +11 -11
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { log } from "@stacksjs/logging";
|
|
4
|
+
import { env as envVars } from "@stacksjs/env";
|
|
5
|
+
import { db } from "./utils";
|
|
6
|
+
import { sqlHelpers } from "./sql-helpers";
|
|
7
|
+
function getDbDriver() {
|
|
8
|
+
return envVars.DB_CONNECTION || "sqlite";
|
|
9
|
+
}
|
|
10
|
+
export function usersEmailVerifiedAtSql(sql) {
|
|
11
|
+
return `ALTER TABLE users ADD COLUMN email_verified_at ${sql.nullableTimestamp}`;
|
|
12
|
+
}
|
|
13
|
+
export function usersPasswordChangedAtSql(sql) {
|
|
14
|
+
return `ALTER TABLE users ADD COLUMN password_changed_at ${sql.nullableTimestamp}`;
|
|
15
|
+
}
|
|
16
|
+
export function usersTwoFactorColumnsSql(sql) {
|
|
17
|
+
return [
|
|
18
|
+
"ALTER TABLE users ADD COLUMN two_factor_secret VARCHAR(255)",
|
|
19
|
+
`ALTER TABLE users ADD COLUMN two_factor_enabled BOOLEAN NOT NULL DEFAULT ${sql.boolFalse}`,
|
|
20
|
+
"ALTER TABLE users ADD COLUMN two_factor_last_used_step BIGINT"
|
|
21
|
+
];
|
|
22
|
+
}
|
|
23
|
+
export function usersStripeIdSql() {
|
|
24
|
+
return "ALTER TABLE users ADD COLUMN stripe_id VARCHAR(255)";
|
|
25
|
+
}
|
|
26
|
+
export async function ensureUsersAuthColumns(sql, options = {}) {
|
|
27
|
+
const alters = [
|
|
28
|
+
usersEmailVerifiedAtSql(sql),
|
|
29
|
+
usersPasswordChangedAtSql(sql),
|
|
30
|
+
...usersTwoFactorColumnsSql(sql),
|
|
31
|
+
usersStripeIdSql()
|
|
32
|
+
];
|
|
33
|
+
for (const alterSql of alters)
|
|
34
|
+
try {
|
|
35
|
+
await db.unsafe(alterSql).execute();
|
|
36
|
+
} catch {
|
|
37
|
+
if (options.verbose)
|
|
38
|
+
log.debug(`[auth-tables] Skipped (already applied or users missing): ${alterSql}`);
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
await db.unsafe("CREATE UNIQUE INDEX IF NOT EXISTS idx_users_stripe_id ON users(stripe_id)").execute();
|
|
42
|
+
} catch {
|
|
43
|
+
if (options.verbose)
|
|
44
|
+
log.debug("[auth-tables] Skipped users.stripe_id unique index (already applied or users missing)");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export async function migrateAuthTables(options = {}) {
|
|
48
|
+
const dbDriver = getDbDriver(), sql = sqlHelpers(dbDriver), { isPostgres, boolTrue, now, pkColumn, nullableTimestamp } = sql;
|
|
49
|
+
if (options.verbose)
|
|
50
|
+
log.info(`Creating auth tables for ${dbDriver}...`);
|
|
51
|
+
try {
|
|
52
|
+
if (options.verbose)
|
|
53
|
+
log.info("Creating oauth_clients table...");
|
|
54
|
+
await db.unsafe(`
|
|
55
|
+
CREATE TABLE IF NOT EXISTS oauth_clients (
|
|
56
|
+
${pkColumn},
|
|
57
|
+
name VARCHAR(255) NOT NULL,
|
|
58
|
+
secret VARCHAR(100),
|
|
59
|
+
provider VARCHAR(255),
|
|
60
|
+
redirect VARCHAR(2000) NOT NULL,
|
|
61
|
+
personal_access_client BOOLEAN NOT NULL DEFAULT ${sql.boolFalse},
|
|
62
|
+
password_client BOOLEAN NOT NULL DEFAULT ${sql.boolFalse},
|
|
63
|
+
revoked BOOLEAN NOT NULL DEFAULT ${sql.boolFalse},
|
|
64
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
65
|
+
updated_at ${nullableTimestamp}
|
|
66
|
+
)
|
|
67
|
+
`).execute();
|
|
68
|
+
if (options.verbose)
|
|
69
|
+
log.info("Creating oauth_access_tokens table...");
|
|
70
|
+
await db.unsafe(`
|
|
71
|
+
CREATE TABLE IF NOT EXISTS oauth_access_tokens (
|
|
72
|
+
${pkColumn},
|
|
73
|
+
user_id INTEGER NOT NULL,
|
|
74
|
+
oauth_client_id INTEGER NOT NULL,
|
|
75
|
+
token TEXT NOT NULL,
|
|
76
|
+
name VARCHAR(255),
|
|
77
|
+
scopes TEXT,
|
|
78
|
+
revoked BOOLEAN NOT NULL DEFAULT ${sql.boolFalse},
|
|
79
|
+
expires_at ${nullableTimestamp},
|
|
80
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
81
|
+
updated_at ${nullableTimestamp}
|
|
82
|
+
)
|
|
83
|
+
`).execute();
|
|
84
|
+
await createTokenIndex("idx_oauth_access_tokens_token", "oauth_access_tokens", "token");
|
|
85
|
+
if (options.verbose)
|
|
86
|
+
log.info("Creating oauth_refresh_tokens table...");
|
|
87
|
+
await db.unsafe(`
|
|
88
|
+
CREATE TABLE IF NOT EXISTS oauth_refresh_tokens (
|
|
89
|
+
${pkColumn},
|
|
90
|
+
access_token_id INTEGER NOT NULL,
|
|
91
|
+
token TEXT NOT NULL,
|
|
92
|
+
revoked BOOLEAN NOT NULL DEFAULT ${sql.boolFalse},
|
|
93
|
+
expires_at ${nullableTimestamp},
|
|
94
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
95
|
+
)
|
|
96
|
+
`).execute();
|
|
97
|
+
await createTokenIndex("idx_oauth_refresh_tokens_token", "oauth_refresh_tokens", "token");
|
|
98
|
+
if (options.verbose)
|
|
99
|
+
log.info("Creating password_resets table...");
|
|
100
|
+
await db.unsafe(`
|
|
101
|
+
CREATE TABLE IF NOT EXISTS password_resets (
|
|
102
|
+
${pkColumn},
|
|
103
|
+
email VARCHAR(255) NOT NULL,
|
|
104
|
+
token VARCHAR(255) NOT NULL,
|
|
105
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
106
|
+
)
|
|
107
|
+
`).execute();
|
|
108
|
+
try {
|
|
109
|
+
await db.unsafe(`
|
|
110
|
+
CREATE INDEX IF NOT EXISTS idx_password_resets_email ON password_resets(email)
|
|
111
|
+
`).execute();
|
|
112
|
+
} catch {}
|
|
113
|
+
if (options.verbose)
|
|
114
|
+
log.info("Creating passkeys table...");
|
|
115
|
+
await db.unsafe(`
|
|
116
|
+
CREATE TABLE IF NOT EXISTS passkeys (
|
|
117
|
+
id VARCHAR(255) PRIMARY KEY,
|
|
118
|
+
cred_public_key TEXT NOT NULL,
|
|
119
|
+
user_id INTEGER NOT NULL,
|
|
120
|
+
webauthn_user_id VARCHAR(255) NOT NULL,
|
|
121
|
+
counter INTEGER NOT NULL DEFAULT 0,
|
|
122
|
+
credential_type VARCHAR(50),
|
|
123
|
+
device_type VARCHAR(50),
|
|
124
|
+
backup_eligible BOOLEAN NOT NULL DEFAULT ${sql.boolFalse},
|
|
125
|
+
backup_status BOOLEAN NOT NULL DEFAULT ${sql.boolFalse},
|
|
126
|
+
transports TEXT,
|
|
127
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
128
|
+
last_used_at ${nullableTimestamp}
|
|
129
|
+
)
|
|
130
|
+
`).execute();
|
|
131
|
+
try {
|
|
132
|
+
await db.unsafe(`
|
|
133
|
+
CREATE INDEX IF NOT EXISTS idx_passkeys_user_id ON passkeys(user_id)
|
|
134
|
+
`).execute();
|
|
135
|
+
} catch {}
|
|
136
|
+
if (options.verbose)
|
|
137
|
+
log.info("Creating webauthn_challenges table...");
|
|
138
|
+
await db.unsafe(`
|
|
139
|
+
CREATE TABLE IF NOT EXISTS webauthn_challenges (
|
|
140
|
+
${pkColumn},
|
|
141
|
+
user_id INTEGER NOT NULL,
|
|
142
|
+
challenge TEXT NOT NULL,
|
|
143
|
+
purpose VARCHAR(20) NOT NULL,
|
|
144
|
+
expires_at TIMESTAMP NOT NULL,
|
|
145
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
146
|
+
)
|
|
147
|
+
`).execute();
|
|
148
|
+
try {
|
|
149
|
+
await db.unsafe(`
|
|
150
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_webauthn_challenges_user_purpose ON webauthn_challenges(user_id, purpose)
|
|
151
|
+
`).execute();
|
|
152
|
+
} catch {}
|
|
153
|
+
if (options.verbose)
|
|
154
|
+
log.info("Creating two_factor_challenges table...");
|
|
155
|
+
await db.unsafe(`
|
|
156
|
+
CREATE TABLE IF NOT EXISTS two_factor_challenges (
|
|
157
|
+
id VARCHAR(255) PRIMARY KEY,
|
|
158
|
+
user_id INTEGER NOT NULL,
|
|
159
|
+
expires_at TIMESTAMP NOT NULL,
|
|
160
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
161
|
+
)
|
|
162
|
+
`).execute();
|
|
163
|
+
try {
|
|
164
|
+
await db.unsafe(`
|
|
165
|
+
CREATE INDEX IF NOT EXISTS idx_two_factor_challenges_user_id ON two_factor_challenges(user_id)
|
|
166
|
+
`).execute();
|
|
167
|
+
} catch {}
|
|
168
|
+
if (options.verbose)
|
|
169
|
+
log.info("Creating two_factor_pending_secrets table...");
|
|
170
|
+
await db.unsafe(`
|
|
171
|
+
CREATE TABLE IF NOT EXISTS two_factor_pending_secrets (
|
|
172
|
+
user_id INTEGER PRIMARY KEY,
|
|
173
|
+
secret VARCHAR(255) NOT NULL,
|
|
174
|
+
expires_at TIMESTAMP NOT NULL,
|
|
175
|
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
176
|
+
)
|
|
177
|
+
`).execute();
|
|
178
|
+
if (options.verbose)
|
|
179
|
+
log.info("Ensuring users auth columns exist (users may not exist yet on a fresh install \u2014 see ensureUsersAuthColumns)...");
|
|
180
|
+
await ensureUsersAuthColumns(sql, options);
|
|
181
|
+
if (options.verbose)
|
|
182
|
+
log.info("Ensuring personal access client exists...");
|
|
183
|
+
if ((await db.unsafe(`
|
|
184
|
+
SELECT id FROM oauth_clients WHERE personal_access_client = ${boolTrue} LIMIT 1
|
|
185
|
+
`).execute())?.length === 0) {
|
|
186
|
+
const secret = randomBytes(40).toString("hex");
|
|
187
|
+
if (isPostgres)
|
|
188
|
+
await db.unsafe(`
|
|
189
|
+
INSERT INTO oauth_clients (name, secret, provider, redirect, personal_access_client, password_client, revoked, created_at)
|
|
190
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
|
|
191
|
+
`, ["Personal Access Client", secret, "local", "http://localhost", !0, !1, !1]).execute();
|
|
192
|
+
else
|
|
193
|
+
await db.unsafe(`
|
|
194
|
+
INSERT INTO oauth_clients (name, secret, provider, redirect, personal_access_client, password_client, revoked, created_at)
|
|
195
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ${now})
|
|
196
|
+
`, ["Personal Access Client", secret, "local", "http://localhost", 1, 0, 0]).execute();
|
|
197
|
+
if (options.verbose)
|
|
198
|
+
log.success("Personal access client created");
|
|
199
|
+
}
|
|
200
|
+
log.debug("Auth tables migrated successfully");
|
|
201
|
+
return { success: !0 };
|
|
202
|
+
} catch (error) {
|
|
203
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
204
|
+
log.error("Failed to migrate auth tables:", errorMessage);
|
|
205
|
+
return { success: !1, error: errorMessage };
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
async function createTokenIndex(indexName, tableName, column) {
|
|
209
|
+
try {
|
|
210
|
+
await db.unsafe(`
|
|
211
|
+
CREATE INDEX IF NOT EXISTS ${indexName} ON ${tableName}(${column}(255))
|
|
212
|
+
`).execute();
|
|
213
|
+
} catch {
|
|
214
|
+
try {
|
|
215
|
+
await db.unsafe(`
|
|
216
|
+
CREATE INDEX IF NOT EXISTS ${indexName} ON ${tableName}(${column})
|
|
217
|
+
`).execute();
|
|
218
|
+
} catch {}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { log } from "@stacksjs/logging";
|
|
2
|
+
import { path } from "@stacksjs/path";
|
|
3
|
+
import { fs } from "@stacksjs/storage";
|
|
4
|
+
|
|
5
|
+
export class Seeder {
|
|
6
|
+
dependencies;
|
|
7
|
+
async call(other) {
|
|
8
|
+
await new other().run();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function topoSortSeeders(seeders) {
|
|
12
|
+
const known = new Set(seeders.map((s) => s.name)), effectiveDeps = new Map;
|
|
13
|
+
for (const s of seeders) {
|
|
14
|
+
const deps = new Set;
|
|
15
|
+
for (const dep of s.dependencies ?? []) {
|
|
16
|
+
if (dep === s.name)
|
|
17
|
+
continue;
|
|
18
|
+
if (!known.has(dep)) {
|
|
19
|
+
log.warn(`[seeder] ${s.name} depends on '${dep}' but no seeder by that name was discovered \u2014 ignoring`);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
deps.add(dep);
|
|
23
|
+
}
|
|
24
|
+
effectiveDeps.set(s.name, deps);
|
|
25
|
+
}
|
|
26
|
+
const indegree = new Map, successors = new Map;
|
|
27
|
+
for (const s of seeders) {
|
|
28
|
+
indegree.set(s.name, effectiveDeps.get(s.name).size);
|
|
29
|
+
successors.set(s.name, new Set);
|
|
30
|
+
}
|
|
31
|
+
for (const s of seeders)
|
|
32
|
+
for (const dep of effectiveDeps.get(s.name))
|
|
33
|
+
successors.get(dep).add(s.name);
|
|
34
|
+
const ready = seeders.filter((s) => indegree.get(s.name) === 0).map((s) => s.name).sort(), result = [];
|
|
35
|
+
while (ready.length > 0) {
|
|
36
|
+
const next = ready.shift();
|
|
37
|
+
result.push(next);
|
|
38
|
+
const newlyReady = [];
|
|
39
|
+
for (const succ of successors.get(next)) {
|
|
40
|
+
const left = (indegree.get(succ) ?? 0) - 1;
|
|
41
|
+
indegree.set(succ, left);
|
|
42
|
+
if (left === 0)
|
|
43
|
+
newlyReady.push(succ);
|
|
44
|
+
}
|
|
45
|
+
if (newlyReady.length > 0) {
|
|
46
|
+
ready.push(...newlyReady);
|
|
47
|
+
ready.sort();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (result.length !== seeders.length) {
|
|
51
|
+
const unresolved = seeders.map((s) => s.name).filter((n) => !result.includes(n));
|
|
52
|
+
throw Error(`[seeder] Cycle in seeder \`dependencies\` among: ${unresolved.join(", ")}`);
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
export async function runClassSeeders(options = {}) {
|
|
57
|
+
try {
|
|
58
|
+
const { injectGlobalAutoImports } = await import("@stacksjs/server");
|
|
59
|
+
await injectGlobalAutoImports();
|
|
60
|
+
} catch {}
|
|
61
|
+
const dir = options.dir ?? path.projectPath("database/seeders"), ran = [], skipped = [];
|
|
62
|
+
if (!fs.existsSync(dir)) {
|
|
63
|
+
log.info(`[seeder] No class seeders directory at ${dir}`);
|
|
64
|
+
return { ran, skipped };
|
|
65
|
+
}
|
|
66
|
+
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".ts") && !f.startsWith("_")).sort(), loaded = [];
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
const className = file.replace(/\.ts$/, "");
|
|
69
|
+
try {
|
|
70
|
+
const mod = await import(`${dir}/${file}`), Klass = mod.default ?? mod[className];
|
|
71
|
+
if (!Klass) {
|
|
72
|
+
log.warn(`[seeder] ${file} has no default export`);
|
|
73
|
+
skipped.push(className);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
const inst = new Klass;
|
|
77
|
+
if (typeof inst.run !== "function") {
|
|
78
|
+
log.warn(`[seeder] ${className} does not implement run()`);
|
|
79
|
+
skipped.push(className);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
loaded.push({ className, inst });
|
|
83
|
+
} catch (err) {
|
|
84
|
+
log.error(`[seeder] ${className} failed to load:`, err);
|
|
85
|
+
skipped.push(className);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const declaresDeps = loaded.some((l) => (l.inst.dependencies?.length ?? 0) > 0);
|
|
89
|
+
let order;
|
|
90
|
+
if (declaresDeps)
|
|
91
|
+
try {
|
|
92
|
+
order = topoSortSeeders(loaded.map((l) => ({ name: l.className, dependencies: l.inst.dependencies })));
|
|
93
|
+
} catch (err) {
|
|
94
|
+
log.error(err instanceof Error ? err.message : String(err));
|
|
95
|
+
return { ran, skipped: [...skipped, ...loaded.map((l) => l.className)] };
|
|
96
|
+
}
|
|
97
|
+
else
|
|
98
|
+
order = loaded.map((l) => l.className);
|
|
99
|
+
const byName = new Map(loaded.map((l) => [l.className, l.inst]));
|
|
100
|
+
for (const className of order) {
|
|
101
|
+
if (options.class && className !== options.class) {
|
|
102
|
+
skipped.push(className);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const inst = byName.get(className);
|
|
106
|
+
try {
|
|
107
|
+
log.info(`[seeder] Running ${className}\u2026`);
|
|
108
|
+
await inst.run();
|
|
109
|
+
ran.push(className);
|
|
110
|
+
} catch (err) {
|
|
111
|
+
log.error(`[seeder] ${className} failed:`, err);
|
|
112
|
+
skipped.push(className);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { ran, skipped };
|
|
116
|
+
}
|
package/dist/column.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare interface Options {
|
|
2
|
+
notNull?: boolean
|
|
3
|
+
default?: any
|
|
4
|
+
primaryKey?: boolean
|
|
5
|
+
autoIncrement?: boolean
|
|
6
|
+
}
|
|
7
|
+
declare type ColumnType = 'integer' | 'varchar' | 'timestamp' | `varchar(${number})`;
|
|
8
|
+
export declare class Column {
|
|
9
|
+
public name: string;
|
|
10
|
+
public type: ColumnType;
|
|
11
|
+
public options?: Options;
|
|
12
|
+
constructor(name: string, type: ColumnType, options?: Options);
|
|
13
|
+
notNullable(): this;
|
|
14
|
+
defaultTo(value: any): this;
|
|
15
|
+
primary(): this;
|
|
16
|
+
autoIncrement(): this;
|
|
17
|
+
}
|
package/dist/column.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class Column {
|
|
2
|
+
name;
|
|
3
|
+
type;
|
|
4
|
+
options;
|
|
5
|
+
constructor(name, type, options = {}) {
|
|
6
|
+
this.name = name;
|
|
7
|
+
this.type = type;
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
notNullable() {
|
|
11
|
+
this.options.notNull = !0;
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
defaultTo(value) {
|
|
15
|
+
this.options.default = value;
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
primary() {
|
|
19
|
+
this.options.primaryKey = !0;
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
autoIncrement() {
|
|
23
|
+
this.options.autoIncrement = !0;
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { log } from "@stacksjs/logging";
|
|
2
|
+
import { database } from "@stacksjs/config";
|
|
3
|
+
import { path } from "@stacksjs/path";
|
|
4
|
+
import { hasTableBeenMigrated } from "../drivers/helpers";
|
|
5
|
+
export async function createModelAuditsTable() {
|
|
6
|
+
if (!["sqlite", "mysql"].includes(getDriver()))
|
|
7
|
+
return;
|
|
8
|
+
if (await hasTableBeenMigrated("model_audits"))
|
|
9
|
+
return;
|
|
10
|
+
let migrationContent = `import type { Database } from '@stacksjs/database'
|
|
11
|
+
import { sql } from '@stacksjs/database'
|
|
12
|
+
|
|
13
|
+
`;
|
|
14
|
+
migrationContent += `export async function up(db: Database<any>) {
|
|
15
|
+
`;
|
|
16
|
+
migrationContent += ` await db.schema
|
|
17
|
+
`;
|
|
18
|
+
migrationContent += ` .createTable('model_audits')
|
|
19
|
+
`;
|
|
20
|
+
migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
|
|
21
|
+
`;
|
|
22
|
+
migrationContent += ` .addColumn('auditable_type', 'varchar(255)', col => col.notNull())
|
|
23
|
+
`;
|
|
24
|
+
migrationContent += ` .addColumn('auditable_id', 'varchar(255)', col => col.notNull())
|
|
25
|
+
`;
|
|
26
|
+
migrationContent += ` .addColumn('event', 'varchar(32)', col => col.notNull())
|
|
27
|
+
`;
|
|
28
|
+
migrationContent += ` .addColumn('old_values', 'text')
|
|
29
|
+
`;
|
|
30
|
+
migrationContent += ` .addColumn('new_values', 'text')
|
|
31
|
+
`;
|
|
32
|
+
migrationContent += ` .addColumn('user_id', 'integer')
|
|
33
|
+
`;
|
|
34
|
+
migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))
|
|
35
|
+
`;
|
|
36
|
+
migrationContent += ` .execute()
|
|
37
|
+
|
|
38
|
+
`;
|
|
39
|
+
migrationContent += ` await db.schema
|
|
40
|
+
`;
|
|
41
|
+
migrationContent += ` .createIndex('idx_model_audits_lookup')
|
|
42
|
+
`;
|
|
43
|
+
migrationContent += ` .on('model_audits')
|
|
44
|
+
`;
|
|
45
|
+
migrationContent += ` .columns(['auditable_type', 'auditable_id', 'created_at'])
|
|
46
|
+
`;
|
|
47
|
+
migrationContent += ` .execute()
|
|
48
|
+
`;
|
|
49
|
+
migrationContent += `}
|
|
50
|
+
`;
|
|
51
|
+
const migrationFileName = `${new Date().getTime().toString()}-create-model-audits-table.ts`, migrationFilePath = path.userMigrationsPath(migrationFileName);
|
|
52
|
+
await Bun.write(migrationFilePath, migrationContent);
|
|
53
|
+
log.success("Created model_audits table migration");
|
|
54
|
+
}
|
|
55
|
+
function getDriver() {
|
|
56
|
+
return database.default || "";
|
|
57
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { log } from "@stacksjs/logging";
|
|
2
|
+
import { database } from "@stacksjs/config";
|
|
3
|
+
import { path } from "@stacksjs/path";
|
|
4
|
+
import { hasTableBeenMigrated } from "../drivers/helpers";
|
|
5
|
+
export async function createErrorsTable() {
|
|
6
|
+
if (["sqlite", "mysql"].includes(getDriver())) {
|
|
7
|
+
if (await hasTableBeenMigrated("errors"))
|
|
8
|
+
return;
|
|
9
|
+
let migrationContent = `import type { Database } from '@stacksjs/database'
|
|
10
|
+
import { sql } from '@stacksjs/database'
|
|
11
|
+
|
|
12
|
+
`;
|
|
13
|
+
migrationContent += `export async function up(db: Database<any>) {
|
|
14
|
+
`;
|
|
15
|
+
migrationContent += ` await db.schema
|
|
16
|
+
`;
|
|
17
|
+
migrationContent += ` .createTable('errors')
|
|
18
|
+
`;
|
|
19
|
+
migrationContent += ` .addColumn('id', 'integer', col => col.primaryKey().autoIncrement())
|
|
20
|
+
`;
|
|
21
|
+
migrationContent += ` .addColumn('type', 'varchar(255)', col => col.notNull())
|
|
22
|
+
`;
|
|
23
|
+
migrationContent += ` .addColumn('message', 'text', col => col.notNull())
|
|
24
|
+
`;
|
|
25
|
+
migrationContent += ` .addColumn('stack', 'text')
|
|
26
|
+
`;
|
|
27
|
+
migrationContent += ` .addColumn('status', 'integer', col => col.notNull().defaultTo(0))
|
|
28
|
+
`;
|
|
29
|
+
migrationContent += ` .addColumn('user_id', 'integer')
|
|
30
|
+
`;
|
|
31
|
+
migrationContent += ` .addColumn('additional_info', 'text')
|
|
32
|
+
`;
|
|
33
|
+
migrationContent += ` .addColumn('created_at', 'timestamp', col => col.notNull().defaultTo(sql.raw('CURRENT_TIMESTAMP')))
|
|
34
|
+
`;
|
|
35
|
+
migrationContent += ` .addColumn('updated_at', 'timestamp')
|
|
36
|
+
`;
|
|
37
|
+
migrationContent += ` .execute()
|
|
38
|
+
`;
|
|
39
|
+
migrationContent += `}
|
|
40
|
+
`;
|
|
41
|
+
const migrationFileName = `${new Date().getTime().toString()}-create-errors-table.ts`, migrationFilePath = path.userMigrationsPath(migrationFileName);
|
|
42
|
+
await Bun.write(migrationFilePath, migrationContent);
|
|
43
|
+
log.success("Created errors table");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function getDriver() {
|
|
47
|
+
return database.default || "";
|
|
48
|
+
}
|