Skip to content

Commit 55614bb

Browse files
orabeCopilot
andcommitted
Remove certificate revocation functionality and related UI elements
Co-authored-by: Copilot <copilot@github.com>
1 parent 06b2185 commit 55614bb

8 files changed

Lines changed: 81 additions & 105 deletions

File tree

assets/css/style.css

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -391,10 +391,7 @@ article.active {
391391
border-color: rgba(39, 104, 237, 0.18);
392392
}
393393

394-
.verify-result-revoked {
395-
background: linear-gradient(180deg, rgba(102, 63, 3, 0.12), rgba(255, 255, 255, 0.18));
396-
border-color: rgba(102, 63, 3, 0.16);
397-
}
394+
/* Revoked state removed from UI */
398395

399396
.verify-result-invalid {
400397
background: linear-gradient(180deg, rgba(184, 12, 0, 0.12), rgba(255, 255, 255, 0.18));
@@ -501,8 +498,7 @@ article.active {
501498
margin-top: 2px;
502499
}
503500

504-
.verify-status.verified,
505-
.verify-status.revoked {
501+
.verify-status.verified {
506502
animation: verify-pop 320ms ease-out both, verify-glow 900ms ease-out 1;
507503
}
508504

@@ -578,11 +574,7 @@ article.active {
578574
border: 1px solid rgba(16,81,50,0.12);
579575
}
580576

581-
.verify-status.revoked {
582-
color: #663f03;
583-
background: rgba(102,63,3,0.06);
584-
border: 1px solid rgba(102,63,3,0.12);
585-
}
577+
/* Revoked status styling removed */
586578

587579
.verify-status.invalid {
588580
color: #842029;

backend/app/crud.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ def create_certificate(db: Session, cert_in: schemas.CertificateCreate):
3131
instruction_language=cert_in.instruction_language,
3232
issuer=cert_in.issuer or "MathCodeLab",
3333
instructor=cert_in.instructor or "Mohammad Orabe",
34-
status=models.CertificateStatus.valid,
3534
)
3635

3736
db.add(cert)
@@ -43,9 +42,5 @@ def revoke_certificate(db: Session, certificate_id: str, reason: str = None):
4342
cert = get_certificate_by_public_id(db, certificate_id)
4443
if not cert:
4544
return None
46-
cert.status = models.CertificateStatus.revoked
47-
cert.revocation_reason = reason
48-
cert.updated_at = datetime.utcnow()
49-
db.commit()
50-
db.refresh(cert)
51-
return cert
45+
# Revocation removed: function retained only for compatibility but does nothing
46+
return None

backend/app/main.py

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,7 @@ def verify_certificate(certificate_id: str, db: Session = Depends(get_db)):
6464
"certificate_id": certificate_id,
6565
"message": "Certificate not found"
6666
})
67-
if cert.status == "revoked":
68-
return {
69-
"status": "revoked",
70-
"certificate_id": cert.certificate_id,
71-
"revocation_reason": cert.revocation_reason or ""
72-
}
67+
7368
return {
7469
"status": "valid",
7570
"certificate_id": cert.certificate_id,
@@ -91,17 +86,7 @@ def create_certificate(
9186
):
9287
return crud.create_certificate(db, cert_in)
9388

94-
@app.patch("/admin/certificates/{certificate_id}/revoke", response_model=schemas.CertificateOut)
95-
def revoke_certificate(
96-
certificate_id: str,
97-
body: schemas.CertificateRevoke,
98-
db: Session = Depends(get_db),
99-
api_key: str = Depends(security.verify_api_key)
100-
):
101-
cert = crud.revoke_certificate(db, certificate_id, body.revocation_reason)
102-
if not cert:
103-
raise HTTPException(status_code=404, detail="Certificate not found")
104-
return cert
89+
# Revocation endpoint removed
10590

10691
@app.get("/admin/certificates")
10792
def list_certificates(

backend/app/models.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
from sqlalchemy import Column, Integer, String, DateTime, Enum
1+
from sqlalchemy import Column, Integer, String, DateTime
22
from sqlalchemy.sql import func
33
from .database import Base
4-
import enum
5-
6-
class CertificateStatus(str, enum.Enum):
7-
valid = "valid"
8-
revoked = "revoked"
94

105
class Certificate(Base):
116
__tablename__ = "certificates"
@@ -23,7 +18,6 @@ class Certificate(Base):
2318

2419
issuer = Column(String, default="MathCodeLab", nullable=False)
2520
instructor = Column(String, default="Mohammad Orabe", nullable=False)
26-
status = Column(Enum(CertificateStatus), default=CertificateStatus.valid, nullable=False)
27-
revocation_reason = Column(String, nullable=True)
2821
created_at = Column(DateTime(timezone=True), server_default=func.now())
2922
updated_at = Column(DateTime(timezone=True), onupdate=func.now(), server_default=func.now())
23+

backend/app/schemas.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ class CertificateCreate(CertificateBase):
1919

2020
class CertificateOut(CertificateBase):
2121
certificate_id: str
22-
status: str
23-
revocation_reason: Optional[str] = None
2422
class Config:
2523
from_attributes = True
2624

@@ -35,13 +33,11 @@ class CertificateVerificationResponse(BaseModel):
3533
issuer: Optional[str] = None
3634
instructor: Optional[str] = None
3735
verified_at: Optional[str] = None
38-
revocation_reason: Optional[str] = None
3936
message: Optional[str] = None
4037
attendance_percentage: Optional[int] = None
4138
assignment_completion_percentage: Optional[int] = None
4239
course_level: Optional[str] = None
4340
course_format: Optional[str] = None
4441
instruction_language: Optional[str] = None
4542

46-
class CertificateRevoke(BaseModel):
47-
revocation_reason: Optional[str] = None
43+
# Revocation support removed: no revoke schema

backend/scripts/seed_demo_data.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,15 @@ def seed():
1515
duration_hours=20
1616
)
1717
c1 = crud.create_certificate(db, cert1)
18-
# Revoke a certificate
18+
# Another demo certificate
1919
cert2 = schemas.CertificateCreate(
20-
student_name="Bob Revoked",
20+
student_name="Bob Example",
2121
course_title="Data Science Intro",
2222
completion_date="2026-03-15",
2323
duration_hours=15
2424
)
2525
c2 = crud.create_certificate(db, cert2)
26-
crud.revoke_certificate(db, c2.certificate_id, reason="Academic misconduct")
27-
print(f"Seeded: {c1.certificate_id} (valid), {c2.certificate_id} (revoked)")
26+
print(f"Seeded: {c1.certificate_id}, {c2.certificate_id}")
2827

2928
if __name__ == "__main__":
3029
seed()

verify/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ <h2 class="h2 article-title">Zertifikat prüfen</h2>
130130

131131
<section class="service">
132132
<div class="verify-card content-card">
133-
<p class="verify-description">Teilnehmer und Arbeitgeber können die Echtheit eines Zertifikats in Sekundenschnelle anhand der ZertifikatID prüfen.</p>
133+
<p class="verify-description">Die Echtheit eines Zertifikats kann anhand der Zertifikat-ID verifiziert werden.</p>
134134

135135
<div id="main-content" class="verify-content">
136136
<div id="loading" class="verify-state hidden"></div>
@@ -144,7 +144,7 @@ <h2 class="h2 article-title">Zertifikat prüfen</h2>
144144
maxlength="20"
145145
required
146146
pattern="MCL-\d{4}-[A-Z0-9]{6,8}"
147-
placeholder="MCL-JJJJ-XXXXXXX z. B. MCL-2026-GLMMEO7"
147+
placeholder="MCL-JJJJ-XXXXXXX"
148148
autocomplete="off"
149149
spellcheck="false"
150150
>

verify/verify.js

Lines changed: 66 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -90,65 +90,56 @@
9090
}
9191

