Implement authentication, organization management, and floor layout features

- Add API authentication with JWT strategy using NestJS.
- Configure Prisma schema for organizations, users, buildings, floors, and spaces.
- Seed initial database with test data and relationships.
- Build SvelteKit web pages for dashboard and floor layout with secured routing and organization hierarchy display.
- Remove deprecated `AppService` from API.
main
Pau Costa Ferrer 2026-03-28 00:36:05 +01:00
parent 398752ad4d
commit 8dc99bc1a4
30 changed files with 1242 additions and 160 deletions

1
.gitignore vendored
View File

@ -13,3 +13,4 @@ yarn-error.log*
.idea/* .idea/*
**/.idea **/.idea
**/.svelte-kit **/.svelte-kit
**/dist

View File

@ -4,14 +4,22 @@
<option name="autoReloadType" value="SELECTIVE" /> <option name="autoReloadType" value="SELECTIVE" />
</component> </component>
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="6c8b3c54-eabb-40e5-967f-c7b594c750bc" name="Changes" comment=""> <list default="true" id="6c8b3c54-eabb-40e5-967f-c7b594c750bc" name="Changes" comment="Remove compiled API files from version control&#10;&#10;- Deleted compiled JavaScript files, type definitions, and source maps from the `dist` folder.">
<change afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/.gitignore" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/.gitignore" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/apps/api/dist/app.service.d.ts" beforeDir="false" />
<change afterPath="$PROJECT_DIR$/package.json" afterDir="false" /> <change beforePath="$PROJECT_DIR$/apps/api/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/package.json" afterDir="false" />
<change afterPath="$PROJECT_DIR$/prompts.md" afterDir="false" /> <change beforePath="$PROJECT_DIR$/apps/api/prisma/schema.prisma" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/prisma/schema.prisma" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/index.ts" afterDir="false" /> <change beforePath="$PROJECT_DIR$/apps/api/src/app.module.ts" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/src/app.module.ts" afterDir="false" />
<change afterPath="$PROJECT_DIR$/tsconfig.json" afterDir="false" /> <change beforePath="$PROJECT_DIR$/apps/api/src/main.ts" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/src/main.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/api/src/prisma/prisma.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/src/prisma/prisma.service.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/api/src/spaces/spaces.controller.ts" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/src/spaces/spaces.controller.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/api/src/spaces/spaces.service.ts" beforeDir="false" afterPath="$PROJECT_DIR$/apps/api/src/spaces/spaces.service.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/web/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/apps/web/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/web/src/routes/+layout.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/apps/web/src/routes/+layout.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/web/src/routes/+page.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/apps/web/src/routes/+page.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/apps/web/src/routes/login/+page.svelte" beforeDir="false" afterPath="$PROJECT_DIR$/apps/web/src/routes/login/+page.svelte" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -66,7 +74,7 @@
"vue.rearranger.settings.migration": "true" "vue.rearranger.settings.migration": "true"
} }
}]]></component> }]]></component>
<component name="RunManager" selected="Docker.docker-compose.yml: Compose Deployment"> <component name="RunManager" selected="npm.web &gt; dev">
<configuration default="true" type="docker-deploy" factoryName="docker-compose.yml" temporary="true"> <configuration default="true" type="docker-deploy" factoryName="docker-compose.yml" temporary="true">
<deployment type="docker-compose.yml" /> <deployment type="docker-compose.yml" />
<method v="2" /> <method v="2" />
@ -101,9 +109,9 @@
</configuration> </configuration>
<recent_temporary> <recent_temporary>
<list> <list>
<item itemvalue="npm.web &gt; dev" />
<item itemvalue="npm.api &gt; start:dev" /> <item itemvalue="npm.api &gt; start:dev" />
<item itemvalue="Docker.docker-compose.yml: Compose Deployment" /> <item itemvalue="Docker.docker-compose.yml: Compose Deployment" />
<item itemvalue="npm.web &gt; dev" />
</list> </list>
</recent_temporary> </recent_temporary>
</component> </component>
@ -122,11 +130,33 @@
<option name="presentableId" value="Default" /> <option name="presentableId" value="Default" />
<updated>1774643312599</updated> <updated>1774643312599</updated>
<workItem from="1774643313611" duration="74000" /> <workItem from="1774643313611" duration="74000" />
<workItem from="1774643422240" duration="2753000" /> <workItem from="1774643422240" duration="4582000" />
</task> </task>
<task id="LOCAL-00001" summary="Initialize Hot Desking app with web and API services &#10;&#10;- Set up SvelteKit for the web interface with Tailwind CSS. &#10;- Build the API using NestJS with Prisma ORM for database interaction. &#10;- Add environment variable management and docker-compose for PostgreSQL and PgAdmin instances. &#10;- Create shared data models for consistent typing across services. &#10;- Establish basic routing and layouts for web and API services.">
<option name="closed" value="true" />
<created>1774652817905</created>
<option name="number" value="00001" />
<option name="presentableId" value="LOCAL-00001" />
<option name="project" value="LOCAL" />
<updated>1774652817905</updated>
</task>
<task id="LOCAL-00002" summary="Remove compiled API files from version control&#10;&#10;- Deleted compiled JavaScript files, type definitions, and source maps from the `dist` folder.">
<option name="closed" value="true" />
<created>1774653909300</created>
<option name="number" value="00002" />
<option name="presentableId" value="LOCAL-00002" />
<option name="project" value="LOCAL" />
<updated>1774653909300</updated>
</task>
<option name="localTasksCounter" value="3" />
<servers /> <servers />
</component> </component>
<component name="TypeScriptGeneratedFilesManager"> <component name="TypeScriptGeneratedFilesManager">
<option name="version" value="3" /> <option name="version" value="3" />
</component> </component>
<component name="VcsManagerConfiguration">
<MESSAGE value="Initialize Hot Desking app with web and API services &#10;&#10;- Set up SvelteKit for the web interface with Tailwind CSS. &#10;- Build the API using NestJS with Prisma ORM for database interaction. &#10;- Add environment variable management and docker-compose for PostgreSQL and PgAdmin instances. &#10;- Create shared data models for consistent typing across services. &#10;- Establish basic routing and layouts for web and API services." />
<MESSAGE value="Remove compiled API files from version control&#10;&#10;- Deleted compiled JavaScript files, type definitions, and source maps from the `dist` folder." />
<option name="LAST_COMMIT_MESSAGE" value="Remove compiled API files from version control&#10;&#10;- Deleted compiled JavaScript files, type definitions, and source maps from the `dist` folder." />
</component>
</project> </project>

