From 41b557a709cb9fb3e8afaeb373813dc4620d10ae Mon Sep 17 00:00:00 2001 From: hyungi Date: Sat, 25 Oct 2025 07:22:20 +0900 Subject: [PATCH] =?UTF-8?q?Fix:=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B0=84?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99=20=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=EC=95=84?= =?UTF-8?q?=EC=9B=83=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 토큰 저장 키 통일 (access_token으로 일관성 확보) - 일일공수 페이지 API 스크립트 로딩 순서 수정 - 프로젝트 관리 페이지 비활성 프로젝트 표시 문제 해결 - 업로드 카테고리에 '기타' 항목 추가 (백엔드 schemas.py 포함) - 비밀번호 변경 기능 API 연동으로 수정 - 프로젝트 드롭다운 z-index 문제 해결 - CORS 설정 및 Nginx 구성 개선 - 비밀번호 해싱 방식 pbkdf2_sha256으로 변경 (bcrypt 72바이트 제한 해결) --- backend/__pycache__/main.cpython-311.pyc | Bin 3228 -> 3294 bytes .../__pycache__/models.cpython-311.pyc | Bin 5904 -> 6704 bytes .../__pycache__/schemas.cpython-311.pyc | Bin 10859 -> 11888 bytes backend/database/models.py | 1 + backend/database/schemas.py | 1 + backend/main.py | 9 +- .../009_add_project_daily_works_table.sql | 1 + .../routers/__pycache__/auth.cpython-311.pyc | Bin 8683 -> 8961 bytes .../__pycache__/issues.cpython-311.pyc | Bin 8212 -> 8443 bytes .../__pycache__/projects.cpython-311.pyc | Bin 6392 -> 6578 bytes backend/routers/auth.py | 5 + backend/routers/projects.py | 11 +- .../__pycache__/auth_service.cpython-311.pyc | Bin 4668 -> 4811 bytes backend/services/auth_service.py | 9 +- docker-compose.yml | 4 +- frontend/admin.html | 37 +- frontend/check-projects.html | 120 ++++++ frontend/daily-work.html | 42 ++- frontend/index.html | 345 ++++++++++++------ frontend/issue-view.html | 88 +++-- frontend/mobile-fix.html | 323 ++++++++++++++++ frontend/project-management.html | 335 +++++++++++------ frontend/static/js/api.js | 73 +++- frontend/static/js/auth-common.js | 17 +- frontend/static/js/common-header.js | 37 +- frontend/sync-projects-from-db.html | 104 ++++++ nginx/nginx.conf | 32 +- 27 files changed, 1283 insertions(+), 311 deletions(-) create mode 100644 frontend/check-projects.html create mode 100644 frontend/mobile-fix.html create mode 100644 frontend/sync-projects-from-db.html diff --git a/backend/__pycache__/main.cpython-311.pyc b/backend/__pycache__/main.cpython-311.pyc index 2533e85e9984edbfb44c80c1060bf0a5148f49a7..2c02d95f380efc11eb111a05d178cc8738c80484 100644 GIT binary patch delta 572 zcmbOuc~4S(IWI340}vSe`kf)h&cN^(#DM`0DC2X{Ms-<6v2=zMfklideCc4kMsOJ$ z1H)>z$sZV%HF*b;$vSyDiz7Q4SD zVgeEh3Poy@f3nN5S^_y%lVvy@7&SJ>ayT;zfLwixIXxBPz#_xR8#tBBL8@+X#K-3) z=BCES-(oH*%_~B6(k~91-29Z%oK(A_AfRGUBo$BD%*iFes3mp^VQt8`WhQ1ycCZ89{iBz%o__hSjW- z`xuos*D|s&3JNY`1PTB#1f&Q}p2VarD2yg5GWi@(R1{5AY%&+KHMe*wQ;I~YQW{H& z{+T%gBemKQlwL4*2pen2HFS25WvWg!V(O|nsSrpG5=u> z(PWu?fW>jLAnQ9;MW7mu$t-MrQffd}krIed01~%Yit_VIiqwHj{mE z2P;BT$&-Q_*=|HrFliD&ich+lbfGXnv-f*gvBPP ff{72@42;4b8Q2(^zJQ1iU_xkf3wIbJ3n(lAG*sP#6oCq8Dj*10v`8co8=(QVBHD;3`tKk`NDgao%YKDHvxmzs$bx?98+8Jez3z&}w+2 z*J~Nlns$EO`e}S_aH3BikH1828Pm^*j>nAXjM&n0hFK+})Ivu^_9`Q|G8?E?j@)=$ z?l6T|Q4vy#Y)B<4Nlt}Xc354IN9Lx2f*N0E8_-F-z}BM^_!aBcZG+wnFi6AAJP{I%92-P zm1=2is9LIfw@N|5{%@h>iVc|On{U7gRRAh^PiTckssu2c`C--}dG z7Hk1S0~^u?F&80UX-FIlvoQwp z31MJ2E`qs6o!s0ExVV_$=4es^QwPKMKhJa135wlS}5$#Gug6f?JP@DQBT8-7w z)ls`Do>KU-9{0`tsA5?&_pQ<^l3Dv%W0E`>otHpb?YfTz)a?4XsQs~NhYC3qhcqVT zCCx#31&Lmbn!8di$#Q1C_}IH77ax{pml4PYH1N!8Plt8y9l%Z zr$z+A0c8aX$w9~_;0_P~t^-4gA`uT;0Sf9z@QZ-r1MP=~sjnY>cQyX_Z1~qAAA~Kk z+4^bJc=$h8SqWF7(-@cp*jC*P``}f;n4OAv$pvYq)KT;6J|4>NX0zNG!;CnP(5Sil z7Z!^ZtCW}XIwbv<+w6l_FJ%nssyfbM?bg=$?ZhD}$ERhVx|w(%as=r%Rhhiv1cK#? A-~a#s diff --git a/backend/database/__pycache__/schemas.cpython-311.pyc b/backend/database/__pycache__/schemas.cpython-311.pyc index ea07441e366a16413feac0ea3c71f874c8fd46fb..9a2735c5dffd8efc8975bc1c018b24d9083d8650 100644 GIT binary patch delta 2825 zcmZ`*T}+c#818}oKwIbs6llv&`6)jg2qG8|5vMSOiA6zXBu0X?NRhSdYcydPLovadZ)-t)fidH>FP z&i6ezxO^n@lZ*_rPCnoL{m-~1`+BBBOx>$0NF7Si?a}$Sy{YrJ2E?6Io$jt2+B4)~ z*>)*w3wV-b6)f8!W$hbemHwRrI`513yHlSCQAQzsfpy5}Hw>BlM#(qPA;W>RcxoUz z>hq@2U4usy(<6NjEg3RJHT`8MQx?s(SOG$9NVvkv|K_wJs24t z8I1-{1-w=@1MEzI4Uh;$GYsCdm0EbUk{8z>3x_9F1!7Jn8&u6YxFr!8l@P;^D%1U< zhNJm6qlO-)HwZ5WTn~Lf1r?iHL_O`wanh9eu&AY<%x@W*rKFAqEv2=sl9E+T3O1e^ z3`Nxn7}*3c09x5{BS?Uk9#|$sBRlV~zG_Ki)yRtNPy}qDMQfXAXT`dV9hIe6Y)bN| zf~)P6W{7R<1{2UsUt~0i-D{a{v$8g-=To(WJD~|^rK6dfMdw;r1vG2UN%8CH&&EGiY98d7dJsaZczhMVKo|1 zWv$|A>k}1j)@8%~5h;f|^#J|i-a_Bn7109gC(u>u5edT}wgb4A!S^c3nQ_=$_*bBr zsNf+?Idbkg&XHSj00K^qo6<`h3P65BGyMK}s3 zV3Y>(kBOr)3nx9yf5|)!0pK_lJBq}(G;<}Fa|u8)0vH3-(r*rzi^)XToC?mex{2LP zf&@%Zo%67m}U^AydaWZE>4R8ukkbpDv zS>dFJu2m|R_7r7{AXJ=Zp#q43m;msepXGEvF1n#`=(+rrBbV`+_XkynC}H4Dl3TtBkAb`HEeV-0P71f5o_$5@pjW7Q-6AWqQBl zLlNVO;fj5uv`uL5gc+y+-lNB*En^b5Qpg3I1qrxKOJ)6HmKw@0SuTV5 zHsB)tQ0`ZFJ2)P;F^iS9tB?aek|Q!kzm`_fw5MBKWrrPBzT!6f_3}+=h}-vTH(cjP zaPst_uzF(mNF<=1gb6;nhVk%8H6kxRQarg9_Q$Tc&bFJ-1AGcs^1V{%EL^|r7A;Dw zH`aSzj##g@vp3hfz?GNmwnGib%fY8=tuY6?fX_iBT4N3yDf;O(vuo`f*k9()1cUrc zFy2X#9~W|HkNg-g$rRJ#O0qAt30AoYaCG9PwOgK+-1UX|$=Y=t;m}y{_(7k_S5+q; ztPkY6Z(!xQHj&efFGlT%aNP{h=aZ$~LANmnd?iQZguH`Xq!d{591D8wLdiSDOW52l>6r;047C&R66P|t$;n#e3f l`m$~#EEF}YfW4ZK8BM~ytlJ0+`86wGuRfF65^CJy{{h#5Xd3_k delta 2077 zcmZ`)T}+!*819k&(0=r5S6b*-pe=1Fl;SdwY-0l^!8wLlHe@DK*xc5#A%Ej5X5yUu z!~sEAU@t^2+=Um$JB=ZD<(9?7JB{tEzO5h6%)mXKMFt@N$s zG>y6n$#3=2R~{F=YE?y)I;|1&WanfjePq4wE0^AMD*%r50zibm^b|Tm5CKIrXzLUq znzCy2yX~Yk3Pp@<;t&BP)RJE*;%qaWzo4+&N@ydb*~7717-Kawh8hxwPExO3b5vst z2-2i|R8+G^#nB_GIN*M$0WrGiI4t(lRG~%>9cM+7YLywwL8*C}eo)Fq13gko+%cHf z04#t8_;A%h1XRf{+e44T8ogG~A?j$kU`EO1SH}^XU=66HW6nm=#0jlBo8nRh*m5U z%~W0yEluaWW6jty`Ig-ZZgJXLdNv zHVirIqfF6pi~-H^%Qn;ZuBvc2Hrc+EzgD>7d|-~r|8I;^(D$)5ZM$y!-hjBPd<;97 z$99N-R@!j4iFW3oc;7SSbtp~%+NjtQPqT{-IZk-?X3eJTJ(oYP%oTAIjyff5TKWb@ zE{y4?FmW2-1sL8rqZGz~6Lin>qe#hKdL~|>Yu;cz>^BuXb9F=?%jlB9&HNVw9bCQ8 zoljqodW6$Y(Xd6Mg@8u?dfnm>A`D;y^>-zlgKz}ETwy-p-di1~&UgV=bkoQFlk{uR=k80eV753=hKcCKB;X=Z@kPjB3p2sMLG_?Jh>?R=6mTV~~krj`;L5 zJ&qx)0Sr-X$+Q^a7W}2;QH(P+LwgOtZa##>#f(Gh;u5kD0ps*{>7d9yXUa#jWgc+_ zCfaqF045;x19;D`bGd((-F9>89Ef9#(^N1J;xUKI6=A61!H9kXB4C;x1_#9r_ShSm zmV`YH9Z-g$9s$(v^x~Ug?{4NkmD(j({6BNtQ#tw!=C~HXEd3F_BPMn(u)F1r!gva1 z;1;k##gW5ehE4h-bM9O{pTVS*iJXTBSfgOHOU%<~^rkY4$vMC@iCEgWy*VHDF^`q5 zMd$%{l}f~@wd&9WH@E|ad}=gEc**#w;@5@W5Pk&sZs*IH?_9q1 z_@3gcflm{k4L%3lzT86G1-#F^BRVt09L$?!C+Uk+kSJ4DWl$l%?1q`@6B~55#_w%i v^KP5WqUtGNi$PuYRd 0; + diff --git a/backend/routers/__pycache__/auth.cpython-311.pyc b/backend/routers/__pycache__/auth.cpython-311.pyc index 7cb5703ffea56a318d285f3007021c1e71f55912..77a4f57fa463ed97babe31e775c41df44b789050 100644 GIT binary patch delta 1154 zcmZWpO-vI(6rS1bmI~HVY3Uy=<*z@qfX0JC36WqFX#^q|(P&KLjHR$poYusP5z%|h=G)otz4v`HZ<1Z>y?M<1 z)NHOqF!pWy8GCZi?82X41P|aq79+F-b88UWZIp%OQh`9{>Zxnd&)?u0D#~73c(r85P20In$hDN@e!&rk>$b8(y}3l)y`w^vJ3BnUwG>oH z9kt}WjKo`}6ab~XcLySFT2-p$DhQxudsc$e!Q2`I5g*@_!x00ir$OR}S@1BChBA*3 zc!aml*I4QRy@1^wXoC7?;9HCO|J7Q`)FPl36>2gf0sc+yYzb0N(cefyWfg6OZtX%O zymfPY71!~3*|EPBeA|n@)?%k3WhxycI*h_?+FL_TxpApbBHp4ew_yI*0}w+PJwOB# zDTyIpzFH6D;Lym4!GU4_G^3Z*WMVAiUw?FeedW1-edXa=c3H2Or0H}#L3L>`_6)I3 zC}Cn(EDE5T)l?!mbul%aNv5XK><~hYCC(E%(V-d_f>W84R!fa?RDiW zuI>^d;v!i@Ko1|-?NW}yGRoE6uMEP8H=7%j<3M$3{pK5zu~#I<_&ZAg_wiqrHf0Ei zBU-3Bi}5l3&gygw1EI@tRShfbl*kqY>*If|5oHvXXSDXc+O|+XU$ofxA6rMmNs%-F z5H_*1Vtr0PpMd-Z2YJlitt8+(uFc!$B;$Dzy1;$T8HcD18(2Uvr4=uo^~wa0*R)U0 zMhW-xP4_`lK3QfeKkSJrS0FmaRnK+2kFR=u_EeN~5$T5MR63(8BdG~G#Y9_lIYBc_ zbVIK^ou;gp(RhY3(E?pgPG#6Aw|ebWVwY@Ozy#m#wS|Psj$7L>bxo$oRh4$LI}jEB oiu7Y`(t8&-`(}L$rVrTl5xa7dguOWg@LfidyU3WdpT6JHKXLH?5&!@I delta 910 zcmZWmOHUI~6rMYssUuCFP};GDK7f|#0L66C5D7j?ASh9z#rR;;crUG5T60@BUV7RJB84{$LW|9~44UAa)tsZPaolKbV}bI$jD=bY&*eJOcgdc6Y@ ze4h2aTKb7sC);nc1>$3&>j@0w_hv}EAjMRe9iV=8a6_TP57ZrqzwTxcE+?73oe~`~ z7?fxPxPB)E+@x#{M*A3Pag69;{r}n>8B}F4s}%BwAT#Eu&=~V~z1=k4r!?+(=OwB$ z&wfSS5r7%~XgdKa(GfB0GL2C-K@)Hs2aP8C95UdL#pxmvP3_lrRI%ei8dSz0JK6L2 zr#arIX@F)9HC+;&5IJQom0_`77jByEW0<%xGAW)bbB7J^ndA&cTe zU|L;;z!6Ie?htZZ?1rL|GZ3&{m3rM!`Dv`i0ACcJL#A4W>p9CeX#Evh5?g^G@iClF ztzgkf07K(zc)x(Kh|ulslrV=E)T{7bw;IEbWcNi(T@pW|TM^_M3eR903+ZI1KX z18XhDOPp2OjN{O@>sq7D%i`Bacp3ZR8wluU`_5KctvgLlOSMX)$~fBa69_X1_^*Cw b^P5e2tIig92c85@>$Y`e^f9q~$#3#+tq8eK diff --git a/backend/routers/__pycache__/issues.cpython-311.pyc b/backend/routers/__pycache__/issues.cpython-311.pyc index 5f7acc8b578e2098bcb34b12f98fe61392235cea..c22b607c0630423742bb1061d9081226e24beac8 100644 GIT binary patch delta 1256 zcmZuxTS!zv7@l+6yX(3a@9DahvR%Bi3&rL=O)ClUQZJE6domYInR9$7))eZoA{wDU zte~*O9*mV~nSX{eTC-kjeUg%5 zBG5|L)-N_^JhytJVI!p1pnsp?2sueOa+jzOC0mdSlQA;RPXITy!E@U+e3*nb-tD9g zk|6=Q2s%DFGj*zZLKu?@*@z_Oh}-#O1|vlFK+3Y1lMEqYHk^@B9~Q!5my&x8qZoPwTcLlRqbX0fX}J2Se9#W4Rch1KeuY)F?;DV`e42UUGTT}NZU zy*xd(GCtz&QItX1y?k?eWqjt__3427isP?ffcA&v^J-^LFyQv;**#rle$2E~~7-TmVSAh1GLJ_CG#-w63>A(Cm<O(i{KGRR`l6z5R`e~Ux!4_jd)~YwGwLW@NX@=698WdabgKm52f|2G+#dO@zsh1OFX&J;632k{a8>1WqAA%D=!CJW!jqZmyW~Opiuv069LJt8`%9f0jD~Yt3 z%DE10paod!M<~J((uE$Xs8oV@Qly8G-*n1?RKcT1kA#D1(}C8)n>zNvEQ3L{<+WfA z;P4{|70h9)G4I9@4~p(#=WGS89Tq^V*{JO(SIp*Zd3G;K_X2oLiKWyrLt0~|wl&M( zK(I@d6Pu$gAZm$pr={`WecADfvu*LlS?XD{%fnT%ewQbIM_Ux(u4yBic6s(`)-{m- zvtLV4?v*!e+d*)P{c^nn*SF~}_&_lbF#}wsZYnDZJprlLnm8e?(2YRQRD>o3t?mUF z6|t9Dfd*}5r!mKg=oti@KDrJvZMIg_cl=872uW!5B-tW~k`v4-WRqRkROM{A4JPBB5Ll zSDnW>;{;&dY>%4r;sS(A_Vl=fyp9-caeY#u$yNozmqoeKIFED<7kRVm7%!sHABKDp AoB#j- delta 980 zcmZuwOGs2v7(VAd=8kjc-FUq?YC1aA)Q}HIbnw+fGeRlR77>UrH?yXcb7wX&lF-tE z=tS*8TNF}4GSJfKK~QTKaVaWj5e0FjHr1y8Kg09@&u~7z|M|`N|CwL=Zwv(Q1p+<- zr~cHdGwa4$FrjvN!MqIj+X+g@Q9{Wrk|pypT%}0PVzM|amX|A>5Qy;QkQtGINEJ7x z@KrTQ75+grqYzRnp`u(=ijmlSm-13=Mi_%gRe-hrt9ZyV_mVAQ03xxR7 z>NtPnF%)Su<>9Sbug0({<>!mqZF3LMAY6MD;1hfnh#MqPgEQ~A`8HI0<&qoF!k$e6 z@Eu`c*bSFHdau#3R;X~~sa!6lvAw_p*D13ib31v-1q+v7)d}e$`)Rh*rM8tyn)q`}t$P z1)Y+Cm$Kf4_1y>={v$k~Cy;JOXy%6^P1Rc{v0i>La*!tZdZZz`6H|8qq+E{sjcNhuU!zYZy_AO;G37^LX`As9y zCZzkowiN&zUU_u>td+C=$c{m55tE+t#<+RC<{_xB)jX0iNpL#eIW{}ZEZb(oV5WlQ zm59j>f`f$+h7iP{n~=5e<=V^+@eRk(#zt%c0ek$vN>1a|>Pc`f^LYFVO>?d8^CbSP z9C_NxJMzqhyx6W-j3dt#@{WE4mp&mzCx*t3V{d#1!%i!NrIF)^e|me)&t?lbtB;)n z6aHPqzEe6@pP+ql8gmK2YdyN5H@Gs;_ff-D(eEKuVOOhUh_p?aNNpRm)s@hlDXldO INeb%x20=yAvj6}9 diff --git a/backend/routers/__pycache__/projects.cpython-311.pyc b/backend/routers/__pycache__/projects.cpython-311.pyc index 1005bddd3985fd004e25b70811858f784b012454..9cdddd0f6e4de906752e15c8472735f1e15e77e4 100644 GIT binary patch delta 1321 zcma)6O>7%Q6rP#=$=a^%b?kLo$9A2+8Y?Z(hAMmz@N}wRra%ghmj6g!DQYz&TEfT8K3lcdj)z-IfWhbH~Q6uXhtp2l#^UxV1?NpobWWysAZbj zU)8xI@;nQ(PZTg@)#AH%ge!K#nS76E*D#oN&i0?f^Dy6m8wD;J>*=|#~VuzPuZYKav5lXHR zPC2Vl;A=~JK{ zDHWYwwD{bgNZQ`0{DgYcutF#gyYnM1`C{*oV-H zV1wkD0P0*U=NttAS>6Ibrx9Cy9J@>zl$BBCU7zc$$vk!29EVl)#NTxOS%`cBJmmr9 z#|N?%O&-HtD;qZJX#nbWJnK9U!i%V>0dx}W`ZV+Q+IS+x=%hMn7l!{cFwmuefHlkG z>LWWha1g~q02Qm9a0(Tztau@EM15yZ8ZU$R##$`(5=%~^)&V#nOMq%V{rJ@X$%bx6 z5HMp!|66yRJU^rdjcKsCDokIc!|KD#w- zOFVpu2NP(GC0}B_fyqT(_(EdPe?7zp*0YN)U#g3v@*G(4GvQ6a0f!o;bdx-{-u|m^ zoIZ8O`DwWLL2+5TOEViZ6Ea2%Ap!VJBg}cIvD6pYYcGV6I8X?{W;7mZu-=O$LIag1 z>CJ{cC|M+8KO(l+80+u`eJW%~D5~pyrLoY6(R4@vHtlq%!Ftb1K2o+U=yzm;J`*zJ J=9IB}`!5d;CV>C| delta 1099 zcma)5OHUI~6rM+C`YwG;Y0I+(LPe0sLreidBf)6c7!ou_CCJ=jNwIjRn5bQ7BrZ+# zuyCm{sUaAR_6N9ds|h45$haYKf6dDr!$Bl*(n zbqQz-AGfYws1SrN6zrO3$JuOtocv^C!B;+bD5U}w%&+^Is=Jvtt?Ge!1yr#W7DZp! zSCj-{3D>sif*#aE;E`j4T2aIkT-%mbbd6Q%VO*80dQZ)zN9L6xQkHg!!lfD(einm- zTT}Ju&Irllf67UE45jvms`^W&7G>%V%A`V6RLuf=g$}(2#-vs;3O}Wk_kbiUOE^K@ zsL)bhx+>q6N_W*cNo<4-N2}Nbwa&O2$C-HoPCJK{qv%H9k@LH#zhjzh^(Mr@4hLk1r@-Z9BZah`VKsaU^+9#_{3yBbBmd3E}b@5o_A4Rp2%if?7WkR zl;GV234#zoS;mi^VMmCA%TpCjp+{U}8sRHG_XH~lDbE8@#?drb@w|XDp+2b~!&~pg z@WDy=VJzZ@pw5@*U$pUFiuT*ZF*IxlaWr0yj_ICa`SDqvn`V=FK8Tqi;$)zxrAwcE z0ZHtKZ~oe*e|AA8R|r&s6VM*0O0;c}*Z*{+(jJ0x*8#YrH7Fw(XW_ZFAT~fx_`_w}Y|hAY`o5-&cQxhA zjl5~Qmdj;1z3K9uO?h@M&uJ)A)mfJ187G-Vf!7j+UeuPOe93j`9qeB`?{mTQYfjC*xqrOLt=fa OVy`6;p$6VZH-7>2WAAkU diff --git a/backend/routers/auth.py b/backend/routers/auth.py index a6a677e..67951bc 100644 --- a/backend/routers/auth.py +++ b/backend/routers/auth.py @@ -36,6 +36,11 @@ async def get_current_admin(current_user: User = Depends(get_current_user)): ) return current_user +@router.options("/login") +async def login_options(): + """OPTIONS preflight 요청 처리""" + return {"message": "OK"} + @router.post("/login", response_model=schemas.Token) async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): user = authenticate_user(db, form_data.username, form_data.password) diff --git a/backend/routers/projects.py b/backend/routers/projects.py index 7896290..c14f066 100644 --- a/backend/routers/projects.py +++ b/backend/routers/projects.py @@ -20,6 +20,11 @@ def check_admin_permission(current_user: User = Depends(get_current_user)): ) return current_user +@router.options("/") +async def projects_options(): + """OPTIONS preflight 요청 처리""" + return {"message": "OK"} + @router.post("/", response_model=ProjectSchema) async def create_project( project: ProjectCreate, @@ -53,8 +58,7 @@ async def get_projects( skip: int = 0, limit: int = 100, active_only: bool = True, - db: Session = Depends(get_db), - current_user: User = Depends(get_current_user) + db: Session = Depends(get_db) ): """프로젝트 목록 조회""" query = db.query(Project) @@ -68,8 +72,7 @@ async def get_projects( @router.get("/{project_id}", response_model=ProjectSchema) async def get_project( project_id: int, - db: Session = Depends(get_db), - current_user: User = Depends(get_current_user) + db: Session = Depends(get_db) ): """특정 프로젝트 조회""" project = db.query(Project).filter(Project.id == project_id).first() diff --git a/backend/services/__pycache__/auth_service.cpython-311.pyc b/backend/services/__pycache__/auth_service.cpython-311.pyc index 217c9f5187054522bf6f67e1d6b7cc6eac30de69..fa1fa5587e72c8f2d3dd9e6c83971ee4a1b0aea2 100644 GIT binary patch delta 767 zcma)3OK1~O6wRBNq(i1NNfFzcCh4^K&;dVfBwg5$T3sj=EP}eQU`(6IhqjsUW>R%! zz@ma~5RN;G%n7R1sRTchgKHQdf23eUpuL<-nYK=ghhHo_F8J;HQxE zS`?i&ct#I@XJuhsnq?%hFt?COM-qBA8HtXQ0#lw+Q1Kjynp-Q)_oe2>lUBJd?{AdM zR~32WAGLf+-mllq%BE~q*IKJd3^|YaI_{H2Bdg|(+&V_1?w16M)2oH5IV|d@mvkB!#FDC1Y}V}TGQpt zGJ`xSoFU|4g6xNu{Loo-IbRQ;;SdQ5XdfjS0R}Th_l@j3BfB-XYuI~+-Pdf$_1JvGgj)VWGy|8g}na* Mx;Sz8p+>d+22(h&!vFvP diff --git a/backend/services/auth_service.py b/backend/services/auth_service.py index 09fedc9..f537ba8 100644 --- a/backend/services/auth_service.py +++ b/backend/services/auth_service.py @@ -13,13 +13,18 @@ SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-here") ALGORITHM = os.getenv("ALGORITHM", "HS256") ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "10080")) # 7 days -# 비밀번호 암호화 -pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +# 비밀번호 암호화 (pbkdf2_sha256 사용 - bcrypt 문제 회피) +pwd_context = CryptContext( + schemes=["pbkdf2_sha256"], + deprecated="auto" +) def verify_password(plain_password: str, hashed_password: str) -> bool: + """비밀번호 검증 (pbkdf2_sha256 - 길이 제한 없음)""" return pwd_context.verify(plain_password, hashed_password) def get_password_hash(password: str) -> str: + """비밀번호 해시 생성 (pbkdf2_sha256 - 길이 제한 없음)""" return pwd_context.hash(password) def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): diff --git a/docker-compose.yml b/docker-compose.yml index ee6d9ee..b26a362 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,13 +28,13 @@ services: ALGORITHM: HS256 ACCESS_TOKEN_EXPIRE_MINUTES: 10080 # 7 days ADMIN_USERNAME: hyungi - ADMIN_PASSWORD: djg3-jj34-X3Q3 + ADMIN_PASSWORD: "123456" TZ: Asia/Seoul volumes: - ./backend:/app - uploads:/app/uploads ports: - - "16000:8000" + - "0.0.0.0:16000:8000" # 모든 IP에서 접근 허용 depends_on: - db networks: diff --git a/frontend/admin.html b/frontend/admin.html index 1e5878d..44dccf4 100644 --- a/frontend/admin.html +++ b/frontend/admin.html @@ -225,20 +225,43 @@ - + + + diff --git a/frontend/daily-work.html b/frontend/daily-work.html index 8e9aa7f..b9877a1 100644 --- a/frontend/daily-work.html +++ b/frontend/daily-work.html @@ -217,7 +217,18 @@ - + diff --git a/frontend/index.html b/frontend/index.html index e75024a..732b87e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -92,6 +92,40 @@ color: white; } + /* 모바일에서 프로젝트 드롭다운 강제 표시 */ + @media (max-width: 768px) { + #projectSelect, + select[id="projectSelect"] { + display: block !important; + visibility: visible !important; + opacity: 1 !important; + width: 100% !important; + min-height: 44px !important; + height: auto !important; + padding: 12px !important; + font-size: 16px !important; + border: 2px solid #3b82f6 !important; + border-radius: 8px !important; + background-color: white !important; + color: black !important; + -webkit-appearance: menulist !important; + -moz-appearance: menulist !important; + appearance: menulist !important; + box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important; + position: relative !important; + z-index: 1000 !important; + } + + #projectSelect option, + select[id="projectSelect"] option { + display: block !important; + padding: 8px !important; + font-size: 16px !important; + color: black !important; + background-color: white !important; + } + } + .loading-overlay { position: fixed; top: 0; @@ -293,7 +327,7 @@ - @@ -308,6 +342,7 @@ + @@ -415,7 +450,38 @@ - + + diff --git a/frontend/mobile-fix.html b/frontend/mobile-fix.html new file mode 100644 index 0000000..00a584f --- /dev/null +++ b/frontend/mobile-fix.html @@ -0,0 +1,323 @@ + + + + + + 모바일 프로젝트 문제 해결 + + + +
+

