@@ -111,6 +111,82 @@ async def create_certificate(certificate_data: dict) -> CertificateResponse:
111111 message = "์๋ฃ์ฆ ๋ฐ๊ธ์ ์๋ฃํ์ง ๋ชปํ์ต๋๋ค. ๊ด๋ฆฌ์์๊ฒ ๋ฌธ์ํด์ฃผ์ธ์." ,
112112 data = None ,
113113 )
114+
115+ @staticmethod
116+ async def verify_certificate (file_bytes : bytes ) -> dict :
117+ """์๋ฃ์ฆ ๊ฒ์ฆ"""
118+ try :
119+ pdf_generator = PDFGenerator ()
120+ watermark_text = pdf_generator .extract_watermark_from_pdf (file_bytes )
121+
122+ if not watermark_text :
123+ return {
124+ "valid" : False ,
125+ "message" : "์๋ฃ์ฆ์์ ์ํฐ๋งํฌ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค."
126+ }
127+
128+ # ๊ธฐ๋ณธ ๊ฒ์ฆ (PSEUDOLAB ์ ๋์ฌ ํ์ธ)
129+ if not watermark_text .startswith ("PSEUDOLAB" ):
130+ return {
131+ "valid" : False ,
132+ "message" : "์ ํจํ์ง ์์ ์๋ฃ์ฆ ์ํฐ๋งํฌ์
๋๋ค." ,
133+ "debug_text" : watermark_text
134+ }
135+
136+ # ์๋ฃ์ฆ ๋ฒํธ ์ถ์ถ (PSEUDOLAB_CERT-XXXX ํฌ๋งท ๊ธฐ๋)
137+ cert_number = ""
138+ if "_" in watermark_text :
139+ cert_number = watermark_text .split ("_" )[1 ]
140+
141+ # ๋ฒํธ๊ฐ ์๋ ๊ฒฝ์ฐ (ํ
์คํธ์ฉ ๋ฑ)
142+ if not cert_number :
143+ return {
144+ "valid" : True ,
145+ "message" : "์ํฐ๋งํฌ๊ฐ ํ์ธ๋์์ต๋๋ค (ํ
์คํธ์ฉ/๋ฒํธ์์)." ,
146+ "watermark_text" : watermark_text
147+ }
148+
149+ # Notion ์ค๋ฐ์ดํฐ ์กฐํ
150+ notion_client = NotionClient ()
151+ cert_page = await notion_client .get_certificate_by_number (cert_number )
152+
153+ if not cert_page :
154+ return {
155+ "valid" : False ,
156+ "message" : f"์๋ฃ์ฆ ๋ฒํธ({ cert_number } )์ ํด๋นํ๋ ๋ฐ๊ธ ๊ธฐ๋ก์ ์ฐพ์ ์ ์์ต๋๋ค."
157+ }
158+
159+ # Notion ๊ฒฐ๊ณผ ํ์ฑ
160+ props = cert_page .get ("properties" , {})
161+
162+ name = props .get ("Name" , {}).get ("title" , [{}])[0 ].get ("plain_text" , "์ ์ ์์" )
163+ course = props .get ("Course Name" , {}).get ("rich_text" , [{}])[0 ].get ("plain_text" , "์ ์ ์์" )
164+ season = props .get ("Season" , {}).get ("select" , {}).get ("name" , "์ ์ ์์" )
165+ issue_date = props .get ("Issue Date" , {}).get ("date" , {}).get ("start" , "์ ์ ์์" )
166+ status = props .get ("Certificate Status" , {}).get ("status" , {}).get ("name" , "์ ์ ์์" )
167+
168+ return {
169+ "valid" : True ,
170+ "message" : "์๋ฃ์ฆ ์ง์ ํ์ธ์ ์ฑ๊ณตํ์ต๋๋ค." ,
171+ "data" : {
172+ "name" : name ,
173+ "course" : course ,
174+ "season" : season ,
175+ "issue_date" : issue_date ,
176+ "certificate_number" : cert_number ,
177+ "status" : status
178+ }
179+ }
180+
181+ except Exception as e :
182+ if "์ํฐ๋งํฌ๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค" in str (e ):
183+ logger .info (f"์๋ฃ์ฆ ๊ฒ์ฆ ์คํจ (์ํฐ๋งํฌ ์์): { str (e )} " )
184+ else :
185+ logger .exception ("์๋ฃ์ฆ ๊ฒ์ฆ ์ค ์์์น ๋ชปํ ์ค๋ฅ" )
186+ return {
187+ "valid" : False ,
188+ "message" : "์๋ฃ์ฆ ๊ฒ์ฆ ์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."
189+ }
114190
115191 @staticmethod
116192 async def _reissue_certificate (
@@ -147,7 +223,26 @@ async def _reissue_certificate(
147223 "๊ธฐ์กด ์๋ฃ์ฆ ๋ฒํธ ์์. ์๋ก ์์ฑ" ,
148224 extra = {"applicant_name" : certificate_data ["applicant_name" ]},
149225 )
150- existing_cert_number = f"CERT-{ datetime .now ().year } { participation_info ['project_code' ]} { str (uuid .uuid4 ())[:2 ].upper ()} "
226+ # ๊ธฐ์กด ์๋ฃ์ฆ ๋ฒํธ๊ฐ ์์ผ๋ฉด DB ID(Unique ID) ๊ธฐ๋ฐ์ผ๋ก ์์ฑ
227+ existing_data = existing_cert .get ("existing_data" , {})
228+ properties = existing_data .get ("properties" , {})
229+
230+ # ๋ก๊ทธ์์ ํ์ธ๋ ๊ตฌ์กฐ: properties['ID']['unique_id']['number']
231+ id_prop = properties .get ("ID" , {})
232+ unique_identifier = None
233+
234+ if id_prop .get ("type" ) == "unique_id" :
235+ unique_identifier = str (id_prop .get ("unique_id" , {}).get ("number" ))
236+
237+ if not unique_identifier :
238+ # fallback: ํน์ ID ์ปฌ๋ผ๋ช
์ด ๋ค๋ฅผ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด ๊ธฐ์กด ๋ฐฉ์ ์ ์ง
239+ for prop_val in properties .values ():
240+ if prop_val .get ("type" ) == "unique_id" :
241+ unique_identifier = str (prop_val .get ("unique_id" , {}).get ("number" ))
242+ break
243+
244+
245+ existing_cert_number = f"CERT-{ datetime .now ().year } { participation_info ['project_code' ]} { unique_identifier .upper ()} "
151246 logger .info (
152247 "์๋ก์ด ์๋ฃ์ฆ ๋ฒํธ ์์ฑ" ,
153248 extra = {"certificate_number" : existing_cert_number },
@@ -170,14 +265,17 @@ async def _reissue_certificate(
170265
171266 # ์ด๋ฉ์ผ ์ฌ๋ฐ์ก
172267 email_sender = EmailSender ()
173- await email_sender .send_certificate_email (
268+ email_sent = await email_sender .send_certificate_email (
174269 recipient_email = certificate_data ["recipient_email" ],
175270 recipient_name = certificate_data ["applicant_name" ],
176271 course_name = certificate_data ["course_name" ],
177272 season = certificate_data ["season" ],
178273 role = participation_info ["user_role" ],
179274 certificate_bytes = pdf_bytes
180275 )
276+
277+ if not email_sent :
278+ raise Exception ("์ฌ๋ฐ๊ธ ์ด๋ฉ์ผ ๋ฐ์ก ์คํจ" )
181279
182280 # ์ฌ๋ฐ๊ธ ๋ก๊ทธ ๊ธฐ๋ก
183281 reissue_log = await notion_client .log_certificate_reissue (
@@ -258,8 +356,27 @@ async def _create_new_certificate(
258356 season = certificate_data ["season" ]
259357 )
260358
261- # TODO: ์์ ๊ฐ, ์ถํ ์์ ํ์
262- certificate_number = f"CERT-{ datetime .now ().year } { participation_info ['project_code' ]} { str (uuid .uuid4 ())[:2 ].upper ()} "
359+ # DB ID(Unique ID)๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฃ์ฆ ๋ฒํธ ์์ฑ
360+ properties = certificate_request .get ("properties" , {})
361+ unique_identifier = None
362+
363+ # ๋ก๊ทธ์์ ํ์ธ๋ ๊ตฌ์กฐ: properties['ID']['unique_id']['number']
364+ id_prop = properties .get ("ID" , {})
365+ if id_prop .get ("type" ) == "unique_id" :
366+ unique_identifier = str (id_prop .get ("unique_id" , {}).get ("number" ))
367+
368+ if not unique_identifier :
369+ # fallback: ํน์ ID ์ปฌ๋ผ๋ช
์ด ๋ค๋ฅผ ๊ฒฝ์ฐ๋ฅผ ๋๋นํด ์ํ ๊ฒ์
370+ for prop_val in properties .values ():
371+ if prop_val .get ("type" ) == "unique_id" :
372+ unique_identifier = str (prop_val .get ("unique_id" , {}).get ("number" ))
373+ break
374+
375+ if not unique_identifier :
376+ # fallback: Page ID์ ๋ง์ง๋ง 5์๋ฆฌ
377+ unique_identifier = request_id .replace ("-" , "" )[- 5 :]
378+
379+ certificate_number = f"CERT-{ datetime .now ().year } { participation_info ['project_code' ]} { unique_identifier .upper ()} "
263380 issue_date = datetime .now ().strftime ("%Y-%m-%d" )
264381
265382 # PDF ์๋ฃ์ฆ ์์ฑ
@@ -275,14 +392,17 @@ async def _create_new_certificate(
275392 )
276393 # ์ด๋ฉ์ผ ๋ฐ์ก
277394 email_sender = EmailSender ()
278- await email_sender .send_certificate_email (
395+ email_sent = await email_sender .send_certificate_email (
279396 recipient_email = certificate_data ["recipient_email" ],
280397 recipient_name = certificate_data ["applicant_name" ],
281398 course_name = certificate_data ["course_name" ],
282399 season = certificate_data ["season" ],
283400 role = participation_info ["user_role" ],
284401 certificate_bytes = pdf_bytes
285402 )
403+
404+ if not email_sent :
405+ raise Exception ("์ด๋ฉ์ผ ๋ฐ์ก ์คํจ" )
286406
287407 # ์๋ฃ์ฆ ์ํ ์
๋ฐ์ดํธ
288408 logger .info (
0 commit comments