fediglam/src/api/services/users.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,
};
}