diff options
Diffstat (limited to 'private')
| -rw-r--r-- | private/Makefile | 8 | ||||
| -rw-r--r-- | private/build.zig | 40 | ||||
| -rw-r--r-- | private/build.zig.zon | 17 | ||||
| -rw-r--r-- | private/src/db.zig | 151 | ||||
| -rw-r--r-- | private/src/main.zig | 5 | ||||
| -rw-r--r-- | private/src/util.zig | 4 |
6 files changed, 225 insertions, 0 deletions
diff --git a/private/Makefile b/private/Makefile new file mode 100644 index 0000000..64cac0a --- /dev/null +++ b/private/Makefile @@ -0,0 +1,8 @@ +debug: + $(info building in) + $(info .-*[DEBUG]*-.) + zig build -Doptimize=Debug +release: + $(info building in) + $(info .-*[RELEASE]*-.) + zig build -Doptimize=ReleaseFast diff --git a/private/build.zig b/private/build.zig new file mode 100644 index 0000000..eae283b --- /dev/null +++ b/private/build.zig @@ -0,0 +1,40 @@ +const z = @import( "std" ); + +var b: *z.Build = undefined; +var target: z.Build.ResolvedTarget = undefined; +var optimize: z.builtin.OptimizeMode = undefined; + +fn import( name: []const u8 ) z.Build.Module.Import { + const i = b.dependency( name, .{ .target = target, .optimize = optimize } ); + return .{ .name = name, .module = i.module( name ) }; +} + +pub fn build( _b: *z.Build ) void { + b = _b; + target = b.standardTargetOptions( .{} ); + optimize = b.standardOptimizeOption( .{} ); + + const exe = b.addExecutable( .{ + .name = "moneyboard-backend", + .root_module = b.createModule( .{ + .root_source_file = b.path( "src/main.zig" ), + .target = target, + .optimize = optimize, + .imports = &.{ + import( "sqlite" ), + }, + } ), + } ); + + b.installArtifact( exe ); + + const run_step = b.step( "run", "Run the app" ); + const run_cmd = b.addRunArtifact( exe ); + run_step.dependOn( &run_cmd.step ); + + run_cmd.step.dependOn( b.getInstallStep() ); + + if( b.args ) |args| { + run_cmd.addArgs( args ); + } +} diff --git a/private/build.zig.zon b/private/build.zig.zon new file mode 100644 index 0000000..1ccf1fd --- /dev/null +++ b/private/build.zig.zon @@ -0,0 +1,17 @@ +.{ + .name = .moneyboard, + .version = "0.0.0", + .fingerprint = 0xeafff85b61626f1c, + .minimum_zig_version = "0.15.2", + .dependencies = .{ + .sqlite = .{ + .url = "git+https://github.com/vrischmann/zig-sqlite#0b75ba276d34de3141ec1697e3ef7c836d7b38e7", + .hash = "sqlite-3.48.0-F2R_a8eODgD9X8XjvWlZ1K7C-tDuHae4FuShM2N-yIT2", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/private/src/db.zig b/private/src/db.zig new file mode 100644 index 0000000..ee8908b --- /dev/null +++ b/private/src/db.zig @@ -0,0 +1,151 @@ +const z = @import( "std" ); +const sql = @import( "sqlite" ); + +const u = @import( "util.zig" ); + +const alloc = u.alloc; + +const ArenaAlloc = z.heap.ArenaAllocator; +const Mutex = z.Thread.Mutex; +const Db = sql.Db; + +pub threadlocal var dbi: Interface = undefined; +pub threadlocal var dbi_init: bool = false; + +var db_mutex = Mutex{}; + +pub fn create( dbfile: [:0]const u8 ) !void { + dbiCheck() catch try init( dbfile ); + + const create_users_q = + \\CREATE TABLE IF NOT EXISTS users ( + \\ uuid TEXT PRIMARY KEY, + \\ email TEXT, + \\ tokens TEXT, + \\ login_token TEXT, + \\ token_resetdate INTEGER, + \\ api_tokens TEXT, + \\ created_at INTEGER, + \\ subscription_data TEXT + \\) + ; + + try dbi.put( create_users_q, .{} ); + // ensure all fields actually exist in the database + dbi.put( "ALTER TABLE users ADD COLUMN email TEXT", .{} ) catch {}; + dbi.put( "ALTER TABLE users ADD COLUMN tokens TEXT", .{} ) catch {}; + dbi.put( "ALTER TABLE users ADD COLUMN login_token TEXT", .{} ) catch {}; + dbi.put( "ALTER TABLE users ADD COLUMN token_resetdate INTEGER", .{} ) catch {}; + dbi.put( "ALTER TABLE users ADD COLUMN api_tokens TEXT", .{} ) catch {}; + dbi.put( "ALTER TABLE users ADD COLUMN created_at INTEGER", .{} ) catch {}; + dbi.put( "ALTER TABLE users ADD COLUMN subscription_data TEXT", .{} ) catch {}; + + const create_userdata_q = + \\CREATE TABLE IF NOT EXISTS user_data ( + \\ uuid TEXT PRIMARY KEY, + \\ nickname TEXT, + \\ prompt_data TEXT, + \\ site_prefs TEXT, + \\ chat_files TEXT + \\) + ; + + try dbi.put( create_userdata_q, .{} ); + // ensure all fields actually exist in the database + dbi.put( "ALTER TABLE user_data ADD COLUMN nickname INTEGER", .{} ) catch {}; + dbi.put( "ALTER TABLE user_data ADD COLUMN prompt_data INTEGER", .{} ) catch {}; + dbi.put( "ALTER TABLE user_data ADD COLUMN site_prefs INTEGER", .{} ) catch {}; + dbi.put( "ALTER TABLE user_data ADD COLUMN chat_files INTEGER", .{} ) catch {}; +} + +pub fn init( dbfile: [:0]const u8 ) !void { + if( dbi_init ) return error.DbiAlreadyInitialized; + + const db = try sql.Db.init( .{ + .mode = Db.Mode{ .File = dbfile }, + .open_flags = .{ + .write = true, + .create = true + }, + .threading_mode = .MultiThread + } ); + + dbi = .{ .db = db }; + dbi_init = true; +} + +fn dbiCheck() !void { + if( !dbi_init ) return error.DbiNotInitialized; +} + +///methods of t get ignored +pub fn Row( comptime t: type ) type { + return struct { + v: t, + alloc: ArenaAlloc, + pub fn deinit( self: @This() ) void { + self.alloc.deinit(); + } + }; +} + +pub const Interface = struct { + const Self = Interface; + db: Db, + pub fn deinit( self: *Interface ) void { + self.db.deinit(); + } + + pub fn put( self: *Interface, comptime query: []const u8, value: anytype ) !void { + try dbiCheck(); + db_mutex.lock(); + defer db_mutex.unlock(); + + var statement = try self.db.prepare( query ); + defer statement.deinit(); + + try statement.exec( .{}, value ); + } + + ///returns the first row of the query + pub fn one( self: *Interface, comptime t: type, comptime query: []const u8, value: anytype ) !t { + try dbiCheck(); + db_mutex.lock(); + defer db_mutex.unlock(); + + var statement = try self.db.prepare( query ); + defer statement.deinit(); + + const ret = try statement.one( t, .{}, value ); + return ret.?; + } + + ///returns all rows of the query + ///must be freed by caller + pub fn all( self: *Interface, comptime t: type, comptime query: []const u8, value: anytype ) ![]t { + try dbiCheck(); + db_mutex.lock(); + defer db_mutex.unlock(); + + var statement = try self.db.prepare( query ); + defer statement.deinit(); + + return try statement.all( t, alloc, .{}, value ); + } + + pub fn oneAlloc( self: *Interface, comptime t: type, comptime query: []const u8, value: anytype ) !Row(t) { + try dbiCheck(); + db_mutex.lock(); + defer db_mutex.unlock(); + + var arena = ArenaAlloc.init( alloc ); + const a = arena.allocator(); + + var statement = try self.db.prepareDynamic( query ); + defer statement.deinit(); + + const ret = try statement.oneAlloc( t, a, .{}, value ); + if( ret == null ) return error.QueryError; + return .{ .v = ret.?, .alloc = arena }; + } +}; diff --git a/private/src/main.zig b/private/src/main.zig new file mode 100644 index 0000000..5f397b8 --- /dev/null +++ b/private/src/main.zig @@ -0,0 +1,5 @@ +const z = @import( "std" ); + +pub fn main() !void { + z.debug.print( "hello world\n", .{} ); +} diff --git a/private/src/util.zig b/private/src/util.zig new file mode 100644 index 0000000..923c196 --- /dev/null +++ b/private/src/util.zig @@ -0,0 +1,4 @@ +const z = @import( "std" ); + +pub var gpa = z.heap.GeneralPurposeAllocator( .{ .thread_safe = true, .stack_trace_frames = 64 } ){}; +pub const alloc = gpa.allocator(); |
