commit current backend
This commit is contained in:
		
							
								
								
									
										27
									
								
								slader-legacy-backend/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								slader-legacy-backend/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | { | ||||||
|  |   "name": "slader-legacy-backend", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "description": "", | ||||||
|  |   "main": "src/index.js", | ||||||
|  |   "scripts": { | ||||||
|  |     "test": "echo \"Error: no test specified\" && exit 1" | ||||||
|  |   }, | ||||||
|  |   "author": "", | ||||||
|  |   "license": "ISC", | ||||||
|  |   "dependencies": { | ||||||
|  |     "@types/better-sqlite3": "^5.4.3", | ||||||
|  |     "@types/cors": "^2.8.12", | ||||||
|  |     "@types/express": "^4.17.13", | ||||||
|  |     "@types/node": "^16.4.8", | ||||||
|  |     "better-sqlite3": "^7.4.3", | ||||||
|  |     "cors": "^2.8.5", | ||||||
|  |     "dotenv": "^10.0.0", | ||||||
|  |     "express": "^4.17.1", | ||||||
|  |     "express-async-handler": "^1.1.4", | ||||||
|  |     "express-validator": "^6.12.1", | ||||||
|  |     "reflect-metadata": "^0.1.13", | ||||||
|  |     "ts-node": "^10.1.0", | ||||||
|  |     "typedi": "^0.10.0", | ||||||
|  |     "typescript": "^4.3.5" | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										5
									
								
								slader-legacy-backend/src/config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								slader-legacy-backend/src/config.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | require('dotenv').config() | ||||||
|  | let appConfig = { | ||||||
|  |     port: parseInt(process.env.SL_PORT as any) | ||||||
|  | } | ||||||
|  | export {appConfig} | ||||||
							
								
								
									
										27
									
								
								slader-legacy-backend/src/entity-fill.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								slader-legacy-backend/src/entity-fill.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | import {IExercise, ITextbook, IToc} from "./types" | ||||||
|  |  | ||||||
|  | export function fillIToc(single: any) { | ||||||
|  |     return <IToc>{ | ||||||
|  |         section: single.section, | ||||||
|  |         count: single.count, | ||||||
|  |         pageBegin: single.pageBegin, | ||||||
|  |         pageEnd: single.pageEnd | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function fillITextbook(single: any) { | ||||||
|  |     return <ITextbook>{ | ||||||
|  |         title: single.title, | ||||||
|  |         quantity: single.quantity | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function fillIExercise(single: any) { | ||||||
|  |     return <IExercise>{ | ||||||
|  |         exercise: single.exercise, | ||||||
|  |         section: single.section, | ||||||
|  |         part: single.part, | ||||||
|  |         page: single.page, | ||||||
|  |         html: single.html | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								slader-legacy-backend/src/includes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								slader-legacy-backend/src/includes.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | import {validationResult} from "express-validator" | ||||||
|  | import {IResultJson} from "./types" | ||||||
|  | import express from "express" | ||||||
|  | import Database from "better-sqlite3" | ||||||
|  | import path from "path" | ||||||
|  |  | ||||||
|  | let db = Database(path.join(__dirname, '../slader.db'), {verbose: message => console.log('SQLITE3: ' + message)}) | ||||||
|  |  | ||||||
|  | export {db} | ||||||
|  | export const resultJson = { | ||||||
|  |     success(data: any) { | ||||||
|  |         return <IResultJson>{ | ||||||
|  |             status: true, | ||||||
|  |             data: data | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     error(data: any) { | ||||||
|  |         return <IResultJson>{ | ||||||
|  |             status: false, | ||||||
|  |             data: data | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function hasValidationErrors(req: express.Request, res: express.Response) { | ||||||
|  |     let errors = validationResult(req) | ||||||
|  |     if (!errors.isEmpty()) { | ||||||
|  |         res.json(resultJson.error(errors.array())) | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  |     return false | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function getTimestampInSeconds() { | ||||||
|  |     return Math.floor(Date.now() / 1000) | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								slader-legacy-backend/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								slader-legacy-backend/src/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | import express from 'express' | ||||||
|  | import cors from 'cors' | ||||||
|  | import {textbookRouter} from "./routers/textbook-router" | ||||||
|  | import {appConfig} from "./config" | ||||||
|  |  | ||||||
|  | let app = express() | ||||||
|  | app.use(cors()) | ||||||
|  |  | ||||||
|  | app.use('/textbook', textbookRouter) | ||||||
|  |  | ||||||
|  | app.listen(appConfig.port, () => { | ||||||
|  |     console.log('Server has started at port ' + appConfig.port) | ||||||
|  | }) | ||||||
							
								
								
									
										65
									
								
								slader-legacy-backend/src/models/textbook-model.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								slader-legacy-backend/src/models/textbook-model.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | |||||||
|  | import 'reflect-metadata' | ||||||
|  | import {Service} from "typedi" | ||||||
|  | import {db} from "../includes" | ||||||
|  | import {fillIExercise, fillITextbook, fillIToc} from "../entity-fill" | ||||||
|  | import {text} from "express" | ||||||
|  |  | ||||||
|  | @Service() | ||||||
|  | export class TextbookModel { | ||||||
|  |     existTextbook(textbook: string) { | ||||||
|  |         let raw = db.prepare('select rowid from solutions where textbook=? limit 1').get(textbook) | ||||||
|  |         return !!raw?.rowid | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     existSection(textbook: string, section: string) { | ||||||
|  |         let raw = db.prepare('select rowid from solutions where textbook=? and section=? limit 1').get(textbook, section) | ||||||
|  |         return !!raw?.rowid | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     existExercise(textbook: string, section: string, exercise: string) { | ||||||
|  |         let raw = db.prepare('select rowid from solutions ' + | ||||||
|  |             'where textbook=? and section=? and exercise=? limit 1').get(textbook, section, exercise) | ||||||
|  |         return !!raw?.rowid | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getTextbooks() { | ||||||
|  |         let raw = db.prepare('SELECT textbook as title,count(rowid) as quantity from solutions GROUP BY textbook').all() | ||||||
|  |         return raw.map(value => fillITextbook(value)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     findExercisesByTextbookSection(textbook: string, section: string) { | ||||||
|  |         let raw = db.prepare('SELECT exercise,section,part,page,html ' + | ||||||
|  |             'from solutions ' + | ||||||
|  |             'where textbook=? ' + | ||||||
|  |             'and section=? order by exercise asc').all(textbook, section) | ||||||
|  |         return raw.map(value => fillIExercise(value)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     findExercise(textbook: string, section: string, exercise: string) { | ||||||
|  |         let raw = db.prepare('SELECT exercise,section,part,page,html ' + | ||||||
|  |             'from solutions ' + | ||||||
|  |             'where textbook=? ' + | ||||||
|  |             'and section=? and exercise=?').get(textbook, section, exercise) | ||||||
|  |         return fillIExercise(raw) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     findExercisesByTextbookPage(textbook: string, page: number) { | ||||||
|  |         let raw = db.prepare('SELECT exercise,section,part,page,html ' + | ||||||
|  |             'from solutions ' + | ||||||
|  |             'where textbook=? ' + | ||||||
|  |             'and page=? order by exercise asc').all(textbook, page) | ||||||
|  |         return raw.map(value => fillIExercise(value)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     findTocByTextbook(textbook: string) { | ||||||
|  |         let raw = db.prepare('SELECT section, ' + | ||||||
|  |             'count(rowid) as count, ' + | ||||||
|  |             'min(page) as pageBegin, ' + | ||||||
|  |             'max(page) as pageEnd ' + | ||||||
|  |             'FROM "solutions" ' + | ||||||
|  |             'where textbook=? ' + | ||||||
|  |             'group by section ' + | ||||||
|  |             'order by pageBegin asc').all(textbook) | ||||||
|  |         return raw.map(value => fillIToc(value)) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								slader-legacy-backend/src/routers/textbook-router.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								slader-legacy-backend/src/routers/textbook-router.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | |||||||
|  | import 'reflect-metadata' | ||||||
|  | import express from "express" | ||||||
|  | import {param, query} from "express-validator" | ||||||
|  | import {Container} from "typedi" | ||||||
|  | import {TextbookModel} from "../models/textbook-model" | ||||||
|  | import expressAsyncHandler from "express-async-handler" | ||||||
|  | import {TextbookService} from "../services/textbook-service" | ||||||
|  | import {hasValidationErrors} from "../includes" | ||||||
|  |  | ||||||
|  | let textbookRouter = express.Router() | ||||||
|  | let textbookModel = Container.get(TextbookModel) | ||||||
|  | let textbookService = Container.get(TextbookService) | ||||||
|  | textbookRouter.get('/', | ||||||
|  |     expressAsyncHandler(async (req: express.Request, res: express.Response) => { | ||||||
|  |         res.json(await textbookService.listTextbooks()) | ||||||
|  |     }) | ||||||
|  | ) | ||||||
|  | textbookRouter.get('/:textbook/:section?/:exercise?', | ||||||
|  |     param('textbook').notEmpty().bail().custom(input => { | ||||||
|  |         if (!textbookModel.existTextbook(input)) { | ||||||
|  |             throw new Error('textbook not exist') | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |     }), | ||||||
|  |     query('page').optional().isInt({min: 1}), | ||||||
|  |     param('section').optional().notEmpty().bail().custom((input, {req}) => { | ||||||
|  |         if (!textbookModel.existSection(req.params!.textbook, input)) { | ||||||
|  |             throw new Error('section not exist') | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |     }), | ||||||
|  |     param('exercise').optional().notEmpty().bail().custom((input, {req}) => { | ||||||
|  |         if (!textbookModel.existExercise(req.params!.textbook, req.params!.section, input)) { | ||||||
|  |             throw new Error('exercise not exist') | ||||||
|  |         } | ||||||
|  |         return true | ||||||
|  |     }), | ||||||
|  |     expressAsyncHandler(async (req: express.Request, res: express.Response) => { | ||||||
|  |         if (hasValidationErrors(req, res)) return | ||||||
|  |         if (req.query.page) { | ||||||
|  |             res.json(await textbookService.getPage(req.params.textbook, Number(req.query.page))) | ||||||
|  |         } else if (req.params.exercise) { | ||||||
|  |             res.json(await textbookService.getExercise(req.params.textbook, req.params.section, req.params.exercise)) | ||||||
|  |         } else if (req.params.section) { | ||||||
|  |             res.json(await textbookService.getSection(req.params.textbook, req.params.section)) | ||||||
|  |         } else { | ||||||
|  |             res.json(await textbookService.getTextbook(req.params.textbook)) | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | export {textbookRouter} | ||||||
							
								
								
									
										30
									
								
								slader-legacy-backend/src/services/textbook-service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								slader-legacy-backend/src/services/textbook-service.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | |||||||
|  | import 'reflect-metadata' | ||||||
|  | import {Inject, Service} from "typedi" | ||||||
|  | import {TextbookModel} from "../models/textbook-model" | ||||||
|  | import {resultJson} from "../includes" | ||||||
|  |  | ||||||
|  | @Service() | ||||||
|  | export class TextbookService { | ||||||
|  |     @Inject() | ||||||
|  |     textbookModel!: TextbookModel | ||||||
|  |  | ||||||
|  |     async getTextbook(textbook: string) { | ||||||
|  |         return resultJson.success(this.textbookModel.findTocByTextbook(textbook)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async getSection(textbook: string, section: string) { | ||||||
|  |         return resultJson.success(this.textbookModel.findExercisesByTextbookSection(textbook, section)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async getExercise(textbook: string, section: string, exercise: string) { | ||||||
|  |         return resultJson.success(this.textbookModel.findExercise(textbook, section, exercise)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async getPage(textbook: string, page: number) { | ||||||
|  |         return resultJson.success(this.textbookModel.findExercisesByTextbookPage(textbook, page)) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async listTextbooks() { | ||||||
|  |         return resultJson.success(this.textbookModel.getTextbooks()) | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								slader-legacy-backend/src/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								slader-legacy-backend/src/types.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | |||||||
|  | export interface IResultJson { | ||||||
|  |     status: boolean, | ||||||
|  |     data: any | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface IToc { | ||||||
|  |     section: string, | ||||||
|  |     count: number, | ||||||
|  |     pageBegin: number, | ||||||
|  |     pageEnd: number | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface ITextbook { | ||||||
|  |     title: string, | ||||||
|  |     quantity: number | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface IExercise { | ||||||
|  |     exercise: string, | ||||||
|  |     section: string, | ||||||
|  |     part: string, | ||||||
|  |     page: number, | ||||||
|  |     html: string | ||||||
|  | } | ||||||
							
								
								
									
										86
									
								
								slader-legacy-backend/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								slader-legacy-backend/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | |||||||
|  | { | ||||||
|  |   "compilerOptions": { | ||||||
|  |     /* Visit https://aka.ms/tsconfig.json to read more about this file */ | ||||||
|  |  | ||||||
|  |     /* Basic Options */ | ||||||
|  |     // "incremental": true,                         /* Enable incremental compilation */ | ||||||
|  |     "target": "ES2016", | ||||||
|  |     /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ | ||||||
|  |     "module": "commonjs", | ||||||
|  |     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ | ||||||
|  |     // "lib": [],                                   /* Specify library files to be included in the compilation. */ | ||||||
|  |     "allowJs": true, | ||||||
|  |     /* Allow javascript files to be compiled. */ | ||||||
|  |     // "checkJs": true,                             /* Report errors in .js files. */ | ||||||
|  |     // "jsx": "preserve",                           /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ | ||||||
|  |     // "declaration": true,                         /* Generates corresponding '.d.ts' file. */ | ||||||
|  |     // "declarationMap": true,                      /* Generates a sourcemap for each corresponding '.d.ts' file. */ | ||||||
|  |     // "sourceMap": true,                           /* Generates corresponding '.map' file. */ | ||||||
|  |     // "outFile": "./",                             /* Concatenate and emit output to single file. */ | ||||||
|  |     "outDir": "./build", | ||||||
|  |     /* Redirect output structure to the directory. */ | ||||||
|  |     "rootDir": "./src", | ||||||
|  |     /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ | ||||||
|  |     // "composite": true,                           /* Enable project compilation */ | ||||||
|  |     // "tsBuildInfoFile": "./",                     /* Specify file to store incremental compilation information */ | ||||||
|  |     // "removeComments": true,                      /* Do not emit comments to output. */ | ||||||
|  |     // "noEmit": true,                              /* Do not emit outputs. */ | ||||||
|  |     // "importHelpers": true,                       /* Import emit helpers from 'tslib'. */ | ||||||
|  |     // "downlevelIteration": true,                  /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ | ||||||
|  |     // "isolatedModules": true,                     /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ | ||||||
|  |  | ||||||
|  |     /* Strict Type-Checking Options */ | ||||||
|  |     "strict": true, | ||||||
|  |     /* Enable all strict type-checking options. */ | ||||||
|  |     // "noImplicitAny": true,                       /* Raise error on expressions and declarations with an implied 'any' type. */ | ||||||
|  |     // "strictNullChecks": true,                    /* Enable strict null checks. */ | ||||||
|  |     // "strictFunctionTypes": true,                 /* Enable strict checking of function types. */ | ||||||
|  |     // "strictBindCallApply": true,                 /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ | ||||||
|  |     // "strictPropertyInitialization": true,        /* Enable strict checking of property initialization in classes. */ | ||||||
|  |     // "noImplicitThis": true,                      /* Raise error on 'this' expressions with an implied 'any' type. */ | ||||||
|  |     // "alwaysStrict": true,                        /* Parse in strict mode and emit "use strict" for each source file. */ | ||||||
|  |  | ||||||
|  |     /* Additional Checks */ | ||||||
|  |     // "noUnusedLocals": true,                      /* Report errors on unused locals. */ | ||||||
|  |     // "noUnusedParameters": true,                  /* Report errors on unused parameters. */ | ||||||
|  |     // "noImplicitReturns": true,                   /* Report error when not all code paths in function return a value. */ | ||||||
|  |     // "noFallthroughCasesInSwitch": true,          /* Report errors for fallthrough cases in switch statement. */ | ||||||
|  |     // "noUncheckedIndexedAccess": true,            /* Include 'undefined' in index signature results */ | ||||||
|  |     // "noPropertyAccessFromIndexSignature": true,  /* Require undeclared properties from index signatures to use element accesses. */ | ||||||
|  |  | ||||||
|  |     /* Module Resolution Options */ | ||||||
|  |     // "moduleResolution": "node",                  /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ | ||||||
|  |     // "baseUrl": "./",                             /* Base directory to resolve non-absolute module names. */ | ||||||
|  |     // "paths": {},                                 /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ | ||||||
|  |     // "rootDirs": [],                              /* List of root folders whose combined content represents the structure of the project at runtime. */ | ||||||
|  |     "typeRoots": [ | ||||||
|  |       //      "./node_modules/**/*", | ||||||
|  |       "./node_modules/@types" | ||||||
|  |     ], | ||||||
|  |     /* List of folders to include type definitions from. */ | ||||||
|  |     // "types": [],                                 /* Type declaration files to be included in compilation. */ | ||||||
|  |     // "allowSyntheticDefaultImports": true,        /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ | ||||||
|  |     "esModuleInterop": true, | ||||||
|  |     /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ | ||||||
|  |     // "preserveSymlinks": true,                    /* Do not resolve the real path of symlinks. */ | ||||||
|  |     // "allowUmdGlobalAccess": true,                /* Allow accessing UMD globals from modules. */ | ||||||
|  |  | ||||||
|  |     /* Source Map Options */ | ||||||
|  |     // "sourceRoot": "",                            /* Specify the location where debugger should locate TypeScript files instead of source locations. */ | ||||||
|  |     // "mapRoot": "",                               /* Specify the location where debugger should locate map files instead of generated locations. */ | ||||||
|  |     // "inlineSourceMap": true,                     /* Emit a single file with source maps instead of having a separate file. */ | ||||||
|  |     // "inlineSources": true,                       /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ | ||||||
|  |  | ||||||
|  |     /* Experimental Options */ | ||||||
|  |     "experimentalDecorators": true, | ||||||
|  |     /* Enables experimental support for ES7 decorators. */ | ||||||
|  |     "emitDecoratorMetadata": true, | ||||||
|  |     /* Enables experimental support for emitting type metadata for decorators. */ | ||||||
|  |  | ||||||
|  |     /* Advanced Options */ | ||||||
|  |     "skipLibCheck": true, | ||||||
|  |     /* Skip type checking of declaration files. */ | ||||||
|  |     "forceConsistentCasingInFileNames": true | ||||||
|  |     /* Disallow inconsistently-cased references to the same file. */ | ||||||
|  |   } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user