2022-09-08 05:10:58 +00:00
|
|
|
const std = @import("std");
|
|
|
|
const builtin = @import("builtin");
|
|
|
|
const util = @import("util");
|
|
|
|
|
|
|
|
const Uuid = util.Uuid;
|
|
|
|
const DateTime = util.DateTime;
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
pub const Kind = enum {
|
2022-09-08 05:10:58 +00:00
|
|
|
system,
|
|
|
|
community_owner,
|
|
|
|
user,
|
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
pub const jsonStringify = util.jsonSerializeEnumAsString;
|
2022-09-08 05:10:58 +00:00
|
|
|
};
|
|
|
|
|
2022-09-15 01:12:07 +00:00
|
|
|
const InviteCount = u16;
|
2022-09-08 05:10:58 +00:00
|
|
|
pub const Invite = struct {
|
|
|
|
id: Uuid,
|
|
|
|
|
|
|
|
created_by: Uuid, // User ID
|
2022-10-04 02:41:59 +00:00
|
|
|
community_id: ?Uuid,
|
2022-09-08 05:10:58 +00:00
|
|
|
name: []const u8,
|
|
|
|
code: []const u8,
|
|
|
|
|
|
|
|
created_at: DateTime,
|
2022-09-15 01:12:07 +00:00
|
|
|
times_used: InviteCount,
|
2022-09-08 05:10:58 +00:00
|
|
|
|
|
|
|
expires_at: ?DateTime,
|
2022-09-15 01:12:07 +00:00
|
|
|
max_uses: ?InviteCount,
|
2022-09-08 05:10:58 +00:00
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
kind: Kind,
|
2022-09-08 06:56:29 +00:00
|
|
|
};
|
2022-09-08 05:10:58 +00:00
|
|
|
|
|
|
|
pub const InviteOptions = struct {
|
|
|
|
name: ?[]const u8 = null,
|
2022-09-15 01:12:07 +00:00
|
|
|
max_uses: ?InviteCount = null,
|
2022-10-02 05:18:24 +00:00
|
|
|
lifespan: ?DateTime.Duration = null,
|
2022-10-04 02:41:59 +00:00
|
|
|
kind: Kind = .user,
|
2022-09-08 05:10:58 +00:00
|
|
|
};
|
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
pub fn create(db: anytype, created_by: Uuid, community_id: ?Uuid, options: InviteOptions, alloc: std.mem.Allocator) !Uuid {
|
2022-10-08 20:47:54 +00:00
|
|
|
const id = Uuid.randV4(util.getThreadPrng());
|
2022-10-02 05:18:24 +00:00
|
|
|
|
2022-09-08 05:10:58 +00:00
|
|
|
var code_bytes: [rand_len]u8 = undefined;
|
2022-10-08 20:47:54 +00:00
|
|
|
util.getThreadPrng().bytes(&code_bytes);
|
2022-09-08 05:10:58 +00:00
|
|
|
|
|
|
|
const code = try alloc.alloc(u8, code_len);
|
2022-10-02 05:18:24 +00:00
|
|
|
defer alloc.free(code);
|
2022-09-08 05:10:58 +00:00
|
|
|
_ = Encoder.encode(code, &code_bytes);
|
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
const name = options.name orelse code;
|
2022-09-08 05:10:58 +00:00
|
|
|
const created_at = DateTime.now();
|
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
try db.insert(
|
|
|
|
"invite",
|
|
|
|
.{
|
|
|
|
.id = id,
|
|
|
|
|
|
|
|
.created_by = created_by,
|
2022-10-04 02:41:59 +00:00
|
|
|
.community_id = community_id,
|
2022-10-02 05:18:24 +00:00
|
|
|
.name = name,
|
|
|
|
.code = code,
|
|
|
|
|
|
|
|
.max_uses = options.max_uses,
|
|
|
|
.created_at = created_at,
|
|
|
|
.expires_at = if (options.lifespan) |lifespan|
|
|
|
|
created_at.add(lifespan)
|
|
|
|
else
|
|
|
|
null,
|
|
|
|
|
2022-10-04 02:41:59 +00:00
|
|
|
.kind = options.kind,
|
2022-10-02 05:18:24 +00:00
|
|
|
},
|
|
|
|
alloc,
|
|
|
|
);
|
|
|
|
|
|
|
|
return id;
|
|
|
|
}
|
2022-09-08 06:56:29 +00:00
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
pub const GetError = error{
|
|
|
|
NotFound,
|
|
|
|
DatabaseFailure,
|
|
|
|
};
|
2022-09-08 05:10:58 +00:00
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
// 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
|
2022-10-04 02:41:59 +00:00
|
|
|
const field_list = comptime util.comptimeJoinWithPrefix(
|
2022-10-02 05:18:24 +00:00
|
|
|
",",
|
|
|
|
"invite.",
|
2022-10-04 02:41:59 +00:00
|
|
|
&.{
|
|
|
|
"id",
|
|
|
|
"created_by",
|
|
|
|
"community_id",
|
|
|
|
"name",
|
|
|
|
"code",
|
|
|
|
"created_at",
|
|
|
|
"expires_at",
|
|
|
|
"max_uses",
|
|
|
|
"kind",
|
|
|
|
},
|
2022-10-02 05:18:24 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
// 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(
|
2022-10-04 02:41:59 +00:00
|
|
|
\\SELECT {s}, COUNT(local_account.account_id) AS times_used
|
2022-10-02 05:18:24 +00:00
|
|
|
\\FROM invite LEFT OUTER JOIN local_account
|
|
|
|
\\ ON invite.id = local_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 => error.NotFound,
|
|
|
|
else => error.DatabaseFailure,
|
2022-09-08 05:10:58 +00:00
|
|
|
};
|
2022-09-08 06:56:29 +00:00
|
|
|
}
|
2022-09-08 05:10:58 +00:00
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
pub fn get(db: anytype, id: Uuid, alloc: std.mem.Allocator) GetError!Invite {
|
|
|
|
return doGetQuery(db, "invite.id = $1", .{id}, alloc);
|
|
|
|
}
|
2022-09-08 06:56:29 +00:00
|
|
|
|
2022-10-02 05:18:24 +00:00
|
|
|
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,
|
|
|
|
);
|
2022-09-08 05:10:58 +00:00
|
|
|
}
|