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,155 +0,0 @@
import { z } from "zod";
import { Common } from "../common";
import { createID, fn } from "../utils";
import { Examples } from "../examples";
import { machineTable } from "./machine.sql";
import { getTableColumns, eq, sql, and, isNull } from "../drizzle";
import { createTransaction, useTransaction } from "../drizzle/transaction";
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
}),
fingerprint: z.string().openapi({
description: "The fingerprint of this machine, deduced from the host machine's machine id - /etc/machine-id",
example: Examples.Machine.fingerprint
}),
location: z.object({ longitude: z.number(), latitude: z.number() }).openapi({
description: "This is the 2d location of this machine, they might not be accurate",
example: Examples.Machine.location
}),
countryCode: z.string().openapi({
description: "This is the 2 character country code of the country this machine [ISO 3166-1 alpha-2] ",
example: Examples.Machine.countryCode
}),
timezone: z.string().openapi({
description: "The IANA timezone formatted string of the timezone of the location where the machine is running",
example: Examples.Machine.timezone
})
})
.openapi({
ref: "Machine",
description: "Represents a hosted or BYOG machine connected to Nestri",
example: Examples.Machine,
});
export type Info = z.infer<typeof Info>;
export const create = fn(Info.partial({ id: true }), async (input) =>
createTransaction(async (tx) => {
const id = input.id ?? createID("machine");
await tx.insert(machineTable).values({
id,
country: input.country,
timezone: input.timezone,
fingerprint: input.fingerprint,
countryCode: input.countryCode,
// userID: input.userID,
location: { x: input.location.longitude, y: input.location.latitude },
})
// await afterTx(() =>
// bus.publish(Resource.Bus, Events.Created, {
// teamID: id,
// }),
// );
return id;
})
)
// 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 fromID = fn(Info.shape.id, async (id) =>
useTransaction(async (tx) =>
tx
.select()
.from(machineTable)
.where(and(eq(machineTable.id, id), isNull(machineTable.timeDeleted)))
.then((rows) => rows.map(serialize).at(0))
)
)
export const fromFingerprint = fn(Info.shape.fingerprint, async (fingerprint) =>
useTransaction(async (tx) =>
tx
.select()
.from(machineTable)
.where(and(eq(machineTable.fingerprint, fingerprint), isNull(machineTable.timeDeleted)))
.execute()
.then((rows) => rows.map(serialize).at(0))
)
)
export const remove = fn(Info.shape.id, (id) =>
useTransaction(async (tx) => {
await tx
.update(machineTable)
.set({
timeDeleted: sql`now()`,
})
.where(and(eq(machineTable.id, id)))
.execute();
return id;
}),
);
export const fromLocation = fn(Info.shape.location, async (location) =>
useTransaction(async (tx) => {
const sqlDistance = sql`location <-> point(${location.longitude}, ${location.latitude})`;
return tx
.select({
...getTableColumns(machineTable),
distance: sql`round((${sqlDistance})::numeric, 2)`
})
.from(machineTable)
.where(isNull(machineTable.timeDeleted))
.orderBy(sqlDistance)
.limit(3)
.then((rows) => rows.map(serialize))
})
)
export function serialize(
input: typeof machineTable.$inferSelect,
): z.infer<typeof Info> {
return {
id: input.id,
// userID: input.userID,
country: input.country,
timezone: input.timezone,
fingerprint: input.fingerprint,
countryCode: input.countryCode,
location: { latitude: input.location.y, longitude: input.location.x },
};
}
}