Skip to content

Commit e22337d

Browse files
Pricing schema is implemented
1 parent 58daa52 commit e22337d

File tree

16 files changed

+816
-73
lines changed

16 files changed

+816
-73
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,6 @@ prisma/.env
5252

5353
# OS
5454
Thumbs.db
55+
56+
# clerk configuration (can include secrets)
57+
/.clerk/
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
-- CreateExtension
2+
CREATE EXTENSION IF NOT EXISTS "vector";
3+
4+
-- CreateEnum
5+
CREATE TYPE "public"."PlanType" AS ENUM ('FREE', 'BASIC', 'PREMIUM', 'ENTERPRISE');
6+
7+
-- CreateTable
8+
CREATE TABLE "public"."User" (
9+
"id" TEXT NOT NULL,
10+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
11+
"updatedAt" TIMESTAMP(3) NOT NULL,
12+
"imageUrl" TEXT,
13+
"firstName" TEXT,
14+
"lastName" TEXT,
15+
"emailAddress" TEXT NOT NULL,
16+
"credits" INTEGER NOT NULL DEFAULT 150,
17+
"plan" "public"."PlanType" NOT NULL DEFAULT 'FREE',
18+
"planUpdated" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
19+
20+
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
21+
);
22+
23+
-- CreateTable
24+
CREATE TABLE "public"."Project" (
25+
"id" TEXT NOT NULL,
26+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
27+
"updatedAt" TIMESTAMP(3) NOT NULL,
28+
"name" TEXT NOT NULL,
29+
"repoUrl" TEXT NOT NULL,
30+
"githubToken" TEXT,
31+
"deletedAt" TIMESTAMP(3),
32+
"userId" TEXT NOT NULL,
33+
34+
CONSTRAINT "Project_pkey" PRIMARY KEY ("id")
35+
);
36+
37+
-- CreateTable
38+
CREATE TABLE "public"."SourceCodeEmbiddings" (
39+
"id" TEXT NOT NULL,
40+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
41+
"summaryEmbedding" vector,
42+
"sourceCode" TEXT NOT NULL,
43+
"fileName" TEXT NOT NULL,
44+
"Summary" TEXT NOT NULL,
45+
"projectId" TEXT NOT NULL,
46+
47+
CONSTRAINT "SourceCodeEmbiddings_pkey" PRIMARY KEY ("id")
48+
);
49+
50+
-- CreateTable
51+
CREATE TABLE "public"."Readme" (
52+
"id" TEXT NOT NULL,
53+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
54+
"updatedAt" TIMESTAMP(3) NOT NULL,
55+
"content" TEXT NOT NULL,
56+
"prompt" TEXT NOT NULL,
57+
"projectId" TEXT NOT NULL,
58+
59+
CONSTRAINT "Readme_pkey" PRIMARY KEY ("id")
60+
);
61+
62+
-- CreateTable
63+
CREATE TABLE "public"."ReadmeQna" (
64+
"id" TEXT NOT NULL,
65+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
66+
"question" TEXT NOT NULL,
67+
"answer" TEXT NOT NULL,
68+
"updatedContent" TEXT NOT NULL,
69+
"readmeId" TEXT NOT NULL,
70+
71+
CONSTRAINT "ReadmeQna_pkey" PRIMARY KEY ("id")
72+
);
73+
74+
-- CreateTable
75+
CREATE TABLE "public"."ReadmeShare" (
76+
"id" TEXT NOT NULL,
77+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
78+
"updatedAt" TIMESTAMP(3) NOT NULL,
79+
"shareToken" TEXT NOT NULL,
80+
"isActive" BOOLEAN NOT NULL DEFAULT true,
81+
"readmeId" TEXT NOT NULL,
82+
83+
CONSTRAINT "ReadmeShare_pkey" PRIMARY KEY ("id")
84+
);
85+
86+
-- CreateTable
87+
CREATE TABLE "public"."Docs" (
88+
"id" TEXT NOT NULL,
89+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
90+
"updatedAt" TIMESTAMP(3) NOT NULL,
91+
"content" TEXT NOT NULL,
92+
"prompt" TEXT NOT NULL,
93+
"projectId" TEXT NOT NULL,
94+
95+
CONSTRAINT "Docs_pkey" PRIMARY KEY ("id")
96+
);
97+
98+
-- CreateTable
99+
CREATE TABLE "public"."DocsQna" (
100+
"id" TEXT NOT NULL,
101+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
102+
"question" TEXT NOT NULL,
103+
"answer" TEXT NOT NULL,
104+
"updatedContent" TEXT NOT NULL,
105+
"docsId" TEXT NOT NULL,
106+
107+
CONSTRAINT "DocsQna_pkey" PRIMARY KEY ("id")
108+
);
109+
110+
-- CreateTable
111+
CREATE TABLE "public"."DocsShare" (
112+
"id" TEXT NOT NULL,
113+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
114+
"updatedAt" TIMESTAMP(3) NOT NULL,
115+
"shareToken" TEXT NOT NULL,
116+
"isActive" BOOLEAN NOT NULL DEFAULT true,
117+
"docsId" TEXT NOT NULL,
118+
119+
CONSTRAINT "DocsShare_pkey" PRIMARY KEY ("id")
120+
);
121+
122+
-- CreateTable
123+
CREATE TABLE "public"."UsageQuota" (
124+
"id" TEXT NOT NULL,
125+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
126+
"updatedAt" TIMESTAMP(3) NOT NULL,
127+
"periodStart" TIMESTAMP(3) NOT NULL,
128+
"userId" TEXT NOT NULL,
129+
"readmeCount" INTEGER NOT NULL DEFAULT 0,
130+
"docsCount" INTEGER NOT NULL DEFAULT 0,
131+
"chatCount" INTEGER NOT NULL DEFAULT 0,
132+
133+
CONSTRAINT "UsageQuota_pkey" PRIMARY KEY ("id")
134+
);
135+
136+
-- CreateIndex
137+
CREATE UNIQUE INDEX "User_emailAddress_key" ON "public"."User"("emailAddress");
138+
139+
-- CreateIndex
140+
CREATE INDEX "Project_userId_deletedAt_idx" ON "public"."Project"("userId", "deletedAt");
141+
142+
-- CreateIndex
143+
CREATE INDEX "Project_userId_createdAt_idx" ON "public"."Project"("userId", "createdAt" DESC);
144+
145+
-- CreateIndex
146+
CREATE INDEX "SourceCodeEmbiddings_projectId_idx" ON "public"."SourceCodeEmbiddings"("projectId");
147+
148+
-- CreateIndex
149+
CREATE INDEX "SourceCodeEmbiddings_fileName_idx" ON "public"."SourceCodeEmbiddings"("fileName");
150+
151+
-- CreateIndex
152+
CREATE UNIQUE INDEX "Readme_projectId_key" ON "public"."Readme"("projectId");
153+
154+
-- CreateIndex
155+
CREATE UNIQUE INDEX "ReadmeShare_shareToken_key" ON "public"."ReadmeShare"("shareToken");
156+
157+
-- CreateIndex
158+
CREATE UNIQUE INDEX "ReadmeShare_readmeId_key" ON "public"."ReadmeShare"("readmeId");
159+
160+
-- CreateIndex
161+
CREATE UNIQUE INDEX "Docs_projectId_key" ON "public"."Docs"("projectId");
162+
163+
-- CreateIndex
164+
CREATE UNIQUE INDEX "DocsShare_shareToken_key" ON "public"."DocsShare"("shareToken");
165+
166+
-- CreateIndex
167+
CREATE UNIQUE INDEX "DocsShare_docsId_key" ON "public"."DocsShare"("docsId");
168+
169+
-- CreateIndex
170+
CREATE UNIQUE INDEX "UsageQuota_userId_periodStart_key" ON "public"."UsageQuota"("userId", "periodStart");
171+
172+
-- AddForeignKey
173+
ALTER TABLE "public"."Project" ADD CONSTRAINT "Project_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
174+
175+
-- AddForeignKey
176+
ALTER TABLE "public"."SourceCodeEmbiddings" ADD CONSTRAINT "SourceCodeEmbiddings_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "public"."Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
177+
178+
-- AddForeignKey
179+
ALTER TABLE "public"."Readme" ADD CONSTRAINT "Readme_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "public"."Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
180+
181+
-- AddForeignKey
182+
ALTER TABLE "public"."ReadmeQna" ADD CONSTRAINT "ReadmeQna_readmeId_fkey" FOREIGN KEY ("readmeId") REFERENCES "public"."Readme"("id") ON DELETE CASCADE ON UPDATE CASCADE;
183+
184+
-- AddForeignKey
185+
ALTER TABLE "public"."ReadmeShare" ADD CONSTRAINT "ReadmeShare_readmeId_fkey" FOREIGN KEY ("readmeId") REFERENCES "public"."Readme"("id") ON DELETE CASCADE ON UPDATE CASCADE;
186+
187+
-- AddForeignKey
188+
ALTER TABLE "public"."Docs" ADD CONSTRAINT "Docs_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "public"."Project"("id") ON DELETE CASCADE ON UPDATE CASCADE;
189+
190+
-- AddForeignKey
191+
ALTER TABLE "public"."DocsQna" ADD CONSTRAINT "DocsQna_docsId_fkey" FOREIGN KEY ("docsId") REFERENCES "public"."Docs"("id") ON DELETE CASCADE ON UPDATE CASCADE;
192+
193+
-- AddForeignKey
194+
ALTER TABLE "public"."DocsShare" ADD CONSTRAINT "DocsShare_docsId_fkey" FOREIGN KEY ("docsId") REFERENCES "public"."Docs"("id") ON DELETE CASCADE ON UPDATE CASCADE;
195+
196+
-- AddForeignKey
197+
ALTER TABLE "public"."UsageQuota" ADD CONSTRAINT "UsageQuota_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Please do not edit this file manually
2+
# It should be added in your version-control system (e.g., Git)
3+
provider = "postgresql"

