Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge master #253

Merged
merged 14 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,4 @@ DISCORD_WEBHOOK_CONTACT=

# Some more random variables
API_RATE_LIMIT=12
API_CART_LIFESPAN=3600
API_CART_LIFESPAN=3600
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ pnpm-error.log

openapi.json

/docs/build
/docs/build

/assets/badges
Binary file removed assets/badges/back-fullaccess.png
Binary file not shown.
Binary file removed assets/badges/back-orgaprice.png
Binary file not shown.
Binary file removed assets/badges/back-restricted.png
Binary file not shown.
Binary file removed assets/badges/badge-fullaccess.png
Binary file not shown.
Binary file removed assets/badges/badge-orgaprice.png
Binary file not shown.
Binary file removed assets/badges/badge-restricted.png
Binary file not shown.
Binary file added assets/defaultbadge/blank.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
29 changes: 10 additions & 19 deletions src/controllers/admin/badges/generateBadges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { hasPermission } from '../../../middlewares/authentication';
const getCommisionPermission = (commissionRole: string, commissionId: string) => {
switch (commissionId) {
case 'vieux': {
return 'restricted';
return 'invite';
}

case 'coord': {
Expand All @@ -33,10 +33,6 @@ const getCommisionPermission = (commissionRole: string, commissionId: string) =>
if (commissionRole === 'respo') return 'fullaccess';
}

case 'ssl': {
if (commissionRole === 'respo') return 'fullaccess';
}

default: {
return 'orgaprice';
}
Expand Down Expand Up @@ -73,6 +69,9 @@ export default [
case 'orgas': {
await database.user
.findMany({
orderBy: {
firstname: 'desc',
},
where: {
permissions: { contains: 'orga' },
},
Expand Down Expand Up @@ -108,6 +107,8 @@ export default [
user.orga.roles[mainCommissionIndex].commission.id,
user.orga.roles[mainCommissionIndex].commission.nameOnBadge,
),
place: user.place,
firstaid: !!user.permissions.includes('firstaid'),
});
}
});
Expand Down Expand Up @@ -173,22 +174,12 @@ export default [

case 'singlecustom': {
listBadgeToGenerate.push({
type: getCommisionPermission(field.commissionRole ?? 'member', field.commissionId ?? 'vieux'),
type: field.permission,
firstName: field.firstname ?? '',
lastName: field.lastname ?? '',
image: '',
commissionName: await database.commission
.findUnique({
where: { id: field.commissionId ?? 'vieux' },
select: { nameOnBadge: true },
})
.then((commission) =>
getCommissionName(
field.commissionRole ?? 'member',
field.commissionId ?? 'vieux',
commission.nameOnBadge,
),
),
image: field.image ?? '',
commissionName: field.commissionId,
place: field.place,
});
break;
}
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/admin/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
description: Le nombre de badges à créer
permission:
type: string
enum: [restricted, orgaprice, fullaccess]
enum: [restricted, orgaprice, fullaccess, invite]
description: La permission associée au badge
email:
type: string
Expand Down
1 change: 1 addition & 0 deletions src/operations/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ export const fetchUsers = async (
{ username: { contains: query.search } },
{ email: { contains: query.search } },
{ id: { contains: query.search } },
{ discordId: { contains: query.search } },
{
team: {
name: { contains: query.search },
Expand Down
6 changes: 5 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,16 @@ export type Tournament = PrimitiveTournament & {
/************/

export type BadgeType = 'orgas' | 'custom' | 'single' | 'singlecustom';
export type BadgePermission = 'restricted' | 'orgaprice' | 'fullaccess';
export type BadgePermission = 'restricted' | 'orgaprice' | 'fullaccess' | 'invite';

export interface Badge {
type: BadgePermission;
firstName: string;
lastName: string;
image: string;
commissionName: string;
place?: string;
firstaid?: boolean;
}

export interface BadgeField {
Expand All @@ -256,6 +258,8 @@ export interface BadgeField {
firstname?: string;
lastname?: string;
name?: string;
image?: string;
place?: string;
}

/**********/
Expand Down
72 changes: 55 additions & 17 deletions src/utils/badge.ts
Suboyyy marked this conversation as resolved.
Show resolved Hide resolved
Suboyyy marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,27 @@ import { readFileSync } from 'fs';
import PDFkit from 'pdfkit';
import sharp from 'sharp';
import { Badge } from '../types';
import env from './env';

const loadImageBadgeRestricted = () =>
`data:image/png;base64,${readFileSync(`assets/badges/badge-restricted.png`, 'base64')}`;
const loadImageBadgeOrgaPrice = () =>
`data:image/png;base64,${readFileSync(`assets/badges/badge-orgaprice.png`, 'base64')}`;
const loadImageBadgeFullAccess = () =>
`data:image/png;base64,${readFileSync(`assets/badges/badge-fullaccess.png`, 'base64')}`;
const getImage = (filename: string) => {
try {
return `data:image/png;base64,${readFileSync(`assets/badges/${filename}`, 'base64')}`;
} catch {
return `data:image/png;base64,${readFileSync(`assets/defaultbadge/blank.png`, 'base64')}`;
}
};

const loadImageBadgeRestricted = () => getImage('badge-restricted.png');
const loadImageBadgeOrgaPrice = () => getImage('badge-orgaprice.png');
const loadImageBadgeFullAccess = () => getImage('badge-fullaccess.png');
const loadImageBadgeInvite = () => getImage('badge-invite.png');

const loadBackRestricted = () => `data:image/png;base64,${readFileSync(`assets/badges/back-restricted.png`, 'base64')}`;
const loadBackOrgaPrice = () => `data:image/png;base64,${readFileSync(`assets/badges/back-orgaprice.png`, 'base64')}`;
const loadBackFullAccess = () => `data:image/png;base64,${readFileSync(`assets/badges/back-fullaccess.png`, 'base64')}`;
const loadBackRestricted = () => getImage('back-restricted.png');
const loadBackOrgaPrice = () => getImage('back-orgaprice.png');
const loadBackFullAccess = () => getImage('back-fullaccess.png');
const loadBackInvite = () => getImage('back-invite.png');

type BadgePermission = 'restricted' | 'orgaprice' | 'fullaccess';
type BadgePermission = 'restricted' | 'orgaprice' | 'fullaccess' | 'invite';

const getBack = (permission: BadgePermission): string => {
switch (permission) {
Expand All @@ -31,6 +39,10 @@ const getBack = (permission: BadgePermission): string => {
return loadBackFullAccess();
}

case 'invite': {
return loadBackInvite();
}

default: {
return loadBackRestricted();
}
Expand All @@ -51,6 +63,10 @@ const getBadge = (permission: BadgePermission): string => {
return loadImageBadgeFullAccess();
}

case 'invite': {
return loadImageBadgeInvite();
}

default: {
return loadImageBadgeRestricted();
}
Expand Down Expand Up @@ -114,6 +130,7 @@ export const generateBadge = async (badges: Badge[]) => {

// Informations about badge
const image = await fetchImage(badges[index].image);
const firstaid = await fetchImage(`${env.front.website}/uploads/files/badges/first-aid.png`);
// Coordonates
const x = pictureX + col * columnOffset;
const y = pictureY + row * rowOffset;
Expand All @@ -127,21 +144,28 @@ export const generateBadge = async (badges: Badge[]) => {
}

// Background
document.image(getBadge(badges[index].type), x, y, { width: pictureSize }); // After the image because of... 42
document.image(await getBadge(badges[index].type), x, y, { width: pictureSize }); // After the image because of... 42

// FirstAid
if (badges[index].firstaid) {
document.image(firstaid, x + 60, y + 216, { width: pictureSize - 120 });
}
}
}

// Place the text containing the name is the bottom middle in bold and in uppercase
// Define a text format
const textFormat = document.font(fontFamily).fill([239, 220, 235]).fontSize(fontSize);

// 'for' because I dont like to repeat but I like potatoes and pain au chocolat
for (let col = 0; col < columns; col++) {
for (let row = 0; row < rows; row++) {
const index = page * columns * rows + col * rows + row;

if (index >= badges.length) break;

// Place the text containing the name is the bottom middle in bold and in uppercase
// Define a text format
const color: PDFKit.Mixins.ColorValue = badges[index].type === 'fullaccess' ? [239, 220, 235] : [23, 18, 74];

const textFormat = document.font(fontFamily).fill(color).fontSize(fontSize);

// Informations about badge
const lastName = `${badges[index].lastName || ' '}`;
const firstName = `${badges[index].firstName || ' '}`;
Expand All @@ -153,7 +177,7 @@ export const generateBadge = async (badges: Badge[]) => {
textFormat.text(
lastName.toUpperCase(),
offsetX - textFormat.widthOfString(lastName.toUpperCase()) / 2,
offsetY - 277 - lastNameHeight / 2,
offsetY - 282 - lastNameHeight / 2,
);
// Firstname
const firstNameHeight = textFormat.heightOfString(firstName);
Expand Down Expand Up @@ -187,7 +211,21 @@ export const generateBadge = async (badges: Badge[]) => {
const y = pictureY + row * rowOffset;

// Background
document.image(getBack(badges[index].type), x, y, { width: pictureSize }); // After the image because of... 42
document.image(await getBack(badges[index].type), x, y, { width: pictureSize }); // After the image because of... 42

const color: PDFKit.Mixins.ColorValue = [23, 18, 74];
const textFormat = document.font(fontFamily).fill(color).fontSize(fontSize);

// Offsets
const offsetX = textX + (3 - col) * columnOffset;
const offsetY = textY + row * rowOffset;

// Place
const place = badges[index].place
? `${badges[index].place}`
: `Z${(index + 501).toString().padStart(3, '0')}`;
const placeHeight = textFormat.heightOfString(place);
textFormat.text(place, offsetX - 75, offsetY - 231 - placeHeight / 2);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/utils/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import crypto from 'crypto';
import dotenv, { DotenvPopulateInput } from 'dotenv';

if (process.env.NODE_ENV === 'test') {
// Make sure to only load the 3 accepted variables in test
// Make sure to only load the accepted variables in test
const environmentVariables: DotenvPopulateInput = {};
dotenv.config({ path: '.env.test', processEnv: environmentVariables });
process.env.DATABASE_URL = environmentVariables.DATABASE_URL ?? process.env.DATABASE_URL;
Expand Down
4 changes: 2 additions & 2 deletions src/utils/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { UserAge, UserType, Permission, Error as ResponseError } from '../types'

// Matches with LoL EUW summoner name
const usernameRegex = /^[0-9\p{L} _#-]{3,22}$/u;
const nameRegex = /^[\p{L}\d _'#-]{1,100}$/u;
const lastnameRegex = /^[\p{L} _'-]{1,100}$/u;
const nameRegex = /^[\p{L}\d _'#-]{3,30}$/u;
const lastnameRegex = /^[\p{L} _'-]{1,50}$/u;
const passwordRegex = /^.{6,100}$/;
const placeRegex = /^[A-Z]\d{1,3}$/;

Expand Down
5 changes: 3 additions & 2 deletions tests/admin/badges/generateBadges.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,10 +208,11 @@ describe('POST /admin/badges', () => {
fields: [
{
type: 'singlecustom',
commissionRole: 'member',
commissionId: 'vieux',
commissionId: 'dev / annimation',
Suboyyy marked this conversation as resolved.
Show resolved Hide resolved
permission: 'orgaprice',
firstname: 'John',
lastname: 'Doe',
firstaid: true,
},
],
})
Expand Down
38 changes: 38 additions & 0 deletions tests/users/createCart.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,24 @@ describe('POST /users/current/carts', () => {
.expect(404, { error: Error.ItemNotFound });
});

it('should fail as the user is not a player or a coach or a spectator', async () => {
const attendantUser = await createFakeUser({ type: UserType.attendant });

await request(app)
.post(`/users/current/carts`)
.set('Authorization', `Bearer ${token}`)
.send({
tickets: { userIds: [attendantUser.id] },
supplements: [],
})
.expect(403, { error: Error.NotPlayerOrCoachOrSpectator });

// Delete the user to not make the results wrong for the success test
await database.cartItem.deleteMany({ where: { forUserId: attendantUser.id } });
await database.cart.deleteMany({ where: { userId: attendantUser.id } });
await database.user.delete({ where: { id: attendantUser.id } });
});

it('should fail as the user is already paid', async () => {
const paidUser = await createFakeUser({ paid: true, type: UserType.player });

Expand All @@ -327,6 +345,26 @@ describe('POST /users/current/carts', () => {
await database.user.delete({ where: { id: paidUser.id } });
});

it('should fail as the user is not in the same team', async () => {
const otherTeam = await createFakeTeam({ members: 1, tournament: 'ssbu', name: 'reallydontcare' });
const userInOtherTeam = getCaptain(otherTeam);

await request(app)
.post(`/users/current/carts`)
.set('Authorization', `Bearer ${token}`)
.send({
tickets: { userIds: [userInOtherTeam.id] },
supplements: [],
})
.expect(403, { error: Error.NotInSameTeam });

// Delete the user to not make the results wrong for the success test
await database.cartItem.deleteMany({ where: { forUserId: userInOtherTeam.id } });
await database.cart.deleteMany({ where: { userId: userInOtherTeam.id } });
await database.team.delete({ where: { captainId: userInOtherTeam.id } });
await database.user.delete({ where: { id: userInOtherTeam.id } });
});

it('should fail with an internal server error (inner try/catch)', () => {
sandbox.stub(cartOperations, 'createCart').throws('Unexpected error');

Expand Down
Loading