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;
}