Skip to content

Commit fb3d0b0

Browse files
Merge pull request #236 from Deodat-Lawson/main
Update Mar 5th
2 parents 40d3600 + 308063d commit fb3d0b0

127 files changed

Lines changed: 13620 additions & 2000 deletions

File tree

Some content is hidden

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

.env.example

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Since the ".env" file is gitignored, you can use the ".env.example" file to
2+
# build a new ".env" file when you clone the repo. Keep this file up-to-date
3+
# when you add new variables to `.env`.
4+
5+
# This file will be committed to version control, so make sure not to have any
6+
# secrets in it. If you are cloning this repo, create a copy of this file named
7+
# ".env" and populate it with your secrets.
8+
9+
# When adding additional environment variables, the schema in "/src/env.js"
10+
# should be updated accordingly.
11+
12+
# Database
13+
DATABASE_URL=""
14+
15+
# Docker Compose: password for PostgreSQL (used by db service)
16+
# POSTGRES_PASSWORD=password
17+
18+
# Clerk Authentication (get from https://clerk.com/)
19+
20+
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
21+
CLERK_SECRET_KEY=
22+
23+
# OpenAI API (get from https://platform.openai.com/)
24+
25+
OPENAI_API_KEY=
26+
27+
# UploadThing (get from https://uploadthing.com/)
28+
UPLOADTHING_SECRET="your_uploadthing_secret"
29+
UPLOADTHING_APP_ID="your_uploadthing_app_id"
30+
31+
# Datalab OCR API (optional - get from https://www.datalab.to/)
32+
# Required only if you want to enable OCR processing for scanned documents
33+
DATALAB_API_KEY="your_datalab_api_key"
34+
35+
# Landing.AI OCR API (optional - get from https://www.landing.ai/)
36+
LANDING_AI_API_KEY="your_landing_ai_api_key"
37+
38+
# Tavily API (optional - get from https://www.tavily.com/)
39+
TAVILY_API_KEY="your_tavily_api_key"
40+
41+
# Azure Document Intelligence OCR API (optional - get from https://learn.microsoft.com/en-us/azure/applied-ai-services/document-intelligence/quickstarts/get-started-with-rest-api?pivots=programming-language-rest-api)
42+
AZURE_DOC_INTELLIGENCE_ENDPOINT="your_azure_doc_intelligence_endpoint"
43+
AZURE_DOC_INTELLIGENCE_KEY="your_azure_doc_intelligence_key"
44+
45+
# Inngest (required for background document processing - https://inngest.com/)
46+
INNGEST_EVENT_KEY="dev_placeholder"
47+
INNGEST_SIGNING_KEY="signkey-dev-xxxxx"

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ Create `.env` from `.env.example` and fill required values:
124124
- `DATABASE_URL`
125125
- `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY`
126126
- `CLERK_SECRET_KEY`
127+
- `BLOB_READ_WRITE_TOKEN` (Vercel Blob read/write token)
127128
- `OPENAI_API_KEY`
128129
- `INNGEST_EVENT_KEY`, as placeholder
129130

@@ -137,6 +138,18 @@ Optional integrations:
137138
- `LANGCHAIN_TRACING_V2`, `LANGCHAIN_API_KEY`, `LANGCHAIN_PROJECT`
138139
- `DEBUG_PERF` (`1` or `true`) to enable dev perf logs for middleware and key auth/dashboard APIs
139140

141+
### 2.1) Configure Vercel Blob Storage
142+
143+
Vercel Blob is used for storing uploaded documents. Both **public** and **private** stores are supported -- the upload logic auto-detects which mode the store uses and adapts automatically.
144+
145+
1. In the Vercel dashboard, go to **Storage → Blob → Create Store**.
146+
2. Choose either **Public** or **Private** access. Both work:
147+
- **Public** stores produce URLs the browser can load directly (faster for previews).
148+
- **Private** stores keep files behind authentication; the app proxies content through `/api/documents/[id]/content` and `/api/files/[id]` so previews still work.
149+
3. Generate a **Read/Write token** for the store and add it as `BLOB_READ_WRITE_TOKEN` in your environment (`.env` locally, or Vercel Project Settings for deploys).
150+
4. Redeploy so the token is available at build and runtime.
151+
5. Verify: sign in to the Employer Upload page, upload a small PDF, and confirm `/api/upload-local` returns a `vercel-storage.com` URL without errors.
152+
140153
### 3) Start database and apply schema
141154

