app/lib/server/jobStore.ts (123 lines of code) (raw):
// In-memory job store (replace with database in production)
import type { Job, JobDiff } from "~/types/job";
export class JobStore {
private jobs = new Map<string, Job>();
private diffs = new Map<string, JobDiff>();
private logs = new Map<string, string>();
async listJobs({
page = 1,
limit = 20,
status,
search,
author,
}: {
page?: number;
limit?: number;
status?: string;
search?: string;
author?: string;
} = {}) {
let jobs = Array.from(this.jobs.values());
// Filter by status
if (status) {
jobs = jobs.filter((job) => job.status === status);
}
// Filter by search
if (search) {
const searchLower = search.toLowerCase();
jobs = jobs.filter(
(job) =>
job.title.toLowerCase().includes(searchLower) ||
job.description.toLowerCase().includes(searchLower)
);
}
// Filter by author
if (author) {
jobs = jobs.filter((job) => job.author === author);
}
// Sort by creation date (newest first)
jobs.sort(
(a, b) =>
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
// Pagination
const total = jobs.length;
const totalPages = Math.ceil(total / limit);
const offset = (page - 1) * limit;
const paginatedJobs = jobs.slice(offset, offset + limit);
return {
jobs: paginatedJobs,
pagination: {
page,
limit,
total,
totalPages,
hasNext: page < totalPages,
hasPrev: page > 1,
},
};
}
async getJob(jobId: string): Promise<Job | null> {
return this.jobs.get(jobId) || null;
}
async createJob(job: Job): Promise<Job> {
this.jobs.set(job.id, { ...job });
return job;
}
async updateJobStatus(
jobId: string,
status: Job["status"],
changes?: any
): Promise<Job | null> {
const job = this.jobs.get(jobId);
if (!job) return null;
const updatedJob: Job = {
...job,
status,
updatedAt: new Date(),
...(changes && { changes }),
};
this.jobs.set(jobId, updatedJob);
return updatedJob;
}
async updateJobEnvironment(
jobId: string,
environment: Record<string, string>,
secrets?: Record<string, string>,
apiJobId?: string
): Promise<Job | null> {
const job = this.jobs.get(jobId);
if (!job) return null;
const updatedJob: Job = {
...job,
environment,
secrets,
apiJobId,
updatedAt: new Date(),
};
this.jobs.set(jobId, updatedJob);
return updatedJob;
}
async deleteJob(jobId: string): Promise<boolean> {
const deleted = this.jobs.delete(jobId);
this.diffs.delete(jobId);
this.logs.delete(jobId);
return deleted;
}
async setJobDiff(jobId: string, diff: JobDiff): Promise<void> {
this.diffs.set(jobId, diff);
}
async getJobDiff(jobId: string): Promise<JobDiff | null> {
return this.diffs.get(jobId) || null;
}
async setJobLogs(jobId: string, logs: string): Promise<void> {
this.logs.set(jobId, logs);
// Also update the job record to include logs
const job = this.jobs.get(jobId);
if (job) {
const updatedJob = { ...job, logs };
this.jobs.set(jobId, updatedJob);
}
}
async getJobLogs(jobId: string): Promise<string | null> {
return this.logs.get(jobId) || null;
}
async getAllJobs(): Promise<Job[]> {
return Array.from(this.jobs.values());
}
}
// Singleton instance
let jobStoreInstance: JobStore | null = null;
export function getJobStore(): JobStore {
if (!jobStoreInstance) {
jobStoreInstance = new JobStore();
}
return jobStoreInstance;
}