diff --git a/.env b/.env index 7bd7cb6..258cfc1 100644 --- a/.env +++ b/.env @@ -2,4 +2,5 @@ TS_NODE_IGNORE="false" TS_NODE_FILES="true" REDIS_URL="redis://localhost:6379" -DEBUG="true" \ No newline at end of file +DEBUG="true" +PORT="3001" \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index d531bd2..6a8046b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -31,4 +31,4 @@ module.exports = { '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-unsafe-return': 'off', }, -}; +} diff --git a/package.json b/package.json index b16809a..481615d 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "clean": "rm -rf ./lib/", "cm": "cz", "lint": "eslint ./src/ --fix", + "server": "ts-node ./src/server.ts", "prepare": "husky install", "benchmark": "bash ./bench/run.sh", "semantic-release": "semantic-release", diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..b1eb285 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,96 @@ +import http from 'http' +import { Database } from './database' + +const hostname = '127.0.0.1' +const port = parseInt(process.env['PORT'] || '3000') +const args = process.argv.slice(2) +const databaseName = args[0] +if (!databaseName) { + throw new Error('Provide a database name') +} +const db = new Database(databaseName) + +export type Response = { + actions: Record + entries: { id: string; 'delete this': string; value: ValueT }[] +} + +const ENTRY_PREFIX = '/entry/' + +async function respondTo(req: http.IncomingMessage, res: http.ServerResponse) { + const host = `http://${req.headers.host || ''}` + const url = new URL(req.url || '', host) + const tagPath = url.pathname.slice(1) + const offset = parseInt(url.searchParams.get('offset') || '0') + const shouldDelete = url.searchParams.get('method') === 'DELETE' + console.log( + `Requesting ${shouldDelete ? 'delete' : 'browse'} on path: ${tagPath}` + ) + + if (shouldDelete) { + return handleDelete(req, res) + } + + const absolutePath = host + tagPath + const entries = await db.filter({ + where: tagPath, + offset: offset, + limit: 40, + ordering: 'desc', + }) + const subTags = await db.tags({ where: tagPath }) + const count = await db.count({ where: tagPath }) + + // Create actions for the UI + const actions: Record = { + [`delete all ${count}`]: absolutePath + '/' + tagPath + '?method=DELETE', + } + for (const subTag of subTags) { + const tagParts = subTag.split('/') + const label = 'browse ' + decodeURIComponent(tagParts[tagParts.length - 1]) + actions[label] = absolutePath + '/' + subTag + } + + return { + total: count, + actions, + entries: entries.map(e => ({ + 'delete this': absolutePath + ENTRY_PREFIX + e.id + '?method=DELETE', + ...e, + })), + } +} + +async function handleDelete( + req: http.IncomingMessage, + res: http.ServerResponse +) { + const host = `http://${req.headers.host || ''}` + const url = new URL(req.url || '', host) + const tagPath = url.pathname.slice(1) + const entryPrefix = ENTRY_PREFIX.substring(1) + res.statusCode = 307 + if (tagPath.startsWith(entryPrefix)) { + // Deleting a single entry + await db.delete(tagPath.substring(entryPrefix.length)) + res.setHeader('Location', '/') + } else { + // Deleting many + await db.clear({ where: tagPath }) + res.setHeader('Location', '/' + tagPath) + } + return null +} + +// Server setup + +// eslint-disable-next-line @typescript-eslint/no-misused-promises +const server = http.createServer(async (req, res) => { + res.statusCode = 200 + res.setHeader('Content-Type', 'application/json') + res.end(JSON.stringify(await respondTo(req, res))) +}) + +server.listen(port, hostname, () => { + console.log(`Server running at http://${hostname}:${port}/`) +}) diff --git a/tsconfig.json b/tsconfig.json index de9dd75..bbbe38f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, "lib": [ - "esnext" + "esnext", + "dom" ] /* Specify library files to be included in the compilation. */, "allowJs": true /* Allow javascript files to be compiled. */, "checkJs": true /* Report errors in .js files. */,