Compare commits

...

11 Commits
1.7.2 ... 1.8.2

Author SHA1 Message Date
1d9c451dfb chore(release): 1.8.2
All checks were successful
Build release images / build-container (push) Successful in 1m28s
2026-02-20 22:39:31 +01:00
3197498ab3 refactor(Dockerfile): Update build process and entry point for TypeScript application 2026-02-20 22:39:11 +01:00
a1a2c2747c chore(PERFORMANCE_IDEAS): Remove outdated performance optimization ideas document 2026-02-20 22:30:21 +01:00
80e7e7939c fix(CreateUser): Await password hashing in toEntity method 2026-02-20 22:28:33 +01:00
6caa1850e3 fix(deps): Add @types/bun dependency to devDependencies 2026-02-20 22:28:01 +01:00
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
21 changed files with 432 additions and 890 deletions

View File

@@ -2,11 +2,37 @@
All notable changes to this project will be documented in this file. Dates are displayed in UTC.
#### [1.8.2](https://git.odit.services/lfk/backend/compare/1.8.1...1.8.2)
- chore(PERFORMANCE_IDEAS): Remove outdated performance optimization ideas document [`a1a2c27`](https://git.odit.services/lfk/backend/commit/a1a2c2747cda8ad4c049d0d3b188e993daa67a01)
- refactor(Dockerfile): Update build process and entry point for TypeScript application [`3197498`](https://git.odit.services/lfk/backend/commit/3197498ab37221156eef42311e58a4038c3309d1)
- fix(deps): Add @types/bun dependency to devDependencies [`6caa185`](https://git.odit.services/lfk/backend/commit/6caa1850e3668bbf72912121d2d1923a5e22d6e8)
- fix(CreateUser): Await password hashing in toEntity method [`80e7e79`](https://git.odit.services/lfk/backend/commit/80e7e7939c1ab35da9ece1cd9e6e1002e4e50d3a)
#### [1.8.1](https://git.odit.services/lfk/backend/compare/1.8.0...1.8.1)
> 20 February 2026
- perf(stats): Cache stats results for 60 seconds [`13e0c81`](https://git.odit.services/lfk/backend/commit/13e0c81957768c1b380914a0b93d3617c60e08a0)
- chore(release): 1.8.1 [`7aaac65`](https://git.odit.services/lfk/backend/commit/7aaac65af4e2d04653645adcf859ca69449e2332)
#### [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)

View File

@@ -1,23 +1,23 @@
# Typescript Build
# Build stage - install dependencies
FROM registry.odit.services/hub/oven/bun:1.3.9-alpine AS build
WORKDIR /app
COPY package.json bun.lockb* ./
RUN bun install --frozen-lockfile
COPY tsconfig.json ormconfig.js bunfig.toml ./
COPY src ./src
RUN bun run build \
&& rm -rf /app/node_modules \
# Production dependencies only
RUN rm -rf /app/node_modules \
&& bun install --production --frozen-lockfile
# final image
# Final image - run TypeScript directly
FROM registry.odit.services/hub/oven/bun:1.3.9-alpine AS final
WORKDIR /app
COPY --from=build /app/package.json /app/package.json
COPY --from=build /app/bun.lockb* /app/
COPY --from=build /app/ormconfig.js /app/ormconfig.js
COPY --from=build /app/bunfig.toml /app/bunfig.toml
COPY --from=build /app/dist /app/dist
COPY --from=build /app/node_modules /app/node_modules
ENTRYPOINT ["bun", "/app/dist/app.js"]
COPY ormconfig.js bunfig.toml tsconfig.json ./
COPY src ./src
ENTRYPOINT ["bun", "/app/src/app.ts"]

View File

@@ -1,589 +0,0 @@
# 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

139
bun.lock
View File

@@ -5,7 +5,6 @@
"": {
"name": "@odit/lfk-backend",
"dependencies": {
"@node-rs/argon2": "^2.0.2",
"@odit/class-validator-jsonschema": "2.1.1",
"axios": "0.21.1",
"body-parser": "1.19.0",
@@ -17,9 +16,7 @@
"cookie-parser": "1.4.5",
"cors": "2.8.5",
"csvtojson": "2.0.10",
"dotenv": "8.2.0",
"express": "4.17.1",
"glob": "^13.0.6",
"jsonwebtoken": "8.5.1",
"libphonenumber-js": "1.9.9",
"mysql": "2.18.1",
@@ -32,12 +29,12 @@
"typeorm": "0.2.30",
"typeorm-routing-controllers-extensions": "0.2.0",
"typeorm-seeding": "1.6.1",
"uuid": "8.3.2",
"validator": "13.5.2",
},
"devDependencies": {
"@faker-js/faker": "7.6.0",
"@odit/license-exporter": "0.0.9",
"@types/bun": "^1.3.9",
"@types/cors": "2.8.19",
"@types/csvtojson": "1.1.5",
"@types/express": "5.0.6",
@@ -48,7 +45,7 @@
"cp-cli": "2.0.0",
"jest": "26.6.3",
"release-it": "14.2.2",
"rimraf": "3.0.2",
"rimraf": "^6.1.3",
"start-server-and-test": "1.11.7",
"ts-jest": "26.5.0",
"typedoc": "0.20.19",
@@ -127,12 +124,6 @@
"@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=="],
"@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="],
@@ -185,38 +176,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=="],
"@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.stat": ["@nodelib/fs.stat@2.0.4", "", {}, "sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q=="],
@@ -283,8 +242,6 @@
"@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__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=="],
@@ -295,6 +252,8 @@
"@types/body-parser": ["@types/body-parser@1.19.0", "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ=="],
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
"@types/cacheable-request": ["@types/cacheable-request@6.0.1", "", { "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "*", "@types/node": "*", "@types/responselike": "*" } }, "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ=="],
"@types/connect": ["@types/connect@3.4.34", "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.34.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ=="],
@@ -479,6 +438,8 @@
"buffer-writer": ["buffer-writer@2.0.0", "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz", {}, "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="],
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
"busboy": ["busboy@0.2.14", "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz", { "dependencies": { "dicer": "0.2.5", "readable-stream": "1.1.x" } }, "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM="],
"bytes": ["bytes@3.1.0", "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz", {}, "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg=="],
@@ -905,7 +866,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="],
"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=="],
@@ -1329,6 +1290,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-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=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
@@ -1503,7 +1466,7 @@
"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=="],
@@ -1771,7 +1734,7 @@
"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=="],
@@ -1871,12 +1834,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=="],
"@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=="],
"@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=="],
@@ -1887,6 +1844,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/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/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=="],
@@ -1905,12 +1864,12 @@
"@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/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/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=="],
@@ -1941,6 +1900,8 @@
"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=="],
"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=="],
@@ -1957,6 +1918,8 @@
"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=="],
"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="],
@@ -1967,6 +1930,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=="],
"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=="],
"cookies/depd": ["depd@2.0.0", "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
@@ -2033,8 +1998,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-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=="],
"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=="],
@@ -2177,8 +2140,12 @@
"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/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=="],
"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="],
@@ -2209,9 +2176,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=="],
"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=="],
@@ -2225,8 +2192,6 @@
"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=="],
"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=="],
"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=="],
@@ -2257,6 +2222,8 @@
"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=="],
"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=="],
@@ -2291,6 +2258,8 @@
"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=="],
"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=="],
@@ -2347,6 +2316,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/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/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=="],
@@ -2359,6 +2330,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/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=="],
"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=="],
@@ -2367,6 +2342,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=="],
"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=="],
"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=="],
@@ -2375,10 +2352,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=="],
"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/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=="],
"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=="],
@@ -2395,6 +2376,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=="],
"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/string_decoder": ["string_decoder@0.10.31", "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz", {}, "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="],
@@ -2427,6 +2410,8 @@
"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/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=="],
@@ -2437,6 +2422,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-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/react-is": ["react-is@17.0.1", "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz", {}, "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA=="],
@@ -2497,6 +2484,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-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/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=="],
@@ -2537,14 +2526,20 @@
"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/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/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=="],
"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=="],
"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=="],
@@ -2581,6 +2576,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=="],
"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/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=="],
@@ -2597,24 +2594,34 @@
"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/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="],
"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=="],
"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=="],
"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=="],
"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=="],
"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/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=="],
@@ -2629,6 +2636,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/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/isobject": ["isobject@2.1.0", "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz", { "dependencies": { "isarray": "1.0.0" } }, "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk="],
@@ -2659,6 +2668,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/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/react-is": ["react-is@17.0.1", "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz", {}, "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA=="],
@@ -2673,6 +2684,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=="],
"@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=="],
"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=="],
@@ -2685,6 +2698,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=="],
"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/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="],

