mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-11 00:05:36 +02:00
⭐ feat(api): Add payments with Polar.sh (#264)
## Description <!-- Briefly describe the purpose and scope of your changes --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a new subscription API endpoint for managing subscriptions and products. - Enhanced subscription management with new entities and functionalities. - Added functionality to retrieve current timestamps in both local and UTC formats. - Added Polar.sh integration with customer portal and checkout session creation APIs. - **Refactor** - Redesigned team details to now present members and subscription information instead of a plan type. - Enhanced member management by incorporating role assignments. - Streamlined user data handling and removed legacy subscription event logic. - Simplified error handling in actor functions for better clarity. - Updated plan types and UI labels to reflect new subscription tiers. - Improved database indexing for Steam user data. - **Chores** - Updated the database schema with new tables and fields to support subscription, team, and member enhancements. - Extended identifier prefixes to broaden system integration. - Added new secrets related to pricing plans in infrastructure configuration. - Configured API and auth routing with new domain and routing rules. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
27
infra/api.ts
27
infra/api.ts
@@ -6,15 +6,21 @@ import { cluster } from "./cluster";
|
||||
import { postgres } from "./postgres";
|
||||
|
||||
export const api = new sst.aws.Service("Api", {
|
||||
cluster,
|
||||
cpu: $app.stage === "production" ? "2 vCPU" : undefined,
|
||||
memory: $app.stage === "production" ? "4 GB" : undefined,
|
||||
cluster,
|
||||
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",
|
||||
@@ -23,16 +29,11 @@ export const api = new sst.aws.Service("Api", {
|
||||
NO_COLOR: "1",
|
||||
},
|
||||
loadBalancer: {
|
||||
domain: "api." + domain,
|
||||
rules: [
|
||||
{
|
||||
listen: "80/http",
|
||||
forward: "3001/http",
|
||||
},
|
||||
{
|
||||
listen: "443/https",
|
||||
forward: "3001/http",
|
||||
},
|
||||
],
|
||||
},
|
||||
dev: {
|
||||
@@ -47,4 +48,16 @@ export const api = new sst.aws.Service("Api", {
|
||||
max: 10,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
export const apiRoute = new sst.aws.Router("ApiRoute", {
|
||||
routes: {
|
||||
// I think api.url should work all the same
|
||||
"/*": api.nodes.loadBalancer.dnsName,
|
||||
},
|
||||
domain: {
|
||||
name: "api." + domain,
|
||||
dns: sst.cloudflare.dns(),
|
||||
},
|
||||
})
|
||||
@@ -1,26 +1,14 @@
|
||||
import { bus } from "./bus";
|
||||
import { domain } from "./dns";
|
||||
// import { email } from "./email";
|
||||
import { secret } from "./secret";
|
||||
import { postgres } from "./postgres";
|
||||
import { cluster } from "./cluster";
|
||||
// sst.Linkable.wrap(random.RandomString, (resource) => ({
|
||||
// properties: {
|
||||
// value: resource.result,
|
||||
// },
|
||||
// }));
|
||||
|
||||
// export const authFingerprintKey = new random.RandomString(
|
||||
// "AuthFingerprintKey",
|
||||
// {
|
||||
// length: 32,
|
||||
// },
|
||||
// );
|
||||
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,
|
||||
cluster,
|
||||
command: ["bun", "run", "./src/auth.ts"],
|
||||
link: [
|
||||
bus,
|
||||
@@ -38,18 +26,12 @@ export const auth = new sst.aws.Service("Auth", {
|
||||
NO_COLOR: "1",
|
||||
STORAGE: $dev ? "/tmp/persist.json" : "/mnt/efs/persist.json"
|
||||
},
|
||||
//TODO: Use API gateway instead, because of the API headers
|
||||
loadBalancer: {
|
||||
domain: "auth." + domain,
|
||||
rules: [
|
||||
{
|
||||
listen: "80/http",
|
||||
forward: "3002/http",
|
||||
},
|
||||
{
|
||||
listen: "443/https",
|
||||
forward: "3002/http",
|
||||
},
|
||||
],
|
||||
},
|
||||
permissions: [
|
||||
@@ -70,4 +52,15 @@ export const auth = new sst.aws.Service("Auth", {
|
||||
max: 10,
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
export const authRoute = new sst.aws.Router("AuthRoute", {
|
||||
routes: {
|
||||
// I think auth.url should work all the same
|
||||
"/*": auth.nodes.loadBalancer.dnsName,
|
||||
},
|
||||
domain: {
|
||||
name: "auth." + domain,
|
||||
dns: sst.cloudflare.dns(),
|
||||
},
|
||||
})
|
||||
@@ -1,11 +1,17 @@
|
||||
export const secret = {
|
||||
// InstantAppId: new sst.Secret("InstantAppId"),
|
||||
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"),
|
||||
// InstantAdminToken: new sst.Secret("InstantAdminToken"),
|
||||
DiscordClientSecret: new sst.Secret("DiscordClientSecret"),
|
||||
|
||||
// Pricing
|
||||
NestriFreeMonthly: new sst.Secret("NestriFreeMonthly"),
|
||||
NestriProMonthly: new sst.Secret("NestriProMonthly"),
|
||||
NestriProYearly: new sst.Secret("NestriProYearly"),
|
||||
NestriFamilyMonthly: new sst.Secret("NestriFamilyMonthly"),
|
||||
NestriFamilyYearly: new sst.Secret("NestriFamilyYearly"),
|
||||
};
|
||||
|
||||
export const allSecrets = Object.values(secret);
|
||||
2
packages/core/migrations/0006_worthless_dreadnoughts.sql
Normal file
2
packages/core/migrations/0006_worthless_dreadnoughts.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE "member" ADD COLUMN "role" text NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "team" DROP COLUMN "plan_type";
|
||||
15
packages/core/migrations/0007_warm_secret_warriors.sql
Normal file
15
packages/core/migrations/0007_warm_secret_warriors.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
CREATE TABLE "subscription" (
|
||||
"id" char(30) NOT NULL,
|
||||
"user_id" char(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"team_id" char(30) NOT NULL,
|
||||
"standing" text NOT NULL,
|
||||
"plan_type" text NOT NULL,
|
||||
"tokens" integer NOT NULL,
|
||||
"product_id" varchar(255),
|
||||
"subscription_id" varchar(255)
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "subscription" ADD CONSTRAINT "subscription_team_id_team_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."team"("id") ON DELETE cascade ON UPDATE no action;
|
||||
3
packages/core/migrations/0008_third_mindworm.sql
Normal file
3
packages/core/migrations/0008_third_mindworm.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE "subscription" ADD CONSTRAINT "subscription_id_team_id_pk" PRIMARY KEY("id","team_id");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "subscription_id" ON "subscription" USING btree ("id");--> statement-breakpoint
|
||||
CREATE INDEX "subscription_user_id" ON "subscription" USING btree ("user_id");
|
||||
2
packages/core/migrations/0009_luxuriant_wraith.sql
Normal file
2
packages/core/migrations/0009_luxuriant_wraith.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
CREATE UNIQUE INDEX "steam_id" ON "steam" USING btree ("steam_id");--> statement-breakpoint
|
||||
CREATE INDEX "steam_user_id" ON "steam" USING btree ("user_id");
|
||||
485
packages/core/migrations/meta/0006_snapshot.json
Normal file
485
packages/core/migrations/meta/0006_snapshot.json
Normal file
@@ -0,0 +1,485 @@
|
||||
{
|
||||
"id": "69827225-1351-4709-a9b2-facb0f569215",
|
||||
"prevId": "0b04858c-a7e3-43b6-98a4-1dc2f6f97488",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_seen": {
|
||||
"name": "last_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_user_id_user_id_fk": {
|
||||
"name": "steam_user_id_user_id_fk",
|
||||
"tableFrom": "steam",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
580
packages/core/migrations/meta/0007_snapshot.json
Normal file
580
packages/core/migrations/meta/0007_snapshot.json
Normal file
@@ -0,0 +1,580 @@
|
||||
{
|
||||
"id": "fff2b73d-85ab-48bc-86de-69d3caf317f0",
|
||||
"prevId": "69827225-1351-4709-a9b2-facb0f569215",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_seen": {
|
||||
"name": "last_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_user_id_user_id_fk": {
|
||||
"name": "steam_user_id_user_id_fk",
|
||||
"tableFrom": "steam",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.subscription": {
|
||||
"name": "subscription",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"standing": {
|
||||
"name": "standing",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tokens": {
|
||||
"name": "tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"subscription_team_id_team_id_fk": {
|
||||
"name": "subscription_team_id_team_id_fk",
|
||||
"tableFrom": "subscription",
|
||||
"tableTo": "team",
|
||||
"columnsFrom": [
|
||||
"team_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
619
packages/core/migrations/meta/0008_snapshot.json
Normal file
619
packages/core/migrations/meta/0008_snapshot.json
Normal file
@@ -0,0 +1,619 @@
|
||||
{
|
||||
"id": "17b9c14f-ff15-44a5-9aaf-3f3b7dd7d294",
|
||||
"prevId": "fff2b73d-85ab-48bc-86de-69d3caf317f0",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_seen": {
|
||||
"name": "last_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"steam_user_id_user_id_fk": {
|
||||
"name": "steam_user_id_user_id_fk",
|
||||
"tableFrom": "steam",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.subscription": {
|
||||
"name": "subscription",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"standing": {
|
||||
"name": "standing",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tokens": {
|
||||
"name": "tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"subscription_user_id": {
|
||||
"name": "subscription_user_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"subscription_team_id_team_id_fk": {
|
||||
"name": "subscription_team_id_team_id_fk",
|
||||
"tableFrom": "subscription",
|
||||
"tableTo": "team",
|
||||
"columnsFrom": [
|
||||
"team_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"subscription_id_team_id_pk": {
|
||||
"name": "subscription_id_team_id_pk",
|
||||
"columns": [
|
||||
"id",
|
||||
"team_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
650
packages/core/migrations/meta/0009_snapshot.json
Normal file
650
packages/core/migrations/meta/0009_snapshot.json
Normal file
@@ -0,0 +1,650 @@
|
||||
{
|
||||
"id": "1717c769-cee0-4242-bcbb-9538c80d985c",
|
||||
"prevId": "17b9c14f-ff15-44a5-9aaf-3f3b7dd7d294",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_seen": {
|
||||
"name": "last_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "steam_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"steam_user_id": {
|
||||
"name": "steam_user_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"steam_user_id_user_id_fk": {
|
||||
"name": "steam_user_id_user_id_fk",
|
||||
"tableFrom": "steam",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.subscription": {
|
||||
"name": "subscription",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"standing": {
|
||||
"name": "standing",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"tokens": {
|
||||
"name": "tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"subscription_user_id": {
|
||||
"name": "subscription_user_id",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"subscription_team_id_team_id_fk": {
|
||||
"name": "subscription_team_id_team_id_fk",
|
||||
"tableFrom": "subscription",
|
||||
"tableTo": "team",
|
||||
"columnsFrom": [
|
||||
"team_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"subscription_id_team_id_pk": {
|
||||
"name": "subscription_id_team_id_pk",
|
||||
"columns": [
|
||||
"id",
|
||||
"team_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,34 @@
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -109,43 +109,34 @@ export function assertActor<T extends Actor["type"]>(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 Error(`Expected actor to have teamID`);
|
||||
}
|
||||
|
||||
export function useMachine() {
|
||||
const actor = useActor();
|
||||
if ("machineID" in actor.properties) return actor.properties.fingerprint;
|
||||
throw new Error(`Expected actor to have fingerprint`);
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`Expected actor to have teamID`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the current user possesses the specified flag.
|
||||
* Returns the fingerprint of the current actor if the actor has a machine identity.
|
||||
*
|
||||
* This function executes a database transaction that queries the user table for the current user's flags.
|
||||
* If the flags are missing, it throws a {@link VisibleError} with the code {@link ErrorCodes.Validation.MISSING_REQUIRED_FIELD}
|
||||
* and a message indicating that the required flag is absent.
|
||||
*
|
||||
* @param flag - The name of the user flag to verify.
|
||||
*
|
||||
* @throws {VisibleError} If the user's flag is missing.
|
||||
* @returns The fingerprint of the current machine actor.
|
||||
* @throws {VisibleError} If the current actor does not have a machine identity.
|
||||
*/
|
||||
export async function assertUserFlag(flag: keyof UserFlags) {
|
||||
return useTransaction((tx) =>
|
||||
tx
|
||||
.select({ flags: userTable.flags })
|
||||
.from(userTable)
|
||||
.where(eq(userTable.id, useUserID()))
|
||||
.then((rows) => {
|
||||
const flags = rows[0]?.flags;
|
||||
if (!flags)
|
||||
throw new VisibleError(
|
||||
"not_found",
|
||||
ErrorCodes.Validation.MISSING_REQUIRED_FIELD,
|
||||
"Actor does not have " + flag + " flag",
|
||||
);
|
||||
}),
|
||||
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`
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
import { sql } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import "zod-openapi/extend";
|
||||
|
||||
export namespace Common {
|
||||
export const IdDescription = `Unique object identifier.
|
||||
The format and length of IDs may change over time.`;
|
||||
|
||||
export const now = () => sql`now()`;
|
||||
export const utc = () => sql`now() at time zone 'utc'`;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
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 });
|
||||
|
||||
|
||||
@@ -34,23 +34,38 @@ export namespace Examples {
|
||||
steamAccounts: [Steam]
|
||||
};
|
||||
|
||||
export const Team = {
|
||||
id: Id("team"),
|
||||
name: "John Does' Team",
|
||||
slug: "john_doe",
|
||||
planType: "BYOG" as const
|
||||
export const Product = {
|
||||
id: Id("product"),
|
||||
name: "RTX 4090",
|
||||
description: "Ideal for dedicated gamers who crave more flexibility and social gaming experiences.",
|
||||
tokensPerHour: 20,
|
||||
}
|
||||
|
||||
export const Subscription = {
|
||||
tokens: 100,
|
||||
id: Id("subscription"),
|
||||
userID: Id("user"),
|
||||
teamID: Id("team"),
|
||||
planType: "pro" as const, // free, pro, family, enterprise
|
||||
standing: "new" as const, // new, good, overdue, cancelled
|
||||
polarProductID: "0bfcb712-df13-4454-81a8-fbee66eddca4",
|
||||
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 Polar = {
|
||||
teamID: Id("team"),
|
||||
timeSeen: new Date("2025-02-23T13:39:52.249Z"),
|
||||
export const Team = {
|
||||
id: Id("team"),
|
||||
name: "John Does' Team",
|
||||
slug: "john_doe",
|
||||
subscriptions: [Subscription],
|
||||
members: [Member]
|
||||
}
|
||||
|
||||
export const Machine = {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { Common } from "../common";
|
||||
import { createID, fn } from "../utils";
|
||||
import { createEvent } from "../event";
|
||||
import { Examples } from "../examples";
|
||||
import { memberTable } from "./member.sql";
|
||||
import { memberTable, role } from "./member.sql";
|
||||
import { and, eq, sql, asc, isNull } from "../drizzle";
|
||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
|
||||
@@ -17,7 +17,7 @@ export namespace Member {
|
||||
description: Common.IdDescription,
|
||||
example: Examples.Member.id,
|
||||
}),
|
||||
timeSeen: z.date().or(z.null()).openapi({
|
||||
timeSeen: z.date().nullable().or(z.undefined()).openapi({
|
||||
description: "The last time this team member was active",
|
||||
example: Examples.Member.timeSeen
|
||||
}),
|
||||
@@ -25,6 +25,10 @@ export namespace Member {
|
||||
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
|
||||
@@ -68,6 +72,7 @@ export namespace Member {
|
||||
id,
|
||||
teamID: useTeam(),
|
||||
email: input.email,
|
||||
role: input.first ? "owner" : "member",
|
||||
timeSeen: input.first ? sql`now()` : null,
|
||||
})
|
||||
|
||||
@@ -113,11 +118,18 @@ export namespace Member {
|
||||
),
|
||||
)
|
||||
|
||||
/**
|
||||
* 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,12 +1,15 @@
|
||||
import { teamIndexes } from "../team/team.sql";
|
||||
import { timestamps, utc, teamID } from "../drizzle/types";
|
||||
import { index, pgTable, uniqueIndex, varchar } from "drizzle-orm/pg-core";
|
||||
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(),
|
||||
},
|
||||
|
||||
@@ -1,69 +1,16 @@
|
||||
import { z } from "zod";
|
||||
import { fn } from "../utils";
|
||||
import { Resource } from "sst";
|
||||
import { eq, and } from "../drizzle";
|
||||
import { useTeam } from "../actor";
|
||||
import { createEvent } from "../event";
|
||||
// import { polarTable, Standing } from "./polar.sql.ts.test";
|
||||
import { useTeam, useUserID } from "../actor";
|
||||
import { Polar as PolarSdk } from "@polar-sh/sdk";
|
||||
import { useTransaction } from "../drizzle/transaction";
|
||||
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 Info = z.object({
|
||||
teamID: z.string(),
|
||||
subscriptionID: z.string().nullable(),
|
||||
customerID: z.string(),
|
||||
subscriptionItemID: z.string().nullable(),
|
||||
// standing: z.enum(Standing),
|
||||
});
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
|
||||
export const Checkout = z.object({
|
||||
annual: z.boolean().optional(),
|
||||
successUrl: z.string(),
|
||||
cancelUrl: z.string(),
|
||||
});
|
||||
|
||||
export const CheckoutSession = z.object({
|
||||
url: z.string().nullable(),
|
||||
});
|
||||
|
||||
export const CustomerSubscriptionEventType = [
|
||||
"created",
|
||||
"updated",
|
||||
"deleted",
|
||||
] as const;
|
||||
|
||||
export const Events = {
|
||||
CustomerSubscriptionEvent: createEvent(
|
||||
"polar.customer-subscription-event",
|
||||
z.object({
|
||||
type: z.enum(CustomerSubscriptionEventType),
|
||||
status: z.string(),
|
||||
teamID: z.string().min(1),
|
||||
customerID: z.string().min(1),
|
||||
subscriptionID: z.string().min(1),
|
||||
subscriptionItemID: z.string().min(1),
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
// export function get() {
|
||||
// return useTransaction(async (tx) =>
|
||||
// tx
|
||||
// .select()
|
||||
// .from(polarTable)
|
||||
// .where(eq(polarTable.teamID, useTeam()))
|
||||
// .execute()
|
||||
// .then((rows) => rows.map(serialize).at(0)),
|
||||
// );
|
||||
// }
|
||||
|
||||
export const fromUserEmail = fn(z.string().min(1), async (email) => {
|
||||
try {
|
||||
const customers = await client.customers.list({ email })
|
||||
@@ -81,89 +28,69 @@ export namespace Polar {
|
||||
}
|
||||
})
|
||||
|
||||
// export const setCustomerID = fn(Info.shape.customerID, async (customerID) =>
|
||||
// useTransaction(async (tx) =>
|
||||
// tx
|
||||
// .insert(polarTable)
|
||||
// .values({
|
||||
// teamID: useTeam(),
|
||||
// customerID,
|
||||
// standing: "new",
|
||||
// })
|
||||
// .execute(),
|
||||
// ),
|
||||
// );
|
||||
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 setSubscription = fn(
|
||||
// Info.pick({
|
||||
// subscriptionID: true,
|
||||
// subscriptionItemID: true,
|
||||
// }),
|
||||
// (input) =>
|
||||
// useTransaction(async (tx) =>
|
||||
// tx
|
||||
// .update(polarTable)
|
||||
// .set({
|
||||
// subscriptionID: input.subscriptionID,
|
||||
// subscriptionItemID: input.subscriptionItemID,
|
||||
// })
|
||||
// .where(eq(polarTable.teamID, useTeam()))
|
||||
// .returning()
|
||||
// .execute()
|
||||
// .then((rows) => rows.map(serialize).at(0)),
|
||||
// ),
|
||||
// );
|
||||
export const createPortal = fn(
|
||||
z.string(),
|
||||
async (customerId) => {
|
||||
const session = await client.customerSessions.create({
|
||||
customerId
|
||||
})
|
||||
|
||||
// export const removeSubscription = fn(
|
||||
// z.string().min(1),
|
||||
// (stripeSubscriptionID) =>
|
||||
// useTransaction((tx) =>
|
||||
// tx
|
||||
// .update(polarTable)
|
||||
// .set({
|
||||
// subscriptionItemID: null,
|
||||
// subscriptionID: null,
|
||||
// })
|
||||
// .where(and(eq(polarTable.subscriptionID, stripeSubscriptionID)))
|
||||
// .execute(),
|
||||
// ),
|
||||
// );
|
||||
return session.customerPortalUrl
|
||||
}
|
||||
)
|
||||
|
||||
// export const setStanding = fn(
|
||||
// Info.pick({
|
||||
// subscriptionID: true,
|
||||
// standing: true,
|
||||
// }),
|
||||
// (input) =>
|
||||
// useTransaction((tx) =>
|
||||
// tx
|
||||
// .update(polarTable)
|
||||
// .set({ standing: input.standing })
|
||||
// .where(and(eq(polarTable.subscriptionID, input.subscriptionID!)))
|
||||
// .execute(),
|
||||
// ),
|
||||
// );
|
||||
//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 fromCustomerID = fn(Info.shape.customerID, (customerID) =>
|
||||
// useTransaction((tx) =>
|
||||
// tx
|
||||
// .select()
|
||||
// .from(polarTable)
|
||||
// .where(and(eq(polarTable.customerID, customerID)))
|
||||
// .execute()
|
||||
// .then((rows) => rows.map(serialize).at(0)),
|
||||
// ),
|
||||
// );
|
||||
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)
|
||||
|
||||
// function serialize(
|
||||
// input: typeof polarTable.$inferSelect,
|
||||
// ): z.infer<typeof Info> {
|
||||
// return {
|
||||
// teamID: input.teamID,
|
||||
// customerID: input.customerID,
|
||||
// subscriptionID: input.subscriptionID,
|
||||
// subscriptionItemID: input.subscriptionItemID,
|
||||
// standing: input.standing,
|
||||
// };
|
||||
// }
|
||||
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
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { timestamps, teamID } from "../drizzle/types";
|
||||
import { teamIndexes, teamTable } from "../team/team.sql";
|
||||
import { pgTable, text, varchar } from "drizzle-orm/pg-core";
|
||||
|
||||
// FIXME: This is causing errors while trying to db push
|
||||
export const Standing = ["new", "good", "overdue"] as const;
|
||||
|
||||
export const polarTable = pgTable(
|
||||
"polar",
|
||||
{
|
||||
teamID: teamID.teamID.primaryKey().references(() => teamTable.id),
|
||||
...timestamps,
|
||||
customerID: varchar("customer_id", { length: 255 }).notNull(),
|
||||
subscriptionID: varchar("subscription_id", { length: 255 }),
|
||||
subscriptionItemID: varchar("subscription_item_id", {
|
||||
length: 255,
|
||||
}),
|
||||
standing: text("standing", { enum: Standing }).notNull(),
|
||||
},
|
||||
(table) => [
|
||||
...teamIndexes(table),
|
||||
]
|
||||
)
|
||||
@@ -1,24 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import { id, timestamps, ulid, userID, utc } from "../drizzle/types";
|
||||
import { index, pgTable, integer, uniqueIndex, varchar, text, primaryKey, json } from "drizzle-orm/pg-core";
|
||||
import { userTable } from "../user/user.sql";
|
||||
|
||||
|
||||
// public string Username { get; set; } = string.Empty;
|
||||
// public ulong SteamId { get; set; }
|
||||
// public string Email { get; set; } = string.Empty;
|
||||
// public string Country { get; set; } = string.Empty;
|
||||
// public string PersonaName { get; set; } = string.Empty;
|
||||
// public string AvatarUrl { get; set; } = string.Empty;
|
||||
// public bool IsLimited { get; set; }
|
||||
// public bool IsLocked { get; set; }
|
||||
// public bool IsBanned { get; set; }
|
||||
// public bool IsAllowedToInviteFriends { get; set; }
|
||||
// public ulong GameId { get; set; }
|
||||
// public string GamePlayingName { get; set; } = string.Empty;
|
||||
// public DateTime LastLogOn { get; set; }
|
||||
// public DateTime LastLogOff { get; set; }
|
||||
// public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
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(),
|
||||
@@ -54,5 +37,9 @@ export const steamTable = pgTable(
|
||||
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),
|
||||
],
|
||||
);
|
||||
192
packages/core/src/subscription/index.ts
Normal file
192
packages/core/src/subscription/index.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { z } from "zod";
|
||||
import { Common } from "../common";
|
||||
import { Examples } from "../examples";
|
||||
import { createID, fn } from "../utils";
|
||||
import { eq, and, isNull } from "../drizzle";
|
||||
import { useTeam, useUserID } from "../actor";
|
||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
import { PlanType, Standing, subscriptionTable } from "./subscription.sql";
|
||||
|
||||
export namespace Subscription {
|
||||
export const Info = z.object({
|
||||
id: z.string().openapi({
|
||||
description: Common.IdDescription,
|
||||
example: Examples.Subscription.id,
|
||||
}),
|
||||
polarSubscriptionID: z.string().nullable().or(z.undefined()).openapi({
|
||||
description: "The unique id of the plan this subscription is on",
|
||||
example: Examples.Subscription.polarSubscriptionID,
|
||||
}),
|
||||
teamID: z.string().openapi({
|
||||
description: "The unique id of the team this subscription is for",
|
||||
example: Examples.Subscription.teamID,
|
||||
}),
|
||||
userID: z.string().openapi({
|
||||
description: "The unique id of the user who is paying this subscription",
|
||||
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",
|
||||
description: "Represents a subscription on Nestri",
|
||||
example: Examples.Subscription
|
||||
});
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
|
||||
export const create = fn(
|
||||
Info
|
||||
.partial({
|
||||
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({
|
||||
id,
|
||||
tokens: input.tokens,
|
||||
polarProductID: input.polarProductID ?? null,
|
||||
polarSubscriptionID: input.polarSubscriptionID ?? null,
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
31
packages/core/src/subscription/subscription.sql.ts
Normal file
31
packages/core/src/subscription/subscription.sql.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
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]
|
||||
}),
|
||||
]
|
||||
)
|
||||
@@ -1,16 +1,18 @@
|
||||
import { z } from "zod";
|
||||
import { Resource } from "sst";
|
||||
import { bus } from "sst/aws/bus";
|
||||
import { Common } from "../common";
|
||||
import { Member } from "../member";
|
||||
import { teamTable } from "./team.sql";
|
||||
import { Examples } from "../examples";
|
||||
import { assertActor } from "../actor";
|
||||
import { createEvent } from "../event";
|
||||
import { createID, fn } from "../utils";
|
||||
import { Subscription } from "../subscription";
|
||||
import { and, eq, sql, isNull } from "../drizzle";
|
||||
import { PlanType, teamTable } from "./team.sql";
|
||||
import { assertActor, withActor } from "../actor";
|
||||
import { memberTable } from "../member/member.sql";
|
||||
import { ErrorCodes, VisibleError } from "../error";
|
||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
import { groupBy, map, pipe, values } from "remeda";
|
||||
import { subscriptionTable } from "../subscription/subscription.sql";
|
||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
|
||||
export namespace Team {
|
||||
export const Info = z
|
||||
@@ -19,6 +21,7 @@ export namespace Team {
|
||||
description: Common.IdDescription,
|
||||
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
|
||||
@@ -27,10 +30,14 @@ export namespace Team {
|
||||
description: "The name of this team",
|
||||
example: Examples.Team.name
|
||||
}),
|
||||
planType: z.enum(PlanType).openapi({
|
||||
description: "The type of Plan this team is subscribed to",
|
||||
example: Examples.Team.planType
|
||||
})
|
||||
members: Member.Info.array().openapi({
|
||||
description: "The members of this team",
|
||||
example: Examples.Team.members
|
||||
}),
|
||||
subscriptions: Subscription.Info.array().openapi({
|
||||
description: "The subscriptions of this team",
|
||||
example: Examples.Team.subscriptions
|
||||
}),
|
||||
})
|
||||
.openapi({
|
||||
ref: "Team",
|
||||
@@ -60,16 +67,14 @@ export namespace Team {
|
||||
}
|
||||
|
||||
export const create = fn(
|
||||
Info.pick({ slug: true, id: true, name: true, planType: true }).partial({
|
||||
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,
|
||||
//Remove spaces and make sure it is lowercase (this is just to make sure the frontend did this)
|
||||
slug: input.slug, //.toLowerCase().replace(/[\s]/g, ''),
|
||||
planType: input.planType,
|
||||
slug: input.slug,
|
||||
name: input.name
|
||||
})
|
||||
.onConflictDoNothing({ target: teamTable.slug })
|
||||
@@ -80,6 +85,7 @@ export namespace Team {
|
||||
})
|
||||
)
|
||||
|
||||
//TODO: "Delete" subscription and member(s) as well
|
||||
export const remove = fn(Info.shape.id, (input) =>
|
||||
useTransaction(async (tx) => {
|
||||
const account = assertActor("user");
|
||||
@@ -106,48 +112,107 @@ export namespace Team {
|
||||
}),
|
||||
);
|
||||
|
||||
export const list = fn(z.void(), () =>
|
||||
useTransaction((tx) =>
|
||||
export const list = fn(z.void(), () => {
|
||||
const actor = assertActor("user");
|
||||
return useTransaction(async (tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(teamTable)
|
||||
.where(isNull(teamTable.timeDeleted))
|
||||
.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) => rows.map(serialize)),
|
||||
.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 fromID = fn(z.string().min(1), async (id) =>
|
||||
useTransaction(async (tx) => {
|
||||
return tx
|
||||
export const fromSlug = fn(z.string().min(1), async (slug) =>
|
||||
useTransaction(async (tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(teamTable)
|
||||
.where(and(eq(teamTable.id, id), isNull(teamTable.timeDeleted)))
|
||||
.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) => rows.map(serialize).at(0))
|
||||
}),
|
||||
);
|
||||
|
||||
export const fromSlug = fn(z.string().min(1), async (input) =>
|
||||
useTransaction(async (tx) => {
|
||||
return tx
|
||||
.select()
|
||||
.from(teamTable)
|
||||
.where(and(eq(teamTable.slug, input), isNull(teamTable.timeDeleted)))
|
||||
.execute()
|
||||
.then((rows) => rows.map(serialize).at(0))
|
||||
}),
|
||||
.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: typeof teamTable.$inferSelect,
|
||||
): z.infer<typeof Info> {
|
||||
return {
|
||||
id: input.id,
|
||||
name: input.name,
|
||||
slug: input.slug,
|
||||
planType: input.planType,
|
||||
};
|
||||
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(),
|
||||
map((group) => ({
|
||||
name: group[0].team.name,
|
||||
id: group[0].team.id,
|
||||
slug: group[0].team.slug,
|
||||
subscriptions: !group[0].subscription ?
|
||||
[] :
|
||||
group.map((row) => ({
|
||||
planType: row.subscription!.planType,
|
||||
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,
|
||||
}))
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,15 +1,11 @@
|
||||
import { } from "drizzle-orm/postgres-js";
|
||||
import { timestamps, id } from "../drizzle/types";
|
||||
import {
|
||||
varchar,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
uniqueIndex,
|
||||
text
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const PlanType = ["Hosted", "BYOG"] as const;
|
||||
|
||||
export const teamTable = pgTable(
|
||||
"team",
|
||||
{
|
||||
@@ -17,7 +13,6 @@ export const teamTable = pgTable(
|
||||
...timestamps,
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
slug: varchar("slug", { length: 255 }).notNull(),
|
||||
planType: text("plan_type", { enum: PlanType }).notNull()
|
||||
},
|
||||
(table) => [
|
||||
uniqueIndex("slug").on(table.slug)
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import { z } from "zod";
|
||||
import { Team } from "../team";
|
||||
import { bus } from "sst/aws/bus";
|
||||
import { Steam } from "../steam";
|
||||
import { Common } from "../common";
|
||||
import { Polar } from "../polar/index";
|
||||
import { createID, fn } from "../utils";
|
||||
import { userTable } from "./user.sql";
|
||||
import { createEvent } from "../event";
|
||||
import { pipe, groupBy, values, map } from "remeda";
|
||||
import { Examples } from "../examples";
|
||||
import { Resource } from "sst/resource";
|
||||
import { teamTable } from "../team/team.sql";
|
||||
import { steamTable } from "../steam/steam.sql";
|
||||
import { assertActor, withActor } from "../actor";
|
||||
import { memberTable } from "../member/member.sql";
|
||||
import { and, eq, isNull, asc, getTableColumns, sql } from "../drizzle";
|
||||
import { pipe, groupBy, values, map } from "remeda";
|
||||
import { and, eq, isNull, asc, sql } from "../drizzle";
|
||||
import { subscriptionTable } from "../subscription/subscription.sql";
|
||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
import { Steam } from "../steam";
|
||||
|
||||
|
||||
export namespace User {
|
||||
@@ -154,91 +155,27 @@ export namespace User {
|
||||
})
|
||||
|
||||
export const fromEmail = fn(z.string(), async (email) =>
|
||||
useTransaction(async (tx) => {
|
||||
const rows = await tx
|
||||
useTransaction(async (tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(userTable)
|
||||
.leftJoin(steamTable, eq(userTable.id, steamTable.userID))
|
||||
.where(and(eq(userTable.email, email), isNull(userTable.timeDeleted)))
|
||||
.orderBy(asc(userTable.timeCreated))
|
||||
|
||||
const result = pipe(
|
||||
rows,
|
||||
groupBy((row) => row.user.id),
|
||||
values(),
|
||||
map(
|
||||
(group): Info => ({
|
||||
id: group[0].user.id,
|
||||
name: group[0].user.name,
|
||||
email: group[0].user.email,
|
||||
avatarUrl: group[0].user.avatarUrl,
|
||||
discriminator: group[0].user.discriminator,
|
||||
polarCustomerID: group[0].user.polarCustomerID,
|
||||
steamAccounts: !group[0].steam ?
|
||||
[] :
|
||||
group.map((row) => ({
|
||||
id: row.steam!.id,
|
||||
userID: row.steam!.userID,
|
||||
steamID: row.steam!.steamID,
|
||||
lastSeen: row.steam!.lastSeen,
|
||||
avatarUrl: row.steam!.avatarUrl,
|
||||
lastGame: row.steam!.lastGame,
|
||||
username: row.steam!.username,
|
||||
countryCode: row.steam!.countryCode,
|
||||
steamEmail: row.steam!.steamEmail,
|
||||
personaName: row.steam!.personaName,
|
||||
limitation: row.steam!.limitation,
|
||||
})),
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
return result[0]
|
||||
}),
|
||||
.then((rows => serialize(rows).at(0)))
|
||||
)
|
||||
)
|
||||
|
||||
export const fromID = fn(z.string(), async (id) =>
|
||||
useTransaction(async (tx) => {
|
||||
const rows = await tx
|
||||
export const fromID = fn(z.string(), (id) =>
|
||||
useTransaction(async (tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(userTable)
|
||||
.leftJoin(steamTable, eq(userTable.id, steamTable.userID))
|
||||
.where(and(eq(userTable.id, id), isNull(userTable.timeDeleted)))
|
||||
.where(and(eq(userTable.id, id), isNull(userTable.timeDeleted), isNull(steamTable.timeDeleted)))
|
||||
.orderBy(asc(userTable.timeCreated))
|
||||
|
||||
const result = pipe(
|
||||
rows,
|
||||
groupBy((row) => row.user.id),
|
||||
values(),
|
||||
map(
|
||||
(group): Info => ({
|
||||
id: group[0].user.id,
|
||||
name: group[0].user.name,
|
||||
email: group[0].user.email,
|
||||
avatarUrl: group[0].user.avatarUrl,
|
||||
discriminator: group[0].user.discriminator,
|
||||
polarCustomerID: group[0].user.polarCustomerID,
|
||||
steamAccounts: !group[0].steam ?
|
||||
[] :
|
||||
group.map((row) => ({
|
||||
id: row.steam!.id,
|
||||
userID: row.steam!.userID,
|
||||
steamID: row.steam!.steamID,
|
||||
lastSeen: row.steam!.lastSeen,
|
||||
avatarUrl: row.steam!.avatarUrl,
|
||||
lastGame: row.steam!.lastGame,
|
||||
username: row.steam!.username,
|
||||
countryCode: row.steam!.countryCode,
|
||||
steamEmail: row.steam!.steamEmail,
|
||||
personaName: row.steam!.personaName,
|
||||
limitation: row.steam!.limitation,
|
||||
})),
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
return result[0]
|
||||
}),
|
||||
.then((rows) => serialize(rows).at(0))
|
||||
),
|
||||
)
|
||||
|
||||
export const remove = fn(Info.shape.id, (id) =>
|
||||
@@ -254,12 +191,54 @@ export namespace User {
|
||||
}),
|
||||
);
|
||||
|
||||
/**
|
||||
* Converts an array of user and Steam account records into structured user objects with associated Steam accounts.
|
||||
*
|
||||
* @param input - An array of objects containing user data and optional Steam account data.
|
||||
* @returns An array of user objects, each including a list of their associated Steam accounts.
|
||||
*/
|
||||
export function serialize(
|
||||
input: { user: typeof userTable.$inferSelect; steam: typeof steamTable.$inferSelect | null }[],
|
||||
): z.infer<typeof Info>[] {
|
||||
return pipe(
|
||||
input,
|
||||
groupBy((row) => row.user.id),
|
||||
values(),
|
||||
map((group) => ({
|
||||
...group[0].user,
|
||||
steamAccounts: !group[0].steam ?
|
||||
[] :
|
||||
group.map((row) => ({
|
||||
id: row.steam!.id,
|
||||
lastSeen: row.steam!.lastSeen,
|
||||
countryCode: row.steam!.countryCode,
|
||||
username: row.steam!.username,
|
||||
steamID: row.steam!.steamID,
|
||||
lastGame: row.steam!.lastGame,
|
||||
limitation: row.steam!.limitation,
|
||||
steamEmail: row.steam!.steamEmail,
|
||||
userID: row.steam!.userID,
|
||||
personaName: row.steam!.personaName,
|
||||
avatarUrl: row.steam!.avatarUrl,
|
||||
})),
|
||||
})),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of teams that the current user belongs to.
|
||||
*
|
||||
* @returns An array of team information objects representing the user's active team memberships.
|
||||
*
|
||||
* @remark Only teams and memberships that have not been deleted are included in the result.
|
||||
*/
|
||||
export function teams() {
|
||||
const actor = assertActor("user");
|
||||
return useTransaction((tx) =>
|
||||
return useTransaction(async (tx) =>
|
||||
tx
|
||||
.select(getTableColumns(teamTable))
|
||||
.select()
|
||||
.from(teamTable)
|
||||
.leftJoin(subscriptionTable, eq(subscriptionTable.teamID, teamTable.id))
|
||||
.innerJoin(memberTable, eq(memberTable.teamID, teamTable.id))
|
||||
.where(
|
||||
and(
|
||||
@@ -269,7 +248,7 @@ export namespace User {
|
||||
),
|
||||
)
|
||||
.execute()
|
||||
.then((rows) => rows.map(Team.serialize))
|
||||
);
|
||||
.then((rows) => Team.serialize(rows))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,14 @@ import { ulid } from "ulid";
|
||||
|
||||
export const prefixes = {
|
||||
user: "usr",
|
||||
team: "tea",
|
||||
team: "tem",
|
||||
task: "tsk",
|
||||
machine: "mch",
|
||||
member: "mbr",
|
||||
steam: "stm",
|
||||
subscription: "sub",
|
||||
invite: "inv",
|
||||
product: "prd",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -42,11 +42,6 @@ export const auth: MiddlewareHandler = async (c, next) => {
|
||||
"Invalid bearer token",
|
||||
);
|
||||
}
|
||||
|
||||
if (result.subject.type === "machine") {
|
||||
console.log("machine detected")
|
||||
return withActor(result.subject, next);
|
||||
}
|
||||
|
||||
if (result.subject.type === "user") {
|
||||
const teamID = c.req.header("x-nestri-team");
|
||||
@@ -58,12 +53,11 @@ export const auth: MiddlewareHandler = async (c, next) => {
|
||||
teamID,
|
||||
},
|
||||
},
|
||||
async () => {
|
||||
return withActor(
|
||||
async () =>
|
||||
withActor(
|
||||
result.subject,
|
||||
next,
|
||||
);
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,7 @@ import { Hono } from "hono";
|
||||
import { auth } from "./auth";
|
||||
import { cors } from "hono/cors";
|
||||
import { TeamApi } from "./team";
|
||||
import { SteamApi } from "./steam";
|
||||
import { PolarApi } from "./polar";
|
||||
import { logger } from "hono/logger";
|
||||
import { Realtime } from "./realtime";
|
||||
import { AccountApi } from "./account";
|
||||
@@ -27,7 +27,7 @@ const routes = app
|
||||
.get("/", (c) => c.text("Hello World!"))
|
||||
.route("/realtime", Realtime.route)
|
||||
.route("/team", TeamApi.route)
|
||||
.route("/steam", SteamApi.route)
|
||||
.route("/polar", PolarApi.route)
|
||||
.route("/account", AccountApi.route)
|
||||
.route("/machine", MachineApi.route)
|
||||
.onError((error, c) => {
|
||||
|
||||
174
packages/functions/src/api/polar.ts
Normal file
174
packages/functions/src/api/polar.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
import { z } from "zod";
|
||||
import { Hono } from "hono";
|
||||
import { Resource } from "sst";
|
||||
import { notPublic } from "./auth";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { User } from "@nestri/core/user/index";
|
||||
import { assertActor } from "@nestri/core/actor";
|
||||
import { Polar } from "@nestri/core/polar/index";
|
||||
import { Examples } from "@nestri/core/examples";
|
||||
import { ErrorResponses, Result, validator } from "./common";
|
||||
import { ErrorCodes, VisibleError } from "@nestri/core/error";
|
||||
import { PlanType } from "@nestri/core/subscription/subscription.sql";
|
||||
import { WebhookVerificationError, validateEvent } from "@polar-sh/sdk/webhooks";
|
||||
|
||||
export namespace PolarApi {
|
||||
export const route = new Hono()
|
||||
.use(notPublic)
|
||||
.get("/",
|
||||
describeRoute({
|
||||
tags: ["Polar"],
|
||||
summary: "Create a Polar.sh customer portal",
|
||||
description: "Creates Polar.sh's customer portal url where the user can manage their payments",
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Result(
|
||||
z.object({
|
||||
portalUrl: z.string()
|
||||
}).openapi({
|
||||
description: "The customer portal url",
|
||||
example: { portalUrl: "https://polar.sh/portal/39393jdie09292" }
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
description: "customer portal url"
|
||||
},
|
||||
400: ErrorResponses[400],
|
||||
404: ErrorResponses[404],
|
||||
429: ErrorResponses[429],
|
||||
}
|
||||
}),
|
||||
async (c) => {
|
||||
const actor = assertActor("user");
|
||||
|
||||
const user = await User.fromID(actor.properties.userID);
|
||||
|
||||
if (!user)
|
||||
throw new VisibleError(
|
||||
"not_found",
|
||||
ErrorCodes.NotFound.RESOURCE_NOT_FOUND,
|
||||
"User not found",
|
||||
);
|
||||
|
||||
if (!user.polarCustomerID)
|
||||
throw new VisibleError(
|
||||
"not_found",
|
||||
ErrorCodes.NotFound.RESOURCE_NOT_FOUND,
|
||||
"User does not contain Polar customer ID"
|
||||
)
|
||||
|
||||
const portalUrl = await Polar.createPortal(user.polarCustomerID)
|
||||
|
||||
return c.json({
|
||||
data: {
|
||||
portalUrl
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
.post("/checkout",
|
||||
describeRoute({
|
||||
tags: ["Polar"],
|
||||
summary: "Create a checkout url",
|
||||
description: "Creates a Polar.sh's checkout url for the user to pay a subscription for this team",
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Result(
|
||||
z.object({
|
||||
checkoutUrl: z.string()
|
||||
}).openapi({
|
||||
description: "The checkout url",
|
||||
example: { checkoutUrl: "https://polar.sh/portal/39393jdie09292" }
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
description: "checkout url"
|
||||
},
|
||||
400: ErrorResponses[400],
|
||||
404: ErrorResponses[404],
|
||||
429: ErrorResponses[429],
|
||||
}
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
z
|
||||
.object({
|
||||
planType: z.enum(PlanType),
|
||||
successUrl: z.string().url("Success url must be a valid url")
|
||||
})
|
||||
.openapi({
|
||||
description: "Details of the team to create",
|
||||
example: {
|
||||
planType: Examples.Subscription.planType,
|
||||
successUrl: "https://your-url.io/thanks"
|
||||
},
|
||||
})
|
||||
),
|
||||
async (c) => {
|
||||
const body = c.req.valid("json");
|
||||
const actor = assertActor("user");
|
||||
|
||||
const user = await User.fromID(actor.properties.userID);
|
||||
|
||||
if (!user)
|
||||
throw new VisibleError(
|
||||
"not_found",
|
||||
ErrorCodes.NotFound.RESOURCE_NOT_FOUND,
|
||||
"User not found",
|
||||
);
|
||||
|
||||
if (!user.polarCustomerID)
|
||||
throw new VisibleError(
|
||||
"not_found",
|
||||
ErrorCodes.NotFound.RESOURCE_NOT_FOUND,
|
||||
"User does not contain Polar customer ID"
|
||||
)
|
||||
|
||||
const checkoutUrl = await Polar.createCheckout({ customerID: user.polarCustomerID, planType: body.planType, successUrl: body.successUrl })
|
||||
|
||||
return c.json({
|
||||
data: {
|
||||
checkoutUrl,
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
.post("/webhook",
|
||||
async (c) => {
|
||||
const requestBody = await c.req.text();
|
||||
|
||||
const webhookSecret = Resource.PolarWebhookSecret.value
|
||||
|
||||
const webhookHeaders = {
|
||||
"webhook-id": c.req.header("webhook-id") ?? "",
|
||||
"webhook-timestamp": c.req.header("webhook-timestamp") ?? "",
|
||||
"webhook-signature": c.req.header("webhook-signature") ?? "",
|
||||
};
|
||||
|
||||
let webhookPayload: ReturnType<typeof validateEvent>;
|
||||
try {
|
||||
webhookPayload = validateEvent(
|
||||
requestBody,
|
||||
webhookHeaders,
|
||||
webhookSecret,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof WebhookVerificationError) {
|
||||
return c.json({ received: false }, { status: 403 });
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
await Polar.handleWebhook(webhookPayload)
|
||||
|
||||
return c.json({ received: true });
|
||||
}
|
||||
)
|
||||
}
|
||||
46
packages/functions/src/api/subscription.ts
Normal file
46
packages/functions/src/api/subscription.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Hono } from "hono";
|
||||
import { notPublic } from "./auth";
|
||||
import { describeRoute } from "hono-openapi";
|
||||
import { Examples } from "@nestri/core/examples";
|
||||
import { assertActor } from "@nestri/core/actor";
|
||||
import { ErrorResponses, Result } from "./common";
|
||||
import { Subscription } from "@nestri/core/subscription/index";
|
||||
|
||||
export namespace SubscriptionApi {
|
||||
export const route = new Hono()
|
||||
.use(notPublic)
|
||||
.get("/",
|
||||
describeRoute({
|
||||
tags: ["Subscription"],
|
||||
summary: "Get user subscriptions",
|
||||
description: "Get all user subscriptions",
|
||||
responses: {
|
||||
200: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Result(
|
||||
Subscription.Info.array().openapi({
|
||||
description: "All the subscriptions this user has",
|
||||
example: [Examples.Subscription]
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
description: "All user subscriptions"
|
||||
},
|
||||
400: ErrorResponses[400],
|
||||
404: ErrorResponses[404],
|
||||
429: ErrorResponses[429],
|
||||
}
|
||||
}),
|
||||
async (c) => {
|
||||
const actor = assertActor("user")
|
||||
|
||||
const subscriptions = await Subscription.fromUserID(actor.properties.userID)
|
||||
|
||||
return c.json({
|
||||
data: subscriptions
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -5,9 +5,12 @@ import { describeRoute } from "hono-openapi";
|
||||
import { User } from "@nestri/core/user/index";
|
||||
import { Team } from "@nestri/core/team/index";
|
||||
import { Examples } from "@nestri/core/examples";
|
||||
import { Polar } from "@nestri/core/polar/index";
|
||||
import { Member } from "@nestri/core/member/index";
|
||||
import { assertActor, withActor } from "@nestri/core/actor";
|
||||
import { ErrorResponses, Result, validator } from "./common";
|
||||
import { Subscription } from "@nestri/core/subscription/index";
|
||||
import { PlanType } from "@nestri/core/subscription/subscription.sql";
|
||||
|
||||
export namespace TeamApi {
|
||||
export const route = new Hono()
|
||||
@@ -49,7 +52,12 @@ export namespace TeamApi {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Result(
|
||||
z.literal("ok")
|
||||
z.object({
|
||||
checkoutUrl: z.string().openapi({
|
||||
description: "The checkout url to confirm subscription for this team",
|
||||
example: "https://polar.sh/checkout/2903038439320298377"
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -63,17 +71,24 @@ export namespace TeamApi {
|
||||
}),
|
||||
validator(
|
||||
"json",
|
||||
Team.create.schema.omit({ id: true }).openapi({
|
||||
description: "Details of the team to create",
|
||||
//@ts-expect-error
|
||||
example: { ...Examples.Team, id: undefined }
|
||||
})
|
||||
Team.create.schema
|
||||
.pick({ slug: true, name: true })
|
||||
.extend({ planType: z.enum(PlanType), successUrl: z.string().url("Success url must be a valid url") })
|
||||
.openapi({
|
||||
description: "Details of the team to create",
|
||||
example: {
|
||||
slug: Examples.Team.slug,
|
||||
name: Examples.Team.name,
|
||||
planType: Examples.Subscription.planType,
|
||||
successUrl: "https://your-url.io/thanks"
|
||||
},
|
||||
})
|
||||
),
|
||||
async (c) => {
|
||||
const body = c.req.valid("json")
|
||||
const actor = assertActor("user");
|
||||
|
||||
const teamID = await Team.create(body);
|
||||
const teamID = await Team.create({ name: body.name, slug: body.slug });
|
||||
|
||||
await withActor(
|
||||
{
|
||||
@@ -82,14 +97,28 @@ export namespace TeamApi {
|
||||
teamID,
|
||||
},
|
||||
},
|
||||
() =>
|
||||
Member.create({
|
||||
async () => {
|
||||
await Member.create({
|
||||
first: true,
|
||||
email: actor.properties.email,
|
||||
}),
|
||||
});
|
||||
|
||||
await Subscription.create({
|
||||
planType: body.planType,
|
||||
userID: actor.properties.userID,
|
||||
// FIXME: Make this make sense
|
||||
tokens: body.planType === "free" ? 100 : body.planType === "pro" ? 1000 : body.planType === "family" ? 10000 : 0,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
return c.json({ data: "ok" })
|
||||
const checkoutUrl = await Polar.createCheckout({ planType: body.planType, successUrl: body.successUrl, teamID })
|
||||
|
||||
return c.json({
|
||||
data: {
|
||||
checkoutUrl,
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
24
packages/functions/sst-env.d.ts
vendored
24
packages/functions/sst-env.d.ts
vendored
@@ -57,10 +57,34 @@ declare module "sst" {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"NestriFamilyMonthly": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"NestriFamilyYearly": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"NestriFreeMonthly": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"NestriProMonthly": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"NestriProYearly": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"PolarSecret": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"PolarWebhookSecret": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Realtime": {
|
||||
"authorizer": string
|
||||
"endpoint": string
|
||||
|
||||
0
packages/scripts/src/db-reset.sh
Executable file → Normal file
0
packages/scripts/src/db-reset.sh
Executable file → Normal file
0
packages/scripts/src/psql.sh
Executable file → Normal file
0
packages/scripts/src/psql.sh
Executable file → Normal file
@@ -8,8 +8,8 @@ import { useNavigate } from "@solidjs/router";
|
||||
import { useOpenAuth } from "@openauthjs/solid";
|
||||
import { utility } from "@nestri/www/ui/utility";
|
||||
import { useAccount } from "../providers/account";
|
||||
import { Container, Screen as FullScreen } from "@nestri/www/ui/layout";
|
||||
import { FormField, Input, Select } from "@nestri/www/ui/form";
|
||||
import { Container, Screen as FullScreen } from "@nestri/www/ui/layout";
|
||||
import { createForm, getValue, setError, valiForm } from "@modular-forms/solid";
|
||||
|
||||
const nameRegex = /^[a-z0-9\-]+$/
|
||||
@@ -32,8 +32,9 @@ const Hr = styled("hr", {
|
||||
})
|
||||
|
||||
const Plan = {
|
||||
Pro: 'BYOG',
|
||||
Basic: 'Hosted',
|
||||
Free: 'free',
|
||||
Pro: 'pro',
|
||||
Family: 'family',
|
||||
} as const;
|
||||
|
||||
const schema = v.object({
|
||||
@@ -110,6 +111,13 @@ const UrlTitle = styled("span", {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Renders a form for creating a new team with validated fields for team name, slug, and plan type.
|
||||
*
|
||||
* Submits the form data to the API to create the team, displays validation errors, and navigates to the new team's page upon success.
|
||||
*
|
||||
* @remark If the chosen team slug is already taken, an error message is shown for the slug field.
|
||||
*/
|
||||
export function CreateTeamComponent() {
|
||||
const [form, { Form, Field }] = createForm({
|
||||
validate: valiForm(schema),
|
||||
@@ -215,12 +223,14 @@ export function CreateTeamComponent() {
|
||||
required
|
||||
value={field.value}
|
||||
badges={[
|
||||
{ label: "BYOG", color: "purple" },
|
||||
{ label: "Hosted", color: "blue" },
|
||||
{ label: "Free", color: "gray" },
|
||||
{ label: "Pro", color: "blue" },
|
||||
{ label: "Family", color: "purple" },
|
||||
]}
|
||||
options={[
|
||||
{ label: "I'll be playing on my machine", value: 'BYOG' },
|
||||
{ label: "I'll be playing on the cloud", value: 'Hosted' },
|
||||
{ label: "I'll be playing by myself", value: 'free' },
|
||||
{ label: "I'll be playing with 3 friends", value: 'pro' },
|
||||
{ label: "I'll be playing with 5 family members", value: 'family' },
|
||||
]}
|
||||
/>
|
||||
</FormField>
|
||||
|
||||
@@ -201,15 +201,12 @@ const Nav = styled("nav", {
|
||||
})
|
||||
|
||||
/**
|
||||
* Renders the application's header, featuring navigation, branding, and team details.
|
||||
* Displays the application's fixed top navigation bar with branding, team information, and navigation links.
|
||||
*
|
||||
* This component displays a navigation bar that includes the logo, team avatar, team name, a badge
|
||||
* reflecting the team's plan type, and navigation links. It adjusts its styling based on the scroll
|
||||
* position by toggling visual effects on the navigation wrapper. A scroll event listener is added
|
||||
* on mount to update the header's appearance when the user scrolls and is removed on unmount.
|
||||
* The header includes the app logo, team avatar and name, a badge indicating the team's plan type, and navigation links related to the team. The header's appearance updates dynamically based on the user's scroll position.
|
||||
*
|
||||
* @param props.children - Optional child elements rendered below the header component.
|
||||
* @returns The header component element.
|
||||
* @param props.children - Optional elements rendered below the header.
|
||||
* @returns The rendered header component.
|
||||
*/
|
||||
export function Header(props: ParentProps) {
|
||||
// const team = useContext(TeamContext)
|
||||
@@ -218,7 +215,7 @@ export function Header(props: ParentProps) {
|
||||
id: "tea_01JPACSPYWTTJ66F32X3AWWFWE",
|
||||
slug: "wanjohiryan",
|
||||
name: "Wanjohi",
|
||||
planType: "BYOG"
|
||||
planType: "Pro"
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
@@ -231,8 +228,9 @@ export function Header(props: ParentProps) {
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
|
||||
// const account = useAccount()
|
||||
|
||||
return (
|
||||
<PageWrapper>
|
||||
<NavWrapper scrolled={hasScrolled()}>
|
||||
@@ -294,14 +292,14 @@ export function Header(props: ParentProps) {
|
||||
/>
|
||||
<TeamLabel style={{ color: theme.color.d1000.gray }}>{team!().name}</TeamLabel>
|
||||
<Switch>
|
||||
<Match when={team!().planType === "BYOG"}>
|
||||
<Match when={team!().planType === "Family"}>
|
||||
<Badge style={{ "background-color": theme.color.purple.d700 }}>
|
||||
<span style={{ "line-height": 0 }} >BYOG</span>
|
||||
<span style={{ "line-height": 0 }} >Family</span>
|
||||
</Badge>
|
||||
</Match>
|
||||
<Match when={team!().planType === "Hosted"}>
|
||||
<Match when={team!().planType === "Pro"}>
|
||||
<Badge style={{ "background-color": theme.color.blue.d700 }}>
|
||||
<span style={{ "line-height": 0 }}>Hosted</span>
|
||||
<span style={{ "line-height": 0 }}>Pro</span>
|
||||
</Badge>
|
||||
</Match>
|
||||
</Switch>
|
||||
|
||||
@@ -44,7 +44,7 @@ export const TeamRoute = (
|
||||
return (
|
||||
<Switch>
|
||||
<Match when={!team()}>
|
||||
TODO: Add a public page for (other) teams
|
||||
{/* TODO: Add a public page for (other) teams */}
|
||||
<NotAllowed header />
|
||||
</Match>
|
||||
<Match when={team()}>
|
||||
|
||||
@@ -157,6 +157,7 @@ export const InputRadio = styled("input", {
|
||||
const Label = styled("p", {
|
||||
base: {
|
||||
fontWeight: 500,
|
||||
textAlign: "left",
|
||||
letterSpacing: -0.1,
|
||||
fontSize: theme.font.size.mono_sm,
|
||||
textTransform: "capitalize",
|
||||
@@ -213,6 +214,7 @@ const Hint = styled("p", {
|
||||
fontSize: theme.font.size.xs,
|
||||
lineHeight: theme.font.lineHeight,
|
||||
color: theme.color.gray.d800,
|
||||
textAlign: "left"
|
||||
},
|
||||
variants: {
|
||||
color: {
|
||||
|
||||
24
sst-env.d.ts
vendored
24
sst-env.d.ts
vendored
@@ -57,10 +57,34 @@ declare module "sst" {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"NestriFamilyMonthly": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"NestriFamilyYearly": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"NestriFreeMonthly": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"NestriProMonthly": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"NestriProYearly": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"PolarSecret": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"PolarWebhookSecret": {
|
||||
"type": "sst.sst.Secret"
|
||||
"value": string
|
||||
}
|
||||
"Realtime": {
|
||||
"authorizer": string
|
||||
"endpoint": string
|
||||
|
||||
Reference in New Issue
Block a user