mirror of
https://github.com/lukevella/rallly.git
synced 2025-07-24 19:58:04 +02:00
✨ Add licensing api (#1723)
This commit is contained in:
parent
982fc39ac7
commit
679d6fb034
14 changed files with 607 additions and 40 deletions
|
@ -20,6 +20,8 @@
|
|||
"@auth/prisma-adapter": "^2.7.4",
|
||||
"@aws-sdk/client-s3": "^3.645.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.645.0",
|
||||
"@hono-rate-limiter/redis": "^0.1.4",
|
||||
"@hono/zod-validator": "^0.5.0",
|
||||
"@hookform/resolvers": "^3.3.1",
|
||||
"@next/bundle-analyzer": "^15.3.1",
|
||||
"@next/env": "^15.3.1",
|
||||
|
@ -50,7 +52,7 @@
|
|||
"@upstash/qstash": "^2.7.17",
|
||||
"@upstash/ratelimit": "^1.2.1",
|
||||
"@vercel/functions": "^2.0.0",
|
||||
"@vercel/kv": "^2.0.0",
|
||||
"@vercel/kv": "^3.0.0",
|
||||
"ai": "^4.1.50",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"calendar-link": "^2.6.0",
|
||||
|
@ -59,6 +61,8 @@
|
|||
"cookie": "^0.7.0",
|
||||
"crypto": "^1.0.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"hono": "^4.7.10",
|
||||
"hono-rate-limiter": "^0.2.1",
|
||||
"i18next": "^24.2.2",
|
||||
"i18next-http-backend": "^3.0.2",
|
||||
"i18next-icu": "^2.3.0",
|
||||
|
@ -92,7 +96,7 @@
|
|||
"superjson": "^2.0.0",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"timezone-soft": "^1.5.1",
|
||||
"zod": "^3.23.8"
|
||||
"zod": "^3.25.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.26.10",
|
||||
|
|
153
apps/web/src/app/api/licensing/v1/[...route]/route.ts
Normal file
153
apps/web/src/app/api/licensing/v1/[...route]/route.ts
Normal file
|
@ -0,0 +1,153 @@
|
|||
import { env } from "@/env";
|
||||
import { generateLicenseKey } from "@/features/licensing/helpers/generate-license-key";
|
||||
import {
|
||||
type CreateLicenseResponse,
|
||||
type ValidateLicenseKeyResponse,
|
||||
createLicenseInputSchema,
|
||||
validateLicenseKeyInputSchema,
|
||||
} from "@/features/licensing/schema";
|
||||
import { isSelfHosted } from "@/utils/constants";
|
||||
import { RedisStore } from "@hono-rate-limiter/redis";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { prisma } from "@rallly/database";
|
||||
import { kv } from "@vercel/kv";
|
||||
import { Hono } from "hono";
|
||||
import { rateLimiter } from "hono-rate-limiter";
|
||||
import { bearerAuth } from "hono/bearer-auth";
|
||||
import { some } from "hono/combine";
|
||||
import { handle } from "hono/vercel";
|
||||
|
||||
const isKvAvailable =
|
||||
process.env.KV_REST_API_URL && process.env.KV_REST_API_TOKEN;
|
||||
|
||||
const app = new Hono().basePath("/api/licensing/v1");
|
||||
|
||||
app.use("*", async (c, next) => {
|
||||
if (isSelfHosted) {
|
||||
return c.json({ error: "Not available in self-hosted instances" }, 404);
|
||||
}
|
||||
return next();
|
||||
});
|
||||
|
||||
if (env.LICENSE_API_AUTH_TOKEN) {
|
||||
app.post(
|
||||
"/licenses",
|
||||
zValidator("json", createLicenseInputSchema),
|
||||
some(
|
||||
bearerAuth({ token: env.LICENSE_API_AUTH_TOKEN }),
|
||||
rateLimiter({
|
||||
windowMs: 60 * 60 * 1000,
|
||||
limit: 10,
|
||||
store: isKvAvailable
|
||||
? new RedisStore({
|
||||
client: kv,
|
||||
})
|
||||
: undefined,
|
||||
}),
|
||||
),
|
||||
async (c) => {
|
||||
const {
|
||||
type,
|
||||
seats,
|
||||
expiresAt,
|
||||
licenseeEmail,
|
||||
licenseeName,
|
||||
version,
|
||||
stripeCustomerId,
|
||||
} = c.req.valid("json");
|
||||
|
||||
try {
|
||||
const license = await prisma.license.create({
|
||||
data: {
|
||||
licenseKey: generateLicenseKey({ version }),
|
||||
version,
|
||||
type,
|
||||
seats,
|
||||
issuedAt: new Date(),
|
||||
expiresAt,
|
||||
licenseeEmail,
|
||||
licenseeName,
|
||||
stripeCustomerId,
|
||||
},
|
||||
});
|
||||
return c.json({
|
||||
data: { key: license.licenseKey },
|
||||
} satisfies CreateLicenseResponse);
|
||||
} catch (error) {
|
||||
console.error("Failed to create license:", error);
|
||||
return c.json({ error: "Failed to create license" }, 500);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
app.post(
|
||||
"/licenses/actions/validate-key",
|
||||
zValidator("json", validateLicenseKeyInputSchema),
|
||||
rateLimiter({
|
||||
keyGenerator: async (c) => {
|
||||
const { key, fingerprint } = await c.req.json();
|
||||
return `validate-key:${key}:${fingerprint}`;
|
||||
},
|
||||
windowMs: 60 * 60 * 1000,
|
||||
limit: 10,
|
||||
store: isKvAvailable
|
||||
? new RedisStore({
|
||||
client: kv,
|
||||
})
|
||||
: undefined,
|
||||
}),
|
||||
async (c) => {
|
||||
const { key, fingerprint } = c.req.valid("json");
|
||||
|
||||
const license = await prisma.license.findUnique({
|
||||
where: {
|
||||
licenseKey: key,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
licenseKey: true,
|
||||
status: true,
|
||||
issuedAt: true,
|
||||
expiresAt: true,
|
||||
licenseeEmail: true,
|
||||
licenseeName: true,
|
||||
seats: true,
|
||||
type: true,
|
||||
version: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!license) {
|
||||
return c.json({ error: "License not found" }, 404);
|
||||
}
|
||||
|
||||
await prisma.licenseValidation.create({
|
||||
data: {
|
||||
licenseId: license.id,
|
||||
ipAddress: c.req.header("x-forwarded-for"),
|
||||
fingerprint,
|
||||
validatedAt: new Date(),
|
||||
userAgent: c.req.header("user-agent"),
|
||||
},
|
||||
});
|
||||
|
||||
return c.json({
|
||||
data: {
|
||||
key: license.licenseKey,
|
||||
valid: license.status === "ACTIVE",
|
||||
status: license.status,
|
||||
issuedAt: license.issuedAt,
|
||||
expiresAt: license.expiresAt,
|
||||
licenseeEmail: license.licenseeEmail,
|
||||
licenseeName: license.licenseeName,
|
||||
seats: license.seats,
|
||||
type: license.type,
|
||||
version: license.version,
|
||||
},
|
||||
} satisfies ValidateLicenseKeyResponse);
|
||||
},
|
||||
);
|
||||
|
||||
export const GET = handle(app);
|
||||
export const POST = handle(app);
|
|
@ -74,6 +74,11 @@ export const env = createEnv({
|
|||
* @default "false"
|
||||
*/
|
||||
MODERATION_ENABLED: z.enum(["true", "false"]).default("false"),
|
||||
/**
|
||||
* Licensing API Configuration
|
||||
*/
|
||||
LICENSE_API_URL: z.string().optional(),
|
||||
LICENSE_API_AUTH_TOKEN: z.string().optional(),
|
||||
},
|
||||
/*
|
||||
* Environment variables available on the client (and server).
|
||||
|
@ -125,6 +130,8 @@ export const env = createEnv({
|
|||
NOREPLY_EMAIL_NAME: process.env.NOREPLY_EMAIL_NAME,
|
||||
OPENAI_API_KEY: process.env.OPENAI_API_KEY,
|
||||
MODERATION_ENABLED: process.env.MODERATION_ENABLED,
|
||||
LICENSE_API_URL: process.env.LICENSE_API_URL,
|
||||
LICENSE_API_AUTH_TOKEN: process.env.LICENSE_API_AUTH_TOKEN,
|
||||
},
|
||||
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
|
||||
});
|
||||
|
|
43
apps/web/src/features/licensing/actions/validate-license.ts
Normal file
43
apps/web/src/features/licensing/actions/validate-license.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
"use server";
|
||||
|
||||
import { rateLimit } from "@/features/rate-limit";
|
||||
import { prisma } from "@rallly/database";
|
||||
import { licensingClient } from "../client";
|
||||
|
||||
export async function validateLicenseKey(key: string) {
|
||||
const { success } = await rateLimit("validate_license_key", 10, "1 m");
|
||||
|
||||
if (!success) {
|
||||
throw new Error("Rate limit exceeded");
|
||||
}
|
||||
|
||||
const { data, error } = await licensingClient.validateLicenseKey({
|
||||
key,
|
||||
});
|
||||
|
||||
if (error) {
|
||||
throw new Error(`License validation failed: ${error}`);
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
return {
|
||||
valid: false,
|
||||
};
|
||||
}
|
||||
|
||||
await prisma.instanceLicense.create({
|
||||
data: {
|
||||
licenseKey: data.key,
|
||||
licenseeName: data.licenseeName,
|
||||
licenseeEmail: data.licenseeEmail,
|
||||
issuedAt: data.issuedAt,
|
||||
expiresAt: data.expiresAt,
|
||||
seats: data.seats,
|
||||
type: data.type,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
valid: true,
|
||||
};
|
||||
}
|
7
apps/web/src/features/licensing/client.ts
Normal file
7
apps/web/src/features/licensing/client.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import { env } from "@/env";
|
||||
import { LicensingClient } from "./lib/licensing-client";
|
||||
|
||||
export const licensingClient = new LicensingClient({
|
||||
apiUrl: env.LICENSE_API_URL,
|
||||
authToken: env.LICENSE_API_AUTH_TOKEN,
|
||||
});
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Calculate a checksum for a string
|
||||
* @param str The string to calculate the checksum for
|
||||
* @returns The checksum
|
||||
*/
|
||||
export function calculateChecksum(str: string): string {
|
||||
// Simple checksum: sum char codes, mod 100000, base36
|
||||
let sum = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
sum += str.charCodeAt(i);
|
||||
}
|
||||
return (sum % 100000).toString(36).toUpperCase().padStart(5, "0");
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { calculateChecksum } from "./calculate-checksum";
|
||||
|
||||
export function checkLicenseKey(key: string): boolean {
|
||||
const parts = key.split("-");
|
||||
if (parts.length !== 6) return false;
|
||||
const checksum = parts[5];
|
||||
const licenseBody = parts.slice(0, 5).join("-");
|
||||
return calculateChecksum(licenseBody) === checksum;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { customAlphabet } from "nanoid";
|
||||
import { calculateChecksum } from "./calculate-checksum";
|
||||
|
||||
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
const generate = customAlphabet(alphabet, 4);
|
||||
|
||||
/**
|
||||
* Generate user friendly licenses
|
||||
* eg. RLYV4-ABCD-1234-ABCD-1234-XXXX
|
||||
*/
|
||||
export const generateLicenseKey = ({ version }: { version?: number }) => {
|
||||
let license = `RLYV${version ?? "X"}-`;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
license += generate();
|
||||
if (i < 3) {
|
||||
license += "-";
|
||||
}
|
||||
}
|
||||
const checksum = calculateChecksum(license);
|
||||
return `${license}-${checksum}`;
|
||||
};
|
1
apps/web/src/features/licensing/index.ts
Normal file
1
apps/web/src/features/licensing/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export { LicensingClient } from "./lib/licensing-client";
|
59
apps/web/src/features/licensing/lib/licensing-client.ts
Normal file
59
apps/web/src/features/licensing/lib/licensing-client.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import {
|
||||
type CreateLicenseInput,
|
||||
type ValidateLicenseInputKeySchema,
|
||||
createLicenseResponseSchema,
|
||||
validateLicenseKeyResponseSchema,
|
||||
} from "../schema";
|
||||
|
||||
export class LicensingClient {
|
||||
apiUrl: string;
|
||||
authToken?: string;
|
||||
|
||||
constructor({
|
||||
apiUrl = "https://licensing.rallly.co",
|
||||
authToken,
|
||||
}: {
|
||||
apiUrl?: string;
|
||||
authToken?: string;
|
||||
}) {
|
||||
this.apiUrl = apiUrl;
|
||||
this.authToken = authToken;
|
||||
}
|
||||
async createLicense(input: CreateLicenseInput) {
|
||||
if (!this.authToken) {
|
||||
throw new Error("Licensing API auth token is not configured.");
|
||||
}
|
||||
|
||||
const res = await fetch(`${this.apiUrl}/api/v1/licenses`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${this.authToken}`,
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to create license.");
|
||||
}
|
||||
return createLicenseResponseSchema.parse(await res.json());
|
||||
}
|
||||
async validateLicenseKey(input: ValidateLicenseInputKeySchema) {
|
||||
const res = await fetch(
|
||||
`${this.apiUrl}/api/v1/licenses/actions/validate-key`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(input),
|
||||
},
|
||||
);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error("Failed to validate license key.");
|
||||
}
|
||||
|
||||
return validateLicenseKeyResponseSchema.parse(await res.json());
|
||||
}
|
||||
}
|
88
apps/web/src/features/licensing/schema.ts
Normal file
88
apps/web/src/features/licensing/schema.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { z } from "zod";
|
||||
|
||||
// =========================
|
||||
// Enums & Basic Types
|
||||
// =========================
|
||||
|
||||
export const licenseTypeSchema = z.enum(["PLUS", "ORGANIZATION", "ENTERPRISE"]);
|
||||
export type LicenseType = z.infer<typeof licenseTypeSchema>;
|
||||
|
||||
export const licenseStatusSchema = z.enum(["ACTIVE", "REVOKED"]);
|
||||
export type LicenseStatus = z.infer<typeof licenseStatusSchema>;
|
||||
|
||||
// =========================
|
||||
// Generic API Response
|
||||
// =========================
|
||||
|
||||
export const apiResponseSchema = <T extends z.ZodTypeAny>(dataSchema: T) =>
|
||||
z.object({
|
||||
data: dataSchema.optional(),
|
||||
error: z.string().optional(),
|
||||
});
|
||||
|
||||
export type ApiResponse<T> = {
|
||||
data?: T;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
// =========================
|
||||
// Create License
|
||||
// =========================
|
||||
|
||||
export const createLicenseInputSchema = z.object({
|
||||
type: licenseTypeSchema,
|
||||
seats: z.number().optional(),
|
||||
expiresAt: z.date().optional(),
|
||||
licenseeEmail: z.string().optional(),
|
||||
licenseeName: z.string().optional(),
|
||||
version: z.number().optional(),
|
||||
stripeCustomerId: z.string().optional(),
|
||||
});
|
||||
export type CreateLicenseInput = z.infer<typeof createLicenseInputSchema>;
|
||||
|
||||
export const createLicenseResponseSchema = apiResponseSchema(
|
||||
z.object({
|
||||
key: z.string(),
|
||||
}),
|
||||
);
|
||||
export type CreateLicenseResponse = z.infer<typeof createLicenseResponseSchema>;
|
||||
|
||||
// =========================
|
||||
// Validate License Key
|
||||
// =========================
|
||||
|
||||
export const validateLicenseKeyInputSchema = z.object({
|
||||
key: z.string(),
|
||||
fingerprint: z.string().optional(),
|
||||
});
|
||||
export type ValidateLicenseInputKeySchema = z.infer<
|
||||
typeof validateLicenseKeyInputSchema
|
||||
>;
|
||||
|
||||
export const validateLicenseKeyResponseSchema = apiResponseSchema(
|
||||
z.object({
|
||||
key: z.string(),
|
||||
valid: z.boolean(),
|
||||
status: licenseStatusSchema,
|
||||
issuedAt: z.date(),
|
||||
expiresAt: z.date().nullable(),
|
||||
licenseeEmail: z.string().nullable(),
|
||||
licenseeName: z.string().nullable(),
|
||||
seats: z.number().nullable(),
|
||||
type: licenseTypeSchema,
|
||||
version: z.number().nullable(),
|
||||
}),
|
||||
);
|
||||
|
||||
export type ValidateLicenseKeyResponse = z.infer<
|
||||
typeof validateLicenseKeyResponseSchema
|
||||
>;
|
||||
|
||||
export const licenseCheckoutMetadataSchema = z.object({
|
||||
licenseType: licenseTypeSchema,
|
||||
seats: z.number(),
|
||||
});
|
||||
|
||||
export type LicenseCheckoutMetada = z.infer<
|
||||
typeof licenseCheckoutMetadataSchema
|
||||
>;
|
|
@ -0,0 +1,59 @@
|
|||
-- CreateEnum
|
||||
CREATE TYPE "LicenseType" AS ENUM ('PLUS', 'ORGANIZATION', 'ENTERPRISE');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LicenseStatus" AS ENUM ('ACTIVE', 'REVOKED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "licenses" (
|
||||
"id" TEXT NOT NULL,
|
||||
"license_key" TEXT NOT NULL,
|
||||
"version" INTEGER,
|
||||
"type" "LicenseType" NOT NULL,
|
||||
"seats" INTEGER,
|
||||
"issued_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expires_at" TIMESTAMP(3),
|
||||
"licensee_email" TEXT,
|
||||
"licensee_name" TEXT,
|
||||
"status" "LicenseStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
"stripe_customer_id" TEXT,
|
||||
|
||||
CONSTRAINT "licenses_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "license_validations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"license_id" TEXT NOT NULL,
|
||||
"ip_address" TEXT,
|
||||
"fingerprint" TEXT,
|
||||
"validated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"user_agent" TEXT,
|
||||
|
||||
CONSTRAINT "license_validations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "instance_licenses" (
|
||||
"id" TEXT NOT NULL,
|
||||
"license_key" TEXT NOT NULL,
|
||||
"version" INTEGER,
|
||||
"type" "LicenseType" NOT NULL,
|
||||
"seats" INTEGER,
|
||||
"issued_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"expires_at" TIMESTAMP(3),
|
||||
"licensee_email" TEXT,
|
||||
"licensee_name" TEXT,
|
||||
"status" "LicenseStatus" NOT NULL DEFAULT 'ACTIVE',
|
||||
|
||||
CONSTRAINT "instance_licenses_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "licenses_license_key_key" ON "licenses"("license_key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "instance_licenses_license_key_key" ON "instance_licenses"("license_key");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "license_validations" ADD CONSTRAINT "license_validations_license_id_fkey" FOREIGN KEY ("license_id") REFERENCES "licenses"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
56
packages/database/prisma/models/licensing.prisma
Normal file
56
packages/database/prisma/models/licensing.prisma
Normal file
|
@ -0,0 +1,56 @@
|
|||
enum LicenseType {
|
||||
PLUS
|
||||
ORGANIZATION
|
||||
ENTERPRISE
|
||||
}
|
||||
|
||||
enum LicenseStatus {
|
||||
ACTIVE
|
||||
REVOKED
|
||||
}
|
||||
|
||||
model License {
|
||||
id String @id @default(cuid())
|
||||
licenseKey String @unique @map("license_key")
|
||||
version Int? @map("version")
|
||||
type LicenseType
|
||||
seats Int? @map("seats")
|
||||
issuedAt DateTime @default(now()) @map("issued_at")
|
||||
expiresAt DateTime? @map("expires_at")
|
||||
licenseeEmail String? @map("licensee_email")
|
||||
licenseeName String? @map("licensee_name")
|
||||
status LicenseStatus @default(ACTIVE) @map("status")
|
||||
stripeCustomerId String? @map("stripe_customer_id")
|
||||
|
||||
validations LicenseValidation[]
|
||||
|
||||
@@map("licenses")
|
||||
}
|
||||
|
||||
model LicenseValidation {
|
||||
id String @id @default(cuid())
|
||||
licenseId String @map("license_id")
|
||||
license License @relation(fields: [licenseId], references: [id], onDelete: Cascade)
|
||||
ipAddress String? @map("ip_address")
|
||||
fingerprint String? @map("fingerprint")
|
||||
validatedAt DateTime @default(now()) @map("validated_at")
|
||||
userAgent String? @map("user_agent")
|
||||
|
||||
@@map("license_validations")
|
||||
}
|
||||
|
||||
|
||||
model InstanceLicense {
|
||||
id String @id @default(cuid())
|
||||
licenseKey String @unique @map("license_key")
|
||||
version Int? @map("version")
|
||||
type LicenseType
|
||||
seats Int? @map("seats")
|
||||
issuedAt DateTime @default(now()) @map("issued_at")
|
||||
expiresAt DateTime? @map("expires_at")
|
||||
licenseeEmail String? @map("licensee_email")
|
||||
licenseeName String? @map("licensee_name")
|
||||
status LicenseStatus @default(ACTIVE) @map("status")
|
||||
|
||||
@@map("instance_licenses")
|
||||
}
|
123
pnpm-lock.yaml
generated
123
pnpm-lock.yaml
generated
|
@ -152,7 +152,7 @@ importers:
|
|||
dependencies:
|
||||
'@ai-sdk/openai':
|
||||
specifier: ^1.2.0
|
||||
version: 1.3.20(zod@3.24.3)
|
||||
version: 1.3.20(zod@3.25.20)
|
||||
'@auth/prisma-adapter':
|
||||
specifier: ^2.7.4
|
||||
version: 2.9.0(@prisma/client@6.8.2(prisma@6.8.2(typescript@5.8.3))(typescript@5.8.3))(nodemailer@6.10.1)
|
||||
|
@ -162,6 +162,12 @@ importers:
|
|||
'@aws-sdk/s3-request-presigner':
|
||||
specifier: ^3.645.0
|
||||
version: 3.797.0
|
||||
'@hono-rate-limiter/redis':
|
||||
specifier: ^0.1.4
|
||||
version: 0.1.4(hono-rate-limiter@0.2.3(hono@4.7.10))
|
||||
'@hono/zod-validator':
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0(hono@4.7.10)(zod@3.25.20)
|
||||
'@hookform/resolvers':
|
||||
specifier: ^3.3.1
|
||||
version: 3.10.0(react-hook-form@7.56.1(react@19.1.0))
|
||||
|
@ -221,7 +227,7 @@ importers:
|
|||
version: 8.1.0(typescript@5.8.3)
|
||||
'@t3-oss/env-nextjs':
|
||||
specifier: ^0.11.0
|
||||
version: 0.11.1(typescript@5.8.3)(zod@3.24.3)
|
||||
version: 0.11.1(typescript@5.8.3)(zod@3.25.20)
|
||||
'@tanstack/react-query':
|
||||
specifier: ^5.74.11
|
||||
version: 5.74.11(react@19.1.0)
|
||||
|
@ -253,11 +259,11 @@ importers:
|
|||
specifier: ^2.0.0
|
||||
version: 2.0.0(@aws-sdk/credential-provider-web-identity@3.797.0)
|
||||
'@vercel/kv':
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
specifier: ^3.0.0
|
||||
version: 3.0.0
|
||||
ai:
|
||||
specifier: ^4.1.50
|
||||
version: 4.3.10(react@19.1.0)(zod@3.24.3)
|
||||
version: 4.3.10(react@19.1.0)(zod@3.25.20)
|
||||
autoprefixer:
|
||||
specifier: ^10.4.13
|
||||
version: 10.4.21(postcss@8.5.3)
|
||||
|
@ -279,6 +285,12 @@ importers:
|
|||
dayjs:
|
||||
specifier: ^1.11.13
|
||||
version: 1.11.13
|
||||
hono:
|
||||
specifier: ^4.7.10
|
||||
version: 4.7.10
|
||||
hono-rate-limiter:
|
||||
specifier: ^0.2.1
|
||||
version: 0.2.3(hono@4.7.10)
|
||||
i18next:
|
||||
specifier: ^24.2.2
|
||||
version: 24.2.3(typescript@5.8.3)
|
||||
|
@ -379,8 +391,8 @@ importers:
|
|||
specifier: ^1.5.1
|
||||
version: 1.5.2
|
||||
zod:
|
||||
specifier: ^3.23.8
|
||||
version: 3.24.3
|
||||
specifier: ^3.25.6
|
||||
version: 3.25.20
|
||||
devDependencies:
|
||||
'@babel/core':
|
||||
specifier: ^7.26.10
|
||||
|
@ -2026,6 +2038,17 @@ packages:
|
|||
resolution: {integrity: sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==}
|
||||
engines: {node: '>=10.13.0'}
|
||||
|
||||
'@hono-rate-limiter/redis@0.1.4':
|
||||
resolution: {integrity: sha512-RSrVX5N2Oo/xXApskegu667cBVHyr8RXGWnbRDGjU2py8pN4BttEKSHA0iKi3BAwh1xSkENgDRng4tpFD9DbKg==}
|
||||
peerDependencies:
|
||||
hono-rate-limiter: ^0.2.1
|
||||
|
||||
'@hono/zod-validator@0.5.0':
|
||||
resolution: {integrity: sha512-ds5bW6DCgAnNHP33E3ieSbaZFd5dkV52ZjyaXtGoR06APFrCtzAsKZxTHwOrJNBdXsi0e5wNwo5L4nVEVnJUdg==}
|
||||
peerDependencies:
|
||||
hono: '>=3.9.0'
|
||||
zod: ^3.19.1
|
||||
|
||||
'@hookform/resolvers@3.10.0':
|
||||
resolution: {integrity: sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==}
|
||||
peerDependencies:
|
||||
|
@ -4221,8 +4244,8 @@ packages:
|
|||
'@aws-sdk/credential-provider-web-identity':
|
||||
optional: true
|
||||
|
||||
'@vercel/kv@2.0.0':
|
||||
resolution: {integrity: sha512-zdVrhbzZBYo5d1Hfn4bKtqCeKf0FuzW8rSHauzQVMUgv1+1JOwof2mWcBuI+YMJy8s0G0oqAUfQ7HgUDzb8EbA==}
|
||||
'@vercel/kv@3.0.0':
|
||||
resolution: {integrity: sha512-pKT8fRnfyYk2MgvyB6fn6ipJPCdfZwiKDdw7vB+HL50rjboEBHDVBEcnwfkEpVSp2AjNtoaOUH7zG+bVC/rvSg==}
|
||||
engines: {node: '>=14.6'}
|
||||
|
||||
'@vitest/expect@2.1.9':
|
||||
|
@ -5296,6 +5319,15 @@ packages:
|
|||
hoist-non-react-statics@3.3.2:
|
||||
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
|
||||
|
||||
hono-rate-limiter@0.2.3:
|
||||
resolution: {integrity: sha512-/pQIJWMd8saoySMoM3rhHNiC3XtZuZDkxBvtnPmRKrpNBZIG38k0cs+nVmBW5pc8HAmkS+I/14rbHfRObekWmw==}
|
||||
peerDependencies:
|
||||
hono: ^4.1.1
|
||||
|
||||
hono@4.7.10:
|
||||
resolution: {integrity: sha512-QkACju9MiN59CKSY5JsGZCYmPZkA6sIW6OFCUp7qDjZu6S6KHtJHhAc9Uy9mV9F8PJ1/HQ3ybZF2yjCa/73fvQ==}
|
||||
engines: {node: '>=16.9.0'}
|
||||
|
||||
html-escaper@2.0.2:
|
||||
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
|
||||
|
||||
|
@ -7341,47 +7373,47 @@ packages:
|
|||
peerDependencies:
|
||||
zod: ^3.24.1
|
||||
|
||||
zod@3.24.3:
|
||||
resolution: {integrity: sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg==}
|
||||
zod@3.25.20:
|
||||
resolution: {integrity: sha512-z03fqpTMDF1G02VLKUMt6vyACE7rNWkh3gpXVHgPTw28NPtDFRGvcpTtPwn2kMKtQ0idtYJUTxchytmnqYswcw==}
|
||||
|
||||
zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@ai-sdk/openai@1.3.20(zod@3.24.3)':
|
||||
'@ai-sdk/openai@1.3.20(zod@3.25.20)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.3
|
||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
||||
zod: 3.24.3
|
||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.25.20)
|
||||
zod: 3.25.20
|
||||
|
||||
'@ai-sdk/provider-utils@2.2.7(zod@3.24.3)':
|
||||
'@ai-sdk/provider-utils@2.2.7(zod@3.25.20)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.3
|
||||
nanoid: 3.3.11
|
||||
secure-json-parse: 2.7.0
|
||||
zod: 3.24.3
|
||||
zod: 3.25.20
|
||||
|
||||
'@ai-sdk/provider@1.1.3':
|
||||
dependencies:
|
||||
json-schema: 0.4.0
|
||||
|
||||
'@ai-sdk/react@1.2.9(react@19.1.0)(zod@3.24.3)':
|
||||
'@ai-sdk/react@1.2.9(react@19.1.0)(zod@3.25.20)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
||||
'@ai-sdk/ui-utils': 1.2.8(zod@3.24.3)
|
||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.25.20)
|
||||
'@ai-sdk/ui-utils': 1.2.8(zod@3.25.20)
|
||||
react: 19.1.0
|
||||
swr: 2.3.3(react@19.1.0)
|
||||
throttleit: 2.1.0
|
||||
optionalDependencies:
|
||||
zod: 3.24.3
|
||||
zod: 3.25.20
|
||||
|
||||
'@ai-sdk/ui-utils@1.2.8(zod@3.24.3)':
|
||||
'@ai-sdk/ui-utils@1.2.8(zod@3.25.20)':
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.3
|
||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
||||
zod: 3.24.3
|
||||
zod-to-json-schema: 3.24.5(zod@3.24.3)
|
||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.25.20)
|
||||
zod: 3.25.20
|
||||
zod-to-json-schema: 3.24.5(zod@3.25.20)
|
||||
|
||||
'@alloc/quick-lru@5.2.0': {}
|
||||
|
||||
|
@ -8973,6 +9005,15 @@ snapshots:
|
|||
dependencies:
|
||||
is-negated-glob: 1.0.0
|
||||
|
||||
'@hono-rate-limiter/redis@0.1.4(hono-rate-limiter@0.2.3(hono@4.7.10))':
|
||||
dependencies:
|
||||
hono-rate-limiter: 0.2.3(hono@4.7.10)
|
||||
|
||||
'@hono/zod-validator@0.5.0(hono@4.7.10)(zod@3.25.20)':
|
||||
dependencies:
|
||||
hono: 4.7.10
|
||||
zod: 3.25.20
|
||||
|
||||
'@hookform/resolvers@3.10.0(react-hook-form@7.56.1(react@19.1.0))':
|
||||
dependencies:
|
||||
react-hook-form: 7.56.1(react@19.1.0)
|
||||
|
@ -10977,16 +11018,16 @@ snapshots:
|
|||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@t3-oss/env-core@0.11.1(typescript@5.8.3)(zod@3.24.3)':
|
||||
'@t3-oss/env-core@0.11.1(typescript@5.8.3)(zod@3.25.20)':
|
||||
dependencies:
|
||||
zod: 3.24.3
|
||||
zod: 3.25.20
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
'@t3-oss/env-nextjs@0.11.1(typescript@5.8.3)(zod@3.24.3)':
|
||||
'@t3-oss/env-nextjs@0.11.1(typescript@5.8.3)(zod@3.25.20)':
|
||||
dependencies:
|
||||
'@t3-oss/env-core': 0.11.1(typescript@5.8.3)(zod@3.24.3)
|
||||
zod: 3.24.3
|
||||
'@t3-oss/env-core': 0.11.1(typescript@5.8.3)(zod@3.25.20)
|
||||
zod: 3.25.20
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
|
@ -11270,7 +11311,7 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@aws-sdk/credential-provider-web-identity': 3.797.0
|
||||
|
||||
'@vercel/kv@2.0.0':
|
||||
'@vercel/kv@3.0.0':
|
||||
dependencies:
|
||||
'@upstash/redis': 1.34.8
|
||||
|
||||
|
@ -11447,15 +11488,15 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
ai@4.3.10(react@19.1.0)(zod@3.24.3):
|
||||
ai@4.3.10(react@19.1.0)(zod@3.25.20):
|
||||
dependencies:
|
||||
'@ai-sdk/provider': 1.1.3
|
||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.24.3)
|
||||
'@ai-sdk/react': 1.2.9(react@19.1.0)(zod@3.24.3)
|
||||
'@ai-sdk/ui-utils': 1.2.8(zod@3.24.3)
|
||||
'@ai-sdk/provider-utils': 2.2.7(zod@3.25.20)
|
||||
'@ai-sdk/react': 1.2.9(react@19.1.0)(zod@3.25.20)
|
||||
'@ai-sdk/ui-utils': 1.2.8(zod@3.25.20)
|
||||
'@opentelemetry/api': 1.9.0
|
||||
jsondiffpatch: 0.6.0
|
||||
zod: 3.24.3
|
||||
zod: 3.25.20
|
||||
optionalDependencies:
|
||||
react: 19.1.0
|
||||
|
||||
|
@ -12469,6 +12510,12 @@ snapshots:
|
|||
dependencies:
|
||||
react-is: 16.13.1
|
||||
|
||||
hono-rate-limiter@0.2.3(hono@4.7.10):
|
||||
dependencies:
|
||||
hono: 4.7.10
|
||||
|
||||
hono@4.7.10: {}
|
||||
|
||||
html-escaper@2.0.2: {}
|
||||
|
||||
html-parse-stringify@3.0.1:
|
||||
|
@ -14858,10 +14905,10 @@ snapshots:
|
|||
toposort: 2.0.2
|
||||
type-fest: 2.19.0
|
||||
|
||||
zod-to-json-schema@3.24.5(zod@3.24.3):
|
||||
zod-to-json-schema@3.24.5(zod@3.25.20):
|
||||
dependencies:
|
||||
zod: 3.24.3
|
||||
zod: 3.25.20
|
||||
|
||||
zod@3.24.3: {}
|
||||
zod@3.25.20: {}
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue