Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
typesense-data
bun.lockb

# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
Expand Down Expand Up @@ -37,4 +38,4 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts

.env
.env
25 changes: 25 additions & 0 deletions app/api/orgs/[orgId]/contests/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NextRequest, NextResponse } from "next/server";
import { searchContests } from "@/lib/typesense/collections/contests";
import { safeSearch } from "@/lib/typesense/client";

export async function GET(
request: NextRequest,
{ params }: { params: { orgId: string } },
) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("q") || "";
const page = parseInt(searchParams.get("page") || "1", 10);
const per_page = parseInt(searchParams.get("per_page") || "10", 10);
const orgId = parseInt(params.orgId, 10);

try {
const results = await safeSearch(
() => searchContests(query, orgId, { page, per_page }),
{ found: 0, hits: [], page },
);
return NextResponse.json(results);
} catch (error) {
console.error("Contests search error:", error);
return NextResponse.json({ error: "Search failed" }, { status: 500 });
}
}
25 changes: 25 additions & 0 deletions app/api/orgs/[orgId]/groups/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NextRequest, NextResponse } from "next/server";
import { searchGroups } from "@/lib/typesense/collections/groups";
import { safeSearch } from "@/lib/typesense/client";

export async function GET(
request: NextRequest,
{ params }: { params: { orgId: string } },
) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("q") || "";
const page = parseInt(searchParams.get("page") || "1", 10);
const per_page = parseInt(searchParams.get("per_page") || "10", 10);
const orgId = parseInt(params.orgId, 10);

try {
const results = await safeSearch(
() => searchGroups(query, orgId, { page, per_page }),
{ found: 0, hits: [], page },
);
return NextResponse.json(results);
} catch (error) {
console.error("Groups search error:", error);
return NextResponse.json({ error: "Search failed" }, { status: 500 });
}
}
25 changes: 25 additions & 0 deletions app/api/orgs/[orgId]/problems/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NextRequest, NextResponse } from "next/server";
import { searchProblems } from "@/lib/typesense/collections/problems";
import { safeSearch } from "@/lib/typesense/client";

export async function GET(
request: NextRequest,
{ params }: { params: { orgId: string } },
) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("q") || "";
const page = parseInt(searchParams.get("page") || "1", 10);
const per_page = parseInt(searchParams.get("per_page") || "10", 10);
const orgId = parseInt(params.orgId, 10);

try {
const results = await safeSearch(
() => searchProblems(query, orgId, { page, per_page }),
{ found: 0, hits: [], page },
);
return NextResponse.json(results);
} catch (error) {
console.error("Problems search error:", error);
return NextResponse.json({ error: "Search failed" }, { status: 500 });
}
}
40 changes: 40 additions & 0 deletions app/api/orgs/[orgId]/submissions/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { NextRequest, NextResponse } from "next/server";
import { filterSubmissions } from "@/lib/typesense/collections/submissions";
import { safeSearch } from "@/lib/typesense/client";

export async function GET(
request: NextRequest,
{ params }: { params: { orgId: string } },
) {
const searchParams = request.nextUrl.searchParams;
const orgId = parseInt(params.orgId, 10);

// Extract filter parameters
const options = {
userNameId: searchParams.get("user") || undefined,
contestNameId: searchParams.get("contest") || undefined,
problemNameId: searchParams.get("problem") || undefined,
language: searchParams.get("language") || undefined,
status: searchParams.get("status") || undefined,
startTime: searchParams.get("start")
? parseInt(searchParams.get("start")!, 10)
: undefined,
endTime: searchParams.get("end")
? parseInt(searchParams.get("end")!, 10)
: undefined,
page: parseInt(searchParams.get("page") || "1", 10),
per_page: parseInt(searchParams.get("per_page") || "10", 10),
};

try {
const query = searchParams.get("q") || "";
const results = await safeSearch(
() => searchSubmissions(query, orgId, options),
{ found: 0, hits: [], page: options.page },
);
return NextResponse.json(results);
} catch (error) {
console.error("Submissions search error:", error);
return NextResponse.json({ error: "Search failed" }, { status: 500 });
}
}
25 changes: 25 additions & 0 deletions app/api/orgs/[orgId]/users/search/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NextRequest, NextResponse } from "next/server";
import { searchUsers } from "@/lib/typesense/collections/users";
import { safeSearch } from "@/lib/typesense/client";

