fediglam/src/api/services/invites.zig

147 lines
3.5 KiB
Zig
Raw Normal View History

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|
2022-11-10 09:53:09 +00:00
@as(?DateTime, created_at.add(lifespan))
2022-10-02 05:18:24 +00:00
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(
\\SELECT {s}, COUNT(account.id) AS times_used
\\FROM invite LEFT OUTER JOIN account
\\ ON invite.id = account.invite_id
2022-10-02 05:18:24 +00:00
\\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
}