From 381214c2cba8155c14ac76faa33e4779b17771fc Mon Sep 17 00:00:00 2001 From: suphicakir Date: Thu, 5 Nov 2026 01:44:59 +0300 Subject: [PATCH 1/6] =?UTF-8?q?feat(brand):=20B=C3=9CT=C3=87EM=20logosu=20?= =?UTF-8?q?ve=20ikon=20seti?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - public/brand: logo.svg, logo-dark.svg, icon.svg, icon-maskable.svg (yazı Noto Sans Bold yolları, OFL) - npm run icons artık icon.svg / icon-maskable.svg'den üretir; maskable ve Apple ikonları köşesiz Co-Authored-By: Claude Opus 5.5 --- public/brand/icon-maskable.svg | 10 ++++++++ public/brand/icon.png | Bin 15646 -> 22969 bytes public/brand/icon.svg | 10 ++++++++ public/brand/logo-dark.svg | 17 ++++++++++--- public/brand/logo.svg | 17 ++++++++++--- public/icons/icon-192.png | Bin 7671 -> 11075 bytes public/icons/icon-512.png | Bin 11018 -> 18618 bytes public/icons/maskable-512.png | Bin 17437 -> 16113 bytes scripts/generate-icons.ts | 45 +++++++++++++++++++++++++++++---- src/app/apple-icon.png | Bin 6447 -> 4397 bytes src/app/favicon.ico | Bin 3403 -> 5937 bytes src/app/icon.png | Bin 7671 -> 11075 bytes src/components/brand/logo.tsx | 2 +- 13 files changed, 87 insertions(+), 14 deletions(-) create mode 100644 public/brand/icon-maskable.svg create mode 100644 public/brand/icon.svg diff --git a/public/brand/icon-maskable.svg b/public/brand/icon-maskable.svg new file mode 100644 index 0000000..ac5755b --- /dev/null +++ b/public/brand/icon-maskable.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/brand/icon.png b/public/brand/icon.png index b83858dd7e649ff73d5d2fb86223c89854db5ef2..b5f293426c68e05ed6b62bdea57bf083fd14eb26 100644 GIT binary patch literal 22969 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

