Skip to content

Commit

Permalink
Merge branch 'develop' into 110-automate-start/end-dates
Browse files Browse the repository at this point in the history
  • Loading branch information
NeemZ16 authored Nov 5, 2024
2 parents b4d4c42 + f26a37e commit b12fd39
Show file tree
Hide file tree
Showing 30 changed files with 740 additions and 115 deletions.
4 changes: 2 additions & 2 deletions devU-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"start": "npm run migrate && ts-node-dev src/index.ts",
"migrate": "npm run typeorm -- migration:run -d src/database.ts",
"update-shared": "cd ../devU-shared && npm run build-local && npm i",
"update-shared": "cd ../devU-shared && npm run build-local && cd ../devU-api && npm i",
"typeorm": "typeorm-ts-node-commonjs",
"test": "jest --passWithNoTests",
"clean": "rimraf build/*",
Expand Down Expand Up @@ -53,7 +53,7 @@
"prettier": "^2.3.0",
"rimraf": "^3.0.2",
"ts-jest": "^27.0.2",
"ts-node": "^10.0.0",
"ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0",
"typescript": "^4.3.2"
},
Expand Down
29 changes: 18 additions & 11 deletions devU-api/scripts/populate-db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,23 @@ async function SendPOST(path: string, requestBody: string | FormData, requesterE
return responseBody
}

async function CreateCourse(name: string, number: string, semester: string) {
async function CreateCourse(
name: string,
number: string,
semester: string,
isPublic: boolean
) {
const courseData = {
name: name,
semester: semester,
number: number,
startDate: '2024-01-24T00:00:00-0500',
endDate: '2024-05-10T23:59:59-0500',
}
console.log('Creating course: ', courseData.name)
return await SendPOST('/courses/instructor', JSON.stringify(courseData), 'admin')
name: name,
semester: semester,
number: number,
startDate: '2024-01-24T00:00:00-0500',
endDate: '2024-05-10T23:59:59-0500',
is_public: isPublic // Include the public property
};

console.log('Creating course: ', courseData.name);
return await SendPOST('/courses/instructor', JSON.stringify(courseData), 'admin');
}

async function joinCourse(courseId: number, userId: number, role: string) {
Expand Down Expand Up @@ -208,8 +215,8 @@ async function runCourseAndSubmission() {
const jones = await fetchToken('jones@buffalo.edu', 'jones')

//Create courses
const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024')).id
const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024')).id
const courseId1 = (await CreateCourse('Testing Course Name1', 'CSE101', 's2024',true)).id
const courseId2 = (await CreateCourse('Testing Course Name2', 'CSE102', 's2024',true)).id

//Enroll students
await joinCourse(courseId1, billy, 'student')
Expand Down
7 changes: 7 additions & 0 deletions devU-api/src/entities/course/course.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,11 @@ export default class CourseModel {

@DeleteDateColumn({ name: 'deleted_at' })
deletedAt?: Date

@Column({ type: 'boolean', name: 'is_public', default: false })
isPublic: boolean;

@Column({ name: 'private_data', type: 'timestamp', default: () => 'now()' })
private_data?: Date;

}
2 changes: 2 additions & 0 deletions devU-api/src/entities/course/course.serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,7 @@ export function serialize(course: CourseModel): Course {
endDate: course.endDate.toISOString(),
createdAt: course.createdAt.toISOString(),
updatedAt: course.updatedAt.toISOString(),
isPublic: course.isPublic,
private_data: course.private_data ? course.private_data.toISOString() : undefined
}
}
71 changes: 71 additions & 0 deletions devU-api/src/entities/webhooks/webhooks.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { NextFunction, Request, Response } from 'express'

import { Updated } from '../../utils/apiResponse.utils'
import { serialize } from './webhooks.serializer'
import WebhooksService from './webhooks.service'

export async function get(req: Request, res: Response, next: NextFunction) {
try {
const hooksModel = await WebhooksService.list()
const hooks = hooksModel.map(serialize)
res.status(200).json(hooks)
} catch (err) {
next(err)
}
}

export async function getById(req: Request, res: Response, next: NextFunction) {
try {
const hooksModel = await WebhooksService.retrieveByUserId(req.currentUser!.userId!)
if (!hooksModel) {
return res.status(404).json({ 'error': 'Webhook for user not found not found' })
}
const hooks = hooksModel.map(serialize)

res.status(200).json(hooks)
} catch (err) {
next(err)
}
}


export async function post(req: Request, res: Response, next: NextFunction) {
try {
req.body['userId'] = req.currentUser!.userId!

const created = await WebhooksService.create(req.body)
res.status(201).json(serialize(created))
} catch (err: any) {
next(err)
}
}

export async function put(req: Request, res: Response, next: NextFunction) {
try {
const webhookId = parseInt(req.params.id)
await WebhooksService.update(webhookId, req.body)

res.status(201).json(Updated)
} catch (err: any) {
next(err)
}
}

export async function _delete(req: Request, res: Response, next: NextFunction) {
try {
const webhookId = parseInt(req.params.id)
await WebhooksService._delete(webhookId)

res.status(204).json('Deleted')
} catch (err: any) {
next(err)
}
}

export default {
get,
put,
post,
_delete,
getById,
}
67 changes: 67 additions & 0 deletions devU-api/src/entities/webhooks/webhooks.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { NextFunction, Request, Response } from 'express'
import WebhooksService from './webhooks.service'
import fetch from 'node-fetch'


export async function processWebhook(statusCode: number, userId: number, path: string, body: any) {
return new Promise(async (resolve, reject) => {
try {
// process path and check for match
const hooks = await WebhooksService.retrieveByUserId(userId)
if (hooks.length !== 0) {
for (let hook of hooks) {
// todo make matcher more generic that checks url patterns
if (hook.matcherUrl == path) {
try {
// todo add multiple ways to format a webhook message currently only formated for discord
const re = await fetch(hook.destinationUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ 'content': body }),
})

if (re.status >= 300) {
reject(`Failed to send webhook ${re.statusText}`)
}
} catch (e) {
reject(e)
}
}
}
// prepare request and send
} else {
reject('Url could not be matched')
}

resolve('Goodbye and thanks for all the fish')
} catch (e) {
reject(e)
}
})
}

