Skip to content

Commit 27e2d4e

Browse files
authored
Merge pull request #555 from captableinc/feat/queue
feat/queue - reinventing the wheel, with our own queue system - WIP
2 parents cd81f52 + 92f3a0e commit 27e2d4e

File tree

44 files changed

+12048
-323
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+12048
-323
lines changed

.cursor/rules/packages/email.mdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ await sendEmail({
189189
template: <WelcomeEmail
190190
userName="John Doe"
191191
companyName="Acme Corp"
192-
loginUrl="https://app.captable.com/login"
192+
loginUrl="https://cloud.captable.inc/login"
193193
/>
194194
})
195195
```

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,11 @@
5050
5151
👷 **Incorporation** (wip) - Captable, Inc. helps you incorporate your company in minutes, with all the necessary legal documents and filings taken care of.
5252

53-
👷 **Cap table management** (wip) - Captable, Inc. helps you keep track of your companys ownership structure, including who owns what percentage of the company, how much stock/options has been issued, and more.
53+
👷 **Cap table management** (wip) - Captable, Inc. helps you keep track of your company's ownership structure, including who owns what percentage of the company, how much stock/options has been issued, and more.
5454

5555
**Fundraise** - Captable, Inc. can help you raise capital, whether its signing standard or custom SAFE or creating and managing fundraising rounds, tracking investor commitments, and more.
5656

57-
**Investor updates** - Delight your investors and team members by sending them regular updates on your companys progress.
57+
**Investor updates** - Delight your investors and team members by sending them regular updates on your company's progress.
5858

5959
**eSign Documents** - Sign SAFE, NDA, contracts, offere letters or any type of documents with Captable Sign.
6060

@@ -78,7 +78,9 @@ We have a community of developers, designers, and entrepreneurs who are passiona
7878

7979
- [Next.js](https://nextjs.org)
8080
- [Tailwind](https://tailwindcss.com)
81-
- [Prisma ORM](https://prisma.io)
81+
- [Drizzle ORM](https://orm.drizzle.team)
82+
- [tRPC](https://trpc.io)
83+
- [NextAuth.js](https://next-auth.js.org)
8284

8385
---
8486

@@ -106,7 +108,7 @@ When contributing to <strong>Captable, Inc.</strong>, whether on GitHub or in ot
106108

107109
- <a href="https://docs.docker.com/get-docker/" target="_blank">Install Docker</a> & <a href="https://docs.docker.com/compose/install/" target="_blank">Docker Compose</a>
108110
- <a href="https://github.com/captableinc/captable/fork" target="_blank">Fork</a> & clone the forked repository
109-
- <a href="https://pnpm.io/installation" target="_blank">Install node and pnpm</a>. (optional)
111+
- <a href="https://bun.sh/docs/installation" target="_blank">Install node and bun</a>. (optional)
110112
- Copy `.env.example` to `.env`
111113

112114
```bash

apps/captable/README.md

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,34 @@ We have a community of developers, designers, and entrepreneurs who are passiona
7878

