Skip to content

Commit 73418a6

Browse files
committed
basic frontend
1 parent 8f62dbf commit 73418a6

File tree

2 files changed

+125
-66
lines changed

2 files changed

+125
-66
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,8 @@ config.js
6565

6666
# Database files
6767
*.db
68+
69+
# Auto save files
70+
.#*
71+
*#
72+
*~

server.js

+120-66
Original file line numberDiff line numberDiff line change
@@ -15,59 +15,51 @@ var database;
1515
// exit on failure
1616
function prepareDatabase()
1717
{
18-
// load the database from file
19-
database = new sqlite3.Database(config.dbFilename, sqlite3.OPEN_READONLY, e => {
20-
if(e) exit(`Error opening database: ${e}`);
21-
});
18+
// load the database from file
19+
database = new sqlite3.Database(config.dbFilename, sqlite3.OPEN_READONLY, e => {
20+
if(e) exit(`Error opening database: ${e}`);
21+
});
2222
};
2323

2424
// close the database if its open
2525
function closeDatabase()
2626
{
27-
if(database) database.close().catch(console.error);
28-
}
29-
30-
// get a row in a table
31-
function selectRow(table, whereColumn, equals, callback)
32-
{
33-
// is this safe?
34-
const sql = `SELECT * FROM ${table} WHERE ${whereColumn} = "${equals}"`;
35-
database.all(sql, [], (e, rows) => {
36-
if(e) exit(`Error querying database: ${e}`);
37-
else callback(rows);
38-
});
27+
if(database) database.close().catch(console.error);
3928
}
4029

4130
// take a timestamp and format it
4231
function formatDate(timestamp)
4332
{
44-
return new Date(timestamp).toUTCString();
33+
return new Date(timestamp).toUTCString();
4534
}
4635

