mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
Compare commits
59 Commits
feat/play
...
feat/image
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fd5608e6e | ||
|
|
a47dc91b22 | ||
|
|
0124af1b70 | ||
|
|
e67a8d2b32 | ||
|
|
8f4bb05143 | ||
|
|
84357ac5bf | ||
|
|
e11012e8d9 | ||
|
|
c0194ecef4 | ||
|
|
ae364f69bd | ||
|
|
d7e6da12ac | ||
|
|
6e19b2e9a0 | ||
|
|
dd20c0049d | ||
|
|
14e4176344 | ||
|
|
baf178afc5 | ||
|
|
80deb82d25 | ||
|
|
e1a903a7c9 | ||
|
|
cc2065299d | ||
|
|
0cc9effdec | ||
|
|
82dfd6506d | ||
|
|
6051e11921 | ||
|
|
86670d5931 | ||
|
|
35f009e925 | ||
|
|
5806dc6e86 | ||
|
|
38ad74d14a | ||
|
|
0b995fa540 | ||
|
|
d933c1e61d | ||
|
|
b86fc625ba | ||
|
|
c250fd557c | ||
|
|
1923cdf2a3 | ||
|
|
7e69af977b | ||
|
|
70d629227a | ||
|
|
a0dc353561 | ||
|
|
47e61599bb | ||
|
|
76d27e4708 | ||
|
|
896832b89c | ||
|
|
492013d610 | ||
|
|
e93099784c | ||
|
|
9a6826b069 | ||
|
|
f408ec56cb | ||
|
|
8394bb4259 | ||
|
|
0305a14fdd | ||
|
|
6b1521d7d4 | ||
|
|
39e187832a | ||
|
|
de80f3e6ab | ||
|
|
6990494b34 | ||
|
|
5a3fdf25ff | ||
|
|
18b14a4261 | ||
|
|
f4aa2ca4a4 | ||
|
|
6092c4e4f8 | ||
|
|
f3d7ea2663 | ||
|
|
7ff4ff8c90 | ||
|
|
7ecc068466 | ||
|
|
633b332700 | ||
|
|
cacdae79c0 | ||
|
|
ca4432bcde | ||
|
|
261a1276f5 | ||
|
|
a45b2bf9b7 | ||
|
|
f62fc1fb4b | ||
|
|
957eca7794 |
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,28 +1,2 @@
|
||||
## Description
|
||||
<!-- Briefly describe the purpose and scope of your changes -->
|
||||
|
||||
## Related Issues
|
||||
<!-- List any related issues (e.g., "Closes #123", "Fixes #456") -->
|
||||
|
||||
## Type of Change
|
||||
|
||||
- [ ] Bug fix (non-breaking change)
|
||||
- [ ] New feature (non-breaking change)
|
||||
- [ ] Breaking change (fix or feature that changes existing functionality)
|
||||
- [ ] Documentation update
|
||||
- [ ] Other (please describe):
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] I have updated relevant documentation
|
||||
- [ ] My code follows the project's coding style
|
||||
- [ ] My changes generate no new warnings/errors
|
||||
|
||||
## Notes for Reviewers
|
||||
<!-- Point out areas you'd like reviewers to focus on, questions you have, or decisions that need discussion -->
|
||||
|
||||
## Screenshots/Demo
|
||||
<!-- If applicable, add screenshots or a GIF demo of your changes (especially for UI changes) -->
|
||||
|
||||
## Additional Context
|
||||
<!-- Add any other context about the pull request here -->
|
||||
40
.github/workflows/docs.yml
vendored
Normal file
40
.github/workflows/docs.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Build docs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/docs/**"
|
||||
- ".github/workflows/docs.yml"
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "apps/docs/**"
|
||||
- ".github/workflows/docs.yml"
|
||||
|
||||
jobs:
|
||||
deploy-docs:
|
||||
name: Build and deploy docs
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "apps/docs"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: latest
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
- name: Build Project Artifacts
|
||||
run: bun run build
|
||||
- name: Deploy Project Artifacts to Cloudflare
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
packageManager: bun
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
wranglerVersion: "3.93.0"
|
||||
workingDirectory: "apps/docs"
|
||||
command: pages deploy ./dist --project-name=${{ vars.CF_DOCS_PAGES_PROJECT_NAME }} --commit-dirty=true
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
6
.github/workflows/runner.yml
vendored
6
.github/workflows/runner.yml
vendored
@@ -27,6 +27,7 @@ env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: nestrilabs/nestri
|
||||
BASE_TAG_PREFIX: runner
|
||||
BASE_IMAGE: docker.io/cachyos/cachyos:latest
|
||||
|
||||
# This makes our release ci quit prematurely
|
||||
# concurrency:
|
||||
@@ -55,7 +56,7 @@ jobs:
|
||||
swap-size-gb: 20
|
||||
-
|
||||
name: Build Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: containers/runner.Containerfile
|
||||
context: ./
|
||||
@@ -107,7 +108,7 @@ jobs:
|
||||
swap-size-gb: 20
|
||||
-
|
||||
name: Build Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
file: containers/runner.Containerfile
|
||||
context: ./
|
||||
@@ -116,3 +117,4 @@ jobs:
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha,mode=max
|
||||
cache-to: type=gha,mode=max
|
||||
pull: ${{ github.event_name == 'schedule' }} # Pull base image for scheduled builds
|
||||
|
||||
40
.github/workflows/www.yml
vendored
Normal file
40
.github/workflows/www.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Build www
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "apps/www/**"
|
||||
- ".github/workflows/www.yml"
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "apps/www/**"
|
||||
- ".github/workflows/www.yml"
|
||||
|
||||
jobs:
|
||||
deploy-www:
|
||||
name: Build and deploy www
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: "apps/www"
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
with:
|
||||
bun-version: latest
|
||||
- name: Install dependencies
|
||||
run: bun install
|
||||
- name: Build Project Artifacts
|
||||
run: bun run build
|
||||
- name: Deploy Project Artifacts to Cloudflare
|
||||
uses: cloudflare/wrangler-action@v3
|
||||
with:
|
||||
packageManager: bun
|
||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
||||
wranglerVersion: "3.93.0"
|
||||
workingDirectory: "apps/www"
|
||||
command: pages deploy ./dist --project-name=${{ vars.CF_WWW_PAGES_PROJECT_NAME }} --commit-dirty=true
|
||||
env:
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
3843
apps/docs/package-lock.json
generated
3843
apps/docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,19 +4,19 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"nestri.dev": "nuxi dev",
|
||||
"build": "nuxi build",
|
||||
"build": "nuxi build --preset=cloudflare_pages",
|
||||
"generate": "nuxi generate",
|
||||
"preview": "nuxi preview",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt-themes/docus": "latest",
|
||||
"@nuxt/devtools": "^1.4.1",
|
||||
"@nuxt/devtools": "^2.3.2",
|
||||
"@nuxt/eslint-config": "^0.5.6",
|
||||
"@nuxt/ui": "^2.19.2",
|
||||
"@nuxtjs/plausible": "^1.0.2",
|
||||
"@types/node": "^20.16.5",
|
||||
"eslint": "^9.10.0",
|
||||
"nuxt": "^3.15.4"
|
||||
"nuxt": "^3.16.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"@nestri/libmoq": "*",
|
||||
"@nestri/sdk": "0.1.0-alpha.14",
|
||||
"@nestri/ui": "*",
|
||||
"@openauthjs/openauth": "^0.2.6",
|
||||
"@openauthjs/openauth": "*",
|
||||
"@polar-sh/checkout": "^0.1.8",
|
||||
"@polar-sh/sdk": "^0.21.1",
|
||||
"@qwik-ui/headless": "^0.6.4",
|
||||
@@ -67,7 +67,7 @@
|
||||
"typescript": "5.4.5",
|
||||
"undici": "*",
|
||||
"valibot": "^0.42.1",
|
||||
"vite": "5.4.12",
|
||||
"vite": "6.0.15",
|
||||
"vite-tsconfig-paths": "^4.2.1",
|
||||
"wrangler": "^3.0.0"
|
||||
},
|
||||
|
||||
@@ -16,8 +16,8 @@ export default component$(() => {
|
||||
<div class="w-screen relative">
|
||||
<HeroSection client:load>
|
||||
<div class="sm:w-full flex gap-3 justify-center pt-4 sm:flex-row flex-col w-auto items-center">
|
||||
<Link href="/auth/login" prefetch={false} class="flex ring-2 ring-primary-500 font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||
Get early access
|
||||
<Link href="https://discord.gg/6um5K6jrYj" prefetch={false} class="flex ring-2 ring-primary-500 font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||
Join our Discord
|
||||
</Link>
|
||||
<Link href="/links/github" prefetch={false} class="sm:flex text-sm sm:text-base hidden font-bricolage items-center gap-2 rounded-full font-semibold text-gray-900/70 dark:text-gray-100/70 bg-white dark:bg-black px-5 py-4 ring-2 ring-gray-300 dark:ring-gray-700 transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" class="h-5 w-5 fill-content3-light"><path fill-rule="evenodd" d="M4.25 5.5a.75.75 0 00-.75.75v8.5c0 .414.336.75.75.75h8.5a.75.75 0 00.75-.75v-4a.75.75 0 011.5 0v4A2.25 2.25 0 0112.75 17h-8.5A2.25 2.25 0 012 14.75v-8.5A2.25 2.25 0 014.25 4h5a.75.75 0 010 1.5h-5z" clip-rule="evenodd"></path><path fill-rule="evenodd" d="M6.194 12.753a.75.75 0 001.06.053L16.5 4.44v2.81a.75.75 0 001.5 0v-4.5a.75.75 0 00-.75-.75h-4.5a.75.75 0 000 1.5h2.553l-9.056 8.194a.75.75 0 00-.053 1.06z" clip-rule="evenodd"></path></svg>
|
||||
@@ -570,8 +570,8 @@ export default component$(() => {
|
||||
</section>
|
||||
<Footer client:load>
|
||||
<div class="w-full flex justify-center flex-col items-center gap-3">
|
||||
<Link href="/auth/login" prefetch={false} class="ring-2 ring-primary-500 flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||
Get early access
|
||||
<Link href="https://discord.gg/6um5K6jrYj" prefetch={false} class="ring-2 ring-primary-500 flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||
Join our Discord
|
||||
</Link>
|
||||
<div class="mt-6 flex w-full items-center justify-center gap-2 text-xs sm:text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
||||
<span class="hover:text-primary-500 transition-colors duration-200">
|
||||
|
||||
@@ -521,8 +521,8 @@ export default component$(() => {
|
||||
</MotionComponent>
|
||||
<Footer client:load>
|
||||
<div class="w-full flex justify-center flex-col items-center gap-3">
|
||||
<Link href="/auth/login" prefetch={false} class="flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||
Get early access
|
||||
<Link href="https://discord.gg/6um5K6jrYj" prefetch={false} class="flex font-bricolage text-sm sm:text-base rounded-full bg-primary-500 px-5 py-4 font-semibold text-white transition-all hover:scale-105 active:scale-95 sm:px-6" >
|
||||
Join our Discord
|
||||
</Link>
|
||||
<div class="mt-6 flex w-full items-center justify-center gap-2 text-xs sm:text-sm font-medium text-neutral-600 dark:text-neutral-400">
|
||||
<span class="hover:text-primary-500 transition-colors duration-200">
|
||||
|
||||
12
containers/maitred.Containerfile
Normal file
12
containers/maitred.Containerfile
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM docker.io/golang:1.24-bookworm AS go-build
|
||||
WORKDIR /builder
|
||||
COPY packages/maitred/ /builder/
|
||||
RUN go build
|
||||
|
||||
FROM docker.io/golang:1.24-bookworm
|
||||
COPY --from=go-build /builder/maitred /maitred/maitred
|
||||
WORKDIR /maitred
|
||||
|
||||
RUN apt update && apt install -y --no-install-recommends pciutils
|
||||
|
||||
ENTRYPOINT ["/maitred/maitred"]
|
||||
@@ -13,6 +13,7 @@ WORKDIR /relay
|
||||
ENV VERBOSE=false
|
||||
ENV DEBUG=false
|
||||
ENV ENDPOINT_PORT=8088
|
||||
ENV MESH_PORT=8089
|
||||
ENV WEBRTC_UDP_START=10000
|
||||
ENV WEBRTC_UDP_END=20000
|
||||
ENV STUN_SERVER="stun.l.google.com:19302"
|
||||
@@ -23,6 +24,7 @@ ENV TLS_CERT=""
|
||||
ENV TLS_KEY=""
|
||||
|
||||
EXPOSE $ENDPOINT_PORT
|
||||
EXPOSE $MESH_PORT
|
||||
EXPOSE $WEBRTC_UDP_START-$WEBRTC_UDP_END/udp
|
||||
EXPOSE $WEBRTC_UDP_MUX/udp
|
||||
|
||||
|
||||
@@ -85,8 +85,8 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
||||
libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput
|
||||
|
||||
# Clone repository with proper directory structure
|
||||
RUN git clone -b dev-dmabuf https://github.com/games-on-whales/gst-wayland-display.git
|
||||
# Clone repository
|
||||
RUN git clone -b dev-dmabuf https://github.com/DatCaptainHorse/gst-wayland-display.git
|
||||
|
||||
#--------------------------------------------------------------------
|
||||
FROM gst-wayland-deps AS gst-wayland-planner
|
||||
@@ -132,9 +132,11 @@ RUN sed -i \
|
||||
# Core system components
|
||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
pacman -Sy --needed --noconfirm \
|
||||
vulkan-intel lib32-vulkan-intel vpl-gpu-rt mesa \
|
||||
steam steam-native-runtime \
|
||||
sudo xorg-xwayland seatd libinput labwc wlr-randr mangohud \
|
||||
vulkan-intel lib32-vulkan-intel vpl-gpu-rt \
|
||||
vulkan-radeon lib32-vulkan-radeon \
|
||||
mesa \
|
||||
steam steam-native-runtime gtk3 lib32-gtk3 \
|
||||
sudo xorg-xwayland seatd libinput gamescope mangohud \
|
||||
libssh2 curl wget \
|
||||
pipewire pipewire-pulse pipewire-alsa wireplumber \
|
||||
noto-fonts-cjk supervisor jq chwd lshw pacman-contrib && \
|
||||
@@ -144,6 +146,9 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||
gst-plugins-bad gst-plugin-pipewire \
|
||||
gst-plugin-webrtchttp gst-plugin-rswebrtc gst-plugin-rsrtp \
|
||||
gst-plugin-va gst-plugin-qsv && \
|
||||
# lib32 GStreamer stack to fix some games with videos
|
||||
pacman -Sy --needed --noconfirm \
|
||||
lib32-gstreamer lib32-gst-plugins-base lib32-gst-plugins-good && \
|
||||
# Cleanup
|
||||
paccache -rk1 && \
|
||||
rm -rf /usr/share/{info,man,doc}/*
|
||||
@@ -163,8 +168,7 @@ ENV USER="nestri" \
|
||||
USER_PWD="nestri1234" \
|
||||
XDG_RUNTIME_DIR=/run/user/1000 \
|
||||
HOME=/home/nestri \
|
||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
||||
NVIDIA_VISIBLE_DEVICES=all
|
||||
NVIDIA_DRIVER_CAPABILITIES=all
|
||||
|
||||
RUN mkdir -p /home/${USER} && \
|
||||
groupadd -g ${GID} ${USER} && \
|
||||
@@ -185,6 +189,30 @@ RUN mkdir -p /run/dbus && \
|
||||
-e '/wants = \[/{s/hooks\.node\.suspend\s*//; s/,\s*\]/]/}' \
|
||||
/usr/share/wireplumber/wireplumber.conf
|
||||
|
||||
### PipeWire Latency Optimizations (1-5ms instead of 20ms) ###
|
||||
RUN mkdir -p /etc/pipewire/pipewire.conf.d && \
|
||||
echo "[audio]\
|
||||
\n default.clock.rate = 48000\
|
||||
\n default.clock.quantum = 128\
|
||||
\n default.clock.min-quantum = 128\
|
||||
\n default.clock.max-quantum = 256" > /etc/pipewire/pipewire.conf.d/low-latency.conf && \
|
||||
mkdir -p /etc/wireplumber/main.lua.d && \
|
||||
echo 'table.insert(default_nodes.rules, {\
|
||||
\n matches = { { { "node.name", "matches", ".*" } } },\
|
||||
\n apply_properties = {\
|
||||
\n ["audio.format"] = "S16LE",\
|
||||
\n ["audio.rate"] = 48000,\
|
||||
\n ["audio.channels"] = 2,\
|
||||
\n ["api.alsa.period-size"] = 128,\
|
||||
\n ["api.alsa.headroom"] = 0,\
|
||||
\n ["session.suspend-timeout-seconds"] = 0\
|
||||
\n }\
|
||||
\n})' > /etc/wireplumber/main.lua.d/50-low-latency.lua && \
|
||||
echo "default-fragments = 2\
|
||||
\ndefault-fragment-size-msec = 2" >> /etc/pulse/daemon.conf && \
|
||||
echo "load-module module-loopback latency_msec=1" >> /etc/pipewire/pipewire.conf.d/loopback.conf
|
||||
|
||||
|
||||
### Artifacts and Verification ###
|
||||
COPY --from=nestri-server-cached-builder /artifacts/nestri-server /usr/bin/
|
||||
COPY --from=gst-wayland-cached-builder /artifacts/lib/ /usr/lib/
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
#FIXME: A simple docker-compose file for running the MoQ relay and the cachyos server
|
||||
140
infra/api.ts
140
infra/api.ts
@@ -1,82 +1,94 @@
|
||||
import { bus } from "./bus";
|
||||
import { auth } from "./auth";
|
||||
import { domain } from "./dns";
|
||||
import { email } from "./email";
|
||||
import { secret } from "./secret";
|
||||
import { database } from "./database";
|
||||
import { cluster } from "./cluster";
|
||||
import { postgres } from "./postgres";
|
||||
|
||||
sst.Linkable.wrap(random.RandomString, (resource) => ({
|
||||
properties: {
|
||||
value: resource.result,
|
||||
export const apiService = new sst.aws.Service("Api", {
|
||||
cluster,
|
||||
cpu: $app.stage === "production" ? "2 vCPU" : undefined,
|
||||
memory: $app.stage === "production" ? "4 GB" : undefined,
|
||||
link: [
|
||||
bus,
|
||||
auth,
|
||||
postgres,
|
||||
secret.SteamApiKey,
|
||||
secret.PolarSecret,
|
||||
secret.PolarWebhookSecret,
|
||||
secret.NestriFamilyMonthly,
|
||||
secret.NestriFamilyYearly,
|
||||
secret.NestriFreeMonthly,
|
||||
secret.NestriProMonthly,
|
||||
secret.NestriProYearly,
|
||||
],
|
||||
command: ["bun", "run", "./src/api/index.ts"],
|
||||
image: {
|
||||
dockerfile: "packages/functions/Containerfile",
|
||||
},
|
||||
}));
|
||||
|
||||
export const urls = new sst.Linkable("Urls", {
|
||||
properties: {
|
||||
api: "https://api." + domain,
|
||||
auth: "https://auth." + domain,
|
||||
site: $dev ? "http://localhost:4321" : "https://" + domain,
|
||||
},
|
||||
});
|
||||
|
||||
export const authFingerprintKey = new random.RandomString(
|
||||
"AuthFingerprintKey",
|
||||
{
|
||||
length: 32,
|
||||
},
|
||||
);
|
||||
|
||||
export const auth = new sst.aws.Auth("Auth", {
|
||||
issuer: {
|
||||
timeout: "3 minutes",
|
||||
handler: "./packages/functions/src/auth.handler",
|
||||
link: [
|
||||
bus,
|
||||
email,
|
||||
database,
|
||||
authFingerprintKey,
|
||||
secret.PolarSecret,
|
||||
secret.GithubClientID,
|
||||
secret.DiscordClientID,
|
||||
secret.GithubClientSecret,
|
||||
secret.DiscordClientSecret,
|
||||
],
|
||||
permissions: [
|
||||
loadBalancer: {
|
||||
rules: [
|
||||
{
|
||||
actions: ["ses:SendEmail"],
|
||||
resources: ["*"],
|
||||
listen: "80/http",
|
||||
forward: "3001/http",
|
||||
},
|
||||
],
|
||||
},
|
||||
domain: {
|
||||
name: "auth." + domain,
|
||||
dns: sst.cloudflare.dns(),
|
||||
dev: {
|
||||
url: "http://localhost:3001",
|
||||
command: "bun dev:api",
|
||||
directory: "packages/functions",
|
||||
},
|
||||
})
|
||||
scaling:
|
||||
$app.stage === "production"
|
||||
? {
|
||||
min: 2,
|
||||
max: 10,
|
||||
}
|
||||
: undefined,
|
||||
// For persisting actor state
|
||||
transform: {
|
||||
taskDefinition: (args) => {
|
||||
const volumes = $output(args.volumes).apply(v => {
|
||||
const next = [...(v || []), {
|
||||
name: "shared-tmp",
|
||||
dockerVolumeConfiguration: {
|
||||
scope: "shared",
|
||||
driver: "local"
|
||||
}
|
||||
}];
|
||||
|
||||
export const apiFunction = new sst.aws.Function("ApiFn", {
|
||||
handler: "packages/functions/src/api/index.handler",
|
||||
link: [
|
||||
bus,
|
||||
urls,
|
||||
database,
|
||||
secret.PolarSecret,
|
||||
],
|
||||
timeout: "3 minutes",
|
||||
streaming: !$dev,
|
||||
url: true
|
||||
})
|
||||
return next;
|
||||
})
|
||||
|
||||
export const api = new sst.aws.Router("Api", {
|
||||
// "containerDefinitions" is a JSON string, parse first
|
||||
let containers = $jsonParse(args.containerDefinitions);
|
||||
|
||||
containers = containers.apply((containerDefinitions) => {
|
||||
containerDefinitions[0].mountPoints = [
|
||||
...(containerDefinitions[0].mountPoints ?? []),
|
||||
{
|
||||
sourceVolume: "shared-tmp",
|
||||
containerPath: "/tmp"
|
||||
},
|
||||
]
|
||||
return containerDefinitions;
|
||||
});
|
||||
|
||||
args.volumes = volumes
|
||||
args.containerDefinitions = $jsonStringify(containers);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export const api = !$dev ? new sst.aws.Router("ApiRoute", {
|
||||
routes: {
|
||||
"/*": apiFunction.url
|
||||
// I think api.url should work all the same
|
||||
"/*": apiService.nodes.loadBalancer.dnsName,
|
||||
},
|
||||
domain: {
|
||||
name: "api." + domain,
|
||||
dns: sst.cloudflare.dns(),
|
||||
},
|
||||
})
|
||||
|
||||
export const outputs = {
|
||||
auth: auth.url,
|
||||
api: api.url,
|
||||
};
|
||||
}) : apiService
|
||||
98
infra/auth.ts
Normal file
98
infra/auth.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { bus } from "./bus";
|
||||
import { domain } from "./dns";
|
||||
import { secret } from "./secret";
|
||||
import { cluster } from "./cluster";
|
||||
import { postgres } from "./postgres";
|
||||
|
||||
export const authService = new sst.aws.Service("Auth", {
|
||||
cluster,
|
||||
cpu: $app.stage === "production" ? "1 vCPU" : undefined,
|
||||
memory: $app.stage === "production" ? "2 GB" : undefined,
|
||||
command: ["bun", "run", "./src/auth/index.ts"],
|
||||
link: [
|
||||
bus,
|
||||
postgres,
|
||||
secret.PolarSecret,
|
||||
secret.GithubClientID,
|
||||
secret.DiscordClientID,
|
||||
secret.GithubClientSecret,
|
||||
secret.DiscordClientSecret,
|
||||
],
|
||||
image: {
|
||||
dockerfile: "packages/functions/Containerfile",
|
||||
},
|
||||
environment: {
|
||||
NO_COLOR: "1",
|
||||
STORAGE: "/tmp/persist.json"
|
||||
},
|
||||
loadBalancer: {
|
||||
rules: [
|
||||
{
|
||||
listen: "80/http",
|
||||
forward: "3002/http",
|
||||
},
|
||||
],
|
||||
},
|
||||
permissions: [
|
||||
{
|
||||
actions: ["ses:SendEmail"],
|
||||
resources: ["*"],
|
||||
},
|
||||
],
|
||||
dev: {
|
||||
command: "bun dev:auth",
|
||||
directory: "packages/functions",
|
||||
url: "http://localhost:3002",
|
||||
},
|
||||
scaling:
|
||||
$app.stage === "production"
|
||||
? {
|
||||
min: 2,
|
||||
max: 10,
|
||||
}
|
||||
: undefined,
|
||||
//For temporarily persisting the persist.json
|
||||
transform: {
|
||||
taskDefinition: (args) => {
|
||||
const volumes = $output(args.volumes).apply(v => {
|
||||
const next = [...(v || []), {
|
||||
name: "shared-tmp",
|
||||
dockerVolumeConfiguration: {
|
||||
scope: "shared",
|
||||
driver: "local"
|
||||
}
|
||||
}];
|
||||
|
||||
return next;
|
||||
})
|
||||
|
||||
// "containerDefinitions" is a JSON string, parse first
|
||||
let containers = $jsonParse(args.containerDefinitions);
|
||||
|
||||
containers = containers.apply((containerDefinitions) => {
|
||||
containerDefinitions[0].mountPoints = [
|
||||
...(containerDefinitions[0].mountPoints ?? []),
|
||||
{
|
||||
sourceVolume: "shared-tmp",
|
||||
containerPath: "/tmp"
|
||||
}
|
||||
]
|
||||
return containerDefinitions;
|
||||
});
|
||||
|
||||
args.volumes = volumes
|
||||
args.containerDefinitions = $jsonStringify(containers);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export const auth = !$dev ? new sst.aws.Router("AuthRoute", {
|
||||
routes: {
|
||||
// I think auth.url should work all the same
|
||||
"/*": authService.nodes.loadBalancer.dnsName,
|
||||
},
|
||||
domain: {
|
||||
name: "auth." + domain,
|
||||
dns: sst.cloudflare.dns(),
|
||||
},
|
||||
}) : authService
|
||||
72
infra/bus.ts
72
infra/bus.ts
@@ -1,20 +1,70 @@
|
||||
import { email } from "./email";
|
||||
import { allSecrets } from "./secret";
|
||||
import { database } from "./database";
|
||||
import { vpc } from "./vpc";
|
||||
import { secret } from "./secret";
|
||||
import { storage } from "./storage";
|
||||
import { postgres } from "./postgres";
|
||||
|
||||
export const dlq = new sst.aws.Queue("Dlq");
|
||||
|
||||
export const retryQueue = new sst.aws.Queue("RetryQueue");
|
||||
|
||||
export const bus = new sst.aws.Bus("Bus");
|
||||
|
||||
bus.subscribe("Event", {
|
||||
handler: "./packages/functions/src/event/event.handler",
|
||||
export const eventSub = bus.subscribe("Event", {
|
||||
vpc,
|
||||
handler: "packages/functions/src/events/index.handler",
|
||||
link: [
|
||||
database,
|
||||
email,
|
||||
...allSecrets],
|
||||
timeout: "5 minutes",
|
||||
// email,
|
||||
bus,
|
||||
storage,
|
||||
postgres,
|
||||
retryQueue,
|
||||
secret.PolarSecret,
|
||||
secret.SteamApiKey
|
||||
],
|
||||
environment: {
|
||||
RETRIES: "2",
|
||||
},
|
||||
memory: "3002 MB",// For faster processing of large(r) images
|
||||
timeout: "10 minutes",
|
||||
});
|
||||
|
||||
new aws.lambda.FunctionEventInvokeConfig("EventConfig", {
|
||||
functionName: $resolve([eventSub.nodes.function.name]).apply(
|
||||
([name]) => name,
|
||||
),
|
||||
maximumRetryAttempts: 1,
|
||||
destinationConfig: {
|
||||
onFailure: {
|
||||
destination: retryQueue.arn,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
retryQueue.subscribe({
|
||||
vpc,
|
||||
handler: "packages/functions/src/queues/retry.handler",
|
||||
timeout: "30 seconds",
|
||||
environment: {
|
||||
RETRIER_QUEUE_URL: retryQueue.url,
|
||||
},
|
||||
link: [
|
||||
dlq,
|
||||
retryQueue,
|
||||
eventSub.nodes.function,
|
||||
],
|
||||
permissions: [
|
||||
{
|
||||
actions: ["ses:SendEmail"],
|
||||
resources: ["*"],
|
||||
actions: ["lambda:GetFunction", "lambda:InvokeFunction"],
|
||||
resources: [
|
||||
$interpolate`arn:aws:lambda:${aws.getRegionOutput().name}:${aws.getCallerIdentityOutput().accountId}:function:*`,
|
||||
],
|
||||
},
|
||||
],
|
||||
transform: {
|
||||
function: {
|
||||
deadLetterConfig: {
|
||||
targetArn: dlq.arn,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
6
infra/cluster.ts
Normal file
6
infra/cluster.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { vpc } from "./vpc";
|
||||
|
||||
export const cluster = new sst.aws.Cluster("Cluster", {
|
||||
vpc,
|
||||
forceUpgrade: "v2"
|
||||
});
|
||||
@@ -1,40 +0,0 @@
|
||||
//Created manually from the dashboard and shared with the whole team/org
|
||||
const dbProject = neon.getProjectOutput({
|
||||
id: "black-sky-26872933"
|
||||
})
|
||||
|
||||
const dbBranchId = $app.stage !== "production" ?
|
||||
new neon.Branch("NeonBranch", {
|
||||
parentId: dbProject.defaultBranchId,
|
||||
projectId: dbProject.id,
|
||||
name: $app.stage,
|
||||
}).id : dbProject.defaultBranchId
|
||||
|
||||
const dbEndpoint = new neon.Endpoint("NeonEndpoint", {
|
||||
projectId: dbProject.id,
|
||||
branchId: dbBranchId,
|
||||
poolerEnabled: true,
|
||||
type: "read_write",
|
||||
})
|
||||
|
||||
const dbRole = new neon.Role("NeonRole", {
|
||||
name: "admin",
|
||||
branchId: dbBranchId,
|
||||
projectId: dbProject.id,
|
||||
})
|
||||
|
||||
const db = new neon.Database("NeonDatabase", {
|
||||
branchId: dbBranchId,
|
||||
projectId: dbProject.id,
|
||||
ownerName: dbRole.name,
|
||||
name: `nestri-${$app.stage}`,
|
||||
})
|
||||
|
||||
export const database = new sst.Linkable("Database", {
|
||||
properties: {
|
||||
name: db.name,
|
||||
user: dbRole.name,
|
||||
host: dbEndpoint.host,
|
||||
password: dbRole.password,
|
||||
},
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { domain } from "./dns";
|
||||
|
||||
export const email = new sst.aws.Email("Mail",{
|
||||
export const email = new sst.aws.Email("Email",{
|
||||
sender: domain,
|
||||
dns: sst.cloudflare.dns(),
|
||||
})
|
||||
57
infra/images.ts
Normal file
57
infra/images.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { domain } from "./dns";
|
||||
import { storage } from "./storage";
|
||||
|
||||
sst.Linkable.wrap(aws.iam.AccessKey, (resource) => ({
|
||||
properties: {
|
||||
key: resource.id,
|
||||
secret: resource.secret,
|
||||
},
|
||||
}))
|
||||
|
||||
const cache = new sst.cloudflare.Kv("ImageCache");
|
||||
|
||||
const bucket = new sst.cloudflare.Bucket("ImageBucket");
|
||||
|
||||
const lambdaInvokerUser = new aws.iam.User("ImageIAMUser", {
|
||||
name: `${$app.name}-${$app.stage}-ImageIAMUser`,
|
||||
forceDestroy: true
|
||||
});
|
||||
|
||||
const imageProcessorFunction = new sst.aws.Function("ImageProcessor",
|
||||
{
|
||||
memory: "1024 MB",
|
||||
link: [storage],
|
||||
timeout: "30 seconds",
|
||||
nodejs: { install: ["sharp"] },
|
||||
handler: "packages/functions/src/images/processor.handler",
|
||||
},
|
||||
);
|
||||
|
||||
new aws.iam.UserPolicy("InvokeLambdaPolicy", {
|
||||
user: lambdaInvokerUser.name,
|
||||
policy: $output({
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Action: ["lambda:InvokeFunction"],
|
||||
Resource: imageProcessorFunction.arn,
|
||||
},
|
||||
],
|
||||
}).apply(JSON.stringify),
|
||||
});
|
||||
|
||||
const accessKey = new aws.iam.AccessKey("ImageInvokerAccessKey", {
|
||||
user: lambdaInvokerUser.name,
|
||||
});
|
||||
|
||||
export const imageCdn = new sst.cloudflare.Worker("ImageCDN", {
|
||||
url: true,
|
||||
domain: "cdn." + domain,
|
||||
link: [bucket, cache, imageProcessorFunction, accessKey],
|
||||
handler: "packages/functions/src/images/index.ts",
|
||||
});
|
||||
|
||||
export const outputs = {
|
||||
cdn: imageCdn.url
|
||||
}
|
||||
64
infra/postgres.ts
Normal file
64
infra/postgres.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import { vpc } from "./vpc";
|
||||
|
||||
export const postgres = new sst.aws.Aurora("Database", {
|
||||
vpc,
|
||||
engine: "postgres",
|
||||
scaling: {
|
||||
min: "0 ACU",
|
||||
max: "1 ACU",
|
||||
},
|
||||
transform: {
|
||||
clusterParameterGroup: {
|
||||
parameters: [
|
||||
{
|
||||
name: "rds.logical_replication",
|
||||
value: "1",
|
||||
applyMethod: "pending-reboot",
|
||||
},
|
||||
{
|
||||
name: "max_slot_wal_keep_size",
|
||||
value: "10240",
|
||||
applyMethod: "pending-reboot",
|
||||
},
|
||||
{
|
||||
name: "rds.force_ssl",
|
||||
value: "0",
|
||||
applyMethod: "pending-reboot",
|
||||
},
|
||||
{
|
||||
name: "max_connections",
|
||||
value: "1000",
|
||||
applyMethod: "pending-reboot",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
new sst.x.DevCommand("Studio", {
|
||||
link: [postgres],
|
||||
dev: {
|
||||
command: "bun db:dev studio",
|
||||
directory: "packages/core",
|
||||
autostart: true,
|
||||
},
|
||||
});
|
||||
|
||||
// const migrator = new sst.aws.Function("DatabaseMigrator", {
|
||||
// handler: "packages/functions/src/migrator.handler",
|
||||
// link: [postgres],
|
||||
// copyFiles: [
|
||||
// {
|
||||
// from: "packages/core/migrations",
|
||||
// to: "./migrations",
|
||||
// },
|
||||
// ],
|
||||
// });
|
||||
|
||||
// if (!$dev) {
|
||||
// new aws.lambda.Invocation("DatabaseMigratorInvocation", {
|
||||
// input: Date.now().toString(),
|
||||
// functionName: migrator.name,
|
||||
// });
|
||||
// }
|
||||
9
infra/realtime.ts
Normal file
9
infra/realtime.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { auth } from "./auth";
|
||||
import { postgres } from "./postgres";
|
||||
|
||||
export const device = new sst.aws.Realtime("Realtime", {
|
||||
authorizer: {
|
||||
link: [auth, postgres],
|
||||
handler: "packages/functions/src/realtime/authorizer.handler"
|
||||
}
|
||||
})
|
||||
@@ -1,11 +1,18 @@
|
||||
export const secret = {
|
||||
// InstantAppId: new sst.Secret("InstantAppId"),
|
||||
PolarSecret: new sst.Secret("PolarSecret", process.env.POLAR_API_KEY),
|
||||
SteamApiKey: new sst.Secret("SteamApiKey"),
|
||||
GithubClientID: new sst.Secret("GithubClientID"),
|
||||
DiscordClientID: new sst.Secret("DiscordClientID"),
|
||||
PolarWebhookSecret: new sst.Secret("PolarWebhookSecret"),
|
||||
GithubClientSecret: new sst.Secret("GithubClientSecret"),
|
||||
// InstantAdminToken: new sst.Secret("InstantAdminToken"),
|
||||
DiscordClientSecret: new sst.Secret("DiscordClientSecret"),
|
||||
|
||||
// Pricing
|
||||
NestriFreeMonthly: new sst.Secret("NestriFreeMonthly"),
|
||||
NestriProMonthly: new sst.Secret("NestriProMonthly"),
|
||||
NestriProYearly: new sst.Secret("NestriProYearly"),
|
||||
NestriFamilyMonthly: new sst.Secret("NestriFamilyMonthly"),
|
||||
NestriFamilyYearly: new sst.Secret("NestriFamilyYearly"),
|
||||
};
|
||||
|
||||
export const allSecrets = Object.values(secret);
|
||||
2
infra/stage.ts
Normal file
2
infra/stage.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export const isPermanentStage =
|
||||
$app.stage === "production" || $app.stage === "dev";
|
||||
1
infra/storage.ts
Normal file
1
infra/storage.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const storage = new sst.aws.Bucket("Storage");
|
||||
11
infra/vpc.ts
Normal file
11
infra/vpc.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { isPermanentStage } from "./stage";
|
||||
|
||||
export const vpc = isPermanentStage
|
||||
? new sst.aws.Vpc("VPC", {
|
||||
az: 2,
|
||||
// For lambdas to work in this VPC
|
||||
nat: "ec2",
|
||||
// For SST tunnel to work
|
||||
bastion: true,
|
||||
})
|
||||
: sst.aws.Vpc.get("VPC", "vpc-0beb1cdc21a725748");
|
||||
@@ -1,9 +1,11 @@
|
||||
// This is the website part where people play and connect
|
||||
import { api } from "./api";
|
||||
import { auth } from "./auth";
|
||||
import { zero } from "./zero";
|
||||
import { domain } from "./dns";
|
||||
import { auth, api } from "./api";
|
||||
|
||||
new sst.aws.StaticSite("Web", {
|
||||
path: "./packages/www",
|
||||
path: "packages/www",
|
||||
build: {
|
||||
output: "./dist",
|
||||
command: "bun run build",
|
||||
@@ -14,7 +16,8 @@ new sst.aws.StaticSite("Web", {
|
||||
},
|
||||
environment: {
|
||||
VITE_API_URL: api.url,
|
||||
VITE_AUTH_URL: auth.url,
|
||||
VITE_STAGE: $app.stage,
|
||||
VITE_AUTH_URL: auth.url,
|
||||
VITE_ZERO_URL: zero.url,
|
||||
},
|
||||
})
|
||||
197
infra/zero.ts
Normal file
197
infra/zero.ts
Normal file
@@ -0,0 +1,197 @@
|
||||
import { vpc } from "./vpc";
|
||||
import { auth } from "./auth";
|
||||
import { domain } from "./dns";
|
||||
import { readFileSync } from "fs";
|
||||
import { cluster } from "./cluster";
|
||||
import { storage } from "./storage";
|
||||
import { postgres } from "./postgres";
|
||||
|
||||
// const connectionString = $interpolate`postgresql://${postgres.username}:${postgres.password}@${postgres.host}/${postgres.database}`
|
||||
const connectionString = $interpolate`postgresql://${postgres.username}:${postgres.password}@${postgres.host}:${postgres.port}/${postgres.database}`;
|
||||
|
||||
const tag = $dev
|
||||
? `latest`
|
||||
: JSON.parse(
|
||||
readFileSync("./node_modules/@rocicorp/zero/package.json").toString(),
|
||||
).version.replace("+", "-");
|
||||
|
||||
const zeroEnv = {
|
||||
FORCE: "1",
|
||||
NO_COLOR: "1",
|
||||
ZERO_LOG_LEVEL: "info",
|
||||
ZERO_LITESTREAM_LOG_LEVEL: "info",
|
||||
ZERO_UPSTREAM_DB: connectionString,
|
||||
ZERO_IMAGE_URL: `rocicorp/zero:${tag}`,
|
||||
ZERO_CVR_DB: connectionString,
|
||||
ZERO_CHANGE_DB: connectionString,
|
||||
ZERO_REPLICA_FILE: "/tmp/nestri.db",
|
||||
ZERO_LITESTREAM_RESTORE_PARALLELISM: "64",
|
||||
ZERO_APP_ID: $app.stage,
|
||||
ZERO_AUTH_JWKS_URL: $interpolate`${auth.url}/.well-known/jwks.json`,
|
||||
ZERO_INITIAL_SYNC_ROW_BATCH_SIZE: "30000",
|
||||
NODE_OPTIONS: "--max-old-space-size=8192",
|
||||
...($dev
|
||||
? {
|
||||
}
|
||||
: {
|
||||
ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${storage.name}/zero/0`,
|
||||
}),
|
||||
};
|
||||
|
||||
// Replication Manager Service
|
||||
const replicationManager = !$dev
|
||||
? new sst.aws.Service(`ZeroReplication`, {
|
||||
cluster,
|
||||
wait: true,
|
||||
...($app.stage === "production"
|
||||
? {
|
||||
cpu: "2 vCPU",
|
||||
memory: "4 GB",
|
||||
}
|
||||
: {}),
|
||||
architecture: "arm64",
|
||||
image: zeroEnv.ZERO_IMAGE_URL,
|
||||
link: [storage, postgres],
|
||||
health: {
|
||||
command: ["CMD-SHELL", "curl -f http://localhost:4849/ || exit 1"],
|
||||
interval: "5 seconds",
|
||||
retries: 3,
|
||||
startPeriod: "300 seconds",
|
||||
},
|
||||
environment: {
|
||||
...zeroEnv,
|
||||
ZERO_CHANGE_MAX_CONNS: "3",
|
||||
ZERO_NUM_SYNC_WORKERS: "0",
|
||||
},
|
||||
logging: {
|
||||
retention: "1 month",
|
||||
},
|
||||
loadBalancer: {
|
||||
public: false,
|
||||
ports: [
|
||||
{
|
||||
listen: "80/http",
|
||||
forward: "4849/http",
|
||||
},
|
||||
],
|
||||
},
|
||||
transform: {
|
||||
loadBalancer: {
|
||||
idleTimeout: 3600,
|
||||
},
|
||||
service: {
|
||||
healthCheckGracePeriodSeconds: 900,
|
||||
},
|
||||
},
|
||||
}) : undefined;
|
||||
|
||||
// Permissions deployment
|
||||
// const permissions = new sst.aws.Function(
|
||||
// "ZeroPermissions",
|
||||
// {
|
||||
// vpc,
|
||||
// link: [postgres],
|
||||
// handler: "packages/functions/src/zero.handler",
|
||||
// // environment: { ["ZERO_UPSTREAM_DB"]: connectionString },
|
||||
// copyFiles: [{
|
||||
// from: "packages/zero/permissions.sql",
|
||||
// to: "./.permissions.sql"
|
||||
// }],
|
||||
// }
|
||||
// );
|
||||
|
||||
// if (replicationManager) {
|
||||
// new aws.lambda.Invocation(
|
||||
// "ZeroPermissionsInvocation",
|
||||
// {
|
||||
// input: Date.now().toString(),
|
||||
// functionName: permissions.name,
|
||||
// },
|
||||
// { dependsOn: replicationManager }
|
||||
// );
|
||||
// // new command.local.Command(
|
||||
// // "ZeroPermission",
|
||||
// // {
|
||||
// // dir: process.cwd() + "/packages/zero",
|
||||
// // environment: {
|
||||
// // ZERO_UPSTREAM_DB: connectionString,
|
||||
// // },
|
||||
// // create: "bun run zero-deploy-permissions",
|
||||
// // triggers: [Date.now()],
|
||||
// // },
|
||||
// // {
|
||||
// // dependsOn: [replicationManager],
|
||||
// // },
|
||||
// // );
|
||||
// }
|
||||
|
||||
export const zero = new sst.aws.Service("Zero", {
|
||||
cluster,
|
||||
image: zeroEnv.ZERO_IMAGE_URL,
|
||||
link: [storage, postgres],
|
||||
architecture: "arm64",
|
||||
...($app.stage === "production"
|
||||
? {
|
||||
cpu: "2 vCPU",
|
||||
memory: "4 GB",
|
||||
capacity: "spot"
|
||||
}
|
||||
: {
|
||||
capacity: "spot"
|
||||
}),
|
||||
environment: {
|
||||
...zeroEnv,
|
||||
...($dev
|
||||
? {
|
||||
ZERO_NUM_SYNC_WORKERS: "1",
|
||||
}
|
||||
: {
|
||||
ZERO_CHANGE_STREAMER_URI: replicationManager?.url.apply((val) =>
|
||||
val.replace("http://", "ws://"),
|
||||
) ?? "",
|
||||
ZERO_UPSTREAM_MAX_CONNS: "15",
|
||||
ZERO_CVR_MAX_CONNS: "160",
|
||||
}),
|
||||
},
|
||||
health: {
|
||||
retries: 3,
|
||||
command: ["CMD-SHELL", "curl -f http://localhost:4848/ || exit 1"],
|
||||
interval: "5 seconds",
|
||||
startPeriod: "300 seconds",
|
||||
},
|
||||
loadBalancer: {
|
||||
domain: {
|
||||
name: "zero." + domain,
|
||||
dns: sst.cloudflare.dns()
|
||||
},
|
||||
rules: [
|
||||
{ listen: "443/https", forward: "4848/http" },
|
||||
{ listen: "80/http", forward: "4848/http" },
|
||||
],
|
||||
},
|
||||
scaling: {
|
||||
min: 1,
|
||||
max: 4,
|
||||
},
|
||||
logging: {
|
||||
retention: "1 month",
|
||||
},
|
||||
transform: {
|
||||
service: {
|
||||
healthCheckGracePeriodSeconds: 900,
|
||||
},
|
||||
// taskDefinition: {
|
||||
// ephemeralStorage: {
|
||||
// sizeInGib: 200,
|
||||
// },
|
||||
// },
|
||||
loadBalancer: {
|
||||
idleTimeout: 3600,
|
||||
},
|
||||
},
|
||||
dev: {
|
||||
command: "bun dev",
|
||||
directory: "packages/zero",
|
||||
url: "http://localhost:4848",
|
||||
},
|
||||
});
|
||||
16
package.json
16
package.json
@@ -3,6 +3,7 @@
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "4.20240821.1",
|
||||
"@pulumi/pulumi": "^3.134.0",
|
||||
"@tsconfig/node22": "^22.0.1",
|
||||
"@types/aws-lambda": "8.10.147",
|
||||
"prettier": "^3.2.5",
|
||||
"typescript": "^5.4.5"
|
||||
@@ -16,9 +17,19 @@
|
||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||
"sso": "aws sso login --sso-session=nestri --no-browser --use-device-code"
|
||||
},
|
||||
"overrides": {
|
||||
"@openauthjs/openauth": "0.4.3",
|
||||
"steam-session": "1.9.3"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@macaron-css/solid@1.5.3": "patches/@macaron-css%2Fsolid@1.5.3.patch",
|
||||
"drizzle-orm@0.36.1": "patches/drizzle-orm@0.36.1.patch",
|
||||
"steam-session@1.9.3": "patches/steam-session@1.9.3.patch"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"core-js-pure",
|
||||
"esbuild",
|
||||
"protobufjs",
|
||||
"workerd"
|
||||
],
|
||||
"workspaces": [
|
||||
@@ -26,6 +37,7 @@
|
||||
"packages/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"sst": "3.9.1"
|
||||
"sharp": "^0.34.2",
|
||||
"sst": "^3.11.21"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,19 @@
|
||||
import { Resource } from "sst";
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
|
||||
function addPoolerSuffix(original: string): string {
|
||||
const firstDotIndex = original.indexOf('.');
|
||||
if (firstDotIndex === -1) return original + '-pooler';
|
||||
return original.slice(0, firstDotIndex) + '-pooler' + original.slice(firstDotIndex);
|
||||
}
|
||||
|
||||
const dbHost = addPoolerSuffix(Resource.Database.host)
|
||||
const connection = {
|
||||
user: Resource.Database.username,
|
||||
password: Resource.Database.password,
|
||||
host: Resource.Database.host,
|
||||
};
|
||||
|
||||
export default defineConfig({
|
||||
schema: "./src/**/*.sql.ts",
|
||||
verbose: true,
|
||||
strict: true,
|
||||
out: "./migrations",
|
||||
dialect: "postgresql",
|
||||
verbose: true,
|
||||
dbCredentials: {
|
||||
url: `postgresql://${Resource.Database.user}:${Resource.Database.password}@${dbHost}/${Resource.Database.name}?sslmode=require`,
|
||||
url: `postgres://${connection.user}:${connection.password}@${connection.host}/nestri`,
|
||||
},
|
||||
schema: "./src/**/*.sql.ts",
|
||||
});
|
||||
@@ -14,8 +14,9 @@ CREATE TABLE "team" (
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"slug" varchar(255) NOT NULL,
|
||||
"name" varchar(255) NOT NULL
|
||||
"plan_type" text NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "user" (
|
||||
@@ -24,14 +25,15 @@ CREATE TABLE "user" (
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"avatar_url" text,
|
||||
"email" varchar(255) NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"discriminator" integer NOT NULL,
|
||||
"polar_customer_id" varchar(255) NOT NULL,
|
||||
"email" varchar(255) NOT NULL,
|
||||
"polar_customer_id" varchar(255),
|
||||
"flags" json DEFAULT '{}'::json,
|
||||
CONSTRAINT "user_polar_customer_id_unique" UNIQUE("polar_customer_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "member_email" ON "member" USING btree ("team_id","email");--> statement-breakpoint
|
||||
CREATE INDEX "email_global" ON "member" USING btree ("email");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "slug" ON "team" USING btree ("slug");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "member_email" ON "member" USING btree ("team_id","email");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "team_slug" ON "team" USING btree ("slug");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "user_email" ON "user" USING btree ("email");
|
||||
@@ -1 +0,0 @@
|
||||
ALTER TABLE "user" ALTER COLUMN "polar_customer_id" DROP NOT NULL;
|
||||
2
packages/core/migrations/0001_nifty_sauron.sql
Normal file
2
packages/core/migrations/0001_nifty_sauron.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
DROP INDEX "team_slug";--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "slug" ON "team" USING btree ("slug");
|
||||
17
packages/core/migrations/0002_simple_outlaw_kid.sql
Normal file
17
packages/core/migrations/0002_simple_outlaw_kid.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE "steam" (
|
||||
"id" char(30) NOT NULL,
|
||||
"user_id" char(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"avatar_url" text NOT NULL,
|
||||
"access_token" text NOT NULL,
|
||||
"email" varchar(255) NOT NULL,
|
||||
"country" varchar(255) NOT NULL,
|
||||
"username" varchar(255) NOT NULL,
|
||||
"persona_name" varchar(255) NOT NULL,
|
||||
CONSTRAINT "steam_user_id_id_pk" PRIMARY KEY("user_id","id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX "global_steam_email" ON "steam" USING btree ("email");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "steam_email" ON "steam" USING btree ("user_id","email");
|
||||
13
packages/core/migrations/0002_tiny_toad_men.sql
Normal file
13
packages/core/migrations/0002_tiny_toad_men.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE "machine" (
|
||||
"id" char(30) PRIMARY KEY NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"country" text NOT NULL,
|
||||
"timezone" text NOT NULL,
|
||||
"location" "point" NOT NULL,
|
||||
"fingerprint" varchar(32) NOT NULL,
|
||||
"country_code" varchar(2) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "machine_fingerprint" ON "machine" USING btree ("fingerprint");
|
||||
22
packages/core/migrations/0003_first_big_bertha.sql
Normal file
22
packages/core/migrations/0003_first_big_bertha.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE "machine" (
|
||||
"id" char(30) PRIMARY KEY NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"country" text NOT NULL,
|
||||
"timezone" text NOT NULL,
|
||||
"location" "point" NOT NULL,
|
||||
"fingerprint" varchar(32) NOT NULL,
|
||||
"country_code" varchar(2) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "steam" RENAME COLUMN "country" TO "country_code";--> statement-breakpoint
|
||||
DROP INDEX "global_steam_email";--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD COLUMN "time_seen" timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD COLUMN "steam_id" integer NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD COLUMN "last_game" json NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD COLUMN "steam_email" varchar(255) NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD COLUMN "limitation" json NOT NULL;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "machine_fingerprint" ON "machine" USING btree ("fingerprint");--> statement-breakpoint
|
||||
ALTER TABLE "steam" DROP COLUMN "access_token";--> statement-breakpoint
|
||||
ALTER TABLE "user" DROP COLUMN "flags";
|
||||
8
packages/core/migrations/0004_amused_mattie_franklin.sql
Normal file
8
packages/core/migrations/0004_amused_mattie_franklin.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
ALTER TABLE "steam" RENAME COLUMN "time_seen" TO "last_seen";--> statement-breakpoint
|
||||
DROP INDEX "steam_email";--> statement-breakpoint
|
||||
ALTER TABLE "steam" DROP CONSTRAINT "steam_user_id_id_pk";--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD PRIMARY KEY ("id");--> statement-breakpoint
|
||||
ALTER TABLE "machine" ADD CONSTRAINT "machine_user_id_id_pk" PRIMARY KEY("user_id","id");--> statement-breakpoint
|
||||
ALTER TABLE "machine" ADD COLUMN "user_id" char(30);--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD CONSTRAINT "steam_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "steam" DROP COLUMN "email";
|
||||
2
packages/core/migrations/0005_aspiring_stature.sql
Normal file
2
packages/core/migrations/0005_aspiring_stature.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "machine" DROP CONSTRAINT "machine_user_id_id_pk";--> statement-breakpoint
|
||||
ALTER TABLE "machine" DROP COLUMN "user_id";
|
||||
2
packages/core/migrations/0006_worthless_dreadnoughts.sql
Normal file
2
packages/core/migrations/0006_worthless_dreadnoughts.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "member" ADD COLUMN "role" text NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "team" DROP COLUMN "plan_type";
|
||||
15
packages/core/migrations/0007_warm_secret_warriors.sql
Normal file
15
packages/core/migrations/0007_warm_secret_warriors.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE "subscription" (
|
||||
"id" char(30) NOT NULL,
|
||||
"user_id" char(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"team_id" char(30) NOT NULL,
|
||||
"standing" text NOT NULL,
|
||||
"plan_type" text NOT NULL,
|
||||
"tokens" integer NOT NULL,
|
||||
"product_id" varchar(255),
|
||||
"subscription_id" varchar(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "subscription" ADD CONSTRAINT "subscription_team_id_team_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."team"("id") ON DELETE cascade ON UPDATE no action;
|
||||
3
packages/core/migrations/0008_third_mindworm.sql
Normal file
3
packages/core/migrations/0008_third_mindworm.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "subscription" ADD CONSTRAINT "subscription_id_team_id_pk" PRIMARY KEY("id","team_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "subscription_id" ON "subscription" USING btree ("id");--> statement-breakpoint
|
||||
CREATE INDEX "subscription_user_id" ON "subscription" USING btree ("user_id");
|
||||
2
packages/core/migrations/0009_luxuriant_wraith.sql
Normal file
2
packages/core/migrations/0009_luxuriant_wraith.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
CREATE UNIQUE INDEX "steam_id" ON "steam" USING btree ("steam_id");--> statement-breakpoint
|
||||
CREATE INDEX "steam_user_id" ON "steam" USING btree ("user_id");
|
||||
94
packages/core/migrations/0010_certain_dust.sql
Normal file
94
packages/core/migrations/0010_certain_dust.sql
Normal file
@@ -0,0 +1,94 @@
|
||||
CREATE TYPE "public"."member_role" AS ENUM('child', 'adult');--> statement-breakpoint
|
||||
CREATE TYPE "public"."steam_status" AS ENUM('online', 'offline', 'dnd', 'playing');--> statement-breakpoint
|
||||
CREATE TABLE "steam_account_credentials" (
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"steam_id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"refresh_token" text NOT NULL,
|
||||
"expiry" timestamp with time zone NOT NULL,
|
||||
"username" varchar(255) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "friends_list" (
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"steam_id" varchar(255) NOT NULL,
|
||||
"friend_steam_id" varchar(255) NOT NULL,
|
||||
CONSTRAINT "friends_list_steam_id_friend_steam_id_pk" PRIMARY KEY("steam_id","friend_steam_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "members" (
|
||||
"id" char(30) NOT NULL,
|
||||
"team_id" char(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"user_id" char(30),
|
||||
"steam_id" varchar(255) NOT NULL,
|
||||
"role" "member_role" NOT NULL,
|
||||
CONSTRAINT "members_id_team_id_pk" PRIMARY KEY("id","team_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "steam_accounts" (
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"steam_id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"user_id" char(30),
|
||||
"status" "steam_status" NOT NULL,
|
||||
"last_synced_at" timestamp with time zone NOT NULL,
|
||||
"real_name" varchar(255),
|
||||
"member_since" timestamp with time zone NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"profile_url" varchar(255),
|
||||
"username" varchar(255) NOT NULL,
|
||||
"avatar_hash" varchar(255) NOT NULL,
|
||||
"limitations" json NOT NULL,
|
||||
CONSTRAINT "idx_steam_username" UNIQUE("username")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "teams" (
|
||||
"id" char(30) PRIMARY KEY NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"owner_id" char(30) NOT NULL,
|
||||
"invite_code" varchar(10) NOT NULL,
|
||||
"slug" varchar(255) NOT NULL,
|
||||
"max_members" bigint NOT NULL,
|
||||
CONSTRAINT "idx_team_invite_code" UNIQUE("invite_code")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" char(30) PRIMARY KEY NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"email" varchar(255) NOT NULL,
|
||||
"avatar_url" text,
|
||||
"last_login" timestamp with time zone NOT NULL,
|
||||
"name" varchar(255) NOT NULL,
|
||||
"polar_customer_id" varchar(255),
|
||||
CONSTRAINT "idx_user_email" UNIQUE("email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
DROP TABLE "machine" CASCADE;--> statement-breakpoint
|
||||
DROP TABLE "member" CASCADE;--> statement-breakpoint
|
||||
DROP TABLE "steam" CASCADE;--> statement-breakpoint
|
||||
DROP TABLE "subscription" CASCADE;--> statement-breakpoint
|
||||
DROP TABLE "team" CASCADE;--> statement-breakpoint
|
||||
DROP TABLE "user" CASCADE;--> statement-breakpoint
|
||||
ALTER TABLE "steam_account_credentials" ADD CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_friend_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("friend_steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "members" ADD CONSTRAINT "members_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "members" ADD CONSTRAINT "members_steam_id_steam_accounts_steam_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("steam_id") ON DELETE cascade ON UPDATE restrict;--> statement-breakpoint
|
||||
ALTER TABLE "steam_accounts" ADD CONSTRAINT "steam_accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "teams" ADD CONSTRAINT "teams_owner_id_users_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "teams" ADD CONSTRAINT "teams_slug_steam_accounts_username_fk" FOREIGN KEY ("slug") REFERENCES "public"."steam_accounts"("username") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "idx_member_steam_id" ON "members" USING btree ("team_id","steam_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "idx_member_user_id" ON "members" USING btree ("team_id","user_id") WHERE "members"."user_id" is not null;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "idx_team_slug" ON "teams" USING btree ("slug");
|
||||
89
packages/core/migrations/0011_simple_azazel.sql
Normal file
89
packages/core/migrations/0011_simple_azazel.sql
Normal file
@@ -0,0 +1,89 @@
|
||||
CREATE TYPE "public"."compatibility" AS ENUM('high', 'mid', 'low', 'unknown');--> statement-breakpoint
|
||||
CREATE TYPE "public"."category_type" AS ENUM('tag', 'genre', 'publisher', 'developer');--> statement-breakpoint
|
||||
CREATE TYPE "public"."image_type" AS ENUM('heroArt', 'icon', 'logo', 'superHeroArt', 'poster', 'boxArt', 'screenshot', 'background');--> statement-breakpoint
|
||||
CREATE TABLE "base_games" (
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"id" varchar(255) PRIMARY KEY NOT NULL,
|
||||
"slug" varchar(255) NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"release_date" timestamp with time zone NOT NULL,
|
||||
"size" json NOT NULL,
|
||||
"description" text NOT NULL,
|
||||
"primary_genre" text NOT NULL,
|
||||
"controller_support" text,
|
||||
"compatibility" "compatibility" DEFAULT 'unknown' NOT NULL,
|
||||
"score" numeric(2, 1) NOT NULL,
|
||||
CONSTRAINT "idx_base_games_slug" UNIQUE("slug")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "categories" (
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"slug" varchar(255) NOT NULL,
|
||||
"type" "category_type" NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
CONSTRAINT "categories_slug_type_pk" PRIMARY KEY("slug","type")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "games" (
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"base_game_id" varchar(255) NOT NULL,
|
||||
"category_slug" varchar(255) NOT NULL,
|
||||
"type" "category_type" NOT NULL,
|
||||
CONSTRAINT "games_base_game_id_category_slug_type_pk" PRIMARY KEY("base_game_id","category_slug","type")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "images" (
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"type" "image_type" NOT NULL,
|
||||
"image_hash" varchar(255) NOT NULL,
|
||||
"base_game_id" varchar(255) NOT NULL,
|
||||
"source_url" text NOT NULL,
|
||||
"position" integer DEFAULT 0 NOT NULL,
|
||||
"file_size" integer NOT NULL,
|
||||
"dimensions" json NOT NULL,
|
||||
"extracted_color" json NOT NULL,
|
||||
CONSTRAINT "images_image_hash_type_base_game_id_position_pk" PRIMARY KEY("image_hash","type","base_game_id","position")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "game_libraries" (
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"base_game_id" varchar(255) NOT NULL,
|
||||
"owner_id" varchar(255) NOT NULL,
|
||||
CONSTRAINT "game_libraries_base_game_id_owner_id_pk" PRIMARY KEY("base_game_id","owner_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "steam_accounts" RENAME COLUMN "steam_id" TO "id";--> statement-breakpoint
|
||||
ALTER TABLE "steam_account_credentials" DROP CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_steam_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "friends_list" DROP CONSTRAINT "friends_list_steam_id_steam_accounts_steam_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "friends_list" DROP CONSTRAINT "friends_list_friend_steam_id_steam_accounts_steam_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "members" DROP CONSTRAINT "members_steam_id_steam_accounts_steam_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "games" ADD CONSTRAINT "games_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "games" ADD CONSTRAINT "games_categories_fkey" FOREIGN KEY ("category_slug","type") REFERENCES "public"."categories"("slug","type") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "images" ADD CONSTRAINT "images_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_base_game_id_base_games_id_fk" FOREIGN KEY ("base_game_id") REFERENCES "public"."base_games"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_owner_id_steam_accounts_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "idx_categories_type" ON "categories" USING btree ("type");--> statement-breakpoint
|
||||
CREATE INDEX "idx_games_category_slug" ON "games" USING btree ("category_slug");--> statement-breakpoint
|
||||
CREATE INDEX "idx_games_category_type" ON "games" USING btree ("type");--> statement-breakpoint
|
||||
CREATE INDEX "idx_images_type" ON "images" USING btree ("type");--> statement-breakpoint
|
||||
CREATE INDEX "idx_images_game_id" ON "images" USING btree ("base_game_id");--> statement-breakpoint
|
||||
CREATE INDEX "idx_game_libraries_owner_id" ON "game_libraries" USING btree ("owner_id");--> statement-breakpoint
|
||||
ALTER TABLE "steam_account_credentials" ADD CONSTRAINT "steam_account_credentials_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "friends_list" ADD CONSTRAINT "friends_list_friend_steam_id_steam_accounts_id_fk" FOREIGN KEY ("friend_steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "members" ADD CONSTRAINT "members_steam_id_steam_accounts_id_fk" FOREIGN KEY ("steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE restrict;--> statement-breakpoint
|
||||
CREATE INDEX "idx_friends_list_friend_steam_id" ON "friends_list" USING btree ("friend_steam_id");
|
||||
4
packages/core/migrations/0012_glorious_jetstream.sql
Normal file
4
packages/core/migrations/0012_glorious_jetstream.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE "games" DROP CONSTRAINT "games_categories_fkey";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "games" ADD CONSTRAINT "games_categories_fkey" FOREIGN KEY ("category_slug","type") REFERENCES "public"."categories"("slug","type") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "idx_games_category_slug_type" ON "games" USING btree ("category_slug","type");
|
||||
3
packages/core/migrations/0013_neat_colleen_wing.sql
Normal file
3
packages/core/migrations/0013_neat_colleen_wing.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
CREATE TYPE "public"."controller_support" AS ENUM('full', 'unknown');--> statement-breakpoint
|
||||
ALTER TABLE "base_games" ALTER COLUMN "controller_support" SET DATA TYPE controller_support;--> statement-breakpoint
|
||||
ALTER TABLE "base_games" ALTER COLUMN "controller_support" SET NOT NULL;
|
||||
1
packages/core/migrations/0014_thin_groot.sql
Normal file
1
packages/core/migrations/0014_thin_groot.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "base_games" ALTER COLUMN "primary_genre" DROP NOT NULL;
|
||||
1
packages/core/migrations/0015_handy_giant_man.sql
Normal file
1
packages/core/migrations/0015_handy_giant_man.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TYPE "public"."controller_support" ADD VALUE 'partial' BEFORE 'unknown';
|
||||
4
packages/core/migrations/0016_melted_johnny_storm.sql
Normal file
4
packages/core/migrations/0016_melted_johnny_storm.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE "game_libraries" ADD COLUMN "time_acquired" timestamp with time zone NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" ADD COLUMN "last_played" timestamp with time zone NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" ADD COLUMN "total_playtime" integer NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" ADD COLUMN "is_family_shared" boolean NOT NULL;
|
||||
4
packages/core/migrations/0017_zippy_nico_minoru.sql
Normal file
4
packages/core/migrations/0017_zippy_nico_minoru.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE "public"."images" ALTER COLUMN "type" SET DATA TYPE text;--> statement-breakpoint
|
||||
DROP TYPE "public"."image_type";--> statement-breakpoint
|
||||
CREATE TYPE "public"."image_type" AS ENUM('heroArt', 'icon', 'logo', 'superHeroArt', 'poster', 'boxArt', 'screenshot', 'backdrop');--> statement-breakpoint
|
||||
ALTER TABLE "public"."images" ALTER COLUMN "type" SET DATA TYPE "public"."image_type" USING "type"::"public"."image_type";
|
||||
4
packages/core/migrations/0018_solid_enchantress.sql
Normal file
4
packages/core/migrations/0018_solid_enchantress.sql
Normal file
@@ -0,0 +1,4 @@
|
||||
ALTER TABLE "public"."images" ALTER COLUMN "type" SET DATA TYPE text;--> statement-breakpoint
|
||||
DROP TYPE "public"."image_type";--> statement-breakpoint
|
||||
CREATE TYPE "public"."image_type" AS ENUM('heroArt', 'icon', 'logo', 'banner', 'poster', 'boxArt', 'screenshot', 'backdrop');--> statement-breakpoint
|
||||
ALTER TABLE "public"."images" ALTER COLUMN "type" SET DATA TYPE "public"."image_type" USING "type"::"public"."image_type";
|
||||
19
packages/core/migrations/0019_charming_namorita.sql
Normal file
19
packages/core/migrations/0019_charming_namorita.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
Unfortunately in current drizzle-kit version we can't automatically get name for primary key.
|
||||
We are working on making it available!
|
||||
|
||||
Meanwhile you can:
|
||||
1. Check pk name in your database, by running
|
||||
SELECT constraint_name FROM information_schema.table_constraints
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'steam_account_credentials'
|
||||
AND constraint_type = 'PRIMARY KEY';
|
||||
2. Uncomment code below and paste pk name manually
|
||||
|
||||
Hope to release this update as soon as possible
|
||||
*/
|
||||
|
||||
-- ALTER TABLE "steam_account_credentials" DROP CONSTRAINT "<constraint_name>";--> statement-breakpoint
|
||||
ALTER TABLE "images" ALTER COLUMN "source_url" DROP NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "steam_account_credentials" ADD CONSTRAINT "steam_account_credentials_steam_id_id_pk" PRIMARY KEY("steam_id","id");--> statement-breakpoint
|
||||
ALTER TABLE "steam_account_credentials" ADD COLUMN "id" char(30) NOT NULL;
|
||||
23
packages/core/migrations/0020_vengeful_wallop.sql
Normal file
23
packages/core/migrations/0020_vengeful_wallop.sql
Normal file
@@ -0,0 +1,23 @@
|
||||
ALTER TABLE "steam_account_credentials" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint
|
||||
DROP TABLE "steam_account_credentials" CASCADE;--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" RENAME COLUMN "owner_id" TO "owner_steam_id";--> statement-breakpoint
|
||||
ALTER TABLE "teams" RENAME COLUMN "owner_id" TO "owner_steam_id";--> statement-breakpoint
|
||||
ALTER TABLE "steam_accounts" DROP CONSTRAINT "idx_steam_username";--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" DROP CONSTRAINT "game_libraries_owner_id_steam_accounts_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "teams" DROP CONSTRAINT "teams_owner_id_users_id_fk";
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "teams" DROP CONSTRAINT "teams_slug_steam_accounts_username_fk";
|
||||
--> statement-breakpoint
|
||||
DROP INDEX "idx_team_slug";--> statement-breakpoint
|
||||
DROP INDEX "idx_game_libraries_owner_id";--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" DROP CONSTRAINT "game_libraries_base_game_id_owner_id_pk";--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" ALTER COLUMN "last_played" DROP NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_base_game_id_owner_steam_id_pk" PRIMARY KEY("base_game_id","owner_steam_id");--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" ADD CONSTRAINT "game_libraries_owner_steam_id_steam_accounts_id_fk" FOREIGN KEY ("owner_steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "teams" ADD CONSTRAINT "teams_owner_steam_id_steam_accounts_id_fk" FOREIGN KEY ("owner_steam_id") REFERENCES "public"."steam_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE INDEX "idx_game_libraries_owner_id" ON "game_libraries" USING btree ("owner_steam_id");--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" DROP COLUMN "time_acquired";--> statement-breakpoint
|
||||
ALTER TABLE "game_libraries" DROP COLUMN "is_family_shared";--> statement-breakpoint
|
||||
ALTER TABLE "steam_accounts" DROP COLUMN "username";--> statement-breakpoint
|
||||
ALTER TABLE "teams" DROP COLUMN "slug";
|
||||
2
packages/core/migrations/0021_real_skreet.sql
Normal file
2
packages/core/migrations/0021_real_skreet.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TYPE "public"."category_type" ADD VALUE 'category';--> statement-breakpoint
|
||||
ALTER TYPE "public"."category_type" ADD VALUE 'franchise';
|
||||
6
packages/core/migrations/0022_clean_living_lightning.sql
Normal file
6
packages/core/migrations/0022_clean_living_lightning.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
ALTER TABLE "public"."categories" ALTER COLUMN "type" SET DATA TYPE text;--> statement-breakpoint
|
||||
ALTER TABLE "public"."games" ALTER COLUMN "type" SET DATA TYPE text;--> statement-breakpoint
|
||||
DROP TYPE "public"."category_type";--> statement-breakpoint
|
||||
CREATE TYPE "public"."category_type" AS ENUM('tag', 'genre', 'publisher', 'developer', 'categorie', 'franchise');--> statement-breakpoint
|
||||
ALTER TABLE "public"."categories" ALTER COLUMN "type" SET DATA TYPE "public"."category_type" USING "type"::"public"."category_type";--> statement-breakpoint
|
||||
ALTER TABLE "public"."games" ALTER COLUMN "type" SET DATA TYPE "public"."category_type" USING "type"::"public"."category_type";
|
||||
2
packages/core/migrations/0023_flawless_steel_serpent.sql
Normal file
2
packages/core/migrations/0023_flawless_steel_serpent.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "base_games" ALTER COLUMN "description" DROP NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "base_games" ADD COLUMN "links" text[];
|
||||
1
packages/core/migrations/0024_damp_cerise.sql
Normal file
1
packages/core/migrations/0024_damp_cerise.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE "base_games" ALTER COLUMN "links" SET DATA TYPE json;
|
||||
3
packages/core/migrations/0025_bitter_jack_flag.sql
Normal file
3
packages/core/migrations/0025_bitter_jack_flag.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
DROP TABLE "members" CASCADE;--> statement-breakpoint
|
||||
DROP TABLE "teams" CASCADE;--> statement-breakpoint
|
||||
DROP TYPE "public"."member_role";
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"id": "08ba0262-ce0a-4d87-b4e2-0d17dc0ee28c",
|
||||
"id": "f09034df-208a-42b3-b61f-f842921c6e24",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
@@ -54,6 +54,21 @@
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
@@ -74,21 +89,6 @@
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
@@ -136,22 +136,28 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"team_slug": {
|
||||
"name": "team_slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
@@ -209,12 +215,6 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
@@ -227,11 +227,24 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
"notNull": false
|
||||
},
|
||||
"flags": {
|
||||
"name": "flags",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'{}'::json"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "c09359df-19fe-4246-9a41-43b3a429c12f",
|
||||
"prevId": "08ba0262-ce0a-4d87-b4e2-0d17dc0ee28c",
|
||||
"id": "6f428226-b5d8-4182-a676-d04f842f9ded",
|
||||
"prevId": "f09034df-208a-42b3-b61f-f842921c6e24",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
@@ -54,6 +54,21 @@
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
@@ -74,21 +89,6 @@
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
@@ -136,15 +136,21 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
@@ -209,12 +215,6 @@
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
@@ -227,11 +227,24 @@
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"flags": {
|
||||
"name": "flags",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'{}'::json"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
|
||||
420
packages/core/migrations/meta/0002_snapshot.json
Normal file
420
packages/core/migrations/meta/0002_snapshot.json
Normal file
@@ -0,0 +1,420 @@
|
||||
{
|
||||
"id": "227c54d2-b643-48d5-964b-af6fe004369a",
|
||||
"prevId": "6f428226-b5d8-4182-a676-d04f842f9ded",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"global_steam_email": {
|
||||
"name": "global_steam_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"steam_user_id_id_pk": {
|
||||
"name": "steam_user_id_id_pk",
|
||||
"columns": [
|
||||
"user_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"flags": {
|
||||
"name": "flags",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'{}'::json"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
507
packages/core/migrations/meta/0003_snapshot.json
Normal file
507
packages/core/migrations/meta/0003_snapshot.json
Normal file
@@ -0,0 +1,507 @@
|
||||
{
|
||||
"id": "eb5d41aa-5f85-4b2d-8633-fc021b211241",
|
||||
"prevId": "227c54d2-b643-48d5-964b-af6fe004369a",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"steam_user_id_id_pk": {
|
||||
"name": "steam_user_id_id_pk",
|
||||
"columns": [
|
||||
"user_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
499
packages/core/migrations/meta/0004_snapshot.json
Normal file
499
packages/core/migrations/meta/0004_snapshot.json
Normal file
@@ -0,0 +1,499 @@
|
||||
{
|
||||
"id": "65574f71-e0d3-4363-9449-394e7c376a30",
|
||||
"prevId": "eb5d41aa-5f85-4b2d-8633-fc021b211241",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"machine_user_id_id_pk": {
|
||||
"name": "machine_user_id_id_pk",
|
||||
"columns": [
|
||||
"user_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_seen": {
|
||||
"name": "last_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_user_id_user_id_fk": {
|
||||
"name": "steam_user_id_user_id_fk",
|
||||
"tableFrom": "steam",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
485
packages/core/migrations/meta/0005_snapshot.json
Normal file
485
packages/core/migrations/meta/0005_snapshot.json
Normal file
@@ -0,0 +1,485 @@
|
||||
{
|
||||
"id": "0b04858c-a7e3-43b6-98a4-1dc2f6f97488",
|
||||
"prevId": "65574f71-e0d3-4363-9449-394e7c376a30",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_seen": {
|
||||
"name": "last_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_user_id_user_id_fk": {
|
||||
"name": "steam_user_id_user_id_fk",
|
||||
"tableFrom": "steam",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
485
packages/core/migrations/meta/0006_snapshot.json
Normal file
485
packages/core/migrations/meta/0006_snapshot.json
Normal file
@@ -0,0 +1,485 @@
|
||||
{
|
||||
"id": "69827225-1351-4709-a9b2-facb0f569215",
|
||||
"prevId": "0b04858c-a7e3-43b6-98a4-1dc2f6f97488",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_seen": {
|
||||
"name": "last_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_user_id_user_id_fk": {
|
||||
"name": "steam_user_id_user_id_fk",
|
||||
"tableFrom": "steam",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
580
packages/core/migrations/meta/0007_snapshot.json
Normal file
580
packages/core/migrations/meta/0007_snapshot.json
Normal file
@@ -0,0 +1,580 @@
|
||||
{
|
||||
"id": "fff2b73d-85ab-48bc-86de-69d3caf317f0",
|
||||
"prevId": "69827225-1351-4709-a9b2-facb0f569215",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_seen": {
|
||||
"name": "last_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_user_id_user_id_fk": {
|
||||
"name": "steam_user_id_user_id_fk",
|
||||
"tableFrom": "steam",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.subscription": {
|
||||
"name": "subscription",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"standing": {
|
||||
"name": "standing",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tokens": {
|
||||
"name": "tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"subscription_team_id_team_id_fk": {
|
||||
"name": "subscription_team_id_team_id_fk",
|
||||
"tableFrom": "subscription",
|
||||
"tableTo": "team",
|
||||
"columnsFrom": [
|
||||
"team_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
619
packages/core/migrations/meta/0008_snapshot.json
Normal file
619
packages/core/migrations/meta/0008_snapshot.json
Normal file
@@ -0,0 +1,619 @@
|
||||
{
|
||||
"id": "17b9c14f-ff15-44a5-9aaf-3f3b7dd7d294",
|
||||
"prevId": "fff2b73d-85ab-48bc-86de-69d3caf317f0",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_seen": {
|
||||
"name": "last_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_user_id_user_id_fk": {
|
||||
"name": "steam_user_id_user_id_fk",
|
||||
"tableFrom": "steam",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.subscription": {
|
||||
"name": "subscription",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"standing": {
|
||||
"name": "standing",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tokens": {
|
||||
"name": "tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"subscription_user_id": {
|
||||
"name": "subscription_user_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"subscription_team_id_team_id_fk": {
|
||||
"name": "subscription_team_id_team_id_fk",
|
||||
"tableFrom": "subscription",
|
||||
"tableTo": "team",
|
||||
"columnsFrom": [
|
||||
"team_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"subscription_id_team_id_pk": {
|
||||
"name": "subscription_id_team_id_pk",
|
||||
"columns": [
|
||||
"id",
|
||||
"team_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
650
packages/core/migrations/meta/0009_snapshot.json
Normal file
650
packages/core/migrations/meta/0009_snapshot.json
Normal file
@@ -0,0 +1,650 @@
|
||||
{
|
||||
"id": "1717c769-cee0-4242-bcbb-9538c80d985c",
|
||||
"prevId": "17b9c14f-ff15-44a5-9aaf-3f3b7dd7d294",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_seen": {
|
||||
"name": "last_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "steam_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"steam_user_id": {
|
||||
"name": "steam_user_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"steam_user_id_user_id_fk": {
|
||||
"name": "steam_user_id_user_id_fk",
|
||||
"tableFrom": "steam",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.subscription": {
|
||||
"name": "subscription",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"standing": {
|
||||
"name": "standing",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tokens": {
|
||||
"name": "tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"subscription_user_id": {
|
||||
"name": "subscription_user_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"subscription_team_id_team_id_fk": {
|
||||
"name": "subscription_team_id_team_id_fk",
|
||||
"tableFrom": "subscription",
|
||||
"tableTo": "team",
|
||||
"columnsFrom": [
|
||||
"team_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"subscription_id_team_id_pk": {
|
||||
"name": "subscription_id_team_id_pk",
|
||||
"columns": [
|
||||
"id",
|
||||
"team_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
651
packages/core/migrations/meta/0010_snapshot.json
Normal file
651
packages/core/migrations/meta/0010_snapshot.json
Normal file
@@ -0,0 +1,651 @@
|
||||
{
|
||||
"id": "56a4d60a-c062-47e5-a97e-625443411ad8",
|
||||
"prevId": "1717c769-cee0-4242-bcbb-9538c80d985c",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.steam_account_credentials": {
|
||||
"name": "steam_account_credentials",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expiry": {
|
||||
"name": "expiry",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_account_credentials_steam_id_steam_accounts_steam_id_fk": {
|
||||
"name": "steam_account_credentials_steam_id_steam_accounts_steam_id_fk",
|
||||
"tableFrom": "steam_account_credentials",
|
||||
"tableTo": "steam_accounts",
|
||||
"columnsFrom": [
|
||||
"steam_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"steam_id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.friends_list": {
|
||||
"name": "friends_list",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"friend_steam_id": {
|
||||
"name": "friend_steam_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"friends_list_steam_id_steam_accounts_steam_id_fk": {
|
||||
"name": "friends_list_steam_id_steam_accounts_steam_id_fk",
|
||||
"tableFrom": "friends_list",
|
||||
"tableTo": "steam_accounts",
|
||||
"columnsFrom": [
|
||||
"steam_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"steam_id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"friends_list_friend_steam_id_steam_accounts_steam_id_fk": {
|
||||
"name": "friends_list_friend_steam_id_steam_accounts_steam_id_fk",
|
||||
"tableFrom": "friends_list",
|
||||
"tableTo": "steam_accounts",
|
||||
"columnsFrom": [
|
||||
"friend_steam_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"steam_id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"friends_list_steam_id_friend_steam_id_pk": {
|
||||
"name": "friends_list_steam_id_friend_steam_id_pk",
|
||||
"columns": [
|
||||
"steam_id",
|
||||
"friend_steam_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.members": {
|
||||
"name": "members",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "member_role",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_member_steam_id": {
|
||||
"name": "idx_member_steam_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "steam_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"idx_member_user_id": {
|
||||
"name": "idx_member_user_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"where": "\"members\".\"user_id\" is not null",
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"members_user_id_users_id_fk": {
|
||||
"name": "members_user_id_users_id_fk",
|
||||
"tableFrom": "members",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"members_steam_id_steam_accounts_steam_id_fk": {
|
||||
"name": "members_steam_id_steam_accounts_steam_id_fk",
|
||||
"tableFrom": "members",
|
||||
"tableTo": "steam_accounts",
|
||||
"columnsFrom": [
|
||||
"steam_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"steam_id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "restrict"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"members_id_team_id_pk": {
|
||||
"name": "members_id_team_id_pk",
|
||||
"columns": [
|
||||
"id",
|
||||
"team_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam_accounts": {
|
||||
"name": "steam_accounts",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "steam_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_synced_at": {
|
||||
"name": "last_synced_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"real_name": {
|
||||
"name": "real_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"member_since": {
|
||||
"name": "member_since",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"profile_url": {
|
||||
"name": "profile_url",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_hash": {
|
||||
"name": "avatar_hash",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitations": {
|
||||
"name": "limitations",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_accounts_user_id_users_id_fk": {
|
||||
"name": "steam_accounts_user_id_users_id_fk",
|
||||
"tableFrom": "steam_accounts",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"idx_steam_username": {
|
||||
"name": "idx_steam_username",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"username"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.teams": {
|
||||
"name": "teams",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"owner_id": {
|
||||
"name": "owner_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"invite_code": {
|
||||
"name": "invite_code",
|
||||
"type": "varchar(10)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"max_members": {
|
||||
"name": "max_members",
|
||||
"type": "bigint",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_team_slug": {
|
||||
"name": "idx_team_slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"teams_owner_id_users_id_fk": {
|
||||
"name": "teams_owner_id_users_id_fk",
|
||||
"tableFrom": "teams",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"owner_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"teams_slug_steam_accounts_username_fk": {
|
||||
"name": "teams_slug_steam_accounts_username_fk",
|
||||
"tableFrom": "teams",
|
||||
"tableTo": "steam_accounts",
|
||||
"columnsFrom": [
|
||||
"slug"
|
||||
],
|
||||
"columnsTo": [
|
||||
"username"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"idx_team_invite_code": {
|
||||
"name": "idx_team_invite_code",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"invite_code"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_login": {
|
||||
"name": "last_login",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"idx_user_email": {
|
||||
"name": "idx_user_email",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.member_role": {
|
||||
"name": "member_role",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"child",
|
||||
"adult"
|
||||
]
|
||||
},
|
||||
"public.steam_status": {
|
||||
"name": "steam_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"online",
|
||||
"offline",
|
||||
"dnd",
|
||||
"playing"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
1235
packages/core/migrations/meta/0011_snapshot.json
Normal file
1235
packages/core/migrations/meta/0011_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1256
packages/core/migrations/meta/0012_snapshot.json
Normal file
1256
packages/core/migrations/meta/0012_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1265
packages/core/migrations/meta/0013_snapshot.json
Normal file
1265
packages/core/migrations/meta/0013_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1265
packages/core/migrations/meta/0014_snapshot.json
Normal file
1265
packages/core/migrations/meta/0014_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1266
packages/core/migrations/meta/0015_snapshot.json
Normal file
1266
packages/core/migrations/meta/0015_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1290
packages/core/migrations/meta/0016_snapshot.json
Normal file
1290
packages/core/migrations/meta/0016_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1290
packages/core/migrations/meta/0017_snapshot.json
Normal file
1290
packages/core/migrations/meta/0017_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1290
packages/core/migrations/meta/0018_snapshot.json
Normal file
1290
packages/core/migrations/meta/0018_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1304
packages/core/migrations/meta/0019_snapshot.json
Normal file
1304
packages/core/migrations/meta/0019_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1158
packages/core/migrations/meta/0020_snapshot.json
Normal file
1158
packages/core/migrations/meta/0020_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1160
packages/core/migrations/meta/0021_snapshot.json
Normal file
1160
packages/core/migrations/meta/0021_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1160
packages/core/migrations/meta/0022_snapshot.json
Normal file
1160
packages/core/migrations/meta/0022_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1166
packages/core/migrations/meta/0023_snapshot.json
Normal file
1166
packages/core/migrations/meta/0023_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
1166
packages/core/migrations/meta/0024_snapshot.json
Normal file
1166
packages/core/migrations/meta/0024_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
930
packages/core/migrations/meta/0025_snapshot.json
Normal file
930
packages/core/migrations/meta/0025_snapshot.json
Normal file
@@ -0,0 +1,930 @@
|
||||
{
|
||||
"id": "735d315b-40e1-46c1-814d-0fd3619b65de",
|
||||
"prevId": "d35aa09b-5739-46a5-86f3-3050913dc2f7",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.base_games": {
|
||||
"name": "base_games",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"links": {
|
||||
"name": "links",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"release_date": {
|
||||
"name": "release_date",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"size": {
|
||||
"name": "size",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"primary_genre": {
|
||||
"name": "primary_genre",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"controller_support": {
|
||||
"name": "controller_support",
|
||||
"type": "controller_support",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"compatibility": {
|
||||
"name": "compatibility",
|
||||
"type": "compatibility",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'unknown'"
|
||||
},
|
||||
"score": {
|
||||
"name": "score",
|
||||
"type": "numeric(2, 1)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"idx_base_games_slug": {
|
||||
"name": "idx_base_games_slug",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"slug"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.categories": {
|
||||
"name": "categories",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "category_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_categories_type": {
|
||||
"name": "idx_categories_type",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"categories_slug_type_pk": {
|
||||
"name": "categories_slug_type_pk",
|
||||
"columns": [
|
||||
"slug",
|
||||
"type"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.friends_list": {
|
||||
"name": "friends_list",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"friend_steam_id": {
|
||||
"name": "friend_steam_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_friends_list_friend_steam_id": {
|
||||
"name": "idx_friends_list_friend_steam_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "friend_steam_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"friends_list_steam_id_steam_accounts_id_fk": {
|
||||
"name": "friends_list_steam_id_steam_accounts_id_fk",
|
||||
"tableFrom": "friends_list",
|
||||
"tableTo": "steam_accounts",
|
||||
"columnsFrom": [
|
||||
"steam_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"friends_list_friend_steam_id_steam_accounts_id_fk": {
|
||||
"name": "friends_list_friend_steam_id_steam_accounts_id_fk",
|
||||
"tableFrom": "friends_list",
|
||||
"tableTo": "steam_accounts",
|
||||
"columnsFrom": [
|
||||
"friend_steam_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"friends_list_steam_id_friend_steam_id_pk": {
|
||||
"name": "friends_list_steam_id_friend_steam_id_pk",
|
||||
"columns": [
|
||||
"steam_id",
|
||||
"friend_steam_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.games": {
|
||||
"name": "games",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"base_game_id": {
|
||||
"name": "base_game_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"category_slug": {
|
||||
"name": "category_slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "category_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_games_category_slug": {
|
||||
"name": "idx_games_category_slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "category_slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"idx_games_category_type": {
|
||||
"name": "idx_games_category_type",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"idx_games_category_slug_type": {
|
||||
"name": "idx_games_category_slug_type",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "category_slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"games_base_game_id_base_games_id_fk": {
|
||||
"name": "games_base_game_id_base_games_id_fk",
|
||||
"tableFrom": "games",
|
||||
"tableTo": "base_games",
|
||||
"columnsFrom": [
|
||||
"base_game_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"games_categories_fkey": {
|
||||
"name": "games_categories_fkey",
|
||||
"tableFrom": "games",
|
||||
"tableTo": "categories",
|
||||
"columnsFrom": [
|
||||
"category_slug",
|
||||
"type"
|
||||
],
|
||||
"columnsTo": [
|
||||
"slug",
|
||||
"type"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"games_base_game_id_category_slug_type_pk": {
|
||||
"name": "games_base_game_id_category_slug_type_pk",
|
||||
"columns": [
|
||||
"base_game_id",
|
||||
"category_slug",
|
||||
"type"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.images": {
|
||||
"name": "images",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "image_type",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"image_hash": {
|
||||
"name": "image_hash",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"base_game_id": {
|
||||
"name": "base_game_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"source_url": {
|
||||
"name": "source_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"position": {
|
||||
"name": "position",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"file_size": {
|
||||
"name": "file_size",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"dimensions": {
|
||||
"name": "dimensions",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"extracted_color": {
|
||||
"name": "extracted_color",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_images_type": {
|
||||
"name": "idx_images_type",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "type",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"idx_images_game_id": {
|
||||
"name": "idx_images_game_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "base_game_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"images_base_game_id_base_games_id_fk": {
|
||||
"name": "images_base_game_id_base_games_id_fk",
|
||||
"tableFrom": "images",
|
||||
"tableTo": "base_games",
|
||||
"columnsFrom": [
|
||||
"base_game_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"images_image_hash_type_base_game_id_position_pk": {
|
||||
"name": "images_image_hash_type_base_game_id_position_pk",
|
||||
"columns": [
|
||||
"image_hash",
|
||||
"type",
|
||||
"base_game_id",
|
||||
"position"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.game_libraries": {
|
||||
"name": "game_libraries",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"base_game_id": {
|
||||
"name": "base_game_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"owner_steam_id": {
|
||||
"name": "owner_steam_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_played": {
|
||||
"name": "last_played",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"total_playtime": {
|
||||
"name": "total_playtime",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"idx_game_libraries_owner_id": {
|
||||
"name": "idx_game_libraries_owner_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "owner_steam_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"game_libraries_base_game_id_base_games_id_fk": {
|
||||
"name": "game_libraries_base_game_id_base_games_id_fk",
|
||||
"tableFrom": "game_libraries",
|
||||
"tableTo": "base_games",
|
||||
"columnsFrom": [
|
||||
"base_game_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"game_libraries_owner_steam_id_steam_accounts_id_fk": {
|
||||
"name": "game_libraries_owner_steam_id_steam_accounts_id_fk",
|
||||
"tableFrom": "game_libraries",
|
||||
"tableTo": "steam_accounts",
|
||||
"columnsFrom": [
|
||||
"owner_steam_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"game_libraries_base_game_id_owner_steam_id_pk": {
|
||||
"name": "game_libraries_base_game_id_owner_steam_id_pk",
|
||||
"columns": [
|
||||
"base_game_id",
|
||||
"owner_steam_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam_accounts": {
|
||||
"name": "steam_accounts",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "steam_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_synced_at": {
|
||||
"name": "last_synced_at",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"real_name": {
|
||||
"name": "real_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"member_since": {
|
||||
"name": "member_since",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"profile_url": {
|
||||
"name": "profile_url",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_hash": {
|
||||
"name": "avatar_hash",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitations": {
|
||||
"name": "limitations",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_accounts_user_id_users_id_fk": {
|
||||
"name": "steam_accounts_user_id_users_id_fk",
|
||||
"tableFrom": "steam_accounts",
|
||||
"tableTo": "users",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"last_login": {
|
||||
"name": "last_login",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"idx_user_email": {
|
||||
"name": "idx_user_email",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.compatibility": {
|
||||
"name": "compatibility",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"high",
|
||||
"mid",
|
||||
"low",
|
||||
"unknown"
|
||||
]
|
||||
},
|
||||
"public.controller_support": {
|
||||
"name": "controller_support",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"full",
|
||||
"partial",
|
||||
"unknown"
|
||||
]
|
||||
},
|
||||
"public.category_type": {
|
||||
"name": "category_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"tag",
|
||||
"genre",
|
||||
"publisher",
|
||||
"developer",
|
||||
"categorie",
|
||||
"franchise"
|
||||
]
|
||||
},
|
||||
"public.image_type": {
|
||||
"name": "image_type",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"heroArt",
|
||||
"icon",
|
||||
"logo",
|
||||
"banner",
|
||||
"poster",
|
||||
"boxArt",
|
||||
"screenshot",
|
||||
"backdrop"
|
||||
]
|
||||
},
|
||||
"public.steam_status": {
|
||||
"name": "steam_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"online",
|
||||
"offline",
|
||||
"dnd",
|
||||
"playing"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,183 @@
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1740345380808,
|
||||
"tag": "0000_wise_black_widow",
|
||||
"when": 1741759978256,
|
||||
"tag": "0000_flaky_matthew_murdock",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1740487217291,
|
||||
"tag": "0001_flaky_tomorrow_man",
|
||||
"when": 1741955636085,
|
||||
"tag": "0001_nifty_sauron",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1743794969007,
|
||||
"tag": "0002_simple_outlaw_kid",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1744287542918,
|
||||
"tag": "0003_first_big_bertha",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"version": "7",
|
||||
"when": 1744614629788,
|
||||
"tag": "0004_amused_mattie_franklin",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1744614896792,
|
||||
"tag": "0005_aspiring_stature",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1744634229644,
|
||||
"tag": "0006_worthless_dreadnoughts",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "7",
|
||||
"when": 1744634322996,
|
||||
"tag": "0007_warm_secret_warriors",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 8,
|
||||
"version": "7",
|
||||
"when": 1744651530530,
|
||||
"tag": "0008_third_mindworm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 9,
|
||||
"version": "7",
|
||||
"when": 1744651817581,
|
||||
"tag": "0009_luxuriant_wraith",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 10,
|
||||
"version": "7",
|
||||
"when": 1746726715456,
|
||||
"tag": "0010_certain_dust",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 11,
|
||||
"version": "7",
|
||||
"when": 1746904821461,
|
||||
"tag": "0011_simple_azazel",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 12,
|
||||
"version": "7",
|
||||
"when": 1746905730079,
|
||||
"tag": "0012_glorious_jetstream",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 13,
|
||||
"version": "7",
|
||||
"when": 1746925065142,
|
||||
"tag": "0013_neat_colleen_wing",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 14,
|
||||
"version": "7",
|
||||
"when": 1746926498096,
|
||||
"tag": "0014_thin_groot",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 15,
|
||||
"version": "7",
|
||||
"when": 1746928882281,
|
||||
"tag": "0015_handy_giant_man",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 16,
|
||||
"version": "7",
|
||||
"when": 1747032794033,
|
||||
"tag": "0016_melted_johnny_storm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 17,
|
||||
"version": "7",
|
||||
"when": 1747034424687,
|
||||
"tag": "0017_zippy_nico_minoru",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 18,
|
||||
"version": "7",
|
||||
"when": 1747073173196,
|
||||
"tag": "0018_solid_enchantress",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 19,
|
||||
"version": "7",
|
||||
"when": 1747202158003,
|
||||
"tag": "0019_charming_namorita",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 20,
|
||||
"version": "7",
|
||||
"when": 1747795508868,
|
||||
"tag": "0020_vengeful_wallop",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 21,
|
||||
"version": "7",
|
||||
"when": 1747975397543,
|
||||
"tag": "0021_real_skreet",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 22,
|
||||
"version": "7",
|
||||
"when": 1748099972605,
|
||||
"tag": "0022_clean_living_lightning",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 23,
|
||||
"version": "7",
|
||||
"when": 1748411845939,
|
||||
"tag": "0023_flawless_steel_serpent",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 24,
|
||||
"version": "7",
|
||||
"when": 1748414049463,
|
||||
"tag": "0024_damp_cerise",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 25,
|
||||
"version": "7",
|
||||
"when": 1748845818197,
|
||||
"tag": "0025_bitter_jack_flag",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -4,37 +4,46 @@
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"db:dev": "drizzle-kit",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"db": "sst shell drizzle-kit",
|
||||
"db:push": "sst shell drizzle-kit push",
|
||||
"db:migrate": "sst shell drizzle-kit migrate",
|
||||
"db:generate": "sst shell drizzle-kit generate",
|
||||
"db:connect": "sst shell ../scripts/src/psql.ts",
|
||||
"db:move": "sst shell drizzle-kit generate && sst shell drizzle-kit migrate && sst shell drizzle-kit push"
|
||||
"db:exec": "sst shell ../scripts/src/psql.sh",
|
||||
"db:reset": "sst shell ../scripts/src/db-reset.sh"
|
||||
},
|
||||
"exports": {
|
||||
"./*": "./src/*.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"@types/sanitize-html": "^2.16.0",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"aws-iot-device-sdk-v2": "^1.21.1",
|
||||
"aws4fetch": "^1.0.20",
|
||||
"drizzle-kit": "^0.30.4",
|
||||
"loops": "^3.4.1",
|
||||
"mqtt": "^5.10.3",
|
||||
"remeda": "^2.19.0",
|
||||
"remeda": "^2.21.2",
|
||||
"ulid": "^2.3.0",
|
||||
"uuid": "^11.0.3",
|
||||
"zod": "^3.24.1",
|
||||
"zod-openapi": "^4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-iot-data-plane": "^3.758.0",
|
||||
"@aws-sdk/client-sesv2": "^3.753.0",
|
||||
"@instantdb/admin": "^0.17.7",
|
||||
"@neondatabase/serverless": "^0.10.4",
|
||||
"@openauthjs/openauth": "0.4.3",
|
||||
"@openauthjs/openauth": "*",
|
||||
"@openauthjs/openevent": "^0.0.27",
|
||||
"@polar-sh/sdk": "^0.26.1",
|
||||
"drizzle-orm": "^0.39.3",
|
||||
"ws": "^8.18.1"
|
||||
"drizzle-kit": "^0.30.5",
|
||||
"drizzle-orm": "^0.40.0",
|
||||
"drizzle-zod": "^0.7.1",
|
||||
"fast-average-color": "^9.5.0",
|
||||
"lru-cache": "^11.1.0",
|
||||
"p-limit": "^6.2.0",
|
||||
"pixelmatch": "^7.1.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"postgres": "^3.4.5",
|
||||
"sanitize-html": "^2.16.0",
|
||||
"sharp": "^0.34.1",
|
||||
"steam-session": "*",
|
||||
"xml2js": "^0.6.2"
|
||||
}
|
||||
}
|
||||
47
packages/core/src/account/index.ts
Normal file
47
packages/core/src/account/index.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { z } from "zod"
|
||||
import { User } from "../user";
|
||||
import { Steam } from "../steam";
|
||||
import { Actor } from "../actor";
|
||||
import { Examples } from "../examples";
|
||||
import { ErrorCodes, VisibleError } from "../error";
|
||||
|
||||
export namespace Account {
|
||||
export const Info =
|
||||
User.Info
|
||||
.extend({
|
||||
profiles: Steam.Info
|
||||
.array()
|
||||
.openapi({
|
||||
description: "The Steam accounts this user owns",
|
||||
example: [Examples.SteamAccount]
|
||||
})
|
||||
})
|
||||
.openapi({
|
||||
ref: "Account",
|
||||
description: "Represents an account's information stored on Nestri",
|
||||
example: { ...Examples.User, profiles: [Examples.SteamAccount] },
|
||||
});
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
|
||||
export const list = async (): Promise<Info> => {
|
||||
const [userResult, steamResult] =
|
||||
await Promise.allSettled([
|
||||
User.fromID(Actor.userID()),
|
||||
Steam.list()
|
||||
])
|
||||
|
||||
if (userResult.status === "rejected" || !userResult.value)
|
||||
throw new VisibleError(
|
||||
"not_found",
|
||||
ErrorCodes.NotFound.RESOURCE_NOT_FOUND,
|
||||
"User not found",
|
||||
);
|
||||
|
||||
return {
|
||||
...userResult.value,
|
||||
profiles: steamResult.status === "rejected" ? [] : steamResult.value
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,92 +1,130 @@
|
||||
import { z } from "zod";
|
||||
import { eq } from "./drizzle";
|
||||
import { VisibleError } from "./error";
|
||||
import { Log } from "./utils";
|
||||
import { createContext } from "./context";
|
||||
import { UserFlags, userTable } from "./user/user.sql";
|
||||
import { useTransaction } from "./drizzle/transaction";
|
||||
import { ErrorCodes, VisibleError } from "./error";
|
||||
|
||||
export const PublicActor = z.object({
|
||||
type: z.literal("public"),
|
||||
properties: z.object({}),
|
||||
});
|
||||
export type PublicActor = z.infer<typeof PublicActor>;
|
||||
export namespace Actor {
|
||||
|
||||
export const UserActor = z.object({
|
||||
type: z.literal("user"),
|
||||
properties: z.object({
|
||||
userID: z.string(),
|
||||
email: z.string().nonempty(),
|
||||
}),
|
||||
});
|
||||
export type UserActor = z.infer<typeof UserActor>;
|
||||
|
||||
export const MemberActor = z.object({
|
||||
type: z.literal("member"),
|
||||
properties: z.object({
|
||||
memberID: z.string(),
|
||||
teamID: z.string(),
|
||||
}),
|
||||
});
|
||||
export type MemberActor = z.infer<typeof MemberActor>;
|
||||
|
||||
export const SystemActor = z.object({
|
||||
type: z.literal("system"),
|
||||
properties: z.object({
|
||||
teamID: z.string(),
|
||||
}),
|
||||
});
|
||||
export type SystemActor = z.infer<typeof SystemActor>;
|
||||
|
||||
export const Actor = z.discriminatedUnion("type", [
|
||||
MemberActor,
|
||||
UserActor,
|
||||
PublicActor,
|
||||
SystemActor,
|
||||
]);
|
||||
export type Actor = z.infer<typeof Actor>;
|
||||
|
||||
const ActorContext = createContext<Actor>("actor");
|
||||
|
||||
export const useActor = ActorContext.use;
|
||||
export const withActor = ActorContext.with;
|
||||
|
||||
export function useUserID() {
|
||||
const actor = ActorContext.use();
|
||||
if (actor.type === "user") return actor.properties.userID;
|
||||
throw new VisibleError(
|
||||
"unauthorized",
|
||||
`You don't have permission to access this resource`,
|
||||
);
|
||||
}
|
||||
|
||||
export function assertActor<T extends Actor["type"]>(type: T) {
|
||||
const actor = useActor();
|
||||
if (actor.type !== type) {
|
||||
throw new Error(`Expected actor type ${type}, got ${actor.type}`);
|
||||
export interface User {
|
||||
type: "user";
|
||||
properties: {
|
||||
userID: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface Steam {
|
||||
type: "steam";
|
||||
properties: {
|
||||
steamID: string;
|
||||
};
|
||||
}
|
||||
|
||||
return actor as Extract<Actor, { type: T }>;
|
||||
}
|
||||
export interface Machine {
|
||||
type: "machine";
|
||||
properties: {
|
||||
machineID: string;
|
||||
fingerprint: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function useTeam() {
|
||||
const actor = useActor();
|
||||
if ("teamID" in actor.properties) return actor.properties.teamID;
|
||||
throw new Error(`Expected actor to have teamID`);
|
||||
}
|
||||
export interface Token {
|
||||
type: "member";
|
||||
properties: {
|
||||
userID: string;
|
||||
steamID: string;
|
||||
};
|
||||
}
|
||||
|
||||
export async function assertUserFlag(flag: keyof UserFlags) {
|
||||
return useTransaction((tx) =>
|
||||
tx
|
||||
.select({ flags: userTable.flags })
|
||||
.from(userTable)
|
||||
.where(eq(userTable.id, useUserID()))
|
||||
.then((rows) => {
|
||||
const flags = rows[0]?.flags;
|
||||
if (!flags)
|
||||
throw new VisibleError(
|
||||
"user.flags",
|
||||
"Actor does not have " + flag + " flag",
|
||||
);
|
||||
}),
|
||||
);
|
||||
export interface Public {
|
||||
type: "public";
|
||||
properties: {};
|
||||
}
|
||||
|
||||
export type Info = User | Public | Token | Machine | Steam;
|
||||
|
||||
export const Context = createContext<Info>();
|
||||
|
||||
export function userID() {
|
||||
const actor = Context.use();
|
||||
if ("userID" in actor.properties) return actor.properties.userID;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function steamID() {
|
||||
const actor = Context.use();
|
||||
if ("steamID" in actor.properties) return actor.properties.steamID;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function user() {
|
||||
const actor = Context.use();
|
||||
if (actor.type == "user") return actor.properties;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function teamID() {
|
||||
const actor = Context.use();
|
||||
if ("teamID" in actor.properties) return actor.properties.teamID;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function fingerprint() {
|
||||
const actor = Context.use();
|
||||
if ("fingerprint" in actor.properties) return actor.properties.fingerprint;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource.`,
|
||||
);
|
||||
}
|
||||
|
||||
export function use() {
|
||||
try {
|
||||
return Context.use();
|
||||
} catch {
|
||||
return { type: "public", properties: {} } as Public;
|
||||
}
|
||||
}
|
||||
|
||||
export function assert<T extends Info["type"]>(type: T) {
|
||||
const actor = use();
|
||||
if (actor.type !== type)
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`Actor is not "${type}"`,
|
||||
);
|
||||
return actor as Extract<Info, { type: T }>;
|
||||
}
|
||||
|
||||
export function provide<
|
||||
T extends Info["type"],
|
||||
Next extends (...args: any) => any,
|
||||
>(type: T, properties: Extract<Info, { type: T }>["properties"], fn: Next) {
|
||||
return Context.provide({ type, properties } as any, () =>
|
||||
Log.provide(
|
||||
{
|
||||
actor: type,
|
||||
...properties,
|
||||
},
|
||||
fn,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
44
packages/core/src/base-game/base-game.sql.ts
Normal file
44
packages/core/src/base-game/base-game.sql.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { z } from "zod";
|
||||
import { timestamps, utc } from "../drizzle/types";
|
||||
import { json, numeric, pgEnum, pgTable, text, unique, varchar } from "drizzle-orm/pg-core";
|
||||
|
||||
export const CompatibilityEnum = pgEnum("compatibility", ["high", "mid", "low", "unknown"])
|
||||
export const ControllerEnum = pgEnum("controller_support", ["full", "partial", "unknown"])
|
||||
|
||||
export const Size =
|
||||
z.object({
|
||||
downloadSize: z.number().positive().int(),
|
||||
sizeOnDisk: z.number().positive().int()
|
||||
});
|
||||
|
||||
export const Links = z.string().array();
|
||||
|
||||
export type Size = z.infer<typeof Size>;
|
||||
export type Links = z.infer<typeof Links>;
|
||||
|
||||
export const baseGamesTable = pgTable(
|
||||
"base_games",
|
||||
{
|
||||
...timestamps,
|
||||
id: varchar("id", { length: 255 })
|
||||
.primaryKey()
|
||||
.notNull(),
|
||||
links: json("links").$type<Links>(),
|
||||
slug: varchar("slug", { length: 255 })
|
||||
.notNull(),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
releaseDate: utc("release_date").notNull(),
|
||||
size: json("size").$type<Size>().notNull(),
|
||||
primaryGenre: text("primary_genre"),
|
||||
controllerSupport: ControllerEnum("controller_support").notNull(),
|
||||
compatibility: CompatibilityEnum("compatibility").notNull().default("unknown"),
|
||||
// Score ranges from 0.0 to 5.0
|
||||
score: numeric("score", { precision: 2, scale: 1 })
|
||||
.$type<number>()
|
||||
.notNull()
|
||||
},
|
||||
(table) => [
|
||||
unique("idx_base_games_slug").on(table.slug),
|
||||
]
|
||||
)
|
||||
162
packages/core/src/base-game/index.ts
Normal file
162
packages/core/src/base-game/index.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
import { z } from "zod";
|
||||
import { fn } from "../utils";
|
||||
import { Common } from "../common";
|
||||
import { Examples } from "../examples";
|
||||
import { createEvent } from "../event";
|
||||
import { eq, isNull, and } from "drizzle-orm";
|
||||
import { ImageTypeEnum } from "../images/images.sql";
|
||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
import { CompatibilityEnum, baseGamesTable, Size, ControllerEnum, Links } from "./base-game.sql";
|
||||
|
||||
export namespace BaseGame {
|
||||
export const Info = z.object({
|
||||
id: z.string().openapi({
|
||||
description: Common.IdDescription,
|
||||
example: Examples.BaseGame.id
|
||||
}),
|
||||
slug: z.string().openapi({
|
||||
description: "A URL-friendly unique identifier for the game, used in web addresses and API endpoints",
|
||||
example: Examples.BaseGame.slug
|
||||
}),
|
||||
name: z.string().openapi({
|
||||
description: "The official title of the game as listed on Steam",
|
||||
example: Examples.BaseGame.name
|
||||
}),
|
||||
size: Size.openapi({
|
||||
description: "Storage requirements in bytes: downloadSize represents the compressed download, and sizeOnDisk represents the installed size",
|
||||
example: Examples.BaseGame.size
|
||||
}),
|
||||
releaseDate: z.date().openapi({
|
||||
description: "The initial public release date of the game on Steam",
|
||||
example: Examples.BaseGame.releaseDate
|
||||
}),
|
||||
description: z.string().nullable().openapi({
|
||||
description: "A comprehensive overview of the game, including its features, storyline, and gameplay elements",
|
||||
example: Examples.BaseGame.description
|
||||
}),
|
||||
score: z.number().openapi({
|
||||
description: "The aggregate user review score on Steam, represented as a percentage of positive reviews",
|
||||
example: Examples.BaseGame.score
|
||||
}),
|
||||
links: Links
|
||||
.nullable()
|
||||
.openapi({
|
||||
description: "The social links of this game",
|
||||
example: Examples.BaseGame.links
|
||||
}),
|
||||
primaryGenre: z.string().nullable().openapi({
|
||||
description: "The main category or genre that best represents the game's content and gameplay style",
|
||||
example: Examples.BaseGame.primaryGenre
|
||||
}),
|
||||
controllerSupport: z.enum(ControllerEnum.enumValues).openapi({
|
||||
description: "Indicates the level of gamepad/controller compatibility: 'Full', 'Partial', or 'Unkown' for no support",
|
||||
example: Examples.BaseGame.controllerSupport
|
||||
}),
|
||||
compatibility: z.enum(CompatibilityEnum.enumValues).openapi({
|
||||
description: "Steam Deck/Proton compatibility rating indicating how well the game runs on Linux systems",
|
||||
example: Examples.BaseGame.compatibility
|
||||
}),
|
||||
}).openapi({
|
||||
ref: "BaseGame",
|
||||
description: "Detailed information about a game available in the Nestri library, including technical specifications and metadata",
|
||||
example: Examples.BaseGame
|
||||
})
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
|
||||
export const Events = {
|
||||
New: createEvent(
|
||||
"new_image.save",
|
||||
z.object({
|
||||
appID: Info.shape.id,
|
||||
url: z.string().url(),
|
||||
type: z.enum(ImageTypeEnum.enumValues)
|
||||
}),
|
||||
),
|
||||
NewBoxArt: createEvent(
|
||||
"new_box_art_image.save",
|
||||
z.object({
|
||||
appID: Info.shape.id,
|
||||
logoUrl: z.string().url(),
|
||||
backgroundUrl: z.string().url(),
|
||||
}),
|
||||
),
|
||||
NewHeroArt: createEvent(
|
||||
"new_hero_art_image.save",
|
||||
z.object({
|
||||
appID: Info.shape.id,
|
||||
backdropUrl: z.string().url(),
|
||||
screenshots: z.string().url().array(),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
export const create = fn(
|
||||
Info,
|
||||
(input) =>
|
||||
createTransaction(async (tx) => {
|
||||
const result = await tx
|
||||
.select()
|
||||
.from(baseGamesTable)
|
||||
.where(
|
||||
and(
|
||||
eq(baseGamesTable.id, input.id),
|
||||
isNull(baseGamesTable.timeDeleted)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.execute()
|
||||
.then(rows => rows.at(0))
|
||||
|
||||
if (result) return result.id
|
||||
|
||||
await tx
|
||||
.insert(baseGamesTable)
|
||||
.values(input)
|
||||
.onConflictDoUpdate({
|
||||
target: baseGamesTable.id,
|
||||
set: {
|
||||
timeDeleted: null
|
||||
}
|
||||
})
|
||||
|
||||
return input.id
|
||||
})
|
||||
)
|
||||
|
||||
export const fromID = fn(
|
||||
Info.shape.id,
|
||||
(id) =>
|
||||
useTransaction(async (tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(baseGamesTable)
|
||||
.where(
|
||||
and(
|
||||
eq(baseGamesTable.id, id),
|
||||
isNull(baseGamesTable.timeDeleted)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.then(rows => rows.map(serialize).at(0))
|
||||
)
|
||||
)
|
||||
|
||||
export function serialize(
|
||||
input: typeof baseGamesTable.$inferSelect,
|
||||
): z.infer<typeof Info> {
|
||||
return {
|
||||
id: input.id,
|
||||
name: input.name,
|
||||
slug: input.slug,
|
||||
size: input.size,
|
||||
links: input.links,
|
||||
score: input.score,
|
||||
description: input.description,
|
||||
releaseDate: input.releaseDate,
|
||||
primaryGenre: input.primaryGenre,
|
||||
compatibility: input.compatibility,
|
||||
controllerSupport: input.controllerSupport,
|
||||
};
|
||||
}
|
||||
}
|
||||
22
packages/core/src/categories/categories.sql.ts
Normal file
22
packages/core/src/categories/categories.sql.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { timestamps } from "../drizzle/types";
|
||||
import { index, pgEnum, pgTable, primaryKey, text, varchar } from "drizzle-orm/pg-core";
|
||||
|
||||
// Intentional grammatical error on category
|
||||
export const CategoryTypeEnum = pgEnum("category_type", ["tag", "genre", "publisher", "developer", "categorie", "franchise"])
|
||||
|
||||
export const categoriesTable = pgTable(
|
||||
"categories",
|
||||
{
|
||||
...timestamps,
|
||||
slug: varchar("slug", { length: 255 })
|
||||
.notNull(),
|
||||
type: CategoryTypeEnum("type").notNull(),
|
||||
name: text("name").notNull(),
|
||||
},
|
||||
(table) => [
|
||||
primaryKey({
|
||||
columns: [table.slug, table.type]
|
||||
}),
|
||||
index("idx_categories_type").on(table.type),
|
||||
]
|
||||
)
|
||||
128
packages/core/src/categories/index.ts
Normal file
128
packages/core/src/categories/index.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { z } from "zod";
|
||||
import { fn } from "../utils";
|
||||
import { Examples } from "../examples";
|
||||
import { eq, isNull, and } from "drizzle-orm";
|
||||
import { createSelectSchema } from "drizzle-zod";
|
||||
import { categoriesTable } from "./categories.sql";
|
||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
|
||||
export namespace Categories {
|
||||
|
||||
const Category = z.object({
|
||||
slug: z.string().openapi({
|
||||
description: "A URL-friendly unique identifier for the category",
|
||||
example: "action-adventure"
|
||||
}),
|
||||
name: z.string().openapi({
|
||||
description: "The human-readable display name of the category",
|
||||
example: "Action Adventure"
|
||||
})
|
||||
})
|
||||
|
||||
export const Info =
|
||||
z.object({
|
||||
publishers: Category.array().openapi({
|
||||
description: "List of companies or organizations responsible for publishing and distributing the game",
|
||||
example: Examples.Categories.publishers
|
||||
}),
|
||||
developers: Category.array().openapi({
|
||||
description: "List of studios, teams, or individuals who created and developed the game",
|
||||
example: Examples.Categories.developers
|
||||
}),
|
||||
tags: Category.array().openapi({
|
||||
description: "User-defined labels that describe specific features, themes, or characteristics of the game",
|
||||
example: Examples.Categories.tags
|
||||
}),
|
||||
genres: Category.array().openapi({
|
||||
description: "Primary classification categories that define the game's style and type of gameplay",
|
||||
example: Examples.Categories.genres
|
||||
}),
|
||||
categories: Category.array().openapi({
|
||||
description: "Primary classification categories that define the game's categorisation on Steam",
|
||||
example: Examples.Categories.genres
|
||||
}),
|
||||
franchises: Category.array().openapi({
|
||||
description: "The franchise this game belongs belongs to on Steam",
|
||||
example: Examples.Categories.genres
|
||||
}),
|
||||
|
||||
}).openapi({
|
||||
ref: "Categories",
|
||||
description: "A comprehensive categorization system for games, including publishing details, development credits, and content classification",
|
||||
example: Examples.Categories
|
||||
})
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
|
||||
export const InputInfo = createSelectSchema(categoriesTable)
|
||||
.omit({ timeCreated: true, timeDeleted: true, timeUpdated: true })
|
||||
|
||||
export const create = fn(
|
||||
InputInfo,
|
||||
(input) =>
|
||||
createTransaction(async (tx) => {
|
||||
const result = await tx
|
||||
.select()
|
||||
.from(categoriesTable)
|
||||
.where(
|
||||
and(
|
||||
eq(categoriesTable.slug, input.slug),
|
||||
eq(categoriesTable.type, input.type),
|
||||
isNull(categoriesTable.timeDeleted)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.execute()
|
||||
.then(rows => rows.at(0))
|
||||
|
||||
if (result) return result.slug
|
||||
|
||||
await tx
|
||||
.insert(categoriesTable)
|
||||
.values(input)
|
||||
.onConflictDoUpdate({
|
||||
target: [categoriesTable.slug, categoriesTable.type],
|
||||
set: { timeDeleted: null }
|
||||
})
|
||||
|
||||
return input.slug
|
||||
})
|
||||
)
|
||||
|
||||
export const get = fn(
|
||||
InputInfo.pick({ slug: true, type: true }),
|
||||
(input) =>
|
||||
useTransaction((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(categoriesTable)
|
||||
.where(
|
||||
and(
|
||||
eq(categoriesTable.slug, input.slug),
|
||||
eq(categoriesTable.type, input.type),
|
||||
isNull(categoriesTable.timeDeleted)
|
||||
)
|
||||
)
|
||||
.limit(1)
|
||||
.execute()
|
||||
.then(rows => serialize(rows))
|
||||
)
|
||||
)
|
||||
|
||||
export function serialize(
|
||||
input: typeof categoriesTable.$inferSelect[],
|
||||
): z.infer<typeof Info> {
|
||||
return input.reduce<Record<`${typeof categoriesTable.$inferSelect["type"]}s`, { slug: string; name: string }[]>>((acc, cat) => {
|
||||
const key = `${cat.type}s` as `${typeof cat.type}s`
|
||||
acc[key]!.push({ slug: cat.slug, name: cat.name })
|
||||
return acc
|
||||
}, {
|
||||
tags: [],
|
||||
genres: [],
|
||||
publishers: [],
|
||||
developers: [],
|
||||
categories: [],
|
||||
franchises: []
|
||||
})
|
||||
}
|
||||
}
|
||||
316
packages/core/src/client/index.ts
Normal file
316
packages/core/src/client/index.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import type {
|
||||
Shot,
|
||||
AppInfo,
|
||||
ImageInfo,
|
||||
ImageType,
|
||||
SteamAccount,
|
||||
GameTagsResponse,
|
||||
GameDetailsResponse,
|
||||
SteamAppDataResponse,
|
||||
SteamOwnedGamesResponse,
|
||||
SteamPlayerBansResponse,
|
||||
SteamFriendsListResponse,
|
||||
SteamPlayerSummaryResponse,
|
||||
SteamStoreResponse,
|
||||
} from "./types";
|
||||
import { z } from "zod";
|
||||
import { fn } from "../utils";
|
||||
import { Resource } from "sst";
|
||||
import { Steam } from "./steam";
|
||||
import { Utils } from "./utils";
|
||||
import { ImageTypeEnum } from "../images/images.sql";
|
||||
|
||||
export namespace Client {
|
||||
export const getUserLibrary = fn(
|
||||
z.string(),
|
||||
async (steamID) =>
|
||||
await Utils.fetchApi<SteamOwnedGamesResponse>(`https://api.steampowered.com/IPlayerService/GetOwnedGames/v0001/?key=${Resource.SteamApiKey.value}&steamid=${steamID}&include_appinfo=1&format=json&include_played_free_games=1&skip_unvetted_apps=0`)
|
||||
)
|
||||
|
||||
export const getFriendsList = fn(
|
||||
z.string(),
|
||||
async (steamID) =>
|
||||
await Utils.fetchApi<SteamFriendsListResponse>(`https://api.steampowered.com/ISteamUser/GetFriendList/v0001/?key=${Resource.SteamApiKey.value}&steamid=${steamID}&relationship=friend`)
|
||||
);
|
||||
|
||||
export const getUserInfo = fn(
|
||||
z.string().array(),
|
||||
async (steamIDs) => {
|
||||
const [userInfo, banInfo, profileInfo] = await Promise.all([
|
||||
Utils.fetchApi<SteamPlayerSummaryResponse>(`https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=${Resource.SteamApiKey.value}&steamids=${steamIDs.join(",")}`),
|
||||
Utils.fetchApi<SteamPlayerBansResponse>(`https://api.steampowered.com/ISteamUser/GetPlayerBans/v1/?key=${Resource.SteamApiKey.value}&steamids=${steamIDs.join(",")}`),
|
||||
Utils.fetchProfilesInfo(steamIDs)
|
||||
])
|
||||
|
||||
// Create a map of bans by steamID for fast lookup
|
||||
const bansBySteamID = new Map(
|
||||
banInfo.players.map((b) => [b.SteamId, b])
|
||||
);
|
||||
|
||||
// Map userInfo.players to your desired output using Promise.allSettled
|
||||
// to prevent one error from closing down the whole pipeline
|
||||
const steamAccounts = await Promise.allSettled(
|
||||
userInfo.response.players.map(async (player) => {
|
||||
const ban = bansBySteamID.get(player.steamid);
|
||||
const info = profileInfo.get(player.steamid);
|
||||
|
||||
if (!info) {
|
||||
throw new Error(`[userInfo] profile info missing for ${player.steamid}`)
|
||||
}
|
||||
|
||||
if ('error' in info) {
|
||||
throw new Error(`error handling profile info for: ${player.steamid}:${info.error}`)
|
||||
} else {
|
||||
return {
|
||||
id: player.steamid,
|
||||
name: player.personaname,
|
||||
realName: player.realname ?? null,
|
||||
steamMemberSince: new Date(player.timecreated * 1000),
|
||||
avatarHash: player.avatarhash,
|
||||
limitations: {
|
||||
isLimited: info.isLimited,
|
||||
privacyState: info.privacyState,
|
||||
isVacBanned: ban?.VACBanned ?? false,
|
||||
tradeBanState: ban?.EconomyBan ?? "none",
|
||||
visibilityState: player.communityvisibilitystate,
|
||||
},
|
||||
lastSyncedAt: new Date(),
|
||||
profileUrl: player.profileurl,
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
steamAccounts
|
||||
.filter(result => result.status === 'rejected')
|
||||
.forEach(result => console.warn('[userInfo] failed:', (result as PromiseRejectedResult).reason))
|
||||
|
||||
return steamAccounts.filter(result => result.status === "fulfilled").map(result => (result as PromiseFulfilledResult<SteamAccount>).value)
|
||||
})
|
||||
|
||||
export const getAppInfo = fn(
|
||||
z.string(),
|
||||
async (appid) => {
|
||||
try {
|
||||
const info = await Promise.all([
|
||||
Utils.fetchApi<SteamAppDataResponse>(`https://api.steamcmd.net/v1/info/${appid}`),
|
||||
Utils.fetchApi<SteamStoreResponse>(`https://api.steampowered.com/IStoreBrowseService/GetItems/v1/?key=${Resource.SteamApiKey.value}&input_json={"ids":[{"appid":"${appid}"}],"context":{"language":"english","country_code":"US","steam_realm":"1"},"data_request":{"include_assets":true,"include_release":true,"include_platforms":true,"include_all_purchase_options":true,"include_screenshots":true,"include_trailers":true,"include_ratings":true,"include_tag_count":"40","include_reviews":true,"include_basic_info":true,"include_supported_languages":true,"include_full_description":true,"include_included_items":true,"include_assets_without_overrides":true,"apply_user_filters":true,"include_links":true}}`),
|
||||
]);
|
||||
|
||||
const cmd = info[0].data[appid]
|
||||
const store = info[1].response.store_items[0]
|
||||
|
||||
if (!cmd) {
|
||||
throw new Error(`App data not found for appid: ${appid}`)
|
||||
}
|
||||
|
||||
if (!store || store.success !== 1) {
|
||||
throw new Error(`Could not get store information or appid: ${appid}`)
|
||||
}
|
||||
|
||||
const tags = store.tagids
|
||||
.map(id => Steam.tags[id.toString() as keyof typeof Steam.tags])
|
||||
.filter((name): name is string => typeof name === 'string')
|
||||
|
||||
const publishers = store.basic_info.publishers
|
||||
.map(i => i.name)
|
||||
|
||||
const developers = store.basic_info.developers
|
||||
.map(i => i.name)
|
||||
|
||||
const franchises = store.basic_info.franchises
|
||||
?.map(i => i.name)
|
||||
|
||||
const genres = cmd?.common.genres &&
|
||||
Object.keys(cmd?.common.genres)
|
||||
.map(id => Steam.genres[id.toString() as keyof typeof Steam.genres])
|
||||
.filter((name): name is string => typeof name === 'string')
|
||||
|
||||
const categories = [
|
||||
...(store.categories?.controller_categoryids?.map(i => Steam.categories[i.toString() as keyof typeof Steam.categories]) ?? []),
|
||||
...(store.categories?.supported_player_categoryids?.map(i => Steam.categories[i.toString() as keyof typeof Steam.categories]) ?? [])
|
||||
].filter((name): name is string => typeof name === 'string')
|
||||
|
||||
const assetUrls = Utils.getAssetUrls(cmd?.common.library_assets_full, appid, cmd?.common.header_image.english);
|
||||
|
||||
const screenshots = store.screenshots.all_ages_screenshots?.map(i => `https://shared.cloudflare.steamstatic.com/store_item_assets/${i.filename}`) ?? [];
|
||||
|
||||
const icon = `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${appid}/${cmd?.common.icon}.jpg`;
|
||||
|
||||
const data: AppInfo = {
|
||||
id: appid,
|
||||
name: cmd?.common.name.trim(),
|
||||
tags: Utils.createType(tags, "tag"),
|
||||
images: { screenshots, icon, ...assetUrls },
|
||||
size: Utils.getPublicDepotSizes(cmd?.depots!),
|
||||
slug: Utils.createSlug(cmd?.common.name.trim()),
|
||||
publishers: Utils.createType(publishers, "publisher"),
|
||||
developers: Utils.createType(developers, "developer"),
|
||||
categories: Utils.createType(categories, "categorie"),
|
||||
links: store.links ? store.links.map(i => i.url) : null,
|
||||
genres: genres ? Utils.createType(genres, "genre") : [],
|
||||
franchises: franchises ? Utils.createType(franchises, "franchise") : [],
|
||||
description: store.basic_info.short_description ? Utils.cleanDescription(store.basic_info.short_description) : null,
|
||||
controllerSupport: cmd?.common.controller_support ?? "unknown" as any,
|
||||
releaseDate: new Date(Number(cmd?.common.steam_release_date) * 1000),
|
||||
primaryGenre: !!cmd?.common.primary_genre && Steam.genres[cmd?.common.primary_genre as keyof typeof Steam.genres] ? Steam.genres[cmd?.common.primary_genre as keyof typeof Steam.genres] : null,
|
||||
compatibility: store?.platforms.steam_os_compat_category ? Utils.compatibilityType(store?.platforms.steam_os_compat_category.toString() as any).toLowerCase() : "unknown" as any,
|
||||
score: Utils.estimateRatingFromSummary(store.reviews.summary_filtered.review_count, store.reviews.summary_filtered.percent_positive)
|
||||
}
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.log(`Error handling: ${appid}`)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
export const getImageUrls = fn(
|
||||
z.string(),
|
||||
async (appid) => {
|
||||
const [appData, details] = await Promise.all([
|
||||
Utils.fetchApi<SteamAppDataResponse>(`https://api.steamcmd.net/v1/info/${appid}`),
|
||||
Utils.fetchApi<GameDetailsResponse>(
|
||||
`https://store.steampowered.com/apphover/${appid}?full=1&review_score_preference=1&pagev6=true&json=1`
|
||||
),
|
||||
]);
|
||||
|
||||
const game = appData.data[appid]?.common;
|
||||
if (!game) throw new Error('Game info missing');
|
||||
|
||||
// 2. Prepare URLs
|
||||
const screenshots = Utils.getScreenshotUrls(details.rgScreenshots || []);
|
||||
const assetUrls = Utils.getAssetUrls(game.library_assets_full, appid, game.header_image.english);
|
||||
const icon = `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${appid}/${game.icon}.jpg`;
|
||||
|
||||
return { screenshots, icon, ...assetUrls }
|
||||
}
|
||||
)
|
||||
|
||||
export const getImageInfo = fn(
|
||||
z.object({
|
||||
type: z.enum(ImageTypeEnum.enumValues),
|
||||
url: z.string()
|
||||
}),
|
||||
async (input) =>
|
||||
Utils.fetchBuffer(input.url)
|
||||
.then(buf => Utils.getImageMetadata(buf))
|
||||
.then(meta => ({ ...meta, position: 0, sourceUrl: input.url, type: input.type } as ImageInfo))
|
||||
)
|
||||
|
||||
export const createBoxArt = fn(
|
||||
z.object({
|
||||
backgroundUrl: z.string(),
|
||||
logoUrl: z.string(),
|
||||
}),
|
||||
async (input) =>
|
||||
Utils.createBoxArtBuffer(input.logoUrl, input.backgroundUrl)
|
||||
.then(buf => Utils.getImageMetadata(buf))
|
||||
.then(meta => ({ ...meta, position: 0, sourceUrl: null, type: 'boxArt' as const }) as ImageInfo)
|
||||
)
|
||||
|
||||
export const createHeroArt = fn(
|
||||
z.object({
|
||||
screenshots: z.string().array(),
|
||||
backdropUrl: z.string()
|
||||
}),
|
||||
async (input) => {
|
||||
// Download screenshot buffers in parallel
|
||||
const shots: Shot[] = await Promise.all(
|
||||
input.screenshots.map(async url => ({ url, buffer: await Utils.fetchBuffer(url) }))
|
||||
);
|
||||
|
||||
const baselineBuffer = await Utils.fetchBuffer(input.backdropUrl);
|
||||
|
||||
// 4. Score screenshots (or pick single)
|
||||
const scores =
|
||||
shots.length === 1
|
||||
? [{ url: shots[0].url, score: 0 }]
|
||||
: (await Utils.rankScreenshots(baselineBuffer, shots, {
|
||||
threshold: 0.08,
|
||||
}))
|
||||
|
||||
// Build url->rank map
|
||||
const rankMap = new Map<string, number>();
|
||||
scores.forEach((s, i) => rankMap.set(s.url, i));
|
||||
|
||||
// 5. Create tasks for all images
|
||||
const tasks: Array<Promise<ImageInfo>> = [];
|
||||
|
||||
// 5a. Screenshots and heroArt metadata (top 4)
|
||||
for (const { url, buffer } of shots) {
|
||||
const rank = rankMap.get(url);
|
||||
if (rank === undefined || rank >= 4) continue;
|
||||
const type: ImageType = rank === 0 ? 'heroArt' : 'screenshot';
|
||||
tasks.push(
|
||||
Utils.getImageMetadata(buffer).then(meta => ({ ...meta, sourceUrl: url, position: type == "screenshot" ? rank - 1 : rank, type } as ImageInfo))
|
||||
);
|
||||
}
|
||||
|
||||
const settled = await Promise.allSettled(tasks);
|
||||
|
||||
settled
|
||||
.filter(r => r.status === "rejected")
|
||||
.forEach(r => console.warn("[getHeroArt] failed:", (r as PromiseRejectedResult).reason));
|
||||
|
||||
// Await all and return
|
||||
return settled.filter(s => s.status === "fulfilled").map(r => (r as PromiseFulfilledResult<ImageInfo>).value)
|
||||
}
|
||||
)
|
||||
|
||||
/**
|
||||
* Verifies a Steam OpenID response by sending a request back to Steam
|
||||
* with mode=check_authentication
|
||||
*/
|
||||
export async function verifyOpenIDResponse(params: URLSearchParams): Promise<string | null> {
|
||||
try {
|
||||
// Create a new URLSearchParams with all the original parameters
|
||||
const verificationParams = new URLSearchParams();
|
||||
|
||||
// Copy all parameters from the original request
|
||||
for (const [key, value] of params.entries()) {
|
||||
verificationParams.append(key, value);
|
||||
}
|
||||
|
||||
// Change mode to check_authentication for verification
|
||||
verificationParams.set('openid.mode', 'check_authentication');
|
||||
|
||||
// Send verification request to Steam
|
||||
const verificationResponse = await fetch('https://steamcommunity.com/openid/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: verificationParams.toString()
|
||||
});
|
||||
|
||||
const responseText = await verificationResponse.text();
|
||||
|
||||
// Check if verification was successful
|
||||
if (!responseText.includes('is_valid:true')) {
|
||||
console.error('OpenID verification failed: Invalid response from Steam', responseText);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract steamID from the claimed_id
|
||||
const claimedId = params.get('openid.claimed_id');
|
||||
if (!claimedId) {
|
||||
console.error('OpenID verification failed: Missing claimed_id');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Extract the Steam ID from the claimed_id
|
||||
const steamID = claimedId.split('/').pop();
|
||||
if (!steamID || !/^\d+$/.test(steamID)) {
|
||||
console.error('OpenID verification failed: Invalid steamID format', steamID);
|
||||
return null;
|
||||
}
|
||||
|
||||
return steamID;
|
||||
} catch (error) {
|
||||
console.error('OpenID verification error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
544
packages/core/src/client/steam.ts
Normal file
544
packages/core/src/client/steam.ts
Normal file
@@ -0,0 +1,544 @@
|
||||
export namespace Steam {
|
||||
//Source: https://github.com/woctezuma/steam-api/blob/master/data/genres.json
|
||||
export const genres = {
|
||||
"1": "Action",
|
||||
"2": "Strategy",
|
||||
"3": "RPG",
|
||||
"4": "Casual",
|
||||
"9": "Racing",
|
||||
"18": "Sports",
|
||||
"23": "Indie",
|
||||
"25": "Adventure",
|
||||
"28": "Simulation",
|
||||
"29": "Massively Multiplayer",
|
||||
"37": "Free to Play",
|
||||
"50": "Accounting",
|
||||
"51": "Animation & Modeling",
|
||||
"52": "Audio Production",
|
||||
"53": "Design & Illustration",
|
||||
"54": "Education",
|
||||
"55": "Photo Editing",
|
||||
"56": "Software Training",
|
||||
"57": "Utilities",
|
||||
"58": "Video Production",
|
||||
"59": "Web Publishing",
|
||||
"60": "Game Development",
|
||||
"70": "Early Access",
|
||||
"71": "Sexual Content",
|
||||
"72": "Nudity",
|
||||
"73": "Violent",
|
||||
"74": "Gore",
|
||||
"80": "Movie",
|
||||
"81": "Documentary",
|
||||
"82": "Episodic",
|
||||
"83": "Short",
|
||||
"84": "Tutorial",
|
||||
"85": "360 Video"
|
||||
}
|
||||
|
||||
//Source: https://github.com/woctezuma/steam-api/blob/master/data/categories.json
|
||||
export const categories = {
|
||||
"1": "Multi-player",
|
||||
"2": "Single-player",
|
||||
"6": "Mods (require HL2)",
|
||||
"7": "Mods (require HL1)",
|
||||
"8": "Valve Anti-Cheat enabled",
|
||||
"9": "Co-op",
|
||||
"10": "Demos",
|
||||
"12": "HDR available",
|
||||
"13": "Captions available",
|
||||
"14": "Commentary available",
|
||||
"15": "Stats",
|
||||
"16": "Includes Source SDK",
|
||||
"17": "Includes level editor",
|
||||
"18": "Partial Controller Support",
|
||||
"19": "Mods",
|
||||
"20": "MMO",
|
||||
"21": "Downloadable Content",
|
||||
"22": "Steam Achievements",
|
||||
"23": "Steam Cloud",
|
||||
"24": "Shared/Split Screen",
|
||||
"25": "Steam Leaderboards",
|
||||
"27": "Cross-Platform Multiplayer",
|
||||
"28": "Full controller support",
|
||||
"29": "Steam Trading Cards",
|
||||
"30": "Steam Workshop",
|
||||
"31": "VR Support",
|
||||
"32": "Steam Turn Notifications",
|
||||
"33": "Native Steam Controller",
|
||||
"35": "In-App Purchases",
|
||||
"36": "Online PvP",
|
||||
"37": "Shared/Split Screen PvP",
|
||||
"38": "Online Co-op",
|
||||
"39": "Shared/Split Screen Co-op",
|
||||
"40": "SteamVR Collectibles",
|
||||
"41": "Remote Play on Phone",
|
||||
"42": "Remote Play on Tablet",
|
||||
"43": "Remote Play on TV",
|
||||
"44": "Remote Play Together",
|
||||
"45": "Cloud Gaming",
|
||||
"46": "Cloud Gaming (NVIDIA)",
|
||||
"47": "LAN PvP",
|
||||
"48": "LAN Co-op",
|
||||
"49": "PvP",
|
||||
"50": "Additional High-Quality Audio",
|
||||
"51": "Steam Workshop",
|
||||
"52": "Tracked Controller Support",
|
||||
"53": "VR Supported",
|
||||
"54": "VR Only"
|
||||
}
|
||||
|
||||
// Source: https://files.catbox.moe/96bty7.json
|
||||
export const tags = {
|
||||
"9": "Strategy",
|
||||
"19": "Action",
|
||||
"21": "Adventure",
|
||||
"84": "Design & Illustration",
|
||||
"87": "Utilities",
|
||||
"113": "Free to Play",
|
||||
"122": "RPG",
|
||||
"128": "Massively Multiplayer",
|
||||
"492": "Indie",
|
||||
"493": "Early Access",
|
||||
"597": "Casual",
|
||||
"599": "Simulation",
|
||||
"699": "Racing",
|
||||
"701": "Sports",
|
||||
"784": "Video Production",
|
||||
"809": "Photo Editing",
|
||||
"872": "Animation & Modeling",
|
||||
"1027": "Audio Production",
|
||||
"1036": "Education",
|
||||
"1038": "Web Publishing",
|
||||
"1445": "Software Training",
|
||||
"1616": "Trains",
|
||||
"1621": "Music",
|
||||
"1625": "Platformer",
|
||||
"1628": "Metroidvania",
|
||||
"1638": "Dog",
|
||||
"1643": "Building",
|
||||
"1644": "Driving",
|
||||
"1645": "Tower Defense",
|
||||
"1646": "Hack and Slash",
|
||||
"1647": "Western",
|
||||
"1649": "GameMaker",
|
||||
"1651": "Satire",
|
||||
"1654": "Relaxing",
|
||||
"1659": "Zombies",
|
||||
"1662": "Survival",
|
||||
"1663": "FPS",
|
||||
"1664": "Puzzle",
|
||||
"1665": "Match 3",
|
||||
"1666": "Card Game",
|
||||
"1667": "Horror",
|
||||
"1669": "Moddable",
|
||||
"1670": "4X",
|
||||
"1671": "Superhero",
|
||||
"1673": "Aliens",
|
||||
"1674": "Typing",
|
||||
"1676": "RTS",
|
||||
"1677": "Turn-Based",
|
||||
"1678": "War",
|
||||
"1680": "Heist",
|
||||
"1681": "Pirates",
|
||||
"1684": "Fantasy",
|
||||
"1685": "Co-op",
|
||||
"1687": "Stealth",
|
||||
"1688": "Ninja",
|
||||
"1693": "Classic",
|
||||
"1695": "Open World",
|
||||
"1697": "Third Person",
|
||||
"1698": "Point & Click",
|
||||
"1702": "Crafting",
|
||||
"1708": "Tactical",
|
||||
"1710": "Surreal",
|
||||
"1714": "Psychedelic",
|
||||
"1716": "Roguelike",
|
||||
"1717": "Hex Grid",
|
||||
"1718": "MOBA",
|
||||
"1719": "Comedy",
|
||||
"1720": "Dungeon Crawler",
|
||||
"1721": "Psychological Horror",
|
||||
"1723": "Action RTS",
|
||||
"1730": "Sokoban",
|
||||
"1732": "Voxel",
|
||||
"1733": "Unforgiving",
|
||||
"1734": "Fast-Paced",
|
||||
"1736": "LEGO",
|
||||
"1738": "Hidden Object",
|
||||
"1741": "Turn-Based Strategy",
|
||||
"1742": "Story Rich",
|
||||
"1743": "Fighting",
|
||||
"1746": "Basketball",
|
||||
"1751": "Comic Book",
|
||||
"1752": "Rhythm",
|
||||
"1753": "Skateboarding",
|
||||
"1754": "MMORPG",
|
||||
"1755": "Space",
|
||||
"1756": "Great Soundtrack",
|
||||
"1759": "Perma Death",
|
||||
"1770": "Board Game",
|
||||
"1773": "Arcade",
|
||||
"1774": "Shooter",
|
||||
"1775": "PvP",
|
||||
"1777": "Steampunk",
|
||||
"3796": "Based On A Novel",
|
||||
"3798": "Side Scroller",
|
||||
"3799": "Visual Novel",
|
||||
"3810": "Sandbox",
|
||||
"3813": "Real Time Tactics",
|
||||
"3814": "Third-Person Shooter",
|
||||
"3834": "Exploration",
|
||||
"3835": "Post-apocalyptic",
|
||||
"3839": "First-Person",
|
||||
"3841": "Local Co-Op",
|
||||
"3843": "Online Co-Op",
|
||||
"3854": "Lore-Rich",
|
||||
"3859": "Multiplayer",
|
||||
"3871": "2D",
|
||||
"3877": "Precision Platformer",
|
||||
"3878": "Competitive",
|
||||
"3916": "Old School",
|
||||
"3920": "Cooking",
|
||||
"3934": "Immersive",
|
||||
"3942": "Sci-fi",
|
||||
"3952": "Gothic",
|
||||
"3955": "Character Action Game",
|
||||
"3959": "Roguelite",
|
||||
"3964": "Pixel Graphics",
|
||||
"3965": "Epic",
|
||||
"3968": "Physics",
|
||||
"3978": "Survival Horror",
|
||||
"3987": "Historical",
|
||||
"3993": "Combat",
|
||||
"4004": "Retro",
|
||||
"4018": "Vampire",
|
||||
"4026": "Difficult",
|
||||
"4036": "Parkour",
|
||||
"4046": "Dragons",
|
||||
"4057": "Magic",
|
||||
"4064": "Thriller",
|
||||
"4085": "Anime",
|
||||
"4094": "Minimalist",
|
||||
"4102": "Combat Racing",
|
||||
"4106": "Action-Adventure",
|
||||
"4115": "Cyberpunk",
|
||||
"4136": "Funny",
|
||||
"4137": "Transhumanism",
|
||||
"4145": "Cinematic",
|
||||
"4150": "World War II",
|
||||
"4155": "Class-Based",
|
||||
"4158": "Beat 'em up",
|
||||
"4161": "Real-Time",
|
||||
"4166": "Atmospheric",
|
||||
"4168": "Military",
|
||||
"4172": "Medieval",
|
||||
"4175": "Realistic",
|
||||
"4182": "Singleplayer",
|
||||
"4184": "Chess",
|
||||
"4190": "Addictive",
|
||||
"4191": "3D",
|
||||
"4195": "Cartoony",
|
||||
"4202": "Trading",
|
||||
"4231": "Action RPG",
|
||||
"4234": "Short",
|
||||
"4236": "Loot",
|
||||
"4242": "Episodic",
|
||||
"4252": "Stylized",
|
||||
"4255": "Shoot 'Em Up",
|
||||
"4291": "Spaceships",
|
||||
"4295": "Futuristic",
|
||||
"4305": "Colorful",
|
||||
"4325": "Turn-Based Combat",
|
||||
"4328": "City Builder",
|
||||
"4342": "Dark",
|
||||
"4345": "Gore",
|
||||
"4364": "Grand Strategy",
|
||||
"4376": "Assassin",
|
||||
"4400": "Abstract",
|
||||
"4434": "JRPG",
|
||||
"4474": "CRPG",
|
||||
"4486": "Choose Your Own Adventure",
|
||||
"4508": "Co-op Campaign",
|
||||
"4520": "Farming",
|
||||
"4559": "Quick-Time Events",
|
||||
"4562": "Cartoon",
|
||||
"4598": "Alternate History",
|
||||
"4604": "Dark Fantasy",
|
||||
"4608": "Swordplay",
|
||||
"4637": "Top-Down Shooter",
|
||||
"4667": "Violent",
|
||||
"4684": "Wargame",
|
||||
"4695": "Economy",
|
||||
"4700": "Movie",
|
||||
"4711": "Replay Value",
|
||||
"4726": "Cute",
|
||||
"4736": "2D Fighter",
|
||||
"4747": "Character Customization",
|
||||
"4754": "Politics",
|
||||
"4758": "Twin Stick Shooter",
|
||||
"4777": "Spectacle fighter",
|
||||
"4791": "Top-Down",
|
||||
"4821": "Mechs",
|
||||
"4835": "6DOF",
|
||||
"4840": "4 Player Local",
|
||||
"4845": "Capitalism",
|
||||
"4853": "Political",
|
||||
"4878": "Parody",
|
||||
"4885": "Bullet Hell",
|
||||
"4947": "Romance",
|
||||
"4975": "2.5D",
|
||||
"4994": "Naval Combat",
|
||||
"5030": "Dystopian",
|
||||
"5055": "eSports",
|
||||
"5094": "Narration",
|
||||
"5125": "Procedural Generation",
|
||||
"5153": "Kickstarter",
|
||||
"5154": "Score Attack",
|
||||
"5160": "Dinosaurs",
|
||||
"5179": "Cold War",
|
||||
"5186": "Psychological",
|
||||
"5228": "Blood",
|
||||
"5230": "Sequel",
|
||||
"5300": "God Game",
|
||||
"5310": "Games Workshop",
|
||||
"5348": "Mod",
|
||||
"5350": "Family Friendly",
|
||||
"5363": "Destruction",
|
||||
"5372": "Conspiracy",
|
||||
"5379": "2D Platformer",
|
||||
"5382": "World War I",
|
||||
"5390": "Time Attack",
|
||||
"5395": "3D Platformer",
|
||||
"5407": "Benchmark",
|
||||
"5411": "Beautiful",
|
||||
"5432": "Programming",
|
||||
"5502": "Hacking",
|
||||
"5537": "Puzzle Platformer",
|
||||
"5547": "Arena Shooter",
|
||||
"5577": "RPGMaker",
|
||||
"5608": "Emotional",
|
||||
"5611": "Mature",
|
||||
"5613": "Detective",
|
||||
"5652": "Collectathon",
|
||||
"5673": "Modern",
|
||||
"5708": "Remake",
|
||||
"5711": "Team-Based",
|
||||
"5716": "Mystery",
|
||||
"5727": "Baseball",
|
||||
"5752": "Robots",
|
||||
"5765": "Gun Customization",
|
||||
"5794": "Science",
|
||||
"5796": "Bullet Time",
|
||||
"5851": "Isometric",
|
||||
"5900": "Walking Simulator",
|
||||
"5914": "Tennis",
|
||||
"5923": "Dark Humor",
|
||||
"5941": "Reboot",
|
||||
"5981": "Mining",
|
||||
"5984": "Drama",
|
||||
"6041": "Horses",
|
||||
"6052": "Noir",
|
||||
"6129": "Logic",
|
||||
"6214": "Birds",
|
||||
"6276": "Inventory Management",
|
||||
"6310": "Diplomacy",
|
||||
"6378": "Crime",
|
||||
"6426": "Choices Matter",
|
||||
"6506": "3D Fighter",
|
||||
"6621": "Pinball",
|
||||
"6625": "Time Manipulation",
|
||||
"6650": "Nudity",
|
||||
"6691": "1990's",
|
||||
"6702": "Mars",
|
||||
"6730": "PvE",
|
||||
"6815": "Hand-drawn",
|
||||
"6869": "Nonlinear",
|
||||
"6910": "Naval",
|
||||
"6915": "Martial Arts",
|
||||
"6948": "Rome",
|
||||
"6971": "Multiple Endings",
|
||||
"7038": "Golf",
|
||||
"7107": "Real-Time with Pause",
|
||||
"7108": "Party",
|
||||
"7113": "Crowdfunded",
|
||||
"7178": "Party Game",
|
||||
"7208": "Female Protagonist",
|
||||
"7250": "Linear",
|
||||
"7309": "Skiing",
|
||||
"7328": "Bowling",
|
||||
"7332": "Base Building",
|
||||
"7368": "Local Multiplayer",
|
||||
"7423": "Sniper",
|
||||
"7432": "Lovecraftian",
|
||||
"7478": "Illuminati",
|
||||
"7481": "Controller",
|
||||
"7556": "Dice",
|
||||
"7569": "Grid-Based Movement",
|
||||
"7622": "Offroad",
|
||||
"7702": "Narrative",
|
||||
"7743": "1980s",
|
||||
"7782": "Cult Classic",
|
||||
"7918": "Dwarf",
|
||||
"7926": "Artificial Intelligence",
|
||||
"7948": "Soundtrack",
|
||||
"8013": "Software",
|
||||
"8075": "TrackIR",
|
||||
"8093": "Minigames",
|
||||
"8122": "Level Editor",
|
||||
"8253": "Music-Based Procedural Generation",
|
||||
"8369": "Investigation",
|
||||
"8461": "Well-Written",
|
||||
"8666": "Runner",
|
||||
"8945": "Resource Management",
|
||||
"9130": "Hentai",
|
||||
"9157": "Underwater",
|
||||
"9204": "Immersive Sim",
|
||||
"9271": "Trading Card Game",
|
||||
"9541": "Demons",
|
||||
"9551": "Dating Sim",
|
||||
"9564": "Hunting",
|
||||
"9592": "Dynamic Narration",
|
||||
"9803": "Snow",
|
||||
"9994": "Experience",
|
||||
"10235": "Life Sim",
|
||||
"10383": "Transportation",
|
||||
"10397": "Memes",
|
||||
"10437": "Trivia",
|
||||
"10679": "Time Travel",
|
||||
"10695": "Party-Based RPG",
|
||||
"10808": "Supernatural",
|
||||
"10816": "Split Screen",
|
||||
"11014": "Interactive Fiction",
|
||||
"11095": "Boss Rush",
|
||||
"11104": "Vehicular Combat",
|
||||
"11123": "Mouse only",
|
||||
"11333": "Villain Protagonist",
|
||||
"11634": "Vikings",
|
||||
"12057": "Tutorial",
|
||||
"12095": "Sexual Content",
|
||||
"12190": "Boxing",
|
||||
"12286": "Warhammer 40K",
|
||||
"12472": "Management",
|
||||
"13070": "Solitaire",
|
||||
"13190": "America",
|
||||
"13276": "Tanks",
|
||||
"13382": "Archery",
|
||||
"13577": "Sailing",
|
||||
"13782": "Experimental",
|
||||
"13906": "Game Development",
|
||||
"14139": "Turn-Based Tactics",
|
||||
"14153": "Dungeons & Dragons",
|
||||
"14720": "Nostalgia",
|
||||
"14906": "Intentionally Awkward Controls",
|
||||
"15045": "Flight",
|
||||
"15172": "Conversation",
|
||||
"15277": "Philosophical",
|
||||
"15339": "Documentary",
|
||||
"15564": "Fishing",
|
||||
"15868": "Motocross",
|
||||
"15954": "Silent Protagonist",
|
||||
"16094": "Mythology",
|
||||
"16250": "Gambling",
|
||||
"16598": "Space Sim",
|
||||
"16689": "Time Management",
|
||||
"17015": "Werewolves",
|
||||
"17305": "Strategy RPG",
|
||||
"17337": "Lemmings",
|
||||
"17389": "Tabletop",
|
||||
"17770": "Asynchronous Multiplayer",
|
||||
"17894": "Cats",
|
||||
"17927": "Pool",
|
||||
"18594": "FMV",
|
||||
"19568": "Cycling",
|
||||
"19780": "Submarine",
|
||||
"19995": "Dark Comedy",
|
||||
"21006": "Underground",
|
||||
"21491": "Demo Available",
|
||||
"21725": "Tactical RPG",
|
||||
"21978": "VR",
|
||||
"22602": "Agriculture",
|
||||
"22955": "Mini Golf",
|
||||
"24003": "Word Game",
|
||||
"24904": "NSFW",
|
||||
"25085": "Touch-Friendly",
|
||||
"26921": "Political Sim",
|
||||
"27758": "Voice Control",
|
||||
"28444": "Snowboarding",
|
||||
"29363": "3D Vision",
|
||||
"29482": "Souls-like",
|
||||
"29855": "Ambient",
|
||||
"30358": "Nature",
|
||||
"30927": "Fox",
|
||||
"31275": "Text-Based",
|
||||
"31579": "Otome",
|
||||
"32322": "Deckbuilding",
|
||||
"33572": "Mahjong",
|
||||
"35079": "Job Simulator",
|
||||
"42089": "Jump Scare",
|
||||
"42329": "Coding",
|
||||
"42804": "Action Roguelike",
|
||||
"44868": "LGBTQ+",
|
||||
"47827": "Wrestling",
|
||||
"49213": "Rugby",
|
||||
"51306": "Foreign",
|
||||
"56690": "On-Rails Shooter",
|
||||
"61357": "Electronic Music",
|
||||
"65443": "Adult Content",
|
||||
"71389": "Spelling",
|
||||
"87918": "Farming Sim",
|
||||
"91114": "Shop Keeper",
|
||||
"92092": "Jet",
|
||||
"96359": "Skating",
|
||||
"97376": "Cozy",
|
||||
"102530": "Elf",
|
||||
"117648": "8-bit Music",
|
||||
"123332": "Bikes",
|
||||
"129761": "ATV",
|
||||
"143739": "Electronic",
|
||||
"150626": "Gaming",
|
||||
"158638": "Cricket",
|
||||
"176981": "Battle Royale",
|
||||
"180368": "Faith",
|
||||
"189941": "Instrumental Music",
|
||||
"198631": "Mystery Dungeon",
|
||||
"198913": "Motorbike",
|
||||
"220585": "Colony Sim",
|
||||
"233824": "Feature Film",
|
||||
"252854": "BMX",
|
||||
"255534": "Automation",
|
||||
"323922": "Musou",
|
||||
"324176": "Hockey",
|
||||
"337964": "Rock Music",
|
||||
"348922": "Steam Machine",
|
||||
"353880": "Looter Shooter",
|
||||
"363767": "Snooker",
|
||||
"379975": "Clicker",
|
||||
"454187": "Traditional Roguelike",
|
||||
"552282": "Wholesome",
|
||||
"603297": "Hardware",
|
||||
"615955": "Idler",
|
||||
"620519": "Hero Shooter",
|
||||
"745697": "Social Deduction",
|
||||
"769306": "Escape Room",
|
||||
"776177": "360 Video",
|
||||
"791774": "Card Battler",
|
||||
"847164": "Volleyball",
|
||||
"856791": "Asymmetric VR",
|
||||
"916648": "Creature Collector",
|
||||
"922563": "Roguevania",
|
||||
"1003823": "Profile Features Limited",
|
||||
"1023537": "Boomer Shooter",
|
||||
"1084988": "Auto Battler",
|
||||
"1091588": "Roguelike Deckbuilder",
|
||||
"1100686": "Outbreak Sim",
|
||||
"1100687": "Automobile Sim",
|
||||
"1100688": "Medical Sim",
|
||||
"1100689": "Open World Survival Craft",
|
||||
"1199779": "Extraction Shooter",
|
||||
"1220528": "Hobby Sim",
|
||||
"1254546": "Football (Soccer)",
|
||||
"1254552": "Football (American)",
|
||||
"1368160": "AI Content Disclosed",
|
||||
}
|
||||
}
|
||||
600
packages/core/src/client/types.ts
Normal file
600
packages/core/src/client/types.ts
Normal file
@@ -0,0 +1,600 @@
|
||||
export interface SteamApp {
|
||||
/** Steam application ID */
|
||||
appid: number;
|
||||
|
||||
/** Array of Steam IDs that own this app */
|
||||
owner_steamids: string[];
|
||||
|
||||
/** Name of the game/application */
|
||||
name: string;
|
||||
|
||||
/** Filename of the game's capsule image */
|
||||
capsule_filename: string;
|
||||
|
||||
/** Hash value for the game's icon */
|
||||
img_icon_hash: string;
|
||||
|
||||
/** Reason code for exclusion (0 indicates no exclusion) */
|
||||
exclude_reason: number;
|
||||
|
||||
/** Unix timestamp when the app was acquired */
|
||||
rt_time_acquired: number;
|
||||
|
||||
/** Unix timestamp when the app was last played */
|
||||
rt_last_played: number;
|
||||
|
||||
/** Total playtime in seconds */
|
||||
rt_playtime: number;
|
||||
|
||||
/** Type identifier for the app (1 = game) */
|
||||
app_type: number;
|
||||
|
||||
/** Array of content descriptor IDs */
|
||||
content_descriptors?: number[];
|
||||
}
|
||||
|
||||
export interface SteamApiResponse {
|
||||
response: {
|
||||
apps: SteamApp[];
|
||||
owner_steamid: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SteamAppDataResponse {
|
||||
data: Record<string, SteamAppEntry>;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface SteamAppEntry {
|
||||
_change_number: number;
|
||||
_missing_token: boolean;
|
||||
_sha: string;
|
||||
_size: number;
|
||||
appid: string;
|
||||
common: CommonData;
|
||||
config: AppConfig;
|
||||
depots: AppDepots;
|
||||
extended: AppExtended;
|
||||
ufs: UFSData;
|
||||
}
|
||||
|
||||
export interface CommonData {
|
||||
associations: Record<string, { name: string; type: string }>;
|
||||
category: Record<string, string>;
|
||||
clienticon: string;
|
||||
clienttga: string;
|
||||
community_hub_visible: string;
|
||||
community_visible_stats: string;
|
||||
content_descriptors: Record<string, string>;
|
||||
controller_support?: string;
|
||||
controllertagwizard: string;
|
||||
gameid: string;
|
||||
genres: Record<string, string>;
|
||||
header_image: Record<string, string>;
|
||||
icon: string;
|
||||
languages: Record<string, string>;
|
||||
library_assets: LibraryAssets;
|
||||
library_assets_full: LibraryAssetsFull;
|
||||
metacritic_fullurl: string;
|
||||
metacritic_name: string;
|
||||
metacritic_score: string;
|
||||
name: string;
|
||||
name_localized: Partial<Record<LanguageCode, string>>;
|
||||
osarch: string;
|
||||
osextended: string;
|
||||
oslist: string;
|
||||
primary_genre: string;
|
||||
releasestate: string;
|
||||
review_percentage: string;
|
||||
review_score: string;
|
||||
small_capsule: Record<string, string>;
|
||||
steam_deck_compatibility: SteamDeckCompatibility;
|
||||
steam_release_date: string;
|
||||
store_asset_mtime: string;
|
||||
store_tags: Record<string, string>;
|
||||
supported_languages: Record<
|
||||
string,
|
||||
{
|
||||
full_audio?: string;
|
||||
subtitles?: string;
|
||||
supported?: string;
|
||||
}
|
||||
>;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface LibraryAssets {
|
||||
library_capsule: string;
|
||||
library_header: string;
|
||||
library_hero: string;
|
||||
library_logo: string;
|
||||
logo_position: LogoPosition;
|
||||
}
|
||||
|
||||
export interface LogoPosition {
|
||||
height_pct: string;
|
||||
pinned_position: string;
|
||||
width_pct: string;
|
||||
}
|
||||
|
||||
export interface LibraryAssetsFull {
|
||||
library_capsule: ImageSet;
|
||||
library_header: ImageSet;
|
||||
library_hero: ImageSet;
|
||||
library_logo: ImageSet & { logo_position: LogoPosition };
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export interface ImageSet {
|
||||
image: Record<string, string>;
|
||||
image2x?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface SteamDeckCompatibility {
|
||||
category: string;
|
||||
configuration: Record<string, string>;
|
||||
test_timestamp: string;
|
||||
tested_build_id: string;
|
||||
tests: Record<string, { display: string; token: string }>;
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
installdir: string;
|
||||
launch: Record<
|
||||
string,
|
||||
{
|
||||
executable: string;
|
||||
type: string;
|
||||
arguments?: string;
|
||||
description?: string;
|
||||
description_loc?: Record<string, string>;
|
||||
config?: {
|
||||
betakey: string;
|
||||
};
|
||||
}
|
||||
>;
|
||||
steamcontrollertemplateindex: string;
|
||||
steamdecktouchscreen: string;
|
||||
}
|
||||
|
||||
export interface AppDepots {
|
||||
branches: AppDepotBranches;
|
||||
privatebranches: Record<string, AppDepotBranches>;
|
||||
[depotId: string]: DepotEntry
|
||||
| AppDepotBranches
|
||||
| Record<string, AppDepotBranches>;
|
||||
}
|
||||
|
||||
|
||||
export interface DepotEntry {
|
||||
manifests: {
|
||||
public: {
|
||||
download: string;
|
||||
gid: string;
|
||||
size: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface AppDepotBranches {
|
||||
[branchName: string]: {
|
||||
buildid: string;
|
||||
timeupdated: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AppExtended {
|
||||
additional_dependencies: Array<{
|
||||
dest_os: string;
|
||||
h264: string;
|
||||
src_os: string;
|
||||
}>;
|
||||
developer: string;
|
||||
dlcavailableonstore: string;
|
||||
homepage: string;
|
||||
listofdlc: string;
|
||||
publisher: string;
|
||||
}
|
||||
|
||||
export interface UFSData {
|
||||
maxnumfiles: string;
|
||||
quota: string;
|
||||
savefiles: Array<{
|
||||
path: string;
|
||||
pattern: string;
|
||||
recursive: string;
|
||||
root: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export type LanguageCode =
|
||||
| "english"
|
||||
| "french"
|
||||
| "german"
|
||||
| "italian"
|
||||
| "japanese"
|
||||
| "koreana"
|
||||
| "polish"
|
||||
| "russian"
|
||||
| "schinese"
|
||||
| "tchinese"
|
||||
| "brazilian"
|
||||
| "spanish";
|
||||
|
||||
export interface Screenshot {
|
||||
appid: number;
|
||||
id: number;
|
||||
filename: string;
|
||||
all_ages: string;
|
||||
normalized_name: string;
|
||||
}
|
||||
|
||||
export interface Category {
|
||||
strDisplayName: string;
|
||||
}
|
||||
|
||||
export interface ReviewSummary {
|
||||
strReviewSummary: string;
|
||||
cReviews: number;
|
||||
cRecommendationsPositive: number;
|
||||
cRecommendationsNegative: number;
|
||||
nReviewScore: number;
|
||||
}
|
||||
|
||||
export interface GameDetailsResponse {
|
||||
strReleaseDate: string;
|
||||
strDescription: string;
|
||||
rgScreenshots: Screenshot[];
|
||||
rgCategories: Category[];
|
||||
strGenres?: string;
|
||||
strFullDescription: string;
|
||||
strMicroTrailerURL: string;
|
||||
ReviewSummary: ReviewSummary;
|
||||
}
|
||||
|
||||
// Define the TypeScript interfaces
|
||||
export interface Tag {
|
||||
tagid: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface TagWithSlug {
|
||||
name: string;
|
||||
slug: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface StoreTags {
|
||||
[key: string]: string; // Index signature for numeric string keys to tag ID strings
|
||||
}
|
||||
|
||||
|
||||
export interface GameTagsResponse {
|
||||
tags: Tag[];
|
||||
success: number;
|
||||
rwgrsn: number;
|
||||
}
|
||||
|
||||
export type GenreType = {
|
||||
type: 'genre';
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
export interface AppInfo {
|
||||
name: string;
|
||||
slug: string;
|
||||
images: {
|
||||
logo: string;
|
||||
backdrop: string;
|
||||
poster: string;
|
||||
banner: string;
|
||||
screenshots: string[];
|
||||
icon: string;
|
||||
}
|
||||
links: string[] | null;
|
||||
score: number;
|
||||
id: string;
|
||||
releaseDate: Date;
|
||||
description: string | null;
|
||||
compatibility: "low" | "mid" | "high" | "unknown";
|
||||
controllerSupport: "partial" | "full" | "unknown";
|
||||
primaryGenre: string | null;
|
||||
size: { downloadSize: number; sizeOnDisk: number };
|
||||
tags: Array<{ name: string; slug: string; type: "tag" }>;
|
||||
genres: Array<{ type: "genre"; name: string; slug: string }>;
|
||||
categories: Array<{ name: string; slug: string; type: "categorie" }>;
|
||||
franchises: Array<{ name: string; slug: string; type: "franchise" }>;
|
||||
developers: Array<{ name: string; slug: string; type: "developer" }>;
|
||||
publishers: Array<{ name: string; slug: string; type: "publisher" }>;
|
||||
}
|
||||
|
||||
export type ImageType =
|
||||
| 'screenshot'
|
||||
| 'boxArt'
|
||||
| 'banner'
|
||||
| 'backdrop'
|
||||
| 'icon'
|
||||
| 'logo'
|
||||
| 'poster'
|
||||
| 'heroArt';
|
||||
|
||||
export interface ImageInfo {
|
||||
type: ImageType;
|
||||
position: number;
|
||||
hash: string;
|
||||
sourceUrl: string | null;
|
||||
format?: string;
|
||||
averageColor: { hex: string; isDark: boolean };
|
||||
dimensions: { width: number; height: number };
|
||||
fileSize: number;
|
||||
buffer: Buffer;
|
||||
}
|
||||
|
||||
export interface CompareOpts {
|
||||
/** Pixelmatch color threshold (0–1). Default: 0.1 */
|
||||
threshold?: number;
|
||||
/** If true, return an image buffer of the diff map. Default: false */
|
||||
diffOutput?: boolean;
|
||||
}
|
||||
|
||||
export interface CompareResult {
|
||||
diffRatio: number;
|
||||
/** Present only if `diffOutput: true` */
|
||||
diffBuffer?: Buffer;
|
||||
}
|
||||
|
||||
export interface Shot {
|
||||
url: string;
|
||||
buffer: Buffer;
|
||||
}
|
||||
|
||||
export interface RankedShot {
|
||||
url: string;
|
||||
score: number;
|
||||
}
|
||||
|
||||
export interface SteamPlayerSummaryResponse {
|
||||
response: {
|
||||
players: SteamPlayerSummary[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SteamPlayerSummary {
|
||||
steamid: string;
|
||||
communityvisibilitystate: number;
|
||||
profilestate?: number;
|
||||
personaname: string;
|
||||
profileurl: string;
|
||||
avatar: string;
|
||||
avatarmedium: string;
|
||||
avatarfull: string;
|
||||
avatarhash: string;
|
||||
lastlogoff?: number;
|
||||
personastate: number;
|
||||
realname?: string;
|
||||
primaryclanid?: string;
|
||||
timecreated: number;
|
||||
personastateflags?: number;
|
||||
loccountrycode?: string;
|
||||
}
|
||||
|
||||
export interface SteamPlayerBansResponse {
|
||||
players: SteamPlayerBan[];
|
||||
}
|
||||
|
||||
export interface SteamPlayerBan {
|
||||
SteamId: string;
|
||||
CommunityBanned: boolean;
|
||||
VACBanned: boolean;
|
||||
NumberOfVACBans: number;
|
||||
DaysSinceLastBan: number;
|
||||
NumberOfGameBans: number;
|
||||
EconomyBan: 'none' | 'probation' | 'banned'; // Enum based on known possible values
|
||||
}
|
||||
|
||||
export type SteamAccount = {
|
||||
id: string;
|
||||
name: string;
|
||||
realName: string | null;
|
||||
steamMemberSince: Date;
|
||||
avatarHash: string;
|
||||
limitations: {
|
||||
isLimited: boolean;
|
||||
tradeBanState: 'none' | 'probation' | 'banned';
|
||||
isVacBanned: boolean;
|
||||
visibilityState: number;
|
||||
privacyState: 'public' | 'private' | 'friendsonly';
|
||||
};
|
||||
profileUrl: string;
|
||||
lastSyncedAt: Date;
|
||||
};
|
||||
|
||||
export interface SteamFriendsListResponse {
|
||||
friendslist: {
|
||||
friends: SteamFriend[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SteamFriend {
|
||||
steamid: string;
|
||||
relationship: 'friend'; // could expand this if Steam ever adds more types
|
||||
friend_since: number; // Unix timestamp (seconds)
|
||||
}
|
||||
|
||||
export interface SteamOwnedGamesResponse {
|
||||
response: {
|
||||
game_count: number;
|
||||
games: SteamOwnedGame[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SteamOwnedGame {
|
||||
appid: number;
|
||||
name: string;
|
||||
playtime_forever: number;
|
||||
img_icon_url: string;
|
||||
|
||||
playtime_windows_forever?: number;
|
||||
playtime_mac_forever?: number;
|
||||
playtime_linux_forever?: number;
|
||||
playtime_deck_forever?: number;
|
||||
|
||||
rtime_last_played?: number; // Unix timestamp
|
||||
content_descriptorids?: number[];
|
||||
playtime_disconnected?: number;
|
||||
has_community_visible_stats?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The shape of the parsed Steam profile information.
|
||||
*/
|
||||
export interface ProfileInfo {
|
||||
steamID64: string;
|
||||
isLimited: boolean;
|
||||
privacyState: 'public' | 'private' | 'friendsonly' | string;
|
||||
visibility: string;
|
||||
}
|
||||
|
||||
export interface SteamStoreResponse {
|
||||
response: {
|
||||
store_items: SteamStoreItem[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SteamStoreItem {
|
||||
item_type: number;
|
||||
id: number;
|
||||
success: number;
|
||||
visible: boolean;
|
||||
name: string;
|
||||
store_url_path: string;
|
||||
appid: number;
|
||||
type: number;
|
||||
tagids: number[];
|
||||
categories: {
|
||||
supported_player_categoryids?: number[];
|
||||
feature_categoryids?: number[];
|
||||
controller_categoryids?: number[];
|
||||
};
|
||||
reviews: {
|
||||
summary_filtered: {
|
||||
review_count: number;
|
||||
percent_positive: number;
|
||||
review_score: number;
|
||||
review_score_label: string;
|
||||
};
|
||||
};
|
||||
basic_info: {
|
||||
short_description?: string;
|
||||
publishers: SteamCreator[];
|
||||
developers: SteamCreator[];
|
||||
franchises?: SteamCreator[];
|
||||
};
|
||||
tags: {
|
||||
tagid: number;
|
||||
weight: number;
|
||||
}[];
|
||||
assets: SteamAssets;
|
||||
assets_without_overrides: SteamAssets;
|
||||
release: {
|
||||
steam_release_date: number;
|
||||
};
|
||||
platforms: {
|
||||
windows: boolean;
|
||||
mac: boolean;
|
||||
steamos_linux: boolean;
|
||||
vr_support: Record<string, never>;
|
||||
steam_deck_compat_category?: number;
|
||||
steam_os_compat_category?: number;
|
||||
};
|
||||
best_purchase_option: PurchaseOption;
|
||||
purchase_options: PurchaseOption[];
|
||||
screenshots: {
|
||||
all_ages_screenshots: {
|
||||
filename: string;
|
||||
ordinal: number;
|
||||
}[];
|
||||
};
|
||||
trailers: {
|
||||
highlights: Trailer[];
|
||||
};
|
||||
supported_languages: SupportedLanguage[];
|
||||
full_description: string;
|
||||
links?: {
|
||||
link_type: number;
|
||||
url: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface SteamCreator {
|
||||
name: string;
|
||||
creator_clan_account_id: number;
|
||||
}
|
||||
|
||||
export interface SteamAssets {
|
||||
asset_url_format: string;
|
||||
main_capsule: string;
|
||||
small_capsule: string;
|
||||
header: string;
|
||||
page_background: string;
|
||||
hero_capsule: string;
|
||||
hero_capsule_2x: string;
|
||||
library_capsule: string;
|
||||
library_capsule_2x: string;
|
||||
library_hero: string;
|
||||
library_hero_2x: string;
|
||||
community_icon: string;
|
||||
page_background_path: string;
|
||||
raw_page_background: string;
|
||||
}
|
||||
|
||||
export interface PurchaseOption {
|
||||
packageid?: number;
|
||||
bundleid?: number;
|
||||
purchase_option_name: string;
|
||||
final_price_in_cents: string;
|
||||
original_price_in_cents: string;
|
||||
formatted_final_price: string;
|
||||
formatted_original_price: string;
|
||||
discount_pct: number;
|
||||
active_discounts: ActiveDiscount[];
|
||||
user_can_purchase_as_gift: boolean;
|
||||
hide_discount_pct_for_compliance: boolean;
|
||||
included_game_count: number;
|
||||
bundle_discount_pct?: number;
|
||||
price_before_bundle_discount?: string;
|
||||
formatted_price_before_bundle_discount?: string;
|
||||
}
|
||||
|
||||
export interface ActiveDiscount {
|
||||
discount_amount: string;
|
||||
discount_description: string;
|
||||
discount_end_date: number;
|
||||
}
|
||||
|
||||
export interface Trailer {
|
||||
trailer_name: string;
|
||||
trailer_url_format: string;
|
||||
trailer_category: number;
|
||||
trailer_480p: TrailerFile[];
|
||||
trailer_max: TrailerFile[];
|
||||
microtrailer: TrailerFile[];
|
||||
screenshot_medium: string;
|
||||
screenshot_full: string;
|
||||
trailer_base_id: number;
|
||||
all_ages: boolean;
|
||||
}
|
||||
|
||||
export interface TrailerFile {
|
||||
filename: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export interface SupportedLanguage {
|
||||
elanguage: number;
|
||||
eadditionallanguage: number;
|
||||
supported: boolean;
|
||||
full_audio: boolean;
|
||||
subtitles: boolean;
|
||||
}
|
||||
524
packages/core/src/client/utils.ts
Normal file
524
packages/core/src/client/utils.ts
Normal file
@@ -0,0 +1,524 @@
|
||||
import type {
|
||||
Tag,
|
||||
StoreTags,
|
||||
AppDepots,
|
||||
GenreType,
|
||||
LibraryAssetsFull,
|
||||
DepotEntry,
|
||||
CompareOpts,
|
||||
CompareResult,
|
||||
RankedShot,
|
||||
Shot,
|
||||
ProfileInfo,
|
||||
} from "./types";
|
||||
import crypto from 'crypto';
|
||||
import pLimit from 'p-limit';
|
||||
import { PNG } from 'pngjs';
|
||||
import pixelmatch from 'pixelmatch';
|
||||
import { LRUCache } from 'lru-cache';
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import { Agent as HttpAgent } from 'http';
|
||||
import { Agent as HttpsAgent } from 'https';
|
||||
import { parseStringPromise } from "xml2js";
|
||||
import sharp, { type Metadata } from 'sharp';
|
||||
import AbortController from 'abort-controller';
|
||||
import fetch, { RequestInit } from 'node-fetch';
|
||||
import { FastAverageColor } from 'fast-average-color';
|
||||
|
||||
const fac = new FastAverageColor()
|
||||
// --- Configuration ---
|
||||
const httpAgent = new HttpAgent({ keepAlive: true, maxSockets: 50 });
|
||||
const httpsAgent = new HttpsAgent({ keepAlive: true, maxSockets: 50 });
|
||||
const downloadCache = new LRUCache<string, Buffer>({
|
||||
max: 100,
|
||||
ttl: 1000 * 60 * 30, // 30-minute expiry
|
||||
allowStale: false,
|
||||
});
|
||||
const downloadLimit = pLimit(10); // max concurrent downloads
|
||||
const compareCache = new LRUCache<string, CompareResult>({
|
||||
max: 50,
|
||||
ttl: 1000 * 60 * 10, // 10-minute expiry
|
||||
});
|
||||
|
||||
export namespace Utils {
|
||||
export async function fetchBuffer(url: string, retries = 3): Promise<Buffer> {
|
||||
if (downloadCache.has(url)) {
|
||||
return downloadCache.get(url)!;
|
||||
}
|
||||
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (let attempt = 0; attempt < retries; attempt++) {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), 15_000);
|
||||
const res = await fetch(url, {
|
||||
signal: controller.signal,
|
||||
agent: (_parsed) => _parsed.protocol === 'http:' ? httpAgent : httpsAgent
|
||||
} as RequestInit);
|
||||
clearTimeout(id);
|
||||
if (!res.ok) throw new Error(`Failed to fetch ${url}: ${res.status}`);
|
||||
const buf = Buffer.from(await res.arrayBuffer());
|
||||
downloadCache.set(url, buf);
|
||||
return buf;
|
||||
} catch (error: any) {
|
||||
lastError = error as Error;
|
||||
console.warn(`Attempt ${attempt + 1} failed for ${url}: ${error.message}`);
|
||||
if (attempt < retries - 1) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error(`Failed to fetch ${url} after ${retries} attempts`);
|
||||
}
|
||||
|
||||
export async function getImageMetadata(buffer: Buffer) {
|
||||
const hash = crypto.createHash('sha256').update(buffer).digest('hex');
|
||||
const { width, height, format, size: fileSize } = await sharp(buffer).metadata();
|
||||
if (!width || !height) throw new Error('Invalid dimensions');
|
||||
|
||||
const slice = await sharp(buffer)
|
||||
.resize({ width: Math.min(width, 256) }) // cheap shrink
|
||||
.ensureAlpha()
|
||||
.raw()
|
||||
.toBuffer();
|
||||
|
||||
const pixelArray = new Uint8Array(slice.buffer);
|
||||
const { hex, isDark } = fac.prepareResult(fac.getColorFromArray4(pixelArray, { mode: "precision" }));
|
||||
|
||||
return { hash, format, averageColor: { hex, isDark }, dimensions: { width, height }, fileSize, buffer };
|
||||
}
|
||||
|
||||
// --- Optimized Box Art creation ---
|
||||
export async function createBoxArtBuffer(
|
||||
logoUrl: string,
|
||||
backgroundUrl: string,
|
||||
logoPercent = 0.9
|
||||
): Promise<Buffer> {
|
||||
const [bgBuf, logoBuf] = await Promise.all([
|
||||
downloadLimit(() =>
|
||||
fetchBuffer(backgroundUrl)
|
||||
.catch(error => {
|
||||
console.error(`Failed to download hero image from ${backgroundUrl}:`, error);
|
||||
throw new Error(`Failed to create box art: hero image unavailable`);
|
||||
}),
|
||||
),
|
||||
downloadLimit(() => fetchBuffer(logoUrl)
|
||||
.catch(error => {
|
||||
console.error(`Failed to download logo image from ${logoUrl}:`, error);
|
||||
throw new Error(`Failed to create box art: logo image unavailable`);
|
||||
}),
|
||||
),
|
||||
]);
|
||||
|
||||
const bgImage = sharp(bgBuf);
|
||||
const meta = await bgImage.metadata();
|
||||
if (!meta.width || !meta.height) throw new Error('Invalid background dimensions');
|
||||
const size = Math.min(meta.width, meta.height);
|
||||
const left = Math.floor((meta.width - size) / 2);
|
||||
const top = Math.floor((meta.height - size) / 2);
|
||||
const squareBg = bgImage.extract({ left, top, width: size, height: size });
|
||||
|
||||
// Resize logo
|
||||
const logoTarget = Math.floor(size * logoPercent);
|
||||
const logoResized = await sharp(logoBuf).resize({ width: logoTarget }).toBuffer();
|
||||
const logoMeta = await sharp(logoResized).metadata();
|
||||
if (!logoMeta.width || !logoMeta.height) throw new Error('Invalid logo dimensions');
|
||||
const logoLeft = Math.floor((size - logoMeta.width) / 2);
|
||||
const logoTop = Math.floor((size - logoMeta.height) / 2);
|
||||
|
||||
return await squareBg
|
||||
.composite([{ input: logoResized, left: logoLeft, top: logoTop }])
|
||||
.jpeg({ quality: 100 })
|
||||
.toBuffer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch JSON from the given URL, with Steam-like headers
|
||||
*/
|
||||
export async function fetchApi<T>(url: string, retries = 3): Promise<T> {
|
||||
let lastError: Error | null = null;
|
||||
|
||||
for (let attempt = 0; attempt < retries; attempt++) {
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
agent: (_parsed) => _parsed.protocol === 'http:' ? httpAgent : httpsAgent,
|
||||
method: "GET",
|
||||
headers: {
|
||||
"User-Agent": "Steam 1291812 / iPhone",
|
||||
"Accept-Language": "en-us",
|
||||
},
|
||||
} as RequestInit);
|
||||
if (!response.ok) {
|
||||
throw new Error(`API error: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as T;
|
||||
} catch (error: any) {
|
||||
lastError = error as Error;
|
||||
// Only retry on network errors or 5xx status codes
|
||||
if (error.message.includes('API error: 5') || !error.message.includes('API error')) {
|
||||
console.warn(`Attempt ${attempt + 1} failed for ${url}: ${error.message}`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt)));
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError || new Error(`Failed to fetch ${url} after ${retries} attempts`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a slug from a name
|
||||
*/
|
||||
export function createSlug(name: string): string {
|
||||
return name
|
||||
.toLowerCase()
|
||||
.normalize("NFKD") // Normalize to decompose accented characters
|
||||
.replace(/[^\p{L}\p{N}\s-]/gu, '') // Keep Unicode letters, numbers, spaces, and hyphens
|
||||
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
||||
.replace(/-+/g, '-') // Collapse multiple hyphens
|
||||
.replace(/^-+|-+$/g, '') // Trim leading/trailing hyphens
|
||||
.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare a candidate screenshot against a UI-free baseline to find how much UI/HUD remains.
|
||||
*
|
||||
* @param baselineBuffer - PNG/JPEG buffer of the clean background.
|
||||
* @param candidateBuffer - PNG/JPEG buffer of the screenshot to test.
|
||||
* @param opts - Options.
|
||||
* @returns Promise resolving to diff ratio (and optional diff image).
|
||||
*/
|
||||
export async function compareWithBaseline(
|
||||
baselineBuffer: Buffer,
|
||||
candidateBuffer: Buffer,
|
||||
opts: CompareOpts = {}
|
||||
): Promise<CompareResult> {
|
||||
// Generate cache key from buffer hashes
|
||||
const baseHash = crypto.createHash('md5').update(baselineBuffer).digest('hex');
|
||||
const candHash = crypto.createHash('md5').update(candidateBuffer).digest('hex');
|
||||
const optsKey = JSON.stringify(opts);
|
||||
const cacheKey = `${baseHash}:${candHash}:${optsKey}`;
|
||||
|
||||
// Check cache
|
||||
if (compareCache.has(cacheKey)) {
|
||||
return compareCache.get(cacheKey)!;
|
||||
}
|
||||
|
||||
const { threshold = 0.1, diffOutput = false } = opts;
|
||||
|
||||
// Get dimensions of baseline
|
||||
const baseMeta: Metadata = await sharp(baselineBuffer).metadata();
|
||||
if (!baseMeta.width || !baseMeta.height) {
|
||||
throw new Error('Invalid baseline dimensions');
|
||||
}
|
||||
|
||||
// Produce PNG buffers of same size
|
||||
const [pngBaseBuf, pngCandBuf] = await Promise.all([
|
||||
sharp(baselineBuffer).png().toBuffer(),
|
||||
sharp(candidateBuffer)
|
||||
.resize(baseMeta.width, baseMeta.height)
|
||||
.png()
|
||||
.toBuffer(),
|
||||
]);
|
||||
|
||||
const imgBase = PNG.sync.read(pngBaseBuf);
|
||||
const imgCand = PNG.sync.read(pngCandBuf);
|
||||
const diffImg = new PNG({ width: baseMeta.width, height: baseMeta.height });
|
||||
|
||||
const numDiff = pixelmatch(
|
||||
imgBase.data,
|
||||
imgCand.data,
|
||||
diffImg.data,
|
||||
baseMeta.width,
|
||||
baseMeta.height,
|
||||
{ threshold }
|
||||
);
|
||||
|
||||
const total = baseMeta.width * baseMeta.height;
|
||||
const diffRatio = numDiff / total;
|
||||
|
||||
const result: CompareResult = { diffRatio };
|
||||
if (diffOutput) {
|
||||
result.diffBuffer = PNG.sync.write(diffImg);
|
||||
}
|
||||
|
||||
compareCache.set(cacheKey, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a baseline buffer and an array of screenshots, returns them sorted
|
||||
* ascending by diffRatio (least UI first).
|
||||
*/
|
||||
export async function rankScreenshots(
|
||||
baselineBuffer: Buffer,
|
||||
shots: Shot[],
|
||||
opts: CompareOpts = {}
|
||||
): Promise<RankedShot[]> {
|
||||
// Process up to 5 comparisons in parallel
|
||||
const compareLimit = pLimit(5);
|
||||
|
||||
// Run all comparisons with limited concurrency
|
||||
const results = await Promise.all(
|
||||
shots.map(shot =>
|
||||
compareLimit(async () => {
|
||||
const { diffRatio } = await compareWithBaseline(
|
||||
baselineBuffer,
|
||||
shot.buffer,
|
||||
opts
|
||||
);
|
||||
return { url: shot.url, score: diffRatio };
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return results.sort((a, b) => a.score - b.score);
|
||||
}
|
||||
|
||||
// --- Helpers for URLs ---
|
||||
export function getScreenshotUrls(screenshots: { appid: number; filename: string }[]): string[] {
|
||||
return screenshots.map(s => `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${s.appid}/${s.filename}`);
|
||||
}
|
||||
|
||||
export function getAssetUrls(assets: LibraryAssetsFull, appid: number | string, header: string) {
|
||||
const base = `https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${appid}`;
|
||||
return {
|
||||
logo: `${base}/${assets.library_logo?.image2x?.english || assets.library_logo?.image?.english}`,
|
||||
backdrop: `${base}/${assets.library_hero?.image2x?.english || assets.library_hero?.image?.english}`,
|
||||
poster: `${base}/${assets.library_capsule?.image2x?.english || assets.library_capsule?.image?.english}`,
|
||||
banner: `${base}/${assets.library_header?.image2x?.english || assets.library_header?.image?.english || header}`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a 0–5 score from positive/negative votes using a Wilson score confidence interval.
|
||||
* This formula adjusts the raw ratio based on the total number of votes to account for
|
||||
* statistical confidence. With few votes, the score regresses toward 2.5 (neutral).
|
||||
*
|
||||
* Compute a 0–5 score from positive/negative votes
|
||||
*/
|
||||
export function getRating(positive: number, negative: number): number {
|
||||
const total = positive + negative;
|
||||
if (!total) return 0;
|
||||
const avg = positive / total;
|
||||
// Apply Wilson score confidence adjustment and scale to 0-5 range
|
||||
const score = avg - (avg - 0.5) * Math.pow(2, -Math.log10(total + 1));
|
||||
return Math.round(score * 5 * 10) / 10;
|
||||
}
|
||||
|
||||
export function getAssociationsByTypeWithSlug<
|
||||
T extends "developer" | "publisher"
|
||||
>(
|
||||
associations: Record<string, { name: string; type: string }>,
|
||||
type: T
|
||||
): Array<{ name: string; slug: string; type: T }> {
|
||||
return Object.values(associations)
|
||||
.filter((a) => a.type === type)
|
||||
.map((a) => ({ name: a.name.trim(), slug: createSlug(a.name.trim()), type }));
|
||||
}
|
||||
|
||||
export function compatibilityType(type?: string): "low" | "mid" | "high" | "unknown" {
|
||||
switch (type) {
|
||||
case "1":
|
||||
return "high";
|
||||
case "2":
|
||||
return "mid";
|
||||
case "3":
|
||||
return "low";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function estimateRatingFromSummary(
|
||||
reviewCount: number,
|
||||
percentPositive: number
|
||||
): number {
|
||||
const positiveVotes = Math.round((percentPositive / 100) * reviewCount);
|
||||
const negativeVotes = reviewCount - positiveVotes;
|
||||
return getRating(positiveVotes, negativeVotes);
|
||||
}
|
||||
|
||||
export function mapGameTags<
|
||||
T extends string = "tag"
|
||||
>(
|
||||
available: Tag[],
|
||||
storeTags: StoreTags,
|
||||
): Array<{ name: string; slug: string; type: T }> {
|
||||
const tagMap = new Map<number, Tag>(available.map((t) => [t.tagid, t]));
|
||||
const result: Array<{ name: string; slug: string; type: T }> = Object.values(storeTags)
|
||||
.map((id) => tagMap.get(Number(id)))
|
||||
.filter((t): t is Tag => Boolean(t))
|
||||
.map((t) => ({ name: t.name.trim(), slug: createSlug(t.name), type: 'tag' as T }));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function createType<
|
||||
T extends "developer" | "publisher" | "franchise" | "tag" | "categorie" | "genre"
|
||||
>(
|
||||
names: string[],
|
||||
type: T
|
||||
) {
|
||||
return names
|
||||
.map(name => ({
|
||||
type,
|
||||
name: name.trim(),
|
||||
slug: createSlug(name.trim())
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tag object with name, slug, and type
|
||||
* @typeparam T Literal type of the `type` field (defaults to 'tag')
|
||||
*/
|
||||
export function createTag<
|
||||
T extends string = 'tag'
|
||||
>(
|
||||
name: string,
|
||||
type?: T
|
||||
): { name: string; slug: string; type: T } {
|
||||
const tagType = (type ?? 'tag') as T;
|
||||
return {
|
||||
name: name.trim(),
|
||||
slug: createSlug(name),
|
||||
type: tagType,
|
||||
};
|
||||
}
|
||||
|
||||
export function capitalise(name: string) {
|
||||
return name
|
||||
.charAt(0) // first character
|
||||
.toUpperCase() // make it uppercase
|
||||
+ name
|
||||
.slice(1) // rest of the string
|
||||
.toLowerCase();
|
||||
}
|
||||
|
||||
function isDepotEntry(e: any): e is DepotEntry {
|
||||
return (
|
||||
e != null &&
|
||||
typeof e === 'object' &&
|
||||
'manifests' in e &&
|
||||
e.manifests != null &&
|
||||
typeof e.manifests.public?.download === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
export function getPublicDepotSizes(depots: AppDepots) {
|
||||
let download = 0;
|
||||
let size = 0;
|
||||
|
||||
for (const key of Object.keys(depots)) {
|
||||
if (key === 'branches' || key === 'privatebranches') continue;
|
||||
const entry = depots[key] as DepotEntry;
|
||||
if (!isDepotEntry(entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dl = Number(entry.manifests.public.download);
|
||||
const sz = Number(entry.manifests.public.size);
|
||||
if (!Number.isFinite(dl) || !Number.isFinite(sz)) {
|
||||
console.warn(`[getPublicDepotSizes] non-numeric size for depot ${key}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
download += dl;
|
||||
size += sz;
|
||||
}
|
||||
|
||||
return { downloadSize: download, sizeOnDisk: size };
|
||||
}
|
||||
|
||||
export function parseGenres(str: string): GenreType[] {
|
||||
return str.split(',')
|
||||
.map((g) => g.trim())
|
||||
.filter(Boolean)
|
||||
.map((g) => ({ type: 'genre', name: g.trim(), slug: createSlug(g) }));
|
||||
}
|
||||
|
||||
export function getPrimaryGenre(
|
||||
genres: GenreType[],
|
||||
map: Record<string, string>,
|
||||
primaryId: string
|
||||
): string | null {
|
||||
const idx = Object.keys(map).find((k) => map[k] === primaryId);
|
||||
return idx !== undefined ? genres[Number(idx)]?.name : null;
|
||||
}
|
||||
|
||||
export function cleanDescription(input: string): string {
|
||||
|
||||
const cleaned = sanitizeHtml(input, {
|
||||
allowedTags: [], // no tags allowed
|
||||
allowedAttributes: {}, // no attributes anywhere
|
||||
textFilter: (text) => text.replace(/\s+/g, ' '), // collapse runs of whitespace
|
||||
});
|
||||
|
||||
return cleaned.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches and parses a single Steam community profile XML.
|
||||
* @param steamIdOrVanity - The 64-bit SteamID or vanity name.
|
||||
* @returns Promise resolving to ProfileInfo.
|
||||
*/
|
||||
export async function fetchProfileInfo(
|
||||
steamIdOrVanity: string
|
||||
): Promise<ProfileInfo> {
|
||||
const isNumericId = /^\d+$/.test(steamIdOrVanity);
|
||||
const path = isNumericId ? `profiles/${steamIdOrVanity}` : `id/${steamIdOrVanity}`;
|
||||
const url = `https://steamcommunity.com/${path}/?xml=1`;
|
||||
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to fetch ${steamIdOrVanity}: HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
const xml = await response.text();
|
||||
const { profile } = await parseStringPromise(xml, {
|
||||
explicitArray: false,
|
||||
trim: true,
|
||||
mergeAttrs: true
|
||||
}) as { profile: any };
|
||||
|
||||
// Extract fields (fall back to limitedAccount tag if needed)
|
||||
const limitedFlag = profile.isLimitedAccount ?? profile.limitedAccount;
|
||||
const isLimited = limitedFlag === '1';
|
||||
|
||||
return {
|
||||
isLimited,
|
||||
steamID64: profile.steamID64,
|
||||
privacyState: profile.privacyState,
|
||||
visibility: profile.visibilityState
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch-fetches multiple Steam profiles in parallel.
|
||||
* @param idsOrVanities - Array of SteamID64 strings or vanity names.
|
||||
* @returns Promise resolving to a record mapping each input to its ProfileInfo or an error.
|
||||
*/
|
||||
export async function fetchProfilesInfo(
|
||||
idsOrVanities: string[]
|
||||
): Promise<Map<string, ProfileInfo | { error: string }>> {
|
||||
const results = await Promise.all(
|
||||
idsOrVanities.map(async (input) => {
|
||||
try {
|
||||
const info = await fetchProfileInfo(input);
|
||||
return { input, result: info };
|
||||
} catch (err) {
|
||||
return { input, result: { error: (err as Error).message } };
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return new Map(
|
||||
results.map(({ input, result }) => [input, result] as [string, ProfileInfo | { error: string }])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
import { z } from "zod";
|
||||
import "zod-openapi/extend";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
export module Common {
|
||||
export namespace Common {
|
||||
export const IdDescription = `Unique object identifier.
|
||||
The format and length of IDs may change over time.`;
|
||||
|
||||
export const now = () => sql`now()`;
|
||||
export const utc = () => sql`now() at time zone 'utc'`;
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import { AsyncLocalStorage } from "node:async_hooks";
|
||||
|
||||
export function createContext<T>(name: string) {
|
||||
export function createContext<T>() {
|
||||
const storage = new AsyncLocalStorage<T>();
|
||||
return {
|
||||
use() {
|
||||
const result = storage.getStore();
|
||||
if (!result) {
|
||||
throw new Error("Context not provided: " + name);
|
||||
throw new Error("No context available");
|
||||
}
|
||||
return result;
|
||||
},
|
||||
with<R>(value: T, fn: () => R) {
|
||||
return storage.run<R, any[]>(value, fn);
|
||||
provide<R>(value: T, fn: () => R) {
|
||||
return storage.run<R>(value, fn);
|
||||
},
|
||||
};
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user