Spaces:
Running
Running
File size: 5,295 Bytes
6068938 46ab86e f237798 6068938 f237798 515e3fb 6068938 f237798 6068938 f237798 46ab86e 6068938 f237798 2f42945 f237798 2f42945 f237798 2f42945 f237798 2f42945 f237798 6068938 f237798 6068938 f237798 6068938 f237798 2f42945 f237798 2f42945 f237798 6068938 f237798 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | import type { CertificatePayload, CertificationTrack, ScoreBreakdown, ValidationSummary } from '../_types.js';
import { getSupabaseAdmin, getSupabaseClientForRequest } from '../_shared/supabaseAdmin.js';
function makeCertificateId(skill: string, track: CertificationTrack = 'verifiedcert'): string {
const date = new Date();
const y = date.getUTCFullYear();
const m = String(date.getUTCMonth() + 1).padStart(2, '0');
const d = String(date.getUTCDate()).padStart(2, '0');
const suffix = Math.random().toString(36).slice(2, 10).toUpperCase();
const normalizedSkill = skill.replace(/[^a-zA-Z0-9]/g, '').slice(0, 6).toUpperCase() || 'SKILL';
const prefix = track === 'free_completion' ? 'FC' : track === 'quickcert' ? 'QC' : track === 'ytcert' ? 'YT' : 'VC';
return `${prefix}-${normalizedSkill}-${y}${m}${d}-${suffix}`;
}
export async function issueCertificate(input: {
userId: string;
name: string;
email: string;
skill: string;
projectTitle: string;
githubUrl: string;
proofUrl: string;
liveDemoUrl?: string;
finalScore?: number;
scoreBreakdown?: ScoreBreakdown;
validationSummary?: ValidationSummary;
projectType?: string;
commitCount?: number;
paymentRequestId?: string;
paymentTxnId?: string | null;
extraMetadata?: Record<string, unknown>;
authToken?: string | null;
track?: CertificationTrack;
}): Promise<CertificatePayload> {
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
const supabase = (!input.authToken && serviceRoleKey) ? getSupabaseAdmin() : getSupabaseClientForRequest(input.authToken);
const certTrack = input.track || 'verifiedcert';
if (input.paymentRequestId) {
const { data: existing } = await supabase
.from('certificates')
.select('user_id, topic, project_title, issued_at, metadata')
.eq('metadata->>payment_request_id', input.paymentRequestId)
.order('issued_at', { ascending: false })
.limit(1)
.maybeSingle();
if (existing) {
return {
certificateId: (existing.metadata as any)?.certificate_id || makeCertificateId(existing.topic || input.skill, certTrack),
userId: existing.user_id,
name: (existing.metadata as any)?.username || (existing as any).username || input.name,
skill: existing.topic,
projectTitle: existing.project_title || input.projectTitle,
githubUrl: (existing.metadata as any)?.github_repo_url || (existing.metadata as any)?.github_url || input.githubUrl,
proofUrl: (existing.metadata as any)?.live_demo_url || (existing.metadata as any)?.proof_url || input.proofUrl,
issuedAt: existing.issued_at,
track: certTrack,
};
}
}
const certificateId = makeCertificateId(input.skill, certTrack);
const payload: CertificatePayload = {
certificateId,
userId: input.userId,
name: input.name,
skill: input.skill,
projectTitle: input.projectTitle,
githubUrl: input.githubUrl,
proofUrl: input.proofUrl,
issuedAt: new Date().toISOString(),
track: certTrack,
};
const normalizedScoreBreakdown = input.scoreBreakdown
? {
interview_score: input.scoreBreakdown.interviewScore,
github_validation_score: input.scoreBreakdown.githubValidationScore,
proof_validation_score: input.scoreBreakdown.proofValidationScore,
commit_score: input.scoreBreakdown.commitScore,
structure_score: input.scoreBreakdown.structureScore,
readme_score: input.scoreBreakdown.readmeScore,
}
: null;
const normalizedValidationSummary = input.validationSummary
? {
overall_passed: input.validationSummary.overallPassed,
commit_passed: input.validationSummary.commitPassed,
readme_passed: input.validationSummary.readmePassed,
proof_passed: input.validationSummary.proofPassed,
commit_count: input.validationSummary.commitCount,
file_count: input.validationSummary.fileCount,
meaningful_file_count: input.validationSummary.meaningfulFileCount,
readme_richness: input.validationSummary.readmeRichness,
suspicious_signals: input.validationSummary.suspiciousSignals,
}
: null;
const { error } = await supabase.from('certificates').insert({
user_id: input.userId,
topic: input.skill,
project_title: input.projectTitle,
metadata: {
certificate_id: certificateId,
username: input.name,
email: input.email,
course_topic: input.skill,
project_topic: input.projectTitle,
github_repo_url: input.githubUrl,
live_demo_url: input.liveDemoUrl || input.proofUrl,
final_score: input.finalScore ?? null,
score_breakdown: normalizedScoreBreakdown,
project_type: input.projectType || null,
commit_count: input.commitCount ?? null,
validation_summary: normalizedValidationSummary,
github_url: input.githubUrl,
proof_url: input.proofUrl,
getcert_version: 'v2',
certification_track: certTrack,
payment_request_id: input.paymentRequestId || null,
payment_txn_id: input.paymentTxnId || null,
...(input.extraMetadata || {}),
},
issued_at: payload.issuedAt,
});
if (error) {
throw new Error(`Failed to issue certificate: ${error.message}`);
}
return payload;
}
|