View File

@ -0,0 +1,9 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}

View File

@ -1,3 +0,0 @@
export declare class AppService {
getHello(): string;
}

View File

@ -21,11 +21,19 @@
}, },
"dependencies": { "dependencies": {
"@nestjs/common": "^10.4.22", "@nestjs/common": "^10.4.22",
"@nestjs/config": "^4.0.3",
"@nestjs/core": "^10.4.22", "@nestjs/core": "^10.4.22",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^10.4.22", "@nestjs/platform-express": "^10.4.22",
"@prisma/client": "^6.4.0", "@prisma/client": "^6.4.0",
"@types/bcrypt": "^6.0.0",
"@types/passport-jwt": "^4.0.1",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.15.1", "class-validator": "^0.15.1",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"prisma": "^6.4.0", "prisma": "^6.4.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2" "rxjs": "^7.8.2"
@ -71,5 +79,8 @@
], ],
"coverageDirectory": "../coverage", "coverageDirectory": "../coverage",
"testEnvironment": "node" "testEnvironment": "node"
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
} }
} }

View File

@ -0,0 +1,79 @@
-- CreateEnum
CREATE TYPE "Role" AS ENUM ('ADMIN', 'SUPERVISOR', 'EMPLOYEE');
-- CreateEnum
CREATE TYPE "SpaceType" AS ENUM ('DESK', 'MEETING_ROOM', 'AMENITY');
-- CreateTable
CREATE TABLE "Organization" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Organization_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Building" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Building_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Floor" (
"id" TEXT NOT NULL,
"number" INTEGER NOT NULL,
"buildingId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Floor_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Space" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"type" "SpaceType" NOT NULL,
"floorId" TEXT NOT NULL,
"x" DOUBLE PRECISION,
"y" DOUBLE PRECISION,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Space_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL,
"role" "Role" NOT NULL DEFAULT 'EMPLOYEE',
"organizationId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- AddForeignKey
ALTER TABLE "Building" ADD CONSTRAINT "Building_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Floor" ADD CONSTRAINT "Floor_buildingId_fkey" FOREIGN KEY ("buildingId") REFERENCES "Building"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Space" ADD CONSTRAINT "Space_floorId_fkey" FOREIGN KEY ("floorId") REFERENCES "Floor"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "User" ADD CONSTRAINT "User_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

View File

@ -7,3 +7,67 @@ datasource db {
provider = "postgresql" provider = "postgresql"
url = env("DATABASE_URL") url = env("DATABASE_URL")
} }
enum Role {
ADMIN
SUPERVISOR
EMPLOYEE
}
enum SpaceType {
DESK
MEETING_ROOM
AMENITY
}
model Organization {
id String @id @default(uuid())
name String
buildings Building[]
users User[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Building {
id String @id @default(uuid())
name String
organization Organization @relation(fields: [organizationId], references: [id])
organizationId String
floors Floor[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Floor {
id String @id @default(uuid())
number Int
building Building @relation(fields: [buildingId], references: [id])
buildingId String
spaces Space[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Space {
id String @id @default(uuid())
name String
type SpaceType
floor Floor @relation(fields: [floorId], references: [id])
floorId String
x Float? // Percentage-based for responsiveness (Stage 3)
y Float?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model User {
id String @id @default(uuid())
email String @unique
password String
role Role @default(EMPLOYEE)
organization Organization @relation(fields: [organizationId], references: [id])
organizationId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

69
apps/api/prisma/seed.ts Normal file
View File

@ -0,0 +1,69 @@
import { PrismaClient, Role, SpaceType } from '@prisma/client';
import * as bcrypt from 'bcrypt';
const prisma = new PrismaClient();
async function main() {
const hashedPassword = await bcrypt.hash('admin123', 10);
const org = await prisma.organization.upsert({
where: { id: 'test-org-id' },
update: {},
create: {
id: 'test-org-id',
name: 'Test Organization',
},
});
const admin = await prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
password: hashedPassword,
role: Role.ADMIN,
organizationId: org.id,
},
});
const building = await prisma.building.create({
data: {
name: 'Headquarters',
organizationId: org.id,
floors: {
create: [
{
number: 1,
spaces: {
create: [
{ name: 'Desk 101', type: SpaceType.DESK, x: 10, y: 10 },
{ name: 'Meeting Room A', type: SpaceType.MEETING_ROOM, x: 50, y: 50 },
{ name: 'Kitchen', type: SpaceType.AMENITY, x: 80, y: 80 },
],
},
},
{
number: 2,
spaces: {
create: [
{ name: 'Desk 201', type: SpaceType.DESK, x: 20, y: 20 },
{ name: 'Boardroom', type: SpaceType.MEETING_ROOM, x: 60, y: 60 },
],
},
},
],
},
},
});
console.log({ org, admin, building });
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

View File

@ -1,11 +1,18 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { PrismaModule } from './prisma/prisma.module'; import { PrismaModule } from './prisma/prisma.module';
import { SpacesModule } from './spaces/spaces.module'; import { SpacesModule } from './spaces/spaces.module';
import { AuthModule } from './auth/auth.module';
@Module({ @Module({
imports: [PrismaModule, SpacesModule], imports: [
ConfigModule.forRoot({ isGlobal: true }),
PrismaModule,
SpacesModule,
AuthModule,
],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],
}) })

View File

@ -0,0 +1,13 @@
import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('login')
async login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto);
}
}

View File

@ -0,0 +1,25 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './strategies/jwt.strategy';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
PassportModule,
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
secret: config.get('JWT_SECRET') || 'secret',
signOptions: { expiresIn: '1d' },
}),
}),
],
providers: [AuthService, JwtStrategy],
controllers: [AuthController],
exports: [AuthService],
})
export class AuthModule {}

