| @@ -6,6 +6,8 @@ import { connectToDatabaseServer } from './db-utils'; | |||||
| import sendGridMail from '@sendgrid/mail'; | import sendGridMail from '@sendgrid/mail'; | ||||
| import { userProfileRoutes } from './user-profile/routes'; | import { userProfileRoutes } from './user-profile/routes'; | ||||
| import { revisionRoutes } from './revision/routes'; | import { revisionRoutes } from './revision/routes'; | ||||
| import { categoryRoutes } from './user-profile/category-routes'; | |||||
| import { shelfRoutes } from './user-profile/shelf-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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'; | ||||
| @@ -19,6 +21,8 @@ app.set('port', process.env.PORT || 8001); | |||||
| app.use('/', authRoutes); | app.use('/', authRoutes); | ||||
| app.use('/', userProfileRoutes); | app.use('/', userProfileRoutes); | ||||
| app.use('/', revisionRoutes); | app.use('/', revisionRoutes); | ||||
| app.use('/', categoryRoutes); | |||||
| app.use('/', shelfRoutes); | |||||
| app.get('/', (request, response) => { | app.get('/', (request, response) => { | ||||
| response.send('Server running @ port' + app.get('port')); | response.send('Server running @ port' + app.get('port')); | ||||
| @@ -1,7 +1,6 @@ | |||||
| import { Shelf } from "./shelf"; | import { Shelf } from "./shelf"; | ||||
| export interface Category { | |||||
| _id: string, | |||||
| export interface Category { //_id is not needed as it is created by default | |||||
| name: string, | name: string, | ||||
| icon: string, | icon: string, | ||||
| shelves?: Array<Shelf>, | shelves?: Array<Shelf>, | ||||
| @@ -1,40 +1,33 @@ | |||||
| import { viewPermissionType } from "./variables"; | import { viewPermissionType } from "./variables"; | ||||
| import { Word } from "./word"; | import { Word } from "./word"; | ||||
| export interface Shelf { | |||||
| _id: string, | |||||
| name: string, | |||||
| description: string, | |||||
| viewType: viewPermissionType, | |||||
| interface RevisionHistoryRecord { | |||||
| day: Date, | |||||
| wasRecallSuccessful: boolean | |||||
| } | |||||
| interface AddedWord { | |||||
| word: Word, | |||||
| notes: Array<string>, | |||||
| isFavourite: boolean, | |||||
| nextRevisionDateTime: Date, | |||||
| spaceBetweenRecall: number, // in Days | |||||
| revisionHistory : Array<RevisionHistoryRecord>, | |||||
| isArchived: boolean, | isArchived: boolean, | ||||
| addedWords: Array<{ | |||||
| word: Word, | |||||
| notes: Array<string>, | |||||
| isFavourite: boolean, | |||||
| nextRevisionDateTime: Date, | |||||
| spaceBetweenRecall: number, // in Days | |||||
| revisionHistory : Array<{ | |||||
| day: Date, | |||||
| wasRecallSuccessful: boolean, | |||||
| }> | |||||
| }> | |||||
| } | } | ||||
| export interface MongoShelf { | |||||
| _id: string, | |||||
| export interface Shelf { | |||||
| name: string, | name: string, | ||||
| description: string, | |||||
| description?: string, | |||||
| viewType: viewPermissionType, | viewType: viewPermissionType, | ||||
| isArchived: boolean, | isArchived: boolean, | ||||
| addedWords: Array<{ | |||||
| word: Word, | |||||
| notes: Array<string>, | |||||
| isFavourite: boolean, | |||||
| nextRevisionDateTime: Date, | |||||
| spaceBetweenRecall: number, // in Days | |||||
| revisionHistory : Array<{ | |||||
| day: Date, | |||||
| wasRecallSuccessful: boolean, | |||||
| }> | |||||
| }> | |||||
| addedWords?: Array<AddedWord> | |||||
| } | |||||
| interface MongoAddedWord extends Omit<AddedWord, "word"> { | |||||
| word: string, | |||||
| } | |||||
| export interface MongoShelf extends Omit<Shelf, "addedWords"> { | |||||
| addedWords?: Array<MongoAddedWord> | |||||
| } | } | ||||
| @@ -12,6 +12,7 @@ export interface User { | |||||
| uncategorised?: Shelf, | uncategorised?: Shelf, | ||||
| } | } | ||||
| export interface MongoUser extends Omit<User, "categories"> { | |||||
| categories?: Array<string> // Category IDs | |||||
| export interface MongoUser extends Omit<Omit<User, "categories">, "uncategorised"> { | |||||
| categories?: Array<string> // Category IDs, | |||||
| uncategorised: string | |||||
| } | } | ||||
| @@ -7,7 +7,7 @@ interface gramaticalDetail { | |||||
| } | } | ||||
| export interface Word { | export interface Word { | ||||
| _id: string, | |||||
| //_id is present by default in the DB | |||||
| name: string, | name: string, | ||||
| pronounciation: { | pronounciation: { | ||||
| text: string, | text: string, | ||||
| @@ -15,11 +15,6 @@ export interface Word { | |||||
| }, | }, | ||||
| similarWords?: Array<Word>, | similarWords?: Array<Word>, | ||||
| grammaticalDetails: Array<gramaticalDetail>, | grammaticalDetails: Array<gramaticalDetail>, | ||||
| wordStats: { | |||||
| favouriteCount: number, // Total users who liked this word | |||||
| addCount: number, // Total users have added this word to their shelf | |||||
| }, | |||||
| isArchived: boolean, | |||||
| } | } | ||||
| export interface MongoWord extends Omit<Word, "similarWords"> { | export interface MongoWord extends Omit<Word, "similarWords"> { | ||||
| @@ -0,0 +1,119 @@ | |||||
| import express from 'express'; | |||||
| import passport from 'passport'; | |||||
| import { MongoUser } from '../models/user'; | |||||
| import { DB_NAME } from '../db-utils'; | |||||
| import { getDatabaseClient } from '../db-utils'; | |||||
| import { ObjectId } from 'bson'; | |||||
| import { MongoCategory } from '..//models/category'; | |||||
| export const categoryRoutes = express.Router(); | |||||
| export const jwtAuthentication = passport.authenticate('jwt', { session: false }); | |||||
| // Get Category Details | |||||
| categoryRoutes.get('/category/', jwtAuthentication, async (request, response) => { | |||||
| const categoryCollection = getDatabaseClient().db(DB_NAME).collection<MongoCategory>('categories'); | |||||
| const currentCategory = await categoryCollection.findOne({ | |||||
| _id: new ObjectId(request.body._id), | |||||
| }); | |||||
| if (currentCategory) { | |||||
| response.status(200); | |||||
| response.json(currentCategory); | |||||
| } else { | |||||
| response.status(400); | |||||
| response.send("Category ID did not match"); | |||||
| } | |||||
| return; | |||||
| }); | |||||
| // Add category | |||||
| categoryRoutes.post('/category/', jwtAuthentication, async (request, response) => { | |||||
| const user: MongoUser = (request.user as any); | |||||
| const categoryCollection = getDatabaseClient().db(DB_NAME).collection<MongoCategory>('categories'); | |||||
| const userCollection = getDatabaseClient().db(DB_NAME).collection<MongoUser>('users'); | |||||
| if (!request.body.name) { | |||||
| response.status(400); | |||||
| response.send("Category Name missing"); | |||||
| return; | |||||
| } | |||||
| try { | |||||
| const newCategory = await categoryCollection.insertOne({ | |||||
| name: request.body.name, | |||||
| icon: request.body.icon ? request.body.icon : "", | |||||
| isArchived: false, | |||||
| }); | |||||
| if (!user.categories) { | |||||
| user.categories = []; | |||||
| } | |||||
| user.categories.push(newCategory.insertedId.toHexString()); | |||||
| await userCollection.updateOne({ | |||||
| _id: user._id | |||||
| }, { | |||||
| $set: { | |||||
| categories: user.categories | |||||
| } | |||||
| }); | |||||
| response.sendStatus(200); | |||||
| } catch(e) { | |||||
| response.sendStatus(500); | |||||
| return; | |||||
| } | |||||
| return; | |||||
| }); | |||||
| // Update category | |||||
| categoryRoutes.put('/category/', jwtAuthentication, async (request, response) => { | |||||
| const categoryCollection = getDatabaseClient().db(DB_NAME).collection('categories'); | |||||
| const currentCategory = await categoryCollection.findOne({ | |||||
| _id: new ObjectId(request.body._id) | |||||
| }); | |||||
| if (!currentCategory) { | |||||
| response.status(400); | |||||
| response.send("Category ID did not match"); | |||||
| return; | |||||
| } | |||||
| if (request.body.isArchived) { | |||||
| if (typeof request.body.isArchived !== "boolean") { | |||||
| response.status(400); | |||||
| response.send("Archived should be a boolean flag"); | |||||
| return; | |||||
| } | |||||
| } | |||||
| try { | |||||
| await categoryCollection.updateOne({ | |||||
| _id: new ObjectId(request.body._id), | |||||
| }, { | |||||
| $set: { | |||||
| name: request.body.name ? request.body.name : currentCategory.name, | |||||
| icon: request.body.icon ? request.body.icon : currentCategory.icon, | |||||
| isArchived: request.body.isArchived !== undefined ? request.body.isArchived : currentCategory.isArchived | |||||
| } | |||||
| }); | |||||
| response.sendStatus(200); | |||||
| } catch (e) { | |||||
| console.log(e); | |||||
| response.status(400); | |||||
| response.send(e.toString()); | |||||
| } | |||||
| return; | |||||
| }); | |||||
| @@ -1,10 +1,6 @@ | |||||
| import express from 'express'; | import express from 'express'; | ||||
| import passport, { use } from 'passport'; | |||||
| import passport from 'passport'; | |||||
| import { MongoUser } from '../models/user'; | import { MongoUser } from '../models/user'; | ||||
| import { Category, MongoCategory } from '../models/category'; | |||||
| import { DB_NAME } from '../db-utils'; | |||||
| import { getDatabaseClient } from '../db-utils'; | |||||
| import { ObjectId } from 'bson'; | |||||
| export const userProfileRoutes = express.Router(); | export const userProfileRoutes = express.Router(); | ||||
| @@ -22,92 +18,3 @@ userProfileRoutes.get('/profile/', jwtAuthentication, async (request, response) | |||||
| }); | }); | ||||
| return; | return; | ||||
| }); | }); | ||||
| userProfileRoutes.post('/category/', jwtAuthentication, async (request, response) => { | |||||
| const user: MongoUser = (request.user as any); | |||||
| const categoryCollection = getDatabaseClient().db(DB_NAME).collection('categories'); | |||||
| const userCollection = getDatabaseClient().db(DB_NAME).collection('users'); | |||||
| if (!request.body.name || !request.body.icon) { | |||||
| response.status(400); | |||||
| response.send("Category Name or icon(base64) missing"); | |||||
| return; | |||||
| } | |||||
| try { | |||||
| const newCategory = await categoryCollection.insertOne({ | |||||
| name: request.body.name, | |||||
| icon: request.body.icon, | |||||
| isArchived: false, | |||||
| }); | |||||
| if (!user.categories) { | |||||
| user.categories = []; | |||||
| } | |||||
| user.categories.push(newCategory.insertedId.toHexString()); | |||||
| await userCollection.updateOne({ | |||||
| _id: user._id | |||||
| }, { | |||||
| $set: { | |||||
| categories: user.categories | |||||
| } | |||||
| }); | |||||
| response.sendStatus(200); | |||||
| } catch(e) { | |||||
| response.sendStatus(500); | |||||
| return; | |||||
| } | |||||
| return; | |||||
| }); | |||||
| userProfileRoutes.put('/category/', jwtAuthentication, async (request, response) => { | |||||
| const categoryCollection = getDatabaseClient().db(DB_NAME).collection('categories'); | |||||
| let currentCategory; | |||||
| try { | |||||
| currentCategory = await categoryCollection.findOne({ | |||||
| _id: new ObjectId(request.body._id) | |||||
| }); | |||||
| } catch { | |||||
| if (!currentCategory) { | |||||
| response.status(400); | |||||
| response.send("Category ID did not match"); | |||||
| return; | |||||
| } | |||||
| } | |||||
| if (request.body.isArchived) { | |||||
| if (typeof request.body.isArchived !== "boolean") { | |||||
| response.status(400); | |||||
| response.send("Archived should be a boolean flag"); | |||||
| return; | |||||
| } | |||||
| } | |||||
| try { | |||||
| await categoryCollection.updateOne({ | |||||
| _id: new ObjectId(request.body._id), | |||||
| }, { | |||||
| $set: { | |||||
| name: request.body.name ? request.body.name : currentCategory.name, | |||||
| icon: request.body.icon ? request.body.icon : currentCategory.icon, | |||||
| isArchived: request.body.isArchived !== undefined ? request.body.isArchived : currentCategory.isArchived | |||||
| } | |||||
| }); | |||||
| response.sendStatus(200); | |||||
| } catch (e) { | |||||
| response.status(400); | |||||
| response.json(e); | |||||
| } | |||||
| return; | |||||
| }); | |||||
| @@ -0,0 +1,140 @@ | |||||
| import { ObjectId } from 'bson'; | |||||
| import express from 'express'; | |||||
| import passport from 'passport'; | |||||
| import { MongoCategory } from '../models/category'; | |||||
| import { DB_NAME } from '../db-utils'; | |||||
| import { getDatabaseClient } from '../db-utils'; | |||||
| import { MongoShelf } from '../models/shelf'; | |||||
| export const shelfRoutes = express.Router(); | |||||
| export const jwtAuthentication = passport.authenticate('jwt', { session: false }); | |||||
| // GET shelf details | |||||
| shelfRoutes.get('/shelf/', jwtAuthentication, async (request, response) => { | |||||
| const shelfCollection = getDatabaseClient().db(DB_NAME).collection<MongoShelf>('shelves'); | |||||
| const currentShelf = await shelfCollection.findOne({ | |||||
| _id: new ObjectId(request.body._id) | |||||
| }); | |||||
| if (!currentShelf) { | |||||
| response.status(400); | |||||
| response.send("No shelf with the ID"); | |||||
| } else { | |||||
| response.status(200); | |||||
| response.json(currentShelf); | |||||
| } | |||||
| return; | |||||
| }); | |||||
| // Add shelf | |||||
| shelfRoutes.post('/shelf/', jwtAuthentication, async (request, response) => { | |||||
| const categoryCollection = getDatabaseClient().db(DB_NAME).collection<MongoCategory>('categories'); | |||||
| const shelfCollection = getDatabaseClient().db(DB_NAME).collection<MongoShelf>('shelves'); | |||||
| const currentCategory = await categoryCollection.findOne({ | |||||
| _id: new ObjectId(request.body.categoryId) | |||||
| }); | |||||
| let newShelf; | |||||
| if (!currentCategory) { | |||||
| response.status(400); | |||||
| response.send("Category ID did not match"); | |||||
| return; | |||||
| } | |||||
| if (!request.body.name) { | |||||
| response.sendStatus(400); | |||||
| response.send("Missing shelf name"); | |||||
| return; | |||||
| } | |||||
| try { | |||||
| newShelf = await shelfCollection.insertOne({ | |||||
| name: request.body.name, | |||||
| description: request.body.description ? request.body.description : "", | |||||
| viewType: request.body.viewType ? request.body.viewType : "PRIVATE", | |||||
| isArchived: false, | |||||
| }); | |||||
| } catch (e) { | |||||
| response.sendStatus(500); | |||||
| return; | |||||
| } | |||||
| if (!newShelf) { | |||||
| response.sendStatus(500); | |||||
| return; | |||||
| } | |||||
| if (!currentCategory.shelves) { | |||||
| currentCategory.shelves = []; | |||||
| } | |||||
| currentCategory.shelves.push(newShelf.insertedId.toHexString()); | |||||
| const updatedCategory = await categoryCollection.updateOne({ | |||||
| _id: new ObjectId(request.body.categoryId) | |||||
| }, { | |||||
| $set : { | |||||
| shelves: currentCategory.shelves | |||||
| } | |||||
| }); | |||||
| if (updatedCategory) { | |||||
| response.sendStatus(200); | |||||
| } else { | |||||
| response.sendStatus(500); | |||||
| } | |||||
| return; | |||||
| }); | |||||
| // Update shelf | |||||
| shelfRoutes.put('/shelf/', jwtAuthentication, async (request, response) => { | |||||
| const shelfCollection = getDatabaseClient().db(DB_NAME).collection<MongoShelf>('shelves'); | |||||
| const currentShelf = await shelfCollection.findOne({ | |||||
| _id: new ObjectId(request.body._id) | |||||
| }); | |||||
| if (!currentShelf) { | |||||
| response.status(400); | |||||
| response.send("No shelf with the ID"); | |||||
| } | |||||
| if (request.body.isArchived) { | |||||
| if (typeof request.body.isArchived !== "boolean") { | |||||
| response.status(400); | |||||
| response.send("Archived should be a boolean flag"); | |||||
| return; | |||||
| } | |||||
| } | |||||
| try { | |||||
| await shelfCollection.updateOne({ | |||||
| _id: new ObjectId(request.body._id), | |||||
| }, { | |||||
| $set: { | |||||
| name: request.body.name ? request.body.name : currentShelf.name, | |||||
| description: request.body.description ? request.body.description : currentShelf.description, | |||||
| isArchived: request.body.isArchived !== undefined ? request.body.isArchived : currentShelf.isArchived | |||||
| } | |||||
| }); | |||||
| response.sendStatus(200); | |||||
| } catch (e) { | |||||
| console.log(e); | |||||
| response.status(400); | |||||
| response.send(e.toString()); | |||||
| } | |||||
| return; | |||||
| }); | |||||