| @@ -6,6 +6,8 @@ import { connectToDatabaseServer } from './db-utils'; | |||
| import sendGridMail from '@sendgrid/mail'; | |||
| import { userProfileRoutes } from './user-profile/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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'; | |||
| @@ -19,6 +21,8 @@ app.set('port', process.env.PORT || 8001); | |||
| app.use('/', authRoutes); | |||
| app.use('/', userProfileRoutes); | |||
| app.use('/', revisionRoutes); | |||
| app.use('/', categoryRoutes); | |||
| app.use('/', shelfRoutes); | |||
| app.get('/', (request, response) => { | |||
| response.send('Server running @ port' + app.get('port')); | |||
| @@ -1,7 +1,6 @@ | |||
| import { Shelf } from "./shelf"; | |||
| export interface Category { | |||
| _id: string, | |||
| export interface Category { //_id is not needed as it is created by default | |||
| name: string, | |||
| icon: string, | |||
| shelves?: Array<Shelf>, | |||
| @@ -1,40 +1,33 @@ | |||
| import { viewPermissionType } from "./variables"; | |||
| 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, | |||
| 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, | |||
| description: string, | |||
| description?: string, | |||
| viewType: viewPermissionType, | |||
| 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, | |||
| } | |||
| 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 { | |||
| _id: string, | |||
| //_id is present by default in the DB | |||
| name: string, | |||
| pronounciation: { | |||
| text: string, | |||
| @@ -15,11 +15,6 @@ export interface Word { | |||
| }, | |||
| similarWords?: Array<Word>, | |||
| 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"> { | |||
| @@ -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 passport, { use } from 'passport'; | |||
| import passport from 'passport'; | |||
| 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(); | |||
| @@ -22,92 +18,3 @@ userProfileRoutes.get('/profile/', jwtAuthentication, async (request, response) | |||
| }); | |||
| 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; | |||
| }); | |||