Skip to content

Commit

Permalink
feat: add server for viewing databases
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderatallah committed Feb 10, 2023
1 parent 768207f commit 309e693
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
TS_NODE_IGNORE="false"
TS_NODE_FILES="true"
REDIS_URL="redis://localhost:6379"
DEBUG="true"
DEBUG="true"
PORT="3001"
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ module.exports = {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
},
};
}
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
96 changes: 96 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -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<unknown>(databaseName)

export type Response<ValueT> = {
actions: Record<string, string>
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<string, string> = {
[`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}/`)
})
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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. */,
Expand Down

0 comments on commit 309e693

Please sign in to comment.