9292
function renderCertificate(data) {
93-
function renderContact() {
94-
return `
95-
<div class="verify-result-footer">
96-
<p class="verify-contact">Bei Fragen kontaktieren Sie uns: <a href="mailto:info@mathcodelab.de">info@mathcodelab.de</a></p>
97-
</div>
98-
`;
93+
function renderContactLine() {
94+
return `<p class="verify-contact">Bei Rückfragen kann folgende Adresse kontaktiert werden: <a href="mailto:info@mathcodelab.de">info@mathcodelab.de</a></p>`;
9995
}
10096

101-
function renderHeader(badge, title, description, tone) {
102-
return `
103-
<div class="verify-result-header ${tone}">
104-
<span class="verify-result-badge">${escapeHtml(badge)}</span>
105-
<h3 class="verify-result-title">${escapeHtml(title)}</h3>
106-
<p class="verify-result-description">${escapeHtml(description)}</p>
107-
</div>
108-
`;
97+
function formatDateTime(iso) {
98+
try {
99+
const d = new Date(iso);
100+
const dd = String(d.getUTCDate()).padStart(2, '0');
101+
const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
102+
const yyyy = d.getUTCFullYear();
103+
const hh = String(d.getUTCHours()).padStart(2, '0');
104+
const min = String(d.getUTCMinutes()).padStart(2, '0');
105+
return `${dd}.${mm}.${yyyy}, ${hh}:${min} UTC`;
106+
} catch (e) {
107+
return iso;
108+
}
109109
}
110110

111111
if (data.status === 'valid') {
112+
const verifiedAt = data.verified_at ? formatDateTime(data.verified_at) : '';
112113
return `
113114
<div class="verify-result-card verify-result-success">
114-
${renderHeader('Verifiziert', 'Zertifikat gültig', 'Dieses Zertifikat wurde erfolgreich bestätigt.', 'success')}
115-
<div class="verify-details-grid">
116-
${renderMeta('Teilnehmer', data.student_name)}
117-
${renderMeta('Kurs', data.course_title)}
118-
${renderMeta('Abschlussdatum', data.completion_date)}
119-
${renderMeta('Dauer', `${data.duration_hours} Stunden`)}
120-
${data.attendance_percentage != null ? renderMeta('Anwesenheit', `${data.attendance_percentage}%`) : ''}
121-
${data.assignment_completion_percentage != null ? renderMeta('Aufgaben', `${data.assignment_completion_percentage}%`) : ''}
122-
${data.course_level ? renderMeta('Niveau', data.course_level) : ''}
123-
${data.course_format ? renderMeta('Format', data.course_format) : ''}
124-
${data.instruction_language ? renderMeta('Sprache', data.instruction_language) : ''}
125-
${renderMeta('Herausgeber', data.issuer)}
126-
${renderMeta('Dozent', data.instructor)}
127-
${renderMeta('Zertifikat‑ID', data.certificate_id)}
128-
${data.verified_at ? renderMeta('Bestätigt am', data.verified_at) : ''}
115+
<div class="verify-result-header success">
116+
<span class="verify-result-badge">Verifiziert</span>
117+
<h3 class="verify-result-title">Verifiziertes Zertifikat</h3>
118+
<p class="verify-result-description">Dieses Zertifikat wurde erfolgreich geprüft und als gültig bestätigt.</p>
129119
</div>
130-
<div class="verify-note">Dieses Zertifikat wurde von MathCodeLab ausgestellt. Weitere Informationen unter <a href="https://mathcodelab.de" target="_blank" rel="noreferrer">mathcodelab.de</a>.</div>
131-
${renderContact()}
132-
</div>
133-
`;
134-
} else if (data.status === 'revoked') {
135-
return `
136-
<div class="verify-result-card verify-result-revoked">
137-
${renderHeader('Widerrufen', 'Zertifikat widerrufen', 'Dieses Zertifikat ist nicht mehr gültig.', 'warning')}
138120
<div class="verify-details-grid">
139-
${renderMeta('Zertifikat‑ID', data.certificate_id)}
140-
${data.revocation_reason ? renderMeta('Widerrufsgrund', data.revocation_reason) : ''}
121+
${renderMeta('Teilnehmer', data.student_name || '-')}
122+
${renderMeta('Kurs', data.course_title || '-')}
123+
${renderMeta('Abschlussdatum', data.completion_date || '-')}
124+
${renderMeta('Dauer', (data.duration_hours != null) ? `${data.duration_hours} Unterrichtsstunden` : '-')}
125+
${renderMeta('Herausgeber', data.issuer || 'MathCodeLab')}
126+
${renderMeta('Dozent', data.instructor || 'Mohammad Orabe')}
127+
${renderMeta('Zertifikat-ID', data.certificate_id)}
128+
${verifiedAt ? renderMeta('Verifiziert am', verifiedAt) : ''}
141129
</div>
142-
<div class="verify-note">Dieses Zertifikat wurde von MathCodeLab widerrufen.</div>
143-
${renderContact()}
130+
<div class="verify-note">Hinweis:\nDieses Zertifikat bestätigt die erfolgreiche Teilnahme bzw. den Abschluss eines von MathCodeLab durchgeführten Kurses. Es stellt keinen akademischen Abschluss dar und beinhaltet keine Vergabe von Leistungspunkten (ECTS). Eine mögliche Anerkennung durch Dritten erfolgt ausschließlich im Ermessen der jeweiligen Institution.<br>Weitere Informationen sind unter <a href="https://mathcodelab.de" target="_blank" rel="noreferrer">https://mathcodelab.de</a> verfügbar.</div>
131+
<div class="verify-result-footer">${renderContactLine()}</div>
144132
</div>
145133
`;
146134
} else {
147135
return `
148136
<div class="verify-result-card verify-result-invalid">
149-
${renderHeader('Nicht gefunden', 'Zertifikat nicht gefunden', 'Es wurde kein Zertifikat mit dieser ID im MathCodeLab-Verifizierungssystem gefunden.', 'danger')}
150-
<div class="verify-suggestion">Vorschläge: Überprüfen Sie das ID‑Format, entfernen Sie Leerzeichen oder versuchen Sie folgendes Beispiel: <strong>MCL-2026-GLMMEO7</strong>.</div>
151-
${renderContact()}
137+
<div class="verify-result-header danger">
138+
<span class="verify-result-badge">Nicht gefunden</span>
139+
<h3 class="verify-result-title">Zertifikat nicht gefunden</h3>
140+
<p class="verify-result-description">Zu der angegebenen Zertifikat-ID konnte kein Eintrag in der MathCodeLab-Zertifikatsdatenbank gefunden werden. Es wird gebeten, die eingegebene ID zu überprüfen und erneut einzugeben.</p>
141+
</div>
142+
<div class="verify-result-footer">${renderContactLine()}</div>
152143
</div>
153144
`;
154145
}
@@ -176,15 +167,29 @@
176167
showLoading(true);
177168
try {
178169
const resp = await fetch(`${API_BASE}/verify/${encodeURIComponent(id)}`);
179-
if (!resp.ok) throw new Error('not found');
170+
if (resp.status === 404) {
171+
showResult(renderCertificate({status: 'invalid', certificate_id: id}));
172+
return;
173+
}
174+
if (!resp.ok) {
175+
throw new Error('server');
176+
}
180177
const data = await resp.json();
178+
// ensure status field
179+
data.status = data.status || 'valid';
181180
showResult(renderCertificate(data));
182181
} catch (err) {
183-
if (err.message === 'not found') {
184-
showResult(renderCertificate({status: 'invalid', certificate_id: id}));
185-
} else {
186-
showResult('<div class="verify-result-card verify-result-invalid"><div class="verify-result-header danger"><span class="verify-result-badge">Fehler</span><h3 class="verify-result-title">Verifizierungsdienst nicht erreichbar</h3><p class="verify-result-description">Der Verifizierungsdienst ist derzeit nicht erreichbar.</p></div><div class="verify-note verify-note-warn">Bitte versuchen Sie es später erneut.</div><div class="verify-result-footer"><p class="verify-contact">Kontakt: <a href="mailto:info@mathcodelab.de">info@mathcodelab.de</a></p></div></div>');
187-
}
182+
// server / network errors -> show server error card
183+
showResult(`
184+
<div class="verify-result-card verify-result-invalid">
185+
<div class="verify-result-header danger">
186+
<span class="verify-result-badge">Fehler</span>
187+
<h3 class="verify-result-title">Fehler bei der Verifizierung</h3>
188+
<p class="verify-result-description">Der Verifizierungsdienst ist derzeit vorübergehend nicht erreichbar. Es wird gebeten, die Anfrage zu einem späteren Zeitpunkt erneut durchzuführen. Sollte das Problem weiterhin bestehen, kann der Support kontaktiert werden.</p>
189+
</div>
190+
<div class="verify-result-footer"><p class="verify-contact">Kontakt: <a href="mailto:info@mathcodelab.de">info@mathcodelab.de</a></p></div>
191+
</div>
192+
`);
188193
} finally {
189194
setFormBusy(false);
190195
}
@@ -200,7 +205,17 @@
200205
const id = document.getElementById('certificate-id').value.trim();
201206
if (!id) return;
202207
if (!CERT_PATTERN.test(id)) {
203-
showResult(`<div class="verify-status invalid">Ungültiges Format</div><div class="verify-note verify-note-warn">Bitte verwenden Sie das Format <strong>MCL-JJJJ-XXXXXXX</strong>. Beispiel: <strong>MCL-2026-GLMMEO7</strong>.</div><div class="verify-contact">kontaktieren Sie uns: <a href="mailto:info@mathcodelab.de">info@mathcodelab.de</a></div>`);
208+
showResult(`
209+
<div class="verify-result-card verify-result-invalid">
210+
<div class="verify-result-header danger">
211+
<span class="verify-result-badge">Ungültige Eingabe</span>
212+
<h3 class="verify-result-title">Ungültige Eingabe</h3>
213+
<p class="verify-result-description">Die eingegebene Zertifikat-ID entspricht nicht dem erwarteten Format. Es wird gebeten, folgendes Format zu verwenden: <strong>MCL-JJJJ-XXXXXXX</strong></p>
214+
</div>
215+
<div class="verify-suggestion">Beispiel: <strong>MCL-2026-GLMMEO7</strong></div>
216+
<div class="verify-result-footer"><p class="verify-contact">Bei Rückfragen kann folgende Adresse kontaktiert werden: <a href="mailto:info@mathcodelab.de">info@mathcodelab.de</a></p></div>
217+
</div>
218+
`);
204219
return;
205220
}
206221
verifyCertificate(id);

0 commit comments

Comments
 (0)