From bfc77be0d68f1124b1435c9ba46d3cd5f21ded8f Mon Sep 17 00:00:00 2001 From: Zikil Date: Fri, 28 Feb 2025 21:57:31 +0700 Subject: [PATCH] =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=81=D1=82=D1=83=D1=80=D1=83=D0=BA=D1=82=D1=83?= =?UTF-8?q?=D1=80=D0=B0=20=D0=B1=D0=B5=D0=BA=D0=B5=D0=BD=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 6148 -> 6148 bytes backend/.DS_Store | Bin 6148 -> 6148 bytes backend/app/.DS_Store | Bin 6148 -> 6148 bytes backend/app/__pycache__/main.cpython-310.pyc | Bin 1649 -> 1710 bytes .../app/__pycache__/routers.cpython-310.pyc | Bin 13681 -> 13355 bytes backend/app/main.py | 32 +- backend/app/routers.py | 354 ------------- backend/app/routers/__init__.py | 23 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 848 bytes .../analytics_router.cpython-310.pyc | Bin 0 -> 1780 bytes .../__pycache__/auth_router.cpython-310.pyc | Bin 0 -> 1158 bytes .../__pycache__/cart_router.cpython-310.pyc | Bin 0 -> 1879 bytes .../catalog_router.cpython-310.pyc | Bin 0 -> 4895 bytes .../content_router.cpython-310.pyc | Bin 0 -> 2116 bytes .../__pycache__/order_router.cpython-310.pyc | Bin 0 -> 2133 bytes .../__pycache__/review_router.cpython-310.pyc | Bin 0 -> 2052 bytes .../__pycache__/user_router.cpython-310.pyc | Bin 0 -> 2204 bytes backend/app/routers/analytics_router.py | 40 ++ backend/app/routers/auth_router.py | 21 + backend/app/routers/cart_router.py | 35 ++ backend/app/routers/catalog_router.py | 107 ++++ backend/app/routers/content_router.py | 40 ++ backend/app/routers/order_router.py | 45 ++ backend/app/routers/review_router.py | 35 ++ backend/app/routers/user_router.py | 40 ++ backend/app/services.py | 482 ------------------ backend/app/services/__init__.py | 25 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 1449 bytes .../catalog_service.cpython-310.pyc | Bin 0 -> 5333 bytes .../content_service.cpython-310.pyc | Bin 0 -> 1870 bytes .../__pycache__/order_service.cpython-310.pyc | Bin 0 -> 3569 bytes .../review_service.cpython-310.pyc | Bin 0 -> 1627 bytes .../__pycache__/user_service.cpython-310.pyc | Bin 0 -> 3028 bytes backend/app/services/catalog_service.py | 203 ++++++++ backend/app/services/content_service.py | 50 ++ backend/app/services/order_service.py | 107 ++++ backend/app/services/review_service.py | 41 ++ backend/app/services/user_service.py | 101 ++++ 38 files changed, 927 insertions(+), 854 deletions(-) delete mode 100644 backend/app/routers.py create mode 100644 backend/app/routers/__init__.py create mode 100644 backend/app/routers/__pycache__/__init__.cpython-310.pyc create mode 100644 backend/app/routers/__pycache__/analytics_router.cpython-310.pyc create mode 100644 backend/app/routers/__pycache__/auth_router.cpython-310.pyc create mode 100644 backend/app/routers/__pycache__/cart_router.cpython-310.pyc create mode 100644 backend/app/routers/__pycache__/catalog_router.cpython-310.pyc create mode 100644 backend/app/routers/__pycache__/content_router.cpython-310.pyc create mode 100644 backend/app/routers/__pycache__/order_router.cpython-310.pyc create mode 100644 backend/app/routers/__pycache__/review_router.cpython-310.pyc create mode 100644 backend/app/routers/__pycache__/user_router.cpython-310.pyc create mode 100644 backend/app/routers/analytics_router.py create mode 100644 backend/app/routers/auth_router.py create mode 100644 backend/app/routers/cart_router.py create mode 100644 backend/app/routers/catalog_router.py create mode 100644 backend/app/routers/content_router.py create mode 100644 backend/app/routers/order_router.py create mode 100644 backend/app/routers/review_router.py create mode 100644 backend/app/routers/user_router.py delete mode 100644 backend/app/services.py create mode 100644 backend/app/services/__init__.py create mode 100644 backend/app/services/__pycache__/__init__.cpython-310.pyc create mode 100644 backend/app/services/__pycache__/catalog_service.cpython-310.pyc create mode 100644 backend/app/services/__pycache__/content_service.cpython-310.pyc create mode 100644 backend/app/services/__pycache__/order_service.cpython-310.pyc create mode 100644 backend/app/services/__pycache__/review_service.cpython-310.pyc create mode 100644 backend/app/services/__pycache__/user_service.cpython-310.pyc create mode 100644 backend/app/services/catalog_service.py create mode 100644 backend/app/services/content_service.py create mode 100644 backend/app/services/order_service.py create mode 100644 backend/app/services/review_service.py create mode 100644 backend/app/services/user_service.py diff --git a/.DS_Store b/.DS_Store index e49474ea570e487d68c75de2845e5e47d1273a9d..d2e2933fc704634269da1b65f7dac2566c76c5e0 100644 GIT binary patch delta 75 zcmZoMXffEJ&BV+SD>YeUERvGAXcliR492W@J`@F(mj4G7N*0 Y^K%OrfIxIr!RA2bZ7iGFIR5bi0P98+KmY&$ delta 75 zcmZoMXffEJ&BV-npn0+$Q#KQ8!Q|OYb}Yhi=QtYtHb7MrcO}+mB1S4 diff --git a/backend/app/.DS_Store b/backend/app/.DS_Store index b507094469541e808351e94817f5d18d85e27088..e5f4b501bfffbf1fc03b172d7062e9fef46587b0 100644 GIT binary patch delta 354 zcmZoMXfc@J&nU4mU^g?P#AF^84W<@xASE?9fQ5(c=p1n==ADx#u*ht#V`*lrFJj1N zC}k*NNM$HuD9%YY3{K9^EnonHiQj<~J5U@+MQ*-}OHxjL5>ST2;{S~6OgoP`VpEku zgsOrJ!(eW>b&L*^f3qqwl?G0hV=JuZU?>JVsf;0$AsNU*b6W2rkOR=wAUiHCwxx{` f;y5ho5YEG{P84D($c1nRGJKd=7_yn2<1aq|>WEtY delta 58 zcmZoMXfc@J&nU1lU^g?Pz+@g44W?EFASE?9fQ4sr7mL#7I+kX}$rIV6nTl;DuVX9R NSX#ljnVsV=KLF;85n=!U diff --git a/backend/app/__pycache__/main.cpython-310.pyc b/backend/app/__pycache__/main.cpython-310.pyc index c335ab52ad7eeac3b5d04fc6305eeb2c77189d74..2973c1c0b39264742255dbf214de07053770034b 100644 GIT binary patch delta 987 zcmZuvOK1~87~YxP&8AJZuf!yM*=;~D9%6l51SuA@Se00fr$wl1{%N~mH(O^n_&}(| z3PK4|_9{M(UZmm^rGn^H*z=N6OZYwh z@MdBEx;}cSNF)g)AdX~;ojSy&CM80a9obb(#Z^tUqbp9r)l97;t4`AGGP{VhUN0|q zEZxq4n=vzP*31%#W$N4NIhKV~i=d`J8g%G^-j;0US-xIi8uTp?=apdb`FvL>A z4Z|%0BQV;MJE;xWj;Y7MSe5YLIFPDT8vi25sFX;o%v!->c#4#Er;k-nR!%d2$!quw zJ*jG71$J%Pc9@T9Z}rrPDW|r`+{;He_jq`hly_dqALh*W85r{(H>%BA4K-tGcBXj% z^W$zrPoh`tTcNHLvqtn9wWrau=t+C+in^yfQJx6jlQAVGyUv7PiM6cap||+^1%wq6 z;Cz4v)$Ed{pMm|L_O;4nUpk-m6i^ctmqO1^?XuO%rLl$44w_54^{!ofQB zc`mLuwRsDBUb4B28W-_qe#=yLZNLUDT0GoTwD6-+C<_|Q%x}LS%dX9fcfd8_>G{J$ z8F>gb^>nzR=Cb|&kb(@nQ>TxLvi{pb@}TQM(_xc1PsyJ|NJ?qahwN=FDzqM%%9P*= QXA`-U)-Cstf}BMB2|^YOZU6uP delta 980 zcmZuvJ8#oa6!!JC9jAG;O`6m#Ergd)kO)W&NPrLs3Is$FB^AS6g4=Tw9PB!>9Y8E? zDFaJXfma3?89;!9gpfc&`~he6%EXAoh6)^;hY;YabNroq9{2Ni{WS9_ov|~SF#(^# zlXu-C_cHS*=ZGMH0K^n5!iXg@kwmd*QcGe|jHxMG3R7Y%nF&i}YK&zw$&&Yal~pUp zazx;hy?k$q=U{9|Fd{tDE1(t8rXe+?OygQ_hD#XFLK-q%5?gjCW3~2h8=rebV4RCE z!RI;>OhWFCv_aTBc5Ub5kcX)ua^orN-+}ujUh3_{IcYdrh!a|G7ZkY?rD1wVK!cef zjYsc>Ssc9$=I#jCxCi8xP@ex7$>3ih<-Mtvr+dcoS;OS+V-lt>wCc4ccl-8=%WGv4 zN)6q+>c1kD-DMiep5d81OaXV79i#6V_DYzz&K(!QVo3J;T~P-KN54mNbLiHIhx&pD z)F2=YMLb-t8J|&irU3VJ!yFvKvfzI3X0$Q7Jz5K125%5=jy6ld+tJvut10SHR>>39H=b z9}w3>DiVb#UmeZ=K%w-Tbr!Sh zE@x_#Xb&8dCF5KKog&Wq+fCzMC^@$69Y*vA8Kkon6D;p#iH`1idf#xwXaYsYraQX7 zPcCW^QI`LiuV5A4UI%Z_KPJykCNLR_wi_n;cx|S!95xoWM&CjZeiOfLMA z8MXW-pY4{A#eY9rJ#N`BF!>3*f5APDZbC?tXsMAG(`15VsYoRfEoDnei1>~Sqc3W5 Oie%L>nk5=l(SHZ}h68s1 diff --git a/backend/app/__pycache__/routers.cpython-310.pyc b/backend/app/__pycache__/routers.cpython-310.pyc index 40350e07d532bd134f89898fa4704e7368135af3..7c35dd464c736b2868d651e9c52902f40961032d 100644 GIT binary patch literal 13355 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 U??u<6HU%rNy9A$C>{^%q0l_BP5dZ)H literal 0 HcmV?d00001 diff --git a/backend/app/routers/__pycache__/auth_router.cpython-310.pyc b/backend/app/routers/__pycache__/auth_router.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9fd8df2547e794c99657b87f4260ca46cb148168 GIT binary patch literal 1158 zcmY+Dy>Hw$6u?PJr_+7t>^pzNK{Ix_b=Lu$M<`Ds-mb*a9scK+u|#a zkiVSr{s>UHhg1KFiy(p)L{d&A<4k(olRo#Uv$LWj10J-pS9E2_!&df-p6v6!9PmM_ zcZ#8mc+|>4F_K$+tChROwjA@ZobU-HKt(8e3o81r8Q+1O^Ib83KJ39@&TbPie9eGC zbVcV>L@*X32}_gE&*GUkXk!^u9*-*M^@F5gf?ex#T5l3cSB@`gT~GBrRukKV;j zTp+ZL(W)l6ydi2AKmU9gI}{yXaVnQP z-`;G_G-Qp+%~H7te59WiX^}k!xtt-q>Y~vGW?ARuLWS;h&sjpO>@34lEBC)!57jm0 zf!omTe5z-TGTA6<=nlJL+kxxF%~M#vt~B<%6JAJTsTJ<~c8 literal 0 HcmV?d00001 diff --git a/backend/app/routers/__pycache__/cart_router.cpython-310.pyc b/backend/app/routers/__pycache__/cart_router.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9536858eafbc6a3a6f88bb85fb7220e5cd7977bb GIT binary patch literal 1879 zcma)7&u`l{6c#C2mg66_6FZ6XqgjisfOm-gfniwD3|J2R93IASn&Px z*B=ABY+3)JbN$27PoStDiDfa1k<=0-Ac6)|*nusaz#-UA(~@ul*R<`lEWE%Im7rpL zCtVTMpekxX&G@CXE*e3@wB7WEXa-Hu3R=c5r>kNuXcLPQ=CR6vu$AXD=VFU4z_w>%KFT!@6wCpl$+ck8QsrYKQF_)O+mak=5J#5AUt# z=*r#0`(I?Ef=gXK;5i>MrR$$Pe0ccj)0pQ)k`1-1il`W=9?|6^t`v0jsCEvLxX||9 z;VC%ofEOV<)*D#IBPsc?2&1@2p73y_xCE#N)srOV3Z^&iMY6bGaB)xasNf6#QO>}J z+0G-Fepd2hm^f+x)5#B$-~WC#`DOC!b{yL%!qtcE5|lroyjTVE9Vx7z$e51F2_0J(_K|f-9`tNk zg)v=?7z>Lm#ML!T81y4XuAymzxwK>#s+V8-cogNIC6E1sEFKAvM)`*_8^}l~p8;LP zS=i5Hs75j96RIMU+&_-u$8Z^bl;^%Q_oIv&;ndmBPj&kmhY;rGSu!l@u;~j241Lz5 zavK_FovAqfH*+b%gnbKg%#;B)M#${LwhINNPGUF#?H@zimK^ZR;k zvZs{h_w*W@W!Rj}2JWzf<`x(Txj5yKj43#?58KGs#849&SHh98bwV%bl4vVtfd#iq zx(J_TT5F5Bb3(}F>Z%)5egNJ};=hrPybTjxQOGmd3PE6$k?-R;#_YAbY0ak0H^sBd zJ7{`f9`tVMazBCxB2OfqUSb+a)Iee~>4y3`jneolE>8C|DI|uKc8k+I84e`;LM-WF zj|XACA4A;B3ijcDMBC6eO|~wMDqJK|unOalD^pL_a5~1kUdKr$=hZ$Y_%#2O#MeVt zFbO|54?@+1{WOcTlV=KEF;z&DD%#FR;6fUiXwxpdARL}Xb7&M;+aQQJ`8B1CW+s%q mkAT1^q+z$|9ny9_go5>LQ~wW#7}ebX literal 0 HcmV?d00001 diff --git a/backend/app/routers/__pycache__/catalog_router.cpython-310.pyc b/backend/app/routers/__pycache__/catalog_router.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fc48d929d00d143f4a00ca782195ccc56908411d GIT binary patch literal 4895 zcmb7I&2!t<5eFVY5RxdGk|>J$vK-rzO@D}X96NIS5jp;d-R8rI?6gHY!@_(|3Ti-P zA3%>Kt21t$YyN?p;-hbw$+0t?>GaY|XO!!^=9F_TY1}s1U3`$DB&&i3i+#ZEd%xZN z;bE!f;9$Q7$3Oq|_m$5|n)WRYx{qNPEW#OhhNf|i>z?N8w(c{V`58Oo8@AzR?W}Lw zrk}HOI@&PL^84&Q(lcJaKVT2|c{}e9+Jk<84lm zN9|F+Y?u8pd(5xc6@T0w_b2QLf6|`xr|chs<565_342t8Tu*}9aKKMBkObk8KK^C9s%ZwMqC_f;M z@S^BjHu(@A{w(`Ux2xcJf{(n?vm{P}cRBU8_$V)v_bKo`{eO6O)*a&&@|*|H8Xz9% ztem1oygK1ze4I~^{TYgIvSYnNgj1rDVl??QKS-WuDZ-godLDuZr^I;bZSmLmVe)>R zBHY`%6X6kllswN-gxfu%=x{}>gu!R|9NE7?5gywU=W#LFg|o^}koS3t^5iSMPr;hU z#Z;H~X+BTh7bwcTy*p9X_!;tilTe@SS=Z#R1M_nH9PzKiLW5)6Z-^{EFBVc9{rm!d zlV-a}Q7rV#ww1>hVTQNFOzLg%OZ+l_;_JI`U4SuGxX5qwJG9CbzPO}SfAJme zL)Dz>yLR`^$IW&qq{`h8EwRc2HF*2py}LIbH$*FRo2$wULMLnos{ek=YdZXv>j`DV zvMK$lu5wEv2*9*zDC0dh2$l6AxjLRQZn%w5Wv;ERRhcqZL|Eq!R2i?0wv=KutUKIy zSL^LSNSMij%OkfTf@)qBt~;SvY09#Qc$J$GZL!d6=~9FOn#&WecF*>W%T%XiyBK*oKD=!LHG7;-Cu8LV5ih7&qo zbERE?GHiaa`PDan+5BqrubY3{{2KGrl499?tc=iE3DyVD7!%<_T69&AB4{;N15x*z zTzJZgpRTEVl2S~jV*EiPgH?_X)ZsIu{R7VLaoIoXq4uS|!8Ww#Y)N~ee^6zz2=~>2 z1|__nOkB;#BXD0HMKKHFg(jSo?bng+0wP zc@mWM@va=zo+QBQF8`An=o&T9D>+cl!bu6iz~h8uHna`>d1gtI$KYm~ls2)hlN3Xq zhQ~dbp}a)m1Uyxh9QgArAp{*na-fc!!Sn=q5OI36=Xy_ShUj&UU)Fjkg{bPplU^7jU4}qrGChpbN5$-C z@;r>z`)bLHqH?jekK`0$+Qgnf-U~2khrGR@#ttG_eY zm0fZk=jyItA?8H_(vTO?WDlfUol>M!zq9Cn3B-O<71JU?k%>_3N~5u@_9*~bn^0Uq z01QNOppMlg7Ip19dbI0oNea;;NhcSf<9Y$Sev}#9c6uu})wQSAWBU{nrIIkc4X*1m zJD0EaDEbxzDi>?pikPB@4j$eC+#Kp!sDH^eGO$g+&KboJ=Z-ee5r^|+v#-bVR^t*c zVj#O_cxngFG$gEUV1i&zO_tZ?JD>tDlMrf8VlCojXRs}#WY^oy%W@CJI^eG(?nM;4 zf}h#hImLesy@@L~LE8sU%RRhHP-P^c+8xl&ekq_^2#7fHE@Zeko&@$C*#P{3aFYln( zHM;4Y{L|Qe3BG{=f;}x-5=ZO9wJ4I>6S@*jtVC5=()Pg>z>j@c4}L?whp692@ha%s zT~p|vpfB;{L(ulYlTuH;DI)j)5Tf0|?Btb#`7MGWj(h~#cO1kA_mmMlbXzLxxxO1h zgDf1`_*C^_BMZ!E2vD8Ji3)Ua8ln40G%RpV4%GYLB(`8TfNa^wMO=Zc#n{Mhn9r=3 zGx8uD?QB0BYfcy=Ns#jHR z*@1q)(p%A{z=@7vbXDG;iR6H~Vw%wumHbRcFlo^qW+AEcTGEj>nv8AA|wZt!`#7`7$ z=kg|MxEm?sL9^-6i!1RpkVrq3U51}vA=Rp@##C)8v~?N6YZLqz!Sam2lhWC_9obyU~gvGOVeaOnmP7wW)*+<8LCmsQ2+n{ literal 0 HcmV?d00001 diff --git a/backend/app/routers/__pycache__/content_router.cpython-310.pyc b/backend/app/routers/__pycache__/content_router.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..bf24fb260272a10533f2daa6ccec063eec80fe01 GIT binary patch literal 2116 zcma)7&2QX96!+MU*PHBaHpwR0d^c%Yx)sDHZU|8kB#@v)q`g>3mgAXSOgw8dV^>X@ zNM!+(+^X%Xw|$%7zL`vM$9FVuB^B=aF0cAkKJ8EQ z8GlCa+ewYr{W@>>4Lb;nz%{$3FU+@=r%Wvs*C0XQ4{xUHbp)RfV2%Uar z`fb+!dWFs~m#wmD*IY4Z?U~6;HhWHh*3U`Tq;YVs%pt(-x z|5I~;ZGd)br0vocUDVo}puP1!v|rm@qRU$I254?i&_t|+qD91M(>7hv@;h37^%~An zevPdTO4tHDdq0kHW!>waDEre`=F0tC zONU9vQcjQM0XBr7SU(TKD32epppal{>Cyq^abNF)2^WqZ#SxPoQ%xU)J@%ns zVa`haVMc)mDf=Oaow{C>_H($qXn->pKVJNL@ynmzU;K;|)SXPQZv0r;dDxSuj+eo0 z=tV;?nWcTn0-jQqC>sw{ZHQ9?H0e=fVTc9;c^l^MFkhjy??`U^M9$1J`q{s`i%o93mau|c4T<>Kksx<~70P!r1`*|JgzCstwZ)>L5 z0Oqt=hQxbJKLrzgmrWo8->oy_j6Amv4Y2~7BmWDXq|$#|Y{Ai$ zAm|X3WF1abYfNJVcOFg#a2uxBhSeMMA=+r;5(Y>s%h@mo8p9|D*UXOzkIZAT#3qtm zG;IPI4&*T;S4)AErEm#|i*>Fm!7L-8@5K@r^M7tAG_sCiDXqT+-0A!vPckWb-V>xx zm5rY_;u5#;hVhUzM@EiL0@Q_k-9e#Ah*qu&kC0&^N@RC|dx<{^fN~sXYAT6&oL@`Q zq{4xwnNT>@p-~BK^ETRf2gxLS^CQs_u-hoRgTw=JzjI4fx*-y8J=e`F(iiEun|9L}VJ+zG)X_J;& XBTZ5x4Xb6|CN29eO#H47<{k1c^w$j@ literal 0 HcmV?d00001 diff --git a/backend/app/routers/__pycache__/order_router.cpython-310.pyc b/backend/app/routers/__pycache__/order_router.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1e5be832fee5f66a55e057fdd1a50a58e46b2484 GIT binary patch literal 2133 zcmb7FOK%)S5T5Rt-JN}Uy+D5(_7nxs1lsZ8P@t&d@y@#b%L+ z%njwtl^lKJhQtkVkHWRDoN~@B;Z@b+-K-OVpjTZ}Rb5s6byan5+HBS=7}du=AFsHU z^&1M4qXEMEFx6vXS|pnpgtA0ZwtqF2G*LOwLuZo&q6Lr6C z^iI+cO}{Bxe#^+EWJ$%0t)7sZmlL@Z8N zmDP?3t3RavGG9Kq!5X~AS9tx1E?TVlkaEgf7X)bYf*euS;vF`{=UJPVkIHPCb-pQG z5Puas*VxRzdUpBxgy$@qGoBmZx%nSFTYP5h?Xr2cV7#{s;>G{5?-E-!o^Ke$4es)8 zaZbqm4YqRav?aC*nAc#g^D18fyV1#OY=do@INNOH!0K&1#Y*=aUA?*g@n`8E=TcYp zc*gro>E=gw?(BbfKjK*)r+r;kd6*AWkLbz)R|-_%=X?^YT)Vf8NtkG7FOG6;-|V0D zs4gG#JYa`<9>38*O5V?dFv{b5JQyf0A(RWBdvU~7&(`(Zl5zQgUNPkw^=i@)uClbRcpy^76J0Xix-kNcp*3ZPk&XGf8pxMm{sQw^2gn1GTi=r*9g-6|v>w?9 z)+M>svt<+3AeK2;0Z`MJ!NLM8$VDVeKrSu02A}7?ho@BDd3@^arO`k@+LX61(_md5?O1=~Jz zfY7hoRBixsUiZE#O|9!Te%iS&9=*pGPfT5d9EI>YWwE3f%0`0KH<5KqeUXGo^f?!2yQvfsdsdh8 zvn=i(OKcE{A!QW_{uOE$ZqRN7S4!4Vhfj#ML07c!BCaY77b=Jc?@w?gNxVmL3P~G% z=FrkSC~DU{P6aQgaj}Y*{8M~w{AIM0JWEv!7dhsmu&8>OH{B>UD=xEkz#Lv$l}mGB zwGC;~He6;nQz;nloCqH@N-@O_ZP^2mTme6b?EQ3)PC_d8{rUM literal 0 HcmV?d00001 diff --git a/backend/app/routers/__pycache__/review_router.cpython-310.pyc b/backend/app/routers/__pycache__/review_router.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f8e8c3cda5a98bc978c4dc82ff18f15191665996 GIT binary patch literal 2052 zcma)7&u`;I6t>5Xm;hgmS?HC5mjT`v*VgqQ@R<= z#IxRP?08OG_v&h`5;fvEZ%)mvXg*%>7UHJYRC+a9j9cE4t}&h3v^LP`%p2mhS^Ka< zXPM2GS*=f2H0rz|j4z(?FlTemysHD z1N=5&+=GG9+H{L(c^R-Y z3zMO=MApl4(bZ*jp9uj=T_VlhFvz6wWO&jwq%~lfPY+}V-$Bkf8)kkl$iidh=K_vy zPi@dR9IACV(uVEhFkk}Cbe^%}kbUzpXT6M#v==D_4J^$UV0vo1Tp5YnfdH<4y8hwn zx9itezh1wF1FV#@e)v+FS#KcT=`MIRI1_0y6wHqk$|7kM?$Q~-RQL!z2n=MTd~jZY z@dpgi1o6F|X+P^{Z3WpE&X%Y^zI(+zV69oBgp73>;oI38S0<0g>Z)I&f572D%;2e6sAYmCM zB?-nEK@!e@jzi<3vaj(DYbBsV**-07>2OMjj+qhx_lRE+QAFtsWvchL^!$68v@2{eXz>|7nr|hFr z<=A)}6Dr$ir7j?`gBMP583{gzn>D-=K3BI>y*#1lX=$d3fJ;_nT(zw<(i~K1X;uBw zDvD6mnaZa$fFs4g1bKMxaMf4#2LK_D*oW}1)&y4PvgYXXdW)cR^oG$Q5A~M$2nN1! HJl6jKAEFPc literal 0 HcmV?d00001 diff --git a/backend/app/routers/__pycache__/user_router.cpython-310.pyc b/backend/app/routers/__pycache__/user_router.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e4d48d7b45beb5553ff6726893bb9e81745137fb GIT binary patch literal 2204 zcmb7G&2Jk;6yKfQtiK({b{yxU%~w_79a_ zR!-Zr#PV#F6}n_qqvfZB5mr9eVJ@BPT|z6YMXSu}nskxYzFj!ioi#wMzXi3#mM)PN zt<#1=ZUFKYZQdwvnbl{IExMwRx0THHjmTBDNtapU5}l`ObX}ph0KHA$j-J|T(G7Y_ zA$I`TrnldaTbc3DCdj=)ZqaRp+@(85T4(ofjDE+Ih5f^iKaB?|<1%-^64s|eRz7<0 z;PAu8K1Uw?} zJYw!Z0PEJ=1&xBfItP|{xPBD)OaPnWF%ZWI^-?A)`;>B!_5o+JWOfTU6WD~~cr4A->xrRZM+`VrfqfG91#_dA zvQU<%iYtu?J@ESH-Q>fAW*iK>*A?WkT^*k^izz^Bq!>G-v#P_IJN;5!_us}-8sdKOtEWyyYU}LcdC;zXA2E` z2c}`sexkAzotm5M-cS;~syteS&1JS~Sd&>U5LJ~N0|-gkf#v$h7cR>N9WrrGnecl+ z-S6CyxvmG@FbO#J18(4i@;R1_tcou}FZ4fW(b-qN(yOZ0aFoCewe8i>|$J1bN*eB mJP6r)@c%&*7`?Vu)@yo`l=Y@w)~iO7?C4GN?quA%T>k}X%O>*x literal 0 HcmV?d00001 diff --git a/backend/app/routers/analytics_router.py b/backend/app/routers/analytics_router.py new file mode 100644 index 0000000..9c12441 --- /dev/null +++ b/backend/app/routers/analytics_router.py @@ -0,0 +1,40 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from typing import Dict, Any, List, Optional +from datetime import datetime + +from app.core import get_db, get_current_active_user, get_current_admin_user +from app import services +from app.schemas.content_schemas import AnalyticsLogCreate +from app.models.user_models import User as UserModel + +# Роутер для аналитики +analytics_router = APIRouter(prefix="/analytics", tags=["Аналитика"]) + +@analytics_router.post("/log", response_model=Dict[str, Any]) +async def log_analytics_event_endpoint(log: AnalyticsLogCreate, db: Session = Depends(get_db)): + return services.log_analytics_event(db, log) + + +@analytics_router.get("/logs", response_model=Dict[str, Any]) +async def get_analytics_logs_endpoint( + event_type: Optional[str] = None, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + skip: int = 0, + limit: int = 100, + current_user: UserModel = Depends(get_current_admin_user), + db: Session = Depends(get_db) +): + return services.get_analytics_logs(db, event_type, start_date, end_date, skip, limit) + + +@analytics_router.get("/report", response_model=Dict[str, Any]) +async def get_analytics_report_endpoint( + report_type: str, + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None, + current_user: UserModel = Depends(get_current_admin_user), + db: Session = Depends(get_db) +): + return services.get_analytics_report(db, report_type, start_date, end_date) \ No newline at end of file diff --git a/backend/app/routers/auth_router.py b/backend/app/routers/auth_router.py new file mode 100644 index 0000000..3cd43f8 --- /dev/null +++ b/backend/app/routers/auth_router.py @@ -0,0 +1,21 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy.orm import Session +from typing import Dict, Any + +from app.core import get_db +from app import services +from app.schemas.user_schemas import UserCreate, Token + +# Роутер для аутентификации +auth_router = APIRouter(prefix="/auth", tags=["Аутентификация"]) + +@auth_router.post("/register", response_model=Dict[str, Any]) +async def register(user: UserCreate, db: Session = Depends(get_db)): + return services.register_user(db, user) + + +@auth_router.post("/token", response_model=Token) +async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): + result = services.login_user(db, form_data.username, form_data.password) + return result \ No newline at end of file diff --git a/backend/app/routers/cart_router.py b/backend/app/routers/cart_router.py new file mode 100644 index 0000000..1ef14e5 --- /dev/null +++ b/backend/app/routers/cart_router.py @@ -0,0 +1,35 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from typing import Dict, Any + +from app.core import get_db, get_current_active_user +from app import services +from app.schemas.order_schemas import CartItemCreate, CartItemUpdate +from app.models.user_models import User as UserModel + +# Роутер для корзины +cart_router = APIRouter(prefix="/cart", tags=["Корзина"]) + +@cart_router.post("/items", response_model=Dict[str, Any]) +async def add_to_cart_endpoint(cart_item: CartItemCreate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.add_to_cart(db, current_user.id, cart_item) + + +@cart_router.put("/items/{cart_item_id}", response_model=Dict[str, Any]) +async def update_cart_item_endpoint(cart_item_id: int, cart_item: CartItemUpdate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.update_cart_item(db, current_user.id, cart_item_id, cart_item) + + +@cart_router.delete("/items/{cart_item_id}", response_model=Dict[str, Any]) +async def remove_from_cart_endpoint(cart_item_id: int, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.remove_from_cart(db, current_user.id, cart_item_id) + + +@cart_router.delete("/clear", response_model=Dict[str, Any]) +async def clear_cart_endpoint(current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.clear_cart(db, current_user.id) + + +@cart_router.get("/", response_model=Dict[str, Any]) +async def get_cart_endpoint(current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.get_cart(db, current_user.id) \ No newline at end of file diff --git a/backend/app/routers/catalog_router.py b/backend/app/routers/catalog_router.py new file mode 100644 index 0000000..383e0b4 --- /dev/null +++ b/backend/app/routers/catalog_router.py @@ -0,0 +1,107 @@ +from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form +from sqlalchemy.orm import Session +from typing import List, Optional, Dict, Any + +from app.core import get_db, get_current_admin_user +from app import services +from app.schemas.catalog_schemas import ( + CategoryCreate, CategoryUpdate, Category, + ProductCreate, ProductUpdate, Product, + ProductVariantCreate, ProductVariantUpdate, ProductVariant, + ProductImageCreate, ProductImageUpdate, ProductImage +) +from app.models.user_models import User as UserModel +from app.repositories.catalog_repo import get_products + +# Роутер для каталога +catalog_router = APIRouter(prefix="/catalog", tags=["Каталог"]) + +@catalog_router.post("/categories", response_model=Dict[str, Any]) +async def create_category_endpoint(category: CategoryCreate, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.create_category(db, category) + + +@catalog_router.put("/categories/{category_id}", response_model=Dict[str, Any]) +async def update_category_endpoint(category_id: int, category: CategoryUpdate, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.update_category(db, category_id, category) + + +@catalog_router.delete("/categories/{category_id}", response_model=Dict[str, Any]) +async def delete_category_endpoint(category_id: int, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.delete_category(db, category_id) + + +@catalog_router.get("/categories", response_model=List[Dict[str, Any]]) +async def get_categories_tree(db: Session = Depends(get_db)): + return services.get_category_tree(db) + + +@catalog_router.post("/products", response_model=Dict[str, Any]) +async def create_product_endpoint(product: ProductCreate, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.create_product(db, product) + + +@catalog_router.put("/products/{product_id}", response_model=Dict[str, Any]) +async def update_product_endpoint(product_id: int, product: ProductUpdate, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.update_product(db, product_id, product) + + +@catalog_router.delete("/products/{product_id}", response_model=Dict[str, Any]) +async def delete_product_endpoint(product_id: int, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.delete_product(db, product_id) + + +@catalog_router.get("/products/{product_id}", response_model=Dict[str, Any]) +async def get_product_details_endpoint(product_id: int, db: Session = Depends(get_db)): + return services.get_product_details(db, product_id) + + +@catalog_router.post("/products/{product_id}/variants", response_model=Dict[str, Any]) +async def add_product_variant_endpoint(product_id: int, variant: ProductVariantCreate, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + variant.product_id = product_id + return services.add_product_variant(db, variant) + + +@catalog_router.put("/variants/{variant_id}", response_model=Dict[str, Any]) +async def update_product_variant_endpoint(variant_id: int, variant: ProductVariantUpdate, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.update_product_variant(db, variant_id, variant) + + +@catalog_router.delete("/variants/{variant_id}", response_model=Dict[str, Any]) +async def delete_product_variant_endpoint(variant_id: int, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.delete_product_variant(db, variant_id) + + +@catalog_router.post("/products/{product_id}/images", response_model=Dict[str, Any]) +async def upload_product_image_endpoint( + product_id: int, + file: UploadFile = File(...), + is_primary: bool = Form(False), + current_user: UserModel = Depends(get_current_admin_user), + db: Session = Depends(get_db) +): + return services.upload_product_image(db, product_id, file, is_primary) + + +@catalog_router.put("/images/{image_id}", response_model=Dict[str, Any]) +async def update_product_image_endpoint(image_id: int, image: ProductImageUpdate, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.update_product_image(db, image_id, image) + + +@catalog_router.delete("/images/{image_id}", response_model=Dict[str, Any]) +async def delete_product_image_endpoint(image_id: int, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.delete_product_image(db, image_id) + + +@catalog_router.get("/products", response_model=List[Product]) +async def get_products_endpoint( + skip: int = 0, + limit: int = 100, + category_id: Optional[int] = None, + search: Optional[str] = None, + min_price: Optional[float] = None, + max_price: Optional[float] = None, + is_active: Optional[bool] = True, + db: Session = Depends(get_db) +): + return get_products(db, skip, limit, category_id, search, min_price, max_price, is_active) \ No newline at end of file diff --git a/backend/app/routers/content_router.py b/backend/app/routers/content_router.py new file mode 100644 index 0000000..b1f3e1c --- /dev/null +++ b/backend/app/routers/content_router.py @@ -0,0 +1,40 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from typing import Dict, Any, List, Optional + +from app.core import get_db, get_current_active_user, get_current_admin_user +from app import services +from app.schemas.content_schemas import PageCreate, PageUpdate +from app.models.user_models import User as UserModel + +# Роутер для контента +content_router = APIRouter(prefix="/content", tags=["Контент"]) + +@content_router.post("/pages", response_model=Dict[str, Any]) +async def create_page_endpoint(page: PageCreate, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.create_page(db, page) + + +@content_router.put("/pages/{page_id}", response_model=Dict[str, Any]) +async def update_page_endpoint(page_id: int, page: PageUpdate, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.update_page(db, page_id, page) + + +@content_router.delete("/pages/{page_id}", response_model=Dict[str, Any]) +async def delete_page_endpoint(page_id: int, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.delete_page(db, page_id) + + +@content_router.get("/pages/{page_id}", response_model=Dict[str, Any]) +async def get_page_endpoint(page_id: int, db: Session = Depends(get_db)): + return services.get_page(db, page_id) + + +@content_router.get("/pages/slug/{slug}", response_model=Dict[str, Any]) +async def get_page_by_slug_endpoint(slug: str, db: Session = Depends(get_db)): + return services.get_page_by_slug(db, slug) + + +@content_router.get("/pages", response_model=Dict[str, Any]) +async def get_pages_endpoint(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)): + return services.get_pages(db, skip, limit) \ No newline at end of file diff --git a/backend/app/routers/order_router.py b/backend/app/routers/order_router.py new file mode 100644 index 0000000..97f7e20 --- /dev/null +++ b/backend/app/routers/order_router.py @@ -0,0 +1,45 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from typing import List, Optional, Dict, Any + +from app.core import get_db, get_current_active_user +from app import services +from app.schemas.order_schemas import OrderCreate, OrderUpdate, Order +from app.models.user_models import User as UserModel +from app.repositories.order_repo import get_all_orders, get_user_orders + +# Роутер для заказов +order_router = APIRouter(prefix="/orders", tags=["Заказы"]) + +@order_router.post("/", response_model=Dict[str, Any]) +async def create_order_endpoint(order: OrderCreate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.create_order(db, current_user.id, order) + + +@order_router.get("/{order_id}", response_model=Dict[str, Any]) +async def get_order_endpoint(order_id: int, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.get_order(db, current_user.id, order_id, current_user.is_admin) + + +@order_router.put("/{order_id}", response_model=Dict[str, Any]) +async def update_order_endpoint(order_id: int, order: OrderUpdate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.update_order(db, current_user.id, order_id, order, current_user.is_admin) + + +@order_router.post("/{order_id}/cancel", response_model=Dict[str, Any]) +async def cancel_order_endpoint(order_id: int, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.cancel_order(db, current_user.id, order_id) + + +@order_router.get("/", response_model=List[Order]) +async def get_orders( + skip: int = 0, + limit: int = 100, + status: Optional[str] = None, + current_user: UserModel = Depends(get_current_active_user), + db: Session = Depends(get_db) +): + if current_user.is_admin: + return get_all_orders(db, skip, limit, status) + else: + return get_user_orders(db, current_user.id, skip, limit) \ No newline at end of file diff --git a/backend/app/routers/review_router.py b/backend/app/routers/review_router.py new file mode 100644 index 0000000..3ddb328 --- /dev/null +++ b/backend/app/routers/review_router.py @@ -0,0 +1,35 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from typing import Dict, Any + +from app.core import get_db, get_current_active_user, get_current_admin_user +from app import services +from app.schemas.review_schemas import ReviewCreate, ReviewUpdate +from app.models.user_models import User as UserModel + +# Роутер для отзывов +review_router = APIRouter(prefix="/reviews", tags=["Отзывы"]) + +@review_router.post("/", response_model=Dict[str, Any]) +async def create_review_endpoint(review: ReviewCreate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.create_review(db, current_user.id, review) + + +@review_router.put("/{review_id}", response_model=Dict[str, Any]) +async def update_review_endpoint(review_id: int, review: ReviewUpdate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.update_review(db, current_user.id, review_id, review, current_user.is_admin) + + +@review_router.delete("/{review_id}", response_model=Dict[str, Any]) +async def delete_review_endpoint(review_id: int, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.delete_review(db, current_user.id, review_id, current_user.is_admin) + + +@review_router.post("/{review_id}/approve", response_model=Dict[str, Any]) +async def approve_review_endpoint(review_id: int, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.approve_review(db, review_id) + + +@review_router.get("/products/{product_id}", response_model=Dict[str, Any]) +async def get_product_reviews_endpoint(product_id: int, skip: int = 0, limit: int = 10, db: Session = Depends(get_db)): + return services.get_product_reviews(db, product_id, skip, limit) \ No newline at end of file diff --git a/backend/app/routers/user_router.py b/backend/app/routers/user_router.py new file mode 100644 index 0000000..431169b --- /dev/null +++ b/backend/app/routers/user_router.py @@ -0,0 +1,40 @@ +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from typing import Dict, Any + +from app.core import get_db, get_current_active_user, get_current_admin_user +from app import services +from app.schemas.user_schemas import UserUpdate, AddressCreate, AddressUpdate +from app.models.user_models import User as UserModel + +# Роутер для пользователей +user_router = APIRouter(prefix="/users", tags=["Пользователи"]) + +@user_router.get("/me", response_model=Dict[str, Any]) +async def read_users_me(current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.get_user_profile(db, current_user.id) + + +@user_router.put("/me", response_model=Dict[str, Any]) +async def update_user_me(user_data: UserUpdate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.update_user_profile(db, current_user.id, user_data) + + +@user_router.post("/me/addresses", response_model=Dict[str, Any]) +async def create_user_address(address: AddressCreate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.add_user_address(db, current_user.id, address) + + +@user_router.put("/me/addresses/{address_id}", response_model=Dict[str, Any]) +async def update_user_address_endpoint(address_id: int, address: AddressUpdate, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.update_user_address(db, current_user.id, address_id, address) + + +@user_router.delete("/me/addresses/{address_id}", response_model=Dict[str, Any]) +async def delete_user_address_endpoint(address_id: int, current_user: UserModel = Depends(get_current_active_user), db: Session = Depends(get_db)): + return services.delete_user_address(db, current_user.id, address_id) + + +@user_router.get("/{user_id}", response_model=Dict[str, Any]) +async def read_user(user_id: int, current_user: UserModel = Depends(get_current_admin_user), db: Session = Depends(get_db)): + return services.get_user_profile(db, user_id) \ No newline at end of file diff --git a/backend/app/services.py b/backend/app/services.py deleted file mode 100644 index 0fe9bd7..0000000 --- a/backend/app/services.py +++ /dev/null @@ -1,482 +0,0 @@ -from sqlalchemy.orm import Session -from fastapi import HTTPException, status, UploadFile -from typing import List, Optional, Dict, Any, Union -from datetime import datetime, timedelta -import os -import uuid -import shutil -from pathlib import Path - -from app.config import settings -from app.core import create_access_token, get_password_hash, verify_password -from app.repositories import ( - user_repo, catalog_repo, order_repo, review_repo, content_repo -) -from app.schemas.user_schemas import UserCreate, UserUpdate, AddressCreate, AddressUpdate, Token -from app.schemas.catalog_schemas import ( - CategoryCreate, CategoryUpdate, - ProductCreate, ProductUpdate, - ProductVariantCreate, ProductVariantUpdate, - ProductImageCreate, ProductImageUpdate -) -from app.schemas.order_schemas import CartItemCreate, CartItemUpdate, OrderCreate, OrderUpdate -from app.schemas.review_schemas import ReviewCreate, ReviewUpdate -from app.schemas.content_schemas import PageCreate, PageUpdate, AnalyticsLogCreate - - -# Сервисы аутентификации и пользователей -def register_user(db: Session, user: UserCreate) -> Dict[str, Any]: - # Создаем пользователя - db_user = user_repo.create_user(db, user) - - # Создаем токен доступа - access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": db_user.username}, expires_delta=access_token_expires - ) - - return { - "user": db_user, - "access_token": access_token, - "token_type": "bearer" - } - - -def login_user(db: Session, username: str, password: str) -> Dict[str, Any]: - # Аутентифицируем пользователя - user = user_repo.authenticate_user(db, username, password) - if not user: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Неверное имя пользователя или пароль", - headers={"WWW-Authenticate": "Bearer"}, - ) - - # Проверяем, что пользователь активен - if not user.is_active: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Неактивный пользователь" - ) - - # Создаем токен доступа - access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) - access_token = create_access_token( - data={"sub": user.username}, expires_delta=access_token_expires - ) - - return { - "access_token": access_token, - "token_type": "bearer" - } - - -def get_user_profile(db: Session, user_id: int) -> Dict[str, Any]: - user = user_repo.get_user(db, user_id) - if not user: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Пользователь не найден" - ) - - # Получаем адреса пользователя - addresses = user_repo.get_user_addresses(db, user_id) - - # Получаем заказы пользователя - orders = order_repo.get_user_orders(db, user_id) - - # Получаем отзывы пользователя - reviews = review_repo.get_user_reviews(db, user_id) - - return { - "user": user, - "addresses": addresses, - "orders": orders, - "reviews": reviews - } - - -def update_user_profile(db: Session, user_id: int, user_data: UserUpdate) -> Dict[str, Any]: - updated_user = user_repo.update_user(db, user_id, user_data) - return {"user": updated_user} - - -def add_user_address(db: Session, user_id: int, address: AddressCreate) -> Dict[str, Any]: - new_address = user_repo.create_address(db, address, user_id) - return {"address": new_address} - - -def update_user_address(db: Session, user_id: int, address_id: int, address: AddressUpdate) -> Dict[str, Any]: - updated_address = user_repo.update_address(db, address_id, address, user_id) - return {"address": updated_address} - - -def delete_user_address(db: Session, user_id: int, address_id: int) -> Dict[str, Any]: - success = user_repo.delete_address(db, address_id, user_id) - return {"success": success} - - -# Сервисы каталога -def create_category(db: Session, category: CategoryCreate) -> Dict[str, Any]: - new_category = catalog_repo.create_category(db, category) - return {"category": new_category} - - -def update_category(db: Session, category_id: int, category: CategoryUpdate) -> Dict[str, Any]: - updated_category = catalog_repo.update_category(db, category_id, category) - return {"category": updated_category} - - -def delete_category(db: Session, category_id: int) -> Dict[str, Any]: - success = catalog_repo.delete_category(db, category_id) - return {"success": success} - - -def get_category_tree(db: Session) -> List[Dict[str, Any]]: - # Получаем все категории верхнего уровня - root_categories = catalog_repo.get_categories(db, parent_id=None) - - result = [] - for category in root_categories: - # Рекурсивно получаем подкатегории - category_dict = { - "id": category.id, - "name": category.name, - "slug": category.slug, - "description": category.description, - "is_active": category.is_active, - "subcategories": _get_subcategories(db, category.id) - } - result.append(category_dict) - - return result - - -def _get_subcategories(db: Session, parent_id: int) -> List[Dict[str, Any]]: - subcategories = catalog_repo.get_categories(db, parent_id=parent_id) - - result = [] - for category in subcategories: - category_dict = { - "id": category.id, - "name": category.name, - "slug": category.slug, - "description": category.description, - "is_active": category.is_active, - "subcategories": _get_subcategories(db, category.id) - } - result.append(category_dict) - - return result - - -def create_product(db: Session, product: ProductCreate) -> Dict[str, Any]: - new_product = catalog_repo.create_product(db, product) - return {"product": new_product} - - -def update_product(db: Session, product_id: int, product: ProductUpdate) -> Dict[str, Any]: - updated_product = catalog_repo.update_product(db, product_id, product) - return {"product": updated_product} - - -def delete_product(db: Session, product_id: int) -> Dict[str, Any]: - success = catalog_repo.delete_product(db, product_id) - return {"success": success} - - -def get_product_details(db: Session, product_id: int) -> Dict[str, Any]: - product = catalog_repo.get_product(db, product_id) - if not product: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Продукт не найден" - ) - - # Получаем варианты продукта - variants = catalog_repo.get_product_variants(db, product_id) - - # Получаем изображения продукта - images = catalog_repo.get_product_images(db, product_id) - - # Получаем рейтинг продукта - rating = review_repo.get_product_rating(db, product_id) - - # Получаем отзывы продукта - reviews = review_repo.get_product_reviews(db, product_id, limit=5) - - return { - "product": product, - "variants": variants, - "images": images, - "rating": rating, - "reviews": reviews - } - - -def add_product_variant(db: Session, variant: ProductVariantCreate) -> Dict[str, Any]: - new_variant = catalog_repo.create_variant(db, variant) - return {"variant": new_variant} - - -def update_product_variant(db: Session, variant_id: int, variant: ProductVariantUpdate) -> Dict[str, Any]: - updated_variant = catalog_repo.update_variant(db, variant_id, variant) - return {"variant": updated_variant} - - -def delete_product_variant(db: Session, variant_id: int) -> Dict[str, Any]: - success = catalog_repo.delete_variant(db, variant_id) - return {"success": success} - - -def upload_product_image(db: Session, product_id: int, file: UploadFile, is_primary: bool = False) -> Dict[str, Any]: - # Проверяем, что продукт существует - product = catalog_repo.get_product(db, product_id) - if not product: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Продукт не найден" - ) - - # Проверяем расширение файла - file_extension = file.filename.split(".")[-1].lower() - if file_extension not in settings.ALLOWED_UPLOAD_EXTENSIONS: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Неподдерживаемый формат файла. Разрешены: {', '.join(settings.ALLOWED_UPLOAD_EXTENSIONS)}" - ) - - # Создаем директорию для загрузок, если она не существует - upload_dir = Path(settings.UPLOAD_DIRECTORY) / "products" / str(product_id) - upload_dir.mkdir(parents=True, exist_ok=True) - - # Генерируем уникальное имя файла - unique_filename = f"{uuid.uuid4()}.{file_extension}" - file_path = upload_dir / unique_filename - - # Сохраняем файл - with file_path.open("wb") as buffer: - shutil.copyfileobj(file.file, buffer) - - # Создаем запись об изображении в БД - image_data = ProductImageCreate( - product_id=product_id, - image_url=f"/uploads/products/{product_id}/{unique_filename}", - alt_text=file.filename, - is_primary=is_primary - ) - - new_image = catalog_repo.create_image(db, image_data) - - return {"image": new_image} - - -def update_product_image(db: Session, image_id: int, image: ProductImageUpdate) -> Dict[str, Any]: - updated_image = catalog_repo.update_image(db, image_id, image) - return {"image": updated_image} - - -def delete_product_image(db: Session, image_id: int) -> Dict[str, Any]: - # Получаем информацию об изображении перед удалением - image = catalog_repo.get_image(db, image_id) - if not image: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Изображение не найдено" - ) - - # Удаляем запись из БД - success = catalog_repo.delete_image(db, image_id) - - # Удаляем файл с диска - if success: - try: - # Получаем путь к файлу из URL - file_path = Path(settings.UPLOAD_DIRECTORY) / image.image_url.lstrip("/uploads/") - if file_path.exists(): - file_path.unlink() - except Exception: - # Если не удалось удалить файл, просто логируем ошибку - # В реальном приложении здесь должно быть логирование - pass - - return {"success": success} - - -# Сервисы корзины и заказов -def add_to_cart(db: Session, user_id: int, cart_item: CartItemCreate) -> Dict[str, Any]: - new_cart_item = order_repo.create_cart_item(db, cart_item, user_id) - - # Логируем событие добавления в корзину - log_data = AnalyticsLogCreate( - user_id=user_id, - event_type="add_to_cart", - product_id=cart_item.product_id, - additional_data={"quantity": cart_item.quantity} - ) - content_repo.log_analytics_event(db, log_data) - - return {"cart_item": new_cart_item} - - -def update_cart_item(db: Session, user_id: int, cart_item_id: int, cart_item: CartItemUpdate) -> Dict[str, Any]: - updated_cart_item = order_repo.update_cart_item(db, cart_item_id, cart_item, user_id) - return {"cart_item": updated_cart_item} - - -def remove_from_cart(db: Session, user_id: int, cart_item_id: int) -> Dict[str, Any]: - success = order_repo.delete_cart_item(db, cart_item_id, user_id) - return {"success": success} - - -def clear_cart(db: Session, user_id: int) -> Dict[str, Any]: - success = order_repo.clear_cart(db, user_id) - return {"success": success} - - -def get_cart(db: Session, user_id: int) -> Dict[str, Any]: - cart_items = order_repo.get_cart_with_product_details(db, user_id) - - # Рассчитываем общую сумму корзины - total_amount = sum(item["total_price"] for item in cart_items) - - return { - "items": cart_items, - "total_amount": total_amount, - "items_count": len(cart_items) - } - - -def create_order(db: Session, user_id: int, order: OrderCreate) -> Dict[str, Any]: - new_order = order_repo.create_order(db, order, user_id) - - # Логируем событие создания заказа - log_data = AnalyticsLogCreate( - user_id=user_id, - event_type="order_created", - additional_data={"order_id": new_order.id, "total_amount": new_order.total_amount} - ) - content_repo.log_analytics_event(db, log_data) - - return {"order": new_order} - - -def get_order(db: Session, user_id: int, order_id: int, is_admin: bool = False) -> Dict[str, Any]: - # Получаем заказ с деталями - order_details = order_repo.get_order_with_details(db, order_id) - - # Проверяем права доступа - if not is_admin and order_details["user_id"] != user_id: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Недостаточно прав для просмотра этого заказа" - ) - - return {"order": order_details} - - -def update_order(db: Session, user_id: int, order_id: int, order: OrderUpdate, is_admin: bool = False) -> Dict[str, Any]: - updated_order = order_repo.update_order(db, order_id, order, is_admin) - - # Проверяем права доступа - if not is_admin and updated_order.user_id != user_id: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Недостаточно прав для обновления этого заказа" - ) - - return {"order": updated_order} - - -def cancel_order(db: Session, user_id: int, order_id: int) -> Dict[str, Any]: - # Отменяем заказ (обычный пользователь может только отменить заказ) - order_update = OrderUpdate(status="cancelled") - updated_order = order_repo.update_order(db, order_id, order_update, is_admin=False) - - # Проверяем права доступа - if updated_order.user_id != user_id: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Недостаточно прав для отмены этого заказа" - ) - - return {"order": updated_order} - - -# Сервисы отзывов -def create_review(db: Session, user_id: int, review: ReviewCreate) -> Dict[str, Any]: - new_review = review_repo.create_review(db, review, user_id) - return {"review": new_review} - - -def update_review(db: Session, user_id: int, review_id: int, review: ReviewUpdate, is_admin: bool = False) -> Dict[str, Any]: - updated_review = review_repo.update_review(db, review_id, review, user_id, is_admin) - return {"review": updated_review} - - -def delete_review(db: Session, user_id: int, review_id: int, is_admin: bool = False) -> Dict[str, Any]: - success = review_repo.delete_review(db, review_id, user_id, is_admin) - return {"success": success} - - -def approve_review(db: Session, review_id: int) -> Dict[str, Any]: - approved_review = review_repo.approve_review(db, review_id) - return {"review": approved_review} - - -def get_product_reviews(db: Session, product_id: int, skip: int = 0, limit: int = 10) -> Dict[str, Any]: - reviews = review_repo.get_product_reviews(db, product_id, skip, limit) - - # Получаем рейтинг продукта - rating = review_repo.get_product_rating(db, product_id) - - return { - "reviews": reviews, - "rating": rating, - "total": rating["total_reviews"], - "skip": skip, - "limit": limit - } - - -# Сервисы информационных страниц -def create_page(db: Session, page: PageCreate) -> Dict[str, Any]: - new_page = content_repo.create_page(db, page) - return {"page": new_page} - - -def update_page(db: Session, page_id: int, page: PageUpdate) -> Dict[str, Any]: - updated_page = content_repo.update_page(db, page_id, page) - return {"page": updated_page} - - -def delete_page(db: Session, page_id: int) -> Dict[str, Any]: - success = content_repo.delete_page(db, page_id) - return {"success": success} - - -def get_page_by_slug(db: Session, slug: str) -> Dict[str, Any]: - page = content_repo.get_page_by_slug(db, slug) - if not page: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Страница не найдена" - ) - - return {"page": page} - - -# Сервисы аналитики -def log_event(db: Session, log: AnalyticsLogCreate) -> Dict[str, Any]: - new_log = content_repo.log_analytics_event(db, log) - return {"log": new_log} - - -def get_analytics_report( - db: Session, - period: str = "day", - start_date: Optional[datetime] = None, - end_date: Optional[datetime] = None -) -> Dict[str, Any]: - report = content_repo.get_analytics_report(db, period, start_date, end_date) - return {"report": report} \ No newline at end of file diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 0000000..7d5ae5e --- /dev/null +++ b/backend/app/services/__init__.py @@ -0,0 +1,25 @@ +from app.services.user_service import ( + register_user, login_user, get_user_profile, update_user_profile, + add_user_address, update_user_address, delete_user_address +) + +from app.services.catalog_service import ( + create_category, update_category, delete_category, get_category_tree, + create_product, update_product, delete_product, get_product_details, + add_product_variant, update_product_variant, delete_product_variant, + upload_product_image, update_product_image, delete_product_image +) + +from app.services.order_service import ( + add_to_cart, update_cart_item, remove_from_cart, clear_cart, get_cart, + create_order, get_order, update_order, cancel_order +) + +from app.services.review_service import ( + create_review, update_review, delete_review, approve_review, get_product_reviews +) + +from app.services.content_service import ( + create_page, update_page, delete_page, get_page_by_slug, + log_event, get_analytics_report +) \ No newline at end of file diff --git a/backend/app/services/__pycache__/__init__.cpython-310.pyc b/backend/app/services/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..731d2c64d01b6a8977bcc04d11e80b3ef1d883ad GIT binary patch literal 1449 zcmZ9M$!^;)5QgQ%OT62$vpc)9D4SlOD01kzC{Ppy=0YeEV+he$1|_>l->5H9pyytC z=C!B3LQkEMD5Z`7f98y4eh!DEqISDs(R2Ru$K?B}W&LL3|7V4ad-}z{2)^Z)LJLb! z!WCG-RanI`l(7O8tU?uQP%ClFO1Oq~sAB^f*n}pwpoMK{V+T6eg)a7>hkfYdI;`UW z1~`NvZomd^!e*Mk8gAh>Y~v2>;4bXq9_--=Mz|0AcmM}@2#0tCM`>(1JjN3^!BaTJ zF^usH&hQ-0(|48d0x#hbZLskQuJ9VJ@dj@27H;W%^`m9i{?MxEPq&qv1aTsjn8#A- zMi@QC5$0_nYcknW%ELE=fYKi zJ5BWFvXUtL%Zi16IG=@)`=S$|JCT~aY2$Kr<9(%(dhNPyav@1XYgCEu75z|&2oj09 zrzA!TDIQgXSxmzVrK_^>TGk!PY?-D+%BP*Jvp*ZQjHIzVck0P7duLa4+oUPE2;>*t zHo>`RYBpxaoza@87-KIrHuJ@HRX6kf;bt`RW~4^m12)nVH=~hwcouOupXfTBM6V4))ytnUQhibT^!@CGP8Q<|S7RFM1i51;$OO+WFMwL-xtPx+&cLNysJMw`)LbQwKHpRvvuFoui`#wG!Fm*z;@Ud*t?QQM3i#x7%z zF=Fg94j6|7*jp0H`dcU+ao92AgmKCk6J9SdnkLlGBJGUh&KVbsONPz3B7C#dHIEs8 zKRchOFR}AAcyiuF-W*9Lc0Q?5Al=(gzAe!-F}Hhc-B)gEqsqAa4=QNV`v3p{ literal 0 HcmV?d00001 diff --git a/backend/app/services/__pycache__/catalog_service.cpython-310.pyc b/backend/app/services/__pycache__/catalog_service.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..541fcd2805d727126255db6f69d76c831213aa85 GIT binary patch literal 5333 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 K+?+WvWBwn5q%Ot) literal 0 HcmV?d00001 diff --git a/backend/app/services/__pycache__/content_service.cpython-310.pyc b/backend/app/services/__pycache__/content_service.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38a296c17968f3382ed64d3ef4c245d2d37fb449 GIT binary patch literal 1870 zcmZ`)&2QsG6t~BY?Icc`w&~X{+lA#z5mK{4dqikgTM!47R%(0k0n7DFgIPN^Gn0j& zR6?s2HxB#<^tiVkSP2dsICDv_bLGT&Z?G)9H;zkNsgCA(=DjyF@AuyCIeDwqFyQ&& z_uq#19mDto7Z;xvEbhY)cTK~f1~pS7GXvAaIY~)o1y*JUwq9FlEpq}Va|1W?0#EDg zw4T+2x}Mk4M%E0PdhVot)(To#J7{MM!2;-9W>Sx}Xq`E%!`vgXVH`QML7PwQU{PCr zwgevSOCBC=(KdK2pL<;1zd$=+?P}{H>s;FF&?UMI))j5-vV|+w6}k%6)%sd}y}nL2 zwburmaf5lRzG2Xt^Y_IY8}v1L>uD`mqw9M{@AjXV%buy+Jtjnwk9tJ4KH1;j{pjnM z6%rK65i*iv(X*7joy1aE4@MK^?Pz0^!d8z)lF1}vF!$qpB-u!YoE152Y3xQr_K-91 zDCY+SdaI=eqbQxoBo?3M!?~T%LuD7>uN=C5gFbJR<|%23wEtI=oaf)>nskYR}@x6>Sf^5&KGOpBa1^w10ipKM;(I{x`{^ z{&pUZGXNp_yF4HAC=-3kAw7(SM>!A0IF6YJh0HnYA4c&bHlqEgDEe@~;{;6oDhYGN zRx#mAa7h!!3&h}?o~bp;#mK^h#t5%^(4__gp^8-y71Sv?v5qYOYav*^2&QwSW1ZL% zsa+z)ttDK)v{G%>fJ=dS0Fa+M)#?Vl6ZSpz#_Q*iFWg^6R2$ZEvvP;ROh!psvZz+Fs54|zqY4iv zAqL5@S{&1*matjEdw1`JgPr~GM{Q?&W4^CHNQFYv?-7&vqHQvxM&}cGHPQiGR zQ`LYRaT)5~^?>hs7Gnmg2MG(`=aRHo@v2r+GPO?ZW2n;9M%p5Epuvlr%PRUVCiws8 z=f+ye;y2+ej^*TUqtIbu%U)pcyRdu@hGuZb0S2Q6@ZW*gV9;AvZSiFqrSa!1n{4Gg z6iFk+HdWpSSx(0(`w;%;4Dmh)-{_cK m(>Hz6H8<@$wN3o_#3e|mmmvQoguHSr@{ePamyKHc@BaXoy{u3G literal 0 HcmV?d00001 diff --git a/backend/app/services/__pycache__/order_service.cpython-310.pyc b/backend/app/services/__pycache__/order_service.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4f63063d39d6660118d6e671d0c3997a8f46f58c GIT binary patch literal 3569 zcmb7H&2JmW72j`C)RLlQOR+55QI#MFfkt#b5cI<}np!a$pk;zM&S8;&SnrJFm3O)H z%+QGf3P@}NJrqIzffTTN^DRwVv}h6+J@-Og=h{>Mg`x@ad$S}(Dlw2QF|+$--n@D9 zoAP z5CwT9su^N)IPL(aP@SYc)nsU?)SACAF#u{u1Tq|>2^KzQ(1UOcw9J8|lTV^LQ`@~~r zPq7uuo}SLG^2Xf$RrUls4URST)RgB8^jzm>=J;ytEIW74kJgXzt@5RB@rLX?dlI~7 z=OoT*U1b~W2iWx-+q`PEp8gNrUdz^%t6a*o=(QYO|K(e6z47Y%3GXWm=s>Di4J0PK z?KDx^eWkY#qE-mTMOW~Cp~Ix;Dc)0tYI(YGITq?Q#q-O8$BG}(*ZPd;#n)-=c(NqQ z87Q*+N-xg#Rhr0O6}w{=hh5i|fy8zx(+dfNcT-5$f#7N&dWlPi7(YJV0or9Wc@dqm z?%P9a=nU-#?t!cDX78N!0i-M?eg-+-qm2mcF$s$=G1CzGoKM2QE^ z$uoCrr!hLcH5d1W+rgcA=0==O38gcJa$rpA=-_j}oP(qiof9P=J7P4bl-*fkJCE)R znk2oN(AG;s;=t_wp+-9sN;~7`{XnPpY(FGbkHz4qs zQo1R4Ufky09Z}?F?UP_K8a7D7O;Cx0Zt{>Pkd{WnQ}`HbXg_cc9O&neeu9Kd6ssi} zkA;!la%WW90M9XP#8Vi18qFvWk^(d`rvPm}RbUImtO5!uFy|heaof6U-?a~1xTUMm z--Q!?;=Ash$8_=o@4UDTe&7^kaAlGijd$V?+Yf}f2q6LkP zD_xxkrAZm03+-i>oGq|}uLR+(edC*Rc01=?KLOLc-5$k6#6?8KPaH%<7ZK6FAACnd zjOwNLmqCmH=+n_>8<_q4@OLE*w$T@0xkGashaVIF=ugD`SMdHF4ey6A@G=v_&KTE0 zX-Orveu?cWnMZjS#^}bzCdh*Xolh@QZtuQy;l=Ki*WdibYunqecIL1qjKquRCYL-B zK`EpJor0xanC`RoA9`|i z=he*^WJ`Ap5?e_=x(ddta{OGgM=eUzEM}GtAkAYZ_Rwbap z*jUHer8GQa)l)3RkHFmuW>tGiDs~C+KpN4XzuY2ToyG3 z8AMQJ12HOZT;?*53Ynp|holJ%&nUR?Uch`77z`_8ORaXOi0UOg%Q$P<#xkzpP4_w6`nBkOf(< zZlVs_qN-P>D34Mu%j1|rZH?w6NsT4MQhhw8!~zMR|4xFcB!h!qpJktv(31tGB6QOp z3T~-{sh>n=;-ZM*^{(1a1>(4fR*m$P?4Oc3Wt^fc4 literal 0 HcmV?d00001 diff --git a/backend/app/services/__pycache__/review_service.cpython-310.pyc b/backend/app/services/__pycache__/review_service.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ee044b88c4df4f82fd7d7bed96a7d7bd265ca75c GIT binary patch literal 1627 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 literal 0 HcmV?d00001 diff --git a/backend/app/services/catalog_service.py b/backend/app/services/catalog_service.py new file mode 100644 index 0000000..63feeb1 --- /dev/null +++ b/backend/app/services/catalog_service.py @@ -0,0 +1,203 @@ +from sqlalchemy.orm import Session +from fastapi import HTTPException, status, UploadFile +from typing import List, Dict, Any +import os +import uuid +import shutil +from pathlib import Path + +from app.config import settings +from app.repositories import catalog_repo, review_repo +from app.schemas.catalog_schemas import ( + CategoryCreate, CategoryUpdate, + ProductCreate, ProductUpdate, + ProductVariantCreate, ProductVariantUpdate, + ProductImageCreate, ProductImageUpdate +) + + +# Сервисы каталога +def create_category(db: Session, category: CategoryCreate) -> Dict[str, Any]: + new_category = catalog_repo.create_category(db, category) + return {"category": new_category} + + +def update_category(db: Session, category_id: int, category: CategoryUpdate) -> Dict[str, Any]: + updated_category = catalog_repo.update_category(db, category_id, category) + return {"category": updated_category} + + +def delete_category(db: Session, category_id: int) -> Dict[str, Any]: + success = catalog_repo.delete_category(db, category_id) + return {"success": success} + + +def get_category_tree(db: Session) -> List[Dict[str, Any]]: + # Получаем все категории верхнего уровня + root_categories = catalog_repo.get_categories(db, parent_id=None) + + result = [] + for category in root_categories: + # Рекурсивно получаем подкатегории + category_dict = { + "id": category.id, + "name": category.name, + "slug": category.slug, + "description": category.description, + "is_active": category.is_active, + "subcategories": _get_subcategories(db, category.id) + } + result.append(category_dict) + + return result + + +def _get_subcategories(db: Session, parent_id: int) -> List[Dict[str, Any]]: + subcategories = catalog_repo.get_categories(db, parent_id=parent_id) + + result = [] + for category in subcategories: + category_dict = { + "id": category.id, + "name": category.name, + "slug": category.slug, + "description": category.description, + "is_active": category.is_active, + "subcategories": _get_subcategories(db, category.id) + } + result.append(category_dict) + + return result + + +def create_product(db: Session, product: ProductCreate) -> Dict[str, Any]: + new_product = catalog_repo.create_product(db, product) + return {"product": new_product} + + +def update_product(db: Session, product_id: int, product: ProductUpdate) -> Dict[str, Any]: + updated_product = catalog_repo.update_product(db, product_id, product) + return {"product": updated_product} + + +def delete_product(db: Session, product_id: int) -> Dict[str, Any]: + success = catalog_repo.delete_product(db, product_id) + return {"success": success} + + +def get_product_details(db: Session, product_id: int) -> Dict[str, Any]: + product = catalog_repo.get_product(db, product_id) + if not product: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Продукт не найден" + ) + + # Получаем варианты продукта + variants = catalog_repo.get_product_variants(db, product_id) + + # Получаем изображения продукта + images = catalog_repo.get_product_images(db, product_id) + + # Получаем рейтинг продукта + rating = review_repo.get_product_rating(db, product_id) + + # Получаем отзывы продукта + reviews = review_repo.get_product_reviews(db, product_id, limit=5) + + return { + "product": product, + "variants": variants, + "images": images, + "rating": rating, + "reviews": reviews + } + + +def add_product_variant(db: Session, variant: ProductVariantCreate) -> Dict[str, Any]: + new_variant = catalog_repo.create_product_variant(db, variant) + return {"variant": new_variant} + + +def update_product_variant(db: Session, variant_id: int, variant: ProductVariantUpdate) -> Dict[str, Any]: + updated_variant = catalog_repo.update_product_variant(db, variant_id, variant) + return {"variant": updated_variant} + + +def delete_product_variant(db: Session, variant_id: int) -> Dict[str, Any]: + success = catalog_repo.delete_product_variant(db, variant_id) + return {"success": success} + + +def upload_product_image(db: Session, product_id: int, file: UploadFile, is_primary: bool = False) -> Dict[str, Any]: + # Проверяем, что продукт существует + product = catalog_repo.get_product(db, product_id) + if not product: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Продукт не найден" + ) + + # Проверяем расширение файла + file_extension = file.filename.split(".")[-1].lower() + if file_extension not in settings.ALLOWED_UPLOAD_EXTENSIONS: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"Неподдерживаемый формат файла. Разрешены: {', '.join(settings.ALLOWED_UPLOAD_EXTENSIONS)}" + ) + + # Создаем директорию для загрузок, если она не существует + upload_dir = Path(settings.UPLOAD_DIRECTORY) / "products" / str(product_id) + upload_dir.mkdir(parents=True, exist_ok=True) + + # Генерируем уникальное имя файла + unique_filename = f"{uuid.uuid4()}.{file_extension}" + file_path = upload_dir / unique_filename + + # Сохраняем файл + with file_path.open("wb") as buffer: + shutil.copyfileobj(file.file, buffer) + + # Создаем запись об изображении в БД + image_data = ProductImageCreate( + product_id=product_id, + image_url=f"/uploads/products/{product_id}/{unique_filename}", + alt_text=file.filename, + is_primary=is_primary + ) + + new_image = catalog_repo.create_product_image(db, image_data) + + return {"image": new_image} + + +def update_product_image(db: Session, image_id: int, is_primary: bool) -> Dict[str, Any]: + updated_image = catalog_repo.update_product_image(db, image_id, is_primary) + return {"image": updated_image} + + +def delete_product_image(db: Session, image_id: int) -> Dict[str, Any]: + # Получаем информацию об изображении перед удалением + image = catalog_repo.get_product_image(db, image_id) + if not image: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Изображение не найдено" + ) + + # Удаляем запись из БД + success = catalog_repo.delete_product_image(db, image_id) + + # Удаляем файл с диска + if success: + try: + # Получаем путь к файлу из URL + file_path = Path(settings.UPLOAD_DIRECTORY) / image.image_url.lstrip("/uploads/") + if file_path.exists(): + file_path.unlink() + except Exception: + # Если не удалось удалить файл, просто логируем ошибку + # В реальном приложении здесь должно быть логирование + pass + + return {"success": success} \ No newline at end of file diff --git a/backend/app/services/content_service.py b/backend/app/services/content_service.py new file mode 100644 index 0000000..f534070 --- /dev/null +++ b/backend/app/services/content_service.py @@ -0,0 +1,50 @@ +from sqlalchemy.orm import Session +from fastapi import HTTPException, status +from typing import Dict, Any, Optional +from datetime import datetime + +from app.repositories import content_repo +from app.schemas.content_schemas import PageCreate, PageUpdate, AnalyticsLogCreate + + +# Сервисы информационных страниц +def create_page(db: Session, page: PageCreate) -> Dict[str, Any]: + new_page = content_repo.create_page(db, page) + return {"page": new_page} + + +def update_page(db: Session, page_id: int, page: PageUpdate) -> Dict[str, Any]: + updated_page = content_repo.update_page(db, page_id, page) + return {"page": updated_page} + + +def delete_page(db: Session, page_id: int) -> Dict[str, Any]: + success = content_repo.delete_page(db, page_id) + return {"success": success} + + +def get_page_by_slug(db: Session, slug: str) -> Dict[str, Any]: + page = content_repo.get_page_by_slug(db, slug) + if not page: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Страница не найдена" + ) + + return {"page": page} + + +# Сервисы аналитики +def log_event(db: Session, log: AnalyticsLogCreate) -> Dict[str, Any]: + new_log = content_repo.log_analytics_event(db, log) + return {"log": new_log} + + +def get_analytics_report( + db: Session, + period: str = "day", + start_date: Optional[datetime] = None, + end_date: Optional[datetime] = None +) -> Dict[str, Any]: + report = content_repo.get_analytics_report(db, period, start_date, end_date) + return {"report": report} \ No newline at end of file diff --git a/backend/app/services/order_service.py b/backend/app/services/order_service.py new file mode 100644 index 0000000..3267516 --- /dev/null +++ b/backend/app/services/order_service.py @@ -0,0 +1,107 @@ +from sqlalchemy.orm import Session +from fastapi import HTTPException, status +from typing import Dict, Any + +from app.repositories import order_repo, content_repo +from app.schemas.order_schemas import CartItemCreate, CartItemUpdate, OrderCreate, OrderUpdate +from app.schemas.content_schemas import AnalyticsLogCreate + + +# Сервисы корзины и заказов +def add_to_cart(db: Session, user_id: int, cart_item: CartItemCreate) -> Dict[str, Any]: + new_cart_item = order_repo.create_cart_item(db, cart_item, user_id) + + # Логируем событие добавления в корзину + log_data = AnalyticsLogCreate( + user_id=user_id, + event_type="add_to_cart", + product_id=new_cart_item.variant.product_id, + additional_data={"quantity": cart_item.quantity} + ) + content_repo.log_analytics_event(db, log_data) + + return {"cart_item": new_cart_item} + + +def update_cart_item(db: Session, user_id: int, cart_item_id: int, cart_item: CartItemUpdate) -> Dict[str, Any]: + updated_cart_item = order_repo.update_cart_item(db, cart_item_id, cart_item, user_id) + return {"cart_item": updated_cart_item} + + +def remove_from_cart(db: Session, user_id: int, cart_item_id: int) -> Dict[str, Any]: + success = order_repo.delete_cart_item(db, cart_item_id, user_id) + return {"success": success} + + +def clear_cart(db: Session, user_id: int) -> Dict[str, Any]: + success = order_repo.clear_cart(db, user_id) + return {"success": success} + + +def get_cart(db: Session, user_id: int) -> Dict[str, Any]: + cart_items = order_repo.get_cart_with_product_details(db, user_id) + + # Рассчитываем общую сумму корзины + total_amount = sum(item["total_price"] for item in cart_items) + + return { + "items": cart_items, + "total_amount": total_amount, + "items_count": len(cart_items) + } + + +def create_order(db: Session, user_id: int, order: OrderCreate) -> Dict[str, Any]: + new_order = order_repo.create_order(db, order, user_id) + + # Логируем событие создания заказа + log_data = AnalyticsLogCreate( + user_id=user_id, + event_type="order_created", + additional_data={"order_id": new_order.id, "total_amount": new_order.total_amount} + ) + content_repo.log_analytics_event(db, log_data) + + return {"order": new_order} + + +def get_order(db: Session, user_id: int, order_id: int, is_admin: bool = False) -> Dict[str, Any]: + # Получаем заказ с деталями + order_details = order_repo.get_order_with_details(db, order_id) + + # Проверяем права доступа + if not is_admin and order_details["user_id"] != user_id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Недостаточно прав для просмотра этого заказа" + ) + + return {"order": order_details} + + +def update_order(db: Session, user_id: int, order_id: int, order: OrderUpdate, is_admin: bool = False) -> Dict[str, Any]: + updated_order = order_repo.update_order(db, order_id, order, is_admin) + + # Проверяем права доступа + if not is_admin and updated_order.user_id != user_id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Недостаточно прав для обновления этого заказа" + ) + + return {"order": updated_order} + + +def cancel_order(db: Session, user_id: int, order_id: int) -> Dict[str, Any]: + # Отменяем заказ (обычный пользователь может только отменить заказ) + order_update = OrderUpdate(status="cancelled") + updated_order = order_repo.update_order(db, order_id, order_update, is_admin=False) + + # Проверяем права доступа + if updated_order.user_id != user_id: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Недостаточно прав для отмены этого заказа" + ) + + return {"order": updated_order} \ No newline at end of file diff --git a/backend/app/services/review_service.py b/backend/app/services/review_service.py new file mode 100644 index 0000000..128c7e8 --- /dev/null +++ b/backend/app/services/review_service.py @@ -0,0 +1,41 @@ +from sqlalchemy.orm import Session +from typing import Dict, Any + +from app.repositories import review_repo +from app.schemas.review_schemas import ReviewCreate, ReviewUpdate + + +# Сервисы отзывов +def create_review(db: Session, user_id: int, review: ReviewCreate) -> Dict[str, Any]: + new_review = review_repo.create_review(db, review, user_id) + return {"review": new_review} + + +def update_review(db: Session, user_id: int, review_id: int, review: ReviewUpdate, is_admin: bool = False) -> Dict[str, Any]: + updated_review = review_repo.update_review(db, review_id, review, user_id, is_admin) + return {"review": updated_review} + + +def delete_review(db: Session, user_id: int, review_id: int, is_admin: bool = False) -> Dict[str, Any]: + success = review_repo.delete_review(db, review_id, user_id, is_admin) + return {"success": success} + + +def approve_review(db: Session, review_id: int) -> Dict[str, Any]: + approved_review = review_repo.approve_review(db, review_id) + return {"review": approved_review} + + +def get_product_reviews(db: Session, product_id: int, skip: int = 0, limit: int = 10) -> Dict[str, Any]: + reviews = review_repo.get_product_reviews(db, product_id, skip, limit) + + # Получаем рейтинг продукта + rating = review_repo.get_product_rating(db, product_id) + + return { + "reviews": reviews, + "rating": rating, + "total": rating["total_reviews"], + "skip": skip, + "limit": limit + } \ No newline at end of file diff --git a/backend/app/services/user_service.py b/backend/app/services/user_service.py new file mode 100644 index 0000000..9aaf83a --- /dev/null +++ b/backend/app/services/user_service.py @@ -0,0 +1,101 @@ +from sqlalchemy.orm import Session +from fastapi import HTTPException, status +from typing import Dict, Any +from datetime import timedelta + +from app.config import settings +from app.core import create_access_token +from app.repositories import user_repo, order_repo, review_repo +from app.schemas.user_schemas import UserCreate, UserUpdate, AddressCreate, AddressUpdate + + +# Сервисы аутентификации и пользователей +def register_user(db: Session, user: UserCreate) -> Dict[str, Any]: + # Создаем пользователя + db_user = user_repo.create_user(db, user) + + # Создаем токен доступа + access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": db_user.email}, expires_delta=access_token_expires + ) + + return { + "user": db_user, + "access_token": access_token, + "token_type": "bearer" + } + + +def login_user(db: Session, email: str, password: str) -> Dict[str, Any]: + # Аутентифицируем пользователя + user = user_repo.authenticate_user(db, email, password) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Неверный email или пароль", + headers={"WWW-Authenticate": "Bearer"}, + ) + + # Проверяем, что пользователь активен + if not user.is_active: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Неактивный пользователь" + ) + + # Создаем токен доступа + access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token( + data={"sub": user.email}, expires_delta=access_token_expires + ) + + return { + "access_token": access_token, + "token_type": "bearer" + } + + +def get_user_profile(db: Session, user_id: int) -> Dict[str, Any]: + user = user_repo.get_user(db, user_id) + if not user: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail="Пользователь не найден" + ) + + # Получаем адреса пользователя + addresses = user_repo.get_user_addresses(db, user_id) + + # Получаем заказы пользователя + orders = order_repo.get_user_orders(db, user_id) + + # Получаем отзывы пользователя + reviews = review_repo.get_user_reviews(db, user_id) + + return { + "user": user, + "addresses": addresses, + "orders": orders, + "reviews": reviews + } + + +def update_user_profile(db: Session, user_id: int, user_data: UserUpdate) -> Dict[str, Any]: + updated_user = user_repo.update_user(db, user_id, user_data) + return {"user": updated_user} + + +def add_user_address(db: Session, user_id: int, address: AddressCreate) -> Dict[str, Any]: + new_address = user_repo.create_address(db, address, user_id) + return {"address": new_address} + + +def update_user_address(db: Session, user_id: int, address_id: int, address: AddressUpdate) -> Dict[str, Any]: + updated_address = user_repo.update_address(db, address_id, address, user_id) + return {"address": updated_address} + + +def delete_user_address(db: Session, user_id: int, address_id: int) -> Dict[str, Any]: + success = user_repo.delete_address(db, address_id, user_id) + return {"success": success} \ No newline at end of file