From f1cf65e7a773430f2db25c90a79fd80a4bf20364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A2=81=E6=B3=BD=E5=86=9B?= <5654792+tcubic21@user.noreply.gitee.com> Date: Mon, 16 Jun 2025 18:12:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 19 ++ README.md | 52 ++++ client-temp/.gitignore | 19 ++ client-temp/README.md | 54 +++++ client-temp/eslint.config.js | 28 +++ client-temp/index.html | 13 + client-temp/package.json | 37 +++ client-temp/postcss.config.js | 6 + client-temp/public/vite.svg | 1 + client-temp/src/App.tsx | 123 ++++++++++ client-temp/src/api/auth.ts | 13 + client-temp/src/api/axios.ts | 15 ++ client-temp/src/api/todo.ts | 21 ++ client-temp/src/assets/react.svg | 1 + client-temp/src/index.css | 1 + client-temp/src/main.tsx | 10 + client-temp/src/pages/Login.tsx | 64 +++++ client-temp/src/pages/Register.tsx | 58 +++++ client-temp/src/types/todo.ts | 20 ++ client-temp/src/vite-env.d.ts | 1 + client-temp/tailwind.config.js | 12 + client-temp/tsconfig.app.json | 27 +++ client-temp/tsconfig.json | 7 + client-temp/tsconfig.node.json | 25 ++ client-temp/vite.config.ts | 22 ++ client/.gitignore | 23 ++ client/README.md | 46 ++++ client/package.json | 56 +++++ client/postcss.config.js | 6 + client/public/favicon.ico | Bin 0 -> 3870 bytes client/public/index.html | 43 ++++ client/public/logo192.png | Bin 0 -> 5347 bytes client/public/logo512.png | Bin 0 -> 9664 bytes client/public/manifest.json | 25 ++ client/public/robots.txt | 3 + client/src/App.css | 38 +++ client/src/App.tsx | 287 +++++++++++++++++++++++ client/src/api/auth.ts | 13 + client/src/api/todo.ts | 37 +++ client/src/index.css | 3 + client/src/index.tsx | 13 + client/src/pages/Login.tsx | 64 +++++ client/src/pages/Register.tsx | 58 +++++ client/src/react-app-env.d.ts | 1 + client/src/types/todo.ts | 23 ++ client/tailwind.config.js | 9 + client/tsconfig.json | 26 ++ server/package.json | 42 ++++ server/src/app.ts | 36 +++ server/src/config/db.ts | 16 ++ server/src/controllers/authController.ts | 42 ++++ server/src/controllers/todoController.ts | 83 +++++++ server/src/models/Todo.ts | 44 ++++ server/src/models/User.ts | 13 + server/src/routes/authRoutes.ts | 9 + server/src/routes/todoRoutes.ts | 17 ++ server/tsconfig.json | 14 ++ 57 files changed, 1739 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 client-temp/.gitignore create mode 100644 client-temp/README.md create mode 100644 client-temp/eslint.config.js create mode 100644 client-temp/index.html create mode 100644 client-temp/package.json create mode 100644 client-temp/postcss.config.js create mode 100644 client-temp/public/vite.svg create mode 100644 client-temp/src/App.tsx create mode 100644 client-temp/src/api/auth.ts create mode 100644 client-temp/src/api/axios.ts create mode 100644 client-temp/src/api/todo.ts create mode 100644 client-temp/src/assets/react.svg create mode 100644 client-temp/src/index.css create mode 100644 client-temp/src/main.tsx create mode 100644 client-temp/src/pages/Login.tsx create mode 100644 client-temp/src/pages/Register.tsx create mode 100644 client-temp/src/types/todo.ts create mode 100644 client-temp/src/vite-env.d.ts create mode 100644 client-temp/tailwind.config.js create mode 100644 client-temp/tsconfig.app.json create mode 100644 client-temp/tsconfig.json create mode 100644 client-temp/tsconfig.node.json create mode 100644 client-temp/vite.config.ts create mode 100644 client/.gitignore create mode 100644 client/README.md create mode 100644 client/package.json create mode 100644 client/postcss.config.js create mode 100644 client/public/favicon.ico create mode 100644 client/public/index.html create mode 100644 client/public/logo192.png create mode 100644 client/public/logo512.png create mode 100644 client/public/manifest.json create mode 100644 client/public/robots.txt create mode 100644 client/src/App.css create mode 100644 client/src/App.tsx create mode 100644 client/src/api/auth.ts create mode 100644 client/src/api/todo.ts create mode 100644 client/src/index.css create mode 100644 client/src/index.tsx create mode 100644 client/src/pages/Login.tsx create mode 100644 client/src/pages/Register.tsx create mode 100644 client/src/react-app-env.d.ts create mode 100644 client/src/types/todo.ts create mode 100644 client/tailwind.config.js create mode 100644 client/tsconfig.json create mode 100644 server/package.json create mode 100644 server/src/app.ts create mode 100644 server/src/config/db.ts create mode 100644 server/src/controllers/authController.ts create mode 100644 server/src/controllers/todoController.ts create mode 100644 server/src/models/Todo.ts create mode 100644 server/src/models/User.ts create mode 100644 server/src/routes/authRoutes.ts create mode 100644 server/src/routes/todoRoutes.ts create mode 100644 server/tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa39f5a --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# dependencies +node_modules/ + +# production +/dist + +# misc +.DS_Store +.env* + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# editor +.vscode/ +.idea/ +*.swp diff --git a/README.md b/README.md new file mode 100644 index 0000000..a930d44 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# TodoList 全栈项目 + +这是一个使用现代技术栈构建的 TodoList 全栈应用。 + +## 技术栈 + +- 前端:React + TypeScript +- 后端:Node.js + Express + TypeScript +- 数据库:MongoDB +- 部署:Docker + Docker Compose + +## 项目结构 + +``` +full-stack/ +├── client/ # 前端代码 +├── server/ # 后端代码 +├── docker/ # Docker配置文件 +└── README.md # 项目说明文档 +``` + +## 开发环境要求 + +- Node.js >= 16 +- Docker & Docker Compose +- MongoDB + +## 如何运行 + +1. 克隆项目 +2. 安装依赖 + ```bash + # 安装前端依赖 + cd client + npm install + + # 安装后端依赖 + cd ../server + npm install + ``` +3. 启动开发环境 + ```bash + # 使用 Docker Compose 启动所有服务 + docker-compose up + ``` + +## 功能特性 + +- 创建、读取、更新、删除待办事项 +- 标记待办事项为已完成 +- 按状态筛选待办事项 +- 响应式设计,支持移动端 \ No newline at end of file diff --git a/client-temp/.gitignore b/client-temp/.gitignore new file mode 100644 index 0000000..fa39f5a --- /dev/null +++ b/client-temp/.gitignore @@ -0,0 +1,19 @@ +# dependencies +node_modules/ + +# production +/dist + +# misc +.DS_Store +.env* + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# editor +.vscode/ +.idea/ +*.swp diff --git a/client-temp/README.md b/client-temp/README.md new file mode 100644 index 0000000..da98444 --- /dev/null +++ b/client-temp/README.md @@ -0,0 +1,54 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default tseslint.config({ + extends: [ + // Remove ...tseslint.configs.recommended and replace with this + ...tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + ...tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + ...tseslint.configs.stylisticTypeChecked, + ], + languageOptions: { + // other options... + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + }, +}) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default tseslint.config({ + plugins: { + // Add the react-x and react-dom plugins + 'react-x': reactX, + 'react-dom': reactDom, + }, + rules: { + // other rules... + // Enable its recommended typescript rules + ...reactX.configs['recommended-typescript'].rules, + ...reactDom.configs.recommended.rules, + }, +}) +``` diff --git a/client-temp/eslint.config.js b/client-temp/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/client-temp/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/client-temp/index.html b/client-temp/index.html new file mode 100644 index 0000000..e4b78ea --- /dev/null +++ b/client-temp/index.html @@ -0,0 +1,13 @@ + + +
+ + + +&*j;=u%25ay-%>3@81tGe^_z*C7pb9y*Ed^H3t$BIKH2o+olp#$q;)_ zfpjCb_^VFg5fU~K)nf*d*r@BCC>UZ!0&b?AGk_jTPXaSnCuW110wjHPPe^9R^;jo3 zwvzTl)C`Zl5}O2}3lec=hZ*$JnkW#7enKKc)(pM${_$9Hc=Sr_A9Biwe*Y=T?~1CK z6eZ9uPICjy-sMGbZl$yQmpB&`ouS8v{58__t0$JP%i3R&%QR3ianbZqDs<2#5FdN@n5bCn^ZtH992~5k(eA|8|@G9u`wdn7bnpg|@{m z^d6Y`*$Zf2Xr&|g%sai#5}Syvv(>Jnx&EM7-|Jr7!M~zdAyjt*xl;OLhvW-a%H1m0 z*x5*nb=R5u><7lyVpN AR?q@1U59 zO+)QW wL8t zyip?u_nI+K$uh{ y)~}qj?(w0&=SE^8`_WMM zTybjG=999h38Yes7}-4*LJ7H)UE8{mE(6;8voE+TYY%33A>S6`G_95^5QHNTo_;Ao ztIQIZ_}49%{8|=O;isBZ?=7kfdF8_@azfoTd+hEJKWE!)$)N%HIe2cplaK`ry#=pV z0q{9w-`i0h@!R8K3GC{ivt{70IWG`EP |(1g7i_Q<>aEAT{5( yD z=!O?kq61VegV+st@XCw475j6vS)_z@efuqQgHQR1T4;|-#OLZNQJPV4k$AX1Uk8Lm z{N*b*ia=I+MB}kWpupJ~>!C@xEN#Wa7V+7{m4j8c?)ChV=D?o~sjT?0C_AQ 7B-vxqX30s0I_`2$in86#`mAsT-w?j{&AL@B3$;P z31G4(lV|b}uSD CIrjk+M1R!X7s 4Aabn<)zpgT}#gE|mIvV38^ODy@<&yflpCwS#fRf9ZX3lPV_?8@C5)A;T zqmouFLFk;qIs4rA=hh=GL~sCFsXHsqO6_y~*AFt93 9UYVBSx1s(=Kb&5;j7cSowdE;7()CC2|-i9Zz+_BIw8#ll~-tyH?F3{%`QCsYa*b#s*9iCc`1P1oC26?`g<9))EJ3%xz+O!B3 zZ7$j~To)C@PquR>a1+Dh>-a%IvH_Y7^ys|4o?E%3`I&ADXfC8++hAdZfzIT#%C+Jz z1lU~K_vAm0m8Qk}K$F>| >RPK%<1SI0(G+8q~H zAsjezyP+u!Se4q3GW)`h`NPSRlMoBjCzNPesWJwVTY!o@G8=(6I%4XHGaSiS3MEBK zhgGFv6Jc>L$4jVE!I?TQuwvz_%CyO!bLh94nqK11C2W$*aa2ueGopG8DnBICVUORP zgytv#)49fVXDaR$SukloYC3u7#5H)}1K21=?DKj^U)8G;MS)&Op)g^zR2($<>C*zW z;X7`hLxiIO#J`ANdyAOJle4V%ppa*(+0i3w;8i*BA_;u8gOO6)MY`ueq7stBMJTB; z-a0R>hT*}>z|Gg}@^zDL1MrH+2hsR8 zHc}*9IvuQC^Ju)^#Y{fOr(96rQNPNhxc;mH@W*m206>Lo<*SaaH?~8zg&f&%YiOEG zGiz?*CP>Bci}!WiS=zj#K5I}>DtpregpP_tfZtPa(N<%vo^#WCQ5BTv0vr%Z{)0q+ z)RbfHktUm|lg&U3YM%lMUM(f u}i#kjX9h>GYctkx9Mt_8{@s%!K_EI zScgwy6%_fR?CG JQtmgNAj^h9B#zma MDWgH55pGuY1Gv7D z;8Psm(vEPiwn#MgJYu4Ty9D|h!?Rj0ddE|&L3S{IP%H4^N!m`60ZwZw^;eg4sk6K{ ziA^`Sbl_4~f&Oo%n;8Ye(tiAdlZKI!Z=|j$5hS|D$bDJ}p{gh$KN&JZYLUjv4h{NY zBJ>X9z !xfDGY z+oh_Z&_e#Q(-}>ssZfm=j$D&4W4FNy&-kAO1~#3Im;F)Nwe{(*75(p=P^VI?X 0GFakfh+X-px4a%Uw@fSbmp9hM1_~R>?Z8+ ziy|e9>8V*`OP}4x5JjdWp}7eX;lVxp5qS} 0YZek;SNmm7tEeSF*-dI)6U-A%m6YvCgM(}_=k#a6o^%-K4{`B1+}O4x zztDT%hVb;v#?j`lTvlFQ3aV#zkX=7 ;YFLS$uIzb0E3lozs5`Xy zi~vF+%{z9uLjKvKPhP%x5f ~7-Gj+%5N`%^=yk*Qn{`> z;xj&ROY6g`iy2a@{O)V(jk&8#hHACVDXey5a+KDod_Z&}kHM}xt7}Md@pil{2x7E~ zL$k^d2@Ec2XskjrN+IILw;#7((abu;OJii&v3?60x>d_Ma(onIPtcVnX@ELF0aL?T zSmWiL3(dOFkt!x=1O!_0n(cAzZW+3nHJ{2S>tgSK?~cF ha^y(l@-Mr2W$%MN{#af8J;V*>hdq!gx=d0h$T7l}>91Wh07)9CTX zh2_ZdQCyFOQ)l(}gft0UZ G`Sh2`x-w`5vC2UD}lZs*5 zG76$akzn}Xi))L3oGJ75#pcN=cX3!=57$Ha=hQ2^lwdyU#a}4JJOz6ddR%zae%#4& za)bFj)z=YQela(F#Y|Q#dp}PJghITwXouVaMq$BM?K%cXn9^Y@g43$=O)F&ZlOUom zJiad#dea;-eywBA@e&D6Pdso1?2^(pXiN91?jvcaUyYoKUmvl5G9e$W!okWe*@a<^ z8cQQ6cNSf+UPDx%?_G4a IiybZHHagF{ ;IcD(dPO!#=u zWfqLcPc^+7Uu#l(B pxft{*4lv#*u7X9AOzDO z1D9?^jIo}?%iz(_dwLa{ex#T}76ZfN_Z-hwpus9y+4xaUu9cX}&P{XrZVWE{1^0yw zO;YhLEW!pJcbCt3L8~a7>jsaN{V3>tz6_7`&pi%GxZ=V3?3K^ U+*ryLSb)8^IblJ0 zSRLNDvIxt)S}g30?s_3NX>F?NKIGrG_zB9@Z>uSW3k2es_H2kU;Rnn%j5qP)!XHKE zPB2mHP~tLCg4K_vH$xv`HbRsJwbZMUV(t=ez;Ec(vyHH)FbfLg`c61I$W_uBB>i^r z&{_P;369-&>23R%qNIULe=1~T$(DA`ev*EWZ6j(B$(te}x1WvmIll21zvygkS%vwG zzkR6Z#RKA2!z!C%M!O>!=Gr0(J0FP=-MN=5t-Ir)of50y10W}j`GtRCsXBakrKtG& zazmITDJMA0C51&BnLY)SY9r)NVTMs);1<=oosS9g31l{4ztjD3#+2H7u_|66b|_*O z;Qk6nalpqdHOjx|K&vUS_6ITgGll;TdaN*ta=M_YtyC)I9Tmr~VaPrH2q b6sd~=AcIxV+%z{E&0@y=DPArw zdV7z(G1hBx7hd{>(cr43^WF%4Y@PXZ?wPpj{OQ#tvc$pABJbvPGvdR`cAtHn)cSEV zrpu}1tJwQ3y!mSmH*uz*x0o|CS<^w%&KJzsj~DU0cLQUxk5B!hWE>aBkjJle8z~;s z-!A=($+}Jq_BTK5^B!`R>!MulZN)F=iXXeUd0w5lUsE5VP*H*oCy( ;?S$p*TVvTxwAeWFB$jHyb0593)$zqalVlDX=GcCN1gU0 zlgU)I$LcXZ8Oyc2TZYTPu@-;7<4YYB-``Qa;IDcvydIA$%kHhJKV^m*- zxcvU4viy &Kr5GVM{IT>WRywKQ9;>SEiQD*NqplK-KK4YR`p0@JW)n_{TU3bt0 zim%;(m1=#v2}zTps=?fU5w^(*y)xT%1vtQH&}50ZF!9YxW=&7*W($2kgKyz1mUgfs zfV<*XVVIFnohW=|j+@Kfo!#liQR^x>2yQdrG;2o8WZR+XzU_nG=Ed2rK?ntA;K5B{ z>M8+*A4 !Jm^Bg}aW?R?6;@QG@uQ8&oJ{hFixcfEnJ4QH?A4>P=q29oDGW;L;= z9-a0;g%c`C+Ai!UmK$NC*4#;Jp<1=TioL=t^YM)<<%u#hnnfSS`nq63QKGO1L8RzX z@MFDq s1z ztYmxDl@LU)5acvHk)~Z`RW7=aJ_nGD!mOSYD>5Odjn@TK#LY{jf?+piB5AM-CAoT_ z?S-*q7}wyLJzK>N%eMPuFgN)Q_otKP;aqy=D5f! 7<=n(lNkYRXVpkB{TAYLYg{|(jtRqYmg$xH zjmq ?B(RE4 zQx^~Pt}gxC2~l=K$$-sYy_r$CO(d=+b3H1MB*y_5g6WLaWTXn+TKQ|hNY^>Mp6k*$ zwkovomhu776vQATqT4blf~g;TY(MWCrf^^yfWJvSAB$p5l;jm@o#=!lqw+Lqfq>X= z$6~kxfm7`3q4zUEB;u4qa#BdJxO!;xGm)wwuisj{0y2x{R(IGMrsIzDY9LW>m!Y`= z04sx3IjnYvL<4JqxQ8f7qYd0s2Ig%`ytYPEMKI)s(LD}D@EY>x`VFtqvnADNBdeao zC96X+MxnwKmjpg{U&gP3HE}1=s!lv&D{6(g_lzyF3A`7Jn*&d_kL<;dAFx!UZ>hB8 z5A*%LsAn;VLp>3${0>M?PSQ)9s3}|h2e?TG4_F{}{Cs>#3Q*t$(CUc}M)I}8cPF6% z=+h(Kh^8)}gj(0}#e7O^FQ6`~fd1#8#!}LMuo3A0bN`o}PYsm!Y}sdOz$+Tegc=qT z8x`PH$7lvnhJp{kHWb22l;@7B7|4yL4UOOVM0MP_>P%S1Lnid)+k9{+3D+JFa#Pyf zhVc#&df87APl4W9X)F3pGS>@etfl=_E5tBcVoOfrD4hmVeTY-cj((pkn%n@EgN{0f zwb_^Rk0I#i ZuHK!l*lN`ceJn(sI{$Fq6nN& zE<-=0_2WN}m+*ivmIOxB@#~Q-cZ>l136w{#TIJe478`KE7@=a{>SzPHsKLzYAyBQO zAtuuF$-JSDy_S@6GW0MOE~R)b;+0f%_NMrW(+V#c_d&U8Z9+ec4=HmOHw?gdjF(Lu zzra 83M_BoO-1b3;9`%&DHfuUY)6YDV21P$C!Rc?mv&{lx#f8oc6?0?x zK08{WP65?#>(vPfA-c=MCY| %*1_<3D4NX zeVTi-JGl2uP_2@0F{G({pxQOXt_d{g_CV6b?jNpfUG9;8yle-^4KHRvZs-_2siata zt+d_T@U$&t*xaD22(fH(W1r$Mo?3dc%Tncm=C6{V9y{v&VT#^1L04vDrLM9qBoZ4@ z6DBN#m57hX7$C(=#$Y5$bJmwA$T8jKD8+6A!-IJwA{WOfs%s}yxUw^?MRZjF$n_KN z6`_bGXcmE#5e4Ym)aQJ)xg3Pg0@k`iGuHe?f(5LtuzSq=nS^5z>vqU0EuZ&75V%Z{ zYyhRLN^)$c6Ds{f7*FBpE;n5iglx5PkHfWrj3`x^j^t z7ntuV`g!9Xg#^3!x)l*}IW=(Tz3>Y5l4uGaB&lz{GDjm2D5S$CExLT`I1#n^lBH7Y zDgpMag@`iETKAI=p<5E#LTkw zVR@=yY|uBVI1HG|8h+d;G-qfuj}-ZR6fN>EfCCW z9~wRQoAPEa#aO?3h?x{YvV*d+NtPkf&4V0k4|L=uj!U{L+oLa(z#&iuhJr3-PjO3R z5s?=nn_5^*^Rawr>>Nr@K(jwkB#JK-=+HqwfdO<+P5byeim)wvqGlP-P|~Nse8=XF zz`?RYB|D6SwS}C+YQv+;}k6$-%D(@+t14BL@vM z2q%q?f6D-A5s$_WY3{^G0F131bbh|g!}#BKw=HQ7mx;Dzg4Z*bTLQSfo{ed{4}NZW zfrRm^Ca$rlE{Ue~uYv>R9{3s mwATcdM_6+yWIO z*ZRH~uXE@#p$XTbCt5j7j2=86e{9>HIB6xDzV+vAo&B?KUiMP|ttOElepnl%|DPqL b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN literal 0 HcmV?d00001 diff --git a/client/public/manifest.json b/client/public/manifest.json new file mode 100644 index 0000000..080d6c7 --- /dev/null +++ b/client/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "Create React App Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/client/public/robots.txt b/client/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/client/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/client/src/App.css b/client/src/App.css new file mode 100644 index 0000000..74b5e05 --- /dev/null +++ b/client/src/App.css @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} diff --git a/client/src/App.tsx b/client/src/App.tsx new file mode 100644 index 0000000..c7e5a3b --- /dev/null +++ b/client/src/App.tsx @@ -0,0 +1,287 @@ +import React, { useEffect, useState } from 'react'; +import { BrowserRouter as Router, Routes, Route, Navigate, useNavigate } from 'react-router-dom'; +import { Todo, Priority, priorityColors, priorityLabels } from './types/todo'; +import * as todoApi from './api/todo'; +import Login from './pages/Login'; +import Register from './pages/Register'; + +// 弹窗组件 +const Modal: React.FC<{ open: boolean; onClose: () => void; onConfirm?: () => void; title: string; content: string; confirmText?: string; cancelText?: string; }> = ({ open, onClose, onConfirm, title, content, confirmText = '确定', cancelText = '取消' }) => { + if (!open) return null; + return ( + ++ ); +}; + +// 随机头像颜色生成 +function stringToColor(str: string) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + const color = `hsl(${hash % 360}, 70%, 60%)`; + return color; +} + +// 状态栏组件 +const StatusBar: React.FC<{ username: string; avatarColor: string; avatarText: string; onLogout: () => void }> = ({ username, avatarColor, avatarText, onLogout }) => ( +++{title}
+{content}++ + {onConfirm && } ++++); + +const TodoApp: React.FC = () => { + const [todos, setTodos] = useState+ + {avatarText} + + {username} + ++([]); + const [title, setTitle] = useState(''); + const [loading, setLoading] = useState(false); + const [editingId, setEditingId] = useState (null); + const [editTitle, setEditTitle] = useState(''); + const [modalOpen, setModalOpen] = useState(false); + const [modalAction, setModalAction] = useState void)>(null); + const [modalContent, setModalContent] = useState(''); + const [modalTitle, setModalTitle] = useState(''); + const [editModalOpen, setEditModalOpen] = useState(false); + const [editTodoId, setEditTodoId] = useState (null); + const [newTodoTitle, setNewTodoTitle] = useState(''); + const [newTodoPriority, setNewTodoPriority] = useState ('medium'); + const [editingTodo, setEditingTodo] = useState (null); + const [isLogoutModalOpen, setIsLogoutModalOpen] = useState(false); + const navigate = useNavigate(); + + // 获取所有 Todo + const fetchTodos = async () => { + setLoading(true); + try { + const data = await todoApi.getTodos(); + setTodos(data); + } catch (error) { + console.error('Failed to fetch todos:', error); + if ((error as any)?.response?.status === 401) { + navigate('/login'); + } + } + setLoading(false); + }; + + useEffect(() => { + fetchTodos(); + }, []); + + // 新增 Todo + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!newTodoTitle.trim()) return; + + try { + const todo = await todoApi.createTodo(newTodoTitle.trim(), newTodoPriority); + setTodos([...todos, todo]); + setNewTodoTitle(''); + setNewTodoPriority('medium'); + } catch (error) { + console.error('Failed to create todo:', error); + } + }; + + // 删除 Todo + const handleDelete = async (id: string) => { + if (!window.confirm('确定要删除吗?')) return; + try { + await todoApi.deleteTodo(id); + setTodos(todos.filter(todo => todo._id !== id)); + } catch { + alert('删除失败'); + } + }; + + // 标记完成/未完成 + const handleToggle = async (todo: Todo) => { + try { + const updatedTodo = await todoApi.updateTodo(todo._id!, { completed: !todo.completed }); + setTodos(todos.map(t => t._id === todo._id ? updatedTodo : t)); + } catch { + alert('更新失败'); + } + }; + + // 开始编辑 + const handleEdit = (todo: Todo) => { + setEditingId(todo._id!); + setEditTitle(todo.title); + setEditTodoId(todo._id!); + setEditingTodo(todo); + setEditModalOpen(true); + }; + + // 保存编辑 + const handleEditSave = async (id: string) => { + try { + const updatedTodo = await todoApi.updateTodo(id, { + title: editingTodo?.title || '', + priority: editingTodo?.priority || 'medium' + }); + setTodos(todos.map(t => t._id === id ? updatedTodo : t)); + setEditingTodo(null); + setEditModalOpen(false); + } catch (error) { + console.error('Failed to update todo:', error); + } + }; + + // 取消编辑 + const handleEditCancel = () => { + setEditingId(null); + setEditModalOpen(false); + setEditTodoId(null); + }; + + // 退出登录 + const handleLogout = () => { + setModalTitle('退出登录'); + setModalContent('确定要退出登录吗?'); + setModalAction(() => () => { + localStorage.removeItem('token'); + localStorage.removeItem('username'); + window.dispatchEvent(new Event('tokenChange')); + navigate('/login'); + }); + setModalOpen(true); + }; + + const username = localStorage.getItem('username') || ''; + const avatarColor = stringToColor(username); + const avatarText = username ? username[0].toUpperCase() : '?'; + + return ( + + {/* 状态栏 */} ++ ); +}; + +const App: React.FC = () => { + const [isLogin, setIsLogin] = useState(!!localStorage.getItem('token')); + + useEffect(() => { + const onStorage = () => setIsLogin(!!localStorage.getItem('token')); + window.addEventListener('storage', onStorage); + // 兼容同窗口内的 token 变化 + const onTokenChange = () => setIsLogin(!!localStorage.getItem('token')); + window.addEventListener('tokenChange', onTokenChange); + return () => { + window.removeEventListener('storage', onStorage); + window.removeEventListener('tokenChange', onTokenChange); + }; + }, []); + + return ( ++ setModalOpen(false)} + onConfirm={modalAction ? () => { setModalOpen(false); modalAction(); } : undefined} + title={modalTitle} + content={modalContent} + /> + {/* 编辑弹窗 */} + {editModalOpen && ( + ++ )} + {/* 主内容整体下移,宽度自适应 */} +++编辑待办事项
++ setEditTitle(e.target.value)} + className="border rounded px-2 sm:px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-400 text-sm sm:text-base" + placeholder="标题" + /> +++ + ++++Todo List
+ + {loading ? ( +加载中...+ ) : ( ++ {todos.map(todo => ( +
+ )} +- +
+ ))} ++ handleToggle(todo)} + className="accent-blue-500 w-5 h-5" + /> + {todo.title} +++ + +++ + ); +}; + +export default App; \ No newline at end of file diff --git a/client/src/api/auth.ts b/client/src/api/auth.ts new file mode 100644 index 0000000..7586d12 --- /dev/null +++ b/client/src/api/auth.ts @@ -0,0 +1,13 @@ +import axios from 'axios'; + +const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5050/api/auth'; + +export const register = async (username: string, password: string) => { + const res = await axios.post(`${API_URL}/register`, { username, password }); + return res.data; +}; + +export const login = async (username: string, password: string) => { + const res = await axios.post(`${API_URL}/login`, { username, password }); + return res.data; +}; \ No newline at end of file diff --git a/client/src/api/todo.ts b/client/src/api/todo.ts new file mode 100644 index 0000000..dabbfdf --- /dev/null +++ b/client/src/api/todo.ts @@ -0,0 +1,37 @@ +import axios from 'axios'; +import { Todo, Priority } from '../types/todo'; + +const API_URL = 'http://localhost:5050/api'; + +// 获取 token 辅助函数 +function getAuthHeader() { + const token = localStorage.getItem('token'); + return token ? { Authorization: `Bearer ${token}` } : {}; +} + +export const getTodos = async (): Promise+ +} /> + } /> + : } /> + => { + const res = await axios.get (`${API_URL}/todos`, { headers: getAuthHeader() }); + return res.data; +}; + +export const createTodo = async (title: string, priority: Priority): Promise => { + const res = await axios.post( + `${API_URL}/todos`, + { title, priority }, + { headers: getAuthHeader() } + ); + return res.data; +}; + +export const updateTodo = async (id: string, updates: Partial ): Promise => { + const res = await axios.put( + `${API_URL}/todos/${id}`, + updates, + { headers: getAuthHeader() } + ); + return res.data; +}; + +export const deleteTodo = async (id: string): Promise => { + await axios.delete(`${API_URL}/todos/${id}`, { headers: getAuthHeader() }); +}; \ No newline at end of file diff --git a/client/src/index.css b/client/src/index.css new file mode 100644 index 0000000..bd6213e --- /dev/null +++ b/client/src/index.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/client/src/index.tsx b/client/src/index.tsx new file mode 100644 index 0000000..1fd12b7 --- /dev/null +++ b/client/src/index.tsx @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import './index.css'; +import App from './App'; + +const root = ReactDOM.createRoot( + document.getElementById('root') as HTMLElement +); +root.render( + + +); diff --git a/client/src/pages/Login.tsx b/client/src/pages/Login.tsx new file mode 100644 index 0000000..8613791 --- /dev/null +++ b/client/src/pages/Login.tsx @@ -0,0 +1,64 @@ +import React, { useState, useEffect } from 'react'; +import { login } from '../api/auth'; +import { useNavigate, Link } from 'react-router-dom'; + +const Login: React.FC = () => { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const navigate = useNavigate(); + const [isLogin, setIsLogin] = useState(!!localStorage.getItem('token')); + + useEffect(() => { + const onStorage = () => setIsLogin(!!localStorage.getItem('token')); + window.addEventListener('storage', onStorage); + return () => window.removeEventListener('storage', onStorage); + }, []); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + try { + const res = await login(username, password); + localStorage.setItem('token', res.token); + localStorage.setItem('username', res.username); + window.dispatchEvent(new Event('tokenChange')); + navigate('/'); + } catch (err: any) { + setError(err.response?.data?.message || '登录失败'); + } + }; + + return ( ++ ++ ); +}; + +export default Login; \ No newline at end of file diff --git a/client/src/pages/Register.tsx b/client/src/pages/Register.tsx new file mode 100644 index 0000000..f75a28f --- /dev/null +++ b/client/src/pages/Register.tsx @@ -0,0 +1,58 @@ +import React, { useState } from 'react'; +import { register } from '../api/auth'; +import { useNavigate, Link } from 'react-router-dom'; + +const Register: React.FC = () => { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(''); + const navigate = useNavigate(); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(''); + setSuccess(''); + try { + await register(username, password); + setSuccess('注册成功,请登录'); + setTimeout(() => navigate('/login'), 1000); + } catch (err: any) { + setError(err.response?.data?.message || '注册失败'); + } + }; + + return ( +++登录
+ ++ 没有账号?注册 ++++ ); +}; + +export default Register; \ No newline at end of file diff --git a/client/src/react-app-env.d.ts b/client/src/react-app-env.d.ts new file mode 100644 index 0000000..6431bc5 --- /dev/null +++ b/client/src/react-app-env.d.ts @@ -0,0 +1 @@ +///++注册
+ ++ 已有账号?登录 ++diff --git a/client/src/types/todo.ts b/client/src/types/todo.ts new file mode 100644 index 0000000..081360a --- /dev/null +++ b/client/src/types/todo.ts @@ -0,0 +1,23 @@ +export type Priority = 'low' | 'medium' | 'high'; + +export interface Todo { + _id: string; + title: string; + completed: boolean; + priority: Priority; + userId: string; + createdAt: string; + updatedAt: string; +} + +export const priorityColors = { + low: 'bg-blue-100 text-blue-800', + medium: 'bg-yellow-100 text-yellow-800', + high: 'bg-red-100 text-red-800' +} as const; + +export const priorityLabels = { + low: '低', + medium: '中', + high: '高' +} as const; \ No newline at end of file diff --git a/client/tailwind.config.js b/client/tailwind.config.js new file mode 100644 index 0000000..115da8e --- /dev/null +++ b/client/tailwind.config.js @@ -0,0 +1,9 @@ +module.exports = { + content: [ + "./src/**/*.{js,jsx,ts,tsx}", + ], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..a273b0c --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": [ + "src" + ] +} diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..fa37cee --- /dev/null +++ b/server/package.json @@ -0,0 +1,42 @@ +{ + "name": "server", + "version": "1.0.0", + "description": "TodoList 后端服务", + "main": "dist/app.js", + "scripts": { + "start": "node dist/app.js", + "dev": "ts-node-dev --respawn --transpile-only src/app.ts", + "build": "tsc", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@types/bcryptjs": "^3.0.0", + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/jsonwebtoken": "^9.0.9", + "@types/mongoose": "^5.11.97", + "@types/node": "^24.0.1", + "bcryptjs": "^3.0.2", + "cors": "^2.8.5", + "dotenv": "^16.5.0", + "express": "^5.1.0", + "jsonwebtoken": "^9.0.2", + "mongoose": "^8.15.1", + "ts-node": "^10.9.2", + "typescript": "^5.8.3" + }, + "devDependencies": { + "@types/cors": "^2.8.19", + "@types/express": "^5.0.3", + "@types/node": "^24.0.1", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.5", + "tailwindcss": "^4.1.10", + "ts-node": "^10.9.2", + "ts-node-dev": "^2.0.0", + "typescript": "^5.8.3" + } +} diff --git a/server/src/app.ts b/server/src/app.ts new file mode 100644 index 0000000..3eb0280 --- /dev/null +++ b/server/src/app.ts @@ -0,0 +1,36 @@ +import express from 'express'; +import cors from 'cors'; +import dotenv from 'dotenv'; +import connectDB from './config/db'; +import todoRoutes from './routes/todoRoutes'; +import authRoutes from './routes/authRoutes'; + +dotenv.config(); + +const app = express(); + +// 连接数据库 +connectDB(); + +// 中间件 +app.use(cors({ + origin: 'http://localhost:5173', + credentials: true +})); +app.use(express.json()); + +// 路由 +app.use('/api/todos', todoRoutes); +app.use('/api/auth', authRoutes); + +// 错误处理中间件 +app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => { + console.error(err.stack); + res.status(500).json({ message: '服务器内部错误' }); +}); + +const PORT = process.env.PORT || 5050; + +app.listen(PORT, () => { + console.log(`服务器运行在端口 ${PORT}`); +}); \ No newline at end of file diff --git a/server/src/config/db.ts b/server/src/config/db.ts new file mode 100644 index 0000000..137e95e --- /dev/null +++ b/server/src/config/db.ts @@ -0,0 +1,16 @@ +import mongoose from 'mongoose'; +import dotenv from 'dotenv'; + +dotenv.config(); + +const connectDB = async () => { + try { + const conn = await mongoose.connect(process.env.MONGODB_URI as string); + console.log(`MongoDB 连接成功: ${conn.connection.host}`); + } catch (error) { + console.error('MongoDB 连接失败:', error); + process.exit(1); + } +}; + +export default connectDB; \ No newline at end of file diff --git a/server/src/controllers/authController.ts b/server/src/controllers/authController.ts new file mode 100644 index 0000000..1df9729 --- /dev/null +++ b/server/src/controllers/authController.ts @@ -0,0 +1,42 @@ +import { Request, Response } from 'express'; +import User, { IUser } from '../models/User'; +import bcrypt from 'bcryptjs'; +import jwt from 'jsonwebtoken'; + +const JWT_SECRET = process.env.JWT_SECRET || 'secret_key'; + +// 用户注册 +export const register = async (req: Request, res: Response) => { + const { username, password } = req.body; + try { + const existingUser = await User.findOne({ username }); + if (existingUser) { + return res.status(400).json({ message: '用户名已存在' }); + } + const hashedPassword = await bcrypt.hash(password, 10); + const user = new User({ username, password: hashedPassword }); + await user.save(); + res.status(201).json({ message: '注册成功' }); + } catch (error) { + res.status(500).json({ message: '注册失败', error }); + } +}; + +// 用户登录 +export const login = async (req: Request, res: Response) => { + const { username, password } = req.body; + try { + const user = await User.findOne({ username }); + if (!user) { + return res.status(400).json({ message: '用户不存在' }); + } + const isMatch = await bcrypt.compare(password, user.password); + if (!isMatch) { + return res.status(400).json({ message: '密码错误' }); + } + const token = jwt.sign({ userId: user._id, username: user.username }, JWT_SECRET, { expiresIn: '7d' }); + res.status(200).json({ token, username: user.username }); + } catch (error) { + res.status(500).json({ message: '登录失败', error }); + } +}; \ No newline at end of file diff --git a/server/src/controllers/todoController.ts b/server/src/controllers/todoController.ts new file mode 100644 index 0000000..37edb94 --- /dev/null +++ b/server/src/controllers/todoController.ts @@ -0,0 +1,83 @@ +import { Request, Response, NextFunction } from 'express'; +import Todo, { ITodo } from '../models/Todo'; +import jwt from 'jsonwebtoken'; + +const JWT_SECRET = process.env.JWT_SECRET || 'secret_key'; + +// 鉴权中间件,解析 token 并注入 req.user +export const authMiddleware = (req: any, res: Response, next: NextFunction): void => { + const authHeader = req.headers.authorization; + if (!authHeader || !authHeader.startsWith('Bearer ')) { + res.status(401).json({ message: '未授权' }); + return; + } + const token = authHeader.split(' ')[1]; + try { + const decoded = jwt.verify(token, JWT_SECRET) as any; + req.user = decoded; + next(); + } catch (err) { + res.status(401).json({ message: '无效的 token' }); + } +}; + +// 获取所有待办事项(只返回当前用户的) +export const getAllTodos = async (req: Request & { user?: any }, res: Response) => { + try { + const todos = await Todo.find({ user: req.user.userId }).sort({ createdAt: -1 }); + res.status(200).json(todos); + } catch (error) { + res.status(500).json({ message: '获取待办事项失败', error }); + } +}; + +// 创建新的待办事项(自动绑定 user) +export const createTodo = async (req: Request & { user?: any }, res: Response) => { + try { + const { title, priority = 'medium' } = req.body; + const todo = new Todo({ + title, + priority, + user: req.user.userId + }); + const savedTodo = await todo.save(); + res.status(201).json(savedTodo); + } catch (error) { + res.status(400).json({ message: '创建待办事项失败', error }); + } +}; + +// 更新待办事项 +export const updateTodo = async (req: Request & { user?: any }, res: Response) => { + try { + const todo = await Todo.findOne({ _id: req.params.id, user: req.user.userId }); + if (!todo) { + return res.status(404).json({ message: '待办事项不存在' }); + } + + const updatedTodo = await Todo.findByIdAndUpdate( + req.params.id, + { ...req.body }, + { new: true, runValidators: true } + ); + + res.status(200).json(updatedTodo); + } catch (error) { + res.status(400).json({ message: '更新待办事项失败', error }); + } +}; + +// 删除待办事项 +export const deleteTodo = async (req: Request & { user?: any }, res: Response) => { + try { + const todo = await Todo.findOne({ _id: req.params.id, user: req.user.userId }); + if (!todo) { + return res.status(404).json({ message: '待办事项不存在' }); + } + + await Todo.findByIdAndDelete(req.params.id); + res.status(200).json({ message: '待办事项已删除' }); + } catch (error) { + res.status(400).json({ message: '删除待办事项失败', error }); + } +}; \ No newline at end of file diff --git a/server/src/models/Todo.ts b/server/src/models/Todo.ts new file mode 100644 index 0000000..49df800 --- /dev/null +++ b/server/src/models/Todo.ts @@ -0,0 +1,44 @@ +import mongoose, { Schema, Document } from 'mongoose'; + +export interface ITodo extends Document { + title: string; + description: string; + completed: boolean; + priority: 'low' | 'medium' | 'high'; + createdAt: Date; + updatedAt: Date; + user: mongoose.Types.ObjectId; +} + +const TodoSchema: Schema = new Schema({ + title: { + type: String, + required: [true, '标题是必需的'], + trim: true, + maxlength: [100, '标题不能超过100个字符'] + }, + description: { + type: String, + trim: true, + maxlength: [500, '描述不能超过500个字符'] + }, + completed: { + type: Boolean, + default: false + }, + priority: { + type: String, + enum: ['low', 'medium', 'high'], + default: 'medium', + required: true + }, + user: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: true + } +}, { + timestamps: true +}); + +export default mongoose.model ('Todo', TodoSchema); \ No newline at end of file diff --git a/server/src/models/User.ts b/server/src/models/User.ts new file mode 100644 index 0000000..1e4abb8 --- /dev/null +++ b/server/src/models/User.ts @@ -0,0 +1,13 @@ +import mongoose, { Schema, Document } from 'mongoose'; + +export interface IUser extends Document { + username: string; + password: string; +} + +const UserSchema: Schema = new Schema({ + username: { type: String, required: true, unique: true }, + password: { type: String, required: true } +}); + +export default mongoose.model ('User', UserSchema); \ No newline at end of file diff --git a/server/src/routes/authRoutes.ts b/server/src/routes/authRoutes.ts new file mode 100644 index 0000000..47bcf62 --- /dev/null +++ b/server/src/routes/authRoutes.ts @@ -0,0 +1,9 @@ +import { Router } from 'express'; +import { register, login } from '../controllers/authController'; + +const router = Router(); + +router.post('/register', register); +router.post('/login', login); + +export default router; \ No newline at end of file diff --git a/server/src/routes/todoRoutes.ts b/server/src/routes/todoRoutes.ts new file mode 100644 index 0000000..04aa88d --- /dev/null +++ b/server/src/routes/todoRoutes.ts @@ -0,0 +1,17 @@ +import { Router } from 'express'; +import { + getAllTodos, + createTodo, + updateTodo, + deleteTodo, + authMiddleware +} from '../controllers/todoController'; + +const router = Router(); + +router.get('/', authMiddleware, getAllTodos); +router.post('/', authMiddleware, createTodo); +router.put('/:id', authMiddleware, updateTodo); +router.delete('/:id', authMiddleware, deleteTodo); + +export default router; \ No newline at end of file diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..904601c --- /dev/null +++ b/server/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} \ No newline at end of file