feat(api): Connect Steam to main user account (#262)

## Description
This attempts to connect the Steam account to user account... for easier
management

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

- **New Features**
- Enhanced user profiles and account views now display integrated Steam
account details and enriched team associations for a more comprehensive
experience.
- **Chores**
- Backend and database refinements have been implemented to improve
system stability, data integrity, and overall performance.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Wanjohi
2025-04-14 10:32:21 +03:00
committed by GitHub
parent 9a6826b069
commit e93099784c
16 changed files with 1500 additions and 504 deletions

732
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -4,8 +4,6 @@ import { domain } from "./dns";
import { secret } from "./secret";
import { postgres } from "./postgres";
import { cluster } from "./cluster";
import { vpc } from "./vpc";
// sst.Linkable.wrap(random.RandomString, (resource) => ({
// properties: {
// value: resource.result,

View File

@@ -0,0 +1,8 @@
ALTER TABLE "steam" RENAME COLUMN "time_seen" TO "last_seen";--> statement-breakpoint
DROP INDEX "steam_email";--> statement-breakpoint
ALTER TABLE "steam" DROP CONSTRAINT "steam_user_id_id_pk";--> statement-breakpoint
ALTER TABLE "steam" ADD PRIMARY KEY ("id");--> statement-breakpoint
ALTER TABLE "machine" ADD CONSTRAINT "machine_user_id_id_pk" PRIMARY KEY("user_id","id");--> statement-breakpoint
ALTER TABLE "machine" ADD COLUMN "user_id" char(30);--> statement-breakpoint
ALTER TABLE "steam" ADD CONSTRAINT "steam_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "steam" DROP COLUMN "email";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "machine" DROP CONSTRAINT "machine_user_id_id_pk";--> statement-breakpoint
ALTER TABLE "machine" DROP COLUMN "user_id";

View File

@@ -0,0 +1,499 @@
{
"id": "65574f71-e0d3-4363-9449-394e7c376a30",
"prevId": "eb5d41aa-5f85-4b2d-8633-fc021b211241",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.machine": {
"name": "machine",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"location": {
"name": "location",
"type": "point",
"primaryKey": false,
"notNull": true
},
"fingerprint": {
"name": "fingerprint",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"machine_fingerprint": {
"name": "machine_fingerprint",
"columns": [
{
"expression": "fingerprint",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"machine_user_id_id_pk": {
"name": "machine_user_id_id_pk",
"columns": [
"user_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.member": {
"name": "member",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"member_team_id_id_pk": {
"name": "member_team_id_id_pk",
"columns": [
"team_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam": {
"name": "steam",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"last_seen": {
"name": "last_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"steam_id": {
"name": "steam_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_game": {
"name": "last_game",
"type": "json",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
},
"steam_email": {
"name": "steam_email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"persona_name": {
"name": "persona_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitation": {
"name": "limitation",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"steam_user_id_user_id_fk": {
"name": "steam_user_id_user_id_fk",
"tableFrom": "steam",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.team": {
"name": "team",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"discriminator": {
"name": "discriminator",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_polar_customer_id_unique": {
"name": "user_polar_customer_id_unique",
"nullsNotDistinct": false,
"columns": [
"polar_customer_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -0,0 +1,485 @@
{
"id": "0b04858c-a7e3-43b6-98a4-1dc2f6f97488",
"prevId": "65574f71-e0d3-4363-9449-394e7c376a30",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.machine": {
"name": "machine",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": true
},
"timezone": {
"name": "timezone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"location": {
"name": "location",
"type": "point",
"primaryKey": false,
"notNull": true
},
"fingerprint": {
"name": "fingerprint",
"type": "varchar(32)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"machine_fingerprint": {
"name": "machine_fingerprint",
"columns": [
{
"expression": "fingerprint",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.member": {
"name": "member",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"team_id": {
"name": "team_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"time_seen": {
"name": "time_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"email_global": {
"name": "email_global",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"member_email": {
"name": "member_email",
"columns": [
{
"expression": "team_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"member_team_id_id_pk": {
"name": "member_team_id_id_pk",
"columns": [
"team_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.steam": {
"name": "steam",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "char(30)",
"primaryKey": false,
"notNull": true
},
"last_seen": {
"name": "last_seen",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"steam_id": {
"name": "steam_id",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"last_game": {
"name": "last_game",
"type": "json",
"primaryKey": false,
"notNull": true
},
"username": {
"name": "username",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"country_code": {
"name": "country_code",
"type": "varchar(2)",
"primaryKey": false,
"notNull": true
},
"steam_email": {
"name": "steam_email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"persona_name": {
"name": "persona_name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"limitation": {
"name": "limitation",
"type": "json",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {
"steam_user_id_user_id_fk": {
"name": "steam_user_id_user_id_fk",
"tableFrom": "steam",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.team": {
"name": "team",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"plan_type": {
"name": "plan_type",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"slug": {
"name": "slug",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "char(30)",
"primaryKey": true,
"notNull": true
},
"time_created": {
"name": "time_created",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_updated": {
"name": "time_updated",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"time_deleted": {
"name": "time_deleted",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"avatar_url": {
"name": "avatar_url",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"discriminator": {
"name": "discriminator",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"polar_customer_id": {
"name": "polar_customer_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"user_email": {
"name": "user_email",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"user_polar_customer_id_unique": {
"name": "user_polar_customer_id_unique",
"nullsNotDistinct": false,
"columns": [
"polar_customer_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -29,6 +29,20 @@
"when": 1744287542918,
"tag": "0003_first_big_bertha",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1744614629788,
"tag": "0004_amused_mattie_franklin",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1744614896792,
"tag": "0005_aspiring_stature",
"breakpoints": true
}
]
}

View File

@@ -19,7 +19,7 @@
"aws4fetch": "^1.0.20",
"loops": "^3.4.1",
"mqtt": "^5.10.3",
"remeda": "^2.19.0",
"remeda": "^2.21.2",
"ulid": "^2.3.0",
"uuid": "^11.0.3",
"zod": "^3.24.1",

View File

@@ -3,34 +3,6 @@ export namespace Examples {
export const Id = (prefix: keyof typeof prefixes) =>
`${prefixes[prefix]}_XXXXXXXXXXXXXXXXXXXXXXXXX`;
export const User = {
id: Id("user"),
name: "John Doe",
email: "john@example.com",
discriminator: 47,
avatarUrl: "https://cdn.discordapp.com/avatars/xxxxxxx/xxxxxxx.png",
polarCustomerID: "0bfcb712-df13-4454-81a8-fbee66eddca4",
};
export const Team = {
id: Id("team"),
name: "John Does' Team",
slug: "john_doe",
planType: "BYOG" as const
}
export const Member = {
id: Id("member"),
email: "john@example.com",
teamID: Id("team"),
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 Steam = {
id: Id("steam"),
userID: Id("user"),
@@ -52,6 +24,35 @@ export namespace Examples {
avatarUrl: "https://avatars.akamai.steamstatic.com/XXXXXXXXXXXX_full.jpg",
}
export const User = {
id: Id("user"),
name: "John Doe",
email: "john@example.com",
discriminator: 47,
avatarUrl: "https://cdn.discordapp.com/avatars/xxxxxxx/xxxxxxx.png",
polarCustomerID: "0bfcb712-df13-4454-81a8-fbee66eddca4",
steamAccounts: [Steam]
};
export const Team = {
id: Id("team"),
name: "John Does' Team",
slug: "john_doe",
planType: "BYOG" as const
}
export const Member = {
id: Id("member"),
email: "john@example.com",
teamID: Id("team"),
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 Machine = {
id: Id("machine"),
userID: Id("user"),

View File

@@ -13,10 +13,10 @@ export namespace Machine {
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
}),
// 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
@@ -55,7 +55,7 @@ export namespace Machine {
timezone: input.timezone,
fingerprint: input.fingerprint,
countryCode: input.countryCode,
userID: input.userID,
// userID: input.userID,
location: { x: input.location.longitude, y: input.location.latitude },
})
@@ -68,26 +68,26 @@ export namespace Machine {
})
)
export const fromUserID = fn(z.string(), async (userID) =>
useTransaction(async (tx) =>
tx
.select()
.from(machineTable)
.where(and(eq(machineTable.userID, userID), isNull(machineTable.timeDeleted)))
.then((rows) => rows.map(serialize))
)
)
// export const fromUserID = fn(z.string(), async (userID) =>
// useTransaction(async (tx) =>
// tx
// .select()
// .from(machineTable)
// .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))
)
)
// 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))
// )
// )
export const fromID = fn(Info.shape.id, async (id) =>
useTransaction(async (tx) =>
@@ -144,7 +144,7 @@ export namespace Machine {
): z.infer<typeof Info> {
return {
id: input.id,
userID: input.userID,
// userID: input.userID,
country: input.country,
timezone: input.timezone,
fingerprint: input.fingerprint,

View File

@@ -14,7 +14,7 @@ export const machineTable = pgTable(
{
...id,
...timestamps,
userID: ulid("user_id"),
// userID: ulid("user_id"),
country: text('country').notNull(),
timezone: text('timezone').notNull(),
location: point('location', { mode: 'xy' }).notNull(),
@@ -35,6 +35,6 @@ export const machineTable = pgTable(
(table) => [
// uniqueIndex("external_id").on(table.externalID),
uniqueIndex("machine_fingerprint").on(table.fingerprint),
primaryKey({ columns: [table.userID, table.id], }),
// primaryKey({ columns: [table.userID, table.id], }),
],
);

View File

@@ -1,6 +1,7 @@
import { z } from "zod";
import { timestamps, userID, utc } from "../drizzle/types";
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;
@@ -37,9 +38,14 @@ export type AccountLimitation = z.infer<typeof AccountLimitation>;
export const steamTable = pgTable(
"steam",
{
...userID,
...id,
...timestamps,
lastSeen: utc("time_seen"),
userID: ulid("user_id")
.notNull()
.references(() => userTable.id, {
onDelete: "cascade",
}),
lastSeen: utc("last_seen").notNull(),
steamID: integer("steam_id").notNull(),
avatarUrl: text("avatar_url").notNull(),
lastGame: json("last_game").$type<LastGame>().notNull(),
@@ -48,11 +54,5 @@ 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) => [
primaryKey({
columns: [table.userID, table.id],
}),
uniqueIndex("steam_email").on(table.userID, table.steamEmail),
],
}
);

View File

@@ -6,13 +6,16 @@ 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 { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
import { Steam } from "../steam";
export namespace User {
@@ -44,6 +47,10 @@ export namespace User {
description: "The (number) discriminator for this user",
example: Examples.User.discriminator,
}),
steamAccounts: Steam.Info.array().openapi({
description: "The steam accounts for this user",
example: Examples.User.steamAccounts,
}),
})
.openapi({
ref: "User",
@@ -102,7 +109,7 @@ export namespace User {
return null;
})
export const create = fn(Info.omit({ polarCustomerID: true, discriminator: true }).partial({ avatarUrl: true, id: true }), async (input) => {
export const create = fn(Info.omit({ polarCustomerID: true, discriminator: true, steamAccounts: true }).partial({ avatarUrl: true, id: true }), async (input) => {
const userID = createID("user")
//FIXME: Do this much later, as Polar.sh has so many inconsistencies for fuck's sake
@@ -147,41 +154,92 @@ export namespace User {
})
export const fromEmail = fn(z.string(), async (email) =>
useTransaction(async (tx) =>
tx
useTransaction(async (tx) => {
const rows = await tx
.select()
.from(userTable)
.leftJoin(steamTable, eq(userTable.id, steamTable.userID))
.where(and(eq(userTable.email, email), isNull(userTable.timeDeleted)))
.orderBy(asc(userTable.timeCreated))
.then((rows) => rows.map(serialize))
.then((rows) => rows.at(0))
),
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]
}),
)
export const fromID = fn(z.string(), async (id) =>
useTransaction(async (tx) =>
tx
useTransaction(async (tx) => {
const rows = await tx
.select()
.from(userTable)
.leftJoin(steamTable, eq(userTable.id, steamTable.userID))
.where(and(eq(userTable.id, id), isNull(userTable.timeDeleted)))
.orderBy(asc(userTable.timeCreated))
.then((rows) => rows.map(serialize))
.then((rows) => rows.at(0))
),
)
export function serialize(
input: typeof userTable.$inferSelect,
): z.infer<typeof Info> {
return {
id: input.id,
name: input.name,
email: input.email,
avatarUrl: input.avatarUrl,
discriminator: input.discriminator,
polarCustomerID: input.polarCustomerID,
};
}
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]
}),
)
export const remove = fn(Info.shape.id, (id) =>
useTransaction(async (tx) => {

View File

@@ -49,7 +49,7 @@ export namespace AccountApi {
"User not found",
);
const { id, email, name, polarCustomerID, avatarUrl, discriminator } = currentUser
const { id, email, name, polarCustomerID, avatarUrl, discriminator, steamAccounts } = currentUser
return c.json({
data: {
@@ -58,6 +58,7 @@ export namespace AccountApi {
email,
teams,
avatarUrl,
steamAccounts,
discriminator,
polarCustomerID,
}

View File

@@ -28,7 +28,13 @@ type OauthUser = {
console.log("STORAGE", process.env.STORAGE)
const app = issuer({
select: Select(),
select: Select({
providers: {
machine: {
hide: true
}
}
}),
//TODO: Create our own Storage
storage: MemoryStorage({
persist: process.env.STORAGE //"/tmp/persist.json",
@@ -122,7 +128,7 @@ const app = issuer({
longitude
},
//FIXME: Make this better
userID: null
// userID: null
})
return ctx.subject("machine", {
machineID,

View File

@@ -33,19 +33,21 @@ export function useStorage() {
}
import { createEffect } from "solid-js";
import { useOpenAuth } from "@openauthjs/solid"
import { Team } from "@nestri/core/team/index";
import { useOpenAuth } from "@openauthjs/solid";
import { Steam } from "@nestri/core/steam/index";
import { createInitializedContext } from "../common/context";
type Storage = {
accounts: Record<string, {
id: string
name: string;
email: string
avatarUrl?: string
discriminator: number
name: string;
polarCustomerID: string;
teams: Team.Info[];
discriminator: number
polarCustomerID: string;
steamAccounts: Steam.Info[];
}>
}