-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
admin user #188
admin user #188
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ export async function detail(req: Request, res: Response, next: NextFunction) { | |
next(err) | ||
} | ||
} | ||
|
||
//USE THIS | ||
export async function getByCourse(req: Request, res: Response, next: NextFunction) { | ||
try { | ||
|
@@ -48,6 +49,48 @@ export async function getByCourse(req: Request, res: Response, next: NextFunctio | |
} | ||
} | ||
|
||
// create an admin, only an admin can create a new admin | ||
export async function createNewAdmin(req: Request, res: Response, next: NextFunction) { | ||
try { | ||
let newAdminUserId = req.body.newAdminUserId | ||
if (!newAdminUserId) { | ||
return res.status(404).send('Not found') | ||
} | ||
|
||
await UserService.createAdmin(newAdminUserId!) | ||
res.status(201).send('Created new admin') | ||
} catch (e) { | ||
next(e) | ||
} | ||
} | ||
|
||
|
||
// delete an admin, only an admin can delete an admin | ||
export async function deleteAdmin(req: Request, res: Response, next: NextFunction) { | ||
try { | ||
let newAdminUserId = req.body.newAdminUserId | ||
if (!newAdminUserId) { | ||
return res.status(404).send('Not found') | ||
} | ||
await UserService.softDeleteAdmin(newAdminUserId) | ||
res.status(204).send('User is no longer admin') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A 204 shouldn't have a body |
||
} catch (e) { | ||
next(e) | ||
} | ||
} | ||
|
||
// list admins | ||
export async function listAdmins(req: Request, res: Response, next: NextFunction) { | ||
try { | ||
let users = await UserService.listAdmin() | ||
const response = users.map(serialize) | ||
res.status(200).json(response) | ||
} catch (e) { | ||
next(e) | ||
} | ||
} | ||
|
||
|
||
export async function post(req: Request, res: Response, next: NextFunction) { | ||
try { | ||
const user = await UserService.create(req.body) | ||
|
@@ -56,7 +99,7 @@ export async function post(req: Request, res: Response, next: NextFunction) { | |
res.status(201).json(response) | ||
} catch (err) { | ||
if (err instanceof Error) { | ||
res.status(400).json(new GenericResponse(err.message)) | ||
res.status(400).json(new GenericResponse(err.message)) | ||
} | ||
} | ||
} | ||
|
@@ -87,4 +130,4 @@ export async function _delete(req: Request, res: Response, next: NextFunction) { | |
} | ||
} | ||
|
||
export default { get, detail, post, put, _delete, getByCourse } | ||
export default { get, detail, post, put, _delete, getByCourse, deleteAdmin, createNewAdmin, listAdmins } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { NextFunction, Request, Response } from 'express' | ||
import UserService from './user.service' | ||
|
||
// is admin middleware, use this when marking an endpoint as only accessible by admin | ||
// different from userCourse permissions. this is attached to a user instead of a course level permission | ||
export async function isAdmin(req: Request, res: Response, next: NextFunction) { | ||
const userId = req.currentUser?.userId | ||
if (!userId) { | ||
return res.status(403).send('Unauthorized') | ||
} | ||
|
||
const isAdmin = await UserService.isAdmin(userId) | ||
if (!isAdmin!.isAdmin!) return res.status(403).send('unauthorized') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Try to be consistent with capitalization on "Unauthorized" |
||
|
||
next() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,13 @@ import UserCourseService from '../userCourse/userCourse.service' | |
const connect = () => dataSource.getRepository(UserModel) | ||
|
||
export async function create(user: User) { | ||
// check if the first account | ||
const users = await connect().count({ take: 1 }) | ||
if (users == 0) { | ||
// make first created account admin | ||
user.isAdmin = true | ||
} | ||
|
||
return await connect().save(user) | ||
} | ||
|
||
|
@@ -29,6 +36,35 @@ export async function retrieve(id: number) { | |
return await connect().findOneBy({ id, deletedAt: IsNull() }) | ||
} | ||
|
||
export async function isAdmin(id: number) { | ||
return await connect().findOne({ | ||
where: { id, deletedAt: IsNull() }, | ||
select: ['isAdmin'], | ||
}) | ||
} | ||
|
||
export async function createAdmin(id: number) { | ||
return await connect().update(id, { isAdmin: true }) | ||
} | ||
|
||
// soft deletes an admin | ||
export async function softDeleteAdmin(id: number) { | ||
let res = await connect().count({ take: 2, where: { isAdmin: true } }) | ||
// check if this deletes the last admin | ||
// there must always be at least 1 admin | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good thinking! |
||
if (res == 1) { | ||
throw Error('Unable to delete, only a single admin remains') | ||
} | ||
|
||
return await connect().update(id, { isAdmin: false }) | ||
} | ||
|
||
// list all admins | ||
export async function listAdmin() { | ||
return await connect().findBy({ isAdmin: true, deletedAt: IsNull() }) | ||
} | ||
|
||
|
||
export async function retrieveByEmail(email: string) { | ||
return await connect().findOneBy({ email: email, deletedAt: IsNull() }) | ||
} | ||
|
@@ -46,13 +82,13 @@ export async function listByCourse(courseId: number, userRole?: string) { | |
} | ||
|
||
export async function ensure(userInfo: User) { | ||
const { externalId, email } = userInfo | ||
const { externalId } = userInfo | ||
|
||
const user = await connect().findOneBy({ externalId }) | ||
|
||
if (user) return { user, isNewUser: false } | ||
|
||
const newUser = await create({ email, externalId }) | ||
const newUser = await create(userInfo) | ||
|
||
return { user: newUser, isNewUser: true } | ||
} | ||
|
@@ -64,6 +100,10 @@ export default { | |
update, | ||
_delete, | ||
list, | ||
isAdmin, | ||
createAdmin, | ||
softDeleteAdmin, | ||
listAdmin, | ||
ensure, | ||
listByCourse, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { MigrationInterface, QueryRunner } from "typeorm"; | ||
|
||
export class UserAdmin1731053786646 implements MigrationInterface { | ||
name = 'UserAdmin1731053786646' | ||
|
||
public async up(queryRunner: QueryRunner): Promise<void> { | ||
await queryRunner.query(`ALTER TABLE "users" ADD "is_admin" boolean NOT NULL DEFAULT false`); | ||
} | ||
|
||
public async down(queryRunner: QueryRunner): Promise<void> { | ||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "is_admin"`); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,4 +5,5 @@ export type User = { | |
createdAt?: string | ||
updatedAt?: string | ||
preferredName?: string | ||
isAdmin: boolean | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a more appropriate variable name