Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 25 additions & 4 deletions app/controllers/auth_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,28 @@

import User from '#models/user'
import { UserGuard } from '#utils/permissions'
import { loginValidator, registerValidator } from '#validators/auth'
import { loginValidator, providerParamValidator, registerValidator } from '#validators/auth'
import type { HttpContext } from '@adonisjs/core/http'
import {
ApiOperation,
ApiRequest,
ApiResponse,
} from '#openapi/decorators'

export default class AuthController {
public async redirect({ ally, params }: HttpContext) {
@ApiOperation({ description: 'Redirects to the social provider for authentication' })
@ApiRequest({ validator: providerParamValidator, withResponse: true })
@ApiResponse(307, { description: 'Redirects to the social provider' })
public async redirect({ ally, request }: HttpContext) {
const { params } = await request.validateUsing(providerParamValidator)
return ally.use(params.provider).redirect()
}

public async callback({ ally, auth, params, response }: HttpContext) {
@ApiOperation({ description: 'Handles the callback from the social provider' })
@ApiRequest({ validator: providerParamValidator, withResponse: true })
@ApiResponse(307, { description: 'Redirects to the status page' })
public async callback({ ally, auth, request, response }: HttpContext) {
const { params } = await request.validateUsing(providerParamValidator)
const provider = ally.use(params.provider)

// Handle common OAuth errors
Expand All @@ -51,7 +64,7 @@ export default class AuthController {
{
email: socialUser.email,
nickname: socialUser.nickName || socialUser.name || socialUser.email.split('@')[0],
// name and surname are kept as null and will be overriten later
// name and surname are kept as null and will be overridden later
avatarUrl: socialUser.avatarUrl,
password: null,
permissions: UserGuard.build(), // TODO: Combine into constant of BASE_PERMISSIONS or sth idk
Expand All @@ -65,6 +78,9 @@ export default class AuthController {
return response.redirect('/status')
}

@ApiOperation({ description: 'Registers a new user with email and password' })
@ApiRequest({ validator: registerValidator, withResponse: true })
@ApiResponse(201, { description: 'User registered successfully', data: User })
public async register({ request, auth, response }: HttpContext) {
// Validate the input (email, password, etc.)
const payload = await request.validateUsing(registerValidator)
Expand All @@ -82,6 +98,9 @@ export default class AuthController {
return response.created({ message: 'Registration successful', user })
}

@ApiOperation({ description: 'Logs in a user with email and password' })
@ApiRequest({ validator: loginValidator, withResponse: true })
@ApiResponse(200, { description: 'User logged in successfully', data: User })
public async login({ request, auth, response }: HttpContext) {
const { email, password } = await request.validateUsing(loginValidator)
const user = await User.findBy('email', email)
Expand All @@ -98,6 +117,8 @@ export default class AuthController {
return response.ok({ message: 'Login successful', user: validatedUser })
}

@ApiOperation({ description: 'Logs out the current user' })
@ApiResponse(200, { description: 'User logged out successfully' })
public async logout({ auth, response }: HttpContext) {
await auth.use('web').logout()
return response.redirect('/')
Expand Down
73 changes: 43 additions & 30 deletions app/controllers/events_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,26 +30,27 @@ import { EventAdminGuard } from '#utils/permissions'
import type { Mask , EventAdminPermissions } from '#utils/permissions'
import EventPolicy from '#policies/event_policy'
import { confirmationValidator } from '#validators/common'
import { ApiOperation, ApiRequest, ApiResponse } from '#openapi/decorators'

export default class EventsController {
/**
* Display a list of resource
*/
@ApiOperation({ description: 'Get a list of all active events' })
@ApiResponse(200, { description: 'A list of active events', data: [Event] })
async index({}: HttpContext) {
return Event.query().where('status', 'ACTIVE')
}

/**
* Handle form submission for the creation action
*/
@ApiOperation({ description: 'Create a new event' })
@ApiRequest({ validator: createEventValidator, withResponse: true })
@ApiResponse(201, { description: 'The newly created event', data: Event })
@ApiResponse(403, { description: 'Missing permission to create events' })
async store({ auth, bouncer, request, response }: HttpContext) {
await bouncer.with(EventPolicy).authorize('create')

const payload = await request.validateUsing(createEventValidator)

if (payload.minTeamSize > payload.maxTeamSize)
return response.unprocessableEntity({ message: 'minTeamSize cannot be greater than maxTeamSize.' })
if (payload.minTeamSize > payload.maxTeamSize)
return response.unprocessableEntity({ errors: [{ message: 'minTeamSize cannot be greater than maxTeamSize.' }] })


const event = await db.transaction(async (trx) => {
const newEvent = await Event.create(payload, { client: trx })
Expand All @@ -65,9 +66,10 @@ export default class EventsController {
return response.created(event)
}

/**
* Show individual record
*/
@ApiOperation({ description: 'Get a specific event by its slug or UUID' })
@ApiResponse(200, { description: 'The requested event', data: Event })
@ApiResponse(404, { description: 'Event not found' })
@ApiResponse(403, { description: 'Missing permission to view this event' })
async show({ bouncer, params }: HttpContext) {
const event = await Event.findByUuidOrSlug(params.id)

Expand All @@ -76,9 +78,11 @@ export default class EventsController {
return event
}

/**
* Handle form submission for the edit action
*/
@ApiOperation({ description: 'Update an existing event by its slug or UUID' })
@ApiRequest({ validator: updateEventValidator, withResponse: true })
@ApiResponse(200, { description: 'The updated event', data: Event })
@ApiResponse(404, { description: 'Event not found' })
@ApiResponse(403, { description: 'Missing permission to edit this event' })
async update({ bouncer, params, request }: HttpContext) {
const payload = await request.validateUsing(updateEventValidator, {
meta: { eventSlug: params.id },
Expand All @@ -93,9 +97,11 @@ export default class EventsController {
return event
}

/**
* Delete record
*/
@ApiOperation({ description: 'Delete an event by its slug or UUID. Requires confirmation of the event slug.' })
@ApiRequest({ validator: confirmationValidator, withResponse: true })
@ApiResponse(204, { description: 'Event deleted successfully' })
@ApiResponse(404, { description: 'Event not found' })
@ApiResponse(403, { description: 'Missing permission to delete this event' })
async destroy({ bouncer, params, request, response }: HttpContext) {
const event = await Event.findByUuidOrSlug(params.id)
await request.validateUsing(confirmationValidator, {
Expand All @@ -111,19 +117,23 @@ export default class EventsController {
return response.noContent()
}

/**
* List all administrators for an event
*/
@ApiOperation({ description: 'Get a list of all administrators for an event' })
@ApiResponse(200, { description: 'A list of administrators for the event', data: [EventAdministrator] })
@ApiResponse(404, { description: 'Event not found' })
@ApiResponse(403, { description: 'Missing permission to view administrators for this event' })
async indexAdministrators({ bouncer, params }: HttpContext) {
const event = await Event.findByUuidOrSlug(params.id)
await bouncer.with(EventPolicy).authorize('manageAdministrators', event)

return event.related('administrators').query().preload('user')
}

/**
* Assign a user as an administrator for an event
*/
@ApiOperation({ description: 'Create a new administrator for an event' })
@ApiRequest({ validator: storeAdministratorValidator, withResponse: true })
@ApiResponse(201, { description: 'The newly created administrator', data: EventAdministrator })
@ApiResponse(404, { description: 'Event not found' })
@ApiResponse(403, { description: 'Missing permission to manage administrators for this event' })
@ApiResponse(409, { description: 'User is already an administrator of this event' })
async storeAdministrator({ bouncer, params, request, response }: HttpContext) {
const event = await Event.findByUuidOrSlug(params.id)
await bouncer.with(EventPolicy).authorize('manageAdministrators', event)
Expand All @@ -146,9 +156,11 @@ export default class EventsController {
return response.created(admin)
}

/**
* Update permissions (bitmask) of an event administrator
*/
@ApiOperation({ description: 'Update permissions for an event administrator' })
@ApiRequest({ validator: updateAdministratorValidator, withResponse: true })
@ApiResponse(200, { description: 'The updated administrator', data: EventAdministrator })
@ApiResponse(404, { description: 'Event or administrator not found' })
@ApiResponse(403, { description: 'Missing permission to manage administrators for this event' })
async updateAdministrator({ bouncer, params, request, response }: HttpContext) {
const event = await Event.findByUuidOrSlug(params.id)
await bouncer.with(EventPolicy).authorize('manageAdministrators', event)
Expand All @@ -169,9 +181,10 @@ export default class EventsController {
return admin
}

/**
* Revoke administrator access from a user for an event
*/
@ApiOperation({ description: 'Remove administrator from an event' })
@ApiResponse(204, { description: 'Administrator removed successfully' })
@ApiResponse(404, { description: 'Event or administrator not found' })
@ApiResponse(403, { description: 'Missing permission to manage administrators for this event' })
async destroyAdministrator({ bouncer, params, response }: HttpContext) {
const event = await Event.findByUuidOrSlug(params.id)
await bouncer.with(EventPolicy).authorize('manageAdministrators', event)
Expand Down
54 changes: 33 additions & 21 deletions app/controllers/hackathons_controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ import TaskPolicy from '#policies/task_policy'
import { createJuryMemberValidator, updateJuryMemberValidator } from '#validators/jury_member'
import { updateHackathonTaskValidator } from '#validators/task'
import type { HttpContext } from '@adonisjs/core/http'
import { ApiOperation, ApiRequest, ApiResponse } from '#openapi/decorators'

export default class HackathonsController {
/**
* Show individual hackathon task record
*/
@ApiOperation({ description: 'Get a specific hackathon task by its ID or slug' })
@ApiResponse(200, { description: 'The requested hackathon task', data: HackathonTask })
@ApiResponse(404, { description: 'Hackathon task not found' })
@ApiResponse(403, { description: 'Missing permission to view this task' })
async showTask({ bouncer, params }: HttpContext) {
let hackathonTask = await HackathonTask.query()
.where('id', params.id)
Expand All @@ -55,9 +57,11 @@ export default class HackathonsController {
return hackathonTask
}

/**
* Handle form submission for the edit action for Hackathon Tasks
*/
@ApiOperation({ description: 'Update a hackathon task by its ID or slug' })
@ApiRequest({ validator: updateHackathonTaskValidator, withResponse: true })
@ApiResponse(200, { description: 'The updated hackathon task', data: HackathonTask })
@ApiResponse(404, { description: 'Hackathon task not found' })
@ApiResponse(403, { description: 'Missing permission to edit this task' })
async updateTask({ bouncer, params, request }: HttpContext) {
const task = await Task.findByUuidOrSlug(params.id)
await bouncer.with(TaskPolicy).authorize('edit', task)
Expand All @@ -71,17 +75,21 @@ export default class HackathonsController {
return hackathonTask
}

/**
* Display a list of Jury Member resources
*/
@ApiOperation({ description: 'Get a list of jury members for a hackathon task' })
@ApiResponse(200, { description: 'A list of jury members for the task', data: [JuryMember] })
@ApiResponse(404, { description: 'Task not found' })
async indexJuryMembers({ params }: HttpContext) {
const task = await Task.findByUuidOrSlug(params.id)
return task.related('juryMembers').query().preload('user')
}

/**
* Handle form submission for the create action of Jury Member
*/
@ApiOperation({ description: 'Create a new jury member for a hackathon task' })
@ApiRequest({ validator: createJuryMemberValidator, withResponse: true })
@ApiResponse(201, { description: 'The newly created jury member', data: JuryMember })
@ApiResponse(404, { description: 'Task not found' })
@ApiResponse(403, { description: 'Missing permission to manage jury members' })
@ApiResponse(400, { description: 'Invalid request or jury members can only be assigned to hackathon tasks' })
@ApiResponse(409, { description: 'User is already a jury member' })
async storeJuryMember({ bouncer, params, request, response }: HttpContext) {
const task = await Task.findByUuidOrSlug(params.id)
const event = await task.related('event').query().firstOrFail()
Expand Down Expand Up @@ -114,9 +122,10 @@ export default class HackathonsController {
return response.created(juryMember)
}

/**
* Show individual record of Jury Member
*/
@ApiOperation({ description: 'Get a specific jury member by ID' })
@ApiResponse(200, { description: 'The requested jury member', data: JuryMember })
@ApiResponse(404, { description: 'Jury member not found' })
@ApiResponse(403, { description: 'Missing permission to view this event' })
async showJuryMember({ bouncer, params }: HttpContext) {
const juryMember = await JuryMember.findOrFail(params.juryMemberId)
const task = await juryMember.related('task').query().firstOrFail()
Expand All @@ -127,9 +136,11 @@ export default class HackathonsController {
return juryMember
}

/**
* Update record of Jury Member
*/
@ApiOperation({ description: 'Update a jury member by ID' })
@ApiRequest({ validator: updateJuryMemberValidator, withResponse: true })
@ApiResponse(200, { description: 'The updated jury member', data: JuryMember })
@ApiResponse(404, { description: 'Jury member not found' })
@ApiResponse(403, { description: 'Missing permission to manage jury members' })
async updateJuryMember({ bouncer, params, request, response }: HttpContext) {
const juryMember = await JuryMember.findOrFail(params.juryMemberId)
const task = await juryMember.related('task').query().firstOrFail()
Expand All @@ -147,9 +158,10 @@ export default class HackathonsController {
return juryMember
}

/**
* Delete record of Jury Member
*/
@ApiOperation({ description: 'Delete a jury member by ID' })
@ApiResponse(204, { description: 'Jury member deleted successfully' })
@ApiResponse(404, { description: 'Jury member not found' })
@ApiResponse(403, { description: 'Missing permission to manage jury members' })
async destroyJuryMember({ bouncer, params, response }: HttpContext) {
const juryMember = await JuryMember.findOrFail(params.juryMemberId)
const task = await juryMember.related('task').query().firstOrFail()
Expand Down
Loading