app/lib/jobService.remix.ts (274 lines of code) (raw):
import { Job, JobDiff, FileDiff } from "~/types/job";
import { getJobStore } from "~/lib/server/jobStore";
import { getJobProcessor } from "~/lib/server/jobProcessor";
// Server-side job service that uses the jobStore directly
// This is much more efficient than making HTTP requests within the same process
export class JobService {
private static jobStore = getJobStore();
private static jobProcessor = getJobProcessor();
static async getAllJobs(
options: {
page?: number;
limit?: number;
status?: string;
search?: string;
author?: string;
} = {}
): Promise<{
jobs: Job[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
};
}> {
try {
const {
page = 1,
limit = 20,
status = "all",
search = "",
author,
} = options;
const pageNum = parseInt(String(page));
const limitNum = Math.min(parseInt(String(limit)), 100);
const result = await this.jobStore.listJobs({
page: pageNum,
limit: limitNum,
status: status === "all" ? undefined : status,
search: search || undefined,
author: author || undefined,
});
return result;
} catch (error) {
console.error("Error listing jobs:", error);
return {
jobs: [],
pagination: {
page: 1,
limit: 20,
total: 0,
totalPages: 0,
hasNext: false,
hasPrev: false,
},
};
}
}
static async getJob(id: string): Promise<Job | null> {
try {
return await this.jobStore.getJob(id);
} catch (error) {
console.error("Failed to fetch job:", error);
return null;
}
}
static async createJob(
data: Omit<Job, "id" | "createdAt" | "updatedAt">
): Promise<Job> {
try {
const job: Job = {
...data,
id: this.generateId(),
createdAt: new Date(),
updatedAt: new Date(),
};
console.log("Creating job:", job);
await this.jobStore.createJob(job);
// show credentials
// console.log("API Credentials:", req.apiCredentials);
// Need to process the job
// this.jobProcessor.processJob(job.id, this.jobStore);
// jobProcessor
// .processJob(job.id, this.jobStore, req.apiCredentials)
// .catch((error) => {
// console.error(`Failed to process job ${job.id}:`, error);
// });
return job;
} catch (error) {
console.error("Failed to create job:", error);
throw error;
}
}
static async getJobDiff(jobId: string): Promise<JobDiff | null> {
try {
return await this.jobStore.getJobDiff(jobId);
} catch (error) {
console.error("Failed to fetch job diff:", error);
return null;
}
}
static async setJobDiff(jobId: string, diff: JobDiff): Promise<void> {
try {
await this.jobStore.setJobDiff(jobId, diff);
} catch (error) {
console.error("Failed to set job diff:", error);
throw error;
}
}
static async getJobLogs(jobId: string): Promise<string | null> {
try {
return await this.jobStore.getJobLogs(jobId);
} catch (error) {
console.error("Failed to fetch job logs:", error);
return null;
}
}
static async setJobLogs(jobId: string, logs: string): Promise<void> {
try {
await this.jobStore.setJobLogs(jobId, logs);
} catch (error) {
console.error("Failed to set job logs:", error);
throw error;
}
}
static async updateJobStatus(
jobId: string,
status: Job["status"],
changes?: any
): Promise<Job | null> {
try {
return await this.jobStore.updateJobStatus(jobId, status, changes);
} catch (error) {
console.error("Failed to update job status:", error);
return null;
}
}
static async deleteJob(jobId: string): Promise<boolean> {
try {
return await this.jobStore.deleteJob(jobId);
} catch (error) {
console.error("Failed to delete job:", error);
return false;
}
}
// Utility method to generate unique IDs
private static generateId(): string {
return Math.random().toString(36).substring(2) + Date.now().toString(36);
}
// Health check method - since we're using in-memory store, we can always return true
static async checkHealth(): Promise<boolean> {
try {
// Simple health check - try to list jobs
await this.jobStore.listJobs({ limit: 1 });
return true;
} catch (error) {
console.error("Health check failed:", error);
return false;
}
}
// Batch operations for efficiency
static async getMultipleJobs(ids: string[]): Promise<(Job | null)[]> {
try {
const promises = ids.map((id) => this.jobStore.getJob(id));
return await Promise.all(promises);
} catch (error) {
console.error("Failed to fetch multiple jobs:", error);
return ids.map(() => null);
}
}
static async getJobsByStatus(
status: Job["status"],
author?: string
): Promise<Job[]> {
try {
const result = await this.jobStore.listJobs({
status,
author,
limit: 1000, // Get all jobs with this status
});
return result.jobs;
} catch (error) {
console.error("Failed to fetch jobs by status:", error);
return [];
}
}
// Search functionality
static async searchJobs(
query: string,
options: {
page?: number;
limit?: number;
status?: string;
author?: string;
} = {}
): Promise<{
jobs: Job[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
};
}> {
try {
return await this.getAllJobs({
...options,
search: query,
});
} catch (error) {
console.error("Failed to search jobs:", error);
return {
jobs: [],
pagination: {
page: 1,
limit: 20,
total: 0,
totalPages: 0,
hasNext: false,
hasPrev: false,
},
};
}
}
static async createBranchAndPush(
jobId: string,
options: {
branch: string;
title: string;
description: string;
baseBranch: string;
},
credentials?: any
): Promise<{ branch: string; commitHash: string }> {
try {
const job = await this.getJob(jobId);
const jobDiff = await this.getJobDiff(jobId);
if (!job || !jobDiff || !job.repository?.url) {
throw new Error("Job, diff, or repository not found");
}
if (!jobDiff.files || jobDiff.files.length === 0) {
throw new Error("No changes to commit");
}
// Use the job processor to create branch and push changes
const result = await this.jobProcessor.createBranchAndPush({
repositoryUrl: job.repository.url,
branch: options.branch,
baseBranch: options.baseBranch,
title: options.title,
description: options.description,
files: jobDiff.files,
credentials,
});
return result;
} catch (error) {
console.error("Failed to create branch and push:", error);
throw error;
}
}
// Statistics and analytics
static async getJobStats(): Promise<{
total: number;
byStatus: Record<string, number>;
recent: Job[];
}> {
try {
const allJobsResult = await this.jobStore.listJobs({ limit: 1000 });
const allJobs = allJobsResult.jobs;
const byStatus: Record<string, number> = {};
allJobs.forEach((job) => {
byStatus[job.status] = (byStatus[job.status] || 0) + 1;
});
// Get 5 most recent jobs
const recent = allJobs.slice(0, 5);
return {
total: allJobs.length,
byStatus,
recent,
};
} catch (error) {
console.error("Failed to get job stats:", error);
return {
total: 0,
byStatus: {},
recent: [],
};
}
}
}