🔧 모바일 프로젝트 문제 해결

+ +
+

📱 디바이스 정보

+
+
+ +
+

💾 localStorage 상태

+
+ + +
+ +
+

🧪 드롭다운 테스트

+
+ + +
+ +
+ +
+

📊 실제 프로젝트 드롭다운

+
+ + +
+ +
+ +
+

🔍 디버그 로그

+

+            
+        
+ +
+ + +
+
+ + + + + diff --git a/frontend/project-management.html b/frontend/project-management.html index 8ecf77e..d7ed22a 100644 --- a/frontend/project-management.html +++ b/frontend/project-management.html @@ -122,85 +122,176 @@ + + + + + + diff --git a/frontend/static/js/api.js b/frontend/static/js/api.js index 1cae9a0..ea622ed 100644 --- a/frontend/static/js/api.js +++ b/frontend/static/js/api.js @@ -1,5 +1,30 @@ -// API 기본 설정 -const API_BASE_URL = '/api'; +// API 기본 설정 (Cloudflare 터널 + 로컬 환경 지원) +const API_BASE_URL = (() => { + const hostname = window.location.hostname; + const protocol = window.location.protocol; + const port = window.location.port; + + console.log('🔧 API URL 생성 - hostname:', hostname, 'protocol:', protocol, 'port:', port); + + // 로컬 환경 (포트 있음) + if (port === '16080') { + const url = `${protocol}//${hostname}:${port}/api`; + console.log('🏠 로컬 환경 URL:', url); + return url; + } + + // Cloudflare 터널 환경 (m.hyungi.net) - 강제 HTTPS + if (hostname === 'm.hyungi.net') { + const url = `https://m-api.hyungi.net/api`; + console.log('☁️ Cloudflare 환경 URL:', url); + return url; + } + + // 기타 환경 + const url = '/api'; + console.log('🌐 기타 환경 URL:', url); + return url; +})(); // 토큰 관리 const TokenManager = { @@ -76,23 +101,29 @@ const AuthAPI = { formData.append('username', username); formData.append('password', password); - const response = await fetch(`${API_BASE_URL}/auth/login`, { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: formData.toString() - }); - - if (!response.ok) { - const error = await response.json(); - throw new Error(error.detail || '로그인 실패'); + try { + const response = await fetch(`${API_BASE_URL}/auth/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: formData.toString() + }); + + if (!response.ok) { + const error = await response.json(); + console.error('로그인 에러:', error); + throw new Error(error.detail || '로그인 실패'); + } + + const data = await response.json(); + TokenManager.setToken(data.access_token); + TokenManager.setUser(data.user); + return data; + } catch (error) { + console.error('로그인 요청 에러:', error); + throw error; } - - const data = await response.json(); - TokenManager.setToken(data.access_token); - TokenManager.setUser(data.user); - return data; }, logout: () => { @@ -103,6 +134,8 @@ const AuthAPI = { getMe: () => apiRequest('/auth/me'), + getCurrentUser: () => apiRequest('/auth/me'), + createUser: (userData) => apiRequest('/auth/users', { method: 'POST', body: JSON.stringify(userData) @@ -252,12 +285,12 @@ function checkAdminAuth() { const ProjectsAPI = { getAll: (activeOnly = false) => { const params = activeOnly ? '?active_only=true' : ''; - return apiRequest(`/projects${params}`); + return apiRequest(`/projects/${params}`); }, get: (id) => apiRequest(`/projects/${id}`), - create: (projectData) => apiRequest('/projects', { + create: (projectData) => apiRequest('/projects/', { method: 'POST', body: JSON.stringify(projectData) }), diff --git a/frontend/static/js/auth-common.js b/frontend/static/js/auth-common.js index 10898da..8443d7d 100644 --- a/frontend/static/js/auth-common.js +++ b/frontend/static/js/auth-common.js @@ -39,8 +39,7 @@ class AuthCommon { 'dailyWorkBtn', 'listBtn', 'summaryBtn', - 'projectBtn', - 'adminBtn' + 'projectBtn' ]; adminMenus.forEach(menuId => { @@ -49,6 +48,20 @@ class AuthCommon { element.style.display = isAdmin ? '' : 'none'; } }); + + // 관리자 버튼 처리 (드롭다운 vs 단순 버튼) + const adminBtnContainer = document.getElementById('adminBtnContainer'); + const userPasswordBtn = document.getElementById('userPasswordBtn'); + + if (isAdmin) { + // 관리자: 드롭다운 메뉴 표시 + if (adminBtnContainer) adminBtnContainer.style.display = ''; + if (userPasswordBtn) userPasswordBtn.style.display = 'none'; + } else { + // 일반 사용자: 비밀번호 변경 버튼만 표시 + if (adminBtnContainer) adminBtnContainer.style.display = 'none'; + if (userPasswordBtn) userPasswordBtn.style.display = ''; + } } static logout() { diff --git a/frontend/static/js/common-header.js b/frontend/static/js/common-header.js index af78f30..a0e7fdf 100644 --- a/frontend/static/js/common-header.js +++ b/frontend/static/js/common-header.js @@ -33,9 +33,22 @@ class CommonHeader { - + + @@ -104,3 +117,21 @@ function showSection(sectionName) { window.showSection(sectionName); } } + +// 관리자 메뉴 토글 +function toggleAdminMenu() { + const menu = document.getElementById('adminMenu'); + if (menu) { + menu.classList.toggle('hidden'); + } +} + +// 메뉴 외부 클릭 시 닫기 +document.addEventListener('click', function(event) { + const adminBtn = document.getElementById('adminBtn'); + const adminMenu = document.getElementById('adminMenu'); + + if (adminBtn && adminMenu && !adminBtn.contains(event.target) && !adminMenu.contains(event.target)) { + adminMenu.classList.add('hidden'); + } +}); diff --git a/frontend/sync-projects-from-db.html b/frontend/sync-projects-from-db.html new file mode 100644 index 0000000..ef14c7a --- /dev/null +++ b/frontend/sync-projects-from-db.html @@ -0,0 +1,104 @@ + + + + + + 프로젝트 DB → localStorage 동기화 + + + +

