diff options
-rw-r--r-- | public/join.js | 25 | ||||
-rw-r--r-- | schema.sql | 28 | ||||
-rw-r--r-- | server.js | 77 | ||||
-rw-r--r-- | views/contacts.pug | 54 | ||||
-rw-r--r-- | views/header.pug | 1 | ||||
-rw-r--r-- | views/join.pug | 6 | ||||
-rw-r--r-- | views/message_send.pug | 4 | ||||
-rw-r--r-- | views/user.pug | 12 |
8 files changed, 193 insertions, 14 deletions
diff --git a/public/join.js b/public/join.js index f199329..d140230 100644 --- a/public/join.js +++ b/public/join.js @@ -99,6 +99,14 @@ function start_event_source() { } } +function is_friend(p) { + return whitelist.includes(p.user_id) +} + +function is_enemy(p) { + return blacklist.includes(p.user_id) +} + function is_active(player, role) { return (game.active === role || game.active === "Both" || game.active === "All") } @@ -127,21 +135,27 @@ function update() { case 1: element.classList.toggle("is_active", is_active(player, role)) if (player.user_id === user_id) - element.innerHTML = `<a class="red" href="javascript:part('${role}')">\u274c</a> <a href="/${game.title_id}/play:${game.game_id}:${role}">${player.name}</a>` + element.innerHTML = `<a href="/${game.title_id}/play:${game.game_id}:${role}">${player.name}</a><a class="red" href="javascript:part('${role}')">\u274c</a>` else if (game.owner_id === user_id) - element.innerHTML = `<a class="red" href="javascript:kick('${role}')">\u274c</a> ${player.name}` + element.innerHTML = `${player.name}<a class="red" href="javascript:kick('${role}')">\u274c</a>` else element.innerHTML = player.name break case 0: if (player.user_id === user_id) - element.innerHTML = `<a class="red" href="javascript:part('${role}')">\u274c</a> ${player.name}` + element.innerHTML = `${player.name}<a class="red" href="javascript:part('${role}')">\u274c</a>` else if (game.owner_id === user_id) - element.innerHTML = `<a class="red" href="javascript:kick('${role}')">\u274c</a> ${player.name}` + element.innerHTML = `${player.name}<a class="red" href="javascript:kick('${role}')">\u274c</a>` else element.innerHTML = player.name break } + element.classList.toggle("friend", is_friend(player)) + element.classList.toggle("enemy", is_enemy(player)) + if (is_enemy(player)) + element.title = "You have blacklisted this user!" + else + element.title = "" } else { switch (game.status) { case 2: @@ -152,6 +166,9 @@ function update() { element.innerHTML = `<a class="join" href="javascript:join('${role}')">Join</a>` break } + element.classList.remove("friend") + element.classList.remove("enemy") + element.title = "" } } @@ -79,6 +79,31 @@ create view user_profile_view as natural left join user_last_seen ; +-- Friend and Block Lists -- + +create table if not exists contacts ( + me integer, + you integer, + relation integer, + primary key (me, you) +) without rowid; + +drop view if exists contact_view; +create view contact_view as + select + contacts.me, + users.user_id, + users.name, + user_last_seen.atime, + contacts.relation + from + contacts + left join users on contacts.you = users.user_id + left join user_last_seen on contacts.you = user_last_seen.user_id + order by + users.name +; + -- Messages -- create table if not exists messages ( @@ -229,7 +254,7 @@ create table if not exists game_replay ( game_id integer, role text, action text, - arguments text + arguments json -- numeric affinity is more compact for numbers ); create index if not exists game_replay_idx on game_replay(game_id); @@ -358,6 +383,7 @@ begin delete from tokens where user_id = old.user_id; delete from user_last_seen where user_id = old.user_id; delete from last_notified where user_id = old.user_id; + delete from contacts where me = old.user_id or you = old.user_id; delete from messages where from_id = old.user_id or to_id = old.user_id; delete from posts where author_id = old.user_id; delete from threads where author_id = old.user_id; @@ -643,7 +643,10 @@ app.get('/user/:who_name', function (req, res) { who.atime = human_date(who.atime) let games = QUERY_LIST_ACTIVE_GAMES_OF_USER.all({ user_id: who.user_id }) annotate_games(games, 0) - res.render('user.pug', { user: req.user, who: who, games: games }) + let relation = 0 + if (req.user) + relation = SQL_SELECT_RELATION.get(req.user.user_id, who.user_id) | 0 + res.render('user.pug', { user: req.user, who, relation, games }) } else { return res.status(404).send("Invalid user name.") } @@ -669,6 +672,60 @@ app.get('/chat/all', must_be_logged_in, function (req, res) { }) /* + * CONTACTS + */ + +const SQL_SELECT_CONTACT_BLACKLIST = SQL("select you from contacts where me=? and relation<0").pluck() +const SQL_SELECT_CONTACT_WHITELIST = SQL("select you from contacts where me=? and relation>0").pluck() +const SQL_SELECT_CONTACT_FRIEND_NAMES = SQL("select name from contact_view where me=? and relation>0").pluck() +const SQL_SELECT_CONTACT_LIST = SQL("select * from contact_view where me=?") +const SQL_INSERT_CONTACT = SQL("insert into contacts (me,you,relation) values (?,?,?)") +const SQL_DELETE_CONTACT = SQL("delete from contacts where me=? and you=?") +const SQL_SELECT_RELATION = SQL("select relation from contacts where me=? and you=?").pluck() + +app.get('/contacts', must_be_logged_in, function (req, res) { + let contacts = SQL_SELECT_CONTACT_LIST.all(req.user.user_id) + contacts.forEach(user => user.atime = human_date(user.atime)) + res.render('contacts.pug', { + user: req.user, + friends: contacts.filter(user => user.relation > 0), + enemies: contacts.filter(user => user.relation < 0), + }) +}) + +app.get("/contact/remove/:who_name", must_be_logged_in, function (req, res) { + let who = SQL_SELECT_USER_BY_NAME.get(req.params.who_name) + if (!who) + return res.status(404).send("User not found.") + SQL_DELETE_CONTACT.run(req.user.user_id, who.user_id) + return res.redirect("/contacts") +}) + +app.get("/contact/add-friend/:who_name", must_be_logged_in, function (req, res) { + let who = SQL_SELECT_USER_BY_NAME.get(req.params.who_name) + if (!who) + return res.status(404).send("User not found.") + SQL_INSERT_CONTACT.run(req.user.user_id, who.user_id, 1) + return res.redirect("/user/" + who.name) +}) + +app.get("/contact/add-enemy/:who_name", must_be_logged_in, function (req, res) { + let who = SQL_SELECT_USER_BY_NAME.get(req.params.who_name) + if (!who) + return res.status(404).send("User not found.") + SQL_INSERT_CONTACT.run(req.user.user_id, who.user_id, -1) + return res.redirect("/user/" + who.name) +}) + +app.get("/contact/remove-user/:who_name", must_be_logged_in, function (req, res) { + let who = SQL_SELECT_USER_BY_NAME.get(req.params.who_name) + if (!who) + return res.status(404).send("User not found.") + SQL_DELETE_CONTACT.run(req.user.user_id, who.user_id) + return res.redirect("/user/" + who.name) +}) + +/* * MESSAGES */ @@ -729,21 +786,25 @@ app.get('/message/read/:message_id', must_be_logged_in, function (req, res) { }) app.get('/message/send', must_be_logged_in, function (req, res) { + let friends = SQL_SELECT_CONTACT_FRIEND_NAMES.all(req.user.user_id) res.render('message_send.pug', { user: req.user, to_name: "", subject: "", body: "", + friends, }) }) app.get('/message/send/:to_name', must_be_logged_in, function (req, res) { + let friends = SQL_SELECT_CONTACT_FRIEND_NAMES.all(req.user.user_id) let to_name = req.params.to_name res.render('message_send.pug', { user: req.user, to_name: to_name, subject: "", body: "", + friends, }) }) @@ -753,13 +814,15 @@ app.post('/message/send', must_be_logged_in, function (req, res) { let body = req.body.body.trim() let to_user = SQL_SELECT_USER_BY_NAME.get(to_name) if (!to_user) { + let friends = SQL_SELECT_CONTACT_FRIEND_NAMES.all(req.user.user_id) return res.render('message_send.pug', { user: req.user, to_id: 0, to_name: to_name, subject: subject, body: body, - flash: "Cannot find that user." + friends, + flash: "Cannot find that user.", }) } let info = MESSAGE_SEND.run(req.user.user_id, to_user.user_id, subject, body) @@ -780,12 +843,14 @@ app.get('/message/reply/:message_id', must_be_logged_in, function (req, res) { let message = MESSAGE_FETCH.get(message_id, req.user.user_id, req.user.user_id) if (!message) return res.status(404).send("Invalid message ID.") + let friends = SQL_SELECT_CONTACT_FRIEND_NAMES.all(req.user.user_id) return res.render('message_send.pug', { user: req.user, to_id: message.from_id, to_name: message.from_name, subject: message.subject.startsWith("Re: ") ? message.subject : "Re: " + message.subject, body: quote_body(message), + friends, }) }) @@ -1416,13 +1481,11 @@ app.get('/join/:game_id', must_be_logged_in, function (req, res) { let roles = get_game_roles(game.title_id, game.scenario, game.options) let players = SQL_SELECT_PLAYERS_JOIN.all(game_id) + let whitelist = SQL_SELECT_CONTACT_WHITELIST.all(req.user.user_id) + let blacklist = SQL_SELECT_CONTACT_BLACKLIST.all(req.user.user_id) let ready = (game.status === 0) && is_game_ready(game.title_id, game.scenario, game.options, players) res.render('join.pug', { - user: req.user, - game: game, - roles: roles, - players: players, - ready: ready, + user: req.user, game, roles, players, ready, whitelist, blacklist }) }) diff --git a/views/contacts.pug b/views/contacts.pug new file mode 100644 index 0000000..62840f9 --- /dev/null +++ b/views/contacts.pug @@ -0,0 +1,54 @@ +//- vim:ts=4:sw=4: +doctype html +html + head + include head + title Contacts + style. + table { min-width: 400px } + td a.red { text-decoration: none; color: brown; font-size: 14px; margin-left: 8px; } + td a.blue { text-decoration: none; color: black; font-size: 16px; margin-left: 8px; } + td a.blue:hover { color: blue; } + body + include header + article + + h1 Friends & Enemies + + table + thead + tr + th Friends + th Last seen + th + tbody + each who in friends + tr + td + a(href="/user/"+who.name)= who.name + td= who.atime + td.r + a.blue(href="/message/send/"+who.name) 📝 + a.red(href="/contact/remove/"+who.name) ❌ + else + tr + td Nobody + + if enemies.length > 0 + p + + table + thead + tr + th Blacklist + th + tbody + each who in enemies + tr + td + a(href="/user/"+who.name)= who.name + td.r + a.red(href="/contact/remove/"+who.name) ❌ + else + tr + td Nobody diff --git a/views/header.pug b/views/header.pug index a0e9707..61f5250 100644 --- a/views/header.pug +++ b/views/header.pug @@ -10,6 +10,7 @@ header a(href="/games/active") Games (#{user.active}) else a(href="/games/active") Games + a(href="/contacts") Friends if user.unread > 0 a(href="/inbox") Inbox (#{user.unread}) else diff --git a/views/join.pug b/views/join.pug index 7a929d1..ab53ddc 100644 --- a/views/join.pug +++ b/views/join.pug @@ -10,17 +10,21 @@ html style. table { min-width: 0; } th,td { border: 1px solid black; } - td a.red { text-decoration: none; color: brown; font-size: 14px; float: right; } + td a.red { text-decoration: none; color: brown; font-size: 15px; float: right; } td a { text-decoration: underline; color: blue; } th { white-space: nowrap; background-color: gainsboro; } td { width: 180px; background-color: white; } #message { background-color: whitesmoke; } .hide { display: none; } + td.enemy { background-color: #f66 } + td.enemy::before { content: "\1f6ab "; color: #000; font-size: 15px; } script. let game = !{ JSON.stringify(game) } let roles = !{ JSON.stringify(roles) } let players = !{ JSON.stringify(players) } let user_id = !{ user.user_id } + let whitelist = !{ JSON.stringify(whitelist) } + let blacklist = !{ JSON.stringify(blacklist) } let ready = !{ ready } script(src="/join.js") body diff --git a/views/message_send.pug b/views/message_send.pug index c573ef6..48e7b64 100644 --- a/views/message_send.pug +++ b/views/message_send.pug @@ -33,7 +33,11 @@ html value=to_name onpress="return next(event,'#subject')" autofocus=(to_name === "") + list="friends" ) + datalist(id="friends") + each who in friends + option= who p Subject: br diff --git a/views/user.pug b/views/user.pug index 4c26716..545d1ab 100644 --- a/views/user.pug +++ b/views/user.pug @@ -25,9 +25,19 @@ html p Member since #{who.ctime}. p Last seen #{who.atime}. - if user + + if user && (who.user_id !== user.user_id) p a(href="/message/send/"+who.name) Send message + br + if relation > 0 + a(href="/contact/remove-user/"+who.name) Remove from friends + else if relation < 0 + a(href="/contact/remove-user/"+who.name) Remove from blacklist + else + a(href="/contact/add-friend/"+who.name) Add to friends + br + a(href="/contact/add-enemy/"+who.name) Blacklist user if open_games.length > 0 h2 Open |