kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: WIP add better-auth
rodzic
6a19da284a
commit
07dc9e60d1
|
@ -1,6 +0,0 @@
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
# This is an example .env file.
|
|
||||||
#
|
|
||||||
# All of these environment vars must be defined either in your environment or in
|
|
||||||
# a local .env file in order to run this project.
|
|
||||||
# ------------------------------------------------------------------------------
|
|
|
@ -7,10 +7,15 @@
|
||||||
|
|
||||||
DATABASE_URL=
|
DATABASE_URL=
|
||||||
|
|
||||||
JWT_SECRET=
|
BETTER_AUTH_SECRET=
|
||||||
|
BETTER_AUTH_URL=
|
||||||
|
|
||||||
|
JWT_SECRET=
|
||||||
SENTRY_DSN=
|
SENTRY_DSN=
|
||||||
|
|
||||||
STRIPE_PUBLISHABLE_KEY=
|
STRIPE_PUBLISHABLE_KEY=
|
||||||
STRIPE_SECRET_KEY=
|
STRIPE_SECRET_KEY=
|
||||||
STRIPE_WEBHOOK_SECRET=
|
STRIPE_WEBHOOK_SECRET=
|
||||||
|
|
||||||
|
GITHUB_CLIENT_ID=
|
||||||
|
GITHUB_CLIENT_SECRET=
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
"@hono/zod-openapi": "^0.19.6",
|
"@hono/zod-openapi": "^0.19.6",
|
||||||
"@redocly/openapi-core": "^1.34.3",
|
"@redocly/openapi-core": "^1.34.3",
|
||||||
"@sentry/node": "^9.19.0",
|
"@sentry/node": "^9.19.0",
|
||||||
|
"better-auth": "^1.2.8",
|
||||||
"eventid": "^2.0.1",
|
"eventid": "^2.0.1",
|
||||||
"exit-hook": "catalog:",
|
"exit-hook": "catalog:",
|
||||||
"hono": "^4.7.9",
|
"hono": "^4.7.9",
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { OpenAPIHono } from '@hono/zod-openapi'
|
||||||
import { fromError } from 'zod-validation-error'
|
import { fromError } from 'zod-validation-error'
|
||||||
|
|
||||||
import type { AuthenticatedEnv } from '@/lib/types'
|
import type { AuthenticatedEnv } from '@/lib/types'
|
||||||
|
import { auth } from '@/lib/auth'
|
||||||
import * as middleware from '@/lib/middleware'
|
import * as middleware from '@/lib/middleware'
|
||||||
import { registerOpenAPIErrorResponses } from '@/lib/openapi-utils'
|
import { registerOpenAPIErrorResponses } from '@/lib/openapi-utils'
|
||||||
|
|
||||||
|
@ -17,6 +18,7 @@ import { registerV1DeploymentsListDeployments } from './deployments/list-deploym
|
||||||
import { registerV1DeploymentsPublishDeployment } from './deployments/publish-deployment'
|
import { registerV1DeploymentsPublishDeployment } from './deployments/publish-deployment'
|
||||||
import { registerV1DeploymentsUpdateDeployment } from './deployments/update-deployment'
|
import { registerV1DeploymentsUpdateDeployment } from './deployments/update-deployment'
|
||||||
import { registerHealthCheck } from './health-check'
|
import { registerHealthCheck } from './health-check'
|
||||||
|
// import { registerV1OAuthRedirect } from './oauth-redirect'
|
||||||
import { registerV1ProjectsCreateProject } from './projects/create-project'
|
import { registerV1ProjectsCreateProject } from './projects/create-project'
|
||||||
import { registerV1ProjectsGetProject } from './projects/get-project'
|
import { registerV1ProjectsGetProject } from './projects/get-project'
|
||||||
import { registerV1ProjectsListProjects } from './projects/list-projects'
|
import { registerV1ProjectsListProjects } from './projects/list-projects'
|
||||||
|
@ -98,11 +100,16 @@ registerV1DeploymentsUpdateDeployment(privateRouter)
|
||||||
registerV1DeploymentsListDeployments(privateRouter)
|
registerV1DeploymentsListDeployments(privateRouter)
|
||||||
registerV1DeploymentsPublishDeployment(privateRouter)
|
registerV1DeploymentsPublishDeployment(privateRouter)
|
||||||
|
|
||||||
|
// Internal admin routes
|
||||||
|
registerV1AdminConsumersGetConsumerByToken(privateRouter)
|
||||||
|
|
||||||
// Webhook event handlers
|
// Webhook event handlers
|
||||||
registerV1StripeWebhook(publicRouter)
|
registerV1StripeWebhook(publicRouter)
|
||||||
|
|
||||||
// Admin routes
|
// OAuth redirect
|
||||||
registerV1AdminConsumersGetConsumerByToken(privateRouter)
|
// registerV1OAuthRedirect(publicRouter)
|
||||||
|
|
||||||
|
publicRouter.on(['POST', 'GET'], 'auth/**', (c) => auth.handler(c.req.raw))
|
||||||
|
|
||||||
// Setup routes and middleware
|
// Setup routes and middleware
|
||||||
apiV1.route('/', publicRouter)
|
apiV1.route('/', publicRouter)
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
import type { OpenAPIHono } from '@hono/zod-openapi'
|
||||||
|
import { assert } from '@agentic/platform-core'
|
||||||
|
|
||||||
|
// TODO: Unused in favor of `better-auth`
|
||||||
|
export function registerV1OAuthRedirect(app: OpenAPIHono) {
|
||||||
|
return app.all('oauth', async (ctx) => {
|
||||||
|
if (ctx.req.query('state')) {
|
||||||
|
const { state: state64, ...query } = ctx.req.query()
|
||||||
|
|
||||||
|
// google oauth + others
|
||||||
|
const { uri, ...state } = JSON.parse(
|
||||||
|
Buffer.from(state64!, 'base64').toString()
|
||||||
|
) as any
|
||||||
|
|
||||||
|
assert(
|
||||||
|
uri,
|
||||||
|
404,
|
||||||
|
`Error oauth redirect not found "${new URLSearchParams(ctx.req.query()).toString()}"`
|
||||||
|
)
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams({
|
||||||
|
...state,
|
||||||
|
...query
|
||||||
|
})
|
||||||
|
|
||||||
|
ctx.redirect(`${uri}?${searchParams.toString()}`)
|
||||||
|
} else {
|
||||||
|
// github oauth
|
||||||
|
// https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/#redirect-urls
|
||||||
|
const { uri, ...params } = ctx.req.query()
|
||||||
|
|
||||||
|
assert(
|
||||||
|
uri,
|
||||||
|
404,
|
||||||
|
`Error oauth redirect not found "${new URLSearchParams(ctx.req.query()).toString()}"`
|
||||||
|
)
|
||||||
|
|
||||||
|
const searchParams = new URLSearchParams(params)
|
||||||
|
ctx.redirect(`${uri}?${searchParams.toString()}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
import type { hc } from 'hono/client'
|
|
||||||
import { expectTypeOf, test } from 'vitest'
|
|
||||||
|
|
||||||
import type { User } from '@/db'
|
|
||||||
|
|
||||||
import type { ApiRoutes } from './index'
|
|
||||||
|
|
||||||
type ApiClient = ReturnType<typeof hc<ApiRoutes>>
|
|
||||||
|
|
||||||
type GetUserResponse = Awaited<
|
|
||||||
ReturnType<Awaited<ReturnType<ApiClient['users'][':userId']['$get']>>['json']>
|
|
||||||
>
|
|
||||||
|
|
||||||
test('User types are compatible', async () => {
|
|
||||||
expectTypeOf<GetUserResponse>().toEqualTypeOf<User>()
|
|
||||||
|
|
||||||
// const client = hc<ApiRoutes>('http://localhost:3000/v1')
|
|
||||||
|
|
||||||
// const user = await client.users[':userId'].$post({
|
|
||||||
// param: {
|
|
||||||
// userId: '123'
|
|
||||||
// },
|
|
||||||
// json: {
|
|
||||||
// firstName: 'John'
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
})
|
|
|
@ -12,7 +12,7 @@ const relevantStripeEvents = new Set<Stripe.Event.Type>([
|
||||||
])
|
])
|
||||||
|
|
||||||
export function registerV1StripeWebhook(app: OpenAPIHono) {
|
export function registerV1StripeWebhook(app: OpenAPIHono) {
|
||||||
return app.post('/webhooks/stripe', async (ctx) => {
|
return app.post('webhooks/stripe', async (ctx) => {
|
||||||
const body = await ctx.req.text()
|
const body = await ctx.req.text()
|
||||||
const signature = ctx.req.header('Stripe-Signature')
|
const signature = ctx.req.header('Stripe-Signature')
|
||||||
assert(signature, 400, 'missing signature')
|
assert(signature, 400, 'missing signature')
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
// import { validators } from '@agentic/platform-validators'
|
||||||
|
import { betterAuth } from 'better-auth'
|
||||||
|
import { drizzleAdapter } from 'better-auth/adapters/drizzle'
|
||||||
|
|
||||||
|
import { createIdForModel, db } from '@/db'
|
||||||
|
|
||||||
|
import { env } from './env'
|
||||||
|
|
||||||
|
export const auth = betterAuth({
|
||||||
|
adapter: drizzleAdapter(db, {
|
||||||
|
provider: 'pg'
|
||||||
|
}),
|
||||||
|
emailAndPassword: {
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
socialProviders: {
|
||||||
|
github: {
|
||||||
|
clientId: env.GITHUB_CLIENT_ID,
|
||||||
|
clientSecret: env.GITHUB_CLIENT_SECRET
|
||||||
|
}
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
modelName: 'users',
|
||||||
|
additionalFields: {
|
||||||
|
role: {
|
||||||
|
type: 'string',
|
||||||
|
required: false,
|
||||||
|
defaultValue: 'user',
|
||||||
|
input: false // don't allow user to set role
|
||||||
|
},
|
||||||
|
// username: {
|
||||||
|
// type: 'string',
|
||||||
|
// required: false
|
||||||
|
// },
|
||||||
|
stripeCustomerId: {
|
||||||
|
type: 'string',
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
session: {
|
||||||
|
modelName: 'sessions'
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
modelName: 'accounts'
|
||||||
|
},
|
||||||
|
verification: {
|
||||||
|
modelName: 'verifications'
|
||||||
|
},
|
||||||
|
advanced: {
|
||||||
|
database: {
|
||||||
|
generateId: ({ model }) => createIdForModel(model as any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO
|
||||||
|
// plugins: [
|
||||||
|
// username({
|
||||||
|
// usernameValidator: validators.username
|
||||||
|
// })
|
||||||
|
// ]
|
||||||
|
})
|
|
@ -12,14 +12,19 @@ export const envSchema = z.object({
|
||||||
|
|
||||||
DATABASE_URL: z.string().url(),
|
DATABASE_URL: z.string().url(),
|
||||||
|
|
||||||
|
BETTER_AUTH_SECRET: z.string().nonempty(),
|
||||||
|
BETTER_AUTH_URL: z.string().url(),
|
||||||
|
|
||||||
JWT_SECRET: z.string().nonempty(),
|
JWT_SECRET: z.string().nonempty(),
|
||||||
SENTRY_DSN: z.string().url(),
|
SENTRY_DSN: z.string().url(),
|
||||||
PORT: z.number().default(3000),
|
PORT: z.number().default(3000),
|
||||||
LOG_LEVEL: logLevelsSchema.default('info'),
|
LOG_LEVEL: logLevelsSchema.default('info'),
|
||||||
|
|
||||||
STRIPE_SECRET_KEY: z.string().nonempty(),
|
STRIPE_SECRET_KEY: z.string().nonempty(),
|
||||||
STRIPE_PUBLISHABLE_KEY: z.string().nonempty(),
|
STRIPE_WEBHOOK_SECRET: z.string().nonempty(),
|
||||||
STRIPE_WEBHOOK_SECRET: z.string().nonempty()
|
|
||||||
|
GITHUB_CLIENT_ID: z.string().nonempty(),
|
||||||
|
GITHUB_CLIENT_SECRET: z.string().nonempty()
|
||||||
})
|
})
|
||||||
export type Env = z.infer<typeof envSchema>
|
export type Env = z.infer<typeof envSchema>
|
||||||
|
|
||||||
|
@ -32,4 +37,4 @@ export const isDev = env.NODE_ENV === 'development'
|
||||||
export const isProd = env.NODE_ENV === 'production'
|
export const isProd = env.NODE_ENV === 'production'
|
||||||
export const isBrowser = (globalThis as any).window !== undefined
|
export const isBrowser = (globalThis as any).window !== undefined
|
||||||
|
|
||||||
export const isStripeLive = env.STRIPE_PUBLISHABLE_KEY.startsWith('pk_live_')
|
export const isStripeLive = env.STRIPE_SECRET_KEY.startsWith('sk_live_')
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
import ky from 'ky'
|
||||||
|
|
||||||
|
import { env } from './env'
|
||||||
|
|
||||||
|
const USER_AGENT = 'agentic-platform'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GitHub (user-level) OAuth token response.
|
||||||
|
*
|
||||||
|
* @see https://docs.github.com/apps/oauth
|
||||||
|
*/
|
||||||
|
export interface GitHubUserTokenResponse {
|
||||||
|
/**
|
||||||
|
* The user access token (always starts with `ghu_`).
|
||||||
|
* Example: `ghu_xxx…`
|
||||||
|
*/
|
||||||
|
access_token: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seconds until `access_token` expires.
|
||||||
|
* Omitted (`undefined`) if you’ve disabled token expiration.
|
||||||
|
* Constant `28800` (8 hours) when present.
|
||||||
|
*/
|
||||||
|
expires_in?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh token for renewing the user access token (starts with `ghr_`).
|
||||||
|
* Omitted (`undefined`) if you’ve disabled token expiration.
|
||||||
|
*/
|
||||||
|
refresh_token?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seconds until `refresh_token` expires.
|
||||||
|
* Omitted (`undefined`) if you’ve disabled token expiration.
|
||||||
|
* Constant `15897600` (6 months) when present.
|
||||||
|
*/
|
||||||
|
refresh_token_expires_in?: number
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scopes granted to the token.
|
||||||
|
* Always an empty string because the token is limited to
|
||||||
|
* the intersection of app-level and user-level permissions.
|
||||||
|
*/
|
||||||
|
scope: ''
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token type – always `'bearer'`.
|
||||||
|
*/
|
||||||
|
token_type: 'bearer'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GitHubUser {
|
||||||
|
login: string
|
||||||
|
id: number
|
||||||
|
user_view_type?: string
|
||||||
|
node_id: string
|
||||||
|
avatar_url: string
|
||||||
|
gravatar_id: string | null
|
||||||
|
url: string
|
||||||
|
html_url: string
|
||||||
|
followers_url: string
|
||||||
|
following_url: string
|
||||||
|
gists_url: string
|
||||||
|
starred_url: string
|
||||||
|
subscriptions_url: string
|
||||||
|
organizations_url: string
|
||||||
|
repos_url: string
|
||||||
|
events_url: string
|
||||||
|
received_events_url: string
|
||||||
|
type: string
|
||||||
|
site_admin: boolean
|
||||||
|
name: string | null
|
||||||
|
company: string | null
|
||||||
|
blog: string | null
|
||||||
|
location: string | null
|
||||||
|
email: string | null
|
||||||
|
notification_email?: string | null
|
||||||
|
hireable: boolean | null
|
||||||
|
bio: string | null
|
||||||
|
twitter_username?: string | null
|
||||||
|
public_repos: number
|
||||||
|
public_gists: number
|
||||||
|
followers: number
|
||||||
|
following: number
|
||||||
|
created_at: string
|
||||||
|
updated_at: string
|
||||||
|
plan?: {
|
||||||
|
collaborators: number
|
||||||
|
name: string
|
||||||
|
space: number
|
||||||
|
private_repos: number
|
||||||
|
[k: string]: unknown
|
||||||
|
}
|
||||||
|
private_gists?: number
|
||||||
|
total_private_repos?: number
|
||||||
|
owned_private_repos?: number
|
||||||
|
disk_usage?: number
|
||||||
|
collaborators?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GitHubUserEmail {
|
||||||
|
email: string
|
||||||
|
primary: boolean
|
||||||
|
verified: boolean
|
||||||
|
visibility?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function exchangeOAuthCodeForAccessToken({
|
||||||
|
code,
|
||||||
|
clientId = env.GITHUB_CLIENT_ID,
|
||||||
|
clientSecret = env.GITHUB_CLIENT_SECRET,
|
||||||
|
redirectUri
|
||||||
|
}: {
|
||||||
|
code: string
|
||||||
|
clientId?: string
|
||||||
|
clientSecret?: string
|
||||||
|
redirectUri?: string
|
||||||
|
}): Promise<GitHubUserTokenResponse> {
|
||||||
|
return ky
|
||||||
|
.post('https://github.com/login/oauth/access_token', {
|
||||||
|
json: {
|
||||||
|
code,
|
||||||
|
client_id: clientId,
|
||||||
|
client_secret: clientSecret,
|
||||||
|
redirect_uri: redirectUri
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'user-agent': USER_AGENT
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json<GitHubUserTokenResponse>()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMe({ token }: { token: string }): Promise<GitHubUser> {
|
||||||
|
return ky
|
||||||
|
.get('https://api.github.com/user', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'user-agent': USER_AGENT
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json<GitHubUser>()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUserEmails({
|
||||||
|
token
|
||||||
|
}: {
|
||||||
|
token: string
|
||||||
|
}): Promise<GitHubUserEmail[]> {
|
||||||
|
return ky
|
||||||
|
.get('https://api.github.com/user/emails', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'user-agent': USER_AGENT
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.json<GitHubUserEmail[]>()
|
||||||
|
}
|
|
@ -1,42 +1,55 @@
|
||||||
import { assert } from '@agentic/platform-core'
|
import { assert } from '@agentic/platform-core'
|
||||||
import { createMiddleware } from 'hono/factory'
|
import { createMiddleware } from 'hono/factory'
|
||||||
import * as jwt from 'hono/jwt'
|
|
||||||
|
|
||||||
|
// import * as jwt from 'hono/jwt'
|
||||||
import type { AuthenticatedEnv } from '@/lib/types'
|
import type { AuthenticatedEnv } from '@/lib/types'
|
||||||
import { db, eq, schema } from '@/db'
|
import { auth } from '@/lib/auth'
|
||||||
import { env } from '@/lib/env'
|
|
||||||
|
|
||||||
export const authenticate = createMiddleware<AuthenticatedEnv>(
|
export const authenticate = createMiddleware<AuthenticatedEnv>(
|
||||||
async function authenticateMiddleware(ctx, next) {
|
async function authenticateMiddleware(ctx, next) {
|
||||||
const credentials = ctx.req.raw.headers.get('Authorization')
|
const session = await auth.api.getSession({
|
||||||
assert(credentials, 401, 'Unauthorized')
|
// TODO: investigate this type issue
|
||||||
|
headers: ctx.req.raw.headers as any
|
||||||
const parts = credentials.split(/\s+/)
|
|
||||||
assert(
|
|
||||||
parts.length === 1 ||
|
|
||||||
(parts.length === 2 && parts[0]?.toLowerCase() === 'bearer'),
|
|
||||||
401,
|
|
||||||
'Unauthorized'
|
|
||||||
)
|
|
||||||
const token = parts.at(-1)
|
|
||||||
assert(token, 401, 'Unauthorized')
|
|
||||||
|
|
||||||
const payload = await jwt.verify(token, env.JWT_SECRET)
|
|
||||||
assert(payload, 401, 'Unauthorized')
|
|
||||||
assert(payload.type === 'user', 401, 'Unauthorized')
|
|
||||||
assert(
|
|
||||||
payload.userId && typeof payload.userId === 'string',
|
|
||||||
401,
|
|
||||||
'Unauthorized'
|
|
||||||
)
|
|
||||||
ctx.set('userId', payload.userId)
|
|
||||||
|
|
||||||
const user = await db.query.users.findFirst({
|
|
||||||
where: eq(schema.users.id, payload.userId)
|
|
||||||
})
|
})
|
||||||
assert(user, 401, 'Unauthorized')
|
assert(session, 401, 'Unauthorized')
|
||||||
ctx.set('user', user as any)
|
assert(session.user?.id, 401, 'Unauthorized')
|
||||||
|
assert(session.session, 401, 'Unauthorized')
|
||||||
|
|
||||||
|
ctx.set('userId', session.user.id)
|
||||||
|
ctx.set('user', session.user)
|
||||||
|
ctx.set('session', session.session)
|
||||||
|
|
||||||
await next()
|
await next()
|
||||||
|
|
||||||
|
// const credentials = ctx.req.raw.headers.get('Authorization')
|
||||||
|
// assert(credentials, 401, 'Unauthorized')
|
||||||
|
|
||||||
|
// const parts = credentials.split(/\s+/)
|
||||||
|
// assert(
|
||||||
|
// parts.length === 1 ||
|
||||||
|
// (parts.length === 2 && parts[0]?.toLowerCase() === 'bearer'),
|
||||||
|
// 401,
|
||||||
|
// 'Unauthorized'
|
||||||
|
// )
|
||||||
|
// const token = parts.at(-1)
|
||||||
|
// assert(token, 401, 'Unauthorized')
|
||||||
|
|
||||||
|
// const payload = await jwt.verify(token, env.JWT_SECRET)
|
||||||
|
// assert(payload, 401, 'Unauthorized')
|
||||||
|
// assert(payload.type === 'user', 401, 'Unauthorized')
|
||||||
|
// assert(
|
||||||
|
// payload.userId && typeof payload.userId === 'string',
|
||||||
|
// 401,
|
||||||
|
// 'Unauthorized'
|
||||||
|
// )
|
||||||
|
// ctx.set('userId', payload.userId)
|
||||||
|
|
||||||
|
// const user = await db.query.users.findFirst({
|
||||||
|
// where: eq(schema.users.id, payload.userId)
|
||||||
|
// })
|
||||||
|
// assert(user, 401, 'Unauthorized')
|
||||||
|
// ctx.set('user', user as any)
|
||||||
|
|
||||||
|
// await next()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,13 +12,13 @@ import { aclTeamMember } from '@/lib/acl-team-member'
|
||||||
export const team = createMiddleware<AuthenticatedEnv>(
|
export const team = createMiddleware<AuthenticatedEnv>(
|
||||||
async function teamMiddleware(ctx, next) {
|
async function teamMiddleware(ctx, next) {
|
||||||
const teamId = ctx.req.query('teamId')
|
const teamId = ctx.req.query('teamId')
|
||||||
const user = ctx.get('user')
|
const userId = ctx.get('userId')
|
||||||
|
|
||||||
if (teamId && user) {
|
if (teamId && userId) {
|
||||||
const teamMember = await db.query.teamMembers.findFirst({
|
const teamMember = await db.query.teamMembers.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
eq(schema.teamMembers.teamId, teamId),
|
eq(schema.teamMembers.teamId, teamId),
|
||||||
eq(schema.teamMembers.userId, user.id)
|
eq(schema.teamMembers.userId, userId)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
assert(teamMember, 403, 'Unauthorized')
|
assert(teamMember, 403, 'Unauthorized')
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import type { Context } from 'hono'
|
import type { Context } from 'hono'
|
||||||
|
|
||||||
import type { RawTeamMember, RawUser } from '@/db'
|
import type { RawTeamMember } from '@/db'
|
||||||
|
|
||||||
|
import type { auth } from './auth'
|
||||||
import type { Env } from './env'
|
import type { Env } from './env'
|
||||||
import type { Logger } from './logger'
|
import type { Logger } from './logger'
|
||||||
|
|
||||||
|
@ -10,6 +11,9 @@ export type { OpenAPI3 as LooseOpenAPI3Spec } from 'openapi-typescript'
|
||||||
export type Environment = Env['NODE_ENV']
|
export type Environment = Env['NODE_ENV']
|
||||||
export type Service = 'api'
|
export type Service = 'api'
|
||||||
|
|
||||||
|
export type AuthUser = typeof auth.$Infer.Session.user
|
||||||
|
export type AuthSession = typeof auth.$Infer.Session.session
|
||||||
|
|
||||||
export type DefaultEnvVariables = {
|
export type DefaultEnvVariables = {
|
||||||
requestId: string
|
requestId: string
|
||||||
logger: Logger
|
logger: Logger
|
||||||
|
@ -17,7 +21,8 @@ export type DefaultEnvVariables = {
|
||||||
|
|
||||||
export type AuthenticatedEnvVariables = DefaultEnvVariables & {
|
export type AuthenticatedEnvVariables = DefaultEnvVariables & {
|
||||||
userId: string
|
userId: string
|
||||||
user?: RawUser
|
user?: AuthUser
|
||||||
|
session?: AuthSession
|
||||||
teamMember?: RawTeamMember
|
teamMember?: RawTeamMember
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import type { Simplify } from 'type-fest'
|
import type { Simplify } from 'type-fest'
|
||||||
|
import { getEnv, sanitizeSearchParams } from '@agentic/platform-core'
|
||||||
import defaultKy, { type KyInstance } from 'ky'
|
import defaultKy, { type KyInstance } from 'ky'
|
||||||
|
|
||||||
import type { operations } from './openapi'
|
import type { operations } from './openapi'
|
||||||
import { getEnv, sanitizeSearchParams } from './utils'
|
|
||||||
|
|
||||||
export class AgenticApiClient {
|
export class AgenticApiClient {
|
||||||
static readonly DEFAULT_API_BASE_URL = 'https://api.agentic.so'
|
static readonly DEFAULT_API_BASE_URL = 'https://api.agentic.so'
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
export function getEnv(name: string): string | undefined {
|
|
||||||
try {
|
|
||||||
return typeof process !== 'undefined'
|
|
||||||
? // eslint-disable-next-line no-process-env
|
|
||||||
process.env?.[name]
|
|
||||||
: undefined
|
|
||||||
} catch {
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new `URLSearchParams` object with all values coerced to strings
|
|
||||||
* that correctly handles arrays of values as repeated keys (or CSV) and
|
|
||||||
* correctly removes `undefined` keys and values.
|
|
||||||
*/
|
|
||||||
export function sanitizeSearchParams(
|
|
||||||
searchParams:
|
|
||||||
| Record<
|
|
||||||
string,
|
|
||||||
string | number | boolean | string[] | number[] | boolean[] | undefined
|
|
||||||
>
|
|
||||||
| object,
|
|
||||||
{
|
|
||||||
csv = false
|
|
||||||
}: {
|
|
||||||
/**
|
|
||||||
* Whether to use comma-separated-values for arrays or multiple entries.
|
|
||||||
*
|
|
||||||
* Defaults to `false` and will use multiple entries.
|
|
||||||
*/
|
|
||||||
csv?: boolean
|
|
||||||
} = {}
|
|
||||||
): URLSearchParams {
|
|
||||||
const entries = Object.entries(searchParams).flatMap(([key, value]) => {
|
|
||||||
if (key === undefined || value === undefined) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value.map((v) => [key, String(v)])
|
|
||||||
}
|
|
||||||
|
|
||||||
return [[key, String(value)]]
|
|
||||||
}) as [string, string][]
|
|
||||||
|
|
||||||
if (!csv) {
|
|
||||||
return new URLSearchParams(entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
const csvEntries: Record<string, string> = {}
|
|
||||||
for (const [key, value] of entries) {
|
|
||||||
csvEntries[key] = csvEntries[key] ? `${csvEntries[key]},${value}` : value
|
|
||||||
}
|
|
||||||
|
|
||||||
return new URLSearchParams(csvEntries)
|
|
||||||
}
|
|
|
@ -115,3 +115,61 @@ export function hashObject(
|
||||||
...options
|
...options
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getEnv(name: string): string | undefined {
|
||||||
|
try {
|
||||||
|
return typeof process !== 'undefined'
|
||||||
|
? // eslint-disable-next-line no-process-env
|
||||||
|
process.env?.[name]
|
||||||
|
: undefined
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new `URLSearchParams` object with all values coerced to strings
|
||||||
|
* that correctly handles arrays of values as repeated keys (or CSV) and
|
||||||
|
* correctly removes `undefined` keys and values.
|
||||||
|
*/
|
||||||
|
export function sanitizeSearchParams(
|
||||||
|
searchParams:
|
||||||
|
| Record<
|
||||||
|
string,
|
||||||
|
string | number | boolean | string[] | number[] | boolean[] | undefined
|
||||||
|
>
|
||||||
|
| object,
|
||||||
|
{
|
||||||
|
csv = false
|
||||||
|
}: {
|
||||||
|
/**
|
||||||
|
* Whether to use comma-separated-values for arrays or multiple entries.
|
||||||
|
*
|
||||||
|
* Defaults to `false` and will use multiple entries.
|
||||||
|
*/
|
||||||
|
csv?: boolean
|
||||||
|
} = {}
|
||||||
|
): URLSearchParams {
|
||||||
|
const entries = Object.entries(searchParams).flatMap(([key, value]) => {
|
||||||
|
if (key === undefined || value === undefined) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map((v) => [key, String(v)])
|
||||||
|
}
|
||||||
|
|
||||||
|
return [[key, String(value)]]
|
||||||
|
}) as [string, string][]
|
||||||
|
|
||||||
|
if (!csv) {
|
||||||
|
return new URLSearchParams(entries)
|
||||||
|
}
|
||||||
|
|
||||||
|
const csvEntries: Record<string, string> = {}
|
||||||
|
for (const [key, value] of entries) {
|
||||||
|
csvEntries[key] = csvEntries[key] ? `${csvEntries[key]},${value}` : value
|
||||||
|
}
|
||||||
|
|
||||||
|
return new URLSearchParams(csvEntries)
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
import { pgTable, text, timestamp } from '@fisch0920/drizzle-orm/pg-core'
|
||||||
|
|
||||||
|
import {
|
||||||
|
accountPrimaryId,
|
||||||
|
sessionPrimaryId,
|
||||||
|
timestamps,
|
||||||
|
userId,
|
||||||
|
verificationPrimaryId
|
||||||
|
} from './common'
|
||||||
|
import { users } from './user'
|
||||||
|
|
||||||
|
// These tables are all managed by better-auth.
|
||||||
|
|
||||||
|
export const sessions = pgTable('sessions', {
|
||||||
|
...sessionPrimaryId,
|
||||||
|
...timestamps,
|
||||||
|
|
||||||
|
expiresAt: timestamp('expiresAt').notNull(),
|
||||||
|
ipAddress: text('ipAddress'),
|
||||||
|
userAgent: text('userAgent'),
|
||||||
|
userId: userId()
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id)
|
||||||
|
})
|
||||||
|
|
||||||
|
export const accounts = pgTable('accounts', {
|
||||||
|
...accountPrimaryId,
|
||||||
|
...timestamps,
|
||||||
|
|
||||||
|
accountId: text('accountId').notNull(),
|
||||||
|
providerId: text('providerId').notNull(),
|
||||||
|
userId: userId()
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
accessToken: text('accessToken'),
|
||||||
|
refreshToken: text('refreshToken'),
|
||||||
|
idToken: text('idToken'),
|
||||||
|
expiresAt: timestamp('expiresAt').notNull(),
|
||||||
|
password: text('password')
|
||||||
|
})
|
||||||
|
|
||||||
|
export const verifications = pgTable('verifications', {
|
||||||
|
...verificationPrimaryId,
|
||||||
|
...timestamps,
|
||||||
|
|
||||||
|
identifier: text('identifier').notNull(),
|
||||||
|
value: text('value').notNull(),
|
||||||
|
|
||||||
|
expiresAt: timestamp('expiresAt').notNull()
|
||||||
|
})
|
|
@ -16,19 +16,31 @@ import { createId as createCuid2 } from '@paralleldrive/cuid2'
|
||||||
|
|
||||||
const usernameAndTeamSlugLength = 64 as const
|
const usernameAndTeamSlugLength = 64 as const
|
||||||
|
|
||||||
// prefix is max 4 characters
|
// prefix is max 5 characters
|
||||||
// separator is 1 character
|
// separator is 1 character
|
||||||
// cuid2 is max 24 characters
|
// cuid2 is max 24 characters
|
||||||
// so use 32 characters to be safe for storing ids
|
// so use 32 characters to be safe for storing ids
|
||||||
export const idMaxLength = 32 as const
|
export const idMaxLength = 32 as const
|
||||||
|
|
||||||
export const idPrefixMap = {
|
export const idPrefixMap = {
|
||||||
user: 'user',
|
|
||||||
team: 'team',
|
team: 'team',
|
||||||
project: 'proj',
|
project: 'proj',
|
||||||
deployment: 'depl',
|
deployment: 'depl',
|
||||||
consumer: 'csmr',
|
consumer: 'csmr',
|
||||||
logEntry: 'log'
|
logEntry: 'log',
|
||||||
|
|
||||||
|
// better-auth
|
||||||
|
user: 'user',
|
||||||
|
account: 'acct',
|
||||||
|
session: 'sess',
|
||||||
|
verification: 'veri',
|
||||||
|
'rate-limit': 'ratel',
|
||||||
|
organization: 'org',
|
||||||
|
member: 'mem',
|
||||||
|
invitation: 'inv',
|
||||||
|
jwks: 'jwks',
|
||||||
|
passkey: 'passk',
|
||||||
|
'two-factor': '2fa'
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type ModelType = keyof typeof idPrefixMap
|
export type ModelType = keyof typeof idPrefixMap
|
||||||
|
@ -57,6 +69,9 @@ export const consumerPrimaryId = getPrimaryId('consumer')
|
||||||
export const logEntryPrimaryId = getPrimaryId('logEntry')
|
export const logEntryPrimaryId = getPrimaryId('logEntry')
|
||||||
export const teamPrimaryId = getPrimaryId('team')
|
export const teamPrimaryId = getPrimaryId('team')
|
||||||
export const userPrimaryId = getPrimaryId('user')
|
export const userPrimaryId = getPrimaryId('user')
|
||||||
|
export const sessionPrimaryId = getPrimaryId('session')
|
||||||
|
export const accountPrimaryId = getPrimaryId('account')
|
||||||
|
export const verificationPrimaryId = getPrimaryId('verification')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All of our model primary ids have the following format:
|
* All of our model primary ids have the following format:
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './auth'
|
||||||
export * from './common'
|
export * from './common'
|
||||||
export * from './consumer'
|
export * from './consumer'
|
||||||
export * from './deployment'
|
export * from './deployment'
|
||||||
|
|
|
@ -1,61 +1,61 @@
|
||||||
import { z } from '@hono/zod-openapi'
|
import { z } from '@hono/zod-openapi'
|
||||||
import parseJson from 'parse-json'
|
import parseJson from 'parse-json'
|
||||||
|
|
||||||
export const authProviderTypeSchema = z
|
// export const authProviderTypeSchema = z
|
||||||
.union([
|
// .union([
|
||||||
z.literal('github'),
|
// z.literal('github'),
|
||||||
z.literal('google'),
|
// z.literal('google'),
|
||||||
z.literal('spotify'),
|
// z.literal('spotify'),
|
||||||
z.literal('twitter'),
|
// z.literal('twitter'),
|
||||||
z.literal('linkedin'),
|
// z.literal('linkedin'),
|
||||||
z.literal('stripe')
|
// z.literal('stripe')
|
||||||
])
|
// ])
|
||||||
.openapi('AuthProviderType')
|
// .openapi('AuthProviderType')
|
||||||
export type AuthProviderType = z.infer<typeof authProviderTypeSchema>
|
// export type AuthProviderType = z.infer<typeof authProviderTypeSchema>
|
||||||
|
|
||||||
export const authProviderSchema = z.object({
|
// export const authProviderSchema = z.object({
|
||||||
provider: authProviderTypeSchema,
|
// provider: authProviderTypeSchema,
|
||||||
|
|
||||||
/** Provider-specific user id */
|
// /** Provider-specific user id */
|
||||||
id: z.string(),
|
// id: z.string(),
|
||||||
|
|
||||||
/** Provider-specific username */
|
// /** Provider-specific username */
|
||||||
username: z.string().optional(),
|
// username: z.string().optional(),
|
||||||
|
|
||||||
/** Standard oauth2 access token */
|
// /** Standard oauth2 access token */
|
||||||
accessToken: z.string().optional(),
|
// accessToken: z.string().optional(),
|
||||||
|
|
||||||
/** Standard oauth2 refresh token */
|
// /** Standard oauth2 refresh token */
|
||||||
refreshToken: z.string().optional(),
|
// refreshToken: z.string().optional(),
|
||||||
|
|
||||||
/** Stripe public key */
|
// /** Stripe public key */
|
||||||
publicKey: z.string().optional(),
|
// publicKey: z.string().optional(),
|
||||||
|
|
||||||
/** OAuth scope(s) */
|
// /** OAuth scope(s) */
|
||||||
scope: z.string().optional()
|
// scope: z.string().optional()
|
||||||
})
|
// })
|
||||||
export type AuthProvider = z.infer<typeof authProviderSchema>
|
// export type AuthProvider = z.infer<typeof authProviderSchema>
|
||||||
|
|
||||||
export const publicAuthProviderSchema = authProviderSchema
|
// export const publicAuthProviderSchema = authProviderSchema
|
||||||
.omit({
|
// .omit({
|
||||||
accessToken: true,
|
// accessToken: true,
|
||||||
refreshToken: true,
|
// refreshToken: true,
|
||||||
publicKey: true
|
// publicKey: true
|
||||||
})
|
// })
|
||||||
.strip()
|
// .strip()
|
||||||
.openapi('AuthProvider')
|
// .openapi('AuthProvider')
|
||||||
export type PublicAuthProvider = z.infer<typeof publicAuthProviderSchema>
|
// export type PublicAuthProvider = z.infer<typeof publicAuthProviderSchema>
|
||||||
|
|
||||||
export const authProvidersSchema = z.record(
|
// export const authProvidersSchema = z.record(
|
||||||
authProviderTypeSchema,
|
// authProviderTypeSchema,
|
||||||
authProviderSchema.optional()
|
// authProviderSchema.optional()
|
||||||
)
|
// )
|
||||||
export type AuthProviders = z.infer<typeof authProvidersSchema>
|
// export type AuthProviders = z.infer<typeof authProvidersSchema>
|
||||||
|
|
||||||
export const publicAuthProvidersSchema = z
|
// export const publicAuthProvidersSchema = z
|
||||||
.record(authProviderTypeSchema, publicAuthProviderSchema.optional())
|
// .record(authProviderTypeSchema, publicAuthProviderSchema.optional())
|
||||||
.openapi('AuthProviders')
|
// .openapi('AuthProviders')
|
||||||
export type PublicAuthProviders = z.infer<typeof publicAuthProvidersSchema>
|
// export type PublicAuthProviders = z.infer<typeof publicAuthProvidersSchema>
|
||||||
|
|
||||||
export const webhookSchema = z
|
export const webhookSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
|
|
@ -1,64 +1,46 @@
|
||||||
import { sha256 } from '@agentic/platform-core'
|
|
||||||
import { validators } from '@agentic/platform-validators'
|
|
||||||
import { relations } from '@fisch0920/drizzle-orm'
|
import { relations } from '@fisch0920/drizzle-orm'
|
||||||
import {
|
import {
|
||||||
boolean,
|
boolean,
|
||||||
index,
|
index,
|
||||||
jsonb,
|
|
||||||
pgTable,
|
pgTable,
|
||||||
text,
|
text,
|
||||||
uniqueIndex
|
uniqueIndex
|
||||||
} from '@fisch0920/drizzle-orm/pg-core'
|
} from '@fisch0920/drizzle-orm/pg-core'
|
||||||
import { hashSync } from 'bcryptjs'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createInsertSchema,
|
|
||||||
createSelectSchema,
|
createSelectSchema,
|
||||||
createUpdateSchema,
|
|
||||||
stripeId,
|
stripeId,
|
||||||
timestamp,
|
|
||||||
timestamps,
|
timestamps,
|
||||||
username,
|
// username,
|
||||||
userPrimaryId,
|
userPrimaryId,
|
||||||
userRoleEnum
|
userRoleEnum
|
||||||
} from './common'
|
} from './common'
|
||||||
import { type AuthProviders, publicAuthProvidersSchema } from './schemas'
|
|
||||||
import { teams } from './team'
|
import { teams } from './team'
|
||||||
|
|
||||||
|
// This table is mostly managed by better-auth.
|
||||||
|
|
||||||
export const users = pgTable(
|
export const users = pgTable(
|
||||||
'users',
|
'users',
|
||||||
{
|
{
|
||||||
...userPrimaryId,
|
...userPrimaryId,
|
||||||
...timestamps,
|
...timestamps,
|
||||||
|
|
||||||
username: username().notNull().unique(),
|
name: text('name').notNull(),
|
||||||
|
email: text('email').notNull().unique(),
|
||||||
|
emailVerified: boolean('emailVerified').default(false).notNull(),
|
||||||
|
image: text('image'),
|
||||||
|
|
||||||
|
// TODO: re-add username
|
||||||
|
// username: username().notNull().unique(),
|
||||||
role: userRoleEnum().default('user').notNull(),
|
role: userRoleEnum().default('user').notNull(),
|
||||||
|
|
||||||
email: text().unique(),
|
|
||||||
password: text(),
|
|
||||||
|
|
||||||
// metadata
|
|
||||||
firstName: text(),
|
|
||||||
lastName: text(),
|
|
||||||
image: text(),
|
|
||||||
|
|
||||||
emailConfirmed: boolean().default(false).notNull(),
|
|
||||||
emailConfirmedAt: timestamp(),
|
|
||||||
emailConfirmToken: text().unique().notNull(),
|
|
||||||
passwordResetToken: text().unique(),
|
|
||||||
|
|
||||||
isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
|
isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
|
||||||
|
|
||||||
// third-party auth providers
|
|
||||||
authProviders: jsonb().$type<AuthProviders>().default({}).notNull(),
|
|
||||||
|
|
||||||
stripeCustomerId: stripeId()
|
stripeCustomerId: stripeId()
|
||||||
},
|
},
|
||||||
(table) => [
|
(table) => [
|
||||||
uniqueIndex('user_email_idx').on(table.email),
|
uniqueIndex('user_email_idx').on(table.email),
|
||||||
uniqueIndex('user_username_idx').on(table.username),
|
// uniqueIndex('user_username_idx').on(table.username),
|
||||||
uniqueIndex('user_emailConfirmToken_idx').on(table.emailConfirmToken),
|
|
||||||
uniqueIndex('user_passwordResetToken_idx').on(table.passwordResetToken),
|
|
||||||
index('user_createdAt_idx').on(table.createdAt),
|
index('user_createdAt_idx').on(table.createdAt),
|
||||||
index('user_updatedAt_idx').on(table.updatedAt),
|
index('user_updatedAt_idx').on(table.updatedAt),
|
||||||
index('user_deletedAt_idx').on(table.deletedAt)
|
index('user_deletedAt_idx').on(table.deletedAt)
|
||||||
|
@ -70,49 +52,49 @@ export const usersRelations = relations(users, ({ many }) => ({
|
||||||
}))
|
}))
|
||||||
|
|
||||||
export const userSelectSchema = createSelectSchema(users, {
|
export const userSelectSchema = createSelectSchema(users, {
|
||||||
authProviders: publicAuthProvidersSchema
|
// authProviders: publicAuthProvidersSchema
|
||||||
})
|
})
|
||||||
.omit({ password: true, emailConfirmToken: true, passwordResetToken: true })
|
// .omit({ password: true, emailConfirmToken: true, passwordResetToken: true })
|
||||||
.strip()
|
.strip()
|
||||||
.openapi('User')
|
.openapi('User')
|
||||||
|
|
||||||
export const userInsertSchema = createInsertSchema(users, {
|
// export const userInsertSchema = createInsertSchema(users, {
|
||||||
username: (schema) =>
|
// username: (schema) =>
|
||||||
schema.refine((username) => validators.username(username), {
|
// schema.refine((username) => validators.username(username), {
|
||||||
message: 'Invalid username'
|
// message: 'Invalid username'
|
||||||
}),
|
// }),
|
||||||
|
|
||||||
email: (schema) => schema.email().optional()
|
// email: (schema) => schema.email().optional()
|
||||||
})
|
// })
|
||||||
.pick({
|
// .pick({
|
||||||
username: true,
|
// username: true,
|
||||||
email: true,
|
// email: true,
|
||||||
password: true,
|
// password: true,
|
||||||
firstName: true,
|
// firstName: true,
|
||||||
lastName: true,
|
// lastName: true,
|
||||||
image: true
|
// image: true
|
||||||
})
|
// })
|
||||||
.strict()
|
// .strict()
|
||||||
.transform((user) => {
|
// .transform((user) => {
|
||||||
return {
|
// return {
|
||||||
...user,
|
// ...user,
|
||||||
emailConfirmToken: sha256(),
|
// emailConfirmToken: sha256(),
|
||||||
password: user.password ? hashSync(user.password) : undefined
|
// password: user.password ? hashSync(user.password) : undefined
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
|
|
||||||
export const userUpdateSchema = createUpdateSchema(users)
|
// export const userUpdateSchema = createUpdateSchema(users)
|
||||||
.pick({
|
// .pick({
|
||||||
firstName: true,
|
// firstName: true,
|
||||||
lastName: true,
|
// lastName: true,
|
||||||
image: true,
|
// image: true,
|
||||||
password: true,
|
// password: true,
|
||||||
isStripeConnectEnabledByDefault: true
|
// isStripeConnectEnabledByDefault: true
|
||||||
})
|
// })
|
||||||
.strict()
|
// .strict()
|
||||||
.transform((user) => {
|
// .transform((user) => {
|
||||||
return {
|
// return {
|
||||||
...user,
|
// ...user,
|
||||||
password: user.password ? hashSync(user.password) : undefined
|
// password: user.password ? hashSync(user.password) : undefined
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
|
|
|
@ -77,3 +77,5 @@ export type RawConsumerUpdate = Partial<
|
||||||
|
|
||||||
export type LogEntry = z.infer<typeof schema.logEntrySelectSchema>
|
export type LogEntry = z.infer<typeof schema.logEntrySelectSchema>
|
||||||
export type RawLogEntry = InferSelectModel<typeof schema.logEntries>
|
export type RawLogEntry = InferSelectModel<typeof schema.logEntries>
|
||||||
|
|
||||||
|
export type RawSession = InferSelectModel<typeof schema.sessions>
|
||||||
|
|
490
pnpm-lock.yaml
490
pnpm-lock.yaml
Plik diff jest za duży
Load Diff
Ładowanie…
Reference in New Issue