diff --git a/.gitignore b/.gitignore index a0b6228..68b65b7 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ yarn-error.log .idea/**/dynamic.xml .idea/**/uiDesigner.xml .idea/**/dbnavigator.xml +.idea/**/copilot.* # Useless stuff that doesn't need to be shared .eslintcache diff --git a/adonisrc.ts b/adonisrc.ts index 4ad44ca..3be4726 100644 --- a/adonisrc.ts +++ b/adonisrc.ts @@ -50,7 +50,7 @@ export default defineConfig({ () => import('@adonisjs/auth/auth_provider'), () => import('@adonisjs/ally/ally_provider'), () => import('@adonisjs/bouncer/bouncer_provider'), - () => import('@adonisjs/drive/drive_provider') + () => import('@adonisjs/drive/drive_provider'), ], /* diff --git a/app/controllers/auth_controller.ts b/app/controllers/auth_controller.ts index 93573ef..e90a2f8 100644 --- a/app/controllers/auth_controller.ts +++ b/app/controllers/auth_controller.ts @@ -55,7 +55,7 @@ export default class AuthController { avatarUrl: socialUser.avatarUrl, password: null, permissions: UserGuard.build(), // TODO: Combine into constant of BASE_PERMISSIONS or sth idk - } + }, ) // Authenticate the session diff --git a/app/controllers/events_controller.ts b/app/controllers/events_controller.ts index 6e4aeda..9fe9197 100644 --- a/app/controllers/events_controller.ts +++ b/app/controllers/events_controller.ts @@ -26,8 +26,8 @@ import type { HttpContext } from '@adonisjs/core/http' import Event from '#models/event/event' import EventAdministrator from '#models/event/event_administrator' import db from '@adonisjs/lucid/services/db' -import { EventAdminGuard, EventAdminPermissions } from '#utils/permissions' -import type { Mask } from '#utils/permissions' +import { EventAdminGuard } from '#utils/permissions' +import type { Mask , EventAdminPermissions } from '#utils/permissions' import EventPolicy from '#policies/event_policy' import { confirmationValidator } from '#validators/common' @@ -36,7 +36,7 @@ export default class EventsController { * Display a list of resource */ async index({}: HttpContext) { - return Event.query().where('status', 'ACTIVE'); + return Event.query().where('status', 'ACTIVE') } /** @@ -77,8 +77,8 @@ export default class EventsController { */ async update({ bouncer, params, request }: HttpContext) { const payload = await request.validateUsing(updateEventValidator, { - meta: { eventSlug: params.id } - }); + meta: { eventSlug: params.id }, + }) const event = await Event.findByUuidOrSlug(params.id) await bouncer.with(EventPolicy).authorize('edit', event) @@ -97,9 +97,9 @@ export default class EventsController { await request.validateUsing(confirmationValidator, { meta: { expectedConfirmation: event.slug, - confirmationMeta: 'event slug' - } - }); + confirmationMeta: 'event slug', + }, + }) await bouncer.with(EventPolicy).authorize('edit', event) diff --git a/app/controllers/hackathons_controller.ts b/app/controllers/hackathons_controller.ts index 84919f1..9c64723 100644 --- a/app/controllers/hackathons_controller.ts +++ b/app/controllers/hackathons_controller.ts @@ -37,7 +37,6 @@ export default class HackathonsController { * Show individual hackathon task record */ async showTask({ bouncer, params }: HttpContext) { - let hackathonTask = await HackathonTask.query() .where('id', params.id) .preload('task') @@ -103,7 +102,7 @@ export default class HackathonsController { if (existing) return response.conflict({ message: 'User is already a jury member' }) - if(!await Organization.belongsToEvent(payload.organizationId, event.id)) + if (!await Organization.belongsToEvent(payload.organizationId, event.id)) return response.badRequest({ message: 'Organization does not belong to the event' }) const juryMember = await task.related('juryMembers').create({ @@ -125,7 +124,7 @@ export default class HackathonsController { await bouncer.with(EventPolicy).authorize('view', event) - return juryMember; + return juryMember } /** @@ -140,12 +139,12 @@ export default class HackathonsController { const payload = await request.validateUsing(updateJuryMemberValidator) - if(!await Organization.belongsToEvent(payload.organizationId, event.id)) + if (!await Organization.belongsToEvent(payload.organizationId, event.id)) return response.badRequest({ message: 'Organization does not belong to the event' }) juryMember.merge(payload) await juryMember.save() - return juryMember; + return juryMember } /** @@ -159,6 +158,6 @@ export default class HackathonsController { await bouncer.with(EventPolicy).authorize('manageJuryMembers', event) await juryMember.delete() - return response.noContent(); + return response.noContent() } } diff --git a/app/controllers/organizations_controller.ts b/app/controllers/organizations_controller.ts index 0e21a47..a4c0917 100644 --- a/app/controllers/organizations_controller.ts +++ b/app/controllers/organizations_controller.ts @@ -29,7 +29,7 @@ import Sponsor from '#models/sponsor' import { createOrganizationValidator, createSponsorValidator, - updateOrganizationValidator + updateOrganizationValidator, } from '#validators/organization' import Task from '#models/task/task' @@ -42,7 +42,7 @@ export default class OrganizationsController { await bouncer.with(EventPolicy).authorize('view', event) - return event.related('organizations').query(); + return event.related('organizations').query() } /** @@ -101,7 +101,7 @@ export default class OrganizationsController { await organization.delete() - return response.noContent(); + return response.noContent() } /** @@ -147,7 +147,7 @@ export default class OrganizationsController { await bouncer.with(EventPolicy).authorize('view', event) - return sponsor; + return sponsor } /** @@ -161,7 +161,6 @@ export default class OrganizationsController { await bouncer.with(EventPolicy).authorize('manageSponsors', event) await sponsor.delete() - return response.noContent(); + return response.noContent() } - } diff --git a/app/controllers/scores_controller.ts b/app/controllers/scores_controller.ts index e156cfb..eba1c91 100644 --- a/app/controllers/scores_controller.ts +++ b/app/controllers/scores_controller.ts @@ -60,7 +60,7 @@ export default class ScoresController { const task = await Task.findByUuidOrSlug(params.task_id) await bouncer.with(TaskPolicy).authorize('view', task) - return await ScoringCriterion.query() + return ScoringCriterion.query() .where('id', params.id) .where('task_id', task.id) .firstOrFail() diff --git a/app/controllers/tasks_controller.ts b/app/controllers/tasks_controller.ts index a5a1a52..81f8a3d 100644 --- a/app/controllers/tasks_controller.ts +++ b/app/controllers/tasks_controller.ts @@ -52,7 +52,7 @@ export default class TasksController { .where('event_id', event.id) .if(!canManage, (q) => q.where('status', 'ACTIVE')) - return Promise.all(tasks.map(async (task) => getTaskByType(task))); + return Promise.all(tasks.map(async (task) => getTaskByType(task))) } /** @@ -69,7 +69,7 @@ export default class TasksController { const task = await db.transaction(async (trx) => { const newTask = await event.related('tasks').create( Task.datesFromPayload({ status: 'DRAFT', autoregister: false, ...taskPayload }), - { client: trx } + { client: trx }, ) switch (newTask.taskType) { @@ -83,7 +83,7 @@ export default class TasksController { await HackathonTask.create( { taskId: newTask.id, requirementsDocumentUrl: requirementsDocumentUrl }, - { client: trx } + { client: trx }, ) break } @@ -91,14 +91,13 @@ export default class TasksController { } // Autoregister all teams to new task that is set to autoregister - if(newTask.autoregister) { + if (newTask.autoregister) { const eventTeams = await event.related('teams').query() - for(const team of eventTeams) + for (const team of eventTeams) await newTask.related('registrations').create({ teamId: team.id, }, { client: trx }) - } return newTask @@ -125,7 +124,7 @@ export default class TasksController { await bouncer.with(TaskPolicy).authorize('edit', task) const payload = await request.validateUsing(updateTaskValidator, { - meta: { taskSlug: task.slug } + meta: { taskSlug: task.slug }, }) task.merge(Task.datesFromPayload(payload)) @@ -159,11 +158,11 @@ export default class TasksController { await bouncer.with(TeamPolicy).authorize('registerToTask', team) // 2. Check if team belongs to the same event as the task - if(team.eventId !== task.eventId) + if (team.eventId !== task.eventId) return response.badRequest({ message: 'Team does not belong to the same event as the task' }) // 4. Check if registration is open for the task - const now = DateTime.now(); + const now = DateTime.now() if (task.registrationStartAt && now < task.registrationStartAt) return response.badRequest({ message: 'Registration for the task has not started yet' }) @@ -176,11 +175,11 @@ export default class TasksController { await team.loadCount('members') const memberCount = team.$extras.members_count - if(memberCount < event.minTeamSize || memberCount > event.maxTeamSize) + if (memberCount < event.minTeamSize || memberCount > event.maxTeamSize) return response.badRequest({ message: `Team size must be between ${event.minTeamSize} and ${event.maxTeamSize}` }) // 6. Check if task is set to autoregister and if so, prevent manual registration - if(task.autoregister) + if (task.autoregister) return response.badRequest({ message: 'Task is set to autoregister, manual registration is not allowed' }) // 7. Check if team is already registered to the task @@ -212,6 +211,6 @@ export default class TasksController { await bouncer.with(TeamPolicy).authorize('registerToTask', team) await taskRegistration.delete() - return response.noContent(); + return response.noContent() } } diff --git a/app/controllers/teams_controller.ts b/app/controllers/teams_controller.ts index 372b9ce..f5c089b 100644 --- a/app/controllers/teams_controller.ts +++ b/app/controllers/teams_controller.ts @@ -29,19 +29,19 @@ import { teamInvitationValidator, updateTeamValidator, } from '#validators/team' -import Team from "#models/team/team"; -import Event from '#models/event/event'; -import TeamPolicy from "#policies/team_policy"; -import db from "@adonisjs/lucid/services/db"; +import Team from '#models/team/team' +import Event from '#models/event/event' +import TeamPolicy from '#policies/team_policy' +import db from '@adonisjs/lucid/services/db' import { TeamMemberGuard } from '#utils/permissions' -import EventPolicy from "#policies/event_policy"; +import EventPolicy from '#policies/event_policy' import { confirmationValidator, paramsIdValidator } from '#validators/common' -import { applyQueryFilters } from "#utils/query"; -import Task from '#models/task/task'; -import { generateMemorableToken, generateSecureToken, invitationValidity } from "#utils/teams"; -import TeamInvitation from "#models/team/team_invitation"; -import InvitationSent from "#events/invitation_sent"; -import env from "#start/env"; +import { applyQueryFilters } from '#utils/query' +import Task from '#models/task/task' +import { generateMemorableToken, generateSecureToken, invitationValidity } from '#utils/teams' +import TeamInvitation from '#models/team/team_invitation' +import InvitationSent from '#events/invitation_sent' +import env from '#start/env' export default class TeamsController { /** @@ -56,8 +56,8 @@ export default class TeamsController { { request, response, searchColumn: 'name', - allowedColumns: ['name', 'created_at'] - } + allowedColumns: ['name', 'created_at'], + }, ) } @@ -80,7 +80,7 @@ export default class TeamsController { userId: auth.getUserOrFail().id, permissions: TeamMemberGuard.allPermissions(), }, - { client: trx } + { client: trx }, ) // Autoregister to tasks @@ -88,7 +88,7 @@ export default class TeamsController { .where('event_id', event.id) .where('autoregister', true) - for(const task of autoregisterTasks) + for (const task of autoregisterTasks) await task.related('registrations').create({ teamId: newTeam.id, }, { client: trx }) @@ -120,8 +120,8 @@ export default class TeamsController { const payload = await request.validateUsing(updateTeamValidator, { meta: { teamName: team.name, - eventId: team.eventId - } + eventId: team.eventId, + }, }) await bouncer.with(TeamPolicy).authorize('edit', team) @@ -141,7 +141,7 @@ export default class TeamsController { await request.validateUsing(confirmationValidator, { meta: { expectedConfirmation: team.name, - confirmationMeta: 'team name' + confirmationMeta: 'team name', }, }) @@ -200,8 +200,8 @@ export default class TeamsController { request, response, searchColumn: 'teams.name', allowedColumns: ['created_at', 'teams.name', 'token'], - defaultTable: 'team_invitations' - } + defaultTable: 'team_invitations', + }, ) } @@ -211,7 +211,7 @@ export default class TeamsController { data: { ...request.qs(), params: request.params(), - } + }, }) const invitation = await TeamInvitation.query() .where('token', params.id) @@ -227,13 +227,13 @@ export default class TeamsController { if (invitation.status === 'ACCEPTED') { const team = await invitation.related('team').query().firstOrFail() - return await team.related('members').create( + return team.related('members').create( { userId: user.id, // No permissions by default, admin must update them after the user joins. permissions: TeamMemberGuard.build(), }, - { client: trx } + { client: trx }, ) } }) diff --git a/app/events/invitation_sent.ts b/app/events/invitation_sent.ts index 0bf69b3..96fef49 100644 --- a/app/events/invitation_sent.ts +++ b/app/events/invitation_sent.ts @@ -22,7 +22,7 @@ */ import { BaseEvent } from '@adonisjs/core/events' -import TeamInvitation from "#models/team/team_invitation"; +import type TeamInvitation from '#models/team/team_invitation' export default class InvitationSent extends BaseEvent { constructor(public invitation: TeamInvitation) { diff --git a/app/exceptions/handler.ts b/app/exceptions/handler.ts index 83bea18..867720e 100644 --- a/app/exceptions/handler.ts +++ b/app/exceptions/handler.ts @@ -22,21 +22,22 @@ */ import app from '@adonisjs/core/services/app' -import { HttpContext, ExceptionHandler } from '@adonisjs/core/http' +import type { HttpContext } from '@adonisjs/core/http' +import { ExceptionHandler } from '@adonisjs/core/http' export default class HttpExceptionHandler extends ExceptionHandler { /** * In sigma mode, the exception handler will display verbose errors * with pretty printed stack traces. */ - protected debug = !app.inProduction; + protected debug = !app.inProduction /** * The method is used for handling errors and returning * response to the client */ async handle(error: unknown, ctx: HttpContext) { - return super.handle(error, ctx); + return super.handle(error, ctx) } /** @@ -46,6 +47,6 @@ export default class HttpExceptionHandler extends ExceptionHandler { * @note You should not attempt to send a response from this method. */ async report(error: unknown, ctx: HttpContext) { - return super.report(error, ctx); + return super.report(error, ctx) } } diff --git a/app/middleware/auth_middleware.ts b/app/middleware/auth_middleware.ts index 3d3fc91..3efb5eb 100644 --- a/app/middleware/auth_middleware.ts +++ b/app/middleware/auth_middleware.ts @@ -40,7 +40,7 @@ export default class AuthMiddleware { next: NextFn, options: { guards?: (keyof Authenticators)[] - } = {} + } = {}, ) { await ctx.auth.authenticateUsing(options.guards, { loginRoute: this.redirectTo }) return next() diff --git a/app/middleware/guest_middleware.ts b/app/middleware/guest_middleware.ts index 2e8e40b..1d0ada2 100644 --- a/app/middleware/guest_middleware.ts +++ b/app/middleware/guest_middleware.ts @@ -41,7 +41,7 @@ export default class GuestMiddleware { async handle( ctx: HttpContext, next: NextFn, - options: { guards?: (keyof Authenticators)[] } = {} + options: { guards?: (keyof Authenticators)[] } = {}, ) { for (const guard of options.guards || [ctx.auth.defaultGuard]) if (await ctx.auth.use(guard).check()) diff --git a/app/middleware/initialize_bouncer_middleware.ts b/app/middleware/initialize_bouncer_middleware.ts index 3bafa5c..c13e2f2 100644 --- a/app/middleware/initialize_bouncer_middleware.ts +++ b/app/middleware/initialize_bouncer_middleware.ts @@ -41,7 +41,7 @@ export default class InitializeBouncerMiddleware { ctx.bouncer = new Bouncer( () => ctx.auth.user || null, abilities, - policies + policies, ).setContainerResolver(ctx.containerResolver) return next() diff --git a/app/models/organization.ts b/app/models/organization.ts index ae2f6a6..18695eb 100644 --- a/app/models/organization.ts +++ b/app/models/organization.ts @@ -73,8 +73,8 @@ export default class Organization extends BaseModel { const organization = await Organization.findOrFail(organizationId) if (organization.eventId !== eventId) - return false; + return false } - return true; + return true } } diff --git a/app/models/task/task.ts b/app/models/task/task.ts index ca54be5..d5a1a45 100644 --- a/app/models/task/task.ts +++ b/app/models/task/task.ts @@ -113,7 +113,6 @@ export default class Task extends BaseModel { q.where('id', value) else q.where('slug', value) - }) .first() @@ -149,5 +148,4 @@ export default class Task extends BaseModel { return parsedPayload as Partial } - } diff --git a/app/policies/event_policy.ts b/app/policies/event_policy.ts index ff8c2f5..6406178 100644 --- a/app/policies/event_policy.ts +++ b/app/policies/event_policy.ts @@ -81,7 +81,6 @@ export default class EventPolicy extends BasePolicy { // Whether user can manage (assign/update/revoke) event administrators async manageAdministrators(user: User, event: Event): Promise { - if (UserGuard.can(user, 'MANAGE_ALL_EVENTS')) return true diff --git a/app/policies/main.ts b/app/policies/main.ts index e5fa2a3..4cb768c 100644 --- a/app/policies/main.ts +++ b/app/policies/main.ts @@ -37,5 +37,5 @@ export const policies = { EventPolicy: () => import('#policies/event_policy'), - TeamPolicy: () => import('#policies/team_policy') + TeamPolicy: () => import('#policies/team_policy'), } diff --git a/app/policies/task_policy.ts b/app/policies/task_policy.ts index 8929893..f1eefe7 100644 --- a/app/policies/task_policy.ts +++ b/app/policies/task_policy.ts @@ -46,10 +46,10 @@ export default class TaskPolicy extends BasePolicy { @allowGuest() async view(user: User | null, task: Task): Promise { if (task.status === 'ACTIVE') { - await task.load('event'); + await task.load('event') // Check if the user can view the event - return !!(await new EventPolicy().view(user, task.event)); + return !!(await new EventPolicy().view(user, task.event)) } if (!user) diff --git a/app/policies/team_policy.ts b/app/policies/team_policy.ts index dcffde7..7aba904 100644 --- a/app/policies/team_policy.ts +++ b/app/policies/team_policy.ts @@ -21,15 +21,15 @@ * */ -import User from '#models/user' -import Event from '#models/event/event' +import type User from '#models/user' +import type Event from '#models/event/event' import { AuthorizationResponse, BasePolicy } from '@adonisjs/bouncer' import type { AuthorizerResponse } from '@adonisjs/bouncer/types' import { TeamMemberGuard, UserGuard } from '#utils/permissions' -import Team from "#models/team/team"; -import TeamInvitation from "#models/team/team_invitation"; -import { DateTime } from "luxon"; -import TeamMember from "#models/team/team_member"; +import type Team from '#models/team/team' +import type TeamInvitation from '#models/team/team_invitation' +import { DateTime } from 'luxon' +import TeamMember from '#models/team/team_member' export default class TeamPolicy extends BasePolicy { // Whether a user can create a new team for an event @@ -72,7 +72,7 @@ export default class TeamPolicy extends BasePolicy { const member = await TeamMember.findOrFail(targetMemberId) if (TeamMemberGuard.can(member, 'IS_OWNER')) return AuthorizationResponse.deny( - 'Owner cannot be kicked or leave. Please delete the team or transfer ownership to another user.' + 'Owner cannot be kicked or leave. Please delete the team or transfer ownership to another user.', ) if (UserGuard.can(user, 'MANAGE_ALL_TEAMS')) @@ -121,7 +121,7 @@ export default class TeamPolicy extends BasePolicy { const team = await invitation.related('team').query().preload('event').preload('members').firstOrFail() - const isAlreadyMember = team.members.some(member => member.userId === user.id) + const isAlreadyMember = team.members.some((member) => member.userId === user.id) if (isAlreadyMember) { if (invitation.inviteeEmail) { invitation.status = 'FAILED' @@ -134,7 +134,7 @@ export default class TeamPolicy extends BasePolicy { if (isAboveMaxMembers) { invitation.status = 'FAILED' await invitation.save() - return AuthorizationResponse.deny('Team already has maximum number of members') + return AuthorizationResponse.deny('Team already has maximum number of members', 422) } return true diff --git a/app/utils/permissions.ts b/app/utils/permissions.ts index e15b29c..d559900 100644 --- a/app/utils/permissions.ts +++ b/app/utils/permissions.ts @@ -34,7 +34,7 @@ const PermissionsGuard = >(maskEnum: E grant(obj: { permissions: Mask }, ...flags: (keyof E)[]): Mask { return flags.reduce( (acc, flag) => acc | (maskEnum[flag] as number), - obj.permissions as number + obj.permissions as number, ) as Mask }, diff --git a/app/utils/query.ts b/app/utils/query.ts index a539fea..6919cd7 100644 --- a/app/utils/query.ts +++ b/app/utils/query.ts @@ -22,8 +22,8 @@ */ import { commonQueryValidator } from '#validators/common' -import { ModelQueryBuilderContract } from "@adonisjs/lucid/types/model"; -import type { Request, Response } from '@adonisjs/core/http'; +import type { ModelQueryBuilderContract } from '@adonisjs/lucid/types/model' +import type { Request, Response } from '@adonisjs/core/http' /** * Applies common filters (search, sorting, status, filter) to any Lucid query @@ -37,7 +37,7 @@ export async function applyQueryFilters searchColumn, defaultPageSize = 25, allowedColumns = [], - defaultTable + defaultTable, }: { request: Request, response: Response, @@ -45,7 +45,7 @@ export async function applyQueryFilters defaultPageSize?: number, allowedColumns?: string[], defaultTable?: string - } + }, ) { const params = await request.validateUsing(commonQueryValidator, { data: request.qs(), @@ -63,9 +63,9 @@ export async function applyQueryFilters { message: 'Invalid sort column', field: 'orderBy', - allowedColumns - } - ] + allowedColumns, + }, + ], }) if (defaultTable && !params.orderBy.includes('.')) params.orderBy = `${defaultTable}.${params.orderBy}` @@ -86,9 +86,9 @@ export async function applyQueryFilters message: 'Invalid filter column', field: 'filter', column: key, - allowedColumns - } - ] + allowedColumns, + }, + ], }) if (defaultTable && !key.includes('.')) @@ -101,7 +101,7 @@ export async function applyQueryFilters '>': '>', '<': '<', '!': '!=', - '~': 'LIKE' + '~': 'LIKE', } // Determine which operator to use diff --git a/app/utils/schema.ts b/app/utils/schema.ts index 26dfdb1..c3812d2 100644 --- a/app/utils/schema.ts +++ b/app/utils/schema.ts @@ -22,10 +22,10 @@ */ import type { VineAny } from '@vinejs/vine' -import { OptionalModifier } from '@vinejs/vine/schema/base/literal' +import type { OptionalModifier } from '@vinejs/vine/schema/base/literal' export function partialSchema( - schema: Record + schema: Record, ): Record> { return Object.fromEntries(Object.entries(schema).map(([k, v]) => [k, v.optional()])) } diff --git a/app/utils/tasks.ts b/app/utils/tasks.ts index fb6ad89..3938398 100644 --- a/app/utils/tasks.ts +++ b/app/utils/tasks.ts @@ -21,9 +21,9 @@ * */ -import HackathonTask from "#models/hackathon/hackathon_task" -import Task from "#models/task/task" -import logger from "@adonisjs/core/services/logger"; +import HackathonTask from '#models/hackathon/hackathon_task' +import type Task from '#models/task/task' +import logger from '@adonisjs/core/services/logger' export async function getTaskByType(task: Task) { switch (task.taskType) { diff --git a/app/utils/teams.ts b/app/utils/teams.ts index 8fc8104..bd51ccf 100644 --- a/app/utils/teams.ts +++ b/app/utils/teams.ts @@ -21,7 +21,7 @@ * */ -import { DateTime } from "luxon"; +import { DateTime } from 'luxon' import { randomInt, randomBytes } from 'node:crypto' export const invitationValidity = { @@ -31,7 +31,7 @@ export const invitationValidity = { '1 day': () => DateTime.now().plus({ days: 1 }), '3 days': () => DateTime.now().plus({ days: 3 }), '1 week': () => DateTime.now().plus({ weeks: 1 }), -} as const; +} as const const adjectives = [ 'ACUTE', 'ADAPT', 'ADEPT', 'ALIVE', 'BASIC', 'BRAVE', 'BRIEF', 'BRISK', @@ -40,8 +40,8 @@ const adjectives = [ 'HARDY', 'HEAVY', 'IDEAL', 'JOLLY', 'LARGE', 'LIGHT', 'LOOSE', 'LUCKY', 'MIGHT', 'NOBLE', 'OUTER', 'POLAR', 'PROUD', 'QUICK', 'QUIET', 'RAPID', 'READY', 'SHARP', 'SMALL', 'SMART', 'SOLID', 'STARK', 'SUPER', 'SWIFT', - 'TOUGH', 'VIVID' -] as const; + 'TOUGH', 'VIVID', +] as const const nouns = [ 'ALDER', 'AMBER', 'ARROW', 'ATLAS', 'BEACH', 'BISON', 'BLAST', 'BREEZ', @@ -50,8 +50,8 @@ const nouns = [ 'FOCUS', 'FORCE', 'FROST', 'GLARE', 'GORGE', 'GROVE', 'HEART', 'LIGHT', 'LUNAR', 'METRO', 'NORTH', 'OCEAN', 'ORBIT', 'PEARL', 'PILOT', 'PLANE', 'PRISM', 'PULSE', 'RIVER', 'SHADE', 'SHARK', 'SOLAR', 'SPARK', 'STORM', - 'TIGER', 'VAPOR' -] as const; + 'TIGER', 'VAPOR', +] as const export function generateMemorableToken(): string { diff --git a/app/validators/auth.ts b/app/validators/auth.ts index 8ccb1ec..90bda23 100644 --- a/app/validators/auth.ts +++ b/app/validators/auth.ts @@ -32,13 +32,13 @@ export const registerValidator = vine.compile( const user = await db.from('users').where('email', value).first() return !user }), - password: vine.string().minLength(8).confirmed() - }) + password: vine.string().minLength(8).confirmed(), + }), ) export const loginValidator = vine.compile( vine.object({ email: vine.string().email().trim(), - password: vine.string() - }) + password: vine.string(), + }), ) diff --git a/app/validators/common.ts b/app/validators/common.ts index c7e0106..dbc2562 100644 --- a/app/validators/common.ts +++ b/app/validators/common.ts @@ -22,7 +22,7 @@ */ import vine from '@vinejs/vine' -import { FieldContext } from '@vinejs/vine/types' +import type { FieldContext } from '@vinejs/vine/types' async function matchesConfirmation(value: unknown, _options: any, field: FieldContext) { if (typeof value !== 'string') @@ -32,18 +32,17 @@ async function matchesConfirmation(value: unknown, _options: any, field: FieldCo if (value !== expected) field.report( - `The confirmation text does not match the ${field.meta.confirmationMeta || "expected value"}.`, + `The confirmation text does not match the ${field.meta.confirmationMeta || 'expected value'}.`, 'matches', - field + field, ) - } const matchesConfirmationRule = vine.createRule(matchesConfirmation) export const confirmationValidator = vine.compile( vine.object({ confirmation: vine.string().use(matchesConfirmationRule()), - }) + }), ) @@ -54,9 +53,8 @@ export const notUUIDv4 = vine.createRule((value: unknown, _options: undefined, f return true - if(uuidV4Regex.test(value)) - field.report(`The ${field.name} cannot be a UUIDv4`, 'not_uuid_v4', field); - + if (uuidV4Regex.test(value)) + field.report(`The ${field.name} cannot be a UUIDv4`, 'not_uuid_v4', field) }) export const commonQuerySchema = vine.object({ @@ -70,7 +68,7 @@ export const commonQuerySchema = vine.object({ vine .string() .trim() - .regex(/^[^:]+:[><=]?[^:]+$/) + .regex(/^[^:]+:[><=]?[^:]+$/), ) .optional(), }) @@ -80,7 +78,7 @@ export const commonQueryValidator = vine.compile(commonQuerySchema) export const paramsIdValidator = vine.compile( vine.object({ params: vine.object({ - id: vine.string().uuid() - }) - }) + id: vine.string().uuid(), + }), + }), ) diff --git a/app/validators/event.ts b/app/validators/event.ts index 16757a9..0c4fd83 100644 --- a/app/validators/event.ts +++ b/app/validators/event.ts @@ -42,7 +42,7 @@ export const createEventValidator = vine.compile( const match = await db.from('events').where('slug', value).first() return !match }), - }) + }), ) export const updateEventValidator = vine.compile( @@ -57,18 +57,18 @@ export const updateEventValidator = vine.compile( .first() return !match }).optional(), - }) + }), ) export const storeAdministratorValidator = vine.compile( vine.object({ userId: vine.number().positive(), permissions: vine.number().min(0).optional(), - }) + }), ) export const updateAdministratorValidator = vine.compile( vine.object({ permissions: vine.number().min(0), - }) + }), ) diff --git a/app/validators/jury_member.ts b/app/validators/jury_member.ts index 69be18d..31b8e1a 100644 --- a/app/validators/jury_member.ts +++ b/app/validators/jury_member.ts @@ -36,7 +36,7 @@ export const createJuryMemberValidator = vine.compile( vine.object({ ...juryMemberSchema, userId: vine.number().positive(), - }) + }), ) /** @@ -44,5 +44,5 @@ export const createJuryMemberValidator = vine.compile( * an existing jury member. */ export const updateJuryMemberValidator = vine.compile( - vine.object(juryMemberSchema) + vine.object(juryMemberSchema), ) diff --git a/app/validators/organization.ts b/app/validators/organization.ts index d7f028d..c05e4ac 100644 --- a/app/validators/organization.ts +++ b/app/validators/organization.ts @@ -35,7 +35,7 @@ const organizationSchema = { * a new organization. */ export const createOrganizationValidator = vine.compile( - vine.object(organizationSchema) + vine.object(organizationSchema), ) /** @@ -43,11 +43,11 @@ export const createOrganizationValidator = vine.compile( * an existing organization. */ export const updateOrganizationValidator = vine.compile( - vine.object(organizationSchema) + vine.object(organizationSchema), ) export const createSponsorValidator = vine.compile( vine.object({ organizationId: vine.string().uuid(), - }) + }), ) \ No newline at end of file diff --git a/app/validators/score.ts b/app/validators/score.ts index 101670f..5928224 100644 --- a/app/validators/score.ts +++ b/app/validators/score.ts @@ -21,8 +21,8 @@ * */ -import { partialSchema } from "#utils/schema" -import vine from "@vinejs/vine" +import { partialSchema } from '#utils/schema' +import vine from '@vinejs/vine' const scoringCriterionCoreSchema = { category: vine.string().trim().minLength(1).maxLength(255), @@ -34,12 +34,12 @@ export const createScoringCriterionValidator = vine.compile( vine.object({ ...scoringCriterionCoreSchema, description: vine.string().trim().escape().nullable().optional(), - }) + }), ) export const updateScoringCriterionValidator = vine.compile( vine.object({ ...partialSchema(scoringCriterionCoreSchema), description: vine.string().trim().escape().nullable().optional(), - }) + }), ) \ No newline at end of file diff --git a/app/validators/task.ts b/app/validators/task.ts index a64a93e..c768f84 100644 --- a/app/validators/task.ts +++ b/app/validators/task.ts @@ -54,13 +54,13 @@ export const createTaskValidator = vine.compile( ...taskDateFields(), // Hackathon specific requirementsDocumentUrl: vine.string().trim().url().optional(), - }) + }), ) export const updateHackathonTaskValidator = vine.compile( vine.object({ requirementsDocumentUrl: vine.string().trim().url(), - }) + }), ) export const updateTaskValidator = vine.compile( @@ -73,5 +73,5 @@ export const updateTaskValidator = vine.compile( return !match }).optional(), ...taskDateFields(), - }) + }), ) diff --git a/app/validators/task_registration.ts b/app/validators/task_registration.ts index 753b337..3434522 100644 --- a/app/validators/task_registration.ts +++ b/app/validators/task_registration.ts @@ -24,7 +24,7 @@ import vine from '@vinejs/vine' const TaskRegistrationSchema = { - teamId: vine.string().uuid() + teamId: vine.string().uuid(), } /** @@ -32,5 +32,5 @@ const TaskRegistrationSchema = { * a new task registration. */ export const createTaskRegistrationValidator = vine.compile( - vine.object(TaskRegistrationSchema) + vine.object(TaskRegistrationSchema), ) diff --git a/app/validators/team.ts b/app/validators/team.ts index cf001ae..a44bf02 100644 --- a/app/validators/team.ts +++ b/app/validators/team.ts @@ -22,8 +22,8 @@ */ import vine from '@vinejs/vine' -import { partialSchema } from "#utils/schema"; -import { invitationValidity } from "#utils/teams"; +import { partialSchema } from '#utils/schema' +import { invitationValidity } from '#utils/teams' const teamSchema = { } @@ -37,7 +37,7 @@ export const createTeamValidator = vine.compile( return !match }), accessCode: vine.string().optional(), - }) + }), ) export const updateTeamValidator = vine.compile( @@ -50,7 +50,7 @@ export const updateTeamValidator = vine.compile( const match = await db.from('teams').where('name', value).where('event_id', eventId).first() return !match }), - }) + }), ) export const kickMemberValidator = vine.compile( @@ -59,14 +59,14 @@ export const kickMemberValidator = vine.compile( params: vine.object({ id: vine.string().uuid(), }), - }) + }), ) export const teamInvitationValidator = vine.compile( vine.object({ email: vine.string().email().trim().optional(), validFor: vine.enum(Object.keys(invitationValidity)), - }) + }), ) export const teamInvitationResponseValidator = vine.compile( @@ -75,6 +75,6 @@ export const teamInvitationResponseValidator = vine.compile( params: vine.object({ id: vine.string().minLength(16).maxLength(45), }), - }) + }), ) diff --git a/config/auth.ts b/config/auth.ts index e163658..7fe7224 100644 --- a/config/auth.ts +++ b/config/auth.ts @@ -31,7 +31,7 @@ const authConfig = defineConfig({ web: sessionGuard({ useRememberMeTokens: false, provider: sessionUserProvider({ - model: () => import('#models/user') + model: () => import('#models/user'), }), }), }, diff --git a/config/drive.ts b/config/drive.ts index 783cdaf..0dcf684 100644 --- a/config/drive.ts +++ b/config/drive.ts @@ -41,7 +41,7 @@ const driveConfig = defineConfig({ bucket: env.get('S3_USERDATA_BUCKET'), endpoint: env.get('S3_ENDPOINT'), visibility: 'public', - forcePathStyle: true + forcePathStyle: true, }), }, }) diff --git a/config/session.ts b/config/session.ts index 131be0d..e3ddd00 100644 --- a/config/session.ts +++ b/config/session.ts @@ -65,7 +65,7 @@ const sessionConfig = defineConfig({ */ stores: { cookie: stores.cookie(), - } + }, }) export default sessionConfig diff --git a/database/factories/team_factory.ts b/database/factories/team_factory.ts index d79288c..ae27819 100644 --- a/database/factories/team_factory.ts +++ b/database/factories/team_factory.ts @@ -23,12 +23,12 @@ import factory from '@adonisjs/lucid/factories' import Team from '#models/team/team' -import TeamMember from "#models/team/team_member"; -import { TeamMemberGuard } from "#utils/permissions"; -import { UserFactory } from "#database/factories/user_factory"; -import Event from "#models/event/event"; -import TeamInvitation from "#models/team/team_invitation"; -import { DateTime } from "luxon"; +import TeamMember from '#models/team/team_member' +import { TeamMemberGuard } from '#utils/permissions' +import { UserFactory } from '#database/factories/user_factory' +import Event from '#models/event/event' +import TeamInvitation from '#models/team/team_invitation' +import { DateTime } from 'luxon' export const TeamFactory = factory .define(Team, async ({ faker }) => ({ diff --git a/database/factories/user_factory.ts b/database/factories/user_factory.ts index 8ce423e..1be39f0 100644 --- a/database/factories/user_factory.ts +++ b/database/factories/user_factory.ts @@ -30,6 +30,6 @@ export const UserFactory = factory nickname: faker.internet.username(), email: faker.internet.email(), password: faker.internet.password({ length: 12, memorable: true }), - permissions: UserGuard.build() + permissions: UserGuard.build(), })) .build() diff --git a/database/migrations/1771616946000_create_team_invitations_table.ts b/database/migrations/1771616946000_create_team_invitations_table.ts index 833b4d3..53bb8f8 100644 --- a/database/migrations/1771616946000_create_team_invitations_table.ts +++ b/database/migrations/1771616946000_create_team_invitations_table.ts @@ -43,9 +43,9 @@ export default class extends BaseSchema { table.timestamp('updated_at').nullable() table.check( - `(status = 'PENDING' AND expires_at IS NOT NULL) OR (status != 'PENDING' AND expires_at IS NULL)`, + '(status = \'PENDING\' AND expires_at IS NOT NULL) OR (status != \'PENDING\' AND expires_at IS NULL)', [], - 'check_expiration_status_for_pending_invitations' + 'check_expiration_status_for_pending_invitations', ) }) diff --git a/database/seeders/0_user_seeder.ts b/database/seeders/0_user_seeder.ts index f61de61..7a4d3a6 100644 --- a/database/seeders/0_user_seeder.ts +++ b/database/seeders/0_user_seeder.ts @@ -31,12 +31,12 @@ export default class extends BaseSeeder { nickname: 'admin', email: 'admin@local.host', password: 'adminpasswordstrong', - permissions: UserGuard.allPermissions() + permissions: UserGuard.allPermissions(), }, { nickname: 'user', email: 'user@local.host', password: 'userpassword', - permissions: UserGuard.build() + permissions: UserGuard.build(), }]) } } diff --git a/database/seeders/1_event_seeder.ts b/database/seeders/1_event_seeder.ts index 8c2fac1..03422b6 100644 --- a/database/seeders/1_event_seeder.ts +++ b/database/seeders/1_event_seeder.ts @@ -1,3 +1,26 @@ +/* + * ______ __ __ + * _ __/ ____/___ ____ / /____ _____/ /_ + * | |/_/ / / __ \/ __ \/ __/ _ \/ ___/ __/ + * _> /) + if (!match) return null + + const notice = match[1] + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/ /g, '\n') + .replace(/$/g, '$') + .replace(/\$today\.year/g, new Date().getFullYear().toString()) + return notice + } catch (e) { + return null + } +} + +const copyrightNotice = getCopyrightFromIdea() + +const copyrightRule = { + meta: { + type: 'layout', + docs: { description: 'Enforce copyright header' }, + fixable: 'prepend', + }, + create(context) { + return { + Program(node) { + const sourceCode = context.sourceCode + const firstComment = sourceCode.getAllComments()[0] + + const hasCopyright = /Copyright \(C\) \d{4}/.test(firstComment?.value || '') + + if (!hasCopyright) { + context.report({ + node, + message: 'Missing mandatory Copyright header at the top of the file.', + fix(fixer) { + const header = `/*\n${copyrightNotice + .split('\n') + .map((line) => ` * ${line}`) + .join('\n')}\n */\n\n` + return fixer.insertTextBefore(node, header) + }, + }) + } + }, + } + }, +} + export default configApp([ { + languageOptions: { + parserOptions: { + projectService: true, // This is the modern way in v8+ to handle type info + tsconfigRootDir: import.meta.dirname, + }, + }, plugins: { // import: importPlugin, }, rules: { 'prettier/prettier': 'off', - 'nonblock-statement-body-position': ['error', 'below'], 'curly': ['error', 'multi'], - 'brace-style': ['error', '1tbs', { allowSingleLine: false }], - 'indent': ['error', 2, { SwitchCase: 1 }], - 'object-curly-spacing': ['error', 'always'], - 'no-console': 'error', 'arrow-body-style': ['error', 'as-needed'], - '@typescript-eslint/no-unused-vars': [ // no-unused-vars, but allows in declarations + 'no-return-await': 'off', + + '@stylistic/nonblock-statement-body-position': ['error', 'below'], + '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: false }], + '@stylistic/indent': ['error', 2, { SwitchCase: 1 }], + '@stylistic/object-curly-spacing': ['error', 'always'], + '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], + '@stylistic/semi': ['error', 'never'], + '@stylistic/comma-dangle': ['error', 'always-multiline'], + '@stylistic/arrow-parens': ['error', 'always'], + '@stylistic/dot-location': ['error', 'property'], + '@stylistic/keyword-spacing': ['error', { before: true, after: true }], + '@stylistic/space-infix-ops': 'error', + '@stylistic/operator-linebreak': ['error', 'before'], + '@stylistic/quote-props': ['error', 'as-needed'], + '@stylistic/no-whitespace-before-property': 'error', + '@stylistic/padded-blocks': ['error', 'never'], + '@stylistic/space-before-function-paren': [ + 'error', + { + anonymous: 'always', + named: 'never', + asyncArrow: 'always', + catch: 'always', + }, + ], + + 'no-console': 'error', + 'prefer-const': 'error', + + '@typescript-eslint/no-unused-vars': [ + // no-unused-vars, but allows in declarations 'error', { argsIgnorePattern: '^_', @@ -25,7 +143,21 @@ export default configApp([ ignoreUsingDeclarations: true, }, ], - 'prefer-const': 'error', + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'variable', + format: ['camelCase', 'UPPER_CASE', 'PascalCase'], + leadingUnderscore: 'allow', + }, + { selector: 'typeLike', format: ['PascalCase'] }, + { + selector: 'interface', + format: ['PascalCase'], + }, + ], + '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }], + '@typescript-eslint/return-await': 'error', // 'import/order': [ // 'error', @@ -44,4 +176,21 @@ export default configApp([ // ], }, }, + { + files: ['ace.js', '*/**.js', 'eslint.config.mjs', 'adonisrc.ts'], + extends: [tseslint.configs.disableTypeChecked], + rules: { + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/return-await': 'off', + }, + }, + { + plugins: { + local: { rules: { 'copyright-header': copyrightRule } }, + }, + files: ['app/**/*.ts', 'config/**/*.ts', 'database/**/*.ts', 'start/**/*.ts', 'tests/**/*.ts'], + rules: { + 'local/copyright-header': 'error', + }, + }, ]) diff --git a/package.json b/package.json index 10c909d..d628f2e 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "pino-pretty": "^13.0.0", "prettier": "^3.5.3", "ts-node-maintained": "^10.9.5", - "typescript": "~5.8" + "typescript": "~5.8", + "typescript-eslint": "^8.56.1" }, "dependencies": { "@adonisjs/ally": "^5.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8ac2a4d..3d1a764 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,6 +105,9 @@ importers: typescript: specifier: ~5.8 version: 5.8.3 + typescript-eslint: + specifier: ^8.56.1 + version: 8.56.1(eslint@9.39.3)(typescript@5.8.3) packages: @@ -1191,16 +1194,16 @@ packages: '@types/validator@13.15.10': resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} - '@typescript-eslint/eslint-plugin@8.56.0': - resolution: {integrity: sha512-lRyPDLzNCuae71A3t9NEINBiTn7swyOhvUj3MyUOxb8x6g6vPEFoOU+ZRmGMusNC3X3YMhqMIX7i8ShqhT74Pw==} + '@typescript-eslint/eslint-plugin@8.56.1': + resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.56.0 + '@typescript-eslint/parser': ^8.56.1 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.56.0': - resolution: {integrity: sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==} + '@typescript-eslint/parser@8.56.1': + resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1212,18 +1215,34 @@ packages: peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/project-service@8.56.1': + resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/scope-manager@8.56.0': resolution: {integrity: sha512-7UiO/XwMHquH+ZzfVCfUNkIXlp/yQjjnlYUyYz7pfvlK3/EyyN6BK+emDmGNyQLBtLGaYrTAI6KOw8tFucWL2w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.56.1': + resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.56.0': resolution: {integrity: sha512-bSJoIIt4o3lKXD3xmDh9chZcjCz5Lk8xS7Rxn+6l5/pKrDpkCwtQNQQwZ2qRPk7TkUYhrq3WPIHXOXlbXP0itg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.56.0': - resolution: {integrity: sha512-qX2L3HWOU2nuDs6GzglBeuFXviDODreS58tLY/BALPC7iu3Fa+J7EOTwnX9PdNBxUI7Uh0ntP0YWGnxCkXzmfA==} + '@typescript-eslint/tsconfig-utils@8.56.1': + resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.56.1': + resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -1233,12 +1252,22 @@ packages: resolution: {integrity: sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.56.1': + resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.56.0': resolution: {integrity: sha512-ex1nTUMWrseMltXUHmR2GAQ4d+WjkZCT4f+4bVsps8QEdh0vlBsaCokKTPlnqBFqqGaxilDNJG7b8dolW2m43Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/typescript-estree@8.56.1': + resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.56.0': resolution: {integrity: sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1246,10 +1275,21 @@ packages: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/utils@8.56.1': + resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.0.0' + '@typescript-eslint/visitor-keys@8.56.0': resolution: {integrity: sha512-q+SL+b+05Ud6LbEE35qe4A99P+htKTKVbyiNEe45eCbJFyh/HVK9QXwlrbz+Q4L8SOW4roxSVwXYj4DMBT7Ieg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.56.1': + resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vinejs/compiler@3.0.0': resolution: {integrity: sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw==} engines: {node: '>=18.0.0'} @@ -1339,6 +1379,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + baseline-browser-mapping@2.10.0: resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} engines: {node: '>=6.0.0'} @@ -1357,6 +1401,10 @@ packages: brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.3: + resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2212,6 +2260,10 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + minimatch@10.2.2: + resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2836,8 +2888,8 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} - typescript-eslint@8.56.0: - resolution: {integrity: sha512-c7toRLrotJ9oixgdW7liukZpsnq5CZ7PuKztubGYlNppuTqhIoWfhgHo/7EU0v06gS2l/x0i2NEFK1qMIf0rIg==} + typescript-eslint@8.56.1: + resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -3119,7 +3171,7 @@ snapshots: eslint-plugin-prettier: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.3))(eslint@9.39.3)(prettier@3.8.1) eslint-plugin-unicorn: 60.0.0(eslint@9.39.3) prettier: 3.8.1 - typescript-eslint: 8.56.0(eslint@9.39.3)(typescript@5.8.3) + typescript-eslint: 8.56.1(eslint@9.39.3)(typescript@5.8.3) transitivePeerDependencies: - '@types/eslint' - supports-color @@ -4555,14 +4607,14 @@ snapshots: '@types/validator@13.15.10': {} - '@typescript-eslint/eslint-plugin@8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.3)(typescript@5.8.3))(eslint@9.39.3)(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.8.3))(eslint@9.39.3)(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.56.0(eslint@9.39.3)(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.56.0 - '@typescript-eslint/type-utils': 8.56.0(eslint@9.39.3)(typescript@5.8.3) - '@typescript-eslint/utils': 8.56.0(eslint@9.39.3)(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.56.0 + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3)(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.3)(typescript@5.8.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3)(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.56.1 eslint: 9.39.3 ignore: 7.0.5 natural-compare: 1.4.0 @@ -4571,12 +4623,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.56.0(eslint@9.39.3)(typescript@5.8.3)': + '@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.8.3)': dependencies: - '@typescript-eslint/scope-manager': 8.56.0 - '@typescript-eslint/types': 8.56.0 - '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.56.0 + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.56.1 debug: 4.4.3 eslint: 9.39.3 typescript: 5.8.3 @@ -4592,20 +4644,38 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/project-service@8.56.1(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.8.3) + '@typescript-eslint/types': 8.56.1 + debug: 4.4.3 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@8.56.0': dependencies: '@typescript-eslint/types': 8.56.0 '@typescript-eslint/visitor-keys': 8.56.0 + '@typescript-eslint/scope-manager@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + '@typescript-eslint/tsconfig-utils@8.56.0(typescript@5.8.3)': dependencies: typescript: 5.8.3 - '@typescript-eslint/type-utils@8.56.0(eslint@9.39.3)(typescript@5.8.3)': + '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.8.3)': dependencies: - '@typescript-eslint/types': 8.56.0 - '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.56.0(eslint@9.39.3)(typescript@5.8.3) + typescript: 5.8.3 + + '@typescript-eslint/type-utils@8.56.1(eslint@9.39.3)(typescript@5.8.3)': + dependencies: + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3)(typescript@5.8.3) debug: 4.4.3 eslint: 9.39.3 ts-api-utils: 2.4.0(typescript@5.8.3) @@ -4615,6 +4685,8 @@ snapshots: '@typescript-eslint/types@8.56.0': {} + '@typescript-eslint/types@8.56.1': {} + '@typescript-eslint/typescript-estree@8.56.0(typescript@5.8.3)': dependencies: '@typescript-eslint/project-service': 8.56.0(typescript@5.8.3) @@ -4630,6 +4702,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.56.1(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.56.1(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.8.3) + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/visitor-keys': 8.56.1 + debug: 4.4.3 + minimatch: 10.2.2 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.56.0(eslint@9.39.3)(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3) @@ -4641,11 +4728,27 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.56.1(eslint@9.39.3)(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.3) + '@typescript-eslint/scope-manager': 8.56.1 + '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.8.3) + eslint: 9.39.3 + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@8.56.0': dependencies: '@typescript-eslint/types': 8.56.0 eslint-visitor-keys: 5.0.1 + '@typescript-eslint/visitor-keys@8.56.1': + dependencies: + '@typescript-eslint/types': 8.56.1 + eslint-visitor-keys: 5.0.1 + '@vinejs/compiler@3.0.0': {} '@vinejs/vine@3.0.1': @@ -4723,6 +4826,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + baseline-browser-mapping@2.10.0: {} basic-auth@2.0.1: @@ -4740,6 +4845,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.3: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -5506,6 +5615,10 @@ snapshots: mimic-function@5.0.1: {} + minimatch@10.2.2: + dependencies: + brace-expansion: 5.0.3 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -6100,12 +6213,12 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typescript-eslint@8.56.0(eslint@9.39.3)(typescript@5.8.3): + typescript-eslint@8.56.1(eslint@9.39.3)(typescript@5.8.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.56.0(@typescript-eslint/parser@8.56.0(eslint@9.39.3)(typescript@5.8.3))(eslint@9.39.3)(typescript@5.8.3) - '@typescript-eslint/parser': 8.56.0(eslint@9.39.3)(typescript@5.8.3) - '@typescript-eslint/typescript-estree': 8.56.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.56.0(eslint@9.39.3)(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.3)(typescript@5.8.3))(eslint@9.39.3)(typescript@5.8.3) + '@typescript-eslint/parser': 8.56.1(eslint@9.39.3)(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.56.1(eslint@9.39.3)(typescript@5.8.3) eslint: 9.39.3 typescript: 5.8.3 transitivePeerDependencies: diff --git a/start/events.ts b/start/events.ts index e69de29..38419f8 100644 --- a/start/events.ts +++ b/start/events.ts @@ -0,0 +1,23 @@ +/* + * ______ __ __ + * _ __/ ____/___ ____ / /____ _____/ /_ + * | |/_/ / / __ \/ __ \/ __/ _ \/ ___/ __/ + * _> ({ // Temporary endpoint for development if (app.inDev) router.get('/adminme', async ({ auth }) => { - const user = await auth.authenticate(); + const user = await auth.authenticate() user.permissions = UserGuard.allPermissions() await user.save() return { diff --git a/tests/bootstrap.ts b/tests/bootstrap.ts index d626eb9..46b58f7 100644 --- a/tests/bootstrap.ts +++ b/tests/bootstrap.ts @@ -27,7 +27,7 @@ import app from '@adonisjs/core/services/app' import type { Config } from '@japa/runner/types' import { pluginAdonisJS } from '@japa/plugin-adonisjs' import testUtils from '@adonisjs/core/services/test_utils' -import { authApiClient } from "@adonisjs/auth/plugins/api_client"; +import { authApiClient } from '@adonisjs/auth/plugins/api_client' import { sessionApiClient } from '@adonisjs/session/plugins/api_client' /** @@ -43,7 +43,7 @@ export const plugins: Config['plugins'] = [ apiClient(), pluginAdonisJS(app), sessionApiClient(app), - authApiClient(app) + authApiClient(app), ] /** @@ -65,5 +65,4 @@ export const runnerHooks: Required> = { export const configureSuite: Config['configureSuite'] = (suite) => { if (['browser', 'functional', 'e2e'].includes(suite.name)) return suite.setup(() => testUtils.httpServer().start()) - } diff --git a/tests/functional/auth/login.spec.ts b/tests/functional/auth/login.spec.ts index 61189d8..ef3088e 100644 --- a/tests/functional/auth/login.spec.ts +++ b/tests/functional/auth/login.spec.ts @@ -52,7 +52,7 @@ test.group('Auth login', (group) => { test('fails with non-existent email', async ({ client }) => { const response = await client.post('/auth/login').json({ email: 'user@no.host', - password: 'testtesttest' + password: 'testtesttest', }) response.assertStatus(400) @@ -60,9 +60,8 @@ test.group('Auth login', (group) => { test('fails when required fields are missing', async ({ client }) => { const response = await client.post('/auth/login').json({ - email: '' + email: '', }) response.assertStatus(422) }) - }) diff --git a/tests/functional/auth/register.spec.ts b/tests/functional/auth/register.spec.ts index 83cd992..95d91cb 100644 --- a/tests/functional/auth/register.spec.ts +++ b/tests/functional/auth/register.spec.ts @@ -66,7 +66,7 @@ test.group('Auth register', (group) => { test('fails when required fields are missing', async ({ client }) => { const response = await client.post('/auth/register').json({ - email: '' + email: '', }) response.assertStatus(422) diff --git a/tests/functional/teams/invites.spec.ts b/tests/functional/teams/invites.spec.ts index b4973f4..e075e8f 100644 --- a/tests/functional/teams/invites.spec.ts +++ b/tests/functional/teams/invites.spec.ts @@ -22,13 +22,13 @@ */ import { test } from '@japa/runner' -import testUtils from "@adonisjs/core/services/test_utils"; +import testUtils from '@adonisjs/core/services/test_utils' import { InvitationFactory, TeamFactory, TeamMemberFactory } from '#database/factories/team_factory' -import Event from "#models/event/event"; -import { UserFactory } from "#database/factories/user_factory"; -import Team from "#models/team/team"; -import User from "#models/user"; -import { DateTime } from "luxon"; +import Event from '#models/event/event' +import { UserFactory } from '#database/factories/user_factory' +import type Team from '#models/team/team' +import type User from '#models/user' +import { DateTime } from 'luxon' declare module '@japa/runner/core' { interface TestContext { @@ -51,7 +51,7 @@ test.group('Invitations functionality', (group) => { test('Can invite other user via code', async ({ client, assert, team }) => { const response = await client.post(`/teams/${team.id}/invites`).json({ - validFor: '1 hour' + validFor: '1 hour', }).loginAs(team.members[0].user) response.assertOk() @@ -70,7 +70,7 @@ test.group('Invitations functionality', (group) => { response.assertOk() assert.exists(response.body().token) response.assertBodyContains({ - inviteeEmail: user.email + inviteeEmail: user.email, }) }) @@ -93,7 +93,7 @@ test.group('Invitations functionality', (group) => { test('Cannot invite if team is at event\'s max member count', async ({ client, event, team, teamAdmin }) => { await TeamMemberFactory.with('user').merge({ - teamId: team.id + teamId: team.id, }).createMany(event.maxTeamSize - team.members.length) const response = await client.post(`/teams/${team.id}/invites`).json({ @@ -103,13 +103,32 @@ test.group('Invitations functionality', (group) => { response.assertForbidden() }) + test('Cannot interact with an invitation if team is full') + .with(['ACCEPT', 'REJECT']) + .run(async ({ client, event, team, teamAdmin }, action) => { + const invitation = await InvitationFactory.merge({ + inviterId: teamAdmin.id, + teamId: team.id, + expiresAt: DateTime.now().minus({ hours: 2 }), + }).create() + const user = await UserFactory.create() + + await TeamMemberFactory.with('user').merge({ + teamId: team.id, + }).createMany(event.maxTeamSize - team.members.length - 1) + + const response = await client.get(`/invitations/${invitation.token}?action=${action}`).loginAs(user) + + response.assertUnprocessableEntity() + }) + test('Cannot interact with expired invitation') .with(['ACCEPT', 'REJECT']) .run(async ({ client, team, teamAdmin }, action) => { const invitation = await InvitationFactory.merge({ inviterId: teamAdmin.id, teamId: team.id, - expiresAt: DateTime.now().minus({ hours: 2 }) + expiresAt: DateTime.now().minus({ hours: 2 }), }).create() const user = await UserFactory.create() @@ -136,13 +155,13 @@ test.group('Invitations functionality', (group) => { response.assertBodyContains({ invitation: { status: data.status, - } + }, }) if (data.status === 'ACCEPTED') assert.exists(response.body().member) }) - test("Can accept direct invitation", async ({ client, team, teamAdmin }) => { + test('Can accept direct invitation', async ({ client, team, teamAdmin }) => { const user = await UserFactory.create() const invitation = await InvitationFactory.merge({ inviteeEmail: user.email, @@ -156,7 +175,7 @@ test.group('Invitations functionality', (group) => { response.assertBodyContains({ invitation: { status: 'ACCEPTED', - } + }, }) }) @@ -176,14 +195,14 @@ test.group('Invitations functionality', (group) => { test('Can list team invitations', async ({ client, team, teamAdmin }) => { const invitations = await InvitationFactory.merge({ inviterId: teamAdmin.id, - teamId: team.id + teamId: team.id, }).createMany(10) const response = await client.get(`/teams/${team.id}/invites`).loginAs(teamAdmin) response.assertOk() - response.assertBodyContains(invitations.map(invitation => ({ - id: invitation.id + response.assertBodyContains(invitations.map((invitation) => ({ + id: invitation.id, }))) }) diff --git a/tests/functional/teams/teams.spec.ts b/tests/functional/teams/teams.spec.ts index a98f9b3..b480f20 100644 --- a/tests/functional/teams/teams.spec.ts +++ b/tests/functional/teams/teams.spec.ts @@ -22,10 +22,10 @@ */ import { test } from '@japa/runner' -import testUtils from "@adonisjs/core/services/test_utils"; -import User from "#models/user"; -import { TeamFactory } from "#database/factories/team_factory"; -import Event from "#models/event/event"; +import testUtils from '@adonisjs/core/services/test_utils' +import User from '#models/user' +import { TeamFactory } from '#database/factories/team_factory' +import Event from '#models/event/event' test.group('Teams core functionality', (group) => { group.each.setup(() => testUtils.db().seed()) @@ -40,7 +40,7 @@ test.group('Teams core functionality', (group) => { response.assertCreated() response.assertBodyContains({ - name: 'My Team' + name: 'My Team', }) }) @@ -48,7 +48,7 @@ test.group('Teams core functionality', (group) => { const user = await User.findByOrFail('nickname', 'user') const response = await client.post('/events/not-visible/teams').json({ - name: 'My Team' + name: 'My Team', }).loginAs(user) response.assertForbidden() @@ -58,12 +58,12 @@ test.group('Teams core functionality', (group) => { const team = await TeamFactory.with('members', 1, (member) => member.with('user')).create() const response = await client.put(`/teams/${team.id}`).json({ - name: 'Updated Team Name' + name: 'Updated Team Name', }).loginAs(team.members[0].user) response.assertOk() response.assertBodyContains({ - name: 'Updated Team Name' + name: 'Updated Team Name', }) }) @@ -108,7 +108,7 @@ test.group('Teams core functionality', (group) => { response.assertOk() response.assertBodyContains({ - data: teams.map(team => ({ id: team.id })) + data: teams.map((team) => ({ id: team.id })), }) }) @@ -164,7 +164,7 @@ test.group('Teams core functionality', (group) => { response.assertUnprocessableEntity() }) - test('User can leave team', async({ client }) => { + test('User can leave team', async ({ client }) => { const event = await Event.findByOrFail('slug', 'no-tasks') const team = await TeamFactory.with('members', 2, (member) => member.with('user')) .merge({ eventId: event.id }) @@ -172,7 +172,7 @@ test.group('Teams core functionality', (group) => { // Kicking self = leaving const response = await client.post(`/teams/${team.id}/kick`).json({ - member: team.members[1].id + member: team.members[1].id, }).loginAs(team.members[1].user) response.assertNoContent() @@ -185,7 +185,7 @@ test.group('Teams core functionality', (group) => { .create() const response = await client.post(`/teams/${team.id}/kick`).json({ - member: team.members[1].id + member: team.members[1].id, }).loginAs(team.members[0].user) response.assertNoContent() @@ -198,13 +198,13 @@ test.group('Teams core functionality', (group) => { .create() const response = await client.post(`/teams/${team.id}/kick`).json({ - member: team.members[1].id + member: team.members[1].id, }).loginAs(team.members[2].user) response.assertForbidden() }) - test('Owner cannot leave the team or be kicked', async({ client }) => { + test('Owner cannot leave the team or be kicked', async ({ client }) => { const event = await Event.findByOrFail('slug', 'no-tasks') const team = await TeamFactory.with('members', 2, (member) => member.with('user')) .merge({ eventId: event.id })