sentences model done

This commit is contained in:
JasterV 2021-07-24 14:07:44 +02:00
parent e557ff9a06
commit c36552bd46
17 changed files with 145 additions and 13 deletions

View file

@ -0,0 +1 @@
API_SECRET=

2
.gitignore vendored
View file

@ -124,4 +124,6 @@ dist
.firebase.json .firebase.json
.env
# End of https://www.toptal.com/developers/gitignore/api/node # End of https://www.toptal.com/developers/gitignore/api/node

View file

@ -21,19 +21,20 @@ const DATA_FILE_PATH = 'scripts/sentences.jsonl.txt';
}); });
console.log('Reading sentences.jsonl.txt') console.log('Reading sentences.jsonl.txt')
console.log('Importing data to firebase...')
rl.on('line', async (line) => { rl.on('line', async (line) => {
const sentence = JSON.parse(line) const sentence = JSON.parse(line)
const sentencesRef = db.collection('sentences'); const sentencesRef = db.collection('sentences');
const categoriesRef = db.collection('categories'); const entry = Object.entries(sentence.cats).find((elem) => elem['1'] == 1)
if(!entry) {
const result = await sentencesRef.add({ console.log('Sentence: ', sentence, ' does not have a category')
text: sentence.text return
}) }
await sentencesRef.add({
await categoriesRef.add({ text: sentence.text,
sentenceId: result.id, category: entry['0']
...sentence.cats
}) })
}); });
console.log('Done!')
})(); })();

View file

@ -1,5 +1,7 @@
import config from '../config' import config from '../config'
import db from '../db'
declare global { declare global {
export type Config = typeof config export type Config = typeof config
export type DB = typeof db
} }

View file

@ -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()
}
}

30
src/api/router.ts Normal file
View file

@ -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

View file

@ -1,11 +1,10 @@
import express from 'express' import express from 'express'
import router from './router'
const app = express() const app = express()
app.use(express.json()) app.use(express.json())
app.get('/dummy/hi', (_req, res) => { app.use('/api/v1/sentences', router)
res.json({success: true, data: 'Hello, World'})
})
export default app export default app

View file

@ -5,11 +5,15 @@ dotenv.config()
const { const {
NODE_ENV = "development", NODE_ENV = "development",
PORT = 8080, PORT = 8080,
API_SECRET,
} = process.env; } = process.env;
if(!API_SECRET) throw new Error('API_SECRET required but not found')
const config = { const config = {
env: NODE_ENV, env: NODE_ENV,
port: PORT port: PORT,
secret: API_SECRET
}; };
export default config; export default config;

15
src/errors.ts Normal file
View file

@ -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) }
}

View file

@ -0,0 +1,10 @@
export interface ListSentencesOptions {
orderBy?: string,
order?: 'asc' | 'desc',
page?: number
}
export interface UpdateSentenceOptions {
text?: string,
category?: string
}

View file

@ -0,0 +1,5 @@
export interface Sentence {
id: string,
text: string,
category: string
}

View file

@ -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<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[]> {
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<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> {
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