Refactor project to transition from Rust backend to Node.js with Fastify; update Dockerfile and Docker configurations for new build process; enhance local development instructions in CONTRIBUTING.md; implement health checks in Docker Compose; update pnpm-lock.yaml with new dependencies for API and shared packages; revise README.md to reflect new stack and development setup.
Build, Test, and Push CFDM Docker Image / test (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / create-release (push) Has been cancelled
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been cancelled

This commit is contained in:
Denozordec committed 2026-06-19 12:06:32 +07:00
1 parent 0a0a1a92f1
commit d11414666f
2065 files changed
+15782 -16699

No files matched your search

+65
View File
@@ -0,0 +1,65 @@
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { readFileSync, readdirSync } from "node:fs";
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { schema } from "./schema.js";
export type Sqlite = Database.Database;
export type Db = ReturnType<typeof drizzle<typeof schema>>;
const __dirname = dirname(fileURLToPath(import.meta.url));
export function resolveDatabasePath(databaseUrl: string): string {
const url = databaseUrl.startsWith("sqlite:")
? databaseUrl.slice("sqlite:".length)
: databaseUrl;
return url;
}
export function createDb(databaseUrl: string): { db: Db; sqlite: Sqlite } {
const path = resolveDatabasePath(databaseUrl);
const sqlite = new Database(path);
sqlite.pragma("journal_mode = WAL");
sqlite.pragma("synchronous = NORMAL");
sqlite.pragma("foreign_keys = ON");
const db = drizzle(sqlite, { schema });
return { db, sqlite };
}
export function createMemoryDb(): { db: Db; sqlite: Sqlite } {
const sqlite = new Database(":memory:");
sqlite.pragma("foreign_keys = ON");
const db = drizzle(sqlite, { schema });
return { db, sqlite };
}
export function runMigrations(sqlite: Sqlite): void {
const migrationsDir = join(__dirname, "..", "migrations");
const files = readdirSync(migrationsDir)
.filter((f) => f.endsWith(".sql"))
.sort();
sqlite.exec(
`CREATE TABLE IF NOT EXISTS _migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
)`,
);
for (const file of files) {
const applied = sqlite
.prepare("SELECT 1 FROM _migrations WHERE name = ?")
.get(file);
if (applied) continue;
const sql = readFileSync(join(migrationsDir, file), "utf-8");
sqlite.exec(sql);
sqlite.prepare("INSERT INTO _migrations (name) VALUES (?)").run(file);
}
}
export function healthCheck(sqlite: Sqlite): void {
sqlite.prepare("SELECT 1").get();
}
+13
View File
@@ -0,0 +1,13 @@
export class NotFoundError extends Error {
constructor(message: string) {
super(message);
this.name = "NotFoundError";
}
}
export class ConflictError extends Error {
constructor(message: string) {
super(message);
this.name = "ConflictError";
}
}
+5
View File
@@ -0,0 +1,5 @@
export * from "./schema.js";
export * from "./client.js";
export * from "./errors.js";
export * as repos from "./repos.js";
export type { DnsListFilter } from "./repos.js";
File diff suppressed because it is too large. Load diff
+225
View File
@@ -0,0 +1,225 @@
import { sql } from "drizzle-orm";
import {
integer,
primaryKey,
sqliteTable,
text,
} from "drizzle-orm/sqlite-core";
export const groups = sqliteTable("groups", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
slug: text("slug").notNull().unique(),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
updated_at: text("updated_at")
.notNull()
.default(sql`datetime('now')`),
});
export const services = sqliteTable("services", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
slug: text("slug").notNull().unique(),
service_group_id: integer("service_group_id").references(
() => serviceGroups.id,
{ onDelete: "set null" },
),
subdomain: text("subdomain"),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(false),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
updated_at: text("updated_at")
.notNull()
.default(sql`datetime('now')`),
});
export const serviceGroups = sqliteTable("service_groups", {
id: integer("id").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
type: text("type").notNull().default("custom"),
icon: text("icon"),
domain: text("domain"),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
updated_at: text("updated_at")
.notNull()
.default(sql`datetime('now')`),
});
export const domains = sqliteTable("domains", {
id: integer("id").primaryKey({ autoIncrement: true }),
group_id: integer("group_id").references(() => groups.id, {
onDelete: "set null",
}),
zone_name: text("zone_name").notNull().unique(),
cf_zone_id: text("cf_zone_id").notNull(),
status: text("status").notNull().default("active"),
last_synced_at: text("last_synced_at"),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
updated_at: text("updated_at")
.notNull()
.default(sql`datetime('now')`),
});
export const subdomains = sqliteTable("subdomains", {
id: integer("id").primaryKey({ autoIncrement: true }),
domain_id: integer("domain_id")
.notNull()
.references(() => domains.id, { onDelete: "cascade" }),
name: text("name").notNull(),
fqdn: text("fqdn").notNull(),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
updated_at: text("updated_at")
.notNull()
.default(sql`datetime('now')`),
});
export const dnsRecords = sqliteTable("dns_records", {
id: integer("id").primaryKey({ autoIncrement: true }),
domain_id: integer("domain_id")
.notNull()
.references(() => domains.id, { onDelete: "cascade" }),
cf_record_id: text("cf_record_id"),
record_type: text("record_type").notNull(),
name: text("name").notNull(),
content: text("content").notNull(),
ttl: integer("ttl").notNull().default(1),
proxied: integer("proxied", { mode: "boolean" }).notNull().default(false),
priority: integer("priority"),
sync_status: text("sync_status").notNull().default("pending_push"),
origin: text("origin").notNull().default("local"),
last_error: text("last_error"),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
updated_at: text("updated_at")
.notNull()
.default(sql`datetime('now')`),
});
export const serviceBindings = sqliteTable("service_bindings", {
id: integer("id").primaryKey({ autoIncrement: true }),
domain_id: integer("domain_id")
.notNull()
.references(() => domains.id, { onDelete: "cascade" }),
service_id: integer("service_id")
.notNull()
.references(() => services.id, { onDelete: "cascade" }),
hostname: text("hostname").notNull().default("@"),
dns_record_id: integer("dns_record_id").references(() => dnsRecords.id, {
onDelete: "set null",
}),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
updated_at: text("updated_at")
.notNull()
.default(sql`datetime('now')`),
});
export const serviceIps = sqliteTable("service_ips", {
id: integer("id").primaryKey({ autoIncrement: true }),
service_id: integer("service_id")
.notNull()
.references(() => services.id, { onDelete: "cascade" }),
ip: text("ip").notNull(),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
});
export const serviceBindingRecords = sqliteTable(
"service_binding_records",
{
binding_id: integer("binding_id")
.notNull()
.references(() => serviceBindings.id, { onDelete: "cascade" }),
dns_record_id: integer("dns_record_id")
.notNull()
.references(() => dnsRecords.id, { onDelete: "cascade" }),
},
(t) => [primaryKey({ columns: [t.binding_id, t.dns_record_id] })],
);
export const serviceBindingIps = sqliteTable(
"service_binding_ips",
{
binding_id: integer("binding_id")
.notNull()
.references(() => serviceBindings.id, { onDelete: "cascade" }),
ip: text("ip").notNull(),
},
(t) => [primaryKey({ columns: [t.binding_id, t.ip] })],
);
export const serviceGroupDnsRecords = sqliteTable(
"service_group_dns_records",
{
group_id: integer("group_id")
.notNull()
.references(() => serviceGroups.id, { onDelete: "cascade" }),
dns_record_id: integer("dns_record_id")
.notNull()
.references(() => dnsRecords.id, { onDelete: "cascade" }),
},
(t) => [primaryKey({ columns: [t.group_id, t.dns_record_id] })],
);
export const certificates = sqliteTable("certificates", {
id: integer("id").primaryKey({ autoIncrement: true }),
domain_id: integer("domain_id")
.notNull()
.references(() => domains.id, { onDelete: "cascade" }),
subdomain_id: integer("subdomain_id").references(() => subdomains.id, {
onDelete: "set null",
}),
hostname: text("hostname").notNull().unique(),
expires_at: text("expires_at"),
last_checked_at: text("last_checked_at"),
last_error: text("last_error"),
status: text("status").notNull().default("unknown"),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
updated_at: text("updated_at")
.notNull()
.default(sql`datetime('now')`),
});
export const syncJobs = sqliteTable("sync_jobs", {
id: text("id").primaryKey(),
status: text("status").notNull().default("pending"),
domain_id: integer("domain_id").references(() => domains.id, {
onDelete: "set null",
}),
message: text("message"),
created_at: text("created_at")
.notNull()
.default(sql`datetime('now')`),
finished_at: text("finished_at"),
});
export const schema = {
groups,
services,
serviceGroups,
domains,
subdomains,
dnsRecords,
serviceBindings,
serviceIps,
serviceBindingRecords,
serviceBindingIps,
serviceGroupDnsRecords,
certificates,
syncJobs,
};