View File

@ -0,0 +1,38 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma/prisma.service';
import * as bcrypt from 'bcrypt';
import { LoginDto } from './dto/login.dto';
@Injectable()
export class AuthService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService,
) {}
async login(loginDto: LoginDto) {
const user = await this.prisma.user.findUnique({
where: { email: loginDto.email },
});
if (user && (await bcrypt.compare(loginDto.password, user.password))) {
const payload = {
sub: user.id,
email: user.email,
role: user.role,
organizationId: user.organizationId,
};
return {
access_token: this.jwtService.sign(payload),
user: {
id: user.id,
email: user.email,
role: user.role,
organizationId: user.organizationId,
},
};
}
throw new UnauthorizedException('Invalid credentials');
}
}

View File

@ -0,0 +1,5 @@
import { SetMetadata } from '@nestjs/common';
import { Role } from '@prisma/client';
export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);

View File

@ -0,0 +1,10 @@
import { IsEmail, IsNotEmpty, MinLength } from 'class-validator';
export class LoginDto {
@IsEmail()
email: string;
@IsNotEmpty()
@MinLength(6)
password: string;
}

View File

@ -0,0 +1,5 @@
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

View File

@ -0,0 +1,28 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from '@prisma/client';
import { ROLES_KEY } from '../decorators/roles.decorator';
interface RequestWithUser {
user: {
role: Role;
};
}
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<Role[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const request = context.switchToHttp().getRequest<RequestWithUser>();
const user = request.user;
return user && requiredRoles.some((role) => user.role === role);
}
}

View File

@ -0,0 +1,29 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(configService: ConfigService) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get('JWT_SECRET') || 'secret',
});
}
validate(payload: {
sub: string;
email: string;
role: string;
organizationId: string;
}) {
return {
id: payload.sub,
email: payload.email,
role: payload.role,
organizationId: payload.organizationId,
};
}
}

View File

@ -23,4 +23,4 @@ async function bootstrap() {
await app.listen(process.env.PORT ?? 3000); await app.listen(process.env.PORT ?? 3000);
} }
bootstrap(); bootstrap().catch((err) => console.error(err));

View File

@ -1,13 +1,9 @@
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common'; import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
@Injectable() @Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy { export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() { async onModuleInit() {
await this.$connect(); await this.$connect();
} }
async onModuleDestroy() {
await this.$disconnect();
}
} }

View File

@ -1,12 +1,20 @@
import { Controller, Get } from '@nestjs/common'; import { Controller, Get, Param, UseGuards, Request } from '@nestjs/common';
import { SpacesService } from './spaces.service'; import { SpacesService } from './spaces.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { RolesGuard } from '../auth/guards/roles.guard';
@Controller('spaces') @Controller('spaces')
@UseGuards(JwtAuthGuard, RolesGuard)
export class SpacesController { export class SpacesController {
constructor(private readonly spacesService: SpacesService) {} constructor(private readonly spacesService: SpacesService) {}
@Get() @Get('floor/:id')
findAll() { async getFloorLayout(@Param('id') id: string) {
return this.spacesService.findAll(); return this.spacesService.getFloorLayout(id);
}
@Get('org-hierarchy')
async getOrgHierarchy(@Request() req: { user: { organizationId: string } }) {
return this.spacesService.getBuildingsAndFloors(req.user.organizationId);
} }
} }

View File

@ -1,18 +1,30 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { SpaceDTO, SpaceType, BookingStatus } from '@hot-desking/shared'; import { PrismaService } from '../prisma/prisma.service';
@Injectable() @Injectable()
export class SpacesService { export class SpacesService {
findAll(): SpaceDTO[] { constructor(private prisma: PrismaService) {}
return [
{ async getFloorLayout(floorId: string) {
id: '1', return this.prisma.floor.findUnique({
name: 'Desk 1', where: { id: floorId },
type: SpaceType.Desk, include: {
x: 10, spaces: true,
y: 20, building: {
status: BookingStatus.Available, include: {
organization: true,
}, },
]; },
},
});
}
async getBuildingsAndFloors(organizationId: string) {
return this.prisma.building.findMany({
where: { organizationId },
include: {
floors: true,
},
});
} }
} }

View File

