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:
@@ -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