mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
⭐ feat(www): Add logic to the homepage and Steam integration (#258)
## 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** - Upgraded API and authentication services with dynamic scaling, enhanced load balancing, and real-time interaction endpoints. - Introduced new commands to streamline local development and container builds. - Added new endpoints for retrieving Steam account information and managing connections. - Implemented a QR code authentication interface for Steam, enhancing user login experiences. - **Database Updates** - Rolled out comprehensive schema migrations that improve data integrity and indexing. - Introduced new tables for managing Steam user credentials and machine information. - **UI Enhancements** - Added refreshed animated assets and an improved QR code login flow for a more engaging experience. - Introduced new styled components for displaying friends and games. - **Maintenance** - Completed extensive refactoring and configuration updates to optimize performance and development workflows. - Updated logging configurations and improved error handling mechanisms. - Streamlined resource definitions in the configuration files. <!-- 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:
17
packages/core/migrations/0002_simple_outlaw_kid.sql
Normal file
17
packages/core/migrations/0002_simple_outlaw_kid.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
CREATE TABLE "steam" (
|
||||
"id" char(30) NOT NULL,
|
||||
"user_id" char(30) NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"avatar_url" text NOT NULL,
|
||||
"access_token" text NOT NULL,
|
||||
"email" varchar(255) NOT NULL,
|
||||
"country" varchar(255) NOT NULL,
|
||||
"username" varchar(255) NOT NULL,
|
||||
"persona_name" varchar(255) NOT NULL,
|
||||
CONSTRAINT "steam_user_id_id_pk" PRIMARY KEY("user_id","id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE INDEX "global_steam_email" ON "steam" USING btree ("email");--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "steam_email" ON "steam" USING btree ("user_id","email");
|
||||
22
packages/core/migrations/0003_first_big_bertha.sql
Normal file
22
packages/core/migrations/0003_first_big_bertha.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE "machine" (
|
||||
"id" char(30) PRIMARY KEY NOT NULL,
|
||||
"time_created" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_updated" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"time_deleted" timestamp with time zone,
|
||||
"country" text NOT NULL,
|
||||
"timezone" text NOT NULL,
|
||||
"location" "point" NOT NULL,
|
||||
"fingerprint" varchar(32) NOT NULL,
|
||||
"country_code" varchar(2) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "steam" RENAME COLUMN "country" TO "country_code";--> statement-breakpoint
|
||||
DROP INDEX "global_steam_email";--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD COLUMN "time_seen" timestamp with time zone;--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD COLUMN "steam_id" integer NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD COLUMN "last_game" json NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD COLUMN "steam_email" varchar(255) NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "steam" ADD COLUMN "limitation" json NOT NULL;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "machine_fingerprint" ON "machine" USING btree ("fingerprint");--> statement-breakpoint
|
||||
ALTER TABLE "steam" DROP COLUMN "access_token";--> statement-breakpoint
|
||||
ALTER TABLE "user" DROP COLUMN "flags";
|
||||
@@ -1,94 +1,9 @@
|
||||
{
|
||||
"id": "aa60489b-b4e2-4a69-aee7-16e050d02ef9",
|
||||
"id": "227c54d2-b643-48d5-964b-af6fe004369a",
|
||||
"prevId": "6f428226-b5d8-4182-a676-d04f842f9ded",
|
||||
"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": "",
|
||||
@@ -191,6 +106,132 @@
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"global_steam_email": {
|
||||
"name": "global_steam_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"steam_user_id_id_pk": {
|
||||
"name": "steam_user_id_id_pk",
|
||||
"columns": [
|
||||
"user_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
|
||||
507
packages/core/migrations/meta/0003_snapshot.json
Normal file
507
packages/core/migrations/meta/0003_snapshot.json
Normal file
@@ -0,0 +1,507 @@
|
||||
{
|
||||
"id": "eb5d41aa-5f85-4b2d-8633-fc021b211241",
|
||||
"prevId": "227c54d2-b643-48d5-964b-af6fe004369a",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.machine": {
|
||||
"name": "machine",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"timezone": {
|
||||
"name": "timezone",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"location": {
|
||||
"name": "location",
|
||||
"type": "point",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"fingerprint": {
|
||||
"name": "fingerprint",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"machine_fingerprint": {
|
||||
"name": "machine_fingerprint",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "fingerprint",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.member": {
|
||||
"name": "member",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"email_global": {
|
||||
"name": "email_global",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": false,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
},
|
||||
"member_email": {
|
||||
"name": "member_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "team_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"member_team_id_id_pk": {
|
||||
"name": "member_team_id_id_pk",
|
||||
"columns": [
|
||||
"team_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.steam": {
|
||||
"name": "steam",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"time_seen": {
|
||||
"name": "time_seen",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"steam_id": {
|
||||
"name": "steam_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"last_game": {
|
||||
"name": "last_game",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country_code": {
|
||||
"name": "country_code",
|
||||
"type": "varchar(2)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"persona_name": {
|
||||
"name": "persona_name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"limitation": {
|
||||
"name": "limitation",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"steam_email": {
|
||||
"name": "steam_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "user_id",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
},
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {
|
||||
"steam_user_id_id_pk": {
|
||||
"name": "steam_user_id_id_pk",
|
||||
"columns": [
|
||||
"user_id",
|
||||
"id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.team": {
|
||||
"name": "team",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"plan_type": {
|
||||
"name": "plan_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"slug": {
|
||||
"name": "slug",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "slug",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "char(30)",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"time_created": {
|
||||
"name": "time_created",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_updated": {
|
||||
"name": "time_updated",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"time_deleted": {
|
||||
"name": "time_deleted",
|
||||
"type": "timestamp with time zone",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"avatar_url": {
|
||||
"name": "avatar_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"discriminator": {
|
||||
"name": "discriminator",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"polar_customer_id": {
|
||||
"name": "polar_customer_id",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"user_email": {
|
||||
"name": "user_email",
|
||||
"columns": [
|
||||
{
|
||||
"expression": "email",
|
||||
"isExpression": false,
|
||||
"asc": true,
|
||||
"nulls": "last"
|
||||
}
|
||||
],
|
||||
"isUnique": true,
|
||||
"concurrently": false,
|
||||
"method": "btree",
|
||||
"with": {}
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_polar_customer_id_unique": {
|
||||
"name": "user_polar_customer_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"polar_customer_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,15 @@
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1743028682022,
|
||||
"tag": "0002_tiny_toad_men",
|
||||
"when": 1743794969007,
|
||||
"tag": "0002_simple_outlaw_kid",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"version": "7",
|
||||
"when": 1744287542918,
|
||||
"tag": "0003_first_big_bertha",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"db:dev": "drizzle-kit",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"db": "sst shell drizzle-kit",
|
||||
"db:exec": "sst shell ../scripts/src/psql.sh",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { eq } from "./drizzle";
|
||||
import { VisibleError } from "./error";
|
||||
import { ErrorCodes, VisibleError } from "./error";
|
||||
import { createContext } from "./context";
|
||||
import { UserFlags, userTable } from "./user/user.sql";
|
||||
import { useTransaction } from "./drizzle/transaction";
|
||||
@@ -60,11 +60,42 @@ export const ActorContext = createContext<Actor>("actor");
|
||||
export const useActor = ActorContext.use;
|
||||
export const withActor = ActorContext.with;
|
||||
|
||||
/**
|
||||
* Retrieves the user ID of the current actor.
|
||||
*
|
||||
* This function accesses the actor context and returns the `userID` if the current
|
||||
* actor is of type "user". If the actor is not a user, it throws a `VisibleError`
|
||||
* with an authentication error code, indicating that the caller is not authorized
|
||||
* to access user-specific resources.
|
||||
*
|
||||
* @throws {VisibleError} When the current actor is not of type "user".
|
||||
*/
|
||||
export function useUserID() {
|
||||
const actor = ActorContext.use();
|
||||
if (actor.type === "user") return actor.properties.userID;
|
||||
throw new VisibleError(
|
||||
"unauthorized",
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the properties of the current user actor.
|
||||
*
|
||||
* This function obtains the current actor from the context and returns its properties if the actor is identified as a user.
|
||||
* If the actor is not of type "user", it throws a {@link VisibleError} with an authentication error code,
|
||||
* indicating that the user is not authorized to access user-specific resources.
|
||||
*
|
||||
* @returns The properties of the current user actor, typically including user-specific details such as userID and email.
|
||||
* @throws {VisibleError} If the current actor is not a user.
|
||||
*/
|
||||
export function useUser() {
|
||||
const actor = ActorContext.use();
|
||||
if (actor.type === "user") return actor.properties;
|
||||
throw new VisibleError(
|
||||
"authentication",
|
||||
ErrorCodes.Authentication.UNAUTHORIZED,
|
||||
`You don't have permission to access this resource`,
|
||||
);
|
||||
}
|
||||
@@ -90,6 +121,17 @@ export function useMachine() {
|
||||
throw new Error(`Expected actor to have fingerprint`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that the current user possesses the specified flag.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
export async function assertUserFlag(flag: keyof UserFlags) {
|
||||
return useTransaction((tx) =>
|
||||
tx
|
||||
@@ -100,7 +142,8 @@ export async function assertUserFlag(flag: keyof UserFlags) {
|
||||
const flags = rows[0]?.flags;
|
||||
if (!flags)
|
||||
throw new VisibleError(
|
||||
"user.flags",
|
||||
"not_found",
|
||||
ErrorCodes.Validation.MISSING_REQUIRED_FIELD,
|
||||
"Actor does not have " + flag + " flag",
|
||||
);
|
||||
}),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { z } from "zod";
|
||||
import "zod-openapi/extend";
|
||||
|
||||
export module Common {
|
||||
export namespace Common {
|
||||
export const IdDescription = `Unique object identifier.
|
||||
The format and length of IDs may change over time.`;
|
||||
}
|
||||
@@ -17,6 +17,15 @@ export const teamID = {
|
||||
},
|
||||
};
|
||||
|
||||
export const userID = {
|
||||
get id() {
|
||||
return ulid("id").notNull();
|
||||
},
|
||||
get userID() {
|
||||
return ulid("user_id").notNull();
|
||||
},
|
||||
};
|
||||
|
||||
export const utc = (name: string) =>
|
||||
rawTs(name, {
|
||||
withTimezone: true,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { prefixes } from "./utils";
|
||||
export module Examples {
|
||||
export namespace Examples {
|
||||
export const Id = (prefix: keyof typeof prefixes) =>
|
||||
`${prefixes[prefix]}_XXXXXXXXXXXXXXXXXXXXXXXXX`;
|
||||
|
||||
@@ -31,8 +31,30 @@ export module Examples {
|
||||
timeSeen: new Date("2025-02-23T13:39:52.249Z"),
|
||||
}
|
||||
|
||||
export const Steam = {
|
||||
id: Id("steam"),
|
||||
userID: Id("user"),
|
||||
countryCode: "KE",
|
||||
steamID: 74839300282033,
|
||||
limitation: {
|
||||
isLimited: false,
|
||||
isBanned: false,
|
||||
isLocked: false,
|
||||
isAllowedToInviteFriends: false,
|
||||
},
|
||||
lastGame: {
|
||||
gameID: 2531310,
|
||||
gameName: "The Last of Us™ Part II Remastered",
|
||||
},
|
||||
personaName: "John",
|
||||
username: "johnsteamaccount",
|
||||
steamEmail: "john@example.com",
|
||||
avatarUrl: "https://avatars.akamai.steamstatic.com/XXXXXXXXXXXX_full.jpg",
|
||||
}
|
||||
|
||||
export const Machine = {
|
||||
id: Id("machine"),
|
||||
userID: Id("user"),
|
||||
country: "Kenya",
|
||||
countryCode: "KE",
|
||||
timezone: "Africa/Nairobi",
|
||||
|
||||
@@ -6,13 +6,17 @@ import { machineTable } from "./machine.sql";
|
||||
import { getTableColumns, eq, sql, and, isNull } from "../drizzle";
|
||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
|
||||
export module Machine {
|
||||
export namespace Machine {
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string().openapi({
|
||||
description: Common.IdDescription,
|
||||
example: Examples.Machine.id,
|
||||
}),
|
||||
userID: z.string().nullable().openapi({
|
||||
description: "The userID of the user who owns this machine, in the case of BYOG",
|
||||
example: Examples.Machine.userID
|
||||
}),
|
||||
country: z.string().openapi({
|
||||
description: "The fullname of the country this machine is running in",
|
||||
example: Examples.Machine.country
|
||||
@@ -42,7 +46,7 @@ export module Machine {
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
|
||||
export const create = fn(Info.partial({ id: true }), async (input) =>
|
||||
export const create = fn(Info.partial({ id: true }), async (input) =>
|
||||
createTransaction(async (tx) => {
|
||||
const id = input.id ?? createID("machine");
|
||||
await tx.insert(machineTable).values({
|
||||
@@ -51,6 +55,7 @@ export module Machine {
|
||||
timezone: input.timezone,
|
||||
fingerprint: input.fingerprint,
|
||||
countryCode: input.countryCode,
|
||||
userID: input.userID,
|
||||
location: { x: input.location.longitude, y: input.location.latitude },
|
||||
})
|
||||
|
||||
@@ -63,12 +68,23 @@ export module Machine {
|
||||
})
|
||||
)
|
||||
|
||||
export const list = fn(z.void(), async () =>
|
||||
useTransaction(async (tx) =>
|
||||
export const fromUserID = fn(z.string(), async (userID) =>
|
||||
useTransaction(async (tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(machineTable)
|
||||
.where(isNull(machineTable.timeDeleted))
|
||||
.where(and(eq(machineTable.userID, userID), isNull(machineTable.timeDeleted)))
|
||||
.then((rows) => rows.map(serialize))
|
||||
)
|
||||
)
|
||||
|
||||
export const list = fn(z.void(), async () =>
|
||||
useTransaction(async (tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(machineTable)
|
||||
// Show only hosted machines, not BYOG machines
|
||||
.where(and(isNull(machineTable.userID), isNull(machineTable.timeDeleted)))
|
||||
.then((rows) => rows.map(serialize))
|
||||
)
|
||||
)
|
||||
@@ -116,7 +132,7 @@ export module Machine {
|
||||
distance: sql`round((${sqlDistance})::numeric, 2)`
|
||||
})
|
||||
.from(machineTable)
|
||||
.where(isNull(machineTable.timeDeleted)) //Should have a status update
|
||||
.where(isNull(machineTable.timeDeleted))
|
||||
.orderBy(sqlDistance)
|
||||
.limit(3)
|
||||
.then((rows) => rows.map(serialize))
|
||||
@@ -128,6 +144,7 @@ export module Machine {
|
||||
): z.infer<typeof Info> {
|
||||
return {
|
||||
id: input.id,
|
||||
userID: input.userID,
|
||||
country: input.country,
|
||||
timezone: input.timezone,
|
||||
fingerprint: input.fingerprint,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { } from "drizzle-orm/postgres-js";
|
||||
import { timestamps, id } from "../drizzle/types";
|
||||
import { timestamps, id, ulid } from "../drizzle/types";
|
||||
import {
|
||||
text,
|
||||
varchar,
|
||||
pgTable,
|
||||
uniqueIndex,
|
||||
point,
|
||||
primaryKey,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const machineTable = pgTable(
|
||||
@@ -13,6 +14,7 @@ export const machineTable = pgTable(
|
||||
{
|
||||
...id,
|
||||
...timestamps,
|
||||
userID: ulid("user_id"),
|
||||
country: text('country').notNull(),
|
||||
timezone: text('timezone').notNull(),
|
||||
location: point('location', { mode: 'xy' }).notNull(),
|
||||
@@ -32,6 +34,7 @@ export const machineTable = pgTable(
|
||||
},
|
||||
(table) => [
|
||||
// uniqueIndex("external_id").on(table.externalID),
|
||||
uniqueIndex("machine_fingerprint").on(table.fingerprint)
|
||||
uniqueIndex("machine_fingerprint").on(table.fingerprint),
|
||||
primaryKey({ columns: [table.userID, table.id], }),
|
||||
],
|
||||
);
|
||||
@@ -10,7 +10,7 @@ import { memberTable } from "./member.sql";
|
||||
import { and, eq, sql, asc, isNull } from "../drizzle";
|
||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
|
||||
export module Member {
|
||||
export namespace Member {
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string().openapi({
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useTransaction } from "../drizzle/transaction";
|
||||
|
||||
const polar = new PolarSdk({ accessToken: Resource.PolarSecret.value, server: Resource.App.stage !== "production" ? "sandbox" : "production" });
|
||||
|
||||
export module Polar {
|
||||
export namespace Polar {
|
||||
export const client = polar;
|
||||
|
||||
export const Info = z.object({
|
||||
|
||||
@@ -2,10 +2,10 @@ import {
|
||||
IoTDataPlaneClient,
|
||||
PublishCommand,
|
||||
} from "@aws-sdk/client-iot-data-plane";
|
||||
import {useMachine} from "../actor";
|
||||
import {Resource} from "sst";
|
||||
import { useMachine } from "../actor";
|
||||
import { Resource } from "sst";
|
||||
|
||||
export module Realtime {
|
||||
export namespace Realtime {
|
||||
const client = new IoTDataPlaneClient({});
|
||||
|
||||
export async function publish(message: any, subTopic?: string) {
|
||||
|
||||
137
packages/core/src/steam/index.ts
Normal file
137
packages/core/src/steam/index.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { z } from "zod";
|
||||
import { Common } from "../common";
|
||||
import { Examples } from "../examples";
|
||||
import { createID, fn } from "../utils";
|
||||
import { useUser, useUserID } from "../actor";
|
||||
import { eq, and, isNull, sql } from "../drizzle";
|
||||
import { steamTable, AccountLimitation, LastGame } from "./steam.sql";
|
||||
import { createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
|
||||
export namespace Steam {
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string().openapi({
|
||||
description: Common.IdDescription,
|
||||
example: Examples.Steam.id,
|
||||
}),
|
||||
avatarUrl: z.string().openapi({
|
||||
description: "The avatar url of this Steam account",
|
||||
example: Examples.Steam.avatarUrl
|
||||
}),
|
||||
steamEmail: z.string().openapi({
|
||||
description: "The email regisered with this Steam account",
|
||||
example: Examples.Steam.steamEmail
|
||||
}),
|
||||
steamID: z.number().openapi({
|
||||
description: "The Steam ID this Steam account",
|
||||
example: Examples.Steam.steamID
|
||||
}),
|
||||
limitation: AccountLimitation.openapi({
|
||||
description: " The limitations of this Steam account",
|
||||
example: Examples.Steam.limitation
|
||||
}),
|
||||
lastGame: LastGame.openapi({
|
||||
description: "The last game played on this Steam account",
|
||||
example: Examples.Steam.lastGame
|
||||
}),
|
||||
userID: z.string().openapi({
|
||||
description: "The unique id of the user who owns this steam account",
|
||||
example: Examples.Steam.userID
|
||||
}),
|
||||
username: z.string().openapi({
|
||||
description: "The unique username of this steam user",
|
||||
example: Examples.Steam.username
|
||||
}),
|
||||
personaName: z.string().openapi({
|
||||
description: "The last recorded persona name used by this account",
|
||||
example: Examples.Steam.personaName
|
||||
}),
|
||||
countryCode: z.string().openapi({
|
||||
description: "The country this account is connected from",
|
||||
example: Examples.Steam.countryCode
|
||||
})
|
||||
})
|
||||
.openapi({
|
||||
ref: "Steam",
|
||||
description: "Represents a steam user's information stored on Nestri",
|
||||
example: Examples.Steam,
|
||||
});
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
|
||||
export const create = fn(
|
||||
Info.partial({
|
||||
id: true,
|
||||
userID: true,
|
||||
}),
|
||||
(input) =>
|
||||
createTransaction(async (tx) => {
|
||||
const id = input.id ?? createID("steam");
|
||||
const user = useUser()
|
||||
await tx.insert(steamTable).values({
|
||||
id,
|
||||
lastSeen: sql`now()`,
|
||||
userID: input.userID ?? user.userID,
|
||||
countryCode: input.countryCode,
|
||||
username: input.username,
|
||||
steamID: input.steamID,
|
||||
lastGame: input.lastGame,
|
||||
limitation: input.limitation,
|
||||
steamEmail: input.steamEmail,
|
||||
avatarUrl: input.avatarUrl,
|
||||
personaName: input.personaName,
|
||||
})
|
||||
return id;
|
||||
}),
|
||||
);
|
||||
|
||||
export const fromUserID = fn(
|
||||
z.string(),
|
||||
(userID) =>
|
||||
useTransaction((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(steamTable)
|
||||
.where(and(eq(steamTable.userID, userID), isNull(steamTable.timeDeleted)))
|
||||
.execute()
|
||||
.then((rows) => rows.map(serialize).at(0)),
|
||||
),
|
||||
)
|
||||
|
||||
export const list = () =>
|
||||
useTransaction((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(steamTable)
|
||||
.where(and(eq(steamTable.userID, useUserID()), isNull(steamTable.timeDeleted)))
|
||||
.execute()
|
||||
.then((rows) => rows.map(serialize)),
|
||||
)
|
||||
|
||||
/**
|
||||
* Serializes a raw Steam table record into a standardized Info object.
|
||||
*
|
||||
* This function maps the fields from a database record (retrieved from the Steam table) to the
|
||||
* corresponding properties defined in the Info schema.
|
||||
*
|
||||
* @param input - A raw record from the Steam table containing user information.
|
||||
* @returns An object conforming to the Info schema.
|
||||
*/
|
||||
export function serialize(
|
||||
input: typeof steamTable.$inferSelect,
|
||||
): z.infer<typeof Info> {
|
||||
return {
|
||||
id: input.id,
|
||||
userID: input.userID,
|
||||
countryCode: input.countryCode,
|
||||
username: input.username,
|
||||
avatarUrl: input.avatarUrl,
|
||||
personaName: input.personaName,
|
||||
steamEmail: input.steamEmail,
|
||||
steamID: input.steamID,
|
||||
limitation: input.limitation,
|
||||
lastGame: input.lastGame,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
58
packages/core/src/steam/steam.sql.ts
Normal file
58
packages/core/src/steam/steam.sql.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { z } from "zod";
|
||||
import { timestamps, userID, utc } from "../drizzle/types";
|
||||
import { index, pgTable, integer, uniqueIndex, varchar, text, primaryKey, json } from "drizzle-orm/pg-core";
|
||||
|
||||
|
||||
// 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;
|
||||
|
||||
export const LastGame = z.object({
|
||||
gameID: z.number(),
|
||||
gameName: z.string()
|
||||
});
|
||||
|
||||
export const AccountLimitation = z.object({
|
||||
isLimited: z.boolean().nullable(),
|
||||
isBanned: z.boolean().nullable(),
|
||||
isLocked: z.boolean().nullable(),
|
||||
isAllowedToInviteFriends: z.boolean().nullable(),
|
||||
});
|
||||
|
||||
export type LastGame = z.infer<typeof LastGame>;
|
||||
export type AccountLimitation = z.infer<typeof AccountLimitation>;
|
||||
|
||||
export const steamTable = pgTable(
|
||||
"steam",
|
||||
{
|
||||
...userID,
|
||||
...timestamps,
|
||||
lastSeen: utc("time_seen"),
|
||||
steamID: integer("steam_id").notNull(),
|
||||
avatarUrl: text("avatar_url").notNull(),
|
||||
lastGame: json("last_game").$type<LastGame>().notNull(),
|
||||
username: varchar("username", { length: 255 }).notNull(),
|
||||
countryCode: varchar('country_code', { length: 2 }).notNull(),
|
||||
steamEmail: varchar("steam_email", { length: 255 }).notNull(),
|
||||
personaName: varchar("persona_name", { length: 255 }).notNull(),
|
||||
limitation: json("limitation").$type<AccountLimitation>().notNull(),
|
||||
},
|
||||
(table) => [
|
||||
primaryKey({
|
||||
columns: [table.userID, table.id],
|
||||
}),
|
||||
uniqueIndex("steam_email").on(table.userID, table.steamEmail),
|
||||
],
|
||||
);
|
||||
@@ -12,7 +12,7 @@ import { memberTable } from "../member/member.sql";
|
||||
import { ErrorCodes, VisibleError } from "../error";
|
||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
|
||||
export module Team {
|
||||
export namespace Team {
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string().openapi({
|
||||
|
||||
@@ -15,7 +15,7 @@ import { and, eq, isNull, asc, getTableColumns, sql } from "../drizzle";
|
||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
|
||||
|
||||
export module User {
|
||||
export namespace User {
|
||||
const MAX_ATTEMPTS = 50;
|
||||
|
||||
export const Info = z
|
||||
|
||||
@@ -19,7 +19,7 @@ export const userTable = pgTable(
|
||||
discriminator: integer("discriminator").notNull(),
|
||||
email: varchar("email", { length: 255 }).notNull(),
|
||||
polarCustomerID: varchar("polar_customer_id", { length: 255 }).unique(),
|
||||
flags: json("flags").$type<UserFlags>().default({}),
|
||||
// flags: json("flags").$type<UserFlags>().default({}),
|
||||
},
|
||||
(user) => [
|
||||
uniqueIndex("user_email").on(user.email),
|
||||
|
||||
@@ -6,8 +6,19 @@ export const prefixes = {
|
||||
task: "tsk",
|
||||
machine: "mch",
|
||||
member: "mbr",
|
||||
steam: "stm",
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Generates a unique identifier by concatenating a predefined prefix with a ULID.
|
||||
*
|
||||
* Given a key from the predefined prefixes mapping (e.g., "user", "team", "member", "steam"),
|
||||
* this function retrieves the corresponding prefix and combines it with a ULID using an underscore
|
||||
* as a separator. The resulting identifier is formatted as "prefix_ulid".
|
||||
*
|
||||
* @param prefix - A key from the prefixes mapping.
|
||||
* @returns A unique identifier string.
|
||||
*/
|
||||
export function createID(prefix: keyof typeof prefixes): string {
|
||||
return [prefixes[prefix], ulid()].join("_");
|
||||
}
|
||||
Reference in New Issue
Block a user