diff --git a/api/controllers/groups/admin.ts b/api/controllers/groups/admin.ts index 1a97bb09..7b4abf99 100644 --- a/api/controllers/groups/admin.ts +++ b/api/controllers/groups/admin.ts @@ -80,11 +80,7 @@ export const addGroupLiteAdmins = async ( }, ); - if ( - !musicianToMakeLiteAdmin || - musicianToMakeLiteAdmin.membership == 'declined' || - musicianToMakeLiteAdmin.membership == 'pending' - ) { + if (!musicianToMakeLiteAdmin) { const err = new Error(`E_MUSICIAN_NOT_MEMBER`); err['musicianId'] = new_lite_admins_id[i]; err['name'] = 'E_MUSICIAN_NOT_MEMBER'; diff --git a/api/controllers/groups/index.ts b/api/controllers/groups/index.ts index b51016b4..d2ac4980 100644 --- a/api/controllers/groups/index.ts +++ b/api/controllers/groups/index.ts @@ -16,3 +16,12 @@ export { transferGroupAdmin, addGroupLiteAdmins, } from './admin'; + +export { + getGroupInvitationsReceived, + getGroupInvitationsSent, + postGroupToUserInvitation, + deleteGroupInvitationById, + acceptGroupInvitation, + declineGroupInvitation, +} from './invitation'; diff --git a/api/controllers/groups/invitation.ts b/api/controllers/groups/invitation.ts new file mode 100644 index 00000000..b119604c --- /dev/null +++ b/api/controllers/groups/invitation.ts @@ -0,0 +1,497 @@ +import { getRepository } from 'typeorm'; +import { + GroupDeclineInvitationNotification, + Instrument, + Invitation, + Musician, + MusicianGroup, + MusicianJoinedGroupNotification, +} from '../../entity'; +import type core from 'express-serve-static-core'; +import type { Request } from 'express'; +import type { NextFunction } from 'express'; +import type { operations } from '@schema'; +import type { + getHTTPCode, + getPathParams, + getRequestBody, + getResponsesBody, +} from '@typing'; + +type GetGroupInvitationReceived = operations['getGroupInvitationReceived']; +type GetGroupInvitationSent = operations['getGroupInvitationSent']; +type PostGroupToUserInvitation = operations['postGroupToUserInvitation']; +type DeleteGroupInvitationById = operations['deleteGroupInvitationById']; +type AcceptGroupInvitation = operations['acceptGroupInvitation']; +type DeclinetGroupInvitation = operations['declineGroupInvitation']; + +export const getGroupInvitationsReceived = async ( + req: Request< + getPathParams, + getResponsesBody, + {}, + {} + >, + res: core.Response< + getResponsesBody, + {}, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const invitationRepo = getRepository(Invitation); + const musicianGroupRepository = getRepository(MusicianGroup); + const groupId = req.params.groupId; + + const group = await musicianGroupRepository.findOne({ + where: { + group: { + id: groupId, + }, + musician: { + id: req.userId, + }, + }, + }); + + if (!group) { + res.status(404).json({ msg: 'E_MUSICIAN_NOT_MEMBER' }); + } + + const invitations = await invitationRepo.find({ + where: { + type: 'musicianToGroup', + group: groupId, + }, + relations: ['musician', 'instruments'], + }); + + return res.status(200).json(invitations); + } catch (err) { + next(err); + } +}; + +export const getGroupInvitationsSent = async ( + req: Request< + getPathParams, + getResponsesBody, + {}, + {} + >, + res: core.Response< + getResponsesBody, + {}, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const invitationRepo = getRepository(Invitation); + const musicianGroupRepository = getRepository(MusicianGroup); + const groupId = req.params.groupId; + + const group = await musicianGroupRepository.findOne({ + where: { + group: { + id: groupId, + }, + musician: { + id: req.userId, + }, + }, + }); + + if (!group) { + res.status(404).json({ msg: 'E_MUSICIAN_NOT_MEMBER' }); + } + + const invitations = await invitationRepo.find({ + where: { + type: 'groupToMusician', + group: groupId, + }, + relations: ['musician', 'instruments', 'invitor'], + }); + return res.status(200).json(invitations); + } catch (err) { + next(err); + } +}; + +export const postGroupToUserInvitation = async ( + req: Request< + getPathParams, + getResponsesBody, + getRequestBody, + {} + >, + res: core.Response< + {}, + getResponsesBody, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const musicianId = req.body.musicianId; + const groupId = req.params.groupId; + const instruments = req.body.instruments; + const description = req.body.description; + + const invitationRepo = getRepository(Invitation); + + const member = await getRepository(MusicianGroup).findOne({ + group: { + id: groupId, + }, + musician: { + id: req.userId, + }, + }); + + if (!member) { + return res.status(403).json({ msg: 'E_UNAUTHORIZED_USER' }); + } + + const invitation = await invitationRepo.findOne({ + where: { + musician: { + id: musicianId, + }, + group: { + id: groupId, + }, + type: 'groupToMusician', + }, + relations: [ + 'group', + 'instruments', + 'group.genres', + 'musician', + 'invitor', + ], + }); + + /** Check if the invitation already exist with the same instruments + * If true, we just return 204 + */ + + if ( + invitation && + instruments.length == invitation.instruments.length && + invitation.instruments.length != 0 && + instruments + .map(({ name }) => name) + .every((name) => + invitation.instruments.map(({ name }) => name).includes(name), + ) && + invitation.description === description && + invitation.invitor.id === req.userId + ) { + return res.sendStatus(200); + } + + const invitationInstruments: Instrument[] = []; + + for (let i = 0; i < instruments.length; i++) { + invitationInstruments.push( + await getRepository(Instrument).findOne({ + name: instruments[i].name, + }), + ); + } + + if (invitation) { + invitation.instruments = invitationInstruments; + invitation.description = description; + invitation.invitor.id = req.userId; + await invitationRepo.save(invitation); + return res.sendStatus(200); + } + + const musician = await getRepository(Musician).findOne({ + id: musicianId, + }); + + if (!musician) { + return res.status(404).json({ msg: 'E_MUSICIAN_DOES_NOT_EXIST' }); + } + + const userAlreadyInGroup = await getRepository(MusicianGroup).findOne({ + musician: { + id: musicianId, + }, + group: { + id: groupId, + }, + }); + + if (userAlreadyInGroup) { + return res.status(422).json({ msg: 'E_USER_ALREADY_IN_GROUP' }); + } + + const newInvition = invitationRepo.create({ + musician: musician, + group: { + id: groupId, + }, + type: 'groupToMusician', + instruments: invitationInstruments, + description, + invitor: { + id: req.userId, + }, + }); + + await invitationRepo.save(newInvition); + + return res.sendStatus(201); + } catch (err) { + next(err); + } +}; + +export const deleteGroupInvitationById = async ( + req: Request< + getPathParams, + getResponsesBody, + {}, + {} + >, + res: core.Response< + getResponsesBody, + {}, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const invationId = req.params.invitationId; + const groupId = req.params.groupId; + const invitationRepository = getRepository(Invitation); + const musicianGroupRepository = getRepository(MusicianGroup); + + const member = await musicianGroupRepository.findOne({ + musician: { + id: req.userId, + }, + group: { + id: groupId, + }, + }); + + if (!member) { + return res.status(403).json({ msg: 'E_UNAUTHORIZED_USER' }); + } + + const invitation = await invitationRepository.findOne({ + id: invationId, + type: 'groupToMusician', + }); + + if (!invitation) { + return res.status(404).json({ msg: 'E_INVITATION_DOES_NOT_EXIST' }); + } + + await invitationRepository.delete({ + id: invationId, + }); + + return res.sendStatus(204); + } catch (err) { + next(err); + } +}; + +export const acceptGroupInvitation = async ( + req: Request< + getPathParams, + getResponsesBody, + {}, + {} + >, + res: core.Response< + getResponsesBody, + {}, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const invationId = req.params.invitationId; + const groupId = req.params.groupId; + + const invitationRepository = getRepository(Invitation); + const musicianGroupRepository = getRepository(MusicianGroup); + + const member = await musicianGroupRepository.findOne({ + musician: { + id: req.userId, + }, + group: { + id: groupId, + }, + }); + + if (!member) { + return res.status(403).json({ msg: 'E_UNAUTHORIZED_USER' }); + } + + const invitation = await invitationRepository.findOne({ + where: { + id: invationId, + type: 'musicianToGroup', + }, + relations: [ + 'instruments', + 'group', + 'musician', + 'group.members', + 'group.members.musician', + ], + }); + + if (!invitation) { + return res.status(404).json({ msg: 'E_INVITATION_DOES_NOT_EXIST' }); + } + + const invitationInstruments: Instrument[] = []; + + for (let i = 0; i < invitation.instruments.length; i++) { + invitationInstruments.push( + await getRepository(Instrument).findOne({ + name: invitation.instruments[i].name, + }), + ); + } + + const newMusicianGroup = musicianGroupRepository.create({ + musician: invitation.musician, + group: invitation.group, + membership: 'member', + instruments: invitationInstruments, + }); + + await musicianGroupRepository.save(newMusicianGroup); + + await invitationRepository.delete({ + musician: invitation.musician, + group: invitation.group, + }); + + const notifications = invitation.group.members.map((member) => + getRepository(MusicianJoinedGroupNotification).create({ + musician: member.musician, + group: invitation.group, + newMusician: invitation.musician, + }), + ); + + await getRepository(MusicianJoinedGroupNotification).save(notifications); + + return res.sendStatus(204); + } catch (err) { + next(err); + } +}; + +export const declineGroupInvitation = async ( + req: Request< + getPathParams, + getResponsesBody, + {}, + {} + >, + res: core.Response< + getResponsesBody, + {}, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const invationId = req.params.invitationId; + const groupId = req.params.groupId; + const invitationRepository = getRepository(Invitation); + const musicianGroupRepository = getRepository(MusicianGroup); + + const member = await musicianGroupRepository.findOne({ + musician: { + id: req.userId, + }, + group: { + id: groupId, + }, + }); + + if (!member) { + return res.status(403).json({ msg: 'E_UNAUTHORIZED_USER' }); + } + const invitation = await invitationRepository.findOne({ + where: { + id: invationId, + type: 'musicianToGroup', + }, + relations: ['musician', 'group'], + }); + + if (!invitation) { + return res.status(404).json({ msg: 'E_INVITATION_DOES_NOT_EXIST' }); + } + + await invitationRepository.delete({ + id: invationId, + }); + + const notification = getRepository( + GroupDeclineInvitationNotification, + ).create({ + musician: invitation.musician, + group: invitation.group, + }); + + await getRepository(GroupDeclineInvitationNotification).save(notification); + + return res.sendStatus(204); + } catch (err) { + next(err); + } +}; diff --git a/api/controllers/profil/index.ts b/api/controllers/profil/index.ts index 17c60ad4..e35a79d5 100644 --- a/api/controllers/profil/index.ts +++ b/api/controllers/profil/index.ts @@ -5,3 +5,12 @@ export { deleteNotificationById, deleteAllNotifications, } from './notification'; + +export { + getUserInvitationsReceived, + getUserInvitationsSent, + postUserToGroupInvitation, + deleteInvitationById, + acceptProfilInvitation, + declineProfilInvitation, +} from './invitation'; diff --git a/api/controllers/profil/invitation.ts b/api/controllers/profil/invitation.ts new file mode 100644 index 00000000..21884f45 --- /dev/null +++ b/api/controllers/profil/invitation.ts @@ -0,0 +1,418 @@ +import { getRepository } from 'typeorm'; +import { + Groups, + Instrument, + Invitation, + MusicianGroup, + GroupReceiveInvitationNotification, + MusicianJoinedGroupNotification, + MusicianDeclineInvitationNotification, +} from '../../entity'; +import type core from 'express-serve-static-core'; +import type { Request } from 'express'; +import type { NextFunction } from 'express'; +import type { operations } from '@schema'; +import type { + getHTTPCode, + getPathParams, + getRequestBody, + getResponsesBody, +} from '@typing'; + +type GetUserInvitationReceived = operations['getUserInvitationReceived']; +type GetUserInvitationSent = operations['getUserInvitationSent']; +type PostUserToGroupInvitation = operations['postUserToGroupInvitation']; +type DeleteProfilInvitationById = operations['deleteProfilInvitationById']; +type AcceptProfilInvitation = operations['acceptProfilInvitation']; +type DeclineProfilInvitation = operations['declineProfilInvitation']; + +export const getUserInvitationsReceived = async ( + req: Request<{}, getResponsesBody, {}, {}>, + res: core.Response< + getResponsesBody, + {}, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const invitationRepo = getRepository(Invitation); + + const invitations = await invitationRepo.find({ + where: { + type: 'groupToMusician', + musician: req.userId, + }, + relations: ['group', 'instruments', 'group.genres', 'invitor'], + }); + + return res.status(200).json(invitations); + } catch (err) { + next(err); + } +}; + +export const getUserInvitationsSent = async ( + req: Request<{}, getResponsesBody, {}, {}>, + res: core.Response< + getResponsesBody, + {}, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const invitationRepo = getRepository(Invitation); + + const invitations = await invitationRepo.find({ + where: { + type: 'musicianToGroup', + musician: req.userId, + }, + relations: ['group', 'instruments', 'group.genres'], + }); + + return res.status(200).json(invitations); + } catch (err) { + next(err); + } +}; + +export const postUserToGroupInvitation = async ( + req: Request< + {}, + getResponsesBody, + getRequestBody, + {} + >, + res: core.Response< + {}, + getResponsesBody, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const groupId = req.body.groupId; + const instruments = req.body.instruments; + const description = req.body.description; + + const invitationRepo = getRepository(Invitation); + + const invitation = await invitationRepo.findOne({ + join: { + alias: 'invitation', + innerJoin: { + group: 'invitation.group', + instruments: 'invitation.instruments', + }, + }, + where: { + musician: { + id: req.userId, + }, + group: { + id: groupId, + }, + type: 'musicianToGroup', + }, + relations: ['group', 'instruments', 'group.genres', 'musician'], + }); + + /** Check if the invitation already exist with the same instruments + * If true, we just return 204 + */ + + if ( + invitation && + instruments.length == invitation.instruments.length && + invitation.instruments.length != 0 && + instruments + .map(({ name }) => name) + .every((name) => + invitation.instruments.map(({ name }) => name).includes(name), + ) && + invitation.description == description + ) { + return res.sendStatus(200); + } + + const invitationInstruments: Instrument[] = []; + + for (let i = 0; i < instruments.length; i++) { + invitationInstruments.push( + await getRepository(Instrument).findOne({ + name: instruments[i].name, + }), + ); + } + + if (invitation) { + invitation.instruments = invitationInstruments; + invitation.description = description; + await invitationRepo.save(invitation); + return res.sendStatus(200); + } + + const group = await getRepository(Groups).findOne({ + where: { + id: groupId, + }, + relations: ['members', 'members.musician'], + }); + + if (!group) { + return res.status(404).json({ msg: 'E_GROUP_DOES_NOT_EXIST' }); + } + + const userAlreadyInGroup = await getRepository(MusicianGroup).findOne({ + musician: { + id: req.userId, + }, + group: { + id: groupId, + }, + }); + + if (userAlreadyInGroup) { + return res.status(422).json({ msg: 'E_USER_ALREADY_IN_GROUP' }); + } + + const newInvition = invitationRepo.create({ + musician: { + id: req.userId, + }, + group: { + id: groupId, + }, + type: 'musicianToGroup', + instruments: invitationInstruments, + description, + }); + + await invitationRepo.save(newInvition); + + const notifications = group.members.map((member) => + getRepository(GroupReceiveInvitationNotification).create({ + musician: member.musician, + group: group, + }), + ); + + await getRepository(GroupReceiveInvitationNotification).save(notifications); + + return res.sendStatus(201); + } catch (err) { + next(err); + } +}; + +export const acceptProfilInvitation = async ( + req: Request< + getPathParams, + getResponsesBody, + {}, + {} + >, + res: core.Response< + getResponsesBody, + {}, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const invationId = req.params.invitationId; + const invitationRepository = getRepository(Invitation); + const musicianGroupRepository = getRepository(MusicianGroup); + + const invitation = await invitationRepository.findOne({ + where: { + id: invationId, + type: 'groupToMusician', + }, + relations: [ + 'instruments', + 'group', + 'group.members', + 'group.members.musician', + 'musician', + ], + }); + + if (!invitation) { + return res.status(404).json({ msg: 'E_INVITATION_DOES_NOT_EXIST' }); + } + + const invitationInstruments: Instrument[] = []; + + for (let i = 0; i < invitation.instruments.length; i++) { + invitationInstruments.push( + await getRepository(Instrument).findOne({ + name: invitation.instruments[i].name, + }), + ); + } + + const newMusicianGroup = musicianGroupRepository.create({ + musician: { + id: req.userId, + }, + group: { + id: invitation.group.id, + }, + membership: 'member', + instruments: invitationInstruments, + }); + + await musicianGroupRepository.save(newMusicianGroup); + + await invitationRepository.delete({ + musician: { + id: req.userId, + }, + group: invitation.group, + }); + + const notifications = invitation.group.members.map((member) => + getRepository(MusicianJoinedGroupNotification).create({ + musician: member.musician, + group: invitation.group, + newMusician: invitation.musician, + }), + ); + + await getRepository(MusicianJoinedGroupNotification).save(notifications); + + return res.sendStatus(204); + } catch (err) { + next(err); + } +}; + +export const declineProfilInvitation = async ( + req: Request< + getPathParams, + getResponsesBody, + {}, + {} + >, + res: core.Response< + getResponsesBody, + {}, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const invationId = req.params.invitationId; + const invitationRepository = getRepository(Invitation); + + const invitation = await invitationRepository.findOne({ + where: { + id: invationId, + type: 'groupToMusician', + }, + relations: ['group', 'group.members', 'group.members.musician'], + }); + + if (!invitation) { + return res.status(404).json({ msg: 'E_INVITATION_DOES_NOT_EXIST' }); + } + + await invitationRepository.delete({ + id: invationId, + }); + + const notifications = invitation.group.members.map((member) => + getRepository(MusicianDeclineInvitationNotification).create({ + group: invitation.group, + musician: member.musician, + newMusician: { + id: req.userId, + }, + }), + ); + + await getRepository(MusicianDeclineInvitationNotification).save( + notifications, + ); + + return res.sendStatus(204); + } catch (err) { + next(err); + } +}; + +export const deleteInvitationById = async ( + req: Request< + getPathParams, + getResponsesBody, + {}, + {} + >, + res: core.Response< + getResponsesBody, + {}, + getHTTPCode + >, + next: NextFunction, +): Promise< + core.Response< + getResponsesBody, + {}, + getHTTPCode + > +> => { + try { + const invationId = req.params.invitationId; + const invitationRepository = getRepository(Invitation); + + const invitation = await invitationRepository.findOne({ + id: invationId, + type: 'musicianToGroup', + }); + + if (!invitation) { + return res.status(404).json({ msg: 'E_INVITATION_DOES_NOT_EXIST' }); + } + + await invitationRepository.delete({ + id: invationId, + }); + + return res.sendStatus(204); + } catch (err) { + next(err); + } +}; diff --git a/api/controllers/profil/notification.ts b/api/controllers/profil/notification.ts index 6c77267a..e3ed951f 100644 --- a/api/controllers/profil/notification.ts +++ b/api/controllers/profil/notification.ts @@ -32,7 +32,13 @@ export const getNotifications = async ( id: req.userId, }, }, - relations: ['group', 'group.genres', 'event', 'event.genres'], + relations: [ + 'group', + 'group.genres', + 'event', + 'event.genres', + 'newMusician', + ], }); return res.status(200).json(notications); diff --git a/api/controllers/profil/userGroup.ts b/api/controllers/profil/userGroup.ts index 9292c9f5..5e3f0910 100644 --- a/api/controllers/profil/userGroup.ts +++ b/api/controllers/profil/userGroup.ts @@ -61,18 +61,10 @@ export const leaveGroupById = async ( */ const otherMembers = musicianGroup.group.members.filter( - (musicianGroup) => - (musicianGroup.membership == 'member' || - musicianGroup.membership == 'lite_admin') && - musicianGroup.musician.id !== req.userId, + (musicianGroup) => musicianGroup.musician.id !== req.userId, ); if (otherMembers.length === 0) { - /* - We are going to delete the group because the admin leave the group - and all the other "members" are either declined or pending - */ - await getRepository(Groups).remove(musicianGroup.group); return res.sendStatus(200); diff --git a/api/db/reset.ts b/api/db/reset.ts index 05967856..d8e186c3 100644 --- a/api/db/reset.ts +++ b/api/db/reset.ts @@ -13,6 +13,7 @@ import { EventGroupKickNotification, GroupDeletedNotification, // GroupDeletedNotification, + Invitation, } from '../entity'; import Logger from '../log/logger'; import { createConnection, getConnection, getRepository } from 'typeorm'; @@ -32,6 +33,7 @@ import { exit } from 'process'; const musGrouRep = connection.getRepository(MusicianGroup); const eveRep = connection.getRepository(Event); const notRep = connection.getRepository(Notification); + const invRep = connection.getRepository(Invitation); // Reset all the database for the moment insRep.query('DELETE FROM instrument'); @@ -41,6 +43,7 @@ import { exit } from 'process'; musGrouRep.query('DELETE FROM musician_group'); eveRep.query('DELETE FROM event'); notRep.query('DELETE FROM notification'); + invRep.query('DELETE FROM invitation'); Logger.info('🚮 Reset all the DB tables'); @@ -157,12 +160,12 @@ import { exit } from 'process'; instruments: [guitare], }); - const spiritboxMusician3 = musGrouRep.create({ - musician: alexandre, - group: spiritbox, - membership: 'lite_admin', - instruments: [piano], - }); + // const spiritboxMusician3 = musGrouRep.create({ + // musician: alexandre, + // group: spiritbox, + // membership: 'lite_admin', + // instruments: [piano], + // }); const peripheryMusician1 = musGrouRep.create({ musician: romain, @@ -202,7 +205,6 @@ import { exit } from 'process'; await musGrouRep.save([ spiritboxMusician1, spiritboxMusician2, - spiritboxMusician3, peripheryMusician1, peripheryMusician2, slipknotMusician, @@ -286,6 +288,43 @@ import { exit } from 'process'; await getRepository(GroupDeletedNotification).save(notif6); Logger.info('📬 Notifications saved'); + const invitation1 = invRep.create({ + type: 'musicianToGroup', + musician: dorian, + group: slipknot, + instruments: [guitare], + description: "J'aimerais bcp rejoindre slipknot pcq c bien", + }); + + const invitation2 = invRep.create({ + type: 'groupToMusician', + musician: dorian, + group: slipknot, + instruments: [piano], + invitor: romain, + }); + + const invitation3 = invRep.create({ + type: 'groupToMusician', + musician: dorian, + group: allThatRemains, + instruments: [guitare], + invitor: romain, + description: 'Rejoins nous fréro, tu verras c lourd', + }); + + const invitation4 = invRep.create({ + type: 'groupToMusician', + musician: romain, + group: jazzGroup, + instruments: [batterie], + invitor: dorian, + description: 'Rejoins nous fréro, tu verras c lourd', + }); + + await invRep.save([invitation1, invitation2, invitation3, invitation4]); + Logger.info('✉️ invitations saved '); + exit(); } catch (err) { Logger.info(`❌ Couldn't reset the db data\n ${err.stack}`); diff --git a/api/docs/config/components.ts b/api/docs/config/components.ts index 5a2e79ac..113a0fe4 100644 --- a/api/docs/config/components.ts +++ b/api/docs/config/components.ts @@ -117,7 +117,7 @@ const components: OpenAPIV3.Document['components'] = { }, membership: { type: 'string', - enum: ['admin', 'member', 'declined', 'pending', 'lite_admin'], + enum: ['admin', 'member', 'lite_admin'], }, }, }, @@ -209,6 +209,22 @@ const components: OpenAPIV3.Document['components'] = { }, }, }, + invitation: { + type: 'object', + required: ['type', 'id', 'instruments'], + properties: { + id: { type: 'string' }, + type: { type: 'string', enum: ['musicianToGroup', 'groupToMusician'] }, + group: { $ref: '#/components/schemas/groupDescription' }, + musician: { $ref: '#/components/schemas/musicianMinimized' }, + invitor: { $ref: '#/components/schemas/musicianMinimized' }, + instruments: { + type: 'array', + items: { $ref: '#/components/schemas/instrument' }, + }, + description: { type: 'string', nullable: true }, + }, + }, }, securitySchemes: { BearerAuth: { diff --git a/api/docs/openApiDoc.ts b/api/docs/openApiDoc.ts index 5dc17775..bb1f7e43 100644 --- a/api/docs/openApiDoc.ts +++ b/api/docs/openApiDoc.ts @@ -1124,45 +1124,135 @@ const openApiDocs: OpenAPIV3.Document = { }, }, }, - '/groups/invitation/response': { + '/groups/{groupId}/invitations/{invitationId}/accept': { post: { - operationId: 'responseGroupInvitation', + operationId: 'acceptGroupInvitation', tags: ['groups'], - description: 'Respond to a group invitation', security: [{ BearerAuth: [] }], - requestBody: { - content: { - 'application/json': { - schema: { - type: 'object', - required: ['groupId', 'response'], - properties: { - groupId: { type: 'string' }, - response: { type: 'string', enum: ['declined', 'member'] }, - }, - example: { - groupId: '0bc1164f-c92b-48f3-aadf-a2be610819d8', - response: 'member', - }, + description: 'Accept an invitation than a group received', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { type: 'string' }, + required: true, + }, + { + in: 'path', + name: 'groupId', + schema: { type: 'string' }, + required: true, + description: 'The ID of the group', + }, + ], + responses: { + '204': { + description: 'The invitations has been accepted', + content: { 'application/json': { schema: { type: 'string' } } }, + }, + '403': { + description: 'The user does not have the right', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + '404': { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, }, }, }, }, + }, + }, + '/groups/{groupId}/invitations/{invitationId}/decline': { + delete: { + operationId: 'declineGroupInvitation', + tags: ['groups'], + security: [{ BearerAuth: [] }], + description: 'Decline an invitation than a group received', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { type: 'string' }, + required: true, + }, + { + in: 'path', + name: 'groupId', + schema: { type: 'string' }, + required: true, + description: 'The ID of the group', + }, + ], responses: { - '201': { - description: 'The user membershhip has been updated', + '204': { + description: 'The invitations has been declined', content: { 'application/json': { schema: { type: 'string' } } }, }, - '400': { - description: 'The user has already responded', + '403': { + description: 'The user does not have the right', content: { 'application/json': { schema: { $ref: '#/components/schemas/httpError' }, }, }, }, - '401': { - description: "User can't respond to this invitation", + '404': { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + }, + }, + }, + '/groups/{groupId}/invitations/{invitationId}': { + delete: { + operationId: 'deleteGroupInvitationById', + tags: ['groups'], + security: [{ BearerAuth: [] }], + description: 'Delete a musician to group invitation by its id', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { type: 'string' }, + required: true, + }, + { + in: 'path', + name: 'groupId', + schema: { type: 'string' }, + required: true, + description: 'The ID of the group', + }, + ], + responses: { + '204': { + description: 'The invitations has been deleted', + content: { 'application/json': { schema: { type: 'string' } } }, + }, + '403': { + description: 'The user does not have the right', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + '404': { + description: 'the invitation does not exist', content: { 'application/json': { schema: { $ref: '#/components/schemas/httpError' }, @@ -1172,49 +1262,167 @@ const openApiDocs: OpenAPIV3.Document = { }, }, }, - '/groups/invitation/send': { + '/groups/{groupId}/invitations/received': { + get: { + operationId: 'getGroupInvitationReceived', + tags: ['groups'], + security: [{ BearerAuth: [] }], + description: 'Get all the invitation received for a group', + parameters: [ + { + in: 'path', + name: 'groupId', + schema: { type: 'string' }, + required: true, + description: 'The ID of the group', + }, + ], + responses: { + '200': { + description: 'The invitations received by the group', + content: { + 'application/json': { + schema: { + type: 'array', + items: { $ref: '#/components/schemas/invitation' }, + }, + }, + }, + }, + '403': { + description: 'The user does not have the right', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + '404': { + description: 'The group does not exist', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + }, + }, + }, + '/groups/{groupId}/invitations': { post: { - operationId: 'sendGroupInvitation', + operationId: 'postGroupToUserInvitation', tags: ['groups'], - description: 'Invite a musician in a group', security: [{ BearerAuth: [] }], + description: 'Post a new invitation from a group to a user', + parameters: [ + { + in: 'path', + name: 'groupId', + schema: { type: 'string' }, + required: true, + description: 'The ID of the group', + }, + ], requestBody: { + required: true, content: { 'application/json': { schema: { type: 'object', - required: ['groupId', 'musicianId', 'instrumentId', 'role'], + required: ['musicianId', 'instruments'], properties: { - groupId: { type: 'string' }, musicianId: { type: 'string' }, - instrumentId: { type: 'string' }, - role: { type: 'string', enum: ['lite_admin', 'member'] }, + instruments: { + type: 'array', + items: { $ref: '#/components/schemas/instrument' }, + }, + description: { type: 'string', nullable: true }, }, }, - example: { - groupId: '0bc1164f-c92b-48f3-aadf-a2be610819d8', - musicianId: '8c9a685a-2be9-4cf0-a03c-0b316fc4b515', - instrumentId: 'cd836a31-1663-4a11-8a88-0a249aa70793', - role: 'member', - }, }, }, }, responses: { + '200': { + description: 'The invitation has been updated', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, '201': { - description: 'The user has been invited', - content: { 'application/json': { schema: { type: 'string' } } }, + description: 'The invitation has been sent', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, }, - '400': { - description: 'The user is already invited', + '403': { + description: 'The user does not have the right', content: { 'application/json': { schema: { $ref: '#/components/schemas/httpError' }, }, }, }, - '401': { - description: "User that invite doesn't have the access", + '404': { + description: 'the musician does not exist', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + '422': { + description: 'the user is already in the group', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + }, + }, + }, + '/groups/{groupId}/invitations/sent': { + get: { + operationId: 'getGroupInvitationSent', + tags: ['groups'], + security: [{ BearerAuth: [] }], + description: 'Get all the invitation that the group sent to musicians', + parameters: [ + { + in: 'path', + name: 'groupId', + schema: { type: 'string' }, + required: true, + description: 'The ID of the group', + }, + ], + responses: { + '200': { + description: 'The invitations sent by the group', + content: { + 'application/json': { + schema: { + type: 'array', + items: { $ref: '#/components/schemas/invitation' }, + }, + }, + }, + }, + '403': { + description: 'The user does not have the right', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + '404': { + description: 'The group does not exist', content: { 'application/json': { schema: { $ref: '#/components/schemas/httpError' }, @@ -1516,6 +1724,258 @@ const openApiDocs: OpenAPIV3.Document = { }, }, }, + '/profil/groups/{groupId}/leave': { + post: { + description: 'Leave a group', + operationId: 'leaveGroup', + tags: ['profil'], + security: [{ BearerAuth: [] }], + parameters: [ + { + in: 'path', + name: 'groupId', + schema: { type: 'string' }, + required: true, + description: 'The id of the group to leave', + }, + ], + requestBody: { + required: false, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + musicianId: { + type: 'string', + description: + "The id of the musician that will become the new admin of the group, only if it's the admin that is leaving the group", + }, + }, + }, + }, + }, + }, + responses: { + '200': { + description: 'The user have leaved the group', + content: { 'application/json': { schema: { type: 'string' } } }, + }, + '400': { + description: 'The body is required for an admin leaving an event', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + '404': { + description: 'This user is not in this group', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + }, + }, + }, + '/profil/invitations/{invitationId}/accept': { + post: { + operationId: 'acceptProfilInvitation', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Accept an invitation than the logged user received', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { type: 'string' }, + required: true, + }, + ], + responses: { + '204': { + description: 'The invitations has been accepted', + content: { 'application/json': { schema: { type: 'string' } } }, + }, + '404': { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + }, + }, + }, + '/profil/invitations/{invitationId}/decline': { + delete: { + operationId: 'declineProfilInvitation', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Decline an invitation than the logged user received', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { type: 'string' }, + required: true, + }, + ], + responses: { + '204': { + description: 'The invitations has been declined', + content: { 'application/json': { schema: { type: 'string' } } }, + }, + '404': { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + }, + }, + }, + '/profil/invitations/{invitationId}': { + delete: { + operationId: 'deleteProfilInvitationById', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Delete a musician to group invitation by its id', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { type: 'string' }, + required: true, + }, + ], + responses: { + '204': { + description: 'The invitations hbas been deleted', + content: { 'application/json': { schema: { type: 'string' } } }, + }, + '404': { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + }, + }, + }, + '/profil/invitations/received': { + get: { + operationId: 'getUserInvitationReceived', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Get all the invitation received by the logged user', + responses: { + '200': { + description: 'The invitations received by the logged user', + content: { + 'application/json': { + schema: { + type: 'array', + items: { $ref: '#/components/schemas/invitation' }, + }, + }, + }, + }, + }, + }, + }, + '/profil/invitations': { + post: { + operationId: 'postUserToGroupInvitation', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Post a new invitation from the logged user to a group', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['groupId', 'instruments'], + properties: { + groupId: { type: 'string' }, + instruments: { + type: 'array', + items: { $ref: '#/components/schemas/instrument' }, + }, + description: { type: 'string', nullable: true }, + }, + }, + }, + }, + }, + responses: { + '200': { + description: 'The invitation has been updated', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + '201': { + description: 'The invitation has been sent', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + '404': { + description: 'the group does not exist', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + '422': { + description: 'the user is already in the group', + content: { + 'application/json': { + schema: { $ref: '#/components/schemas/httpError' }, + }, + }, + }, + }, + }, + }, + '/profil/invitations/sent': { + get: { + operationId: 'getUserInvitationSent', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Get all the invitation sent by the logged user', + responses: { + '200': { + description: 'The invitations sent by the logged user', + content: { + 'application/json': { + schema: { + type: 'array', + items: { $ref: '#/components/schemas/invitation' }, + }, + }, + }, + }, + }, + }, + }, '/profil/notifications/{notificationId}': { delete: { description: 'Delete a notification by its id', @@ -1612,13 +2072,7 @@ const openApiDocs: OpenAPIV3.Document = { }, membership: { type: 'string', - enum: [ - 'admin', - 'member', - 'declined', - 'pending', - 'lite_admin', - ], + enum: ['admin', 'member', 'lite_admin'], }, group: { $ref: '#/components/schemas/groupDescription', @@ -1689,62 +2143,6 @@ const openApiDocs: OpenAPIV3.Document = { }, }, }, - '/profil/groups/{groupId}/leave': { - post: { - description: 'Leave a group', - operationId: 'leaveGroup', - tags: ['profil'], - security: [{ BearerAuth: [] }], - parameters: [ - { - in: 'path', - name: 'groupId', - schema: { type: 'string' }, - required: true, - description: 'The id of the group to leave', - }, - ], - requestBody: { - required: false, - content: { - 'application/json': { - schema: { - type: 'object', - properties: { - musicianId: { - type: 'string', - description: - "The id of the musician that will become the new admin of the group, only if it's the admin that is leaving the group", - }, - }, - }, - }, - }, - }, - responses: { - '200': { - description: 'The user have leaved the group', - content: { 'application/json': { schema: { type: 'string' } } }, - }, - '400': { - description: 'The body is required for an admin leaving an event', - content: { - 'application/json': { - schema: { $ref: '#/components/schemas/httpError' }, - }, - }, - }, - '404': { - description: 'This user is not in this group', - content: { - 'application/json': { - schema: { $ref: '#/components/schemas/httpError' }, - }, - }, - }, - }, - }, - }, }, openapi: '3.0.1', info: { @@ -1867,7 +2265,7 @@ const openApiDocs: OpenAPIV3.Document = { }, membership: { type: 'string', - enum: ['admin', 'member', 'declined', 'pending', 'lite_admin'], + enum: ['admin', 'member', 'lite_admin'], }, }, }, @@ -1950,6 +2348,25 @@ const openApiDocs: OpenAPIV3.Document = { }, }, }, + invitation: { + type: 'object', + required: ['type', 'id', 'instruments'], + properties: { + id: { type: 'string' }, + type: { + type: 'string', + enum: ['musicianToGroup', 'groupToMusician'], + }, + group: { $ref: '#/components/schemas/groupDescription' }, + musician: { $ref: '#/components/schemas/musicianMinimized' }, + invitor: { $ref: '#/components/schemas/musicianMinimized' }, + instruments: { + type: 'array', + items: { $ref: '#/components/schemas/instrument' }, + }, + description: { type: 'string', nullable: true }, + }, + }, }, securitySchemes: { BearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }, diff --git a/api/docs/schemas/groups/invitation/groupResponseInvitation.ts b/api/docs/schemas/groups/invitation/groupResponseInvitation.ts deleted file mode 100644 index f05e2c84..00000000 --- a/api/docs/schemas/groups/invitation/groupResponseInvitation.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { HandlerDefinition } from '@typing'; - -const schema: HandlerDefinition = { - path: '/groups/invitation/response', - post: { - operationId: 'responseGroupInvitation', - tags: ['groups'], - description: 'Respond to a group invitation', - security: [{ BearerAuth: [] }], - requestBody: { - content: { - 'application/json': { - schema: { - type: 'object', - required: ['groupId', 'response'], - properties: { - groupId: { type: 'string' }, - response: { type: 'string', enum: ['declined', 'member'] }, - }, - example: { - groupId: '0bc1164f-c92b-48f3-aadf-a2be610819d8', - response: 'member', - }, - }, - }, - }, - }, - responses: { - 201: { - description: 'The user membershhip has been updated', - content: { - 'application/json': { - schema: { - type: 'string', - }, - }, - }, - }, - 401: { - description: "User can't respond to this invitation", - content: { - 'application/json': { - schema: { $ref: '#/components/schemas/httpError' }, - }, - }, - }, - 400: { - description: 'The user has already responded', - content: { - 'application/json': { - schema: { $ref: '#/components/schemas/httpError' }, - }, - }, - }, - }, - }, -}; - -export default schema; diff --git a/api/docs/schemas/groups/invitation/groupsSendInvitation.ts b/api/docs/schemas/groups/invitation/groupsSendInvitation.ts deleted file mode 100644 index 9c87f310..00000000 --- a/api/docs/schemas/groups/invitation/groupsSendInvitation.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { HandlerDefinition } from '@typing'; - -const schema: HandlerDefinition = { - path: '/groups/invitation/send', - post: { - operationId: 'sendGroupInvitation', - tags: ['groups'], - description: 'Invite a musician in a group', - security: [{ BearerAuth: [] }], - requestBody: { - content: { - 'application/json': { - schema: { - type: 'object', - required: ['groupId', 'musicianId', 'instrumentId', 'role'], - properties: { - groupId: { type: 'string' }, - musicianId: { type: 'string' }, - instrumentId: { type: 'string' }, - role: { type: 'string', enum: ['lite_admin', 'member'] }, - }, - }, - example: { - groupId: '0bc1164f-c92b-48f3-aadf-a2be610819d8', - musicianId: '8c9a685a-2be9-4cf0-a03c-0b316fc4b515', - instrumentId: 'cd836a31-1663-4a11-8a88-0a249aa70793', - role: 'member', - }, - }, - }, - }, - responses: { - 201: { - description: 'The user has been invited', - content: { - 'application/json': { - schema: { - type: 'string', - }, - }, - }, - }, - 401: { - description: "User that invite doesn't have the access", - content: { - 'application/json': { - schema: { $ref: '#/components/schemas/httpError' }, - }, - }, - }, - 400: { - description: 'The user is already invited', - content: { - 'application/json': { - schema: { $ref: '#/components/schemas/httpError' }, - }, - }, - }, - }, - }, -}; - -export default schema; diff --git a/api/docs/schemas/groups/invitations/accept.ts b/api/docs/schemas/groups/invitations/accept.ts new file mode 100644 index 00000000..a8de9d96 --- /dev/null +++ b/api/docs/schemas/groups/invitations/accept.ts @@ -0,0 +1,65 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/groups/{groupId}/invitations/{invitationId}/accept', + post: { + operationId: 'acceptGroupInvitation', + tags: ['groups'], + security: [{ BearerAuth: [] }], + description: 'Accept an invitation than a group received', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { + type: 'string', + }, + required: true, + }, + { + in: 'path', + name: 'groupId', + schema: { + type: 'string', + }, + required: true, + description: 'The ID of the group', + }, + ], + responses: { + 204: { + description: 'The invitations has been accepted', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + 403: { + description: 'The user does not have the right', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 404: { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/groups/invitations/decline.ts b/api/docs/schemas/groups/invitations/decline.ts new file mode 100644 index 00000000..e30d5436 --- /dev/null +++ b/api/docs/schemas/groups/invitations/decline.ts @@ -0,0 +1,65 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/groups/{groupId}/invitations/{invitationId}/decline', + delete: { + operationId: 'declineGroupInvitation', + tags: ['groups'], + security: [{ BearerAuth: [] }], + description: 'Decline an invitation than a group received', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { + type: 'string', + }, + required: true, + }, + { + in: 'path', + name: 'groupId', + schema: { + type: 'string', + }, + required: true, + description: 'The ID of the group', + }, + ], + responses: { + 204: { + description: 'The invitations has been declined', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + 403: { + description: 'The user does not have the right', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 404: { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/groups/invitations/delete.ts b/api/docs/schemas/groups/invitations/delete.ts new file mode 100644 index 00000000..e5534478 --- /dev/null +++ b/api/docs/schemas/groups/invitations/delete.ts @@ -0,0 +1,65 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/groups/{groupId}/invitations/{invitationId}', + delete: { + operationId: 'deleteGroupInvitationById', + tags: ['groups'], + security: [{ BearerAuth: [] }], + description: 'Delete a musician to group invitation by its id', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { + type: 'string', + }, + required: true, + }, + { + in: 'path', + name: 'groupId', + schema: { + type: 'string', + }, + required: true, + description: 'The ID of the group', + }, + ], + responses: { + 204: { + description: 'The invitations has been deleted', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + 403: { + description: 'The user does not have the right', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 404: { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/groups/invitations/received.ts b/api/docs/schemas/groups/invitations/received.ts new file mode 100644 index 00000000..2cc5aa93 --- /dev/null +++ b/api/docs/schemas/groups/invitations/received.ts @@ -0,0 +1,59 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/groups/{groupId}/invitations/received', + get: { + operationId: 'getGroupInvitationReceived', + tags: ['groups'], + security: [{ BearerAuth: [] }], + description: 'Get all the invitation received for a group', + parameters: [ + { + in: 'path', + name: 'groupId', + schema: { + type: 'string', + }, + required: true, + description: 'The ID of the group', + }, + ], + responses: { + 200: { + description: 'The invitations received by the group', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + $ref: '#/components/schemas/invitation', + }, + }, + }, + }, + }, + 403: { + description: 'The user does not have the right', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 404: { + description: 'The group does not exist', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/groups/invitations/send.ts b/api/docs/schemas/groups/invitations/send.ts new file mode 100644 index 00000000..1aeb4a31 --- /dev/null +++ b/api/docs/schemas/groups/invitations/send.ts @@ -0,0 +1,95 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/groups/{groupId}/invitations', + post: { + operationId: 'postGroupToUserInvitation', + tags: ['groups'], + security: [{ BearerAuth: [] }], + description: 'Post a new invitation from a group to a user', + parameters: [ + { + in: 'path', + name: 'groupId', + schema: { + type: 'string', + }, + required: true, + description: 'The ID of the group', + }, + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['musicianId', 'instruments'], + properties: { + musicianId: { type: 'string' }, + instruments: { + type: 'array', + items: { $ref: '#/components/schemas/instrument' }, + }, + description: { type: 'string', nullable: true }, + }, + }, + }, + }, + }, + responses: { + 201: { + description: 'The invitation has been sent', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 200: { + description: 'The invitation has been updated', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 403: { + description: 'The user does not have the right', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 422: { + description: 'the user is already in the group', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 404: { + description: 'the musician does not exist', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/groups/invitations/sent.ts b/api/docs/schemas/groups/invitations/sent.ts new file mode 100644 index 00000000..51e60213 --- /dev/null +++ b/api/docs/schemas/groups/invitations/sent.ts @@ -0,0 +1,59 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/groups/{groupId}/invitations/sent', + get: { + operationId: 'getGroupInvitationSent', + tags: ['groups'], + security: [{ BearerAuth: [] }], + description: 'Get all the invitation that the group sent to musicians', + parameters: [ + { + in: 'path', + name: 'groupId', + schema: { + type: 'string', + }, + required: true, + description: 'The ID of the group', + }, + ], + responses: { + 200: { + description: 'The invitations sent by the group', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + $ref: '#/components/schemas/invitation', + }, + }, + }, + }, + }, + 403: { + description: 'The user does not have the right', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 404: { + description: 'The group does not exist', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/profil/userGroup/leaveGroup.ts b/api/docs/schemas/profil/group/leaveGroup.ts similarity index 100% rename from api/docs/schemas/profil/userGroup/leaveGroup.ts rename to api/docs/schemas/profil/group/leaveGroup.ts diff --git a/api/docs/schemas/profil/invitation/accept.ts b/api/docs/schemas/profil/invitation/accept.ts new file mode 100644 index 00000000..63a277b7 --- /dev/null +++ b/api/docs/schemas/profil/invitation/accept.ts @@ -0,0 +1,46 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/profil/invitations/{invitationId}/accept', + post: { + operationId: 'acceptProfilInvitation', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Accept an invitation than the logged user received', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { + type: 'string', + }, + required: true, + }, + ], + responses: { + 204: { + description: 'The invitations has been accepted', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + 404: { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/profil/invitation/decline.ts b/api/docs/schemas/profil/invitation/decline.ts new file mode 100644 index 00000000..88f64d6f --- /dev/null +++ b/api/docs/schemas/profil/invitation/decline.ts @@ -0,0 +1,46 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/profil/invitations/{invitationId}/decline', + delete: { + operationId: 'declineProfilInvitation', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Decline an invitation than the logged user received', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { + type: 'string', + }, + required: true, + }, + ], + responses: { + 204: { + description: 'The invitations has been declined', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + 404: { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/profil/invitation/delete.ts b/api/docs/schemas/profil/invitation/delete.ts new file mode 100644 index 00000000..f976bbc2 --- /dev/null +++ b/api/docs/schemas/profil/invitation/delete.ts @@ -0,0 +1,46 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/profil/invitations/{invitationId}', + delete: { + operationId: 'deleteProfilInvitationById', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Delete a musician to group invitation by its id', + parameters: [ + { + in: 'path', + description: 'the invitation id', + name: 'invitationId', + schema: { + type: 'string', + }, + required: true, + }, + ], + responses: { + 204: { + description: 'The invitations hbas been deleted', + content: { + 'application/json': { + schema: { + type: 'string', + }, + }, + }, + }, + 404: { + description: 'the invitation does not exist', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/profil/invitation/received.ts b/api/docs/schemas/profil/invitation/received.ts new file mode 100644 index 00000000..2be482cd --- /dev/null +++ b/api/docs/schemas/profil/invitation/received.ts @@ -0,0 +1,28 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/profil/invitations/received', + get: { + operationId: 'getUserInvitationReceived', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Get all the invitation received by the logged user', + responses: { + 200: { + description: 'The invitations received by the logged user', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + $ref: '#/components/schemas/invitation', + }, + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/profil/invitation/send.ts b/api/docs/schemas/profil/invitation/send.ts new file mode 100644 index 00000000..eb500714 --- /dev/null +++ b/api/docs/schemas/profil/invitation/send.ts @@ -0,0 +1,74 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/profil/invitations', + post: { + operationId: 'postUserToGroupInvitation', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Post a new invitation from the logged user to a group', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + required: ['groupId', 'instruments'], + properties: { + groupId: { type: 'string' }, + instruments: { + type: 'array', + items: { $ref: '#/components/schemas/instrument' }, + }, + description: { type: 'string', nullable: true }, + }, + }, + }, + }, + }, + responses: { + 201: { + description: 'The invitation has been sent', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 200: { + description: 'The invitation has been updated', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 422: { + description: 'the user is already in the group', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + 404: { + description: 'the group does not exist', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/httpError', + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/profil/invitation/sent.ts b/api/docs/schemas/profil/invitation/sent.ts new file mode 100644 index 00000000..c27dc473 --- /dev/null +++ b/api/docs/schemas/profil/invitation/sent.ts @@ -0,0 +1,28 @@ +import { HandlerDefinition } from '@typing'; + +const schema: HandlerDefinition = { + path: '/profil/invitations/sent', + get: { + operationId: 'getUserInvitationSent', + tags: ['profil'], + security: [{ BearerAuth: [] }], + description: 'Get all the invitation sent by the logged user', + responses: { + 200: { + description: 'The invitations sent by the logged user', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + $ref: '#/components/schemas/invitation', + }, + }, + }, + }, + }, + }, + }, +}; + +export default schema; diff --git a/api/docs/schemas/profil/profil.ts b/api/docs/schemas/profil/profil.ts index 0cb17a56..52a094b8 100644 --- a/api/docs/schemas/profil/profil.ts +++ b/api/docs/schemas/profil/profil.ts @@ -31,13 +31,7 @@ const schema: HandlerDefinition = { }, membership: { type: 'string', - enum: [ - 'admin', - 'member', - 'declined', - 'pending', - 'lite_admin', - ], + enum: ['admin', 'member', 'lite_admin'], }, group: { $ref: '#/components/schemas/groupDescription', diff --git a/api/entity/Invitation.ts b/api/entity/Invitation.ts new file mode 100644 index 00000000..c5221cf0 --- /dev/null +++ b/api/entity/Invitation.ts @@ -0,0 +1,52 @@ +import { + Column, + Entity, + JoinTable, + ManyToMany, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { Groups } from './Groups'; +import { Instrument } from './Instrument'; +import { Musician } from './Musician'; + +export type InvitationType = 'musicianToGroup' | 'groupToMusician'; + +@Entity() +export class Invitation { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ + type: 'enum', + enum: ['musicianToGroup', 'groupToMusician'], + }) + type: InvitationType; + + @ManyToOne(() => Groups, { + primary: true, + onDelete: 'CASCADE', + }) + group: Groups; + + @ManyToOne(() => Musician, { + primary: true, + onDelete: 'CASCADE', + }) + musician: Musician; + + @ManyToOne(() => Musician, { + onDelete: 'CASCADE', + nullable: true, + }) + invitor: Musician; + + @ManyToMany(() => Instrument, { + onDelete: 'CASCADE', + }) + @JoinTable() + instruments: Instrument[]; + + @Column('text', { nullable: true }) + description: string; +} diff --git a/api/entity/MusicianGroup.ts b/api/entity/MusicianGroup.ts index 51ee4a8c..bf227f36 100644 --- a/api/entity/MusicianGroup.ts +++ b/api/entity/MusicianGroup.ts @@ -7,12 +7,7 @@ import { Musician } from './Musician'; * - **lite_admin** : Can modify the group and kick members * - **admin** : lite_admin rights && can delete the group */ -export type Membership = - | 'pending' - | 'member' - | 'admin' - | 'lite_admin' - | 'declined'; +export type Membership = 'member' | 'admin' | 'lite_admin'; @Entity() export class MusicianGroup { @@ -30,8 +25,8 @@ export class MusicianGroup { @Column({ type: 'enum', - enum: ['pending', 'member', 'admin', 'declined', 'lite_admin'], - default: 'pending', + enum: ['member', 'admin', 'lite_admin'], + default: 'member', }) membership: Membership; diff --git a/api/entity/index.ts b/api/entity/index.ts index d6e8cbf3..8ac29ccd 100644 --- a/api/entity/index.ts +++ b/api/entity/index.ts @@ -12,3 +12,8 @@ export { GroupKickNotification } from './notifications/group/GroupKickNotificati export { EventDeletedNotification } from './notifications/event/EventDeletedNotification'; export { EventGroupJoin } from './notifications/event/EventGroupJoin'; export { EventGroupKickNotification } from './notifications/event/EventGroupKickNotification'; +export { GroupReceiveInvitationNotification } from './notifications/invitation/GroupReceiveInvitationNotification'; +export { MusicianJoinedGroupNotification } from './notifications/invitation/MusicianJoinedGroupNotification'; +export { GroupDeclineInvitationNotification } from './notifications/invitation/GroupDeclineInvitationNotification'; +export { MusicianDeclineInvitationNotification } from './notifications/invitation/MusicianDeclineInvitationNotification'; +export { Invitation } from './Invitation'; diff --git a/api/entity/notifications/invitation/GroupDeclineInvitationNotification.ts b/api/entity/notifications/invitation/GroupDeclineInvitationNotification.ts new file mode 100644 index 00000000..2cc469be --- /dev/null +++ b/api/entity/notifications/invitation/GroupDeclineInvitationNotification.ts @@ -0,0 +1,16 @@ +import { Groups, Notification } from '../../index'; +import { ChildEntity, ManyToOne } from 'typeorm'; + +/** + * @description This notification is used when a group or a musician + * has receive an invitation + */ + +@ChildEntity() +export class GroupDeclineInvitationNotification extends Notification { + @ManyToOne(() => Groups, { + onDelete: 'CASCADE', + nullable: true, + }) + group: Groups; +} diff --git a/api/entity/notifications/invitation/GroupReceiveInvitationNotification.ts b/api/entity/notifications/invitation/GroupReceiveInvitationNotification.ts new file mode 100644 index 00000000..8ce20046 --- /dev/null +++ b/api/entity/notifications/invitation/GroupReceiveInvitationNotification.ts @@ -0,0 +1,16 @@ +import { Groups, Notification } from '../../index'; +import { ChildEntity, ManyToOne } from 'typeorm'; + +/** + * @description This notification is used when a group or a musician + * has receive an invitation + */ + +@ChildEntity() +export class GroupReceiveInvitationNotification extends Notification { + @ManyToOne(() => Groups, { + onDelete: 'CASCADE', + nullable: true, + }) + group: Groups; +} diff --git a/api/entity/notifications/invitation/MusicianDeclineInvitationNotification.ts b/api/entity/notifications/invitation/MusicianDeclineInvitationNotification.ts new file mode 100644 index 00000000..712ed547 --- /dev/null +++ b/api/entity/notifications/invitation/MusicianDeclineInvitationNotification.ts @@ -0,0 +1,22 @@ +import { Groups, Notification, Musician } from '../../index'; +import { ChildEntity, ManyToOne } from 'typeorm'; + +/** + * @description This notification is used when a group or a musician + * has receive an invitation + */ + +@ChildEntity() +export class MusicianDeclineInvitationNotification extends Notification { + @ManyToOne(() => Groups, { + onDelete: 'CASCADE', + nullable: true, + }) + group: Groups; + + @ManyToOne(() => Musician, { + onDelete: 'CASCADE', + nullable: true, + }) + newMusician: Musician; +} diff --git a/api/entity/notifications/invitation/MusicianJoinedGroupNotification.ts b/api/entity/notifications/invitation/MusicianJoinedGroupNotification.ts new file mode 100644 index 00000000..d28fdaeb --- /dev/null +++ b/api/entity/notifications/invitation/MusicianJoinedGroupNotification.ts @@ -0,0 +1,22 @@ +import { Groups, Musician, Notification } from '../../index'; +import { ChildEntity, ManyToOne } from 'typeorm'; + +/** + * @description This notification is used when a group or a musician + * has receive an invitation + */ + +@ChildEntity() +export class MusicianJoinedGroupNotification extends Notification { + @ManyToOne(() => Musician, { + onDelete: 'CASCADE', + nullable: true, + }) + newMusician: Musician; + + @ManyToOne(() => Groups, { + onDelete: 'CASCADE', + nullable: true, + }) + group: Groups; +} diff --git a/api/routes/groups.ts b/api/routes/groups.ts index 3d933c1a..a09de05f 100644 --- a/api/routes/groups.ts +++ b/api/routes/groups.ts @@ -32,6 +32,26 @@ router.delete( groupController.kickMusicianFromGroup, ); -// router.use('/invitation', invitationRouter); +router.get( + '/:groupId/invitations/received', + groupController.getGroupInvitationsReceived, +); +router.get( + '/:groupId/invitations/sent', + groupController.getGroupInvitationsSent, +); +router.post('/:groupId/invitations', groupController.postGroupToUserInvitation); +router.delete( + '/:groupId/invitations/:invitationId', + groupController.deleteGroupInvitationById, +); +router.post( + '/:groupId/invitations/:invitationId/accept', + groupController.acceptGroupInvitation, +); +router.delete( + '/:groupId/invitations/:invitationId/decline', + groupController.declineGroupInvitation, +); export default router; diff --git a/api/routes/profil.ts b/api/routes/profil.ts index 3c57a8fd..265c34cb 100644 --- a/api/routes/profil.ts +++ b/api/routes/profil.ts @@ -16,4 +16,23 @@ router.delete( profilController.deleteNotificationById, ); +router.get('/invitations/sent', profilController.getUserInvitationsSent); +router.get( + '/invitations/received', + profilController.getUserInvitationsReceived, +); +router.post('/invitations', profilController.postUserToGroupInvitation); +router.delete( + '/invitations/:invitationId', + profilController.deleteInvitationById, +); +router.post( + '/invitations/:invitationId/accept', + profilController.acceptProfilInvitation, +); +router.delete( + '/invitations/:invitationId/decline', + profilController.declineProfilInvitation, +); + export default router; diff --git a/api/server/server.ts b/api/server/server.ts index 68d5c70e..71628fc7 100644 --- a/api/server/server.ts +++ b/api/server/server.ts @@ -124,7 +124,7 @@ app.use( stack: (err as ErrorOpenApi).errors, }); } else { - Logger.error(`500 error : \n${err} \n ${err.stack}`); + Logger.error(`500 error : \n${err.stack}`); res .status(500) .json({ status: 500, message: 'E_UNKNOWN_ERROR', stack: err }); diff --git a/api/types/schema.ts b/api/types/schema.ts index e1dd0137..56d6591c 100644 --- a/api/types/schema.ts +++ b/api/types/schema.ts @@ -76,13 +76,29 @@ export interface paths { /** Create a new group */ post: operations["createGroup"]; }; - "/groups/invitation/response": { - /** Respond to a group invitation */ - post: operations["responseGroupInvitation"]; + "/groups/{groupId}/invitations/{invitationId}/accept": { + /** Accept an invitation than a group received */ + post: operations["acceptGroupInvitation"]; }; - "/groups/invitation/send": { - /** Invite a musician in a group */ - post: operations["sendGroupInvitation"]; + "/groups/{groupId}/invitations/{invitationId}/decline": { + /** Decline an invitation than a group received */ + delete: operations["declineGroupInvitation"]; + }; + "/groups/{groupId}/invitations/{invitationId}": { + /** Delete a musician to group invitation by its id */ + delete: operations["deleteGroupInvitationById"]; + }; + "/groups/{groupId}/invitations/received": { + /** Get all the invitation received for a group */ + get: operations["getGroupInvitationReceived"]; + }; + "/groups/{groupId}/invitations": { + /** Post a new invitation from a group to a user */ + post: operations["postGroupToUserInvitation"]; + }; + "/groups/{groupId}/invitations/sent": { + /** Get all the invitation that the group sent to musicians */ + get: operations["getGroupInvitationSent"]; }; "/groups/{groupId}/kick/{musicianId}": { /** Kick a member from a group */ @@ -106,6 +122,34 @@ export interface paths { "/musicians": { get: operations["getMusicians"]; }; + "/profil/groups/{groupId}/leave": { + /** Leave a group */ + post: operations["leaveGroup"]; + }; + "/profil/invitations/{invitationId}/accept": { + /** Accept an invitation than the logged user received */ + post: operations["acceptProfilInvitation"]; + }; + "/profil/invitations/{invitationId}/decline": { + /** Decline an invitation than the logged user received */ + delete: operations["declineProfilInvitation"]; + }; + "/profil/invitations/{invitationId}": { + /** Delete a musician to group invitation by its id */ + delete: operations["deleteProfilInvitationById"]; + }; + "/profil/invitations/received": { + /** Get all the invitation received by the logged user */ + get: operations["getUserInvitationReceived"]; + }; + "/profil/invitations": { + /** Post a new invitation from the logged user to a group */ + post: operations["postUserToGroupInvitation"]; + }; + "/profil/invitations/sent": { + /** Get all the invitation sent by the logged user */ + get: operations["getUserInvitationSent"]; + }; "/profil/notifications/{notificationId}": { /** Delete a notification by its id */ delete: operations["deleteNotificationById"]; @@ -122,10 +166,6 @@ export interface paths { delete: operations["deleteProfil"]; patch: operations["patchProfil"]; }; - "/profil/groups/{groupId}/leave": { - /** Leave a group */ - post: operations["leaveGroup"]; - }; } export interface components { @@ -177,7 +217,7 @@ export interface components { groupMember: { musician?: components["schemas"]["musicianMinimized"]; instruments?: components["schemas"]["instrument"][]; - membership?: "admin" | "member" | "declined" | "pending" | "lite_admin"; + membership?: "admin" | "member" | "lite_admin"; }; instrument: { id: string; @@ -221,6 +261,15 @@ export interface components { group?: components["schemas"]["groupDescription"]; membership?: "admin" | "member" | "declined" | "pending" | "lite_admin"; }; + invitation: { + id: string; + type: "musicianToGroup" | "groupToMusician"; + group?: components["schemas"]["groupDescription"]; + musician?: components["schemas"]["musicianMinimized"]; + invitor?: components["schemas"]["musicianMinimized"]; + instruments: components["schemas"]["instrument"][]; + description?: string | null; + }; }; } @@ -870,54 +919,163 @@ export interface operations { }; }; }; - /** Respond to a group invitation */ - responseGroupInvitation: { + /** Accept an invitation than a group received */ + acceptGroupInvitation: { + parameters: { + path: { + /** the invitation id */ + invitationId: string; + /** The ID of the group */ + groupId: string; + }; + }; responses: { - /** The user membershhip has been updated */ - 201: { + /** The invitations has been accepted */ + 204: { content: { "application/json": string; }; }; - /** The user has already responded */ - 400: { + /** The user does not have the right */ + 403: { content: { "application/json": components["schemas"]["httpError"]; }; }; - /** User can't respond to this invitation */ - 401: { + /** the invitation does not exist */ + 404: { content: { "application/json": components["schemas"]["httpError"]; }; }; }; - requestBody: { - content: { - "application/json": { - groupId: string; - response: "declined" | "member"; + }; + /** Decline an invitation than a group received */ + declineGroupInvitation: { + parameters: { + path: { + /** the invitation id */ + invitationId: string; + /** The ID of the group */ + groupId: string; + }; + }; + responses: { + /** The invitations has been declined */ + 204: { + content: { + "application/json": string; + }; + }; + /** The user does not have the right */ + 403: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** the invitation does not exist */ + 404: { + content: { + "application/json": components["schemas"]["httpError"]; }; }; }; }; - /** Invite a musician in a group */ - sendGroupInvitation: { + /** Delete a musician to group invitation by its id */ + deleteGroupInvitationById: { + parameters: { + path: { + /** the invitation id */ + invitationId: string; + /** The ID of the group */ + groupId: string; + }; + }; responses: { - /** The user has been invited */ - 201: { + /** The invitations has been deleted */ + 204: { content: { "application/json": string; }; }; - /** The user is already invited */ - 400: { + /** The user does not have the right */ + 403: { content: { "application/json": components["schemas"]["httpError"]; }; }; - /** User that invite doesn't have the access */ - 401: { + /** the invitation does not exist */ + 404: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + }; + }; + /** Get all the invitation received for a group */ + getGroupInvitationReceived: { + parameters: { + path: { + /** The ID of the group */ + groupId: string; + }; + }; + responses: { + /** The invitations received by the group */ + 200: { + content: { + "application/json": components["schemas"]["invitation"][]; + }; + }; + /** The user does not have the right */ + 403: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** The group does not exist */ + 404: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + }; + }; + /** Post a new invitation from a group to a user */ + postGroupToUserInvitation: { + parameters: { + path: { + /** The ID of the group */ + groupId: string; + }; + }; + responses: { + /** The invitation has been updated */ + 200: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** The invitation has been sent */ + 201: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** The user does not have the right */ + 403: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** the musician does not exist */ + 404: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** the user is already in the group */ + 422: { content: { "application/json": components["schemas"]["httpError"]; }; @@ -926,10 +1084,38 @@ export interface operations { requestBody: { content: { "application/json": { - groupId: string; musicianId: string; - instrumentId: string; - role: "lite_admin" | "member"; + instruments: components["schemas"]["instrument"][]; + description?: string | null; + }; + }; + }; + }; + /** Get all the invitation that the group sent to musicians */ + getGroupInvitationSent: { + parameters: { + path: { + /** The ID of the group */ + groupId: string; + }; + }; + responses: { + /** The invitations sent by the group */ + 200: { + content: { + "application/json": components["schemas"]["invitation"][]; + }; + }; + /** The user does not have the right */ + 403: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** The group does not exist */ + 404: { + content: { + "application/json": components["schemas"]["httpError"]; }; }; }; @@ -1077,6 +1263,172 @@ export interface operations { }; }; }; + /** Leave a group */ + leaveGroup: { + parameters: { + path: { + /** The id of the group to leave */ + groupId: string; + }; + }; + responses: { + /** The user have leaved the group */ + 200: { + content: { + "application/json": string; + }; + }; + /** The body is required for an admin leaving an event */ + 400: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** This user is not in this group */ + 404: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + }; + requestBody: { + content: { + "application/json": { + /** @description The id of the musician that will become the new admin of the group, only if it's the admin that is leaving the group */ + musicianId?: string; + }; + }; + }; + }; + /** Accept an invitation than the logged user received */ + acceptProfilInvitation: { + parameters: { + path: { + /** the invitation id */ + invitationId: string; + }; + }; + responses: { + /** The invitations has been accepted */ + 204: { + content: { + "application/json": string; + }; + }; + /** the invitation does not exist */ + 404: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + }; + }; + /** Decline an invitation than the logged user received */ + declineProfilInvitation: { + parameters: { + path: { + /** the invitation id */ + invitationId: string; + }; + }; + responses: { + /** The invitations has been declined */ + 204: { + content: { + "application/json": string; + }; + }; + /** the invitation does not exist */ + 404: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + }; + }; + /** Delete a musician to group invitation by its id */ + deleteProfilInvitationById: { + parameters: { + path: { + /** the invitation id */ + invitationId: string; + }; + }; + responses: { + /** The invitations hbas been deleted */ + 204: { + content: { + "application/json": string; + }; + }; + /** the invitation does not exist */ + 404: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + }; + }; + /** Get all the invitation received by the logged user */ + getUserInvitationReceived: { + responses: { + /** The invitations received by the logged user */ + 200: { + content: { + "application/json": components["schemas"]["invitation"][]; + }; + }; + }; + }; + /** Post a new invitation from the logged user to a group */ + postUserToGroupInvitation: { + responses: { + /** The invitation has been updated */ + 200: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** The invitation has been sent */ + 201: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** the group does not exist */ + 404: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + /** the user is already in the group */ + 422: { + content: { + "application/json": components["schemas"]["httpError"]; + }; + }; + }; + requestBody: { + content: { + "application/json": { + groupId: string; + instruments: components["schemas"]["instrument"][]; + description?: string | null; + }; + }; + }; + }; + /** Get all the invitation sent by the logged user */ + getUserInvitationSent: { + responses: { + /** The invitations sent by the logged user */ + 200: { + content: { + "application/json": components["schemas"]["invitation"][]; + }; + }; + }; + }; /** Delete a notification by its id */ deleteNotificationById: { parameters: { @@ -1131,12 +1483,7 @@ export interface operations { "application/json": components["schemas"]["musician"] & { groups: { instruments?: components["schemas"]["instrument"][]; - membership?: - | "admin" - | "member" - | "declined" - | "pending" - | "lite_admin"; + membership?: "admin" | "member" | "lite_admin"; group?: components["schemas"]["groupDescription"]; }[]; }; @@ -1180,43 +1527,6 @@ export interface operations { }; }; }; - /** Leave a group */ - leaveGroup: { - parameters: { - path: { - /** The id of the group to leave */ - groupId: string; - }; - }; - responses: { - /** The user have leaved the group */ - 200: { - content: { - "application/json": string; - }; - }; - /** The body is required for an admin leaving an event */ - 400: { - content: { - "application/json": components["schemas"]["httpError"]; - }; - }; - /** This user is not in this group */ - 404: { - content: { - "application/json": components["schemas"]["httpError"]; - }; - }; - }; - requestBody: { - content: { - "application/json": { - /** @description The id of the musician that will become the new admin of the group, only if it's the admin that is leaving the group */ - musicianId?: string; - }; - }; - }; - }; } export interface external {} diff --git a/api/postgres.ts b/assets/postgres.ts similarity index 100% rename from api/postgres.ts rename to assets/postgres.ts diff --git a/dockerfile b/dockerfile deleted file mode 100644 index bc38606d..00000000 --- a/dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM node:lts - -ENV NODE_VERSION 16.2.0 - -WORKDIR /app - -COPY ./package.json package-lock.json ./ - -RUN npm install - -COPY . . - -EXPOSE 8000 - -CMD npm start -