View File

@@ -4,3 +4,10 @@
[runtime]
# Enable Node.js compatibility mode
bun = true
# TypeScript transpiler settings
# Required for TypeORM decorators
[transpiler]
tsconfig = "tsconfig.json"
emitDecoratorMetadata = true
experimentalDecorators = true

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
**Author**: Aleksi Pekkala <aleksipekkala@gmail.com>
**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.
# 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
**Author**: TJ Holowaychuk <tj@vision-media.ca>
**Repo**: expressjs/express
@@ -379,77 +319,6 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# glob
**Author**: Isaac Z. Schlueter <i@izs.me> (https://blog.izs.me/)
**Repo**: [object Object]
**License**: BlueOak-1.0.0
**Description**: the most correct and second fastest glob implementation in JavaScript
## License Text
All packages under `src/` are licensed according to the terms in
their respective `LICENSE` or `LICENSE.md` files.
The remainder of this project is licensed under the Blue Oak
Model License, as follows:
-----
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
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.***
# jsonwebtoken
**Author**: auth0
**Repo**: [object Object]
@@ -949,23 +818,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
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
**Author**: Chris O'Hara <cohara87@gmail.com>
**Repo**: [object Object]
@@ -1090,6 +942,35 @@ 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.
# @types/bun
**Author**: undefined
**Repo**: [object Object]
**License**: MIT
**Description**: TypeScript definitions for bun
## License Text
MIT License
Copyright (c) Microsoft Corporation.
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
# @types/cors
**Author**: undefined
**Repo**: [object Object]
@@ -1382,25 +1263,65 @@ SOFTWARE.
# rimraf
**Author**: Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)
**Repo**: git://github.com/isaacs/rimraf.git
**License**: ISC
**Repo**: git@github.com:isaacs/rimraf.git
**License**: BlueOak-1.0.0
**Description**: A deep deletion module for node (like `rm -rf`)
## 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 with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
## Purpose
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
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

