diff --git a/.env b/.env index d3e4ac4..a9403d6 100644 --- a/.env +++ b/.env @@ -1 +1,5 @@ -PORT = 8080 \ No newline at end of file +PORT = 8080 +PGUSER=alok-pg +PGHOST=localhost +PGDATABASE=okiba +PGPORT=5432 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7d888dd..5b9f813 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules/ public/ -src/test +test/ +data/ +.env \ No newline at end of file diff --git a/package.json b/package.json index 99201c0..391b60d 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,15 @@ "license": "ISC", "devDependencies": { "@types/express": "^4.17.13", - "@types/node": "^18.0.1", + "@types/node": "^18.0.3", + "@types/pg": "^8.6.5", "ts-node-dev": "^2.0.0", "typescript": "^4.7.4" }, "dependencies": { + "chalk": "^4.1.2", "dotenv": "^16.0.1", - "express": "^4.18.1" + "express": "^4.18.1", + "pg": "^8.7.3" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a6f6b6d..7b43667 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,20 +2,26 @@ lockfileVersion: 5.4 specifiers: '@types/express': ^4.17.13 - '@types/node': ^18.0.1 + '@types/node': ^18.0.3 + '@types/pg': ^8.6.5 + chalk: ^4.1.2 dotenv: ^16.0.1 express: ^4.18.1 + pg: ^8.7.3 ts-node-dev: ^2.0.0 typescript: ^4.7.4 dependencies: + chalk: 4.1.2 dotenv: 16.0.1 express: 4.18.1 + pg: 8.7.3 devDependencies: '@types/express': 4.17.13 - '@types/node': 18.0.1 - ts-node-dev: 2.0.0_qqrtuuv3y2pz7xoxv47yka6pmi + '@types/node': 18.0.3 + '@types/pg': 8.6.5 + ts-node-dev: 2.0.0_fxk5i3xm3ivo7fjwhcizcinpla typescript: 4.7.4 packages: @@ -27,8 +33,8 @@ packages: '@jridgewell/trace-mapping': 0.3.9 dev: true - /@jridgewell/resolve-uri/3.0.8: - resolution: {integrity: sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==} + /@jridgewell/resolve-uri/3.1.0: + resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} dev: true @@ -39,7 +45,7 @@ packages: /@jridgewell/trace-mapping/0.3.9: resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} dependencies: - '@jridgewell/resolve-uri': 3.0.8 + '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 dev: true @@ -63,19 +69,19 @@ packages: resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==} dependencies: '@types/connect': 3.4.35 - '@types/node': 18.0.1 + '@types/node': 18.0.3 dev: true /@types/connect/3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 18.0.1 + '@types/node': 18.0.3 dev: true /@types/express-serve-static-core/4.17.29: resolution: {integrity: sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q==} dependencies: - '@types/node': 18.0.1 + '@types/node': 18.0.3 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 dev: true @@ -93,8 +99,16 @@ packages: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} dev: true - /@types/node/18.0.1: - resolution: {integrity: sha512-CmR8+Tsy95hhwtZBKJBs0/FFq4XX7sDZHlGGf+0q+BRZfMbOTkzkj0AFAuTyXbObDIoanaBBW0+KEW+m3N16Wg==} + /@types/node/18.0.3: + resolution: {integrity: sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==} + dev: true + + /@types/pg/8.6.5: + resolution: {integrity: sha512-tOkGtAqRVkHa/PVZicq67zuujI4Oorfglsr2IbKofDwBSysnaqSx7W1mDqFqdkGE6Fbgh+PZAl0r/BWON/mozw==} + dependencies: + '@types/node': 18.0.3 + pg-protocol: 1.5.0 + pg-types: 2.2.0 dev: true /@types/qs/6.9.7: @@ -109,7 +123,7 @@ packages: resolution: {integrity: sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==} dependencies: '@types/mime': 1.3.2 - '@types/node': 18.0.1 + '@types/node': 18.0.3 dev: true /@types/strip-bom/3.0.0: @@ -139,6 +153,13 @@ packages: hasBin: true dev: true + /ansi-styles/4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + dev: false + /anymatch/3.1.2: resolution: {integrity: sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==} engines: {node: '>= 8'} @@ -202,6 +223,11 @@ packages: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} dev: true + /buffer-writer/2.0.0: + resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} + engines: {node: '>=4'} + dev: false + /bytes/3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} @@ -214,6 +240,14 @@ packages: get-intrinsic: 1.1.2 dev: false + /chalk/4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: false + /chokidar/3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -229,6 +263,17 @@ packages: fsevents: 2.3.2 dev: true + /color-convert/2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + dev: false + + /color-name/1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + dev: false + /concat-map/0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true @@ -425,6 +470,11 @@ packages: path-is-absolute: 1.0.1 dev: true + /has-flag/4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: false + /has-symbols/1.0.3: resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} engines: {node: '>= 0.4'} @@ -586,6 +636,10 @@ packages: wrappy: 1.0.2 dev: true + /packet-reader/1.0.0: + resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} + dev: false + /parseurl/1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -604,11 +658,82 @@ packages: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: false + /pg-connection-string/2.5.0: + resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==} + dev: false + + /pg-int8/1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + + /pg-pool/3.5.1_pg@8.7.3: + resolution: {integrity: sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.7.3 + dev: false + + /pg-protocol/1.5.0: + resolution: {integrity: sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==} + + /pg-types/2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + + /pg/8.7.3: + resolution: {integrity: sha512-HPmH4GH4H3AOprDJOazoIcpI49XFsHCe8xlrjHkWiapdbHK+HLtbm/GQzXYAZwmPju/kzKhjaSfMACG+8cgJcw==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=2.0.0' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-connection-string: 2.5.0 + pg-pool: 3.5.1_pg@8.7.3 + pg-protocol: 1.5.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + dev: false + + /pgpass/1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + dependencies: + split2: 4.1.0 + dev: false + /picomatch/2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} dev: true + /postgres-array/2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + + /postgres-bytea/1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + + /postgres-date/1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + + /postgres-interval/1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + dependencies: + xtend: 4.0.2 + /proxy-addr/2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -727,6 +852,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /split2/4.1.0: + resolution: {integrity: sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==} + engines: {node: '>= 10.x'} + dev: false + /statuses/2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -742,6 +872,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /supports-color/7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: false + /supports-preserve-symlinks-flag/1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -764,7 +901,7 @@ packages: hasBin: true dev: true - /ts-node-dev/2.0.0_qqrtuuv3y2pz7xoxv47yka6pmi: + /ts-node-dev/2.0.0_fxk5i3xm3ivo7fjwhcizcinpla: resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==} engines: {node: '>=0.8.0'} hasBin: true @@ -783,7 +920,7 @@ packages: rimraf: 2.7.1 source-map-support: 0.5.21 tree-kill: 1.2.2 - ts-node: 10.8.2_qqrtuuv3y2pz7xoxv47yka6pmi + ts-node: 10.8.2_fxk5i3xm3ivo7fjwhcizcinpla tsconfig: 7.0.0 typescript: 4.7.4 transitivePeerDependencies: @@ -792,7 +929,7 @@ packages: - '@types/node' dev: true - /ts-node/10.8.2_qqrtuuv3y2pz7xoxv47yka6pmi: + /ts-node/10.8.2_fxk5i3xm3ivo7fjwhcizcinpla: resolution: {integrity: sha512-LYdGnoGddf1D6v8REPtIH+5iq/gTDuZqv2/UJUU7tKjuEU8xVZorBM+buCGNjj+pGEud+sOoM4CX3/YzINpENA==} hasBin: true peerDependencies: @@ -811,7 +948,7 @@ packages: '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.3 - '@types/node': 18.0.1 + '@types/node': 18.0.3 acorn: 8.7.1 acorn-walk: 8.2.0 arg: 4.1.3 @@ -872,7 +1009,6 @@ packages: /xtend/4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - dev: true /yn/3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} diff --git a/src/db/bin.ts b/src/db/bin.ts new file mode 100644 index 0000000..1f7a885 --- /dev/null +++ b/src/db/bin.ts @@ -0,0 +1,21 @@ +import { Pool, QueryResult } from "pg"; +import { Word } from "../utils/types"; +import { Query } from "./queries"; + +// query wrappers +export const getAvailableWord = async (db: Pool): Promise => { + const data: void | QueryResult = await db + .query(Query.getAvailableWord) + .catch((err) => err); + + if (data != undefined) { + let word = data.rows[0]; + return word; + } + + return undefined; +}; + +export const setWordTaken = async (db: Pool, id: number) => { + await db.query(Query.setWordTaken(id)).catch((err) => err); +}; diff --git a/src/db/queries.ts b/src/db/queries.ts new file mode 100644 index 0000000..8c720fd --- /dev/null +++ b/src/db/queries.ts @@ -0,0 +1,9 @@ +// query strings +export const Query = { + getAvailableWord: + "SELECT * FROM words WHERE taken = 'f' ORDER BY random() LIMIT 1;", + + setWordTaken(id: number) { + return `UPDATE words SET taken = 't' WHERE id = ${id}`; + }, +}; diff --git a/src/main.ts b/src/main.ts index 7e16203..867ce0e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,15 +1,37 @@ import express, { Response, Request, Express } from "express"; import dotenv from "dotenv"; +import BinRouter from "./routes/bin"; +import { Client, Pool } from "pg"; +import { createDataDir, errorHandler, logError, logSuccess } from "./utils"; dotenv.config(); -const app: Express = express(); +createDataDir(); +// init db +const pool = new Pool(); +const client = new Client(); + +client + .connect() + .then(() => { + logSuccess("Connected to database!"); + }) + .catch((err: Error) => { + logError(`Could not connect to database! \n\n${err.message}`); + }); + +// start server +const app: Express = express(); const port = process.env.PORT; +app.use(express.text()); + app.get("/", (req: Request, res: Response) => { res.send("Hello, World!"); }); +app.use("/bin/", BinRouter(pool)); +app.use(errorHandler); app.listen(port, () => { - console.log(`Server started on port ${port}`); + logSuccess(`Server started on port ${port}`); }); diff --git a/src/routes/bin.ts b/src/routes/bin.ts new file mode 100644 index 0000000..89dad87 --- /dev/null +++ b/src/routes/bin.ts @@ -0,0 +1,36 @@ +import { Router, Request, Response, NextFunction } from "express"; +import { Pool, QueryResult } from "pg"; +import fs from "fs"; +import path from "path"; +import { Word } from "../utils/types"; +import { projectRoot } from "../utils"; +import { getAvailableWord, setWordTaken } from "../db/bin"; + +export default function BinRouter(db: Pool) { + const router: Router = Router(); + + router.post("/paste", async (req: Request, res: Response) => { + let body: string = req.body; + + const word: Word | undefined = await getAvailableWord(db); + + if (word != undefined) { + await fs.writeFile( + path.join(projectRoot, "data", word.val + ".txt"), + body, + (err) => err + ); + + setWordTaken(db, word.id); + console.log(word.id); + return res.status(200).json({ + endpoint: word.val, + message: "Code pasted successfully!", + }); + } + + res.status(500).json({ message: "Something went wrong" }); + }); + + return router; +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..4a86448 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,24 @@ +import chalk from "chalk"; +import { ErrorRequestHandler } from "express"; +import path from "path"; +import fs from "fs"; + +// logs +export const logError = (msg: string) => console.error(chalk.bold.red(msg)); +export const logSuccess = (msg: string) => console.log(chalk.bold.green(msg)); + +export const projectRoot = path.join(__dirname, "..", ".."); + +export const errorHandler: ErrorRequestHandler = (err, _, res, __) => { + logError(err); + + return res.status(500).json({ message: "Internal server error!" }); +}; + +export const createDataDir = () => { + let dir = path.join(projectRoot, "data"); + + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } +}; diff --git a/src/utils/types.ts b/src/utils/types.ts new file mode 100644 index 0000000..bebfa4d --- /dev/null +++ b/src/utils/types.ts @@ -0,0 +1,5 @@ +export type Word = { + id: number; + val: string; + taken: boolean; +};