feat(infra): Update infra and add support for teams to SST (#186)

## Description
- [x] Adds support for AWS SSO, which makes us (the team) able to use
SST and update the components independently
- [x] Splits the webpage into the landing page (Qwik), and Astro (the
console) in charge of playing. This allows us to pass in Environment
Variables to the console
- ~Migrates the docs from Nuxt to Nextjs, and connects them to SST. This
allows us to use Fumadocs _citation needed_ that's much more beautiful,
and supports OpenApi~
- Cloudflare pages with github integration is not working on our new CF
account. So we will have to push the pages deployment manually with
Github actions
- [x] Moves the current set up from my personal CF and AWS accounts to
dedicated Nestri accounts -

## Related Issues
<!-- List any related issues (e.g., "Closes #123", "Fixes #456") -->

## Type of Change

- [ ] Bug fix (non-breaking change)
- [x] New feature (non-breaking change)
- [ ] Breaking change (fix or feature that changes existing
functionality)
- [x] Documentation update
- [ ] Other (please describe):

## Checklist

- [x] I have updated relevant documentation
- [x] My code follows the project's coding style
- [x] My changes generate no new warnings/errors

## Notes for Reviewers
<!-- Point out areas you'd like reviewers to focus on, questions you
have, or decisions that need discussion -->
Please approve my PR 🥹


## Screenshots/Demo
<!-- If applicable, add screenshots or a GIF demo of your changes
(especially for UI changes) -->

## Additional Context
<!-- Add any other context about the pull request here -->
This commit is contained in:
Wanjohi
2025-02-27 18:52:05 +03:00
committed by GitHub
parent 237e016b2d
commit 457aac2258
138 changed files with 4218 additions and 2579 deletions

View File

@@ -0,0 +1,133 @@
import { z } from "zod";
import { Resource } from "sst";
import { bus } from "sst/aws/bus";
import { useTeam } from "../actor";
import { Common } from "../common";
import { createID, fn } from "../utils";
import { createEvent } from "../event";
import { Examples } from "../examples";
import { memberTable } from "./member.sql";
import { and, eq, sql, asc, isNull } from "../drizzle";
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
export module Member {
export const Info = z
.object({
id: z.string().openapi({
description: Common.IdDescription,
example: Examples.Member.id,
}),
timeSeen: z.date().or(z.null()).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",
example: Examples.Member.teamID
}),
email: z.string().openapi({
description: "The email of this team member",
example: Examples.Member.email
})
})
.openapi({
ref: "Member",
description: "Represents a team member on Nestri",
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 })
.partial({
id: true,
})
.extend({
first: z.boolean().optional(),
}),
(input) =>
createTransaction(async (tx) => {
const id = input.id ?? createID("member");
await tx.insert(memberTable).values({
id,
email: input.email,
teamID: useTeam(),
timeSeen: input.first ? sql`CURRENT_TIMESTAMP()` : null,
}).onConflictDoUpdate({
target: memberTable.id,
set: {
timeDeleted: null,
}
})
await afterTx(() =>
async () => bus.publish(Resource.Bus, Events.Created, { memberID: id }),
);
return id;
}),
);
export const remove = fn(Info.shape.id, (input) =>
useTransaction(async (tx) => {
await tx
.update(memberTable)
.set({
timeDeleted: sql`CURRENT_TIMESTAMP()`,
})
.where(and(eq(memberTable.id, input), eq(memberTable.teamID, useTeam())))
.execute();
return input;
}),
);
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))
.then((rows) => rows.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))
.then((rows) => rows.at(0))
),
)
export function serialize(
input: typeof memberTable.$inferSelect,
): z.infer<typeof Info> {
return {
id: input.id,
email: input.email,
teamID: input.teamID,
timeSeen: input.timeSeen
};
}
}