+ // TODO: choose buffer size
+ var alloc = std.heap.FixedBufferAllocator.init(&HashBuffer);
+
+ // TODO: choose limits
+ const result = try std.crypto.pwhash.argon2.strHash(password, .{
+ .allocator = alloc.allocator(),
+ .params = std.crypto.pwhash.argon2.Params.owasp_2id,
+ }, hash_buffer.slice());
+
+ try hash_buffer.resize(result.len);
+
+ return hash_buffer;
+ }
+
+ pub fn verify_password(password: []const u8, hash: PasswordHash) bool {
+ var alloc = std.heap.FixedBufferAllocator.init(&HashBuffer);
+
+ if (std.crypto.pwhash.argon2.strVerify(hash.constSlice(), password, .{
+ .allocator = alloc.allocator(),
+ })) {
+ return true;
+ } else |err| {
+ std.debug.print("verify error: {}\n", .{err});
+ return false;
+ }
+ }
+
+ pub fn register_user(env: lmdb.Env, username: []const u8, password: []const u8) !bool {
+ const username_array = try Username.fromSlice(username);
+ const display_name = try DisplayName.fromSlice(username);
+
+ const txn = try env.txn();
+ defer txn.commit() catch |err| {
+ std.debug.print("error registering user: {}\n", .{err});
+ };
+
+ const users = try Db.users(txn);
+ const user_ids = try Db.user_ids(txn);
+
+ if (try user_ids.has(username_array)) {
+ return false;
+ } else {
+ const user_id = try db.Prng.gen(users.dbi, UserId);
+
+ try users.put(user_id, User{
+ .id = user_id,
+ .name = username_array,
+ .display_name = display_name,
+ .description = try UserDescription.init(0),
+ .password_hash = try hash_password(password),
+ .posts = try PostList.init(txn),
+ .following = try UserList.init(txn),
+ .followers = try UserList.init(txn),
+ .post_lists = try PostListList.init(txn),
+ .feeds = try UserListList.init(txn),
+ });
+
+ try user_ids.put(username_array, user_id);
+
+ return true;
+ }
+ }
+
+ pub fn login_user(
+ env: lmdb.Env,
+ username: []const u8,
+ password: []const u8,
+ ) !SessionToken {
+ const username_array = try Username.fromSlice(username);
+
+ const txn = try env.txn();
+ defer txn.commit() catch {};
+
+ const user_ids = try Db.user_ids(txn);
+ const user_id = try user_ids.get(username_array);
+ std.debug.print("user logging in, id: {}\n", .{user_id});
+
+ const users = try Db.users(txn);
+ const user = try users.get(user_id);
+
+ if (verify_password(password, user.password_hash)) {
+ const sessions = try Db.sessions(txn);
+ const session_token = try db.Prng.gen(sessions.dbi, SessionToken);
+ try sessions.put(session_token, user_id);
+ return session_token;
+ } else {
+ return error.IncorrectPassword;
+ }
+ }
+
+ fn logout_user(env: lmdb.Env, session_token: SessionToken) !void {
+ const txn = try env.txn();
+ defer txn.commit() catch {};
+
+ const sessions = try Db.sessions(txn);
+ try sessions.del(session_token);
+ }
+
+ fn append_post(env: lmdb.Env, user_id: UserId, post_list: PostList, parent_id: ?PostId, quote_id: ?PostId, text: []const u8) !PostId {
+ var post_id: PostId = undefined;
+
+ // TODO: do this in one commit
+
+ var txn: lmdb.Txn = undefined;
+ {
+ // create post
+ txn = try env.txn();
+ defer txn.commit() catch {};
+
+ const posts = try Db.posts(txn);
+ post_id = try db.Prng.gen(posts.dbi, PostId);
+
+ const decoded_text = try reencode(PostText, text);
+ try posts.put(post_id, Post{
+ .id = post_id,
+ .parent_id = parent_id,
+ .quote_id = quote_id,
+ .user_id = user_id,
+ .time = std.time.timestamp(),
+ .votes = try VoteList.init(txn),
+ .comments = try PostList.init(txn),
+ .quotes = try PostList.init(txn),
+ .text = decoded_text,
+ });
+ }
+
+ {
+ // append to user's posts
+ txn = try env.txn();
+ defer txn.commit() catch {};
+
+ var posts_view = try post_list.open(txn);
+ try posts_view.append(post_id);
+ }
+
+ if (quote_id != null) {
+ txn = try env.txn();
+ defer txn.commit() catch {};
+
+ const posts = try Db.posts(txn);
+ const quote_post = try posts.get(quote_id.?);
+ var quotes = try quote_post.quotes.open(txn);
+ try quotes.append(post_id);
+ }
+
+ return post_id;
+ }
+
+ fn post(env: lmdb.Env, user_id: UserId, text: []const u8) !void {
+ var txn = try env.txn();
+ const users = try Db.users(txn);
+ const user = try users.get(user_id);
+ txn.abort();
+
+ const post_id = try append_post(env, user_id, user.posts, null, null, text);
+ _ = post_id;
+ }
+
+ fn comment(env: lmdb.Env, user_id: UserId, parent_post_id: PostId, text: []const u8) !void {
+ var txn = try env.txn();
+ const users = try Db.users(txn);
+ const user = try users.get(user_id);
+
+ const posts = try Db.posts(txn);
+ const parent_post = try posts.get(parent_post_id);
+ txn.abort();
+
+ const post_id = try append_post(env, user_id, parent_post.comments, parent_post_id, null, text);
+
+ txn = try env.txn();
+ var replies_view = try user.posts.open(txn);
+ try replies_view.append(post_id);
+ try txn.commit();
+ }
+
+ fn quote(env: lmdb.Env, user_id: UserId, quote_post_id: PostId, text: []const u8) !void {
+ var txn = try env.txn();
+ const users = try Db.users(txn);
+ const user = try users.get(user_id);
+ txn.abort();
+
+ const post_id = try append_post(env, user_id, user.posts, null, quote_post_id, text);
+ _ = post_id;
+ }
+
+ fn vote(env: lmdb.Env, post_id: PostId, user_id: UserId, kind: Vote.Kind) !void {
+ const txn = try env.txn();
+ defer txn.commit() catch {};
+
+ const posts = try Db.posts(txn);
+
+ var p = try posts.get(post_id);
+ var votes_view = try p.votes.open(txn);
+
+ var add_vote = true;
+
+ if (try votes_view.has(user_id)) {
+ const old_vote = try votes_view.get(user_id);
+
+ add_vote = old_vote.kind != kind;
+
+ try votes_view.del(user_id);
+
+ switch (old_vote.kind) {
+ .Up => p.upvotes -= 1,
+ .Down => p.downvotes -= 1,
+ }
+ try posts.put(post_id, p);
+ }
+
+ if (add_vote) {
+ try votes_view.append(user_id, Vote{
+ .kind = kind,
+ .time = std.time.timestamp(),
+ });
+
+ if (kind == .Up) {
+ p.upvotes += 1;
+ } else {
+ p.downvotes += 1;
+ }
+ try posts.put(post_id, p);
+ }
+ }
+
+ fn follow(env: lmdb.Env, user_id: UserId, user_id_to_follow: UserId) !void {
+ const txn = try env.txn();
+ defer txn.commit() catch {};
+
+ const users = try Db.users(txn);
+
+ const user = try users.get(user_id);
+ const user_to_follow = try users.get(user_id_to_follow);
+
+ var user_following = try user.following.open(txn);
+ var user_to_follow_followers = try user_to_follow.followers.open(txn);
+
+ if ((user_following.has(user_id_to_follow) catch false) and (user_to_follow_followers.has(user_id) catch false)) {
+ try user_following.del(user_id_to_follow);
+ try user_to_follow_followers.del(user_id);
+ } else if (!(user_following.has(user_id_to_follow) catch true) and !(user_to_follow_followers.has(user_id) catch true)) {
+ try user_following.append(user_id_to_follow);
+ try user_to_follow_followers.append(user_id);
+ } else {
+ std.debug.print("Something went wrong when trying to unfollow\n", .{});
+ }
+ }
+
+ fn get_session_user_id(env: lmdb.Env, session_token: SessionToken) !UserId {
+ const txn = try env.txn();
+ defer txn.abort();
+
+ const sessions = try Db.sessions(txn);
+
+ return try sessions.get(session_token);
+ }
+
+ fn get_user(env: lmdb.Env, user_id: UserId) !User {
+ const txn = try env.txn();
+ defer txn.abort();
+
+ const users = try Db.users(txn);
+ return try users.get(user_id);
+ }