Files
netris-nestri/packages/core/src/team/index.ts
Wanjohi f408ec56cb 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>
2025-04-13 14:30:45 +03:00

153 lines
4.9 KiB
TypeScript

import { z } from "zod";
import { Resource } from "sst";
import { bus } from "sst/aws/bus";
import { Common } from "../common";
import { Examples } from "../examples";
import { createEvent } from "../event";
import { createID, fn } from "../utils";
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";
export namespace Team {
export const Info = z
.object({
id: z.string().openapi({
description: Common.IdDescription,
example: Examples.Team.id,
}),
slug: z.string().regex(/^[a-z0-9\-]+$/, "Use a URL friendly name.").openapi({
description: "The unique and url-friendly slug of this team",
example: Examples.Team.slug
}),
name: z.string().openapi({
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
})
})
.openapi({
ref: "Team",
description: "Represents a team on Nestri",
example: Examples.Team,
});
export type Info = z.infer<typeof Info>;
export const Events = {
Created: createEvent(
"team.created",
z.object({
teamID: z.string().nonempty(),
}),
),
};
export class TeamExistsError extends VisibleError {
constructor(slug: string) {
super(
"already_exists",
ErrorCodes.Validation.TEAM_ALREADY_EXISTS,
`There is already a team named "${slug}"`
);
}
}
export const create = fn(
Info.pick({ slug: true, id: true, name: true, planType: 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,
name: input.name
})
.onConflictDoNothing({ target: teamTable.slug })
if (result.count === 0) throw new TeamExistsError(input.slug);
return id;
})
)
export const remove = fn(Info.shape.id, (input) =>
useTransaction(async (tx) => {
const account = assertActor("user");
const row = await tx
.select({
teamID: memberTable.teamID,
})
.from(memberTable)
.where(
and(
eq(memberTable.teamID, input),
eq(memberTable.email, account.properties.email),
),
)
.execute()
.then((rows) => rows.at(0));
if (!row) return;
await tx
.update(teamTable)
.set({
timeDeleted: sql`now()`,
})
.where(eq(teamTable.id, row.teamID));
}),
);
export const list = fn(z.void(), () =>
useTransaction((tx) =>
tx
.select()
.from(teamTable)
.where(isNull(teamTable.timeDeleted))
.execute()
.then((rows) => rows.map(serialize)),
),
);
export const fromID = fn(z.string().min(1), async (id) =>
useTransaction(async (tx) => {
return tx
.select()
.from(teamTable)
.where(and(eq(teamTable.id, id), 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))
}),
);
export function serialize(
input: typeof teamTable.$inferSelect,
): z.infer<typeof Info> {
return {
id: input.id,
name: input.name,
slug: input.slug,
planType: input.planType,
};
}
}