169 lines
3.9 KiB
Zig
169 lines
3.9 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 ActorDetailed = struct {
|
|
id: Uuid,
|
|
username: []const u8,
|
|
host: []const u8,
|
|
display_name: ?[]const u8,
|
|
bio: []const u8,
|
|
avatar_file_id: ?Uuid,
|
|
header_file_id: ?Uuid,
|
|
profile_fields: ProfileField,
|
|
created_at: DateTime,
|
|
updated_at: DateTime,
|
|
};
|
|
|
|
pub const Profile = struct {
|
|
display_name: ?[]const u8,
|
|
bio: []const u8,
|
|
|
|
avatar_file_id: ?Uuid,
|
|
header_file_id: ?Uuid,
|
|
|
|
profile_fields: ProfileField,
|
|
};
|
|
|
|
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 actor
|
|
\\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_]
|
|
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,
|
|
alloc: std.mem.Allocator,
|
|
) CreateError!Uuid {
|
|
const id = Uuid.randV4(util.getThreadPrng());
|
|
|
|
try validateUsername(username);
|
|
|
|
db.insert("actor", .{
|
|
.id = id,
|
|
.username = username,
|
|
.community_id = community_id,
|
|
.created_at = DateTime.now(),
|
|
}, alloc) catch |err| return switch (err) {
|
|
error.UniqueViolation => error.UsernameTaken,
|
|
else => error.DatabaseFailure,
|
|
};
|
|
|
|
return id;
|
|
}
|
|
|
|
pub const ProfileField = struct {
|
|
key: []const u8,
|
|
value: []const u8,
|
|
};
|
|
|
|
pub const Actor = struct {
|
|
id: Uuid,
|
|
|
|
username: []const u8,
|
|
host: []const u8,
|
|
|
|
display_name: ?[]const u8,
|
|
bio: []const u8,
|
|
|
|
avatar_file_id: ?Uuid,
|
|
header_file_id: ?Uuid,
|
|
|
|
profile_fields: []const ProfileField,
|
|
|
|
community_id: Uuid,
|
|
|
|
created_at: DateTime,
|
|
updated_at: DateTime,
|
|
|
|
pub const sql_serialize = struct {
|
|
pub const profile_fields = .json;
|
|
};
|
|
};
|
|
|
|
pub const GetError = error{ NotFound, DatabaseFailure };
|
|
pub fn get(db: anytype, id: Uuid, alloc: std.mem.Allocator) GetError!Actor {
|
|
return db.queryRow(
|
|
Actor,
|
|
\\SELECT
|
|
\\ actor.id,
|
|
\\ actor.username,
|
|
\\ community.host,
|
|
\\ actor.display_name,
|
|
\\ actor.bio,
|
|
\\ actor.avatar_file_id,
|
|
\\ actor.header_file_id,
|
|
\\ actor.profile_fields,
|
|
\\ actor.community_id,
|
|
\\ actor.created_at,
|
|
\\ actor.updated_at
|
|
\\FROM actor JOIN community
|
|
\\ ON actor.community_id = community.id
|
|
\\WHERE actor.id = $1
|
|
\\LIMIT 1
|
|
,
|
|
.{id},
|
|
alloc,
|
|
) catch |err| switch (err) {
|
|
error.NoRows => error.NotFound,
|
|
else => error.DatabaseFailure,
|
|
};
|
|
}
|
|
|
|
pub const max_fields = 32;
|
|
//pub fn update(db: anytype, id: Uuid, new: Partial(Profile), alloc: std.mem.Allocator) !Actor {}
|