jetzig/src/commands/database.zig
Bob Farrell 320c2706ae WIP
2024-11-09 17:13:32 +00:00

128 lines
4.7 KiB
Zig

const std = @import("std");
const build_options = @import("build_options");
const jetquery = @import("jetquery");
const jetzig = @import("jetzig");
const Migrate = @import("jetquery_migrate").Migrate;
const MigrateSchema = @import("jetquery_migrate").MigrateSchema;
const Schema = @import("Schema");
const confirm_drop_env = "JETZIG_DROP_PRODUCTION_DATABASE";
const production_drop_failure_message = "To drop a production database, " ++
"set `" ++ confirm_drop_env ++ "={s}`. Exiting.";
const environment = jetzig.build_options.environment;
const config = @field(jetquery.config.database, @tagName(environment));
const Action = enum { migrate, rollback, create, drop, schema };
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(gpa.deinit() == .ok);
const gpa_allocator = gpa.allocator();
var arena = std.heap.ArenaAllocator.init(gpa_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const args = try std.process.argsAlloc(allocator);
if (args.len < 2) return error.JetzigMissingDatabaseArgument;
const map = std.StaticStringMap(Action).initComptime(.{
.{ "migrate", .migrate },
.{ "rollback", .rollback },
.{ "create", .create },
.{ "drop", .drop },
.{ "schema", .schema },
});
const action = map.get(args[1]) orelse return error.JetzigUnrecognizedDatabaseArgument;
switch (action) {
.migrate => {
var repo = try migrationsRepo(action, allocator);
defer repo.deinit();
try Migrate(config.adapter).init(&repo).migrate();
},
.rollback => {
var repo = try migrationsRepo(action, allocator);
defer repo.deinit();
try Migrate(config.adapter).init(&repo).rollback();
},
.create => {
var repo = try migrationsRepo(action, allocator);
defer repo.deinit();
try repo.createDatabase(config.database, .{});
},
.drop => {
if (environment == .production) {
const confirm = std.process.getEnvVarOwned(allocator, confirm_drop_env) catch |err| {
switch (err) {
error.EnvironmentVariableNotFound => {
std.log.err(production_drop_failure_message, .{config.database});
std.process.exit(1);
},
else => return err,
}
};
if (std.mem.eql(u8, confirm, config.database)) {
var repo = try migrationsRepo(action, allocator);
defer repo.deinit();
try repo.dropDatabase(config.database, .{});
} else {
std.log.err(production_drop_failure_message, .{config.database});
std.process.exit(1);
}
} else {
var repo = try migrationsRepo(action, allocator);
defer repo.deinit();
try repo.dropDatabase(config.database, .{});
}
},
.schema => {
var cwd = try jetzig.util.detectJetzigProjectDir();
defer cwd.close();
const Repo = jetquery.Repo(config.adapter, Schema);
var repo = try Repo.loadConfig(
allocator,
std.enums.nameCast(jetquery.Environment, environment),
.{ .context = .migration },
);
const reflect = @import("jetquery_reflect").Reflect(config.adapter, Schema).init(
allocator,
&repo,
.{
.import_jetquery =
\\@import("jetzig").jetquery
,
},
);
const schema = try reflect.generateSchema();
const path = try cwd.realpathAlloc(
allocator,
try std.fs.path.join(allocator, &.{ "src", "app", "database", "Schema.zig" }),
);
try jetzig.util.createFile(path, schema);
std.log.info("Database schema written to `{s}`.", .{path});
},
}
}
const MigrationsRepo = jetquery.Repo(config.adapter, MigrateSchema);
fn migrationsRepo(action: Action, allocator: std.mem.Allocator) !MigrationsRepo {
return try MigrationsRepo.loadConfig(
allocator,
std.enums.nameCast(jetquery.Environment, environment),
.{
.admin = switch (action) {
.migrate, .rollback => false,
.create, .drop => true,
.schema => undefined, // We use a separate repo for schema reflection.
},
.context = .migration,
},
);
}