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",
|
"name": "@nestri/core",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@polar-sh/sdk": "^0.40.1",
|
||||||
"drizzle-orm": "^0.44.6",
|
"drizzle-orm": "^0.44.6",
|
||||||
"hono": "catalog:",
|
"hono": "catalog:",
|
||||||
"ioredis": "^5.8.2",
|
"ioredis": "^5.8.2",
|
||||||
@@ -109,6 +110,7 @@
|
|||||||
},
|
},
|
||||||
"packages/function": {
|
"packages/function": {
|
||||||
"name": "@nestri/function",
|
"name": "@nestri/function",
|
||||||
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openauthjs/openauth": "catalog:",
|
"@openauthjs/openauth": "catalog:",
|
||||||
"hono": "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/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-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=="],
|
"@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/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/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=="],
|
"@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"
|
"typescript": "^5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@polar-sh/sdk": "^0.40.1",
|
||||||
"drizzle-orm": "^0.44.6",
|
"drizzle-orm": "^0.44.6",
|
||||||
"hono": "catalog:",
|
"hono": "catalog:",
|
||||||
"ioredis": "^5.8.2",
|
"ioredis": "^5.8.2",
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
import { userTable } from "../user/user.sql.js";
|
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";
|
import { id, timestamps, ulid } from "../drizzle/types.js";
|
||||||
|
|
||||||
export const orgTable = pgTable("organisations", {
|
export const orgTable = pgTable(
|
||||||
...id,
|
"organisations",
|
||||||
...timestamps,
|
{
|
||||||
ownerID: ulid("owner_id")
|
...id,
|
||||||
.references(() => userTable.id, {
|
...timestamps,
|
||||||
onDelete: "cascade",
|
ownerID: ulid("owner_id")
|
||||||
})
|
.references(() => userTable.id, {
|
||||||
.notNull(),
|
onDelete: "cascade",
|
||||||
name: varchar("name", { length: 255 }).unique().notNull(),
|
})
|
||||||
slug: varchar("slug", { length: 255 }).unique().notNull(),
|
.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 { 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 { 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 namespace User {
|
||||||
export const Info = z
|
export const Info = z
|
||||||
@@ -8,7 +14,7 @@ export namespace User {
|
|||||||
description: "The unique identifier of the user",
|
description: "The unique identifier of the user",
|
||||||
example: Examples.User.id,
|
example: Examples.User.id,
|
||||||
}),
|
}),
|
||||||
name: z.string().optional().meta({
|
name: z.string().nullable().meta({
|
||||||
description: "The full name of the user",
|
description: "The full name of the user",
|
||||||
example: Examples.User.name,
|
example: Examples.User.name,
|
||||||
}),
|
}),
|
||||||
@@ -28,4 +34,67 @@ export namespace User {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export type Info = z.infer<typeof Info>;
|
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 { 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", {
|
export const userTable = pgTable(
|
||||||
...id,
|
"users",
|
||||||
...timestamps,
|
{
|
||||||
name: varchar("name", { length: 255 }),
|
...id,
|
||||||
email: varchar("email", { length: 255 }).unique().notNull(),
|
...timestamps,
|
||||||
customerID: varchar("customer_id", { length: 255 }).notNull(),
|
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 = {
|
export const prefixes = {
|
||||||
user: "usr",
|
user: "usr",
|
||||||
steam: "stm",
|
steam: "stm",
|
||||||
team: "tm",
|
|
||||||
app: "app",
|
app: "app",
|
||||||
member: "mbr",
|
member: "mbr",
|
||||||
apiSecret: "sec",
|
apiSecret: "sec",
|
||||||
@@ -14,7 +13,7 @@ export const prefixes = {
|
|||||||
/**
|
/**
|
||||||
* Generates a unique identifier by concatenating a predefined prefix with a ULID.
|
* 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
|
* 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".
|
* 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 { logger } from "hono/logger";
|
||||||
import { subjects } from "@/subjects.js";
|
import { subjects } from "@/subjects.js";
|
||||||
import { issuer } from "@openauthjs/openauth";
|
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 { RedisStorage } from "./src/storage.js";
|
||||||
import { CodeProvider } from "@openauthjs/openauth/provider/code";
|
import { CodeProvider } from "@openauthjs/openauth/provider/code";
|
||||||
|
|
||||||
@@ -48,15 +48,17 @@ const app = issuer({
|
|||||||
let email = res.claims.email;
|
let email = res.claims.email;
|
||||||
|
|
||||||
if (!email) throw new Error("No email found");
|
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);
|
console.log("creating account for", email);
|
||||||
teamID = await Team.create({
|
userID = await User.create({
|
||||||
email: email!,
|
email: email!,
|
||||||
|
name: null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return ctx.subject("team", email!, {
|
return ctx.subject("user", email!, {
|
||||||
teamID: teamID!,
|
userID: userID!,
|
||||||
email: email,
|
email: email,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import { z } from "zod/v4";
|
|||||||
import { createSubjects } from "@openauthjs/openauth/subject";
|
import { createSubjects } from "@openauthjs/openauth/subject";
|
||||||
|
|
||||||
export const subjects = createSubjects({
|
export const subjects = createSubjects({
|
||||||
team: z.object({
|
user: z.object({
|
||||||
email: z.email(),
|
email: z.email(),
|
||||||
teamID: z.string(),
|
userID: z.string(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user