plIhccmyUYQ#y@gw^B$k2Jp*sEdy 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 diff --git a/public/brand/icon.svg b/public/brand/icon.svg new file mode 100644 index 0000000..bf52c7b --- /dev/null +++ b/public/brand/icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/brand/logo-dark.svg b/public/brand/logo-dark.svg index 700c969..b5274a2 100644 --- a/public/brand/logo-dark.svg +++ b/public/brand/logo-dark.svg @@ -1,5 +1,14 @@ - - - - BÜTÇEM + + + + + + + + + + + + + diff --git a/public/brand/logo.svg b/public/brand/logo.svg index 60fd7b4..d36e9db 100644 --- a/public/brand/logo.svg +++ b/public/brand/logo.svg @@ -1,5 +1,14 @@ - - - - BÜTÇEM + + + + + + + + + + + + + diff --git a/public/icons/icon-192.png b/public/icons/icon-192.png index 0147bc1678c25c18dd7c5b3f7effec448c48e087..495c57f1a9837f305506650585dfb62f16ad8ae9 100644 GIT binary patch literal 11075 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 literal 7671 zcmai3byU>Rv;Xe0$P&V?ARxJPC?Fvq9ZMsKq;yD2iGVn!vtxwsqqR~^F9jdu;|EpzuCcR!n55g- ze5&tYk1E^Q(yXZKdXoqXAjl#+ZBVf{aF{sZK>Q}JuFfhcaSM!jQ&Swo`~=?sHk}kC zh1h!^v9{%R55BIt1tlTMPPjqCYmgrC3nz^Q)YT2V624_xPt zU8oP+ir4VEnpWy_*E$xXNK>`@=YiChL!rHSuMAtlUtt!!H~P6Vc^dl@waLD#7rs2f za9U{TK@&uvTR#>?Hlx_y^@gA9FXshnv2zOet2`I(pK z=|TKYS^eQ5lbBv^Bn1XK05>87t-|WwxOr~byiZQSqn0 zp>RI(Z*C~fi)>W$FbnCx8Y$aY_@djm4jA0WLiZ6adq>WAV(T{`jKO?+wTQSRvs>v{ zSXqjYSXJMBvZKcWj_@8msM=nGmO^`UHt&`%ZnjOrS3DpXtrDz}&?R{hlH*Ppg;On4* zl=Qxu;#Wb1GWuuAjsd2MP=;R20H-Zm{QB>khyK!jA!};XUcMe0FIDNXb3}FCo6^Psc0@m1=$7r%V`C(T|OZz2K58Z+o%|6W-!>1cxv^pKp9* zEHb2uVoUiL|1fofiMXuIC#u5e@O6^GoD;L$yGGsI=m~A%kmW0V<-n^mHSwW};1W+m(d0Xr1cDgl>i+r1MA!<2cBvA8cz?af$nn0nMkHP=fqr z#a62D%oh_Zx%@*gDOn<*?jPQJ&HVLtM|sfTbH$%W>2gs{;$YYx6pp=ytkJ&Kp4-m$`iU)4>C61=npIRse-qy?&EnbNy%gc2&XgL>**Um zbvwX`qxE;uvCh;sY2zL$Lqttd;`KfvuF5o2pTf&kCO%bM*Nk~;1ye2+Zt@7hmHO1X z(jabr1AWj=#8_%DA5184AgZz*8vSmRlNSXEGreb~)<&COX@&Q;YKrAnnV}T{my20C zG4kJE)51p)JQya)bk3iZ4BkdzY1|2ZfXJD(9AC10*8DffO5fJ{lEze8Mx;ZlVlIGc z2Xo1@K@xye4o3{dA6GsAPkozMqhQmEQB-q=%cH%{0EGhL#w+%KA7B?c(Fr}~3noRa z7$AWBoB1V>9j5U@}H0=`=zz%VEh0Q*Cw5HJuF!U*{PZ%jm#89k(T?+KXlXE^IU zvRD}v*UO3IV!)y*s_Kc?AlIoh#5vCEmX{O<2OKkta4+o4i{taz98VLon>s%K`Oep$ z%3ao?00Fe`qE>F6?C zt!+-P_1B;I2=MwT9BAIIr7L`?pdV29t;C3yNq_{RzcB|Fo{@Pt}s~Etm7!DSNOd$IDdAsxaGY~~XppN5W>DH{zneF^~%0xug$s`7(X zPV?t1M`LT7G(Om!eDC};9%^)Ha8}t30!b6`MuSUI9|gHCUm}te`PAhA9B#CQ$Ukt= zX#qT;*ImCsh;uPBUHYps(#|wH!`r*!Vu-QDV+I}Yqf|`~?UN+^1Rf@Tk+Js^%UVKB=zDaEYE5+UyS?UJ6O-FPkTvo=vU^*c& ze;N`|GbcOrluxyb=uQKLKpbt$0#AdBy^?3Q2oOP>h=0D>E?KsASfLKH5U|Ru>M-Hv<&%9gY}{q$!~f_ zp4J?bN9Q-o+IGK+CunvJD|ehdUr4#q_m*)*OqSlJdazAWeV%HCnvUoO{khh|v}9M* zDjDUsoDm=J8lXR}gHQs8H1j!gvDUpin@qJFd6Jm^+MULtLCwgtDtN z^)lun;xvmph9Oh8U@hd(p;#{Fk^?MktIZG*{G?ghq(6f)eDnU&-t(!#+}n!8{sLDH zH-=UyBE3J0a=D*Q-&-u?6)iWT3Am{5SI(WH5b3zU7{SA$TbX_qS~8A~C;vFAoB)|p zMNIr98(0wlRk;St-fu0?F-h0y2;VllpzL#fM~6r7g7SpLc=`T7BuhbNzc zUl9Vsu8d|2w+nOr-qRI|hrw<`D04)rpa{^<>@61ZjoX-(&jVPMtqE(6Nb`hkDkiXS zz1heKX~z_6mawKHfM^@t+oEr!IOGUDqq2N50^9NzV%T-$*yzJ&k=}=}b?`7pVG4C^g~HlM$Z< z9Li{Eo$*7=+H`w1P@^pVo66g=&fB=;^RxClLogh!0yAhBW=j(P$p$W6gfgM7;q79Nj0621L7b#SG!@K?dggIJpBx zOb;3F?m`Nvk|UjmvX!`!`MyesbbbWrGFqi+%;4zfxM-hSSDV=DAz$Aj7zGDFHud=& zfTL{{(1&@6`SMht84DVa-|ZiAj(A4!(=rn0mCnz9Hk~WZ0QFl<`8&x+*;4GWYBiTt zlEsk)FB2Ki3fQZlFQ#`cy-f=Ym?CPcl~(GuQnF$>!S+4igP*OnibutWx>xU zA<4e}ilbpGD&CWh+68<5Q-9$Fojq=hD6@9vos@BK`%_-8+6~)VdWJ9i)SJ=y9{tqg zzGRcP!+1%~`Z!CMp$E=oJf|-pBPM8kP=IgnyZ1iNytNq~HXM_JaiHqQ#?_g2B=J|; zd&I{BjcN6mKQCHKG_XFVVHX2AGTgc0L4px#?zJYNJYY(_wqjv3w>3XmknX3Cw33Fy z1YMh4SCMZuESBwKjA;zvZJA$dC%)i;<-2Saj?{L8|8I==e*(z=X8!*;^MB4j&_Nan zf08gLBK5wsWOi*H(N%Z#8Gm>8^+-y0_Y3oxduW86owuL=(#VEvVN0x$vsY1H`+mAl zZPp6HnXmquchZF$ddQ4sVg^i_xr##5kAeqozP+-suzBn7Ci>lBc@nvCC${fmxwMV@ ziMy|La_?IF(eO7*xjpuVme$8{1xB7G4|e9m-I1brrDjdP$Lb^Y(5F;8n*=_RhDBRW zKu0zglhaAQCjQii?o9zN*`0MbC-UbJ59Uuqc~zt zn>biDvqV)y=aHiEUFwY-*;z2`!UscrCitOVQypD3y9%Zv=nrlu$EYGb{t zUTOP%&+zaNi|n=AP9Q#L(X3IqzQ{;RSZNS@cDQrjZn_(%Ftb79TxVB1JypKi(?PQAVDhuxo*SgPCM2v8l>0xqj*iuHgyiGTUUFI&LKIjc)(>A>FkPi+#nvdE=Yy zxaiX&lWPm6z4PPfuEueusyrUS%I?J6d*{yS!73g_nAb(gZ^IM!H2fzn6Kdrf1s6x@ zTTMpWGE#WUbed~({KFgwo!0)YZHXP9-rsb?63?g(_lp{vzkz)C=kvU}GIS`?U1!w8 zr!gnJb@-e)_1BE==4M`Efu_^JzD6U{hsZR*w<5ZTi*q-EO~gWM6umV^|-T-a&Q0|HjMU3!Umos9Ij!xOD*_N zVe+wM^9Dl>a_ToDCci6RiVz~&yzkwv&c8rzi?9}rhTpQhRLGLZWEE=;XtCyv)?a6L zyl{AyCPcZC=p@o+-hHm109~{{qTQdV_0xf~f%mKZ504rLYH}KUG*N0Vb64UwTA#L&dsG0 z(MRhXiiwUF;~Jdvrgz$(T@5P&gh{&_Z%WI7_3Av^oHP@yo=IdHYfJg3uJ9clF3CQN zQ2=(J2p%G=_#Lrpij>mEo!G3qX! zEiWXb!sa)L$GZZdL|N@`+Xn}-(#uTcERyxg1ErnMz%2G@$r0+!m9hT_`CM9rtCRcA zmrZQI=z88%)i1*_p5?Nl1NlyKCPO)) zXxqeGr?gMLLEcR0SRs2WQ{HptQ_V{k;%xEQ9knMQo4eQ9v3WVywU4tHPI&l=PO6R z(X@+Nx_=bNm&>1Dd2B_MZ{TLmzvP+S>)*(Ik=3irTp*RBND9sEg8GKIcjBP|owp+S zr6{Z53xel4u7@=hk6iQE zS28)HOi(u;gGuDz@9hsTvfJ#W^s}YHg-GBiz0tTp+mE2y{7vV1MUmK$ZM}rapnjcp zv-0RuLsF}e?Ky|H+>hd4e~dOmdW|>^JT}dd)q<|krx_`>DlvpZ#U)!Lvoz-;(Yd@R z!?QsguKyH$-7bpE&XPVtGQ=C_j;vxB(8HrS58h%U)^s;zy+VL=U#H4c1xSTqe2;i; z@U~TdP{96^jc6M>;1GX#w*&GuS|N;sr%YqQfcRhUuXvLE^I4dB4@Tz2S%Z9;FV>#E zh=q)?D~O__!jm>igvKh_FgclTXb_ya#YiBK2KD{K4LA&Sb~d~0M0Ji^@T|G+*DDs= z(94(IB^;f1-Urq&Kklq`eWYG}<=cs ze=!VVM&Yw1)0ob^`Ef0+8U&u4q+R`hqgFCv{mPYNH)=l%&KJoFM35J)Hw8>SvX$ zxnY8sEszM?k5gSn-qQZnhrag$+SjYoWxgKv33OEF#${$?M=gF8`W@0)g=NO0ccr{# zZpD}A9Iv;p%SInYw*ev{0-9@)vFH3@of+|Z0M%(VyiMefc-o+LQ(wBa`DPWADGBeN zC(^w|C2EVkS@&5MvNq=R5xDEO33Hoa+p;b}O%c7=>n?Gu5WDe>0G!m85Yc198k@ux zb(2A}8otYE(O9o(o|+!z9}3S>)|JRI65@k%ryRJBls-EAT_9vbx5UMGI@h$bNwwKE zs_F5NOoF6!QHNlq<778A<{rbsewrPDi{qN>b;?z00Z-Y=iTR1p!FBdg%DZG#@^x_h z6&sb&{s$il=skzJe~aRSFG1%{`9e4ye&LDgFRLa(X!?#P1}e} zHA`I@XZ>!PzUbpAc96S1RBhz(Q{iOMhgz+PcjK9%lgXq#buQ@)2`_rvb6_n>aR;pw zqy_}ge{1cTb8lq;25xj1E)MmW*F?`jN|V0MiG$jD!>-_=vt-Kf#O|UO0B)4*8zVK6*N4FeN!d14aZ;y|RnXc^iR%hIo#dmkH7BUB}+t^j^ zVwDC4)Z4WMtne@Fqp!pGV!heu{;O=X)JW{5=C|HqUqsxNQMB1bc%FfgTmqxfy_Kr| zXtt-mt86aQo7{yJZ(1b^NhhO4-&^JgH|yHlL+%ju*y#=CG9z{4yfxkHmOVEHRpa~4 zNBBnoBzUYSMdHWGLab!RbK(9k8E;%4a!s=Ky!q4A7Q~Bko;0c(;JfMM&k}8!r{$k; zjv}z+ZWv$XcKr35=u!>>9-F!zc61mWGIA<<(KSSrMmGaT<(d!M}*vTdIuh1#0-`JWI}{v11E3Q(xA!-ici@!=-AA! zeu(A>3lpqj#4R@-!OyP)oe{Z(J=!IX*xM1l#%hdIlJ?w?(tAcoyA+Imjxox%vaw}m zTy-zH;jWiNlB&%wAGlGU7pwH1ex*ZbKVZ2yE+$b1r107Y(w_IaQze(U4v8^e62-Yc z{zt;Jk2qqpT+vIkQ}iY4=s-$wOTw0Twx-cdPyXKv!6)LC>YQtGyl-RbP(pQwd=^S#NVNqqAAxv=y(Y>YbWc+NSj_R>p*7>~n#M8^TL|{sZnD%!Tif$uhCQ^@D0bK-bZZqTeFfvX}Oy zr8SQa(8jx=*~3Bj`*QS2{}t&Uf{<|CVcpDqbY4* zLym)ZZ=NHngU6#rP-|+2atW`1OlEy4S*pvnG{EUAf`{`kOF-1F$|9cF%V&x+-rj{| z&Z1><{E^*hy0DaCrfOh;d!Z&Zw=U&$!Mpd?rrTF2oJ}o&x*{~B`mU1;D9NcIOJz(0 F{tHzNYGMEY diff --git a/public/icons/icon-512.png b/public/icons/icon-512.png index faf826579f3b41a022339da7adf45eca4575bbc8..c00dd304ea99f99852cb850d598ab3c581707396 100644 GIT binary patch literal 18618 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 diff --git a/public/icons/maskable-512.png b/public/icons/maskable-512.png index 609e48eb5285bc3575c4206786a91cc6a5a0ad51..6767574ec94ac1ec1886ee73be40242c4e4f1e3f 100644 GIT binary patch literal 16113 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 S@sb37X&lu$l5)uG?*9W>k@rFX literal 17437 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 Tr)_q3!B;QoUd*{*8T@|$zH^jo diff --git a/scripts/generate-icons.ts b/scripts/generate-icons.ts index c55f90d..5cdfc66 100644 --- a/scripts/generate-icons.ts +++ b/scripts/generate-icons.ts @@ -1,23 +1,30 @@ /** * Uygulama ikonlarını logodan üretir: npm run icons * - * Kaynak: public/brand/icon.png (kare, en az 512×512). + * Kaynak: public/brand/icon.svg (varsa; önce 512×512 icon.png olarak render edilir) + * veya doğrudan public/brand/icon.png (kare, en az 512×512). * Çıktılar (commit edilir): + * public/brand/icon.png 512 px (icon.svg varsa ondan üretilir) * src/app/favicon.ico 16/32/48 px (PNG gömülü ICO) * src/app/icon.png 192 px → * src/app/apple-icon.png 180 px, saydamsız → iOS ana ekran * public/icons/icon-192.png PWA manifest * public/icons/icon-512.png PWA manifest - * public/icons/maskable-512.png PWA manifest (purpose: maskable, %80 güvenli alan) + * public/icons/maskable-512.png PWA manifest (purpose: maskable) + * + * public/brand/icon-maskable.svg (köşesiz, tam zemin) varsa maskable ve Apple ikonları ondan + * üretilir; yoksa icon.png marka mavisi zemine %80 güvenli alanla yerleştirilir. * * Logoyu değiştirdikten sonra bu betiği yeniden çalıştırın. */ -import { mkdir, writeFile } from "node:fs/promises"; +import { access, mkdir, writeFile } from "node:fs/promises"; import path from "node:path"; import sharp from "sharp"; const ROOT = process.cwd(); const SOURCE = path.join(ROOT, "public/brand/icon.png"); +const SOURCE_SVG = path.join(ROOT, "public/brand/icon.svg"); +const MASKABLE_SVG = path.join(ROOT, "public/brand/icon-maskable.svg"); /** Saydamlık kabul etmeyen ikonlarda arka plan: marka mavisi (--primary, açık tema). */ const BRAND_BG = "#105bbe"; @@ -60,19 +67,47 @@ function toIco(images: { size: number; data: Buffer }[]): Buffer { return Buffer.concat([header, ...entries, ...images.map((i) => i.data)]); } +async function exists(file: string) { + return access(file).then( + () => true, + () => false, + ); +} + async function main() { + if (await exists(SOURCE_SVG)) { + // Vektörden yüksek yoğunlukta render: küçük boyutlarda da keskin kenarlar. + const png512 = await sharp(SOURCE_SVG, { density: 288 }).resize(512, 512).png().toBuffer(); + await writeFile(SOURCE, png512); + console.log(`✓ public/brand/icon.png ← icon.svg (${png512.length} bayt)`); + } const meta = await sharp(SOURCE).metadata(); if (!meta.width || !meta.height || meta.width !== meta.height || meta.width < 512) { throw new Error(`${SOURCE} kare ve en az 512×512 olmalı (şu an ${meta.width}×${meta.height}).`); } await mkdir(path.join(ROOT, "public/icons"), { recursive: true }); + const fullBleed = (await exists(MASKABLE_SVG)) + ? (size: number) => + sharp(MASKABLE_SVG, { density: 288 }) + .resize(size, size) + .flatten({ background: BRAND_BG }) + .png() + .toBuffer() + : null; + const out: [string, Buffer][] = [ ["src/app/icon.png", await png(192)], - ["src/app/apple-icon.png", await png(180, { flatten: true })], + [ + "src/app/apple-icon.png", + fullBleed ? await fullBleed(180) : await png(180, { flatten: true }), + ], ["public/icons/icon-192.png", await png(192)], ["public/icons/icon-512.png", await png(512)], - ["public/icons/maskable-512.png", await png(512, { padding: 0.1 })], + [ + "public/icons/maskable-512.png", + fullBleed ? await fullBleed(512) : await png(512, { padding: 0.1 }), + ], [ "src/app/favicon.ico", toIco(await Promise.all([16, 32, 48].map(async (size) => ({ size, data: await png(size) })))), diff --git a/src/app/apple-icon.png b/src/app/apple-icon.png index d442934c03c3ecad6bc7b0335c1eb8b030d17a26..5132667d645b43b2159f3b164612dfc04ccae7fe 100644 GIT binary patch literal 4397 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 diff --git a/src/app/favicon.ico b/src/app/favicon.ico index fbab8dd7f71ef07952801605e325060f8f97add1..38bd21774919b8990b0c28cb36ecf2f2fffe31b5 100644 GIT binary patch delta 5810 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 literal 7671 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 F{tHzNYGMEY diff --git a/src/components/brand/logo.tsx b/src/components/brand/logo.tsx index d544b6c..d9688bb 100644 --- a/src/components/brand/logo.tsx +++ b/src/components/brand/logo.tsx @@ -7,7 +7,7 @@ import { tr } from "@/i18n/tr"; * hydration uyumsuzluğu olmaz). Dosyalar public/brand/ altında. */ export function Logo({ className, priority }: { className?: string; priority?: boolean }) { - const common = { width: 120, height: 32, priority, unoptimized: true } as const; + const common = { width: 114, height: 32, priority, unoptimized: true } as const; return ( Date: Thu, 5 Nov 2026 02:02:00 +0300 Subject: [PATCH 2/6] =?UTF-8?q?feat(auth):=20Better=20Auth,=20kullan=C4=B1?= =?UTF-8?q?c=C4=B1=20ad=C4=B1=20ile=20giri=C5=9F,=20DAL=20ve=20proxy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MongoDB/Mongoose bağlantıları, kayıt kapalı, admin + username plugin, rate limit - DAL: getCurrentUser, requireSession, requireRole, getActionUser - Kullanıcı adı normalizasyonu: İ/I/ı → i (tr-TR 'ADMIN' → 'admın' hatası) Co-Authored-By: Claude Opus 5.5 --- src/app/api/auth/[...all]/route.ts | 11 ++++ src/i18n/tr.ts | 4 ++ src/lib/auth-client.ts | 9 ++++ src/lib/auth-errors.ts | 8 +++ src/lib/auth.ts | 81 ++++++++++++++++++++++++++++++ src/lib/dal.ts | 68 +++++++++++++++++++++++++ src/lib/db/.gitkeep | 0 src/lib/db/mongo.ts | 36 +++++++++++++ src/lib/db/mongoose.ts | 19 +++++++ src/proxy.ts | 22 ++++++++ src/shared/account-email.test.ts | 20 ++++++++ src/shared/account-email.ts | 24 +++++++++ src/shared/schemas/common.ts | 12 +++-- src/shared/schemas/user.test.ts | 9 +++- 14 files changed, 319 insertions(+), 4 deletions(-) create mode 100644 src/app/api/auth/[...all]/route.ts create mode 100644 src/lib/auth-client.ts create mode 100644 src/lib/auth-errors.ts create mode 100644 src/lib/auth.ts create mode 100644 src/lib/dal.ts delete mode 100644 src/lib/db/.gitkeep create mode 100644 src/lib/db/mongo.ts create mode 100644 src/lib/db/mongoose.ts create mode 100644 src/proxy.ts create mode 100644 src/shared/account-email.test.ts create mode 100644 src/shared/account-email.ts diff --git a/src/app/api/auth/[...all]/route.ts b/src/app/api/auth/[...all]/route.ts new file mode 100644 index 0000000..50c5361 --- /dev/null +++ b/src/app/api/auth/[...all]/route.ts @@ -0,0 +1,11 @@ +import { toNextJsHandler } from "better-auth/next-js"; +import { getAuth } from "@/lib/auth"; + +/** Better Auth uç noktaları (/api/auth/*). Kayıt (sign-up) yapılandırmada kapalıdır. */ +export function GET(request: Request) { + return toNextJsHandler(getAuth()).GET(request); +} + +export function POST(request: Request) { + return toNextJsHandler(getAuth()).POST(request); +} diff --git a/src/i18n/tr.ts b/src/i18n/tr.ts index 9b583a2..e76d8ee 100644 --- a/src/i18n/tr.ts +++ b/src/i18n/tr.ts @@ -31,6 +31,8 @@ export const tr = { showPassword: "Şifreyi göster", hidePassword: "Şifreyi gizle", genericError: "Beklenmeyen bir hata oluştu. Lütfen tekrar deneyin.", + unauthorized: "Bu işlem için yetkiniz yok.", + notFound: "Kayıt bulunamadı.", }, theme: { label: "Tema", @@ -90,6 +92,8 @@ export const tr = { submit: "Giriş yap", submitting: "Giriş yapılıyor…", invalidCredentials: "Kullanıcı adı/e-posta veya şifre hatalı.", + banned: "Hesabınız askıya alınmış. Lütfen yöneticiyle iletişime geçin.", + tooManyAttempts: "Çok fazla deneme yapıldı. Lütfen bir dakika sonra tekrar deneyin.", noSignUp: "Hesaplar yalnızca yönetici tarafından açılır.", identifierRequired: "Kullanıcı adı veya e-posta girin", passwordRequired: "Şifre girin", diff --git a/src/lib/auth-client.ts b/src/lib/auth-client.ts new file mode 100644 index 0000000..cf80318 --- /dev/null +++ b/src/lib/auth-client.ts @@ -0,0 +1,9 @@ +"use client"; + +import { adminClient, usernameClient } from "better-auth/client/plugins"; +import { createAuthClient } from "better-auth/react"; + +/** Tarayıcı tarafı Better Auth istemcisi (aynı origin: /api/auth). */ +export const authClient = createAuthClient({ + plugins: [usernameClient(), adminClient()], +}); diff --git a/src/lib/auth-errors.ts b/src/lib/auth-errors.ts new file mode 100644 index 0000000..8989af7 --- /dev/null +++ b/src/lib/auth-errors.ts @@ -0,0 +1,8 @@ +import { isAPIError } from "better-auth/api"; + +/** Better Auth hatasından makine okunur kodu çıkarır (ör. "USERNAME_IS_ALREADY_TAKEN"). */ +export function authErrorCode(error: unknown): string | undefined { + if (!isAPIError(error)) return undefined; + const body = error.body as { code?: unknown } | undefined; + return typeof body?.code === "string" ? body.code : undefined; +} diff --git a/src/lib/auth.ts b/src/lib/auth.ts new file mode 100644 index 0000000..d53d67f --- /dev/null +++ b/src/lib/auth.ts @@ -0,0 +1,81 @@ +import "server-only"; +import { betterAuth } from "better-auth"; +import { mongodbAdapter } from "better-auth/adapters/mongodb"; +import { nextCookies } from "better-auth/next-js"; +import { admin, username } from "better-auth/plugins"; +import { getMongoClient } from "@/lib/db/mongo"; +import { env } from "@/lib/env"; +import { normalizeUsername, USERNAME_PATTERN } from "@/shared/schemas/common"; + +function createAuth() { + const e = env(); + const client = getMongoClient(); + const isProd = e.NODE_ENV === "production"; + + return betterAuth({ + appName: "BÜTÇEM", + baseURL: e.BETTER_AUTH_URL, + secret: e.BETTER_AUTH_SECRET, + // client verildiği için çok adımlı auth yazmaları transaction içinde yapılır (replica set). + database: mongodbAdapter(client.db(), { client }), + emailAndPassword: { + enabled: true, + // Kullanıcılar kendi hesabını açamaz; hesaplar yalnızca admin panelinden oluşturulur. + disableSignUp: true, + minPasswordLength: 8, + maxPasswordLength: 128, + revokeSessionsOnPasswordReset: true, + }, + session: { + expiresIn: 60 * 60 * 24 * 30, // 30 gün + updateAge: 60 * 60 * 24, // günde bir uzat + }, + rateLimit: { + enabled: true, + // Yeniden başlatmalarda sıfırlanmasın; tek örnek çalıştığı için yeterli. + storage: "database", + window: 60, + max: 100, + customRules: { + "/sign-in/email": { window: 60, max: 5 }, + "/sign-in/username": { window: 60, max: 5 }, + }, + }, + advanced: { + useSecureCookies: isProd, + cookiePrefix: "butcem", + // Rate limit istemci IP'sini X-Forwarded-For'dan okur. Canlıda Traefik güvenilmeyen + // istemcilerden gelen X-Forwarded-* başlıklarını silip kendisi yazmalı + // (forwardedHeaders.insecure=false, varsayılan) — docs/DEPLOY.md (Faz 15). + }, + telemetry: { enabled: false }, + plugins: [ + username({ + minUsernameLength: 3, + maxUsernameLength: 30, + // shared/schemas/common.ts ile aynı kural (İ/I/ı → i, ASCII küçük harf). + usernameNormalization: normalizeUsername, + // Giriş uç noktası normalizasyondan ÖNCE doğrular; büyük harfli girişler ("ADMIN") + // reddedilmesin diye doğrulama her zaman normalize edilmiş değer üzerinde yapılır. + usernameValidator: (value) => USERNAME_PATTERN.test(normalizeUsername(value)), + validationOrder: { username: "post-normalization" }, + }), + admin({ + defaultRole: "user", + adminRoles: ["admin"], + bannedUserMessage: "Hesabınız askıya alınmış. Lütfen yöneticiyle iletişime geçin.", + }), + nextCookies(), // Server Action'larda cookie ayarlamak için en sonda olmalı + ], + }); +} + +export type Auth = ReturnType; + +const globalForAuth = globalThis as unknown as { __butcemAuth?: Auth }; + +/** Better Auth örneği (tembel: build sırasında ortam değişkeni gerektirmez). */ +export function getAuth(): Auth { + globalForAuth.__butcemAuth ??= createAuth(); + return globalForAuth.__butcemAuth; +} diff --git a/src/lib/dal.ts b/src/lib/dal.ts new file mode 100644 index 0000000..f648cc4 --- /dev/null +++ b/src/lib/dal.ts @@ -0,0 +1,68 @@ +import "server-only"; +import { headers } from "next/headers"; +import { notFound, redirect } from "next/navigation"; +import { cache } from "react"; +import type { SessionUser } from "@/features/auth/types"; +import { getAuth } from "@/lib/auth"; +import { visibleEmail } from "@/shared/account-email"; +import type { SystemRole } from "@/shared/schemas/common"; + +/** + * Data Access Layer — her korumalı sayfa, layout, Server Action ve Route Handler oturumu + * BURADAN okur. proxy.ts yalnızca iyimser (cookie var mı?) kontrol yapar; güvenlik bu katmandadır. + */ + +type RawUser = { + id: string; + name: string; + email: string; + username?: string | null; + role?: string | null; + banned?: boolean | null; + image?: string | null; +}; + +export function toSessionUser(user: RawUser): SessionUser { + return { + id: user.id, + name: user.name, + username: user.username ?? "", + email: visibleEmail(user.email), + role: user.role === "admin" ? "admin" : "user", + image: user.image ?? undefined, + }; +} + +/** İstek başına bir kez çalışır (React cache). Oturum yoksa veya kullanıcı askıdaysa null. */ +export const getCurrentUser = cache(async (): Promise => { + // headers() önce çağrılır: Cache Components prerender'ı burada durur, auth hiç oluşturulmaz. + const requestHeaders = await headers(); + const session = await getAuth().api.getSession({ headers: requestHeaders }); + if (!session || session.user.banned) return null; + return toSessionUser(session.user); +}); + +/** Sayfa/layout: oturum yoksa /login'e yönlendirir. */ +export async function requireSession(): Promise { + const user = await getCurrentUser(); + if (!user) redirect("/login"); + return user; +} + +/** Sayfa/layout: rol yoksa 404 (sayfanın varlığı bile gösterilmez). */ +export async function requireRole(role: SystemRole): Promise { + const user = await requireSession(); + if (user.role !== role) notFound(); + return user; +} + +/** + * Server Action / Route Handler: yönlendirme yapmaz, yetkisizse null döner + * (çağıran `fail(...)` veya 401/403 döndürür). + */ +export async function getActionUser(role?: SystemRole): Promise { + const user = await getCurrentUser(); + if (!user) return null; + if (role && user.role !== role) return null; + return user; +} diff --git a/src/lib/db/.gitkeep b/src/lib/db/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/db/mongo.ts b/src/lib/db/mongo.ts new file mode 100644 index 0000000..20f8d5f --- /dev/null +++ b/src/lib/db/mongo.ts @@ -0,0 +1,36 @@ +import "server-only"; +import { MongoClient, type Db } from "mongodb"; +import { env } from "@/lib/env"; + +/** + * Uygulama genelinde tek MongoClient. Geliştirmede HMR yeniden yüklemelerinde bağlantı + * çoğalmasın diye globalThis üzerinde saklanır. İstemci tembel oluşturulur: `next build` + * sırasında ortam değişkeni veya bağlantı gerektirmez (sürücü ilk işlemde bağlanır). + */ +const globalForMongo = globalThis as unknown as { __butcemMongo?: MongoClient }; + +export function getMongoClient(): MongoClient { + if (!globalForMongo.__butcemMongo) { + globalForMongo.__butcemMongo = new MongoClient(env().MONGODB_URI, { + appName: "butcem", + maxPoolSize: 20, + serverSelectionTimeoutMS: 5_000, + }); + } + return globalForMongo.__butcemMongo; +} + +/** Bağlantı URI'sindeki veritabanı (ör. /butcem). */ +export function getDb(): Db { + return getMongoClient().db(); +} + +/** Sağlık kontrolü için hızlı ping. */ +export async function pingDb(timeoutMs = 2_000): Promise { + try { + await getDb().command({ ping: 1 }, { timeoutMS: timeoutMs }); + return true; + } catch { + return false; + } +} diff --git a/src/lib/db/mongoose.ts b/src/lib/db/mongoose.ts new file mode 100644 index 0000000..b36cc51 --- /dev/null +++ b/src/lib/db/mongoose.ts @@ -0,0 +1,19 @@ +import "server-only"; +import mongoose from "mongoose"; +import { env } from "@/lib/env"; + +/** + * Alan (domain) modelleri için Mongoose bağlantısı; MongoClient ile aynı önbellekleme deseni. + * Kimlik doğrulama koleksiyonları Better Auth tarafından native sürücüyle yönetilir. + */ +const globalForMongoose = globalThis as unknown as { __butcemMongoose?: Promise }; + +export function connectMongoose(): Promise { + globalForMongoose.__butcemMongoose ??= mongoose.connect(env().MONGODB_URI, { + appName: "butcem-odm", + maxPoolSize: 20, + serverSelectionTimeoutMS: 5_000, + autoIndex: env().NODE_ENV !== "production", + }); + return globalForMongoose.__butcemMongoose; +} diff --git a/src/proxy.ts b/src/proxy.ts new file mode 100644 index 0000000..a292ad9 --- /dev/null +++ b/src/proxy.ts @@ -0,0 +1,22 @@ +import { getSessionCookie } from "better-auth/cookies"; +import { NextResponse, type NextRequest } from "next/server"; + +/** + * İYİMSER kontrol: yalnızca oturum cookie'si var mı diye bakar (veritabanına gitmez). + * Cookie yoksa korumalı sayfaları /login'e yönlendirir. Asıl yetki kontrolü lib/dal.ts'de; + * bu katman tek başına güvenlik sağlamaz. + */ +export function proxy(request: NextRequest) { + const hasSessionCookie = Boolean(getSessionCookie(request, { cookiePrefix: "butcem" })); + if (!hasSessionCookie) { + return NextResponse.redirect(new URL("/login", request.url)); + } + return NextResponse.next(); +} + +export const config = { + // Herkese açık yollar hariç: giriş, auth ve health API'leri, statik dosyalar, ikonlar, manifest. + matcher: [ + "/((?!login|api/auth|api/health|_next/static|_next/image|brand/|icons/|favicon.ico|icon.png|apple-icon.png|manifest.webmanifest|robots.txt).*)", + ], +}; diff --git a/src/shared/account-email.test.ts b/src/shared/account-email.test.ts new file mode 100644 index 0000000..f4a4a65 --- /dev/null +++ b/src/shared/account-email.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; +import { isPlaceholderEmail, placeholderEmail, storedEmail, visibleEmail } from "./account-email"; + +describe("yer tutucu e-posta", () => { + it("kullanıcı adından .invalid adres üretir ve tanır", () => { + const e = placeholderEmail("emre"); + expect(e).toBe("emre@kullanici.butcem.invalid"); + expect(isPlaceholderEmail(e)).toBe(true); + expect(isPlaceholderEmail("EMRE@KULLANICI.BUTCEM.INVALID")).toBe(true); + expect(isPlaceholderEmail("emre@ornek.com")).toBe(false); + expect(isPlaceholderEmail(undefined)).toBe(false); + }); + + it("kaydedilen ve gösterilen e-posta", () => { + expect(storedEmail("emre", undefined)).toBe("emre@kullanici.butcem.invalid"); + expect(storedEmail("ayse", "ayse@ornek.com")).toBe("ayse@ornek.com"); + expect(visibleEmail("emre@kullanici.butcem.invalid")).toBeUndefined(); + expect(visibleEmail("ayse@ornek.com")).toBe("ayse@ornek.com"); + }); +}); diff --git a/src/shared/account-email.ts b/src/shared/account-email.ts new file mode 100644 index 0000000..47a7b8b --- /dev/null +++ b/src/shared/account-email.ts @@ -0,0 +1,24 @@ +/** + * E-postası olmayan kullanıcılar (ör. çocuklar) kullanıcı adıyla giriş yapar. Better Auth + * e-postayı zorunlu tuttuğu için bunlara ayrılmış `.invalid` alan adında (RFC 2606) + * yer tutucu bir adres verilir; arayüzde "e-posta yok" olarak gösterilir. + */ +export const PLACEHOLDER_EMAIL_DOMAIN = "kullanici.butcem.invalid"; + +export function placeholderEmail(username: string): string { + return `${username}@${PLACEHOLDER_EMAIL_DOMAIN}`; +} + +export function isPlaceholderEmail(email: string | null | undefined): boolean { + return Boolean(email?.toLowerCase().endsWith(`@${PLACEHOLDER_EMAIL_DOMAIN}`)); +} + +/** Kaydedilecek e-posta: girilmişse o, yoksa kullanıcı adından yer tutucu. */ +export function storedEmail(username: string, email: string | undefined): string { + return email ?? placeholderEmail(username); +} + +/** Gösterilecek e-posta: yer tutucuysa undefined. */ +export function visibleEmail(email: string | null | undefined): string | undefined { + return email && !isPlaceholderEmail(email) ? email : undefined; +} diff --git a/src/shared/schemas/common.ts b/src/shared/schemas/common.ts index 6b90e49..377dd4e 100644 --- a/src/shared/schemas/common.ts +++ b/src/shared/schemas/common.ts @@ -12,11 +12,17 @@ export const USERNAME_PATTERN = /^[a-z0-9._]{3,30}$/; export const nameSchema = z.string().trim().min(2, v.nameMin).max(80, v.nameMax); -/** Kullanıcı adı küçük harfe çevrilir; Türkçe büyük İ/I sorununu önlemek için tr-TR kullanılır. */ +/** + * Kullanıcı adı normalizasyonu (ASCII). Türkçe yerel ayarı KULLANILMAZ: tr-TR'de "I" → "ı" olur + * ve "ADMIN" "admın"e dönüşür. Bunun yerine İ/I/ı hepsi "i" yapılır, sonra küçük harfe çevrilir. + */ +export function normalizeUsername(value: string): string { + return value.trim().replace(/[İIı]/g, "i").toLowerCase(); +} + export const usernameSchema = z .string() - .trim() - .transform((s) => s.toLocaleLowerCase("tr-TR")) + .transform(normalizeUsername) .pipe(z.string().regex(USERNAME_PATTERN, v.usernameFormat)); export const emailSchema = z.string().trim().toLowerCase().pipe(z.email(v.email)); diff --git a/src/shared/schemas/user.test.ts b/src/shared/schemas/user.test.ts index a1d2630..d5c6f61 100644 --- a/src/shared/schemas/user.test.ts +++ b/src/shared/schemas/user.test.ts @@ -12,11 +12,18 @@ const base = { }; describe("usernameSchema", () => { - it("küçük harfe çevirir ve boşlukları kırpar (Türkçe İ dahil)", () => { + it("küçük harfe çevirir ve boşlukları kırpar", () => { expect(usernameSchema.parse(" AYSE.K ")).toBe("ayse.k"); expect(usernameSchema.parse("Ali_2")).toBe("ali_2"); }); + it("I/İ/ı harflerini i yapar (tr-TR'deki I → ı tuzağı)", () => { + expect(usernameSchema.parse("ADMIN")).toBe("admin"); + expect(usernameSchema.parse("ALI")).toBe("ali"); + expect(usernameSchema.parse("İREM")).toBe("irem"); + expect(usernameSchema.parse("ırmak")).toBe("irmak"); + }); + it("Türkçe karakter, boşluk ve kısa değerleri reddeder", () => { for (const bad of ["ayşe", "ali veli", "ab", "a".repeat(31), "ali-veli"]) { expect(usernameSchema.safeParse(bad).success, bad).toBe(false); From 2035f73e7d495abe12ba362a9788e042f5d661b0 Mon Sep 17 00:00:00 2001 From: suphicakir Date: Thu, 5 Nov 2026 02:02:00 +0300 Subject: [PATCH 3/6] =?UTF-8?q?feat(admin):=20Server=20Action'larla=20kull?= =?UTF-8?q?an=C4=B1c=C4=B1=20y=C3=B6netimi=20ve=20ger=C3=A7ek=20oturumlu?= =?UTF-8?q?=20sayfalar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- src/app/(app)/admin/kullanicilar/page.tsx | 10 +- src/app/(app)/admin/layout.tsx | 12 +- src/app/(app)/dashboard/page.tsx | 6 +- src/app/(app)/layout.tsx | 20 +- src/app/(auth)/login/login-form.tsx | 20 +- src/app/(auth)/login/page.tsx | 13 + src/app/api/health/route.ts | 11 +- src/app/page.tsx | 4 +- src/components/layout/app-shell-fallback.tsx | 26 ++ src/components/layout/user-menu.tsx | 12 +- src/features/admin-users/actions.ts | 250 ++++++++++++++++++ .../admin-users/components/users-table.tsx | 122 +++------ src/features/admin-users/guards.test.ts | 19 +- src/features/admin-users/guards.ts | 15 -- src/features/admin-users/mapper.ts | 27 ++ src/features/admin-users/mock.ts | 111 -------- src/features/admin-users/queries.ts | 16 ++ src/features/auth/mock.ts | 13 - 18 files changed, 437 insertions(+), 270 deletions(-) create mode 100644 src/components/layout/app-shell-fallback.tsx create mode 100644 src/features/admin-users/actions.ts create mode 100644 src/features/admin-users/mapper.ts delete mode 100644 src/features/admin-users/mock.ts create mode 100644 src/features/admin-users/queries.ts delete mode 100644 src/features/auth/mock.ts diff --git a/src/app/(app)/admin/kullanicilar/page.tsx b/src/app/(app)/admin/kullanicilar/page.tsx index 6875a05..87f8070 100644 --- a/src/app/(app)/admin/kullanicilar/page.tsx +++ b/src/app/(app)/admin/kullanicilar/page.tsx @@ -1,18 +1,18 @@ import type { Metadata } from "next"; import { PageHeader } from "@/components/page-header"; import { UsersTable } from "@/features/admin-users/components/users-table"; -import { MOCK_ADMIN_USERS } from "@/features/admin-users/mock"; -import { MOCK_SESSION_USER } from "@/features/auth/mock"; +import { listAdminUsers } from "@/features/admin-users/queries"; import { tr } from "@/i18n/tr"; +import { requireRole } from "@/lib/dal"; export const metadata: Metadata = { title: tr.adminUsers.title }; -export default function AdminUsersPage() { - // FAZ 3: const users = await listUsers() (Better Auth admin API) +export default async function AdminUsersPage() { + const [admin, users] = await Promise.all([requireRole("admin"), listAdminUsers()]); return ( <> - + ); } diff --git a/src/app/(app)/admin/layout.tsx b/src/app/(app)/admin/layout.tsx index 7cc8092..f9a61b8 100644 --- a/src/app/(app)/admin/layout.tsx +++ b/src/app/(app)/admin/layout.tsx @@ -1,11 +1,7 @@ -import { notFound } from "next/navigation"; -import { MOCK_SESSION_USER } from "@/features/auth/mock"; +import { requireRole } from "@/lib/dal"; -/** - * Yalnızca sistem admini. Admin olmayana 404 (sayfanın varlığını bile göstermez). - * FAZ 3: `await requireRole("admin")` (lib/dal.ts) — sunucu tarafı gerçek kontrol. - */ -export default function AdminLayout({ children }: { children: React.ReactNode }) { - if (MOCK_SESSION_USER.role !== "admin") notFound(); +/** Yalnızca sistem admini; diğerlerine 404. Sayfalar ayrıca kendi kontrolünü yapar. */ +export default async function AdminLayout({ children }: { children: React.ReactNode }) { + await requireRole("admin"); return children; } diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx index 7d40c44..d3879db 100644 --- a/src/app/(app)/dashboard/page.tsx +++ b/src/app/(app)/dashboard/page.tsx @@ -2,9 +2,9 @@ import type { Metadata } from "next"; import { Suspense } from "react"; import { PageHeader } from "@/components/page-header"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; -import { MOCK_SESSION_USER } from "@/features/auth/mock"; import { SummaryCardsSkeleton } from "@/features/dashboard/components/summary-cards-skeleton"; import { tr } from "@/i18n/tr"; +import { requireSession } from "@/lib/dal"; import { cn } from "@/lib/utils"; import { formatMoney } from "@/shared/format"; @@ -51,8 +51,8 @@ async function SummaryCards() { ); } -export default function DashboardPage() { - const user = MOCK_SESSION_USER; +export default async function DashboardPage() { + const user = await requireSession(); return ( <> }> + {children} + + ); +} + +async function AuthenticatedShell({ children }: { children: React.ReactNode }) { + const user = await requireSession(); return ( + {/* FAZ 5: kullanıcının üye olduğu haneler */} {/* Mobilde alt menü yüksekliği kadar boşluk */}

