feat: New account system with improved team management (#273)

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 comprehensive account management with combined user and
team info.
  - Added advanced, context-aware logging utilities.
- Implemented invite code generation for teams with uniqueness
guarantees.
- Expanded example data for users, teams, subscriptions, sessions, and
games.

- **Enhancements**
- Refined user, team, member, and Steam account schemas for richer data
and validation.
  - Streamlined user creation, login acknowledgment, and error handling.
  - Improved API authentication and unified actor context management.
- Added persistent shared temporary volume support to API and auth
services.
- Enhanced Steam account management with create, update, and event
notifications.
- Refined team listing and serialization integrating Steam accounts as
members.
  - Simplified event, context, and logging systems.
- Updated API and auth middleware for better token handling and actor
provisioning.

- **Bug Fixes**
  - Fixed multiline log output to prefix each line with log level.

- **Removals**
- Removed machine and subscription management features, including
schemas and DB tables.
- Disabled machine-based authentication and removed related subject
schemas.
- Removed deprecated fields and legacy logic from member and team
management.
- Removed legacy event and error handling related to teams and members.

- **Chores**
  - Reorganized and cleaned exports across utility and API modules.
- Updated database schemas for users, teams, members, and Steam
accounts.
  - Improved internal code structure, imports, and error messaging.
- Moved logger patching to earlier initialization for consistent
logging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Wanjohi
2025-05-06 07:26:59 +03:00
committed by GitHub
parent a0dc353561
commit 70d629227a
39 changed files with 1194 additions and 1480 deletions

View File

@@ -1,14 +1,10 @@
import { z } from "zod";
import { Resource } from "sst";
import { bus } from "sst/aws/bus";
import { useTeam } from "../actor";
import { Actor } from "../actor";
import { Common } from "../common";
import { createID, fn } from "../utils";
import { createEvent } from "../event";
import { Examples } from "../examples";
import { memberTable, role } from "./member.sql";
import { and, eq, sql, asc, isNull } from "../drizzle";
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
import { createID, fn } from "../utils";
import { memberTable, RoleEnum } from "./member.sql";
import { createTransaction } from "../drizzle/transaction";
export namespace Member {
export const Info = z
@@ -17,107 +13,53 @@ export namespace Member {
description: Common.IdDescription,
example: Examples.Member.id,
}),
timeSeen: z.date().nullable().or(z.undefined()).openapi({
description: "The last time this team member was active",
example: Examples.Member.timeSeen
}),
teamID: z.string().openapi({
description: "The unique id of the team this member is on",
description: "Associated team identifier for this membership",
example: Examples.Member.teamID
}),
role: z.enum(role).openapi({
description: "The role of this team member",
role: z.enum(RoleEnum.enumValues).openapi({
description: "Assigned permission role within the team",
example: Examples.Member.role
}),
email: z.string().openapi({
description: "The email of this team member",
example: Examples.Member.email
})
steamID: z.string().openapi({
description: "Steam platform identifier for Steam account integration",
example: Examples.Member.steamID
}),
userID: z.string().nullable().openapi({
description: "Optional associated user account identifier",
example: Examples.Member.userID
}),
})
.openapi({
ref: "Member",
description: "Represents a team member on Nestri",
description: "Team membership entity defining user roles and platform connections",
example: Examples.Member,
});
export type Info = z.infer<typeof Info>;
export const Events = {
Created: createEvent(
"member.created",
z.object({
memberID: Info.shape.id,
}),
),
Updated: createEvent(
"member.updated",
z.object({
memberID: Info.shape.id,
}),
),
};
export const create = fn(
Info.pick({ email: true, id: true })
Info
.partial({
id: true,
})
.extend({
first: z.boolean().optional(),
userID: true,
teamID: true
}),
(input) =>
createTransaction(async (tx) => {
const id = input.id ?? createID("member");
await tx.insert(memberTable).values({
id,
teamID: useTeam(),
email: input.email,
role: input.first ? "owner" : "member",
timeSeen: input.first ? sql`now()` : null,
role: input.role,
userID: input.userID,
steamID: input.steamID,
teamID: input.teamID ?? Actor.teamID(),
})
await afterTx(() =>
async () => bus.publish(Resource.Bus, Events.Created, { memberID: id }),
);
return id;
}),
);
export const remove = fn(Info.shape.id, (id) =>
useTransaction(async (tx) => {
await tx
.update(memberTable)
.set({
timeDeleted: sql`now()`,
})
.where(and(eq(memberTable.id, id), eq(memberTable.teamID, useTeam())))
.execute();
return id;
}),
);
export const fromEmail = fn(z.string(), async (email) =>
useTransaction(async (tx) =>
tx
.select()
.from(memberTable)
.where(and(eq(memberTable.email, email), isNull(memberTable.timeDeleted)))
.orderBy(asc(memberTable.timeCreated))
.then((rows) => rows.map(serialize).at(0))
)
)
export const fromID = fn(z.string(), async (id) =>
useTransaction(async (tx) =>
tx
.select()
.from(memberTable)
.where(and(eq(memberTable.id, id), isNull(memberTable.timeDeleted)))
.orderBy(asc(memberTable.timeCreated))
.then((rows) => rows.map(serialize).at(0))
),
)
/**
* Converts a raw member database row into a standardized {@link Member.Info} object.
*
@@ -130,9 +72,9 @@ export namespace Member {
return {
id: input.id,
role: input.role,
email: input.email,
userID: input.userID,
teamID: input.teamID,
timeSeen: input.timeSeen
steamID: input.steamID
};
}