4736
// format a massege for code blocks
4837
function formatMessage(string)
4938
{
50-
const regex = /\`\`\`(([a-z]+)\n)?\n*([\s\S]*?)\n*\`\`\`/g;
51-
return string.replace(regex, (match, p1, p2, p3, offset, string) =>
52-
`<div class="code">${p2 ? `<span class="lang">#${p2}</span><br />` : ""}${p3}</div>`);
39+
const regex = /\`\`\`(([a-z]+)\n)?\n*([\s\S]*?)\n*\`\`\`/g;
40+
const s = string.replace(regex, (match, p1, p2, p3, offset, string) =>
41+
`<div class="code">${p2 ? `<span class="lang">#${p2}</span><br />` : ""}${p3.replace(/\n/g, "<br />")}</div>`);
42+
// also parse inline `code` and urls
43+
return s;
5344
}
5445

5546
// make html output for a message
56-
function makeMessagePage(message, revisions)
47+
function makeMessagePage(rows)
5748
{
58-
// just something for now, i don't know what we actually want to do
59-
const header = `
60-
<span class="field">Message ID:</span> <span class="value">${message.msgId}</span><br />
61-
<span class="field">Author:</span> <span class="value">${message.author}</span><br />
62-
<span class="field">Channel:</span> <span class="value">${message.channel}</span><br />
63-
`;
64-
const body = revisions.map(revision => `
49+
const body = rows.map(row => `
50+
<a class="perma" href="/code?msgId=${row.msgId}">permalink</a><br />
51+
Author: ${row.author}<br />
52+
Channel: ${row.channel}<br />` + row.revisions.map(revision => `
6553
<span class="timestamp">${formatDate(revision.date)}</span>
6654
<div class="messageBody">${formatMessage(revision.fullMessage)}</div>
67-
`).join("<br /><br />");
55+
`).join("<br /><br />")).join("<br /><br />");
6856

69-
return `
57+
return `
7058
<style>
59+
.perma
60+
{
61+
font-size: small;
62+
}
7163
.wrapper
7264
{
7365
width: 400px;
@@ -108,60 +100,122 @@ function makeMessagePage(message, revisions)
108100
</style>
109101
110102
<div class="wrapper">
111-
${header}
112-
<br />
113103
${body}
114104
</div>
115105
`;
116106
}
117107

118-
// request a message by id
119-
frontend.get("/code/:id", (request, response) => {
120-
// select the message by id
121-
selectRow("Message", "msgId", request.params.id, rows => {
122-
if(rows.length)
108+
// search page
109+
frontend.get("/", (request, response) => {
110+
const sql = `SELECT DISTINCT author FROM Message`;
111+
database.all(sql, [], (e, authors) => {
112+
if(e) exit(`Error querying database: ${e}`);
113+
else
114+
{
115+
const sql = `SELECT DISTINCT channel FROM Message`;
116+
database.all(sql, [], (e, channels) => {
117+
if(e) exit(`Error querying database: ${e}`);
118+
else
123119
{
124-
// assuming there's only one message per id
125-
// as there should be
126-
const message = rows[0];
127-
128-
// select all the revisions of this message
129-
selectRow("Content", "associatedMsg", message.msgId, rows => {
130-
if(rows.length)
131-
{
132-
// should we sort in sqlite instead of here?
133-
// sort the revisions by timestamp
134-
const sorted = rows.sort((a, b) => a.date - b.date);
135-
136-
// what should we actually be displaying here?
137-
// a pretty display for the user?
138-
// or some raw information?
139-
// also, should we just show the most recent revision?
140-
response.send(makeMessagePage(message, rows));
141-
}
142-
else response.send("No revisions found!");
143-
});
120+
response.send(`
121+
<form action="/code/">
122+
<label for="author">Author</label>
123+
<select id="author" name="author">
124+
<option value="">*</option>
125+
${authors.map(author => `<option value="${author.author}">${author.author}</option>`).join("")}
126+
</select><br />
127+
<label for="channel">Channel</label>
128+
<select id="channel" name="channel">
129+
<option value="">*</option>
130+
${channels.map(channel => `<option value="${channel.channel}">${channel.channel}</option>`).join("")}
131+
</select><br />
132+
<label for="from">From</label>
133+
<input id="from" name="from" type="date"><br />
134+
<label for="to">To</label>
135+
<input id="to" name="to" type="date"><br />
136+
<input type="submit">
137+
</form>
138+
`);
144139
}
145-
else response.send("Message not found!");
146-
});
140+
});
141+
}
142+
});
143+
});
144+
145+
function strtotime(string)
146+
{
147+
return new Date(string).getTime();
148+
}
149+
150+
// filter messages query
151+
frontend.get("/code/", (request, response) => {
152+
var sql = `SELECT * FROM Message`;
153+
const bindings = [];
154+
155+
var kw = "WHERE";
156+
if(request.query.author)
157+
{
158+
sql += ` ${kw} author = ?`;
159+
bindings.push(request.query.author);
160+
kw = "AND";
161+
}
162+
if(request.query.channel)
163+
{
164+
sql += ` ${kw} channel = ?`;
165+
bindings.push(request.query.channel);
166+
kw = "AND";
167+
}
168+
if(request.query.msgId)
169+
{
170+
sql += ` ${kw} msgId = ?`;
171+
bindings.push(request.query.msgId);
172+
kw = "AND";
173+
}
174+
175+
database.all(sql, bindings, (e, messageRows) => {
176+
if(e) exit(`Error querying database: ${e}`);
177+
else
178+
{
179+
const sql = `SELECT * FROM Content`;
180+
database.all(sql, [], (e, contentRows) => {
181+
if(e) exit(`Error querying database: ${e}`);
182+
else
183+
{
184+
var rows = messageRows;
185+
rows = rows.map(row => {
186+
row.revisions = contentRows
187+
.filter(revision => revision.associatedMsg === row.msgId);
188+
delete row.revisions.associatedMsg;
189+
return row;
190+
});
191+
if(request.query.from)
192+
rows = rows.filter(row => row.revisions[0].date >= strtotime(request.query.from));
193+
if(request.query.to)
194+
rows = rows.filter(row => row.revisions[0].date < strtotime(request.query.to));
195+
if(request.query.as === "json") response.send(rows);
196+
else response.send(makeMessagePage(rows));
197+
};
198+
});
199+
};
200+
});
147201
});
148202

149203
// exit with error message
150204
function exit(message)
151205
{
152-
console.error(message);
153-
closeDatabase();
154-
process.exit(1);
206+
console.error(message);
207+
closeDatabase();
208+
process.exit(1);
155209
}
156210

157211
// start the server!
158212
function main()
159213
{
160-
// open the database
161-
prepareDatabase();
214+
// open the database
215+
prepareDatabase();
162216

163-
// start the server listening
164-
frontend.listen(config.port, () => console.log(`Server running on port ${config.port}!`));
217+
// start the server listening
218+
frontend.listen(config.port, () => console.log(`Server running on port ${config.port}!`));
165219
}
166220

167221
// go!

0 commit comments

Comments
 (0)