From 6d88b87f9341d55d4276e7f1299fd396357111c8 Mon Sep 17 00:00:00 2001 From: Thomas Peetz Date: Sun, 21 Sep 2025 20:09:46 +0200 Subject: [PATCH] add comic artists --- kontor-angular/public/cross.png | Bin 0 -> 544 bytes kontor-angular/public/logo.png | Bin 0 -> 17196 bytes kontor-angular/public/tick.png | Bin 0 -> 634 bytes .../comic-artists-list.component.ts | 1 - .../comic/comic-artists/artist.model.ts | 6 ++ .../comic/comic-artists/artist.service.ts | 19 ++++- .../comic-artists.component.html | 7 +- .../comic-artists/comic-artists.component.ts | 15 +++- .../comic-section/comic-section.routes.ts | 13 ++-- .../media/media-file/media-file.component.css | 0 .../media-file/media-file.component.html | 17 +++++ .../media-file/media-file.component.spec.ts | 23 ++++++ .../media/media-file/media-file.component.ts | 13 ++++ .../media-files-list.component.css | 14 ++++ .../media-files-list.component.html | 7 ++ .../media-files-list.component.spec.ts | 23 ++++++ .../media-files-list.component.ts | 37 ++++++++++ .../media/media-files/media-file.model.ts | 11 +++ .../media/media-files/media-file.service.ts | 30 ++++++++ .../media-files/media-files.component.css | 5 ++ .../media-files/media-files.component.html | 8 +- .../media-files/media-files.component.ts | 3 +- .../media-section/media-section.routes.ts | 14 +++- kontor-api/src/apis/version1/comic.py | 15 ++-- kontor-api/src/db/repository/comics/artist.py | 24 ++++-- kontor-api/src/db/repository/comics/comic.py | 51 ++++++++++++- kontor-api/src/schema/comics/artist.py | 9 --- .../src/schema/comics/artist_details.py | 16 ++++ kontor-api/src/schema/comics/comic.py | 69 +----------------- kontor-api/src/schema/comics/comic_details.py | 23 ++++++ 30 files changed, 366 insertions(+), 107 deletions(-) create mode 100644 kontor-angular/public/cross.png create mode 100644 kontor-angular/public/logo.png create mode 100644 kontor-angular/public/tick.png create mode 100644 kontor-angular/src/app/kontor/media/media-file/media-file.component.css create mode 100644 kontor-angular/src/app/kontor/media/media-file/media-file.component.html create mode 100644 kontor-angular/src/app/kontor/media/media-file/media-file.component.spec.ts create mode 100644 kontor-angular/src/app/kontor/media/media-file/media-file.component.ts create mode 100644 kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.css create mode 100644 kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.html create mode 100644 kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.spec.ts create mode 100644 kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.ts create mode 100644 kontor-angular/src/app/kontor/media/media-files/media-file.model.ts create mode 100644 kontor-angular/src/app/kontor/media/media-files/media-file.service.ts create mode 100644 kontor-api/src/schema/comics/artist_details.py create mode 100644 kontor-api/src/schema/comics/comic_details.py diff --git a/kontor-angular/public/cross.png b/kontor-angular/public/cross.png new file mode 100644 index 0000000000000000000000000000000000000000..6b9fa6dd36ee8165272a13dd263f573507c78ca6 GIT binary patch literal 544 zcmV+*0^j|KP)L-ku(! z6_?D-?!0+#=VtbVZQGdTnZt~apI_HPK#=zVadHM((E`e*C+RoFb)Qo8-U{Lb7{`Tz zw4B8FZ|o?Wox+p=1wp47C;7ar*Xu~;a?<=sjPv>+otDjJ6FaGt!YnNyxQQhp)G1V! zk;r6ZtyV)c8pTbiRAnHRNXTxti%=+p$4aG2*+mMM&xrdiU^`VPk^N*+HX98^7!HT% zwA%;A{T)GxeQ|RcxyzV%! z$AbYD+)i1R64zB?q}NmTz}5|04~J#YG_goA*Eq(QJvp7pG14hUj1pZ^t>3S*xqHUO ze~r;}zTY_XkZ*}d@gm!;N90h8nBQenBUZ_ukZKNicn*hc_Pmc!Jn|2=s<~}>!Sb>Qm3!QP46b_JFw5YU70Tu$X}=T}ez@@t%j iG9vD)nDux55?}x$+|UyQVK_bj0000KKfcNJ0=KdK;n}H6psyi`aLHBC(1VPOvYGTb6UR--Kt{$wru0-s z$2%22Y3R=IEUk4To3pdBo}Y&=>!teJLiOL9mkj*>%25A%^U`~%GgCCzq|SV&xh8(* z?>wJ#>!RxC*exZY#?G{}@?UvS^D?GFidLxmvZXNWb8Fr z6ZLE7mfG8u#M65RF!NhJ2sgJ+=Ve&1YO>j1iGNxQTO{yk-h=fGpwm9Ya zH$q|eHWp;v;f#EVKbMA!deUwQ zsSX4*@|~EdDmX&@rj2hkLf<(*IB4AWt3 z?e88n1R7o8nh`SA>|5`~SQs9DWdRU=d;6OJbmuBL(<@J2+N~DL3AhpF3M|`wXAEMxZ4=g#PLBzJ`b|?B zMOWn?m)SL_I2Icf$HN}MDFNggytE!UvB|v)5k_i5+m>l!Vq%}1f2dL4VP|=}m*8B* z4cSb|wNGYO>Q9`)u~U_o_J6}Ls`i?ae|Dp!qSB=gR(AA14t%5(ZPGi>Oc~-JoHEp4 zQaI#*nwgm~y5oV=D;#={H}hXg;tQXt)~piuV+qiexj4pH?1_NEV$DoUVwbrh6tcDf zuJIn<->|q^Qkx{_+E-z21QIrztI~XIrP9M2Df@84gSwQ>UeE97g~lU?2LirCX7zdIZCvKVNfjxm*kR60XV6)&XF zUmOW&m+QtD-WYwvYROHb_ZSgUgXuaO~O-6w%r`#96N5v z^^$=lq0Gb-W`C~DH2dNRh_fahZhTaM`$SrqX%;v&o_emT`=!u>A?Nn+k3!uzxwy!7 zOK>3+hD}S3fK+>SwDV{2N2K~Bv3rC|oBqD;kDqVj6L5kS1kCvo*2*v zI|$uI@l)iaXjNYY(#je3u!0>oBVs{U4RnE(K(H*+(4>tsi~D`h6{y1j_}#`kzrNnL zmn)!q16j}J6aM)Uu`5n%X>2U%(*n4D1LLJtdVS+&h9_-mY?y|f)zvQ4y@sN;^@>?| zj_NH3k)7K~59XCoFiBYBsE{ns%h7S<3#(6JLwzNRZpeSg|R^HJ0bUs)xLOWW20=e%MsX36?O+v#)2u={Db3@^l>kD_j~;%n497694d@* z*zdoy;B?15PPw=+{5twcdT7Q^uX^jRh>(ijh%K^1(xS152KKe#Qi4 z_p|Ys0t#ay0zHmNLHCsG^i&MTsdtyVjVfamv0=-=+Y)epu0N|hDYr7SA&Q6B)m+|l z4`RtLj=<>b--|;q1OB)F^}>=v6lLi=Ei7|`WC@NjBuntI0_BluJoZAvlvdfvb{ z96@48Ld4BNn`pF!`P9&6-EQP>5ijAOPwat~kE!e&`VXz_6s-a3sClzvve3&E4xzUW z&}Qo7GQS7AcE7*Su)on_EeH%7=0$jzV}(&>R4=954mF@-00%|W_Uvr-u5agY!0FFg z*OW^^Py7rXsdi7~#V!828qx&grx$%Jgu%nPAsjTU_t~Wq`c3NIO?xb6G>Nv;blH|C zE$<*I?ku_?5Dx;hFeB!H(Aig1p>hVm@?8%4I|+iB5iL0qS1fwm$P;Pxt5rXY88O*h z`ap4u)j!f3iQd3`lC(nX@27b4QbWW?5DOOcddyHan@%ITd6;69)~W$e*W1a@qL@S1 zkWxrBq-ebyA}P5Y`t*Lhh^IKyMyQqHx8b<(&)mK82tkwNcxmZZv60?q72izDUat~+ zj((Z#yTI;D2wFb8mV}Br|9ob8Zd8;`{V@Tp#o7g;RB4SZ8jk%bZS1+ zWC^Ly6Tq%dzqdKoRlQrgsz};EeNKj_e8~U7v|+aIlU(A@5?_>2G~3ckA!-6re{xcN z(`n1|lYyrIbJXefsa3VRn1=RS14V?>>doNl4HF-}L-a(BI2^``(h+P}#Jc<)5tVi6+eVJqwbp0dwV(!in8+ z(M!k>i|WlF*MtTdMi557f0row!ZOBGl3Mcs>ywr3=TNHLe#CD zTkB9^P{@ghw81|F%8_6np3^lBE{8o;uZ$dN#1Tdg7}?fjF35ajSJJBOR*DwY z&(01WAJ~q9totF-VBbl5qNsiD6Yrl!BCCm6XU*qleaA{*})9=`JU>j*4j+g!~ktY z(HoN=FE1+})hIQ;lQU4+?I|~%T5eE8`HBi(3{q~HXgR*A;uNN>Cwvz^!~>x-=g!oc zdjd$KQ)5@%&P$0>a@lhDn!qVW_j&EUdDgG|e);flZgIBCMb!a3%~W=8Ifo<21ua=B z*(LDfd?kbH^MVZMt=b{TEfCJ?EFX%!1k*b zuV;RjpHykZlLOH~-p5g_b=8Jm1Z;~xzC8l?F#51!zIBj)Z7k?L%T6drV(y3?oqnvk zf}|K(f<5TQdb#DBx_A4)-O3%3CN_(_rkzL<{d2gVZ6q>E2&uauG15Kj_)u(IW5n)0 zMH7hX<9P#0O0wdOJkm@jiX`ZM5D8@J_W;)XmF)c=6j(PFY#&i`eB^lqWFwg(0hwU> zOd(4%=f)*oXBJk-V1sjPZ_6i&uYx2+uv+PyScqUF-D~Yw5t3dj#fntpV4@7k!7NRy zK-&%Rc(^Zq=FuQFAE08O7{^!VIlontKr@@o9%Z8f^5A%hF%{uL^eSM496p+%ZTs+8 zh@z>YikIHOMF(K=@Tns-CqGJol~6LA1CBZQ5t}@ruV3oLPp^~AJw2^aH)zMN6dlo$ zYKV^r2efS&r4Nvnke~@P%e{xz=~lSD?SuCLQl-;%Uo8-GvwfeTSkDpfa1jW{*T(>d zc$(I-jbaP`F&Q<7O@p5>f{A;c1cp^a=34g5Ms+`VxMS4^CuGydXQd;?#$Bd><)l(X zcJNz^mwrkqRK#I6b=~q`XQOXBms;I~0W}(7_f#$sxf9jO5VCJN;S@3p)L&8cmk|Tc zvT8aT2jo`IPtK6-b`xP7cY5%6#jqvADVx&Qjj|dS>>?o2ApJnh94NwAh2ZXUG>sXQ z@;t7CC{eGZk1fHBP-M06DIYl8cAbgSc7V|6NGFv|a-HWa5icw4ctpRqtwX7u*LglCUt;vT3LGiisGp@dc zLj^+Ddn!~lO+?eYm_`%?6j9(p7k|$B&$3Tyi#)!?r4;?6ir>j(Pm!mwTx=q(dq2Nl z!pnYoK6eM(p9~XF6IQ4{^Hel9ymqD2R7DDxS;gyg;|U7#H_m%Zt}lGa{8eledqL)~BV7LL`Bu*~A;g~D!20bgc6 z>_Say7w;+nNu5^w8X=+Lx>JCg?SthFa2?4*v)UAyjvYLzfpsX*B247mwO%TIH{!PO z>b8~`U^0jhpw{P){G{jtc?&Hag54@Fz^_*#BVBi}*@90qt$vnMXRo?KOxT#2G1{M`ey0;mn0fZhDLi&(d&%TiGjSa*mJs z-lhyuQ&ljEM1fF^P)|AvFO(BsI3}v?$4@U5&|Op5BAK#v_xRviT}|PuR1i3V9m-re znVedhv|Kr{%F$0jyarOH`zckLg+qdy9tXz_;3X3i)7**3r1MCdRVj6FMnyWBl;|I9 zfqft>h9I`{2OFiOr5jJ2ltX6>kVs@`r9~X527&KE-J_N(|1~M2th91qa=mu$9pbPu z&;_@t;-D?9c})Q2@$WWD4vFS0of_cpA+O|I- z_|(NY2Z1}3A+%deSLN2$jS7dfWUlF)h>i7u1XS4(xq=%_rO1ozay^`BjfyxfTJ`4= z{O&pif^r_=U!6l1yIWfxH{?Gp@?2~qMgPru1rOhJJR}&VdF?esX1oX;s8iw9Um+gm z_%1}#Oqopf48^H>zJCey@O@mOkuMexFAcQC-gEGUGz{)9522Sya-hwJzz_8;J(K4U!uV2;@Qd90%q0 zVrh|I3{F)TfS{-P+whsucFUe)_piLGbCi5>lb%k7l`eH!%Lv>7)QLcmr*l%7_|4$q z1A=Hg4^cu)jBAGg5+P`383?U)fvuh^+XRX1gGj4g>sEzn{Tpu+RcPfE5VD;TnRA9G zwp%(CbF^X|!?ZNSUa)Ve+jSWMHANo%ye83p3k0W@LhS4xczShu>yC;eGc>fHZ-f;f zP9-JWD_5_+rBQ`rn2Xf-wnRKAj%)ot9p0D8(Jc2H7!k4iN2EzxNh z!+Ulb|8c^>;*I|t}d$??O!7>Rkv4&+y2^Ai%dgNp(;X^hdlIKCc&DCi+Mx$%kCf6Ckn_LO zFQ{LFb)elX?~#NMz0!4ki# zsgO#1^r4|s19QGpz#p#QO0974nBLM4U-8|PFSwevHR^`NA!1@~4q1s)u5Ih$0!)Cg zT4K3ybz>}tq`G=Z2`hJwntq|+>LeciTy^@9*x;JwUEuX99yYN`khq{F#vYuNAj0Pc z6-*7qT(XHpzX0ZLFzl?h9NvOnT*Ma?Bfmnq%l-k(j(E{&Ug?8~_guxKwKT6D3wG?X zb?){Q_B<067k6J{7JHXENKXRG<+J9wpt;QwFCLX^~vLT zALnfLnycuQ+r7;E3OosZ&vDsZo;&;hbl+Us)rP)u*Zy3rhH$!MyioGsoiM979M zW@PuP#t)Ub_$oLAZV0x*6g0D?4p+-%Pjd&BXc0x6VS_LDImdO6trtq2{{Rw1j z-f=)oK=lld%;jI6D#)#BjbP*XksR`Rm=?UlvF{LS9B zwRf#i^;JKhME-`o6^H?ZXc{|KI4i8uRur%`SlwiQDJ%M52=qzbe1C-%p9Kzu>o(0JWO+re zk6j0yDL3>UwQ<8u!$32bz9>x=qgHF1aLWt+7yT2F!OY^fIU;8|VqV*pkL|FP@KaQs z9JgC*IhewAQG=xL(sI_bdxEhd?#!!D*^lX+oV> zjlsjGYm>?ke?4$Nl_V@G0GXjrc>~gRU0QjEG6VX3A1Tnc`~KpIx_^j9-!D$~;WX)h zBLh&z_xYT0jC{gPz_6CV z@vVErfR%|V|D@&w*I$PVJ_~}QMtAv{qd>nUM?DxrZsh3c=JuUkNJ7?cBK@PJK*g>J z>WJ90g01owlPAiTI*23Z9TJAcuQq+@Q+^|^cCGMmrxq&7Ea7h+$WQDtY`me^m1i*>>M5!Lp#>q&@CaKo6+(Bd75bLvqifZ z6OVIt7tH574rM=p!1ArrhvD&2y;9ZvACKI8Gu}(~iiBm#vNBSKjLq&pw4sA{k_U*> zZ$0@&M#ij1ngu}SqRse5pcl09A4i8pJUl(`pO{@E#g2Cww5{KJA^%7aujzZ02{TRy z%vSeW-AyjpNoW0|@)?B62g$hm{=bxqNTGp~3RcMp(@?+pz+E&nV(|FBG;>r{CD+BE zAH_s)C0*QEA6#_@1GWM|)x_oP1b*L#uAnl=8?UP8Au2YFhW^-4j-i%8+K zl;a>Dj8RpgRRI-&k+1EpswM@)&5BOu#v@$;TA;*dAk*7+?k2{psD>spVOdR1eziNUh=9S-Mu?_pp4w4QX&-1k%Co0Kgmfq)|AKL;t# z-Er>al~frWOmnJ6xA9!zbuP?XGbDhH?U5Lb)peIAR$uK!RcgH!j8&jJ$qBP42g6O1 zzpaURs$Ic2VH#7%Zmo)B;L`AQ=W>_kuSoQ#@^_+NspdUb>QdV^oGf?Av9no#7}rla zbLVuW#--W8eLoRp5bs`51+ldGFC&#a(^oG@a4wQN9Z>skFKR~cY@`g=Zmx)d_RB(g zVA=|S_vV$y%u(DBGhH9vi+p^1rQu&f`F=ymO}TPFqrl@-vOUXIQx%)#IwgF$R>ov5 z*~0Q$RvTUH)2PKtNyyqY=Ae!{`$q;9WVx9L!^!Et@ZFaxR|N z?Ca+2=Qdf`fqwTh;am&}31K+(D>InjT*RE52ygdH3Nc3w{raVeXH@{j+5_(|Z-Sh! z>DLT_ykXYUNs9{ol2EVM>w|=nLnxXi2u)7HS)Um|cm>_7WV^4h(APZ!6a!F} zzioK@h(m?LodXYxdn1)W4JNOE-p{pS z^!Sa6_11bf_V@RHhM%Qf_#w}p92(?IkKW!~HtUp5Qb{!sK|_5^;lC^_#nQDfSH$xQ z+LP7t)Fv*=ZfW#g%?dFCvkba#ub{VBE>;HEehrU=*Fk5uDwcde>CLy|=iJ==rlxp) zzB4u&eOKDH=H#i+srQ=dbE`G_!Y8Cf%2L%)-RU#;B$_l~t5d?~J3fD=`?!$sff*qt zAMB&*Hf3g$76gj^5vJ>)(-0ArMD%}wy8WB^a;?R7UpTj5mtz<=krTLN8sJpMLp?QV z=h9*o1**a$&j3&iKM#IQFH60b=Mo9V#EqY{KvKG6wdUiufbT9rUNo5Xe<7G^>gh#c zvhV-oG41wW)gjKUC6ra%75>48F!64GYGbn%;CsT)#}|yX_CPt`1euYUTh8COHUHQ~ zI7O_?L>1*HSLitr$@vxe5bbeTur_2)i-QJy{%rjBU7mdw(q7I5` z6We{G0tA!3=yVGfd*bP8>hr)-y1#Xc42>K=O~Lio=;rtPL8pyav*t6&yC`27ljG%_ z{xsz8xa-_Hax#1@1l@;_ZNFvgEj82#hv8#Xh}kS5E{^E$x>T{1)Jz~JpQNINT`_Qv zjzUFfg}I_w=C}rh*$uG`{N0CA+X#pP%0Yv8yak;9%FK5_Me;5fu4k}jLB2+C0^|fi z&NI`lHJl+9iN5DvO~DVt>hI*bo=)7M-441L-ZI?SWVDT6mLf_Hd}FS~^AYP~sDY~8 ziZ6cMj2FLLD-9I1?B*l|C^Lhw$a&)929Rgl?FCaaGwdFrK4xn4RtYn8BONU)P@|8> z!s`7F8>OXYM=|iDo2+6!AzpOhX{ z`y0OB--&uJ_&)wS*}R|4I>oQK4p2EJ?Yrr4M>wj}UN;||o0PH~x>lkr~F z$adGA_cI38I#EGubeC)IJMoq$H;-?PIn7Tr-$7kZb<$#K!aIWzj_W=qmut;A7xN>U zWgaTmj7cQi{@%{(wCQB_qsMjUWfg`ykPna|UGILPP?E~+s^zmOntePd->N+JkBQq^ zwCFC(W>4-K@z8I|IJd$KBSF|f=rumuL$6bjPu^S0I+a;wxvAf~7JuMG5z;iVMQ_45 zwys(?X?AG%1;W_lS{KNG1YaE0S? ztvv0vw4!Qo7Tsn@yLGg0H4z5E+$-~Q<4eKD&UIc=Nh;of5P6xZ z_epCi!N$)lwM?*Pcj#o{p5n*~xstweZYUC*iu2 ziShc2^IlDX(@p18l{Jq8%WfIs^>eSqArRBUg@lBJ zky8J<$;#nKwLB}lT@w??_EnW{^+V$o^9BSa8rxcT+Ff;}o>khQpIQoM{d$RaOk-=0 z2rb{*pXl&?PVf_vr9z*ttN#AsDIvApb6ESR2J!$f7n1SPaj(sv^NR#w2gR!%@=(WbCkG?WCQ8N?oEWC;<5WrjGqdG0_ z4e?Jn_q4RM{8VxcZ<$sPxiMx`Ae_=|P*7>bx%hbOUD6uqGC$B;4xEd&VBQUic6F)3 zi112P3=hMDvl0pH8x}$AIjaZDm?kHw`Q_z~bzq@X>rc5WF29H~#kTiZ_w@8sHVhX! zLq}`nJ$=tZ#v=OU`0`gj))AhezPk7Z@G^~R?{0mN2GEb~>5nVhtvB;Bb~wX|iZ7Pc z%rH*K%anfVCH_)ZQ+vvsFtzJI?_8QY>FmNYKjyzRmt%!dGb@}vI!e7#!6Yxkm)|kV zZGHuD?cBXyJb2medZWwsky+#AX;EzTv%^hX9s14G(h@4}Q*w)?48bo%v+qj5Ci&t@ zvnZ-vWV@#@!^_N!v9$&LN&#SV`f2{igUFpPEc&zi}`-Q`JN%fgt*)m`FvqwD44!v!J_ zu-p_ilHSW1b|Q4inV;SOlQ*?{0AFRljJTv5x#(13bl&(L7*Y#`vNieta7ZLK{Sa#D z>`2)S%8g0tL5?IPd_EK*<2N>9YRQ@;1RZX`7f%29=u0aPDH$V;lef*n@y>%|$DMX5eAi=iEwoIO zblHE}7c)RGrVbnr%+;a_Hpe!mHcW4}x@C+lng<^5L|n4Z%gbAyp8m0H{j1&D5^8l@ zD1A>?o<`gA*poDbM`?4f;*$yuXu9l-p~94O3W3csS_F}$=S_J{o|vbGE^3t^Z;4PD zN?Oy`*PrgqP|1diZP_OpZ0_kq4jp-f^;oM9NQaRk^>!(`!J zSL{#XB@8|2LJ-x~PXuAiW(?hf3H*W7@{B77WPnC^*lcd*Ns;t3<#CnpWc*-xwk4Gw6iRQWZ+M&R(~Y;xaZz`|`e7o71;gdqmz(x0 z6S`dG{)OC}^Ua`x-?x`&5G9q~i#*85^)fS8F}dbEvn8)JY5XO5cKV&9J?zhtTDs-_ z1YY#kfmZAOCNH)hmCSPSwoj>~oB+p7P-x||{6XsBGAj>B7&?yeg5L5d^Yu>w0l6N2 zeiNf+KFMqI@3>L-r?0NxI}X6L@zq{2{;s@(fFD2a8J-f6VoDls_MGdCW*gh@6)U^F zao+p7Hss+_`*W>lzH9CTKQZAjL?eza&`ejGSTiHcrZhcQOf?N!oWJEkD$??wJjNdzVI$A`KFbhPrH_IwvCzYW`iI+@OBNR!1(h^Ifj zSug+QQwK27Qy+#pE3({>nPBAAz%7xO^0BVB)I2=rp>JQXwDJeZjQ^nhXH8 zFmcqH9dw@=^D1(^uwTx}{2qdgafa2?o2d_s(4i>G{;bUXVN*mG1oc|G|dT?4yp(yB2LBhRqT{DRSa#oIF-t4)YR&$)zd6)O1NeN50i)G>e@%R6WMX<80NMY_ zrc|H!Ua=ksQ2hs#;S4YXdST}D!Io&YwMT1ue%!P62M8jBS2^?zIHwBrRiI69;1L$I zVh=42zmTlok^c7!DS!CM<$31enARO?W4%YED&JuKt%j5bxjqpwCd*>YLoa zyX+G}1|mKQw=(lO=}SQ@U`l?=foWebctoj*75fVL9u+eh4?1$iczJ6G=Mo>5O=)q- z`y*#qGam)i(i^$=5gm9+HTW4vC1!m*#Sh31_6YR&T+EFP)8$aR#0oVy zq)5)|)v(dk5V;*N0Q+1d=Ucn=^p25D0?1wqfEfj?)f(c9GJ|~Qs!nnD-#Ab-Y+IS~ z(&i{1;!&aw7Ijnshk?a&-NkD{j!O~8Ca`~gPoqVerlw$v zwBl}oC`Eq*lgZMJ?Ccax$W@dbv6*Z7?I43~jW1_d<_aS-bNs=rvH0XJ;S%0Rt06p` zG~W)>E=p2i2Se|L0gb&K7oJ=s_rLJ4)T1XF&{-#@eHOv9?1s%?cz7L`lWx@EdQ^@61=z+d? zaYdWjYtZKg&5V`l?w42lC}?4SyBU_5W8HW$o+Yb&$6%Zss0|p;W61D6+bu71;r7~Y z{d>=7Hj5v(e+yg|2Q}Dg)=BVFEe$4eCNeTAjlMHv>tz4)?|;x?BF>rY$ZMg82^8+V zW=@IAL)>w>_9cNSxl7}Xb&ZXsW(|IZ`4BHc!^vo)_r|>u91erg_F8jl*zG>_i$xr# z?h=VZ;dFfNrzwJAlD)cwU(l4anM$I)i;FrQk4Nd3niU`K7n&_(67ZGqqC*kS`a7?n zv@iqW@9$@KdL#%FdxAI|PG9=;s2s65+y0a9*99u(d^$ce;7jyQ_5)8A0t;Lk_7#p# z4hhDXjkP*L0hQF=6B2A8)gh#W*Wf^&I%q91VAip|1k{^1Z&prw!f@_oJ_p+}4QkB18r|6ZnQE>%4|QUm`PLZz|-+J4Ym`S1}cGr@#yAvv6RyMj*KO zr~TQmQe0JXdbl=Ow>;OGAaZg-#08e7Ne4D;;-vldH0~>92JFuz#08dA=p7y&F1N>U zIATr!PWe$%2a7))%K-hr>Jl(%b+9vHJ|evlh}rcq)N4IFGZ-2a2`(4TVE)zD*LOK9 zN=oZa;8EEDK(9#prc=dKN*&*Z~?~qgoe|TLx$7igvKKqtJjrtV6+0gVYUjW4T$p$ z0JIbO-IFvbQPLU%}<08E=#;3Kqz(=B6M z8g)r%zM+!(vb*QCHldmBw;4)8)|oaNaM4dnU;1P(>tJstid$Mu?`VE&KO$|1#3?}V z#>&LHhu{o=1}gAmD}8w;N*aq;VU4QAQhj!&g}qlA&_R1|{6NTz7!~gOHSC_?sicoS zV*^k99isp;ghIRCv|^?bwdkjS@9OCN`IILmhE8OyT0YP-_lZ_7l3OvMDiAV(`=PrYva%j3V9>eEa z5kTe+qE7dyJY8Ks9`4wOE%c`2s(a1#kc0~w9H+c&+hnV2OKQr71MkK zLWis{pTz{f&{ut7JO>Eo5EvLZgX7}vRXIxFGrxUH?F2~YAW32YVIjag+9wwPq)>V4 z>GR1tdV0yZ#&+%cNSPDBS=Lg`KZjiwcW`rc{R}psS5DOibdCXWYiwEV9(~i_UoGzy zKEVLhB&jE=&WqrjIvd-)TzR**tG!l7+%uhDyNQ~YA^uO)% z0Rt>U&4!q5=Z^x;@~3WbwK%HJytJ^vd*ByeEtp_{cp*uA@iUFbyBG%-m)>zYZ#aRN zmmXlWzd#ZJA^}&md2qCgaajhP!vw^}^t*%KZ>WlHY~un?O~Ex>@aZ^kw}X3GMNqjp zAEkynuT(#9efI@@3!vq3F)g6cI0#gwD8T0wAh%3Xa{$zQe}RfwQlmEwQYd-F-^IlR z+PekB2q(^F*3v6|T7>l{PR2D264xdRP5KD9#!{f(cC$g*Bza~aXTtiP9Mu5TB&iL0 z<9+}E3)xx9OD_PI@?Xj2*R4nFDEkA}FUP1$X@aqZk$0XzM*KJ1IhKp7u;bNJ%4R_F za}Tx$)Q1iUje%KTuxMX>`{LrAo;U=AdYvT3Ayjv;izpIdmZzFRNa&CJG@TwMiJg(- z&PMBD^n^6r-Pl)h^18#ZvpTG&zGShYg_2^fOnWQZn$wkV}U0T1{=xU{#cg_WZXjK$K%&fXcqL8xowV6nG`aOgi)=i~G8^5XS+#Ova2$0q=~4Sf89e1d{JUDahc^`pk-h%cYDi!LwEMzwfOVKleC2wAkAlC?ZGF& z%TKx%3yb7G>&0cPJxGuL9HMRG_UGZBCnx(qC%IarHz0n&tu_7)|S>n!jHv-h5ilbpDq6#jDjVA@>uY(AXq9OBp@R8Sd{?$26DJ`#1E?EfLkpND@@<=;F0A=y6*|AQ_6_gQ6a^=~%0 zdb&IP5dmu}J{u<+sEsp>#3X@#G0ECW9A*!5viUC-$vDCOJBxtqi91<1+d(+Id8}=0 zEj*oI98$`%{|7w&wN;YuKcN1itYd4K$mX5uyr2F@fdffp{&DSHtl4|Bn9=ukpCx`#%PTUr-D(@b7;C zhJXJjrnw{;1KBM=6&|E`fsNrGL!Y6%zUh}QUl`(@V)PmQFtotEKmafTHP0uP}7&%m4pcKQ#X)BiAJ2y+PrtBI>9eEIt2-_c7)?*Lsf z5vX5*Af@m-wBJRV%#Fiz=BdPr0!2^a2d-yv7gSU);Z6{`~Oy?E5qSnHUln4*Y%!){F!Y1~5WXoC44g zb7l_)X~q^V6MmWR7e3wj|L|Wb!|^}Y86N$^h+kv_Kw-fP!~#If$6(B4$)LlK#&G6; zC&ShMSAb$5tA9Xg5dOsg@-z^@3;;QS9f&!gG&3|;{Da~@Q2ZB({s%XJ5&#fj0J|In U+D>(nEdT%j07*qoM6N<$g0@&8X#fBK literal 0 HcmV?d00001 diff --git a/kontor-angular/src/app/kontor/comic/comic-artists-list/comic-artists-list.component.ts b/kontor-angular/src/app/kontor/comic/comic-artists-list/comic-artists-list.component.ts index f1972da..025d9a6 100644 --- a/kontor-angular/src/app/kontor/comic/comic-artists-list/comic-artists-list.component.ts +++ b/kontor-angular/src/app/kontor/comic/comic-artists-list/comic-artists-list.component.ts @@ -1,5 +1,4 @@ import { Component, DestroyRef, inject, OnInit, signal } from '@angular/core'; -import { RouterLink, RouterLinkActive } from '@angular/router'; import { Artist } from '../comic-artists/artist.model'; import { ComicArtistComponent } from '../comic-artist/comic-artist.component'; import { ArtistService } from '../comic-artists/artist.service'; diff --git a/kontor-angular/src/app/kontor/comic/comic-artists/artist.model.ts b/kontor-angular/src/app/kontor/comic/comic-artists/artist.model.ts index 8529fa7..9a7ec9f 100644 --- a/kontor-angular/src/app/kontor/comic/comic-artists/artist.model.ts +++ b/kontor-angular/src/app/kontor/comic/comic-artists/artist.model.ts @@ -2,3 +2,9 @@ export interface Artist { id: string; name: string; } + +export interface ArtistDetails { + id: string; + name: string; + weblink: string; +} \ No newline at end of file diff --git a/kontor-angular/src/app/kontor/comic/comic-artists/artist.service.ts b/kontor-angular/src/app/kontor/comic/comic-artists/artist.service.ts index 7b29420..10a61e4 100644 --- a/kontor-angular/src/app/kontor/comic/comic-artists/artist.service.ts +++ b/kontor-angular/src/app/kontor/comic/comic-artists/artist.service.ts @@ -2,7 +2,7 @@ import { inject, Injectable, signal } from "@angular/core"; import { HttpClient } from "@angular/common/http"; import { catchError, map, throwError } from "rxjs"; import { ErrorService } from "../../../shared/error.service"; -import { Artist } from "./artist.model"; +import { Artist, ArtistDetails } from "./artist.model"; @Injectable({ providedIn: 'root', @@ -18,6 +18,10 @@ export class ArtistService { return this.fetchArtists('http://127.0.0.1:8800/api/comics/artists', 'Someting went wrong fetching artists. Please try again later-'); } + loadArtistDetails(artistId: string | null) { + return this.fetchArtistDetails('http://127.0.0.1:8800/api/comics/artists/' + artistId, 'Someting went wrong fetching comic artists. Please try again later.'); + } + private fetchArtists(url: string, errorMessage: string) { return this.httpClient.get(url).pipe( map((resData) => resData), @@ -27,4 +31,17 @@ export class ArtistService { }) ); } + + private fetchArtistDetails(url: string, errorMessage: string) { + return this.httpClient.get(url).pipe( + map((resData) => { + console.log(resData); + return resData; + }), + catchError((error) => { + console.log(error); + return throwError(() => new Error(errorMessage)); + }) + ); + } } diff --git a/kontor-angular/src/app/kontor/comic/comic-artists/comic-artists.component.html b/kontor-angular/src/app/kontor/comic/comic-artists/comic-artists.component.html index 792b214..736d4a3 100644 --- a/kontor-angular/src/app/kontor/comic/comic-artists/comic-artists.component.html +++ b/kontor-angular/src/app/kontor/comic/comic-artists/comic-artists.component.html @@ -4,6 +4,11 @@
-

Artist Details

+ @if (artist()) { +

{{ artist().name }}

+ {{ artist().name }} + } @else { +

Artist Details

+ }
diff --git a/kontor-angular/src/app/kontor/comic/comic-artists/comic-artists.component.ts b/kontor-angular/src/app/kontor/comic/comic-artists/comic-artists.component.ts index d5e70a2..43cd7d7 100644 --- a/kontor-angular/src/app/kontor/comic/comic-artists/comic-artists.component.ts +++ b/kontor-angular/src/app/kontor/comic/comic-artists/comic-artists.component.ts @@ -1,5 +1,8 @@ -import { Component } from '@angular/core'; +import { Component, inject, input } from '@angular/core'; import { ComicArtistsListComponent } from '../comic-artists-list/comic-artists-list.component'; +import { ArtistDetails } from './artist.model'; +import { ActivatedRouteSnapshot, ResolveFn, RouterStateSnapshot } from '@angular/router'; +import { ArtistService } from './artist.service'; @Component({ selector: 'app-comic-artists', @@ -8,5 +11,13 @@ import { ComicArtistsListComponent } from '../comic-artists-list/comic-artists-l styleUrl: './comic-artists.component.css' }) export class ComicArtistsComponent { - + artist = input.required(); } + +export const artistResolver: ResolveFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => { + const artistService = inject(ArtistService); + const artistId = route.paramMap.get('artistId'); + const artistDetails = artistService.loadArtistDetails(artistId); + console.log(artistDetails); + return artistDetails; +}; diff --git a/kontor-angular/src/app/kontor/comic/comic-section/comic-section.routes.ts b/kontor-angular/src/app/kontor/comic/comic-section/comic-section.routes.ts index 465a5d3..06d5f0d 100644 --- a/kontor-angular/src/app/kontor/comic/comic-section/comic-section.routes.ts +++ b/kontor-angular/src/app/kontor/comic/comic-section/comic-section.routes.ts @@ -1,5 +1,5 @@ import { Routes } from "@angular/router"; -import { ComicArtistsComponent } from "../comic-artists/comic-artists.component"; +import { artistResolver, ComicArtistsComponent } from "../comic-artists/comic-artists.component"; import { ComicPublishersComponent } from './../comic-publishers/comic-publishers.component'; import { ComicComicsComponent, comicResolver } from "../comic-comics/comic-comics.component"; @@ -19,10 +19,10 @@ export const comicRoutes: Routes = [ path: 'publisher', component: ComicPublishersComponent }, - // { - // path: 'publishers/:publisherId', - // component: PublishersComponent, - // }, + { + path: 'publishers/:publisherId', + component: ComicPublishersComponent, + }, { path: 'artist', component: ComicArtistsComponent @@ -30,5 +30,8 @@ export const comicRoutes: Routes = [ { path: 'artist/:artistId', component: ComicArtistsComponent, + resolve: { + artist: artistResolver + } }, ]; diff --git a/kontor-angular/src/app/kontor/media/media-file/media-file.component.css b/kontor-angular/src/app/kontor/media/media-file/media-file.component.css new file mode 100644 index 0000000..e69de29 diff --git a/kontor-angular/src/app/kontor/media/media-file/media-file.component.html b/kontor-angular/src/app/kontor/media/media-file/media-file.component.html new file mode 100644 index 0000000..b735566 --- /dev/null +++ b/kontor-angular/src/app/kontor/media/media-file/media-file.component.html @@ -0,0 +1,17 @@ +
+ +
diff --git a/kontor-angular/src/app/kontor/media/media-file/media-file.component.spec.ts b/kontor-angular/src/app/kontor/media/media-file/media-file.component.spec.ts new file mode 100644 index 0000000..4050101 --- /dev/null +++ b/kontor-angular/src/app/kontor/media/media-file/media-file.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MediaFileComponent } from './media-file.component'; + +describe('MediaFileComponent', () => { + let component: MediaFileComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MediaFileComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MediaFileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/kontor-angular/src/app/kontor/media/media-file/media-file.component.ts b/kontor-angular/src/app/kontor/media/media-file/media-file.component.ts new file mode 100644 index 0000000..924844b --- /dev/null +++ b/kontor-angular/src/app/kontor/media/media-file/media-file.component.ts @@ -0,0 +1,13 @@ +import { Component, input } from '@angular/core'; +import { MediaFile } from '../media-files/media-file.model'; +import { RouterLink, RouterLinkActive } from '@angular/router'; + +@Component({ + selector: 'app-media-file', + imports: [RouterLink, RouterLinkActive], + templateUrl: './media-file.component.html', + styleUrl: './media-file.component.css' +}) +export class MediaFileComponent { + mediafile = input.required(); +} diff --git a/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.css b/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.css new file mode 100644 index 0000000..d95c44d --- /dev/null +++ b/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.css @@ -0,0 +1,14 @@ +ul { + list-style: none; + margin: 0; + padding: 0; + display: flex; + gap: 0.5rem; + overflow: auto; +} + +@media (min-width: 768px) { + ul { + flex-direction: column; + } +} diff --git a/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.html b/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.html new file mode 100644 index 0000000..1688d6b --- /dev/null +++ b/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.html @@ -0,0 +1,7 @@ +
    + @for (mediafile of files(); track mediafile.id) { +
  • + +
  • + } +
\ No newline at end of file diff --git a/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.spec.ts b/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.spec.ts new file mode 100644 index 0000000..a97b136 --- /dev/null +++ b/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MediaFilesListComponent } from './media-files-list.component'; + +describe('MediaFilesListComponent', () => { + let component: MediaFilesListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [MediaFilesListComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(MediaFilesListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.ts b/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.ts new file mode 100644 index 0000000..eac3322 --- /dev/null +++ b/kontor-angular/src/app/kontor/media/media-files-list/media-files-list.component.ts @@ -0,0 +1,37 @@ +import { Component, DestroyRef, inject, OnInit, signal } from '@angular/core'; +import { MediaFileComponent } from '../media-file/media-file.component'; +import { MediaFileService } from '../media-files/media-file.service'; +import { MediaFile } from '../media-files/media-file.model'; + +@Component({ + selector: 'app-media-files-list', + imports: [MediaFileComponent], + templateUrl: './media-files-list.component.html', + styleUrl: './media-files-list.component.css' +}) +export class MediaFilesListComponent implements OnInit { + files = signal([]); + isFetching = signal(false); + error = signal(''); + private mediaFileService = inject(MediaFileService); + private destroyRef = inject(DestroyRef); + + ngOnInit() { + this.isFetching.set(true); + const subscription = this.mediaFileService.loadFiles().subscribe({ + next: (files) => { + this.files.set(files); + }, + error: (error: Error) => { + this.error.set(error.message); + }, + complete: () => { + this.isFetching.set(false); + }, + }); + + this.destroyRef.onDestroy(() => { + subscription.unsubscribe(); + }); + } +} diff --git a/kontor-angular/src/app/kontor/media/media-files/media-file.model.ts b/kontor-angular/src/app/kontor/media/media-files/media-file.model.ts new file mode 100644 index 0000000..9418003 --- /dev/null +++ b/kontor-angular/src/app/kontor/media/media-files/media-file.model.ts @@ -0,0 +1,11 @@ +import { StreamingResourceOptions } from "@angular/core"; + +export interface MediaFile { + id: string; + title: string; + file_name: string; + cloud_link: string; + url: string; + review: boolean; + should_download: boolean; +} \ No newline at end of file diff --git a/kontor-angular/src/app/kontor/media/media-files/media-file.service.ts b/kontor-angular/src/app/kontor/media/media-files/media-file.service.ts new file mode 100644 index 0000000..96e24e7 --- /dev/null +++ b/kontor-angular/src/app/kontor/media/media-files/media-file.service.ts @@ -0,0 +1,30 @@ +import { inject, Injectable, signal } from "@angular/core"; +import { ErrorService } from "../../../shared/error.service"; +import { HttpClient } from "@angular/common/http"; +import { MediaFile } from "./media-file.model"; +import { catchError, map, throwError } from "rxjs"; + +@Injectable({ + providedIn: 'root', +}) +export class MediaFileService { + private errorService = inject(ErrorService); + private httpClient = inject(HttpClient); + private files = signal([]); + + loadedFiles = this.files.asReadonly(); + + loadFiles() { + return this.fetchMediaFiles('http://127.0.0.1:8800/api/media/files', 'Someting went wrong fetching artists. Please try again later-'); + } + + private fetchMediaFiles(url: string, errorMessage: string) { + return this.httpClient.get(url).pipe( + map((resData) => resData), + catchError((error) => { + console.log(error); + return throwError(() => new Error(errorMessage)); + }) + ); + } +} diff --git a/kontor-angular/src/app/kontor/media/media-files/media-files.component.css b/kontor-angular/src/app/kontor/media/media-files/media-files.component.css index e69de29..a997f58 100644 --- a/kontor-angular/src/app/kontor/media/media-files/media-files.component.css +++ b/kontor-angular/src/app/kontor/media/media-files/media-files.component.css @@ -0,0 +1,5 @@ +.grid-container { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 20px; +} diff --git a/kontor-angular/src/app/kontor/media/media-files/media-files.component.html b/kontor-angular/src/app/kontor/media/media-files/media-files.component.html index 367cd62..ac8ed9f 100644 --- a/kontor-angular/src/app/kontor/media/media-files/media-files.component.html +++ b/kontor-angular/src/app/kontor/media/media-files/media-files.component.html @@ -1 +1,7 @@ -

media-files works!

+
+
+ +
+
+
+
diff --git a/kontor-angular/src/app/kontor/media/media-files/media-files.component.ts b/kontor-angular/src/app/kontor/media/media-files/media-files.component.ts index 58d983c..06fbc0e 100644 --- a/kontor-angular/src/app/kontor/media/media-files/media-files.component.ts +++ b/kontor-angular/src/app/kontor/media/media-files/media-files.component.ts @@ -1,8 +1,9 @@ import { Component } from '@angular/core'; +import { MediaFilesListComponent } from '../media-files-list/media-files-list.component'; @Component({ selector: 'app-media-files', - imports: [], + imports: [MediaFilesListComponent], templateUrl: './media-files.component.html', styleUrl: './media-files.component.css' }) diff --git a/kontor-angular/src/app/kontor/media/media-section/media-section.routes.ts b/kontor-angular/src/app/kontor/media/media-section/media-section.routes.ts index 9311eed..8214e84 100644 --- a/kontor-angular/src/app/kontor/media/media-section/media-section.routes.ts +++ b/kontor-angular/src/app/kontor/media/media-section/media-section.routes.ts @@ -8,12 +8,24 @@ export const mediaRoutes: Routes = [ path: 'file', component: MediaFilesComponent }, + { + path: 'file/:fileId', + component: MediaFilesComponent, + }, { path: 'actor', component: MediaActorsComponent }, + { + path: 'actor/:actorId', + component: MediaActorsComponent, + }, { path: 'video', component: MediaVideosComponent }, -]; \ No newline at end of file + { + path: 'video/:videoId', + component: MediaVideosComponent, + }, +]; diff --git a/kontor-api/src/apis/version1/comic.py b/kontor-api/src/apis/version1/comic.py index a4afb7b..4b2abde 100644 --- a/kontor-api/src/apis/version1/comic.py +++ b/kontor-api/src/apis/version1/comic.py @@ -1,13 +1,15 @@ -from typing import List, AnyStr +from typing import List from fastapi import APIRouter, HTTPException, status from src.apis.utils import SessionDep from src.core.log_conf import logger from src.db.repository.comics.artist import get_artist_details -from src.db.repository.comics.comic import list_comics, get_issue_details -from src.schema.comics.comic import ComicResponse, ComicDetailsResponse, get_comic_details, get_short_info -from src.schema.comics.artist import ArtistCreation, ArtistDetailResponse, ArtistResponse +from src.db.repository.comics.comic import get_comic_details, get_short_info, list_comics, get_issue_details +from src.schema.comics.artist_details import ArtistDetailResponse +from src.schema.comics.comic import ComicResponse +from src.schema.comics.artist import ArtistCreation, ArtistResponse from src.db.models.comic import Comic, Artist, Issue +from src.schema.comics.comic_details import ComicDetailsResponse from src.schema.comics.issue import IssueDetailsResponse router = APIRouter() @@ -23,7 +25,7 @@ def get_all_comics(db: SessionDep) -> List[ComicResponse]: return results @router.get("/comics/{comic_id}", response_model=ComicDetailsResponse) -def get_comic(comic_id: AnyStr, db: SessionDep) -> ComicDetailsResponse: +def get_comic(comic_id: str, db: SessionDep) -> ComicDetailsResponse: comic = db.get(Comic, comic_id) if comic is None: raise HTTPException(status_code=404, detail="Comic could not be found") @@ -41,7 +43,7 @@ def get_all_artists(db: SessionDep) -> List[ArtistResponse]: return results @router.get("/artists/{artist_id}", response_model=ArtistDetailResponse) -def get_artist(artist_id: AnyStr, db: SessionDep) -> ArtistDetailResponse: +def get_artist(artist_id: str, db: SessionDep) -> ArtistDetailResponse: artist = db.get(Artist, artist_id) if artist is None: raise HTTPException(status_code=404, detail="Artist could not be found") @@ -67,4 +69,3 @@ def get_issues(db: SessionDep) -> List[IssueDetailsResponse]: for issue in issues: results.append(get_issue_details(issue)) return results - diff --git a/kontor-api/src/db/repository/comics/artist.py b/kontor-api/src/db/repository/comics/artist.py index 8dda467..80c328a 100644 --- a/kontor-api/src/db/repository/comics/artist.py +++ b/kontor-api/src/db/repository/comics/artist.py @@ -1,19 +1,29 @@ +from typing import List from src.db.models.comic import Artist -from src.schema.comics.artist import ArtistDetailResponse +from src.schema.comics.artist_details import ArtistDetailResponse, ArtistWorktypeComicResponse +from src.schema.comics.comic import ComicResponse +from src.schema.comics.worktype import WorktypeResponse def get_artist_details(artist: Artist) -> ArtistDetailResponse: - works = {} + works: List[ArtistWorktypeComicResponse] = [] + works_map = {} for work in artist.comic_works: - work_type = work.work_type.name - comic_title = work.comic.title - if work_type in works: - works[work_type].append(comic_title) + worktype_id = work.work_type.id + if worktype_id in works_map: + comic = ComicResponse(id=work.comic.id, title=work.comic.title, completed=work.comic.completed) + works_map[worktype_id].comics.append(comic) else: - works[work_type] = [comic_title] + works_map[worktype_id] = ArtistWorktypeComicResponse( + worktype=WorktypeResponse(id=worktype_id, name=work.work_type.name), + comics=[ComicResponse(id=work.comic.id, title=work.comic.title, completed=work.comic.completed)] + ) + for value in works_map.values(): + works.append(value) response = ArtistDetailResponse( id=artist.id, name=artist.name, + weblink=artist.weblink, works=works ) return response diff --git a/kontor-api/src/db/repository/comics/comic.py b/kontor-api/src/db/repository/comics/comic.py index 0ff87b4..315a59f 100644 --- a/kontor-api/src/db/repository/comics/comic.py +++ b/kontor-api/src/db/repository/comics/comic.py @@ -1,11 +1,15 @@ -from typing import List, Type, AnyStr +from typing import Dict, List from sqlalchemy.orm import Session from src.core.log_conf import logger from src.db.models.comic import Comic, Issue -from src.schema.comics.comic import ComicSchema +from src.schema.comics.artist import ArtistResponse +from src.schema.comics.comic import ComicResponse, ComicSchema +from src.schema.comics.comic_details import ComicDetailsResponse, ComicWorktypeArtistResponse from src.schema.comics.issue import IssueDetailsResponse +from src.schema.comics.volume import VolumeResponse +from src.schema.comics.worktype import WorktypeResponse def list_comics(db: Session) -> List[Comic]: @@ -25,7 +29,48 @@ def get_issue_details(issue: Issue) -> IssueDetailsResponse: return response -def update_comic(comic: ComicSchema, comic_id: AnyStr, db: Session) -> type[Comic] | None: +def update_comic(comic: ComicSchema, comic_id: str, db: Session) -> type[Comic] | None: logger.info(f"update_comic: {comic} with {comic_id}") comic = db.get(Comic, comic_id) # type: ignore return comic # type: ignore + +def get_short_info(comic: Comic) -> ComicResponse: + response = ComicResponse( + id=comic.id, + title=str(comic.title), + completed=bool(comic.completed == 1) + ) + return response + +def get_comic_details(comic: Comic) -> ComicDetailsResponse: + volumes: List[VolumeResponse] = [] + for volume in comic.volumes: + volumes.append(VolumeResponse(id=volume.id, name=volume.name)) + works: List[ComicWorktypeArtistResponse] = [] + works_map: Dict[str, ComicWorktypeArtistResponse] = {} + for work in comic.comic_works: + worktype_id = work.work_type.id + if worktype_id in works_map: + artist = ArtistResponse(id=work.artist.id, name=work.artist.name) + works_map[worktype_id].artists.append(artist) + logger.info(f"add artist to response map: {artist} -> {works_map}") + print(f"add artist to response map: {artist} -> {works_map}") + else: + works_map[worktype_id] = ComicWorktypeArtistResponse( + worktype=WorktypeResponse(id=worktype_id, name=work.work_type.name), + artists=[ArtistResponse(id=work.artist.id, name=work.artist.name)] + ) + for value in works_map.values(): + works.append(value) + response = ComicDetailsResponse( + id=comic.id, + created=str(comic.created_date), + title=str(comic.title), + completed=bool(comic.completed), + current_order=bool(comic.current_order), + weblink=str(comic.weblink), + publisher=comic.publisher.name, + volumes=volumes, + works=works + ) + return response diff --git a/kontor-api/src/schema/comics/artist.py b/kontor-api/src/schema/comics/artist.py index 48d6210..4a967d4 100644 --- a/kontor-api/src/schema/comics/artist.py +++ b/kontor-api/src/schema/comics/artist.py @@ -1,9 +1,6 @@ -from typing import List, Dict - from pydantic import BaseModel - class ArtistCreation(BaseModel): id: str name: str @@ -11,9 +8,3 @@ class ArtistCreation(BaseModel): class ArtistResponse(BaseModel): id: str name: str - -class ArtistDetailResponse(BaseModel): - id: str - name: str - weblink: str - works: Dict[str, List[str]] diff --git a/kontor-api/src/schema/comics/artist_details.py b/kontor-api/src/schema/comics/artist_details.py new file mode 100644 index 0000000..cd03ca2 --- /dev/null +++ b/kontor-api/src/schema/comics/artist_details.py @@ -0,0 +1,16 @@ +from typing import List +from pydantic import BaseModel + +from src.schema.comics.comic import ComicResponse +from src.schema.comics.worktype import WorktypeResponse + + +class ArtistWorktypeComicResponse(BaseModel): + worktype: WorktypeResponse + comics: List[ComicResponse] + +class ArtistDetailResponse(BaseModel): + id: str + name: str + weblink: str + works: List[ArtistWorktypeComicResponse] diff --git a/kontor-api/src/schema/comics/comic.py b/kontor-api/src/schema/comics/comic.py index 784410d..a57c7ff 100644 --- a/kontor-api/src/schema/comics/comic.py +++ b/kontor-api/src/schema/comics/comic.py @@ -1,12 +1,6 @@ -from typing import List, Dict, Optional - +from typing import Optional from pydantic import BaseModel, AnyUrl - from src.core.log_conf import logger -from src.db.models.comic import Comic -from src.schema.comics.artist import ArtistResponse -from src.schema.comics.volume import VolumeResponse -from src.schema.comics.worktype import WorktypeResponse class ComicResponse(BaseModel): @@ -15,70 +9,9 @@ class ComicResponse(BaseModel): completed: bool - -class ComicWorktypeArtistResponse(BaseModel): - worktype: WorktypeResponse - artists: List[ArtistResponse] - - -class ComicDetailsResponse(BaseModel): - id: str - created: str - title: str - completed : bool - current_order : bool - weblink: str - publisher: str - volumes: List[VolumeResponse] - works: List[ComicWorktypeArtistResponse] - - class ComicSchema(BaseModel): id: str title: str weblink: Optional[AnyUrl] completed: Optional[bool] current_order: Optional[bool] - - -def get_short_info(comic: Comic) -> ComicResponse: - response = ComicResponse( - id=comic.id, - title=str(comic.title), - completed=bool(comic.completed == 1) - ) - return response - - -def get_comic_details(comic: Comic) -> ComicDetailsResponse: - volumes: List[VolumeResponse] = [] - for volume in comic.volumes: - volumes.append(VolumeResponse(id=volume.id, name=volume.name)) - works: List[ComicWorktypeArtistResponse] = [] - works_map: Dict[str, ComicWorktypeArtistResponse] = {} - for work in comic.comic_works: - worktype_id = work.work_type.id - if worktype_id in works_map: - artist = ArtistResponse(id=work.artist.id, name=work.artist.name) - works_map[worktype_id].artists.append(artist) - logger.info(f"add artist to response map: {artist} -> {works_map}") - print(f"add artist to response map: {artist} -> {works_map}") - else: - works_map[worktype_id] = ComicWorktypeArtistResponse( - worktype=WorktypeResponse(id=worktype_id, name=work.work_type.name), - artists=[ArtistResponse(id=work.artist.id, name=work.artist.name)] - ) - for value in works_map.values(): - works.append(value) - response = ComicDetailsResponse( - id=comic.id, - created=str(comic.created_date), - title=str(comic.title), - completed=bool(comic.completed), - current_order=bool(comic.current_order), - weblink=str(comic.weblink), - publisher=comic.publisher.name, - volumes=volumes, - works=works - ) - return response diff --git a/kontor-api/src/schema/comics/comic_details.py b/kontor-api/src/schema/comics/comic_details.py new file mode 100644 index 0000000..99411b7 --- /dev/null +++ b/kontor-api/src/schema/comics/comic_details.py @@ -0,0 +1,23 @@ +from typing import List +from pydantic import BaseModel + +from src.schema.comics.artist import ArtistResponse +from src.schema.comics.volume import VolumeResponse +from src.schema.comics.worktype import WorktypeResponse + + +class ComicWorktypeArtistResponse(BaseModel): + worktype: WorktypeResponse + artists: List[ArtistResponse] + + +class ComicDetailsResponse(BaseModel): + id: str + created: str + title: str + completed : bool + current_order : bool + weblink: str + publisher: str + volumes: List[VolumeResponse] + works: List[ComicWorktypeArtistResponse]