backend code done

This commit is contained in:
JasterV 2021-07-24 16:40:02 +02:00
parent 4c6065c50e
commit bb2bfd3991
26 changed files with 226 additions and 99 deletions

30
package-lock.json generated
View file

@ -12,10 +12,10 @@
"axios": "^0.21.1",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"fastest-validator": "^1.11.1",
"firebase-admin": "^9.11.0"
},
"devDependencies": {
"@types/convict": "^6.1.1",
"@types/express": "^4.17.13",
"@types/jest": "^26.0.24",
"@types/node": "^16.3.3",
@ -1481,15 +1481,6 @@
"@types/node": "*"
}
},
"node_modules/@types/convict": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@types/convict/-/convict-6.1.1.tgz",
"integrity": "sha512-R+JLaTvhsD06p4jyjUDtbd5xMtZTRE3c0iI+lrFWZogSVEjgTWPYwvJPVf+t92E+yrlbXa4X4Eg9ro6gPdUt4w==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/express": {
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
@ -3613,6 +3604,11 @@
"integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==",
"optional": true
},
"node_modules/fastest-validator": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/fastest-validator/-/fastest-validator-1.11.1.tgz",
"integrity": "sha512-y0pXBCgGfY3mqbBL9sn2LtAxfJICyOr5cuOnCjyiKg4VoXVmDaLBDwnnj4QC1pSY5B4upln5k8RyLszlLXzXlw=="
},
"node_modules/fastq": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz",
@ -9460,15 +9456,6 @@
"@types/node": "*"
}
},
"@types/convict": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@types/convict/-/convict-6.1.1.tgz",
"integrity": "sha512-R+JLaTvhsD06p4jyjUDtbd5xMtZTRE3c0iI+lrFWZogSVEjgTWPYwvJPVf+t92E+yrlbXa4X4Eg9ro6gPdUt4w==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/express": {
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz",
@ -11129,6 +11116,11 @@
"integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==",
"optional": true
},
"fastest-validator": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/fastest-validator/-/fastest-validator-1.11.1.tgz",
"integrity": "sha512-y0pXBCgGfY3mqbBL9sn2LtAxfJICyOr5cuOnCjyiKg4VoXVmDaLBDwnnj4QC1pSY5B4upln5k8RyLszlLXzXlw=="
},
"fastq": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.1.tgz",

View file

@ -22,7 +22,6 @@
"author": "Victor Martinez",
"license": "MIT",
"devDependencies": {
"@types/convict": "^6.1.1",
"@types/express": "^4.17.13",
"@types/jest": "^26.0.24",
"@types/node": "^16.3.3",
@ -41,6 +40,7 @@
"axios": "^0.21.1",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"fastest-validator": "^1.11.1",
"firebase-admin": "^9.11.0"
}
}

View file