{children}
diff --git a/src/app/(auth)/login/login-form.tsx b/src/app/(auth)/login/login-form.tsx index 3d0e25a..7763127 100644 --- a/src/app/(auth)/login/login-form.tsx +++ b/src/app/(auth)/login/login-form.tsx @@ -10,6 +10,8 @@ import { Button } from "@/components/ui/button"; import { Field, FieldError, FieldGroup, FieldLabel } from "@/components/ui/field"; import { Input } from "@/components/ui/input"; import { tr } from "@/i18n/tr"; +import { authClient } from "@/lib/auth-client"; +import { looksLikeEmail, normalizeUsername } from "@/shared/schemas/common"; import { loginSchema, type LoginInput } from "@/shared/schemas/auth"; export function LoginForm() { @@ -20,11 +22,23 @@ export function LoginForm() { defaultValues: { identifier: "", password: "" }, }); - async function onSubmit(_values: LoginInput) { + async function onSubmit({ identifier, password }: LoginInput) { setFormError(null); - // FAZ 3: looksLikeEmail(identifier) ? authClient.signIn.email : authClient.signIn.username - await new Promise((r) => setTimeout(r, 400)); + const value = identifier.trim(); + const { error } = looksLikeEmail(value) + ? await authClient.signIn.email({ email: value.toLowerCase(), password }) + : await authClient.signIn.username({ username: normalizeUsername(value), password }); + + if (error) { + // Hangi bilginin yanlış olduğu söylenmez (hesap tahmini zorlaşır). + if (error.status === 429) setFormError(tr.auth.tooManyAttempts); + else if (error.code === "BANNED_USER") setFormError(tr.auth.banned); + else setFormError(tr.auth.invalidCredentials); + form.setValue("password", ""); + return; + } router.replace("/dashboard"); + router.refresh(); } const submitting = form.formState.isSubmitting; diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 1c00480..c6bca9f 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -1,14 +1,27 @@ import type { Metadata } from "next"; +import { redirect } from "next/navigation"; +import { Suspense } from "react"; import { Logo } from "@/components/brand/logo"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { tr } from "@/i18n/tr"; +import { getCurrentUser } from "@/lib/dal"; import { LoginForm } from "./login-form"; export const metadata: Metadata = { title: tr.auth.loginTitle }; +/** Oturum açıksa dashboard'a yönlendirir (istek anında, Suspense içinde). */ +async function RedirectIfSignedIn() { + const user = await getCurrentUser(); + if (user) redirect("/dashboard"); + return null; +} + export default function LoginPage() { return (
+ + + diff --git a/src/app/api/health/route.ts b/src/app/api/health/route.ts index 5edfb33..47b26c1 100644 --- a/src/app/api/health/route.ts +++ b/src/app/api/health/route.ts @@ -1,20 +1,23 @@ import { connection } from "next/server"; +import { pingDb } from "@/lib/db/mongo"; import { APP_COMMIT, APP_VERSION } from "@/lib/version"; /** - * Sağlık kontrolü (Dokploy / Docker HEALTHCHECK). - * Faz 3'te veritabanı ping'i eklenecek; DB erişilemezse 503 dönecek. + * Sağlık kontrolü (Dokploy / Docker HEALTHCHECK). Veritabanına erişilemezse 503. + * İç hata detayı döndürülmez. */ export async function GET() { // Cache Components açıkken GET handler'ı build'de prerender edilmesin. await connection(); + const dbOk = await pingDb(); return Response.json( { - status: "ok", + status: dbOk ? "ok" : "degraded", + db: dbOk ? "ok" : "unreachable", version: APP_VERSION, commit: APP_COMMIT, time: new Date().toISOString(), }, - { headers: { "Cache-Control": "no-store" } }, + { status: dbOk ? 200 : 503, headers: { "Cache-Control": "no-store" } }, ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index 0abc6c8..24c584c 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,6 @@ import { redirect } from "next/navigation"; -/** Uygulama her zaman login ile açılır. FAZ 3: oturum varsa /dashboard'a yönlenecek. */ +/** Oturum yoksa proxy zaten /login'e yönlendirir; varsa dashboard. */ export default function Home() { - redirect("/login"); + redirect("/dashboard"); } diff --git a/src/components/layout/app-shell-fallback.tsx b/src/components/layout/app-shell-fallback.tsx new file mode 100644 index 0000000..3b22624 --- /dev/null +++ b/src/components/layout/app-shell-fallback.tsx @@ -0,0 +1,26 @@ +import { Skeleton } from "@/components/ui/skeleton"; +import { tr } from "@/i18n/tr"; + +/** Oturum okunurken gösterilen kabuk iskeleti (yan menü + üst bar + içerik). */ +export function AppShellFallback() { + return ( +
+
+ + {Array.from({ length: 8 }, (_, i) => ( + + ))} +
+
+
+ + +
+
+ + +
+
+
+ ); +} diff --git a/src/components/layout/user-menu.tsx b/src/components/layout/user-menu.tsx index 3987572..434a33f 100644 --- a/src/components/layout/user-menu.tsx +++ b/src/components/layout/user-menu.tsx @@ -15,6 +15,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import type { SessionUser } from "@/features/auth/types"; +import { authClient } from "@/lib/auth-client"; import { tr } from "@/i18n/tr"; export function initials(name: string) { @@ -30,10 +31,15 @@ export function initials(name: string) { export function UserMenu({ user, version }: { user: SessionUser; version: string }) { const router = useRouter(); - function logout() { - // FAZ 3: authClient.signOut() sonrası yönlendirme. + async function logout() { + const { error } = await authClient.signOut(); + if (error) { + toast.error(tr.common.genericError); + return; + } toast.success(tr.userMenu.loggedOut); router.replace("/login"); + router.refresh(); } return ( @@ -61,7 +67,7 @@ export function UserMenu({ user, version }: { user: SessionUser; version: string - + void logout()}> {tr.userMenu.logout} diff --git a/src/features/admin-users/actions.ts b/src/features/admin-users/actions.ts new file mode 100644 index 0000000..38e62df --- /dev/null +++ b/src/features/admin-users/actions.ts @@ -0,0 +1,250 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { headers } from "next/headers"; +import { z } from "zod"; +import { tr } from "@/i18n/tr"; +import { fail, ok, type ActionResult } from "@/lib/action-result"; +import { getAuth } from "@/lib/auth"; +import { authErrorCode } from "@/lib/auth-errors"; +import { getActionUser } from "@/lib/dal"; +import { storedEmail } from "@/shared/account-email"; +import { systemRoleSchema, type SystemRole } from "@/shared/schemas/common"; +import { + banUserSchema, + setPasswordSchema, + userCreateSchema, + userUpdateSchema, +} from "@/shared/schemas/user"; +import { checkAdminAction, type AdminAction, type GuardViolation } from "./guards"; + +/** + * Admin kullanıcı yönetimi Server Action'ları. + * Sıra: yetki (sunucuda) → zod doğrulama → iş kuralları (guards) → Better Auth admin API → revalidate. + */ + +const t = tr.adminUsers; +const PATH = "/admin/kullanicilar"; +const userIdSchema = z.string().min(1).max(64); + +const guardMessage = (v: GuardViolation) => (v === "self" ? t.guards.self : t.guards.lastAdmin); + +/** Bilinen Better Auth hata kodlarını kullanıcıya gösterilecek alan hatalarına çevirir. */ +function mapAuthError(error: unknown): ActionResult { + const code = authErrorCode(error); + switch (code) { + case "USERNAME_IS_ALREADY_TAKEN": + return fail(t.errors.checkFields, { username: t.errors.usernameTaken }); + case "USER_ALREADY_EXISTS": + case "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": + return fail(t.errors.checkFields, { email: t.errors.emailTaken }); + case "INVALID_USERNAME": + case "USERNAME_TOO_SHORT": + case "USERNAME_TOO_LONG": + return fail(t.errors.checkFields, { username: tr.validation.usernameFormat }); + case "INVALID_EMAIL": + return fail(t.errors.checkFields, { email: tr.validation.email }); + case "PASSWORD_TOO_SHORT": + case "PASSWORD_TOO_LONG": + return fail(t.errors.checkFields, { password: tr.validation.passwordMin }); + case "YOU_CANNOT_BAN_YOURSELF": + case "YOU_CANNOT_REMOVE_YOURSELF": + return fail(t.guards.self); + default: + // İç detay kullanıcıya sızdırılmaz; sunucuda loglanır. + console.error("[admin-users] beklenmeyen hata", code ?? error); + return fail(tr.common.genericError); + } +} + +/** Fiziksel kontrol: oturumdaki kullanıcı admin mi? Değilse işlem yapılmaz. */ +async function adminContext() { + const actor = await getActionUser("admin"); + if (!actor) return null; + return { actor, headers: await headers(), api: getAuth().api }; +} + +type Ctx = NonNullable>>; + +async function loadTarget(ctx: Ctx, userId: string) { + try { + const user = await ctx.api.getUser({ headers: ctx.headers, query: { id: userId } }); + return user + ? { + id: user.id, + role: (user.role === "admin" ? "admin" : "user") as SystemRole, + name: user.name, + } + : null; + } catch { + return null; + } +} + +async function countAdmins(ctx: Ctx): Promise { + const result = await ctx.api.listUsers({ + headers: ctx.headers, + query: { filterField: "role", filterOperator: "eq", filterValue: "admin", limit: 1 }, + }); + return result.total; +} + +async function guard(ctx: Ctx, action: AdminAction, target: { id: string; role: SystemRole }) { + const adminCount = target.role === "admin" ? await countAdmins(ctx) : Number.POSITIVE_INFINITY; + return checkAdminAction(action, { actorId: ctx.actor.id, target, adminCount }); +} + +// --- Action'lar --- + +export async function createUserAction(input: unknown): Promise { + const ctx = await adminContext(); + if (!ctx) return fail(tr.common.unauthorized); + const parsed = userCreateSchema.safeParse(input); + if (!parsed.success) return fail(t.errors.checkFields); + const v = parsed.data; + try { + await ctx.api.createUser({ + headers: ctx.headers, + body: { + name: v.name, + email: storedEmail(v.username, v.email), + password: v.password, + role: v.role, + data: { username: v.username }, + }, + }); + } catch (error) { + return mapAuthError(error); + } + revalidatePath(PATH); + return ok(undefined); +} + +export async function updateUserAction(userId: unknown, input: unknown): Promise { + const ctx = await adminContext(); + if (!ctx) return fail(tr.common.unauthorized); + const id = userIdSchema.safeParse(userId); + const parsed = userUpdateSchema.safeParse(input); + if (!id.success || !parsed.success) return fail(t.errors.checkFields); + const target = await loadTarget(ctx, id.data); + if (!target) return fail(tr.common.notFound); + const v = parsed.data; + + if (v.role !== target.role) { + const violation = await guard(ctx, { type: "setRole", role: v.role }, target); + if (violation) return fail(guardMessage(violation), { role: guardMessage(violation) }); + } + try { + await ctx.api.adminUpdateUser({ + headers: ctx.headers, + body: { + userId: target.id, + data: { name: v.name, username: v.username, email: storedEmail(v.username, v.email) }, + }, + }); + if (v.role !== target.role) { + await ctx.api.setRole({ headers: ctx.headers, body: { userId: target.id, role: v.role } }); + } + } catch (error) { + return mapAuthError(error); + } + revalidatePath(PATH); + return ok(undefined); +} + +export async function setRoleAction(userId: unknown, role: unknown): Promise { + const ctx = await adminContext(); + if (!ctx) return fail(tr.common.unauthorized); + const id = userIdSchema.safeParse(userId); + const r = systemRoleSchema.safeParse(role); + if (!id.success || !r.success) return fail(tr.common.genericError); + const target = await loadTarget(ctx, id.data); + if (!target) return fail(tr.common.notFound); + const violation = await guard(ctx, { type: "setRole", role: r.data }, target); + if (violation) return fail(guardMessage(violation)); + try { + await ctx.api.setRole({ headers: ctx.headers, body: { userId: target.id, role: r.data } }); + } catch (error) { + return mapAuthError(error); + } + revalidatePath(PATH); + return ok(undefined); +} + +export async function setPasswordAction(userId: unknown, input: unknown): Promise { + const ctx = await adminContext(); + if (!ctx) return fail(tr.common.unauthorized); + const id = userIdSchema.safeParse(userId); + const parsed = setPasswordSchema.safeParse(input); + if (!id.success || !parsed.success) return fail(t.errors.checkFields); + const target = await loadTarget(ctx, id.data); + if (!target) return fail(tr.common.notFound); + try { + await ctx.api.setUserPassword({ + headers: ctx.headers, + body: { userId: target.id, newPassword: parsed.data.password }, + }); + // Başka bir kullanıcının şifresi sıfırlandıysa açık oturumlarını kapat. + if (target.id !== ctx.actor.id) { + await ctx.api.revokeUserSessions({ headers: ctx.headers, body: { userId: target.id } }); + } + } catch (error) { + return mapAuthError(error); + } + return ok(undefined); +} + +export async function banUserAction(userId: unknown, input: unknown): Promise { + const ctx = await adminContext(); + if (!ctx) return fail(tr.common.unauthorized); + const id = userIdSchema.safeParse(userId); + const parsed = banUserSchema.safeParse(input); + if (!id.success || !parsed.success) return fail(t.errors.checkFields); + const target = await loadTarget(ctx, id.data); + if (!target) return fail(tr.common.notFound); + const violation = await guard(ctx, { type: "ban" }, target); + if (violation) return fail(guardMessage(violation)); + try { + // banUser açık oturumları da sonlandırır. + await ctx.api.banUser({ + headers: ctx.headers, + body: { userId: target.id, banReason: parsed.data.reason }, + }); + } catch (error) { + return mapAuthError(error); + } + revalidatePath(PATH); + return ok(undefined); +} + +export async function unbanUserAction(userId: unknown): Promise { + const ctx = await adminContext(); + if (!ctx) return fail(tr.common.unauthorized); + const id = userIdSchema.safeParse(userId); + if (!id.success) return fail(tr.common.genericError); + try { + await ctx.api.unbanUser({ headers: ctx.headers, body: { userId: id.data } }); + } catch (error) { + return mapAuthError(error); + } + revalidatePath(PATH); + return ok(undefined); +} + +export async function removeUserAction(userId: unknown): Promise { + const ctx = await adminContext(); + if (!ctx) return fail(tr.common.unauthorized); + const id = userIdSchema.safeParse(userId); + if (!id.success) return fail(tr.common.genericError); + const target = await loadTarget(ctx, id.data); + if (!target) return fail(tr.common.notFound); + const violation = await guard(ctx, { type: "delete" }, target); + if (violation) return fail(guardMessage(violation)); + try { + await ctx.api.removeUser({ headers: ctx.headers, body: { userId: target.id } }); + } catch (error) { + return mapAuthError(error); + } + revalidatePath(PATH); + return ok(undefined); +} diff --git a/src/features/admin-users/components/users-table.tsx b/src/features/admin-users/components/users-table.tsx index e0486a9..388bbf2 100644 --- a/src/features/admin-users/components/users-table.tsx +++ b/src/features/admin-users/components/users-table.tsx @@ -59,18 +59,22 @@ import { TableRow, } from "@/components/ui/table"; import { tr } from "@/i18n/tr"; -import { fail, ok, type ActionResult } from "@/lib/action-result"; +import { fail, type ActionResult } from "@/lib/action-result"; import { cn } from "@/lib/utils"; import { formatDate } from "@/shared/format"; import type { SystemRole } from "@/shared/schemas/common"; import type { UserCreate, UserUpdate } from "@/shared/schemas/user"; import { compareTr, includesTr } from "@/shared/search"; import { - checkAdminAction, - findUniqueConflicts, - type AdminAction, - type GuardViolation, -} from "../guards"; + banUserAction, + createUserAction, + removeUserAction, + setPasswordAction, + setRoleAction, + unbanUserAction, + updateUserAction, +} from "../actions"; +import { checkAdminAction, type AdminAction, type GuardViolation } from "../guards"; import type { AdminUser } from "../types"; import { BanUserDialog, DeleteUserDialog, SetPasswordDialog } from "./user-action-dialogs"; import { UserFormDialog } from "./user-form-dialog"; @@ -311,15 +315,14 @@ const columns = helper.columns([ ]); export function UsersTable({ - initialUsers, + users, currentUserId, }: { - initialUsers: AdminUser[]; + users: AdminUser[]; currentUserId: string; }) { - // FAZ 3: yerel durum yerine Server Action + revalidatePath. - const [users, setUsers] = useState(initialUsers); const [dialog, setDialog] = useState(null); + // Arayüzdeki kural kontrolü yalnızca erken geri bildirim içindir; asıl kontrol sunucuda. const adminCount = users.filter((u) => u.role === "admin").length; const guard = (action: AdminAction, target: AdminUser): GuardViolation | null => @@ -327,84 +330,41 @@ export function UsersTable({ const guardMessage = (v: GuardViolation) => (v === "self" ? t.guards.self : t.guards.lastAdmin); - // --- Mock işlemler (FAZ 3'te Server Action'lara taşınacak; aynı ActionResult sözleşmesi) --- - - async function createUser(values: UserCreate): Promise { - const conflicts = findUniqueConflicts(users, values); - if (conflicts.username || conflicts.email) { - return fail(t.errors.checkFields, { - username: conflicts.username && t.errors.usernameTaken, - email: conflicts.email && t.errors.emailTaken, - }); + /** Server Action'ı çağırır; başarıda bildirim gösterir. Sayfa revalidatePath ile tazelenir. */ + async function run(promise: Promise, success: string): Promise { + try { + const result = await promise; + if (result.ok) toast.success(success); + return result; + } catch { + return fail(tr.common.genericError); } - const { password: _password, ...rest } = values; - setUsers((prev) => [ - { - ...rest, - id: `u_${crypto.randomUUID().slice(0, 8)}`, - banned: false, - createdAt: new Date().toISOString(), - }, - ...prev, - ]); - toast.success(t.toast.created(values.name)); - return ok(undefined); } - async function updateUser(id: string, values: UserUpdate): Promise { - const target = users.find((u) => u.id === id); - if (!target) return fail(tr.common.genericError); - const conflicts = findUniqueConflicts(users, values, id); - if (conflicts.username || conflicts.email) { - return fail(t.errors.checkFields, { - username: conflicts.username && t.errors.usernameTaken, - email: conflicts.email && t.errors.emailTaken, - }); - } - const v = - values.role !== target.role ? guard({ type: "setRole", role: values.role }, target) : null; - if (v) return fail(guardMessage(v), { role: guardMessage(v) }); - setUsers((prev) => prev.map((u) => (u.id === id ? { ...u, ...values } : u))); - toast.success(t.toast.updated(values.name)); - return ok(undefined); + const createUser = (values: UserCreate) => + run(createUserAction(values), t.toast.created(values.name)); + + const updateUser = (id: string, values: UserUpdate) => + run(updateUserAction(id, values), t.toast.updated(values.name)); + + async function setRole(user: AdminUser, role: SystemRole) { + const result = await run(setRoleAction(user.id, role), t.toast.roleChanged(user.name)); + if (!result.ok) toast.error(result.error); } - function setRole(user: AdminUser, role: SystemRole) { - const v = guard({ type: "setRole", role }, user); - if (v) return void toast.error(guardMessage(v)); - setUsers((prev) => prev.map((u) => (u.id === user.id ? { ...u, role } : u))); - toast.success(t.toast.roleChanged(user.name)); + const setPassword = (user: AdminUser, values: { password: string }) => + run(setPasswordAction(user.id, values), t.toast.passwordSet(user.name)); + + const banUser = (user: AdminUser, values: { reason?: string }) => + run(banUserAction(user.id, values), t.toast.banned(user.name)); + + async function unbanUser(user: AdminUser) { + const result = await run(unbanUserAction(user.id), t.toast.unbanned(user.name)); + if (!result.ok) toast.error(result.error); } - async function setPassword(user: AdminUser): Promise { - toast.success(t.toast.passwordSet(user.name)); - return ok(undefined); - } - - async function banUser(user: AdminUser, { reason }: { reason?: string }): Promise { - const v = guard({ type: "ban" }, user); - if (v) return fail(guardMessage(v)); - setUsers((prev) => - prev.map((u) => (u.id === user.id ? { ...u, banned: true, banReason: reason } : u)), - ); - toast.success(t.toast.banned(user.name)); - return ok(undefined); - } - - function unbanUser(user: AdminUser) { - setUsers((prev) => - prev.map((u) => (u.id === user.id ? { ...u, banned: false, banReason: undefined } : u)), - ); - toast.success(t.toast.unbanned(user.name)); - } - - async function removeUser(user: AdminUser): Promise { - const v = guard({ type: "delete" }, user); - if (v) return fail(guardMessage(v)); - setUsers((prev) => prev.filter((u) => u.id !== user.id)); - toast.success(t.toast.removed(user.name)); - return ok(undefined); - } + const removeUser = (user: AdminUser) => + run(removeUserAction(user.id), t.toast.removed(user.name)); const table = useTable({ features, diff --git a/src/features/admin-users/guards.test.ts b/src/features/admin-users/guards.test.ts index 8df02f7..63108e2 100644 --- a/src/features/admin-users/guards.test.ts +++ b/src/features/admin-users/guards.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { checkAdminAction, findUniqueConflicts } from "./guards"; +import { checkAdminAction } from "./guards"; const admin = { id: "a1", role: "admin" as const }; const user = { id: "u1", role: "user" as const }; @@ -40,20 +40,3 @@ describe("checkAdminAction", () => { expect(checkAdminAction({ type: "setRole", role: "admin" }, ctx)).toBeNull(); }); }); - -describe("findUniqueConflicts", () => { - const users = [ - { id: "1", username: "ayse", email: "ayse@x.com" }, - { id: "2", username: "emre" }, - ]; - it("çakışan kullanıcı adı ve e-postayı bulur", () => { - expect(findUniqueConflicts(users, { username: "ayse", email: "ayse@x.com" })).toEqual({ - username: "taken", - email: "taken", - }); - }); - it("düzenlenen kaydın kendisini saymaz, boş e-postayı kontrol etmez", () => { - expect(findUniqueConflicts(users, { username: "ayse", email: "ayse@x.com" }, "1")).toEqual({}); - expect(findUniqueConflicts(users, { username: "yeni" })).toEqual({}); - }); -}); diff --git a/src/features/admin-users/guards.ts b/src/features/admin-users/guards.ts index a3230e1..e1fbde2 100644 --- a/src/features/admin-users/guards.ts +++ b/src/features/admin-users/guards.ts @@ -35,18 +35,3 @@ export function checkAdminAction(action: AdminAction, ctx: GuardContext): GuardV if (removesAdmin && ctx.adminCount <= 1) return "lastAdmin"; return null; } - -/** Kullanıcı adı / e-posta benzersizlik kontrolü (normalize edilmiş değerlerle). */ -export function findUniqueConflicts( - users: ReadonlyArray<{ id: string; username: string; email?: string }>, - values: { username: string; email?: string }, - excludeId?: string, -): { username?: "taken"; email?: "taken" } { - const others = users.filter((u) => u.id !== excludeId); - return { - ...(others.some((u) => u.username === values.username) ? { username: "taken" as const } : {}), - ...(values.email && others.some((u) => u.email === values.email) - ? { email: "taken" as const } - : {}), - }; -} diff --git a/src/features/admin-users/mapper.ts b/src/features/admin-users/mapper.ts new file mode 100644 index 0000000..b836c8f --- /dev/null +++ b/src/features/admin-users/mapper.ts @@ -0,0 +1,27 @@ +import { visibleEmail } from "@/shared/account-email"; +import type { AdminUser } from "./types"; + +type RawAdminUser = { + id: string; + name: string; + email: string; + username?: string | null; + role?: string | null; + banned?: boolean | null; + banReason?: string | null; + createdAt: Date | string; +}; + +/** Better Auth kullanıcı kaydını arayüz satırına çevirir (yer tutucu e-posta gizlenir). */ +export function toAdminUser(u: RawAdminUser): AdminUser { + return { + id: u.id, + name: u.name, + username: u.username ?? "", + email: visibleEmail(u.email), + role: u.role === "admin" ? "admin" : "user", + banned: Boolean(u.banned), + banReason: u.banReason ?? undefined, + createdAt: new Date(u.createdAt).toISOString(), + }; +} diff --git a/src/features/admin-users/mock.ts b/src/features/admin-users/mock.ts deleted file mode 100644 index 724387a..0000000 --- a/src/features/admin-users/mock.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { AdminUser } from "./types"; - -/** FAZ 2 GEÇİCİ: Faz 3'te Better Auth admin API'sinden gelen listeyle değiştirilecek. */ -export const MOCK_ADMIN_USERS: AdminUser[] = [ - { - id: "u_admin", - name: "Suphi Çakır", - username: "suphi", - email: "admin@butcem.local", - role: "admin", - banned: false, - createdAt: "2026-05-02T08:00:00Z", - }, - { - id: "u_ayse", - name: "Ayşe Çakır", - username: "ayse", - email: "ayse@butcem.local", - role: "user", - banned: false, - createdAt: "2026-05-02T08:10:00Z", - }, - { - id: "u_emre", - name: "Emre Çakır", - username: "emre", - role: "user", - banned: false, - createdAt: "2026-05-03T17:45:00Z", - }, - { - id: "u_zeynep", - name: "Zeynep Çakır", - username: "zeynep", - role: "user", - banned: false, - createdAt: "2026-05-03T17:50:00Z", - }, - { - id: "u_fatma", - name: "Fatma Demir", - username: "fatma.demir", - email: "fatma@butcem.local", - role: "user", - banned: false, - createdAt: "2026-06-11T12:00:00Z", - }, - { - id: "u_mehmet", - name: "Mehmet Demir", - username: "mehmet.demir", - email: "mehmet@butcem.local", - role: "user", - banned: false, - createdAt: "2026-06-11T12:05:00Z", - }, - { - id: "u_ozan", - name: "Ozan Kaya", - username: "ozan", - email: "ozan@butcem.local", - role: "user", - banned: true, - banReason: "Eski ev arkadaşı", - createdAt: "2026-07-20T09:30:00Z", - }, - { - id: "u_elif", - name: "Elif Şahin", - username: "elif_s", - email: "elif@butcem.local", - role: "user", - banned: false, - createdAt: "2026-08-14T19:15:00Z", - }, - { - id: "u_burak", - name: "Burak Öztürk", - username: "burak", - email: "burak@butcem.local", - role: "admin", - banned: false, - createdAt: "2026-09-01T06:40:00Z", - }, - { - id: "u_irem", - name: "İrem Güneş", - username: "irem", - role: "user", - banned: false, - createdAt: "2026-09-22T15:00:00Z", - }, - { - id: "u_can", - name: "Can Yıldız", - username: "can.y", - email: "can@butcem.local", - role: "user", - banned: false, - createdAt: "2026-10-05T10:20:00Z", - }, - { - id: "u_selin", - name: "Selin Aydın", - username: "selin", - email: "selin@butcem.local", - role: "user", - banned: false, - createdAt: "2026-10-30T21:05:00Z", - }, -]; diff --git a/src/features/admin-users/queries.ts b/src/features/admin-users/queries.ts new file mode 100644 index 0000000..f043a2f --- /dev/null +++ b/src/features/admin-users/queries.ts @@ -0,0 +1,16 @@ +import "server-only"; +import { headers } from "next/headers"; +import { getAuth } from "@/lib/auth"; +import { requireRole } from "@/lib/dal"; +import { toAdminUser } from "./mapper"; +import type { AdminUser } from "./types"; + +/** Admin: tüm kullanıcılar (en yeni önce). Kişisel kullanım ölçeğinde tek sayfa yeterli. */ +export async function listAdminUsers(): Promise { + await requireRole("admin"); + const result = await getAuth().api.listUsers({ + headers: await headers(), + query: { limit: 1000, sortBy: "createdAt", sortDirection: "desc" }, + }); + return result.users.map(toAdminUser); +} diff --git a/src/features/auth/mock.ts b/src/features/auth/mock.ts deleted file mode 100644 index 07d770d..0000000 --- a/src/features/auth/mock.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { SessionUser } from "./types"; - -/** - * FAZ 2 GEÇİCİ: Better Auth bağlanana kadar arayüzü besleyen sahte oturum. - * Faz 3'te `requireSession()` (lib/dal.ts) ile değiştirilecek ve bu dosya silinecek. - */ -export const MOCK_SESSION_USER: SessionUser = { - id: "u_admin", - name: "Suphi Çakır", - username: "suphi", - email: "admin@butcem.local", - role: "admin", -}; From 97e7ebe9aa422bdc640ef4fb1fe7511327eb4f1a Mon Sep 17 00:00:00 2001 From: suphicakir Date: Thu, 5 Nov 2026 02:02:00 +0300 Subject: [PATCH 4/6] =?UTF-8?q?feat(security):=20CSP=20ve=20g=C3=BCvenlik?= =?UTF-8?q?=20ba=C5=9Fl=C4=B1klar=C4=B1,=20health=20DB=20ping?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- next.config.ts | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/next.config.ts b/next.config.ts index d80125d..8e8b26e 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,5 +1,47 @@ import type { NextConfig } from "next"; +const isDev = process.env.NODE_ENV === "development"; + +/** + * Content-Security-Policy (nonce'suz): Cache Components'in statik kabuğu ile uyumlu. + * 'unsafe-inline' script: Next.js'in satır içi hydration verisi ve next-themes'in tema betiği için. + * Grafikler (Recharts, SVG) ve carousel (Embla) ek kaynak gerektirmez; ek dosyalar + * kendi origin'imizden (/api/files/...) ve blob: önizlemelerle servis edilir. + */ +const csp = [ + "default-src 'self'", + `script-src 'self' 'unsafe-inline'${isDev ? " 'unsafe-eval'" : ""}`, + "style-src 'self' 'unsafe-inline'", + "img-src 'self' blob: data:", + "font-src 'self'", + "connect-src 'self'", + "media-src 'self' blob:", + "worker-src 'self' blob:", + "manifest-src 'self'", + "object-src 'none'", + "base-uri 'self'", + "form-action 'self'", + "frame-ancestors 'none'", + ...(isDev ? [] : ["upgrade-insecure-requests"]), +].join("; "); + +const securityHeaders = [ + { key: "Content-Security-Policy", value: csp }, + { key: "X-Content-Type-Options", value: "nosniff" }, + { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" }, + { key: "X-Frame-Options", value: "DENY" }, + { key: "Cross-Origin-Opener-Policy", value: "same-origin" }, + // Kamera: Faz 6'da fiş fotoğrafı için yalnızca kendi origin'imiz. + { + key: "Permissions-Policy", + value: "camera=(self), microphone=(), geolocation=(), payment=(), usb=()", + }, + ...(isDev + ? [] + : // includeSubDomains/preload bilinçli olarak yok: ta1sph.website'in diğer alt alanlarını bağlamasın. + [{ key: "Strict-Transport-Security", value: "max-age=31536000" }]), +]; + const nextConfig: NextConfig = { // Docker imajı için yalnızca gerekli dosyaları .next/standalone altına kopyala. output: "standalone", @@ -17,6 +59,9 @@ const nextConfig: NextConfig = { }, }, }, + async headers() { + return [{ source: "/:path*", headers: securityHeaders }]; + }, }; export default nextConfig; From fc3e0ae21bbbe7c20f73193058286deae4c0c720 Mon Sep 17 00:00:00 2001 From: suphicakir Date: Thu, 5 Nov 2026 02:02:00 +0300 Subject: [PATCH 5/6] =?UTF-8?q?test:=20admin=20seed=20ve=20kimlik=20do?= =?UTF-8?q?=C4=9Frulama=20entegrasyon=20testleri?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- package.json | 9 +- scripts/seed.ts | 70 +++++++++++-- src/lib/auth.int.test.ts | 191 ++++++++++++++++++++++++++++++++++ src/test/server-only-stub.ts | 2 + vitest.config.mts | 9 +- vitest.integration.config.mts | 37 +++++++ 6 files changed, 305 insertions(+), 13 deletions(-) create mode 100644 src/lib/auth.int.test.ts create mode 100644 src/test/server-only-stub.ts create mode 100644 vitest.integration.config.mts diff --git a/package.json b/package.json index a2686e4..ad71ec8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "butcem", - "version": "0.2.0", + "version": "0.3.0", "private": true, "description": "BÜTÇEM — aile bütçe, cari, birikim, ev stoğu ve akıllı alışveriş uygulaması", "engines": { @@ -18,9 +18,10 @@ "format:check": "prettier --check .", "db:up": "docker compose up -d --wait", "db:down": "docker compose down", - "seed": "tsx --env-file=.env.local scripts/seed.ts", - "seed:reset": "tsx --env-file=.env.local scripts/seed.ts --reset", - "icons": "tsx scripts/generate-icons.ts" + "seed": "tsx --conditions=react-server --env-file=.env.local scripts/seed.ts", + "seed:reset": "tsx --conditions=react-server --env-file=.env.local scripts/seed.ts --reset", + "icons": "tsx scripts/generate-icons.ts", + "test:integration": "vitest run --config vitest.integration.config.mts" }, "dependencies": { "@hookform/resolvers": "^7.0.2", diff --git a/scripts/seed.ts b/scripts/seed.ts index 9f80154..38aa3e8 100644 --- a/scripts/seed.ts +++ b/scripts/seed.ts @@ -1,10 +1,68 @@ /** - * Seed betiği — Faz 4'te doldurulacak (admin, örnek hane, aile üyeleri ve alan verisi). - * Çalıştırma: npm run seed | Sıfırlayıp doldurma: npm run seed:reset + * Seed betiği: npm run seed | Sıfırlayıp doldurma: npm run seed:reset + * + * Faz 3: yalnızca ilk admin (SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD, kullanıcı adı "admin"). + * Faz 4: örnek kullanıcılar, hane ve aile üyeleri eklenecek. + * + * İdempotent: kullanıcı e-postasına göre kontrol eder, tekrar çalıştırınca kopya oluşturmaz. + * Kullanıcılar Better Auth API'siyle oluşturulur (şifre doğru hashlenir; koleksiyona elle yazılmaz). + * `server-only` modülleri Next dışında yükleyebilmek için --conditions=react-server ile çalışır. */ -if (process.env.NODE_ENV === "production" && process.argv.includes("--reset")) { - console.error("seed:reset production ortamında çalıştırılamaz."); - process.exit(1); +import { getAuth } from "@/lib/auth"; +import { getDb, getMongoClient } from "@/lib/db/mongo"; +import { env } from "@/lib/env"; + +const reset = process.argv.includes("--reset"); + +async function main() { + const e = env(); + if (reset && e.NODE_ENV === "production") { + throw new Error("seed:reset production ortamında çalıştırılamaz."); + } + if (!e.SEED_ADMIN_EMAIL || !e.SEED_ADMIN_PASSWORD) { + throw new Error("SEED_ADMIN_EMAIL ve SEED_ADMIN_PASSWORD tanımlı olmalı (.env.local)."); + } + + const db = getDb(); + if (reset) { + for (const name of ["user", "account", "session", "verification", "rateLimit"]) { + await db.collection(name).deleteMany({}); + } + console.log("• Kimlik doğrulama koleksiyonları temizlendi."); + } + + const email = e.SEED_ADMIN_EMAIL.toLowerCase(); + const existing = await db.collection("user").findOne({ email }); + if (existing) { + console.log(`• Admin zaten var: ${email}`); + } else { + await getAuth().api.createUser({ + body: { + email, + password: e.SEED_ADMIN_PASSWORD, + name: "Yönetici", + role: "admin", + data: { username: "admin" }, + }, + }); + console.log(`✓ Admin oluşturuldu: ${email} (kullanıcı adı: admin)`); + } + + if (e.NODE_ENV !== "production") { + console.table([ + { + rol: "admin", + kullaniciAdi: "admin", + eposta: email, + sifre: "SEED_ADMIN_PASSWORD (.env.local)", + }, + ]); + } } -console.log("Seed henüz uygulanmadı (Faz 4)."); +main() + .catch((err: unknown) => { + console.error(err instanceof Error ? err.message : err); + process.exitCode = 1; + }) + .finally(() => getMongoClient().close()); diff --git a/src/lib/auth.int.test.ts b/src/lib/auth.int.test.ts new file mode 100644 index 0000000..e3a7ba0 --- /dev/null +++ b/src/lib/auth.int.test.ts @@ -0,0 +1,191 @@ +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { getAuth } from "@/lib/auth"; +import { authErrorCode } from "@/lib/auth-errors"; +import { getDb, getMongoClient } from "@/lib/db/mongo"; +import { placeholderEmail } from "@/shared/account-email"; + +/** + * Better Auth yapılandırmasının güvenlik sözleşmesi (gerçek MongoDB, butcem_test veritabanı). + */ + +const auth = getAuth(); +const PASSWORD = "Gizli-Sifre-123"; + +/** Giriş yapıp oturum cookie'sini içeren istek başlıklarını döner. */ +async function signIn(username: string, password = PASSWORD): Promise { + const { headers } = await auth.api.signInUsername({ + body: { username, password }, + returnHeaders: true, + }); + const cookie = headers + .getSetCookie() + .map((c) => c.split(";")[0]) + .join("; "); + return new Headers({ cookie }); +} + +async function expectAuthError(promise: Promise, code: string) { + const error = await promise.then( + () => null, + (e: unknown) => e, + ); + expect(error, `beklenen hata: ${code}`).not.toBeNull(); + expect(authErrorCode(error)).toBe(code); +} + +beforeAll(async () => { + const db = getDb(); + expect(db.databaseName).toBe("butcem_test"); + await db.dropDatabase(); + await auth.api.createUser({ + body: { + email: "admin@test.local", + password: PASSWORD, + name: "Admin", + role: "admin", + data: { username: "admin" }, + }, + }); + await auth.api.createUser({ + body: { + email: placeholderEmail("emre"), + password: PASSWORD, + name: "Emre", + role: "user", + data: { username: "Emre" }, + }, + }); +}); + +afterAll(async () => { + await getDb().dropDatabase(); + await getMongoClient().close(); +}); + +describe("kayıt kapalı", () => { + it("sign-up/email uç noktası reddedilir ve kullanıcı oluşmaz", async () => { + const res = await auth.handler( + new Request("http://localhost:3000/api/auth/sign-up/email", { + method: "POST", + headers: { "content-type": "application/json", origin: "http://localhost:3000" }, + body: JSON.stringify({ email: "yeni@test.local", password: PASSWORD, name: "Yeni" }), + }), + ); + expect(res.ok).toBe(false); + expect(await getDb().collection("user").countDocuments({ email: "yeni@test.local" })).toBe(0); + }); +}); + +describe("giriş", () => { + it("kullanıcı adı küçük harfe normalize edilir ve kullanıcı adıyla giriş yapılır", async () => { + const stored = await getDb() + .collection("user") + .findOne({ email: placeholderEmail("emre") }); + expect(stored?.username).toBe("emre"); + const headers = await signIn("EMRE"); + const session = await auth.api.getSession({ headers }); + expect(session?.user.name).toBe("Emre"); + expect(session?.user.role).toBe("user"); + }); + + it("HTTP üzerinden büyük harfli (I içeren) kullanıcı adıyla giriş yapılır", async () => { + const res = await auth.handler( + new Request("http://localhost:3000/api/auth/sign-in/username", { + method: "POST", + headers: { + "content-type": "application/json", + origin: "http://localhost:3000", + "x-forwarded-for": "198.51.100.1", + }, + // "I" içeren büyük harfli giriş (tr-TR'de "ADMIN" → "admın" tuzağı) + body: JSON.stringify({ username: "ADMIN", password: PASSWORD }), + }), + ); + expect(res.status).toBe(200); + expect(res.headers.getSetCookie().some((c) => c.includes("session_token"))).toBe(true); + }); + + it("e-posta ile giriş yapılır", async () => { + const result = await auth.api.signInEmail({ + body: { email: "admin@test.local", password: PASSWORD }, + }); + expect(result.user.email).toBe("admin@test.local"); + }); + + it("yanlış şifre reddedilir", async () => { + await expectAuthError( + auth.api.signInUsername({ body: { username: "emre", password: "yanlis-sifre" } }), + "INVALID_USERNAME_OR_PASSWORD", + ); + }); + + it("aynı kullanıcı adı ikinci kez kullanılamaz", async () => { + await expectAuthError( + auth.api.createUser({ + body: { + email: "baska@test.local", + password: PASSWORD, + name: "Kopya", + data: { username: "emre" }, + }, + }), + "USERNAME_IS_ALREADY_TAKEN", + ); + }); +}); + +describe("admin yetkileri", () => { + it("admin olmayan kullanıcı admin API'sini çağıramaz", async () => { + const headers = await signIn("emre"); + const res = await auth.api + .listUsers({ headers, query: { limit: 10 } }) + .then(() => "ok") + .catch((e: unknown) => authErrorCode(e) ?? "error"); + expect(res).not.toBe("ok"); + }); + + it("askıya alınan kullanıcı giriş yapamaz, askı kaldırılınca yapabilir", async () => { + const adminHeaders = await signIn("admin"); + const emre = await getDb().collection("user").findOne({ username: "emre" }); + const userId = String(emre?._id); + + await auth.api.banUser({ headers: adminHeaders, body: { userId, banReason: "test" } }); + await expectAuthError( + auth.api.signInUsername({ body: { username: "emre", password: PASSWORD } }), + "BANNED_USER", + ); + + await auth.api.unbanUser({ headers: adminHeaders, body: { userId } }); + await expect(signIn("emre")).resolves.toBeInstanceOf(Headers); + }); + + it("admin kendini askıya alamaz", async () => { + const adminHeaders = await signIn("admin"); + const self = await auth.api.getSession({ headers: adminHeaders }); + await expectAuthError( + auth.api.banUser({ headers: adminHeaders, body: { userId: self!.user.id } }), + "YOU_CANNOT_BAN_YOURSELF", + ); + }); +}); + +describe("rate limit", () => { + it("giriş denemeleri dakikada 5 ile sınırlıdır", async () => { + const attempt = () => + auth.handler( + new Request("http://localhost:3000/api/auth/sign-in/username", { + method: "POST", + headers: { + "content-type": "application/json", + origin: "http://localhost:3000", + "x-forwarded-for": "203.0.113.7", + }, + body: JSON.stringify({ username: "emre", password: "yanlis-sifre" }), + }), + ); + const statuses: number[] = []; + for (let i = 0; i < 6; i++) statuses.push((await attempt()).status); + expect(statuses.slice(0, 5).every((s) => s !== 429)).toBe(true); + expect(statuses[5]).toBe(429); + }); +}); diff --git a/src/test/server-only-stub.ts b/src/test/server-only-stub.ts new file mode 100644 index 0000000..b159a7a --- /dev/null +++ b/src/test/server-only-stub.ts @@ -0,0 +1,2 @@ +// Testlerde "server-only" paketinin yerine geçer (paket React Server ortamı dışında hata fırlatır). +export {}; diff --git a/vitest.config.mts b/vitest.config.mts index c50fe37..5f08968 100644 --- a/vitest.config.mts +++ b/vitest.config.mts @@ -3,12 +3,15 @@ import { defineConfig } from "vitest/config"; export default defineConfig({ resolve: { - alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) }, + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + "server-only": fileURLToPath(new URL("./src/test/server-only-stub.ts", import.meta.url)), + }, }, test: { environment: "node", include: ["src/**/*.test.ts", "scripts/**/*.test.ts"], - // "server-only" paketi test ortamında hata fırlatmasın. - server: { deps: { inline: ["server-only"] } }, + // Veritabanı gerektiren testler ayrı: npm run test:integration + exclude: ["**/*.int.test.ts", "node_modules/**"], }, }); diff --git a/vitest.integration.config.mts b/vitest.integration.config.mts new file mode 100644 index 0000000..6b388c5 --- /dev/null +++ b/vitest.integration.config.mts @@ -0,0 +1,37 @@ +import { fileURLToPath } from "node:url"; +import { loadEnv } from "vite"; +import { defineConfig } from "vitest/config"; + +/** + * Entegrasyon testleri: gerçek MongoDB (npm run db:up) üzerinde, ayrı "butcem_test" veritabanı. + * Çalıştırma: npm run test:integration + */ +const fileEnv = loadEnv("test", process.cwd(), ""); +const local = { ...loadEnv("development", process.cwd(), ""), ...fileEnv }; +const baseUri = + process.env.MONGODB_URI_TEST ?? + ( + local.MONGODB_URI ?? "mongodb://localhost:27019/butcem?replicaSet=rs0&directConnection=true" + ).replace(/\/([^/?]+)(\?|$)/, "/butcem_test$2"); + +export default defineConfig({ + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + "server-only": fileURLToPath(new URL("./src/test/server-only-stub.ts", import.meta.url)), + }, + }, + test: { + environment: "node", + include: ["src/**/*.int.test.ts"], + fileParallelism: false, + testTimeout: 20_000, + hookTimeout: 30_000, + env: { + NODE_ENV: "test", + MONGODB_URI: baseUri, + BETTER_AUTH_SECRET: "test-secret-test-secret-test-secret-0123", + BETTER_AUTH_URL: "http://localhost:3000", + }, + }, +}); From cdbd7a0446d4f6dd605fe972baba7b88a3ff6e50 Mon Sep 17 00:00:00 2001 From: suphicakir Date: Thu, 5 Nov 2026 02:02:00 +0300 Subject: [PATCH 6/6] docs: CHANGELOG ve README (0.3.0) Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 34 +++++++++++++++++++++++++++++++++- README.md | 11 ++++++++--- 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0fee12..26f1b47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,37 @@ Biçim [Keep a Changelog](https://keepachangelog.com/tr-TR/1.1.0/) esas alınır ## [Yayınlanmamış] +## [0.3.0] - 2026-11-05 + +Faz 3: Backend — kimlik doğrulama, yetki katmanı ve güvenlik. + +### Eklendi + +- Better Auth (MongoDB adaptörü, replica set transaction): e-posta veya **kullanıcı adı** ile giriş, kayıt (sign-up) kapalı, admin plugin, 30 günlük oturum, `HttpOnly`/`Secure`/`SameSite=Lax` cookie. +- Rate limit (veritabanında): genel 100 istek/dk, giriş denemeleri 5/dk. +- Data Access Layer (`lib/dal.ts`): `getCurrentUser` (istek başına tek okuma), `requireSession`, `requireRole`, `getActionUser`. Askıdaki kullanıcının oturumu reddedilir. +- `proxy.ts`: iyimser oturum cookie kontrolü (oturumsuz → `/login`); asıl kontrol DAL'da. +- Admin kullanıcı yönetimi Server Action'ları: oluşturma, düzenleme, rol, şifre sıfırlama (diğer oturumları kapatır), askıya alma/kaldırma, silme. Kendini silememe/askıya alamama ve son admini düşürememe sunucuda da uygulanır; Better Auth hataları Türkçe alan hatalarına çevrilir. +- E-postası olmayan kullanıcılar için `.invalid` yer tutucu adres (arayüzde gizlenir). +- MongoDB (`lib/db/mongo.ts`) ve Mongoose (`lib/db/mongoose.ts`) bağlantıları (HMR güvenli tekil örnek, tembel oluşturma). +- Güvenlik başlıkları: CSP (nonce'suz, Cache Components uyumlu), `X-Content-Type-Options`, `Referrer-Policy`, `X-Frame-Options`, `Permissions-Policy`, `Cross-Origin-Opener-Policy`; production'da HSTS. +- `GET /api/health` veritabanı ping'i; erişilemezse 503. +- `npm run seed`: ilk admini (`SEED_ADMIN_*`, kullanıcı adı `admin`) idempotent olarak oluşturur. +- `npm run test:integration`: gerçek MongoDB'de (`butcem_test`) kimlik doğrulama sözleşmesi testleri (kayıt kapalı, giriş, askı, admin yetkisi, rate limit). +- BÜTÇEM logosu ve ikon seti (`logo.svg`, `logo-dark.svg`, `icon.svg`, `icon-maskable.svg`); `npm run icons` SVG kaynaktan üretir. + +### Değişti + +- Giriş, çıkış, dashboard ve admin sayfaları mock yerine gerçek oturum ve verilerle çalışır; oturuma bağlı kabuk Suspense içinde akış olarak gelir. + +### Düzeltildi + +- Kullanıcı adı normalizasyonu: `tr-TR` küçük harf dönüşümü "ADMIN"i "admın" yapıyordu; artık İ/I/ı → i ve ASCII küçük harf. + +### Kaldırıldı + +- Oturum ve admin kullanıcı listesi mock verileri (hane mock'u Faz 5'e kadar duruyor). + ## [0.2.0] - 2026-11-05 Faz 2: Frontend iskeleti (veriler Faz 3'e kadar mock). @@ -50,6 +81,7 @@ Faz 1: Framework, klasör yapısı ve altyapı. - `.nvmrc` (Node 26), `engines.node`, `typecheck`, `test`, `format`, `db:up`, `seed` scriptleri. - `docs/PROMPT.md` (ana prompt ve Faz 0 kararları), `README.md`, `CHANGELOG.md`. -[Yayınlanmamış]: https://gitea.ta1sph.info/suphicakir/butcem/compare/v0.2.0...HEAD +[Yayınlanmamış]: https://gitea.ta1sph.info/suphicakir/butcem/compare/v0.3.0...HEAD +[0.3.0]: https://gitea.ta1sph.info/suphicakir/butcem/compare/v0.2.0...v0.3.0 [0.2.0]: https://gitea.ta1sph.info/suphicakir/butcem/compare/v0.1.0...v0.2.0 [0.1.0]: https://gitea.ta1sph.info/suphicakir/butcem/releases/tag/v0.1.0 diff --git a/README.md b/README.md index af77d27..7f8b5dd 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,15 @@ cp .env.example .env.local # 3) MongoDB (tek düğümlü replica set rs0, host portu 27019) — sağlıklı olana kadar bekler npm run db:up -# 4) Uygulama -npm run dev # http://localhost:3000 +# 4) İlk admin (SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD; kullanıcı adı: admin) +npm run seed + +# 5) Uygulama +npm run dev # http://localhost:3000 → /login ``` +Giriş kullanıcı adı **veya** e-posta ile yapılır. Kayıt kapalıdır; kullanıcıları admin `/admin/kullanicilar` sayfasından oluşturur. + ## Scriptler | Komut | Açıklama | @@ -45,7 +50,7 @@ npm run dev # http://localhost:3000 ## Marka dosyaları -`public/brand/` altındaki `logo.svg`, `logo-dark.svg` ve `icon.png` (512×512) şu an **yer tutucudur**. Gerçek logoları aynı adlarla koyup `npm run icons` çalıştırın. +`public/brand/`: `logo.svg` (açık tema), `logo-dark.svg` (koyu tema), `icon.svg` (uygulama ikonu), `icon-maskable.svg` (Android/iOS, köşesiz). Yazı Noto Sans Bold (OFL) harf yollarıdır. İkonları değiştirdikten sonra `npm run icons` çalıştırın; favicon ve PWA ikonları bu kaynaklardan üretilir. ## Docker imajı