mirror of
https://github.com/nestriness/nestri.git
synced 2026-08-01 17:34:15 +03:00
feat: Add user to auth.ts function
This commit is contained in:
6
bun.lock
6
bun.lock
@@ -89,6 +89,7 @@
|
||||
"name": "@nestri/core",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@polar-sh/sdk": "^0.40.1",
|
||||
"drizzle-orm": "^0.44.6",
|
||||
"hono": "catalog:",
|
||||
"ioredis": "^5.8.2",
|
||||
@@ -109,6 +110,7 @@
|
||||
},
|
||||
"packages/function": {
|
||||
"name": "@nestri/function",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"@openauthjs/openauth": "catalog:",
|
||||
"hono": "catalog:",
|
||||
@@ -3760,6 +3762,8 @@
|
||||
|
||||
"@nestri/cloud-functions/@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="],
|
||||
|
||||
"@nestri/core/@polar-sh/sdk": ["@polar-sh/sdk@0.40.1", "", { "dependencies": { "standardwebhooks": "^1.0.0", "zod": "^3.25.76" } }, "sha512-P/hugjmo8G6vl6hdvXcZHjC2sOaj5ZRyo2BIdn5MKgiq6vXBcWRWfCEpIjcF2cZZ8H9ZVB+0kDYb9Dp5ZkhXuA=="],
|
||||
|
||||
"@nestri/core/drizzle-kit": ["drizzle-kit@0.31.5", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-+CHgPFzuoTQTt7cOYCV6MOw2w8vqEn/ap1yv4bpZOWL03u7rlVRQhUY0WYT3rHsgVTXwYQDZaSUJSQrMBUKuWg=="],
|
||||
|
||||
"@nestri/core/drizzle-orm": ["drizzle-orm@0.44.6", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-uy6uarrrEOc9K1u5/uhBFJbdF5VJ5xQ/Yzbecw3eAYOunv5FDeYkR2m8iitocdHBOHbvorviKOW5GVw0U1j4LQ=="],
|
||||
@@ -4840,6 +4844,8 @@
|
||||
|
||||
"@nestri/cloud-functions/@types/bun/bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
||||
|
||||
"@nestri/core/@polar-sh/sdk/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@nestri/core/drizzle-kit/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="],
|
||||
|
||||
"@nestri/studio/@types/bun/bun-types": ["bun-types@1.3.1", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-NMrcy7smratanWJ2mMXdpatalovtxVggkj11bScuWuiOoXTiKIu2eVS1/7qbyI/4yHedtsn175n4Sm4JcdHLXw=="],
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"typescript": "^5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@polar-sh/sdk": "^0.40.1",
|
||||
"drizzle-orm": "^0.44.6",
|
||||
"hono": "catalog:",
|
||||
"ioredis": "^5.8.2",
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
import { userTable } from "../user/user.sql.js";
|
||||
import { pgTable, varchar } from "drizzle-orm/pg-core";
|
||||
import { pgTable, unique, varchar } from "drizzle-orm/pg-core";
|
||||
import { id, timestamps, ulid } from "../drizzle/types.js";
|
||||
|
||||
export const orgTable = pgTable("organisations", {
|
||||
...id,
|
||||
...timestamps,
|
||||
ownerID: ulid("owner_id")
|
||||
.references(() => userTable.id, {
|
||||
onDelete: "cascade",
|
||||
})
|
||||
.notNull(),
|
||||
name: varchar("name", { length: 255 }).unique().notNull(),
|
||||
slug: varchar("slug", { length: 255 }).unique().notNull(),
|
||||
});
|
||||
export const orgTable = pgTable(
|
||||
"organisations",
|
||||
{
|
||||
...id,
|
||||
...timestamps,
|
||||
ownerID: ulid("owner_id")
|
||||
.references(() => userTable.id, {
|
||||
onDelete: "cascade",
|
||||
})
|
||||
.notNull(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
slug: varchar("slug", { length: 255 }).notNull(),
|
||||
},
|
||||
(org) => [unique("idx_organisation_slug").on(org.slug)],
|
||||
);
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { z } from "zod/v4";
|
||||
import { fn } from "../utils/fn.js";
|
||||
import { userTable } from "./user.sql.js";
|
||||
import { createID } from "../utils/id.js";
|
||||
import { Examples } from "../examples.js";
|
||||
import { Polar } from "../polar/index.js";
|
||||
import { Database } from "../drizzle/index.js";
|
||||
import { eq, and, isNotNull } from "drizzle-orm";
|
||||
|
||||
export namespace User {
|
||||
export const Info = z
|
||||
@@ -8,7 +14,7 @@ export namespace User {
|
||||
description: "The unique identifier of the user",
|
||||
example: Examples.User.id,
|
||||
}),
|
||||
name: z.string().optional().meta({
|
||||
name: z.string().nullable().meta({
|
||||
description: "The full name of the user",
|
||||
example: Examples.User.name,
|
||||
}),
|
||||
@@ -28,4 +34,67 @@ export namespace User {
|
||||
});
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
|
||||
export const create = fn(
|
||||
Info.omit({ customerID: true }).partial({ id: true }),
|
||||
async (input) => {
|
||||
const id = input.id ?? createID("user");
|
||||
|
||||
const customerID = await Polar.fromUserEmail(input.email).then(
|
||||
(z) => z.id,
|
||||
);
|
||||
|
||||
await Database.transaction((tx) =>
|
||||
tx
|
||||
.insert(userTable)
|
||||
.values({
|
||||
id,
|
||||
customerID,
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: userTable.email,
|
||||
set: {
|
||||
timeDeleted: null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
return id;
|
||||
},
|
||||
);
|
||||
|
||||
export const fromEmail = fn(Info.shape.email, (email) =>
|
||||
Database.transaction((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(userTable)
|
||||
.where(
|
||||
and(eq(userTable.email, email), isNotNull(userTable.timeDeleted)),
|
||||
)
|
||||
.then((rows) => rows.map(serialize).at(0)),
|
||||
),
|
||||
);
|
||||
|
||||
export const fromID = fn(Info.shape.id, (id) =>
|
||||
Database.transaction((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(userTable)
|
||||
.where(and(eq(userTable.id, id), isNotNull(userTable.timeDeleted)))
|
||||
.then((rows) => rows.map(serialize).at(0)),
|
||||
),
|
||||
);
|
||||
|
||||
export function serialize(
|
||||
input: typeof userTable.$inferSelect,
|
||||
): z.infer<typeof Info> {
|
||||
return {
|
||||
id: input.id,
|
||||
name: input.name,
|
||||
email: input.email,
|
||||
customerID: input.customerID,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { id, timestamps } from "../drizzle/types.js";
|
||||
import { pgTable, varchar } from "drizzle-orm/pg-core";
|
||||
import { pgTable, varchar, unique } from "drizzle-orm/pg-core";
|
||||
|
||||
export const userTable = pgTable("users", {
|
||||
...id,
|
||||
...timestamps,
|
||||
name: varchar("name", { length: 255 }),
|
||||
email: varchar("email", { length: 255 }).unique().notNull(),
|
||||
customerID: varchar("customer_id", { length: 255 }).notNull(),
|
||||
});
|
||||
export const userTable = pgTable(
|
||||
"users",
|
||||
{
|
||||
...id,
|
||||
...timestamps,
|
||||
name: varchar("name", { length: 255 }),
|
||||
email: varchar("email", { length: 255 }).notNull(),
|
||||
customerID: varchar("customer_id", { length: 255 }).notNull(),
|
||||
},
|
||||
(user) => [unique("idx_user_email").on(user.email)],
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { ulid } from "ulid";
|
||||
export const prefixes = {
|
||||
user: "usr",
|
||||
steam: "stm",
|
||||
team: "tm",
|
||||
app: "app",
|
||||
member: "mbr",
|
||||
apiSecret: "sec",
|
||||
@@ -14,7 +13,7 @@ export const prefixes = {
|
||||
/**
|
||||
* Generates a unique identifier by concatenating a predefined prefix with a ULID.
|
||||
*
|
||||
* Given a key from the predefined prefixes mapping (e.g.,"team", "member", "steam"),
|
||||
* Given a key from the predefined prefixes mapping (e.g.,"user", "member", "steam"),
|
||||
* this function retrieves the corresponding prefix and combines it with a ULID using an underscore
|
||||
* as a separator. The resulting identifier is formatted as "prefix_ulid".
|
||||
*
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RedisClient } from "bun";
|
||||
import { logger } from "hono/logger";
|
||||
import { subjects } from "@/subjects.js";
|
||||
import { issuer } from "@openauthjs/openauth";
|
||||
import { Team } from "@nestri/core/team/index";
|
||||
import { User } from "@nestri/core/user/index";
|
||||
import { RedisStorage } from "./src/storage.js";
|
||||
import { CodeProvider } from "@openauthjs/openauth/provider/code";
|
||||
|
||||
@@ -48,15 +48,17 @@ const app = issuer({
|
||||
let email = res.claims.email;
|
||||
|
||||
if (!email) throw new Error("No email found");
|
||||
let teamID = await Team.fromEmail(email).then((x) => x?.id);
|
||||
if (!teamID) {
|
||||
|
||||
let userID = await User.fromEmail(email).then((x) => x?.id);
|
||||
if (!userID) {
|
||||
console.log("creating account for", email);
|
||||
teamID = await Team.create({
|
||||
userID = await User.create({
|
||||
email: email!,
|
||||
name: null,
|
||||
});
|
||||
}
|
||||
return ctx.subject("team", email!, {
|
||||
teamID: teamID!,
|
||||
return ctx.subject("user", email!, {
|
||||
userID: userID!,
|
||||
email: email,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -2,8 +2,8 @@ import { z } from "zod/v4";
|
||||
import { createSubjects } from "@openauthjs/openauth/subject";
|
||||
|
||||
export const subjects = createSubjects({
|
||||
team: z.object({
|
||||
user: z.object({
|
||||
email: z.email(),
|
||||
teamID: z.string(),
|
||||
userID: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user