From c36552bd46f792d002ee205f3eb20b4d7861391d Mon Sep 17 00:00:00 2001 From: JasterV Date: Sat, 24 Jul 2021 14:07:44 +0200 Subject: [PATCH] sentences model done --- .env.example | 1 + .gitignore | 2 + scripts/upload_dataset.js | 19 ++++---- src/@types/global.d.ts | 2 + .../createSentenceController.ts} | 0 .../deleteSentenceController.ts} | 0 src/api/controllers/getSentenceController.ts | 0 .../controllers/listSentencesController.ts | 0 .../controllers/updateSentenceController.ts | 0 src/api/middlewares/authMiddleware.ts | 19 ++++++++ src/api/router.ts | 30 +++++++++++++ src/api/server.ts | 5 +-- src/config/index.ts | 6 ++- src/errors.ts | 15 +++++++ src/interfaces/queryOptions.ts | 10 +++++ src/interfaces/sentence.ts | 5 +++ src/models/sentencesModel.ts | 44 +++++++++++++++++++ 17 files changed, 145 insertions(+), 13 deletions(-) rename src/api/{middlewares/index.ts => controllers/createSentenceController.ts} (100%) rename src/api/{routers/index.ts => controllers/deleteSentenceController.ts} (100%) create mode 100644 src/api/controllers/getSentenceController.ts create mode 100644 src/api/controllers/listSentencesController.ts create mode 100644 src/api/controllers/updateSentenceController.ts create mode 100644 src/api/middlewares/authMiddleware.ts create mode 100644 src/api/router.ts create mode 100644 src/errors.ts create mode 100644 src/interfaces/queryOptions.ts create mode 100644 src/interfaces/sentence.ts create mode 100644 src/models/sentencesModel.ts diff --git a/.env.example b/.env.example index e69de29..8d9a38b 100644 --- a/.env.example +++ b/.env.example @@ -0,0 +1 @@ +API_SECRET= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 09cc5de..3a9de96 100644 --- a/.gitignore +++ b/.gitignore @@ -124,4 +124,6 @@ dist .firebase.json +.env + # End of https://www.toptal.com/developers/gitignore/api/node diff --git a/scripts/upload_dataset.js b/scripts/upload_dataset.js index 9d3ede6..43248ed 100644 --- a/scripts/upload_dataset.js +++ b/scripts/upload_dataset.js @@ -21,19 +21,20 @@ const DATA_FILE_PATH = 'scripts/sentences.jsonl.txt'; }); console.log('Reading sentences.jsonl.txt') + console.log('Importing data to firebase...') rl.on('line', async (line) => { const sentence = JSON.parse(line) const sentencesRef = db.collection('sentences'); - const categoriesRef = db.collection('categories'); - - const result = await sentencesRef.add({ - text: sentence.text - }) - - await categoriesRef.add({ - sentenceId: result.id, - ...sentence.cats + const entry = Object.entries(sentence.cats).find((elem) => elem['1'] == 1) + if(!entry) { + console.log('Sentence: ', sentence, ' does not have a category') + return + } + await sentencesRef.add({ + text: sentence.text, + category: entry['0'] }) }); + console.log('Done!') })(); diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index dd5f806..000929a 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -1,5 +1,7 @@ import config from '../config' +import db from '../db' declare global { export type Config = typeof config + export type DB = typeof db } diff --git a/src/api/middlewares/index.ts b/src/api/controllers/createSentenceController.ts similarity index 100% rename from src/api/middlewares/index.ts rename to src/api/controllers/createSentenceController.ts diff --git a/src/api/routers/index.ts b/src/api/controllers/deleteSentenceController.ts similarity index 100% rename from src/api/routers/index.ts rename to src/api/controllers/deleteSentenceController.ts diff --git a/src/api/controllers/getSentenceController.ts b/src/api/controllers/getSentenceController.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/api/controllers/listSentencesController.ts b/src/api/controllers/listSentencesController.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/api/controllers/updateSentenceController.ts b/src/api/controllers/updateSentenceController.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/api/middlewares/authMiddleware.ts b/src/api/middlewares/authMiddleware.ts new file mode 100644 index 0000000..b7c6b0c --- /dev/null +++ b/src/api/middlewares/authMiddleware.ts @@ -0,0 +1,19 @@ +import { Request, Response, NextFunction } from 'express' + +export default (secret: string) => { + return (req: Request, res: Response, next: NextFunction) => { + const authHeader = req.header('Authorization') + if (!authHeader) return res.status(401).json({ + success: false, + msg: 'Unauthorized' + }) + const token = authHeader.split('Bearer ')[1] + if(token !== secret) { + return res.status(401).json({ + success: false, + msg: 'Unauthorized' + }) + } + return next() + } +} \ No newline at end of file diff --git a/src/api/router.ts b/src/api/router.ts new file mode 100644 index 0000000..1c71d4f --- /dev/null +++ b/src/api/router.ts @@ -0,0 +1,30 @@ +import { Router } from 'express' +import config from 'src/config' +import authMiddleware from './middlewares/authMiddleware' + +const router = Router() + +router.use(authMiddleware(config.secret)) + +router.get('/list', (req, res) => { + console.log('hi') +}) + +router.get('/:id', (req, res) => { + console.log('hi') +}) + +router.post('/', (req, res) => { + console.log('hi') +}) + +router.put('/:id', (req, res) => { + console.log('hi') +}) + +router.delete('/:id', (req, res) => { + console.log('hi') +}) + +export default router + diff --git a/src/api/server.ts b/src/api/server.ts index 8ad117f..a6cf391 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -1,11 +1,10 @@ import express from 'express' +import router from './router' const app = express() app.use(express.json()) -app.get('/dummy/hi', (_req, res) => { - res.json({success: true, data: 'Hello, World'}) -}) +app.use('/api/v1/sentences', router) export default app \ No newline at end of file diff --git a/src/config/index.ts b/src/config/index.ts index 0041952..ebbccf6 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -5,11 +5,15 @@ dotenv.config() const { NODE_ENV = "development", PORT = 8080, + API_SECRET, } = process.env; +if(!API_SECRET) throw new Error('API_SECRET required but not found') + const config = { env: NODE_ENV, - port: PORT + port: PORT, + secret: API_SECRET }; export default config; \ No newline at end of file diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..a8910ad --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,15 @@ +export class ApiError extends Error { + constructor(description: string) { + super(description) + Object.setPrototypeOf(this, new.target.prototype) // restore prototype chain + Error.captureStackTrace(this) + } +} + +export class NotFoundError extends ApiError { + constructor(description: string) { super(description) } +} + +export class DatabaseError extends ApiError { + constructor(description: string) { super(description) } +} \ No newline at end of file diff --git a/src/interfaces/queryOptions.ts b/src/interfaces/queryOptions.ts new file mode 100644 index 0000000..4bbc29a --- /dev/null +++ b/src/interfaces/queryOptions.ts @@ -0,0 +1,10 @@ +export interface ListSentencesOptions { + orderBy?: string, + order?: 'asc' | 'desc', + page?: number +} + +export interface UpdateSentenceOptions { + text?: string, + category?: string +} \ No newline at end of file diff --git a/src/interfaces/sentence.ts b/src/interfaces/sentence.ts new file mode 100644 index 0000000..5d63b36 --- /dev/null +++ b/src/interfaces/sentence.ts @@ -0,0 +1,5 @@ +export interface Sentence { + id: string, + text: string, + category: string +} \ No newline at end of file diff --git a/src/models/sentencesModel.ts b/src/models/sentencesModel.ts new file mode 100644 index 0000000..2d06db3 --- /dev/null +++ b/src/models/sentencesModel.ts @@ -0,0 +1,44 @@ +import { NotFoundError } from "src/errors"; +import { ListSentencesOptions, UpdateSentenceOptions } from "src/interfaces/queryOptions" +import { Sentence } from "src/interfaces/sentence" + +const model = (db: DB) => { + const PAGE_LIMIT = 20 + + async function getSentence(id: string): Promise { + const sentenceRaw = await db.collection('sentence').doc(id).get(); + if (!sentenceRaw.exists) throw new NotFoundError(`Sentence with id ${id} not found`) + const sentence: Sentence = { id, ...sentenceRaw.data() } as Sentence + return sentence + } + + async function listSentence(options?: ListSentencesOptions): Promise { + const { page = 0, orderBy = null, order = 'desc' } = options || {} + const sentences: Sentence[] = [] + let query = db.collection('sentences'); + // order by property + if (orderBy) query = query.orderBy(orderBy, order) as any; + // query and return + const rawResult = await query.startAt(page * PAGE_LIMIT).limit(PAGE_LIMIT).get() + rawResult.forEach((doc) => sentences.push({ id: doc.id, ...doc.data() } as Sentence)) + return sentences + } + + async function deleteSentence(id: string): Promise { + const sentenceRaw = await db.collection('sentence').doc(id).get(); + if (!sentenceRaw.exists) throw new NotFoundError(`Sentence with id ${id} not found`) + await sentenceRaw.ref.delete(); + return id + } + + async function updateSentence(id: string, data: UpdateSentenceOptions): Promise { + const sentenceRaw = await db.collection('sentence').doc(id).get(); + if (!sentenceRaw.exists) throw new NotFoundError(`Sentence with id ${id} not found`) + await sentenceRaw.ref.update(data); + return { id, ...sentenceRaw.data() } as Sentence + } + + return { getSentence, listSentence, deleteSentence, updateSentence } +} + +export default model \ No newline at end of file