prisma/schema.prisma

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,26 @@ datasource db {
99
extensions = [vector]
1010
}
1111

12+
enum PlanType {
13+
FREE
14+
BASIC
15+
PREMIUM
16+
ENTERPRISE
17+
}
18+
1219
model User {
13-
id String @id @default(uuid())
14-
createdAt DateTime @default(now())
15-
updatedAt DateTime @updatedAt
20+
id String @id @default(uuid())
21+
createdAt DateTime @default(now())
22+
updatedAt DateTime @updatedAt
1623
imageUrl String?
1724
firstName String?
1825
lastName String?
19-
emailAddress String @unique
20-
credits Int @default(150)
26+
emailAddress String @unique
27+
credits Int @default(150)
28+
plan PlanType @default(FREE)
29+
planUpdated DateTime @default(now())
2130
projects Project[]
31+
usage UsageQuota[]
2232
}
2333

2434
model Project {
@@ -116,3 +126,17 @@ model DocsShare {
116126
docsId String @unique
117127
docs Docs @relation(fields: [docsId], references: [id], onDelete: Cascade)
118128
}
129+
130+
model UsageQuota {
131+
id String @id @default(uuid())
132+
createdAt DateTime @default(now())
133+
updatedAt DateTime @updatedAt
134+
periodStart DateTime
135+
userId String
136+
readmeCount Int @default(0)
137+
docsCount Int @default(0)
138+
chatCount Int @default(0)
139+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
140+
141+
@@unique([userId, periodStart])
142+
}

src/app/(auth)/sync-user/page.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { auth, clerkClient } from '@clerk/nextjs/server'
1+
import { auth, currentUser } from '@clerk/nextjs/server'
22
import { notFound, redirect } from 'next/navigation'
33
import prisma from '@/lib/prisma'
44
import React from 'react'
@@ -9,9 +9,10 @@ async function page() {
99
return redirect('/')
1010
}
1111

12-
const client = await clerkClient()
13-
14-
const user = await client.users.getUser(userId)
12+
const user = await currentUser()
13+
if (!user) {
14+
return notFound()
15+
}
1516

1617
if(!user.emailAddresses[0]?.emailAddress) {
1718
return notFound()

src/app/api/query/route.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server';
22
import { auth } from '@clerk/nextjs/server';
33
import prisma from '@/lib/prisma';
44
import { queryCodebase } from '@/lib/rag';
5+
import { ensureQuotaAvailable, incrementUsage } from '@/lib/quota';
56

67
export const runtime = 'nodejs';
78
export const maxDuration = 60; // 60 seconds for complex queries
@@ -73,6 +74,19 @@ export async function POST(request: NextRequest) {
7374
);
7475
}
7576

77+
try {
78+
await ensureQuotaAvailable(userId, 'chat');
79+
} catch (error) {
80+
const message = error instanceof Error ? error.message : 'Chat usage limit reached';
81+
return NextResponse.json(
82+
{
83+
error: 'Usage limit reached',
84+
message,
85+
},
86+
{ status: 403 }
87+
);
88+
}
89+
7690
// Perform RAG query
7791
const result = await queryCodebase(projectId, question, conversationHistory);
7892

@@ -87,6 +101,8 @@ export async function POST(request: NextRequest) {
87101
console.log('Query tracking not available:', dbError);
88102
}
89103

104+
await incrementUsage(userId, 'chat');
105+
90106
return NextResponse.json({
91107
success: true,
92108
answer: result.answer,

src/app/cancellation/page.tsx

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import Link from "next/link"
2+
import type { Metadata } from "next"
3+
import { Button } from "@/components/ui/button"
4+
5+
export const metadata: Metadata = {
6+
title: "Cancellation & Refund Policy | RepoDoc",
7+
description: "Learn how RepoDoc handles cancellations, refunds, and pro-rated charges.",
8+
}
9+
10+
export default function CancellationPolicyPage() {
11+
return (
12+
<main className="black-bg min-h-screen px-4 py-16 text-white sm:px-6 lg:px-8">
13+
<div className="mx-auto flex max-w-3xl flex-col gap-10">
14+
<header className="flex items-center justify-between">
15+
<div>
16+
<h1 className="text-4xl font-semibold tracking-tight sm:text-5xl">Cancellation & Refund Policy</h1>
17+
<p className="mt-4 text-white/70">
18+
RepoDoc is billed monthly. You can cancel anytime from the dashboard billing screen. We believe in keeping
19+
policies transparent and free of surprises.
20+
</p>
21+
</div>
22+
<Button asChild variant="outline" className="border-white/20 text-white hover:bg-white/10">
23+
<Link href="/">Back to Home</Link>
24+
</Button>
25+
</header>
26+
27+
<section className="space-y-8 rounded-3xl border border-white/10 bg-white/[0.03] p-10 shadow-xl backdrop-blur-xl text-white/80">
28+
<div>
29+
<h2 className="text-xl font-semibold text-white">Cancelling</h2>
30+
<p className="mt-3">
31+
You can cancel a subscription at any time. Access remains active until the end of the current billing
32+
cycle, at which point your account automatically downgrades to the free plan.
33+
</p>
34+
</div>
35+
36+
<div>
37+
<h2 className="text-xl font-semibold text-white">Refunds</h2>
38+
<p className="mt-3">
39+
Because RepoDoc provides immediate access to digital infrastructure and AI usage, refunds are not issued
40+
retroactively. If you believe you were charged in error, contact{" "}
41+
<a href="mailto:help@productsolution.net" className="text-white underline underline-offset-4">
42+
help@productsolution.net
43+
</a>{" "}
44+
with the receipt ID and we&rsquo;ll review the charge.
45+
</p>
46+
</div>
47+
48+
<div>
49+
<h2 className="text-xl font-semibold text-white">Custom Agreements</h2>
50+
<p className="mt-3">
51+
Enterprise customers may have bespoke cancellation terms documented in their contract. Those terms take
52+
precedence over the policy outlined here.
53+
</p>
54+
</div>
55+
</section>
56+
</div>
57+
</main>
58+
)
59+
}
60+
61+

0 commit comments

Comments
 (0)