134 lines
3.1 KiB
Zig
134 lines
3.1 KiB
Zig
const std = @import("std");
|
|
const util = @import("util");
|
|
const auth = @import("./auth.zig");
|
|
|
|
const Uuid = util.Uuid;
|
|
const DateTime = util.DateTime;
|
|
|
|
pub const CreateError = error{
|
|
UsernameTaken,
|
|
UsernameContainsInvalidChar,
|
|
UsernameTooLong,
|
|
UsernameEmpty,
|
|
DatabaseFailure,
|
|
};
|
|
|
|
pub const Kind = enum {
|
|
user,
|
|
admin,
|
|
};
|
|
|
|
pub const CreateOptions = struct {
|
|
invite_id: ?Uuid = null,
|
|
email: ?[]const u8 = null,
|
|
kind: Kind = .user,
|
|
};
|
|
|
|
pub const LookupError = error{
|
|
DatabaseFailure,
|
|
};
|
|
pub fn lookupByUsername(
|
|
db: anytype,
|
|
username: []const u8,
|
|
community_id: Uuid,
|
|
alloc: std.mem.Allocator,
|
|
) LookupError!?Uuid {
|
|
const row = db.queryRow(
|
|
std.meta.Tuple(&.{Uuid}),
|
|
\\SELECT id
|
|
\\FROM account
|
|
\\WHERE username = $1 AND community_id = $2
|
|
\\LIMIT 1
|
|
,
|
|
.{ username, community_id },
|
|
alloc,
|
|
) catch |err| return switch (err) {
|
|
error.NoRows => null,
|
|
else => error.DatabaseFailure,
|
|
};
|
|
|
|
return row[0];
|
|
}
|
|
|
|
pub const max_username_chars = 32;
|
|
pub const UsernameValidationError = error{
|
|
UsernameContainsInvalidChar,
|
|
UsernameTooLong,
|
|
UsernameEmpty,
|
|
};
|
|
|
|
/// Usernames must satisfy:
|
|
/// - Be at least 1 character
|
|
/// - Be no more than 32 characters
|
|
/// - All characters are in [A-Za-z0-9_.]
|
|
/// Note that the '.' character is not allowed in all usernames, and
|
|
/// is intended for use in federated instance actors (as many instances do)
|
|
pub fn validateUsername(username: []const u8) UsernameValidationError!void {
|
|
if (username.len == 0) return error.UsernameEmpty;
|
|
if (username.len > max_username_chars) return error.UsernameTooLong;
|
|
|
|
for (username) |ch| {
|
|
const valid = std.ascii.isAlNum(ch) or ch == '_';
|
|
if (!valid) return error.UsernameContainsInvalidChar;
|
|
}
|
|
}
|
|
|
|
pub fn create(
|
|
db: anytype,
|
|
username: []const u8,
|
|
community_id: Uuid,
|
|
kind: Kind,
|
|
alloc: std.mem.Allocator,
|
|
) CreateError!Uuid {
|
|
const id = Uuid.randV4(util.getThreadPrng());
|
|
|
|
try validateUsername(username);
|
|
|
|
db.insert("account", .{
|
|
.id = id,
|
|
.username = username,
|
|
.community_id = community_id,
|
|
.kind = kind,
|
|
}, alloc) catch |err| return switch (err) {
|
|
error.UniqueViolation => error.UsernameTaken,
|
|
else => error.DatabaseFailure,
|
|
};
|
|
|
|
return id;
|
|
}
|
|
|
|
pub const User = struct {
|
|
id: Uuid,
|
|
|
|
username: []const u8,
|
|
host: []const u8,
|
|
|
|
community_id: Uuid,
|
|
kind: Kind,
|
|
|
|
created_at: DateTime,
|
|
};
|
|
|
|
pub fn get(db: anytype, id: Uuid, alloc: std.mem.Allocator) !User {
|
|
return db.queryRow(
|
|
User,
|
|
\\SELECT
|
|
\\ account.id,
|
|
\\ account.username,
|
|
\\ community.host,
|
|
\\ account.community_id,
|
|
\\ account.kind,
|
|
\\ account.created_at
|
|
\\FROM account JOIN community
|
|
\\ ON account.community_id = community.id
|
|
\\WHERE account.id = $1
|
|
\\LIMIT 1
|
|
,
|
|
.{id},
|
|
alloc,
|
|
) catch |err| switch (err) {
|
|
error.NoRows => error.NotFound,
|
|
else => error.DatabaseFailure,
|
|
};
|
|
}
|