mirror of
https://codeberg.org/JasterV/sentences-crud.git
synced 2026-04-26 18:10:05 +00:00
backend code done
This commit is contained in:
parent
4c6065c50e
commit
bb2bfd3991
26 changed files with 226 additions and 99 deletions
30
package-lock.json
generated
30
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/api/middlewares/bodySchemaValidator.ts
Normal file
16
src/api/middlewares/bodySchemaValidator.ts
Normal 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()
|
||||
}
|
||||
}
|
||||
16
src/api/middlewares/querySchemaValidator.ts
Normal file
16
src/api/middlewares/querySchemaValidator.ts
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,8 @@
|
|||
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,
|
||||
|
|
@ -6,14 +11,17 @@ import {
|
|||
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
|
||||
|
||||
|
|
|
|||
6
src/api/schemas/createSchema.ts
Normal file
6
src/api/schemas/createSchema.ts
Normal 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
3
src/api/schemas/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export * from './listSchema'
|
||||
export * from './createSchema'
|
||||
export * from './updateSchema'
|
||||
11
src/api/schemas/listSchema.ts
Normal file
11
src/api/schemas/listSchema.ts
Normal 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'
|
||||
}
|
||||
6
src/api/schemas/updateSchema.ts
Normal file
6
src/api/schemas/updateSchema.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import { ValidationSchema } from "fastest-validator";
|
||||
|
||||
export const updateSchema: ValidationSchema = {
|
||||
text: 'string|optional',
|
||||
category: 'string|optional'
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
7
src/interfaces/crudModel.ts
Normal file
7
src/interfaces/crudModel.ts
Normal 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>;
|
||||
}
|
||||
|
|
@ -8,3 +8,8 @@ export interface UpdateSentenceOptions {
|
|||
text?: string,
|
||||
category?: string
|
||||
}
|
||||
|
||||
export interface CreateSentenceOptions {
|
||||
text: string,
|
||||
category: string
|
||||
}
|
||||
|
|
@ -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 }
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export const createSentenceService = () => {
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export const deleteSentenceService = () => {
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { Response } from 'express'
|
||||
import { ApiError, DatabaseError, RequestError } from 'src/errors'
|
||||
import { ApiError, DatabaseError, RequestError } from '../errors'
|
||||
|
||||
export const errorService = () => {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
export const getSentenceService = () => {
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
export * from './createSentenceService'
|
||||
export * from './deleteSentenceService'
|
||||
export * from './listSentenceService'
|
||||
export * from './updateSentenceService'
|
||||
export * from './errorService'
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export const listSentenceService = () => {
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export const updateSentenceService = () => {
|
||||
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue