summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--public/join.js25
-rw-r--r--schema.sql28
-rw-r--r--server.js77
-rw-r--r--views/contacts.pug54
-rw-r--r--views/header.pug1
-rw-r--r--views/join.pug6
-rw-r--r--views/message_send.pug4
-rw-r--r--views/user.pug12
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 = ""
}
}
diff --git a/schema.sql b/schema.sql
index 0613da4..4fcb505 100644
--- a/schema.sql
+++ b/schema.sql
@@ -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;
diff --git a/server.js b/server.js
index 4c3ca57..1197275 100644
--- a/server.js
+++ b/server.js
@@ -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 &amp; 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) &#x1f4dd;
+ a.red(href="/contact/remove/"+who.name) &#x274c;
+ 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) &#x274c;
+ 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