Merge pull request #21 from rome-xi/contrib/mailflare-19
fix(setup): keep fresh D1 bootstrap schema in sync with migrations 0013/0021/0022
This commit is contained in:
2 files changed
+77
-1
No files matched your search
@@ -12,6 +12,7 @@ const MIGRATION_NAMES = [
|
||||
"0010_hard_squirrel_girl.sql",
|
||||
"0011_add_folder_colors.sql",
|
||||
"0012_add_app_settings.sql",
|
||||
"0013_add_license_settings.sql",
|
||||
"0014_add_account_permissions.sql",
|
||||
"0015_add_forwarding_email.sql",
|
||||
"0016_add_message_snooze.sql",
|
||||
@@ -19,6 +20,8 @@ const MIGRATION_NAMES = [
|
||||
"0018_add_mailbox_domain_aliases.sql",
|
||||
"0019_merge_message_bodies.sql",
|
||||
"0020_add_calendar_templates_schedule.sql",
|
||||
"0021_add_mailbox_signature.sql",
|
||||
"0022_add_mailbox_auto_reply.sql",
|
||||
];
|
||||
|
||||
const INITIAL_SCHEMA_SQL = `
|
||||
@@ -27,8 +30,11 @@ CREATE INDEX IF NOT EXISTS users_created_by_idx ON users(created_by_user_id);
|
||||
CREATE TABLE IF NOT EXISTS domains (id text PRIMARY KEY NOT NULL, user_id text NOT NULL REFERENCES users(id) ON DELETE cascade, hostname text NOT NULL, zone_id text NOT NULL, status text DEFAULT 'pending' NOT NULL, routing_status text, sending_subdomain_tag text, sending_enabled integer DEFAULT false NOT NULL, routing_enabled integer DEFAULT false NOT NULL, created_at integer NOT NULL);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS domains_hostname_idx ON domains(hostname);
|
||||
CREATE INDEX IF NOT EXISTS domains_user_idx ON domains(user_id);
|
||||
CREATE TABLE IF NOT EXISTS mailboxes (id text PRIMARY KEY NOT NULL, user_id text NOT NULL REFERENCES users(id) ON DELETE cascade, domain_id text NOT NULL REFERENCES domains(id) ON DELETE cascade, local_part text NOT NULL, display_name text, avatar_key text, type text DEFAULT 'personal' NOT NULL, use_all_domains integer DEFAULT true NOT NULL, disabled integer DEFAULT false NOT NULL, created_at integer NOT NULL);
|
||||
CREATE TABLE IF NOT EXISTS mailboxes (id text PRIMARY KEY NOT NULL, user_id text NOT NULL REFERENCES users(id) ON DELETE cascade, domain_id text NOT NULL REFERENCES domains(id) ON DELETE cascade, local_part text NOT NULL, display_name text, signature text, auto_reply_enabled integer DEFAULT false NOT NULL, auto_reply_subject text DEFAULT 'Out of office' NOT NULL, auto_reply_body text DEFAULT '' NOT NULL, avatar_key text, type text DEFAULT 'personal' NOT NULL, use_all_domains integer DEFAULT true NOT NULL, disabled integer DEFAULT false NOT NULL, created_at integer NOT NULL);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS mailboxes_address_idx ON mailboxes(domain_id, local_part);
|
||||
CREATE TABLE IF NOT EXISTS auto_reply_deliveries (id text PRIMARY KEY NOT NULL, mailbox_id text NOT NULL REFERENCES mailboxes(id) ON DELETE cascade, recipient text NOT NULL, sent_at integer NOT NULL);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS auto_reply_deliveries_mailbox_recipient_idx ON auto_reply_deliveries(mailbox_id, recipient);
|
||||
CREATE INDEX IF NOT EXISTS auto_reply_deliveries_sent_idx ON auto_reply_deliveries(sent_at);
|
||||
CREATE TABLE IF NOT EXISTS mailbox_access (id text PRIMARY KEY NOT NULL, mailbox_id text NOT NULL REFERENCES mailboxes(id) ON DELETE cascade, user_id text NOT NULL REFERENCES users(id) ON DELETE cascade, permission text DEFAULT 'read_only' NOT NULL, created_by_user_id text REFERENCES users(id) ON DELETE set null, created_at integer NOT NULL);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS mailbox_access_mailbox_user_idx ON mailbox_access(mailbox_id, user_id);
|
||||
CREATE INDEX IF NOT EXISTS mailbox_access_user_idx ON mailbox_access(user_id);
|
||||
@@ -67,6 +73,8 @@ CREATE INDEX IF NOT EXISTS backups_created_idx ON backups(created_at);
|
||||
CREATE INDEX IF NOT EXISTS backups_status_idx ON backups(status);
|
||||
CREATE TABLE IF NOT EXISTS app_settings (id text PRIMARY KEY NOT NULL, app_name text DEFAULT 'Mailflare' NOT NULL, icon_key text, updated_at integer NOT NULL);
|
||||
INSERT OR IGNORE INTO app_settings (id, app_name, updated_at) VALUES ('default', 'Mailflare', unixepoch());
|
||||
CREATE TABLE IF NOT EXISTS license_settings (id text PRIMARY KEY NOT NULL, instance_id text NOT NULL, instance_url text, license_key_hash text, plan text DEFAULT 'community' NOT NULL, state text DEFAULT 'inactive' NOT NULL, features text DEFAULT '[]' NOT NULL, activated_at integer, validated_at integer, updated_at integer NOT NULL);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS license_settings_instance_id_unique ON license_settings(instance_id);
|
||||
CREATE TABLE IF NOT EXISTS d1_migrations (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL);
|
||||
`;
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import test from "node:test";
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const src = readFileSync(join(root, "src/lib/setup/migration.ts"), "utf8");
|
||||
|
||||
function migrationNames() {
|
||||
const match = src.match(/const MIGRATION_NAMES = \[([\s\S]*?)\];/);
|
||||
assert.ok(match, "MIGRATION_NAMES array not found");
|
||||
return [...match[1].matchAll(/"([^"]+)"/g)].map((item) => item[1]);
|
||||
}
|
||||
|
||||
function initialSchemaSql() {
|
||||
const match = src.match(/const INITIAL_SCHEMA_SQL = `([\s\S]*?)`;/);
|
||||
assert.ok(match, "INITIAL_SCHEMA_SQL not found");
|
||||
return match[1];
|
||||
}
|
||||
|
||||
test("bootstrap records migrations 0013, 0021, and 0022 so later deploys do not re-apply them", () => {
|
||||
const names = migrationNames();
|
||||
for (const name of [
|
||||
"0013_add_license_settings.sql",
|
||||
"0021_add_mailbox_signature.sql",
|
||||
"0022_add_mailbox_auto_reply.sql",
|
||||
]) {
|
||||
assert.ok(names.includes(name), `MIGRATION_NAMES is missing ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("fresh bootstrap schema accepts the current Drizzle mailbox and license inserts", () => {
|
||||
const sql = initialSchemaSql();
|
||||
const mailboxCreate = sql.match(/CREATE TABLE IF NOT EXISTS mailboxes \(([\s\S]*?)\);/);
|
||||
assert.ok(mailboxCreate, "mailboxes CREATE TABLE not found");
|
||||
assert.match(mailboxCreate[1], /\bsignature\b/);
|
||||
assert.match(mailboxCreate[1], /\bauto_reply_enabled\b/);
|
||||
assert.match(mailboxCreate[1], /\bauto_reply_subject\b/);
|
||||
assert.match(mailboxCreate[1], /\bauto_reply_body\b/);
|
||||
assert.match(sql, /CREATE TABLE IF NOT EXISTS license_settings \(/);
|
||||
assert.match(sql, /CREATE TABLE IF NOT EXISTS auto_reply_deliveries \(/);
|
||||
|
||||
const py = `
|
||||
import sqlite3, sys
|
||||
sql = sys.stdin.read()
|
||||
db = sqlite3.connect(":memory:")
|
||||
for stmt in sql.split(";"):
|
||||
s = stmt.strip()
|
||||
if s:
|
||||
db.execute(s)
|
||||
db.execute("INSERT INTO users (id,email,password_hash,name,created_at) VALUES ('u','a@b.c','x','n',1)")
|
||||
db.execute("INSERT INTO domains (id,user_id,hostname,zone_id,created_at) VALUES ('d','u','ex.com','z',1)")
|
||||
db.execute("""
|
||||
INSERT INTO mailboxes (
|
||||
id, user_id, domain_id, local_part, display_name,
|
||||
signature, auto_reply_enabled, auto_reply_subject, auto_reply_body, created_at
|
||||
) VALUES ('m','u','d','admin','admin','sig',0,'Out of office','',1)
|
||||
""")
|
||||
db.execute("INSERT INTO license_settings (id, instance_id, updated_at) VALUES ('default','inst',1)")
|
||||
db.execute("INSERT INTO auto_reply_deliveries (id, mailbox_id, recipient, sent_at) VALUES ('ar','m','x@y.z',1)")
|
||||
print("ok")
|
||||
`;
|
||||
const result = spawnSync("python3", ["-c", py], { input: sql, encoding: "utf8" });
|
||||
assert.equal(result.status, 0, result.stderr + result.stdout);
|
||||
assert.match(result.stdout, /^ok$/m);
|
||||
});
|
||||
Reference in new issue
Block a user