| @@ -8,6 +8,8 @@ import { userProfileRoutes } from './user-profile/profile-routes'; | |||||
| import { categoryRoutes } from './user-profile/category-routes'; | import { categoryRoutes } from './user-profile/category-routes'; | ||||
| import { shelfRoutes } from './user-profile/shelf-routes'; | import { shelfRoutes } from './user-profile/shelf-routes'; | ||||
| import { libraryRoutes } from './library/library-routes'; | import { libraryRoutes } from './library/library-routes'; | ||||
| import { recollectionRoutes } from './revision/revision-routes'; | |||||
| import { recollectionHistoryRoutes } from './user-profile/recollection-history-routes'; | |||||
| const SENDGRID_API_KEY = process.env.SENDGRID_API_KEY || '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'; | const SENDGRID_API_KEY = process.env.SENDGRID_API_KEY || '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'; | ||||
| @@ -23,6 +25,8 @@ app.use('/user/', userProfileRoutes); | |||||
| app.use('/category/', categoryRoutes); | app.use('/category/', categoryRoutes); | ||||
| app.use('/shelf/', shelfRoutes); | app.use('/shelf/', shelfRoutes); | ||||
| app.use('/library/', libraryRoutes); | app.use('/library/', libraryRoutes); | ||||
| app.use('/recollection/', recollectionRoutes); | |||||
| app.use('/recollection-history/', recollectionHistoryRoutes); | |||||
| app.get('/', (request, response) => { | app.get('/', (request, response) => { | ||||
| response.send('Server running @ port' + app.get('port')); | response.send('Server running @ port' + app.get('port')); | ||||
| @@ -0,0 +1,6 @@ | |||||
| export interface RecollectionHistory { | |||||
| userId: string, | |||||
| wordId: string, | |||||
| timeStamp: Date, | |||||
| wasRecallSuccessful: boolean | |||||
| } | |||||
| @@ -1,18 +1,11 @@ | |||||
| import { viewPermissionType } from "./variables"; | import { viewPermissionType } from "./variables"; | ||||
| import { Word } from "./word"; | import { Word } from "./word"; | ||||
| interface RevisionHistoryRecord { | |||||
| day: Date, | |||||
| wasRecallSuccessful: boolean | |||||
| } | |||||
| interface AddedWord { | |||||
| interface ShelfWord { | |||||
| word: Word, | word: Word, | ||||
| notes: Array<string>, | notes: Array<string>, | ||||
| isFavourite: boolean, | |||||
| nextRevisionDateTime: Date, | nextRevisionDateTime: Date, | ||||
| spaceBetweenRecall: number, // in Days | spaceBetweenRecall: number, // in Days | ||||
| revisionHistory : Array<RevisionHistoryRecord>, | |||||
| isArchived: boolean, | isArchived: boolean, | ||||
| } | } | ||||
| @@ -21,13 +14,13 @@ export interface Shelf { | |||||
| description?: string, | description?: string, | ||||
| viewType: viewPermissionType, | viewType: viewPermissionType, | ||||
| isArchived: boolean, | isArchived: boolean, | ||||
| addedWords?: Array<AddedWord> | |||||
| words?: Array<ShelfWord> | |||||
| } | } | ||||
| export interface MongoAddedWord extends Omit<AddedWord, "word"> { | |||||
| export interface MongoShelfWord extends Omit<ShelfWord, "word"> { | |||||
| word: string, | word: string, | ||||
| } | } | ||||
| export interface MongoShelf extends Omit<Shelf, "addedWords"> { | |||||
| addedWords?: Array<MongoAddedWord> | |||||
| export interface MongoShelf extends Omit<Shelf, "words"> { | |||||
| words?: Array<MongoShelfWord> | |||||
| } | } | ||||
| @@ -1,5 +1,6 @@ | |||||
| import { Category } from "./category"; | import { Category } from "./category"; | ||||
| import { Shelf } from "./shelf"; | import { Shelf } from "./shelf"; | ||||
| import { Word } from "./word"; | |||||
| export interface User { | export interface User { | ||||
| _id: string, | _id: string, | ||||
| @@ -10,9 +11,11 @@ export interface User { | |||||
| otp: number, | otp: number, | ||||
| categories?: Array<Category>, | categories?: Array<Category>, | ||||
| uncategorised?: Shelf, | uncategorised?: Shelf, | ||||
| favouriteWords?: Array<Word>, | |||||
| } | } | ||||
| export interface MongoUser extends Omit<Omit<User, "categories">, "uncategorised"> { | |||||
| export interface MongoUser extends Omit<User, "categories" | "uncategorised" | "favouriteWords"> { | |||||
| categories?: Array<string> // Category IDs, | categories?: Array<string> // Category IDs, | ||||
| uncategorised: string | |||||
| uncategorised?: string, // Shelf ID | |||||
| favouriteWords?: Array<string> // Favourite Words ID | |||||
| } | } | ||||
| @@ -0,0 +1,16 @@ | |||||
| import express from 'express'; | |||||
| import passport from 'passport'; | |||||
| import { MongoShelf } from '../models/shelf'; | |||||
| import { DB_NAME, getDatabaseClient } from '../db-utils'; | |||||
| import { MongoCategory } from '../models/category'; | |||||
| export const recollectionRoutes = express.Router(); | |||||
| export const jwtAuthentication = passport.authenticate('jwt', { session: false }); | |||||
| recollectionRoutes.get('/questions/', jwtAuthentication, async (request, response) => { | |||||
| const shelfCollection = getDatabaseClient().db(DB_NAME).collection<MongoShelf>('shelves'); | |||||
| const categoryCollection = getDatabaseClient().db(DB_NAME).collection<MongoCategory>('categories'); | |||||
| }); | |||||
| @@ -1,10 +1,8 @@ | |||||
| import { ObjectId } from 'bson'; | |||||
| import express from 'express'; | import express from 'express'; | ||||
| import passport from 'passport'; | import passport from 'passport'; | ||||
| import { MongoShelf } from '../models/shelf'; | |||||
| import { DB_NAME, getDatabaseClient } from '../db-utils'; | import { DB_NAME, getDatabaseClient } from '../db-utils'; | ||||
| import { MongoCategory } from '../models/category'; | |||||
| import { MongoUser } from '../models/user'; | import { MongoUser } from '../models/user'; | ||||
| import { ObjectId } from 'bson'; | |||||
| import { Word } from '../models/word'; | import { Word } from '../models/word'; | ||||
| export const userProfileRoutes = express.Router(); | export const userProfileRoutes = express.Router(); | ||||
| @@ -19,91 +17,86 @@ userProfileRoutes.get('/profile/', jwtAuthentication, async (request, response) | |||||
| email: user.email, | email: user.email, | ||||
| isVerified: user.isVerified, | isVerified: user.isVerified, | ||||
| categories: user.categories, | categories: user.categories, | ||||
| uncategorised: user.uncategorised | |||||
| uncategorised: user.uncategorised, | |||||
| favouriteWords: user.favouriteWords, | |||||
| }); | }); | ||||
| return; | return; | ||||
| }); | }); | ||||
| // Add favourite word | |||||
| userProfileRoutes.get('/profile/deep-copy/', jwtAuthentication, async (request, response) => { | |||||
| userProfileRoutes.post('/favourite-words/', jwtAuthentication, async (request, response) => { | |||||
| const user: MongoUser = (request.user as any); | const user: MongoUser = (request.user as any); | ||||
| const categoryCollection = getDatabaseClient().db(DB_NAME).collection<MongoCategory>('categories'); | |||||
| const shelfCollection = getDatabaseClient().db(DB_NAME).collection<MongoShelf>('shelves'); | |||||
| const userCollection = getDatabaseClient().db(DB_NAME).collection<MongoUser>('users'); | |||||
| const wordCollection = getDatabaseClient().db(DB_NAME).collection<Word>('words'); | const wordCollection = getDatabaseClient().db(DB_NAME).collection<Word>('words'); | ||||
| let deepCategories = []; | |||||
| if (!user.favouriteWords) { | |||||
| user.favouriteWords = []; | |||||
| } | |||||
| for (let i = 0; i < user.categories.length; i += 1) { | |||||
| let deepShelves = []; | |||||
| if (!request.body.wordId) { | |||||
| response.status(400); | |||||
| response.send("Missing wordID"); | |||||
| return; | |||||
| } else if (user.favouriteWords.includes(request.body.wordId)) { | |||||
| response.status(400); | |||||
| response.send("WordID already present"); | |||||
| return; | |||||
| } | |||||
| const matchedCategory: any = await categoryCollection.findOne({ | |||||
| _id: new ObjectId(user.categories[i]) | |||||
| }); | |||||
| const matchedWord = await wordCollection.findOne({ | |||||
| _id: new ObjectId(request.body.wordId) | |||||
| }); | |||||
| if (matchedCategory && matchedCategory.shelves) { | |||||
| for (let j = 0; j < matchedCategory.shelves.length; j += 1) { | |||||
| let deepAddedWords = []; | |||||
| const matchedShelf: any = await shelfCollection.findOne({ | |||||
| _id: new ObjectId(matchedCategory.shelves[j]) | |||||
| }); | |||||
| if (matchedShelf && matchedShelf.addedWords) { | |||||
| for (let k = 0; k < matchedShelf.addedWords.length; k += 1) { | |||||
| const matchedWord = await wordCollection.findOne({ | |||||
| _id: new ObjectId(matchedShelf.addedWords[k].word) | |||||
| }); | |||||
| if (matchedWord) { | |||||
| deepAddedWords.push(matchedWord); | |||||
| } | |||||
| } | |||||
| } | |||||
| matchedShelf.addedWords = deepAddedWords; | |||||
| deepShelves.push(matchedShelf); | |||||
| } | |||||
| } | |||||
| if (!matchedWord) { | |||||
| response.status(400); | |||||
| response.send("Word not present in the library"); | |||||
| return; | |||||
| } | |||||
| matchedCategory.shelves = deepShelves; | |||||
| deepCategories.push(matchedCategory); | |||||
| user.favouriteWords.push(request.body.wordId); | |||||
| const updatedUser = await userCollection.updateOne({ | |||||
| _id: new ObjectId(user._id) | |||||
| }, { | |||||
| $set: { | |||||
| favouriteWords: user.favouriteWords | |||||
| } | |||||
| }); | |||||
| if (updatedUser.acknowledged) { | |||||
| response.sendStatus(200); | |||||
| } else { | |||||
| response.sendStatus(500); | |||||
| } | } | ||||
| let deepUncategorisedAddedWords = []; | |||||
| const matchedUncategoisedShelf: any = await shelfCollection.findOne({ | |||||
| _id: new ObjectId(user.uncategorised) | |||||
| }); | |||||
| if (matchedUncategoisedShelf && matchedUncategoisedShelf.addedWords) { | |||||
| for (let k = 0; k < matchedUncategoisedShelf.addedWords.length; k += 1) { | |||||
| const matchedWord = await wordCollection.findOne({ | |||||
| _id: new ObjectId(matchedUncategoisedShelf.addedWords[k].word) | |||||
| }); | |||||
| if (matchedWord) { | |||||
| deepUncategorisedAddedWords.push(matchedWord); | |||||
| } | |||||
| } | |||||
| return; | |||||
| }); | |||||
| matchedUncategoisedShelf.addedWords = deepUncategorisedAddedWords; | |||||
| // Update favourite word | |||||
| userProfileRoutes.put('/favourite-words/', jwtAuthentication, async (request, response) => { | |||||
| const user: MongoUser = (request.user as any); | |||||
| const userCollection = getDatabaseClient().db(DB_NAME).collection<MongoUser>('users'); | |||||
| if (!request.body.favouriteWords) { | |||||
| response.status(400); | |||||
| response.send("Missing favouriteWords param"); | |||||
| return; | |||||
| } | } | ||||
| response.json({ | |||||
| _id: user._id, | |||||
| name: user.name, | |||||
| email: user.email, | |||||
| isVerified: user.isVerified, | |||||
| categories: deepCategories, | |||||
| uncategorised: matchedUncategoisedShelf | |||||
| const updatedUser = await userCollection.updateOne({ | |||||
| _id: new ObjectId(user._id) | |||||
| }, { | |||||
| $set: { | |||||
| favouriteWords: request.body.favouriteWords | |||||
| } | |||||
| }); | }); | ||||
| if (updatedUser.acknowledged) { | |||||
| response.sendStatus(200); | |||||
| } else { | |||||
| response.sendStatus(500); | |||||
| } | |||||
| return; | return; | ||||
| }); | }); | ||||
| @@ -0,0 +1,59 @@ | |||||
| import { RecollectionHistory } from '../models/recollection-history'; | |||||
| import express from 'express'; | |||||
| import passport from 'passport'; | |||||
| import { DB_NAME, getDatabaseClient } from '../db-utils'; | |||||
| import { MongoUser } from '../models/user'; | |||||
| import { ObjectId } from 'bson'; | |||||
| export const recollectionHistoryRoutes = express.Router(); | |||||
| export const jwtAuthentication = passport.authenticate('jwt', { session: false }); | |||||
| // Get recollection history for the user | |||||
| recollectionHistoryRoutes.get('', jwtAuthentication, async (request, response) => { | |||||
| const user: MongoUser = (request.user as any); | |||||
| const recollectionHistoryCollection = getDatabaseClient().db(DB_NAME).collection<RecollectionHistory>('recollection-history'); | |||||
| const recollectionHistoryRecords = await recollectionHistoryCollection.find({ | |||||
| userId: new ObjectId(user._id) | |||||
| }).toArray(); | |||||
| if (recollectionHistoryRecords.length > 0) { | |||||
| response.status(200); | |||||
| response.json(recollectionHistoryRecords); | |||||
| } else { | |||||
| response.status(200); | |||||
| response.json([]); | |||||
| } | |||||
| return; | |||||
| }); | |||||
| // Add user recollection history | |||||
| recollectionHistoryRoutes.post('', jwtAuthentication, async (request, response) => { | |||||
| const user: MongoUser = (request.user as any); | |||||
| const recollectionHistoryCollection = getDatabaseClient().db(DB_NAME).collection<RecollectionHistory>('recollection-history'); | |||||
| if (!request.body.userId || !request.body.wordId || !request.body.timeStamp || !request.body.wasRecallSuccessful) { | |||||
| response.status(400); | |||||
| response.send("Missing attributes"); | |||||
| return; | |||||
| } | |||||
| const newRecollectionHistoryRecord = await recollectionHistoryCollection.insertOne({ | |||||
| userId: user._id, | |||||
| wordId: request.body.wordId, | |||||
| timeStamp: request.body.timeStamp, | |||||
| wasRecallSuccessful: request.body.wasRecallSuccessful | |||||
| }); | |||||
| if (newRecollectionHistoryRecord.acknowledged) { | |||||
| response.sendStatus(200); | |||||
| return; | |||||
| } else { | |||||
| response.sendStatus(500); | |||||
| return; | |||||
| } | |||||
| }); | |||||
| @@ -1,10 +1,10 @@ | |||||
| import { ObjectID, ObjectId } from 'bson'; | |||||
| import { ObjectId } from 'bson'; | |||||
| import express from 'express'; | import express from 'express'; | ||||
| import passport from 'passport'; | import passport from 'passport'; | ||||
| import { MongoCategory } from '../models/category'; | import { MongoCategory } from '../models/category'; | ||||
| import { DB_NAME } from '../db-utils'; | import { DB_NAME } from '../db-utils'; | ||||
| import { getDatabaseClient } from '../db-utils'; | import { getDatabaseClient } from '../db-utils'; | ||||
| import { MongoAddedWord, MongoShelf } from '../models/shelf'; | |||||
| import { MongoShelfWord, MongoShelf } from '../models/shelf'; | |||||
| import { Word } from '../models/word'; | import { Word } from '../models/word'; | ||||
| export const shelfRoutes = express.Router(); | export const shelfRoutes = express.Router(); | ||||
| @@ -99,7 +99,7 @@ shelfRoutes.post('/add/', jwtAuthentication, async (request, response) => { | |||||
| shelfRoutes.put('/update/', jwtAuthentication, async (request, response) => { | shelfRoutes.put('/update/', jwtAuthentication, async (request, response) => { | ||||
| const shelfCollection = getDatabaseClient().db(DB_NAME).collection<MongoShelf>('shelves'); | const shelfCollection = getDatabaseClient().db(DB_NAME).collection<MongoShelf>('shelves'); | ||||
| const wordCollection = getDatabaseClient().db(DB_NAME).collection<Word>('words'); | const wordCollection = getDatabaseClient().db(DB_NAME).collection<Word>('words'); | ||||
| const updatedAddedWords: Array<MongoAddedWord> = request.body.addedWords ? request.body.addedWords : []; | |||||
| const updatedAddedWords: Array<MongoShelfWord> = request.body.addedWords ? request.body.addedWords : []; | |||||
| const currentShelf = await shelfCollection.findOne({ | const currentShelf = await shelfCollection.findOne({ | ||||
| _id: new ObjectId(request.body._id) | _id: new ObjectId(request.body._id) | ||||
| @@ -121,8 +121,7 @@ shelfRoutes.put('/update/', jwtAuthentication, async (request, response) => { | |||||
| for (let i = 0; i < updatedAddedWords.length; i += 1) { | for (let i = 0; i < updatedAddedWords.length; i += 1) { | ||||
| if (!updatedAddedWords[i].word || updatedAddedWords[i].isArchived === undefined || | if (!updatedAddedWords[i].word || updatedAddedWords[i].isArchived === undefined || | ||||
| updatedAddedWords[i].isFavourite === undefined || !updatedAddedWords[i].nextRevisionDateTime || | |||||
| updatedAddedWords[i].notes === undefined || updatedAddedWords[i].revisionHistory === undefined || | |||||
| !updatedAddedWords[i].nextRevisionDateTime || updatedAddedWords[i].notes === undefined || | |||||
| !updatedAddedWords[i].spaceBetweenRecall) { | !updatedAddedWords[i].spaceBetweenRecall) { | ||||
| response.status(400); | response.status(400); | ||||
| response.send("Missing key in " + JSON.stringify(updatedAddedWords[i])); | response.send("Missing key in " + JSON.stringify(updatedAddedWords[i])); | ||||