refactor: Simplify API calls
API calls can now make with a simple wrapper function. Additionally, the error code "200" now means success for all API calls.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lateinicusserver",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.0",
|
||||
"description": "The backend server for Lateinicus",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
@@ -22,7 +22,8 @@
|
||||
"body-parser": "1.18.3",
|
||||
"cors": "^2.8.4",
|
||||
"express": "4.16.3",
|
||||
"mongodb": "^3.1.6"
|
||||
"mongodb": "^3.1.6",
|
||||
"profanity-util": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.4",
|
||||
|
||||
276
backend/src/#main.ts#
Normal file
276
backend/src/#main.ts#
Normal file
@@ -0,0 +1,276 @@
|
||||
import { env, exit } from "process";
|
||||
// import * as fs from "fs";
|
||||
import { randomBytes, pbkdf2Sync } from "crypto";
|
||||
import * as assert from "assert";
|
||||
|
||||
import * as express from "express";
|
||||
import * as cors from "cors";
|
||||
|
||||
import * as bodyparser from "body-parser";
|
||||
|
||||
//@ts-ignore
|
||||
import * as profanity from "profanity-util";
|
||||
|
||||
import { isAuthenticated, performLogin } from "./security/auth";
|
||||
|
||||
import { LRequest } from "./types/express";
|
||||
|
||||
import UserRouter from "./api/user";
|
||||
import ClassRouter from "./api/class";
|
||||
import LevelRouter from "./api/level";
|
||||
|
||||
import { ITrackerDBModel } from "./models/tracker";
|
||||
|
||||
const baseRouter = express.Router();
|
||||
const authRouter = express.Router();
|
||||
|
||||
import { MongoClient } from "mongodb";
|
||||
|
||||
const user = encodeURIComponent("backend");
|
||||
const password = encodeURIComponent(env["LATEINICUS_USER_PW"]);
|
||||
|
||||
(async function() {
|
||||
// Load the profanity list
|
||||
// const list = JSON.parse(fs.readFileSync("/etc/profanity", { encoding: "utf-8" }));
|
||||
// const profanityFilter = new Filter({
|
||||
// list,
|
||||
// });
|
||||
|
||||
// Database Name
|
||||
const dbName = 'lateinicus';
|
||||
// Connection URL
|
||||
const url = `mongodb://${user}:${password}@128.1.0.2:27017/?authMechanism=SCRAM-SHA-1&authSource=${dbName}`;
|
||||
let client: MongoClient;
|
||||
|
||||
try {
|
||||
// Use connect method to connect to the Server
|
||||
client = await MongoClient.connect(url);
|
||||
console.log("Connected to MongoDB");
|
||||
} catch (err) {
|
||||
console.log(err.stack);
|
||||
assert(false);
|
||||
}
|
||||
|
||||
const db = client.db(dbName);
|
||||
console.log("Connected to the database");
|
||||
|
||||
|
||||
const app = express();
|
||||
app.use(bodyparser.json());
|
||||
app.options("*", cors());
|
||||
|
||||
app.use((req: LRequest, res, next) => {
|
||||
// Every route should have access to the database so that
|
||||
// we can easily make calls to it
|
||||
req.db = db;
|
||||
next();
|
||||
});
|
||||
app.use("/api/level", LevelRouter);
|
||||
app.use("/api/class", ClassRouter);
|
||||
app.use("/api/user", UserRouter);
|
||||
app.post("/api/tracker", async (req, res) => {
|
||||
// Did we get any data
|
||||
if (!req.body) {
|
||||
res.send({
|
||||
error: "403",
|
||||
data: {
|
||||
msg: "No request body provided",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Did we get all arguments?
|
||||
if (!("session" in req.body) || !("event" in req.body)) {
|
||||
res.send({
|
||||
error: "403",
|
||||
data: {
|
||||
msg: "Invalid request",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert it into the database
|
||||
const tracker: ITrackerDBModel = Object.assign({}, req.body, {
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
await db.collection("tracker").insertOne(tracker);
|
||||
|
||||
res.send({
|
||||
error: "200",
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
app.get("/api/levels", async (req, res) => {
|
||||
// TODO: if (levels)
|
||||
const levels = (await db.collection("levels").find({}, {
|
||||
// The order in which we send the levels is important, so better
|
||||
// sort them
|
||||
sort: {
|
||||
level: 1,
|
||||
},
|
||||
})
|
||||
.toArray())
|
||||
.map((el) => {
|
||||
let tmp = Object.assign({}, el);
|
||||
delete tmp.vocab;
|
||||
delete tmp._id;
|
||||
|
||||
return tmp;
|
||||
});
|
||||
|
||||
res.send({
|
||||
error: "200",
|
||||
data: {
|
||||
levels,
|
||||
},
|
||||
});
|
||||
});
|
||||
app.post("/api/register", async (req, res) => {
|
||||
// Check if any data was sent
|
||||
if (!req.body) {
|
||||
res.send({
|
||||
error: "403",
|
||||
data: {
|
||||
msg: `No data sent`,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have all we need
|
||||
const params = ["username", "password", "classId"];
|
||||
for (let param of params) {
|
||||
if (!(param in req.body)) {
|
||||
res.send({
|
||||
error: "403",
|
||||
data: {
|
||||
msg: `${param} not specified!`,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const { username, password, classId } = req.body;
|
||||
|
||||
// Check if the registration is open for the class Id
|
||||
// NOTE: This to prevent people from spamming the database
|
||||
const classes = env["LATEINICUS_CLASSES"].split(",");
|
||||
if (classes.indexOf(classId) === -1) {
|
||||
res.send({
|
||||
error: "403",
|
||||
data: {
|
||||
msg: "Class does not exist",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Check if the username is profane
|
||||
// if (profanityFilter.isProfane(username)) {
|
||||
// res.send({
|
||||
// error: "451",
|
||||
// data: {
|
||||
// msg: "Profane username",
|
||||
// },
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
const matches = profanity.check(username, { substring: true });
|
||||
if (matches.length > 0) {
|
||||
res.send({
|
||||
error: "451",
|
||||
data: {
|
||||
msg: "Profane username",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user already exists
|
||||
const checkUser = await db.collection("users").findOne({
|
||||
username,
|
||||
});
|
||||
if (checkUser) {
|
||||
res.send({
|
||||
error: "403",
|
||||
data: {
|
||||
msg: "User already exists",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const salt = randomBytes(30).toString("hex");
|
||||
const hash = pbkdf2Sync(password, salt, 50000, 512, "sha512").toString("hex");
|
||||
const user = {
|
||||
username,
|
||||
salt,
|
||||
hash,
|
||||
classId,
|
||||
score: 0,
|
||||
showWelcome: true,
|
||||
|
||||
lastReview: {
|
||||
correct: 0,
|
||||
wrong: 0,
|
||||
},
|
||||
|
||||
lastLevel: 0,
|
||||
levels: [] as number[],
|
||||
vocabMetadata: {},
|
||||
};
|
||||
await db.collection("users").insertOne(user);
|
||||
|
||||
res.send({
|
||||
error: "200",
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
app.get("/api/health", (req, res) => {
|
||||
res.send({
|
||||
error: "200",
|
||||
data: {
|
||||
msg: "lol",
|
||||
},
|
||||
});
|
||||
});
|
||||
app.post("/api/login", async (req, res) => {
|
||||
// Check if all arguments were sent
|
||||
const { body } = req;
|
||||
if (!body || !("username" in body) || !("password" in body)) {
|
||||
res.send({
|
||||
error: "400",
|
||||
data: {
|
||||
msg: "Username or password not specified",
|
||||
},
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to log the user in
|
||||
try {
|
||||
const userData = await performLogin(body.username, body.password, db);
|
||||
res.send({
|
||||
error: "200",
|
||||
data: userData,
|
||||
});
|
||||
} catch (err) {
|
||||
console.log("Could not resolve login promise!", err);
|
||||
|
||||
// If anything was wrong, just tell the client
|
||||
res.send({
|
||||
error: "1",
|
||||
data: {
|
||||
msg: "Username or password is wrong",
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
const server = app.listen(8080, () => {
|
||||
console.log("Starting on port 8080");
|
||||
});
|
||||
})();
|
||||
1
backend/src/.#main.ts
Symbolic link
1
backend/src/.#main.ts
Symbolic link
@@ -0,0 +1 @@
|
||||
alexander@nishimiya.6541:1539788829
|
||||
@@ -1,11 +1,15 @@
|
||||
import { dayInNDays } from "../../utils/date";
|
||||
|
||||
export interface ISchedulingData {
|
||||
easiness: number;
|
||||
consecutiveCorrectAnswers: number;
|
||||
nextDueDate: number;
|
||||
};
|
||||
|
||||
function dateInNDays(n: number): number {
|
||||
let today = new Date();
|
||||
today.setDate(today.getDate() + n);
|
||||
return Date.parse(today.toString());
|
||||
}
|
||||
|
||||
export enum AnswerType {
|
||||
CORRECT,
|
||||
WRONG,
|
||||
@@ -28,8 +32,8 @@ export function updateSchedulingData(data: ISchedulingData, answer: AnswerType):
|
||||
data.consecutiveCorrectAnswers + 1
|
||||
) : 0;
|
||||
data.nextDueDate = answer === AnswerType.CORRECT ? (
|
||||
dayInNDays(6 * Math.pow(data.easiness, data.consecutiveCorrectAnswers - 1))
|
||||
) : dayInNDays(1);
|
||||
dateInNDays(6 * Math.pow(data.easiness, data.consecutiveCorrectAnswers - 1))
|
||||
) : dateInNDays(1);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ levelRouter.get("/:id/vocab", async (req: LRequest, res: Response) => {
|
||||
// Fetch all the vocabulary
|
||||
const vocab = await db.collection("vocabulary").find({ id: { $in: level.vocab } }).toArray();
|
||||
res.send({
|
||||
error: "0",
|
||||
error: "200",
|
||||
data: {
|
||||
vocab,
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ userRouter.get("/me", async (req: LRequest, res) => {
|
||||
delete copy.vocabMetadata;
|
||||
|
||||
res.send({
|
||||
error: "0",
|
||||
error: "200",
|
||||
data: copy,
|
||||
});
|
||||
} else {
|
||||
@@ -55,7 +55,7 @@ userRouter.get("/logout", async (req: LRequest, res) => {
|
||||
await db.collection("sessions").findOneAndDelete({ token, });
|
||||
|
||||
res.send({
|
||||
error: "0",
|
||||
error: "200",
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
@@ -143,9 +143,9 @@ userRouter.get("/queue", async (req: LRequest, res) => {
|
||||
});
|
||||
|
||||
res.send({
|
||||
error: "0",
|
||||
error: "200",
|
||||
data: {
|
||||
queue: vocab,
|
||||
vocab,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -157,17 +157,11 @@ userRouter.get("/lastReview", async (req: LRequest, res) => {
|
||||
const user = await userFromSession(token, db);
|
||||
|
||||
res.send({
|
||||
error: "0",
|
||||
error: "200",
|
||||
data: user.lastReview,
|
||||
});
|
||||
});
|
||||
|
||||
function dateInNDays(n: number): number {
|
||||
let today = new Date();
|
||||
today.setDate(today.getDate() + n);
|
||||
return Date.parse(today.toString());
|
||||
}
|
||||
|
||||
userRouter.post("/lastReview", async (req: LRequest, res) => {
|
||||
// Check if we get the needed data
|
||||
if (!req.body || !("meta" in req.body) || !("sm2" in req.body) || !("delta" in req.body)) {
|
||||
@@ -228,7 +222,7 @@ userRouter.post("/lastReview", async (req: LRequest, res) => {
|
||||
});
|
||||
|
||||
res.send({
|
||||
error: "0",
|
||||
error: "200",
|
||||
data: {},
|
||||
});
|
||||
});
|
||||
@@ -259,7 +253,7 @@ async function getNextLevel(token: string, db: Db): Promise<any> {
|
||||
userRouter.get("/nextLevel", async (req: LRequest, res) => {
|
||||
const level = await getNextLevel(req.token, req.db);
|
||||
res.send({
|
||||
error: "0",
|
||||
error: "200",
|
||||
data: level,
|
||||
});
|
||||
});
|
||||
@@ -307,7 +301,7 @@ userRouter.post("/level/:id", async (req: LRequest, res) => {
|
||||
});
|
||||
|
||||
// Convert the level's vocabulary to SM2 metadata
|
||||
let sm2: { [id: number]: ISM2Metadata } = {};
|
||||
let sm2: { [id: number]: ISchedulingData } = {};
|
||||
level.vocab.forEach((id: number) => {
|
||||
sm2[id] = {
|
||||
easiness: 1.3,
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as cors from "cors";
|
||||
import * as bodyparser from "body-parser";
|
||||
|
||||
//@ts-ignore
|
||||
//import * as Filter from "bad-words";
|
||||
import * as profanity from "profanity-util";
|
||||
|
||||
import { isAuthenticated, performLogin } from "./security/auth";
|
||||
|
||||
@@ -121,7 +121,7 @@ const password = encodeURIComponent(env["LATEINICUS_USER_PW"]);
|
||||
});
|
||||
|
||||
res.send({
|
||||
error: "0",
|
||||
error: "200",
|
||||
data: {
|
||||
levels,
|
||||
},
|
||||
@@ -178,6 +178,16 @@ const password = encodeURIComponent(env["LATEINICUS_USER_PW"]);
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
const matches = profanity.check(username, { substring: true });
|
||||
if (matches.length > 0) {
|
||||
res.send({
|
||||
error: "451",
|
||||
data: {
|
||||
msg: "Profane username",
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the user already exists
|
||||
const checkUser = await db.collection("users").findOne({
|
||||
@@ -221,7 +231,7 @@ const password = encodeURIComponent(env["LATEINICUS_USER_PW"]);
|
||||
});
|
||||
app.get("/api/health", (req, res) => {
|
||||
res.send({
|
||||
error: "0",
|
||||
error: "200",
|
||||
data: {
|
||||
msg: "lol",
|
||||
},
|
||||
@@ -245,7 +255,7 @@ const password = encodeURIComponent(env["LATEINICUS_USER_PW"]);
|
||||
try {
|
||||
const userData = await performLogin(body.username, body.password, db);
|
||||
res.send({
|
||||
error: "0",
|
||||
error: "200",
|
||||
data: userData,
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user