@ -1,7 +1,18 @@
import { Request, Response, NextFunction } from 'express'
import { CrudModel } from '../../interfaces/crudModel'
import { Sentence } from '../../interfaces/sentence'
export const createSentenceController = () => {
return (_req: Request, _res: Response, _next: NextFunction) => {
export const createSentenceController = (model: CrudModel<Sentence>) => {
return async (req: Request, res: Response, next: NextFunction) => {
const { text, category } = req.body
try {
const id = await model.create({ text, category })
return res.status(201).json({
success: true,
data: id
})
} catch (err) {
return next(err)
}
}
}

View file

@ -1,7 +1,25 @@
import { Request, Response, NextFunction } from 'express'
import { NotFoundError } from '../../errors'
import { CrudModel } from '../../interfaces/crudModel'
import { Sentence } from '../../interfaces/sentence'
export const deleteSentenceController = () => {
return (_req: Request, _res: Response, _next: NextFunction) => {
export const deleteSentenceController = (model: CrudModel<Sentence>) => {
return async (req: Request, res: Response, next: NextFunction) => {
const { id } = req.params
try {
const deletedId = await model.del(id)
return res.status(201).json({
success: true,
data: deletedId
})
} catch (err) {
if(err instanceof NotFoundError) {
return res.status(404).json({
success: false,
msg: err.message
})
}
return next(err)
}
}
}

View file

@ -1,7 +1,25 @@
import { Request, Response, NextFunction } from 'express'
import { NotFoundError } from '../../errors'
import { CrudModel } from '../../interfaces/crudModel'
import { Sentence } from '../../interfaces/sentence'
export const getSentenceController = () => {
return (_req: Request, _res: Response, _next: NextFunction) => {
export const getSentenceController = (model: CrudModel<Sentence>) => {
return async (req: Request, res: Response, next: NextFunction) => {
const { id } = req.params
try {
const sentence: Sentence = await model.getById(id)
return res.status(201).json({
success: true,
data: sentence
})
} catch (err) {
if(err instanceof NotFoundError) {
return res.status(404).json({
success: false,
msg: err.message
})
}
return next(err)
}
}
}

View file

@ -1,7 +1,25 @@
import { Request, Response, NextFunction } from 'express'
import { NotFoundError } from '../../errors'
import { CrudModel } from '../../interfaces/crudModel'
import { Sentence } from '../../interfaces/sentence'
export const listSentencesController = () => {
return (_req: Request, _res: Response, _next: NextFunction) => {
export const listSentencesController = (model: CrudModel<Sentence>) => {
return async (req: Request, res: Response, next: NextFunction) => {
const { orderBy, order, page } = req.query
try {
const sentences: Sentence[] = await model.list({ orderBy, order, page })
return res.status(201).json({
success: true,
data: sentences
})
} catch (err) {
if (err instanceof NotFoundError) {
return res.status(404).json({
success: false,
msg: err.message
})
}
return next(err)
}
}
}

View file

@ -1,7 +1,26 @@
import { Request, Response, NextFunction } from 'express'
import { NotFoundError } from '../../errors'
import { CrudModel } from '../../interfaces/crudModel'
import { Sentence } from '../../interfaces/sentence'
export const updateSentenceController = () => {
return (_req: Request, _res: Response, _next: NextFunction) => {
export const updateSentenceController = (model: CrudModel<Sentence>) => {
return async (req: Request, res: Response, next: NextFunction) => {
const { id } = req.params
const { text, category } = req.body
try {
const sentence: Sentence = await model.update(id, { text, category })
return res.status(201).json({
success: true,
data: sentence
})
} catch (err) {
if (err instanceof NotFoundError) {
return res.status(404).json({
success: false,
msg: err.message
})
}
return next(err)
}
}
}

View file

@ -0,0 +1,16 @@
import { NextFunction, Request, Response } from "express"
import Validator from 'fastest-validator'
export const bodySchemaValidator = (schema: any, validator: Validator) => {
return async (req: Request, res: Response, next: NextFunction) => {
const body = req.body
const valid = await validator.validate(body, schema)
if(valid !== true) {
return res.status(400).json({
success: false,
errors: valid
})
}
return next()
}
}

View file

@ -0,0 +1,16 @@
import { NextFunction, Request, Response } from "express"
import Validator from 'fastest-validator'
export const querySchemaValidator = (schema: any, validator: Validator) => {
return async (req: Request, res: Response, next: NextFunction) => {
const query = req.query
const valid = await validator.validate(query, schema)
if(valid !== true) {
return res.status(400).json({
success: false,
errors: valid
})
}
return next()
}
}

View file

@ -1,19 +1,27 @@
import { bodySchemaValidator } from './middlewares/bodySchemaValidator'
import { querySchemaValidator } from './middlewares/querySchemaValidator'
import { createSchema, listSchema, updateSchema } from './schemas'
import { sentenceModel } from '../models/sentenceModel'
import Validator from 'fastest-validator'
import { Router } from 'express'
import {
createSentenceController,
updateSentenceController,
deleteSentenceController,
listSentencesController,
getSentenceController
createSentenceController,
updateSentenceController,
deleteSentenceController,
listSentencesController,
getSentenceController
} from './controllers'
import db from '../db'
const router = Router()
const validator = new Validator()
const model = sentenceModel(db)
router.get('/list', listSentencesController())
router.get('/:id', getSentenceController())
router.post('/', createSentenceController())
router.put('/:id', updateSentenceController())
router.delete('/:id', deleteSentenceController())
router.get('/list', querySchemaValidator(listSchema, validator), listSentencesController(model))
router.get('/:id', getSentenceController(model))
router.post('/', bodySchemaValidator(createSchema, validator), createSentenceController(model))
router.put('/:id', bodySchemaValidator(updateSchema, validator), updateSentenceController(model))
router.delete('/:id', deleteSentenceController(model))
export default router

View file

@ -0,0 +1,6 @@
import { ValidationSchema } from "fastest-validator";
export const createSchema: ValidationSchema = {
text: 'string',
category: 'string'
}

3
src/api/schemas/index.ts Normal file
View file

@ -0,0 +1,3 @@
export * from './listSchema'
export * from './createSchema'
export * from './updateSchema'

View file

@ -0,0 +1,11 @@
import { ValidationSchema } from "fastest-validator";
export const listSchema: ValidationSchema = {
orderBy: 'string',
order: {
optional: true,
type: 'equal',
values: ['asc', 'desc']
},
page: 'number|integer|positive|optional'
}

View file

@ -0,0 +1,6 @@
import { ValidationSchema } from "fastest-validator";
export const updateSchema: ValidationSchema = {
text: 'string|optional',
category: 'string|optional'
}

View file

@ -1,7 +1,7 @@
import express, { NextFunction, Request, Response } from 'express'
import config from 'src/config'
import { errorService } from 'src/services'
import authMiddleware from './middlewares/authMiddleware'
import { errorService } from '../services/errorService'
import config from '../config'
import router from './router'
const { handle } = errorService()

View file

@ -0,0 +1,7 @@
export interface CrudModel<T> {
create(data: any): Promise<string>;
getById(id: string): Promise<T>;
list(options?: any): Promise<T[]>;
del(id: string): Promise<string>;
update(id: string, data: any): Promise<T>;
}

View file

@ -7,4 +7,9 @@ export interface ListSentencesOptions {
export interface UpdateSentenceOptions {
text?: string,
category?: string
}
export interface CreateSentenceOptions {
text: string,
category: string
}

View file

@ -1,18 +1,24 @@
import { NotFoundError } from "src/errors";
import { ListSentencesOptions, UpdateSentenceOptions } from "src/interfaces/queryOptions"
import { Sentence } from "src/interfaces/sentence"
import { NotFoundError } from "../errors";
import { CrudModel } from "../interfaces/crudModel";
import { CreateSentenceOptions, ListSentencesOptions, UpdateSentenceOptions } from "../interfaces/queryOptions"
import { Sentence } from "../interfaces/sentence"
export const sentenceModel = (db: DB) => {
export const sentenceModel = (db: DB): CrudModel<Sentence> => {
const PAGE_LIMIT = 20
async function getSentence(id: string): Promise<Sentence> {
async function create(data: CreateSentenceOptions): Promise<string> {
const sentenceRaw = await db.collection('sentence').add(data);
return sentenceRaw.id
}
async function getById(id: string): Promise<Sentence> {
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<Sentence[]> {
async function list(options?: ListSentencesOptions): Promise<Sentence[]> {
const { page = 0, orderBy = null, order = 'desc' } = options || {}
const sentences: Sentence[] = []
let query = db.collection('sentences');
@ -24,19 +30,19 @@ export const sentenceModel = (db: DB) => {
return sentences
}
async function deleteSentence(id: string): Promise<string> {
async function del(id: string): Promise<string> {
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<Sentence> {
async function update(id: string, data: UpdateSentenceOptions): Promise<Sentence> {
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 }
return { create, getById, list, update, del }
}

View file

@ -1,3 +0,0 @@
export const createSentenceService = () => {
}

View file

@ -1,3 +0,0 @@
export const deleteSentenceService = () => {
}

View file

@ -1,5 +1,5 @@
import { Response } from 'express'
import { ApiError, DatabaseError, RequestError } from 'src/errors'
import { ApiError, DatabaseError, RequestError } from '../errors'
export const errorService = () => {

View file

@ -1,3 +0,0 @@
export const getSentenceService = () => {
}

View file

@ -1,5 +0,0 @@
export * from './createSentenceService'
export * from './deleteSentenceService'
export * from './listSentenceService'
export * from './updateSentenceService'
export * from './errorService'

View file

@ -1,3 +0,0 @@
export const listSentenceService = () => {
}

View file

@ -1,3 +0,0 @@
export const updateSentenceService = () => {
}

View file

@ -1,35 +1,22 @@
{
"compilerOptions": {
"target": "es2020",
"module": "es2020",
"lib": [
"es2020"
],
"skipLibCheck": true,
"target": "esnext",
"module": "commonjs",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"moduleResolution": "node",
"outDir": "dist",
"removeComments": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"esModuleInterop": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"resolveJsonModule": true,
"baseUrl": "."
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"exclude": [
"node_modules",
"**/*.test.ts"
],
"include": [
"./src/**/*.ts",
"./src/**/*.ts"
]
}