start backend work
@ -10,7 +10,7 @@ spec:
|
|||||||
entryPoints:
|
entryPoints:
|
||||||
- websecure
|
- websecure
|
||||||
routes:
|
routes:
|
||||||
- match: Host(`ssr.fascinated.cc`) && PathPrefix(`/api`)
|
- match: Host(`ssr.fascinated.cc`) && PathPrefix(`/api-test`)
|
||||||
kind: Rule
|
kind: Rule
|
||||||
middlewares:
|
middlewares:
|
||||||
- name: default-headers
|
- name: default-headers
|
||||||
|
@ -7,4 +7,4 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
stripPrefix:
|
stripPrefix:
|
||||||
prefixes:
|
prefixes:
|
||||||
- "/api"
|
- "/api-test"
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
name: "Deploy Website"
|
name: "Deploy Website"
|
||||||
|
|
||||||
on:
|
#on:
|
||||||
workflow_dispatch:
|
# workflow_dispatch:
|
||||||
push:
|
# push:
|
||||||
branches:
|
# branches:
|
||||||
- master
|
# - master
|
||||||
paths:
|
# paths:
|
||||||
- website/**
|
# - website/**
|
||||||
- common/**
|
# - common/**
|
||||||
- .gitea/kubernetes/website/**
|
# - .gitea/kubernetes/website/**
|
||||||
- .gitea/workflows/deploy-website.yml
|
# - .gitea/workflows/deploy-website.yml
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
parserOptions: {
|
|
||||||
project: 'tsconfig.json',
|
|
||||||
tsconfigRootDir: __dirname,
|
|
||||||
sourceType: 'module',
|
|
||||||
},
|
|
||||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
|
||||||
extends: [
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:prettier/recommended',
|
|
||||||
],
|
|
||||||
root: true,
|
|
||||||
env: {
|
|
||||||
node: true,
|
|
||||||
jest: true,
|
|
||||||
},
|
|
||||||
ignorePatterns: ['.eslintrc.js'],
|
|
||||||
rules: {
|
|
||||||
'@typescript-eslint/interface-name-prefix': 'off',
|
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,31 +0,0 @@
|
|||||||
FROM node:20-alpine3.17
|
|
||||||
|
|
||||||
# Install pnpm globally
|
|
||||||
RUN npm install -g pnpm
|
|
||||||
ENV PNPM_HOME=/usr/local/bin
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
ARG GIT_REV
|
|
||||||
ENV GIT_REV=${GIT_REV}
|
|
||||||
|
|
||||||
# Copy necessary files for installation
|
|
||||||
COPY package.json* pnpm-lock.yaml* pnpm-workspace.yaml* ./
|
|
||||||
COPY common ./common
|
|
||||||
COPY backend ./backend
|
|
||||||
|
|
||||||
# Install all dependencies (for common and backend)
|
|
||||||
RUN pnpm install
|
|
||||||
|
|
||||||
# Run in production mode
|
|
||||||
ENV NODE_ENV=production
|
|
||||||
|
|
||||||
# Build the common workspace first, then the backend
|
|
||||||
RUN pnpm --filter ...common build
|
|
||||||
RUN pnpm --filter ...backend build
|
|
||||||
|
|
||||||
# Expose the port your application runs on
|
|
||||||
EXPOSE 8080
|
|
||||||
|
|
||||||
# Command to run your app
|
|
||||||
CMD ["node", "backend/dist/main.js"]
|
|
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/nest-cli",
|
|
||||||
"collection": "@nestjs/schematics",
|
|
||||||
"sourceRoot": "src",
|
|
||||||
"compilerOptions": {
|
|
||||||
"deleteOutDir": true
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "backend",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"author": "fascinated7",
|
|
||||||
"license": "MIT",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "nest start --watch --webpack webpack-hmr.config.js",
|
|
||||||
"build": "nest build",
|
|
||||||
"start": "nest start"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fastify/one-line-logger": "^2.0.0",
|
|
||||||
"@nestjs/common": "^10.0.0",
|
|
||||||
"@nestjs/core": "^10.0.0",
|
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
|
||||||
"@nestjs/platform-fastify": "^10.4.4",
|
|
||||||
"@ssr/common": "workspace:*",
|
|
||||||
"reflect-metadata": "^0.2.0",
|
|
||||||
"rxjs": "^7.8.1"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@nestjs/cli": "^10.0.0",
|
|
||||||
"@nestjs/schematics": "^10.0.0",
|
|
||||||
"@nestjs/testing": "^10.0.0",
|
|
||||||
"@types/express": "^4.17.17",
|
|
||||||
"@types/node": "^20.3.1",
|
|
||||||
"@types/supertest": "^6.0.0",
|
|
||||||
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
||||||
"@typescript-eslint/parser": "^8.0.0",
|
|
||||||
"concurrently": "^9.0.1",
|
|
||||||
"eslint": "^8.42.0",
|
|
||||||
"eslint-config-prettier": "^9.0.0",
|
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
|
||||||
"nodemon": "^2.0.20",
|
|
||||||
"prettier": "^3.0.0",
|
|
||||||
"source-map-support": "^0.5.21",
|
|
||||||
"supertest": "^7.0.0",
|
|
||||||
"ts-loader": "^9.4.3",
|
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"tsup": "^8.3.0",
|
|
||||||
"typescript": "^5"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import { Module } from "@nestjs/common";
|
|
||||||
import { AppController } from "./controller/app.controller";
|
|
||||||
import { PlayerService } from "./service/player.service";
|
|
||||||
import { PlayerController } from "./controller/player.controller";
|
|
||||||
import { AppService } from "./service/app.service";
|
|
||||||
|
|
||||||
@Module({
|
|
||||||
imports: [],
|
|
||||||
controllers: [AppController, PlayerController],
|
|
||||||
providers: [AppService, PlayerService],
|
|
||||||
})
|
|
||||||
export class AppModule {}
|
|
@ -1,15 +0,0 @@
|
|||||||
import { Controller, Get } from "@nestjs/common";
|
|
||||||
import { AppService } from "../service/app.service";
|
|
||||||
|
|
||||||
@Controller()
|
|
||||||
export class AppController {
|
|
||||||
constructor(private readonly appService: AppService) {}
|
|
||||||
|
|
||||||
@Get("/")
|
|
||||||
getHome() {
|
|
||||||
return {
|
|
||||||
message: "ScoreSaber Reloaded API",
|
|
||||||
version: this.appService.getVersion(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import { Controller, Get, Param } from "@nestjs/common";
|
|
||||||
import { PlayerService } from "../service/player.service";
|
|
||||||
|
|
||||||
@Controller("/player")
|
|
||||||
export class PlayerController {
|
|
||||||
constructor(private readonly playerService: PlayerService) {}
|
|
||||||
|
|
||||||
@Get("/history/:id")
|
|
||||||
getHistory(@Param("id") id: string) {
|
|
||||||
return this.playerService.getHistory(id);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
import { NestFactory } from "@nestjs/core";
|
|
||||||
import { FastifyAdapter, NestFastifyApplication } from "@nestjs/platform-fastify";
|
|
||||||
import { AppModule } from "./app.module";
|
|
||||||
|
|
||||||
async function bootstrap() {
|
|
||||||
const app = await NestFactory.create<NestFastifyApplication>(
|
|
||||||
AppModule,
|
|
||||||
new FastifyAdapter({
|
|
||||||
logger: {
|
|
||||||
transport: {
|
|
||||||
target: "@fastify/one-line-logger",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
logger: ["error", "warn", "log"],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await app.listen(8080, "0.0.0.0");
|
|
||||||
}
|
|
||||||
bootstrap();
|
|
@ -1,14 +0,0 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
|
||||||
import { isProduction } from "@ssr/common/dist";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class AppService {
|
|
||||||
/**
|
|
||||||
* Gets the app version.
|
|
||||||
*
|
|
||||||
* @returns the app version
|
|
||||||
*/
|
|
||||||
getVersion(): string {
|
|
||||||
return `1.0.0-${isProduction() ? process.env.GIT_REV.substring(0, 7) : "dev"}`;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class PlayerService {
|
|
||||||
/**
|
|
||||||
* Gets the statistic history for the given player
|
|
||||||
*
|
|
||||||
* @param id the id of the player
|
|
||||||
* @returns the players statistic history
|
|
||||||
*/
|
|
||||||
getHistory(id: string) {
|
|
||||||
return {
|
|
||||||
id: id,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "./tsconfig.json",
|
|
||||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
|
||||||
}
|
|
@ -1,6 +0,0 @@
|
|||||||
module.exports = function (options) {
|
|
||||||
return {
|
|
||||||
...options,
|
|
||||||
stats: "minimal", // This disables the full-screen mode and simplifies the output
|
|
||||||
};
|
|
||||||
};
|
|
BIN
bun.lockb
Normal file
@ -1 +0,0 @@
|
|||||||
export * from "src/utils";
|
|
@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"module": "NodeNext",
|
|
||||||
"moduleResolution": "NodeNext",
|
|
||||||
"declaration": true,
|
|
||||||
"removeComments": true,
|
|
||||||
"emitDecoratorMetadata": true,
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
|
||||||
"target": "ES2021",
|
|
||||||
"sourceMap": true,
|
|
||||||
"outDir": "./dist",
|
|
||||||
"baseUrl": "./",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strictNullChecks": false,
|
|
||||||
"noImplicitAny": false,
|
|
||||||
"strictBindCallApply": false,
|
|
||||||
"forceConsistentCasingInFileNames": false,
|
|
||||||
"noFallthroughCasesInSwitch": false
|
|
||||||
}
|
|
||||||
}
|
|
15
package.json
@ -1,14 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "scoresaber-reloadedv3",
|
"name": "scoresaber-reloaded",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"workspaces": [
|
||||||
|
"projects/backend",
|
||||||
|
"projects/website",
|
||||||
|
"projects/common"
|
||||||
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm --parallel --workspace-concurrency=4 run -r dev",
|
"dev": "bun run --filter '*' dev"
|
||||||
|
|
||||||
"build:website": "pnpm --filter website build",
|
|
||||||
"build:backend": "pnpm --filter backend build",
|
|
||||||
|
|
||||||
"start:website": "pnpm --filter website start",
|
|
||||||
"start:backend": "pnpm --filter backend start"
|
|
||||||
},
|
},
|
||||||
"author": "fascinated7",
|
"author": "fascinated7",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
10721
pnpm-lock.yaml
generated
@ -1,4 +0,0 @@
|
|||||||
packages:
|
|
||||||
- "common"
|
|
||||||
- "website"
|
|
||||||
- "backend"
|
|
42
projects/backend/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
**/*.trace
|
||||||
|
**/*.zip
|
||||||
|
**/*.tar.gz
|
||||||
|
**/*.tgz
|
||||||
|
**/*.log
|
||||||
|
package-lock.json
|
||||||
|
**/*.bun
|
19
projects/backend/Dockerfile
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
FROM imbios/bun-node AS base
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
FROM base AS depends
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
# Run the app
|
||||||
|
FROM base AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
ENV NODE_ENV production
|
||||||
|
|
||||||
|
COPY --from=depends /app/node_modules ./node_modules
|
||||||
|
COPY --from=depends /app/package.json* /app/bun.lockb* ./
|
||||||
|
COPY --from=depends /app/projects/backend ./projects/backend
|
||||||
|
|
||||||
|
CMD ["bun", "run", "--filter", "backend", "start"]
|
9
projects/backend/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Backend
|
||||||
|
|
||||||
|
## Development
|
||||||
|
To start the development server run:
|
||||||
|
```bash
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Open http://localhost:3000/ with your browser to see the result.
|
21
projects/backend/package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"dev": "bun run --watch src/index.ts",
|
||||||
|
"start": "bun run src/index.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@elysiajs/cors": "^1.1.1",
|
||||||
|
"@ssr/common": "workspace:common",
|
||||||
|
"@tqman/nice-logger": "^1.0.1",
|
||||||
|
"elysia": "latest",
|
||||||
|
"elysia-autoroutes": "^0.5.0",
|
||||||
|
"elysia-decorators": "^1.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"bun-types": "latest"
|
||||||
|
},
|
||||||
|
"module": "src/index.js"
|
||||||
|
}
|
10
projects/backend/src/common/app-utils.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* Gets the app version.
|
||||||
|
*/
|
||||||
|
export function getAppVersion() {
|
||||||
|
if (!process.env.APP_VERSION) {
|
||||||
|
const packageJson = require("../../package.json");
|
||||||
|
process.env.APP_VERSION = packageJson.version;
|
||||||
|
}
|
||||||
|
return process.env.APP_VERSION + "-" + (process.env.GIT_REV?.substring(0, 7) ?? "dev");
|
||||||
|
}
|
13
projects/backend/src/controller/app.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Controller, Get } from "elysia-decorators";
|
||||||
|
import { getAppVersion } from "../common/app-utils";
|
||||||
|
|
||||||
|
@Controller("/")
|
||||||
|
export default class AppController {
|
||||||
|
@Get()
|
||||||
|
public index() {
|
||||||
|
return {
|
||||||
|
app: "backend",
|
||||||
|
version: getAppVersion(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
53
projects/backend/src/index.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { Elysia } from "elysia";
|
||||||
|
import cors from "@elysiajs/cors";
|
||||||
|
import { decorators } from "elysia-decorators";
|
||||||
|
import { logger } from "@tqman/nice-logger";
|
||||||
|
import AppController from "./controller/app";
|
||||||
|
|
||||||
|
const app = new Elysia();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom error handler
|
||||||
|
*/
|
||||||
|
app.onError({ as: "global" }, ({ code, error }) => {
|
||||||
|
// Return default error for type validation
|
||||||
|
if (code === "VALIDATION") {
|
||||||
|
return error.all;
|
||||||
|
}
|
||||||
|
|
||||||
|
let status = "status" in error ? error.status : undefined;
|
||||||
|
return {
|
||||||
|
...((status && { statusCode: status }) || { status: code }),
|
||||||
|
...(error.message != code && { message: error.message }),
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enable CORS
|
||||||
|
*/
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request logger
|
||||||
|
*/
|
||||||
|
app.use(
|
||||||
|
logger({
|
||||||
|
mode: "combined",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controllers
|
||||||
|
*/
|
||||||
|
app.use(
|
||||||
|
decorators({
|
||||||
|
controllers: [AppController],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
app.onStart(() => {
|
||||||
|
console.log("Listening on port http://localhost:8080");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(8080);
|
12
projects/backend/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2021",
|
||||||
|
"module": "ES2022",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"types": ["bun-types"],
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "@ssr/common",
|
"name": "@ssr/common",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsup src/index.ts --watch",
|
"dev": "tsup src/index.ts --watch",
|
||||||
"build": "tsup src/index.ts"
|
"build": "tsup src/index.ts"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.7.4",
|
"@types/node": "^22.7.4",
|
||||||
"tsup": "^6.5.0",
|
"tsup": "^8",
|
||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ky": "^1.7.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
49
projects/common/src/index.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
export * from "src/utils/utils";
|
||||||
|
export * from "src/utils/time-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Player stuff
|
||||||
|
*/
|
||||||
|
export * from "src/types/player/player-history";
|
||||||
|
export * from "src/types/player/player-tracked-since";
|
||||||
|
export * from "src/types/player/player";
|
||||||
|
export * from "src/types/player/impl/scoresaber-player";
|
||||||
|
export * from "src/utils/player-utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Score stuff
|
||||||
|
*/
|
||||||
|
export * from "src/types/score/score";
|
||||||
|
export * from "src/types/score/score-sort";
|
||||||
|
export * from "src/types/score/modifier";
|
||||||
|
export * from "src/types/score/impl/scoresaber-score";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service stuff
|
||||||
|
*/
|
||||||
|
export * from "src/service/impl/beatsaver";
|
||||||
|
export * from "src/service/impl/scoresaber";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scoresaber Tokens
|
||||||
|
*/
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-badge-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-difficulty-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-leaderboard-player-info-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-leaderboard-scores-page-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-leaderboard-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-metadata-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-player-score-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-player-scores-page-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-player-search-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-player-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-players-page-token";
|
||||||
|
export * from "src/types/token/scoresaber/score-saber-score-token";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Beatsaver Tokens
|
||||||
|
*/
|
||||||
|
export * from "src/types/token/beatsaver/beat-saver-account-token";
|
||||||
|
export * from "src/types/token/beatsaver/beat-saver-map-metadata-token";
|
||||||
|
export * from "src/types/token/beatsaver/beat-saver-map-stats-token";
|
||||||
|
export * from "src/types/token/beatsaver/beat-saver-map-token";
|
34
projects/common/src/service/impl/beatsaver.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import Service from "../service";
|
||||||
|
import { BeatSaverMapToken } from "../../types/token/beatsaver/beat-saver-map-token";
|
||||||
|
|
||||||
|
const API_BASE = "https://api.beatsaver.com";
|
||||||
|
const LOOKUP_MAP_BY_HASH_ENDPOINT = `${API_BASE}/maps/hash/:query`;
|
||||||
|
|
||||||
|
class BeatSaverService extends Service {
|
||||||
|
constructor() {
|
||||||
|
super("BeatSaver");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the map that match the query.
|
||||||
|
*
|
||||||
|
* @param query the query to search for
|
||||||
|
* @param useProxy whether to use the proxy or not
|
||||||
|
* @returns the map that match the query, or undefined if no map were found
|
||||||
|
*/
|
||||||
|
async lookupMap(query: string): Promise<BeatSaverMapToken | undefined> {
|
||||||
|
const before = performance.now();
|
||||||
|
this.log(`Looking up map "${query}"...`);
|
||||||
|
|
||||||
|
const response = await this.fetch<BeatSaverMapToken>(LOOKUP_MAP_BY_HASH_ENDPOINT.replace(":query", query));
|
||||||
|
// Map not found
|
||||||
|
if (response == undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log(`Found map "${response.id}" in ${(performance.now() - before).toFixed(0)}ms`);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const beatsaverService = new BeatSaverService();
|
@ -1,12 +1,12 @@
|
|||||||
import ScoreSaberLeaderboardScoresPageToken from "@/common/model/token/scoresaber/score-saber-leaderboard-scores-page-token";
|
|
||||||
import ScoreSaberPlayerScoresPageToken from "@/common/model/token/scoresaber/score-saber-player-scores-page-token";
|
|
||||||
import { ScoreSaberPlayerSearchToken } from "@/common/model/token/scoresaber/score-saber-player-search-token";
|
|
||||||
import ScoreSaberPlayerToken from "@/common/model/token/scoresaber/score-saber-player-token";
|
|
||||||
import { ScoreSaberPlayersPageToken } from "@/common/model/token/scoresaber/score-saber-players-page-token";
|
|
||||||
import { ScoreSort } from "../../model/score/score-sort";
|
|
||||||
import Service from "../service";
|
import Service from "../service";
|
||||||
import ScoreSaberPlayer, { getScoreSaberPlayerFromToken } from "@/common/model/player/impl/scoresaber-player";
|
import { ScoreSaberPlayerSearchToken } from "../../types/token/scoresaber/score-saber-player-search-token";
|
||||||
import ScoreSaberLeaderboardToken from "@/common/model/token/scoresaber/score-saber-leaderboard-token";
|
import ScoreSaberPlayerToken from "../../types/token/scoresaber/score-saber-player-token";
|
||||||
|
import ScoreSaberPlayer, { getScoreSaberPlayerFromToken } from "../../types/player/impl/scoresaber-player";
|
||||||
|
import { ScoreSaberPlayersPageToken } from "../../types/token/scoresaber/score-saber-players-page-token";
|
||||||
|
import { ScoreSort } from "../../types/score/score-sort";
|
||||||
|
import ScoreSaberPlayerScoresPageToken from "../../types/token/scoresaber/score-saber-player-scores-page-token";
|
||||||
|
import ScoreSaberLeaderboardToken from "../../types/token/scoresaber/score-saber-leaderboard-token";
|
||||||
|
import ScoreSaberLeaderboardScoresPageToken from "../../types/token/scoresaber/score-saber-leaderboard-scores-page-token";
|
||||||
|
|
||||||
const API_BASE = "https://scoresaber.com/api";
|
const API_BASE = "https://scoresaber.com/api";
|
||||||
|
|
||||||
@ -34,16 +34,12 @@ class ScoreSaberService extends Service {
|
|||||||
* Gets the players that match the query.
|
* Gets the players that match the query.
|
||||||
*
|
*
|
||||||
* @param query the query to search for
|
* @param query the query to search for
|
||||||
* @param useProxy whether to use the proxy or not
|
|
||||||
* @returns the players that match the query, or undefined if no players were found
|
* @returns the players that match the query, or undefined if no players were found
|
||||||
*/
|
*/
|
||||||
async searchPlayers(query: string, useProxy = true): Promise<ScoreSaberPlayerSearchToken | undefined> {
|
async searchPlayers(query: string): Promise<ScoreSaberPlayerSearchToken | undefined> {
|
||||||
const before = performance.now();
|
const before = performance.now();
|
||||||
this.log(`Searching for players matching "${query}"...`);
|
this.log(`Searching for players matching "${query}"...`);
|
||||||
const results = await this.fetch<ScoreSaberPlayerSearchToken>(
|
const results = await this.fetch<ScoreSaberPlayerSearchToken>(SEARCH_PLAYERS_ENDPOINT.replace(":query", query));
|
||||||
useProxy,
|
|
||||||
SEARCH_PLAYERS_ENDPOINT.replace(":query", query)
|
|
||||||
);
|
|
||||||
if (results === undefined) {
|
if (results === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -59,12 +55,12 @@ class ScoreSaberService extends Service {
|
|||||||
* Looks up a player by their ID.
|
* Looks up a player by their ID.
|
||||||
*
|
*
|
||||||
* @param playerId the ID of the player to look up
|
* @param playerId the ID of the player to look up
|
||||||
* @param useProxy whether to use the proxy or not
|
* @param apiUrl the url to the API for SSR
|
||||||
* @returns the player that matches the ID, or undefined
|
* @returns the player that matches the ID, or undefined
|
||||||
*/
|
*/
|
||||||
async lookupPlayer(
|
async lookupPlayer(
|
||||||
playerId: string,
|
playerId: string,
|
||||||
useProxy = true
|
apiUrl: string
|
||||||
): Promise<
|
): Promise<
|
||||||
| {
|
| {
|
||||||
player: ScoreSaberPlayer;
|
player: ScoreSaberPlayer;
|
||||||
@ -74,13 +70,13 @@ class ScoreSaberService extends Service {
|
|||||||
> {
|
> {
|
||||||
const before = performance.now();
|
const before = performance.now();
|
||||||
this.log(`Looking up player "${playerId}"...`);
|
this.log(`Looking up player "${playerId}"...`);
|
||||||
const token = await this.fetch<ScoreSaberPlayerToken>(useProxy, LOOKUP_PLAYER_ENDPOINT.replace(":id", playerId));
|
const token = await this.fetch<ScoreSaberPlayerToken>(LOOKUP_PLAYER_ENDPOINT.replace(":id", playerId));
|
||||||
if (token === undefined) {
|
if (token === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
this.log(`Found player "${playerId}" in ${(performance.now() - before).toFixed(0)}ms`);
|
this.log(`Found player "${playerId}" in ${(performance.now() - before).toFixed(0)}ms`);
|
||||||
return {
|
return {
|
||||||
player: await getScoreSaberPlayerFromToken(token),
|
player: await getScoreSaberPlayerFromToken(apiUrl, token),
|
||||||
rawPlayer: token,
|
rawPlayer: token,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -89,14 +85,12 @@ class ScoreSaberService extends Service {
|
|||||||
* Lookup players on a specific page
|
* Lookup players on a specific page
|
||||||
*
|
*
|
||||||
* @param page the page to get players for
|
* @param page the page to get players for
|
||||||
* @param useProxy whether to use the proxy or not
|
|
||||||
* @returns the players on the page, or undefined
|
* @returns the players on the page, or undefined
|
||||||
*/
|
*/
|
||||||
async lookupPlayers(page: number, useProxy = true): Promise<ScoreSaberPlayersPageToken | undefined> {
|
async lookupPlayers(page: number): Promise<ScoreSaberPlayersPageToken | undefined> {
|
||||||
const before = performance.now();
|
const before = performance.now();
|
||||||
this.log(`Looking up players on page "${page}"...`);
|
this.log(`Looking up players on page "${page}"...`);
|
||||||
const response = await this.fetch<ScoreSaberPlayersPageToken>(
|
const response = await this.fetch<ScoreSaberPlayersPageToken>(
|
||||||
useProxy,
|
|
||||||
LOOKUP_PLAYERS_ENDPOINT.replace(":page", page.toString())
|
LOOKUP_PLAYERS_ENDPOINT.replace(":page", page.toString())
|
||||||
);
|
);
|
||||||
if (response === undefined) {
|
if (response === undefined) {
|
||||||
@ -111,18 +105,12 @@ class ScoreSaberService extends Service {
|
|||||||
*
|
*
|
||||||
* @param page the page to get players for
|
* @param page the page to get players for
|
||||||
* @param country the country to get players for
|
* @param country the country to get players for
|
||||||
* @param useProxy whether to use the proxy or not
|
|
||||||
* @returns the players on the page, or undefined
|
* @returns the players on the page, or undefined
|
||||||
*/
|
*/
|
||||||
async lookupPlayersByCountry(
|
async lookupPlayersByCountry(page: number, country: string): Promise<ScoreSaberPlayersPageToken | undefined> {
|
||||||
page: number,
|
|
||||||
country: string,
|
|
||||||
useProxy = true
|
|
||||||
): Promise<ScoreSaberPlayersPageToken | undefined> {
|
|
||||||
const before = performance.now();
|
const before = performance.now();
|
||||||
this.log(`Looking up players on page "${page}" for country "${country}"...`);
|
this.log(`Looking up players on page "${page}" for country "${country}"...`);
|
||||||
const response = await this.fetch<ScoreSaberPlayersPageToken>(
|
const response = await this.fetch<ScoreSaberPlayersPageToken>(
|
||||||
useProxy,
|
|
||||||
LOOKUP_PLAYERS_BY_COUNTRY_ENDPOINT.replace(":page", page.toString()).replace(":country", country)
|
LOOKUP_PLAYERS_BY_COUNTRY_ENDPOINT.replace(":page", page.toString()).replace(":country", country)
|
||||||
);
|
);
|
||||||
if (response === undefined) {
|
if (response === undefined) {
|
||||||
@ -139,7 +127,6 @@ class ScoreSaberService extends Service {
|
|||||||
* @param sort the sort to use
|
* @param sort the sort to use
|
||||||
* @param page the page to get scores for
|
* @param page the page to get scores for
|
||||||
* @param search
|
* @param search
|
||||||
* @param useProxy whether to use the proxy or not
|
|
||||||
* @returns the scores of the player, or undefined
|
* @returns the scores of the player, or undefined
|
||||||
*/
|
*/
|
||||||
async lookupPlayerScores({
|
async lookupPlayerScores({
|
||||||
@ -147,7 +134,6 @@ class ScoreSaberService extends Service {
|
|||||||
sort,
|
sort,
|
||||||
page,
|
page,
|
||||||
search,
|
search,
|
||||||
useProxy = true,
|
|
||||||
}: {
|
}: {
|
||||||
playerId: string;
|
playerId: string;
|
||||||
sort: ScoreSort;
|
sort: ScoreSort;
|
||||||
@ -160,7 +146,6 @@ class ScoreSaberService extends Service {
|
|||||||
`Looking up scores for player "${playerId}", sort "${sort}", page "${page}"${search ? `, search "${search}"` : ""}...`
|
`Looking up scores for player "${playerId}", sort "${sort}", page "${page}"${search ? `, search "${search}"` : ""}...`
|
||||||
);
|
);
|
||||||
const response = await this.fetch<ScoreSaberPlayerScoresPageToken>(
|
const response = await this.fetch<ScoreSaberPlayerScoresPageToken>(
|
||||||
useProxy,
|
|
||||||
LOOKUP_PLAYER_SCORES_ENDPOINT.replace(":id", playerId)
|
LOOKUP_PLAYER_SCORES_ENDPOINT.replace(":id", playerId)
|
||||||
.replace(":limit", 8 + "")
|
.replace(":limit", 8 + "")
|
||||||
.replace(":sort", sort)
|
.replace(":sort", sort)
|
||||||
@ -179,13 +164,11 @@ class ScoreSaberService extends Service {
|
|||||||
* Looks up a leaderboard
|
* Looks up a leaderboard
|
||||||
*
|
*
|
||||||
* @param leaderboardId the ID of the leaderboard to look up
|
* @param leaderboardId the ID of the leaderboard to look up
|
||||||
* @param useProxy whether to use the proxy or not
|
|
||||||
*/
|
*/
|
||||||
async lookupLeaderboard(leaderboardId: string, useProxy = true): Promise<ScoreSaberLeaderboardToken | undefined> {
|
async lookupLeaderboard(leaderboardId: string): Promise<ScoreSaberLeaderboardToken | undefined> {
|
||||||
const before = performance.now();
|
const before = performance.now();
|
||||||
this.log(`Looking up leaderboard "${leaderboardId}"...`);
|
this.log(`Looking up leaderboard "${leaderboardId}"...`);
|
||||||
const response = await this.fetch<ScoreSaberLeaderboardToken>(
|
const response = await this.fetch<ScoreSaberLeaderboardToken>(
|
||||||
useProxy,
|
|
||||||
LOOKUP_LEADERBOARD_ENDPOINT.replace(":id", leaderboardId)
|
LOOKUP_LEADERBOARD_ENDPOINT.replace(":id", leaderboardId)
|
||||||
);
|
);
|
||||||
if (response === undefined) {
|
if (response === undefined) {
|
||||||
@ -200,18 +183,15 @@ class ScoreSaberService extends Service {
|
|||||||
*
|
*
|
||||||
* @param leaderboardId the ID of the leaderboard to look up
|
* @param leaderboardId the ID of the leaderboard to look up
|
||||||
* @param page the page to get scores for
|
* @param page the page to get scores for
|
||||||
* @param useProxy whether to use the proxy or not
|
|
||||||
* @returns the scores of the leaderboard, or undefined
|
* @returns the scores of the leaderboard, or undefined
|
||||||
*/
|
*/
|
||||||
async lookupLeaderboardScores(
|
async lookupLeaderboardScores(
|
||||||
leaderboardId: string,
|
leaderboardId: string,
|
||||||
page: number,
|
page: number
|
||||||
useProxy = true
|
|
||||||
): Promise<ScoreSaberLeaderboardScoresPageToken | undefined> {
|
): Promise<ScoreSaberLeaderboardScoresPageToken | undefined> {
|
||||||
const before = performance.now();
|
const before = performance.now();
|
||||||
this.log(`Looking up scores for leaderboard "${leaderboardId}", page "${page}"...`);
|
this.log(`Looking up scores for leaderboard "${leaderboardId}", page "${page}"...`);
|
||||||
const response = await this.fetch<ScoreSaberLeaderboardScoresPageToken>(
|
const response = await this.fetch<ScoreSaberLeaderboardScoresPageToken>(
|
||||||
useProxy,
|
|
||||||
LOOKUP_LEADERBOARD_SCORES_ENDPOINT.replace(":id", leaderboardId).replace(":page", page.toString())
|
LOOKUP_LEADERBOARD_SCORES_ENDPOINT.replace(":id", leaderboardId).replace(":page", page.toString())
|
||||||
);
|
);
|
||||||
if (response === undefined) {
|
if (response === undefined) {
|
@ -1,11 +1,10 @@
|
|||||||
import ky from "ky";
|
import ky from "ky";
|
||||||
import { isRunningAsWorker } from "@/common/browser-utils";
|
|
||||||
|
|
||||||
export default class Service {
|
export default class Service {
|
||||||
/**
|
/**
|
||||||
* The name of the service.
|
* The name of the service.
|
||||||
*/
|
*/
|
||||||
private name: string;
|
private readonly name: string;
|
||||||
|
|
||||||
constructor(name: string) {
|
constructor(name: string) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -17,7 +16,7 @@ export default class Service {
|
|||||||
* @param data the data to log
|
* @param data the data to log
|
||||||
*/
|
*/
|
||||||
public log(data: unknown) {
|
public log(data: unknown) {
|
||||||
console.log(`[${isRunningAsWorker() ? "Worker - " : ""}${this.name}]: ${data}`);
|
console.log(`[${this.name}]: ${data}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,25 +28,17 @@ export default class Service {
|
|||||||
*/
|
*/
|
||||||
private buildRequestUrl(useProxy: boolean, url: string): string {
|
private buildRequestUrl(useProxy: boolean, url: string): string {
|
||||||
return (useProxy ? "https://proxy.fascinated.cc/" : "") + url;
|
return (useProxy ? "https://proxy.fascinated.cc/" : "") + url;
|
||||||
// return (useProxy ? config.siteUrl + "/api/proxy?url=" : "") + url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches data from the given url.
|
* Fetches data from the given url.
|
||||||
*
|
*
|
||||||
* @param useProxy whether to use proxy or not
|
|
||||||
* @param url the url to fetch
|
* @param url the url to fetch
|
||||||
* @returns the fetched data
|
* @returns the fetched data
|
||||||
*/
|
*/
|
||||||
public async fetch<T>(useProxy: boolean, url: string): Promise<T | undefined> {
|
public async fetch<T>(url: string): Promise<T | undefined> {
|
||||||
try {
|
try {
|
||||||
return await ky
|
return await ky.get<T>(this.buildRequestUrl(true, url)).json();
|
||||||
.get<T>(this.buildRequestUrl(useProxy, url), {
|
|
||||||
next: {
|
|
||||||
revalidate: 60, // 1 minute
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.json();
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error fetching data from ${url}:`, error);
|
console.error(`Error fetching data from ${url}:`, error);
|
||||||
return undefined;
|
return undefined;
|
@ -1,9 +1,8 @@
|
|||||||
import Player, { StatisticChange } from "../player";
|
import Player, { StatisticChange } from "../player";
|
||||||
import ScoreSaberPlayerToken from "@/common/model/token/scoresaber/score-saber-player-token";
|
|
||||||
import { PlayerHistory } from "@/common/player/player-history";
|
|
||||||
import { config } from "../../../../../config";
|
|
||||||
import ky from "ky";
|
import ky from "ky";
|
||||||
import { formatDateMinimal, getDaysAgoDate, getMidnightAlignedDate } from "@/common/time-utils";
|
import { PlayerHistory } from "../player-history";
|
||||||
|
import ScoreSaberPlayerToken from "../../token/scoresaber/score-saber-player-token";
|
||||||
|
import { formatDateMinimal, getDaysAgoDate, getMidnightAlignedDate } from "../../../utils/time-utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A ScoreSaber player.
|
* A ScoreSaber player.
|
||||||
@ -66,7 +65,10 @@ export default interface ScoreSaberPlayer extends Player {
|
|||||||
isBeingTracked?: boolean;
|
isBeingTracked?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getScoreSaberPlayerFromToken(token: ScoreSaberPlayerToken): Promise<ScoreSaberPlayer> {
|
export async function getScoreSaberPlayerFromToken(
|
||||||
|
apiUrl: string,
|
||||||
|
token: ScoreSaberPlayerToken
|
||||||
|
): Promise<ScoreSaberPlayer> {
|
||||||
const bio: ScoreSaberBio = {
|
const bio: ScoreSaberBio = {
|
||||||
lines: token.bio?.split("\n") || [],
|
lines: token.bio?.split("\n") || [],
|
||||||
linesStripped: token.bio?.replace(/<[^>]+>/g, "")?.split("\n") || [],
|
linesStripped: token.bio?.replace(/<[^>]+>/g, "")?.split("\n") || [],
|
||||||
@ -87,7 +89,7 @@ export async function getScoreSaberPlayerFromToken(token: ScoreSaberPlayerToken)
|
|||||||
const history = await ky
|
const history = await ky
|
||||||
.get<{
|
.get<{
|
||||||
[key: string]: PlayerHistory;
|
[key: string]: PlayerHistory;
|
||||||
}>(`${config.siteUrl}/api/player/history?id=${token.id}`)
|
}>(`${apiUrl}/api/player/history?id=${token.id}`)
|
||||||
.json();
|
.json();
|
||||||
if (history === undefined || Object.entries(history).length === 0) {
|
if (history === undefined || Object.entries(history).length === 0) {
|
||||||
console.log("Player has no history, using fallback");
|
console.log("Player has no history, using fallback");
|
@ -1,4 +1,4 @@
|
|||||||
import { PlayerHistory } from "@/common/player/player-history";
|
import { PlayerHistory } from "./player-history";
|
||||||
|
|
||||||
export default class Player {
|
export default class Player {
|
||||||
/**
|
/**
|
@ -1,6 +1,6 @@
|
|||||||
import Score from "@/common/model/score/score";
|
import Score from "../score";
|
||||||
import { Modifier } from "@/common/model/score/modifier";
|
import { Modifier } from "../modifier";
|
||||||
import ScoreSaberScoreToken from "@/common/model/token/scoresaber/score-saber-score-token";
|
import ScoreSaberScoreToken from "../../token/scoresaber/score-saber-score-token";
|
||||||
|
|
||||||
export default class ScoreSaberScore extends Score {
|
export default class ScoreSaberScore extends Score {
|
||||||
constructor(
|
constructor(
|
@ -1,4 +1,4 @@
|
|||||||
import { Modifier } from "@/common/model/score/modifier";
|
import { Modifier } from "./modifier";
|
||||||
|
|
||||||
export default class Score {
|
export default class Score {
|
||||||
/**
|
/**
|
13
projects/common/src/utils/player-utils.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { PlayerHistory } from "../types/player/player-history";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sorts the player history based on date,
|
||||||
|
* so the most recent date is first
|
||||||
|
*
|
||||||
|
* @param history the player history
|
||||||
|
*/
|
||||||
|
export function sortPlayerHistory(history: Map<string, PlayerHistory>) {
|
||||||
|
return Array.from(history.entries()).sort(
|
||||||
|
(a, b) => Date.parse(b[0]) - Date.parse(a[0]) // Sort in descending order
|
||||||
|
);
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "ES2022",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"target": "ES2022",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"target": "ES2021",
|
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"incremental": true,
|
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strictNullChecks": false,
|
"strictNullChecks": false,
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
@ -5,5 +5,6 @@ export default defineConfig({
|
|||||||
splitting: false,
|
splitting: false,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
clean: true,
|
clean: true,
|
||||||
dts: true, // This line enables type declaration file generation
|
dts: true, // Generates type declarations
|
||||||
|
format: ["esm"], // Ensures output is in ESM format
|
||||||
});
|
});
|
@ -9,6 +9,7 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ssr/common": "workspace:*",
|
||||||
"@formkit/tempo": "^0.1.2",
|
"@formkit/tempo": "^0.1.2",
|
||||||
"@heroicons/react": "^2.1.5",
|
"@heroicons/react": "^2.1.5",
|
||||||
"@hookform/resolvers": "^3.9.0",
|
"@hookform/resolvers": "^3.9.0",
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 841 B After Width: | Height: | Size: 841 B |
Before Width: | Height: | Size: 132 B After Width: | Height: | Size: 132 B |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 766 B After Width: | Height: | Size: 766 B |
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 659 B |
Before Width: | Height: | Size: 604 B After Width: | Height: | Size: 604 B |
Before Width: | Height: | Size: 121 B After Width: | Height: | Size: 121 B |
Before Width: | Height: | Size: 522 B After Width: | Height: | Size: 522 B |
Before Width: | Height: | Size: 445 B After Width: | Height: | Size: 445 B |
Before Width: | Height: | Size: 320 B After Width: | Height: | Size: 320 B |
Before Width: | Height: | Size: 909 B After Width: | Height: | Size: 909 B |
Before Width: | Height: | Size: 109 B After Width: | Height: | Size: 109 B |
Before Width: | Height: | Size: 554 B After Width: | Height: | Size: 554 B |
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
Before Width: | Height: | Size: 179 B After Width: | Height: | Size: 179 B |
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 214 B |
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 339 B |
Before Width: | Height: | Size: 324 B After Width: | Height: | Size: 324 B |
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 282 B |
Before Width: | Height: | Size: 127 B After Width: | Height: | Size: 127 B |
Before Width: | Height: | Size: 254 B After Width: | Height: | Size: 254 B |
Before Width: | Height: | Size: 105 B After Width: | Height: | Size: 105 B |
Before Width: | Height: | Size: 326 B After Width: | Height: | Size: 326 B |