@ -16,8 +16,9 @@
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.0",
"lucide-svelte": "^0.300.0", "lucide-svelte": "^0.300.0",
"postcss": "^8.4.0", "postcss": "^8.4.0",
"sade": "^1.8.1",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"svelte-check": "^3.6.0", "svelte-check": "^3.8.6",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^5.0.0", "typescript": "^5.0.0",

View File

@ -0,0 +1,28 @@
import { writable } from "svelte/store";
import { browser } from "$app/environment";
const initialUser = browser ? JSON.parse(localStorage.getItem("user") || "null") : null;
const initialToken = browser ? localStorage.getItem("token") : null;
export const user = writable(initialUser);
export const token = writable(initialToken);
export function logout() {
user.set(null);
token.set(null);
if (browser) {
localStorage.removeItem("user");
localStorage.removeItem("token");
}
}
if (browser) {
user.subscribe((value) => {
if (value) localStorage.setItem("user", JSON.stringify(value));
else localStorage.removeItem("user");
});
token.subscribe((value) => {
if (value) localStorage.setItem("token", value);
else localStorage.removeItem("token");
});
}

View File

@ -1,13 +1,26 @@
<script> <script lang="ts">
import "../app.css"; import "../app.css";
import { token, user } from "$lib/stores/auth";
</script> </script>
<div class="min-h-screen bg-gray-50"> <div class="min-h-screen bg-gray-50">
<nav class="bg-white shadow-sm px-4 py-3 flex justify-between items-center"> <nav class="bg-white shadow-sm px-4 py-3 flex justify-between items-center">
<h1 class="text-xl font-bold text-indigo-600">Hot Desking</h1> <div class="flex items-center space-x-2">
<div class="space-x-4"> <div class="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center">
<a href="/" class="text-gray-600 hover:text-indigo-600">Dashboard</a> <span class="text-white font-bold">H</span>
<a href="/login" class="text-gray-600 hover:text-indigo-600">Login</a> </div>
<h1 class="text-xl font-bold text-gray-800">Hot Desking</h1>
</div>
<div class="space-x-4 flex items-center">
{#if $token}
<a href="/dashboard" class="text-gray-600 hover:text-indigo-600 font-medium">Dashboard</a>
<a href="/login" class="bg-indigo-50 text-indigo-700 px-3 py-1.5 rounded-md hover:bg-indigo-100 font-medium flex items-center space-x-2">
<div class="w-5 h-5 bg-indigo-200 rounded-full text-[10px] flex items-center justify-center font-bold">{$user?.email?.charAt(0).toUpperCase()}</div>
<span>Profile</span>
</a>
{:else}
<a href="/login" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700 font-medium transition-colors">Login</a>
{/if}
</div> </div>
</nav> </nav>

View File

@ -1,55 +1,17 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { apiFetch } from '$lib/api/client'; import { token } from "$lib/stores/auth";
import type { SpaceDTO } from '@shared/index'; import { goto } from "$app/navigation";
import { MapPin, Monitor as DeskIcon } from 'lucide-svelte';
let spaces: SpaceDTO[] = []; onMount(() => {
let error: string | null = null; if ($token) {
goto("/dashboard");
onMount(async () => { } else {
try { goto("/login");
spaces = await apiFetch<SpaceDTO[]>('/spaces');
} catch (e: any) {
error = e.message;
} }
}); });
</script> </script>
<div class="space-y-6"> <div class="flex items-center justify-center min-h-[50vh]">
<div class="flex justify-between items-center"> <p class="text-gray-500 animate-pulse font-medium">Redirecting...</p>
<h2 class="text-2xl font-bold text-gray-800">Space Dashboard</h2>
<div class="flex items-center space-x-2 text-sm text-gray-500">
<MapPin size={16} />
<span>Main Office</span>
</div>
</div>
{#if error}
<div class="bg-red-50 border-l-4 border-red-400 p-4 text-red-700">
<p>Error loading spaces: {error}</p>
</div>
{:else if spaces.length === 0}
<p class="text-gray-500 italic">No spaces found or loading...</p>
{:else}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{#each spaces as space}
<div class="bg-white p-4 rounded-lg shadow border border-gray-100 flex items-center space-x-4">
<div class="p-3 bg-indigo-50 text-indigo-600 rounded-full">
<DeskIcon size={24} />
</div>
<div>
<h3 class="font-semibold text-gray-900">{space.name}</h3>
<p class="text-sm text-gray-500 capitalize">{space.type}{space.status}</p>
</div>
<div class="ml-auto">
<span class="px-2 py-1 text-xs font-medium rounded-full
{space.status === 'Available' ? 'bg-green-100 text-green-700' : 'bg-orange-100 text-orange-700'}">
{space.status}
</span>
</div>
</div>
{/each}
</div>
{/if}
</div> </div>

View File

@ -0,0 +1,57 @@
<script lang="ts">
import { onMount } from "svelte";
import { token, user } from "$lib/stores/auth";
import { goto } from "$app/navigation";
let buildings = [];
let loading = true;
onMount(async () => {
if (!$token) {
goto("/login?redirect=/dashboard");
return;
}
try {
const res = await fetch("http://localhost:3000/spaces/org-hierarchy", {
headers: {
Authorization: `Bearer ${$token}`,
},
});
if (res.ok) {
buildings = await res.json();
} else {
goto("/login");
}
} catch (e) {
console.error(e);
} finally {
loading = false;
}
});
</script>
<div class="p-8">
<h1 class="text-2xl font-bold mb-6">Dashboard</h1>
<p class="mb-4">Welcome, {$user?.email}!</p>
{#if loading}
<p>Loading...</p>
{:else}
<div class="grid gap-6">
{#each buildings as building}
<div class="border p-4 rounded-lg shadow-sm">
<h2 class="text-xl font-semibold mb-2">{building.name}</h2>
<ul class="list-disc pl-5">
{#each building.floors as floor}
<li class="mb-1">
Floor {floor.number}
<a href="/dashboard/floor/{floor.id}" class="ml-2 text-blue-600 hover:underline">View Layout</a>
</li>
{/each}
</ul>
</div>
{/each}
</div>
{/if}
</div>

View File

@ -0,0 +1,54 @@
<script lang="ts">
import { page } from "$app/stores";
import { onMount } from "svelte";
import { token } from "$lib/stores/auth";
import { goto } from "$app/navigation";
let floor: any = null;
let loading = true;
onMount(async () => {
const floorId = $page.params.id;
if (!$token) {
goto("/login");
return;
}
try {
const res = await fetch(`http://localhost:3000/spaces/floor/${floorId}`, {
headers: {
Authorization: `Bearer ${$token}`,
},
});
if (res.ok) {
floor = await res.json();
}
} catch (e) {
console.error(e);
} finally {
loading = false;
}
});
</script>
<div class="p-8">
<a href="/dashboard" class="text-blue-600 hover:underline mb-4 inline-block">&larr; Back to Dashboard</a>
{#if loading}
<p>Loading floor layout...</p>
{:else if floor}
<h1 class="text-2xl font-bold mb-6">Floor {floor.number} - {floor.building?.name}</h1>
<p class="mb-8">{floor.building?.organization?.name}</p>
<div class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-4">
{#each floor.spaces as space}
<div class="border p-4 rounded bg-gray-50 flex flex-col items-center justify-center text-center">
<span class="font-semibold">{space.name}</span>
<span class="text-xs text-gray-500 uppercase">{space.type}</span>
</div>
{/each}
</div>
{:else}
<p>Floor not found.</p>
{/if}
</div>

View File

@ -1,27 +1,85 @@
<script lang="ts"> <script lang="ts">
import { LogIn } from 'lucide-svelte'; import { token, user, logout } from "$lib/stores/auth";
import { goto } from "$app/navigation";
import { page } from "$app/stores";
let email = "";
let password = "";
let error = "";
async function handleLogin() {
try {
const res = await fetch("http://localhost:3000/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
if (res.ok) {
const data = await res.json();
token.set(data.access_token);
user.set(data.user);
const redirect = $page.url.searchParams.get("redirect") || "/dashboard";
goto(redirect);
} else {
error = "Invalid credentials";
}
} catch (e) {
error = "Failed to connect to server";
}
}
function handleLogout() {
logout();
goto("/login");
}
</script> </script>
<div class="max-w-md mx-auto mt-20 bg-white p-8 rounded-xl shadow-lg border border-gray-100"> <div class="flex items-center justify-center min-h-screen bg-gray-100 p-4">
<div class="flex flex-col items-center mb-8"> <div class="p-8 bg-white rounded shadow-md w-full max-w-md">
<div class="p-4 bg-indigo-100 text-indigo-600 rounded-full mb-4"> {#if $token}
<LogIn size={32} /> <div class="text-center">
</div> <h1 class="text-2xl font-bold mb-6">User Profile</h1>
<h2 class="text-3xl font-bold text-gray-900">Sign In</h2> <div class="bg-blue-50 p-4 rounded-lg mb-6 text-left">
<p class="text-gray-500">Access the Hot Desking system</p> <p class="text-sm text-gray-500 uppercase font-bold mb-1">Email</p>
<p class="text-lg mb-4">{$user?.email}</p>
<p class="text-sm text-gray-500 uppercase font-bold mb-1">Role</p>
<p class="text-lg mb-4 capitalize">{$user?.role}</p>
<p class="text-sm text-gray-500 uppercase font-bold mb-1">Organization ID</p>
<p class="text-xs font-mono text-gray-600">{$user?.organizationId}</p>
</div> </div>
<form class="space-y-4"> <div class="flex gap-4">
<a href="/dashboard" class="flex-1 bg-gray-100 text-gray-700 p-2 rounded hover:bg-gray-200 text-center font-semibold">Dashboard</a>
<button on:click={handleLogout} class="flex-1 bg-red-600 text-white p-2 rounded hover:bg-red-700 font-semibold">Sign Out</button>
</div>
</div>
{:else}
<h1 class="text-2xl font-bold mb-6 text-center">Login</h1>
{#if error}
<div class="bg-red-50 text-red-600 p-3 rounded mb-4 text-center text-sm">
{error}
</div>
{/if}
<form on:submit|preventDefault={handleLogin} class="space-y-4">
<div> <div>
<label for="email" class="block text-sm font-medium text-gray-700">Email Address</label> <label class="block text-gray-700 text-sm font-bold mb-1" for="email">Email Address</label>
<input type="email" id="email" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" placeholder="you@company.com" required> <input id="email" type="email" bind:value={email} class="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 outline-none" placeholder="name@example.com" required />
</div> </div>
<div> <div>
<label for="password" class="block text-sm font-medium text-gray-700">Password</label> <label class="block text-gray-700 text-sm font-bold mb-1" for="password">Password</label>
<input type="password" id="password" class="mt-1 block w-full px-3 py-2 bg-white border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" required> <input id="password" type="password" bind:value={password} class="w-full p-2 border rounded focus:ring-2 focus:ring-blue-500 outline-none" placeholder="••••••••" required />
</div> </div>
<button type="submit" class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 transition-colors"> <button type="submit" class="w-full bg-blue-600 text-white p-2 rounded hover:bg-blue-700 font-semibold transition-colors">Sign In</button>
Login
</button>
</form> </form>
<div class="mt-6 text-center text-sm text-gray-500">
<p>Use seeded credentials:</p>
<p class="font-mono mt-1">admin@example.com / admin123</p>
</div>
{/if}
</div>
</div> </div>

564
package-lock.json generated
View File

@ -15,11 +15,19 @@
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@nestjs/common": "^10.4.22", "@nestjs/common": "^10.4.22",
"@nestjs/config": "^4.0.3",
"@nestjs/core": "^10.4.22", "@nestjs/core": "^10.4.22",
"@nestjs/jwt": "^11.0.2",
"@nestjs/passport": "^11.0.5",
"@nestjs/platform-express": "^10.4.22", "@nestjs/platform-express": "^10.4.22",
"@prisma/client": "^6.4.0", "@prisma/client": "^6.4.0",
"@types/bcrypt": "^6.0.0",
"@types/passport-jwt": "^4.0.1",
"bcrypt": "^6.0.0",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.15.1", "class-validator": "^0.15.1",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"prisma": "^6.4.0", "prisma": "^6.4.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.2" "rxjs": "^7.8.2"
@ -62,8 +70,9 @@
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.0",
"lucide-svelte": "^0.300.0", "lucide-svelte": "^0.300.0",
"postcss": "^8.4.0", "postcss": "^8.4.0",
"sade": "^1.8.1",
"svelte": "^5.0.0", "svelte": "^5.0.0",
"svelte-check": "^3.6.0", "svelte-check": "^3.8.6",
"tailwindcss": "^3.4.0", "tailwindcss": "^3.4.0",
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "^5.0.0", "typescript": "^5.0.0",
@ -197,25 +206,6 @@
"node": ">=18" "node": ">=18"
} }
}, },
"apps/web/node_modules/svelte-check": {
"version": "3.8.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.17",
"chokidar": "^3.4.1",
"picocolors": "^1.0.0",
"sade": "^1.7.4",
"svelte-preprocess": "^5.1.3",
"typescript": "^5.0.3"
},
"bin": {
"svelte-check": "bin/svelte-check"
},
"peerDependencies": {
"svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
}
},
"apps/web/node_modules/tailwindcss": { "apps/web/node_modules/tailwindcss": {
"version": "3.4.19", "version": "3.4.19",
"dev": true, "dev": true,
@ -2405,6 +2395,31 @@
} }
} }
}, },
"node_modules/@nestjs/config": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.3.tgz",
"integrity": "sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==",
"dependencies": {
"dotenv": "17.2.3",
"dotenv-expand": "12.0.3",
"lodash": "4.17.23"
},
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"rxjs": "^7.1.0"
}
},
"node_modules/@nestjs/config/node_modules/dotenv": {
"version": "17.2.3",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
"integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/@nestjs/core": { "node_modules/@nestjs/core": {
"version": "10.4.22", "version": "10.4.22",
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.22.tgz", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.22.tgz",
@ -2442,6 +2457,27 @@
} }
} }
}, },
"node_modules/@nestjs/jwt": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-11.0.2.tgz",
"integrity": "sha512-rK8aE/3/Ma45gAWfCksAXUNbOoSOUudU0Kn3rT39htPF7wsYXtKfjALKeKKJbFrIWbLjsbqfXX5bIJNvgBugGA==",
"dependencies": {
"@types/jsonwebtoken": "9.0.10",
"jsonwebtoken": "9.0.3"
},
"peerDependencies": {
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0"
}
},
"node_modules/@nestjs/passport": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz",
"integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==",
"peerDependencies": {
"@nestjs/common": "^10.0.0 || ^11.0.0",
"passport": "^0.5.0 || ^0.6.0 || ^0.7.0"
}
},
"node_modules/@nestjs/platform-express": { "node_modules/@nestjs/platform-express": {
"version": "10.4.22", "version": "10.4.22",
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.22.tgz", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.22.tgz",
@ -3236,11 +3272,18 @@
"@babel/types": "^7.28.2" "@babel/types": "^7.28.2"
} }
}, },
"node_modules/@types/bcrypt": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-6.0.0.tgz",
"integrity": "sha512-/oJGukuH3D2+D+3H4JWLaAsJ/ji86dhRidzZ/Od7H/i8g+aCmvkeCc6Ni/f9uxGLSQVCRZkX2/lqEFG2BvWtlQ==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/body-parser": { "node_modules/@types/body-parser": {
"version": "1.19.6", "version": "1.19.6",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
"integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==",
"dev": true,
"dependencies": { "dependencies": {
"@types/connect": "*", "@types/connect": "*",
"@types/node": "*" "@types/node": "*"
@ -3250,7 +3293,6 @@
"version": "3.4.38", "version": "3.4.38",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
"integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
"dev": true,
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
} }
@ -3297,7 +3339,6 @@
"version": "5.0.6", "version": "5.0.6",
"resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz",
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"dev": true,
"dependencies": { "dependencies": {
"@types/body-parser": "*", "@types/body-parser": "*",
"@types/express-serve-static-core": "^5.0.0", "@types/express-serve-static-core": "^5.0.0",
@ -3308,7 +3349,6 @@
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz",
"integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==",
"dev": true,
"dependencies": { "dependencies": {
"@types/node": "*", "@types/node": "*",
"@types/qs": "*", "@types/qs": "*",
@ -3319,8 +3359,7 @@
"node_modules/@types/http-errors": { "node_modules/@types/http-errors": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz",
"integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="
"dev": true
}, },
"node_modules/@types/istanbul-lib-coverage": { "node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6", "version": "2.0.6",
@ -3362,38 +3401,80 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true "dev": true
}, },
"node_modules/@types/jsonwebtoken": {
"version": "9.0.10",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
"integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
"dependencies": {
"@types/ms": "*",
"@types/node": "*"
}
},
"node_modules/@types/methods": { "node_modules/@types/methods": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz",
"integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==",
"dev": true "dev": true
}, },
"node_modules/@types/ms": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
"integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.19.15", "version": "22.19.15",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz",
"integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==",
"dev": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
}, },
"node_modules/@types/passport": {
"version": "1.0.17",
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz",
"integrity": "sha512-aciLyx+wDwT2t2/kJGJR2AEeBz0nJU4WuRX04Wu9Dqc5lSUtwu0WERPHYsLhF9PtseiAMPBGNUOtFjxZ56prsg==",
"dependencies": {
"@types/express": "*"
}
},
"node_modules/@types/passport-jwt": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-4.0.1.tgz",
"integrity": "sha512-Y0Ykz6nWP4jpxgEUYq8NoVZeCQPo1ZndJLfapI249g1jHChvRfZRO/LS3tqu26YgAS/laI1qx98sYGz0IalRXQ==",
"dependencies": {
"@types/jsonwebtoken": "*",
"@types/passport-strategy": "*"
}
},
"node_modules/@types/passport-strategy": {
"version": "0.2.38",
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz",
"integrity": "sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==",
"dependencies": {
"@types/express": "*",
"@types/passport": "*"
}
},
"node_modules/@types/pug": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
"integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==",
"dev": true
},
"node_modules/@types/qs": { "node_modules/@types/qs": {
"version": "6.15.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
"integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==", "integrity": "sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow=="
"dev": true
}, },
"node_modules/@types/range-parser": { "node_modules/@types/range-parser": {
"version": "1.2.7", "version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="
"dev": true
}, },
"node_modules/@types/send": { "node_modules/@types/send": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
"integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==",
"dev": true,
"dependencies": { "dependencies": {
"@types/node": "*" "@types/node": "*"
} }
@ -3402,7 +3483,6 @@
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz",
"integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==",
"dev": true,
"dependencies": { "dependencies": {
"@types/http-errors": "*", "@types/http-errors": "*",
"@types/node": "*" "@types/node": "*"
@ -4574,6 +4654,19 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/bcrypt": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
"integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==",
"hasInstallScript": true,
"dependencies": {
"node-addon-api": "^8.3.0",
"node-gyp-build": "^4.8.4"
},
"engines": {
"node": ">= 18"
}
},
"node_modules/binary-extensions": { "node_modules/binary-extensions": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
@ -4733,6 +4826,20 @@
"ieee754": "^1.1.13" "ieee754": "^1.1.13"
} }
}, },
"node_modules/buffer-crc32": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
"integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
"dev": true,
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"node_modules/buffer-from": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@ -5489,6 +5596,15 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/detect-indent": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz",
"integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/detect-newline": { "node_modules/detect-newline": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@ -5546,6 +5662,20 @@
"url": "https://dotenvx.com" "url": "https://dotenvx.com"
} }
}, },
"node_modules/dotenv-expand": {
"version": "12.0.3",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz",
"integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==",
"dependencies": {
"dotenv": "^16.4.5"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@ -5565,6 +5695,14 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true "dev": true
}, },
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": { "node_modules/ee-first": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -5690,6 +5828,12 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/es6-promise": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz",
"integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==",
"dev": true
},
"node_modules/esbuild": { "node_modules/esbuild": {
"version": "0.21.5", "version": "0.21.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
@ -8048,6 +8192,46 @@
"graceful-fs": "^4.1.6" "graceful-fs": "^4.1.6"
} }
}, },
"node_modules/jsonwebtoken": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
"integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
"dependencies": {
"jws": "^4.0.1",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/jwa": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
"dependencies": {
"buffer-equal-constant-time": "^1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
"integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
"dependencies": {
"jwa": "^2.0.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -8148,8 +8332,37 @@
"node_modules/lodash": { "node_modules/lodash": {
"version": "4.17.23", "version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="
"dev": true },
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
}, },
"node_modules/lodash.memoize": { "node_modules/lodash.memoize": {
"version": "4.1.2", "version": "4.1.2",
@ -8163,6 +8376,11 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true "dev": true
}, },
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"node_modules/log-symbols": { "node_modules/log-symbols": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@ -8360,6 +8578,15 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/min-indent": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.5", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
@ -8400,6 +8627,15 @@
"mkdirp": "bin/cmd.js" "mkdirp": "bin/cmd.js"
} }
}, },
"node_modules/mri": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
"integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/mrmime": { "node_modules/mrmime": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz",
@ -8507,6 +8743,14 @@
"integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==",
"dev": true "dev": true
}, },
"node_modules/node-addon-api": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz",
"integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==",
"engines": {
"node": "^18 || ^20 || >= 21"
}
},
"node_modules/node-emoji": { "node_modules/node-emoji": {
"version": "1.11.0", "version": "1.11.0",
"resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz",
@ -8540,6 +8784,16 @@
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
"integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==" "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="
}, },
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/node-int64": { "node_modules/node-int64": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
@ -8794,6 +9048,40 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/passport": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
"integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
"dependencies": {
"passport-strategy": "1.x.x",
"pause": "0.0.1",
"utils-merge": "^1.0.1"
},
"engines": {
"node": ">= 0.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jaredhanson"
}
},
"node_modules/passport-jwt": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz",
"integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==",
"dependencies": {
"jsonwebtoken": "^9.0.0",
"passport-strategy": "^1.0.0"
}
},
"node_modules/passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/path-exists": { "node_modules/path-exists": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@ -8868,6 +9156,11 @@
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==" "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="
}, },
"node_modules/pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"node_modules/perfect-debounce": { "node_modules/perfect-debounce": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@ -9075,9 +9368,9 @@
} }
}, },
"node_modules/postcss-load-config": { "node_modules/postcss-load-config": {
"version": "6.0.1", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz",
"integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -9090,7 +9383,8 @@
} }
], ],
"dependencies": { "dependencies": {
"lilconfig": "^3.1.1" "lilconfig": "^3.1.1",
"yaml": "^2.4.2"
}, },
"engines": { "engines": {
"node": ">= 18" "node": ">= 18"
@ -9098,8 +9392,7 @@
"peerDependencies": { "peerDependencies": {
"jiti": ">=1.21.0", "jiti": ">=1.21.0",
"postcss": ">=8.0.9", "postcss": ">=8.0.9",
"tsx": "^4.8.1", "tsx": "^4.8.1"
"yaml": "^2.4.2"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"jiti": { "jiti": {
@ -9110,9 +9403,6 @@
}, },
"tsx": { "tsx": {
"optional": true "optional": true
},
"yaml": {
"optional": true
} }
} }
}, },
@ -9511,6 +9801,40 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/rimraf/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.60.0", "version": "4.60.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz",
@ -9595,6 +9919,18 @@
"tslib": "^2.1.0" "tslib": "^2.1.0"
} }
}, },
"node_modules/sade": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
"integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
"dev": true,
"dependencies": {
"mri": "^1.1.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -9619,6 +9955,18 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
}, },
"node_modules/sander": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz",
"integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==",
"dev": true,
"dependencies": {
"es6-promise": "^3.1.2",
"graceful-fs": "^4.1.3",
"mkdirp": "^0.5.1",
"rimraf": "^2.5.2"
}
},
"node_modules/schema-utils": { "node_modules/schema-utils": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
@ -9641,7 +9989,6 @@
"version": "7.7.4", "version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true,
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
}, },
@ -9851,6 +10198,21 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/sorcery": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.1.tgz",
"integrity": "sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ==",
"dev": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.4.14",
"buffer-crc32": "^1.0.0",
"minimist": "^1.2.0",
"sander": "^0.5.0"
},
"bin": {
"sorcery": "bin/sorcery"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.7.4", "version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
@ -10024,6 +10386,18 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
"integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
"dev": true,
"dependencies": {
"min-indent": "^1.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-json-comments": { "node_modules/strip-json-comments": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@ -10177,6 +10551,26 @@
"node": ">=16" "node": ">=16"
} }
}, },
"node_modules/svelte-check": {
"version": "3.8.6",
"resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.8.6.tgz",
"integrity": "sha512-ij0u4Lw/sOTREP13BdWZjiXD/BlHE6/e2e34XzmVmsp5IN4kVa3PWP65NM32JAgwjZlwBg/+JtiNV1MM8khu0Q==",
"dev": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.17",
"chokidar": "^3.4.1",
"picocolors": "^1.0.0",
"sade": "^1.7.4",
"svelte-preprocess": "^5.1.3",
"typescript": "^5.0.3"
},
"bin": {
"svelte-check": "bin/svelte-check"
},
"peerDependencies": {
"svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0"
}
},
"node_modules/svelte-hmr": { "node_modules/svelte-hmr": {
"version": "0.16.0", "version": "0.16.0",
"resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz", "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz",
@ -10190,6 +10584,68 @@
"svelte": "^3.19.0 || ^4.0.0" "svelte": "^3.19.0 || ^4.0.0"
} }
}, },
"node_modules/svelte-preprocess": {
"version": "5.1.4",
"resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz",
"integrity": "sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@types/pug": "^2.0.6",
"detect-indent": "^6.1.0",
"magic-string": "^0.30.5",
"sorcery": "^0.11.0",
"strip-indent": "^3.0.0"
},
"engines": {
"node": ">= 16.0.0"
},
"peerDependencies": {
"@babel/core": "^7.10.2",
"coffeescript": "^2.5.1",
"less": "^3.11.3 || ^4.0.0",
"postcss": "^7 || ^8",
"postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0",
"pug": "^3.0.0",
"sass": "^1.26.8",
"stylus": "^0.55.0",
"sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0",
"svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0",
"typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
},
"coffeescript": {
"optional": true
},
"less": {
"optional": true
},
"postcss": {
"optional": true
},
"postcss-load-config": {
"optional": true
},
"pug": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"typescript": {
"optional": true
}
}
},
"node_modules/symbol-observable": { "node_modules/symbol-observable": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
@ -10854,8 +11310,7 @@
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "6.21.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="
"dev": true
}, },
"node_modules/universalify": { "node_modules/universalify": {
"version": "2.0.1", "version": "2.0.1",
@ -11371,6 +11826,21 @@
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"dev": true "dev": true
}, },
"node_modules/yaml": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"dev": true,
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/yargs": { "node_modules/yargs": {
"version": "17.7.2", "version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",