diff --git a/server/fetch-events-for-timestamp.js b/server/fetch-events-for-timestamp.js index 2da8475..7bf502e 100644 --- a/server/fetch-events-for-timestamp.js +++ b/server/fetch-events-for-timestamp.js @@ -1,6 +1,8 @@ const assert = require('assert'); -const path = require('path'); const fetch = require('node-fetch'); + +const urlJoin = require('./lib/url-join'); + const { matrixServerUrl } = require('../config.json'); const secrets = require('../secrets.json'); @@ -10,7 +12,7 @@ assert(matrixAccessToken); class HTTPResponseError extends Error { constructor(response, responseText, ...args) { super( - `HTTP Error Response: ${response.status} ${response.statusText}: ${responseText}\n\tURL=${response.url}`, + `HTTP Error Response: ${response.status} ${response.statusText}: ${responseText}\n URL=${response.url}`, ...args ); this.response = response; @@ -40,7 +42,10 @@ async function fetchEndpoint(endpoint) { } async function fetchEventsForTimestamp(roomId, ts) { - const timestampToEventEndpoint = path.join( + assert(roomId); + assert(ts); + + const timestampToEventEndpoint = urlJoin( matrixServerUrl, `_matrix/client/unstable/org.matrix.msc3030/rooms/${roomId}/timestamp_to_event?ts=${ts}&dir=f` ); @@ -50,14 +55,14 @@ async function fetchEventsForTimestamp(roomId, ts) { const eventIdForTimestamp = timestampToEventResData.event_id; assert(eventIdForTimestamp); - const contextEndpoint = path.join( + const contextEndpoint = urlJoin( matrixServerUrl, `_matrix/client/r0/rooms/${roomId}/context/${eventIdForTimestamp}?limit=0` ); const contextResData = await fetchEndpoint(contextEndpoint); //console.log('contextResData', contextResData); - const messagesEndpoint = path.join( + const messagesEndpoint = urlJoin( matrixServerUrl, `_matrix/client/r0/rooms/${roomId}/messages?from=${contextResData.start}&limit=50&filter={"lazy_load_members":true,"include_redundant_members":true}` ); diff --git a/server/express-async-handler.js b/server/lib/express-async-handler.js similarity index 100% rename from server/express-async-handler.js rename to server/lib/express-async-handler.js diff --git a/server/lib/url-join.js b/server/lib/url-join.js new file mode 100644 index 0000000..df50ff5 --- /dev/null +++ b/server/lib/url-join.js @@ -0,0 +1,9 @@ +const path = require('path'); + +// via https://javascript.plainenglish.io/how-to-safely-concatenate-url-with-node-js-f6527b623d5 +function urlJoin(baseUrl, ...pathParts) { + const fullUrl = new URL(path.join(...pathParts), baseUrl).toString(); + return fullUrl; +} + +module.exports = urlJoin; diff --git a/server/server1.js b/server/server1.js index 4894145..c8ad934 100644 --- a/server/server1.js +++ b/server/server1.js @@ -1,9 +1,15 @@ +const assert = require('assert'); const express = require('express'); -const asyncHandler = require('./express-async-handler'); +const asyncHandler = require('./lib/express-async-handler'); +const urlJoin = require('./lib/url-join'); const fetchEventsForTimestamp = require('./fetch-events-for-timestamp'); const renderHydrogenToString = require('./render-hydrogen-to-string'); +const config = require('../config.json'); +const basePath = config.basePath; +assert(basePath); + const app = express(); app.get('/style.css', async function (req, res) { @@ -12,20 +18,75 @@ app.get('/style.css', async function (req, res) { }); app.get( - '/', + '/:roomIdOrAlias/event/:eventId', asyncHandler(async function (req, res) { - const { events, stateEventMap } = await fetchEventsForTimestamp( - '!HBehERstyQBxyJDLfR:my.synapse.server', - new Date('2022-02-08').getTime() - ); + res.send('todo'); + }) +); + +app.get( + '/:roomIdOrAlias/date/:yyyy(\\d{4})/:mm(\\d{2})/:dd(\\d{2})/:hourRange(\\d\\d?-\\d\\d?)?', + asyncHandler(async function (req, res) { + const roomIdOrAlias = req.params.roomIdOrAlias; + assert(roomIdOrAlias.startsWith('!') || roomIdOrAlias.startsWith('#')); + + const yyyy = parseInt(req.params.yyyy, 10); + // Month is the only zero-based index in this group + const mm = parseInt(req.params.mm, 10) - 1; + const dd = parseInt(req.params.dd, 10); + + const hourRange = req.params.hourRange; + + let fromHour = 0; + let toHour = 0; + if (hourRange) { + const hourMatches = hourRange.match(/^(\d\d?)-(\d\d?)$/); + + if (!hourMatches) { + throw new StatusError(404, 'Hour was unable to be parsed'); + } + + fromHour = parseInt(hourMatches[1], 10); + toHour = parseInt(hourMatches[2], 10); + + if (Number.isNaN(fromHour) || fromHour < 0 || fromHour > 23) { + throw new StatusError(404, 'From hour can only be in range 0-23'); + } + + // Currently we force the range to always be 1 hour + // If the format isn't correct, redirect to the correct hour range + if (toHour !== fromHour + 1) { + res.redirect( + urlJoin( + basePath, + roomIdOrAlias, + 'date', + req.params.yyyy, + req.params.mm, + req.params.dd, + `${fromHour}-${fromHour + 1}` + ) + ); + return; + } + } + + const fromTimestamp = Date.UTC(yyyy, mm, dd, fromHour); + let toTimestamp = Date.UTC(yyyy, mm, dd + 1, fromHour); + if (hourRange) { + toTimestamp = Date.UTC(yyyy, mm, dd, toHour); + } + + const { events, stateEventMap } = await fetchEventsForTimestamp(roomIdOrAlias, fromTimestamp); const hydrogenHtmlOutput = await renderHydrogenToString(events, stateEventMap); + const styleUrl = urlJoin(basePath, 'style.css'); const pageHtml = ` - + ${hydrogenHtmlOutput}