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:
parent
7de3cedb15
commit
3b7e55d957
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lateinicusserver",
|
"name": "lateinicusserver",
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"description": "The backend server for Lateinicus",
|
"description": "The backend server for Lateinicus",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -22,7 +22,8 @@
|
|||||||
"body-parser": "1.18.3",
|
"body-parser": "1.18.3",
|
||||||
"cors": "^2.8.4",
|
"cors": "^2.8.4",
|
||||||
"express": "4.16.3",
|
"express": "4.16.3",
|
||||||
"mongodb": "^3.1.6"
|
"mongodb": "^3.1.6",
|
||||||
|
"profanity-util": "^0.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cors": "^2.8.4",
|
"@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 {
|
export interface ISchedulingData {
|
||||||
easiness: number;
|
easiness: number;
|
||||||
consecutiveCorrectAnswers: number;
|
consecutiveCorrectAnswers: number;
|
||||||
nextDueDate: 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 {
|
export enum AnswerType {
|
||||||
CORRECT,
|
CORRECT,
|
||||||
WRONG,
|
WRONG,
|
||||||
@ -28,8 +32,8 @@ export function updateSchedulingData(data: ISchedulingData, answer: AnswerType):
|
|||||||
data.consecutiveCorrectAnswers + 1
|
data.consecutiveCorrectAnswers + 1
|
||||||
) : 0;
|
) : 0;
|
||||||
data.nextDueDate = answer === AnswerType.CORRECT ? (
|
data.nextDueDate = answer === AnswerType.CORRECT ? (
|
||||||
dayInNDays(6 * Math.pow(data.easiness, data.consecutiveCorrectAnswers - 1))
|
dateInNDays(6 * Math.pow(data.easiness, data.consecutiveCorrectAnswers - 1))
|
||||||
) : dayInNDays(1);
|
) : dateInNDays(1);
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
@ -49,7 +49,7 @@ levelRouter.get("/:id/vocab", async (req: LRequest, res: Response) => {
|
|||||||
// Fetch all the vocabulary
|
// Fetch all the vocabulary
|
||||||
const vocab = await db.collection("vocabulary").find({ id: { $in: level.vocab } }).toArray();
|
const vocab = await db.collection("vocabulary").find({ id: { $in: level.vocab } }).toArray();
|
||||||
res.send({
|
res.send({
|
||||||
error: "0",
|
error: "200",
|
||||||
data: {
|
data: {
|
||||||
vocab,
|
vocab,
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ userRouter.get("/me", async (req: LRequest, res) => {
|
|||||||
delete copy.vocabMetadata;
|
delete copy.vocabMetadata;
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
error: "0",
|
error: "200",
|
||||||
data: copy,
|
data: copy,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -55,7 +55,7 @@ userRouter.get("/logout", async (req: LRequest, res) => {
|
|||||||
await db.collection("sessions").findOneAndDelete({ token, });
|
await db.collection("sessions").findOneAndDelete({ token, });
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
error: "0",
|
error: "200",
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -143,9 +143,9 @@ userRouter.get("/queue", async (req: LRequest, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
error: "0",
|
error: "200",
|
||||||
data: {
|
data: {
|
||||||
queue: vocab,
|
vocab,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -157,17 +157,11 @@ userRouter.get("/lastReview", async (req: LRequest, res) => {
|
|||||||
const user = await userFromSession(token, db);
|
const user = await userFromSession(token, db);
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
error: "0",
|
error: "200",
|
||||||
data: user.lastReview,
|
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) => {
|
userRouter.post("/lastReview", async (req: LRequest, res) => {
|
||||||
// Check if we get the needed data
|
// Check if we get the needed data
|
||||||
if (!req.body || !("meta" in req.body) || !("sm2" in req.body) || !("delta" in req.body)) {
|
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({
|
res.send({
|
||||||
error: "0",
|
error: "200",
|
||||||
data: {},
|
data: {},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -259,7 +253,7 @@ async function getNextLevel(token: string, db: Db): Promise<any> {
|
|||||||
userRouter.get("/nextLevel", async (req: LRequest, res) => {
|
userRouter.get("/nextLevel", async (req: LRequest, res) => {
|
||||||
const level = await getNextLevel(req.token, req.db);
|
const level = await getNextLevel(req.token, req.db);
|
||||||
res.send({
|
res.send({
|
||||||
error: "0",
|
error: "200",
|
||||||
data: level,
|
data: level,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -307,7 +301,7 @@ userRouter.post("/level/:id", async (req: LRequest, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Convert the level's vocabulary to SM2 metadata
|
// Convert the level's vocabulary to SM2 metadata
|
||||||
let sm2: { [id: number]: ISM2Metadata } = {};
|
let sm2: { [id: number]: ISchedulingData } = {};
|
||||||
level.vocab.forEach((id: number) => {
|
level.vocab.forEach((id: number) => {
|
||||||
sm2[id] = {
|
sm2[id] = {
|
||||||
easiness: 1.3,
|
easiness: 1.3,
|
||||||
|
@ -9,7 +9,7 @@ import * as cors from "cors";
|
|||||||
import * as bodyparser from "body-parser";
|
import * as bodyparser from "body-parser";
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
//import * as Filter from "bad-words";
|
import * as profanity from "profanity-util";
|
||||||
|
|
||||||
import { isAuthenticated, performLogin } from "./security/auth";
|
import { isAuthenticated, performLogin } from "./security/auth";
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ const password = encodeURIComponent(env["LATEINICUS_USER_PW"]);
|
|||||||
});
|
});
|
||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
error: "0",
|
error: "200",
|
||||||
data: {
|
data: {
|
||||||
levels,
|
levels,
|
||||||
},
|
},
|
||||||
@ -178,6 +178,16 @@ const password = encodeURIComponent(env["LATEINICUS_USER_PW"]);
|
|||||||
// });
|
// });
|
||||||
// return;
|
// 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
|
// Check if the user already exists
|
||||||
const checkUser = await db.collection("users").findOne({
|
const checkUser = await db.collection("users").findOne({
|
||||||
@ -221,7 +231,7 @@ const password = encodeURIComponent(env["LATEINICUS_USER_PW"]);
|
|||||||
});
|
});
|
||||||
app.get("/api/health", (req, res) => {
|
app.get("/api/health", (req, res) => {
|
||||||
res.send({
|
res.send({
|
||||||
error: "0",
|
error: "200",
|
||||||
data: {
|
data: {
|
||||||
msg: "lol",
|
msg: "lol",
|
||||||
},
|
},
|
||||||
@ -245,7 +255,7 @@ const password = encodeURIComponent(env["LATEINICUS_USER_PW"]);
|
|||||||
try {
|
try {
|
||||||
const userData = await performLogin(body.username, body.password, db);
|
const userData = await performLogin(body.username, body.password, db);
|
||||||
res.send({
|
res.send({
|
||||||
error: "0",
|
error: "200",
|
||||||
data: userData,
|
data: userData,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ services:
|
|||||||
ipv4_address: 128.1.0.2
|
ipv4_address: 128.1.0.2
|
||||||
|
|
||||||
backend:
|
backend:
|
||||||
image: lateinicus/server:1.2.0
|
image: lateinicus/server:1.3.0
|
||||||
environment:
|
environment:
|
||||||
- LATEINICUS_USER_PW=abc123
|
- LATEINICUS_USER_PW=abc123
|
||||||
- LATEINICUS_CLASSES=test
|
- LATEINICUS_CLASSES=test
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "seminarfach",
|
"name": "seminarfach",
|
||||||
"version": "1.2.0",
|
"version": "1.3.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
33
frontend/src/api/call.ts
Normal file
33
frontend/src/api/call.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { BACKEND_URL } from "../config.in";
|
||||||
|
|
||||||
|
interface IAPIOptions {
|
||||||
|
token?: string;
|
||||||
|
body?: any;
|
||||||
|
method: "post" | "get";
|
||||||
|
};
|
||||||
|
export function makeAPICall<T>(endpoint: string, options: IAPIOptions): Promise<T> {
|
||||||
|
const { token, body, method } = options;
|
||||||
|
const headers = token !== "" ? ({
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Token": token,
|
||||||
|
}) : ({
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
fetch(`${BACKEND_URL}${endpoint}`, {
|
||||||
|
// WHUT
|
||||||
|
headers: new Headers(headers),
|
||||||
|
body: body !== {} ? JSON.stringify(body) : "",
|
||||||
|
method,
|
||||||
|
})
|
||||||
|
.then(resp => resp.json(), err => rej(err))
|
||||||
|
.then(data => {
|
||||||
|
if (data.error === "200") {
|
||||||
|
res(data.data);
|
||||||
|
} else {
|
||||||
|
rej(data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -17,6 +17,7 @@ import VocabPage from "../containers/VocabPage";
|
|||||||
import Drawer from "../containers/Drawer";
|
import Drawer from "../containers/Drawer";
|
||||||
|
|
||||||
import { trackAction } from "../api/tracker";
|
import { trackAction } from "../api/tracker";
|
||||||
|
import { makeAPICall } from "../api/call";
|
||||||
|
|
||||||
import { BACKEND_URL } from "../config.in";
|
import { BACKEND_URL } from "../config.in";
|
||||||
|
|
||||||
@ -60,76 +61,26 @@ export default class Application extends React.Component<IProps> {
|
|||||||
// Track the end of a review
|
// Track the end of a review
|
||||||
trackAction(TrackerEvent.LOG_IN);
|
trackAction(TrackerEvent.LOG_IN);
|
||||||
|
|
||||||
return new Promise((res, rej) => {
|
return makeAPICall("/api/user/me", {
|
||||||
fetch(`${BACKEND_URL}/api/user/me`, {
|
token: this.props.user.sessionToken,
|
||||||
headers: new Headers({
|
method: "get",
|
||||||
"Content-Type": "application/json",
|
})
|
||||||
"Token": token,
|
|
||||||
}),
|
|
||||||
}).then(resp => resp.json(), err => rej(err))
|
|
||||||
.then(data => {
|
|
||||||
if (data.error === "0") {
|
|
||||||
res(data.data);
|
|
||||||
} else {
|
|
||||||
rej(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getVocab = (): Promise<IVocab[]> => {
|
getVocab = () => makeAPICall("/api/user/vocab", {
|
||||||
return new Promise((res, rej) => {
|
token: this.props.user.sessionToken,
|
||||||
fetch(`${BACKEND_URL}/api/user/vocab`, {
|
method: "get",
|
||||||
headers: new Headers({
|
})
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
}).then(resp => resp.json(), err => rej(err))
|
|
||||||
.then(data => {
|
|
||||||
if (data.error === "200") {
|
|
||||||
res(data.data);
|
|
||||||
} else {
|
|
||||||
rej(data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getLevels = (): Promise<ILevel[]> => {
|
getLevels = () => makeAPICall("/api/levels", {
|
||||||
return new Promise((res, rej) => {
|
token: this.props.user.sessionToken,
|
||||||
fetch(`${BACKEND_URL}/api/levels`, {
|
method: "get",
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
}).then(resp => resp.json(), err => rej(err))
|
|
||||||
.then(data => {
|
|
||||||
if (data.error === "0") {
|
|
||||||
res(data.data.levels);
|
|
||||||
} else {
|
|
||||||
rej(data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getLastReview = (): Promise<IReviewMetadata> => {
|
getLastReview = () => makeAPICall("/api/user/lastReview", {
|
||||||
return new Promise((res, rej) => {
|
token: this.props.user.sessionToken,
|
||||||
fetch(`${BACKEND_URL}/api/user/lastReview`, {
|
method: "get",
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
}).then(resp => resp.json(), err => rej(err))
|
|
||||||
.then(data => {
|
|
||||||
if (data.error === "0") {
|
|
||||||
res(data.data);
|
|
||||||
} else {
|
|
||||||
rej(data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Type?
|
// TODO: Type?
|
||||||
setLastReview = (meta: IReviewMetadata, sm2: any, delta: number) => {
|
setLastReview = (meta: IReviewMetadata, sm2: any, delta: number) => {
|
||||||
@ -138,149 +89,55 @@ export default class Application extends React.Component<IProps> {
|
|||||||
this.props.setUserScoreDelta(delta);
|
this.props.setUserScoreDelta(delta);
|
||||||
|
|
||||||
// Tell the server about the last review
|
// Tell the server about the last review
|
||||||
fetch(`${BACKEND_URL}/api/user/lastReview`, {
|
makeAPICall("/api/user/lastReview", {
|
||||||
headers: new Headers({
|
token: this.props.user.sessionToken,
|
||||||
"Content-Type": "application/json",
|
body: {
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
meta,
|
meta,
|
||||||
sm2,
|
sm2,
|
||||||
delta,
|
delta,
|
||||||
}),
|
},
|
||||||
}).then(resp => resp.json(), err => {
|
method: "post",
|
||||||
console.log("Application::setLastReview: POSTing last results failed");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Track the end of a review
|
// Track the end of a review
|
||||||
trackAction(TrackerEvent.FINISH_LEARNING);
|
trackAction(TrackerEvent.FINISH_LEARNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
getReviewQueue = (): Promise<IVocab[]> => {
|
getReviewQueue = () => makeAPICall("/api/user/queue", {
|
||||||
return new Promise((res, rej) => {
|
token: this.props.user.sessionToken,
|
||||||
fetch(`${BACKEND_URL}/api/user/queue`, {
|
method: "get",
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
}).then(resp => resp.json(), err => rej(err))
|
|
||||||
.then(data => {
|
|
||||||
if (data.error === "0") {
|
|
||||||
res(data.data.queue);
|
|
||||||
} else {
|
|
||||||
rej(data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getTopTenLearners = (): Promise<TopTen[]> => {
|
getNextLevel = () => makeAPICall("/api/user/nextLevel", {
|
||||||
// TODO: Deprecate?
|
token: this.props.user.sessionToken,
|
||||||
const id = this.props.user.classId;
|
method: "get",
|
||||||
return new Promise((res, rej) => {
|
|
||||||
fetch(`${BACKEND_URL}/api/class/${id}/topTen`, {
|
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
}).then(resp => resp.json(),
|
|
||||||
err => rej(err))
|
|
||||||
.then(data => {
|
|
||||||
if (data.error === "0") {
|
|
||||||
res(data.data.topTen);
|
|
||||||
} else {
|
|
||||||
rej(data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getNextLevel = (): Promise<ILevel> => {
|
getLevelVocab = (id: number) => makeAPICall(`/api/level/${id}/vocab`, {
|
||||||
return new Promise((res, rej) => {
|
token: this.props.user.sessionToken,
|
||||||
fetch(`${BACKEND_URL}/api/user/nextLevel`, {
|
method: "get",
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
}).then(resp => resp.json(),
|
|
||||||
err => rej(err))
|
|
||||||
.then(data => {
|
|
||||||
if (data.error === "0") {
|
|
||||||
res(data.data);
|
|
||||||
} else {
|
|
||||||
rej(data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
getLevelVocab = (id: number): Promise<IVocab[]> => {
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
fetch(`${BACKEND_URL}/api/level/${id}/vocab`, {
|
|
||||||
method: "GET",
|
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
}).then(data => data.json(), err => {
|
|
||||||
rej(err);
|
|
||||||
}).then((resp: IResponse) => {
|
|
||||||
if (resp.error === "0") {
|
|
||||||
res(resp.data.vocab);
|
|
||||||
} else {
|
|
||||||
rej(resp);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
introDontShowAgain = (): void => {
|
|
||||||
// NOTE: This is not a promise, as we do not care about any response
|
// NOTE: This is not a promise, as we do not care about any response
|
||||||
// being sent, since we don't need to update any client-side
|
// being sent, since we don't need to update any client-side
|
||||||
// state.
|
// state.
|
||||||
fetch(`${BACKEND_URL}/api/user/showWelcome`, {
|
introDontShowAgain = () => makeAPICall("/api/user/showWelcome", {
|
||||||
headers: new Headers({
|
token: this.props.user.sessionToken,
|
||||||
"Content-Type": "application/json",
|
body: {
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
method: "POST",
|
|
||||||
body: JSON.stringify({
|
|
||||||
state: false,
|
state: false,
|
||||||
}),
|
},
|
||||||
|
method: "post",
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Type?
|
getDashboard = () => makeAPICall("/api/user/dashboard", {
|
||||||
getDashboard = (): Promise<any> => {
|
token: this.props.user.sessionToken,
|
||||||
return new Promise((res, rej) => {
|
method: "get",
|
||||||
fetch(`${BACKEND_URL}/api/user/dashboard`, {
|
|
||||||
headers: new Headers({
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then(resp => resp.json(), err => rej(err))
|
|
||||||
.then(data => {
|
|
||||||
if (data.error === "200") {
|
|
||||||
res(data.data);
|
|
||||||
} else {
|
|
||||||
console.log("Application::getDashboard: Failed to get dashboard");
|
|
||||||
rej(data);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDoneLevels = (id: string): void => {
|
updateDoneLevels = (id: string) => makeAPICall(`/api/user/level/${id}`, {
|
||||||
fetch(`${BACKEND_URL}/api/user/level/${id}`, {
|
token: this.props.user.sessionToken,
|
||||||
headers: new Headers({
|
method: "post",
|
||||||
"Content-Type": "application/json",
|
|
||||||
"Token": this.props.user.sessionToken,
|
|
||||||
}),
|
|
||||||
method: "POST",
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
login = (username: string, password: string): Promise<IUser | IResponse> => {
|
login = (username: string, password: string): Promise<IUser | IResponse> => {
|
||||||
// Track the login
|
// Track the login
|
||||||
@ -302,7 +159,7 @@ export default class Application extends React.Component<IProps> {
|
|||||||
// The fetch failed
|
// The fetch failed
|
||||||
rej(err);
|
rej(err);
|
||||||
}).then((resp: IResponse) => {
|
}).then((resp: IResponse) => {
|
||||||
if (resp.error === "0") {
|
if (resp.error === "200") {
|
||||||
// Successful login
|
// Successful login
|
||||||
this.props.setUser(resp.data);
|
this.props.setUser(resp.data);
|
||||||
this.props.setDidLogin(true);
|
this.props.setDidLogin(true);
|
||||||
|
@ -28,7 +28,7 @@ import { IVocab, VocabType } from "../models/vocab";
|
|||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
id: string;
|
id: string;
|
||||||
levelVocab: (id: string) => Promise<IVocab[]>;
|
levelVocab: (id: string) => Promise<any>;
|
||||||
|
|
||||||
history: any;
|
history: any;
|
||||||
|
|
||||||
@ -64,7 +64,8 @@ const LevelPageWithRouter = withRouter(
|
|||||||
this.props.setLoading(true);
|
this.props.setLoading(true);
|
||||||
|
|
||||||
// TODO: Error handling
|
// TODO: Error handling
|
||||||
this.props.levelVocab(this.props.id).then(vocab => {
|
this.props.levelVocab(this.props.id).then(data => {
|
||||||
|
const { vocab } = data;
|
||||||
this.props.setVocab(vocab);
|
this.props.setVocab(vocab);
|
||||||
this.props.setCurrentVocab(vocab[0]);
|
this.props.setCurrentVocab(vocab[0]);
|
||||||
this.props.setLookedAt([vocab[0].id]);
|
this.props.setLookedAt([vocab[0].id]);
|
||||||
|
@ -6,7 +6,6 @@ import Button from "@material-ui/core/Button";
|
|||||||
import Card from '@material-ui/core/Card';
|
import Card from '@material-ui/core/Card';
|
||||||
import CardActions from '@material-ui/core/CardActions';
|
import CardActions from '@material-ui/core/CardActions';
|
||||||
import CardContent from '@material-ui/core/CardContent';
|
import CardContent from '@material-ui/core/CardContent';
|
||||||
import Paper from "@material-ui/core/Paper";
|
|
||||||
import Snackbar from "@material-ui/core/Snackbar";
|
import Snackbar from "@material-ui/core/Snackbar";
|
||||||
|
|
||||||
import Loader from "../components/loading";
|
import Loader from "../components/loading";
|
||||||
@ -17,7 +16,7 @@ import { ILevel } from "../models/level";
|
|||||||
import { IUser } from "../models/user";
|
import { IUser } from "../models/user";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
getLevels: () => Promise<ILevel[]>;
|
getLevels: () => Promise<any>;
|
||||||
|
|
||||||
history: any;
|
history: any;
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ const LevelListWithRouter = withRouter(
|
|||||||
|
|
||||||
// Fetch the levels
|
// Fetch the levels
|
||||||
this.props.getLevels().then(res => {
|
this.props.getLevels().then(res => {
|
||||||
this.props.setLevels(res);
|
this.props.setLevels(res.levels);
|
||||||
this.props.setLoading(false);
|
this.props.setLoading(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -40,8 +40,8 @@ import { Queue } from "../utils/queue";
|
|||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
levelId?: number;
|
levelId?: number;
|
||||||
vocabByLevel?: (level: number) => Promise<IVocab[]>;
|
vocabByLevel?: (level: number) => Promise<any>;
|
||||||
vocabByQueue?: () => Promise<IVocab[]>;
|
vocabByQueue?: () => Promise<any>;
|
||||||
updateDoneLevels?: (id: string) => void;
|
updateDoneLevels?: (id: string) => void;
|
||||||
setLastReview: (meta: IReviewMetadata, sm2: any, delta: number) => void;
|
setLastReview: (meta: IReviewMetadata, sm2: any, delta: number) => void;
|
||||||
reviewType: ReviewType;
|
reviewType: ReviewType;
|
||||||
@ -110,19 +110,19 @@ const ReviewPageWithRouter = withRouter(
|
|||||||
// Track the start of a session
|
// Track the start of a session
|
||||||
trackAction(TrackerEvent.START_LEARNING);
|
trackAction(TrackerEvent.START_LEARNING);
|
||||||
|
|
||||||
getVocab().then((res: IVocab[]) => {
|
getVocab().then((res: any) => {
|
||||||
// Check if we received any vocabulary
|
// Check if we received any vocabulary
|
||||||
if (res.length === 0) {
|
if (res.vocab.length === 0) {
|
||||||
this.openModal();
|
this.openModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the loading
|
// Stop the loading
|
||||||
this.props.setLoading(false);
|
this.props.setLoading(false);
|
||||||
this.vocab = res;
|
this.vocab = res.vocab;
|
||||||
|
|
||||||
// Convert the vocab items into review queue cards
|
// Convert the vocab items into review queue cards
|
||||||
res.forEach(vocab => {
|
res.vocab.forEach((vocab: IVocab) => {
|
||||||
// Set the error data for the group
|
// Set the error data for the group
|
||||||
this.error_data[vocab.id] = 0;
|
this.error_data[vocab.id] = 0;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user