Skip to content

Commit 3263926

Browse files
orabeCopilot
andcommitted
Enhance certificate management and verification features
- Updated CSS for improved layout and responsiveness of the verification page. - Modified CRUD operations to ensure unique certificate IDs and added new fields for certificate details. - Implemented API key protection for documentation endpoints. - Added new fields to certificate schemas and updated the certificate creation script for additional inputs. - Removed obsolete database seeding and viewing scripts. Co-authored-by: Copilot <copilot@github.com>
1 parent fa18a73 commit 3263926

10 files changed

Lines changed: 111 additions & 72 deletions

File tree

assets/css/style.css

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2267,8 +2267,49 @@ textarea.form-input::-webkit-resizer {
22672267

22682268
.main-content {
22692269
position: relative;
2270-
width: max-content;
2271-
margin: auto;
2270+
flex: 1;
2271+
width: 100%;
2272+
min-width: 0;
2273+
margin: 0;
2274+
display: flex;
2275+
flex-direction: column;
2276+
align-items: stretch;
2277+
gap: 25px;
2278+
}
2279+
2280+
.main-content article {
2281+
width: 100%;
2282+
margin-top: 0;
2283+
flex: 1;
2284+
}
2285+
2286+
.verify-certificate {
2287+
display: flex;
2288+
flex-direction: column;
2289+
height: 100%;
2290+
}
2291+
2292+
.verify-certificate .service,
2293+
.verify-certificate .verify-card {
2294+
width: 100%;
2295+
max-width: 950px;
2296+
margin-inline: auto;
2297+
}
2298+
2299+
.verify-certificate .service {
2300+
flex: 1;
2301+
display: flex;
2302+
}
2303+
2304+
.verify-certificate .verify-card {
2305+
flex: 1;
2306+
display: flex;
2307+
flex-direction: column;
2308+
}
2309+
2310+
.verify-certificate .verify-content {
2311+
width: 100%;
2312+
flex: 1;
22722313
}
22732314

22742315

@@ -2278,17 +2319,23 @@ textarea.form-input::-webkit-resizer {
22782319
*/
22792320

22802321
.navbar {
2281-
position: absolute;
2322+
position: relative;
22822323
bottom: auto;
2283-
top: 20px;
2324+
top: auto;
22842325
left: auto;
2285-
right: 30px;
2286-
width: max-content;
2326+
right: auto;
2327+
width: 100%;
2328+
max-width: 950px;
2329+
margin: 0 auto;
22872330
border-radius: 12px 12px 12px 12px;
22882331
padding: 0 20px;
22892332
box-shadow: var(--shadow-2)
22902333
}
22912334

2335+
.main-content article header {
2336+
clear: both;
2337+
}
2338+
22922339
.navbar-list {
22932340
gap: 30px;
22942341
padding: 0 20px;
@@ -2372,7 +2419,7 @@ textarea.form-input::-webkit-resizer {
23722419
border: 5px solid var(--smoky-black);
23732420
background: hsla(0, 0%, 100%, 0.1);
23742421
border-radius: 20px;
2375-
/* box-shadow: inset 1px 1px 0 hsla(0, 0%, 100%, 0.11), */
2422+
box-shadow: inset 1px 1px 0 hsla(0, 0%, 100%, 0.11),
23762423
inset -1px -1px 0 hsla(0, 0%, 100%, 0.11);
23772424
}
23782425

backend/app/crud.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,24 @@ def get_certificate_by_public_id(db: Session, certificate_id: str):
1111
return db.query(models.Certificate).filter(models.Certificate.certificate_id == certificate_id).first()
1212

1313
def create_certificate(db: Session, cert_in: schemas.CertificateCreate):
14-
year = datetime.now().year
15-
cert_id = generate_certificate_id(year)
14+
year = datetime.utcnow().year
15+
16+
while True:
17+
cert_id = generate_certificate_id(year)
18+
if not get_certificate_by_public_id(db, cert_id):
19+
break
20+
1621
cert = models.Certificate(
1722
certificate_id=cert_id,
1823
student_name=cert_in.student_name,
1924
course_title=cert_in.course_title,
2025
completion_date=cert_in.completion_date,
2126
duration_hours=cert_in.duration_hours,
22-
issuer=cert_in.issuer,
23-
instructor=cert_in.instructor,
24-
status=models.CertificateStatus.valid
27+
issuer=cert_in.issuer or "MathCodeLab",
28+
instructor=cert_in.instructor or "Mohammad Orabe",
29+
status=models.CertificateStatus.valid,
2530
)
31+
2632
db.add(cert)
2733
db.commit()
2834
db.refresh(cert)

backend/app/database.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
from sqlalchemy import create_engine
33
from sqlalchemy.orm import sessionmaker, declarative_base
44

5-
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./certificates.db")
5+
DATABASE_URL = os.getenv("DATABASE_URL")
6+
if not DATABASE_URL:
7+
raise RuntimeError("DATABASE_URL is not set")
68

7-
# SQLite needs check_same_thread=False.
8-
# PostgreSQL does not need extra connect_args.
99
connect_args = {}
1010

1111
if DATABASE_URL.startswith("sqlite"):

backend/app/main.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from starlette.responses import JSONResponse
66
from datetime import datetime
77
from . import models, schemas, crud, database, security
8+
from fastapi import Request
89

910
app = FastAPI(title="MathCodeLab Certificate Verification API")
1011
models.Base.metadata.create_all(bind=database.engine)
@@ -19,6 +20,14 @@
1920
allow_headers=["*"],
2021
)
2122

23+
@app.middleware("http")
24+
async def protect_docs(request: Request, call_next):
25+
if request.url.path.startswith("/docs") or request.url.path.startswith("/openapi"):
26+
auth = request.headers.get("authorization")
27+
if auth != f"Bearer {os.getenv('ADMIN_API_KEY')}":
28+
return JSONResponse(status_code=401, content={"detail": "Unauthorized"})
29+
return await call_next(request)
30+
2231
@app.get("/")
2332
def root():
2433
return {
@@ -83,3 +92,11 @@ def revoke_certificate(
8392
if not cert:
8493
raise HTTPException(status_code=404, detail="Certificate not found")
8594
return cert
95+
96+
@app.get("/admin/certificates")
97+
def list_certificates(
98+
db: Session = Depends(get_db),
99+
api_key: str = Depends(security.verify_api_key)
100+
):
101+
certs = db.query(models.Certificate).all()
102+
return certs

backend/app/models.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ class Certificate(Base):
1515
course_title = Column(String, nullable=False)
1616
completion_date = Column(String, nullable=False)
1717
duration_hours = Column(Integer, nullable=False)
18+
attendance_percentage = Column(Integer, nullable=True)
19+
assignment_completion_percentage = Column(Integer, nullable=True)
20+
course_level = Column(String, nullable=True)
21+
course_format = Column(String, nullable=True)
22+
instruction_language = Column(String, nullable=True)
23+
1824
issuer = Column(String, default="MathCodeLab", nullable=False)
1925
instructor = Column(String, default="Mohammad Orabe", nullable=False)
2026
status = Column(Enum(CertificateStatus), default=CertificateStatus.valid, nullable=False)

backend/app/schemas.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ class CertificateVerificationResponse(BaseModel):
3232
verified_at: Optional[str] = None
3333
revocation_reason: Optional[str] = None
3434
message: Optional[str] = None
35-
35+
attendance_percentage: Optional[int] = None
36+
assignment_completion_percentage: Optional[int] = None
37+
course_level: Optional[str] = None
38+
course_format: Optional[str] = None
39+
instruction_language: Optional[str] = None
40+
3641
class CertificateRevoke(BaseModel):
3742
revocation_reason: Optional[str] = None

backend/app/security.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import os
2-
from fastapi import Header, HTTPException, status, Depends
2+
from fastapi import Header, HTTPException, status
33

44
def verify_api_key(authorization: str = Header(...)):
55
api_key = os.getenv("ADMIN_API_KEY")

backend/scripts/create_certificate.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,30 @@
77
def main():
88
db = next(database.get_db())
99
print("Enter certificate details:")
10+
1011
student_name = input("Student name: ")
1112
course_title = input("Course title: ")
1213
completion_date = input("Completion date (YYYY-MM-DD): ")
1314
duration_hours = int(input("Duration (hours): "))
15+
attendance_percentage = int(input("Attendance percentage (0-100): "))
16+
assignment_completion_percentage = int(input("Assignment completion percentage (0-100): "))
17+
course_level = input("Course level (e.g. Master-level, Bachelor-level, High School-level): ")
18+
course_format = input("Course format (e.g. Online (via Zoom), In-person, Hybrid): ")
19+
instruction_language = input("Instruction language (e.g. English, German, Arabic): ")
1420
cert_in = schemas.CertificateCreate(
1521
student_name=student_name,
1622
course_title=course_title,
1723
completion_date=completion_date,
18-
duration_hours=duration_hours
24+
duration_hours=duration_hours,
25+
attendance_percentage=attendance_percentage,
26+
assignment_completion_percentage=assignment_completion_percentage,
27+
course_level=course_level,
28+
course_format=course_format,
29+
instruction_language=instruction_language
1930
)
2031
cert = crud.create_certificate(db, cert_in)
2132
print(f"Created certificate: {cert.certificate_id}")
33+
print(f"Verification URL: https://mathcodelab.de/verify/?id={cert.certificate_id}")
2234

2335
if __name__ == "__main__":
2436
main()

backend/seed_database.py

Lines changed: 0 additions & 49 deletions
This file was deleted.

backend/view_db.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)