From f6ed6bd574a2baeacf54b54854db209a52a80167 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Sat, 25 Oct 2025 13:48:21 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A4=91=EB=B3=B5=20=EC=8B=A0=EA=B3=A0?= =?UTF-8?q?=20=EC=B6=94=EC=A0=81=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20-=20=EC=8B=A0=EA=B3=A0=EC=9E=90=20=EC=9D=B8?= =?UTF-8?q?=EC=A7=80=EB=8F=84=20=EB=B0=8F=20=EB=8C=80=EC=9D=91=20=EC=86=8D?= =?UTF-8?q?=EB=8F=84=20=EB=B6=84=EC=84=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ”„ Duplicate Tracking System: - ์ค‘๋ณต ์‹ ๊ณ  ์‹œ ์›๋ณธ ์ด์Šˆ์— ์‹ ๊ณ ์ž ์ •๋ณด ์ž๋™ ์ถ”๊ฐ€ - ์‹ ๊ณ  ์ธ์ง€๋„ ๋ฐ ๋Œ€์‘ ์†๋„ ๋ถ„์„์„ ์œ„ํ•œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ - ๋’ท๋ถ์น˜๋Š” ์‹ ๊ณ ์ž ํŒŒ์•… ๋ฐ ์ง‘๊ณ„ ๊ธฐ๋Šฅ ๐Ÿ“Š Database Schema Updates: - duplicate_of_issue_id: ์ค‘๋ณต ๋Œ€์ƒ ์ด์Šˆ ID (FK) - duplicate_reporters: ์ค‘๋ณต ์‹ ๊ณ ์ž ๋ชฉ๋ก (JSONB ๋ฐฐ์—ด) - 015_add_duplicate_tracking.sql ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰ - GIN ์ธ๋ฑ์Šค๋กœ JSONB ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๐Ÿ”ง Backend Enhancements: - ์ค‘๋ณต ํ๊ธฐ ์‹œ ๋Œ€์ƒ ์ด์Šˆ์— ์‹ ๊ณ ์ž ์ •๋ณด ์ž๋™ ์ถ”๊ฐ€ - ์‹ ๊ณ ์ž ์ค‘๋ณต ์ฒดํฌ ๋กœ์ง (๋™์ผ ์‚ฌ์šฉ์ž ์žฌ์ถ”๊ฐ€ ๋ฐฉ์ง€) - /api/inbox/management-issues API ์ถ”๊ฐ€ (์ค‘๋ณต ์„ ํƒ์šฉ) - ํ”„๋กœ์ ํŠธ๋ณ„ ๊ด€๋ฆฌํ•จ ์ด์Šˆ ๋ชฉ๋ก ์กฐํšŒ ์ง€์› ๐ŸŽจ Frontend UI Improvements: - ์ค‘๋ณต ์„ ํƒ ์‹œ ๊ด€๋ฆฌํ•จ ์ด์Šˆ ๋ชฉ๋ก ํ‘œ์‹œ - ํ”„๋กœ์ ํŠธ๋ณ„ ํ•„ํ„ฐ๋ง๋œ ์ด์Šˆ ๋ชฉ๋ก ์ œ๊ณต - ๊ฐ„๋‹จํ•œ ์ด์Šˆ ์ •๋ณด ํ‘œ์‹œ (์ œ๋ชฉ, ์นดํ…Œ๊ณ ๋ฆฌ, ์‹ ๊ณ ์ž, ์ค‘๋ณต ๊ฑด์ˆ˜) - ์ง๊ด€์ ์ธ ์„ ํƒ UI (ํด๋ฆญ์œผ๋กœ ์„ ํƒ, ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ) ๐Ÿ“‹ Duplicate Selection Process: 1. ํ๊ธฐ ์‚ฌ์œ ๋กœ '์ค‘๋ณต' ์„ ํƒ 2. ๋™์ผ ํ”„๋กœ์ ํŠธ์˜ ๊ด€๋ฆฌํ•จ ์ด์Šˆ ๋ชฉ๋ก ์ž๋™ ๋กœ๋“œ 3. ์ค‘๋ณต ๋Œ€์ƒ ์ด์Šˆ ์„ ํƒ (ํ•„์ˆ˜) 4. ํ™•์ธ ์‹œ ์‹ ๊ณ ์ž ์ •๋ณด๊ฐ€ ์›๋ณธ ์ด์Šˆ์— ์ถ”๊ฐ€ ๐Ÿ’พ Data Structure: - duplicate_reporters: [ { user_id: 123, username: 'reporter1', full_name: '์‹ ๊ณ ์ž1', report_date: '2024-10-25T14:30:00', added_at: '2024-10-25T15:00:00' } ] ๐Ÿ” Analytics Features: - ์ค‘๋ณต ์‹ ๊ณ  ๊ฑด์ˆ˜ ํ‘œ์‹œ - ์‹ ๊ณ ์ž๋ณ„ ์‹ ๊ณ  ์‹œ์  ์ถ”์  - ์›๋ณธ ์ด์Šˆ ๋Œ€๋น„ ์ง€์—ฐ ์‹ ๊ณ  ๋ถ„์„ ๊ฐ€๋Šฅ - ๋ถ€์„œ๋ณ„/์‚ฌ์šฉ์ž๋ณ„ ์ธ์ง€๋„ ๋ถ„์„ ๋ฐ์ดํ„ฐ ์ œ๊ณต ๐Ÿš€ User Experience: - ์ค‘๋ณต ์ฒ˜๋ฆฌ ์‹œ ๋ช…ํ™•ํ•œ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€ - ๊ด€๋ฆฌํ•จ ์ด์Šˆ ๋ชฉ๋ก ์‹ค์‹œ๊ฐ„ ๋กœ๋“œ - ์„ ํƒ ํ•„์ˆ˜ ๊ฒ€์ฆ (์ค‘๋ณต ๋Œ€์ƒ ๋ฏธ์„ ํƒ ์‹œ ๊ฒฝ๊ณ ) - ์ฒ˜๋ฆฌ ์™„๋ฃŒ ํ›„ ์ž๋™ ๋ชฉ๋ก ์ƒˆ๋กœ๊ณ ์นจ Expected Result: โœ… ์ค‘๋ณต ์‹ ๊ณ  ์‹œ ์‹ ๊ณ ์ž ์ •๋ณด ์ž๋™ ์ถ”์  โœ… ์‹ ๊ณ  ์ธ์ง€๋„ ๋ฐ ๋Œ€์‘ ์†๋„ ๋ถ„์„ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ โœ… ์ง๊ด€์ ์ธ ์ค‘๋ณต ๋Œ€์ƒ ์„ ํƒ UI โœ… ๋ถ€์„œ๋ณ„/๊ฐœ์ธ๋ณ„ ์‹ ๊ณ  ํŒจํ„ด ๋ถ„์„ ๊ธฐ๋ฐ˜ ๋งˆ๋ จ --- .../__pycache__/models.cpython-311.pyc | Bin 10090 -> 10491 bytes .../__pycache__/schemas.cpython-311.pyc | Bin 16320 -> 16574 bytes backend/database/models.py | 7 +- backend/database/schemas.py | 5 + .../migrations/015_add_duplicate_tracking.sql | 100 +++++++++++++ .../routers/__pycache__/inbox.cpython-311.pyc | Bin 14276 -> 17900 bytes backend/routers/inbox.py | 61 ++++++++ frontend/issues-inbox.html | 138 +++++++++++++++++- 8 files changed, 304 insertions(+), 7 deletions(-) create mode 100644 backend/migrations/015_add_duplicate_tracking.sql diff --git a/backend/database/__pycache__/models.cpython-311.pyc b/backend/database/__pycache__/models.cpython-311.pyc index 02df15bb0b067df87233111f26dd18fad6a176a7..72796a919cb9aabb52a86d397ff63a85ba1b41de 100644 GIT binary patch delta 1220 zcmZ{j-%Aux6vyXI>#pnQ?Ao~Mj_bN=sau<0C~M*s%E2tWWt0Y<myxE{;@tk^85y-TqbfJdY2KBg-`+(arzU4V?liryz z;-=aOB~L-BG=a9nw>0b~gl1#pWJXmhCy-*x@ zC#TD#g?rST6T3jJZH)vcLrTzlht6qGrx^Evk8xNA_e+4QfH4X;kFU{3Iy-t>#gX8Y zhKE49&ZM+})ye3L>es@G%KUupI$RD86@W@Wrc2PyzimpqDeOX?v`v|viiF34^z-Pr zpXKcjD`{ze8mm)LJfmWbd@U*!)4BUfbex!qo%{tpQC+-&>Pst^I#!z2yVtwp^GQ!z z%F~u~w5J^Hy1~9JyOyd}Z1J3UjxM((<(8D(q8p0HCwp~g?@iq@nzW3jETgf`ZBbr~ zES-%H>!LR)dQ+k|c6nPWd)m5UiihF@de^Al>DQ$|QVOJ`K&%(8Ou_oeE3=(6%6mpu z8kMw^l%Eg7ep!E5J)vHiCO)T@k9PFXDuGuPV1z7}T$F=gngIcT6)*@0K}&O3I6#cp z96B0;JQ9139%LK9kRsuXp_{s!? z23Qcy)r1z7!T&CR{fzrNW_dwKpS$4y``kaXCRpPofbE?f;fL^=4X^?PFtmEAfSRc0fq7r&#gh~Anj(Q_=s ZHSA)M(vivmqoUUv1MQr5g)UMbl!55~@|Gkzk`q1A;cOrHGalFM4Re zOM{ofh>Bu!ixzWG_OAHx2Q*M5e}M;~kU&pu5$Bnz3sG=~{joF8%)HP0?z@}$o>pHu z9ETErr(2J>_H<<=Js9OY^LvJ&Mlq9fl3)0t$cvECF`FU}LVbJ&(`4 z`Q9}`{DQS^pZrB;AJtJa;XWfT4;PgpmE7}G@2T@%<8gK;%X_vHAhY68h?Lj zaiOV2syjQBBSr19q`Qnue5WzTF=I?|o-?Mga1mJK*in{@JBy_`cFh|nomIS(6UJGH z8DIv;0arQF`iK$6WDtk|oynUodM^;ud?9wq*C%h0S@5kB8xKSWovDU1wdqWi&o@>7 z%B^SPZ*v=JvY{q7)ns`J!(0pWlU+o+$vy|ee)!vWDjdPWembk{N6@QTu@68guxKci zxPi}Z;5wiIR{)g2@Bw}x0Ek$?1NJhsK{@kwUyHC2AT~;M>Y&sHHwo(SuNW3h^7`jCWUJ2#!7>#4r@po%25y} zzK}ZjWCORjQp^<#wJODQPSHCG;Z;d3Qn2U)DQHTnBOAmzXICV0DAd^%TrjS=!=au7;b(Z>ezLff_xM5Y^A&CfjKEF~ zmz~Z{y8;Q0`uwsrTulZ$7W%#&_P>^wnG=%5Xz-vPNOD= zm!hR7BC8JWsr=RqjV5aGw4s`v{YiGLdOsfyXJ7qClCgV z)1qT9J3(2KLi@ISXE+bxX+WeO6qhCJfos&d^;JeR>8qmqTfK4|0`L`CoHiC;J;qC! zNNmG=ySE&ykNL7<7lC5Q828-M~p4Ghw<(=Cs{1iq$n*IqV)iYe~;U2}>1 zzNJ&;?eay005h~y-p4MAQa`R(EW83?7jPBO0uyww$}Eq=1Tr+{?q=hnkfzEf*k`Cy znS=~{4`Ub*GdU@;4_2NwT!v4aFOHs|#wv?N>_rxlR5Wf?=1J+KH;=tc9#;`vt=dU7 z)dghmOzTE*(&xYnG@?-`>uJ_}!?*W&Z612lTP9CI1n$x~Z=6l>`ktl3)ot=L1mHLN zp}LMui^3h-PHS&Mu!|>4!vq#-VOu*(ighfF*jdCdzJ! z@S{c_75IwS7v%Ikm!Clh@F!o93cBJ}=u@ASWq1=?RvUO5F~B1jYYn_D8W^bUVWTur z8`aEnSwl{L4S%&^t682!IPfpU{D;{r{pC;Y6Ms7KKM~_ECR@xcCQ(eC7!WZ6;!(xZ zg;P*P??j){*oyK*0pf@5p(nm~G5TkE4Ib19+F$q=1@vv(n=jsDA+O N@|u*JnrhJH{0CVMj7b0h delta 1186 zcmZXTTS!z<6o$_po2iaC8m7&RI@)A5<4n$IW||j_tPr|sMRa*7Cg>q5vvCsO~UTg2Q*Xchb?uM+L zQBk@kpS5LwwjWFEvHqT6>J4)i4K6lG|f7K_3}tEpCC*lRPkB^C?3 zO)A6Z+3{HF%$g*FnsMA&X!1njg|h&gUD451j6@ufqjC|7l8d4qPYTF)G)Wc08Ft)E z{wu0+BgGeai2hTEsm!N`IVI|#&kTz&wpi^rKl5};d=e&zK zL1R9l(w$P9Yvec4jTP>#q7m(8JA4^&c;gP}Efk3!*gQ$1MTRLw$g|kdMByaSOq?PX zOL%k?`{R(}Hsia;t+&xmT!%Y-gJ@HQYDxcUz94OP(cmqIGkvPKg)iR2qFvRc*7qr{ zi_6v!R|pHyja`15-pMI(4~;TU-`w@0b8N?RL@N(uze*S40Vih&)e2W-@~)W6{8sT* z=x*fu9S-%avZ$o0I%6`E_HYgHP$u1hP=*6D0*0l7=5u%)2%2xn=}UPWKelJiLapB_ z8ZhK_;Cg1YFyygHus>_H-pd8VTRh3i6}{@bv*tBf?oe>5>*(i{_<-m0%0<8ODaqau z*+=0vaRYtXl~Q4A4bVV*!t(ih!~h272gN0%QqxzlnK~9NZsLDMc2Js{J;wJUa zgsG^0R;|i@RIR33O7%sBilc5--J2@4K|fV2RSk7UbsDwJLHx{15>Jizg{2W|Z6bL{ hn~1v7VOmGVrJM+nIiyX*$-H4&N5&=HFbX3h{sF9^L?ZwI diff --git a/backend/database/models.py b/backend/database/models.py index dd9983a..6760c4a 100644 --- a/backend/database/models.py +++ b/backend/database/models.py @@ -115,10 +115,15 @@ class Issue(Base): original_data = Column(JSONB) # ์›๋ณธ ๋ฐ์ดํ„ฐ ๋ณด์กด modification_log = Column(JSONB, default=lambda: []) # ์ˆ˜์ • ์ด๋ ฅ + # ์ค‘๋ณต ์‹ ๊ณ  ์ถ”์  ์‹œ์Šคํ…œ + duplicate_of_issue_id = Column(Integer, ForeignKey("issues.id")) # ์ค‘๋ณต ๋Œ€์ƒ ์ด์Šˆ ID + duplicate_reporters = Column(JSONB, default=lambda: []) # ์ค‘๋ณต ์‹ ๊ณ ์ž ๋ชฉ๋ก + # Relationships reporter = relationship("User", back_populates="issues", foreign_keys=[reporter_id]) - reviewer = relationship("User", foreign_keys=[reviewed_by_id]) + reviewer = relationship("User", foreign_keys=[reviewed_by_id], overlaps="reviewed_issues") project = relationship("Project", back_populates="issues") + duplicate_of = relationship("Issue", remote_side=[id], foreign_keys=[duplicate_of_issue_id]) class Project(Base): __tablename__ = "projects" diff --git a/backend/database/schemas.py b/backend/database/schemas.py index 8e59d1d..d443933 100644 --- a/backend/database/schemas.py +++ b/backend/database/schemas.py @@ -124,6 +124,10 @@ class Issue(IssueBase): original_data: Optional[Dict[str, Any]] = None modification_log: Optional[List[Dict[str, Any]]] = None + # ์ค‘๋ณต ์‹ ๊ณ  ์ถ”์  ์‹œ์Šคํ…œ + duplicate_of_issue_id: Optional[int] = None + duplicate_reporters: Optional[List[Dict[str, Any]]] = None + class Config: from_attributes = True @@ -132,6 +136,7 @@ class IssueDisposalRequest(BaseModel): """๋ถ€์ ํ•ฉ ํ๊ธฐ ์š”์ฒญ""" disposal_reason: DisposalReasonType = DisposalReasonType.duplicate custom_disposal_reason: Optional[str] = None + duplicate_of_issue_id: Optional[int] = None # ์ค‘๋ณต ๋Œ€์ƒ ์ด์Šˆ ID class IssueReviewRequest(BaseModel): """๋ถ€์ ํ•ฉ ๊ฒ€ํ†  ๋ฐ ์ˆ˜์ • ์š”์ฒญ""" diff --git a/backend/migrations/015_add_duplicate_tracking.sql b/backend/migrations/015_add_duplicate_tracking.sql new file mode 100644 index 0000000..11e161b --- /dev/null +++ b/backend/migrations/015_add_duplicate_tracking.sql @@ -0,0 +1,100 @@ +-- 015_add_duplicate_tracking.sql +-- ์ค‘๋ณต ์‹ ๊ณ  ์ถ”์  ์‹œ์Šคํ…œ ์ถ”๊ฐ€ + +BEGIN; + +-- migration_log ํ…Œ์ด๋ธ” ์ƒ์„ฑ (๋ฉฑ๋“ฑ์„ฑ) +CREATE TABLE IF NOT EXISTS migration_log ( + id SERIAL PRIMARY KEY, + migration_file VARCHAR(255) NOT NULL UNIQUE, + executed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + status VARCHAR(50), + notes TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() +); + +-- ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํŒŒ์ผ ์ด๋ฆ„ +DO $$ +DECLARE + migration_name VARCHAR(255) := '015_add_duplicate_tracking.sql'; + migration_notes TEXT := '์ค‘๋ณต ์‹ ๊ณ  ์ถ”์  ์‹œ์Šคํ…œ: duplicate_of_issue_id, duplicate_reporters ์ปฌ๋Ÿผ ์ถ”๊ฐ€'; + current_status VARCHAR(50); +BEGIN + SELECT status INTO current_status FROM migration_log WHERE migration_file = migration_name; + + IF current_status IS NULL THEN + RAISE NOTICE '--- ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ % ์‹œ์ž‘ ---', migration_name; + + -- issues ํ…Œ์ด๋ธ”์— ์ค‘๋ณต ์ถ”์  ์ปฌ๋Ÿผ ์ถ”๊ฐ€ + -- ์ค‘๋ณต ๋Œ€์ƒ ์ด์Šˆ ID + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'duplicate_of_issue_id') THEN + ALTER TABLE issues ADD COLUMN duplicate_of_issue_id INTEGER; + RAISE NOTICE 'โœ… issues.duplicate_of_issue_id ์ปฌ๋Ÿผ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + ELSE + RAISE NOTICE 'โ„น๏ธ issues.duplicate_of_issue_id ์ปฌ๋Ÿผ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.'; + END IF; + + -- ์ค‘๋ณต ์‹ ๊ณ ์ž ๋ชฉ๋ก (JSONB ๋ฐฐ์—ด) + IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'issues' AND column_name = 'duplicate_reporters') THEN + ALTER TABLE issues ADD COLUMN duplicate_reporters JSONB DEFAULT '[]'::jsonb; + RAISE NOTICE 'โœ… issues.duplicate_reporters ์ปฌ๋Ÿผ์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + ELSE + RAISE NOTICE 'โ„น๏ธ issues.duplicate_reporters ์ปฌ๋Ÿผ์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.'; + END IF; + + -- ์™ธ๋ž˜ ํ‚ค ์ œ์•ฝ ์กฐ๊ฑด ์ถ”๊ฐ€ (duplicate_of_issue_id) + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'issues_duplicate_of_issue_id_fkey') THEN + ALTER TABLE issues ADD CONSTRAINT issues_duplicate_of_issue_id_fkey + FOREIGN KEY (duplicate_of_issue_id) REFERENCES issues(id); + RAISE NOTICE 'โœ… issues_duplicate_of_issue_id_fkey ์™ธ๋ž˜ ํ‚ค ์ œ์•ฝ ์กฐ๊ฑด์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + ELSE + RAISE NOTICE 'โ„น๏ธ issues_duplicate_of_issue_id_fkey ์™ธ๋ž˜ ํ‚ค ์ œ์•ฝ ์กฐ๊ฑด์ด ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.'; + END IF; + + -- ์ธ๋ฑ์Šค ์ถ”๊ฐ€ + IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'issues' AND indexname = 'idx_issues_duplicate_of') THEN + CREATE INDEX idx_issues_duplicate_of ON issues (duplicate_of_issue_id) WHERE duplicate_of_issue_id IS NOT NULL; + RAISE NOTICE 'โœ… idx_issues_duplicate_of ์ธ๋ฑ์Šค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + ELSE + RAISE NOTICE 'โ„น๏ธ idx_issues_duplicate_of ์ธ๋ฑ์Šค๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.'; + END IF; + + -- JSONB ์ธ๋ฑ์Šค ์ถ”๊ฐ€ (์ค‘๋ณต ์‹ ๊ณ ์ž ๊ฒ€์ƒ‰ ์„ฑ๋Šฅ ํ–ฅ์ƒ) + IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE tablename = 'issues' AND indexname = 'idx_issues_duplicate_reporters_gin') THEN + CREATE INDEX idx_issues_duplicate_reporters_gin ON issues USING GIN (duplicate_reporters); + RAISE NOTICE 'โœ… idx_issues_duplicate_reporters_gin ์ธ๋ฑ์Šค๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'; + ELSE + RAISE NOTICE 'โ„น๏ธ idx_issues_duplicate_reporters_gin ์ธ๋ฑ์Šค๊ฐ€ ์ด๋ฏธ ์กด์žฌํ•ฉ๋‹ˆ๋‹ค.'; + END IF; + + -- ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒ€์ฆ + DECLARE + col_count INTEGER; + idx_count INTEGER; + fk_count INTEGER; + BEGIN + SELECT COUNT(*) INTO col_count FROM information_schema.columns WHERE table_name = 'issues' AND column_name IN ('duplicate_of_issue_id', 'duplicate_reporters'); + SELECT COUNT(*) INTO idx_count FROM pg_indexes WHERE tablename = 'issues' AND indexname IN ('idx_issues_duplicate_of', 'idx_issues_duplicate_reporters_gin'); + SELECT COUNT(*) INTO fk_count FROM pg_constraint WHERE conname = 'issues_duplicate_of_issue_id_fkey'; + + RAISE NOTICE '=== ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒ€์ฆ ๊ฒฐ๊ณผ ==='; + RAISE NOTICE '์ถ”๊ฐ€๋œ ์ปฌ๋Ÿผ: %/2๊ฐœ', col_count; + RAISE NOTICE '์ƒ์„ฑ๋œ ์ธ๋ฑ์Šค: %/2๊ฐœ', idx_count; + RAISE NOTICE '์ƒ์„ฑ๋œ FK: %/1๊ฐœ', fk_count; + + IF col_count = 2 AND idx_count = 2 AND fk_count = 1 THEN + RAISE NOTICE 'โœ… ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค!'; + INSERT INTO migration_log (migration_file, status, notes) VALUES (migration_name, 'SUCCESS', migration_notes); + ELSE + RAISE EXCEPTION 'โŒ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒ€์ฆ ์‹คํŒจ!'; + END IF; + END; + + ELSIF current_status = 'SUCCESS' THEN + RAISE NOTICE 'โ„น๏ธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ %๋Š” ์ด๋ฏธ ์„ฑ๊ณต์ ์œผ๋กœ ์‹คํ–‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์Šคํ‚ตํ•ฉ๋‹ˆ๋‹ค.', migration_name; + ELSE + RAISE NOTICE 'โš ๏ธ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ %๋Š” ์ด์ „์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜๋™ ํ™•์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.', migration_name; + END IF; +END $$; + +COMMIT; diff --git a/backend/routers/__pycache__/inbox.cpython-311.pyc b/backend/routers/__pycache__/inbox.cpython-311.pyc index e53ad54a42f8a5bf5d3968cca9c6f8c8d9140c73..0794dd77dc8065dad9a5e34b10db5c25ef3cbd88 100644 GIT binary patch delta 4008 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 delta 1128 zcmZ8fOH30{6rDFSEgcGNu`Lt`rG*0h#ugP4B_huE{v%QUEn9aJB7CPCGX66=iPhXd3WB|74mk` z^tiIJoMXe;^?Sx2x?^(iUxb(5Z9yM5jdH%Ax+ebB@#})R;jj~UW?UIai*qJSz@ZGW zl{v>bZqBmDm|9iD_Z4w#5x2oTa}Zusc*V3BHNj1vz1dONj_UKA%t?7J!Yy+(Y&KND zL$e1HYav^2LsEz3jEvOp2E%=4Ev!1NuEyigkTxL@l6VSH$&K;JR-#>mg^V=9`(&zj_e;{luF=r{5%ejE( zfu@d(VM&o`BDNsYQ+Qb3)}EsoR7)H!03RGSUSm72&`XHdyrhG5VxR}ZPa^a(@RjAR zrCFIkRcqkx`ESCWizWRn2^_$bLeI3u5203ajF>y7$R-?(wCK|;^e3mzL=>G1Z=Vt4 zv6P(aKbFY|vxz88V!^BMtD}|NfTqqt-7*uZ)Kupr;eD+TKJjrVBTUOFj84%5S{?QZ zQ`#G%_(v$Vg5qfubDwbZAaZ 100 else issue.description, + "category": issue.category.value, + "reporter_name": issue.reporter.full_name or issue.reporter.username, + "reviewed_at": issue.reviewed_at.isoformat() if issue.reviewed_at else None, + "duplicate_count": len(issue.duplicate_reporters) if issue.duplicate_reporters else 0 + }) + + return result + + except Exception as e: + raise HTTPException(status_code=500, detail=f"๊ด€๋ฆฌํ•จ ์ด์Šˆ ์กฐํšŒ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}") diff --git a/frontend/issues-inbox.html b/frontend/issues-inbox.html index 0158f61..e4ba2b0 100644 --- a/frontend/issues-inbox.html +++ b/frontend/issues-inbox.html @@ -293,7 +293,7 @@
- @@ -308,6 +308,20 @@ placeholder="ํ๊ธฐ ์‚ฌ์œ ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”...">
+ +
+ +

๋™์ผ ํ”„๋กœ์ ํŠธ์˜ ๊ด€๋ฆฌํ•จ์— ์žˆ๋Š” ์ด์Šˆ ์ค‘ ์ค‘๋ณต ๋Œ€์ƒ์„ ์„ ํƒํ•˜์„ธ์š”:

+ +
+
+ ๊ด€๋ฆฌํ•จ ์ด์Šˆ๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘... +
+
+ + +
+