프로젝트 DB → localStorage 동기화

+ +
+

+    
+    
+    
+    
+    
+
+
+
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
index 7759e59..bc0b3e1 100644
--- a/nginx/nginx.conf
+++ b/nginx/nginx.conf
@@ -16,7 +16,29 @@ http {
     server {
         listen 80;
         server_name localhost;
+        
+        # 경기도 IP 대역만 허용 (주요 ISP)
+        allow 211.0.0.0/8;    # KT
+        allow 175.0.0.0/8;    # KT
+        allow 121.0.0.0/8;    # SK브로드밴드
+        allow 1.0.0.0/8;      # SK브로드밴드
+        allow 112.0.0.0/8;    # LG유플러스
+        allow 106.0.0.0/8;    # LG유플러스
+        allow 127.0.0.1;      # 로컬호스트
+        allow 192.168.0.0/16; # 내부 네트워크
+        deny all;             # 나머지 모든 IP 차단
 
+        # HTML 파일 캐시 제어
+        location ~* \.(html)$ {
+            root /usr/share/nginx/html;
+            expires -1;
+            add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
+            add_header Pragma "no-cache";
+            add_header Last-Modified $date_gmt;
+            add_header ETag "";
+            if_modified_since off;
+        }
+        
         # 프론트엔드 파일 서빙
         location / {
             root /usr/share/nginx/html;
@@ -24,15 +46,21 @@ http {
             try_files $uri $uri/ /index.html;
         }
         
-        # JS/CSS 파일 캐시 제어
+        # JS/CSS 파일 캐시 제어 (모바일 강화)
         location ~* \.(js|css)$ {
             root /usr/share/nginx/html;
             expires -1;
             add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
             add_header Pragma "no-cache";
+            add_header Last-Modified $date_gmt;
+            add_header ETag "";
+            if_modified_since off;
+            # 모바일 캐시 방지 추가 헤더
+            add_header Vary "User-Agent";
+            add_header X-Accel-Expires "0";
         }
 
-        # API 프록시
+        # API 프록시 (백엔드에서 CORS 처리)
         location /api/ {
             proxy_pass http://backend:8000/api/;
             proxy_http_version 1.1;