+ {
+ // append to user's posts
+ txn = try env.txn();
+ defer txn.commit() catch {};
+
+ const users = try Db.users(txn);
+ var user = try users.get(user_id);
+
+ const posts = try Db.posts(txn);
+ var posts_view = try user.posts.open(posts.dbi);
+ try posts_view.append(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);
+ const votes = try txn.dbi("votes");
+
+ var p = try posts.get(post_id);
+ var votes_view = try p.votes.open(votes);
+
+ if (try votes_view.has(user_id)) {
+ const old_vote = try votes_view.get(user_id);
+
+ if (old_vote.kind == kind) {
+ return;
+ } else {
+ try votes_view.del(user_id);
+
+ if (old_vote.kind == .Up) {
+ p.upvotes -= 1;
+ } else {
+ p.downvotes -= 1;
+ }
+ try posts.put(post_id, p);
+ }
+ }
+ 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 unvote(env: *lmdb.Env, post_id: PostId, user_id: UserId) !void {
+ const txn = try env.txn();
+ defer txn.commit() catch {};
+
+ const posts = try Db.posts(txn);
+ const votes = try txn.dbi("votes");
+
+ var p = try posts.get(post_id);
+ var votes_view = try p.votes.open(votes);
+
+ if (try votes_view.has(user_id)) {
+ const v = try votes_view.get(user_id);
+
+ if (v.kind == .Up) {
+ p.upvotes -= 1;
+ } else {
+ p.downvotes -= 1;
+ }
+ try posts.put(post_id, p);
+
+ try votes_view.del(user_id);
+ }
+ }
+
+ 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);
+ }
+};
+
+// }}}
+
+// html {{{
+fn html_form(res: *http.Response, comptime fmt_action: []const u8, args_action: anytype, inputs: anytype) !void {
+ try res.write("<form style=\"display: inline-block!important;\" action=\"", .{});
+ try res.write(fmt_action, args_action);
+ try res.write("\" method=\"post\">", .{});
+
+ inline for (inputs) |input| {
+ switch (@typeInfo(@TypeOf(input))) {
+ .Struct => {
+ try res.write("<input ", .{});
+ try res.write(input[0], input[1]);
+ try res.write(" />", .{});
+ },
+ else => {
+ try res.write("<input ", .{});
+ try res.write(input, .{});
+ try res.write(" />", .{});
+ },
+ }
+ }