export async function GET(
request: NextRequest,
{ params }: { params: { orgId: string } },
) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("q") || "";
const page = parseInt(searchParams.get("page") || "1", 10);
const per_page = parseInt(searchParams.get("per_page") || "10", 10);
const orgId = parseInt(params.orgId, 10);

try {
const results = await safeSearch(
() => searchUsers(query, orgId, { page, per_page }),
{ found: 0, hits: [], page },
);
return NextResponse.json(results);
} catch (error) {
console.error("Users search error:", error);
return NextResponse.json({ error: "Search failed" }, { status: 500 });
}
}
20 changes: 20 additions & 0 deletions app/api/search/contests/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
import { searchContests } from "@/lib/typesense/collections/contests";
import { safeSearch } from "@/lib/typesense/client";

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("q") || "";
const page = parseInt(searchParams.get("page") || "1", 10);
const per_page = parseInt(searchParams.get("per_page") || "10", 10);

try {
const results = await safeSearch(
() => searchContests(query, { page, per_page }),
{ found: 0, hits: [], page },
);
return NextResponse.json(results);
} catch (error) {
return NextResponse.json({ error: "Search failed" }, { status: 500 });
}
}
20 changes: 20 additions & 0 deletions app/api/search/orgs/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
import { searchOrgs } from "@/lib/typesense/collections/orgs";
import { safeSearch } from "@/lib/typesense/client";

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("q") || "";
const page = parseInt(searchParams.get("page") || "1", 10);
const per_page = parseInt(searchParams.get("per_page") || "10", 10);

try {
const results = await safeSearch(
() => searchOrgs(query, { page, per_page }),
{ found: 0, hits: [], page },
);
return NextResponse.json(results);
} catch (error) {
return NextResponse.json({ error: "Search failed" }, { status: 500 });
}
}
20 changes: 20 additions & 0 deletions app/api/search/problems/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
import { searchProblems } from "@/lib/typesense/collections/problems";
import { safeSearch } from "@/lib/typesense/client";

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("q") || "";
const page = parseInt(searchParams.get("page") || "1", 10);
const per_page = parseInt(searchParams.get("per_page") || "10", 10);

try {
const results = await safeSearch(
() => searchProblems(query, { page, per_page }),
{ found: 0, hits: [], page },
);
return NextResponse.json(results);
} catch (error) {
return NextResponse.json({ error: "Search failed" }, { status: 500 });
}
}
20 changes: 20 additions & 0 deletions app/api/search/users/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
import { searchUsers } from "@/lib/typesense/collections/users";
import { safeSearch } from "@/lib/typesense/client";

export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const query = searchParams.get("q") || "";
const page = parseInt(searchParams.get("page") || "1", 10);
const per_page = parseInt(searchParams.get("per_page") || "10", 10);

try {
const results = await safeSearch(
() => searchUsers(query, { page, per_page }),
{ found: 0, hits: [], page },
);
return NextResponse.json(results);
} catch (error) {
return NextResponse.json({ error: "Search failed" }, { status: 500 });
}
}
15 changes: 15 additions & 0 deletions app/api/typesense/health/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { testTypesenseConnection } from "@/lib/typesense/test-connection";
import { NextResponse } from "next/server";

