const std = @import("std"); const builtin = @import("builtin"); const util = @import("util"); const types = @import("./types.zig"); const Uuid = util.Uuid; const DateTime = util.DateTime; const Invite = types.Invite; // 9 random bytes = 12 random b64 const rand_len = 8; const code_len = 12; const Encoder = std.base64.url_safe.Encoder; const Decoder = std.base64.url_safe.Decoder; pub fn create( db: anytype, created_by: Uuid, community_id: Uuid, name: []const u8, options: Invite.InternalCreateOptions, alloc: std.mem.Allocator, ) !Uuid { const id = Uuid.randV4(util.getThreadPrng()); var code_bytes: [rand_len]u8 = undefined; util.getThreadPrng().bytes(&code_bytes); const code = try alloc.alloc(u8, code_len); defer alloc.free(code); _ = Encoder.encode(code, &code_bytes); const created_at = DateTime.now(); try db.insert( "invite", .{ .id = id, .created_by = created_by, .community_id = community_id, .name = name, .code = code, .max_uses = options.max_uses, .created_at = created_at, .expires_at = if (options.lifespan) |lifespan| @as(?DateTime, created_at.add(lifespan)) else null, .kind = options.kind, }, alloc, ); return id; } pub const GetError = error{ NotFound, DatabaseFailure, }; // Helper fn for getting a single invite fn doGetQuery( db: anytype, comptime where: []const u8, query_args: anytype, alloc: std.mem.Allocator, ) GetError!Invite { // Generate list of fields from struct const field_list = comptime util.comptimeJoinWithPrefix( ",", "invite.", &.{ "id", "created_by", "community_id", "name", "code", "created_at", "expires_at", "max_uses", "kind", }, ); // times_used field is not stored directly in the DB, instead // it is calculated based on the number of accounts that were created // from it const query = std.fmt.comptimePrint( \\SELECT {s}, COUNT(account.id) AS times_used \\FROM invite LEFT OUTER JOIN account \\ ON invite.id = account.invite_id \\WHERE {s} \\GROUP BY invite.id \\LIMIT 1 , .{ field_list, where }, ); return db.queryRow(Invite, query, query_args, alloc) catch |err| switch (err) { error.NoRows => return error.NotFound, else => return error.DatabaseFailure, }; } pub fn get(db: anytype, id: Uuid, alloc: std.mem.Allocator) GetError!Invite { return doGetQuery(db, "invite.id = $1", .{id}, alloc); } pub fn getByCode(db: anytype, code: []const u8, community_id: Uuid, alloc: std.mem.Allocator) GetError!Invite { return doGetQuery( db, "invite.code = $1 AND invite.community_id = $2", .{ code, community_id }, alloc, ); }