// adds a
export function responseInterceptor(req: Request, res: Response, next: NextFunction) {
const originalSend = res.send

// Override function
// @ts-ignore
res.send = function(body) {
// send response to client
originalSend.call(this, body)

// send body for processing in webhook
if (req.currentUser?.userId) {
processWebhook(res.statusCode, req.currentUser?.userId, req.originalUrl, body).then(
value => {
console.log('Sent webhook successfully')
},
).catch(err => {
console.error('Error sending webhook', err)
})
}
}
next()
}
38 changes: 38 additions & 0 deletions devU-api/src/entities/webhooks/webhooks.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
JoinColumn,
ManyToOne,
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
DeleteDateColumn,
} from 'typeorm'

import UserModel from '../user/user.model'

@Entity('Webhooks')
export default class WebhooksModel {
@PrimaryGeneratedColumn()
id: number

@Column({name: 'destination_url'})
destinationUrl: string

@Column({name: 'matcher_url'})
matcherUrl: string

@Column({ name: 'user_id' })
@JoinColumn({ name: 'user_id' })
@ManyToOne(() => UserModel)
userId: number

@CreateDateColumn({ name: 'created_at' })
createdAt: Date

@UpdateDateColumn({ name: 'updated_at' })
updatedAt: Date

@DeleteDateColumn({ name: 'deleted_at' })
deletedAt?: Date
}
54 changes: 54 additions & 0 deletions devU-api/src/entities/webhooks/webhooks.router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import express from 'express'

import validator from './webhooks.validator'

import WebhooksController from './webhooks.controller'
import { isAuthorized } from '../../authorization/authorization.middleware'

const Router = express.Router({ mergeParams: true })


/**
* @swagger
* /webhooks:
* get:
* summary: get all webhooks created by a user
*/
Router.get('/', isAuthorized('courseViewAll'), WebhooksController.getById)

/**
* @swagger
* /webhooks:
* get:
* summary: get all webhooks, only for admins
*/
Router.get('/all', isAuthorized('admin'), WebhooksController.get)


/**
* @swagger
* /webhooks:
* get:
* summary: create a webhook
*/
Router.post('/', isAuthorized('courseViewAll'), validator, WebhooksController.post)


/**
* @swagger
* /webhooks:
* put:
* summary: Edit webhook urls
*/
Router.put('/:id', isAuthorized('courseViewAll'), validator, WebhooksController.put)


/**
* @swagger
* /webhooks:
* delete:
* summary: delete a webhook
*/
Router.delete('/:id', isAuthorized('courseViewAll'), WebhooksController._delete)

export default Router
13 changes: 13 additions & 0 deletions devU-api/src/entities/webhooks/webhooks.serializer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import WebhooksModel from './webhooks.model'
import { Webhooks } from 'devu-shared-modules'

export function serialize(webhooks: WebhooksModel): Webhooks {
return {
id: webhooks.id,
userId: webhooks.userId,
destinationUrl: webhooks.destinationUrl,
matcherUrl: webhooks.matcherUrl,
updatedAt: webhooks.updatedAt.toISOString(),
createdAt: webhooks.createdAt.toISOString(),
}
}
36 changes: 36 additions & 0 deletions devU-api/src/entities/webhooks/webhooks.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { dataSource } from '../../database'
import WebhooksModel from './webhooks.model'
import { Webhooks } from 'devu-shared-modules'
import { IsNull } from 'typeorm'

const connect = () => dataSource.getRepository(WebhooksModel)

async function create(input: Webhooks) {
return await connect().save(input)
}



async function retrieveByUserId(userId: number) {
return await connect().findBy({ userId: userId, deletedAt: IsNull() })
}

async function update(id: number, input: Webhooks) {
return await connect().update(id, { matcherUrl: input.matcherUrl, destinationUrl: input.destinationUrl })
}

async function list() {
return await connect().findBy({ deletedAt: IsNull() })
}

async function _delete(id: number) {
return await connect().softDelete({ id, deletedAt: IsNull() })
}

export default {
create,
list,
update,
retrieveByUserId,
_delete,
}
16 changes: 16 additions & 0 deletions devU-api/src/entities/webhooks/webhooks.validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { check } from 'express-validator'

import validate from '../../middleware/validator/generic.validator'


const destinationUrl = check('destinationUrl').isString().trim()
const matcherUrl = check('matcherUrl').isString().trim()


const validator = [
destinationUrl,
matcherUrl,
validate,
]

export default validator
Loading

0 comments on commit b12fd39

Please sign in to comment.