Skip to content

Commit

Permalink
Merge pull request #253 from ungdev/dev
Browse files Browse the repository at this point in the history
Merge master
  • Loading branch information
Suboyyy authored Nov 2, 2024
2 parents 3b6fee3 + 5c28a74 commit 6474f52
Show file tree
Hide file tree
Showing 18 changed files with 122 additions and 45 deletions.
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.
30 changes: 11 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 @@ -161,6 +162,7 @@ export default [
firstName: user[0].firstname,
lastName: user[0].lastname,
image: `${env.front.website}/uploads/files/orga/${user[0].orga.photoFilename}.webp`,
firstaid: !!user[0].permissions.includes('firstaid'),
commissionName: getCommissionName(
user[0].orga.roles[mainCommissionIndex].commissionRole,
user[0].orga.roles[mainCommissionIndex].commission.id,
Expand All @@ -173,22 +175,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
7 changes: 6 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export enum Permission {
admin = 'admin',
repo = 'repo',
orga = 'orga',
firstaid = 'firstaid',
}

export { TransactionState, UserAge, UserType, ItemCategory, Log, RepoItemType } from '@prisma/client';
Expand Down Expand Up @@ -236,14 +237,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 +259,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
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',
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

0 comments on commit 6474f52

Please sign in to comment.