View File

@@ -1,6 +1,3 @@
const dotenv = require('dotenv');
dotenv.config();
module.exports = {
type: process.env.DB_TYPE,
host: process.env.DB_HOST,

View File

@@ -1,6 +1,6 @@
{
"name": "@odit/lfk-backend",
"version": "1.7.2",
"version": "1.8.2",
"main": "src/app.ts",
"repository": "https://git.odit.services/lfk/backend",
"author": {
@@ -22,7 +22,6 @@
],
"license": "CC-BY-NC-SA-4.0",
"dependencies": {
"@node-rs/argon2": "^2.0.2",
"@odit/class-validator-jsonschema": "2.1.1",
"axios": "0.21.1",
"body-parser": "1.19.0",
@@ -34,9 +33,7 @@
"cookie-parser": "1.4.5",
"cors": "2.8.5",
"csvtojson": "2.0.10",
"dotenv": "8.2.0",
"express": "4.17.1",
"glob": "^13.0.6",
"jsonwebtoken": "8.5.1",
"libphonenumber-js": "1.9.9",
"mysql": "2.18.1",
@@ -49,12 +46,12 @@
"typeorm": "0.2.30",
"typeorm-routing-controllers-extensions": "0.2.0",
"typeorm-seeding": "1.6.1",
"uuid": "8.3.2",
"validator": "13.5.2"
},
"devDependencies": {
"@faker-js/faker": "7.6.0",
"@odit/license-exporter": "0.0.9",
"@types/bun": "^1.3.9",
"@types/cors": "2.8.19",
"@types/csvtojson": "1.1.5",
"@types/express": "5.0.6",
@@ -65,7 +62,7 @@
"cp-cli": "2.0.0",
"jest": "26.6.3",
"release-it": "14.2.2",
"rimraf": "3.0.2",
"rimraf": "^6.1.3",
"start-server-and-test": "1.11.7",
"ts-jest": "26.5.0",
"typedoc": "0.20.19",
@@ -74,7 +71,6 @@
"scripts": {
"dev": "bun --watch src/app.ts",
"start": "bun src/app.ts",
"build": "rimraf ./dist && tsc && cp-cli ./src/static ./dist/static",
"docs": "typedoc --out docs src",
"test": "jest",
"test:watch": "jest --watchAll",

View File

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

View File

@@ -14,6 +14,7 @@ import { ResponseStats } from '../models/responses/ResponseStats';
import { ResponseStatsOrgnisation } from '../models/responses/ResponseStatsOrganization';
import { ResponseStatsRunner } from '../models/responses/ResponseStatsRunner';
import { ResponseStatsTeam } from '../models/responses/ResponseStatsTeam';
import { getStatsCache, setStatsCache } from '../nats/StatsKV';
@JsonController('/stats')
export class StatsController {
@@ -22,6 +23,13 @@ export class StatsController {
@ResponseSchema(ResponseStats)
@OpenAPI({ description: "A very basic stats endpoint providing basic counters for a dashboard or simmilar" })
async get() {
// Try cache first
const cached = await getStatsCache<ResponseStats>('overview');
if (cached) {
return cached;
}
// Cache miss - compute fresh stats
const connection = getConnection();
const runnersViaSelfservice = await connection.getRepository(Runner).count({ where: { created_via: "selfservice" } });
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'] });
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")
@@ -51,6 +64,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsRunner, { isArray: true })
@OpenAPI({ description: "Returns the top ten runners by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
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'] });
if (!runners || runners.length == 0) {
return [];
@@ -60,6 +80,10 @@ export class StatsController {
topRunners.forEach(runner => {
responseRunners.push(new ResponseStatsRunner(runner));
});
// Store in cache for 60 seconds
await setStatsCache('runners.distance', responseRunners);
return responseRunners;
}
@@ -68,6 +92,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsRunner, { isArray: true })
@OpenAPI({ description: "Returns the top ten runners by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
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'] });
if (!runners || runners.length == 0) {
return [];
@@ -77,6 +108,10 @@ export class StatsController {
topRunners.forEach(runner => {
responseRunners.push(new ResponseStatsRunner(runner));
});
// Store in cache for 60 seconds
await setStatsCache('runners.donations', responseRunners);
return responseRunners;
}
@@ -85,6 +120,14 @@ export class StatsController {
@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": [] }] })
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'] });
if (!scans || scans.length == 0) {
return [];
@@ -105,6 +148,10 @@ export class StatsController {
topScans.forEach(scan => {
responseRunners.push(new ResponseStatsRunner(scan.runner, scan.lapTime));
});
// Store in cache for 60 seconds
await setStatsCache(cacheKey, responseRunners);
return responseRunners;
}
@@ -121,6 +168,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsTeam, { isArray: true })
@OpenAPI({ description: "Returns the top ten teams by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
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'] });
if (!teams || teams.length == 0) {
return [];
@@ -130,6 +184,10 @@ export class StatsController {
topTeams.forEach(team => {
responseTeams.push(new ResponseStatsTeam(team));
});
// Store in cache for 60 seconds
await setStatsCache('teams.distance', responseTeams);
return responseTeams;
}
@@ -138,6 +196,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsTeam, { isArray: true })
@OpenAPI({ description: "Returns the top ten teams by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
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'] });
if (!teams || teams.length == 0) {
return [];
@@ -147,6 +212,10 @@ export class StatsController {
topTeams.forEach(team => {
responseTeams.push(new ResponseStatsTeam(team));
});
// Store in cache for 60 seconds
await setStatsCache('teams.donations', responseTeams);
return responseTeams;
}
@@ -155,6 +224,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
@OpenAPI({ description: "Returns the top ten organizations by distance.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
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'] });
if (!orgs || orgs.length == 0) {
return [];
@@ -164,6 +240,10 @@ export class StatsController {
topOrgs.forEach(org => {
responseOrgs.push(new ResponseStatsOrgnisation(org));
});
// Store in cache for 60 seconds
await setStatsCache('organizations.distance', responseOrgs);
return responseOrgs;
}
@@ -172,6 +252,13 @@ export class StatsController {
@ResponseSchema(ResponseStatsOrgnisation, { isArray: true })
@OpenAPI({ description: "Returns the top ten organizations by donations.", security: [{ "StatsApiToken": [] }, { "AuthToken": [] }, { "RefreshTokenCookie": [] }] })
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'] });
if (!orgs || orgs.length == 0) {
return [];
@@ -181,6 +268,10 @@ export class StatsController {
topOrgs.forEach(org => {
responseOrgs.push(new ResponseStatsOrgnisation(org));
});
// Store in cache for 60 seconds
await setStatsCache('organizations.donations', responseOrgs);
return responseOrgs;
}
}

View File

@@ -1,4 +1,4 @@
import { verify } from '@node-rs/argon2';
import * as Bun from 'bun';
import { Request, Response } from 'express';
import { getConnectionManager } from 'typeorm';
import { StatsClient } from '../models/entities/StatsClient';
@@ -55,7 +55,7 @@ const StatsAuth = async (req: Request, res: Response, next: () => void) => {
}
}
else {
if (!(await verify(client.key, provided_token))) {
if (!(await Bun.password.verify(provided_token, client.key))) {
res.status(401).send("Api token invalid.");
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 * as jsonwebtoken from 'jsonwebtoken';
import { getConnectionManager } from 'typeorm';
@@ -49,7 +49,7 @@ export class ResetPassword {
if (found_user.refreshTokenCount !== decoded["refreshTokenCount"]) { throw new RefreshTokenCountInvalidError(); }
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);
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 { getConnectionManager } from 'typeorm';
import { InvalidCredentialsError, PasswordNeededError, UserDisabledError, UserNotFoundError } from '../../../errors/AuthError';
@@ -56,7 +56,7 @@ export class CreateAuth {
throw new UserNotFoundError();
}
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();
}

View File

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

View File

@@ -1,7 +1,6 @@
import { IsBoolean, IsInt, IsOptional, IsPositive, IsString } from 'class-validator';
import crypto from 'crypto';
import { getConnection } from 'typeorm';
import * as uuid from 'uuid';
import { config } from '../../../config';
import { TrackNotFoundError } from '../../../errors/TrackErrors';
import { ScanStation } from '../../entities/ScanStation';
@@ -42,7 +41,7 @@ export class CreateScanStation {
newStation.enabled = this.enabled;
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.cleartextkey = newStation.prefix + "." + newUUID;
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 crypto from 'crypto';
import * as uuid from 'uuid';
import { StatsClient } from '../../entities/StatsClient';
/**
@@ -23,9 +22,9 @@ export class CreateStatsClient {
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.key = await hash(newClient.prefix + "." + newUUID);
newClient.key = await Bun.password.hash(newClient.prefix + "." + newUUID);
newClient.cleartextkey = newClient.prefix + "." + newUUID;
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 { IsBoolean, IsEmail, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
import { getConnectionManager } from 'typeorm';
import * as uuid from 'uuid';
import { config } from '../../../config';
import { PasswordMustContainLowercaseLetterError, PasswordMustContainNumberError, PasswordMustContainUppercaseLetterError, PasswordTooShortError, UserEmailNeededError, UsernameContainsIllegalCharacterError } from '../../../errors/UserErrors';
import { UserGroupNotFoundError } from '../../../errors/UserGroupErrors';
@@ -108,9 +107,9 @@ export class CreateUser {
newUser.firstname = this.firstname
newUser.middlename = this.middlename
newUser.lastname = this.lastname
newUser.uuid = uuid.v4()
newUser.uuid = crypto.randomUUID()
newUser.phone = this.phone
newUser.password = await hash(this.password + newUser.uuid);
newUser.password = await Bun.password.hash(this.password + newUser.uuid);
newUser.groups = await this.getGroups();
newUser.enabled = this.enabled;

View File

@@ -1,5 +1,4 @@
import { IsBoolean, IsInt, IsObject, IsOptional } from 'class-validator';
import * as uuid from 'uuid';
import { Address } from '../../entities/Address';
import { RunnerOrganization } from '../../entities/RunnerOrganization';
import { CreateRunnerGroup } from '../create/CreateRunnerGroup';
@@ -42,7 +41,7 @@ export class UpdateRunnerOrganization extends CreateRunnerGroup {
Address.validate(organization.address);
if (this.registrationEnabled && !organization.key) {
organization.key = uuid.v4().toUpperCase();
organization.key = crypto.randomUUID().toUpperCase();
}
else {
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 { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl } from 'class-validator';
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("number")) { throw new PasswordMustContainNumberError(); }
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;
}

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