Compare commits

..

10 Commits
1.7.1 ... 1.8.1

Author SHA1 Message Date
7aaac65af4 chore(release): 1.8.1
Some checks failed
Build release images / build-container (push) Failing after 1m4s
2026-02-20 22:18:36 +01:00
13e0c81957 perf(stats): Cache stats results for 60 seconds 2026-02-20 22:16:14 +01:00
329a29aca7 chore(release): 1.8.0
Some checks failed
Build release images / build-container (push) Failing after 59s
2026-02-20 22:08:57 +01:00
abdadb8e64 refactor(deps): Remove unused glob dependency from package.json and bun.lock 2026-02-20 22:06:21 +01:00
abce517d86 refactor: Replace uuid and dotenv with bun primitives 2026-02-20 22:05:23 +01:00
a1e697acb2 refactor: Switch from official argon2 to Bun's implementation 2026-02-20 21:59:56 +01:00
c9b8614f53 chore(release): 1.7.2
All checks were successful
Build release images / build-container (push) Successful in 2m12s
2026-02-20 21:50:46 +01:00
cbf1da31c9 docs: Added agents file to support ai assisted coding 2026-02-20 21:50:21 +01:00
fd18e56251 refactor(dev): Yeet the funky dev script out of this codebase 2026-02-20 21:49:52 +01:00
3bb8b202b0 fix(dev): We did it funky bun dev workarounds are no more 2026-02-20 21:45:15 +01:00
46 changed files with 1454 additions and 478 deletions

282
AGENTS.md Normal file
View File

