mirror of
https://github.com/nestriness/nestri.git
synced 2026-08-02 01:35:18 +03:00
feat: Add a lot of stuff
This commit is contained in:
@@ -1,51 +1,51 @@
|
||||
{
|
||||
"name": "@nestri/core",
|
||||
"version": "0.0.0",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"db:dev": "drizzle-kit",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"db": "sst shell drizzle-kit",
|
||||
"db:exec": "sst shell ../scripts/src/psql.sh",
|
||||
"db:reset": "sst shell ../scripts/src/db-reset.sh"
|
||||
},
|
||||
"exports": {
|
||||
"./*": "./src/*.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"@types/sanitize-html": "^2.16.0",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"aws-iot-device-sdk-v2": "^1.21.1",
|
||||
"aws4fetch": "^1.0.20",
|
||||
"mqtt": "^5.10.3",
|
||||
"remeda": "^2.21.2",
|
||||
"ulid": "^2.3.0",
|
||||
"zod": "^3.24.1",
|
||||
"zod-openapi": "^4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-iot-data-plane": "^3.758.0",
|
||||
"@aws-sdk/client-sesv2": "^3.753.0",
|
||||
"@neondatabase/serverless": "^1.0.1",
|
||||
"@openauthjs/openauth": "*",
|
||||
"@openauthjs/openevent": "^0.0.27",
|
||||
"@polar-sh/sdk": "^0.26.1",
|
||||
"drizzle-kit": "^0.30.5",
|
||||
"drizzle-orm": "^0.40.0",
|
||||
"drizzle-zod": "^0.7.1",
|
||||
"fast-average-color": "^9.5.0",
|
||||
"lru-cache": "^11.1.0",
|
||||
"p-limit": "^6.2.0",
|
||||
"pixelmatch": "^7.1.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"postgres": "^3.4.5",
|
||||
"sanitize-html": "^2.16.0",
|
||||
"sharp": "^0.34.1",
|
||||
"steam-session": "*",
|
||||
"ws": "^8.18.3",
|
||||
"xml2js": "^0.6.2"
|
||||
}
|
||||
}
|
||||
"name": "@nestri/cloud-core",
|
||||
"version": "0.0.0",
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"db:dev": "drizzle-kit",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"db": "sst shell drizzle-kit",
|
||||
"db:exec": "sst shell ../scripts/src/psql.sh",
|
||||
"db:reset": "sst shell ../scripts/src/db-reset.sh"
|
||||
},
|
||||
"exports": {
|
||||
"./*": "./src/*.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/pngjs": "^6.0.5",
|
||||
"@types/sanitize-html": "^2.16.0",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"aws-iot-device-sdk-v2": "^1.21.1",
|
||||
"aws4fetch": "^1.0.20",
|
||||
"mqtt": "^5.10.3",
|
||||
"remeda": "^2.21.2",
|
||||
"ulid": "^2.3.0",
|
||||
"zod": "^3.24.1",
|
||||
"zod-openapi": "^4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-iot-data-plane": "^3.758.0",
|
||||
"@aws-sdk/client-sesv2": "^3.753.0",
|
||||
"@neondatabase/serverless": "^1.0.1",
|
||||
"@openauthjs/openauth": "catalog:",
|
||||
"@openauthjs/openevent": "^0.0.27",
|
||||
"@polar-sh/sdk": "^0.26.1",
|
||||
"drizzle-kit": "^0.30.5",
|
||||
"drizzle-orm": "^0.40.0",
|
||||
"drizzle-zod": "^0.7.1",
|
||||
"fast-average-color": "^9.5.0",
|
||||
"lru-cache": "^11.1.0",
|
||||
"p-limit": "^6.2.0",
|
||||
"pixelmatch": "^7.1.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"postgres": "^3.4.5",
|
||||
"sanitize-html": "^2.16.0",
|
||||
"sharp": "^0.34.1",
|
||||
"steam-session": "*",
|
||||
"ws": "^8.18.3",
|
||||
"xml2js": "^0.6.2"
|
||||
}
|
||||
}
|
||||
|
||||
97
cloud/packages/core/src/team/index.ts
Normal file
97
cloud/packages/core/src/team/index.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { z } from "zod/v4";
|
||||
import { fn } from "../utils/fn";
|
||||
import { Examples } from "../examples.js";
|
||||
import { createID } from "../utils/id.js";
|
||||
import { Polar } from "../polar/index.js";
|
||||
import { Database } from "../drizzle/index.js";
|
||||
import { teamTable } from "../team/team.sql.js";
|
||||
import { and, asc, eq, isNull } from "drizzle-orm";
|
||||
import { VisibleError, ErrorCodes } from "../error.js";
|
||||
|
||||
export namespace Team {
|
||||
export const Info = z
|
||||
.object({
|
||||
id: z.string().meta({
|
||||
description: "The unique identifier of the team",
|
||||
example: Examples.Team.id,
|
||||
}),
|
||||
email: z.email().meta({
|
||||
description: "The contact email address for the team",
|
||||
example: Examples.Team.email,
|
||||
}),
|
||||
polarCustomerID: z.string().nullable().meta({
|
||||
description: "Associated Polar.sh customer identifier",
|
||||
example: Examples.Team.polarCustomerID,
|
||||
}),
|
||||
})
|
||||
.meta({
|
||||
ref: "Team",
|
||||
description: "Represents a team entity",
|
||||
example: Examples.Team,
|
||||
});
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
|
||||
export class TeamExistsError extends VisibleError {
|
||||
constructor(email: string) {
|
||||
super(
|
||||
"already_exists",
|
||||
ErrorCodes.Validation.ALREADY_EXISTS,
|
||||
`A team with this email ${email} already exists`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const create = fn(
|
||||
Info.omit({ polarCustomerID: true }).partial({ id: true }),
|
||||
async (input) => {
|
||||
const userID = createID("team");
|
||||
const customerID = await Polar.fromUserEmail(input.email).then(
|
||||
(polarUser) => polarUser?.id || null,
|
||||
);
|
||||
|
||||
const id = input.id ?? userID;
|
||||
|
||||
await Database.transaction(async (tx) => {
|
||||
const result = await tx
|
||||
.insert(teamTable)
|
||||
.values({
|
||||
id,
|
||||
email: input.email,
|
||||
polarCustomerID: customerID,
|
||||
})
|
||||
.onConflictDoNothing({
|
||||
target: [teamTable.email],
|
||||
});
|
||||
|
||||
if (result.count === 0) {
|
||||
throw new TeamExistsError(input.email);
|
||||
}
|
||||
});
|
||||
|
||||
return id;
|
||||
},
|
||||
);
|
||||
|
||||
export const fromEmail = fn(Info.shape.email, async (email) =>
|
||||
Database.transaction(async (tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(teamTable)
|
||||
.where(and(eq(teamTable.email, email), isNull(teamTable.timeDeleted)))
|
||||
.orderBy(asc(teamTable.timeCreated))
|
||||
.execute()
|
||||
.then((rows) => rows.map(serialize).at(0)),
|
||||
),
|
||||
);
|
||||
|
||||
export function serialize(
|
||||
input: typeof teamTable.$inferSelect,
|
||||
): z.infer<typeof Info> {
|
||||
return {
|
||||
id: input.id,
|
||||
email: input.email,
|
||||
polarCustomerID: input.polarCustomerID,
|
||||
};
|
||||
}
|
||||
}
|
||||
9
cloud/packages/core/src/team/team.sql.ts
Normal file
9
cloud/packages/core/src/team/team.sql.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { ulid, timestamps } from "@/drizzle/types.js";
|
||||
import { pgTable, varchar } from "drizzle-orm/pg-core";
|
||||
|
||||
export const teamTable = pgTable("team", {
|
||||
...timestamps,
|
||||
id: ulid("id").primaryKey().notNull(),
|
||||
email: varchar("email", { length: 255 }).unique().notNull(),
|
||||
polarCustomerID: varchar("polar_customer_id", { length: 255 }),
|
||||
});
|
||||
@@ -1,27 +1,30 @@
|
||||
import { ZodSchema, z } from "zod";
|
||||
import { ZodType, z } from "zod/v4";
|
||||
|
||||
export function fn<
|
||||
Arg1 extends ZodSchema,
|
||||
Callback extends (arg1: z.output<Arg1>) => any,
|
||||
Arg1 extends ZodType,
|
||||
Callback extends (arg1: z.output<Arg1>) => any,
|
||||
>(arg1: Arg1, cb: Callback) {
|
||||
const result = function (input: z.input<typeof arg1>): ReturnType<Callback> {
|
||||
const parsed = arg1.parse(input);
|
||||
return cb.apply(cb, [parsed as any]);
|
||||
};
|
||||
result.schema = arg1;
|
||||
return result;
|
||||
const result = function (input: z.input<typeof arg1>): ReturnType<Callback> {
|
||||
const parsed = arg1.parse(input);
|
||||
return cb.apply(cb, [parsed as any]);
|
||||
};
|
||||
result.schema = arg1;
|
||||
return result;
|
||||
}
|
||||
|
||||
export function doubleFn<
|
||||
Arg1 extends ZodSchema,
|
||||
Arg2 extends ZodSchema,
|
||||
Callback extends (arg1: z.output<Arg1>, arg2: z.output<Arg2>) => any,
|
||||
Arg1 extends ZodType,
|
||||
Arg2 extends ZodType,
|
||||
Callback extends (arg1: z.output<Arg1>, arg2: z.output<Arg2>) => any,
|
||||
>(arg1: Arg1, arg2: Arg2, cb: Callback) {
|
||||
const result = function (input: z.input<typeof arg1>, input2: z.input<typeof arg2>): ReturnType<Callback> {
|
||||
const parsed = arg1.parse(input);
|
||||
const parsed2 = arg2.parse(input2);
|
||||
return cb.apply(cb, [parsed as any, parsed2 as any]);
|
||||
};
|
||||
result.schema = arg1;
|
||||
return result;
|
||||
}
|
||||
const result = function (
|
||||
input: z.input<typeof arg1>,
|
||||
input2: z.input<typeof arg2>,
|
||||
): ReturnType<Callback> {
|
||||
const parsed = arg1.parse(input);
|
||||
const parsed2 = arg2.parse(input2);
|
||||
return cb.apply(cb, [parsed as any, parsed2 as any]);
|
||||
};
|
||||
result.schema = arg1;
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@nestri/functions",
|
||||
"name": "@nestri/cloud-functions",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
@@ -22,6 +22,7 @@
|
||||
"@aws-sdk/client-s3": "^3.806.0",
|
||||
"@aws-sdk/client-sqs": "^3.806.0",
|
||||
"@nestri/core": "workspace:",
|
||||
"@nestri/cloud-core": "workspace:",
|
||||
"actor-core": "^0.8.0",
|
||||
"hono": "^4.7.8",
|
||||
"hono-openapi": "^0.4.8",
|
||||
|
||||
@@ -3,9 +3,10 @@ import { Resource } from "sst";
|
||||
import { logger } from "hono/logger";
|
||||
import { subjects } from "../subjects";
|
||||
import { handleDiscord } from "./utils";
|
||||
import { Team } from "@nestri/cloud-core/team/index";
|
||||
import { DiscordAdapter } from "./adapters";
|
||||
import { issuer } from "@openauthjs/openauth";
|
||||
import { User } from "@nestri/core/user/index";
|
||||
import { User } from "@nestri/cloud-core/user/index";
|
||||
import { patchLogger } from "../utils/patch-logger";
|
||||
import type { KVNamespace } from "@cloudflare/workers-types";
|
||||
import { CloudflareStorage } from "@openauthjs/openauth/storage/cloudflare";
|
||||
@@ -63,6 +64,7 @@ export default {
|
||||
|
||||
if (user) {
|
||||
try {
|
||||
await Team.fromEmail(email);
|
||||
const matching = await User.fromEmail(user.primary.email);
|
||||
|
||||
//Sign Up
|
||||
|
||||
Reference in New Issue
Block a user