@@ -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; | |||||
}); |