feat: Implement updating the done levels

This commit is contained in:
Alexander Polynomdivision 2018-09-29 22:00:15 +02:00
parent e6e7505383
commit ab0c75331d
9 changed files with 146 additions and 18 deletions

View File

@ -21,10 +21,15 @@ levelRouter.get("/:id/vocab", async (req: LRequest, res: Response) => {
}
const levelId = parseInt(req.params.id);
// TODO: Handle this
// if (levelId === NaN) {
// // Something
// }
if (levelId === NaN) {
res.send({
error: "400",
data: {
msg: "Invalid level id",
},
});
return;
}
// Find the level
// TODO: if (level)
@ -33,12 +38,9 @@ levelRouter.get("/:id/vocab", async (req: LRequest, res: Response) => {
// TODO: This aint safe, boi
level: levelId,
});
console.log(level);
// Fetch all the vocabulary
const vocab = await db.collection("vocabulary").find({ id: { $in: level.vocab } }).toArray();
console.log(vocab);
res.send({
error: "0",
data: {

View File

@ -155,12 +155,54 @@ userRouter.get("/nextLevel", async (req: LRequest, res) => {
});
// Mark a level as done
userRouter.post("/level/:id", async (req, res) => {
console.log("STUB(post): /user/level/:id");
// TODO: Stub
userRouter.post("/level/:id", async (req: LRequest, res) => {
// Is everything specified?
if (!req.params || !req.params.id) {
res.send({
error: "0",
error: "400",
data: {
msg: "No level ID specified",
},
});
return;
}
const id = parseInt(req.params.id);
if (id === NaN) {
res.send({
error: "400",
data: {
msg: "Invalid level ID",
},
});
return;
}
// TODO: if (user)
const { token, db } = req;
const user = await userFromSession(token, db);
if (id in user.levels) {
// Nothing
} else {
// The level is new to the user
// Is the new level higher than the "highest" already completed level?
let update = {
levels: user.levels.concat(id),
};
if (id > Math.max(...user.levels)) {
// Also update the lastLevel attribute
Object.assign(update, { lastLevel: id });
}
await db.collection("users").updateOne({
username: user.username,
}, {
$set: update,
});
}
res.send({
error: "200",
data: {},
});
});
@ -206,4 +248,35 @@ userRouter.get("/dashboard", async (req: LRequest, res) => {
});
});
userRouter.post("/showWelcome", async (req: LRequest, res) => {
const { db, token } = req;
// Are all arguments specified?
if (!req.body || !("state" in req.body)) {
res.send({
error: "400",
data: {
msg: "State not specified",
},
});
return;
}
// Find the user
// TODO: if (user)
const user = await userFromSession(token, db);
await db.collection("users").updateOne({
username: user.username,
}, {
$set: {
showWelcome: req.body.state,
},
});
res.send({
error: "200",
data: {},
});
});
export default userRouter;

View File

@ -5,6 +5,8 @@ import * as bodyparser from "body-parser";
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";
@ -39,10 +41,9 @@ const assert = require('assert');
app.use(bodyparser.json());
app.options("*", cors());
app.use((req, res, next) => {
app.use((req: LRequest, res, next) => {
// Every route should have access to the database so that
// we can easily make calls to it
// @ts-ignore
req.db = db;
next();
});

View File

@ -12,9 +12,21 @@ export interface IUserDBModel {
wrong: number;
};
// Levels that we have done
levels: number[];
// The "highest" level the user has done
lastLevel: number;
queue: number[];
// Vocabulary ID -> SM2 Metadata
metadata: {
[key: number]: {
easiness: number;
nextDueDate: number;
consecutiveCorrectAnswers: number;
};
};
};
export interface IUser {

View File

@ -195,6 +195,22 @@ export default class Application extends React.Component<IProps> {
});
}
introDontShowAgain = (): void => {
// 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
// state.
fetch(`${BACKEND_URL}/api/user/showWelcome`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
method: "POST",
body: JSON.stringify({
state: false,
}),
});
}
// TODO: Type?
getDashboard = (): Promise<any> => {
return new Promise((res, rej) => {
@ -216,6 +232,16 @@ export default class Application extends React.Component<IProps> {
});
}
updateDoneLevels = (id: string): void => {
fetch(`${BACKEND_URL}/api/user/level/${id}`, {
headers: new Headers({
"Content-Type": "application/json",
"Token": this.props.user.sessionToken,
}),
method: "POST",
});
}
login = (username: string, password: string): Promise<IUser | IResponse> => {
return new Promise((res, rej) => {
fetch(`${BACKEND_URL}/api/login`, {
@ -282,7 +308,8 @@ export default class Application extends React.Component<IProps> {
isAuth={this.isAuthenticated}
path="/welcome"
component={() => {
return <WelcomePage />
return <WelcomePage
dontShowAgain={this.introDontShowAgain} />
}} />
<AuthRoute
isAuth={this.isAuthenticated}
@ -297,7 +324,7 @@ export default class Application extends React.Component<IProps> {
return <LevelPage
id={match.params.id}
levelVocab={this.getLevelVocab}
drawerButtonState={this.drawerButtonState}
updateDoneLevels={this.updateDoneLevels}
setLastReview={this.setLastReview} />;
} else {
return <Redirect to="/login" />;

View File

@ -9,7 +9,11 @@ import CardContent from "@material-ui/core/CardContent";
import CardActions from "@material-ui/core/CardActions";
import Button from "@material-ui/core/Button";
export default class IntroPage extends React.Component<{}> {
interface IProps {
dontShowAgain: () => void;
};
export default class IntroPage extends React.Component<IProps> {
render() {
const small = window.matchMedia("(max-width: 700px)").matches;
const cName = small ? "intro-card-xs" : "intro-card-lg";
@ -68,11 +72,11 @@ export default class IntroPage extends React.Component<{}> {
</Typography>
</CardContent>
<CardActions>
{/*TODO: Tell the server to not show this page again*/}
<Button
fullWidth={true}
component={Link}
to="/dashboard">
to="/dashboard"
onClick={this.props.dontShowAgain}>
Lass uns loslegen
</Button>
</CardActions>

View File

@ -17,6 +17,7 @@ import { IVocab } from "../models/vocab";
interface IProps {
id: string;
levelVocab: (id: string) => Promise<IVocab[]>;
updateDoneLevels: (id: string) => void;
history: any;
@ -88,6 +89,7 @@ const LevelPageWithRouter = withRouter(
const { vocab, lookedAt, id } = this.props;
// Only go to the review if all vocabulary item have been looked at
if (vocab.length === lookedAt.length) {
this.props.updateDoneLevels(id);
this.props.setLoading(true);
this.props.history.push(`/review/level/${id}`);
}

View File

@ -18,3 +18,6 @@ Sick
# 18.09.2018
- Move the whole application from "vanilla React" to React with Redux to avoid issues with components
being re-mounted, when the AppBar gets updated
# 29.09.2018
- Finish implementing the most needed API endpoints

View File

@ -13,6 +13,10 @@ http {
# server_name lateinicus;
listen 80 default_server;
# Enable gzip compression
gzip on;
gzip_min_length 256K;
# Reverse Proxy
location /api/ {
# Seems weird, but it is (Prevent /api/api/)