2 Commits

Author SHA1 Message Date
Mathis HERRIOT
02796e4e1f chore: bump version to 1.2.1
All checks were successful
CI/CD Pipeline / Valider backend (push) Successful in 1m35s
CI/CD Pipeline / Valider documentation (push) Successful in 2m7s
CI/CD Pipeline / Valider frontend (push) Successful in 2m4s
CI/CD Pipeline / Déploiement en Production (push) Successful in 17s
2026-01-21 11:38:47 +01:00
Mathis HERRIOT
951b38db67 chore: update lint scripts and improve formatting consistency
- Added `lint:fix` scripts for backend, frontend, and documentation.
- Enabled `biome check --write` for unsafe fixes in backend scripts.
- Fixed imports, formatting, and logging for improved code clarity.
- Adjusted service unit tests for better readability and maintainability.
2026-01-21 11:38:25 +01:00
10 changed files with 65 additions and 25 deletions

View File

@@ -24,7 +24,8 @@
"rules": { "rules": {
"recommended": true, "recommended": true,
"suspicious": { "suspicious": {
"noUnknownAtRules": "off" "noUnknownAtRules": "off",
"noExplicitAny": "off"
}, },
"style": { "style": {
"useImportType": "off" "useImportType": "off"

View File

@@ -1,6 +1,6 @@
{ {
"name": "@memegoat/backend", "name": "@memegoat/backend",
"version": "1.2.0", "version": "1.2.1",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -13,7 +13,7 @@
"scripts": { "scripts": {
"build": "nest build", "build": "nest build",
"lint": "biome check", "lint": "biome check",
"lint:write": "biome check --write", "lint:write": "biome check --write --unsafe",
"format": "biome format --write", "format": "biome format --write",
"start": "nest start", "start": "nest start",
"start:dev": "nest start --watch", "start:dev": "nest start --watch",

View File

@@ -24,6 +24,7 @@ import { ConfigService } from "@nestjs/config";
import { Test, TestingModule } from "@nestjs/testing"; import { Test, TestingModule } from "@nestjs/testing";
import { AuthController } from "./auth.controller"; import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service"; import { AuthService } from "./auth.service";
import { BootstrapService } from "./bootstrap.service";
jest.mock("iron-session", () => ({ jest.mock("iron-session", () => ({
getIronSession: jest.fn().mockResolvedValue({ getIronSession: jest.fn().mockResolvedValue({
@@ -44,6 +45,10 @@ describe("AuthController", () => {
refresh: jest.fn(), refresh: jest.fn(),
}; };
const mockBootstrapService = {
consumeToken: jest.fn(),
};
const mockConfigService = { const mockConfigService = {
get: jest get: jest
.fn() .fn()
@@ -55,6 +60,7 @@ describe("AuthController", () => {
controllers: [AuthController], controllers: [AuthController],
providers: [ providers: [
{ provide: AuthService, useValue: mockAuthService }, { provide: AuthService, useValue: mockAuthService },
{ provide: BootstrapService, useValue: mockBootstrapService },
{ provide: ConfigService, useValue: mockConfigService }, { provide: ConfigService, useValue: mockConfigService },
], ],
}).compile(); }).compile();
@@ -75,7 +81,6 @@ describe("AuthController", () => {
password: "password", password: "password",
username: "test", username: "test",
}; };
// biome-ignore lint/suspicious/noExplicitAny: Necessary to avoid defining full DTO in test
await controller.register(dto as any); await controller.register(dto as any);
expect(authService.register).toHaveBeenCalledWith(dto); expect(authService.register).toHaveBeenCalledWith(dto);
}); });

View File

@@ -1,4 +1,13 @@
import { Body, Controller, Get, Headers, Post, Query, Req, Res } from "@nestjs/common"; import {
Body,
Controller,
Get,
Headers,
Post,
Query,
Req,
Res,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config"; import { ConfigService } from "@nestjs/config";
import { Throttle } from "@nestjs/throttler"; import { Throttle } from "@nestjs/throttler";
import type { Request, Response } from "express"; import type { Request, Response } from "express";

View File

@@ -1,14 +1,14 @@
import { Test, TestingModule } from "@nestjs/testing";
import { ConfigService } from "@nestjs/config";
import { UnauthorizedException } from "@nestjs/common"; import { UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Test, TestingModule } from "@nestjs/testing";
import { UsersService } from "../users/users.service";
import { BootstrapService } from "./bootstrap.service"; import { BootstrapService } from "./bootstrap.service";
import { RbacService } from "./rbac.service"; import { RbacService } from "./rbac.service";
import { UsersService } from "../users/users.service";
describe("BootstrapService", () => { describe("BootstrapService", () => {
let service: BootstrapService; let service: BootstrapService;
let rbacService: RbacService; let rbacService: RbacService;
let usersService: UsersService; let _usersService: UsersService;
const mockRbacService = { const mockRbacService = {
countAdmins: jest.fn(), countAdmins: jest.fn(),
@@ -36,7 +36,7 @@ describe("BootstrapService", () => {
service = module.get<BootstrapService>(BootstrapService); service = module.get<BootstrapService>(BootstrapService);
rbacService = module.get<RbacService>(RbacService); rbacService = module.get<RbacService>(RbacService);
usersService = module.get<UsersService>(UsersService); _usersService = module.get<UsersService>(UsersService);
}); });
it("should be defined", () => { it("should be defined", () => {
@@ -46,7 +46,10 @@ describe("BootstrapService", () => {
describe("onApplicationBootstrap", () => { describe("onApplicationBootstrap", () => {
it("should generate a token if no admin exists", async () => { it("should generate a token if no admin exists", async () => {
mockRbacService.countAdmins.mockResolvedValue(0); mockRbacService.countAdmins.mockResolvedValue(0);
const generateTokenSpy = jest.spyOn(service as any, "generateBootstrapToken"); const generateTokenSpy = jest.spyOn(
service as any,
"generateBootstrapToken",
);
await service.onApplicationBootstrap(); await service.onApplicationBootstrap();
@@ -56,7 +59,10 @@ describe("BootstrapService", () => {
it("should not generate a token if admin exists", async () => { it("should not generate a token if admin exists", async () => {
mockRbacService.countAdmins.mockResolvedValue(1); mockRbacService.countAdmins.mockResolvedValue(1);
const generateTokenSpy = jest.spyOn(service as any, "generateBootstrapToken"); const generateTokenSpy = jest.spyOn(
service as any,
"generateBootstrapToken",
);
await service.onApplicationBootstrap(); await service.onApplicationBootstrap();
@@ -70,9 +76,9 @@ describe("BootstrapService", () => {
mockRbacService.countAdmins.mockResolvedValue(0); mockRbacService.countAdmins.mockResolvedValue(0);
await service.onApplicationBootstrap(); await service.onApplicationBootstrap();
await expect( await expect(service.consumeToken("wrong-token", "user1")).rejects.toThrow(
service.consumeToken("wrong-token", "user1"), UnauthorizedException,
).rejects.toThrow(UnauthorizedException); );
}); });
it("should throw UnauthorizedException if user not found", async () => { it("should throw UnauthorizedException if user not found", async () => {
@@ -97,7 +103,10 @@ describe("BootstrapService", () => {
const result = await service.consumeToken(token, "user1"); const result = await service.consumeToken(token, "user1");
expect(rbacService.assignRoleToUser).toHaveBeenCalledWith("user-uuid", "admin"); expect(rbacService.assignRoleToUser).toHaveBeenCalledWith(
"user-uuid",
"admin",
);
expect((service as any).bootstrapToken).toBeNull(); expect((service as any).bootstrapToken).toBeNull();
expect(result.message).toContain("user1 is now an administrator"); expect(result.message).toContain("user1 is now an administrator");
}); });

View File

@@ -1,3 +1,4 @@
import * as crypto from "node:crypto";
import { import {
Injectable, Injectable,
Logger, Logger,
@@ -5,7 +6,6 @@ import {
UnauthorizedException, UnauthorizedException,
} from "@nestjs/common"; } from "@nestjs/common";
import { ConfigService } from "@nestjs/config"; import { ConfigService } from "@nestjs/config";
import * as crypto from "node:crypto";
import { UsersService } from "../users/users.service"; import { UsersService } from "../users/users.service";
import { RbacService } from "./rbac.service"; import { RbacService } from "./rbac.service";
@@ -34,9 +34,15 @@ export class BootstrapService implements OnApplicationBootstrap {
const url = `${protocol}://${domain}/auth/bootstrap-admin`; const url = `${protocol}://${domain}/auth/bootstrap-admin`;
this.logger.warn("SECURITY ALERT: No administrator found in database."); this.logger.warn("SECURITY ALERT: No administrator found in database.");
this.logger.warn("To create the first administrator, use the following endpoint:"); this.logger.warn(
this.logger.warn(`Endpoint: GET ${url}?token=${this.bootstrapToken}&username=votre_nom_utilisateur`); "To create the first administrator, use the following endpoint:",
this.logger.warn("Exemple: curl -X GET \"http://localhost/auth/bootstrap-admin?token=...&username=...\""); );
this.logger.warn(
`Endpoint: GET ${url}?token=${this.bootstrapToken}&username=votre_nom_utilisateur`,
);
this.logger.warn(
'Exemple: curl -X GET "http://localhost/auth/bootstrap-admin?token=...&username=..."',
);
this.logger.warn("This token is one-time use only."); this.logger.warn("This token is one-time use only.");
} }
@@ -53,7 +59,9 @@ export class BootstrapService implements OnApplicationBootstrap {
await this.rbacService.assignRoleToUser(user.uuid, "admin"); await this.rbacService.assignRoleToUser(user.uuid, "admin");
this.bootstrapToken = null; // One-time use this.bootstrapToken = null; // One-time use
this.logger.log(`User ${username} has been promoted to administrator via bootstrap token.`); this.logger.log(
`User ${username} has been promoted to administrator via bootstrap token.`,
);
return { message: `User ${username} is now an administrator` }; return { message: `User ${username} is now an administrator` };
} }
} }

View File

@@ -18,7 +18,11 @@ export class RbacService implements OnApplicationBootstrap {
if (count === 0) { if (count === 0) {
this.logger.log("No roles found, seeding default roles..."); this.logger.log("No roles found, seeding default roles...");
const defaultRoles = [ const defaultRoles = [
{ name: "Administrator", slug: "admin", description: "Full system access" }, {
name: "Administrator",
slug: "admin",
description: "Full system access",
},
{ {
name: "Moderator", name: "Moderator",
slug: "moderator", slug: "moderator",

View File

@@ -8,7 +8,6 @@ jest.mock("minio");
describe("S3Service", () => { describe("S3Service", () => {
let service: S3Service; let service: S3Service;
let configService: ConfigService; let configService: ConfigService;
// biome-ignore lint/suspicious/noExplicitAny: Fine for testing purposes
let minioClient: any; let minioClient: any;
beforeEach(async () => { beforeEach(async () => {

View File

@@ -1,12 +1,13 @@
{ {
"name": "@memegoat/frontend", "name": "@memegoat/frontend",
"version": "1.2.0", "version": "1.2.1",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "biome check", "lint": "biome check",
"lint:write": "biome check --write",
"format": "biome format --write" "format": "biome format --write"
}, },
"dependencies": { "dependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@memegoat/source", "name": "@memegoat/source",
"version": "1.2.0", "version": "1.2.1",
"description": "", "description": "",
"scripts": { "scripts": {
"version:get": "cmake -P version.cmake GET", "version:get": "cmake -P version.cmake GET",
@@ -13,9 +13,13 @@
"build:back": "pnpm run -F @memegoat/backend build", "build:back": "pnpm run -F @memegoat/backend build",
"build:docs": "pnpm run -F @memegoat/documentation build", "build:docs": "pnpm run -F @memegoat/documentation build",
"lint": "pnpm run lint:back && pnpm run lint:front && pnpm run lint:docs", "lint": "pnpm run lint:back && pnpm run lint:front && pnpm run lint:docs",
"lint:fix": "pnpm run lint:back:fix && pnpm run lint:front:fix && pnpm run lint:docs:fix",
"lint:back": "pnpm run -F @memegoat/backend lint", "lint:back": "pnpm run -F @memegoat/backend lint",
"lint:back:fix": "pnpm run -F @memegoat/backend lint:write",
"lint:front": "pnpm run -F @memegoat/frontend lint", "lint:front": "pnpm run -F @memegoat/frontend lint",
"lint:front:fix": "pnpm run -F @memegoat/frontend lint:write",
"lint:docs": "pnpm run -F @memegoat/documentation lint", "lint:docs": "pnpm run -F @memegoat/documentation lint",
"lint:docs:fix": "pnpm run -F @memegoat/documentation lint:write",
"test": "pnpm run test:back && pnpm run test:front", "test": "pnpm run test:back && pnpm run test:front",
"test:back": "pnpm run -F @memegoat/backend test", "test:back": "pnpm run -F @memegoat/backend test",
"test:front": "pnpm run -F @memegoat/frontend test", "test:front": "pnpm run -F @memegoat/frontend test",