142155
```bash

__tests__/api/fetchDocument/fetchDocument.test.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { POST } from "~/app/api/fetchDocument/route";
2-
import { auth } from "@clerk/nextjs/server";
3-
import { validateRequestBody } from "~/lib/validation";
4-
import { dbCore } from "~/server/db/core";
1+
jest.mock("~/server/storage/vercel-blob", () => ({
2+
isPrivateBlobUrl: jest.fn(() => false),
3+
fetchBlob: jest.fn(),
4+
putFile: jest.fn(),
5+
}));
56

67
jest.mock("@clerk/nextjs/server", () => ({
78
auth: jest.fn(),
@@ -11,13 +12,17 @@ jest.mock("~/lib/validation", () => ({
1112
validateRequestBody: jest.fn(),
1213
}));
1314

14-
// Route uses dbCore from core, not db from index
1515
jest.mock("~/server/db/core", () => ({
1616
dbCore: {
1717
select: jest.fn(),
1818
},
1919
}));
2020

21+
import { POST } from "~/app/api/fetchDocument/route";
22+
import { auth } from "@clerk/nextjs/server";
23+
import { validateRequestBody } from "~/lib/validation";
24+
import { dbCore } from "~/server/db/core";
25+
2126
describe("POST /api/fetchDocument", () => {
2227
beforeEach(() => {
2328
jest.clearAllMocks();

__tests__/api/trendSearch/web-search.pbt.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ describe("Unit: one sub-query returns 0 results, pipeline continues", () => {
115115
const result = await executeSearch(subQueries);
116116

117117
expect(fetchSpy).toHaveBeenCalledTimes(3);
118-
expect(result).toHaveLength(2);
119-
expect(result.map((r) => r.url)).toEqual(["https://b.com", "https://c.com"]);
118+
expect(result.results).toHaveLength(2);
119+
expect(result.results.map((r) => r.url)).toEqual(["https://b.com", "https://c.com"]);
120120
});
121121
});
122122

@@ -158,7 +158,7 @@ describe("Unit: Tavily fails, retries 2 times then marks sub-query failed", () =
158158

159159
// 1 + 2 retries for first sub-query, then 1 for second
160160
expect(fetchSpy).toHaveBeenCalledTimes(4);
161-
expect(result).toHaveLength(1);
162-
expect(result[0].url).toBe("https://ok.com");
161+
expect(result.results).toHaveLength(1);
162+
expect(result.results[0].url).toBe("https://ok.com");
163163
});
164164
});

__tests__/lib/ocr/complexity.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
jest.mock("~/server/storage/vercel-blob", () => ({
2+
fetchBlob: jest.fn(),
3+
putFile: jest.fn(),
4+
isPrivateBlobUrl: jest.fn(() => false),
5+
}));
6+
17
import { selectSamplePages } from "~/lib/ocr/complexity";
28

39
describe("OCR Complexity Module", () => {

dev-output.log

2.01 KB
Binary file not shown.

docs/deployment.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ docker compose --env-file .env --profile dev up
5151

5252
1. Import repository into Vercel.
5353
2. Configure managed PostgreSQL (Vercel Postgres, Neon, Supabase, etc.).
54-
3. Set `DATABASE_URL` and app environment variables.
54+
3. Set `DATABASE_URL`, `BLOB_READ_WRITE_TOKEN`, and the other app environment variables.
5555
4. Deploy with Vercel defaults.
5656
5. Apply schema once:
5757

@@ -65,6 +65,12 @@ Optional integrations:
6565
- LangSmith for tracing
6666
- Sidecar (deploy separately and set `SIDECAR_URL`)
6767

68+
### Verifying Blob uploads on Vercel
69+
70+
1. After deploy, sign in to the Employer portal and open `/employer/upload`.
71+
2. Upload any small PDF or DOCX. The `/api/upload-local` response should return a `vercel-storage.com` URL.
72+
3. Paste that URL into a new tab. The file should download directly, confirming Blob access end to end.
73+
6874
## Option 3: VPS self-hosted (Node + reverse proxy)
6975

7076
1. Install Node.js 18+, pnpm, Nginx, and PostgreSQL with pgvector.
@@ -89,7 +95,8 @@ Optional: Run the sidecar separately and point `SIDECAR_URL` to it.
8995
| `CLERK_SECRET_KEY` | Yes | Clerk secret key |
9096
| `OPENAI_API_KEY` | Yes | OpenAI API key |
9197
| `INNGEST_EVENT_KEY` | Yes (prod) | Inngest event key for background jobs |
92-
| `UPLOADTHING_TOKEN` | Optional | UploadThing for cloud storage |
98+
| `BLOB_READ_WRITE_TOKEN` | Yes (Vercel) | Required for Vercel Blob uploads |
99+
| `UPLOADTHING_TOKEN` | Optional | UploadThing legacy uploader |
93100
| `SIDECAR_URL` | Optional | Sidecar URL for reranking and Graph RAG |
94101
| `TAVILY_API_KEY` | Optional | Web search for analysis |
95102
| `AZURE_DOC_INTELLIGENCE_*` | Optional | OCR for scanned PDFs |

drizzle/0002_company_metadata.sql

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
-- Company Metadata Tables
2+
-- One canonical JSON row per company + append-only audit history.
3+
4+
CREATE TABLE IF NOT EXISTS "company_metadata" (
5+
"id" serial PRIMARY KEY NOT NULL,
6+
"company_id" bigint NOT NULL REFERENCES "company"("id") ON DELETE CASCADE,
7+
"schema_version" varchar(20) NOT NULL DEFAULT '1.0.0',
8+
"metadata" jsonb NOT NULL,
9+
"last_extraction_document_id" bigint REFERENCES "document"("id") ON DELETE SET NULL,
10+
"created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP,
11+
"updated_at" timestamptz
12+
);
13+
14+
CREATE UNIQUE INDEX IF NOT EXISTS "company_metadata_company_id_unique"
15+
ON "company_metadata" ("company_id");
16+
17+
-- ============================================================================
18+
19+
CREATE TABLE IF NOT EXISTS "company_metadata_history" (
20+
"id" serial PRIMARY KEY NOT NULL,
21+
"company_id" bigint NOT NULL REFERENCES "company"("id") ON DELETE CASCADE,
22+
"document_id" bigint REFERENCES "document"("id") ON DELETE SET NULL,
23+
"change_type" varchar(32) NOT NULL, -- extraction | merge | manual_override | deprecation
24+
"diff" jsonb NOT NULL,
25+
"changed_by" varchar(256) NOT NULL,
26+
"created_at" timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP
27+
);
28+
29+
CREATE INDEX IF NOT EXISTS "company_metadata_history_company_id_idx"
30+
ON "company_metadata_history" ("company_id");
31+
32+
CREATE INDEX IF NOT EXISTS "company_metadata_history_document_id_idx"
33+
ON "company_metadata_history" ("document_id");
34+
35+
CREATE INDEX IF NOT EXISTS "company_metadata_history_created_at_idx"
36+
ON "company_metadata_history" ("created_at");
37+
38+
CREATE INDEX IF NOT EXISTS "company_metadata_history_change_type_idx"
39+
ON "company_metadata_history" ("change_type");

drizzle/0002_vercel_blob.sql

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ALTER TABLE "file_uploads"
2+
ADD COLUMN IF NOT EXISTS "storage_provider" varchar(64) NOT NULL DEFAULT 'database',
3+
ADD COLUMN IF NOT EXISTS "storage_url" varchar(1024),
4+
ADD COLUMN IF NOT EXISTS "storage_pathname" varchar(1024),
5+
ADD COLUMN IF NOT EXISTS "blob_checksum" varchar(128);
6+
7+
ALTER TABLE "file_uploads"
8+
ALTER COLUMN "file_data" DROP NOT NULL;
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Add onboarding profile columns to company table
2+
ALTER TABLE "company"
3+
ADD COLUMN IF NOT EXISTS "description" text,
4+
ADD COLUMN IF NOT EXISTS "industry" varchar(256);

0 commit comments

Comments
 (0)