7979
- [Next.js](https://nextjs.org)
8080
- [Tailwind](https://tailwindcss.com)
81-
- [Prisma ORM](https://prisma.io)
81+
- [Drizzle ORM](https://orm.drizzle.team)
8282

8383
---
84+
<h3 id="background-jobs">Background Jobs</h3>
85+
86+
Captable uses a custom job queue system for handling background tasks like:
87+
- 📧 Email notifications (welcome, password reset, invites)
88+
- 📄 PDF generation (e-signatures, documents)
89+
- 🔄 Data processing and synchronization
90+
91+
**Development Setup:**
92+
```bash
93+
# Start with job processing (recommended)
94+
bun dx
95+
96+
# Or start jobs separately
97+
bun run jobs:dev
98+
```
99+
100+
**Job Management:**
101+
```bash
102+
bun run jobs # Process pending jobs
103+
bun run test-jobs # Queue sample test jobs
104+
bun run jobs stats # View queue statistics
105+
```
106+
107+
Jobs are automatically processed in production via Cron jobs.
108+
84109

85110
<h3 id="start">Getting started</h3>
86111
When contributing to <strong>Captable, Inc.</strong>, whether on GitHub or in other community spaces:
@@ -106,7 +131,7 @@ When contributing to <strong>Captable, Inc.</strong>, whether on GitHub or in ot
106131

107132
- <a href="https://docs.docker.com/get-docker/" target="_blank">Install Docker</a> & <a href="https://docs.docker.com/compose/install/" target="_blank">Docker Compose</a>
108133
- <a href="https://github.com/captableinc/captable/fork" target="_blank">Fork</a> & clone the forked repository
109-
- <a href="https://pnpm.io/installation" target="_blank">Install node and pnpm</a>. (optional)
134+
- <a href="https://bun.sh/docs/installation" target="_blank">Install node and bun</a>. (optional)
110135
- Copy `.env.example` to `.env`
111136

112137
```bash
@@ -117,10 +142,10 @@ When contributing to <strong>Captable, Inc.</strong>, whether on GitHub or in ot
117142

118143
```bash
119144

120-
# With pnpm installed
121-
pnpm dx
145+
# With bun installed
146+
bun dx
122147

123-
# Without pnpm installed
148+
# Without bun installed
124149
docker compose up
125150

126151
```
@@ -129,8 +154,8 @@ When contributing to <strong>Captable, Inc.</strong>, whether on GitHub or in ot
129154

130155
```bash
131156

132-
docker compose exec app pnpm db:migrate
133-
docker compose exec app pnpm db:seed
157+
docker compose exec app bun db:migrate
158+
docker compose exec app bun db:seed
134159

135160
```
136161

@@ -143,15 +168,15 @@ When contributing to <strong>Captable, Inc.</strong>, whether on GitHub or in ot
143168
- Emails will be intercepted: [http://localhost:8025](http://localhost:8025)
144169
- SMTP will be on PORT `http://localhost:1025`
145170
- Postgres will be on PORT `http://localhost:5432`
146-
- Prisma studio will be on PORT `http://localhost:5555`
171+
- Database studio will be on PORT `http://localhost:5555`
147172

148173
- Frequently used commands
149174
- `docker compose up` - Start the development environment
150175
- `docker compose down` - Stop the development environment
151176
- `docker compose logs -f` - View logs of the running services
152177
- `docker compose up --build` - Rebuild the docker image
153-
- `docker compose run app pnpm db:migrate` - Run database migrations
154-
- `docker compose run app pnpm db:seed` - Seed the database
178+
- `docker compose run app bun db:migrate` - Run database migrations
179+
- `docker compose run app bun db:seed` - Seed the database
155180

156181
---
157182

@@ -218,23 +243,23 @@ When contributing to <strong>Captable, Inc.</strong>, whether on GitHub or in ot
218243
- Run the following command to install dependencies
219244

220245
```bash
221-
pnpm install
246+
bun install
222247
```
223248

224249
- Run the following command to migrate and seed the database
225250

226251
```bash
227-
pnpm db:migrate
228-
pnpm db:seed
252+
bun db:migrate
253+
bun db:seed
229254
```
230255

231256
- Run the following command to start the development server
232257

233258
```bash
234-
pnpm dev
259+
bun dev
235260

236261
# On a different terminal, run the following command to start the mail server
237-
pnpm email:dev
262+
bun email:dev
238263
```
239264

240265
- App will be running on [http://localhost:3000](http://localhost:3000)
@@ -243,10 +268,10 @@ When contributing to <strong>Captable, Inc.</strong>, whether on GitHub or in ot
243268
- Postgres will be on PORT `http://localhost:5432`
244269

245270
- Frequently used commands
246-
- `pnpm dev` - Start the development server
247-
- `pnpm email:dev` - Start the mail server
248-
- `pnpm db:migrate` - Run database migrations
249-
- `pnpm db:seed` - Seed the database
271+
- `bun dev` - Start the development server
272+
- `bun email:dev` - Start the mail server
273+
- `bun db:migrate` - Run database migrations
274+
- `bun db:seed` - Seed the database
250275

251276
<h4 id="changes">Implement your changes</h4>
252277

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { logger } from "@captable/logger";
2+
import { cleanupJobs } from "@captable/queue";
3+
import { type NextRequest, NextResponse } from "next/server";
4+
5+
const log = logger.child({ module: "cron-cleanup" });
6+
7+
export async function GET(request: NextRequest) {
8+
// Verify cron secret
9+
const authHeader = request.headers.get("authorization");
10+
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
11+
return new Response("Unauthorized", { status: 401 });
12+
}
13+
14+
try {
15+
const cleaned = await cleanupJobs(7); // Clean jobs older than 7 days
16+
17+
log.info({ cleaned }, "Job cleanup completed");
18+
19+
return NextResponse.json({
20+
success: true,
21+
cleaned,
22+
});
23+
} catch (error) {
24+
log.error({ error }, "Job cleanup failed");
25+
return NextResponse.json(
26+
{ error: "Internal server error" },
27+
{ status: 500 },
28+
);
29+
}
30+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { logger } from "@captable/logger";
2+
import { processJobs } from "@captable/queue";
3+
import { type NextRequest, NextResponse } from "next/server";
4+
import "@/jobs"; // Import to register all jobs
5+
6+
const log = logger.child({ module: "cron-jobs" });
7+
8+
export async function GET(request: NextRequest) {
9+
// Verify cron secret for security
10+
const authHeader = request.headers.get("authorization");
11+
const expectedAuth = `Bearer ${process.env.CRON_SECRET}`;
12+
13+
if (!expectedAuth || authHeader !== expectedAuth) {
14+
log.warn({ authHeader }, "Unauthorized cron job access attempt");
15+
return new Response("Unauthorized", { status: 401 });
16+
}
17+
18+
try {
19+
const startTime = Date.now();
20+
21+
// Process jobs in batches
22+
let totalProcessed = 0;
23+
let batchCount = 0;
24+
const maxBatches = 10; // Prevent infinite loops
25+
26+
while (batchCount < maxBatches) {
27+
const processed = await processJobs(20); // Process 20 jobs per batch
28+
totalProcessed += processed;
29+
batchCount++;
30+
31+
if (processed === 0) {
32+
break; // No more jobs to process
33+
}
34+
35+
// Small delay between batches
36+
await new Promise((resolve) => setTimeout(resolve, 100));
37+
}
38+
39+
const duration = Date.now() - startTime;
40+
41+
log.info(
42+
{
43+
totalProcessed,
44+
batches: batchCount,
45+
duration,
46+
},
47+
"Cron job processing completed",
48+
);
49+
50+
return NextResponse.json({
51+
success: true,
52+
processed: totalProcessed,
53+
batches: batchCount,
54+
duration,
55+
});
56+
} catch (error) {
57+
log.error({ error }, "Cron job processing failed");
58+
return NextResponse.json(
59+
{ error: "Internal server error" },
60+
{ status: 500 },
61+
);
62+
}
63+
}

apps/captable/components/onboarding/forgot-password/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const ForgotPassword = () => {
4040
},
4141
});
4242
const onSubmit = async (values: z.infer<typeof inputSchema>) => {
43-
await mutateAsync(values.email);
43+
await mutateAsync(values);
4444
};
4545

4646
return (

apps/captable/jobs/auth-verification-email.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { env } from "@/env";
2-
import { BaseJob } from "@/jobs/base";
31
import { sendMail } from "@/server/mailer";
4-
import type { Job } from "pg-boss";
2+
import { logger } from "@captable/logger";
3+
import { BaseJob } from "@captable/queue";
4+
5+
const log = logger.child({ module: "auth-verification-email-job" });
56

67
export type AuthVerificationEmailPayloadType = {
78
email: string;
@@ -11,6 +12,8 @@ export type AuthVerificationEmailPayloadType = {
1112
const sendAuthVerificationEmail = async (
1213
payload: AuthVerificationEmailPayloadType,
1314
) => {
15+
log.info({ email: payload.email }, "Sending auth verification email");
16+
1417
// Dynamic import to avoid build-time processing
1518
const { render } = await import("@captable/email");
1619
const { AccountVerificationEmail } = await import(
@@ -28,14 +31,30 @@ const sendAuthVerificationEmail = async (
2831
subject: "Verify your account",
2932
html,
3033
});
34+
35+
log.info(
36+
{ email: payload.email },
37+
"Auth verification email sent successfully",
38+
);
3139
};
3240

3341
export { sendAuthVerificationEmail };
3442

3543
export class AuthVerificationEmailJob extends BaseJob<AuthVerificationEmailPayloadType> {
3644
readonly type = "email.auth-verify";
45+
protected readonly options = {
46+
maxAttempts: 5, // Critical for account verification
47+
retryDelay: 1000,
48+
priority: 3, // High priority
49+
};
3750

38-
async work(job: Job<AuthVerificationEmailPayloadType>): Promise<void> {
39-
await sendAuthVerificationEmail(job.data);
51+
async work(payload: AuthVerificationEmailPayloadType): Promise<void> {
52+
await sendAuthVerificationEmail(payload);
4053
}
4154
}
55+
56+
// Create and register the job instance
57+
const authVerificationEmailJob = new AuthVerificationEmailJob();
58+
authVerificationEmailJob.register();
59+
60+
export { authVerificationEmailJob };

0 commit comments

Comments
 (0)