feat: Implement updating the done levels
This commit is contained in:
parent
e6e7505383
commit
ab0c75331d
@ -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: {
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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 {
|
||||
|
@ -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" />;
|
||||
|
@ -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>
|
||||
|
@ -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}`);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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/)
|
||||
|
Reference in New Issue
Block a user