@@ -0,0 +1,282 @@
# AGENTS.md — LfK Backend
Guidance for agentic coding agents working in this repository.
---
## Project Overview
Express + [`routing-controllers`](https://github.com/typestack/routing-controllers) REST API written in TypeScript. Uses TypeORM for database access (SQLite in dev/test, PostgreSQL or MySQL in production). OpenAPI docs are auto-generated from decorators at startup.
**Runtime & Package Manager**: Bun (replaces Node.js + npm/pnpm).
---
## Build / Run / Test Commands
### Development
```sh
bun run dev # Start dev server with auto-reload (uses Bun's --watch)
```
**Auto-reload**: The `dev` script uses Bun's built-in `--watch` flag, which automatically restarts the server when TypeScript files in `src/` change. Bun runs TypeScript directly - no build step needed.
**Performance**: Bun delivers 8-15% better latency under concurrent load compared to Node.js. See `BUN_BENCHMARK_RESULTS.md` for details.
### Build
```sh
bun run build # rimraf dist && tsc && copy static assets → dist/
```
**Note**: The build script exists for legacy compatibility and type-checking, but is **not required** for development or production. Bun runs TypeScript source files directly.
### Production
```sh
bun start # bun src/app.ts (runs TypeScript directly)
```
### Tests
Tests are **integration tests** that hit a live running server via HTTP. The server must be started before Jest is invoked.
```sh
# Full CI test flow (generates .env, starts server, runs jest):
bun run test:ci
# Run Jest directly (server must already be running):
bun test
# Watch mode:
bun run test:watch
# Run a single test file:
bunx jest src/tests/runners/runner_add.spec.ts
# Run tests matching a name pattern:
bunx jest --testNamePattern="POST /api/runners"
# Run all tests in a subdirectory:
bunx jest src/tests/runners/
```
# Run all tests in a subdirectory:
bunx jest src/tests/runners/
```
> **Important:** `bun test` alone will fail unless the dev server is already running on `http://localhost:<config.internal_port>`. In CI, `start-server-and-test` handles this automatically via `bun run test:ci`.
### Other Utilities
```sh
bun run seed # Sync DB schema and run seeders
bun run openapi:export # Export OpenAPI spec to file
bun run docs # Generate TypeDoc documentation
bun run licenses:export # Export third-party license report
```
---
## TypeScript Configuration
- **Target:** ES2020, **Module:** CommonJS
- **`strict: false`** — TypeScript strictness is disabled; types are used but not exhaustively enforced
- **`experimentalDecorators: true`** and **`emitDecoratorMetadata: true`** — required by `routing-controllers`, `TypeORM`, and `class-validator`
- Spec files (`**/*.spec.ts`) are excluded from compilation
- Source root: `src/`, output: `dist/`
---
## Code Style Guidelines
### No Linter / Formatter Configured
There is no ESLint or Prettier configuration. Follow the patterns already established in the codebase rather than introducing new tooling.
### Imports
- Use named imports for decorator packages: `import { Get, JsonController, Param } from 'routing-controllers'`
- Use named imports for TypeORM: `import { Column, Entity, getConnectionManager } from 'typeorm'`
- Use named imports for class-validator: `import { IsInt, IsOptional, IsString } from 'class-validator'`
- Use `import * as X from 'module'` for modules without clean default exports (e.g., `import * as jwt from 'jsonwebtoken'`)
- Use default imports for simple modules (e.g., `import cookie from 'cookie'`)
- `reflect-metadata` is imported once at the top of `src/app.ts` — do not re-import it
- No barrel/index re-export files; import source files directly by path
### Naming Conventions
| Construct | Convention | Example |
|---|---|---|
| Classes | `PascalCase` | `RunnerController`, `CreateRunner` |
| Files | `PascalCase.ts` matching class name | `RunnerController.ts` |
| Local variables | `camelCase` (some `snake_case` in tests) | `accessToken`, `access_token` |
| DB entity fields | `snake_case` preferred | `created_at`, `updated_at` |
| Controller methods | REST-conventional | `getAll`, `getOne`, `post`, `put`, `remove` |
| Custom errors | `{Entity}{Issue}Error` | `RunnerNotFoundError`, `RunnerIdsNotMatchingError` |
| Response DTOs | `Response{Entity}` | `ResponseRunner`, `ResponseAuth` |
| Create DTOs | `Create{Entity}` | `CreateRunner` |
| Update DTOs | `Update{Entity}` | `UpdateRunner` |
| Enums | `PascalCase` | `ResponseObjectType`, `PermissionAction` |
### Formatting
- 4-space indentation (observed throughout the codebase)
- Single quotes for string literals in most files
- No trailing semicolons style inconsistency — follow what's already in the file you're editing
### Types
- Add TypeScript types to all function parameters and return values
- Use `class-validator` decorators (`@IsString`, `@IsInt`, `@IsOptional`, `@IsUUID`, etc.) on every DTO and response class field — these drive both runtime validation and OpenAPI schema generation
- Use abstract classes for shared entity base types (e.g., `abstract class Participant`)
- Use interfaces for response contracts (e.g., `interface IResponse`)
- Use enums for typed string/number constants
- Avoid `any` where possible; when unavoidable, keep it localised
- `strict` is off — but still annotate types explicitly rather than relying on inference
### Controller Pattern
```typescript
import { Authorized, Body, Delete, Get, JsonController, Param, Post, Put } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
@JsonController('/runners')
@Authorized()
export class RunnerController {
@Get('/')
@OpenAPI({ description: 'Returns all runners' })
@ResponseSchema(ResponseRunner, { isArray: true })
async getAll() { ... }
@Get('/:id')
@ResponseSchema(ResponseRunner)
async getOne(@Param('id') id: number) { ... }
@Post('/')
@ResponseSchema(ResponseRunner)
async post(@Body({ validate: true }) createRunner: CreateRunner) { ... }
@Put('/:id')
@ResponseSchema(ResponseRunner)
async put(@Param('id') id: number, @Body({ validate: true }) updateRunner: UpdateRunner) { ... }
@Delete('/:id')
@ResponseSchema(ResponseRunner)
async remove(@Param('id') id: number) { ... }
}
```
### Error Handling
- Define custom error classes in `src/errors/` extending `routing-controllers` error types (`NotFoundError`, `NotAcceptableError`, etc.)
- Every custom error class must include `@IsString()` decorated `name` and `message` fields for OpenAPI schema generation
- Throw custom errors directly in controllers: `throw new RunnerNotFoundError()`
- Use try/catch in controllers and re-throw meaningful errors; do not swallow errors silently
- The global `ErrorHandler` middleware (registered in `src/middlewares/`) catches all unhandled errors and serialises them as JSON — do not duplicate this logic in controllers
- Auth errors are thrown from `src/middlewares/authchecker.ts`, not from individual controllers
### Entity Pattern (TypeORM)
- Entities live in `src/models/entities/`
- Decorate every entity with `@Entity()` and every column with the appropriate `@Column`, `@PrimaryGeneratedColumn`, etc.
- Use `@CreateDateColumn()` / `@UpdateDateColumn()` for timestamp fields
- Use table inheritance (`@TableInheritance` + `@ChildEntity`) for polymorphic entities
- Access repositories via `getConnectionManager().get().getRepository(EntityClass)` — do not inject repositories as constructor dependencies
- Database schema is synchronised automatically on startup (`connection.synchronize()`) — no manual migration files
### DTO Pattern (Create / Update)
- Create DTOs in `src/models/actions/create/` and `src/models/actions/update/`
- Use `class-validator` decorators for every field
- `@IsOptional()` for fields that are not required on update; all fields on create DTOs should be mandatory unless explicitly optional in the API contract
- Response DTOs live in `src/models/responses/` and follow the `Response{Entity}` naming pattern
---
## Test Style Guidelines
> **IMPORTANT: Do not run existing tests and do not create new tests.** The existing test suite in `src/tests/` is outdated and no longer reflects the current state of the codebase. Ignore all test files when working in this repository. Do not write new tests for any changes or additions.
All tests are integration tests in `src/tests/` organised by domain entity:
```
src/tests/
auth/
auth_login.spec.ts
auth_refresh.spec.ts
runners/
runner_add.spec.ts
runner_get.spec.ts
runner_update.spec.ts
runner_delete.spec.ts
...
```
### Test File Template
```typescript
import axios from 'axios';
import { config } from '../../config';
const base = "http://localhost:" + config.internal_port;
let access_token: string;
let axios_config: object;
beforeAll(async () => {
jest.setTimeout(20000);
const res = await axios.post(base + '/api/auth/login', { username: "demo", password: "demo" });
access_token = res.data["access_token"];
axios_config = {
headers: { "authorization": "Bearer " + access_token },
validateStatus: undefined // prevents axios from throwing on non-2xx responses
};
});
describe('POST /api/runners working', () => {
it('creating a runner with required params should return 200', async () => {
const res = await axios.post(base + '/api/runners', { ... }, axios_config);
expect(res.status).toEqual(200);
expect(res.headers['content-type']).toContain("application/json");
});
});
describe('POST /api/runners failing', () => {
it('creating a runner without required params should return 400', async () => {
const res = await axios.post(base + '/api/runners', {}, axios_config);
expect(res.status).toEqual(400);
});
});
```
- Always set `validateStatus: undefined` in `axios_config` to prevent axios throwing on error responses
- Group tests by HTTP verb + route in `describe()` blocks; separate "working" and "failing" cases
- Use `jest.setTimeout(20000)` in `beforeAll` for slow integration tests
- Assert both `res.status` and `res.headers['content-type']` on success paths
---
## Environment Configuration
- Copy `.env.example` to `.env` and fill in values before running locally
- Database type is set via `DB_TYPE` env var (`sqlite`, `postgres`, or `mysql`)
- Server port is set via `INTERNAL_PORT` (accessed as `config.internal_port` in code)
- All config values are validated at startup in `src/config.ts`
- CI env is generated by `bun run test:ci:generate_env` (`scripts/create_testenv.ts`)
### NATS Configuration
The backend uses **NATS JetStream** as a KV cache for scan intake performance optimization.
- `NATS_URL` — connection URL for NATS server (default: `nats://localhost:4222`)
- `NATS_PREWARM` — if `true`, preloads all runner state into the KV cache at startup to eliminate DB reads from the first scan onward (default: `false`)
**KV buckets** (auto-created by `NatsClient` at startup):
- `station_state` — station token cache (1-hour TTL)
- `card_state` — card→runner mapping cache (1-hour TTL)
- `runner_state` — runner display name, total distance, latest scan timestamp (no TTL, CAS-based updates)
**Development**: NATS runs in Docker via `docker-compose.yml` (port 4222). The JetStream volume is persisted to `./nats-data/` to survive container restarts.
**Station intake hot path**: `POST /api/scans/trackscans` from scan stations uses a KV-first flow that eliminates DB reads on cache hits and prevents race conditions via compare-and-swap (CAS) updates. See `SCAN_NATS_PLAN.md` for full architecture details.

View File

@@ -2,10 +2,35 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC. All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.8.1](https://git.odit.services/lfk/backend/compare/1.8.0...1.8.1)
- perf(stats): Cache stats results for 60 seconds [`13e0c81`](https://git.odit.services/lfk/backend/commit/13e0c81957768c1b380914a0b93d3617c60e08a0)
#### [1.8.0](https://git.odit.services/lfk/backend/compare/1.7.2...1.8.0)
> 20 February 2026
- chore(release): 1.8.0 [`329a29a`](https://git.odit.services/lfk/backend/commit/329a29aca70b8c779c592149dc1cfe197ab62463)
- refactor: Switch from official argon2 to Bun's implementation [`a1e697a`](https://git.odit.services/lfk/backend/commit/a1e697acb264a753534c5ff8f5f43357cbc287da)
- refactor: Replace uuid and dotenv with bun primitives [`abce517`](https://git.odit.services/lfk/backend/commit/abce517d86daa00d76d691081907cb832494cb91)
- refactor(deps): Remove unused glob dependency from package.json and bun.lock [`abdadb8`](https://git.odit.services/lfk/backend/commit/abdadb8e6419c5ec9f8cc0a9e5ebf68671d84a94)
#### [1.7.2](https://git.odit.services/lfk/backend/compare/1.7.1...1.7.2)
> 20 February 2026
- fix(dev): We did it funky bun dev workarounds are no more [`3bb8b20`](https://git.odit.services/lfk/backend/commit/3bb8b202b00f8b7c52c700373ed09a92714528be)
- docs: Added agents file to support ai assisted coding [`cbf1da3`](https://git.odit.services/lfk/backend/commit/cbf1da31c9f02a810d8c85caae60ab9483f826c2)
- refactor(dev): Yeet the funky dev script out of this codebase [`fd18e56`](https://git.odit.services/lfk/backend/commit/fd18e562518f5b3437f11ceb68e69e50f042891e)
- chore(release): 1.7.2 [`c9b8614`](https://git.odit.services/lfk/backend/commit/c9b8614f53619ec76ccf76875c138c986699c746)
#### [1.7.1](https://git.odit.services/lfk/backend/compare/1.7.0...1.7.1) #### [1.7.1](https://git.odit.services/lfk/backend/compare/1.7.0...1.7.1)
> 20 February 2026
- fix(ci): Switch to bun in ci [`fe90414`](https://git.odit.services/lfk/backend/commit/fe90414dd910baff8107197408575b6af0cc4cbf) - fix(ci): Switch to bun in ci [`fe90414`](https://git.odit.services/lfk/backend/commit/fe90414dd910baff8107197408575b6af0cc4cbf)
- perf(db): Added indexes [`21ceb9f`](https://git.odit.services/lfk/backend/commit/21ceb9fa265df2f2193a6c4fb58080ead9c72bf8) - perf(db): Added indexes [`21ceb9f`](https://git.odit.services/lfk/backend/commit/21ceb9fa265df2f2193a6c4fb58080ead9c72bf8)
- chore(release): 1.7.1 [`d1c4744`](https://git.odit.services/lfk/backend/commit/d1c47442314508a95bfa66b83740c957b75f152a)
#### [1.7.0](https://git.odit.services/lfk/backend/compare/1.6.0...1.7.0) #### [1.7.0](https://git.odit.services/lfk/backend/compare/1.6.0...1.7.0)

589
PERFORMANCE_IDEAS.md Normal file
View File

@@ -0,0 +1,589 @@
# Performance Optimization Ideas for LfK Backend
This document outlines potential performance improvements for the LfK backend API, organized by impact and complexity.
---
## ✅ Already Implemented
### 1. Bun Runtime Migration
**Status**: Complete
**Impact**: 8-15% latency improvement
**Details**: Migrated from Node.js to Bun runtime, achieving:
- Parallel throughput: +8.3% (306 → 331 scans/sec)
- Parallel p50 latency: -9.5% (21ms → 19ms)
### 2. NATS KV Cache for Scan Intake
**Status**: Complete (based on code analysis)
**Impact**: Significant reduction in DB reads for hot path
**Details**: `ScanController.stationIntake()` uses NATS JetStream KV store to cache:
- Station tokens (1-hour TTL)
- Card→Runner mappings (1-hour TTL)
- Runner state (no TTL, CAS-based updates)
- Eliminates DB reads on cache hits
- Prevents race conditions via compare-and-swap (CAS)
---
## 🚀 High Impact, Low-Medium Complexity
### 3. Add Database Indexes
**Priority**: HIGH
**Complexity**: Low
**Estimated Impact**: 30-70% query time reduction
**Problem**: TypeORM synchronize() doesn't automatically create indexes on foreign keys or commonly queried fields.
**Observations**:
- Heavy use of `find()` with complex nested relations (e.g., `['runner', 'track', 'runner.scans', 'runner.group', 'runner.scans.track']`)
- No explicit `@Index()` decorators found in entity files
- Frequent filtering by foreign keys (runner_id, track_id, station_id, card_id)
**Recommended Indexes**:
```typescript
// src/models/entities/Scan.ts
@Index(['runner', 'timestamp']) // For runner scan history queries
@Index(['station', 'timestamp']) // For station-based queries
@Index(['card']) // For card lookup
// src/models/entities/Runner.ts
@Index(['email']) // For authentication/lookup
@Index(['group']) // For group-based queries
// src/models/entities/RunnerCard.ts
@Index(['runner']) // For card→runner lookups
@Index(['code']) // For barcode scans
// src/models/entities/Donation.ts
@Index(['runner']) // For runner donations
@Index(['donor']) // For donor contributions
```
**Implementation Steps**:
1. Audit all entities and add `@Index()` decorators
2. Test query performance with `EXPLAIN` before/after
3. Monitor index usage with database tools
4. Consider composite indexes for frequently combined filters
**Expected Results**:
- 50-70% faster JOIN operations
- 30-50% faster foreign key lookups
- Reduced database CPU usage
---
### 4. Implement Query Result Caching
**Priority**: HIGH
**Complexity**: Medium
**Estimated Impact**: 50-90% latency reduction for repeated queries
**Problem**: Stats endpoints and frequently accessed data (org totals, team rankings, runner lists) are recalculated on every request.
**Observations**:
- `StatsController` methods load entire datasets with deep relations:
- `getRunnerStats()`: loads all runners with scans, groups, donations
- `getTeamStats()`: loads all teams with nested runner data
- `getOrgStats()`: loads all orgs with teams, runners, scans
- Many `find()` calls without any caching layer
- Data changes infrequently (only during scan intake)
**Solution Options**:
**Option A: NATS KV Cache (Recommended)**
```typescript
// src/nats/StatsKV.ts
export async function getOrgStatsCache(): Promise<ResponseOrgStats[] | null> {
const kv = await NatsClient.getKV('stats_cache', { ttl: 60 * 1000 }); // 60s TTL
const entry = await kv.get('org_stats');
return entry ? JSON.parse(entry.string()) : null;
}
export async function setOrgStatsCache(stats: ResponseOrgStats[]): Promise<void> {
const kv = await NatsClient.getKV('stats_cache', { ttl: 60 * 1000 });
await kv.put('org_stats', JSON.stringify(stats));
}
// Invalidate on scan creation
// src/controllers/ScanController.ts (after line 173)
await invalidateStatsCache(); // Clear stats on new scan
```
**Option B: In-Memory Cache with TTL**
```typescript
// src/cache/MemoryCache.ts
import NodeCache from 'node-cache';
const cache = new NodeCache({ stdTTL: 60 }); // 60s TTL
export function getCached<T>(key: string): T | undefined {
return cache.get<T>(key);
}
export function setCached<T>(key: string, value: T, ttl?: number): void {
cache.set(key, value, ttl);
}
export function invalidatePattern(pattern: string): void {
const keys = cache.keys().filter(k => k.includes(pattern));
cache.del(keys);
}
```
**Option C: Redis Cache** (if Redis is already in stack)
**Recommended Cache Strategy**:
- **TTL**: 30-60 seconds for stats endpoints
- **Invalidation**: On scan creation, runner updates, donation changes
- **Keys**: `stats:org`, `stats:team:${id}`, `stats:runner:${id}`
- **Warm on startup**: Pre-populate cache for critical endpoints
**Expected Results**:
- 80-90% latency reduction for stats endpoints (from ~500ms to ~50ms)
- 70-80% reduction in database load
- Improved user experience for dashboards and leaderboards
---
### 5. Lazy Load Relations & DTOs
**Priority**: HIGH
**Complexity**: Medium
**Estimated Impact**: 40-60% query time reduction
**Problem**: Many queries eagerly load deeply nested relations that aren't always needed.
**Observations**:
```typescript
// Current: Loads everything
scan = await this.scanRepository.findOne(
{ id: scan.id },
{ relations: ['runner', 'track', 'runner.scans', 'runner.group',
'runner.scans.track', 'card', 'station'] }
);
```
**Solutions**:
**A. Create Lightweight Response DTOs**
```typescript
// src/models/responses/ResponseScanLight.ts
export class ResponseScanLight {
@IsInt() id: number;
@IsInt() distance: number;
@IsInt() timestamp: number;
@IsBoolean() valid: boolean;
// Omit nested runner.scans, runner.group, etc.
}
// Use for list views
@Get()
@ResponseSchema(ResponseScanLight, { isArray: true })
async getAll() {
const scans = await this.scanRepository.find({
relations: ['runner', 'track'] // Minimal relations
});
return scans.map(s => new ResponseScanLight(s));
}
// Keep detailed DTO for single-item views
@Get('/:id')
@ResponseSchema(ResponseScan) // Full details
async getOne(@Param('id') id: number) { ... }
```
**B. Use Query Builder for Selective Loading**
```typescript
// Instead of loading all scans with runner relations:
const scans = await this.scanRepository
.createQueryBuilder('scan')
.leftJoinAndSelect('scan.runner', 'runner')
.leftJoinAndSelect('scan.track', 'track')
.select([
'scan.id', 'scan.distance', 'scan.timestamp', 'scan.valid',
'runner.id', 'runner.firstname', 'runner.lastname',
'track.id', 'track.name'
])
.where('scan.id = :id', { id })
.getOne();
```
**C. Implement GraphQL-style Field Selection**
```typescript
@Get()
async getAll(@QueryParam('fields') fields?: string) {
const relations = [];
if (fields?.includes('runner')) relations.push('runner');
if (fields?.includes('track')) relations.push('track');
return this.scanRepository.find({ relations });
}
```
**Expected Results**:
- 40-60% faster list queries
- 50-70% reduction in data transfer size
- Reduced JOIN complexity and memory usage
---
### 6. Pagination Optimization
**Priority**: MEDIUM
**Complexity**: Low
**Estimated Impact**: 20-40% improvement for large result sets
**Problem**: Current pagination uses `skip/take` which becomes slow with large offsets.
**Current Implementation**:
```typescript
// Inefficient for large page numbers (e.g., page=1000)
scans = await this.scanRepository.find({
skip: page * page_size, // Scans 100,000 rows to skip them
take: page_size
});
```
**Solutions**:
**A. Cursor-Based Pagination (Recommended)**
```typescript
@Get()
async getAll(
@QueryParam('cursor') cursor?: number, // Last ID from previous page
@QueryParam('page_size') page_size: number = 100
) {
const query = this.scanRepository.createQueryBuilder('scan')
.orderBy('scan.id', 'ASC')
.take(page_size + 1); // Get 1 extra to determine if more pages exist
if (cursor) {
query.where('scan.id > :cursor', { cursor });
}
const scans = await query.getMany();
const hasMore = scans.length > page_size;
const results = scans.slice(0, page_size);
const nextCursor = hasMore ? results[results.length - 1].id : null;
return {
data: results.map(s => s.toResponse()),
pagination: { nextCursor, hasMore }
};
}
```
**B. Add Total Count Caching**
```typescript
// Cache total counts to avoid expensive COUNT(*) queries
const totalCache = new Map<string, { count: number, expires: number }>();
async function getTotalCount(repo: Repository<any>): Promise<number> {
const cacheKey = repo.metadata.tableName;
const cached = totalCache.get(cacheKey);
if (cached && cached.expires > Date.now()) {
return cached.count;
}
const count = await repo.count();
totalCache.set(cacheKey, { count, expires: Date.now() + 60000 }); // 60s TTL
return count;
}
```
**Expected Results**:
- 60-80% faster pagination for large page numbers
- Consistent query performance regardless of offset
- Better mobile app experience with cursor-based loading
---
## 🔧 Medium Impact, Medium Complexity
### 7. Database Connection Pooling Optimization
**Priority**: MEDIUM
**Complexity**: Medium
**Estimated Impact**: 10-20% improvement under load
**Current**: Default TypeORM connection pooling (likely 10 connections)
**Recommendations**:
```typescript
// ormconfig.js
module.exports = {
// ... existing config
extra: {
// PostgreSQL specific
max: 20, // Max pool size (adjust based on load)
min: 5, // Min pool size
idleTimeoutMillis: 30000, // Close idle connections after 30s
connectionTimeoutMillis: 2000,
// MySQL specific
connectionLimit: 20,
waitForConnections: true,
queueLimit: 0
},
// Enable query logging in dev to identify slow queries
logging: process.env.NODE_ENV !== 'production' ? ['query', 'error'] : ['error'],
maxQueryExecutionTime: 1000, // Log queries taking >1s
};
```
**Monitor**:
- Connection pool exhaustion
- Query execution times
- Active connection count
---
### 8. Bulk Operations for Import
**Priority**: MEDIUM
**Complexity**: Medium
**Estimated Impact**: 50-80% faster imports
**Problem**: Import endpoints likely save entities one-by-one in loops.
**Solution**:
```typescript
// Instead of:
for (const runnerData of importData) {
const runner = await createRunner.toEntity();
await this.runnerRepository.save(runner); // N queries
}
// Use bulk insert:
const runners = await Promise.all(
importData.map(data => createRunner.toEntity())
);
await this.runnerRepository.save(runners); // 1 query
// Or use raw query for massive imports:
await getConnection()
.createQueryBuilder()
.insert()
.into(Runner)
.values(runners)
.execute();
```
---
### 9. Response Compression
**Priority**: MEDIUM
**Complexity**: Low
**Estimated Impact**: 60-80% reduction in response size
**Implementation**:
```typescript
// src/app.ts
import compression from 'compression';
const app = createExpressServer({ ... });
app.use(compression({
level: 6, // Compression level (1-9)
threshold: 1024, // Only compress responses >1KB
filter: (req, res) => {
if (req.headers['x-no-compression']) return false;
return compression.filter(req, res);
}
}));
```
**Benefits**:
- 70-80% smaller JSON responses
- Faster transfer times on slow networks
- Reduced bandwidth costs
**Dependencies**: `bun add compression @types/compression`
---
## 🎯 Lower Priority / High Complexity
### 10. Implement Read Replicas
**Priority**: LOW (requires infrastructure)
**Complexity**: High
**Estimated Impact**: 30-50% read query improvement
**When to Consider**:
- Database CPU consistently >70%
- Read-heavy workload (already true for stats endpoints)
- Running PostgreSQL/MySQL in production
**Implementation**:
```typescript
// ormconfig.js
module.exports = {
type: 'postgres',
replication: {
master: {
host: process.env.DB_WRITE_HOST,
port: 5432,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
},
slaves: [
{
host: process.env.DB_READ_REPLICA_1,
port: 5432,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
}
]
}
};
```
---
### 11. Move to Serverless/Edge Functions
**Priority**: LOW (architectural change)
**Complexity**: Very High
**Estimated Impact**: Variable (depends on workload)
**Considerations**:
- Good for: Infrequent workloads, global distribution
- Bad for: High-frequency scan intake (cold starts)
- May conflict with TypeORM's connection model
---
### 12. GraphQL API Layer
**Priority**: LOW (major refactor)
**Complexity**: Very High
**Estimated Impact**: 30-50% for complex queries
**Benefits**:
- Clients request only needed fields
- Single request for complex nested data
- Better mobile app performance
**Trade-offs**:
- Complete rewrite of controller layer
- Learning curve for frontend teams
- More complex caching strategy
---
## 📊 Recommended Implementation Order
**Phase 1: Quick Wins** (1-2 weeks)
1. Add database indexes → Controllers still work, immediate improvement
2. Enable response compression → One-line change in `app.ts`
3. Implement cursor-based pagination → Better mobile UX
**Phase 2: Caching Layer** (2-3 weeks)
4. Add NATS KV cache for stats endpoints
5. Create lightweight response DTOs for list views
6. Cache total counts for pagination
**Phase 3: Query Optimization** (2-3 weeks)
7. Refactor controllers to use query builder with selective loading
8. Optimize database connection pooling
9. Implement bulk operations for imports
**Phase 4: Infrastructure** (ongoing)
10. Monitor query performance and add more indexes as needed
11. Consider read replicas when database becomes bottleneck
---
## 🔍 Performance Monitoring Recommendations
### Add Metrics Endpoint
```typescript
// src/controllers/MetricsController.ts
import { performance } from 'perf_hooks';
const requestMetrics = {
totalRequests: 0,
avgLatency: 0,
p95Latency: 0,
dbQueryCount: 0,
cacheHitRate: 0,
};
@JsonController('/metrics')
export class MetricsController {
@Get()
@Authorized('ADMIN') // Restrict to admins
async getMetrics() {
return requestMetrics;
}
}
```
### Enable Query Logging
```typescript
// ormconfig.js
logging: ['query', 'error'],
maxQueryExecutionTime: 1000, // Warn on queries >1s
```
### Add Request Timing Middleware
```typescript
// src/middlewares/TimingMiddleware.ts
export function timingMiddleware(req: Request, res: Response, next: NextFunction) {
const start = performance.now();
res.on('finish', () => {
const duration = performance.now() - start;
if (duration > 1000) {
consola.warn(`Slow request: ${req.method} ${req.path} took ${duration}ms`);
}
});
next();
}
```
---
## 📝 Performance Testing Commands
```bash
# Run baseline benchmark
bun run benchmark > baseline.txt
# After implementing changes, compare
bun run benchmark > optimized.txt
diff baseline.txt optimized.txt
# Load testing with artillery (if added)
artillery quick --count 100 --num 10 http://localhost:4010/api/runners
# Database query profiling (PostgreSQL)
EXPLAIN ANALYZE SELECT * FROM scan WHERE runner_id = 1;
# Check database indexes
SELECT * FROM pg_indexes WHERE tablename = 'scan';
# Monitor NATS cache hit rate
# (Add custom logging in NATS KV functions)
```
---
## 🎓 Key Principles
1. **Measure first**: Always benchmark before and after changes
2. **Start with indexes**: Biggest impact, lowest risk
3. **Cache strategically**: Stats endpoints benefit most
4. **Lazy load by default**: Only eager load when absolutely needed
5. **Monitor in production**: Use APM tools (New Relic, DataDog, etc.)
---
## 📚 Additional Resources
- [TypeORM Performance Tips](https://typeorm.io/performance)
- [PostgreSQL Index Best Practices](https://www.postgresql.org/docs/current/indexes.html)
- [Bun Performance Benchmarks](https://bun.sh/docs/runtime/performance)
- [NATS JetStream KV Guide](https://docs.nats.io/nats-concepts/jetstream/key-value-store)
---
**Last Updated**: 2026-02-20
**Status**: Ready for review and prioritization

173
bun.lock
View File

@@ -5,7 +5,6 @@
"": { "": {
"name": "@odit/lfk-backend", "name": "@odit/lfk-backend",
"dependencies": { "dependencies": {
"@node-rs/argon2": "^2.0.2",
"@odit/class-validator-jsonschema": "2.1.1", "@odit/class-validator-jsonschema": "2.1.1",
"axios": "0.21.1", "axios": "0.21.1",
"body-parser": "1.19.0", "body-parser": "1.19.0",
@@ -17,7 +16,6 @@
"cookie-parser": "1.4.5", "cookie-parser": "1.4.5",
"cors": "2.8.5", "cors": "2.8.5",
"csvtojson": "2.0.10", "csvtojson": "2.0.10",
"dotenv": "8.2.0",
"express": "4.17.1", "express": "4.17.1",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "8.5.1",
"libphonenumber-js": "1.9.9", "libphonenumber-js": "1.9.9",
@@ -31,7 +29,6 @@
"typeorm": "0.2.30", "typeorm": "0.2.30",
"typeorm-routing-controllers-extensions": "0.2.0", "typeorm-routing-controllers-extensions": "0.2.0",
"typeorm-seeding": "1.6.1", "typeorm-seeding": "1.6.1",
"uuid": "8.3.2",
"validator": "13.5.2", "validator": "13.5.2",
}, },
"devDependencies": { "devDependencies": {
@@ -47,7 +44,7 @@
"cp-cli": "2.0.0", "cp-cli": "2.0.0",
"jest": "26.6.3", "jest": "26.6.3",
"release-it": "14.2.2", "release-it": "14.2.2",
"rimraf": "3.0.2", "rimraf": "^6.1.3",
"start-server-and-test": "1.11.7", "start-server-and-test": "1.11.7",
"ts-jest": "26.5.0", "ts-jest": "26.5.0",
"typedoc": "0.20.19", "typedoc": "0.20.19",
@@ -126,12 +123,6 @@
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
"@emnapi/core": ["@emnapi/core@1.8.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg=="],
"@emnapi/runtime": ["@emnapi/runtime@1.8.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg=="],
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
"@faker-js/faker": ["@faker-js/faker@7.6.0", "", {}, "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw=="], "@faker-js/faker": ["@faker-js/faker@7.6.0", "", {}, "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw=="],
"@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],
@@ -184,38 +175,6 @@
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
"@node-rs/argon2": ["@node-rs/argon2@2.0.2", "", { "optionalDependencies": { "@node-rs/argon2-android-arm-eabi": "2.0.2", "@node-rs/argon2-android-arm64": "2.0.2", "@node-rs/argon2-darwin-arm64": "2.0.2", "@node-rs/argon2-darwin-x64": "2.0.2", "@node-rs/argon2-freebsd-x64": "2.0.2", "@node-rs/argon2-linux-arm-gnueabihf": "2.0.2", "@node-rs/argon2-linux-arm64-gnu": "2.0.2", "@node-rs/argon2-linux-arm64-musl": "2.0.2", "@node-rs/argon2-linux-x64-gnu": "2.0.2", "@node-rs/argon2-linux-x64-musl": "2.0.2", "@node-rs/argon2-wasm32-wasi": "2.0.2", "@node-rs/argon2-win32-arm64-msvc": "2.0.2", "@node-rs/argon2-win32-ia32-msvc": "2.0.2", "@node-rs/argon2-win32-x64-msvc": "2.0.2" } }, "sha512-t64wIsPEtNd4aUPuTAyeL2ubxATCBGmeluaKXEMAFk/8w6AJIVVkeLKMBpgLW6LU2t5cQxT+env/c6jxbtTQBg=="],
"@node-rs/argon2-android-arm-eabi": ["@node-rs/argon2-android-arm-eabi@2.0.2", "", { "os": "android", "cpu": "arm" }, "sha512-DV/H8p/jt40lrao5z5g6nM9dPNPGEHL+aK6Iy/og+dbL503Uj0AHLqj1Hk9aVUSCNnsDdUEKp4TVMi0YakDYKw=="],
"@node-rs/argon2-android-arm64": ["@node-rs/argon2-android-arm64@2.0.2", "", { "os": "android", "cpu": "arm64" }, "sha512-1LKwskau+8O1ktKx7TbK7jx1oMOMt4YEXZOdSNIar1TQKxm6isZ0cRXgHLibPHEcNHgYRsJWDE9zvDGBB17QDg=="],
"@node-rs/argon2-darwin-arm64": ["@node-rs/argon2-darwin-arm64@2.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-3TTNL/7wbcpNju5YcqUrCgXnXUSbD7ogeAKatzBVHsbpjZQbNb1NDxDjqqrWoTt6XL3z9mJUMGwbAk7zQltHtA=="],
"@node-rs/argon2-darwin-x64": ["@node-rs/argon2-darwin-x64@2.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-vNPfkLj5Ij5111UTiYuwgxMqE7DRbOS2y58O2DIySzSHbcnu+nipmRKg+P0doRq6eKIJStyBK8dQi5Ic8pFyDw=="],
"@node-rs/argon2-freebsd-x64": ["@node-rs/argon2-freebsd-x64@2.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-M8vQZk01qojQfCqQU0/O1j1a4zPPrz93zc9fSINY7Q/6RhQRBCYwDw7ltDCZXg5JRGlSaeS8cUXWyhPGar3cGg=="],
"@node-rs/argon2-linux-arm-gnueabihf": ["@node-rs/argon2-linux-arm-gnueabihf@2.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-7EmmEPHLzcu0G2GDh30L6G48CH38roFC2dqlQJmtRCxs6no3tTE/pvgBGatTp/o2n2oyOJcfmgndVFcUpwMnww=="],
"@node-rs/argon2-linux-arm64-gnu": ["@node-rs/argon2-linux-arm64-gnu@2.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-6lsYh3Ftbk+HAIZ7wNuRF4SZDtxtFTfK+HYFAQQyW7Ig3LHqasqwfUKRXVSV5tJ+xTnxjqgKzvZSUJCAyIfHew=="],
"@node-rs/argon2-linux-arm64-musl": ["@node-rs/argon2-linux-arm64-musl@2.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-p3YqVMNT/4DNR67tIHTYGbedYmXxW9QlFmF39SkXyEbGQwpgSf6pH457/fyXBIYznTU/smnG9EH+C1uzT5j4hA=="],
"@node-rs/argon2-linux-x64-gnu": ["@node-rs/argon2-linux-x64-gnu@2.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ZM3jrHuJ0dKOhvA80gKJqBpBRmTJTFSo2+xVZR+phQcbAKRlDMSZMFDiKbSTnctkfwNFtjgDdh5g1vaEV04AvA=="],
"@node-rs/argon2-linux-x64-musl": ["@node-rs/argon2-linux-x64-musl@2.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-of5uPqk7oCRF/44a89YlWTEfjsftPywyTULwuFDKyD8QtVZoonrJR6ZWvfFE/6jBT68S0okAkAzzMEdBVWdxWw=="],
"@node-rs/argon2-wasm32-wasi": ["@node-rs/argon2-wasm32-wasi@2.0.2", "", { "dependencies": { "@napi-rs/wasm-runtime": "^0.2.5" }, "cpu": "none" }, "sha512-U3PzLYKSQYzTERstgtHLd4ZTkOF9co57zTXT77r0cVUsleGZOrd6ut7rHzeWwoJSiHOVxxa0OhG1JVQeB7lLoQ=="],
"@node-rs/argon2-win32-arm64-msvc": ["@node-rs/argon2-win32-arm64-msvc@2.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Eisd7/NM0m23ijrGr6xI2iMocdOuyl6gO27gfMfya4C5BODbUSP7ljKJ7LrA0teqZMdYHesRDzx36Js++/vhiQ=="],
"@node-rs/argon2-win32-ia32-msvc": ["@node-rs/argon2-win32-ia32-msvc@2.0.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-GsE2ezwAYwh72f9gIjbGTZOf4HxEksb5M2eCaj+Y5rGYVwAdt7C12Q2e9H5LRYxWcFvLH4m4jiSZpQQ4upnPAQ=="],
"@node-rs/argon2-win32-x64-msvc": ["@node-rs/argon2-win32-x64-msvc@2.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-cJxWXanH4Ew9CfuZ4IAEiafpOBCe97bzoKowHCGk5lG/7kR4WF/eknnBlHW9m8q7t10mKq75kruPLtbSDqgRTw=="],
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.4", "", { "dependencies": { "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" } }, "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.4", "", { "dependencies": { "@nodelib/fs.stat": "2.0.4", "run-parallel": "^1.1.9" } }, "sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA=="],
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.4", "", {}, "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q=="], "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.4", "", {}, "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q=="],
@@ -282,8 +241,6 @@
"@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="], "@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/babel__core": ["@types/babel__core@7.1.12", "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ=="], "@types/babel__core": ["@types/babel__core@7.1.12", "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ=="],
"@types/babel__generator": ["@types/babel__generator@7.6.2", "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ=="], "@types/babel__generator": ["@types/babel__generator@7.6.2", "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.2.tgz", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ=="],
@@ -816,7 +773,7 @@
"github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="],
"glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="], "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"glob-parent": ["glob-parent@5.1.1", "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ=="], "glob-parent": ["glob-parent@5.1.1", "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ=="],
@@ -904,7 +861,7 @@
"inflight": ["inflight@1.0.6", "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="], "inflight": ["inflight@1.0.6", "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk="],
"inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], "inherits": ["inherits@2.0.3", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz", {}, "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="],
"ini": ["ini@1.3.8", "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], "ini": ["ini@1.3.8", "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
@@ -1146,7 +1103,7 @@
"lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="], "lowercase-keys": ["lowercase-keys@2.0.0", "", {}, "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="],
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
"lunr": ["lunr@2.3.9", "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz", {}, "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="], "lunr": ["lunr@2.3.9", "https://registry.yarnpkg.com/lunr/-/lunr-2.3.9.tgz", {}, "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="],
@@ -1198,7 +1155,7 @@
"minimist": ["minimist@1.2.5", "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz", {}, "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="], "minimist": ["minimist@1.2.5", "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz", {}, "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="],
"minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
"minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="], "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="],
@@ -1328,6 +1285,8 @@
"package-json": ["package-json@6.5.0", "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz", { "dependencies": { "got": "^9.6.0", "registry-auth-token": "^4.0.0", "registry-url": "^5.0.0", "semver": "^6.2.0" } }, "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ=="], "package-json": ["package-json@6.5.0", "https://registry.yarnpkg.com/package-json/-/package-json-6.5.0.tgz", { "dependencies": { "got": "^9.6.0", "registry-auth-token": "^4.0.0", "registry-url": "^5.0.0", "semver": "^6.2.0" } }, "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
"packet-reader": ["packet-reader@1.0.0", "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz", {}, "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="], "packet-reader": ["packet-reader@1.0.0", "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz", {}, "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
@@ -1358,6 +1317,8 @@
"path-parse": ["path-parse@1.0.6", "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz", {}, "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="], "path-parse": ["path-parse@1.0.6", "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz", {}, "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="],
"path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
"path-to-regexp": ["path-to-regexp@0.1.7", "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz", {}, "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="], "path-to-regexp": ["path-to-regexp@0.1.7", "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz", {}, "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="],
"path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="],
@@ -1500,7 +1461,7 @@
"reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="], "reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="],
"rimraf": ["rimraf@3.0.2", "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": "bin.js" }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], "rimraf": ["rimraf@6.1.3", "", { "dependencies": { "glob": "^13.0.3", "package-json-from-dist": "^1.0.1" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA=="],
"routing-controllers": ["routing-controllers@0.9.0-alpha.6", "https://registry.yarnpkg.com/routing-controllers/-/routing-controllers-0.9.0-alpha.6.tgz", { "dependencies": { "body-parser": "^1.19.0", "cookie": "^0.4.0", "express": "^4.17.1", "express-session": "^1.17.1", "glob": "^7.1.4", "koa": "^2.8.2", "koa-bodyparser": "^4.2.1", "koa-multer": "^1.0.2", "koa-router": "^7.4.0", "multer": "^1.4.2", "reflect-metadata": "^0.1.13", "template-url": "^1.0.0" }, "optionalDependencies": { "body-parser": "^1.19.0", "express": "^4.17.1", "koa": "^2.8.2", "koa-bodyparser": "^4.2.1", "koa-multer": "^1.0.2", "koa-router": "^7.4.0", "multer": "^1.4.2" }, "peerDependencies": { "class-transformer": "^0.2.3", "class-validator": "^0.12.2" } }, "sha512-KnZ7SB6S1q0eX2mVLSelSwB3Gf3cmC4Ul6krDZ9j7+tUa12BPzblmTkuiIDwmhNtbZLXJQCgTD7pj18iqW57Ww=="], "routing-controllers": ["routing-controllers@0.9.0-alpha.6", "https://registry.yarnpkg.com/routing-controllers/-/routing-controllers-0.9.0-alpha.6.tgz", { "dependencies": { "body-parser": "^1.19.0", "cookie": "^0.4.0", "express": "^4.17.1", "express-session": "^1.17.1", "glob": "^7.1.4", "koa": "^2.8.2", "koa-bodyparser": "^4.2.1", "koa-multer": "^1.0.2", "koa-router": "^7.4.0", "multer": "^1.4.2", "reflect-metadata": "^0.1.13", "template-url": "^1.0.0" }, "optionalDependencies": { "body-parser": "^1.19.0", "express": "^4.17.1", "koa": "^2.8.2", "koa-bodyparser": "^4.2.1", "koa-multer": "^1.0.2", "koa-router": "^7.4.0", "multer": "^1.4.2" }, "peerDependencies": { "class-transformer": "^0.2.3", "class-validator": "^0.12.2" } }, "sha512-KnZ7SB6S1q0eX2mVLSelSwB3Gf3cmC4Ul6krDZ9j7+tUa12BPzblmTkuiIDwmhNtbZLXJQCgTD7pj18iqW57Ww=="],
@@ -1768,7 +1729,7 @@
"utils-merge": ["utils-merge@1.0.1", "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz", {}, "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="], "utils-merge": ["utils-merge@1.0.1", "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz", {}, "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="],
"uuid": ["uuid@8.3.2", "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz", { "bin": "dist/bin/uuid" }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], "uuid": ["uuid@8.3.1", "", { "bin": "dist/bin/uuid" }, "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="],
"v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="], "v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="],
@@ -1868,12 +1829,6 @@
"@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.12.11", "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", {}, "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="], "@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.12.11", "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", {}, "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw=="],
"@emnapi/core/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@emnapi/runtime/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
"@jest/console/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="], "@jest/console/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="],
@@ -1884,6 +1839,8 @@
"@jest/core/jest-message-util": ["jest-message-util@26.6.2", "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz", { "dependencies": { "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "pretty-format": "^26.6.2", "slash": "^3.0.0", "stack-utils": "^2.0.2" } }, "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA=="], "@jest/core/jest-message-util": ["jest-message-util@26.6.2", "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz", { "dependencies": { "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "pretty-format": "^26.6.2", "slash": "^3.0.0", "stack-utils": "^2.0.2" } }, "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA=="],
"@jest/core/rimraf": ["rimraf@3.0.2", "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": "bin.js" }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"@jest/environment/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="], "@jest/environment/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="],
"@jest/environment/jest-mock": ["jest-mock@26.6.2", "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "@types/node": "*" } }, "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew=="], "@jest/environment/jest-mock": ["jest-mock@26.6.2", "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "@types/node": "*" } }, "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew=="],
@@ -1898,14 +1855,16 @@
"@jest/pattern/jest-regex-util": ["jest-regex-util@30.0.1", "", {}, "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA=="], "@jest/pattern/jest-regex-util": ["jest-regex-util@30.0.1", "", {}, "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA=="],
"@jest/reporters/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"@jest/types/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="], "@jest/types/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="],
"@npmcli/move-file/rimraf": ["rimraf@3.0.2", "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": "bin.js" }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@5.5.0", "", { "dependencies": { "@types/node": ">= 8" } }, "sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ=="], "@octokit/plugin-rest-endpoint-methods/@octokit/types": ["@octokit/types@5.5.0", "", { "dependencies": { "@types/node": ">= 8" } }, "sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ=="],
"@octokit/types/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="], "@octokit/types/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="],
"@tybys/wasm-util/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@types/body-parser/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="], "@types/body-parser/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="],
"@types/cacheable-request/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="], "@types/cacheable-request/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="],
@@ -1936,6 +1895,8 @@
"bcrypt-pbkdf/tweetnacl": ["tweetnacl@0.14.5", "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz", {}, "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="], "bcrypt-pbkdf/tweetnacl": ["tweetnacl@0.14.5", "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz", {}, "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="],
"bl/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"bl/readable-stream": ["readable-stream@3.6.0", "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA=="], "bl/readable-stream": ["readable-stream@3.6.0", "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA=="],
"body-parser/debug": ["debug@2.6.9", "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "body-parser/debug": ["debug@2.6.9", "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
@@ -1946,10 +1907,14 @@
"busboy/readable-stream": ["readable-stream@1.1.14", "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } }, "sha1-fPTFTvZI44EwhMY23SB54WbAgdk="], "busboy/readable-stream": ["readable-stream@1.1.14", "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", "isarray": "0.0.1", "string_decoder": "~0.10.x" } }, "sha1-fPTFTvZI44EwhMY23SB54WbAgdk="],
"cacache/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"cacache/lru-cache": ["lru-cache@6.0.0", "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "cacache/lru-cache": ["lru-cache@6.0.0", "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
"cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
"cacache/rimraf": ["rimraf@3.0.2", "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": "bin.js" }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"chalk/ansi-styles": ["ansi-styles@4.3.0", "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "chalk/ansi-styles": ["ansi-styles@4.3.0", "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"class-utils/define-property": ["define-property@0.2.5", "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz", { "dependencies": { "is-descriptor": "^0.1.0" } }, "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY="], "class-utils/define-property": ["define-property@0.2.5", "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz", { "dependencies": { "is-descriptor": "^0.1.0" } }, "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY="],
@@ -1960,6 +1925,8 @@
"co-body/raw-body": ["raw-body@2.4.1", "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz", { "dependencies": { "bytes": "3.1.0", "http-errors": "1.7.3", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA=="], "co-body/raw-body": ["raw-body@2.4.1", "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.1.tgz", { "dependencies": { "bytes": "3.1.0", "http-errors": "1.7.3", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA=="],
"concat-stream/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"cookie-parser/cookie": ["cookie@0.4.0", "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz", {}, "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="], "cookie-parser/cookie": ["cookie@0.4.0", "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz", {}, "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg=="],
"cookies/depd": ["depd@2.0.0", "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "cookies/depd": ["depd@2.0.0", "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
@@ -2014,6 +1981,8 @@
"gauge/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], "gauge/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"glob/minimatch": ["minimatch@10.2.2", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw=="],
"global-dirs/ini": ["ini@1.3.7", "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="], "global-dirs/ini": ["ini@1.3.7", "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz", {}, "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ=="],
"has-ansi/ansi-regex": ["ansi-regex@2.1.1", "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz", {}, "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="], "has-ansi/ansi-regex": ["ansi-regex@2.1.1", "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz", {}, "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="],
@@ -2024,8 +1993,6 @@
"http-assert/http-errors": ["http-errors@1.7.3", "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" } }, "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw=="], "http-assert/http-errors": ["http-errors@1.7.3", "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" } }, "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw=="],
"http-errors/inherits": ["inherits@2.0.3", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz", {}, "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="],
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], "import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"is-extendable/is-plain-object": ["is-plain-object@2.0.4", "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="], "is-extendable/is-plain-object": ["is-plain-object@2.0.4", "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="],
@@ -2034,6 +2001,8 @@
"jest-cli/yargs": ["yargs@15.4.1", "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], "jest-cli/yargs": ["yargs@15.4.1", "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
"jest-config/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"jest-config/pretty-format": ["pretty-format@26.6.2", "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="], "jest-config/pretty-format": ["pretty-format@26.6.2", "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="],
"jest-diff/pretty-format": ["pretty-format@26.6.2", "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="], "jest-diff/pretty-format": ["pretty-format@26.6.2", "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="],
@@ -2084,6 +2053,8 @@
"jest-runner/jest-message-util": ["jest-message-util@26.6.2", "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz", { "dependencies": { "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "pretty-format": "^26.6.2", "slash": "^3.0.0", "stack-utils": "^2.0.2" } }, "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA=="], "jest-runner/jest-message-util": ["jest-message-util@26.6.2", "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz", { "dependencies": { "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "pretty-format": "^26.6.2", "slash": "^3.0.0", "stack-utils": "^2.0.2" } }, "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA=="],
"jest-runtime/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"jest-runtime/jest-message-util": ["jest-message-util@26.6.2", "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz", { "dependencies": { "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "pretty-format": "^26.6.2", "slash": "^3.0.0", "stack-utils": "^2.0.2" } }, "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA=="], "jest-runtime/jest-message-util": ["jest-message-util@26.6.2", "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz", { "dependencies": { "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "pretty-format": "^26.6.2", "slash": "^3.0.0", "stack-utils": "^2.0.2" } }, "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA=="],
"jest-runtime/jest-mock": ["jest-mock@26.6.2", "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "@types/node": "*" } }, "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew=="], "jest-runtime/jest-mock": ["jest-mock@26.6.2", "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "@types/node": "*" } }, "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew=="],
@@ -2138,8 +2109,6 @@
"koa-router/path-to-regexp": ["path-to-regexp@1.8.0", "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz", { "dependencies": { "isarray": "0.0.1" } }, "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA=="], "koa-router/path-to-regexp": ["path-to-regexp@1.8.0", "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz", { "dependencies": { "isarray": "0.0.1" } }, "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA=="],
"lru-cache/yallist": ["yallist@3.1.1", "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"make-dir/semver": ["semver@6.3.0", "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz", { "bin": "bin/semver.js" }, "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="], "make-dir/semver": ["semver@6.3.0", "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz", { "bin": "bin/semver.js" }, "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="],
"make-fetch-happen/lru-cache": ["lru-cache@6.0.0", "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "make-fetch-happen/lru-cache": ["lru-cache@6.0.0", "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
@@ -2162,16 +2131,24 @@
"multer/mkdirp": ["mkdirp@0.5.5", "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz", { "dependencies": { "minimist": "^1.2.5" }, "bin": "bin/cmd.js" }, "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ=="], "multer/mkdirp": ["mkdirp@0.5.5", "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz", { "dependencies": { "minimist": "^1.2.5" }, "bin": "bin/cmd.js" }, "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ=="],
"node-gyp/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"node-gyp/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "node-gyp/graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"node-gyp/rimraf": ["rimraf@3.0.2", "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz", { "dependencies": { "glob": "^7.1.3" }, "bin": "bin.js" }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"node-notifier/semver": ["semver@7.3.4", "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": "bin/semver.js" }, "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw=="], "node-notifier/semver": ["semver@7.3.4", "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": "bin/semver.js" }, "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw=="],
"node-notifier/uuid": ["uuid@8.3.2", "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz", { "bin": "dist/bin/uuid" }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
"normalize-package-data/semver": ["semver@5.7.1", "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz", { "bin": "bin/semver" }, "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="], "normalize-package-data/semver": ["semver@5.7.1", "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz", { "bin": "bin/semver" }, "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ=="],
"object-copy/define-property": ["define-property@0.2.5", "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz", { "dependencies": { "is-descriptor": "^0.1.0" } }, "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY="], "object-copy/define-property": ["define-property@0.2.5", "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz", { "dependencies": { "is-descriptor": "^0.1.0" } }, "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY="],
"object-copy/kind-of": ["kind-of@3.2.2", "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="], "object-copy/kind-of": ["kind-of@3.2.2", "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="],
"onigasm/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"openapi3-ts/yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], "openapi3-ts/yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
"os-locale/execa": ["execa@1.0.0", "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz", { "dependencies": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" } }, "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA=="], "os-locale/execa": ["execa@1.0.0", "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz", { "dependencies": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", "is-stream": "^1.1.0", "npm-run-path": "^2.0.0", "p-finally": "^1.0.0", "signal-exit": "^3.0.0", "strip-eof": "^1.0.0" } }, "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA=="],
@@ -2194,9 +2171,9 @@
"read-pkg-up/type-fest": ["type-fest@0.8.1", "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="], "read-pkg-up/type-fest": ["type-fest@0.8.1", "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="],
"release-it/semver": ["semver@7.3.2", "", { "bin": "bin/semver.js" }, "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="], "readable-stream/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"release-it/uuid": ["uuid@8.3.1", "", { "bin": "dist/bin/uuid" }, "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="], "release-it/semver": ["semver@7.3.2", "", { "bin": "bin/semver.js" }, "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="],
"request/form-data": ["form-data@2.3.3", "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ=="], "request/form-data": ["form-data@2.3.3", "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ=="],
@@ -2210,6 +2187,8 @@
"request-promise-native/tough-cookie": ["tough-cookie@2.5.0", "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz", { "dependencies": { "psl": "^1.1.28", "punycode": "^2.1.1" } }, "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g=="], "request-promise-native/tough-cookie": ["tough-cookie@2.5.0", "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz", { "dependencies": { "psl": "^1.1.28", "punycode": "^2.1.1" } }, "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g=="],
"routing-controllers/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"routing-controllers-openapi/openapi3-ts": ["openapi3-ts@1.4.0", "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-1.4.0.tgz", {}, "sha512-8DmE2oKayvSkIR3XSZ4+pRliBsx19bSNeIzkTPswY8r4wvjX86bMxsORdqwAwMxE8PefOcSAT2auvi/0TZe9yA=="], "routing-controllers-openapi/openapi3-ts": ["openapi3-ts@1.4.0", "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-1.4.0.tgz", {}, "sha512-8DmE2oKayvSkIR3XSZ4+pRliBsx19bSNeIzkTPswY8r4wvjX86bMxsORdqwAwMxE8PefOcSAT2auvi/0TZe9yA=="],
"routing-controllers-openapi/path-to-regexp": ["path-to-regexp@2.4.0", "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz", {}, "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w=="], "routing-controllers-openapi/path-to-regexp": ["path-to-regexp@2.4.0", "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz", {}, "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w=="],
@@ -2238,8 +2217,12 @@
"set-value/is-plain-object": ["is-plain-object@2.0.4", "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="], "set-value/is-plain-object": ["is-plain-object@2.0.4", "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz", { "dependencies": { "isobject": "^3.0.1" } }, "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og=="],
"sha.js/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"sha.js/safe-buffer": ["safe-buffer@5.2.1", "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "sha.js/safe-buffer": ["safe-buffer@5.2.1", "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"shelljs/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"snapdragon/debug": ["debug@2.6.9", "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "snapdragon/debug": ["debug@2.6.9", "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"snapdragon/define-property": ["define-property@0.2.5", "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz", { "dependencies": { "is-descriptor": "^0.1.0" } }, "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY="], "snapdragon/define-property": ["define-property@0.2.5", "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz", { "dependencies": { "is-descriptor": "^0.1.0" } }, "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY="],
@@ -2266,10 +2249,16 @@
"static-extend/define-property": ["define-property@0.2.5", "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz", { "dependencies": { "is-descriptor": "^0.1.0" } }, "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY="], "static-extend/define-property": ["define-property@0.2.5", "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz", { "dependencies": { "is-descriptor": "^0.1.0" } }, "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY="],
"tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="],
"tar-fs/chownr": ["chownr@1.1.4", "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], "tar-fs/chownr": ["chownr@1.1.4", "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
"tar-stream/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"tar-stream/readable-stream": ["readable-stream@3.6.0", "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA=="], "tar-stream/readable-stream": ["readable-stream@3.6.0", "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA=="],
"test-exclude/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"to-object-path/kind-of": ["kind-of@3.2.2", "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="], "to-object-path/kind-of": ["kind-of@3.2.2", "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="],
"ts-jest/@types/jest": ["@types/jest@26.0.19", "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.19.tgz", { "dependencies": { "jest-diff": "^26.0.0", "pretty-format": "^26.0.0" } }, "sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ=="], "ts-jest/@types/jest": ["@types/jest@26.0.19", "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.19.tgz", { "dependencies": { "jest-diff": "^26.0.0", "pretty-format": "^26.0.0" } }, "sha512-jqHoirTG61fee6v6rwbnEuKhpSKih0tuhqeFbCmMmErhtu3BYlOZaXWjffgOstMM4S/3iQD31lI5bGLTrs97yQ=="],
@@ -2278,8 +2267,12 @@
"typedoc/fs-extra": ["fs-extra@9.0.1", "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^1.0.0" } }, "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ=="], "typedoc/fs-extra": ["fs-extra@9.0.1", "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^1.0.0" } }, "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ=="],
"typeorm/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"typeorm/tslib": ["tslib@1.14.1", "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], "typeorm/tslib": ["tslib@1.14.1", "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
"typeorm-seeding/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"typeorm-seeding/ora": ["ora@4.0.3", "https://registry.yarnpkg.com/ora/-/ora-4.0.3.tgz", { "dependencies": { "chalk": "^3.0.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.2.0", "is-interactive": "^1.0.0", "log-symbols": "^3.0.0", "mute-stream": "0.0.8", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg=="], "typeorm-seeding/ora": ["ora@4.0.3", "https://registry.yarnpkg.com/ora/-/ora-4.0.3.tgz", { "dependencies": { "chalk": "^3.0.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.2.0", "is-interactive": "^1.0.0", "log-symbols": "^3.0.0", "mute-stream": "0.0.8", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-fnDebVFyz309A73cqCipVL1fBZewq4vwgSHfxh43vVy31mbyoQ8sCH3Oeaog/owYOs/lLlGVPCISQonTneg6Pg=="],
"typeorm-seeding/yargs": ["yargs@15.3.1", "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.1" } }, "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA=="], "typeorm-seeding/yargs": ["yargs@15.3.1", "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.1" } }, "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA=="],
@@ -2318,6 +2311,8 @@
"@jest/core/jest-message-util/stack-utils": ["stack-utils@2.0.3", "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw=="], "@jest/core/jest-message-util/stack-utils": ["stack-utils@2.0.3", "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw=="],
"@jest/core/rimraf/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"@jest/fake-timers/jest-message-util/@types/stack-utils": ["@types/stack-utils@2.0.0", "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz", {}, "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw=="], "@jest/fake-timers/jest-message-util/@types/stack-utils": ["@types/stack-utils@2.0.0", "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz", {}, "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw=="],
"@jest/fake-timers/jest-message-util/pretty-format": ["pretty-format@26.6.2", "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="], "@jest/fake-timers/jest-message-util/pretty-format": ["pretty-format@26.6.2", "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="],
@@ -2330,6 +2325,10 @@
"@jest/globals/expect/jest-message-util": ["jest-message-util@26.6.2", "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz", { "dependencies": { "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "pretty-format": "^26.6.2", "slash": "^3.0.0", "stack-utils": "^2.0.2" } }, "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA=="], "@jest/globals/expect/jest-message-util": ["jest-message-util@26.6.2", "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz", { "dependencies": { "@babel/code-frame": "^7.0.0", "@jest/types": "^26.6.2", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "micromatch": "^4.0.2", "pretty-format": "^26.6.2", "slash": "^3.0.0", "stack-utils": "^2.0.2" } }, "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA=="],
"@jest/reporters/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"@npmcli/move-file/rimraf/glob": ["glob@7.1.6", "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA=="],
"@octokit/plugin-rest-endpoint-methods/@octokit/types/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="], "@octokit/plugin-rest-endpoint-methods/@octokit/types/@types/node": ["@types/node@14.14.14", "https://registry.yarnpkg.com/@types/node/-/node-14.14.14.tgz", {}, "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ=="],
"ansi-align/string-width/emoji-regex": ["emoji-regex@7.0.3", "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz", {}, "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="], "ansi-align/string-width/emoji-regex": ["emoji-regex@7.0.3", "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz", {}, "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="],
@@ -2338,6 +2337,8 @@
"ansi-align/string-width/strip-ansi": ["strip-ansi@5.2.0", "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="], "ansi-align/string-width/strip-ansi": ["strip-ansi@5.2.0", "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="],
"are-we-there-yet/readable-stream/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"are-we-there-yet/readable-stream/string_decoder": ["string_decoder@1.3.0", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], "are-we-there-yet/readable-stream/string_decoder": ["string_decoder@1.3.0", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"bl/readable-stream/string_decoder": ["string_decoder@1.3.0", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], "bl/readable-stream/string_decoder": ["string_decoder@1.3.0", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
@@ -2346,10 +2347,14 @@
"boxen/chalk/ansi-styles": ["ansi-styles@4.3.0", "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "boxen/chalk/ansi-styles": ["ansi-styles@4.3.0", "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"busboy/readable-stream/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"busboy/readable-stream/isarray": ["isarray@0.0.1", "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz", {}, "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="], "busboy/readable-stream/isarray": ["isarray@0.0.1", "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz", {}, "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="],
"busboy/readable-stream/string_decoder": ["string_decoder@0.10.31", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz", {}, "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="], "busboy/readable-stream/string_decoder": ["string_decoder@0.10.31", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz", {}, "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="],
"cacache/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"class-utils/define-property/is-descriptor": ["is-descriptor@0.1.6", "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz", { "dependencies": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", "kind-of": "^5.0.0" } }, "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg=="], "class-utils/define-property/is-descriptor": ["is-descriptor@0.1.6", "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz", { "dependencies": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", "kind-of": "^5.0.0" } }, "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg=="],
"co-body/raw-body/http-errors": ["http-errors@1.7.3", "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" } }, "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw=="], "co-body/raw-body/http-errors": ["http-errors@1.7.3", "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz", { "dependencies": { "depd": "~1.1.2", "inherits": "2.0.4", "setprototypeof": "1.1.1", "statuses": ">= 1.5.0 < 2", "toidentifier": "1.0.0" } }, "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw=="],
@@ -2366,6 +2371,8 @@
"cp-cli/yargs/yargs-parser": ["yargs-parser@11.1.1", "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ=="], "cp-cli/yargs/yargs-parser": ["yargs-parser@11.1.1", "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ=="],
"dicer/readable-stream/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"dicer/readable-stream/isarray": ["isarray@0.0.1", "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz", {}, "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="], "dicer/readable-stream/isarray": ["isarray@0.0.1", "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz", {}, "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="],
"dicer/readable-stream/string_decoder": ["string_decoder@0.10.31", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz", {}, "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="], "dicer/readable-stream/string_decoder": ["string_decoder@0.10.31", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz", {}, "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="],
@@ -2394,8 +2401,12 @@
"gauge/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "gauge/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"glob/minimatch/brace-expansion": ["brace-expansion@5.0.2", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw=="],
"has-values/is-number/kind-of": ["kind-of@3.2.2", "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="], "has-values/is-number/kind-of": ["kind-of@3.2.2", "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ="],
"http-assert/http-errors/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"jest-cli/yargs/cliui": ["cliui@6.0.0", "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], "jest-cli/yargs/cliui": ["cliui@6.0.0", "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
"jest-cli/yargs/find-up": ["find-up@4.1.0", "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], "jest-cli/yargs/find-up": ["find-up@4.1.0", "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
@@ -2406,6 +2417,8 @@
"jest-cli/yargs/yargs-parser": ["yargs-parser@18.1.3", "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], "jest-cli/yargs/yargs-parser": ["yargs-parser@18.1.3", "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
"jest-config/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"jest-config/pretty-format/ansi-styles": ["ansi-styles@4.3.0", "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "jest-config/pretty-format/ansi-styles": ["ansi-styles@4.3.0", "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"jest-config/pretty-format/react-is": ["react-is@17.0.1", "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz", {}, "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA=="], "jest-config/pretty-format/react-is": ["react-is@17.0.1", "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz", {}, "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA=="],
@@ -2466,6 +2479,8 @@
"jest-runner/jest-message-util/stack-utils": ["stack-utils@2.0.3", "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw=="], "jest-runner/jest-message-util/stack-utils": ["stack-utils@2.0.3", "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw=="],
"jest-runtime/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"jest-runtime/jest-message-util/@types/stack-utils": ["@types/stack-utils@2.0.0", "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz", {}, "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw=="], "jest-runtime/jest-message-util/@types/stack-utils": ["@types/stack-utils@2.0.0", "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz", {}, "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw=="],
"jest-runtime/jest-message-util/pretty-format": ["pretty-format@26.6.2", "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="], "jest-runtime/jest-message-util/pretty-format": ["pretty-format@26.6.2", "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="],
@@ -2506,18 +2521,26 @@
"koa-multer/multer/object-assign": ["object-assign@3.0.0", "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz", {}, "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I="], "koa-multer/multer/object-assign": ["object-assign@3.0.0", "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz", {}, "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I="],
"koa-router/http-errors/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"koa-router/http-errors/setprototypeof": ["setprototypeof@1.2.0", "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], "koa-router/http-errors/setprototypeof": ["setprototypeof@1.2.0", "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"koa-router/path-to-regexp/isarray": ["isarray@0.0.1", "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz", {}, "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="], "koa-router/path-to-regexp/isarray": ["isarray@0.0.1", "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz", {}, "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="],
"koa/debug/ms": ["ms@2.0.0", "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz", {}, "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="], "koa/debug/ms": ["ms@2.0.0", "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz", {}, "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="],
"koa/http-errors/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"koa/http-errors/setprototypeof": ["setprototypeof@1.2.0", "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], "koa/http-errors/setprototypeof": ["setprototypeof@1.2.0", "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
"node-gyp/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"node-notifier/semver/lru-cache": ["lru-cache@6.0.0", "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "node-notifier/semver/lru-cache": ["lru-cache@6.0.0", "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="],
"object-copy/define-property/is-descriptor": ["is-descriptor@0.1.6", "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz", { "dependencies": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", "kind-of": "^5.0.0" } }, "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg=="], "object-copy/define-property/is-descriptor": ["is-descriptor@0.1.6", "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz", { "dependencies": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", "kind-of": "^5.0.0" } }, "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg=="],
"onigasm/lru-cache/yallist": ["yallist@3.1.1", "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"os-locale/execa/cross-spawn": ["cross-spawn@6.0.5", "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz", { "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } }, "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ=="], "os-locale/execa/cross-spawn": ["cross-spawn@6.0.5", "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz", { "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } }, "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ=="],
"os-locale/execa/get-stream": ["get-stream@4.1.0", "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz", { "dependencies": { "pump": "^3.0.0" } }, "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w=="], "os-locale/execa/get-stream": ["get-stream@4.1.0", "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz", { "dependencies": { "pump": "^3.0.0" } }, "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w=="],
@@ -2548,6 +2571,8 @@
"read-pkg-up/find-up/locate-path": ["locate-path@5.0.0", "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "read-pkg-up/find-up/locate-path": ["locate-path@5.0.0", "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
"routing-controllers/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"sane/anymatch/normalize-path": ["normalize-path@2.1.1", "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz", { "dependencies": { "remove-trailing-separator": "^1.0.1" } }, "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk="], "sane/anymatch/normalize-path": ["normalize-path@2.1.1", "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz", { "dependencies": { "remove-trailing-separator": "^1.0.1" } }, "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk="],
"sane/execa/cross-spawn": ["cross-spawn@6.0.5", "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz", { "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } }, "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ=="], "sane/execa/cross-spawn": ["cross-spawn@6.0.5", "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz", { "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" } }, "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ=="],
@@ -2564,24 +2589,34 @@
"send/debug/ms": ["ms@2.0.0", "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz", {}, "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="], "send/debug/ms": ["ms@2.0.0", "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz", {}, "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="],
"send/http-errors/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"shelljs/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"snapdragon/debug/ms": ["ms@2.0.0", "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz", {}, "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="], "snapdragon/debug/ms": ["ms@2.0.0", "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz", {}, "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="],
"snapdragon/define-property/is-descriptor": ["is-descriptor@0.1.6", "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz", { "dependencies": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", "kind-of": "^5.0.0" } }, "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg=="], "snapdragon/define-property/is-descriptor": ["is-descriptor@0.1.6", "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz", { "dependencies": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", "kind-of": "^5.0.0" } }, "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg=="],
"snapdragon/extend-shallow/is-extendable": ["is-extendable@0.1.1", "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz", {}, "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="], "snapdragon/extend-shallow/is-extendable": ["is-extendable@0.1.1", "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz", {}, "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik="],
"split2/readable-stream/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"split2/readable-stream/string_decoder": ["string_decoder@1.3.0", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], "split2/readable-stream/string_decoder": ["string_decoder@1.3.0", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"static-extend/define-property/is-descriptor": ["is-descriptor@0.1.6", "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz", { "dependencies": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", "kind-of": "^5.0.0" } }, "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg=="], "static-extend/define-property/is-descriptor": ["is-descriptor@0.1.6", "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz", { "dependencies": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", "kind-of": "^5.0.0" } }, "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg=="],
"tar-stream/readable-stream/string_decoder": ["string_decoder@1.3.0", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], "tar-stream/readable-stream/string_decoder": ["string_decoder@1.3.0", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="],
"test-exclude/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ts-jest/@types/jest/pretty-format": ["pretty-format@26.6.2", "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="], "ts-jest/@types/jest/pretty-format": ["pretty-format@26.6.2", "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="],
"typedoc/fs-extra/jsonfile": ["jsonfile@6.1.0", "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz", { "dependencies": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="], "typedoc/fs-extra/jsonfile": ["jsonfile@6.1.0", "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz", { "dependencies": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="],
"typedoc/fs-extra/universalify": ["universalify@1.0.0", "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz", {}, "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug=="], "typedoc/fs-extra/universalify": ["universalify@1.0.0", "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz", {}, "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug=="],
"typeorm-seeding/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"typeorm-seeding/ora/chalk": ["chalk@3.0.0", "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], "typeorm-seeding/ora/chalk": ["chalk@3.0.0", "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="],
"typeorm-seeding/ora/log-symbols": ["log-symbols@3.0.0", "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz", { "dependencies": { "chalk": "^2.4.2" } }, "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ=="], "typeorm-seeding/ora/log-symbols": ["log-symbols@3.0.0", "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz", { "dependencies": { "chalk": "^2.4.2" } }, "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ=="],
@@ -2596,6 +2631,8 @@
"typeorm-seeding/yargs/yargs-parser": ["yargs-parser@18.1.3", "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], "typeorm-seeding/yargs/yargs-parser": ["yargs-parser@18.1.3", "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
"typeorm/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"unset-value/has-value/has-values": ["has-values@0.1.4", "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz", {}, "sha1-bWHeldkd/Km5oCCJrThL/49it3E="], "unset-value/has-value/has-values": ["has-values@0.1.4", "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz", {}, "sha1-bWHeldkd/Km5oCCJrThL/49it3E="],
"unset-value/has-value/isobject": ["isobject@2.1.0", "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz", { "dependencies": { "isarray": "1.0.0" } }, "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk="], "unset-value/has-value/isobject": ["isobject@2.1.0", "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz", { "dependencies": { "isarray": "1.0.0" } }, "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk="],
@@ -2626,6 +2663,8 @@
"@jest/core/jest-message-util/stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], "@jest/core/jest-message-util/stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
"@jest/core/rimraf/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"@jest/fake-timers/jest-message-util/pretty-format/ansi-styles": ["ansi-styles@4.3.0", "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "@jest/fake-timers/jest-message-util/pretty-format/ansi-styles": ["ansi-styles@4.3.0", "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"@jest/fake-timers/jest-message-util/pretty-format/react-is": ["react-is@17.0.1", "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz", {}, "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA=="], "@jest/fake-timers/jest-message-util/pretty-format/react-is": ["react-is@17.0.1", "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz", {}, "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA=="],
@@ -2640,6 +2679,8 @@
"@jest/globals/expect/jest-message-util/stack-utils": ["stack-utils@2.0.3", "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw=="], "@jest/globals/expect/jest-message-util/stack-utils": ["stack-utils@2.0.3", "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.3.tgz", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw=="],
"@npmcli/move-file/rimraf/glob/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@4.1.0", "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz", {}, "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="], "ansi-align/string-width/strip-ansi/ansi-regex": ["ansi-regex@4.1.0", "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz", {}, "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="],
"are-we-there-yet/readable-stream/string_decoder/safe-buffer": ["safe-buffer@5.2.1", "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "are-we-there-yet/readable-stream/string_decoder/safe-buffer": ["safe-buffer@5.2.1", "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
@@ -2652,6 +2693,8 @@
"class-utils/define-property/is-descriptor/kind-of": ["kind-of@5.1.0", "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz", {}, "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="], "class-utils/define-property/is-descriptor/kind-of": ["kind-of@5.1.0", "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz", {}, "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw=="],
"co-body/raw-body/http-errors/inherits": ["inherits@2.0.4", "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
"cp-cli/yargs/cliui/strip-ansi": ["strip-ansi@4.0.0", "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz", { "dependencies": { "ansi-regex": "^3.0.0" } }, "sha1-qEeQIusaw2iocTibY1JixQXuNo8="], "cp-cli/yargs/cliui/strip-ansi": ["strip-ansi@4.0.0", "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz", { "dependencies": { "ansi-regex": "^3.0.0" } }, "sha1-qEeQIusaw2iocTibY1JixQXuNo8="],
"cp-cli/yargs/cliui/wrap-ansi": ["wrap-ansi@2.1.0", "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz", { "dependencies": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" } }, "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU="], "cp-cli/yargs/cliui/wrap-ansi": ["wrap-ansi@2.1.0", "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz", { "dependencies": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" } }, "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU="],
@@ -2676,6 +2719,8 @@
"expect/jest-util/chalk/ansi-styles": ["ansi-styles@4.3.0", "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], "expect/jest-util/chalk/ansi-styles": ["ansi-styles@4.3.0", "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.3", "", {}, "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g=="],
"jest-cli/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], "jest-cli/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
"jest-cli/yargs/find-up/locate-path": ["locate-path@5.0.0", "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "jest-cli/yargs/find-up/locate-path": ["locate-path@5.0.0", "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],

View File

@@ -1,32 +1,3 @@
# @node-rs/argon2
**Author**: undefined
**Repo**: [object Object]
**License**: MIT
**Description**: RustCrypto: Argon2 binding for Node.js
## License Text
MIT License
Copyright (c) 2020-present LongYinan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# @odit/class-validator-jsonschema # @odit/class-validator-jsonschema
**Author**: Aleksi Pekkala <aleksipekkala@gmail.com> **Author**: Aleksi Pekkala <aleksipekkala@gmail.com>
**Repo**: git@github.com:epiphone/class-validator-jsonschema.git **Repo**: git@github.com:epiphone/class-validator-jsonschema.git
@@ -316,37 +287,6 @@ The above copyright notice and this permission notice shall be included in all c
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# dotenv
**Author**: undefined
**Repo**: [object Object]
**License**: BSD-2-Clause
**Description**: Loads environment variables from .env file
## License Text
Copyright (c) 2015, Scott Motte
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# express # express
**Author**: TJ Holowaychuk <tj@vision-media.ca> **Author**: TJ Holowaychuk <tj@vision-media.ca>
**Repo**: expressjs/express **Repo**: expressjs/express
@@ -878,23 +818,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
# uuid
**Author**: undefined
**Repo**: [object Object]
**License**: MIT
**Description**: RFC4122 (v1, v4, and v5) UUIDs
## License Text
The MIT License (MIT)
Copyright (c) 2010-2020 Robert Kieffer and other contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# validator # validator
**Author**: Chris O'Hara <cohara87@gmail.com> **Author**: Chris O'Hara <cohara87@gmail.com>
**Repo**: [object Object] **Repo**: [object Object]
@@ -1311,25 +1234,65 @@ SOFTWARE.
# rimraf # rimraf
**Author**: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/) **Author**: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)
**Repo**: git://github.com/isaacs/rimraf.git **Repo**: git@github.com:isaacs/rimraf.git
**License**: ISC **License**: BlueOak-1.0.0
**Description**: A deep deletion module for node (like `rm -rf`) **Description**: A deep deletion module for node (like `rm -rf`)
## License Text ## License Text
The ISC License # Blue Oak Model License
Copyright (c) Isaac Z. Schlueter and Contributors Version 1.0.0
Permission to use, copy, modify, and/or distribute this software for any ## Purpose
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES This license gives everyone as much permission to work with
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF this software as possible, while protecting contributors
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR from liability.
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ## Acceptance
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
***As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim.***
# start-server-and-test # start-server-and-test

View File

@@ -1,8 +1,3 @@
const dotenv = require('dotenv');
dotenv.config();
//
// Bun workflow: always compile first, then run from dist/
const SOURCE_PATH = 'dist';
module.exports = { module.exports = {
type: process.env.DB_TYPE, type: process.env.DB_TYPE,
host: process.env.DB_HOST, host: process.env.DB_HOST,
@@ -10,7 +5,7 @@ module.exports = {
username: process.env.DB_USER, username: process.env.DB_USER,
password: process.env.DB_PASSWORD, password: process.env.DB_PASSWORD,
database: process.env.DB_NAME, database: process.env.DB_NAME,
// Always load compiled .js files from dist/ (TypeORM entities have circular deps) // Run directly from TypeScript source (Bun workflow)
entities: [ `${SOURCE_PATH}/**/entities/*.js` ], entities: ["src/models/entities/**/*.ts"],
seeds: [ `${SOURCE_PATH}/**/seeds/*.js` ] seeds: ["src/seeds/**/*.ts"]
}; };

View File

@@ -1,6 +1,6 @@
{ {
"name": "@odit/lfk-backend", "name": "@odit/lfk-backend",
"version": "1.7.1", "version": "1.8.1",
"main": "src/app.ts", "main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend", "repository": "https://git.odit.services/lfk/backend",
"author": { "author": {
@@ -22,7 +22,6 @@
], ],
"license": "CC-BY-NC-SA-4.0", "license": "CC-BY-NC-SA-4.0",
"dependencies": { "dependencies": {
"@node-rs/argon2": "^2.0.2",
"@odit/class-validator-jsonschema": "2.1.1", "@odit/class-validator-jsonschema": "2.1.1",
"axios": "0.21.1", "axios": "0.21.1",
"body-parser": "1.19.0", "body-parser": "1.19.0",
@@ -34,7 +33,6 @@
"cookie-parser": "1.4.5", "cookie-parser": "1.4.5",
"cors": "2.8.5", "cors": "2.8.5",
"csvtojson": "2.0.10", "csvtojson": "2.0.10",
"dotenv": "8.2.0",
"express": "4.17.1", "express": "4.17.1",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "8.5.1",
"libphonenumber-js": "1.9.9", "libphonenumber-js": "1.9.9",
@@ -48,7 +46,6 @@
"typeorm": "0.2.30", "typeorm": "0.2.30",
"typeorm-routing-controllers-extensions": "0.2.0", "typeorm-routing-controllers-extensions": "0.2.0",
"typeorm-seeding": "1.6.1", "typeorm-seeding": "1.6.1",
"uuid": "8.3.2",
"validator": "13.5.2" "validator": "13.5.2"
}, },
"devDependencies": { "devDependencies": {
@@ -64,15 +61,15 @@
"cp-cli": "2.0.0", "cp-cli": "2.0.0",
"jest": "26.6.3", "jest": "26.6.3",
"release-it": "14.2.2", "release-it": "14.2.2",
"rimraf": "3.0.2", "rimraf": "^6.1.3",
"start-server-and-test": "1.11.7", "start-server-and-test": "1.11.7",
"ts-jest": "26.5.0", "ts-jest": "26.5.0",
"typedoc": "0.20.19", "typedoc": "0.20.19",
"typescript": "5.9.3" "typescript": "5.9.3"
}, },
"scripts": { "scripts": {
"dev": "bun scripts/dev_watch.ts", "dev": "bun --watch src/app.ts",
"start": "bun dist/app.js", "start": "bun src/app.ts",
"build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static", "build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static",
"docs": "typedoc --out docs src", "docs": "typedoc --out docs src",
"test": "jest", "test": "jest",

View File

@@ -1,138 +0,0 @@
#!/usr/bin/env bun
/**
* Development watch script for Bun
*
* Watches src/ for changes, rebuilds on change, and restarts the server.
* This is necessary because we must compile TypeScript first due to circular
* TypeORM entity dependencies that Bun's TS loader cannot handle directly.
*/
import { watch } from "fs";
import { spawn } from "child_process";
import consola from "consola";
let serverProcess: ReturnType<typeof spawn> | null = null;
let isRebuilding = false;
let pendingRestart = false;
let debounceTimer: NodeJS.Timeout | null = null;
let watcherReady = false;
function killServer() {
return new Promise<void>((resolve) => {
if (serverProcess) {
consola.info("Stopping server...");
serverProcess.kill();
serverProcess = null;
// Wait for port to be fully released (longer on Windows)
setTimeout(resolve, 2000);
} else {
resolve();
}
});
}
function startServer() {
consola.info("Starting server...");
serverProcess = spawn("bun", ["dist/app.js"], {
stdio: "inherit",
shell: true,
});
serverProcess.on("error", (err) => {
consola.error("Server process error:", err);
});
serverProcess.on("exit", (code) => {
if (code !== null && code !== 0 && code !== 143) {
consola.error(`Server exited with code ${code}`);
}
});
// Enable watcher after initial server start
if (!watcherReady) {
setTimeout(() => {
watcherReady = true;
consola.success("👀 Watching for file changes...");
}, 1000);
}
}
async function rebuild() {
if (isRebuilding) {
pendingRestart = true;
return;
}
isRebuilding = true;
pendingRestart = false;
consola.info("Rebuilding...");
await killServer();
const buildProcess = spawn("bun", ["run", "build"], {
stdio: "inherit",
shell: true,
});
buildProcess.on("exit", (code) => {
isRebuilding = false;
if (code === 0) {
consola.success("Build complete!");
startServer();
if (pendingRestart) {
consola.info("Change detected during build, rebuilding again...");
setTimeout(() => rebuild(), 100);
}
} else {
consola.error(`Build failed with code ${code}`);
}
});
}
// Initial build and start
consola.info("🔄 Development mode - watching for changes...");
rebuild();
// Watch src/ for changes (including subdirectories)
const watcher = watch(
"./src",
{ recursive: true },
(eventType, filename) => {
if (!watcherReady) return; // Ignore changes during initial build
if (filename && filename.endsWith(".ts")) {
// Ignore test files and declaration files
if (filename.endsWith(".spec.ts") || filename.endsWith(".d.ts")) {
return;
}
// Debounce: wait 500ms for multiple rapid changes
if (debounceTimer) {
clearTimeout(debounceTimer);
}
debounceTimer = setTimeout(() => {
consola.info(`File changed: ${filename}`);
rebuild();
}, 500);
}
}
);
// Cleanup on exit
process.on("SIGINT", () => {
consola.info("\nShutting down...");
if (debounceTimer) clearTimeout(debounceTimer);
killServer();
watcher.close();
process.exit(0);
});
process.on("SIGTERM", () => {
if (debounceTimer) clearTimeout(debounceTimer);
killServer();
watcher.close();
process.exit(0);
});

View File

@@ -7,8 +7,28 @@ import authchecker from "./middlewares/authchecker";
import { ErrorHandler } from './middlewares/ErrorHandler'; import { ErrorHandler } from './middlewares/ErrorHandler';
import UserChecker from './middlewares/UserChecker'; import UserChecker from './middlewares/UserChecker';
// Always use .js when running from dist/ (Bun workflow: always build first) // Import all controllers directly to avoid Bun + routing-controllers glob/require issues
const CONTROLLERS_FILE_EXTENSION = 'js'; import { AuthController } from './controllers/AuthController';
import { DonationController } from './controllers/DonationController';
import { DonorController } from './controllers/DonorController';
import { GroupContactController } from './controllers/GroupContactController';
import { ImportController } from './controllers/ImportController';
import { MeController } from './controllers/MeController';
import { PermissionController } from './controllers/PermissionController';
import { RunnerCardController } from './controllers/RunnerCardController';
import { RunnerController } from './controllers/RunnerController';
import { RunnerOrganizationController } from './controllers/RunnerOrganizationController';
import { RunnerSelfServiceController } from './controllers/RunnerSelfServiceController';
import { RunnerTeamController } from './controllers/RunnerTeamController';
import { ScanController } from './controllers/ScanController';
import { ScanStationController } from './controllers/ScanStationController';
import { StatsClientController } from './controllers/StatsClientController';
import { StatsController } from './controllers/StatsController';
import { StatusController } from './controllers/StatusController';
import { TrackController } from './controllers/TrackController';
import { UserController } from './controllers/UserController';
import { UserGroupController } from './controllers/UserGroupController';
const app = createExpressServer({ const app = createExpressServer({
authorizationChecker: authchecker, authorizationChecker: authchecker,
currentUserChecker: UserChecker, currentUserChecker: UserChecker,
@@ -16,7 +36,28 @@ const app = createExpressServer({
development: config.development, development: config.development,
cors: true, cors: true,
routePrefix: "/api", routePrefix: "/api",
controllers: [`${__dirname}/controllers/*.${CONTROLLERS_FILE_EXTENSION}`], controllers: [
AuthController,
DonationController,
DonorController,
GroupContactController,
ImportController,
MeController,
PermissionController,
RunnerCardController,
RunnerController,
RunnerOrganizationController,
RunnerSelfServiceController,
RunnerTeamController,
ScanController,
ScanStationController,
StatsClientController,
StatsController,
StatusController,
TrackController,
UserController,
UserGroupController,
],
}); });
async function main() { async function main() {

View File

@@ -1,9 +1,7 @@
import consola from 'consola'; import consola from 'consola';
import { config as configDotenv } from 'dotenv';
import { CountryCode } from 'libphonenumber-js'; import { CountryCode } from 'libphonenumber-js';
import ValidatorJS from 'validator'; import ValidatorJS from 'validator';
configDotenv();
export const config = { export const config = {
internal_port: parseInt(process.env.APP_PORT) || 4010, internal_port: parseInt(process.env.APP_PORT) || 4010,
development: process.env.NODE_ENV === "production", development: process.env.NODE_ENV === "production",
@@ -22,15 +20,15 @@ export const config = {
mailer_url: process.env.MAILER_URL || "", mailer_url: process.env.MAILER_URL || "",
mailer_key: process.env.MAILER_KEY || "" mailer_key: process.env.MAILER_KEY || ""
} }
let errors = 0 let errors = 0
if (typeof config.internal_port !== "number") { if (typeof config.internal_port !== "number") {
consola.error("Error: APP_PORT is not a number") consola.error("Error: APP_PORT is not a number")
errors++ errors++
} }
if (typeof config.development !== "boolean") { if (typeof config.development !== "boolean") {
consola.error("Error: NODE_ENV is not a boolean") consola.error("Error: NODE_ENV is not a boolean")
errors++ errors++
} }
if (config.mailer_url == "" || config.mailer_key == "") { if (config.mailer_url == "" || config.mailer_key == "") {
consola.error("Error: invalid mailer config") consola.error("Error: invalid mailer config")
errors++; errors++;
@@ -39,23 +37,23 @@ if (config.station_token_secret.length < 32) {
consola.error("Error: STATION_TOKEN_SECRET must be set and at least 32 characters long") consola.error("Error: STATION_TOKEN_SECRET must be set and at least 32 characters long")
errors++; errors++;
} }
function getPhoneCodeLocale(): CountryCode { function getPhoneCodeLocale(): CountryCode {
return (process.env.PHONE_COUNTRYCODE as CountryCode); return (process.env.PHONE_COUNTRYCODE as CountryCode);
} }
function getPostalCodeLocale(): any { function getPostalCodeLocale(): any {
try { try {
const stringArray: String[] = ValidatorJS.isPostalCodeLocales; const stringArray: String[] = ValidatorJS.isPostalCodeLocales;
let index = stringArray.indexOf(process.env.POSTALCODE_COUNTRYCODE); let index = stringArray.indexOf(process.env.POSTALCODE_COUNTRYCODE);
return ValidatorJS.isPostalCodeLocales[index]; return ValidatorJS.isPostalCodeLocales[index];
} catch (error) { } catch (error) {
return null; return null;
} }
} }
function getDataSeeding(): Boolean { function getDataSeeding(): Boolean {
try { try {
return JSON.parse(process.env.SEED_TEST_DATA); return JSON.parse(process.env.SEED_TEST_DATA);
} catch (error) { } catch (error) {
return false; return false;
} }
} }
export let e = errors export let e = errors

View File

@@ -1,4 +1,4 @@
import { Request } from "express"; import type { Request } from "express";
import * as jwt from "jsonwebtoken"; import * as jwt from "jsonwebtoken";
import { BadRequestError, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers'; import { BadRequestError, Body, Delete, Get, JsonController, OnUndefined, Param, Post, QueryParam, Req, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';

View File

@@ -1,4 +1,4 @@
import { Request } from "express"; import type { Request } from "express";
import { Authorized, Body, Delete, Get, HttpError, JsonController, OnUndefined, Param, Post, Put, QueryParam, Req, UseBefore } from 'routing-controllers'; import { Authorized, Body, Delete, Get, HttpError, JsonController, OnUndefined, Param, Post, Put, QueryParam, Req, UseBefore } from 'routing-controllers';
import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi';
import { Repository, getConnection, getConnectionManager } from 'typeorm'; import { Repository, getConnection, getConnectionManager } from 'typeorm';

View File

@@ -14,6 +14,7 @@ import { ResponseStats } from '../models/responses/ResponseStats';
import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganization'; import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganization';
import { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner'; import { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner';
import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam'; import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam';
import { getStatsCache, setStatsCache } from '../nats/StatsKV';
@JsonController('/stats') @JsonController('/stats')
export class StatsController { export class StatsController {
@@ -22,6 +23,13 @@ export class StatsController {
@ResponseSchema(ResponseStats) @ResponseSchema(ResponseStats)
@OpenAPI({ description: "A very basic stats endpoint providing basic counters for a dashboard or simmilar" }) @OpenAPI({ description: "A very basic stats endpoint providing basic counters for a dashboard or simmilar" })
async get() { async get() {
// Try cache first
const cached = await getStatsCache<ResponseStats>('overview');
if (cached) {
return cached;
}
// Cache miss - compute fresh stats
const connection = getConnection(); const connection = getConnection();
const runnersViaSelfservice = await connection.getRepository(Runner).count({ where: { created_via: "selfservice" } }); const runnersViaSelfservice = await connection.getRepository(Runner).count({ where: { created_via: "selfservice" } });
const runnersViaKiosk = await connection.getRepository(Runner).count({ where: { created_via: "kiosk" } }); const runnersViaKiosk = await connection.getRepository(Runner).count({ where: { created_via: "kiosk" } });
@@ -43,7 +51,12 @@ export class StatsController {
let donations = await connection.getRepository(Donation).find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] }); let donations = await connection.getRepository(Donation).find({ relations: ['runner', 'runner.scans', 'runner.scans.track'] });
const donors = await connection.getRepository(Donor).count(); const donors = await connection.getRepository(Donor).count();
return new ResponseStats(runnersViaSelfservice, runners, teams, orgs, users, scans, donations, distace, donors, runnersViaKiosk) const result = new ResponseStats(runnersViaSelfservice, runners, teams, orgs, users, scans, donations, distace, donors, runnersViaKiosk);
// Store in cache for 60 seconds
await setStatsCache('overview', result);
return result;
} }
@Get("/runners/distance") @Get("/runners/distance")
@@ -51,6 +64,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsRunner, { isArray: true }) @ResponseSchema(ResponseStatsRunner, { isArray: true })
@OpenAPI({ description: "Returns the top ten runners by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten runners by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopRunnersByDistance() { async getTopRunnersByDistance() {
// Try cache first
const cached = await getStatsCache<ResponseStatsRunner[]>('runners.distance');
if (cached) {
return cached;
}
// Cache miss - compute fresh stats
let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] }); let runners = await getConnection().getRepository(Runner).find({ relations: ['scans', 'group', 'distanceDonations', 'scans.track'] });
if (!runners || runners.length == 0) { if (!runners || runners.length == 0) {
return []; return [];
@@ -60,6 +80,10 @@ export class StatsController {
topRunners.forEach(runner => { topRunners.forEach(runner => {
responseRunners.push(new ResponseStatsRunner(runner)); responseRunners.push(new ResponseStatsRunner(runner));
}); });
// Store in cache for 60 seconds
await setStatsCache('runners.distance', responseRunners);
return responseRunners; return responseRunners;
} }
@@ -68,6 +92,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsRunner, { isArray: true }) @ResponseSchema(ResponseStatsRunner, { isArray: true })
@OpenAPI({ description: "Returns the top ten runners by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten runners by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopRunnersByDonations() { async getTopRunnersByDonations() {
// Try cache first
const cached = await getStatsCache<ResponseStatsRunner[]>('runners.donations');
if (cached) {
return cached;
}
// Cache miss - compute fresh stats
let runners = await getConnection().getRepository(Runner).find({ relations: ['group', 'distanceDonations', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] }); let runners = await getConnection().getRepository(Runner).find({ relations: ['group', 'distanceDonations', 'distanceDonations.runner', 'distanceDonations.runner.scans', 'distanceDonations.runner.scans.track'] });
if (!runners || runners.length == 0) { if (!runners || runners.length == 0) {
return []; return [];
@@ -77,6 +108,10 @@ export class StatsController {
topRunners.forEach(runner => { topRunners.forEach(runner => {
responseRunners.push(new ResponseStatsRunner(runner)); responseRunners.push(new ResponseStatsRunner(runner));
}); });
// Store in cache for 60 seconds
await setStatsCache('runners.donations', responseRunners);
return responseRunners; return responseRunners;
} }
@@ -85,6 +120,14 @@ export class StatsController {
@ResponseSchema(ResponseStatsRunner, { isArray: true }) @ResponseSchema(ResponseStatsRunner, { isArray: true })
@OpenAPI({ description: "Returns the top ten runners by fastest laptime on your selected track (track by id).", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten runners by fastest laptime on your selected track (track by id).", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopRunnersByLaptime(@QueryParam("track") track: number) { async getTopRunnersByLaptime(@QueryParam("track") track: number) {
// Try cache first (cache key includes track id, using dots for NATS KV compatibility)
const cacheKey = `runners.laptime.${track}`;
const cached = await getStatsCache<ResponseStatsRunner[]>(cacheKey);
if (cached) {
return cached;
}
// Cache miss - compute fresh stats
let scans = await getConnection().getRepository(TrackScan).find({ relations: ['track', 'runner', 'runner.group', 'runner.scans', 'runner.scans.track', 'runner.distanceDonations'] }); let scans = await getConnection().getRepository(TrackScan).find({ relations: ['track', 'runner', 'runner.group', 'runner.scans', 'runner.scans.track', 'runner.distanceDonations'] });
if (!scans || scans.length == 0) { if (!scans || scans.length == 0) {
return []; return [];
@@ -105,6 +148,10 @@ export class StatsController {
topScans.forEach(scan => { topScans.forEach(scan => {
responseRunners.push(new ResponseStatsRunner(scan.runner, scan.lapTime)); responseRunners.push(new ResponseStatsRunner(scan.runner, scan.lapTime));
}); });
// Store in cache for 60 seconds
await setStatsCache(cacheKey, responseRunners);
return responseRunners; return responseRunners;
} }
@@ -121,6 +168,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsTeam, { isArray: true }) @ResponseSchema(ResponseStatsTeam, { isArray: true })
@OpenAPI({ description: "Returns the top ten teams by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten teams by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopTeamsByDistance() { async getTopTeamsByDistance() {
// Try cache first
const cached = await getStatsCache<ResponseStatsTeam[]>('teams.distance');
if (cached) {
return cached;
}
// Cache miss - compute fresh stats
let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.scans.track'] }); let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.scans.track'] });
if (!teams || teams.length == 0) { if (!teams || teams.length == 0) {
return []; return [];
@@ -130,6 +184,10 @@ export class StatsController {
topTeams.forEach(team => { topTeams.forEach(team => {
responseTeams.push(new ResponseStatsTeam(team)); responseTeams.push(new ResponseStatsTeam(team));
}); });
// Store in cache for 60 seconds
await setStatsCache('teams.distance', responseTeams);
return responseTeams; return responseTeams;
} }
@@ -138,6 +196,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsTeam, { isArray: true }) @ResponseSchema(ResponseStatsTeam, { isArray: true })
@OpenAPI({ description: "Returns the top ten teams by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten teams by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopTeamsByDonations() { async getTopTeamsByDonations() {
// Try cache first
const cached = await getStatsCache<ResponseStatsTeam[]>('teams.donations');
if (cached) {
return cached;
}
// Cache miss - compute fresh stats
let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] }); let teams = await getConnection().getRepository(RunnerTeam).find({ relations: ['parentGroup', 'runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track'] });
if (!teams || teams.length == 0) { if (!teams || teams.length == 0) {
return []; return [];
@@ -147,6 +212,10 @@ export class StatsController {
topTeams.forEach(team => { topTeams.forEach(team => {
responseTeams.push(new ResponseStatsTeam(team)); responseTeams.push(new ResponseStatsTeam(team));
}); });
// Store in cache for 60 seconds
await setStatsCache('teams.donations', responseTeams);
return responseTeams; return responseTeams;
} }
@@ -155,6 +224,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true }) @ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
@OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopOrgsByDistance() { async getTopOrgsByDistance() {
// Try cache first
const cached = await getStatsCache<ResponseStatsOrgnisation[]>('organizations.distance');
if (cached) {
return cached;
}
// Cache miss - compute fresh stats
let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] }); let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.scans', 'runners.distanceDonations', 'runners.scans.track', 'teams', 'teams.runners', 'teams.runners.scans', 'teams.runners.distanceDonations', 'teams.runners.scans.track'] });
if (!orgs || orgs.length == 0) { if (!orgs || orgs.length == 0) {
return []; return [];
@@ -164,6 +240,10 @@ export class StatsController {
topOrgs.forEach(org => { topOrgs.forEach(org => {
responseOrgs.push(new ResponseStatsOrgnisation(org)); responseOrgs.push(new ResponseStatsOrgnisation(org));
}); });
// Store in cache for 60 seconds
await setStatsCache('organizations.distance', responseOrgs);
return responseOrgs; return responseOrgs;
} }
@@ -172,6 +252,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true }) @ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
@OpenAPI({ description: "Returns the top ten organizations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] }) @OpenAPI({ description: "Returns the top ten organizations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
async getTopOrgsByDonations() { async getTopOrgsByDonations() {
// Try cache first
const cached = await getStatsCache<ResponseStatsOrgnisation[]>('organizations.donations');
if (cached) {
return cached;
}
// Cache miss - compute fresh stats
let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.distanceDonations', 'runners.distanceDonations.runner', 'runners.distanceDonations.runner.scans', 'runners.distanceDonations.runner.scans.track', 'teams', 'teams.runners', 'teams.runners.distanceDonations', 'teams.runners.distanceDonations.runner', 'teams.runners.distanceDonations.runner.scans', 'teams.runners.distanceDonations.runner.scans.track'] }); let orgs = await getConnection().getRepository(RunnerOrganization).find({ relations: ['runners', 'runners.distanceDonations', 'runners.distanceDonations.runner', 'runners.distanceDonations.runner.scans', 'runners.distanceDonations.runner.scans.track', 'teams', 'teams.runners', 'teams.runners.distanceDonations', 'teams.runners.distanceDonations.runner', 'teams.runners.distanceDonations.runner.scans', 'teams.runners.distanceDonations.runner.scans.track'] });
if (!orgs || orgs.length == 0) { if (!orgs || orgs.length == 0) {
return []; return [];
@@ -181,6 +268,10 @@ export class StatsController {
topOrgs.forEach(org => { topOrgs.forEach(org => {
responseOrgs.push(new ResponseStatsOrgnisation(org)); responseOrgs.push(new ResponseStatsOrgnisation(org));
}); });
// Store in cache for 60 seconds
await setStatsCache('organizations.donations', responseOrgs);
return responseOrgs; return responseOrgs;
} }
} }

View File

@@ -1,5 +1,6 @@
import { createConnection } from "typeorm"; import { createConnection } from "typeorm";
import { runSeeder } from 'typeorm-seeding'; import { runSeeder } from 'typeorm-seeding';
import consola from 'consola';
import { config } from '../config'; import { config } from '../config';
import { ConfigFlag } from '../models/entities/ConfigFlags'; import { ConfigFlag } from '../models/entities/ConfigFlags';
import SeedPublicOrg from '../seeds/SeedPublicOrg'; import SeedPublicOrg from '../seeds/SeedPublicOrg';
@@ -11,6 +12,11 @@ import SeedUsers from '../seeds/SeedUsers';
*/ */
export default async () => { export default async () => {
const connection = await createConnection(); const connection = await createConnection();
// Log discovered entities for debugging
consola.info(`TypeORM discovered ${connection.entityMetadatas.length} entities:`);
consola.info(connection.entityMetadatas.map(m => m.name).sort().join(', '));
await connection.synchronize(); await connection.synchronize();
//The data seeding part //The data seeding part

View File

@@ -1,4 +1,4 @@
import { verify } from '@node-rs/argon2'; import * as Bun from 'bun';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { getConnectionManager } from 'typeorm'; import { getConnectionManager } from 'typeorm';
import { StatsClient } from '../models/entities/StatsClient'; import { StatsClient } from '../models/entities/StatsClient';
@@ -55,7 +55,7 @@ const StatsAuth = async (req: Request, res: Response, next: () => void) => {
} }
} }
else { else {
if (!(await verify(client.key, provided_token))) { if (!(await Bun.password.verify(provided_token, client.key))) {
res.status(401).send("Api token invalid."); res.status(401).send("Api token invalid.");
return; return;
} }

View File

@@ -1,4 +1,4 @@
import { hash } from '@node-rs/argon2'; import * as Bun from 'bun';
import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
import * as jsonwebtoken from 'jsonwebtoken'; import * as jsonwebtoken from 'jsonwebtoken';
import { getConnectionManager } from 'typeorm'; import { getConnectionManager } from 'typeorm';
@@ -49,7 +49,7 @@ export class ResetPassword {
if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) { throw new RefreshTokenCountInvalidError(); } if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) { throw new RefreshTokenCountInvalidError(); }
found_user.refreshTokenCount = found_user.refreshTokenCount + 1; found_user.refreshTokenCount = found_user.refreshTokenCount + 1;
found_user.password = await hash(this.password + found_user.uuid); found_user.password = await Bun.password.hash(this.password + found_user.uuid);
await getConnectionManager().get().getRepository(User).save(found_user); await getConnectionManager().get().getRepository(User).save(found_user);
return "password reset successfull"; return "password reset successfull";

View File

@@ -1,4 +1,4 @@
import { verify } from '@node-rs/argon2'; import * as Bun from 'bun';
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator'; import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { getConnectionManager } from 'typeorm'; import { getConnectionManager } from 'typeorm';
import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError'; import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
@@ -56,7 +56,7 @@ export class CreateAuth {
throw new UserNotFoundError(); throw new UserNotFoundError();
} }
if (found_user.enabled == false) { throw new UserDisabledError(); } if (found_user.enabled == false) { throw new UserDisabledError(); }
if (!(await verify(found_user.password, this.password + found_user.uuid))) { if (!(await Bun.password.verify(this.password + found_user.uuid, found_user.password))) {
throw new InvalidCredentialsError(); throw new InvalidCredentialsError();
} }

View File

@@ -1,5 +1,4 @@
import { IsBoolean, IsObject, IsOptional } from 'class-validator'; import { IsBoolean, IsObject, IsOptional } from 'class-validator';
import * as uuid from 'uuid';
import { Address } from '../../entities/Address'; import { Address } from '../../entities/Address';
import { RunnerOrganization } from '../../entities/RunnerOrganization'; import { RunnerOrganization } from '../../entities/RunnerOrganization';
import { CreateRunnerGroup } from './CreateRunnerGroup'; import { CreateRunnerGroup } from './CreateRunnerGroup';
@@ -35,7 +34,7 @@ export class CreateRunnerOrganization extends CreateRunnerGroup {
Address.validate(newRunnerOrganization.address); Address.validate(newRunnerOrganization.address);
if (this.registrationEnabled) { if (this.registrationEnabled) {
newRunnerOrganization.key = uuid.v4().toUpperCase(); newRunnerOrganization.key = crypto.randomUUID()
} }
return newRunnerOrganization; return newRunnerOrganization;

View File

@@ -1,7 +1,6 @@
import { IsBoolean, IsInt, IsOptional, IsPositive, IsString } from 'class-validator'; import { IsBoolean, IsInt, IsOptional, IsPositive, IsString } from 'class-validator';
import crypto from 'crypto'; import crypto from 'crypto';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import * as uuid from 'uuid';
import { config } from '../../../config'; import { config } from '../../../config';
import { TrackNotFoundError } from '../../../errors/TrackErrors'; import { TrackNotFoundError } from '../../../errors/TrackErrors';
import { ScanStation } from '../../entities/ScanStation'; import { ScanStation } from '../../entities/ScanStation';
@@ -42,7 +41,7 @@ export class CreateScanStation {
newStation.enabled = this.enabled; newStation.enabled = this.enabled;
newStation.track = await this.getTrack(); newStation.track = await this.getTrack();
let newUUID = uuid.v4().toUpperCase(); let newUUID = crypto.randomUUID().toUpperCase();
newStation.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase(); newStation.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase();
newStation.cleartextkey = newStation.prefix + "." + newUUID; newStation.cleartextkey = newStation.prefix + "." + newUUID;
newStation.key = crypto.createHmac("sha256", config.station_token_secret).update(newStation.cleartextkey).digest('hex'); newStation.key = crypto.createHmac("sha256", config.station_token_secret).update(newStation.cleartextkey).digest('hex');

View File

@@ -1,7 +1,6 @@
import { hash } from '@node-rs/argon2'; import * as Bun from 'bun';
import { IsOptional, IsString } from 'class-validator'; import { IsOptional, IsString } from 'class-validator';
import crypto from 'crypto'; import crypto from 'crypto';
import * as uuid from 'uuid';
import { StatsClient } from '../../entities/StatsClient'; import { StatsClient } from '../../entities/StatsClient';
/** /**
@@ -23,9 +22,9 @@ export class CreateStatsClient {
newClient.description = this.description; newClient.description = this.description;
let newUUID = uuid.v4().toUpperCase(); let newUUID = crypto.randomUUID().toUpperCase();
newClient.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase(); newClient.prefix = crypto.createHash("sha3-512").update(newUUID).digest('hex').substring(0, 7).toUpperCase();
newClient.key = await hash(newClient.prefix + "." + newUUID); newClient.key = await Bun.password.hash(newClient.prefix + "." + newUUID);
newClient.cleartextkey = newClient.prefix + "." + newUUID; newClient.cleartextkey = newClient.prefix + "." + newUUID;
return newClient; return newClient;

View File

@@ -1,8 +1,7 @@
import { hash } from "@node-rs/argon2"; import * as Bun from 'bun';
import { passwordStrength } from "check-password-strength"; import { passwordStrength } from "check-password-strength";
import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator'; import { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
import { getConnectionManager } from 'typeorm'; import { getConnectionManager } from 'typeorm';
import * as uuid from 'uuid';
import { config } from '../../../config'; import { config } from '../../../config';
import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors'; import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors';
import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors'; import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors';
@@ -108,9 +107,9 @@ export class CreateUser {
newUser.firstname = this.firstname newUser.firstname = this.firstname
newUser.middlename = this.middlename newUser.middlename = this.middlename
newUser.lastname = this.lastname newUser.lastname = this.lastname
newUser.uuid = uuid.v4() newUser.uuid = crypto.randomUUID()
newUser.phone = this.phone newUser.phone = this.phone
newUser.password = await hash(this.password + newUser.uuid); newUser.password = Bun.password.hash(this.password + newUser.uuid);
newUser.groups = await this.getGroups(); newUser.groups = await this.getGroups();
newUser.enabled = this.enabled; newUser.enabled = this.enabled;

View File

@@ -1,5 +1,4 @@
import { IsBoolean, IsInt, IsObject, IsOptional } from 'class-validator'; import { IsBoolean, IsInt, IsObject, IsOptional } from 'class-validator';
import * as uuid from 'uuid';
import { Address } from '../../entities/Address'; import { Address } from '../../entities/Address';
import { RunnerOrganization } from '../../entities/RunnerOrganization'; import { RunnerOrganization } from '../../entities/RunnerOrganization';
import { CreateRunnerGroup } from '../create/CreateRunnerGroup'; import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
@@ -42,7 +41,7 @@ export class UpdateRunnerOrganization extends CreateRunnerGroup {
Address.validate(organization.address); Address.validate(organization.address);
if (this.registrationEnabled && !organization.key) { if (this.registrationEnabled && !organization.key) {
organization.key = uuid.v4().toUpperCase(); organization.key = crypto.randomUUID().toUpperCase();
} }
else { else {
organization.key = null; organization.key = null;

View File

@@ -1,4 +1,4 @@
import { hash } from '@node-rs/argon2'; import * as Bun from 'bun';
import { passwordStrength } from "check-password-strength"; import { passwordStrength } from "check-password-strength";
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator'; import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
import { getConnectionManager } from 'typeorm'; import { getConnectionManager } from 'typeorm';
@@ -111,7 +111,7 @@ export class UpdateUser {
if (!password_strength.contains.includes("lowercase")) { throw new PasswordMustContainLowercaseLetterError(); } if (!password_strength.contains.includes("lowercase")) { throw new PasswordMustContainLowercaseLetterError(); }
if (!password_strength.contains.includes("number")) { throw new PasswordMustContainNumberError(); } if (!password_strength.contains.includes("number")) { throw new PasswordMustContainNumberError(); }
if (!(password_strength.length > 9)) { throw new PasswordTooShortError(); } if (!(password_strength.length > 9)) { throw new PasswordTooShortError(); }
user.password = await hash(this.password + user.uuid); user.password = await Bun.password.hash(this.password + user.uuid);
user.refreshTokenCount = user.refreshTokenCount + 1; user.refreshTokenCount = user.refreshTokenCount + 1;
} }

View File

@@ -2,7 +2,7 @@ import { IsInt, IsNotEmpty, IsPositive } from "class-validator";
import { ChildEntity, Column, Index, ManyToOne } from "typeorm"; import { ChildEntity, Column, Index, ManyToOne } from "typeorm";
import { ResponseDistanceDonation } from '../responses/ResponseDistanceDonation'; import { ResponseDistanceDonation } from '../responses/ResponseDistanceDonation';
import { Donation } from "./Donation"; import { Donation } from "./Donation";
import { Runner } from "./Runner"; import type { Runner } from "./Runner";
/** /**
* Defines the DistanceDonation entity. * Defines the DistanceDonation entity.
@@ -16,8 +16,8 @@ export class DistanceDonation extends Donation {
* Used as the source of the donation's distance. * Used as the source of the donation's distance.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => Runner, runner => runner.distanceDonations) @ManyToOne(() => require("./Runner").Runner, (runner: Runner) => runner.distanceDonations)
runner: Runner; runner!: Runner;
/** /**
* The donation's amount donated per distance. * The donation's amount donated per distance.

View File

@@ -4,7 +4,7 @@ import {
} from "class-validator"; } from "class-validator";
import { BeforeInsert, BeforeUpdate, Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { ResponseDonation } from '../responses/ResponseDonation'; import { ResponseDonation } from '../responses/ResponseDonation';
import { Donor } from './Donor'; import type { Donor } from './Donor';
/** /**
* Defines the Donation entity. * Defines the Donation entity.
@@ -25,8 +25,8 @@ export abstract class Donation {
/** /**
* The donations's donor. * The donations's donor.
*/ */
@ManyToOne(() => Donor, donor => donor.donations) @ManyToOne(() => require("./Donor").Donor, (donor: Donor) => donor.donations)
donor: Donor; donor!: Donor;
/** /**
* The donation's amount in cents (or whatever your currency's smallest unit is.). * The donation's amount in cents (or whatever your currency's smallest unit is.).

View File

@@ -1,7 +1,7 @@
import { IsBoolean, IsInt } from "class-validator"; import { IsBoolean, IsInt } from "class-validator";
import { ChildEntity, Column, OneToMany } from "typeorm"; import { ChildEntity, Column, OneToMany } from "typeorm";
import { ResponseDonor } from '../responses/ResponseDonor'; import { ResponseDonor } from '../responses/ResponseDonor';
import { Donation } from './Donation'; import type { Donation } from './Donation';
import { Participant } from "./Participant"; import { Participant } from "./Participant";
/** /**
@@ -21,8 +21,8 @@ export class Donor extends Participant {
* Used to link the participant as the donor of a donation. * Used to link the participant as the donor of a donation.
* Attention: Only runner's can be associated as a distanceDonations distance source. * Attention: Only runner's can be associated as a distanceDonations distance source.
*/ */
@OneToMany(() => Donation, donation => donation.donor, { nullable: true }) @OneToMany(() => require("./Donation").Donation, (donation: Donation) => donation.donor, { nullable: true })
donations: Donation[]; donations!: Donation[];
/** /**
* Returns the total donations of a donor based on his linked donations. * Returns the total donations of a donor based on his linked donations.

View File

@@ -1,19 +1,19 @@
import { import {
IsEmail, IsEmail,
IsInt, IsInt,
IsNotEmpty, IsNotEmpty,
IsOptional, IsOptional,
IsPhoneNumber, IsPhoneNumber,
IsPositive, IsPositive,
IsString IsString
} from "class-validator"; } from "class-validator";
import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { config } from '../../config'; import { config } from '../../config';
import { ResponseGroupContact } from '../responses/ResponseGroupContact'; import { ResponseGroupContact } from '../responses/ResponseGroupContact';
import { Address } from "./Address"; import { Address } from "./Address";
import { RunnerGroup } from "./RunnerGroup"; import type { RunnerGroup } from "./RunnerGroup";
/** /**
* Defines the GroupContact entity. * Defines the GroupContact entity.
@@ -77,11 +77,11 @@ export class GroupContact {
@IsEmail() @IsEmail()
email?: string; email?: string;
/** /**
* Used to link contacts to groups. * Used to link contacts to groups.
*/ */
@OneToMany(() => RunnerGroup, group => group.contact, { nullable: true }) @OneToMany(() => require("./RunnerGroup").RunnerGroup, (group: RunnerGroup) => group.contact, { nullable: true })
groups: RunnerGroup[]; groups!: RunnerGroup[];
@Column({ type: 'bigint', nullable: true, readonly: true }) @Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt() @IsInt()

View File

@@ -8,7 +8,7 @@ import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, PrimaryGenerated
import { PermissionAction } from '../enums/PermissionAction'; import { PermissionAction } from '../enums/PermissionAction';
import { PermissionTarget } from '../enums/PermissionTargets'; import { PermissionTarget } from '../enums/PermissionTargets';
import { ResponsePermission } from '../responses/ResponsePermission'; import { ResponsePermission } from '../responses/ResponsePermission';
import { Principal } from './Principal'; import type { Principal } from './Principal';
/** /**
* Defines the Permission entity. * Defines the Permission entity.
* Permissions can be granted to principals. * Permissions can be granted to principals.
@@ -26,8 +26,8 @@ export class Permission {
/** /**
* The permission's principal. * The permission's principal.
*/ */
@ManyToOne(() => Principal, principal => principal.permissions) @ManyToOne(() => require("./Principal").Principal, (principal: Principal) => principal.permissions)
principal: Principal; principal!: Principal;
/** /**
* The permission's target. * The permission's target.

View File

@@ -1,7 +1,7 @@
import { IsInt, IsPositive } from 'class-validator'; import { IsInt, IsPositive } from 'class-validator';
import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn, TableInheritance } from 'typeorm'; import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn, TableInheritance } from 'typeorm';
import { ResponsePrincipal } from '../responses/ResponsePrincipal'; import { ResponsePrincipal } from '../responses/ResponsePrincipal';
import { Permission } from './Permission'; import type { Permission } from './Permission';
/** /**
* Defines the principal entity. * Defines the principal entity.
@@ -20,8 +20,8 @@ export abstract class Principal {
/** /**
* The participant's permissions. * The participant's permissions.
*/ */
@OneToMany(() => Permission, permission => permission.principal, { nullable: true }) @OneToMany(() => require("./Permission").Permission, (permission: Permission) => permission.principal, { nullable: true })
permissions: Permission[]; permissions!: Permission[];
@Column({ type: 'bigint', nullable: true, readonly: true }) @Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt() @IsInt()

View File

@@ -1,11 +1,11 @@
import { IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator"; import { IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator";
import { ChildEntity, Column, Index, ManyToOne, OneToMany } from "typeorm"; import { ChildEntity, Column, Index, ManyToOne, OneToMany } from "typeorm";
import { ResponseRunner } from '../responses/ResponseRunner'; import { ResponseRunner } from '../responses/ResponseRunner';
import { DistanceDonation } from "./DistanceDonation"; import type { DistanceDonation } from "./DistanceDonation";
import { Participant } from "./Participant"; import { Participant } from "./Participant";
import { RunnerCard } from "./RunnerCard"; import type { RunnerCard } from "./RunnerCard";
import { RunnerGroup } from "./RunnerGroup"; import { RunnerGroup } from "./RunnerGroup";
import { Scan } from "./Scan"; import type { Scan } from "./Scan";
/** /**
* Defines the runner entity. * Defines the runner entity.
@@ -27,22 +27,22 @@ export class Runner extends Participant {
* The runner's associated distanceDonations. * The runner's associated distanceDonations.
* Used to link runners to distanceDonations in order to calculate the donation's amount based on the distance the runner ran. * Used to link runners to distanceDonations in order to calculate the donation's amount based on the distance the runner ran.
*/ */
@OneToMany(() => DistanceDonation, distanceDonation => distanceDonation.runner, { nullable: true }) @OneToMany(() => require("./DistanceDonation").DistanceDonation, (distanceDonation: DistanceDonation) => distanceDonation.runner, { nullable: true })
distanceDonations: DistanceDonation[]; distanceDonations!: DistanceDonation[];
/** /**
* The runner's associated cards. * The runner's associated cards.
* Used to link runners to cards - yes a runner be associated with multiple cards this came in handy in the past. * Used to link runners to cards - yes a runner be associated with multiple cards this came in handy in the past.
*/ */
@OneToMany(() => RunnerCard, card => card.runner, { nullable: true }) @OneToMany(() => require("./RunnerCard").RunnerCard, (card: RunnerCard) => card.runner, { nullable: true })
cards: RunnerCard[]; cards!: RunnerCard[];
/** /**
* The runner's associated scans. * The runner's associated scans.
* Used to link runners to scans (valid and fraudulant). * Used to link runners to scans (valid and fraudulant).
*/ */
@OneToMany(() => Scan, scan => scan.runner, { nullable: true }) @OneToMany(() => require("./Scan").Scan, (scan: Scan) => scan.runner, { nullable: true })
scans: Scan[]; scans!: Scan[];
/** /**
* The last time the runner requested a selfservice link. * The last time the runner requested a selfservice link.

View File

@@ -9,8 +9,8 @@ import {
import { BeforeInsert, BeforeUpdate, Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { RunnerCardIdOutOfRangeError } from '../../errors/RunnerCardErrors'; import { RunnerCardIdOutOfRangeError } from '../../errors/RunnerCardErrors';
import { ResponseRunnerCard } from '../responses/ResponseRunnerCard'; import { ResponseRunnerCard } from '../responses/ResponseRunnerCard';
import { Runner } from "./Runner"; import type { Runner } from "./Runner";
import { TrackScan } from "./TrackScan"; import type { TrackScan } from "./TrackScan";
/** /**
* Defines the RunnerCard entity. * Defines the RunnerCard entity.
@@ -33,8 +33,8 @@ export class RunnerCard {
* To increase reusability a card can be reassigned. * To increase reusability a card can be reassigned.
*/ */
@IsOptional() @IsOptional()
@ManyToOne(() => Runner, runner => runner.cards, { nullable: true }) @ManyToOne(() => require("./Runner").Runner, (runner: Runner) => runner.cards, { nullable: true })
runner: Runner; runner!: Runner;
/** /**
* Is the card enabled (for fraud reasons)? * Is the card enabled (for fraud reasons)?
@@ -48,8 +48,8 @@ export class RunnerCard {
* The card's associated scans. * The card's associated scans.
* Used to link cards to track scans. * Used to link cards to track scans.
*/ */
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) @OneToMany(() => require("./TrackScan").TrackScan, (scan: TrackScan) => scan.card, { nullable: true })
scans: TrackScan[]; scans!: TrackScan[];
@Column({ type: 'bigint', nullable: true, readonly: true }) @Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt() @IsInt()

View File

@@ -8,7 +8,7 @@ import {
import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { ResponseRunnerGroup } from '../responses/ResponseRunnerGroup'; import { ResponseRunnerGroup } from '../responses/ResponseRunnerGroup';
import { GroupContact } from "./GroupContact"; import { GroupContact } from "./GroupContact";
import { Runner } from "./Runner"; import type { Runner } from "./Runner";
/** /**
* Defines the RunnerGroup entity. * Defines the RunnerGroup entity.
@@ -44,8 +44,8 @@ export abstract class RunnerGroup {
* The group's associated runners. * The group's associated runners.
* Used to link runners to a runner group. * Used to link runners to a runner group.
*/ */
@OneToMany(() => Runner, runner => runner.group, { nullable: true }) @OneToMany(() => require("./Runner").Runner, (runner: Runner) => runner.group, { nullable: true })
runners: Runner[]; runners!: Runner[];
@Column({ type: 'bigint', nullable: true, readonly: true }) @Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt() @IsInt()

View File

@@ -4,7 +4,7 @@ import { ResponseRunnerOrganization } from '../responses/ResponseRunnerOrganizat
import { Address } from './Address'; import { Address } from './Address';
import { Runner } from './Runner'; import { Runner } from './Runner';
import { RunnerGroup } from "./RunnerGroup"; import { RunnerGroup } from "./RunnerGroup";
import { RunnerTeam } from "./RunnerTeam"; import type { RunnerTeam } from "./RunnerTeam";
/** /**
* Defines the RunnerOrganization entity. * Defines the RunnerOrganization entity.
@@ -24,8 +24,8 @@ export class RunnerOrganization extends RunnerGroup {
* The organization's teams. * The organization's teams.
* Used to link teams to a organization. * Used to link teams to a organization.
*/ */
@OneToMany(() => RunnerTeam, team => team.parentGroup, { nullable: true }) @OneToMany(() => require("./RunnerTeam").RunnerTeam, (team: RunnerTeam) => team.parentGroup, { nullable: true })
teams: RunnerTeam[]; teams!: RunnerTeam[];
/** /**
* The organization's api key for self-service registration. * The organization's api key for self-service registration.

View File

@@ -2,7 +2,7 @@ import { IsNotEmpty } from "class-validator";
import { ChildEntity, Index, ManyToOne } from "typeorm"; import { ChildEntity, Index, ManyToOne } from "typeorm";
import { ResponseRunnerTeam } from '../responses/ResponseRunnerTeam'; import { ResponseRunnerTeam } from '../responses/ResponseRunnerTeam';
import { RunnerGroup } from "./RunnerGroup"; import { RunnerGroup } from "./RunnerGroup";
import { RunnerOrganization } from "./RunnerOrganization"; import type { RunnerOrganization } from "./RunnerOrganization";
/** /**
* Defines the RunnerTeam entity. * Defines the RunnerTeam entity.
@@ -17,7 +17,7 @@ export class RunnerTeam extends RunnerGroup {
* Every team has to be part of a runnerOrganization - this get's checked on creation and update. * Every team has to be part of a runnerOrganization - this get's checked on creation and update.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => RunnerOrganization, org => org.teams, { nullable: true }) @ManyToOne(() => require("./RunnerOrganization").RunnerOrganization, (org: RunnerOrganization) => org.teams, { nullable: true })
parentGroup?: RunnerOrganization; parentGroup?: RunnerOrganization;
/** /**

View File

@@ -7,7 +7,7 @@ import {
} from "class-validator"; } from "class-validator";
import { BeforeInsert, BeforeUpdate, Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn, TableInheritance } from "typeorm";
import { ResponseScan } from '../responses/ResponseScan'; import { ResponseScan } from '../responses/ResponseScan';
import { Runner } from "./Runner"; import type { Runner } from "./Runner";
/** /**
* Defines the Scan entity. * Defines the Scan entity.
@@ -31,8 +31,8 @@ export class Scan {
* This is important to link ran distances to runners. * This is important to link ran distances to runners.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => Runner, runner => runner.scans, { nullable: false }) @ManyToOne(() => require("./Runner").Runner, (runner: Runner) => runner.scans, { nullable: false })
runner: Runner; runner!: Runner;
/** /**
* Is the scan valid (for fraud reasons). * Is the scan valid (for fraud reasons).

View File

@@ -8,8 +8,8 @@ import {
} from "class-validator"; } from "class-validator";
import { BeforeInsert, BeforeUpdate, Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { ResponseScanStation } from '../responses/ResponseScanStation'; import { ResponseScanStation } from '../responses/ResponseScanStation';
import { Track } from "./Track"; import type { Track } from "./Track";
import { TrackScan } from "./TrackScan"; import type { TrackScan } from "./TrackScan";
/** /**
* Defines the ScanStation entity. * Defines the ScanStation entity.
@@ -41,8 +41,8 @@ export class ScanStation {
* All scans created by this station will also be associated with this track. * All scans created by this station will also be associated with this track.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => Track, track => track.stations, { nullable: false }) @ManyToOne(() => require("./Track").Track, (track: Track) => track.stations, { nullable: false })
track: Track; track!: Track;
/** /**
* The client's api key prefix. * The client's api key prefix.
@@ -72,8 +72,8 @@ export class ScanStation {
/** /**
* Used to link track scans to a scan station. * Used to link track scans to a scan station.
*/ */
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) @OneToMany(() => require("./TrackScan").TrackScan, (scan: TrackScan) => scan.station, { nullable: true })
scans: TrackScan[]; scans!: TrackScan[];
/** /**
* Is this station enabled? * Is this station enabled?

View File

@@ -7,8 +7,8 @@ import {
} from "class-validator"; } from "class-validator";
import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { ResponseTrack } from '../responses/ResponseTrack'; import { ResponseTrack } from '../responses/ResponseTrack';
import { ScanStation } from "./ScanStation"; import type { ScanStation } from "./ScanStation";
import { TrackScan } from "./TrackScan"; import type { TrackScan } from "./TrackScan";
/** /**
* Defines the Track entity. * Defines the Track entity.
@@ -53,15 +53,15 @@ export class Track {
* Used to link scan stations to a certain track. * Used to link scan stations to a certain track.
* This makes the configuration of the scan stations easier. * This makes the configuration of the scan stations easier.
*/ */
@OneToMany(() => ScanStation, station => station.track, { nullable: true }) @OneToMany(() => require("./ScanStation").ScanStation, (station: ScanStation) => station.track, { nullable: true })
stations: ScanStation[]; stations!: ScanStation[];
/** /**
* Used to link track scans to a track. * Used to link track scans to a track.
* The scan will derive it's distance from the track's distance. * The scan will derive it's distance from the track's distance.
*/ */
@OneToMany(() => TrackScan, scan => scan.track, { nullable: true }) @OneToMany(() => require("./TrackScan").TrackScan, (scan: TrackScan) => scan.track, { nullable: true })
scans: TrackScan[]; scans!: TrackScan[];
@Column({ type: 'bigint', nullable: true, readonly: true }) @Column({ type: 'bigint', nullable: true, readonly: true })
@IsInt() @IsInt()

View File

@@ -8,10 +8,10 @@ import {
} from "class-validator"; } from "class-validator";
import { ChildEntity, Column, Index, ManyToOne } from "typeorm"; import { ChildEntity, Column, Index, ManyToOne } from "typeorm";
import { ResponseTrackScan } from '../responses/ResponseTrackScan'; import { ResponseTrackScan } from '../responses/ResponseTrackScan';
import { RunnerCard } from "./RunnerCard"; import type { RunnerCard } from "./RunnerCard";
import { Scan } from "./Scan"; import { Scan } from "./Scan";
import { ScanStation } from "./ScanStation"; import type { ScanStation } from "./ScanStation";
import { Track } from "./Track"; import type { Track } from "./Track";
/** /**
* Defines the TrackScan entity. * Defines the TrackScan entity.
@@ -29,24 +29,24 @@ export class TrackScan extends Scan {
* This is used to determine the scan's distance. * This is used to determine the scan's distance.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => Track, track => track.scans, { nullable: true }) @ManyToOne(() => require("./Track").Track, (track: Track) => track.scans, { nullable: true })
track: Track; track!: Track;
/** /**
* The runnerCard associated with the scan. * The runnerCard associated with the scan.
* This get's saved for documentation and management purposes. * This get's saved for documentation and management purposes.
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => RunnerCard, card => card.scans, { nullable: true }) @ManyToOne(() => require("./RunnerCard").RunnerCard, (card: RunnerCard) => card.scans, { nullable: true })
card: RunnerCard; card!: RunnerCard;
/** /**
* The scanning station that created the scan. * The scanning station that created the scan.
* Mainly used for logging and traceing back scans (or errors) * Mainly used for logging and traceing back scans (or errors)
*/ */
@IsNotEmpty() @IsNotEmpty()
@ManyToOne(() => ScanStation, station => station.scans, { nullable: true }) @ManyToOne(() => require("./ScanStation").ScanStation, (station: ScanStation) => station.scans, { nullable: true })
station: ScanStation; station!: ScanStation;
/** /**
* The scan's distance in meters. * The scan's distance in meters.

View File

@@ -1,12 +1,12 @@
import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl, IsUUID } from "class-validator"; import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl, IsUUID } from "class-validator";
import { ChildEntity, Column, Index, JoinTable, ManyToMany, OneToMany } from "typeorm"; import { ChildEntity, Column, Index, JoinTable, ManyToMany, OneToMany } from "typeorm";
import { config } from '../../config'; import { config } from '../../config';
import { ResponsePrincipal } from '../responses/ResponsePrincipal'; import { ResponsePrincipal } from '../responses/ResponsePrincipal';
import { ResponseUser } from '../responses/ResponseUser'; import { ResponseUser } from '../responses/ResponseUser';
import { Permission } from './Permission'; import { Permission } from './Permission';
import { Principal } from './Principal'; import { Principal } from './Principal';
import { UserAction } from './UserAction'; import type { UserAction } from './UserAction';
import { UserGroup } from './UserGroup'; import { UserGroup } from './UserGroup';
/** /**
* Defines the User entity. * Defines the User entity.
@@ -125,11 +125,11 @@ export class User extends Principal {
/** /**
* The actions performed by this user. * The actions performed by this user.
* For documentation purposes only, will be implemented later. * For documentation purposes only, will be implemented later.
*/ */
@IsOptional() @IsOptional()
@OneToMany(() => UserAction, action => action.user, { nullable: true }) @OneToMany(() => require("./UserAction").UserAction, (action: UserAction) => action.user, { nullable: true })
actions: UserAction[] actions!: UserAction[]
/** /**
* Resolves all permissions granted to this user through groups. * Resolves all permissions granted to this user through groups.

View File

@@ -8,7 +8,7 @@ import {
} from "class-validator"; } from "class-validator";
import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; import { BeforeInsert, BeforeUpdate, Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { PermissionAction } from '../enums/PermissionAction'; import { PermissionAction } from '../enums/PermissionAction';
import { User } from './User'; import type { User } from './User';
/** /**
* Defines the UserAction entity. * Defines the UserAction entity.
@@ -26,8 +26,8 @@ export class UserAction {
/** /**
* The user that performed the action. * The user that performed the action.
*/ */
@ManyToOne(() => User, user => user.actions) @ManyToOne(() => require("./User").User, (user: User) => user.actions)
user: User user!: User
/** /**
* The actions's target (e.g. Track#2) * The actions's target (e.g. Track#2)

View File

@@ -3,7 +3,7 @@ import { Donation } from '../entities/Donation';
import { DonationStatus } from '../enums/DonationStatus'; import { DonationStatus } from '../enums/DonationStatus';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
import { ResponseDonor } from './ResponseDonor'; import type { ResponseDonor } from './ResponseDonor';
/** /**
* Defines the donation response. * Defines the donation response.
@@ -33,7 +33,7 @@ export class ResponseDonation implements IResponse {
* The donation's donor. * The donation's donor.
*/ */
@IsNotEmpty() @IsNotEmpty()
donor: ResponseDonor; donor?: ResponseDonor;
/** /**
* The donation's amount in the smalles unit of your currency (default: euro cent). * The donation's amount in the smalles unit of your currency (default: euro cent).

View File

@@ -4,7 +4,7 @@ import {
import { Donor } from '../entities/Donor'; import { Donor } from '../entities/Donor';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
import { ResponseDonation } from './ResponseDonation'; import type { ResponseDonation } from './ResponseDonation';
import { ResponseParticipant } from './ResponseParticipant'; import { ResponseParticipant } from './ResponseParticipant';
/** /**
@@ -35,7 +35,7 @@ export class ResponseDonor extends ResponseParticipant implements IResponse {
@IsInt() @IsInt()
paidDonationAmount: number; paidDonationAmount: number;
donations: Array<ResponseDonation>; donations?: Array<ResponseDonation>;
/** /**
* Creates a ResponseRunner object from a runner. * Creates a ResponseRunner object from a runner.
@@ -46,6 +46,7 @@ export class ResponseDonor extends ResponseParticipant implements IResponse {
this.receiptNeeded = donor.receiptNeeded; this.receiptNeeded = donor.receiptNeeded;
this.donationAmount = donor.donationAmount; this.donationAmount = donor.donationAmount;
this.paidDonationAmount = donor.paidDonationAmount; this.paidDonationAmount = donor.paidDonationAmount;
const ResponseDonation = require('./ResponseDonation').ResponseDonation;
this.donations = new Array<ResponseDonation>(); this.donations = new Array<ResponseDonation>();
if (donor.donations?.length > 0) { if (donor.donations?.length > 0) {
for (const donation of donor.donations) { for (const donation of donor.donations) {

View File

@@ -14,7 +14,7 @@ import { RunnerOrganization } from '../entities/RunnerOrganization';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
import { ResponseRunnerGroup } from './ResponseRunnerGroup'; import { ResponseRunnerGroup } from './ResponseRunnerGroup';
import { ResponseRunnerTeam } from './ResponseRunnerTeam'; import type { ResponseRunnerTeam } from './ResponseRunnerTeam';
/** /**
* Defines the runnerOrganization response. * Defines the runnerOrganization response.
@@ -37,7 +37,7 @@ export class ResponseRunnerOrganization extends ResponseRunnerGroup implements I
* The runnerOrganization associated teams. * The runnerOrganization associated teams.
*/ */
@IsArray() @IsArray()
teams: ResponseRunnerTeam[]; teams?: ResponseRunnerTeam[];
/** /**
* The organization's registration key. * The organization's registration key.
@@ -62,6 +62,7 @@ export class ResponseRunnerOrganization extends ResponseRunnerGroup implements I
public constructor(org: RunnerOrganization) { public constructor(org: RunnerOrganization) {
super(org); super(org);
this.address = org.address; this.address = org.address;
const ResponseRunnerTeam = require('./ResponseRunnerTeam').ResponseRunnerTeam;
this.teams = new Array<ResponseRunnerTeam>(); this.teams = new Array<ResponseRunnerTeam>();
if (org.teams) { if (org.teams) {
for (let team of org.teams) { for (let team of org.teams) {

View File

@@ -3,7 +3,7 @@ import { RunnerTeam } from '../entities/RunnerTeam';
import { ResponseObjectType } from '../enums/ResponseObjectType'; import { ResponseObjectType } from '../enums/ResponseObjectType';
import { IResponse } from './IResponse'; import { IResponse } from './IResponse';
import { ResponseRunnerGroup } from './ResponseRunnerGroup'; import { ResponseRunnerGroup } from './ResponseRunnerGroup';
import { ResponseRunnerOrganization } from './ResponseRunnerOrganization'; import type { ResponseRunnerOrganization } from './ResponseRunnerOrganization';
/** /**
* Defines the runnerTeam response. * Defines the runnerTeam response.
@@ -20,7 +20,7 @@ export class ResponseRunnerTeam extends ResponseRunnerGroup implements IResponse
*/ */
@IsObject() @IsObject()
@IsNotEmpty() @IsNotEmpty()
parentGroup: ResponseRunnerOrganization; parentGroup?: ResponseRunnerOrganization;
/** /**
* Creates a ResponseRunnerTeam object from a runnerTeam. * Creates a ResponseRunnerTeam object from a runnerTeam.

86
src/nats/StatsKV.ts Normal file
View File

@@ -0,0 +1,86 @@
import { KvEntry } from 'nats';
import NatsClient from './NatsClient';
const BUCKET = 'stats_cache';
const TTL_SECONDS = 60; // 60 second TTL
/**
* Stats cache stored in NATS KV with 60 second TTL.
* Used to cache expensive aggregation queries from the stats endpoints.
*/
async function getBucket() {
return NatsClient.getKV(BUCKET, { ttl: TTL_SECONDS * 1000 }); // TTL in milliseconds
}
/**
* Cache key patterns (using dots instead of colons for NATS KV compatibility):
* - "stats.overview" - main stats endpoint (GET /stats)
* - "stats.runners.distance" - top runners by distance
* - "stats.runners.donations" - top runners by donations
* - "stats.runners.laptime.{trackId}" - top runners by laptime for specific track
* - "stats.teams.distance" - top teams by distance
* - "stats.teams.donations" - top teams by donations
* - "stats.organizations.distance" - top organizations by distance
* - "stats.organizations.donations" - top organizations by donations
*/
function cacheKey(path: string): string {
// Replace colons with dots for NATS KV compatibility
return `stats.${path.replace(/:/g, '.')}`;
}
/**
* Returns the cached value for the given stats cache key, or null on a miss.
*/
export async function getStatsCache<T>(path: string): Promise<T | null> {
const bucket = await getBucket();
let entry: KvEntry | null = null;
try {
entry = await bucket.get(cacheKey(path));
} catch {
return null;
}
if (!entry || entry.operation === 'DEL' || entry.operation === 'PURGE') {
return null;
}
return JSON.parse(entry.string()) as T;
}
/**
* Stores a value in the stats cache with 60 second TTL.
* The TTL is applied at the bucket level, so all entries expire automatically.
*/
export async function setStatsCache<T>(path: string, value: T): Promise<void> {
const bucket = await getBucket();
await bucket.put(cacheKey(path), JSON.stringify(value));
}
/**
* Removes the cached entry for the given stats path.
* Useful for cache invalidation when data changes.
*/
export async function deleteStatsCache(path: string): Promise<void> {
const bucket = await getBucket();
try {
await bucket.delete(cacheKey(path));
} catch {
// Entry doesn't exist or already deleted - ignore
}
}
/**
* Removes all cached stats entries.
* Call this when runners, scans, or donations are modified to ensure fresh data.
*/
export async function invalidateAllStats(): Promise<void> {
const bucket = await getBucket();
try {
// Purge the entire bucket to clear all cached stats
await bucket.destroy();
// Recreate the bucket for future use
await NatsClient.getKV(BUCKET, { ttl: TTL_SECONDS * 1000 });
} catch {
// Bucket operations can fail if bucket doesn't exist - ignore
}
}

View File

@@ -1,7 +1,6 @@
import { hash } from '@node-rs/argon2'; import * as Bun from 'bun';
import { Connection } from 'typeorm'; import { Connection } from 'typeorm';
import { Factory, Seeder } from 'typeorm-seeding'; import { Factory, Seeder } from 'typeorm-seeding';
import * as uuid from 'uuid';
import { CreatePermission } from '../models/actions/create/CreatePermission'; import { CreatePermission } from '../models/actions/create/CreatePermission';
import { CreateUserGroup } from '../models/actions/create/CreateUserGroup'; import { CreateUserGroup } from '../models/actions/create/CreateUserGroup';
import { Permission } from '../models/entities/Permission'; import { Permission } from '../models/entities/Permission';
@@ -32,8 +31,8 @@ export default class SeedUsers implements Seeder {
initialUser.firstname = "demo"; initialUser.firstname = "demo";
initialUser.lastname = "demo"; initialUser.lastname = "demo";
initialUser.username = "demo"; initialUser.username = "demo";
initialUser.uuid = uuid.v4(); initialUser.uuid = crypto.randomUUID();
initialUser.password = await hash("demo" + initialUser.uuid); initialUser.password = await Bun.password.hash("demo" + initialUser.uuid);
initialUser.email = "demo@dev.lauf-fuer-kaya.de" initialUser.email = "demo@dev.lauf-fuer-kaya.de"
initialUser.groups = [group]; initialUser.groups = [group];
return await connection.getRepository(User).save(initialUser); return await connection.getRepository(User).save(initialUser);