mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 16:55:37 +02:00
Compare commits
1 Commits
feat/prici
...
feat/cloud
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f4512385b1 |
@@ -1,2 +0,0 @@
|
|||||||
CLOUDFLARE_API_TOKEN=
|
|
||||||
NEON_API_KEY=
|
|
||||||
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
26
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,2 +1,28 @@
|
|||||||
## Description
|
## Description
|
||||||
<!-- Briefly describe the purpose and scope of your changes -->
|
<!-- 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
40
.github/workflows/docs.yml
vendored
@@ -1,40 +0,0 @@
|
|||||||
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 }}
|
|
||||||
8
.github/workflows/runner.yml
vendored
8
.github/workflows/runner.yml
vendored
@@ -7,7 +7,6 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "containers/runner.Containerfile"
|
- "containers/runner.Containerfile"
|
||||||
- "packages/scripts/**"
|
- "packages/scripts/**"
|
||||||
- "packages/server/**"
|
|
||||||
- ".github/workflows/runner.yml"
|
- ".github/workflows/runner.yml"
|
||||||
schedule:
|
schedule:
|
||||||
- cron: 7 0 * * 1,3,6 # Regularly to keep that build cache warm
|
- cron: 7 0 * * 1,3,6 # Regularly to keep that build cache warm
|
||||||
@@ -17,7 +16,6 @@ on:
|
|||||||
- "containers/runner.Containerfile"
|
- "containers/runner.Containerfile"
|
||||||
- ".github/workflows/runner.yml"
|
- ".github/workflows/runner.yml"
|
||||||
- "packages/scripts/**"
|
- "packages/scripts/**"
|
||||||
- "packages/server/**"
|
|
||||||
tags:
|
tags:
|
||||||
- v*.*.*
|
- v*.*.*
|
||||||
release:
|
release:
|
||||||
@@ -27,7 +25,6 @@ env:
|
|||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
IMAGE_NAME: nestrilabs/nestri
|
IMAGE_NAME: nestrilabs/nestri
|
||||||
BASE_TAG_PREFIX: runner
|
BASE_TAG_PREFIX: runner
|
||||||
BASE_IMAGE: docker.io/cachyos/cachyos:latest
|
|
||||||
|
|
||||||
# This makes our release ci quit prematurely
|
# This makes our release ci quit prematurely
|
||||||
# concurrency:
|
# concurrency:
|
||||||
@@ -56,7 +53,7 @@ jobs:
|
|||||||
swap-size-gb: 20
|
swap-size-gb: 20
|
||||||
-
|
-
|
||||||
name: Build Docker image
|
name: Build Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
file: containers/runner.Containerfile
|
file: containers/runner.Containerfile
|
||||||
context: ./
|
context: ./
|
||||||
@@ -108,7 +105,7 @@ jobs:
|
|||||||
swap-size-gb: 20
|
swap-size-gb: 20
|
||||||
-
|
-
|
||||||
name: Build Docker image
|
name: Build Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
file: containers/runner.Containerfile
|
file: containers/runner.Containerfile
|
||||||
context: ./
|
context: ./
|
||||||
@@ -117,4 +114,3 @@ jobs:
|
|||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha,mode=max
|
cache-from: type=gha,mode=max
|
||||||
cache-to: 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
40
.github/workflows/www.yml
vendored
@@ -1,40 +0,0 @@
|
|||||||
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 }}
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,7 +8,6 @@ node_modules
|
|||||||
# Local env files
|
# Local env files
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
.env.sst
|
|
||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
|||||||
4
.vscode/extensions.json
vendored
Normal file
4
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["dbaeumer.vscode-eslint", "unifiedjs.vscode-mdx"],
|
||||||
|
"unwantedRecommendations": []
|
||||||
|
}
|
||||||
24
.vscode/launch.json
vendored
Normal file
24
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Chrome",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "chrome",
|
||||||
|
"url": "http://localhost:5173",
|
||||||
|
"webRoot": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"name": "dev.debug",
|
||||||
|
"request": "launch",
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"program": "${workspaceFolder}/node_modules/vite/bin/vite.js",
|
||||||
|
"args": ["--mode", "ssr", "--force"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
36
.vscode/qwik-city.code-snippets
vendored
Normal file
36
.vscode/qwik-city.code-snippets
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"onRequest": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "qonRequest",
|
||||||
|
"description": "onRequest function for a route index",
|
||||||
|
"body": [
|
||||||
|
"export const onRequest: RequestHandler = (request) => {",
|
||||||
|
" $0",
|
||||||
|
"};",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"loader$": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "qloader$",
|
||||||
|
"description": "loader$()",
|
||||||
|
"body": ["export const $1 = routeLoader$(() => {", " $0", "});"],
|
||||||
|
},
|
||||||
|
"action$": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "qaction$",
|
||||||
|
"description": "action$()",
|
||||||
|
"body": ["export const $1 = routeAction$((data) => {", " $0", "});"],
|
||||||
|
},
|
||||||
|
"Full Page": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "qpage",
|
||||||
|
"description": "Simple page component",
|
||||||
|
"body": [
|
||||||
|
"import { component$ } from '@builder.io/qwik';",
|
||||||
|
"",
|
||||||
|
"export default component$(() => {",
|
||||||
|
" $0",
|
||||||
|
"});",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
78
.vscode/qwik.code-snippets
vendored
Normal file
78
.vscode/qwik.code-snippets
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"Qwik component (simple)": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "qcomponent$",
|
||||||
|
"description": "Simple Qwik component",
|
||||||
|
"body": [
|
||||||
|
"export const ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}} = component$(() => {",
|
||||||
|
" return <${2:div}>$4</$2>",
|
||||||
|
"});",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Qwik component (props)": {
|
||||||
|
"scope": "typescriptreact",
|
||||||
|
"prefix": "qcomponent$ + props",
|
||||||
|
"description": "Qwik component w/ props",
|
||||||
|
"body": [
|
||||||
|
"export interface ${1:${TM_FILENAME_BASE/(.*)/${1:/pascalcase}/}}Props {",
|
||||||
|
" $2",
|
||||||
|
"}",
|
||||||
|
"",
|
||||||
|
"export const $1 = component$<$1Props>((props) => {",
|
||||||
|
" const ${2:count} = useSignal(0);",
|
||||||
|
" return (",
|
||||||
|
" <${3:div} on${4:Click}$={(ev) => {$5}}>",
|
||||||
|
" $6",
|
||||||
|
" </${3}>",
|
||||||
|
" );",
|
||||||
|
"});",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"Qwik signal": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "quseSignal",
|
||||||
|
"description": "useSignal() declaration",
|
||||||
|
"body": ["const ${1:foo} = useSignal($2);", "$0"],
|
||||||
|
},
|
||||||
|
"Qwik store": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "quseStore",
|
||||||
|
"description": "useStore() declaration",
|
||||||
|
"body": ["const ${1:state} = useStore({", " $2", "});", "$0"],
|
||||||
|
},
|
||||||
|
"$ hook": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "q$",
|
||||||
|
"description": "$() function hook",
|
||||||
|
"body": ["$(() => {", " $0", "});", ""],
|
||||||
|
},
|
||||||
|
"useVisibleTask": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "quseVisibleTask",
|
||||||
|
"description": "useVisibleTask$() function hook",
|
||||||
|
"body": ["useVisibleTask$(({ track }) => {", " $0", "});", ""],
|
||||||
|
},
|
||||||
|
"useTask": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "quseTask$",
|
||||||
|
"description": "useTask$() function hook",
|
||||||
|
"body": [
|
||||||
|
"useTask$(({ track }) => {",
|
||||||
|
" track(() => $1);",
|
||||||
|
" $0",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"useResource": {
|
||||||
|
"scope": "javascriptreact,typescriptreact",
|
||||||
|
"prefix": "quseResource$",
|
||||||
|
"description": "useResource$() declaration",
|
||||||
|
"body": [
|
||||||
|
"const $1 = useResource$(({ track, cleanup }) => {",
|
||||||
|
" $0",
|
||||||
|
"});",
|
||||||
|
"",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
10
.vscode/settings.json
vendored
10
.vscode/settings.json
vendored
@@ -1,3 +1,11 @@
|
|||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib"
|
"material-icon-theme.activeIconPack": "qwik",
|
||||||
|
"emmet.includeLanguages": {
|
||||||
|
"typescriptreact": "html"
|
||||||
|
},
|
||||||
|
"emmet.preferences": {
|
||||||
|
// to ensure closing tags are used (e.g. <img/> not just <img> like in HTML)
|
||||||
|
// https://github.com/microsoft/vscode/commit/083bf9020407ea5a91199eb1f0b373859df8d600#diff-88456bc9b7caa2f8126aea0107b4671db0f094961aaf39a7c689f890e23aaaba
|
||||||
|
"output.selfClosingStyle": "xhtml"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# What is this?
|
|
||||||
|
|
||||||
This is the part of the docs dedicated for the team working on Nestri
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
# Setup
|
|
||||||
|
|
||||||
- Install bun [https://bun.sh/](https://bun.sh/)
|
|
||||||
- Generate your Cloudflare token from [here](https://dash.cloudflare.com/profile/api-tokens?permissionGroupKeys=%5B%7B%22key%22%3A%22account_settings%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22dns%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22memberships%22%2C%22type%22%3A%22read%22%7D%2C%7B%22key%22%3A%22user_details%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_kv_storage%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_r2%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_routes%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_scripts%22%2C%22type%22%3A%22edit%22%7D%2C%7B%22key%22%3A%22workers_tail%22%2C%22type%22%3A%22read%22%7D%5D&name=sst&accountId=*&zoneId=all)
|
|
||||||
- save it to a `.env` file like this
|
|
||||||
```
|
|
||||||
CLOUDFLARE_API_TOKEN=xxx
|
|
||||||
```
|
|
||||||
- Copy this to your `~/.aws/config` file
|
|
||||||
```
|
|
||||||
[sso-session nestri]
|
|
||||||
sso_start_url = https://nestri.awsapps.com/start
|
|
||||||
sso_region = us-east-1
|
|
||||||
|
|
||||||
[profile nestri-dev]
|
|
||||||
sso_session = nestri
|
|
||||||
sso_account_id = 535002871375
|
|
||||||
sso_role_name = AdministratorAccess
|
|
||||||
region = us-east-1
|
|
||||||
|
|
||||||
[profile nestri-production]
|
|
||||||
sso_session = nestri
|
|
||||||
sso_account_id = 209479283398
|
|
||||||
sso_role_name = AdministratorAccess
|
|
||||||
region = us-east-1
|
|
||||||
```
|
|
||||||
- You need to login once a day with `bun sso` in root
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
title: 'Nestri Internals'
|
|
||||||
icon: heroicons-outline:bookmark-alt
|
|
||||||
3827
apps/docs/package-lock.json
generated
3827
apps/docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,19 +4,19 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"nestri.dev": "nuxi dev",
|
"nestri.dev": "nuxi dev",
|
||||||
"build": "nuxi build --preset=cloudflare_pages",
|
"build": "nuxi build",
|
||||||
"generate": "nuxi generate",
|
"generate": "nuxi generate",
|
||||||
"preview": "nuxi preview",
|
"preview": "nuxi preview",
|
||||||
"lint": "eslint ."
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nuxt-themes/docus": "latest",
|
"@nuxt-themes/docus": "latest",
|
||||||
"@nuxt/devtools": "^2.3.2",
|
"@nuxt/devtools": "^1.4.1",
|
||||||
"@nuxt/eslint-config": "^0.5.6",
|
"@nuxt/eslint-config": "^0.5.6",
|
||||||
"@nuxt/ui": "^2.19.2",
|
"@nuxt/ui": "^2.19.2",
|
||||||
"@nuxtjs/plausible": "^1.0.2",
|
"@nuxtjs/plausible": "^1.0.2",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.5",
|
||||||
"eslint": "^9.10.0",
|
"eslint": "^9.10.0",
|
||||||
"nuxt": "^3.16.1"
|
"nuxt": "^3.15.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,16 +35,14 @@
|
|||||||
"@builder.io/qwik": "^1.8.0",
|
"@builder.io/qwik": "^1.8.0",
|
||||||
"@builder.io/qwik-city": "^1.8.0",
|
"@builder.io/qwik-city": "^1.8.0",
|
||||||
"@builder.io/qwik-react": "0.5.0",
|
"@builder.io/qwik-react": "0.5.0",
|
||||||
"@fontsource-variable/bricolage-grotesque": "^5.0.1",
|
"@fontsource-variable/bricolage-grotesque": "^5.1.1",
|
||||||
"@fontsource/geist-mono": "^5.1.0",
|
|
||||||
"@fontsource/geist-sans": "^5.1.0",
|
|
||||||
"@fontsource-variable/mona-sans": "^5.0.1",
|
"@fontsource-variable/mona-sans": "^5.0.1",
|
||||||
"@modular-forms/qwik": "^0.29.0",
|
"@modular-forms/qwik": "^0.29.0",
|
||||||
"@nestri/input": "*",
|
"@nestri/input": "*",
|
||||||
"@nestri/libmoq": "*",
|
"@nestri/libmoq": "*",
|
||||||
"@nestri/sdk": "0.1.0-alpha.14",
|
"@nestri/sdk": "0.1.0-alpha.14",
|
||||||
"@nestri/ui": "*",
|
"@nestri/ui": "*",
|
||||||
"@openauthjs/openauth": "*",
|
"@openauthjs/openauth": "^0.2.6",
|
||||||
"@polar-sh/checkout": "^0.1.8",
|
"@polar-sh/checkout": "^0.1.8",
|
||||||
"@polar-sh/sdk": "^0.21.1",
|
"@polar-sh/sdk": "^0.21.1",
|
||||||
"@qwik-ui/headless": "^0.6.4",
|
"@qwik-ui/headless": "^0.6.4",
|
||||||
@@ -63,11 +61,10 @@
|
|||||||
"prettier": "3.3.3",
|
"prettier": "3.3.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"semver": "^7.7.1",
|
|
||||||
"typescript": "5.4.5",
|
"typescript": "5.4.5",
|
||||||
"undici": "*",
|
"undici": "*",
|
||||||
"valibot": "^0.42.1",
|
"valibot": "^0.42.1",
|
||||||
"vite": "6.0.15",
|
"vite": "5.4.12",
|
||||||
"vite-tsconfig-paths": "^4.2.1",
|
"vite-tsconfig-paths": "^4.2.1",
|
||||||
"wrangler": "^3.0.0"
|
"wrangler": "^3.0.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ export default component$(() => {
|
|||||||
<div class="w-screen relative">
|
<div class="w-screen relative">
|
||||||
<HeroSection client:load>
|
<HeroSection client:load>
|
||||||
<div class="sm:w-full flex gap-3 justify-center pt-4 sm:flex-row flex-col w-auto items-center">
|
<div class="sm:w-full flex gap-3 justify-center pt-4 sm:flex-row flex-col w-auto items-center">
|
||||||
<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" >
|
<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" >
|
||||||
Join our Discord
|
Get early access
|
||||||
</Link>
|
</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" >
|
<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>
|
<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>
|
</section>
|
||||||
<Footer client:load>
|
<Footer client:load>
|
||||||
<div class="w-full flex justify-center flex-col items-center gap-3">
|
<div class="w-full flex justify-center flex-col items-center gap-3">
|
||||||
<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" >
|
<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" >
|
||||||
Join our Discord
|
Get early access
|
||||||
</Link>
|
</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">
|
<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">
|
<span class="hover:text-primary-500 transition-colors duration-200">
|
||||||
|
|||||||
@@ -116,30 +116,8 @@ export default component$(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="w-screen relative">
|
<div class="w-screen relative">
|
||||||
<TitleSection client:load title="Pricing" description={"The biggest bang, binge, and blast for your buck"} />
|
<TitleSection client:load title="Pricing" description={"We're growing at the speed of trust. Choose a price that feels right for you and help support Nestri"} />
|
||||||
|
<MotionComponent
|
||||||
<Footer client:load>
|
|
||||||
<div class="w-full flex justify-center flex-col items-center gap-3">
|
|
||||||
<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">
|
|
||||||
<Link rel="noreferrer" href="/terms" >Terms of Service</Link></span>
|
|
||||||
<span class="text-gray-400 dark:text-gray-600">•</span>
|
|
||||||
<span class="hover:text-primary-500 transition-colors duration-200" >
|
|
||||||
<Link href="/privacy">Privacy Policy</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Footer>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <MotionComponent
|
|
||||||
initial={{ opacity: 0, y: 100 }}
|
initial={{ opacity: 0, y: 100 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true }}
|
||||||
@@ -166,6 +144,7 @@ export default component$(() => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col w-full">
|
<div class="flex flex-col w-full">
|
||||||
<p class="text-[4rem] leading-[1] font-medium font-title"> Free </p>
|
<p class="text-[4rem] leading-[1] font-medium font-title"> Free </p>
|
||||||
|
{/**FIXME: Add the link to the docs here */}
|
||||||
<a href={CONSTANTS.githubLink} ref={v => bookRef.value = v} class="h-[154px] w-full flex items-start pt-4 justify-center overflow-hidden">
|
<a href={CONSTANTS.githubLink} ref={v => bookRef.value = v} class="h-[154px] w-full flex items-start pt-4 justify-center overflow-hidden">
|
||||||
<Book textColor="#FFF"
|
<Book textColor="#FFF"
|
||||||
bgColor="#FF4F01"
|
bgColor="#FF4F01"
|
||||||
@@ -540,4 +519,21 @@ export default component$(() => {
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</MotionComponent>
|
</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>
|
||||||
|
<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">
|
||||||
|
<Link rel="noreferrer" href="/terms" >Terms of Service</Link></span>
|
||||||
|
<span class="text-gray-400 dark:text-gray-600">•</span>
|
||||||
|
<span class="hover:text-primary-500 transition-colors duration-200" >
|
||||||
|
<Link href="/privacy">Privacy Policy</Link>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Footer>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|||||||
84
cloud/ubuntu-cloud-init.yaml
Normal file
84
cloud/ubuntu-cloud-init.yaml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
#cloud-config
|
||||||
|
# Cloud-init script to install NVIDIA 570 drivers on Ubuntu
|
||||||
|
# Tested with TensorDock Ubuntu 22.04 instances
|
||||||
|
|
||||||
|
# Install required packages
|
||||||
|
packages:
|
||||||
|
- build-essential
|
||||||
|
- linux-headers-generic
|
||||||
|
- dkms
|
||||||
|
|
||||||
|
# Our scripts
|
||||||
|
write_files:
|
||||||
|
- path: /root/prevent_driver_update.sh
|
||||||
|
encoding: b64
|
||||||
|
permissions: '0755'
|
||||||
|
content: ZHBrZy1xdWVyeSAtVyAtLXNob3dmb3JtYXQ9JyR7UGFja2FnZX0gJHtTdGF0dXN9XG4nIHwgZ3JlcCAtdiBkZWluc3RhbGwgfCBhd2sgJ3sgcHJpbnQgJDEgfScgfCBncmVwIC1FICdudmlkaWEuKi1bMC05XSskJyB8IHhhcmdzIC1yIC1MIDEgc3VkbyBhcHQtbWFyayBob2xk
|
||||||
|
# Blacklist nouveau just in case
|
||||||
|
- path: /etc/modprobe.d/blacklist-nouveau.conf
|
||||||
|
content: |
|
||||||
|
blacklist nouveau
|
||||||
|
# Enable modesetting for NVIDIA drivers
|
||||||
|
- path: /etc/modprobe.d/nvidia.conf
|
||||||
|
content: |
|
||||||
|
options nvidia-drm modeset=1
|
||||||
|
# Main setup script
|
||||||
|
- path: /root/setup_nvidia.sh
|
||||||
|
permissions: '0755'
|
||||||
|
content: |
|
||||||
|
#!/bin/bash
|
||||||
|
echo "Starting Nestri NVIDIA driver setup..."
|
||||||
|
|
||||||
|
echo "Purging old NVIDIA packages..."
|
||||||
|
apt remove --purge -y '*nvidia*'
|
||||||
|
apt autoremove -y
|
||||||
|
|
||||||
|
echo "Unloading conflicting kernel modules..."
|
||||||
|
modprobe -r nouveau 2>/dev/null || true
|
||||||
|
modprobe -r nvidia_drm 2>/dev/null || true
|
||||||
|
modprobe -r nvidia_modeset 2>/dev/null || true
|
||||||
|
modprobe -r nvidia 2>/dev/null || true
|
||||||
|
|
||||||
|
# Update initramfs to apply blacklist
|
||||||
|
update-initramfs -u
|
||||||
|
|
||||||
|
echo "Installing NVIDIA 570 driver..."
|
||||||
|
wget https://us.download.nvidia.com/XFree86/Linux-x86_64/570.86.16/NVIDIA-Linux-x86_64-570.86.16.run -O /root/NVIDIA-Linux-x86_64-570.86.16.run
|
||||||
|
chmod +x /root/NVIDIA-Linux-x86_64-570.86.16.run
|
||||||
|
# Install without building kernel module immediately, then build with DKMS
|
||||||
|
/root/NVIDIA-Linux-x86_64-570.86.16.run --silent --dkms
|
||||||
|
# Clean up
|
||||||
|
rm /root/NVIDIA-Linux-x86_64-570.86.16.run
|
||||||
|
|
||||||
|
# Prevent auto-update from nuking driver
|
||||||
|
echo "Making the new driver held version..."
|
||||||
|
bash /root/prevent_driver_update.sh
|
||||||
|
|
||||||
|
echo "Loading new NVIDIA modules..."
|
||||||
|
modprobe nvidia
|
||||||
|
modprobe nvidia_modeset
|
||||||
|
modprobe nvidia_drm
|
||||||
|
|
||||||
|
# Re-install container toolkit
|
||||||
|
echo "Re-installing NVIDIA container toolkit..."
|
||||||
|
apt install -y nvidia-container-toolkit
|
||||||
|
|
||||||
|
echo "Configuring NVIDIA container toolkit..."
|
||||||
|
nvidia-ctk runtime configure --runtime=docker
|
||||||
|
# Restart Docker only if necessary
|
||||||
|
if ! nvidia-smi > /dev/null 2>&1; then
|
||||||
|
echo "Restarting Docker to apply GPU changes..."
|
||||||
|
systemctl restart docker
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Setting up permissions..."
|
||||||
|
chmod 777 /dev/dri/* 2>/dev/null || true
|
||||||
|
|
||||||
|
cd /home/user/ && mkdir -p nestri && chown user:user nestri
|
||||||
|
docker run --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" --name=nestri -d --shm-size=6g --runtime=nvidia --gpus=all -e RELAY_URL='https://relay.dathorse.com' -e NESTRI_ROOM=cloudinit123 -e RESOLUTION=1920x1080 -e FRAMERATE=60 -e GST_DEBUG=3 -e NESTRI_PARAMS='--verbose=true --video-codec=h264 --video-bitrate=6000 --video-bitrate-max=8000' -v /home/user/nestri:/home/nestri ghcr.io/datcaptainhorse/nestri-cachyos:latest-v3
|
||||||
|
|
||||||
|
echo "Nestri NVIDIA driver setup complete!"
|
||||||
|
|
||||||
|
# Run setup script on first launch
|
||||||
|
runcmd:
|
||||||
|
- /root/setup_nvidia.sh
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
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"]
|
|
||||||
@@ -1,31 +1,20 @@
|
|||||||
FROM docker.io/golang:1.24-alpine AS go-build
|
FROM docker.io/golang:1.23-alpine AS go-build
|
||||||
WORKDIR /builder
|
WORKDIR /builder
|
||||||
COPY packages/relay/ /builder/
|
COPY packages/relay/ /builder/
|
||||||
RUN go build
|
RUN go build
|
||||||
|
|
||||||
FROM docker.io/golang:1.24-alpine
|
FROM docker.io/golang:1.23-alpine
|
||||||
COPY --from=go-build /builder/relay /relay/relay
|
COPY --from=go-build /builder/relay /relay/relay
|
||||||
WORKDIR /relay
|
WORKDIR /relay
|
||||||
|
|
||||||
# TODO: Switch running layer to just alpine (doesn't need golang dev stack)
|
|
||||||
|
|
||||||
# ENV flags
|
# ENV flags
|
||||||
ENV VERBOSE=false
|
ENV VERBOSE=false
|
||||||
ENV DEBUG=false
|
|
||||||
ENV ENDPOINT_PORT=8088
|
ENV ENDPOINT_PORT=8088
|
||||||
ENV MESH_PORT=8089
|
|
||||||
ENV WEBRTC_UDP_START=10000
|
ENV WEBRTC_UDP_START=10000
|
||||||
ENV WEBRTC_UDP_END=20000
|
ENV WEBRTC_UDP_END=20000
|
||||||
ENV STUN_SERVER="stun.l.google.com:19302"
|
ENV STUN_SERVER="stun.l.google.com:19302"
|
||||||
ENV WEBRTC_UDP_MUX=8088
|
|
||||||
ENV WEBRTC_NAT_IPS=""
|
|
||||||
ENV AUTO_ADD_LOCAL_IP=true
|
|
||||||
ENV TLS_CERT=""
|
|
||||||
ENV TLS_KEY=""
|
|
||||||
|
|
||||||
EXPOSE $ENDPOINT_PORT
|
EXPOSE $ENDPOINT_PORT
|
||||||
EXPOSE $MESH_PORT
|
|
||||||
EXPOSE $WEBRTC_UDP_START-$WEBRTC_UDP_END/udp
|
EXPOSE $WEBRTC_UDP_START-$WEBRTC_UDP_END/udp
|
||||||
EXPOSE $WEBRTC_UDP_MUX/udp
|
|
||||||
|
|
||||||
ENTRYPOINT ["/relay/relay"]
|
ENTRYPOINT ["/relay/relay"]
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
# Container build arguments #
|
# Container build arguments #
|
||||||
ARG BASE_IMAGE=docker.io/cachyos/cachyos:latest
|
ARG BASE_IMAGE=docker.io/cachyos/cachyos:latest
|
||||||
|
|
||||||
#******************************************************************************
|
|
||||||
# Base Stage - Updates system packages
|
|
||||||
#******************************************************************************
|
|
||||||
FROM ${BASE_IMAGE} AS base
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
|
||||||
pacman --noconfirm -Syu
|
|
||||||
|
|
||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
# Base Builder Stage - Prepares core build environment
|
# Base Builder Stage - Prepares core build environment
|
||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
FROM base AS base-builder
|
FROM ${BASE_IMAGE} AS base-builder
|
||||||
|
|
||||||
# Environment setup for Rust and Cargo
|
# Environment setup for Rust and Cargo
|
||||||
ENV CARGO_HOME=/usr/local/cargo \
|
ENV CARGO_HOME=/usr/local/cargo \
|
||||||
@@ -22,12 +14,9 @@ ENV CARGO_HOME=/usr/local/cargo \
|
|||||||
|
|
||||||
# Install build essentials and caching tools
|
# Install build essentials and caching tools
|
||||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
||||||
pacman -Sy --noconfirm mold rustup && \
|
pacman -Sy --noconfirm mold rust && \
|
||||||
mkdir -p "${ARTIFACTS}"
|
mkdir -p "${ARTIFACTS}"
|
||||||
|
|
||||||
# Install latest Rust using rustup
|
|
||||||
RUN rustup default stable
|
|
||||||
|
|
||||||
# Install cargo-chef with proper caching
|
# Install cargo-chef with proper caching
|
||||||
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
||||||
cargo install -j $(nproc) cargo-chef cargo-c --locked
|
cargo install -j $(nproc) cargo-chef cargo-c --locked
|
||||||
@@ -39,8 +28,7 @@ FROM base-builder AS nestri-server-deps
|
|||||||
WORKDIR /builder
|
WORKDIR /builder
|
||||||
|
|
||||||
# Install build dependencies
|
# Install build dependencies
|
||||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
RUN pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
||||||
pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
|
||||||
gstreamer gst-plugins-base gst-plugins-good gst-plugin-rswebrtc
|
gstreamer gst-plugins-base gst-plugins-good gst-plugin-rswebrtc
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
@@ -85,8 +73,8 @@ RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
|||||||
pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
pacman -Sy --noconfirm meson pkgconf cmake git gcc make \
|
||||||
libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput
|
libxkbcommon wayland gstreamer gst-plugins-base gst-plugins-good libinput
|
||||||
|
|
||||||
# Clone repository
|
# Clone repository with proper directory structure
|
||||||
RUN git clone -b dev-dmabuf https://github.com/DatCaptainHorse/gst-wayland-display.git
|
RUN git clone -b dev-dmabuf https://github.com/games-on-whales/gst-wayland-display.git
|
||||||
|
|
||||||
#--------------------------------------------------------------------
|
#--------------------------------------------------------------------
|
||||||
FROM gst-wayland-deps AS gst-wayland-planner
|
FROM gst-wayland-deps AS gst-wayland-planner
|
||||||
@@ -119,7 +107,7 @@ RUN --mount=type=cache,target=${CARGO_HOME}/registry \
|
|||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
# Final Runtime Stage
|
# Final Runtime Stage
|
||||||
#******************************************************************************
|
#******************************************************************************
|
||||||
FROM base AS runtime
|
FROM ${BASE_IMAGE} AS runtime
|
||||||
|
|
||||||
### System Configuration ###
|
### System Configuration ###
|
||||||
RUN sed -i \
|
RUN sed -i \
|
||||||
@@ -129,31 +117,27 @@ RUN sed -i \
|
|||||||
dirmngr </dev/null > /dev/null 2>&1
|
dirmngr </dev/null > /dev/null 2>&1
|
||||||
|
|
||||||
### Package Installation ###
|
### Package Installation ###
|
||||||
|
RUN pacman --noconfirm -Sy && \
|
||||||
# Core system components
|
# Core system components
|
||||||
RUN --mount=type=cache,target=/var/cache/pacman/pkg \
|
pacman -S --needed --noconfirm \
|
||||||
pacman -Sy --needed --noconfirm \
|
archlinux-keyring vulkan-intel lib32-vulkan-intel mesa \
|
||||||
vulkan-intel lib32-vulkan-intel vpl-gpu-rt mesa \
|
steam steam-native-runtime \
|
||||||
steam steam-native-runtime gtk3 lib32-gtk3 \
|
sudo xorg-xwayland labwc wlr-randr mangohud \
|
||||||
sudo xorg-xwayland seatd libinput labwc wlr-randr gamescope mangohud \
|
|
||||||
libssh2 curl wget \
|
|
||||||
pipewire pipewire-pulse pipewire-alsa wireplumber \
|
pipewire pipewire-pulse pipewire-alsa wireplumber \
|
||||||
noto-fonts-cjk supervisor jq chwd lshw pacman-contrib && \
|
noto-fonts-cjk supervisor jq chwd lshw pacman-contrib && \
|
||||||
# GStreamer stack
|
# GStreamer stack
|
||||||
pacman -Sy --needed --noconfirm \
|
pacman -S --needed --noconfirm \
|
||||||
gstreamer gst-plugins-base gst-plugins-good \
|
gstreamer gst-plugins-base gst-plugins-good \
|
||||||
gst-plugins-bad gst-plugin-pipewire \
|
gst-plugins-bad gst-plugin-pipewire \
|
||||||
gst-plugin-webrtchttp gst-plugin-rswebrtc gst-plugin-rsrtp \
|
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
|
# Cleanup
|
||||||
paccache -rk1 && \
|
paccache -rk1 && \
|
||||||
rm -rf /usr/share/{info,man,doc}/*
|
rm -rf /usr/share/{info,man,doc}/*
|
||||||
|
|
||||||
### Application Installation ###
|
### Application Installation ###
|
||||||
ARG LUDUSAVI_VERSION="0.28.0"
|
ARG LUDUSAVI_VERSION="0.28.0"
|
||||||
RUN curl -fsSL -o ludusavi.tar.gz \
|
RUN pacman -Sy --noconfirm --needed curl && \
|
||||||
|
curl -fsSL -o ludusavi.tar.gz \
|
||||||
"https://github.com/mtkennerly/ludusavi/releases/download/v${LUDUSAVI_VERSION}/ludusavi-v${LUDUSAVI_VERSION}-linux.tar.gz" && \
|
"https://github.com/mtkennerly/ludusavi/releases/download/v${LUDUSAVI_VERSION}/ludusavi-v${LUDUSAVI_VERSION}-linux.tar.gz" && \
|
||||||
tar -xzvf ludusavi.tar.gz && \
|
tar -xzvf ludusavi.tar.gz && \
|
||||||
mv ludusavi /usr/bin/ && \
|
mv ludusavi /usr/bin/ && \
|
||||||
@@ -188,30 +172,6 @@ RUN mkdir -p /run/dbus && \
|
|||||||
-e '/wants = \[/{s/hooks\.node\.suspend\s*//; s/,\s*\]/]/}' \
|
-e '/wants = \[/{s/hooks\.node\.suspend\s*//; s/,\s*\]/]/}' \
|
||||||
/usr/share/wireplumber/wireplumber.conf
|
/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 ###
|
### Artifacts and Verification ###
|
||||||
COPY --from=nestri-server-cached-builder /artifacts/nestri-server /usr/bin/
|
COPY --from=nestri-server-cached-builder /artifacts/nestri-server /usr/bin/
|
||||||
COPY --from=gst-wayland-cached-builder /artifacts/lib/ /usr/lib/
|
COPY --from=gst-wayland-cached-builder /artifacts/lib/ /usr/lib/
|
||||||
|
|||||||
103
infra/api.ts
103
infra/api.ts
@@ -1,63 +1,54 @@
|
|||||||
import { bus } from "./bus";
|
import { authFingerprintKey } from "./auth";
|
||||||
import { auth } from "./auth";
|
|
||||||
import { domain } from "./dns";
|
import { domain } from "./dns";
|
||||||
import { secret } from "./secret";
|
import { secret } from "./secrets"
|
||||||
import { cluster } from "./cluster";
|
// import { party } from "./party"
|
||||||
import { postgres } from "./postgres";
|
import { gpuTaskDefinition, ecsCluster } from "./cluster";
|
||||||
|
|
||||||
export const api = new sst.aws.Service("Api", {
|
export const urls = new sst.Linkable("Urls", {
|
||||||
cluster,
|
properties: {
|
||||||
cpu: $app.stage === "production" ? "2 vCPU" : undefined,
|
api: "https://api." + domain,
|
||||||
memory: $app.stage === "production" ? "4 GB" : undefined,
|
auth: "https://auth." + domain,
|
||||||
command: ["bun", "run", "./src/api/index.ts"],
|
|
||||||
link: [
|
|
||||||
bus,
|
|
||||||
auth,
|
|
||||||
postgres,
|
|
||||||
secret.PolarSecret,
|
|
||||||
secret.PolarWebhookSecret,
|
|
||||||
secret.NestriFamilyMonthly,
|
|
||||||
secret.NestriFamilyYearly,
|
|
||||||
secret.NestriFreeMonthly,
|
|
||||||
secret.NestriProMonthly,
|
|
||||||
secret.NestriProYearly,
|
|
||||||
],
|
|
||||||
image: {
|
|
||||||
dockerfile: "packages/functions/Containerfile",
|
|
||||||
},
|
},
|
||||||
environment: {
|
|
||||||
NO_COLOR: "1",
|
|
||||||
},
|
|
||||||
loadBalancer: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
listen: "80/http",
|
|
||||||
forward: "3001/http",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
dev: {
|
|
||||||
command: "bun dev:api",
|
|
||||||
directory: "packages/functions",
|
|
||||||
url: "http://localhost:3001",
|
|
||||||
},
|
|
||||||
scaling:
|
|
||||||
$app.stage === "production"
|
|
||||||
? {
|
|
||||||
min: 2,
|
|
||||||
max: 10,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const kv = new sst.cloudflare.Kv("CloudflareAuthKV")
|
||||||
|
|
||||||
export const apiRoute = new sst.aws.Router("ApiRoute", {
|
export const auth = new sst.cloudflare.Worker("Auth", {
|
||||||
routes: {
|
link: [
|
||||||
// I think api.url should work all the same
|
kv,
|
||||||
"/*": api.nodes.loadBalancer.dnsName,
|
urls,
|
||||||
},
|
authFingerprintKey,
|
||||||
domain: {
|
secret.InstantAdminToken,
|
||||||
name: "api." + domain,
|
secret.InstantAppId,
|
||||||
dns: sst.cloudflare.dns(),
|
secret.LoopsApiKey,
|
||||||
},
|
secret.GithubClientID,
|
||||||
|
secret.GithubClientSecret,
|
||||||
|
secret.DiscordClientID,
|
||||||
|
secret.DiscordClientSecret,
|
||||||
|
],
|
||||||
|
handler: "./packages/functions/src/auth.ts",
|
||||||
|
url: true,
|
||||||
|
domain: "auth." + domain
|
||||||
|
});
|
||||||
|
|
||||||
|
export const api = new sst.cloudflare.Worker("Api", {
|
||||||
|
link: [
|
||||||
|
urls,
|
||||||
|
ecsCluster,
|
||||||
|
gpuTaskDefinition,
|
||||||
|
authFingerprintKey,
|
||||||
|
secret.LoopsApiKey,
|
||||||
|
secret.InstantAppId,
|
||||||
|
secret.AwsAccessKey,
|
||||||
|
secret.AwsSecretKey,
|
||||||
|
secret.InstantAdminToken,
|
||||||
|
],
|
||||||
|
url: true,
|
||||||
|
handler: "./packages/functions/src/api/index.ts",
|
||||||
|
domain: "api." + domain
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const outputs = {
|
||||||
|
auth: auth.url,
|
||||||
|
api: api.url
|
||||||
|
}
|
||||||
@@ -1,66 +1,12 @@
|
|||||||
import { bus } from "./bus";
|
export const authFingerprintKey = new random.RandomString(
|
||||||
import { domain } from "./dns";
|
"AuthFingerprintKey",
|
||||||
import { secret } from "./secret";
|
|
||||||
import { cluster } from "./cluster";
|
|
||||||
import { postgres } from "./postgres";
|
|
||||||
|
|
||||||
//FIXME: Use a shared /tmp folder
|
|
||||||
export const auth = 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.ts"],
|
|
||||||
link: [
|
|
||||||
bus,
|
|
||||||
postgres,
|
|
||||||
secret.PolarSecret,
|
|
||||||
secret.GithubClientID,
|
|
||||||
secret.DiscordClientID,
|
|
||||||
secret.GithubClientSecret,
|
|
||||||
secret.DiscordClientSecret,
|
|
||||||
],
|
|
||||||
image: {
|
|
||||||
dockerfile: "packages/functions/Containerfile",
|
|
||||||
},
|
|
||||||
environment: {
|
|
||||||
NO_COLOR: "1",
|
|
||||||
STORAGE: $dev ? "/tmp/persist.json" : "/mnt/efs/persist.json"
|
|
||||||
},
|
|
||||||
loadBalancer: {
|
|
||||||
rules: [
|
|
||||||
{
|
{
|
||||||
listen: "80/http",
|
length: 32,
|
||||||
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
export const authRoute = new sst.aws.Router("AuthRoute", {
|
sst.Linkable.wrap(random.RandomString, (resource) => ({
|
||||||
routes: {
|
properties: {
|
||||||
// I think auth.url should work all the same
|
value: resource.result,
|
||||||
"/*": auth.nodes.loadBalancer.dnsName,
|
|
||||||
},
|
},
|
||||||
domain: {
|
}));
|
||||||
name: "auth." + domain,
|
|
||||||
dns: sst.cloudflare.dns(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
23
infra/bus.ts
23
infra/bus.ts
@@ -1,23 +0,0 @@
|
|||||||
import { vpc } from "./vpc";
|
|
||||||
// import { email } from "./email";
|
|
||||||
import { allSecrets } from "./secret";
|
|
||||||
import { postgres } from "./postgres";
|
|
||||||
|
|
||||||
export const bus = new sst.aws.Bus("Bus");
|
|
||||||
|
|
||||||
bus.subscribe("Event", {
|
|
||||||
vpc,
|
|
||||||
handler: "./packages/functions/src/event/event.handler",
|
|
||||||
link: [
|
|
||||||
// email,
|
|
||||||
postgres,
|
|
||||||
...allSecrets
|
|
||||||
],
|
|
||||||
timeout: "5 minutes",
|
|
||||||
permissions: [
|
|
||||||
{
|
|
||||||
actions: ["ses:SendEmail"],
|
|
||||||
resources: ["*"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
157
infra/cluster.ts
157
infra/cluster.ts
@@ -1,6 +1,155 @@
|
|||||||
import { vpc } from "./vpc";
|
import { sshKey } from "./ssh";
|
||||||
|
import { authFingerprintKey } from "./auth";
|
||||||
|
|
||||||
export const cluster = new sst.aws.Cluster("Cluster", {
|
export const ecsCluster = new aws.ecs.Cluster("NestriGPUCluster", {
|
||||||
vpc,
|
name: "NestriGPUCluster",
|
||||||
forceUpgrade: "v2"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ecsInstanceRole = new aws.iam.Role("NestriGPUInstanceRole", {
|
||||||
|
name: "GPUAssumeRoleProd",
|
||||||
|
assumeRolePolicy: JSON.stringify({
|
||||||
|
Version: "2012-10-17",
|
||||||
|
Statement: [{
|
||||||
|
Action: "sts:AssumeRole",
|
||||||
|
Principal: {
|
||||||
|
Service: "ec2.amazonaws.com",
|
||||||
|
},
|
||||||
|
Effect: "Allow",
|
||||||
|
Sid: "",
|
||||||
|
}],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
new aws.iam.RolePolicyAttachment("NestriGPUInstancePolicyAttachment", {
|
||||||
|
role: ecsInstanceRole.name,
|
||||||
|
policyArn: "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role",
|
||||||
|
});
|
||||||
|
|
||||||
|
const ecsInstanceProfile = new aws.iam.InstanceProfile("NestriGPUInstanceProfile", {
|
||||||
|
role: ecsInstanceRole.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
// const server = new aws.ec2.Instance("NestriGPU", {
|
||||||
|
// instanceType: aws.ec2.InstanceType.G4dn_XLarge,
|
||||||
|
// ami: "ami-046a6af96ef510bb6",//Fedora cloud
|
||||||
|
// keyName: sshKey.keyName,
|
||||||
|
// instanceMarketOptions: {
|
||||||
|
// marketType: "spot",
|
||||||
|
// spotOptions: {
|
||||||
|
// maxPrice: "0.2",
|
||||||
|
// spotInstanceType: "persistent",
|
||||||
|
// instanceInterruptionBehavior: "stop"
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// iamInstanceProfile: ecsInstanceProfile,
|
||||||
|
// });
|
||||||
|
|
||||||
|
const logGroup = new aws.cloudwatch.LogGroup("NestriGPULogGroup", {
|
||||||
|
name: "/ecs/nestri-gpu-prod",
|
||||||
|
retentionInDays: 7,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a Task Definition for the ECS service to test it
|
||||||
|
export const gpuTaskDefinition = new aws.ecs.TaskDefinition("NestriGPUTask", {
|
||||||
|
family: "NestriGPUTaskProd",
|
||||||
|
requiresCompatibilities: ["EC2"],
|
||||||
|
volumes: [
|
||||||
|
{
|
||||||
|
name: "host",
|
||||||
|
hostPath: "/mnt/"
|
||||||
|
// efsVolumeConfiguration: {
|
||||||
|
// fileSystemId: storage.id,
|
||||||
|
// authorizationConfig: { accessPointId: storage.accessPoint },
|
||||||
|
// transitEncryption: "ENABLED",
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
containerDefinitions: authFingerprintKey.result.apply(v => JSON.stringify([{
|
||||||
|
"essential": true,
|
||||||
|
"name": "nestri",
|
||||||
|
"memory": 1024,
|
||||||
|
"cpu": 200,
|
||||||
|
"gpu": 1,
|
||||||
|
"image": "ghcr.io/nestrilabs/nestri/runner:nightly",
|
||||||
|
"environment": [
|
||||||
|
{
|
||||||
|
"name": "RESOLUTION",
|
||||||
|
"value": "1920x1080"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "AUTH_FINGERPRINT",
|
||||||
|
"value": v
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "FRAMERATE",
|
||||||
|
"value": "60"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NESTRI_ROOM",
|
||||||
|
"value": "aws-testing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RELAY_URL",
|
||||||
|
"value": "https://relay.dathorse.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "NESTRI_PARAMS",
|
||||||
|
"value": "--verbose=true --video-codec=h264 --video-bitrate=4000 --video-bitrate-max=6000 --gpu-card-path=/dev/dri/card0"
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"mountPoints": [{ "containerPath": "/home/nestri", "sourceVolume": "host" }],
|
||||||
|
"disableNetworking": false,
|
||||||
|
"linuxParameter": {
|
||||||
|
"sharedMemorySize": 5120
|
||||||
|
},
|
||||||
|
"logConfiguration": {
|
||||||
|
"logDriver": "awslogs",
|
||||||
|
"options": {
|
||||||
|
"awslogs-group": "/ecs/nestri-gpu-prod",
|
||||||
|
"awslogs-region": "us-east-1",
|
||||||
|
"awslogs-stream-prefix": "nestri-gpu-task"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}]))
|
||||||
|
});
|
||||||
|
|
||||||
|
sst.Linkable.wrap(aws.ecs.TaskDefinition, (resource) => ({
|
||||||
|
properties: {
|
||||||
|
value: resource.arn,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
sst.Linkable.wrap(aws.ecs.Cluster, (resource) => ({
|
||||||
|
properties: {
|
||||||
|
value: resource.arn,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// userData: $interpolate`#!/bin/bash
|
||||||
|
// sudo rm /etc/sysconfig/docker
|
||||||
|
// echo DAEMON_MAXFILES=1048576 | sudo tee -a /etc/sysconfig/docker
|
||||||
|
// echo DAEMON_PIDFILE_TIMEOUT=10 | sud o tee -a /etc/sysconfig/docker
|
||||||
|
// echo OPTIONS="--default-ulimit nofile=32768:65536" | sudo tee -a /etc/sysconfig/docker
|
||||||
|
// sudo tee "/etc/docker/daemon.json" > /dev/null <<EOF
|
||||||
|
// {
|
||||||
|
// "default-runtime": "nvidia",
|
||||||
|
// "runtimes": {
|
||||||
|
// "nvidia": {
|
||||||
|
// "path": "/usr/bin/nvidia-container-runtime",
|
||||||
|
// "runtimeArgs": []
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// EOF
|
||||||
|
// sudo systemctl restart docker
|
||||||
|
// echo ECS_CLUSTER='${ecsCluster.name}' | sudo tee -a /etc/ecs/ecs.config
|
||||||
|
// echo ECS_ENABLE_GPU_SUPPORT=true | sudo tee -a /etc/ecs/ecs.config
|
||||||
|
// echo ECS_CONTAINER_STOP_TIMEOUT=3h | sudo tee -a /etc/ecs/ecs.config
|
||||||
|
// echo ECS_ENABLE_SPOT_INSTANCE_DRAINING=true | sudo tee -a /etc/ecs/ecs.config
|
||||||
|
// `,
|
||||||
|
|
||||||
|
// This is used for requesting a container to be deployed on AWS
|
||||||
|
// const queue = new sst.aws.Queue("PartyQueue", { fifo: true });
|
||||||
|
|
||||||
|
// queue.subscribe({ handler: "packages/functions/src/party/subscriber.handler", permissions:{}, link:[taskF]})
|
||||||
|
// const authRes = $interpolate`${authFingerprintKey.result}`
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
import { domain } from "./dns";
|
|
||||||
|
|
||||||
export const email = new sst.aws.Email("Email",{
|
|
||||||
sender: domain,
|
|
||||||
dns: sst.cloudflare.dns(),
|
|
||||||
})
|
|
||||||
33
infra/party.ts
Normal file
33
infra/party.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
// This is for the websocket/MQTT endpoint that helps the API communicate with the container
|
||||||
|
// [API] <-> party <-websocket-> container
|
||||||
|
// The container is it's own this, and can listen to Websocket connections to start or stop a Steam Game
|
||||||
|
|
||||||
|
// import { authFingerprintKey } from "./auth";
|
||||||
|
// import { ecsCluster, gpuTaskDefinition } from "./cluster";
|
||||||
|
|
||||||
|
// export const party = new sst.aws.Realtime("Party", {
|
||||||
|
// authorizer: "packages/functions/src/party/authorizer.handler"
|
||||||
|
// });
|
||||||
|
|
||||||
|
// export const partyFn = new sst.aws.Function("NestriPartyFn", {
|
||||||
|
// handler: "packages/functions/src/party/create.handler",
|
||||||
|
// // link: [queue],
|
||||||
|
// link: [authFingerprintKey],
|
||||||
|
// environment: {
|
||||||
|
// TASK_DEFINITION: gpuTaskDefinition.arn,
|
||||||
|
// // AUTH_FINGERPRINT: authFingerprintKey.result,
|
||||||
|
// ECS_CLUSTER: ecsCluster.arn,
|
||||||
|
// },
|
||||||
|
// permissions: [
|
||||||
|
// {
|
||||||
|
// effect: "allow",
|
||||||
|
// actions: ["ecs:RunTask"],
|
||||||
|
// resources: [gpuTaskDefinition.arn]
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// url: true,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// export const outputs = {
|
||||||
|
// partyFunction: partyFn.url
|
||||||
|
// }
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
import { vpc } from "./vpc";
|
|
||||||
import { isPermanentStage } from "./stage";
|
|
||||||
|
|
||||||
// TODO: Add a dev db to use, this will help with running zero locally... and testing it
|
|
||||||
export const postgres = new sst.aws.Aurora("Database", {
|
|
||||||
vpc,
|
|
||||||
engine: "postgres",
|
|
||||||
scaling: isPermanentStage
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
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"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
179
infra/relay.ts
Normal file
179
infra/relay.ts
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
// const vpc = new sst.aws.Vpc("NestriRelayVpc", { az: 2 })
|
||||||
|
// import { subnet1, subnet2, securityGroup } from "./vpc"
|
||||||
|
|
||||||
|
// const taskExecutionRole = new aws.iam.Role('NestriRelayExecutionRole', {
|
||||||
|
// assumeRolePolicy: JSON.stringify({
|
||||||
|
// Version: '2012-10-17',
|
||||||
|
// Statement: [
|
||||||
|
// {
|
||||||
|
// Effect: 'Allow',
|
||||||
|
// Principal: {
|
||||||
|
// Service: 'ecs-tasks.amazonaws.com',
|
||||||
|
// },
|
||||||
|
// Action: 'sts:AssumeRole',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// }),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const taskRole = new aws.iam.Role('NestriRelayTaskRole', {
|
||||||
|
// assumeRolePolicy: JSON.stringify({
|
||||||
|
// Version: '2012-10-17',
|
||||||
|
// Statement: [
|
||||||
|
// {
|
||||||
|
// Effect: 'Allow',
|
||||||
|
// Principal: {
|
||||||
|
// Service: 'ecs-tasks.amazonaws.com',
|
||||||
|
// },
|
||||||
|
// Action: 'sts:AssumeRole',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// }),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.cloudwatch.LogGroup('NestriRelayLogGroup', {
|
||||||
|
// name: '/ecs/nestri-relay',
|
||||||
|
// retentionInDays: 7,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.iam.RolePolicyAttachment('NestriRelayExecutionRoleAttachment', {
|
||||||
|
// policyArn: 'arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy',
|
||||||
|
// role: taskRole,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const logPolicy = new aws.iam.Policy('NestriRelayLogPolicy', {
|
||||||
|
// policy: JSON.stringify({
|
||||||
|
// Version: '2012-10-17',
|
||||||
|
// Statement: [
|
||||||
|
// {
|
||||||
|
// Effect: 'Allow',
|
||||||
|
// Action: ['logs:CreateLogStream', 'logs:PutLogEvents'],
|
||||||
|
// Resource: 'arn:aws:logs:*:*:*',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// }),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.iam.RolePolicyAttachment('NestriRelayTaskRoleAttachment', {
|
||||||
|
// policyArn: logPolicy.arn,
|
||||||
|
// role: taskExecutionRole,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const taskDefinition = new aws.ecs.TaskDefinition("NestriRelayTask", {
|
||||||
|
// family: "NestriRelay",
|
||||||
|
// cpu: "1024",
|
||||||
|
// memory: "2048",
|
||||||
|
// networkMode: "awsvpc",
|
||||||
|
// taskRoleArn: taskRole.arn,
|
||||||
|
// requiresCompatibilities: ["FARGATE"],
|
||||||
|
// executionRoleArn: taskExecutionRole.arn,
|
||||||
|
// containerDefinitions: JSON.stringify([{
|
||||||
|
// name: "nestri-relay",
|
||||||
|
// essential: true,
|
||||||
|
// memory: 2048,
|
||||||
|
// image: "ghcr.io/nestrilabs/nestri/relay:nightly",
|
||||||
|
// portMappings: [
|
||||||
|
// // HTTP port
|
||||||
|
// {
|
||||||
|
// protocol: "tcp",
|
||||||
|
// hostPort: 80,
|
||||||
|
// containerPort: 80,
|
||||||
|
// },
|
||||||
|
// // UDP port range (1,000 ports)
|
||||||
|
// {
|
||||||
|
// containerPortRange: "10000-11000",
|
||||||
|
// protocol: "udp",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// "environment": [
|
||||||
|
// {
|
||||||
|
// name: "ENDPOINT_PORT",
|
||||||
|
// value: "80"
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// logConfiguration: {
|
||||||
|
// logDriver: 'awslogs',
|
||||||
|
// options: {
|
||||||
|
// 'awslogs-group': '/ecs/nestri-relay',
|
||||||
|
// 'awslogs-region': 'us-east-1',
|
||||||
|
// 'awslogs-stream-prefix': 'ecs',
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }]),
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const relayCluster = new aws.ecs.Cluster('NestriRelay');
|
||||||
|
|
||||||
|
// new aws.ecs.Service('NestriRelayService', {
|
||||||
|
// name: 'NestriRelayService',
|
||||||
|
// cluster: relayCluster.arn,
|
||||||
|
// desiredCount: 1,
|
||||||
|
// launchType: 'FARGATE',
|
||||||
|
// taskDefinition: taskDefinition.arn,
|
||||||
|
// deploymentCircuitBreaker: {
|
||||||
|
// enable: true,
|
||||||
|
// rollback: true,
|
||||||
|
// },
|
||||||
|
// enableExecuteCommand: true,
|
||||||
|
// networkConfiguration: {
|
||||||
|
// assignPublicIp: true,
|
||||||
|
// subnets: [subnet1.id, subnet2.id],
|
||||||
|
// securityGroups: [securityGroup.id],
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
//FIXME: I cannot create Global Accelerators (Something to do with Quotas - Yet my account is fine)
|
||||||
|
// const usWest2 = new aws.Provider("GlobalAccelerator", { region: aws.Region.USWest2 })
|
||||||
|
|
||||||
|
// const accelerator = new aws.globalaccelerator.Accelerator('Accelerator', {
|
||||||
|
// name: 'NestriRelayAccelerator',
|
||||||
|
// enabled: true,
|
||||||
|
// ipAddressType: 'IPV4',
|
||||||
|
// }, { provider: usWest2 });
|
||||||
|
|
||||||
|
// const httpListener = new aws.globalaccelerator.Listener('TcpListener', {
|
||||||
|
// acceleratorArn: accelerator.id,
|
||||||
|
// clientAffinity: 'SOURCE_IP',
|
||||||
|
// protocol: 'TCP',
|
||||||
|
// portRanges: [{
|
||||||
|
// fromPort: 80,
|
||||||
|
// toPort: 80,
|
||||||
|
// }],
|
||||||
|
// }, { provider: usWest2 });
|
||||||
|
|
||||||
|
// const udpListener = new aws.globalaccelerator.Listener('UdpListener', {
|
||||||
|
// acceleratorArn: accelerator.id,
|
||||||
|
// clientAffinity: 'SOURCE_IP',
|
||||||
|
// protocol: 'UDP',
|
||||||
|
// portRanges: [{
|
||||||
|
// fromPort: 10000,
|
||||||
|
// toPort: 11000,
|
||||||
|
// }],
|
||||||
|
// }, { provider: usWest2 });
|
||||||
|
|
||||||
|
// new aws.globalaccelerator.EndpointGroup('TcpRelay', {
|
||||||
|
// listenerArn: httpListener.id,
|
||||||
|
// // healthCheckPath: '/',
|
||||||
|
// endpointGroupRegion: aws.Region.USEast1,
|
||||||
|
// endpointConfigurations: [{
|
||||||
|
// clientIpPreservationEnabled: true,
|
||||||
|
// endpointId: subnet1.id, //vpc.publicSubnets[0].apply(i => i),
|
||||||
|
// weight: 100,
|
||||||
|
// }],
|
||||||
|
// }, { provider: usWest2 });
|
||||||
|
|
||||||
|
// new aws.globalaccelerator.EndpointGroup('UdpRelay', {
|
||||||
|
// listenerArn: udpListener.id,
|
||||||
|
// // healthCheckPort: 80,
|
||||||
|
// // healthCheckPath: "/",
|
||||||
|
// endpointGroupRegion: aws.Region.USEast1,
|
||||||
|
// endpointConfigurations: [{
|
||||||
|
// clientIpPreservationEnabled: true,
|
||||||
|
// endpointId: subnet1.id,//vpc.publicSubnets[0].apply(i => i),
|
||||||
|
// weight: 100,
|
||||||
|
// }],
|
||||||
|
// }, { provider: usWest2 });
|
||||||
|
|
||||||
|
// export const outputs = {
|
||||||
|
// relay: accelerator.dnsName
|
||||||
|
// }
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export const secret = {
|
|
||||||
PolarSecret: new sst.Secret("PolarSecret", process.env.POLAR_API_KEY),
|
|
||||||
GithubClientID: new sst.Secret("GithubClientID"),
|
|
||||||
DiscordClientID: new sst.Secret("DiscordClientID"),
|
|
||||||
PolarWebhookSecret: new sst.Secret("PolarWebhookSecret"),
|
|
||||||
GithubClientSecret: new sst.Secret("GithubClientSecret"),
|
|
||||||
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);
|
|
||||||
13
infra/secrets.ts
Normal file
13
infra/secrets.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export const secret = {
|
||||||
|
LoopsApiKey: new sst.Secret("LoopsApiKey"),
|
||||||
|
InstantAppId: new sst.Secret("InstantAppId"),
|
||||||
|
AwsSecretKey: new sst.Secret("AwsSecretKey"),
|
||||||
|
AwsAccessKey: new sst.Secret("AwsAccessKey"),
|
||||||
|
GithubClientID: new sst.Secret("GithubClientID"),
|
||||||
|
DiscordClientID: new sst.Secret("DiscordClientID"),
|
||||||
|
GithubClientSecret: new sst.Secret("GithubClientSecret"),
|
||||||
|
InstantAdminToken: new sst.Secret("InstantAdminToken"),
|
||||||
|
DiscordClientSecret: new sst.Secret("DiscordClientSecret"),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const allSecrets = Object.values(secret);
|
||||||
19
infra/ssh.ts
Normal file
19
infra/ssh.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { resolve } from "path";
|
||||||
|
import { writeFileSync } from "fs";
|
||||||
|
|
||||||
|
export const privateKey = new tls.PrivateKey("NestriGPUPrivateKey", {
|
||||||
|
algorithm: "RSA",
|
||||||
|
rsaBits: 4096,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Just in case you want to SSH
|
||||||
|
export const sshKey = new aws.ec2.KeyPair("NestriGPUKey", {
|
||||||
|
keyName: "NestriGPUKeyProd",
|
||||||
|
publicKey: privateKey.publicKeyOpenssh
|
||||||
|
})
|
||||||
|
|
||||||
|
export const keyPath = privateKey.privateKeyOpenssh.apply((key) => {
|
||||||
|
const path = "key_ssh";
|
||||||
|
writeFileSync(path, key, { mode: 0o600 });
|
||||||
|
return resolve(path);
|
||||||
|
});
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
new sst.x.DevCommand("Steam", {
|
|
||||||
dev: {
|
|
||||||
command: "bun dev",
|
|
||||||
directory: "packages/steam",
|
|
||||||
autostart: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1 +1,4 @@
|
|||||||
export const storage = new sst.aws.Bucket("Storage");
|
// export const vpc = new sst.aws.Vpc("Vpc")
|
||||||
|
|
||||||
|
// export const storage = new sst.aws.Efs("GameStorage",{ vpc })
|
||||||
|
// //
|
||||||
112
infra/vpc.ts
112
infra/vpc.ts
@@ -1,11 +1,103 @@
|
|||||||
import { isPermanentStage } from "./stage";
|
// export const vpc = new aws.ec2.Vpc('NestriVpc', {
|
||||||
|
// cidrBlock: '172.16.0.0/16',
|
||||||
|
// });
|
||||||
|
|
||||||
export const vpc = isPermanentStage
|
// export const subnet1 = new aws.ec2.Subnet('NestriSubnet1', {
|
||||||
? new sst.aws.Vpc("VPC", {
|
// vpcId: vpc.id,
|
||||||
az: 2,
|
// cidrBlock: '172.16.1.0/24',
|
||||||
// For lambdas to work in this VPC
|
// // cidrBlock: '110.0.12.0/22',
|
||||||
nat: "ec2",
|
// availabilityZone: 'us-east-1a',
|
||||||
// For SST tunnel to work
|
// });
|
||||||
bastion: true,
|
|
||||||
})
|
// export const subnet2 = new aws.ec2.Subnet('NestriSubnet2', {
|
||||||
: sst.aws.Vpc.get("VPC", "vpc-0beb1cdc21a725748");
|
// vpcId: vpc.id,
|
||||||
|
// cidrBlock: '172.16.2.0/24',
|
||||||
|
// // cidrBlock: '10.0.20.0/22',
|
||||||
|
// availabilityZone: 'us-east-1b',
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const internetGateway = new aws.ec2.InternetGateway('NestriInternetGateway', {
|
||||||
|
// vpcId: vpc.id,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const routeTable = new aws.ec2.RouteTable('NestriRouteTable', {
|
||||||
|
// vpcId: vpc.id,
|
||||||
|
// routes: [
|
||||||
|
// {
|
||||||
|
// cidrBlock: '0.0.0.0/0',
|
||||||
|
// gatewayId: internetGateway.id,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.ec2.RouteTableAssociation('NestriSubnet1RouteTable', {
|
||||||
|
// subnetId: subnet1.id,
|
||||||
|
// routeTableId: routeTable.id,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.ec2.RouteTableAssociation('NestriSubnet2RouteTable', {
|
||||||
|
// subnetId: subnet2.id,
|
||||||
|
// routeTableId: routeTable.id,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // const vpc = new sst.aws.Vpc("NestriRelayVpc")
|
||||||
|
|
||||||
|
// export const securityGroup = new aws.ec2.SecurityGroup("NestriSecurityGroup", {
|
||||||
|
// vpcId: vpc.id,
|
||||||
|
// description: "Managed thru SST",
|
||||||
|
// ingress: [
|
||||||
|
// {
|
||||||
|
// protocol: "tcp",
|
||||||
|
// fromPort: 80,
|
||||||
|
// toPort: 80,
|
||||||
|
// cidrBlocks: ["0.0.0.0/0"],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// protocol: "udp",
|
||||||
|
// fromPort: 10000,
|
||||||
|
// toPort: 20000,
|
||||||
|
// cidrBlocks: ["0.0.0.0/0"],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// egress: [
|
||||||
|
// {
|
||||||
|
// protocol: "-1",
|
||||||
|
// cidrBlocks: ["0.0.0.0/0"],
|
||||||
|
// fromPort: 0,
|
||||||
|
// toPort: 0
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const loadBalancer = new aws.lb.LoadBalancer('NestriVpcLoadBalancer', {
|
||||||
|
// name: 'NestriVpcLoadBalancer',
|
||||||
|
// internal: false,
|
||||||
|
// securityGroups: [securityGroup.id],
|
||||||
|
// subnets: vpc.publicSubnets
|
||||||
|
// });
|
||||||
|
|
||||||
|
// const targetGroup = new aws.lb.TargetGroup('NestriVpcTargetGroup', {
|
||||||
|
// name: 'NestriVpcTargetGroup',
|
||||||
|
// port: 80,
|
||||||
|
// protocol: 'HTTP',
|
||||||
|
// targetType: 'ip',
|
||||||
|
// vpcId: vpc.id,
|
||||||
|
// healthCheck: {
|
||||||
|
// path: '/',
|
||||||
|
// protocol: 'HTTP',
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// new aws.lb.Listener('NestriVpcLoadBalancerListener', {
|
||||||
|
// loadBalancerArn: loadBalancer.arn,
|
||||||
|
// port: 80,
|
||||||
|
// protocol: 'HTTP',
|
||||||
|
// defaultActions: [
|
||||||
|
// {
|
||||||
|
// type: 'forward',
|
||||||
|
// targetGroupArn: targetGroup.arn,
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // export const subnets = [subnet1, subnet2]
|
||||||
|
|||||||
23
infra/www.ts
23
infra/www.ts
@@ -1,23 +0,0 @@
|
|||||||
// 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";
|
|
||||||
|
|
||||||
new sst.aws.StaticSite("Web", {
|
|
||||||
path: "packages/www",
|
|
||||||
build: {
|
|
||||||
output: "./dist",
|
|
||||||
command: "bun run build",
|
|
||||||
},
|
|
||||||
domain: {
|
|
||||||
dns: sst.cloudflare.dns(),
|
|
||||||
name: "console." + domain
|
|
||||||
},
|
|
||||||
environment: {
|
|
||||||
VITE_API_URL: api.url,
|
|
||||||
VITE_STAGE: $app.stage,
|
|
||||||
VITE_AUTH_URL: auth.url,
|
|
||||||
VITE_ZERO_URL: zero.url,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
196
infra/zero.ts
196
infra/zero.ts
@@ -1,196 +0,0 @@
|
|||||||
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_SHARD_ID: $app.stage,
|
|
||||||
ZERO_AUTH_JWKS_URL: $interpolate`${auth.url}/.well-known/jwks.json`,
|
|
||||||
...($dev
|
|
||||||
? {
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
ZERO_LITESTREAM_BACKUP_URL: $interpolate`s3://${storage.name}/zero`,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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",
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
wait: true,
|
|
||||||
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",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
29
nestri.sln
29
nestri.sln
@@ -1,29 +0,0 @@
|
|||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
|
||||||
# Visual Studio Version 17
|
|
||||||
VisualStudioVersion = 17.5.2.0
|
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "packages", "packages", "{809F86A1-1C4C-B159-0CD4-DF9D33D876CE}"
|
|
||||||
EndProject
|
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "steam", "packages\steam\steam.csproj", "{96118F95-BF02-0ED3-9042-36FA1B740D67}"
|
|
||||||
EndProject
|
|
||||||
Global
|
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
|
||||||
Debug|Any CPU = Debug|Any CPU
|
|
||||||
Release|Any CPU = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
|
||||||
{96118F95-BF02-0ED3-9042-36FA1B740D67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
|
||||||
{96118F95-BF02-0ED3-9042-36FA1B740D67}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
|
||||||
{96118F95-BF02-0ED3-9042-36FA1B740D67}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
|
||||||
{96118F95-BF02-0ED3-9042-36FA1B740D67}.Release|Any CPU.Build.0 = Release|Any CPU
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
|
||||||
HideSolutionNode = FALSE
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(NestedProjects) = preSolution
|
|
||||||
{96118F95-BF02-0ED3-9042-36FA1B740D67} = {809F86A1-1C4C-B159-0CD4-DF9D33D876CE}
|
|
||||||
EndGlobalSection
|
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
|
||||||
SolutionGuid = {526AD703-4D15-43CF-B7C0-83F10D3158DB}
|
|
||||||
EndGlobalSection
|
|
||||||
EndGlobal
|
|
||||||
39
package.json
39
package.json
@@ -1,41 +1,34 @@
|
|||||||
{
|
{
|
||||||
"name": "nestri",
|
"name": "nestri",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "turbo build",
|
||||||
|
"dev": "turbo dev",
|
||||||
|
"sst": "sst dev",
|
||||||
|
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
||||||
|
"lint": "turbo lint"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@cloudflare/workers-types": "4.20240821.1",
|
"@cloudflare/workers-types": "4.20240821.1",
|
||||||
"@pulumi/pulumi": "^3.134.0",
|
"@pulumi/pulumi": "^3.134.0",
|
||||||
"@types/aws-lambda": "8.10.147",
|
"@types/aws-lambda": "8.10.145",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"packageManager": "bun@1.2.4",
|
"packageManager": "bun@1.1.18",
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
|
|
||||||
"sso": "aws sso login --sso-session=nestri --no-browser --use-device-code"
|
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"@openauthjs/openauth": "0.4.3",
|
|
||||||
"@rocicorp/zero": "0.16.2025022000"
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
},
|
|
||||||
"trustedDependencies": [
|
|
||||||
"core-js-pure",
|
|
||||||
"esbuild",
|
|
||||||
"protobufjs",
|
|
||||||
"@rocicorp/zero-sqlite3",
|
|
||||||
"workerd"
|
|
||||||
],
|
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
|
"trustedDependencies": [
|
||||||
|
"core-js-pure",
|
||||||
|
"esbuild",
|
||||||
|
"workerd"
|
||||||
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sst": "^3.11.21"
|
"sst": "3.6.27"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { Resource } from "sst";
|
|
||||||
import { defineConfig } from "drizzle-kit";
|
|
||||||
|
|
||||||
const connection = {
|
|
||||||
user: Resource.Database.username,
|
|
||||||
password: Resource.Database.password,
|
|
||||||
host: Resource.Database.host,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
verbose: true,
|
|
||||||
strict: true,
|
|
||||||
out: "./migrations",
|
|
||||||
dialect: "postgresql",
|
|
||||||
dbCredentials: {
|
|
||||||
url: `postgres://${connection.user}:${connection.password}@${connection.host}/nestri`,
|
|
||||||
},
|
|
||||||
schema: "./src/**/*.sql.ts",
|
|
||||||
});
|
|
||||||
30
packages/core/instant.perms.ts
Normal file
30
packages/core/instant.perms.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Docs: https://www.instantdb.com/docs/permissions
|
||||||
|
|
||||||
|
import type { InstantRules } from "@instantdb/core";
|
||||||
|
|
||||||
|
const rules = {
|
||||||
|
/**
|
||||||
|
* Welcome to Instant's permission system!
|
||||||
|
* Right now your rules are empty. To start filling them in, check out the docs:
|
||||||
|
* https://www.instantdb.com/docs/permissions
|
||||||
|
*
|
||||||
|
* Here's an example to give you a feel:
|
||||||
|
* posts: {
|
||||||
|
* allow: {
|
||||||
|
* view: "true",
|
||||||
|
* create: "isOwner",
|
||||||
|
* update: "isOwner",
|
||||||
|
* delete: "isOwner",
|
||||||
|
* },
|
||||||
|
* bind: ["isOwner", "auth.id != null && auth.id == data.ownerId"],
|
||||||
|
* },
|
||||||
|
*/
|
||||||
|
// $default: {
|
||||||
|
// allow: {
|
||||||
|
// $default: "isOwner"
|
||||||
|
// },
|
||||||
|
// bind: ["isOwner", "auth.id != null && auth.id == data.ownerID"],
|
||||||
|
// }
|
||||||
|
} satisfies InstantRules;
|
||||||
|
|
||||||
|
export default rules;
|
||||||
123
packages/core/instant.schema.ts
Normal file
123
packages/core/instant.schema.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import { i } from "@instantdb/core";
|
||||||
|
|
||||||
|
const _schema = i.schema({
|
||||||
|
entities: {
|
||||||
|
$users: i.entity({
|
||||||
|
email: i.string().unique().indexed(),
|
||||||
|
}),
|
||||||
|
// machines: i.entity({
|
||||||
|
// hostname: i.string(),
|
||||||
|
// fingerprint: i.string().unique().indexed(),
|
||||||
|
// deletedAt: i.date().optional().indexed(),
|
||||||
|
// createdAt: i.date()
|
||||||
|
// }),
|
||||||
|
tasks: i.entity({
|
||||||
|
type: i.string(),
|
||||||
|
lastStatus: i.string(),
|
||||||
|
healthStatus: i.string(),
|
||||||
|
startedAt: i.string(),
|
||||||
|
lastUpdated: i.date(),
|
||||||
|
stoppedAt: i.string().optional(),
|
||||||
|
taskID: i.string().unique().indexed()
|
||||||
|
}),
|
||||||
|
instances: i.entity({
|
||||||
|
hostname: i.string(),
|
||||||
|
lastActive: i.date().optional(),
|
||||||
|
createdAt: i.date()
|
||||||
|
}),
|
||||||
|
profiles: i.entity({
|
||||||
|
avatarUrl: i.string().optional(),
|
||||||
|
username: i.string().indexed(),
|
||||||
|
status: i.string().indexed(),
|
||||||
|
updatedAt: i.date().indexed(),
|
||||||
|
createdAt: i.date(),
|
||||||
|
discriminator: i.string().indexed()
|
||||||
|
}),
|
||||||
|
teams: i.entity({
|
||||||
|
name: i.string(),
|
||||||
|
slug: i.string().unique().indexed(),
|
||||||
|
deletedAt: i.date().optional(),//.indexed(),
|
||||||
|
updatedAt: i.date(),
|
||||||
|
createdAt: i.date(),
|
||||||
|
}),
|
||||||
|
// games: i.entity({
|
||||||
|
// name: i.string(),
|
||||||
|
// steamID: i.number().unique().indexed(),
|
||||||
|
// }),
|
||||||
|
sessions: i.entity({
|
||||||
|
startedAt: i.date(),
|
||||||
|
endedAt: i.date().optional().indexed(),
|
||||||
|
public: i.boolean().indexed(),
|
||||||
|
}),
|
||||||
|
subscriptions: i.entity({
|
||||||
|
checkoutID: i.string(),
|
||||||
|
canceledAt: i.date(),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
links: {
|
||||||
|
UserSubscriptions: {
|
||||||
|
forward: { on: "subscriptions", has: "one", label: "owner" },
|
||||||
|
reverse: { on: "$users", has: "many", label: "subscriptions" }
|
||||||
|
},
|
||||||
|
UserProfiles: {
|
||||||
|
forward: { on: "profiles", has: "one", label: "owner" },
|
||||||
|
reverse: { on: "$users", has: "one", label: "profile" }
|
||||||
|
},
|
||||||
|
UserTasks: {
|
||||||
|
forward: { on: "tasks", has: "one", label: "owner" },
|
||||||
|
reverse: { on: "$users", has: "many", label: "tasks" }
|
||||||
|
},
|
||||||
|
TaskSessions: {
|
||||||
|
forward: { on: "tasks", has: "many", label: "sessions" },
|
||||||
|
reverse: { on: "sessions", has: "one", label: "task" }
|
||||||
|
},
|
||||||
|
UserSession: {
|
||||||
|
forward: { on: "sessions", has: "one", label: "owner" },
|
||||||
|
reverse: { on: "$users", has: "many", label: "sessions" }
|
||||||
|
},
|
||||||
|
TeamsOwned: {
|
||||||
|
forward: { on: "teams", has: "one", label: "owner" },
|
||||||
|
reverse: { on: "$users", has: "many", label: "teamsOwned" },
|
||||||
|
},
|
||||||
|
TeamsJoined: {
|
||||||
|
forward: { on: "teams", has: "many", label: "members" },
|
||||||
|
reverse: { on: "$users", has: "many", label: "teamsJoined" },
|
||||||
|
},
|
||||||
|
// UserMachines: {
|
||||||
|
// forward: { on: "machines", has: "one", label: "owner" },
|
||||||
|
// reverse: { on: "$users", has: "many", label: "machines" }
|
||||||
|
// },
|
||||||
|
// UserGames: {
|
||||||
|
// forward: { on: "games", has: "many", label: "owners" },
|
||||||
|
// reverse: { on: "$users", has: "many", label: "games" }
|
||||||
|
// },
|
||||||
|
// TeamInstances: {
|
||||||
|
// forward: { on: "instances", has: "many", label: "owners" },
|
||||||
|
// reverse: { on: "teams", has: "many", label: "instances" }
|
||||||
|
// },
|
||||||
|
// MachineSessions: {
|
||||||
|
// forward: { on: "machines", has: "many", label: "sessions" },
|
||||||
|
// reverse: { on: "sessions", has: "one", label: "machine" }
|
||||||
|
// },
|
||||||
|
// GamesMachines: {
|
||||||
|
// forward: { on: "machines", has: "many", label: "games" },
|
||||||
|
// reverse: { on: "games", has: "many", label: "machines" }
|
||||||
|
// },
|
||||||
|
// GameSessions: {
|
||||||
|
// forward: { on: "games", has: "many", label: "sessions" },
|
||||||
|
// reverse: { on: "sessions", has: "one", label: "game" }
|
||||||
|
// },
|
||||||
|
// UserSessions: {
|
||||||
|
// forward: { on: "sessions", has: "one", label: "owner" },
|
||||||
|
// reverse: { on: "$users", has: "many", label: "sessions" }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// This helps Typescript display nicer intellisense
|
||||||
|
type _AppSchema = typeof _schema;
|
||||||
|
interface AppSchema extends _AppSchema { }
|
||||||
|
const schema: AppSchema = _schema;
|
||||||
|
|
||||||
|
export type { AppSchema };
|
||||||
|
export default schema;
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
CREATE TABLE "member" (
|
|
||||||
"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,
|
|
||||||
"time_seen" timestamp with time zone,
|
|
||||||
"email" varchar(255) NOT NULL,
|
|
||||||
CONSTRAINT "member_team_id_id_pk" PRIMARY KEY("team_id","id")
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "team" (
|
|
||||||
"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,
|
|
||||||
"slug" varchar(255) NOT NULL,
|
|
||||||
"plan_type" text NOT NULL
|
|
||||||
);
|
|
||||||
--> statement-breakpoint
|
|
||||||
CREATE TABLE "user" (
|
|
||||||
"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,
|
|
||||||
"avatar_url" text,
|
|
||||||
"name" varchar(255) NOT NULL,
|
|
||||||
"discriminator" integer 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 INDEX "email_global" ON "member" USING btree ("email");--> 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,2 +0,0 @@
|
|||||||
DROP INDEX "team_slug";--> statement-breakpoint
|
|
||||||
CREATE UNIQUE INDEX "slug" ON "team" USING btree ("slug");
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
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");
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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");
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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";
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
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";
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "machine" DROP CONSTRAINT "machine_user_id_id_pk";--> statement-breakpoint
|
|
||||||
ALTER TABLE "machine" DROP COLUMN "user_id";
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "member" ADD COLUMN "role" text NOT NULL;--> statement-breakpoint
|
|
||||||
ALTER TABLE "team" DROP COLUMN "plan_type";
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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");
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
CREATE UNIQUE INDEX "steam_id" ON "steam" USING btree ("steam_id");--> statement-breakpoint
|
|
||||||
CREATE INDEX "steam_user_id" ON "steam" USING btree ("user_id");
|
|
||||||
@@ -1,294 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "f09034df-208a-42b3-b61f-f842921c6e24",
|
|
||||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
|
||||||
"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.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": {
|
|
||||||
"team_slug": {
|
|
||||||
"name": "team_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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,294 +0,0 @@
|
|||||||
{
|
|
||||||
"id": "6f428226-b5d8-4182-a676-d04f842f9ded",
|
|
||||||
"prevId": "f09034df-208a-42b3-b61f-f842921c6e24",
|
|
||||||
"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.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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,420 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,507 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,499 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,485 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,485 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,580 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,619 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,650 +0,0 @@
|
|||||||
{
|
|
||||||
"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": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "7",
|
|
||||||
"dialect": "postgresql",
|
|
||||||
"entries": [
|
|
||||||
{
|
|
||||||
"idx": 0,
|
|
||||||
"version": "7",
|
|
||||||
"when": 1741759978256,
|
|
||||||
"tag": "0000_flaky_matthew_murdock",
|
|
||||||
"breakpoints": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"idx": 1,
|
|
||||||
"version": "7",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -3,13 +3,6 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
|
||||||
"db:dev": "drizzle-kit",
|
|
||||||
"typecheck": "tsc --noEmit",
|
|
||||||
"db": "sst shell drizzle-kit",
|
|
||||||
"db:exec": "sst shell ../scripts/src/psql.sh",
|
|
||||||
"db:reset": "sst shell ../scripts/src/db-reset.sh"
|
|
||||||
},
|
|
||||||
"exports": {
|
"exports": {
|
||||||
"./*": "./src/*.ts"
|
"./*": "./src/*.ts"
|
||||||
},
|
},
|
||||||
@@ -19,22 +12,13 @@
|
|||||||
"aws4fetch": "^1.0.20",
|
"aws4fetch": "^1.0.20",
|
||||||
"loops": "^3.4.1",
|
"loops": "^3.4.1",
|
||||||
"mqtt": "^5.10.3",
|
"mqtt": "^5.10.3",
|
||||||
"remeda": "^2.21.2",
|
"remeda": "^2.19.0",
|
||||||
"ulid": "^2.3.0",
|
"ulid": "^2.3.0",
|
||||||
"uuid": "^11.0.3",
|
"uuid": "^11.0.3",
|
||||||
"zod": "^3.24.1",
|
"zod": "^3.24.1",
|
||||||
"zod-openapi": "^4.2.2"
|
"zod-openapi": "^4.2.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-iot-data-plane": "^3.758.0",
|
"@instantdb/admin": "^0.17.7"
|
||||||
"@aws-sdk/client-rds-data": "^3.758.0",
|
|
||||||
"@aws-sdk/client-sesv2": "^3.753.0",
|
|
||||||
"@instantdb/admin": "^0.17.7",
|
|
||||||
"@openauthjs/openauth": "*",
|
|
||||||
"@openauthjs/openevent": "^0.0.27",
|
|
||||||
"@polar-sh/sdk": "^0.26.1",
|
|
||||||
"drizzle-kit": "^0.30.5",
|
|
||||||
"drizzle-orm": "^0.40.0",
|
|
||||||
"postgres": "^3.4.5"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,142 +1,86 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { eq } from "./drizzle";
|
|
||||||
import { ErrorCodes, VisibleError } from "./error";
|
|
||||||
import { createContext } from "./context";
|
import { createContext } from "./context";
|
||||||
import { UserFlags, userTable } from "./user/user.sql";
|
import { VisibleError } from "./error";
|
||||||
import { useTransaction } from "./drizzle/transaction";
|
|
||||||
|
|
||||||
export const PublicActor = z.object({
|
export interface UserActor {
|
||||||
type: z.literal("public"),
|
type: "user";
|
||||||
properties: z.object({}),
|
properties: {
|
||||||
});
|
accessToken: string;
|
||||||
export type PublicActor = z.infer<typeof PublicActor>;
|
userID: string;
|
||||||
|
auth?:
|
||||||
|
| {
|
||||||
|
type: "personal";
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "oauth";
|
||||||
|
clientID: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const UserActor = z.object({
|
export interface DeviceActor {
|
||||||
type: z.literal("user"),
|
type: "device";
|
||||||
properties: z.object({
|
properties: {
|
||||||
userID: z.string(),
|
teamSlug: string;
|
||||||
email: z.string().nonempty(),
|
hostname: string;
|
||||||
}),
|
auth?:
|
||||||
});
|
| {
|
||||||
export type UserActor = z.infer<typeof UserActor>;
|
type: "personal";
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "oauth";
|
||||||
|
clientID: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export const MemberActor = z.object({
|
export interface PublicActor {
|
||||||
type: z.literal("member"),
|
type: "public";
|
||||||
properties: z.object({
|
properties: {};
|
||||||
memberID: z.string(),
|
}
|
||||||
teamID: z.string(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
export type MemberActor = z.infer<typeof MemberActor>;
|
|
||||||
|
|
||||||
export const SystemActor = z.object({
|
type Actor = UserActor | PublicActor | DeviceActor;
|
||||||
type: z.literal("system"),
|
export const ActorContext = createContext<Actor>();
|
||||||
properties: z.object({
|
|
||||||
teamID: z.string(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
export type SystemActor = z.infer<typeof SystemActor>;
|
|
||||||
|
|
||||||
export const MachineActor = z.object({
|
export function useCurrentUser() {
|
||||||
type: z.literal("machine"),
|
|
||||||
properties: z.object({
|
|
||||||
fingerprint: z.string(),
|
|
||||||
machineID: z.string(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
export type MachineActor = z.infer<typeof MachineActor>;
|
|
||||||
|
|
||||||
export const Actor = z.discriminatedUnion("type", [
|
|
||||||
MemberActor,
|
|
||||||
UserActor,
|
|
||||||
PublicActor,
|
|
||||||
SystemActor,
|
|
||||||
MachineActor
|
|
||||||
]);
|
|
||||||
export type Actor = z.infer<typeof Actor>;
|
|
||||||
|
|
||||||
export const ActorContext = createContext<Actor>("actor");
|
|
||||||
|
|
||||||
export const useActor = ActorContext.use;
|
|
||||||
export const withActor = ActorContext.with;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the user ID of the current actor.
|
|
||||||
*
|
|
||||||
* This function accesses the actor context and returns the `userID` if the current
|
|
||||||
* actor is of type "user". If the actor is not a user, it throws a `VisibleError`
|
|
||||||
* with an authentication error code, indicating that the caller is not authorized
|
|
||||||
* to access user-specific resources.
|
|
||||||
*
|
|
||||||
* @throws {VisibleError} When the current actor is not of type "user".
|
|
||||||
*/
|
|
||||||
export function useUserID() {
|
|
||||||
const actor = ActorContext.use();
|
const actor = ActorContext.use();
|
||||||
if (actor.type === "user") return actor.properties.userID;
|
if (actor.type === "user") return {
|
||||||
|
id:actor.properties.userID,
|
||||||
|
token: actor.properties.accessToken,
|
||||||
|
};
|
||||||
|
|
||||||
throw new VisibleError(
|
throw new VisibleError(
|
||||||
"authentication",
|
"auth",
|
||||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
"unauthorized",
|
||||||
`You don't have permission to access this resource`,
|
`You don't have permission to access this resource`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export function useCurrentDevice() {
|
||||||
* Retrieves the properties of the current user actor.
|
|
||||||
*
|
|
||||||
* This function obtains the current actor from the context and returns its properties if the actor is identified as a user.
|
|
||||||
* If the actor is not of type "user", it throws a {@link VisibleError} with an authentication error code,
|
|
||||||
* indicating that the user is not authorized to access user-specific resources.
|
|
||||||
*
|
|
||||||
* @returns The properties of the current user actor, typically including user-specific details such as userID and email.
|
|
||||||
* @throws {VisibleError} If the current actor is not a user.
|
|
||||||
*/
|
|
||||||
export function useUser() {
|
|
||||||
const actor = ActorContext.use();
|
const actor = ActorContext.use();
|
||||||
if (actor.type === "user") return actor.properties;
|
if (actor.type === "device") return {
|
||||||
|
hostname:actor.properties.hostname,
|
||||||
|
teamSlug: actor.properties.teamSlug
|
||||||
|
};
|
||||||
throw new VisibleError(
|
throw new VisibleError(
|
||||||
"authentication",
|
"auth",
|
||||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
"unauthorized",
|
||||||
`You don't have permission to access this resource`,
|
`You don't have permission to access this resource`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useActor() {
|
||||||
|
try {
|
||||||
|
return ActorContext.use();
|
||||||
|
} catch {
|
||||||
|
return { type: "public", properties: {} } as PublicActor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function assertActor<T extends Actor["type"]>(type: T) {
|
export function assertActor<T extends Actor["type"]>(type: T) {
|
||||||
const actor = useActor();
|
const actor = useActor();
|
||||||
if (actor.type !== type) {
|
if (actor.type !== type)
|
||||||
throw new Error(`Expected actor type ${type}, got ${actor.type}`);
|
throw new VisibleError("auth", "actor.invalid", `Actor is not "${type}"`);
|
||||||
}
|
|
||||||
|
|
||||||
return actor as Extract<Actor, { type: T }>;
|
return actor as Extract<Actor, { type: T }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current actor's team ID.
|
|
||||||
*
|
|
||||||
* @returns The team ID associated with the current actor.
|
|
||||||
* @throws {VisibleError} If the current actor does not have a {@link teamID} property.
|
|
||||||
*/
|
|
||||||
export function useTeam() {
|
|
||||||
const actor = useActor();
|
|
||||||
if ("teamID" in actor.properties) return actor.properties.teamID;
|
|
||||||
throw new VisibleError(
|
|
||||||
"authentication",
|
|
||||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
|
||||||
`Expected actor to have teamID`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the fingerprint of the current actor if the actor has a machine identity.
|
|
||||||
*
|
|
||||||
* @returns The fingerprint of the current machine actor.
|
|
||||||
* @throws {VisibleError} If the current actor does not have a machine identity.
|
|
||||||
*/
|
|
||||||
export function useMachine() {
|
|
||||||
const actor = useActor();
|
|
||||||
if ("machineID" in actor.properties) return actor.properties.fingerprint;
|
|
||||||
throw new VisibleError(
|
|
||||||
"authentication",
|
|
||||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
|
||||||
`Expected actor to have fingerprint`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
90
packages/core/src/aws/client.ts
Normal file
90
packages/core/src/aws/client.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
import { Resource } from "sst";
|
||||||
|
import { doubleFn, fn } from "../utils";
|
||||||
|
import { AwsClient } from "aws4fetch";
|
||||||
|
import { DescribeTasksCommandOutput, StopTaskCommandOutput, type RunTaskCommandOutput } from "@aws-sdk/client-ecs";
|
||||||
|
|
||||||
|
|
||||||
|
export module Aws {
|
||||||
|
export const client = async () => {
|
||||||
|
return new AwsClient({
|
||||||
|
accessKeyId: Resource.AwsAccessKey.value,
|
||||||
|
secretAccessKey: Resource.AwsSecretKey.value,
|
||||||
|
region: "us-east-1",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EcsRunTask = fn(z.object({
|
||||||
|
cluster: z.string(),
|
||||||
|
count: z.number(),
|
||||||
|
taskDefinition: z.string(),
|
||||||
|
launchType: z.enum(["EC2", "FARGATE"]),
|
||||||
|
overrides: z.object({
|
||||||
|
containerOverrides: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
environment: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
value: z.string().or(z.number())
|
||||||
|
}).array()
|
||||||
|
}).array()
|
||||||
|
})
|
||||||
|
}), async (body) => {
|
||||||
|
|
||||||
|
const c = await client();
|
||||||
|
|
||||||
|
const url = new URL(`https://ecs.${c.region}.amazonaws.com/`)
|
||||||
|
|
||||||
|
const res = await c.fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"X-Amz-Target": "AmazonEC2ContainerServiceV20141113.RunTask",
|
||||||
|
"Content-Type": "application/x-amz-json-1.1",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
|
||||||
|
return await res.json() as RunTaskCommandOutput
|
||||||
|
})
|
||||||
|
|
||||||
|
export const EcsDescribeTasks = fn(z.object({ tasks: z.string().array(), cluster: z.string() }), async (body) => {
|
||||||
|
const c = await client();
|
||||||
|
|
||||||
|
const url = new URL(`https://ecs.${c.region}.amazonaws.com/`)
|
||||||
|
|
||||||
|
const res = await c.fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"X-Amz-Target": "AmazonEC2ContainerServiceV20141113.DescribeTasks",
|
||||||
|
"Content-Type": "application/x-amz-json-1.1",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
|
||||||
|
return await res.json() as DescribeTasksCommandOutput
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export const EcsStopTask = fn(z.object({
|
||||||
|
cluster: z.string().optional(),
|
||||||
|
reason: z.string().optional(),
|
||||||
|
task: z.string()
|
||||||
|
}), async (body) => {
|
||||||
|
const c = await client();
|
||||||
|
|
||||||
|
const url = new URL(`https://ecs.${c.region}.amazonaws.com/`)
|
||||||
|
|
||||||
|
const res = await c.fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"X-Amz-Target": "AmazonEC2ContainerServiceV20141113.StopTask",
|
||||||
|
"Content-Type": "application/x-amz-json-1.1",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
})
|
||||||
|
|
||||||
|
return await res.json() as StopTaskCommandOutput
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import { sql } from "drizzle-orm";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import "zod-openapi/extend";
|
import "zod-openapi/extend";
|
||||||
|
|
||||||
export namespace Common {
|
export module Common {
|
||||||
export const IdDescription = `Unique object identifier.
|
export const IdDescription = `Unique object identifier.
|
||||||
The format and length of IDs may change over time.`;
|
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";
|
import { AsyncLocalStorage } from "node:async_hooks";
|
||||||
|
|
||||||
export function createContext<T>(name: string) {
|
export function createContext<T>() {
|
||||||
const storage = new AsyncLocalStorage<T>();
|
const storage = new AsyncLocalStorage<T>();
|
||||||
return {
|
return {
|
||||||
use() {
|
use() {
|
||||||
const result = storage.getStore();
|
const result = storage.getStore();
|
||||||
if (!result) {
|
if (!result) {
|
||||||
throw new Error("Context not provided: " + name);
|
throw new Error("No context available");
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
with<R>(value: T, fn: () => R) {
|
with<R>(value: T, fn: () => R) {
|
||||||
return storage.run<R, any[]>(value, fn);
|
return storage.run<R>(value, fn);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
12
packages/core/src/database.ts
Normal file
12
packages/core/src/database.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Resource } from "sst";
|
||||||
|
import { init } from "@instantdb/admin";
|
||||||
|
import schema from "../instant.schema";
|
||||||
|
|
||||||
|
const databaseClient = () => init({
|
||||||
|
appId: Resource.InstantAppId.value,
|
||||||
|
adminToken: Resource.InstantAdminToken.value,
|
||||||
|
schema
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
export default databaseClient
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
export * from "drizzle-orm";
|
|
||||||
import { Resource } from "sst";
|
|
||||||
import postgres from "postgres";
|
|
||||||
import { drizzle } from "drizzle-orm/postgres-js";
|
|
||||||
|
|
||||||
const client = postgres({
|
|
||||||
idle_timeout: 30000,
|
|
||||||
connect_timeout: 30000,
|
|
||||||
host: Resource.Database.host,
|
|
||||||
database: Resource.Database.database,
|
|
||||||
user: Resource.Database.username,
|
|
||||||
password: Resource.Database.password,
|
|
||||||
port: Resource.Database.port,
|
|
||||||
max: parseInt(process.env.POSTGRES_POOL_MAX || "1"),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const db = drizzle(client, {});
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
import { db } from ".";
|
|
||||||
import {
|
|
||||||
PgTransaction,
|
|
||||||
PgTransactionConfig
|
|
||||||
} from "drizzle-orm/pg-core";
|
|
||||||
import {
|
|
||||||
PostgresJsQueryResultHKT
|
|
||||||
} from "drizzle-orm/postgres-js";
|
|
||||||
import { ExtractTablesWithRelations } from "drizzle-orm";
|
|
||||||
import { createContext } from "../context";
|
|
||||||
|
|
||||||
export type Transaction = PgTransaction<
|
|
||||||
PostgresJsQueryResultHKT,
|
|
||||||
Record<string, never>,
|
|
||||||
ExtractTablesWithRelations<Record<string, never>>
|
|
||||||
>;
|
|
||||||
|
|
||||||
type TxOrDb = Transaction | typeof db;
|
|
||||||
|
|
||||||
const TransactionContext = createContext<{
|
|
||||||
tx: Transaction;
|
|
||||||
effects: (() => void | Promise<void>)[];
|
|
||||||
}>("TransactionContext");
|
|
||||||
|
|
||||||
export async function useTransaction<T>(callback: (trx: TxOrDb) => Promise<T>) {
|
|
||||||
try {
|
|
||||||
const { tx } = TransactionContext.use();
|
|
||||||
return callback(tx);
|
|
||||||
} catch {
|
|
||||||
return callback(db);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function afterTx(effect: () => any | Promise<any>) {
|
|
||||||
try {
|
|
||||||
const { effects } = TransactionContext.use();
|
|
||||||
effects.push(effect);
|
|
||||||
} catch {
|
|
||||||
await effect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createTransaction<T>(
|
|
||||||
callback: (tx: Transaction) => Promise<T>,
|
|
||||||
isolationLevel?: PgTransactionConfig["isolationLevel"],
|
|
||||||
): Promise<T> {
|
|
||||||
try {
|
|
||||||
const { tx } = TransactionContext.use();
|
|
||||||
return callback(tx);
|
|
||||||
} catch {
|
|
||||||
const effects: (() => void | Promise<void>)[] = [];
|
|
||||||
const result = await db.transaction(
|
|
||||||
async (tx) => {
|
|
||||||
return TransactionContext.with({ tx, effects }, () => callback(tx));
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isolationLevel: isolationLevel || "read committed",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await Promise.all(effects.map((x) => x()));
|
|
||||||
return result as T;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { char, timestamp as rawTs } from "drizzle-orm/pg-core";
|
|
||||||
import { teamTable } from "../team/team.sql";
|
|
||||||
|
|
||||||
export const ulid = (name: string) => char(name, { length: 26 + 4 });
|
|
||||||
|
|
||||||
export const id = {
|
|
||||||
get id() {
|
|
||||||
return ulid("id").primaryKey().notNull();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const teamID = {
|
|
||||||
get id() {
|
|
||||||
return ulid("id").notNull();
|
|
||||||
},
|
|
||||||
get teamID() {
|
|
||||||
return ulid("team_id").notNull();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const userID = {
|
|
||||||
get id() {
|
|
||||||
return ulid("id").notNull();
|
|
||||||
},
|
|
||||||
get userID() {
|
|
||||||
return ulid("user_id").notNull();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const utc = (name: string) =>
|
|
||||||
rawTs(name, {
|
|
||||||
withTimezone: true,
|
|
||||||
// mode: "date"
|
|
||||||
});
|
|
||||||
|
|
||||||
export const timestamps = {
|
|
||||||
timeCreated: utc("time_created").notNull().defaultNow(),
|
|
||||||
timeUpdated: utc("time_updated").notNull().defaultNow(),
|
|
||||||
timeDeleted: utc("time_deleted"),
|
|
||||||
};
|
|
||||||
@@ -1,36 +1,45 @@
|
|||||||
import { Resource } from "sst";
|
import { LoopsClient } from "loops";
|
||||||
import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
|
import { Resource } from "sst/resource"
|
||||||
|
|
||||||
export namespace Email {
|
export namespace Email {
|
||||||
export const Client = new SESv2Client({});
|
export const Client = () => new LoopsClient(Resource.LoopsApiKey.value);
|
||||||
|
|
||||||
export async function send(
|
export async function send(
|
||||||
from: string,
|
|
||||||
to: string,
|
to: string,
|
||||||
subject: string,
|
|
||||||
body: string,
|
body: string,
|
||||||
) {
|
) {
|
||||||
from = from + "@" + Resource.Email.sender;
|
|
||||||
console.log("sending email", subject, from, to);
|
try {
|
||||||
await Client.send(
|
await Client().sendTransactionalEmail(
|
||||||
new SendEmailCommand({
|
{
|
||||||
Destination: {
|
transactionalId: "cm58pdf8d03upb5ecirnmvrfb",
|
||||||
ToAddresses: [to],
|
email: to,
|
||||||
},
|
dataVariables: {
|
||||||
Content: {
|
logincode: body
|
||||||
Simple: {
|
}
|
||||||
Body: {
|
}
|
||||||
Text: {
|
|
||||||
Data: body,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Subject: {
|
|
||||||
Data: subject,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
FromEmailAddress: `Nestri <${from}>`,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("error sending email", error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sendWelcome(
|
||||||
|
to: string,
|
||||||
|
name: string,
|
||||||
|
) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Client().sendTransactionalEmail(
|
||||||
|
{
|
||||||
|
transactionalId: "cm61jrbbx02twlstfwfcywt5u",
|
||||||
|
email: to,
|
||||||
|
dataVariables: {
|
||||||
|
name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("error sending email", error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,145 +1,9 @@
|
|||||||
import { z } from "zod"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard error response schema used for OpenAPI documentation
|
|
||||||
*/
|
|
||||||
export const ErrorResponse = z
|
|
||||||
.object({
|
|
||||||
type: z
|
|
||||||
.enum([
|
|
||||||
"validation",
|
|
||||||
"authentication",
|
|
||||||
"forbidden",
|
|
||||||
"not_found",
|
|
||||||
"already_exists",
|
|
||||||
"rate_limit",
|
|
||||||
"internal",
|
|
||||||
])
|
|
||||||
.openapi({
|
|
||||||
description: "The error type category",
|
|
||||||
examples: ["validation", "authentication"],
|
|
||||||
}),
|
|
||||||
code: z.string().openapi({
|
|
||||||
description: "Machine-readable error code identifier",
|
|
||||||
examples: ["invalid_parameter", "missing_required_field", "unauthorized"],
|
|
||||||
}),
|
|
||||||
message: z.string().openapi({
|
|
||||||
description: "Human-readable error message",
|
|
||||||
examples: ["The request was invalid", "Authentication required"],
|
|
||||||
}),
|
|
||||||
param: z
|
|
||||||
.string()
|
|
||||||
.optional()
|
|
||||||
.openapi({
|
|
||||||
description: "The parameter that caused the error (if applicable)",
|
|
||||||
examples: ["email", "user_id", "team_id"],
|
|
||||||
}),
|
|
||||||
details: z.any().optional().openapi({
|
|
||||||
description: "Additional error context information",
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.openapi({ ref: "ErrorResponse" });
|
|
||||||
|
|
||||||
export type ErrorResponseType = z.infer<typeof ErrorResponse>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standardized error codes for the API
|
|
||||||
*/
|
|
||||||
export const ErrorCodes = {
|
|
||||||
// Validation errors (400)
|
|
||||||
Validation: {
|
|
||||||
MISSING_REQUIRED_FIELD: "missing_required_field",
|
|
||||||
ALREADY_EXISTS: "resource_already_exists",
|
|
||||||
TEAM_ALREADY_EXISTS: "team_already_exists",
|
|
||||||
INVALID_PARAMETER: "invalid_parameter",
|
|
||||||
INVALID_FORMAT: "invalid_format",
|
|
||||||
INVALID_STATE: "invalid_state",
|
|
||||||
IN_USE: "resource_in_use",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Authentication errors (401)
|
|
||||||
Authentication: {
|
|
||||||
UNAUTHORIZED: "unauthorized",
|
|
||||||
INVALID_TOKEN: "invalid_token",
|
|
||||||
EXPIRED_TOKEN: "expired_token",
|
|
||||||
INVALID_CREDENTIALS: "invalid_credentials",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Permission errors (403)
|
|
||||||
Permission: {
|
|
||||||
FORBIDDEN: "forbidden",
|
|
||||||
INSUFFICIENT_PERMISSIONS: "insufficient_permissions",
|
|
||||||
ACCOUNT_RESTRICTED: "account_restricted",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Resource not found errors (404)
|
|
||||||
NotFound: {
|
|
||||||
RESOURCE_NOT_FOUND: "resource_not_found",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Rate limit errors (429)
|
|
||||||
RateLimit: {
|
|
||||||
TOO_MANY_REQUESTS: "too_many_requests",
|
|
||||||
QUOTA_EXCEEDED: "quota_exceeded",
|
|
||||||
},
|
|
||||||
|
|
||||||
// Server errors (500)
|
|
||||||
Server: {
|
|
||||||
INTERNAL_ERROR: "internal_error",
|
|
||||||
SERVICE_UNAVAILABLE: "service_unavailable",
|
|
||||||
DEPENDENCY_FAILURE: "dependency_failure",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard error that will be exposed to clients through API responses
|
|
||||||
*/
|
|
||||||
export class VisibleError extends Error {
|
export class VisibleError extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
public type: ErrorResponseType["type"],
|
public kind: "input" | "auth",
|
||||||
public code: string,
|
public code: string,
|
||||||
public message: string,
|
public message: string,
|
||||||
public param?: string,
|
|
||||||
public details?: any,
|
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert this error to an HTTP status code
|
|
||||||
*/
|
|
||||||
public statusCode(): number {
|
|
||||||
switch (this.type) {
|
|
||||||
case "validation":
|
|
||||||
return 400;
|
|
||||||
case "authentication":
|
|
||||||
return 401;
|
|
||||||
case "forbidden":
|
|
||||||
return 403;
|
|
||||||
case "not_found":
|
|
||||||
return 404;
|
|
||||||
case "already_exists":
|
|
||||||
return 409;
|
|
||||||
case "rate_limit":
|
|
||||||
return 429;
|
|
||||||
case "internal":
|
|
||||||
return 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert this error to a standard response object
|
|
||||||
*/
|
|
||||||
public toResponse(): ErrorResponseType {
|
|
||||||
const response: ErrorResponseType = {
|
|
||||||
type: this.type,
|
|
||||||
code: this.code,
|
|
||||||
message: this.message,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.param) response.param = this.param;
|
|
||||||
if (this.details) response.details = this.details;
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { useActor } from "./actor";
|
|
||||||
import { event as sstEvent } from "sst/event";
|
|
||||||
import { ZodValidator } from "sst/event/validator";
|
|
||||||
|
|
||||||
export const createEvent = sstEvent.builder({
|
|
||||||
validator: ZodValidator,
|
|
||||||
metadata() {
|
|
||||||
return {
|
|
||||||
actor: useActor(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
import { openevent } from "@openauthjs/openevent/event";
|
|
||||||
export { publish } from "@openauthjs/openevent/publisher/drizzle";
|
|
||||||
|
|
||||||
export const event = openevent({
|
|
||||||
metadata() {
|
|
||||||
return {
|
|
||||||
actor: useActor(),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,81 +1,75 @@
|
|||||||
import { prefixes } from "./utils";
|
export module Examples {
|
||||||
export namespace Examples {
|
|
||||||
export const Id = (prefix: keyof typeof prefixes) =>
|
|
||||||
`${prefixes[prefix]}_XXXXXXXXXXXXXXXXXXXXXXXXX`;
|
|
||||||
|
|
||||||
export const Steam = {
|
|
||||||
id: Id("steam"),
|
|
||||||
userID: Id("user"),
|
|
||||||
countryCode: "KE",
|
|
||||||
steamID: 74839300282033,
|
|
||||||
limitation: {
|
|
||||||
isLimited: false,
|
|
||||||
isBanned: false,
|
|
||||||
isLocked: false,
|
|
||||||
isAllowedToInviteFriends: false,
|
|
||||||
},
|
|
||||||
lastGame: {
|
|
||||||
gameID: 2531310,
|
|
||||||
gameName: "The Last of Us™ Part II Remastered",
|
|
||||||
},
|
|
||||||
personaName: "John",
|
|
||||||
username: "johnsteamaccount",
|
|
||||||
steamEmail: "john@example.com",
|
|
||||||
avatarUrl: "https://avatars.akamai.steamstatic.com/XXXXXXXXXXXX_full.jpg",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const User = {
|
export const User = {
|
||||||
id: Id("user"),
|
id: "0bfcc712-df13-4454-81a8-fbee66eddca4",
|
||||||
name: "John Doe",
|
|
||||||
email: "john@example.com",
|
email: "john@example.com",
|
||||||
discriminator: 47,
|
|
||||||
avatarUrl: "https://cdn.discordapp.com/avatars/xxxxxxx/xxxxxxx.png",
|
|
||||||
polarCustomerID: "0bfcb712-df13-4454-81a8-fbee66eddca4",
|
|
||||||
steamAccounts: [Steam]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Product = {
|
export const Task = {
|
||||||
id: Id("product"),
|
id: "0bfcc712-df13-4454-81a8-fbee66eddca4",
|
||||||
name: "RTX 4090",
|
taskID: "b8302fca2d224d91ab342a2e4ab926d3",
|
||||||
description: "Ideal for dedicated gamers who crave more flexibility and social gaming experiences.",
|
type: "AWS" as const, //or "on-premises",
|
||||||
tokensPerHour: 20,
|
lastStatus: "RUNNING" as const,
|
||||||
|
healthStatus: "UNKNOWN" as const,
|
||||||
|
startedAt: '2025-01-09T01:56:23.902Z',
|
||||||
|
lastUpdated: '2025-01-09T01:56:23.902Z',
|
||||||
|
stoppedAt: '2025-01-09T04:46:23.902Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Profile = {
|
||||||
|
id: "0bfcb712-df13-4454-81a8-fbee66eddca4",
|
||||||
|
username: "janedoe47",
|
||||||
|
status: "active" as const,
|
||||||
|
avatarUrl: "https://cdn.discordapp.com/avatars/xxxxxxx/xxxxxxx.png",
|
||||||
|
discriminator: 12, //it needs to be two digits
|
||||||
|
createdAt: '2025-01-04T11:56:23.902Z',
|
||||||
|
updatedAt: '2025-01-09T01:56:23.902Z'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Subscription = {
|
export const Subscription = {
|
||||||
tokens: 100,
|
id: "0bfcb712-df13-4454-81a8-fbee66eddca4",
|
||||||
id: Id("subscription"),
|
checkoutID: "0bfcb712-df43-4454-81a8-fbee66eddca4",
|
||||||
userID: Id("user"),
|
// productID: "0bfcb712-df43-4454-81a8-fbee66eddca4",
|
||||||
teamID: Id("team"),
|
// quantity: 1,
|
||||||
planType: "pro" as const, // free, pro, family, enterprise
|
// frequency: "monthly" as const,
|
||||||
standing: "new" as const, // new, good, overdue, cancelled
|
// next: '2025-01-09T01:56:23.902Z',
|
||||||
polarProductID: "0bfcb712-df13-4454-81a8-fbee66eddca4",
|
canceledAt: '2025-02-09T01:56:23.902Z'
|
||||||
polarSubscriptionID: "0bfcb712-df13-4454-81a8-fbee66eddca4",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Member = {
|
|
||||||
id: Id("member"),
|
|
||||||
email: "john@example.com",
|
|
||||||
teamID: Id("team"),
|
|
||||||
role: "admin" as const,
|
|
||||||
timeSeen: new Date("2025-02-23T13:39:52.249Z"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Team = {
|
export const Team = {
|
||||||
id: Id("team"),
|
id: "0bfcb712-df13-4454-81a8-fbee66eddca4",
|
||||||
name: "John Does' Team",
|
// owner: true,
|
||||||
slug: "john_doe",
|
name: "Jane Doe's Games",
|
||||||
subscriptions: [Subscription],
|
slug: "jane-does-games",
|
||||||
members: [Member]
|
createdAt: '2025-01-04T11:56:23.902Z',
|
||||||
|
updatedAt: '2025-01-09T01:56:23.902Z'
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Machine = {
|
export const Machine = {
|
||||||
id: Id("machine"),
|
id: "0bfcb712-df13-4454-81a8-fbee66eddca4",
|
||||||
userID: Id("user"),
|
hostname: "DESKTOP-EUO8VSF",
|
||||||
country: "Kenya",
|
fingerprint: "fc27f428f9ca47d4b41b70889ae0c62090",
|
||||||
countryCode: "KE",
|
createdAt: '2025-01-04T11:56:23.902Z',
|
||||||
timezone: "Africa/Nairobi",
|
deletedAt: '2025-01-09T01:56:23.902Z'
|
||||||
location: { latitude: 36.81550, longitude: -1.28410 },
|
|
||||||
fingerprint: "fc27f428f9ca47d4b41b707ae0c62090",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const Instance = {
|
||||||
|
id: "0bfcb712-df13-4454-81a8-fbee66eddca4",
|
||||||
|
hostname: "a955e059f05d",
|
||||||
|
createdAt: '2025-01-04T11:56:23.902Z',
|
||||||
|
lastActive: '2025-01-09T01:56:23.902Z'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Game = {
|
||||||
|
id: '0bfcb712-df13-4454-81a8-fbee66eddca4',
|
||||||
|
name: "Control Ultimate Edition",
|
||||||
|
steamID: 870780,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Session = {
|
||||||
|
id: "0bfcb712-df13-4454-81a8-fbee66eddca4",
|
||||||
|
public: true,
|
||||||
|
startedAt: '2025-01-04T11:56:23.902Z',
|
||||||
|
endedAt: '2025-01-04T12:36:23.902Z'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
151
packages/core/src/game/index.ts
Normal file
151
packages/core/src/game/index.ts
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// import { z } from "zod"
|
||||||
|
// import { fn } from "../utils";
|
||||||
|
// import { Common } from "../common";
|
||||||
|
// import { Examples } from "../examples";
|
||||||
|
// import databaseClient from "../database"
|
||||||
|
// import { id as createID } from "@instantdb/admin";
|
||||||
|
// import { groupBy, map, pipe, values } from "remeda"
|
||||||
|
// import { useCurrentDevice, useCurrentUser } from "../actor";
|
||||||
|
|
||||||
|
// export module Games {
|
||||||
|
// export const Info = z
|
||||||
|
// .object({
|
||||||
|
// id: z.string().openapi({
|
||||||
|
// description: Common.IdDescription,
|
||||||
|
// example: Examples.Game.id,
|
||||||
|
// }),
|
||||||
|
// name: z.string().openapi({
|
||||||
|
// description: "A human-readable name for the game, used for easy identification.",
|
||||||
|
// example: Examples.Game.name,
|
||||||
|
// }),
|
||||||
|
// steamID: z.number().openapi({
|
||||||
|
// description: "The Steam ID of the game, used to identify it during installation and runtime.",
|
||||||
|
// example: Examples.Game.steamID,
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// .openapi({
|
||||||
|
// ref: "Game",
|
||||||
|
// description: "Represents a Steam game that can be installed and played on a machine.",
|
||||||
|
// example: Examples.Game,
|
||||||
|
// });
|
||||||
|
|
||||||
|
// export type Info = z.infer<typeof Info>;
|
||||||
|
|
||||||
|
// export const create = fn(Info.pick({ name: true, steamID: true }), async (input) => {
|
||||||
|
// const id = createID()
|
||||||
|
// const db = databaseClient()
|
||||||
|
// const device = useCurrentDevice()
|
||||||
|
|
||||||
|
// await db.transact(
|
||||||
|
// db.tx.games[id]!.update({
|
||||||
|
// name: input.name,
|
||||||
|
// steamID: input.steamID,
|
||||||
|
// }).link({ machines: device.id })
|
||||||
|
// )
|
||||||
|
// //
|
||||||
|
// return id
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const list = async () => {
|
||||||
|
// const db = databaseClient()
|
||||||
|
// const user = useCurrentUser()
|
||||||
|
|
||||||
|
// const query = {
|
||||||
|
// $users: {
|
||||||
|
// $: { where: { id: user.id } },
|
||||||
|
// games: {}
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const res = await db.query(query)
|
||||||
|
|
||||||
|
// const games = res.$users[0]?.games
|
||||||
|
// if (games && games.length > 0) {
|
||||||
|
// const result = pipe(
|
||||||
|
// games,
|
||||||
|
// groupBy(x => x.id),
|
||||||
|
// values(),
|
||||||
|
// map((group): Info => ({
|
||||||
|
// id: group[0].id,
|
||||||
|
// name: group[0].name,
|
||||||
|
// steamID: group[0].steamID,
|
||||||
|
// }))
|
||||||
|
// )
|
||||||
|
// return result
|
||||||
|
// }
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export const fromSteamID = fn(z.number(), async (steamID) => {
|
||||||
|
// const db = databaseClient()
|
||||||
|
|
||||||
|
// const query = {
|
||||||
|
// games: {
|
||||||
|
// $: {
|
||||||
|
// where: {
|
||||||
|
// steamID,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const res = await db.query(query)
|
||||||
|
|
||||||
|
// const games = res.games
|
||||||
|
|
||||||
|
// if (games.length > 0) {
|
||||||
|
// const result = pipe(
|
||||||
|
// games,
|
||||||
|
// groupBy(x => x.id),
|
||||||
|
// values(),
|
||||||
|
// map((group): Info => ({
|
||||||
|
// id: group[0].id,
|
||||||
|
// name: group[0].name,
|
||||||
|
// steamID: group[0].steamID,
|
||||||
|
// }))
|
||||||
|
// )
|
||||||
|
// return result[0]
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return null
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const linkToCurrentUser = fn(z.string(), async (steamID) => {
|
||||||
|
// const user = useCurrentUser()
|
||||||
|
// const db = databaseClient()
|
||||||
|
|
||||||
|
// await db.transact(db.tx.games[steamID]!.link({ owners: user.id }))
|
||||||
|
|
||||||
|
// return "ok"
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const unLinkFromCurrentUser = fn(z.number(), async (steamID) => {
|
||||||
|
// const user = useCurrentUser()
|
||||||
|
// const db = databaseClient()
|
||||||
|
|
||||||
|
// const query = {
|
||||||
|
// $users: {
|
||||||
|
// $: { where: { id: user.id } },
|
||||||
|
// games: {
|
||||||
|
// $: {
|
||||||
|
// where: {
|
||||||
|
// steamID,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const res = await db.query(query)
|
||||||
|
// const games = res.$users[0]?.games
|
||||||
|
// if (games && games.length > 0) {
|
||||||
|
// const game = games[0] as Info
|
||||||
|
// await db.transact(db.tx.games[game.id]!.unlink({ owners: user.id }))
|
||||||
|
|
||||||
|
// return "ok"
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return null
|
||||||
|
// })
|
||||||
|
|
||||||
|
// }
|
||||||
83
packages/core/src/instance/index.ts
Normal file
83
packages/core/src/instance/index.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
import { fn } from "../utils";
|
||||||
|
import { Common } from "../common";
|
||||||
|
import { Examples } from "../examples";
|
||||||
|
import databaseClient from "../database"
|
||||||
|
import { id as createID } from "@instantdb/admin";
|
||||||
|
import { groupBy, map, pipe, values } from "remeda"
|
||||||
|
|
||||||
|
export module Instances {
|
||||||
|
export const Info = z
|
||||||
|
.object({
|
||||||
|
id: z.string().openapi({
|
||||||
|
description: Common.IdDescription,
|
||||||
|
example: Examples.Instance.id,
|
||||||
|
}),
|
||||||
|
hostname: z.string().openapi({
|
||||||
|
description: "The container's hostname",
|
||||||
|
example: Examples.Instance.hostname,
|
||||||
|
}),
|
||||||
|
createdAt: z.string().or(z.number()).openapi({
|
||||||
|
description: "The time this instances was registered on the network",
|
||||||
|
example: Examples.Instance.createdAt,
|
||||||
|
}),
|
||||||
|
lastActive: z.string().or(z.number()).optional().openapi({
|
||||||
|
description: "The time this instance was last seen on the network",
|
||||||
|
example: Examples.Instance.lastActive,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
ref: "Instance",
|
||||||
|
description: "Represents a running container that is connected to the Nestri network..",
|
||||||
|
example: Examples.Instance,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Info = z.infer<typeof Info>;
|
||||||
|
export const create = fn(z.object({ hostname: z.string(), teamID: z.string() }), async (input) => {
|
||||||
|
const id = createID()
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
const db = databaseClient()
|
||||||
|
await db.transact(
|
||||||
|
db.tx.instances[id]!.update({
|
||||||
|
hostname: input.hostname,
|
||||||
|
createdAt: now,
|
||||||
|
}).link({ owners: input.teamID })
|
||||||
|
)
|
||||||
|
|
||||||
|
return "ok"
|
||||||
|
})
|
||||||
|
|
||||||
|
export const fromTeamID = fn(z.string(), async (teamID) => {
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
instances: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
owners: teamID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
const data = res.instances
|
||||||
|
|
||||||
|
if (data && data.length > 0) {
|
||||||
|
const result = pipe(
|
||||||
|
data,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
lastActive: group[0].lastActive,
|
||||||
|
hostname: group[0].hostname,
|
||||||
|
createdAt: group[0].createdAt
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,155 +1,232 @@
|
|||||||
import { z } from "zod";
|
// import { z } from "zod"
|
||||||
import { Common } from "../common";
|
// import { fn } from "../utils";
|
||||||
import { createID, fn } from "../utils";
|
// import { Games } from "../game"
|
||||||
import { Examples } from "../examples";
|
// import { Common } from "../common";
|
||||||
import { machineTable } from "./machine.sql";
|
// import { Examples } from "../examples";
|
||||||
import { getTableColumns, eq, sql, and, isNull } from "../drizzle";
|
// import { useCurrentUser } from "../actor";
|
||||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
// import databaseClient from "../database"
|
||||||
|
// import { id as createID } from "@instantdb/admin";
|
||||||
export namespace Machine {
|
// import { groupBy, map, pipe, values } from "remeda"
|
||||||
export const Info = z
|
// export module Machines {
|
||||||
.object({
|
// export const Info = z
|
||||||
id: z.string().openapi({
|
// .object({
|
||||||
description: Common.IdDescription,
|
// id: z.string().openapi({
|
||||||
example: Examples.Machine.id,
|
// description: Common.IdDescription,
|
||||||
}),
|
// example: Examples.Machine.id,
|
||||||
// userID: z.string().nullable().openapi({
|
|
||||||
// description: "The userID of the user who owns this machine, in the case of BYOG",
|
|
||||||
// example: Examples.Machine.userID
|
|
||||||
// }),
|
// }),
|
||||||
country: z.string().openapi({
|
// hostname: z.string().openapi({
|
||||||
description: "The fullname of the country this machine is running in",
|
// description: "The Linux hostname that identifies this machine",
|
||||||
example: Examples.Machine.country
|
// example: Examples.Machine.hostname,
|
||||||
}),
|
|
||||||
fingerprint: z.string().openapi({
|
|
||||||
description: "The fingerprint of this machine, deduced from the host machine's machine id - /etc/machine-id",
|
|
||||||
example: Examples.Machine.fingerprint
|
|
||||||
}),
|
|
||||||
location: z.object({ longitude: z.number(), latitude: z.number() }).openapi({
|
|
||||||
description: "This is the 2d location of this machine, they might not be accurate",
|
|
||||||
example: Examples.Machine.location
|
|
||||||
}),
|
|
||||||
countryCode: z.string().openapi({
|
|
||||||
description: "This is the 2 character country code of the country this machine [ISO 3166-1 alpha-2] ",
|
|
||||||
example: Examples.Machine.countryCode
|
|
||||||
}),
|
|
||||||
timezone: z.string().openapi({
|
|
||||||
description: "The IANA timezone formatted string of the timezone of the location where the machine is running",
|
|
||||||
example: Examples.Machine.timezone
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.openapi({
|
|
||||||
ref: "Machine",
|
|
||||||
description: "Represents a hosted or BYOG machine connected to Nestri",
|
|
||||||
example: Examples.Machine,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Info = z.infer<typeof Info>;
|
|
||||||
|
|
||||||
export const create = fn(Info.partial({ id: true }), async (input) =>
|
|
||||||
createTransaction(async (tx) => {
|
|
||||||
const id = input.id ?? createID("machine");
|
|
||||||
await tx.insert(machineTable).values({
|
|
||||||
id,
|
|
||||||
country: input.country,
|
|
||||||
timezone: input.timezone,
|
|
||||||
fingerprint: input.fingerprint,
|
|
||||||
countryCode: input.countryCode,
|
|
||||||
// userID: input.userID,
|
|
||||||
location: { x: input.location.longitude, y: input.location.latitude },
|
|
||||||
})
|
|
||||||
|
|
||||||
// await afterTx(() =>
|
|
||||||
// bus.publish(Resource.Bus, Events.Created, {
|
|
||||||
// teamID: id,
|
|
||||||
// }),
|
// }),
|
||||||
// );
|
// fingerprint: z.string().openapi({
|
||||||
return id;
|
// description: "A unique identifier derived from the machine's Linux machine ID.",
|
||||||
})
|
// example: Examples.Machine.fingerprint,
|
||||||
)
|
// }),
|
||||||
|
// createdAt: z.string().or(z.number()).openapi({
|
||||||
|
// description: "Represents a machine running on the Nestri network, containing its identifying information and metadata.",
|
||||||
|
// example: Examples.Machine.createdAt,
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// .openapi({
|
||||||
|
// ref: "Machine",
|
||||||
|
// description: "Represents a physical or virtual machine connected to the Nestri network..",
|
||||||
|
// example: Examples.Machine,
|
||||||
|
// });
|
||||||
|
|
||||||
// export const fromUserID = fn(z.string(), async (userID) =>
|
// export type Info = z.infer<typeof Info>;
|
||||||
// useTransaction(async (tx) =>
|
|
||||||
// tx
|
// export const create = fn(Info.pick({ fingerprint: true, hostname: true }), async (input) => {
|
||||||
// .select()
|
// const id = createID()
|
||||||
// .from(machineTable)
|
// const now = new Date().toISOString()
|
||||||
// .where(and(eq(machineTable.userID, userID), isNull(machineTable.timeDeleted)))
|
// const db = databaseClient()
|
||||||
// .then((rows) => rows.map(serialize))
|
// await db.transact(
|
||||||
// )
|
// db.tx.machines[id]!.update({
|
||||||
|
// fingerprint: input.fingerprint,
|
||||||
|
// hostname: input.hostname,
|
||||||
|
// createdAt: now,
|
||||||
|
// //Just in case it had been previously deleted
|
||||||
|
// deletedAt: undefined
|
||||||
|
// })
|
||||||
// )
|
// )
|
||||||
|
|
||||||
// export const list = fn(z.void(), async () =>
|
// return id
|
||||||
// useTransaction(async (tx) =>
|
// })
|
||||||
// tx
|
|
||||||
// .select()
|
// // export const fromID = fn(z.string(), async (id) => {
|
||||||
// .from(machineTable)
|
// const db = databaseClient()
|
||||||
// // Show only hosted machines, not BYOG machines
|
|
||||||
// .where(and(isNull(machineTable.userID), isNull(machineTable.timeDeleted)))
|
// const query = {
|
||||||
// .then((rows) => rows.map(serialize))
|
// machines: {
|
||||||
|
// $: {
|
||||||
|
// where: {
|
||||||
|
// id: id,
|
||||||
|
// deletedAt: { $isNull: true }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const res = await db.query(query)
|
||||||
|
// const machines = res.machines
|
||||||
|
|
||||||
|
// if (machines && machines.length > 0) {
|
||||||
|
// const result = pipe(
|
||||||
|
// machines,
|
||||||
|
// groupBy(x => x.id),
|
||||||
|
// values(),
|
||||||
|
// map((group): Info => ({
|
||||||
|
// id: group[0].id,
|
||||||
|
// fingerprint: group[0].fingerprint,
|
||||||
|
// hostname: group[0].hostname,
|
||||||
|
// createdAt: group[0].createdAt
|
||||||
|
// }))
|
||||||
// )
|
// )
|
||||||
|
// return result
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return null
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const installedGames = fn(z.string(), async (id) => {
|
||||||
|
// const db = databaseClient()
|
||||||
|
|
||||||
|
// const query = {
|
||||||
|
// machines: {
|
||||||
|
// $: {
|
||||||
|
// where: {
|
||||||
|
// id: id,
|
||||||
|
// deletedAt: { $isNull: true }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// games: {}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const res = await db.query(query)
|
||||||
|
// const machines = res.machines
|
||||||
|
|
||||||
|
// if (machines && machines.length > 0) {
|
||||||
|
// const games = machines[0]?.games as any
|
||||||
|
// if (games.length > 0) {
|
||||||
|
// return games as Games.Info[]
|
||||||
|
// }
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return null
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const fromFingerprint = fn(z.string(), async (input) => {
|
||||||
|
// const db = databaseClient()
|
||||||
|
|
||||||
|
// const query = {
|
||||||
|
// machines: {
|
||||||
|
// $: {
|
||||||
|
// where: {
|
||||||
|
// fingerprint: input,
|
||||||
|
// deletedAt: { $isNull: true }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const res = await db.query(query)
|
||||||
|
|
||||||
|
// const machines = res.machines
|
||||||
|
|
||||||
|
// if (machines.length > 0) {
|
||||||
|
// const result = pipe(
|
||||||
|
// machines,
|
||||||
|
// groupBy(x => x.id),
|
||||||
|
// values(),
|
||||||
|
// map((group): Info => ({
|
||||||
|
// id: group[0].id,
|
||||||
|
// fingerprint: group[0].fingerprint,
|
||||||
|
// hostname: group[0].hostname,
|
||||||
|
// createdAt: group[0].createdAt
|
||||||
|
// }))
|
||||||
// )
|
// )
|
||||||
|
// return result[0]
|
||||||
|
// }
|
||||||
|
|
||||||
export const fromID = fn(Info.shape.id, async (id) =>
|
// return null
|
||||||
useTransaction(async (tx) =>
|
// })
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(machineTable)
|
|
||||||
.where(and(eq(machineTable.id, id), isNull(machineTable.timeDeleted)))
|
|
||||||
.then((rows) => rows.map(serialize).at(0))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const fromFingerprint = fn(Info.shape.fingerprint, async (fingerprint) =>
|
// export const list = async () => {
|
||||||
useTransaction(async (tx) =>
|
// const user = useCurrentUser()
|
||||||
tx
|
// const db = databaseClient()
|
||||||
.select()
|
|
||||||
.from(machineTable)
|
|
||||||
.where(and(eq(machineTable.fingerprint, fingerprint), isNull(machineTable.timeDeleted)))
|
|
||||||
.execute()
|
|
||||||
.then((rows) => rows.map(serialize).at(0))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const remove = fn(Info.shape.id, (id) =>
|
// const query = {
|
||||||
useTransaction(async (tx) => {
|
// $users: {
|
||||||
await tx
|
// $: { where: { id: user.id } },
|
||||||
.update(machineTable)
|
// machines: {
|
||||||
.set({
|
// $: {
|
||||||
timeDeleted: sql`now()`,
|
// where: {
|
||||||
})
|
// deletedAt: { $isNull: true }
|
||||||
.where(and(eq(machineTable.id, id)))
|
// }
|
||||||
.execute();
|
// }
|
||||||
return id;
|
// }
|
||||||
}),
|
// },
|
||||||
);
|
// }
|
||||||
|
|
||||||
export const fromLocation = fn(Info.shape.location, async (location) =>
|
// const res = await db.query(query)
|
||||||
useTransaction(async (tx) => {
|
|
||||||
const sqlDistance = sql`location <-> point(${location.longitude}, ${location.latitude})`;
|
|
||||||
return tx
|
|
||||||
.select({
|
|
||||||
...getTableColumns(machineTable),
|
|
||||||
distance: sql`round((${sqlDistance})::numeric, 2)`
|
|
||||||
})
|
|
||||||
.from(machineTable)
|
|
||||||
.where(isNull(machineTable.timeDeleted))
|
|
||||||
.orderBy(sqlDistance)
|
|
||||||
.limit(3)
|
|
||||||
.then((rows) => rows.map(serialize))
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export function serialize(
|
// const machines = res.$users[0]?.machines
|
||||||
input: typeof machineTable.$inferSelect,
|
// if (machines && machines.length > 0) {
|
||||||
): z.infer<typeof Info> {
|
// const result = pipe(
|
||||||
return {
|
// machines,
|
||||||
id: input.id,
|
// groupBy(x => x.id),
|
||||||
// userID: input.userID,
|
// values(),
|
||||||
country: input.country,
|
// map((group): Info => ({
|
||||||
timezone: input.timezone,
|
// id: group[0].id,
|
||||||
fingerprint: input.fingerprint,
|
// fingerprint: group[0].fingerprint,
|
||||||
countryCode: input.countryCode,
|
// hostname: group[0].hostname,
|
||||||
location: { latitude: input.location.y, longitude: input.location.x },
|
// createdAt: group[0].createdAt
|
||||||
};
|
// }))
|
||||||
}
|
// )
|
||||||
}
|
// return result
|
||||||
|
// }
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
|
|
||||||
|
// export const linkToCurrentUser = fn(z.string(), async (id) => {
|
||||||
|
// const user = useCurrentUser()
|
||||||
|
// const db = databaseClient()
|
||||||
|
|
||||||
|
// await db.transact(db.tx.machines[id]!.link({ owner: user.id }))
|
||||||
|
|
||||||
|
// return "ok"
|
||||||
|
// })
|
||||||
|
|
||||||
|
// export const unLinkFromCurrentUser = fn(z.string(), async (id) => {
|
||||||
|
// const user = useCurrentUser()
|
||||||
|
// const db = databaseClient()
|
||||||
|
// const now = new Date().toISOString()
|
||||||
|
|
||||||
|
// const query = {
|
||||||
|
// $users: {
|
||||||
|
// $: { where: { id: user.id } },
|
||||||
|
// machines: {
|
||||||
|
// $: {
|
||||||
|
// where: {
|
||||||
|
// id,
|
||||||
|
// deletedAt: { $isNull: true }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const res = await db.query(query)
|
||||||
|
// const machines = res.$users[0]?.machines
|
||||||
|
// if (machines && machines.length > 0) {
|
||||||
|
// const machine = machines[0] as Info
|
||||||
|
// await db.transact(db.tx.machines[machine.id]!.update({ deletedAt: now }))
|
||||||
|
|
||||||
|
// return "ok"
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return null
|
||||||
|
// })
|
||||||
|
|
||||||
|
// }
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { } from "drizzle-orm/postgres-js";
|
|
||||||
import { timestamps, id, ulid } from "../drizzle/types";
|
|
||||||
import {
|
|
||||||
text,
|
|
||||||
varchar,
|
|
||||||
pgTable,
|
|
||||||
uniqueIndex,
|
|
||||||
point,
|
|
||||||
primaryKey,
|
|
||||||
} from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
export const machineTable = pgTable(
|
|
||||||
"machine",
|
|
||||||
{
|
|
||||||
...id,
|
|
||||||
...timestamps,
|
|
||||||
// userID: ulid("user_id"),
|
|
||||||
country: text('country').notNull(),
|
|
||||||
timezone: text('timezone').notNull(),
|
|
||||||
location: point('location', { mode: 'xy' }).notNull(),
|
|
||||||
fingerprint: varchar('fingerprint', { length: 32 }).notNull(),
|
|
||||||
countryCode: varchar('country_code', { length: 2 }).notNull(),
|
|
||||||
// provider: text("provider").notNull(),
|
|
||||||
// gpuType: text("gpu_type").notNull(),
|
|
||||||
// storage: numeric("storage").notNull(),
|
|
||||||
// ipaddress: text("ipaddress").notNull(),
|
|
||||||
// gpuNumber: integer("gpu_number").notNull(),
|
|
||||||
// computePrice: numeric("compute_price").notNull(),
|
|
||||||
// driverVersion: integer("driver_version").notNull(),
|
|
||||||
// operatingSystem: text("operating_system").notNull(),
|
|
||||||
// fingerprint: varchar("fingerprint", { length: 32 }).notNull(),
|
|
||||||
// externalID: varchar("external_id", { length: 255 }).notNull(),
|
|
||||||
// cudaVersion: numeric("cuda_version", { precision: 4, scale: 2 }).notNull(),
|
|
||||||
},
|
|
||||||
(table) => [
|
|
||||||
// uniqueIndex("external_id").on(table.externalID),
|
|
||||||
uniqueIndex("machine_fingerprint").on(table.fingerprint),
|
|
||||||
// primaryKey({ columns: [table.userID, table.id], }),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
@@ -1,139 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { Resource } from "sst";
|
|
||||||
import { bus } from "sst/aws/bus";
|
|
||||||
import { useTeam } from "../actor";
|
|
||||||
import { Common } from "../common";
|
|
||||||
import { createID, fn } from "../utils";
|
|
||||||
import { createEvent } from "../event";
|
|
||||||
import { Examples } from "../examples";
|
|
||||||
import { memberTable, role } from "./member.sql";
|
|
||||||
import { and, eq, sql, asc, isNull } from "../drizzle";
|
|
||||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
|
||||||
|
|
||||||
export namespace Member {
|
|
||||||
export const Info = z
|
|
||||||
.object({
|
|
||||||
id: z.string().openapi({
|
|
||||||
description: Common.IdDescription,
|
|
||||||
example: Examples.Member.id,
|
|
||||||
}),
|
|
||||||
timeSeen: z.date().nullable().or(z.undefined()).openapi({
|
|
||||||
description: "The last time this team member was active",
|
|
||||||
example: Examples.Member.timeSeen
|
|
||||||
}),
|
|
||||||
teamID: z.string().openapi({
|
|
||||||
description: "The unique id of the team this member is on",
|
|
||||||
example: Examples.Member.teamID
|
|
||||||
}),
|
|
||||||
role: z.enum(role).openapi({
|
|
||||||
description: "The role of this team member",
|
|
||||||
example: Examples.Member.role
|
|
||||||
}),
|
|
||||||
email: z.string().openapi({
|
|
||||||
description: "The email of this team member",
|
|
||||||
example: Examples.Member.email
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.openapi({
|
|
||||||
ref: "Member",
|
|
||||||
description: "Represents a team member on Nestri",
|
|
||||||
example: Examples.Member,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Info = z.infer<typeof Info>;
|
|
||||||
|
|
||||||
export const Events = {
|
|
||||||
Created: createEvent(
|
|
||||||
"member.created",
|
|
||||||
z.object({
|
|
||||||
memberID: Info.shape.id,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
Updated: createEvent(
|
|
||||||
"member.updated",
|
|
||||||
z.object({
|
|
||||||
memberID: Info.shape.id,
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const create = fn(
|
|
||||||
Info.pick({ email: true, id: true })
|
|
||||||
.partial({
|
|
||||||
id: true,
|
|
||||||
})
|
|
||||||
.extend({
|
|
||||||
first: z.boolean().optional(),
|
|
||||||
}),
|
|
||||||
(input) =>
|
|
||||||
createTransaction(async (tx) => {
|
|
||||||
const id = input.id ?? createID("member");
|
|
||||||
await tx.insert(memberTable).values({
|
|
||||||
id,
|
|
||||||
teamID: useTeam(),
|
|
||||||
email: input.email,
|
|
||||||
role: input.first ? "owner" : "member",
|
|
||||||
timeSeen: input.first ? sql`now()` : null,
|
|
||||||
})
|
|
||||||
|
|
||||||
await afterTx(() =>
|
|
||||||
async () => bus.publish(Resource.Bus, Events.Created, { memberID: id }),
|
|
||||||
);
|
|
||||||
return id;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const remove = fn(Info.shape.id, (id) =>
|
|
||||||
useTransaction(async (tx) => {
|
|
||||||
await tx
|
|
||||||
.update(memberTable)
|
|
||||||
.set({
|
|
||||||
timeDeleted: sql`now()`,
|
|
||||||
})
|
|
||||||
.where(and(eq(memberTable.id, id), eq(memberTable.teamID, useTeam())))
|
|
||||||
.execute();
|
|
||||||
return id;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const fromEmail = fn(z.string(), async (email) =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(memberTable)
|
|
||||||
.where(and(eq(memberTable.email, email), isNull(memberTable.timeDeleted)))
|
|
||||||
.orderBy(asc(memberTable.timeCreated))
|
|
||||||
.then((rows) => rows.map(serialize).at(0))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const fromID = fn(z.string(), async (id) =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(memberTable)
|
|
||||||
.where(and(eq(memberTable.id, id), isNull(memberTable.timeDeleted)))
|
|
||||||
.orderBy(asc(memberTable.timeCreated))
|
|
||||||
.then((rows) => rows.map(serialize).at(0))
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a raw member database row into a standardized {@link Member.Info} object.
|
|
||||||
*
|
|
||||||
* @param input - The database row representing a member.
|
|
||||||
* @returns The member information formatted as a {@link Member.Info} object.
|
|
||||||
*/
|
|
||||||
export function serialize(
|
|
||||||
input: typeof memberTable.$inferSelect,
|
|
||||||
): z.infer<typeof Info> {
|
|
||||||
return {
|
|
||||||
id: input.id,
|
|
||||||
role: input.role,
|
|
||||||
email: input.email,
|
|
||||||
teamID: input.teamID,
|
|
||||||
timeSeen: input.timeSeen
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import { teamIndexes } from "../team/team.sql";
|
|
||||||
import { timestamps, utc, teamID } from "../drizzle/types";
|
|
||||||
import { index, pgTable, text, uniqueIndex, varchar } from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
export const role = ["admin", "member", "owner"] as const;
|
|
||||||
|
|
||||||
export const memberTable = pgTable(
|
|
||||||
"member",
|
|
||||||
{
|
|
||||||
...teamID,
|
|
||||||
...timestamps,
|
|
||||||
role: text("role", { enum: role }).notNull(),
|
|
||||||
timeSeen: utc("time_seen"),
|
|
||||||
email: varchar("email", { length: 255 }).notNull(),
|
|
||||||
},
|
|
||||||
(table) => [
|
|
||||||
...teamIndexes(table),
|
|
||||||
index("email_global").on(table.email),
|
|
||||||
uniqueIndex("member_email").on(table.teamID, table.email),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { fn } from "../utils";
|
|
||||||
import { Resource } from "sst";
|
|
||||||
import { useTeam, useUserID } from "../actor";
|
|
||||||
import { Polar as PolarSdk } from "@polar-sh/sdk";
|
|
||||||
import { validateEvent } from "@polar-sh/sdk/webhooks";
|
|
||||||
import { PlanType } from "../subscription/subscription.sql";
|
|
||||||
|
|
||||||
const polar = new PolarSdk({ accessToken: Resource.PolarSecret.value, server: Resource.App.stage !== "production" ? "sandbox" : "production" });
|
|
||||||
const planType = z.enum(PlanType)
|
|
||||||
export namespace Polar {
|
|
||||||
export const client = polar;
|
|
||||||
|
|
||||||
export const fromUserEmail = fn(z.string().min(1), async (email) => {
|
|
||||||
try {
|
|
||||||
const customers = await client.customers.list({ email })
|
|
||||||
|
|
||||||
if (customers.result.items.length === 0) {
|
|
||||||
return await client.customers.create({ email })
|
|
||||||
} else {
|
|
||||||
return customers.result.items[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
//FIXME: This is the issue [Polar.sh/#5147](https://github.com/polarsource/polar/issues/5147)
|
|
||||||
// console.log("error", err)
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const getProductIDs = (plan: z.infer<typeof planType>) => {
|
|
||||||
switch (plan) {
|
|
||||||
case "free":
|
|
||||||
return [Resource.NestriFreeMonthly.value]
|
|
||||||
case "pro":
|
|
||||||
return [Resource.NestriProYearly.value, Resource.NestriProMonthly.value]
|
|
||||||
case "family":
|
|
||||||
return [Resource.NestriFamilyYearly.value, Resource.NestriFamilyMonthly.value]
|
|
||||||
default:
|
|
||||||
return [Resource.NestriFreeMonthly.value]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createPortal = fn(
|
|
||||||
z.string(),
|
|
||||||
async (customerId) => {
|
|
||||||
const session = await client.customerSessions.create({
|
|
||||||
customerId
|
|
||||||
})
|
|
||||||
|
|
||||||
return session.customerPortalUrl
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
//TODO: Implement this
|
|
||||||
export const handleWebhook = async(payload: ReturnType<typeof validateEvent>) => {
|
|
||||||
switch (payload.type) {
|
|
||||||
case "subscription.created":
|
|
||||||
const teamID = payload.data.metadata.teamID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createCheckout = fn(
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
planType: z.enum(PlanType),
|
|
||||||
customerEmail: z.string(),
|
|
||||||
successUrl: z.string(),
|
|
||||||
customerID: z.string(),
|
|
||||||
allowDiscountCodes: z.boolean(),
|
|
||||||
teamID: z.string()
|
|
||||||
})
|
|
||||||
.partial({
|
|
||||||
customerEmail: true,
|
|
||||||
allowDiscountCodes: true,
|
|
||||||
customerID: true,
|
|
||||||
teamID: true
|
|
||||||
}),
|
|
||||||
async (input) => {
|
|
||||||
const productIDs = getProductIDs(input.planType)
|
|
||||||
|
|
||||||
const checkoutUrl =
|
|
||||||
await client.checkouts.create({
|
|
||||||
products: productIDs,
|
|
||||||
customerEmail: input.customerEmail ?? useUserID(),
|
|
||||||
successUrl: `${input.successUrl}?checkout={CHECKOUT_ID}`,
|
|
||||||
allowDiscountCodes: input.allowDiscountCodes ?? false,
|
|
||||||
customerId: input.customerID,
|
|
||||||
customerMetadata: {
|
|
||||||
teamID: input.teamID ?? useTeam()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return checkoutUrl.url
|
|
||||||
})
|
|
||||||
}
|
|
||||||
412
packages/core/src/profile/index.ts
Normal file
412
packages/core/src/profile/index.ts
Normal file
@@ -0,0 +1,412 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
import { fn } from "../utils";
|
||||||
|
import { Common } from "../common";
|
||||||
|
import { Examples } from "../examples";
|
||||||
|
import databaseClient from "../database";
|
||||||
|
import { groupBy, map, pipe, values } from "remeda"
|
||||||
|
import { id as createID, } from "@instantdb/admin";
|
||||||
|
import { useCurrentUser } from "../actor";
|
||||||
|
|
||||||
|
export const userStatus = z.enum([
|
||||||
|
"active", //online and playing a game
|
||||||
|
"idle", //online and not playing
|
||||||
|
"offline",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export module Profiles {
|
||||||
|
const MAX_ATTEMPTS = 50;
|
||||||
|
|
||||||
|
export const Info = z
|
||||||
|
.object({
|
||||||
|
id: z.string().openapi({
|
||||||
|
description: Common.IdDescription,
|
||||||
|
example: Examples.Machine.id,
|
||||||
|
}),
|
||||||
|
username: z.string().openapi({
|
||||||
|
description: "The user's unique username",
|
||||||
|
example: Examples.Profile.username,
|
||||||
|
}),
|
||||||
|
avatarUrl: z.string().or(z.undefined()).openapi({
|
||||||
|
description: "The url to the profile picture.",
|
||||||
|
example: Examples.Profile.username,
|
||||||
|
}),
|
||||||
|
status: userStatus.openapi({
|
||||||
|
description: "Whether the user is active, idle or offline",
|
||||||
|
example: Examples.Profile.status
|
||||||
|
}),
|
||||||
|
discriminator: z.string().or(z.number()).openapi({
|
||||||
|
description: "The number discriminator for each username",
|
||||||
|
example: Examples.Profile.discriminator,
|
||||||
|
}),
|
||||||
|
createdAt: z.string().or(z.number()).openapi({
|
||||||
|
description: "The time when this profile was first created",
|
||||||
|
example: Examples.Profile.createdAt,
|
||||||
|
}),
|
||||||
|
updatedAt: z.string().or(z.number()).openapi({
|
||||||
|
description: "The time when this profile was last edited",
|
||||||
|
example: Examples.Profile.updatedAt,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
ref: "Profile",
|
||||||
|
description: "Represents a profile of a user on Nestri",
|
||||||
|
example: Examples.Profile,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Info = z.infer<typeof Info>;
|
||||||
|
export type userStatus = z.infer<typeof userStatus>;
|
||||||
|
|
||||||
|
export const sanitizeUsername = (username: string): string => {
|
||||||
|
// Remove spaces and numbers
|
||||||
|
return username.replace(/[\s0-9]/g, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateDiscriminator = (): string => {
|
||||||
|
return Math.floor(Math.random() * 100).toString().padStart(2, '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isValidDiscriminator = (discriminator: string): boolean => {
|
||||||
|
return /^\d{2}$/.test(discriminator);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fromUsername = fn(z.string(), async (input) => {
|
||||||
|
const sanitizedUsername = sanitizeUsername(input);
|
||||||
|
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
profiles: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
username: sanitizedUsername,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
|
||||||
|
const profiles = res.profiles
|
||||||
|
|
||||||
|
if (!profiles || profiles.length == 0) {
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipe(
|
||||||
|
profiles,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
username: group[0].username,
|
||||||
|
createdAt: group[0].createdAt,
|
||||||
|
discriminator: group[0].discriminator,
|
||||||
|
updatedAt: group[0].updatedAt,
|
||||||
|
status: group[0].status as userStatus
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const findAvailableDiscriminator = fn(z.string(), async (input) => {
|
||||||
|
const db = databaseClient()
|
||||||
|
const username = sanitizeUsername(input);
|
||||||
|
|
||||||
|
for (let i = 0; i < MAX_ATTEMPTS; i++) {
|
||||||
|
const discriminator = generateDiscriminator();
|
||||||
|
const query = {
|
||||||
|
profiles: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
username,
|
||||||
|
discriminator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await db.query(query)
|
||||||
|
const profiles = res.profiles
|
||||||
|
if (profiles.length === 0) {
|
||||||
|
return discriminator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null; // No available discriminators
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
export const create = fn(z.object({ username: z.string(), customDiscriminator: z.string().optional(), avatarUrl: z.string().optional(), owner: z.string() }), async (input) => {
|
||||||
|
const username = sanitizeUsername(input.username);
|
||||||
|
|
||||||
|
const db = databaseClient()
|
||||||
|
const id = createID()
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
|
let discriminator: string | null;
|
||||||
|
if (input.customDiscriminator) {
|
||||||
|
if (!isValidDiscriminator(input.customDiscriminator)) {
|
||||||
|
console.error('Invalid discriminator format')
|
||||||
|
return null
|
||||||
|
// throw new Error('Invalid discriminator format');
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
profiles: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
username,
|
||||||
|
discriminator: input.customDiscriminator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
const profiles = res.profiles
|
||||||
|
if (profiles.length != 0) {
|
||||||
|
console.error("Username and discriminator combination already taken ")
|
||||||
|
return null
|
||||||
|
// throw new Error('Username and discriminator combination already taken');
|
||||||
|
}
|
||||||
|
|
||||||
|
discriminator = input.customDiscriminator
|
||||||
|
} else {
|
||||||
|
// Generate a random available discriminator
|
||||||
|
discriminator = await findAvailableDiscriminator(username);
|
||||||
|
|
||||||
|
if (!discriminator) {
|
||||||
|
console.error("No available discriminators for this username ")
|
||||||
|
return null
|
||||||
|
// throw new Error('No available discriminators for this username');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await db.transact(
|
||||||
|
db.tx.profiles[id]!.update({
|
||||||
|
username,
|
||||||
|
avatarUrl: input.avatarUrl,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
discriminator,
|
||||||
|
status: "idle"
|
||||||
|
}).link({ owner: input.owner })
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getFullUsername = async (username: string) => {
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
profiles: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
username,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await db.query(query)
|
||||||
|
const profiles = res.profiles
|
||||||
|
|
||||||
|
if (!profiles || profiles.length === 0) {
|
||||||
|
console.error('User not found')
|
||||||
|
return null
|
||||||
|
// throw new Error('User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${profiles[0]?.username}#${profiles[0]?.discriminator}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fromOwnerID = async (ownerID: string) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
profiles: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
owner: ownerID
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await db.query(query)
|
||||||
|
|
||||||
|
const profiles = res.profiles
|
||||||
|
|
||||||
|
if (!profiles || profiles.length === 0) {
|
||||||
|
throw new Error("No profiles were found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const profile = pipe(
|
||||||
|
profiles,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
username: group[0].username,
|
||||||
|
createdAt: group[0].createdAt,
|
||||||
|
updatedAt: group[0].updatedAt,
|
||||||
|
avatarUrl: group[0].avatarUrl,
|
||||||
|
discriminator: group[0].discriminator,
|
||||||
|
status: group[0].status as userStatus
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return profile[0]
|
||||||
|
} catch (error) {
|
||||||
|
console.log("user fromOwnerID", error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fromID = async (id: string) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
profiles: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await db.query(query)
|
||||||
|
|
||||||
|
const profiles = res.profiles
|
||||||
|
|
||||||
|
if (!profiles || profiles.length === 0) {
|
||||||
|
throw new Error("No profiles were found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const profile = pipe(
|
||||||
|
profiles,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
username: group[0].username,
|
||||||
|
createdAt: group[0].createdAt,
|
||||||
|
updatedAt: group[0].updatedAt,
|
||||||
|
avatarUrl: group[0].avatarUrl,
|
||||||
|
discriminator: group[0].discriminator,
|
||||||
|
status: group[0].status as userStatus
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return profile[0]
|
||||||
|
} catch (error) {
|
||||||
|
console.log("user fromID", error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fromIDToOwner = async (id: string) => {
|
||||||
|
try {
|
||||||
|
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
profiles: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res = await db.query(query)
|
||||||
|
|
||||||
|
const profiles = res.profiles as any
|
||||||
|
|
||||||
|
if (!profiles || profiles.length === 0) {
|
||||||
|
throw new Error("No profiles were found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return profiles[0]!.owner as string
|
||||||
|
} catch (error) {
|
||||||
|
console.log("user fromID", error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const getCurrentProfile = async () => {
|
||||||
|
const user = useCurrentUser()
|
||||||
|
const currentProfile = await fromOwnerID(user.id);
|
||||||
|
|
||||||
|
return currentProfile
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setStatus = fn(userStatus, async (status) => {
|
||||||
|
try {
|
||||||
|
const user = useCurrentUser()
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
|
await db.transact(
|
||||||
|
db.tx.profiles[user.id]!.update({
|
||||||
|
status,
|
||||||
|
updatedAt: now
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} catch (error) {
|
||||||
|
console.log("user setStatus error", error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const list = async () => {
|
||||||
|
try {
|
||||||
|
const db = databaseClient()
|
||||||
|
// const ago = new Date(Date.now() - (60 * 1000 * 30)).toISOString()
|
||||||
|
const ago = new Date(Date.now() - (24 * 60 * 60 * 1000)).toISOString()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
profiles: {
|
||||||
|
$: {
|
||||||
|
limit: 10,
|
||||||
|
where: {
|
||||||
|
updatedAt: { $gt: ago },
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
updatedAt: "desc" as const,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
|
||||||
|
const profiles = res.profiles
|
||||||
|
|
||||||
|
if (!profiles || profiles.length === 0) {
|
||||||
|
throw new Error("No profiles were found");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
profiles,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
username: group[0].username,
|
||||||
|
createdAt: group[0].createdAt,
|
||||||
|
updatedAt: group[0].updatedAt,
|
||||||
|
avatarUrl: group[0].avatarUrl,
|
||||||
|
discriminator: group[0].discriminator,
|
||||||
|
status: group[0].status as userStatus
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log("user list error", error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import {
|
|
||||||
IoTDataPlaneClient,
|
|
||||||
PublishCommand,
|
|
||||||
} from "@aws-sdk/client-iot-data-plane";
|
|
||||||
import { useMachine } from "../actor";
|
|
||||||
import { Resource } from "sst";
|
|
||||||
|
|
||||||
export namespace Realtime {
|
|
||||||
const client = new IoTDataPlaneClient({});
|
|
||||||
|
|
||||||
export async function publish(message: any, subTopic?: string) {
|
|
||||||
const fingerprint = useMachine();
|
|
||||||
let topic = `${Resource.App.name}/${Resource.App.stage}/${fingerprint}/`;
|
|
||||||
if (subTopic)
|
|
||||||
topic = `${topic}${subTopic}`;
|
|
||||||
|
|
||||||
await client.send(
|
|
||||||
new PublishCommand({
|
|
||||||
payload: Buffer.from(JSON.stringify(message)),
|
|
||||||
topic: topic,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
251
packages/core/src/session/index.ts
Normal file
251
packages/core/src/session/index.ts
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
import { fn } from "../utils";
|
||||||
|
import { Common } from "../common";
|
||||||
|
import { Examples } from "../examples";
|
||||||
|
import databaseClient from "../database"
|
||||||
|
import { useCurrentUser } from "../actor";
|
||||||
|
import { groupBy, map, pipe, values } from "remeda"
|
||||||
|
import { id as createID } from "@instantdb/admin";
|
||||||
|
|
||||||
|
export module Sessions {
|
||||||
|
export const Info = z
|
||||||
|
.object({
|
||||||
|
id: z.string().openapi({
|
||||||
|
description: Common.IdDescription,
|
||||||
|
example: Examples.Session.id,
|
||||||
|
}),
|
||||||
|
public: z.boolean().openapi({
|
||||||
|
description: "If true, the session is publicly viewable by all users. If false, only authorized users can access it",
|
||||||
|
example: Examples.Session.public,
|
||||||
|
}),
|
||||||
|
endedAt: z.string().or(z.number()).or(z.undefined()).openapi({
|
||||||
|
description: "The timestamp indicating when this session was completed or terminated. Null if session is still active.",
|
||||||
|
example: Examples.Session.endedAt,
|
||||||
|
}),
|
||||||
|
startedAt: z.string().or(z.number()).openapi({
|
||||||
|
description: "The timestamp indicating when this session started.",
|
||||||
|
example: Examples.Session.startedAt,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
ref: "Session",
|
||||||
|
description: "Represents a single game play session, tracking its lifetime and accessibility settings.",
|
||||||
|
example: Examples.Session,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Info = z.infer<typeof Info>;
|
||||||
|
|
||||||
|
export const create = fn(z.object({ public: z.boolean() }), async (input) => {
|
||||||
|
try {
|
||||||
|
const id = createID()
|
||||||
|
const db = databaseClient()
|
||||||
|
const user = useCurrentUser()
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
|
await db.transact(
|
||||||
|
db.tx.sessions[id]!.update({
|
||||||
|
public: input.public,
|
||||||
|
startedAt: now,
|
||||||
|
}).link({ owner: user.id })
|
||||||
|
)
|
||||||
|
|
||||||
|
return id
|
||||||
|
} catch (err) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const getActive = async () => {
|
||||||
|
try {
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
sessions: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
endedAt: { $isNull: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
|
||||||
|
const sessions = res.sessions
|
||||||
|
if (!sessions || sessions.length === 0) {
|
||||||
|
throw new Error("No active sessions found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
sessions,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
endedAt: group[0].endedAt,
|
||||||
|
startedAt: group[0].startedAt,
|
||||||
|
public: group[0].public,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fromID = fn(z.string(), async (id) => {
|
||||||
|
try {
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
sessions: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
const sessions = res.sessions
|
||||||
|
|
||||||
|
if (!sessions || sessions.length === 0) {
|
||||||
|
throw new Error("No sessions were found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
sessions,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
endedAt: group[0].endedAt,
|
||||||
|
startedAt: group[0].startedAt,
|
||||||
|
public: group[0].public,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
} catch (err) {
|
||||||
|
console.log("sessions error", err)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const fromTaskID = fn(z.string(), async (taskID) => {
|
||||||
|
try {
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
sessions: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
task: taskID,
|
||||||
|
endedAt: { $isNull: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
const sessions = res.sessions
|
||||||
|
|
||||||
|
if (!sessions || sessions.length === 0) {
|
||||||
|
throw new Error("No sessions were found");
|
||||||
|
}
|
||||||
|
console.log("sessions", sessions)
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
sessions,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
endedAt: group[0].endedAt,
|
||||||
|
startedAt: group[0].startedAt,
|
||||||
|
public: group[0].public,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
return result[0]
|
||||||
|
} catch (err) {
|
||||||
|
console.log("sessions error", err)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const end = fn(z.string(), async (id) => {
|
||||||
|
const user = useCurrentUser()
|
||||||
|
try {
|
||||||
|
const db = databaseClient()
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
sessions: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
owner: user.id,
|
||||||
|
id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
const sessions = res.sessions
|
||||||
|
if (!sessions || sessions.length === 0) {
|
||||||
|
throw new Error("No sessions were found");
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.transact(db.tx.sessions[sessions[0]!.id]!.update({ endedAt: now }))
|
||||||
|
|
||||||
|
return "ok"
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
export const fromOwnerID = fn(z.string(), async (id) => {
|
||||||
|
try {
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
sessions: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
owner: id,
|
||||||
|
endedAt: { $isNull: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
const sessions = res.sessions
|
||||||
|
|
||||||
|
if (!sessions || sessions.length === 0) {
|
||||||
|
throw new Error("No sessions were found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
sessions,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
endedAt: group[0].endedAt,
|
||||||
|
startedAt: group[0].startedAt,
|
||||||
|
public: group[0].public,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
return result[0]
|
||||||
|
} catch (err) {
|
||||||
|
console.log("session owner error", err)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { Common } from "../common";
|
|
||||||
import { Examples } from "../examples";
|
|
||||||
import { createID, fn } from "../utils";
|
|
||||||
import { useUser, useUserID } from "../actor";
|
|
||||||
import { eq, and, isNull, sql } from "../drizzle";
|
|
||||||
import { steamTable, AccountLimitation, LastGame } from "./steam.sql";
|
|
||||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
|
||||||
|
|
||||||
export namespace Steam {
|
|
||||||
export const Info = z
|
|
||||||
.object({
|
|
||||||
id: z.string().openapi({
|
|
||||||
description: Common.IdDescription,
|
|
||||||
example: Examples.Steam.id,
|
|
||||||
}),
|
|
||||||
avatarUrl: z.string().openapi({
|
|
||||||
description: "The avatar url of this Steam account",
|
|
||||||
example: Examples.Steam.avatarUrl
|
|
||||||
}),
|
|
||||||
steamEmail: z.string().openapi({
|
|
||||||
description: "The email regisered with this Steam account",
|
|
||||||
example: Examples.Steam.steamEmail
|
|
||||||
}),
|
|
||||||
steamID: z.number().openapi({
|
|
||||||
description: "The Steam ID this Steam account",
|
|
||||||
example: Examples.Steam.steamID
|
|
||||||
}),
|
|
||||||
limitation: AccountLimitation.openapi({
|
|
||||||
description: " The limitations of this Steam account",
|
|
||||||
example: Examples.Steam.limitation
|
|
||||||
}),
|
|
||||||
lastGame: LastGame.openapi({
|
|
||||||
description: "The last game played on this Steam account",
|
|
||||||
example: Examples.Steam.lastGame
|
|
||||||
}),
|
|
||||||
userID: z.string().openapi({
|
|
||||||
description: "The unique id of the user who owns this steam account",
|
|
||||||
example: Examples.Steam.userID
|
|
||||||
}),
|
|
||||||
username: z.string().openapi({
|
|
||||||
description: "The unique username of this steam user",
|
|
||||||
example: Examples.Steam.username
|
|
||||||
}),
|
|
||||||
personaName: z.string().openapi({
|
|
||||||
description: "The last recorded persona name used by this account",
|
|
||||||
example: Examples.Steam.personaName
|
|
||||||
}),
|
|
||||||
countryCode: z.string().openapi({
|
|
||||||
description: "The country this account is connected from",
|
|
||||||
example: Examples.Steam.countryCode
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.openapi({
|
|
||||||
ref: "Steam",
|
|
||||||
description: "Represents a steam user's information stored on Nestri",
|
|
||||||
example: Examples.Steam,
|
|
||||||
});
|
|
||||||
|
|
||||||
export type Info = z.infer<typeof Info>;
|
|
||||||
|
|
||||||
export const create = fn(
|
|
||||||
Info.partial({
|
|
||||||
id: true,
|
|
||||||
userID: true,
|
|
||||||
}),
|
|
||||||
(input) =>
|
|
||||||
createTransaction(async (tx) => {
|
|
||||||
const id = input.id ?? createID("steam");
|
|
||||||
const user = useUser()
|
|
||||||
await tx.insert(steamTable).values({
|
|
||||||
id,
|
|
||||||
lastSeen: sql`now()`,
|
|
||||||
userID: input.userID ?? user.userID,
|
|
||||||
countryCode: input.countryCode,
|
|
||||||
username: input.username,
|
|
||||||
steamID: input.steamID,
|
|
||||||
lastGame: input.lastGame,
|
|
||||||
limitation: input.limitation,
|
|
||||||
steamEmail: input.steamEmail,
|
|
||||||
avatarUrl: input.avatarUrl,
|
|
||||||
personaName: input.personaName,
|
|
||||||
})
|
|
||||||
return id;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const fromUserID = fn(
|
|
||||||
z.string(),
|
|
||||||
(userID) =>
|
|
||||||
useTransaction((tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(steamTable)
|
|
||||||
.where(and(eq(steamTable.userID, userID), isNull(steamTable.timeDeleted)))
|
|
||||||
.execute()
|
|
||||||
.then((rows) => rows.map(serialize).at(0)),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const list = () =>
|
|
||||||
useTransaction((tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(steamTable)
|
|
||||||
.where(and(eq(steamTable.userID, useUserID()), isNull(steamTable.timeDeleted)))
|
|
||||||
.execute()
|
|
||||||
.then((rows) => rows.map(serialize)),
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serializes a raw Steam table record into a standardized Info object.
|
|
||||||
*
|
|
||||||
* This function maps the fields from a database record (retrieved from the Steam table) to the
|
|
||||||
* corresponding properties defined in the Info schema.
|
|
||||||
*
|
|
||||||
* @param input - A raw record from the Steam table containing user information.
|
|
||||||
* @returns An object conforming to the Info schema.
|
|
||||||
*/
|
|
||||||
export function serialize(
|
|
||||||
input: typeof steamTable.$inferSelect,
|
|
||||||
): z.infer<typeof Info> {
|
|
||||||
return {
|
|
||||||
id: input.id,
|
|
||||||
userID: input.userID,
|
|
||||||
countryCode: input.countryCode,
|
|
||||||
username: input.username,
|
|
||||||
avatarUrl: input.avatarUrl,
|
|
||||||
personaName: input.personaName,
|
|
||||||
steamEmail: input.steamEmail,
|
|
||||||
steamID: input.steamID,
|
|
||||||
limitation: input.limitation,
|
|
||||||
lastGame: input.lastGame,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { userTable } from "../user/user.sql";
|
|
||||||
import { id, timestamps, ulid, utc } from "../drizzle/types";
|
|
||||||
import { index, pgTable, integer, uniqueIndex, varchar, text, json } from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
export const LastGame = z.object({
|
|
||||||
gameID: z.number(),
|
|
||||||
gameName: z.string()
|
|
||||||
});
|
|
||||||
|
|
||||||
export const AccountLimitation = z.object({
|
|
||||||
isLimited: z.boolean().nullable(),
|
|
||||||
isBanned: z.boolean().nullable(),
|
|
||||||
isLocked: z.boolean().nullable(),
|
|
||||||
isAllowedToInviteFriends: z.boolean().nullable(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type LastGame = z.infer<typeof LastGame>;
|
|
||||||
export type AccountLimitation = z.infer<typeof AccountLimitation>;
|
|
||||||
|
|
||||||
export const steamTable = pgTable(
|
|
||||||
"steam",
|
|
||||||
{
|
|
||||||
...id,
|
|
||||||
...timestamps,
|
|
||||||
userID: ulid("user_id")
|
|
||||||
.notNull()
|
|
||||||
.references(() => userTable.id, {
|
|
||||||
onDelete: "cascade",
|
|
||||||
}),
|
|
||||||
lastSeen: utc("last_seen").notNull(),
|
|
||||||
steamID: integer("steam_id").notNull(),
|
|
||||||
avatarUrl: text("avatar_url").notNull(),
|
|
||||||
lastGame: json("last_game").$type<LastGame>().notNull(),
|
|
||||||
username: varchar("username", { length: 255 }).notNull(),
|
|
||||||
countryCode: varchar('country_code', { length: 2 }).notNull(),
|
|
||||||
steamEmail: varchar("steam_email", { length: 255 }).notNull(),
|
|
||||||
personaName: varchar("persona_name", { length: 255 }).notNull(),
|
|
||||||
limitation: json("limitation").$type<AccountLimitation>().notNull(),
|
|
||||||
},
|
|
||||||
(table) => [
|
|
||||||
uniqueIndex("steam_id").on(table.steamID),
|
|
||||||
index("steam_user_id").on(table.userID),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
@@ -1,192 +1,205 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import databaseClient from "../database"
|
||||||
|
import { fn } from "../utils";
|
||||||
|
import { groupBy, map, pipe, values } from "remeda"
|
||||||
import { Common } from "../common";
|
import { Common } from "../common";
|
||||||
import { Examples } from "../examples";
|
import { Examples } from "../examples";
|
||||||
import { createID, fn } from "../utils";
|
import { useCurrentUser } from "../actor";
|
||||||
import { eq, and, isNull } from "../drizzle";
|
import { id as createID } from "@instantdb/admin";
|
||||||
import { useTeam, useUserID } from "../actor";
|
import { Email } from "../email";
|
||||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
import { Profiles } from "../profile";
|
||||||
import { PlanType, Standing, subscriptionTable } from "./subscription.sql";
|
|
||||||
|
|
||||||
export namespace Subscription {
|
export const SubscriptionFrequency = z.enum([
|
||||||
export const Info = z.object({
|
"fixed",
|
||||||
|
"daily",
|
||||||
|
"weekly",
|
||||||
|
"monthly",
|
||||||
|
"yearly",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type SubscriptionFrequency = z.infer<typeof SubscriptionFrequency>;
|
||||||
|
|
||||||
|
export namespace Subscriptions {
|
||||||
|
export const Info = z
|
||||||
|
.object({
|
||||||
id: z.string().openapi({
|
id: z.string().openapi({
|
||||||
description: Common.IdDescription,
|
description: Common.IdDescription,
|
||||||
example: Examples.Subscription.id,
|
example: Examples.Subscription.id,
|
||||||
}),
|
}),
|
||||||
polarSubscriptionID: z.string().nullable().or(z.undefined()).openapi({
|
checkoutID: z.string().openapi({
|
||||||
description: "The unique id of the plan this subscription is on",
|
description: "The polar.sh checkout id",
|
||||||
example: Examples.Subscription.polarSubscriptionID,
|
example: Examples.Subscription.checkoutID,
|
||||||
}),
|
}),
|
||||||
teamID: z.string().openapi({
|
// productID: z.string().openapi({
|
||||||
description: "The unique id of the team this subscription is for",
|
// description: "ID of the product being subscribed to.",
|
||||||
example: Examples.Subscription.teamID,
|
// example: Examples.Subscription.productID,
|
||||||
|
// }),
|
||||||
|
// quantity: z.number().int().openapi({
|
||||||
|
// description: "Quantity of the subscription.",
|
||||||
|
// example: Examples.Subscription.quantity,
|
||||||
|
// }),
|
||||||
|
// frequency: SubscriptionFrequency.openapi({
|
||||||
|
// description: "Frequency of the subscription.",
|
||||||
|
// example: Examples.Subscription.frequency,
|
||||||
|
// }),
|
||||||
|
// next: z.string().or(z.number()).openapi({
|
||||||
|
// description: "Next billing date for the subscription.",
|
||||||
|
// example: Examples.Subscription.next,
|
||||||
|
// }),
|
||||||
|
canceledAt: z.string().or(z.number()).optional().openapi({
|
||||||
|
description: "Cancelled date for the subscription.",
|
||||||
|
example: Examples.Subscription.canceledAt,
|
||||||
}),
|
}),
|
||||||
userID: z.string().openapi({
|
})
|
||||||
description: "The unique id of the user who is paying this subscription",
|
.openapi({
|
||||||
example: Examples.Subscription.userID,
|
|
||||||
}),
|
|
||||||
polarProductID: z.string().nullable().or(z.undefined()).openapi({
|
|
||||||
description: "The unique id of the product this subscription is for",
|
|
||||||
example: Examples.Subscription.polarProductID,
|
|
||||||
}),
|
|
||||||
tokens: z.number().openapi({
|
|
||||||
description: "The number of tokens this subscription has left",
|
|
||||||
example: Examples.Subscription.tokens,
|
|
||||||
}),
|
|
||||||
planType: z.enum(PlanType).openapi({
|
|
||||||
description: "The type of plan this subscription is for",
|
|
||||||
example: Examples.Subscription.planType,
|
|
||||||
}),
|
|
||||||
standing: z.enum(Standing).openapi({
|
|
||||||
description: "The standing of this subscription",
|
|
||||||
example: Examples.Subscription.standing,
|
|
||||||
}),
|
|
||||||
}).openapi({
|
|
||||||
ref: "Subscription",
|
ref: "Subscription",
|
||||||
description: "Represents a subscription on Nestri",
|
description: "Subscription to a Nestri product.",
|
||||||
example: Examples.Subscription
|
example: Examples.Subscription,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Info = z.infer<typeof Info>;
|
export type Info = z.infer<typeof Info>;
|
||||||
|
|
||||||
export const create = fn(
|
export const list = fn(z.string().optional(), async (userID) => {
|
||||||
Info
|
const db = databaseClient()
|
||||||
.partial({
|
const user = userID ? userID : useCurrentUser().id
|
||||||
teamID: true,
|
|
||||||
userID: true,
|
|
||||||
id: true,
|
|
||||||
standing: true,
|
|
||||||
planType: true,
|
|
||||||
polarProductID: true,
|
|
||||||
polarSubscriptionID: true,
|
|
||||||
}),
|
|
||||||
(input) =>
|
|
||||||
createTransaction(async (tx) => {
|
|
||||||
const id = input.id ?? createID("subscription");
|
|
||||||
|
|
||||||
await tx.insert(subscriptionTable).values({
|
const query = {
|
||||||
|
subscriptions: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
owner: user,
|
||||||
|
canceledAt: { $isNull: true }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
|
||||||
|
const response = res.subscriptions
|
||||||
|
if (!response || response.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
response,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
// next: group[0].next,
|
||||||
|
// frequency: group[0].frequency as any,
|
||||||
|
// quantity: group[0].quantity,
|
||||||
|
// productID: group[0].productID,
|
||||||
|
checkoutID: group[0].checkoutID,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
|
export const create = fn(Info.omit({ id: true, canceledAt: true }), async (input) => {
|
||||||
|
// const id = createID()
|
||||||
|
const id = createID()
|
||||||
|
const db = databaseClient()
|
||||||
|
const user = useCurrentUser()
|
||||||
|
|
||||||
|
//Use the polar.sh ID
|
||||||
|
await db.transact(db.tx.subscriptions[id]!.update({
|
||||||
|
// next: input.next,
|
||||||
|
// frequency: input.frequency,
|
||||||
|
// quantity: input.quantity,
|
||||||
|
checkoutID: input.checkoutID,
|
||||||
|
}).link({ owner: user.id }))
|
||||||
|
const res = await db.auth.getUser({ id: user.id })
|
||||||
|
const profile = await Profiles.fromOwnerID(user.id)
|
||||||
|
if (profile) {
|
||||||
|
await Email.sendWelcome(res.email, profile.username)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
export const remove = fn(z.string(), async (id) => {
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
await db.transact(db.tx.subscriptions[id]!.update({
|
||||||
|
canceledAt: new Date().toISOString()
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const fromID = fn(z.string(), async (id) => {
|
||||||
|
const db = databaseClient()
|
||||||
|
const user = useCurrentUser()
|
||||||
|
const query = {
|
||||||
|
subscriptions: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
id,
|
id,
|
||||||
tokens: input.tokens,
|
//Make sure they can only get subscriptions they own
|
||||||
polarProductID: input.polarProductID ?? null,
|
owner: user.id,
|
||||||
polarSubscriptionID: input.polarSubscriptionID ?? null,
|
canceledAt: { $isNull: true }
|
||||||
standing: input.standing ?? "new",
|
}
|
||||||
planType: input.planType ?? "free",
|
},
|
||||||
userID: input.userID ?? useUserID(),
|
}
|
||||||
teamID: input.teamID ?? useTeam(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return id;
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export const setPolarProductID = fn(
|
|
||||||
Info.pick({
|
|
||||||
id: true,
|
|
||||||
polarProductID: true,
|
|
||||||
}),
|
|
||||||
(input) =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx.update(subscriptionTable)
|
|
||||||
.set({
|
|
||||||
polarProductID: input.polarProductID,
|
|
||||||
})
|
|
||||||
.where(eq(subscriptionTable.id, input.id))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const setPolarSubscriptionID = fn(
|
|
||||||
Info.pick({
|
|
||||||
id: true,
|
|
||||||
polarSubscriptionID: true,
|
|
||||||
}),
|
|
||||||
(input) =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx.update(subscriptionTable)
|
|
||||||
.set({
|
|
||||||
polarSubscriptionID: input.polarSubscriptionID,
|
|
||||||
})
|
|
||||||
.where(eq(subscriptionTable.id, input.id))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const fromID = fn(z.string(), async (id) =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(subscriptionTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(subscriptionTable.id, id),
|
|
||||||
isNull(subscriptionTable.timeDeleted)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.orderBy(subscriptionTable.timeCreated)
|
|
||||||
.then((rows) => rows.map(serialize))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
export const fromTeamID = fn(z.string(), async (teamID) =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(subscriptionTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(subscriptionTable.teamID, teamID),
|
|
||||||
isNull(subscriptionTable.timeDeleted)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.orderBy(subscriptionTable.timeCreated)
|
|
||||||
.then((rows) => rows.map(serialize))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
export const fromUserID = fn(z.string(), async (userID) =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(subscriptionTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(subscriptionTable.userID, userID),
|
|
||||||
isNull(subscriptionTable.timeDeleted)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.orderBy(subscriptionTable.timeCreated)
|
|
||||||
.then((rows) => rows.map(serialize))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
export const remove = fn(Info.shape.id, (id) =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx
|
|
||||||
.update(subscriptionTable)
|
|
||||||
.set({
|
|
||||||
timeDeleted: Common.now(),
|
|
||||||
})
|
|
||||||
.where(eq(subscriptionTable.id, id))
|
|
||||||
.execute()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a raw subscription database record into a structured {@link Info} object.
|
|
||||||
*
|
|
||||||
* @param input - The subscription record retrieved from the database.
|
|
||||||
* @returns The subscription data formatted according to the {@link Info} schema.
|
|
||||||
*/
|
|
||||||
export function serialize(
|
|
||||||
input: typeof subscriptionTable.$inferSelect
|
|
||||||
): z.infer<typeof Info> {
|
|
||||||
return {
|
|
||||||
id: input.id,
|
|
||||||
userID: input.userID,
|
|
||||||
teamID: input.teamID,
|
|
||||||
standing: input.standing,
|
|
||||||
planType: input.planType,
|
|
||||||
tokens: input.tokens,
|
|
||||||
polarProductID: input.polarProductID,
|
|
||||||
polarSubscriptionID: input.polarSubscriptionID,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
|
||||||
|
const response = res.subscriptions
|
||||||
|
if (!response || response.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
response,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
checkoutID: group[0].checkoutID,
|
||||||
|
// next: group[0].next,
|
||||||
|
// frequency: group[0].frequency as any,
|
||||||
|
// quantity: group[0].quantity,
|
||||||
|
// productID: group[0].productID,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return result[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
export const fromCheckoutID = fn(z.string(), async (id) => {
|
||||||
|
const db = databaseClient()
|
||||||
|
const user = useCurrentUser()
|
||||||
|
const query = {
|
||||||
|
subscriptions: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
id,
|
||||||
|
//Make sure they can only get subscriptions they own
|
||||||
|
checkoutID: id,
|
||||||
|
canceledAt: { $isNull: true }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
|
||||||
|
const response = res.subscriptions
|
||||||
|
if (!response || response.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
response,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
checkoutID: group[0].checkoutID,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return result[0]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { teamTable } from "../team/team.sql";
|
|
||||||
import { ulid, userID, timestamps } from "../drizzle/types";
|
|
||||||
import { index, integer, pgTable, primaryKey, text, uniqueIndex, varchar } from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
export const Standing = ["new", "good", "overdue", "cancelled"] as const;
|
|
||||||
export const PlanType = ["free", "pro", "family", "enterprise"] as const;
|
|
||||||
|
|
||||||
export const subscriptionTable = pgTable(
|
|
||||||
"subscription",
|
|
||||||
{
|
|
||||||
...userID,
|
|
||||||
...timestamps,
|
|
||||||
teamID: ulid("team_id")
|
|
||||||
.references(() => teamTable.id, { onDelete: "cascade" })
|
|
||||||
.notNull(),
|
|
||||||
standing: text("standing", { enum: Standing })
|
|
||||||
.notNull(),
|
|
||||||
planType: text("plan_type", { enum: PlanType })
|
|
||||||
.notNull(),
|
|
||||||
tokens: integer("tokens").notNull(),
|
|
||||||
polarProductID: varchar("product_id", { length: 255 }),
|
|
||||||
polarSubscriptionID: varchar("subscription_id", { length: 255 }),
|
|
||||||
},
|
|
||||||
(table) => [
|
|
||||||
uniqueIndex("subscription_id").on(table.id),
|
|
||||||
index("subscription_user_id").on(table.userID),
|
|
||||||
primaryKey({
|
|
||||||
columns: [table.id, table.teamID]
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
331
packages/core/src/task/index.ts
Normal file
331
packages/core/src/task/index.ts
Normal file
@@ -0,0 +1,331 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { fn } from "../utils";
|
||||||
|
import { Resource } from "sst";
|
||||||
|
import { Aws } from "../aws/client";
|
||||||
|
import { Common } from "../common";
|
||||||
|
import { Examples } from "../examples";
|
||||||
|
import databaseClient from "../database"
|
||||||
|
import { useCurrentUser } from "../actor";
|
||||||
|
import { id as createID } from "@instantdb/admin";
|
||||||
|
import { groupBy, map, pipe, values } from "remeda"
|
||||||
|
import { Sessions } from "../session";
|
||||||
|
|
||||||
|
export const lastStatus = z.enum([
|
||||||
|
"RUNNING",
|
||||||
|
"PENDING",
|
||||||
|
"UNKNOWN",
|
||||||
|
"STOPPED",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const taskType = z.enum([
|
||||||
|
"AWS",
|
||||||
|
"ON_PREMISES",
|
||||||
|
"UNKNOWN"
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const healthStatus = z.enum([
|
||||||
|
"HEALTHY",
|
||||||
|
"UNHEALTHY",
|
||||||
|
"UNKNOWN",
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type taskType = z.infer<typeof taskType>;
|
||||||
|
export type lastStatus = z.infer<typeof lastStatus>;
|
||||||
|
export type healthStatus = z.infer<typeof healthStatus>;
|
||||||
|
|
||||||
|
export module Tasks {
|
||||||
|
export const Info = z
|
||||||
|
.object({
|
||||||
|
id: z.string().openapi({
|
||||||
|
description: Common.IdDescription,
|
||||||
|
example: Examples.Task.id,
|
||||||
|
}),
|
||||||
|
type: taskType.openapi({
|
||||||
|
description: "Where this task is hosted on",
|
||||||
|
example: Examples.Task.type,
|
||||||
|
}),
|
||||||
|
taskID: z.string().openapi({
|
||||||
|
description: "The id of this task as seen on AWS",
|
||||||
|
example: Examples.Task.taskID,
|
||||||
|
}),
|
||||||
|
startedAt: z.string().or(z.number()).openapi({
|
||||||
|
description: "The time this task was started",
|
||||||
|
example: Examples.Task.startedAt,
|
||||||
|
}),
|
||||||
|
lastUpdated: z.string().or(z.number()).openapi({
|
||||||
|
description: "The time the information about this task was last updated",
|
||||||
|
example: Examples.Task.lastUpdated,
|
||||||
|
}),
|
||||||
|
stoppedAt: z.string().or(z.number()).optional().openapi({
|
||||||
|
description: "The time this task was stopped or quit",
|
||||||
|
example: Examples.Task.lastUpdated,
|
||||||
|
}),
|
||||||
|
lastStatus: lastStatus.openapi({
|
||||||
|
description: "The last registered status of this task",
|
||||||
|
example: Examples.Task.lastStatus,
|
||||||
|
}),
|
||||||
|
healthStatus: healthStatus.openapi({
|
||||||
|
description: "The health status of this task",
|
||||||
|
example: Examples.Task.healthStatus,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.openapi({
|
||||||
|
ref: "Subscription",
|
||||||
|
description: "Subscription to a Nestri product.",
|
||||||
|
example: Examples.Task,
|
||||||
|
});
|
||||||
|
|
||||||
|
export type Info = z.infer<typeof Info>;
|
||||||
|
|
||||||
|
export const list = async () => {
|
||||||
|
const db = databaseClient()
|
||||||
|
const user = useCurrentUser()
|
||||||
|
|
||||||
|
try {
|
||||||
|
const query = {
|
||||||
|
tasks: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
stoppedAt: { $isNull: true },
|
||||||
|
owner: user.id
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await db.query(query)
|
||||||
|
|
||||||
|
const response = data.tasks
|
||||||
|
if (!response || response.length === 0) {
|
||||||
|
throw new Error("No task for this user were found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
response,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
taskID: group[0].taskID,
|
||||||
|
type: group[0].type as taskType,
|
||||||
|
lastStatus: group[0].lastStatus as lastStatus,
|
||||||
|
healthStatus: group[0].healthStatus as healthStatus,
|
||||||
|
startedAt: group[0].startedAt,
|
||||||
|
stoppedAt: group[0].stoppedAt,
|
||||||
|
lastUpdated: group[0].lastUpdated,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (e) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const create = async () => {
|
||||||
|
const user = useCurrentUser()
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
//TODO: Use a simpler way to set the session ID
|
||||||
|
// const sessionID = createID()
|
||||||
|
|
||||||
|
const sessionID = await Sessions.create({ public: true })
|
||||||
|
if (!sessionID) throw new Error("No session id was given");
|
||||||
|
|
||||||
|
const run = await Aws.EcsRunTask({
|
||||||
|
count: 1,
|
||||||
|
cluster: Resource.NestriGPUCluster.value,
|
||||||
|
taskDefinition: Resource.NestriGPUTask.value,
|
||||||
|
launchType: "EC2",
|
||||||
|
overrides: {
|
||||||
|
containerOverrides: [
|
||||||
|
{
|
||||||
|
name: "nestri",
|
||||||
|
environment: [
|
||||||
|
{
|
||||||
|
name: "NESTRI_ROOM",
|
||||||
|
value: sessionID
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!run.tasks || run.tasks.length === 0) {
|
||||||
|
throw new Error(`No tasks were started`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract task details
|
||||||
|
const task = run.tasks[0];
|
||||||
|
const taskArn = task?.taskArn!;
|
||||||
|
const taskId = taskArn.split('/').pop()!; // Extract task ID from ARN
|
||||||
|
const taskStatus = task?.lastStatus;
|
||||||
|
const taskHealthStatus = task?.healthStatus;
|
||||||
|
const startedAt = task?.startedAt!;
|
||||||
|
|
||||||
|
const id = createID()
|
||||||
|
const db = databaseClient()
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
await db.transact(db.tx.tasks[id]!.update({
|
||||||
|
taskID: taskId,
|
||||||
|
type: "AWS",
|
||||||
|
healthStatus: taskHealthStatus ? taskHealthStatus.toString() : "UNKNOWN",
|
||||||
|
startedAt: startedAt ? startedAt.toISOString() : now,
|
||||||
|
lastStatus: taskStatus,
|
||||||
|
lastUpdated: now,
|
||||||
|
}).link({ owner: user.id, sessions: sessionID }))
|
||||||
|
|
||||||
|
return id
|
||||||
|
} catch (e) {
|
||||||
|
console.error("error", e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fromID = fn(z.string(), async (taskID) => {
|
||||||
|
const db = databaseClient()
|
||||||
|
try {
|
||||||
|
const query = {
|
||||||
|
tasks: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
id: taskID,
|
||||||
|
stoppedAt: { $isNull: true }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await db.query(query)
|
||||||
|
|
||||||
|
const response = data.tasks
|
||||||
|
if (!response || response.length === 0) {
|
||||||
|
throw new Error("No task with the given id was found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
response,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
taskID: group[0].taskID,
|
||||||
|
type: group[0].type as taskType,
|
||||||
|
lastStatus: group[0].lastStatus as lastStatus,
|
||||||
|
healthStatus: group[0].healthStatus as healthStatus,
|
||||||
|
startedAt: group[0].startedAt,
|
||||||
|
stoppedAt: group[0].stoppedAt,
|
||||||
|
lastUpdated: group[0].lastUpdated,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return result[0]
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const update = fn(z.string(), async (taskID) => {
|
||||||
|
try {
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
tasks: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
id: taskID,
|
||||||
|
stoppedAt: { $isNull: true }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await db.query(query)
|
||||||
|
|
||||||
|
const response = data.tasks
|
||||||
|
if (!response || response.length === 0) {
|
||||||
|
throw new Error("No task with the given taskID was found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
const describeResponse = await Aws.EcsDescribeTasks({
|
||||||
|
tasks: [response[0]!.taskID],
|
||||||
|
cluster: Resource.NestriGPUCluster.value
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!describeResponse.tasks || describeResponse.tasks.length === 0) {
|
||||||
|
throw new Error("No tasks were found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = describeResponse.tasks[0]!
|
||||||
|
|
||||||
|
const updatedDb = {
|
||||||
|
healthStatus: task.healthStatus ? task.healthStatus : "UNKNOWN",
|
||||||
|
lastStatus: task.lastStatus ? task.lastStatus : "UNKNOWN",
|
||||||
|
lastUpdated: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.transact(db.tx.tasks[response[0]!.id]!.update({
|
||||||
|
...updatedDb
|
||||||
|
}))
|
||||||
|
|
||||||
|
const updatedRes = [{ ...response[0]!, ...updatedDb }]
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
updatedRes,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
taskID: group[0].taskID,
|
||||||
|
type: group[0].type as taskType,
|
||||||
|
lastStatus: group[0].lastStatus as lastStatus,
|
||||||
|
healthStatus: group[0].healthStatus as healthStatus,
|
||||||
|
startedAt: group[0].startedAt,
|
||||||
|
stoppedAt: group[0].stoppedAt,
|
||||||
|
lastUpdated: group[0].lastUpdated,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("update error", error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
export const stop = fn(z.object({ taskID: z.string(), id: z.string() }), async (input) => {
|
||||||
|
const db = databaseClient()
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
try {
|
||||||
|
//TODO:Check whether they own this task first
|
||||||
|
|
||||||
|
const stopResponse = await Aws.EcsStopTask({
|
||||||
|
task: input.taskID,
|
||||||
|
cluster: Resource.NestriGPUCluster.value,
|
||||||
|
reason: "Client requested a shutdown"
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!stopResponse.task) {
|
||||||
|
throw new Error(`No task was stopped`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.transact(db.tx.tasks[input.id]!.update({
|
||||||
|
stoppedAt: now,
|
||||||
|
lastUpdated: now,
|
||||||
|
lastStatus: "STOPPED",
|
||||||
|
healthStatus: "UNKNOWN"
|
||||||
|
}))
|
||||||
|
|
||||||
|
return "ok"
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error("stop error", error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { id, timestamps } from "../drizzle/types";
|
|
||||||
import { pgTable, uniqueIndex, varchar } from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
//This represents a task created on a machine for running a game
|
|
||||||
//Add billing info here?
|
|
||||||
//Add who owns the task here
|
|
||||||
// Add the session ID here
|
|
||||||
//Add which machine owns this task
|
|
||||||
|
|
||||||
export const taskTable = pgTable(
|
|
||||||
"task",
|
|
||||||
{
|
|
||||||
...id,
|
|
||||||
...timestamps,
|
|
||||||
fingerprint: varchar('fingerprint', { length: 32 }).notNull(),
|
|
||||||
},
|
|
||||||
(table) => [
|
|
||||||
uniqueIndex("task_fingerprint").on(table.fingerprint),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
@@ -1,218 +1,164 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
import databaseClient from "../database"
|
||||||
|
import { fn } from "../utils";
|
||||||
|
import { groupBy, map, pipe, values } from "remeda"
|
||||||
import { Common } from "../common";
|
import { Common } from "../common";
|
||||||
import { Member } from "../member";
|
|
||||||
import { teamTable } from "./team.sql";
|
|
||||||
import { Examples } from "../examples";
|
import { Examples } from "../examples";
|
||||||
import { assertActor } from "../actor";
|
import { useCurrentUser } from "../actor";
|
||||||
import { createEvent } from "../event";
|
import { id as createID } from "@instantdb/admin";
|
||||||
import { createID, fn } from "../utils";
|
|
||||||
import { Subscription } from "../subscription";
|
|
||||||
import { and, eq, sql, isNull } from "../drizzle";
|
|
||||||
import { memberTable } from "../member/member.sql";
|
|
||||||
import { ErrorCodes, VisibleError } from "../error";
|
|
||||||
import { groupBy, map, pipe, values } from "remeda";
|
|
||||||
import { subscriptionTable } from "../subscription/subscription.sql";
|
|
||||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
|
||||||
|
|
||||||
export namespace Team {
|
export namespace Teams {
|
||||||
export const Info = z
|
export const Info = z
|
||||||
.object({
|
.object({
|
||||||
id: z.string().openapi({
|
id: z.string().openapi({
|
||||||
description: Common.IdDescription,
|
description: Common.IdDescription,
|
||||||
example: Examples.Team.id,
|
example: Examples.Team.id,
|
||||||
}),
|
}),
|
||||||
// Remove spaces and make sure it is lowercase (this is just to make sure the frontend did this)
|
|
||||||
slug: z.string().regex(/^[a-z0-9\-]+$/, "Use a URL friendly name.").openapi({
|
|
||||||
description: "The unique and url-friendly slug of this team",
|
|
||||||
example: Examples.Team.slug
|
|
||||||
}),
|
|
||||||
name: z.string().openapi({
|
name: z.string().openapi({
|
||||||
description: "The name of this team",
|
description: "Name of the team",
|
||||||
example: Examples.Team.name
|
example: Examples.Team.name,
|
||||||
}),
|
}),
|
||||||
members: Member.Info.array().openapi({
|
createdAt: z.string().or(z.number()).openapi({
|
||||||
description: "The members of this team",
|
description: "The time when this team was first created",
|
||||||
example: Examples.Team.members
|
example: Examples.Team.createdAt,
|
||||||
}),
|
}),
|
||||||
subscriptions: Subscription.Info.array().openapi({
|
updatedAt: z.string().or(z.number()).openapi({
|
||||||
description: "The subscriptions of this team",
|
description: "The time when this team was last edited",
|
||||||
example: Examples.Team.subscriptions
|
example: Examples.Team.updatedAt,
|
||||||
}),
|
}),
|
||||||
|
// owner: z.boolean().openapi({
|
||||||
|
// description: "Whether this team is owned by this user",
|
||||||
|
// example: Examples.Team.owner,
|
||||||
|
// }),
|
||||||
|
slug: z.string().openapi({
|
||||||
|
description: "This is the unique name identifier for the team",
|
||||||
|
example: Examples.Team.slug
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.openapi({
|
.openapi({
|
||||||
ref: "Team",
|
ref: "Team",
|
||||||
description: "Represents a team on Nestri",
|
description: "A group of users sharing the same machines for gaming.",
|
||||||
example: Examples.Team,
|
example: Examples.Team,
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Info = z.infer<typeof Info>;
|
export type Info = z.infer<typeof Info>;
|
||||||
|
|
||||||
export const Events = {
|
export const list = async () => {
|
||||||
Created: createEvent(
|
const db = databaseClient()
|
||||||
"team.created",
|
const user = useCurrentUser()
|
||||||
z.object({
|
|
||||||
teamID: z.string().nonempty(),
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
export class TeamExistsError extends VisibleError {
|
const query = {
|
||||||
constructor(slug: string) {
|
teams: {
|
||||||
super(
|
$: {
|
||||||
"already_exists",
|
where: {
|
||||||
ErrorCodes.Validation.TEAM_ALREADY_EXISTS,
|
members: user.id,
|
||||||
`There is already a team named "${slug}"`
|
deletedAt: { $isNull: true }
|
||||||
);
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const create = fn(
|
const res = await db.query(query)
|
||||||
Info.pick({ slug: true, id: true, name: true, }).partial({
|
|
||||||
id: true,
|
|
||||||
}), (input) =>
|
|
||||||
createTransaction(async (tx) => {
|
|
||||||
const id = input.id ?? createID("team");
|
|
||||||
const result = await tx.insert(teamTable).values({
|
|
||||||
id,
|
|
||||||
slug: input.slug,
|
|
||||||
name: input.name
|
|
||||||
})
|
|
||||||
.onConflictDoNothing({ target: teamTable.slug })
|
|
||||||
|
|
||||||
if (result.count === 0) throw new TeamExistsError(input.slug);
|
const teams = res.teams
|
||||||
|
if (!teams || teams.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return id;
|
const result = pipe(
|
||||||
})
|
teams,
|
||||||
)
|
groupBy(x => x.id),
|
||||||
|
|
||||||
//TODO: "Delete" subscription and member(s) as well
|
|
||||||
export const remove = fn(Info.shape.id, (input) =>
|
|
||||||
useTransaction(async (tx) => {
|
|
||||||
const account = assertActor("user");
|
|
||||||
const row = await tx
|
|
||||||
.select({
|
|
||||||
teamID: memberTable.teamID,
|
|
||||||
})
|
|
||||||
.from(memberTable)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(memberTable.teamID, input),
|
|
||||||
eq(memberTable.email, account.properties.email),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.execute()
|
|
||||||
.then((rows) => rows.at(0));
|
|
||||||
if (!row) return;
|
|
||||||
await tx
|
|
||||||
.update(teamTable)
|
|
||||||
.set({
|
|
||||||
timeDeleted: sql`now()`,
|
|
||||||
})
|
|
||||||
.where(eq(teamTable.id, row.teamID));
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const list = fn(z.void(), () => {
|
|
||||||
const actor = assertActor("user");
|
|
||||||
return useTransaction(async (tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(teamTable)
|
|
||||||
.leftJoin(subscriptionTable, eq(subscriptionTable.teamID, teamTable.id))
|
|
||||||
.innerJoin(memberTable, eq(memberTable.teamID, teamTable.id))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(memberTable.email, actor.properties.email),
|
|
||||||
isNull(memberTable.timeDeleted),
|
|
||||||
isNull(teamTable.timeDeleted),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.execute()
|
|
||||||
.then((rows) => serialize(rows))
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
export const fromID = fn(z.string().min(1), async (id) =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(teamTable)
|
|
||||||
.leftJoin(subscriptionTable, eq(subscriptionTable.teamID, teamTable.id))
|
|
||||||
.innerJoin(memberTable, eq(memberTable.teamID, teamTable.id))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(teamTable.id, id),
|
|
||||||
isNull(memberTable.timeDeleted),
|
|
||||||
isNull(teamTable.timeDeleted),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.execute()
|
|
||||||
.then((rows) => serialize(rows).at(0))
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
export const fromSlug = fn(z.string().min(1), async (slug) =>
|
|
||||||
useTransaction(async (tx) =>
|
|
||||||
tx
|
|
||||||
.select()
|
|
||||||
.from(teamTable)
|
|
||||||
.leftJoin(subscriptionTable, eq(subscriptionTable.teamID, teamTable.id))
|
|
||||||
.innerJoin(memberTable, eq(memberTable.teamID, teamTable.id))
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(teamTable.slug, slug),
|
|
||||||
isNull(memberTable.timeDeleted),
|
|
||||||
isNull(teamTable.timeDeleted),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.execute()
|
|
||||||
.then((rows) => serialize(rows).at(0))
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms an array of team, subscription, and member records into structured team objects.
|
|
||||||
*
|
|
||||||
* Groups input rows by team ID and constructs an array of team objects, each including its associated members and subscriptions.
|
|
||||||
*
|
|
||||||
* @param input - Array of objects containing team, subscription, and member data.
|
|
||||||
* @returns An array of team objects with their members and subscriptions.
|
|
||||||
*/
|
|
||||||
export function serialize(
|
|
||||||
input: { team: typeof teamTable.$inferSelect, subscription: typeof subscriptionTable.$inferInsert | null, member: typeof memberTable.$inferInsert | null }[],
|
|
||||||
): z.infer<typeof Info>[] {
|
|
||||||
console.log("serialize", input)
|
|
||||||
return pipe(
|
|
||||||
input,
|
|
||||||
groupBy((row) => row.team.id),
|
|
||||||
values(),
|
values(),
|
||||||
map((group) => ({
|
map((group): Info => ({
|
||||||
name: group[0].team.name,
|
id: group[0].id,
|
||||||
id: group[0].team.id,
|
name: group[0].name,
|
||||||
slug: group[0].team.slug,
|
createdAt: group[0].createdAt,
|
||||||
subscriptions: !group[0].subscription ?
|
updatedAt: group[0].updatedAt,
|
||||||
[] :
|
slug: group[0].slug,
|
||||||
group.map((row) => ({
|
//@ts-expect-error
|
||||||
planType: row.subscription!.planType,
|
owner: group[0].owner === user.id
|
||||||
polarProductID: row.subscription!.polarProductID,
|
|
||||||
polarSubscriptionID: row.subscription!.polarSubscriptionID,
|
|
||||||
standing: row.subscription!.standing,
|
|
||||||
tokens: row.subscription!.tokens,
|
|
||||||
teamID: row.subscription!.teamID,
|
|
||||||
userID: row.subscription!.userID,
|
|
||||||
id: row.subscription!.id,
|
|
||||||
})),
|
|
||||||
members:
|
|
||||||
!group[0].member ?
|
|
||||||
[] :
|
|
||||||
group.map((row) => ({
|
|
||||||
id: row.member!.id,
|
|
||||||
email: row.member!.email,
|
|
||||||
role: row.member!.role,
|
|
||||||
teamID: row.member!.teamID,
|
|
||||||
timeSeen: row.member!.timeSeen,
|
|
||||||
}))
|
}))
|
||||||
})),
|
)
|
||||||
);
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const fromSlug = fn(z.string(), async (slug) => {
|
||||||
|
const db = databaseClient()
|
||||||
|
|
||||||
|
const query = {
|
||||||
|
teams: {
|
||||||
|
$: {
|
||||||
|
where: {
|
||||||
|
slug,
|
||||||
|
deletedAt: { $isNull: true }
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const res = await db.query(query)
|
||||||
|
|
||||||
|
const teams = res.teams
|
||||||
|
if (!teams || teams.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = pipe(
|
||||||
|
teams,
|
||||||
|
groupBy(x => x.id),
|
||||||
|
values(),
|
||||||
|
map((group): Info => ({
|
||||||
|
id: group[0].id,
|
||||||
|
name: group[0].name,
|
||||||
|
slug: group[0].slug,
|
||||||
|
createdAt: group[0].createdAt,
|
||||||
|
updatedAt: group[0].updatedAt,
|
||||||
|
// owner: group[0].owner === user.id
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
return result[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
export const create = fn(Info.pick({ name: true, slug: true }), async (input) => {
|
||||||
|
const id = createID()
|
||||||
|
const db = databaseClient()
|
||||||
|
const user = useCurrentUser()
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
|
await db.transact(db.tx.teams[id]!.update({
|
||||||
|
name: input.name,
|
||||||
|
slug: input.slug,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
}).link({ owner: user.id, members: user.id }))
|
||||||
|
|
||||||
|
return id
|
||||||
|
})
|
||||||
|
|
||||||
|
export const remove = fn(z.string(), async (id) => {
|
||||||
|
const db = databaseClient()
|
||||||
|
const now = new Date().toISOString()
|
||||||
|
|
||||||
|
await db.transact(db.tx.teams[id]!.update({
|
||||||
|
deletedAt: now
|
||||||
|
}))
|
||||||
|
|
||||||
|
return "ok"
|
||||||
|
})
|
||||||
|
|
||||||
|
export const invite = fn(z.object({email:z.string(), id: z.string()}), async (input) => {
|
||||||
|
//TODO:
|
||||||
|
// const db = databaseClient()
|
||||||
|
// const now = new Date().toISOString()
|
||||||
|
|
||||||
|
// await db.transact(db.tx.teams[id]!.update({
|
||||||
|
// deletedAt: now
|
||||||
|
// }))
|
||||||
|
|
||||||
|
return "ok"
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { timestamps, id } from "../drizzle/types";
|
|
||||||
import {
|
|
||||||
varchar,
|
|
||||||
pgTable,
|
|
||||||
primaryKey,
|
|
||||||
uniqueIndex,
|
|
||||||
} from "drizzle-orm/pg-core";
|
|
||||||
|
|
||||||
export const teamTable = pgTable(
|
|
||||||
"team",
|
|
||||||
{
|
|
||||||
...id,
|
|
||||||
...timestamps,
|
|
||||||
name: varchar("name", { length: 255 }).notNull(),
|
|
||||||
slug: varchar("slug", { length: 255 }).notNull(),
|
|
||||||
},
|
|
||||||
(table) => [
|
|
||||||
uniqueIndex("slug").on(table.slug)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
export function teamIndexes(table: any) {
|
|
||||||
return [
|
|
||||||
primaryKey({
|
|
||||||
columns: [table.teamID, table.id],
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
17
packages/core/src/types.ts
Normal file
17
packages/core/src/types.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export interface CloudflareCF {
|
||||||
|
colo: string;
|
||||||
|
continent: string;
|
||||||
|
country: string,
|
||||||
|
city: string;
|
||||||
|
region: string;
|
||||||
|
longitude: number;
|
||||||
|
latitude: number;
|
||||||
|
metroCode: string;
|
||||||
|
postalCode: string;
|
||||||
|
timezone: string;
|
||||||
|
regionCode: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CFRequest extends Request {
|
||||||
|
cf: CloudflareCF
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user