diff --git a/src/api/app/index.ts b/src/api/app/index.ts index c3bc6bf..c865847 100644 --- a/src/api/app/index.ts +++ b/src/api/app/index.ts @@ -3,6 +3,7 @@ import verifySession from '../_common/authMiddleware/verifySession'; import deck from './deck'; import decks from './decks'; import session from './session'; +import review from './review'; const app = express.Router(); app.use(verifySession); @@ -10,5 +11,6 @@ app.use(verifySession); app.use('/session', session); app.use('/deck', deck); app.use('/decks', decks); +app.use('/review', review); export default app; diff --git a/src/api/app/review/index.ts b/src/api/app/review/index.ts new file mode 100644 index 0000000..c98d3bb --- /dev/null +++ b/src/api/app/review/index.ts @@ -0,0 +1,71 @@ +import express from 'express'; +import db from '../../../db'; + +const review = express.Router(); + +review.get('/:deck', async (req, res) => { + const cards = await db.query(/* sql */ ` + SELECT "front", "back", "card_id", "due", "pulled", "forgotten" FROM "user_decks" + JOIN "cards" ON "user_decks"."deck_id" = "cards"."deck_id" + WHERE "user_decks"."deck_id" = $1 AND "user_decks"."user_id" = $2 + AND "due" <= CURRENT_TIMESTAMP; + `, [req.params.deck, req.user!.id]); + if (cards.rows.length === 0) { + res.status(204).send(); + return; + } + const pulledCard = cards.rows[Math.floor(Math.random() * cards.rows.length)]; + const difficulty = pulledCard.forgotten / (pulledCard.pulled + pulledCard.forgotten + 1); + // Harder challenge = smaller number. + let challengeType: 'easy' | 'hard' = difficulty < 0.5 ? 'hard' : 'easy'; + if (pulledCard.pulled === 0) challengeType = 'easy'; + res.status(200).send({ + card: { + front: pulledCard.front, + back: pulledCard.back, + card_id: pulledCard.card_id, + }, + challengeType, + }); +}); + +review.patch('/:deck/:card/', async (req, res) => { + console.log(req.body); + if (!req.params.deck.match(/^[0-9]+$/ || !req.params.card.match(/^[0-9]+$/))) { + res.status(400).send({ error: 'invalidId' }); + return; + } + + if (!('deltaT' in req.body) || typeof req.body.deltaT !== 'number' || req.body.deltaT < 0 || !('forgotten' in req.body) || typeof req.body.forgotten !== 'boolean') { + res.status(400).send({ error: 'invalidBody' }); + return; + } + + const card = await db.query(/* sql */ ` + SELECT "due", "pulled", "forgotten" FROM "user_decks" + JOIN "cards" ON "user_decks"."deck_id" = "cards"."deck_id" + WHERE "user_decks"."deck_id" = $1 AND "user_decks"."user_id" = $2 + AND "due" <= CURRENT_TIMESTAMP + AND "card_id" = $3; + `, [req.params.deck, req.user!.id, req.params.card]); + + if (card.rows.length === 0) { + res.status(404).send({ error: 'pullNotFound' }); + return; + } + + const q = db.query(/* sql */ ` + UPDATE "cards" + SET "pulled" = "pulled" + 1, + "due" = CURRENT_TIMESTAMP + ($2 * INTERVAL '1 second'), + "forgotten" = "forgotten" + $3 + WHERE "card_id" = $1; + `, [req.params.card, req.body.deltaT, req.body.forgotten ? 1 : 0]) + .then(async () => { + res.status(204).send(); + console.log(await q); + }) + .catch(() => res.status(500).send({ error: 'internalError' })); +}); + +export default review; diff --git a/src/db.ts b/src/db.ts index 227126d..5ee3735 100644 --- a/src/db.ts +++ b/src/db.ts @@ -55,23 +55,15 @@ export async function init() { "front" text NOT NULL, "back" text NOT NULL, "deck_id" INT NOT NULL, + "due" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + "pulled" INT NOT NULL DEFAULT 0, + "forgotten" INT NOT NULL DEFAULT 0, CONSTRAINT "deck_id" FOREIGN KEY ("deck_id") REFERENCES decks("deck_id") ON DELETE CASCADE, CONSTRAINT "cards_pk" PRIMARY KEY ("card_id") ); - CREATE TABLE IF NOT EXISTS card_events ( - "event_id" INT GENERATED ALWAYS AS IDENTITY, - "user_id" INT NOT NULL, - "card_id" INT NOT NULL, - "date" timestamp NOT NULL, - "confidence" INT NOT NULL, - CONSTRAINT "card_id" FOREIGN KEY ("card_id") REFERENCES cards("card_id") ON DELETE CASCADE, - CONSTRAINT "user_id" FOREIGN KEY ("user_id") REFERENCES users("user_id") ON DELETE CASCADE, - CONSTRAINT "card_events_pk" PRIMARY KEY ("event_id") - ); - CREATE INDEX IF NOT EXISTS "card_index" ON cards ( - "front", "back", "deck_id" + "front", "back", "deck_id", "due" ); `); }