Skip to content
This repository has been archived by the owner on Jun 24, 2020. It is now read-only.

Commit

Permalink
Merge pull request #19 from paywteam/dev
Browse files Browse the repository at this point in the history
Regular merge from stable development branch
  • Loading branch information
ihooni authored Sep 9, 2019
2 parents 7014104 + d7f9127 commit e4a0607
Show file tree
Hide file tree
Showing 60 changed files with 58,843 additions and 36,053 deletions.
21 changes: 21 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
APP_NAME=eodiro-api-dev
APP_ENV=development
APP_PORT=1234

DB_HOST=localhost
DB_PORT=27017
DB_DATABASE=database-name
DB_USERNAME=username
DB_PASSWORD=password

MAIL_SERVICE=Zoho
MAIL_HOST=smtp.zoho.com
MAIL_PORT=465
MAIL_USERNAME=username
MAIL_PASSWORD=password

SESSION_SECRET=session-secret
SESSION_NAME=session-name

FOOD_SCRAPER_ID=cau-portal-id
FOOD_SCRAPER_PASSWORD=cau-portal-password
15 changes: 13 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,30 +22,41 @@
},
"homepage": "https://github.com/payasyouwant/eodiro-api#readme",
"dependencies": {
"@payw/cau-food-scraper": "^1.1.3",
"body-parser": "^1.19.0",
"bottleneck": "^2.19.5",
"connect-mongo": "^3.0.0",
"cors": "^2.8.5",
"express": "^4.17.1",
"express-session": "^1.16.2",
"express-validator": "^6.1.1",
"fuse.js": "^3.4.5",
"helmet": "^3.20.0",
"lru-cache": "^5.1.1",
"mongoose": "^5.5.12",
"nanoid": "^2.0.4",
"node-schedule": "^1.3.2",
"nodemailer": "^6.3.0",
"uniqid": "^5.0.3",
"uuid": "^3.3.3",
"winston": "^3.2.1"
},
"devDependencies": {
"@babel/core": "^7.4.5",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/preset-env": "^7.4.5",
"@types/app-root-path": "^1.2.4",
"@types/connect-mongo": "^0.0.43",
"@types/cors": "^2.8.5",
"@types/dotenv": "^6.1.1",
"@types/express": "^4.17.0",
"@types/express-session": "^1.15.14",
"@types/helmet": "^0.0.44",
"@types/lru-cache": "^5.1.0",
"@types/mongoose": "^5.5.6",
"@types/nanoid": "^2.0.0",
"@types/node-schedule": "^1.2.3",
"@types/nodemailer": "^6.2.1",
"@types/uniqid": "^4.1.3",
"@types/uuid": "^3.4.5",
"@typescript-eslint/eslint-plugin": "^2.0.0",
"@typescript-eslint/parser": "^2.0.0",
"app-root-path": "^2.2.1",
Expand Down
15 changes: 15 additions & 0 deletions pm2.config.example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require('dotenv').config()

module.exports = {
apps: [
{
name: process.env.APP_NAME,
script: './build/eodiro.api.built.js',
instances: 1,
exec_mode: 'cluster',
autorestart: true,
watch: true,
max_memory_restart: '1G'
}
]
}
171 changes: 87 additions & 84 deletions src/app/db/DBSeeder.ts → src/app/db/ClassSeeder.ts
Original file line number Diff line number Diff line change
@@ -1,112 +1,116 @@
import { GlobalNameDoc } from 'Database/schemas/global_name'
import metadataSeoulJSON from 'Resources/metadata/cau_seoul.json'
import metadataAnseongJSON from 'Resources/metadata/cau_anseong.json'
import classesSeoulJSON from 'Resources/classes/cau_seoul.json'
import classesAnseongJSON from 'Resources/classes/cau_anseong.json'
import classesSeoulUnderJSON from 'Resources/classes/cau-seoul-학부.json'
import classesSeoulGradJSON from 'Resources/classes/cau-seoul-대학원.json'
import University from 'Database/models/university'
import ClassList from 'Database/models/class-list'
import Building from 'Database/models/building'
import Class from 'Database/models/class'
import logger from 'Configs/log'
import { ClassDoc } from 'Database/schemas/class'
import Floor from 'Database/models/floor'
import Classroom from 'Database/models/classroom'
import Lecture from 'Database/models/lecture'
import LogHelper from 'Helpers/LogHelper'
import { ClassListDoc } from 'Database/schemas/class-list'

interface ClassesJSON {
year: string
semester: string
vendor: string
mainCourse: string
classes: ClassDoc[]
}

interface BldgMetaJSON {
number: string
name: string
// Set the current semester with this.
const currentSemester = {
year: '2019',
semester: '2'
}

interface UnivMetaJSON {
name: GlobalNameDoc
campus: GlobalNameDoc
vendor: string
buildings: BldgMetaJSON[]
}

export default class DBSeeder {
/**
* Seed(resource) data for metadata
*/
private metadataSeed: UnivMetaJSON[] = [
metadataSeoulJSON as UnivMetaJSON,
metadataAnseongJSON as UnivMetaJSON
]

export default class ClassSeeder {
/**
* Seed(resource) data for classes
*/
private classesSeed: ClassesJSON[] = [
classesSeoulJSON as ClassesJSON,
classesAnseongJSON as ClassesJSON
classesSeoulUnderJSON as ClassesJSON,
classesSeoulGradJSON as ClassesJSON
]

/**
* Seed the collection of university and building.
* Seed the data associated with class.
*/
public async seedMetadata(): Promise<void> {
// asynchronously seed metadata
await Promise.all(
this.metadataSeed.map(async (seed: UnivMetaJSON) => {
// create university
const university = await University.create({
name: seed.name,
campus: seed.campus,
vendor: seed.vendor
})
public async run(): Promise<void> {
const classCount = await Class.estimatedDocumentCount()

// create buildings
const buildings = await Building.insertMany(seed.buildings)
// get building id list
const buildingsIds = buildings.map(bldg => {
return bldg._id
})

// link university to buildings
await Building.updateMany(
{ _id: { $in: buildingsIds } },
{ university: university._id }
)

// link buildings to university
await University.findByIdAndUpdate(university._id, {
buildings: buildings
})
})
)
// if class collection is empty, then seed data.
if (classCount === 0) {
await this.seedClasses()
await this.linkClassesOfCurrentSemester()
}
}

/**
* Seed the collection of class, floor, classroom, lecture.
*/
public async seedClasses(): Promise<void> {
private async seedClasses(): Promise<void> {
// asynchronously seed classes
await Promise.all(
this.classesSeed.map(async (seed: ClassesJSON) => {
try {
// create classes
const classes = (await Class.insertMany(seed.classes)) as ClassDoc[]

// create class list
const classList = (await ClassList.create({
year: seed.year,
semester: seed.semester,
mainCourse: seed.mainCourse,
classes: classes
})) as ClassListDoc

// link classes to university
await University.findOneAndUpdate(
{ vendor: seed.vendor },
{ classes: classes },
{ new: true }
{
$push: {
classLists: classList._id
}
}
)
} catch (err) {
logger.error('Class save error: ' + err)
LogHelper.log('error', 'Class save error: ' + err)
}
})
)
}

/**
* Create floors, classrooms and lectures using current semester's class documents.
*/
private async linkClassesOfCurrentSemester(): Promise<void> {
// get all current semester's classes
const classLists = (await ClassList.find(
{ year: currentSemester.year, semester: currentSemester.semester },
{ classes: 1 }
)
.lean()
.populate({
path: 'classes',
select: '_id closed locations times'
})) as ClassListDoc[]

// gather classes
const classes: ClassDoc[] = []
for (const classList of classLists) {
classes.push(...(classList.classes as ClassDoc[]))
}

const unregisteredBldgSet = new Set()

const classes = (await Class.find()) as ClassDoc[]
// create floors, classrooms, lectures
for (const cls of classes) {
// skip closed class
if (cls.closed) {
continue
}

for (let i = 0; i < cls.locations.length; i++) {
const location = cls.locations[i]

Expand All @@ -116,15 +120,21 @@ export default class DBSeeder {
// find class building
const building = await Building.findOne({
number: location.building
})
}).lean()

// skip unregistered building
if (building === null) {
unregisteredBldgSet.add(location.building)
continue
}

// find class floor
// if not exist, create floor
const floor = await Floor.findOneAndUpdate(
{ building: building._id, number: floorNum },
{ building: building._id, number: floorNum },
{ upsert: true, new: true }
)
).lean()

// link floor to building
await Building.findByIdAndUpdate(building._id, {
Expand All @@ -137,7 +147,7 @@ export default class DBSeeder {
{ floor: floor._id, number: location.room },
{ floor: floor._id, number: location.room },
{ upsert: true, new: true }
)
).lean()

// link classroom to floor
await Floor.findByIdAndUpdate(floor._id, {
Expand All @@ -157,24 +167,17 @@ export default class DBSeeder {
})
}
}
}

/**
* Apply refreshed seed data.
*/
public async refreshMetadataAndClasses(): Promise<void> {
// delete all documents about metadata and classes
await Promise.all([
University.deleteMany({}),
Building.deleteMany({}),
Class.deleteMany({}),
Floor.deleteMany({}),
Classroom.deleteMany({}),
Lecture.deleteMany({})
])

// re-seeding
await this.seedMetadata()
await this.seedClasses()
// log unregistered buildings
if (unregisteredBldgSet.size !== 0) {
LogHelper.log(
'warn',
'Buildings `' +
Array.from(unregisteredBldgSet)
.sort()
.join(', ') +
'` are unregistered.'
)
}
}
}
22 changes: 16 additions & 6 deletions src/app/db/DBConnector.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import mongoose from 'mongoose'
import dbConfig from 'Configs/database'
import logger from 'Configs/log'
import LogHelper from 'Helpers/LogHelper'

export default class DBConnector {
/**
* Connect to database and set connection event listeners.
*/
public async connect(): Promise<void> {
mongoose.connection.on('error', err => {
logger.info('Mongoose default connection has occured ' + err + ' error')
// open the database connection
await mongoose.connect(dbConfig.uri, {
useNewUrlParser: true,
useFindAndModify: false,
useCreateIndex: true
})

await mongoose.connect(dbConfig.uri, {
useNewUrlParser: true
mongoose.connection.on('error', err => {
LogHelper.log(
'info',
'Mongoose default connection has occured ' + err + ' error'
)
})

process.on('SIGINT', () => {
mongoose.connection.close(() => {
logger.error(
LogHelper.log(
'error',
'Mongoose default connection is disconnected due to application termination'
)
process.exit(0)
Expand Down
Loading

0 comments on commit e4a0607

Please sign in to comment.