export async function GET() {
const isHealthy = await testTypesenseConnection();

if (!isHealthy) {
return NextResponse.json(
{ error: "Typesense connection failed" },
{ status: 500 },
);
}

return NextResponse.json({ status: "healthy" });
}
3 changes: 3 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import "@uiw/react-markdown-preview/markdown.css";
import { Toaster } from "@/components/ui/toaster";
import { AuthProvider } from "@/contexts/auth-context";
import { ThemeProvider } from "@/contexts/theme-context"; // Added import statement
import { initializeServices } from "@/lib/init";

initializeServices().catch(console.error);

const geistSans = localFont({
src: "./fonts/GeistVF.woff",
Expand Down
11 changes: 11 additions & 0 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"js-cookie": "^3.0.5",
"lodash.debounce": "^4.0.8",
"lucide-react": "^0.446.0",
"marked": "^15.0.6",
"next": "14.2.13",
Expand All @@ -78,12 +79,14 @@
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"ts-jest": "^29.3.2",
"typesense": "^2.0.3",
"zod": "^3.24.3",
},
"devDependencies": {
"@radix-ui/react-label": "^2.1.0",
"@tailwindcss/typography": "^0.5.15",
"@types/codemirror": "^5.60.15",
"@types/lodash.debounce": "^4.0.9",
"@types/node": "^20",
"@types/nodemailer": "^6.4.17",
"@types/pg": "^8.11.10",
Expand Down Expand Up @@ -970,6 +973,10 @@

"@types/koa__router": ["@types/koa__router@12.0.3", "", { "dependencies": { "@types/koa": "*" } }, "sha512-5YUJVv6NwM1z7m6FuYpKfNLTZ932Z6EF6xy2BbtpJSyn13DKNQEkXVffFVSnJHxvwwWh2SAeumpjAYUELqgjyw=="],

"@types/lodash": ["@types/lodash@4.17.16", "", {}, "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="],

"@types/lodash.debounce": ["@types/lodash.debounce@4.0.9", "", { "dependencies": { "@types/lodash": "*" } }, "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ=="],

"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],

"@types/memcached": ["@types/memcached@2.2.10", "", { "dependencies": { "@types/node": "*" } }, "sha512-AM9smvZN55Gzs2wRrqeMHVP7KE8KWgCJO/XL5yCly2xF6EKa4YlbpK+cLSAH4NG/Ah64HrlegmGqW8kYws7Vxg=="],
Expand Down Expand Up @@ -2120,6 +2127,8 @@

"lodash.mergewith": ["lodash.mergewith@4.6.2", "", {}, "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ=="],

"loglevel": ["loglevel@1.9.2", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="],

"long": ["long@5.3.1", "", {}, "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng=="],

"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
Expand Down Expand Up @@ -2834,6 +2843,8 @@

"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],

"typesense": ["typesense@2.0.3", "", { "dependencies": { "axios": "^1.7.2", "loglevel": "^1.8.1", "tslib": "^2.6.2" }, "peerDependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-fRJjFdDNZn6qF9XzIk+bB8n8cm0fiAx1SGcpLDfNcsGtp8znITfG+SO+l/qk63GCRXZwJGq7wrMDLFUvblJSHA=="],

"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],

"undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
Expand Down
11 changes: 11 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
typesense:
image: typesense/typesense:28.0
ports:
- "8108:8108"
volumes:
- ./typesense-data:/data
environment:
- TYPESENSE_API_KEY=your_api_key_here
- TYPESENSE_DATA_DIR=/data
- TYPESENSE_ENABLE_CORS=true
20 changes: 20 additions & 0 deletions lib/init.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { initializeTypesenseCollections } from "./typesense/init";
import { initializeTypesenseSync } from "./typesense/subscriber";
import { syncExistingData } from "./typesense/sync-existing";

let initialized = false;

export async function initializeServices() {
if (initialized) return;

// Initialize Typesense collections
await initializeTypesenseCollections();

// Initialize TypesenseSubscriber
initializeTypesenseSync();

// Sync existing data
await syncExistingData();

initialized = true;
}
Loading
Loading