in app/routes/api.jobs.create-with-key.tsx [8:211]
export async function action({ request }: ActionFunctionArgs) {
console.log("POST /api/jobs/create-with-key");
if (request.method !== "POST") {
return json({ error: "Method not allowed" }, { status: 405 });
}
// Check for API key in Authorization header
const authHeader = request.headers.get("Authorization");
const apiKey = authHeader?.replace("Bearer ", "");
if (!apiKey) {
return json(
{
error: {
code: "UNAUTHORIZED",
message: "API key required in Authorization header (Bearer token)",
},
},
{ status: 401 }
);
}
// For now, we'll validate that the API key is a Hugging Face token
// In a production environment, you might want to validate against your own API key system
if (!isValidHuggingFaceToken(apiKey)) {
return json(
{
error: {
code: "UNAUTHORIZED",
message:
"Invalid API key format. Expected a valid Hugging Face token.",
},
},
{ status: 401 }
);
}
try {
const {
title,
description,
branch,
author,
repository,
environment,
secrets,
} = await request.json();
// Validation
if (!title || title.length === 0 || title.length > 200) {
return json(
{
error: {
code: "VALIDATION_ERROR",
message: "Title is required and must be between 1-200 characters",
details: [
{
field: "title",
message:
"Title is required and must be between 1-200 characters",
},
],
},
},
{ status: 400 }
);
}
if (description && description.length > 1000) {
return json(
{
error: {
code: "VALIDATION_ERROR",
message: "Description must be less than 1000 characters",
details: [
{
field: "description",
message: "Description must be less than 1000 characters",
},
],
},
},
{ status: 400 }
);
}
// Validate environment variables if provided
if (environment && typeof environment !== "object") {
return json(
{
error: {
code: "VALIDATION_ERROR",
message: "Environment must be an object",
details: [
{
field: "environment",
message: "Environment must be an object with key-value pairs",
},
],
},
},
{ status: 400 }
);
}
// Validate secrets if provided
if (secrets && typeof secrets !== "object") {
return json(
{
error: {
code: "VALIDATION_ERROR",
message: "Secrets must be an object",
details: [
{
field: "secrets",
message: "Secrets must be an object with key-value pairs",
},
],
},
},
{ status: 400 }
);
}
if (repository?.url) {
const githubUrlPattern =
/^https:\/\/github\.com\/[\w\-\.]+\/[\w\-\.]+(?:\.git)?(?:\/)?$/;
if (!githubUrlPattern.test(repository.url)) {
return json(
{
error: {
code: "VALIDATION_ERROR",
message: "Repository URL must be a valid GitHub repository URL",
details: [
{
field: "repository.url",
message:
"Repository URL must be a valid GitHub repository URL (e.g., https://github.com/username/repo)",
},
],
},
},
{ status: 400 }
);
}
}
// Get user info from the API key to set as author
let apiKeyAuthor = author;
try {
const userInfo = await getUserInfoFromApiKey(apiKey);
console.log("User info from API key:", userInfo);
apiKeyAuthor = userInfo?.username || author || "api-user";
} catch (error) {
console.warn("Could not get user info from API key:", error);
apiKeyAuthor = author || "api-user";
}
console.log("API Key Author:", apiKeyAuthor);
const job: Job = {
id: uuidv4(),
title,
description: description || "",
status: "pending",
createdAt: new Date(),
updatedAt: new Date(),
branch: branch || undefined,
author: apiKeyAuthor,
repository: repository || undefined,
environment: environment || undefined,
secrets: secrets || undefined,
};
const jobStore = getJobStore();
await jobStore.createJob(job);
// Create credentials object for job processor
const credentials = {
huggingfaceToken: apiKey,
hfUserInfo: {
username: apiKeyAuthor,
},
};
// Start processing job asynchronously with credentials
const jobProcessor = getJobProcessor();
jobProcessor.processJob(job.id, jobStore, credentials).catch((error) => {
console.error(`Failed to process job ${job.id}:`, error);
});
return json(job, { status: 201 });
} catch (error) {
console.error("Error creating job:", error);
return json(
{
error: {
code: "INTERNAL_ERROR",
message: "Failed to create job",
},
},
{ status: 500 }
);
}
}