├── README.md ├── package-lock.json ├── package.json ├── src ├── app.ts ├── config │ ├── cors.ts │ └── regex.ts ├── controllers │ ├── address.ts │ ├── cart.ts │ ├── category.ts │ ├── comment.ts │ ├── favourite.ts │ ├── file.ts │ ├── location.ts │ ├── order.ts │ ├── payment.ts │ ├── product.ts │ ├── tag.ts │ └── user.ts ├── db │ ├── connect.ts │ └── models │ │ ├── address.model.ts │ │ ├── cart.model.ts │ │ ├── category.model.ts │ │ ├── comment.model.ts │ │ ├── favourite.model.ts │ │ ├── file.model.ts │ │ ├── meta.model.ts │ │ ├── order.model.ts │ │ ├── product.model.ts │ │ ├── tag.model.ts │ │ └── user.model.ts ├── middlewares │ ├── auth.ts │ ├── error.ts │ ├── role.ts │ └── upload.ts ├── routes │ ├── address.route.ts │ ├── cart.route.ts │ ├── category.route.ts │ ├── comment.route.ts │ ├── favourite.route.ts │ ├── file.route.ts │ ├── location.route.ts │ ├── order.route.ts │ ├── payment.route.ts │ ├── product.route.ts │ ├── tag.route.ts │ └── user.route.ts ├── templates │ └── emails │ │ └── forgot.ts ├── types │ ├── custom.d.ts │ └── types.ts └── utils │ ├── error.ts │ ├── file.ts │ ├── filter.ts │ ├── logger.ts │ ├── nodemailer.ts │ ├── payment.ts │ └── validate.ts └── tsconfig.json /README.md: -------------------------------------------------------------------------------- 1 | # Ecommerce API 2 | برای راه اندازی یک فروشگاه آنلاین با Nodejs میتوانید از اسکریپت Ecommerce API استفاده کنید. اسکریپت Ecommerce API با زبان Typescript نوشته شده است و در آن از زبانها و ابزارهای Node.js – Mongodb – Mongoose – Express.js و … استفاده شده است. 3 | 4 | ## ویژگی‌های Ecommerce API 5 | اسکریپت Ecommerce API دارای اکثر ویژگی‌های یک سایت فروشگاهی کامل می‌باشد که در ادمه به شرح برخی از این ویژگی‌ها می پردازم: 6 | 7 | ### سیستم احراز هویت 8 | در سیستم احراز هویت ایکامرس ای پی آی، از ابزار JsonWebToken برای ایجاد توکن تایید هویت استفاده شده است. این سیستم به صورت Role Based تنظیم شده و دارای چهار سطح دسترسی میباشد: 9 | 1. **ریشه: (Root)** این سطح دسترسی مدیر اصلی برنامه است و کلیه تنظیمات و قابلیت های سایت را میتواند مدیریت کند. 10 | 2. **مدیر: (Admin)** این سطح دسترسی مدیران سایت است که توسط مدیر اصلی (Root) سایت ایجاد شده است و توانایی ایجاد تنظیمات سطح پایین سایت مثل ایجاد حساب کاربری فروشنده (Seller) و … دارا است. 11 | 3. **فروشنده: (Seller)** این سطح دسترسی فروشنده سایت است که توانایی بررسی و مدیریت فروشگاه سایت را دارا است. ایجاد و حذف و ویرایش محصول و بارگذاری فایل و … از جمله توانایی سطح دسترسی فروشنده است. 12 | 4. **مشتری: (Customer)** این سطح دسترسی مشتریان سایت است که میتوانند در سایت ثبت نام کرده و به محصولات سایت را خریداری کنند و یا اطلاعات حساب کاربری خود را بررسی کرده و به سفارشات خود دسترسی داشته باشند. (به صورت پیشفرض کاربرانی که در سایت ثبت نام میکنند در این سطح دسترسی قرار میگیرند) 13 | 14 | ویژگی های سیستم احراز هویت ایکامرس ای پی آی: 15 | 16 | ### ورود و ثبت نام 17 | - ثبت نام با ایمیل و شماره موبایل (بدون ارسال کد) 18 | - ورود با ایمیل و شماره موبایل 19 | - خروج از حساب کاربری 20 | - خروج از همه دستگاه ها 21 | - نمایش پروفایل کاربری 22 | - بروز رسانی حساب کاربری 23 | - فراموشی رمز عبور (ارسال ایمیل) 24 | - بازیابی رمز عبور 25 | - حذف حساب کاربری 26 | 27 | ### بخش مدیریت کاربران 28 | - نقش بندی کاربران (ریشه،مدیر،فروشنده،مشتری) 29 | - نمایش فهرست کاربران 30 | - جستجو در کاربران 31 | - ایجاد حساب کاربری 32 | - ویرایش حساب های کاربری 33 | - حذف حساب های کاربری 34 | 35 | ### مدیریت محصولات 36 | مدیران و فروشندگان سایت میتوانند محصول ایجاد کرده و یا محصولات سایت را ویرایش و حذف کنند. همچنین میتوانند فهرست خرید های آنجام شده در سایت را بررسی کرده و آنها را تایید و رد کنند. 37 | - ایجاد یا افزودن محصول 38 | - بروز رسانی محصولات 39 | - فهرست گرفتن از محصولات 40 | - نمایش جزئیات محصول 41 | - جستجو در محصولات 42 | - حذف محصولات 43 | 44 | ### مدیریت سبد خرید 45 | مشتریان میتوانند محصولات مورد نظر خود را به سبد خرید اضافه کنند و یا سبد خرید خود را ویرایش و حذف کنند. 46 | - افزودن به سبد خرید 47 | - اهده سبد خرید 48 | - وز رسانی سبد خرید 49 | - حذف سبد خرید 50 | 51 | ### پرداخت آنلاین 52 | پس از افزودن محصول به سبد خرید توانایی پرداخت آنلاین فعال شده و با پرداخت و تایید تراکنش، خرید انجام شده و سبد خرید کاربر به فهرست سفارشات کاربر منتقل میشود. 53 | - پرداخت با درگاه پرداخت آنلاین 54 | - بررسی پرداخت انجام شده 55 | 56 | ### مدیریت سفارش‌ها 57 | مشتریان میتوانند فهرست سفارشات خود را دریافت کرده و جزئیات هر سفارش را بررسی کنند. همچنین این فهرست توسط فروشندگان نیز قابل مشاهده است. 58 | - فهرست گرفتن از سفارشات 59 | - نمایش جزئیات سفارش 60 | 61 | ### دسته بندی محصولات 62 | مدیران سایت میتوانند برای محصولات دسته ایجاد کرده و این دسته ها را ویرایش و حذف کنند. همچنین قابلیت نمایش محصولات بر اساس دسته ها وجود دارد. 63 | - افزودن دسته و زیر دسته 64 | - ویرایش دسته 65 | - فهرست گیری دسته ها 66 | - نمایش محصولات دسته 67 | - حذف دسته ها 68 | 69 | ### مدیریت برچسب ها 70 | مدیران سایت میتوانند برای محصولات برچسب ایجاد کرده و این برچسب ها را ویرایش و حذف کنند. همچنین قابلیت نمایش محصولات بر اساس برچسب ها وجود دارد. 71 | - فهرست گیری از برچسب ها 72 | - ویرایش برچسب ها 73 | - افزودن برچسب ها 74 | - نمایش محصولات هر برچسب 75 | - حذف برچسب ها 76 | 77 | ### مدیریت دیدگاه ها و نظرات 78 | کاربران ثبت نام شده و غیر ثبت نام شده میتوانند برای هر محصول دیدگاه ثبت کنند و به محصولات امتیاز دهند. همچنین کاربران ثبت نام شده میتوانند دیدگاه های خود را ویرایش کنند. کلیه دیدگاه ها توسط مدیران سایت باید تایید و یا رد شود. 79 | - ثبت دیدگاه یا نظر روی محصول 80 | - فهرست گیری از نظرات 81 | - نمایش نظرات هر محصول 82 | - ویرایش نظرات 83 | - حذف نظرات 84 | 85 | ### سیستم مدیریت فایل 86 | فروشندگان و مدیران سایت میتوانند فایلهای خود را آپلود کنند. فایلهای آپلود شده فروشندگان فقط توسط خود آنها قابل ویرایش و حذف شدن است و مدیران سایت میتوانند کلیه فایلها را مدیریت کنند. 87 | - فهرست گیری از فایل‌ها 88 | - مشاهده جزئیات هر فایل 89 | - آپلود کردن فایل (پسوند های مشخص) 90 | - ویرایش فایل‌ها 91 | - حذف فایل های آپلود شده 92 | 93 | ### علاقه مندی ها 94 | مشتریان سایت میتوانند محصولات مورد علاقه خود را به فهرست علاقه مندی های اضافه کرده و یا آنرا از فهرست خود حذف کنند. 95 | - افزودن به علاقه مندی ها 96 | - حذف از علاقه مندی ها 97 | - نمایش فهرست علاقه مندی ها 98 | 99 | ### مدیریت مکان ها 100 | مدیران سایت میتوانند در سایت کشور و استان/ایالت و شهر ایجاد کرده و مشخص کنند استان ها و یا شهر ها متعلق به کدام کشور یا استان/ایالت است. 101 | - ایجاد یا افزودن مکان (کشور – استان/ایالت – شهر) 102 | - فهرست گرفتن از مکان ها 103 | - نمایش مکان (مثلا استان و شهر های زیرمجموع) 104 | - ویرایش و بروز رسانی مکان 105 | - حذف مکان ها 106 | 107 | ### مدیریت آدرس‌ها 108 | مشتریان میتوانند آدرس خود را در پروفایل کاربری خود ثبت و اضافه کنند تا محصول به آدرس مشخص شده در پروفایل کاربر، ارسال شود. 109 | - افزودن یا ایجاد آدرس 110 | - نمایش آدرس 111 | - ویرایش آدرس 112 | - حذف آدرس 113 | 114 | ## مستندات برنامه 115 | 116 | برای اجرا و استفاده از Ecommerce API مستندات مذکور در این بخش را به صورت کامل مطالعه کرده و طبق دستورالعمل پیش روید. 117 | 118 | ### اجرای برنامه در سیستم شخصی 119 | برای اجرای برنامه در سیستم شخصی باید نرم افزار ها و ماژول های زیر را در سیستم خود نصب داشته باشید و یا آنها را نصب کنید: 120 | ``` 121 | Node.js ==> https://nodejs.org/en 122 | TypeScript ==> https://www.typescriptlang.org/download 123 | Mongodb ==> https://www.mongodb.com 124 | NPM ==> https://www.npmjs.com/ 125 | ``` 126 | ``` 127 | bcrypt: 5.1.0 128 | cookie-parser: 1.4.6 129 | cors: 2.8.5 130 | express: 4.18.2 131 | express-fileupload: 1.4.0 132 | jsonwebtoken: 8.5.1 133 | mongoose: .8.0 134 | nodemailer: 6.8.0 135 | dotenv: 16.0.3 136 | ``` 137 | در صورتیکه برنامه Nodejs را در سیستم خود نصب داشته باشید با اجرای دستور زیر در پوشه پروژه کلیه ماژولهای مورد نیاز به صورت خودکار نصب خواهند شد: 138 | ``` 139 | npm install 140 | ``` 141 | برای نصب TypeScript میتوانید از دستور زیر استفاده کنید: 142 | ``` 143 | npm install typescript 144 | ``` 145 | ### تنظیم متغیر های محیطی 146 | 147 | بعد از نصب برنامه ها و ماژول های مورد نیاز یک فایل با نام .env در پوشه اصلی پروژه ایجاد کنید و متغیر های زیر را در آن ایجاد کنید: 148 | ``` 149 | PORT=8000 150 | # پورت دسترسی به برنامه 151 | DB_URI=mongodb://127.0.0.1:27017/ 152 | # آدرس دسترسی به سرور Mongodb میتواند Mongodb Atlas یا Local Database باشد 153 | DB_NAM=ecommerce 154 | # نام پایگاه داده 155 | JWT_SECRET_KEY=7b51677da05da7ebcd4be6499ea87cfe31f5a7af9552ad234e2b52dd934ec 156 | # یک مقدار تصادفی 30 بایتی برای متد ساین JWT 157 | 158 | EMAIL_USER=email@example.com 159 | EMAIL_PASS=password 160 | EMAIL_SERV=mail.example.com 161 | # آدرس ایمیل و رمز عبور و میل سرور خود را در این سه فیلد به ترتیب وارد کنید. 162 | 163 | ZARIN_PAY_MERCHANT=merchantId 164 | ZARIN_PAY_ADDRESS=https://api.zarinpal.com/pg/v4/payment/request.json 165 | ZARIN_PAY_PGSTART=https://www.zarinpal.com/pg/StartPay/ 166 | PAYMENT_CALLBACK_URL=http://localhost:3000/checkout 167 | # در این بخش اطلاعات درگاه پرداخت را باید پر کنید که در حال حاضر فقط درگاه پرداخت زرین پال قابل استفاده است. 168 | ``` 169 | ### اجرای بک اند 170 | 171 | بعد از نصب برنامه ها و ماژول های مورد نیاز و تنظیم متغیر های محیطی در فایل .env نوبت به اجرای ایکامرس ای پی آی میرسد. برای این منظور کافیست از دستورات زیر استفاده کنید: 172 | 173 | ``` 174 | tsc -w 175 | npm run dev 176 | ``` 177 | دستور tsc فایلهای تایپ اسکریپت را به JavaScript تبدیل میکند و آنها را در پوشه dist ذخیره میکند. سپس دستور npm run dev برنامه را در حالت توسعه اجرا میکند. 178 | 179 | نکته: برای اجرای برنامه به صورت عادی میتوانید از npm start استفاده کنید. 180 | 181 | ## نحوه ارسال ریکوست 182 | برای ارسال ریکوست به بک اند به فرمت JSON باید طبق دستورالعمل های زیر اقدام کنید، توجه کنید بجای مقدار {{URL}} باید از آدرس سرور مورد نظر خود استفاده کنید. 183 | 184 | ### ثبت نام در برنامه 185 | 186 | برای ثبت نام در برنامه به آدرس زیر با متد Post ریکوست ارسال کنید: 187 | 188 | POST: {{URL}}/api/user/signup 189 | 190 | 191 | | فیلد | نوع | توضیحات | 192 | | :---: | :---: | ---: | 193 | | name* | string | نام و نام خانوادگی کاربر | 194 | | email* | string | آدرس ایمیل کاربر | 195 | | phone | string | شماره موبایل کاربر | 196 | | password* | string | رمز عبور کاربر | 197 | | role | number | غیر قابل انتخاب (پیشفرض مشتری) | 198 | 199 | نمونه ریکوست: 200 | ``` json 201 | { 202 | "name": "Mohammad Barghamadi", 203 | "email": "mohammadbarghamadi@gmail.com", 204 | "phone": "9304551004", 205 | "password": "83d43c6e56bb7af02962ce0f" 206 | } 207 | ``` 208 | مقدار بازگشتی: 209 | 210 | اگر فرایند ثبت نام به صورت صحیح و درست انجام شود خروجی مقدار بازگشتی به صورت زیر میباشد: 211 | ``` json 212 | { 213 | "status": 200, 214 | "data": { 215 | "name": "Mohammad Barghamadi", 216 | "email": "mohammadbarghamadi@gmail.com", 217 | "phone": "9304551004", 218 | "role": 2000, 219 | "_id": "63bfba9f3c79dfd0eb8d0dae", 220 | "createdAt": "2023-01-12T07:45:35.044Z", 221 | "updatedAt": "2023-01-12T07:45:35.044Z", 222 | "__v": 0 223 | }, 224 | "message": "New user created!" 225 | } 226 | ``` 227 | 228 | ### ورود به برنامه 229 | 230 | برای ورود به برنامه به آدرس زیر با متد Post ریکوست ارسال کنید: 231 | 232 | POST: {{URL}}/api/user/signin 233 | 234 | 235 | | فیلد | نوع | توضیحات | 236 | | :---: | :---: | ---: | 237 | | username | string | آدرس ایمیل یا شماره موبایل کاربر ثبت نام شده | 238 | | password | string | رمز عبور حساب کاربر | 239 | 240 | 241 | نمونه ریکوست: 242 | 243 | ``` json 244 | { 245 | "phone": "9304551004", 246 | "password": "83d43c6e56bb7af02962ce0f" 247 | } 248 | ``` 249 | 250 | مقدار بازگشتی: 251 | 252 | در صورتیکه فرایند ورود به برنامه موفقیت آمیز باشد همراه با پاسخ یک توکن در هدر به سمت کاربر ارسال میشود که نام آن authToken است و برای ارسال ریکویست های بعدی (احراز هویت شده) میتوانید از این توکن استفاده کنید. 253 | ``` json 254 | { 255 | "status": 200, 256 | "data": { 257 | "_id": "63bfba9f3c79dfd0eb8d0dae", 258 | "name": "Mohammad Barghamadi", 259 | "email": "mohammadbarghamadi@gmail.com", 260 | "phone": "9304551004", 261 | "role": 2000, 262 | "createdAt": "2023-01-12T07:45:35.044Z", 263 | "updatedAt": "2023-01-12T08:15:44.581Z", 264 | "__v": 1 265 | }, 266 | "message": "User signed in." 267 | } 268 | ``` 269 | در صورت اشتباه بودن نام کاربری یا رمز عبور مقدار بازگشتی به صورت زیر میشود: 270 | ``` json 271 | { 272 | "success": false, 273 | "error": "Invalid Credentials!" 274 | } 275 | ``` 276 | 277 | ### خروج از برنامه 278 | 279 | برای خروج از برنامه با متد Post به آدرس زیر ریکوست ارسال کنید، به طور پیشفرض توکن احراز هویت که به صورت HttpOnly در Cookie ذخیره شده به برنامه ارسال می‌شود. 280 | 281 | POST: {{URL}}/api/user/signout 282 | 283 | نکته: با ارسال این ریکوست توکن از حساب کاربری در پایگاه داده حذف شده و فاقد اعتبار میشود. 284 | 285 | مقدار بازگشتی: 286 | ``` json 287 | { 288 | "status": 200, 289 | "message": "signout with success!" 290 | } 291 | ``` 292 | 293 | ### خروج از همه دستگاه ها 294 | 295 | برای خروج از همه دستگاه ها بجز دستگاه فعلی که با آن به برنامه وارد شده اید میبایست به آدرس زیر با متد Post ریکوست ارسال کنید: 296 | 297 | POST: {{URL}}/api/user/signoutall 298 | 299 | مقدار بازگشتی: 300 | ``` json 301 | { 302 | "status": 200, 303 | "message": "Signed out from all devices!" 304 | } 305 | ``` 306 | ### دریافت پروفایل 307 | 308 | برای دریافت پروفایل کاربر به آدرس زیر با متد Get ریکوست ارسال کنید. 309 | 310 | GET: {{URL}}/api/user/profile 311 | 312 | مقدار بازگشتی: 313 | ``` json 314 | { 315 | "status": 200, 316 | "data": { 317 | "_id": "63bfba9f3c79dfd0eb8d0dae", 318 | "name": "Mohammad Barghamadi", 319 | "email": "mohammadbarghamadi@gmail.com", 320 | "phone": "9304551004", 321 | "role": 2000, 322 | "createdAt": "2023-01-12T07:45:35.044Z", 323 | "updatedAt": "2023-01-13T04:51:49.026Z", 324 | "__v": 3 325 | }, 326 | "message": "User profile" 327 | } 328 | ``` 329 | ### بروز رسانی پروفایل 330 | 331 | بای بروز رسانی پروفایل کاربر به آدرس زیر با متد Patch ریکوست ارسال کنید: 332 | 333 | PATCH: {{URL}}/api/user/update 334 | 335 | | فیلد | نوع | توضیحات | 336 | | :---: | :---: | ---: | 337 | | name | string | نام و نام خانوادگی کاربر | 338 | | email | string | آدرس ایمیل کاربر | 339 | | phone | string | شماره موبایل کاربر | 340 | | password | string | رمز عبور کاربر | 341 | | role | number | غیر قابل تغییر (فقط مدیر میتواند تغییر دهد) | 342 | 343 | نمونه ریکوست: 344 | ``` json 345 | { 346 | "password": "mynewpassword" 347 | } 348 | ``` 349 | 350 | مقدار بازگشتی: 351 | ``` json 352 | { 353 | "status": 200, 354 | "data": { 355 | "_id": "63bfba9f3c79dfd0eb8d0dae", 356 | "name": "Mohammad Barghamadi", 357 | "email": "mohammadbarghamadi@gmail.com", 358 | "phone": "9304551004", 359 | "role": 2000, 360 | "createdAt": "2023-01-12T07:45:35.044Z", 361 | "updatedAt": "2023-01-13T05:24:22.869Z", 362 | "__v": 3 363 | }, 364 | "message": "User updated!" 365 | } 366 | ``` 367 | خطای بازگشتی در صورت ارسال فیلد نامرتبط: 368 | ``` json 369 | { 370 | "success": false, 371 | "error": "Invalid fields!" 372 | } 373 | ``` 374 | ### فراموشی رمز عبور 375 | 376 | در صورت فراموشی رمز عبور به آدرس زیر با متد Post ریکوست ارسال کنید: 377 | 378 | POST: {{URL}}/api/user/forgot 379 | 380 | | فیلد | نوع | توضیحات | 381 | | :---: | :---: | ---: | 382 | | email | string | آدرس ایمیل کاربر | 383 | 384 | نمونه ریکوست: 385 | ``` json 386 | { 387 | "email": "mohammadbarghamadi@gmail.com" 388 | } 389 | ``` 390 | در صورت موفقیت آمیز بودن فرایند یک ایمیل به کاربر ارسال میشود که حاوی لینک بازیابی رمز عبور است. نمونه لینک: 391 | 392 | {{URL}}/reset/4447f4ea8917250b4bdf3f3d1946f42f977 393 | 394 | با کلیک بر روی لینک بالا کاربر به صفحه ریست پسور در فرانت منتقل میشود و از آن قسمت میبایست مقدار پارامتری که در جلوی reset وجود دارد به همراه رمز عبور تازه به بکاند یا همان برنامه ما ارسال شود. 395 | 396 | ### بازیابی رمز عبور 397 | 398 | برای بازیابی رمز عبور ریکوست خود را به آدرس زیر با متد Post ارسال کنید. 399 | 400 | POST: {{URL}}/api/user/reset/4447f4ea8917250b4bdf3f3d1946f42f977 401 | 402 | | فیلد | نوع | توضیحات | 403 | | :---: | :---: | ---: | 404 | | password | string | رمز عبور جدید کاربر را وارد کنید | 405 | 406 | نمونه ریکوست: 407 | ``` json 408 | { 409 | "password": "mynewpass" 410 | } 411 | ``` 412 | 413 | نکته: برای منقضی نشدن توکن حداکثر 10 دقیقه زمان برای کلیک بر روی لینک بازیابی رمز عبور و ارسال رمز عبور دارید. 414 | 415 | مقدار بازگشتی: 416 | ``` json 417 | { 418 | "status": 200, 419 | "message": "Your password changed!" 420 | } 421 | ``` 422 | مقدار بازگشتی در صورت بروز خطا: 423 | ``` json 424 | { 425 | "success": false, 426 | "error": "Invalid request!" 427 | } 428 | ``` 429 | 430 | ### حذف حساب کاربری 431 | 432 | برای حذف حساب کاربری با متد Delete یک ریکوست به آدرس زیر بفرستید: 433 | 434 | DELETE: {{URL}}/user/delete 435 | 436 | مقدار بازگشتی: 437 | ``` json 438 | { 439 | "status": 200, 440 | "message": "Your account removed!" 441 | } 442 | ``` 443 | 444 | ### فهرست گیری از کاربران 445 | 446 | برای فهرست گرفتن کاربران ثبت نام شده باید به آدرس زیر با متد Get ریکوست ارسال کنید: 447 | 448 | POST: {{URL}}/user/list 449 | 450 | کوئری های قابل استفاده: 451 | 452 | | کلید | مقدار | توضیحات | 453 | | :---: | :---: | ---: | 454 | | csort | asc\dsc | مقدار dsc کاربران را بر اساس تاریخ ثبت نام منظم میکند | 455 | | limit | N | عدد مورد نظر برای محدود کردن تعداد دیتاهای بازگشتی | 456 | | skip | N | عدد مورد نظر برای رد شدن از دیتاهای بازگشتی | 457 | 458 | نمونه ریکوست: 459 | 460 | {{URL}}/user/list?csort=dsc&limit=10&skip=10 461 | 462 | نمونه پاسخ: 463 | 464 | ``` json 465 | { 466 | "status": 200, 467 | "data": [ 468 | { 469 | "_id": "63c235facb76f81556b2df72", 470 | "name": "Deyl Karnegi", 471 | "email": "deylkarnegi@gmail.com", 472 | "phone": "9304551013", 473 | "role": 2000, 474 | "createdAt": "2023-01-14T04:56:26.124Z", 475 | "updatedAt": "2023-01-14T04:56:26.124Z", 476 | "__v": 0 477 | }, 478 | { 479 | "_id": "63c23625cb76f81556b2df74", 480 | "name": "Esther Hicks", 481 | }, 482 | { 483 | "_id": "63c2364bcb76f81556b2df76", 484 | "name": "Jerry Hicks", 485 | } 486 | ] 487 | } 488 | ``` 489 | ### جستجو در کاربران 490 | 491 | برای جستجو در بین کاربران به آدرس زیر با متد Get یک ریکوست ارسال کنید: 492 | 493 | GET: {{URL}}/user/search 494 | 495 | 496 | | کلید | مقدار | توضیحات | 497 | | :---: | :---: | ---: | 498 | | keyphrase | string | در جلوی کلیدواژه مقدار مورد نظر خود را جستجو کنید. | 499 | | csort | asc\dsc | مقدار dsc کاربران را بر اساس تاریخ ثبت نام منظم میکند | 500 | | limit | N | عدد مورد نظر برای محدود کردن تعداد دیتاهای بازگشتی | 501 | | skip | N | عدد مورد نظر برای رد شدن از دیتاهای بازگشتی | 502 | 503 | نمونه ریکوست: 504 | 505 | {{URL}}/user/search?keyphrase=mohammad 506 | 507 | مقدار بازگشتی: 508 | 509 | ``` json 510 | { 511 | "status": 200, 512 | "data": [ 513 | { 514 | "_id": "63c23094cb76f81556b2df3a", 515 | "name": "Mohammad Barghamadi", 516 | "email": "mohammadbarghamadi@gmail.com", 517 | "phone": "9304551004", 518 | "role": 1100, 519 | "createdAt": "2023-01-14T04:33:24.124Z", 520 | "updatedAt": "2023-01-14T04:33:26.712Z", 521 | "__v": 1 522 | } 523 | ] 524 | } 525 | ``` 526 | نکته: اگر چیزی یافت نشود مقدار دیتا خالی باز میگردد. 527 | 528 | 529 | ساخت حساب کاربری 530 | برای ساخت حساب کاربری در برنامه به آدرس زیر با متد Post ریکوست ارسال کنید: 531 | 532 | POST: {{URL}}/user/create 533 | 534 | 535 | | فیلد | نوع | توضیحات | 536 | | :---: | :---: | ---: | 537 | | name* | string | نام و نام خانوادگی کاربر | 538 | | email* | string | آدرس ایمیل کاربر | 539 | | phone | string | شماره موبایل کاربر | 540 | | password* | string | رمز عبور کاربر | 541 | | role | number | رووت: 1000 ادمین: 1100 فروشنده: 1500 مشتری: 2000 | 542 | 543 | 544 | نکته: اگر سطح دسترسی شما پایین تر از مدیریت باشد نمی‌توانید به این بخش ریکوست ارسال کنید و همچنین فقط مدیر اصلی (1000) میتواند حساب کاربری مدیر اصلی (1000) را ایجاد کند. 545 | 546 | حساب های مدیریت (1100) میتوانند حساب مدیرت و حساب های فروشنده و مشتری را ایجاد و ویرایش کنند. 547 | 548 | نمونه ریکوست: 549 | 550 | ``` json 551 | { 552 | "name": "Rhonda Byrne", 553 | "email": "rhondabyrne@gmail.com", 554 | "phone": "9304551016", 555 | "password": "themagicsecret", 556 | "role": 1500 557 | } 558 | ``` 559 | نمونه پاسخ: 560 | 561 | ``` json 562 | { 563 | "status": 200, 564 | "data": { 565 | "name": "Rhonda Byrne", 566 | "email": "rhondabyrne@gmail.com", 567 | "phone": "9304551016", 568 | "role": 1500, 569 | "_id": "63c24c0f2d0db7ae22d57b89", 570 | "createdAt": "2023-01-14T06:30:39.910Z", 571 | "updatedAt": "2023-01-14T06:30:39.910Z", 572 | "__v": 0 573 | }, 574 | "message": "New user created!" 575 | } 576 | ``` 577 | ### ویرایش حساب کاربری 578 | 579 | برای ویرایش حساب های کاربری با متد Patch به آدرس زیر ریکوست ارسال کنید: 580 | 581 | PATCH: {{URL}}/user/edit/userId 582 | 583 | | فیلد | نوع | توضیحات | 584 | | :---: | :---: | ---: | 585 | | name* | string | نام و نام خانوادگی کاربر | 586 | | email* | string | آدرس ایمیل کاربر | 587 | | phone | string | شماره موبایل کاربر | 588 | | password* | string | رمز عبور کاربر | 589 | | role | number | رووت: 1000 ادمین: 1100 فروشنده: 1500 مشتری: 2000 | 590 | 591 | نکته: فرایند تغییر نقش کاربر همانند ایجاد کاربر میباشد. یعنی حساب Root دسترسی کامل در تغییر کاربران دارد و حساب ادمین فقط میتواند سطح خود و فروشنده ها و مشتری ها را تا به سطح ادمین افزایش دهد. 592 | نمونه ریکوست: 593 | 594 | PATCH: {{URL}}/user/edit/63c24c0f2d0db7ae22d57b89 595 | 596 | ``` json 597 | { 598 | "role": 1100 599 | } 600 | ``` 601 | مقدار بازگشتی: 602 | 603 | ``` json 604 | { 605 | "status": 200, 606 | "data": { 607 | "_id": "63c24c0f2d0db7ae22d57b89", 608 | "name": "Rhonda Byrne", 609 | "email": "rhondabyrne@gmail.com", 610 | "phone": "9304551016", 611 | "role": 1100, 612 | "createdAt": "2023-01-14T06:30:39.910Z", 613 | "updatedAt": "2023-01-14T07:41:16.935Z", 614 | "__v": 0 615 | }, 616 | "message": "User updated!" 617 | } 618 | ``` 619 | ### حذف حساب کاربری 620 | 621 | برای حذف یک حساب کاربری به آدرس زیر با متد Delete ریکوست ارسال کنید: 622 | 623 | DELETE: {{URL}}/user/remove/userId 624 | 625 | نمونه ریکوست: 626 | 627 | {{URL}}/user/remove/63c23409cb76f81556b2df60 628 | 629 | نمونه پاسخ: 630 | 631 | ``` json 632 | { 633 | "status": 200, 634 | "data": { 635 | "_id": "63c23409cb76f81556b2df60", 636 | "name": "Jack Cherry", 637 | "email": "jackcherry@gmail.com", 638 | "phone": "9304551005", 639 | "role": 2000, 640 | "createdAt": "2023-01-14T04:48:09.351Z", 641 | "updatedAt": "2023-01-14T04:48:09.351Z", 642 | "__v": 0 643 | }, 644 | "message": "User removed!" 645 | } 646 | ``` 647 | 648 | ### بارگذاری فایل 649 | 650 | برای بارگذاری فایل به آدرس زیر با متد Post ریکوست ارسال کنید. 651 | 652 | POST: {{URL}}/file/upload 653 | 654 | نمونه پاسخ: 655 | ``` json 656 | { 657 | "status": 200, 658 | "data": [ 659 | { 660 | "name": "mbp16-silver-gallery2-202110.jpeg", 661 | "encoding": "7bit", 662 | "size": "208874", 663 | "filepath": "dist/files/images/2023/1/15/1673757892069-157159.jpg", 664 | "mimetype": "image/jpeg", 665 | "md5": "84dc87e1cfb4850cb4bddd75a75997f1", 666 | "userId": "63c23094cb76f81556b2df3a", 667 | "_id": "63c384c44c7daae474b9b611", 668 | "__v": 0 669 | } 670 | ] 671 | } 672 | ``` 673 | بارگذاری فایل ها بر اساس تاریخ و نوع فایل میباشد. یعنی اگر فرمت فایل jpg باشد یک پوشه با نام images ایجاد شده و زیر مجموع آن بر اساس تاریخ سال و ماه و روز پوشه ساخته میشود و در نهایت فایل با نام تصادفی و تاریخ همان روز ذخیره میشود. برای مثال: 674 | 675 | /files/images/2023/1/15/1673757892069-157159.jpg 676 | 677 | به صورت پیشفرض فقط پسوند های jpg,jpeg,png,mpeg با حداکثر حجم 2 مگابایت قابل بارگذاری هستند. 678 | فایل بارگذاری شده متعلق به کاربری میباشد که آنرا بارگذاری کرده است و دیگر کاربران نمیتوانند به آن دسترسی داشته باشند اما مدیران برنامه میتوانند به کلیه فایل های بارگذاری شده دسترسی داشته باشند. 679 | 680 | ### فهرست گیری از فایلها 681 | 682 | برای فهرست گیری از فایل ها به آدرس زیر با متد Get ریکوست ارسال کنید: 683 | 684 | GET: {{URL}}/file/list 685 | 686 | نمونه پاسخ: 687 | 688 | ``` json 689 | { 690 | "status": 200, 691 | "data": [ 692 | { 693 | "_id": "63aaa7110c832c33351fb79e", 694 | "name": "Image16.jpg", 695 | "encoding": "7bit", 696 | "size": "652006", 697 | "filepath": "/dist/files/images/2022/12/27/1672128273496-997340.jpg", 698 | "mimetype": "image/jpeg", 699 | "md5": "0a0e1ef9b26931a856852ed90f12aa62", 700 | "userId": "63aaa69e0c832c33351fb77e", 701 | "__v": 0 702 | }, 703 | { 704 | "_id": "63c388b34c7daae474b9b615", 705 | "name": "6183d38056d48692be8226c6be39f063.png", 706 | "encoding": "7bit", 707 | "size": "1605859", 708 | "filepath": "/dist/files/images/2023/1/15/1673758899966-6809644.png", 709 | "mimetype": "image/png", 710 | "md5": "01f092bd23c85e7b2c9166e3aa807ed5", 711 | "userId": "63c23094cb76f81556b2df3a", 712 | "__v": 0 713 | } 714 | ], 715 | "message": "Files retrived." 716 | } 717 | ``` 718 | ### دریافت یک فایل 719 | 720 | برای دریافت یک فایل شناسه آنرا با متد Get به آدرس زیر ارسال کنید: 721 | 722 | GET: {{URL}}/file/get/fileId 723 | 724 | نمونه ریکوست: 725 | 726 | {{URL}}/file/get/63aaa7110c832c33351fb79e 727 | 728 | نمونه پاسخ: 729 | 730 | ``` json 731 | { 732 | "status": 200, 733 | "data": { 734 | "_id": "63aaa7110c832c33351fb79e", 735 | "name": "Image16.jpg", 736 | "encoding": "7bit", 737 | "size": "652006", 738 | "filepath": "/dist/files/images/2022/12/27/1672128273496-997340.jpg", 739 | "mimetype": "image/jpeg", 740 | "md5": "0a0e1ef9b26931a856852ed90f12aa62", 741 | "userId": "63aaa69e0c832c33351fb77e", 742 | "__v": 0 743 | }, 744 | "message": "File found." 745 | } 746 | ``` 747 | ### بروز رسانی فایل 748 | 749 | برای ویرایش نام یا تغییر مالک فایل میتوانید به آدرس زیر با متد Patch ریکوست ارسال کنید: 750 | 751 | PATCH: {{URL}}/file/update/fileId 752 | 753 | | فیلد | نوع | توضیحات | 754 | | :---: | :---: | ---: | 755 | | name | string | نام جدید فایل | 756 | | userId | string | شناسه مالک جدید | 757 | 758 | فقط حساب های مدیریت میتوانند مالک یک فایل را تغییر دهند. 759 | 760 | نمونه ریکوست: 761 | 762 | {{URL}}/file/update/63aaa7110c832c33351fb79e 763 | 764 | نمونه پاسخ: 765 | ``` json 766 | { 767 | "status": 200, 768 | "data": { 769 | "_id": "63bcf356ce90d6dbda7696b0", 770 | "name": "Apple Laptop", 771 | "encoding": "7bit", 772 | "size": "208874", 773 | "filepath": "/dist/files/images/2023/1/10/1673327446389-6649813.jpeg", 774 | "mimetype": "image/jpeg", 775 | "md5": "84dc87e1cfb4850cb4bddd75a75997f1", 776 | "userId": "63b51152f7daee2dfbd6d28a", 777 | "__v": 0 778 | }, 779 | "message": "File updated." 780 | } 781 | ``` 782 | ### حذف یک فایل 783 | 784 | برای حذف یک فایل باید به آدرس زیر با متد Delete ریکوست ارسال کنید: 785 | 786 | DELETE: {{URL}}/file/delete/fileId 787 | 788 | نمونه ریکوست: 789 | 790 | {{URL}}/file/delete/63aaa7110c832c33351fb79d 791 | 792 | نمونه پاسخ: 793 | 794 | ``` json 795 | { 796 | "status": 200, 797 | "data": { 798 | "_id": "63bcf356ce90d6dbda7696b0", 799 | "name": "Apple Laptop", 800 | "encoding": "7bit", 801 | "size": "208874", 802 | "filepath": "/dist/files/images/2023/1/10/1673327446389-6649813.jpeg", 803 | "mimetype": "image/jpeg", 804 | "md5": "84dc87e1cfb4850cb4bddd75a75997f1", 805 | "userId": "63b51152f7daee2dfbd6d28a", 806 | "__v": 0 807 | }, 808 | "message": "File deleted!" 809 | } 810 | ``` 811 | 812 | ### افزودن دسته 813 | 814 | برای افزودن دسته به آدرس زیر با متد Post ریکوست ارسال کنید: 815 | 816 | POST: {{URL}}/cate/add 817 | 818 | | فیلد | نوع | توضیحات | 819 | | :---: | :---: | ---: | 820 | | name* | string | نام دسته | 821 | | url* | string | لینک دسته | 822 | | category | string | شناسه دسته والد | 823 | | meta | meta | شامل: title – description – keyphrase | 824 | 825 | نمونه ریکوست: 826 | ``` json 827 | { 828 | "name": "Backend Development", 829 | "url": "backend-dev", 830 | "meta": { 831 | "title": "Backend Archive - Ecommerce-API", 832 | "description": "If you want to learn Backend you should understand its basic and here you can find much good information.", 833 | "keyphrase": [ 834 | "backend", 835 | "backend development", 836 | "server side" 837 | ] 838 | } 839 | } 840 | ``` 841 | نمونه پاسخ: 842 | ``` json 843 | { 844 | "status": 200, 845 | "data": { 846 | "category": { 847 | "name": "Backend Development", 848 | "url": "backend-dev", 849 | "children": [], 850 | "_id": "63c398914c7daae474b9b637", 851 | "meta": "63c398914c7daae474b9b638", 852 | "__v": 0 853 | }, 854 | "meta": { 855 | "title": "Backend Archive - Ecommerce-API", 856 | "description": "If you want to learn Backend you should understand its basic and here you can find much good information.", 857 | "keyphrase": [ 858 | "backend", 859 | "backend development", 860 | "server side" 861 | ], 862 | "link": "63c398914c7daae474b9b637", 863 | "_id": "63c398914c7daae474b9b638", 864 | "__v": 0 865 | } 866 | }, 867 | "message": "New category added!" 868 | } 869 | ``` 870 | ### فهرست گیری از دسته ها 871 | 872 | 873 | برای فهرست گرفتن از دسته ها به آدرس زیر با متد Get ریکوست ارسال کنید: 874 | 875 | GET: {{URL}}/cate/list 876 | 877 | نمونه پاسخ: 878 | ``` json 879 | { 880 | "status": 200, 881 | "message": "Category found", 882 | "data": [ 883 | { 884 | "_id": "63b12f67ff0cf3e6dcfd1d79", 885 | "name": "Frontend Dev", 886 | "url": "frontend", 887 | "children": [ 888 | "63ba7b18af12483d41a1e454" 889 | ], 890 | "__v": 0, 891 | "meta": "63bbaa15e2459b34c12fa2b0" 892 | }, 893 | { 894 | "_id": "63ba7b18af12483d41a1e454", 895 | "name": "Javascript", 896 | "url": "javascript", 897 | "children": [], 898 | "meta": "63ba7b18af12483d41a1e455", 899 | "__v": 0, 900 | "category": "63b12f67ff0cf3e6dcfd1d79" 901 | }, 902 | { 903 | "_id": "63c398914c7daae474b9b637", 904 | "name": "Backend Development", 905 | "url": "backend-dev", 906 | "children": [], 907 | "meta": "63c398914c7daae474b9b638", 908 | "__v": 0 909 | } 910 | ] 911 | } 912 | ``` 913 | ### نمایش محتوای یک دسته 914 | 915 | برای دیدن محتوای یک دسته و محصولات زیر مجموع آن به آدرس زیر با متد Get ریکوست ارسال کنید: 916 | 917 | GET: {{URL}}/cate/get/categoryId 918 | 919 | نمونه ریکوست: 920 | 921 | {{URL}}/cate/get/63b12f67ff0cf3e6dcfd1d79 922 | 923 | نمونه پاسخ: 924 | 925 | ``` json 926 | { 927 | "status": 200, 928 | "data": { 929 | "category": { 930 | "_id": "63b12f67ff0cf3e6dcfd1d79", 931 | "name": "Frontend Dev", 932 | "url": "frontend", 933 | "children": [ 934 | "63ba7b18af12483d41a1e454" 935 | ], 936 | "__v": 0, 937 | "meta": { 938 | "_id": "63bbaa15e2459b34c12fa2b0", 939 | "title": "Frontend development - Ecommerce API", 940 | "description": "Do you want to become a frontend development? here is the best resources you can use on you path." 941 | } 942 | }, 943 | "products": [ 944 | { 945 | "images": { 946 | "main": null 947 | }, 948 | "_id": "63bcf64ece90d6dbda7696c4", 949 | "title": "HTML & CSS Elementry", 950 | "price": 124000, 951 | "url": "html-css-guide" 952 | } 953 | ] 954 | }, 955 | "message": "Category and its products" 956 | } 957 | ``` 958 | ### ویرایش دسته 959 | 960 | برای ویرایش یک دسته به آدرس زیر با متد Patch ریکوست ارسال کنید: 961 | 962 | PATCH: {{URL}}/cate/edit/categoryId 963 | 964 | | فیلد | نوع | توضیحات | 965 | | :---: | :---: | ---: | 966 | | name* | string | نام دسته | 967 | | url* | string | لینک دسته | 968 | | category | string | شناسه دسته والد | 969 | | meta | meta | شامل: title – description – keyphrase | 970 | 971 | نمونه ریکوست: 972 | 973 | {{URL}}/cate/edit/63b12fb1ff0cf3e6dcfd1d8f 974 | ``` json 975 | { 976 | "category": "63c398914c7daae474b9b637" 977 | } 978 | ``` 979 | نمونه پاسخ: 980 | 981 | ``` json 982 | { 983 | "status": 200, 984 | "data": { 985 | "removed": null, 986 | "assigned": { 987 | "_id": "63c398914c7daae474b9b637", 988 | "name": "Backend Development", 989 | "url": "backend-dev", 990 | "children": [], 991 | "meta": "63c398914c7daae474b9b638", 992 | "__v": 0 993 | }, 994 | "saved": { 995 | "_id": "63b12fb1ff0cf3e6dcfd1d8f", 996 | "name": "GraphQL", 997 | "url": "graphql", 998 | "children": [], 999 | "__v": 0, 1000 | "category": "63c398914c7daae474b9b637" 1001 | } 1002 | }, 1003 | "message": "Category updated!" 1004 | } 1005 | ``` 1006 | در مثال بالا دسته 63b12fb1ff0cf3e6dcfd1d8f به زیر مجموعه دسته 63c398914c7daae474b9b637 اضافه شد. 1007 | 1008 | ### حذف دسته 1009 | 1010 | برای حذف یک دسته به آدرس زیر با متد Delete ریکوست ارسال کنید: 1011 | 1012 | DELETE: {{URL}}/cate/delete/categoryId 1013 | 1014 | نمونه ریکوست: 1015 | 1016 | {{URL}}/cate/delete/63bbaa7ee2459b34c12fa2b7 1017 | 1018 | نمونه پاسخ: 1019 | ``` json 1020 | { 1021 | "status": 200, 1022 | "data": { 1023 | "category": { 1024 | "_id": "63bbaa7ee2459b34c12fa2b7", 1025 | "name": "Backend Development", 1026 | "url": "backend-dev", 1027 | "children": [ 1028 | "63b12f89ff0cf3e6dcfd1d83", 1029 | "63b12fb1ff0cf3e6dcfd1d8f" 1030 | ], 1031 | "meta": "63bbaa7ee2459b34c12fa2b8", 1032 | "__v": 0 1033 | }, 1034 | "meta": { 1035 | "_id": "63bbaa7ee2459b34c12fa2b8", 1036 | "title": "Backend Archive - Ecommerce-API", 1037 | "description": "If you want to learn Backend you should understand its basic and here you can find much good information.", 1038 | "keyphrase": [], 1039 | "link": "63bbaa7ee2459b34c12fa2b7", 1040 | "__v": 0 1041 | } 1042 | }, 1043 | "message": "Category deleted" 1044 | } 1045 | ``` 1046 | 1047 | ### افزودن برچسب 1048 | 1049 | برای افزودن برچسب به آدرس زیر با متد Post ریکوست ارسال کنید: 1050 | 1051 | POST: {{URL}}/tags/add 1052 | 1053 | | فیلد | نوع | توضیحات | 1054 | | :---: | :---: | ---: | 1055 | | name* | string | نام برچسب | 1056 | | url* | string | لینک برچسب | 1057 | | meta | meta | شامل: title – description – keyphrase | 1058 | 1059 | نمونه ریکوست: 1060 | ``` json 1061 | { 1062 | "name": "Node.js", 1063 | "url": "nodejs", 1064 | "meta": { 1065 | "title": "Nodejs Tag", 1066 | "description": "Latest Nodejs videos and courses and news", 1067 | "keyphrase": [ 1068 | "nodejs", 1069 | "node" 1070 | ] 1071 | } 1072 | } 1073 | ``` 1074 | نمونه پاسخ: 1075 | 1076 | ``` json 1077 | { 1078 | "status": 200, 1079 | "data": { 1080 | "tag": { 1081 | "name": "Node.js", 1082 | "url": "nodejs", 1083 | "_id": "63c3aee04c7daae474b9b65c", 1084 | "meta": "63c3aee04c7daae474b9b65d", 1085 | "__v": 0 1086 | }, 1087 | "meta": { 1088 | "title": "Nodejs Tag", 1089 | "description": "Latest Nodejs videos and courses and news", 1090 | "keyphrase": [ 1091 | "nodejs", 1092 | "node" 1093 | ], 1094 | "link": "63c3aee04c7daae474b9b65c", 1095 | "_id": "63c3aee04c7daae474b9b65d", 1096 | "__v": 0 1097 | } 1098 | }, 1099 | "message": "New tag added!" 1100 | } 1101 | ``` 1102 | ### نمایش فهرست برچسب ها 1103 | 1104 | برای گرفتن فهرست کلیه برچسب ها به آدرس زیر با متد Get ریکوست ارسال کنید: 1105 | 1106 | GET: {{URL}}/tags/list 1107 | 1108 | نمونه پاسخ: 1109 | ``` json 1110 | { 1111 | "status": 200, 1112 | "message": "Tag found", 1113 | "data": [ 1114 | { 1115 | "_id": "63bbb75d9753a93e0e9dbaa3", 1116 | "name": "React.js", 1117 | "url": "reactjs", 1118 | "meta": "63bbb75d9753a93e0e9dbaa4", 1119 | "__v": 0 1120 | }, 1121 | { 1122 | "_id": "63bbbe58bae680a81673f7cd", 1123 | "name": "ReduxJs Posts", 1124 | "url": "redux", 1125 | "meta": "63bbbe58bae680a81673f7ce", 1126 | "__v": 0 1127 | }, 1128 | { 1129 | "_id": "63c3aee04c7daae474b9b65c", 1130 | "name": "Node.js", 1131 | "url": "nodejs", 1132 | "meta": "63c3aee04c7daae474b9b65d", 1133 | "__v": 0 1134 | } 1135 | ] 1136 | } 1137 | ``` 1138 | ### نمایش محتوای برچسب 1139 | 1140 | برای دریافت کلیه محصولات افزوده شده به برچسب به آدرس زیر با متد Get ریکوست ارسال کنید: 1141 | 1142 | GET: {{URL}}/tags/get/tagId 1143 | 1144 | نمونه ریکوست: 1145 | ``` json 1146 | { 1147 | "status": 200, 1148 | "data": { 1149 | "_id": "63bbb75d9753a93e0e9dbaa3", 1150 | "name": "React.js", 1151 | "url": "reactjs", 1152 | "meta": { 1153 | "_id": "63bbb75d9753a93e0e9dbaa4", 1154 | "title": "React.js Archive - Ecommerce API", 1155 | "description": "Here is some info about React.js and if you want to learn this stuf you have to follow me", 1156 | "keyphrase": [] 1157 | }, 1158 | "__v": 0 1159 | }, 1160 | "products": [ 1161 | { 1162 | "images": { 1163 | "main": null 1164 | }, 1165 | "_id": "63aaa7dc465015f29b89fee1", 1166 | "title": "Frontend dev with React", 1167 | "price": 98000, 1168 | "url": "frontend-dev-with-react-68465-1672128476128" 1169 | }, 1170 | { 1171 | "images": { 1172 | "main": null 1173 | }, 1174 | "_id": "63bcf64ece90d6dbda7696c4", 1175 | "title": "HTML & CSS Elementry", 1176 | "price": 124000, 1177 | "url": "html-css-guide" 1178 | } 1179 | ] 1180 | } 1181 | ``` 1182 | ### ویرایش برچسب 1183 | 1184 | برای ویرایش برچسب ها به آدرس زیر با متد Patch ریکوست ارسال کنید: 1185 | 1186 | Patch: {{URL}}/tags/edit/tagId 1187 | 1188 | | فیلد | نوع | توضیحات | 1189 | | :---: | :---: | ---: | 1190 | | name* | string | نام برچسب | 1191 | | url* | string | لینک برچسب | 1192 | | meta | meta | شامل: title – description – keyphrase | 1193 | 1194 | نمونه ریکوست: 1195 | 1196 | {{URL}}/tags/edit/63bbbe58bae680a81673f7cd 1197 | ``` json 1198 | { 1199 | "name": "ReduxJs Posts", 1200 | "meta": { 1201 | "title": "Redux js products", 1202 | "description": "Here is you can find all reduxjs posts and content" 1203 | } 1204 | } 1205 | ``` 1206 | نمونه پاسخ: 1207 | 1208 | ``` json 1209 | { 1210 | "status": 200, 1211 | "data": { 1212 | "updatedTag": { 1213 | "_id": "63c3aee04c7daae474b9b65c", 1214 | "name": "Nodejs Posts", 1215 | "url": "nodejs", 1216 | "meta": "63c3aee04c7daae474b9b65d", 1217 | "__v": 0 1218 | }, 1219 | "meta": { 1220 | "_id": "63c3aee04c7daae474b9b65d", 1221 | "title": "Node.js products", 1222 | "description": "Here is you can find all nodejs posts and content", 1223 | "keyphrase": [ 1224 | "nodejs", 1225 | "node" 1226 | ], 1227 | "link": "63c3aee04c7daae474b9b65c", 1228 | "__v": 0 1229 | } 1230 | }, 1231 | "message": "Tag updated!" 1232 | } 1233 | ``` 1234 | 1235 | ### حذف برچسب 1236 | 1237 | برای حذف یک برچسب به آدرس زیر با متد Delete ریکوست ارسال کنید: 1238 | 1239 | DELETE: {{URL}}/tags/delete/tagId 1240 | 1241 | نمونه ریکوست: 1242 | 1243 | {{URL}}/tags/delete/63bbb75d9753a93e0e9dbaa3 1244 | 1245 | نمونه پاسخ: 1246 | 1247 | ``` json 1248 | { 1249 | "status": 200, 1250 | "data": { 1251 | "tag": { 1252 | "_id": "63bbb75d9753a93e0e9dbaa3", 1253 | "name": "React.js", 1254 | "url": "reactjs", 1255 | "meta": "63bbb75d9753a93e0e9dbaa4", 1256 | "__v": 0 1257 | }, 1258 | "meta": { 1259 | "_id": "63bbb75d9753a93e0e9dbaa4", 1260 | "title": "React.js Archive - Ecommerce API", 1261 | "description": "Here is some info about React.js and if you want to learn this stuf you have to follow me", 1262 | "keyphrase": [], 1263 | "link": "63bbb75d9753a93e0e9dbaa3", 1264 | "__v": 0 1265 | }, 1266 | "products": { 1267 | "acknowledged": true, 1268 | "modifiedCount": 2, 1269 | "upsertedId": null, 1270 | "upsertedCount": 0, 1271 | "matchedCount": 2 1272 | } 1273 | }, 1274 | "message": "tag deleted" 1275 | } 1276 | ``` 1277 | 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | ### افزودن محصول 1285 | 1286 | برای ایجاد یا افزودن محصول به آدرس زیر با متد Post ریکوست ارسال کنید: 1287 | 1288 | POST: {{URL}}/prod/add 1289 | 1290 | | فیلد | نوع | توضیحات | 1291 | | :---: | :---: | ---: | 1292 | | title | string | عنوان محصول | 1293 | | excerpt | string | توضیحات مختصر درباره محصول | 1294 | | content | string| توضیحات کامل محصول به همراه جزئیات | 1295 | | price | number | قیمت محصول | 1296 | | url | string | آدرس لینک مستقیم محصول | 1297 | | images | ObjectId | شامل: تصویر اصلی (main) و گالری تصاویر ([]Gallery) | 1298 | | category | []ObjectId | آرایه ای از شناسه دسته ها | 1299 | | tag | []ObjectId | آرایه ای از شناسه برچسب ها | 1300 | | owner | ObjectId | شناسه مالک محصول (به صورت پیشفرض شخص سازنده است) | 1301 | | meta | meta| شامل: title – description – keyphrase | 1302 | 1303 | نمونه ریکوست: 1304 | 1305 | ``` json 1306 | { 1307 | "title": "Reactjs Study Guide", 1308 | "excerpt": "short description to introduce this product", 1309 | "content": "complete description and detail about this product", 1310 | "price": 124000, 1311 | "url": "reactjs-guide", 1312 | "images": { 1313 | "main": "63aaa7110c832c33351fb79e", 1314 | "gallery": [ 1315 | "63c388b34c7daae474b9b615", 1316 | "63aaa7110c832c33351fb79e" 1317 | ] 1318 | }, 1319 | "category": [ 1320 | "63b12f67ff0cf3e6dcfd1d79" 1321 | ], 1322 | "tag": [ 1323 | "63bbbe58bae680a81673f7cd", 1324 | "63c3aee04c7daae474b9b65c" 1325 | ], 1326 | "meta": { 1327 | "title": "Reactjs Study Guide", 1328 | "description": "If you want to learn Reactjs here I will teach you how you can build a frontend app" 1329 | } 1330 | } 1331 | ``` 1332 | نمونه پاسخ: 1333 | ``` json 1334 | { 1335 | "status": 200, 1336 | "message": "New product added.", 1337 | "data": { 1338 | "meta": { 1339 | "title": "Reactjs Study Guide", 1340 | "description": "If you want to learn Reactjs here I will teach you how you can build a frontend app", 1341 | "keyphrase": [], 1342 | "_id": "63c4dc6213d8575602ccf5de", 1343 | "link": "63c4dc6213d8575602ccf5df", 1344 | "__v": 0 1345 | }, 1346 | "product": { 1347 | "title": "Reactjs Study Guide", 1348 | "excerpt": "short description to introduce this product", 1349 | "content": "complete description and detail about this product", 1350 | "price": 124000, 1351 | "url": "reactjs-guide", 1352 | "images": { 1353 | "main": "63aaa7110c832c33351fb79e", 1354 | "gallery": [ 1355 | "63c388b34c7daae474b9b615", 1356 | "63aaa7110c832c33351fb79e" 1357 | ] 1358 | }, 1359 | "category": [ 1360 | "63b12f67ff0cf3e6dcfd1d79" 1361 | ], 1362 | "tag": [ 1363 | "63bbbe58bae680a81673f7cd", 1364 | "63c3aee04c7daae474b9b65c" 1365 | ], 1366 | "meta": "63c4dc6213d8575602ccf5de", 1367 | "owner": "63c23094cb76f81556b2df3a", 1368 | "_id": "63c4dc6213d8575602ccf5df", 1369 | "createdAt": "2023-01-16T05:10:58.314Z", 1370 | "updatedAt": "2023-01-16T05:10:58.314Z", 1371 | "__v": 0 1372 | } 1373 | } 1374 | } 1375 | ``` 1376 | ### نمایش محتوای محصول 1377 | 1378 | برای دیدن جزئیات یک محصول میتوانید به آدرس زیر با متد Get ریکوست ارسال کنید: 1379 | 1380 | GET: {{URL}}/prod/get/prodId 1381 | 1382 | نمونه ریکوست: 1383 | 1384 | {{URL}}/prod/get/63c4dc6213d8575602ccf5df 1385 | 1386 | نمونه پاسخ: 1387 | 1388 | ``` json 1389 | { 1390 | "status": 200, 1391 | "data": { 1392 | "images": { 1393 | "main": { 1394 | "_id": "63aaa7110c832c33351fb79e", 1395 | "name": "Image16.jpg", 1396 | "encoding": "7bit", 1397 | "size": "652006", 1398 | "filepath": "/dist/files/images/2022/12/27/1672128273496-997340.jpg", 1399 | "mimetype": "image/jpeg", 1400 | "md5": "0a0e1ef9b26931a856852ed90f12aa62", 1401 | "userId": "63aaa69e0c832c33351fb77e", 1402 | "__v": 0 1403 | }, 1404 | "gallery": [ 1405 | { 1406 | "_id": "63c388b34c7daae474b9b615", 1407 | "name": "6183d38056d48692be8226c6be39f063.png", 1408 | "encoding": "7bit", 1409 | "size": "1605859", 1410 | "filepath": "/dist/files/images/2023/1/15/1673758899966-6809644.png", 1411 | "mimetype": "image/png", 1412 | "md5": "01f092bd23c85e7b2c9166e3aa807ed5", 1413 | "userId": "63c23094cb76f81556b2df3a", 1414 | "__v": 0 1415 | }, 1416 | { 1417 | "_id": "63aaa7110c832c33351fb79e", 1418 | "name": "Image16.jpg", 1419 | "encoding": "7bit", 1420 | "size": "652006", 1421 | "filepath": "/dist/files/images/2022/12/27/1672128273496-997340.jpg", 1422 | "mimetype": "image/jpeg", 1423 | "md5": "0a0e1ef9b26931a856852ed90f12aa62", 1424 | "userId": "63aaa69e0c832c33351fb77e", 1425 | "__v": 0 1426 | } 1427 | ] 1428 | }, 1429 | "_id": "63c4dc6213d8575602ccf5df", 1430 | "title": "Reactjs Study Guide", 1431 | "excerpt": "short description to introduce this product", 1432 | "content": "complete description and detail about this product", 1433 | "price": 124000, 1434 | "url": "reactjs-guide", 1435 | "category": [ 1436 | { 1437 | "_id": "63b12f67ff0cf3e6dcfd1d79", 1438 | "name": "Frontend Dev", 1439 | "url": "frontend", 1440 | "children": [ 1441 | "63ba7b18af12483d41a1e454" 1442 | ], 1443 | "__v": 0, 1444 | "meta": "63bbaa15e2459b34c12fa2b0" 1445 | } 1446 | ], 1447 | "tag": [ 1448 | { 1449 | "_id": "63bbbe58bae680a81673f7cd", 1450 | "name": "ReduxJs Posts", 1451 | "url": "redux", 1452 | "meta": "63bbbe58bae680a81673f7ce", 1453 | "__v": 0 1454 | }, 1455 | { 1456 | "_id": "63c3aee04c7daae474b9b65c", 1457 | "name": "Nodejs Posts", 1458 | "url": "nodejs", 1459 | "meta": "63c3aee04c7daae474b9b65d", 1460 | "__v": 0 1461 | } 1462 | ], 1463 | "meta": { 1464 | "_id": "63c4dc6213d8575602ccf5de", 1465 | "title": "Reactjs Study Guide", 1466 | "description": "If you want to learn Reactjs here I will teach you how you can build a frontend app", 1467 | "keyphrase": [], 1468 | "link": "63c4dc6213d8575602ccf5df", 1469 | "__v": 0 1470 | }, 1471 | "owner": "63c23094cb76f81556b2df3a", 1472 | "createdAt": "2023-01-16T05:10:58.314Z", 1473 | "updatedAt": "2023-01-16T05:10:58.314Z", 1474 | "__v": 0 1475 | }, 1476 | "message": "Product found." 1477 | } 1478 | ``` 1479 | 1480 | ### فهرست گرفتن از محصولات 1481 | 1482 | برای فهرست گرفتن از کلیه محصولات به آدرس زیر با متد Get ریکوست ارسال کنید: 1483 | 1484 | GET: {{URL}}/prod/list 1485 | 1486 | نمونه ریکوست: 1487 | 1488 | ``` json 1489 | { 1490 | "status": 200, 1491 | "data": [ 1492 | { 1493 | "images": { 1494 | "main": { 1495 | "_id": "63aaa7110c832c33351fb79e", 1496 | "name": "Image16.jpg", 1497 | "filepath": "/dist/files/images/2022/12/27/1672128273496-997340.jpg" 1498 | }, 1499 | "gallery": [ 1500 | { 1501 | "_id": "63aaa7110c832c33351fb79e", 1502 | "name": "Image16.jpg", 1503 | "filepath": "/dist/files/images/2022/12/27/1672128273496-997340.jpg" 1504 | } 1505 | ] 1506 | }, 1507 | "_id": "63ad11e3d7261f57cfbb4566", 1508 | "title": "Backend dev with Nodejs", 1509 | "excerpt": "You will learn how to use some advanced tools such as Nodejs work", 1510 | "content": "Here is I have to describe tutorial detail", 1511 | "price": 98000, 1512 | "category": [ 1513 | { 1514 | "_id": "63b12fb1ff0cf3e6dcfd1d8f", 1515 | "name": "GraphQL", 1516 | "url": "graphql" 1517 | } 1518 | ], 1519 | "tag": [], 1520 | "meta": "63ad11e3d7261f57cfbb4565", 1521 | "owner": "63aaa69e0c832c33351fb77e", 1522 | "comments": [], 1523 | "createdAt": "2022-12-29T04:04:51.815Z", 1524 | "updatedAt": "2023-01-01T09:45:09.732Z", 1525 | "url": "backend-dev-with-nodejs-58949-1672286691815", 1526 | "__v": 4 1527 | }, 1528 | { 1529 | "images": { 1530 | "main": { 1531 | "_id": "63aaa7110c832c33351fb79e", 1532 | "name": "Image16.jpg", 1533 | "filepath": "/dist/files/images/2022/12/27/1672128273496-997340.jpg" 1534 | }, 1535 | "gallery": [ 1536 | { 1537 | "_id": "63c388b34c7daae474b9b615", 1538 | "name": "6183d38056d48692be8226c6be39f063.png", 1539 | "filepath": "/dist/files/images/2023/1/15/1673758899966-6809644.png" 1540 | }, 1541 | { 1542 | "_id": "63aaa7110c832c33351fb79e", 1543 | "name": "Image16.jpg", 1544 | "filepath": "/dist/files/images/2022/12/27/1672128273496-997340.jpg" 1545 | } 1546 | ] 1547 | }, 1548 | "_id": "63c4dc6213d8575602ccf5df", 1549 | "title": "Reactjs Study Guide", 1550 | "excerpt": "short description to introduce this product", 1551 | "content": "complete description and detail about this product", 1552 | "price": 124000, 1553 | "url": "reactjs-guide", 1554 | "category": [ 1555 | { 1556 | "_id": "63b12f67ff0cf3e6dcfd1d79", 1557 | "name": "Frontend Dev", 1558 | "url": "frontend" 1559 | } 1560 | ], 1561 | "tag": [ 1562 | { 1563 | "_id": "63bbbe58bae680a81673f7cd", 1564 | "name": "ReduxJs Posts", 1565 | "url": "redux" 1566 | }, 1567 | { 1568 | "_id": "63c3aee04c7daae474b9b65c", 1569 | "name": "Nodejs Posts", 1570 | "url": "nodejs" 1571 | } 1572 | ], 1573 | "meta": "63c4dc6213d8575602ccf5de", 1574 | "owner": "63c23094cb76f81556b2df3a", 1575 | "createdAt": "2023-01-16T05:10:58.314Z", 1576 | "updatedAt": "2023-01-16T05:10:58.314Z", 1577 | "__v": 0 1578 | } 1579 | ], 1580 | "message": "Products found" 1581 | } 1582 | ``` 1583 | ### ویرایش محصول 1584 | 1585 | برای ویرایش و بروز رسانی یک محصول به آدرس زیر با متد Patch ریکوست ارسال کنید: 1586 | 1587 | PATCH: {{URL}}/prod/update/prodId 1588 | 1589 | | فیلد | نوع | توضیحات | 1590 | | :---: | :---: | ---: | 1591 | | title | string | عنوان محصول | 1592 | | excerpt | string | توضیحات مختصر درباره محصول | 1593 | | content | string| توضیحات کامل محصول به همراه جزئیات | 1594 | | price | number | قیمت محصول | 1595 | | url | string | آدرس لینک مستقیم محصول | 1596 | | images | ObjectId | شامل: تصویر اصلی (main) و گالری تصاویر ([]Gallery) | 1597 | | category | []ObjectId | آرایه ای از شناسه دسته ها | 1598 | | tag | []ObjectId | آرایه ای از شناسه برچسب ها | 1599 | | owner | ObjectId | شناسه مالک جدید | 1600 | | meta | meta| شامل: title – description – keyphrase | 1601 | 1602 | 1603 | نمونه ریکوست: 1604 | 1605 | {{URL}}/prod/update/63aaa7dc465015f29b89fee1 1606 | 1607 | ``` json 1608 | { 1609 | "title": "Backend Dev with Nodejs", 1610 | "content":"Complete description and detail about Nodejs and backend development", 1611 | "images": { 1612 | "main": "63bcf356ce90d6dbda7696b0", 1613 | "gallery": [ 1614 | "63aaa7110c832c33351fb79e" 1615 | ] 1616 | }, 1617 | "url":"nodejs-backend-dev" 1618 | } 1619 | ``` 1620 | 1621 | نمونه پاسخ: 1622 | 1623 | ``` json 1624 | { 1625 | "status": 200, 1626 | "data": { 1627 | "product": { 1628 | "images": { 1629 | "main": "63bcf356ce90d6dbda7696b0", 1630 | "gallery": [ 1631 | "63aaa7110c832c33351fb79e" 1632 | ] 1633 | }, 1634 | "_id": "63aaa7dc465015f29b89fee1", 1635 | "title": "Backend Dev with Nodejs", 1636 | "excerpt": "You will learn how to use some advanced tools such as Nodejs work", 1637 | "content": "Complete description and detail about Nodejs and backend development", 1638 | "price": 98000, 1639 | "category": [ 1640 | "63b12f89ff0cf3e6dcfd1d83" 1641 | ], 1642 | "tag": [], 1643 | "meta": "63bbcec3ee1416b96dbf8f53", 1644 | "owner": "63aaa69e0c832c33351fb77e", 1645 | "comments": [], 1646 | "createdAt": "2022-12-27T08:07:56.128Z", 1647 | "updatedAt": "2023-01-16T05:28:21.863Z", 1648 | "url": "nodejs-backend-dev", 1649 | "__v": 2 1650 | }, 1651 | "meta": { 1652 | "_id": "63bbcec3ee1416b96dbf8f53", 1653 | "keyphrase": [], 1654 | "link": "63aaa7dc465015f29b89fee1", 1655 | "__v": 0 1656 | } 1657 | }, 1658 | "message": "Your product updated." 1659 | } 1660 | ``` 1661 | 1662 | 1663 | 1664 | ### افزودن مکان 1665 | 1666 | برای افزودن مکان، (کشور/ایالت|استان/شهر) به آدرس زیر با متد Post ریکوست ارسال کنید: 1667 | 1668 | POST: {{URL}}loca/add 1669 | 1670 | | فیلد | نوع | توضیحات | 1671 | | :---: | :---: | ---: | 1672 | | type* | string | میتواند: COUNTRY – PROVESTATE - CITY | 1673 | | name | string | نام کشور یا استان/ایالت یا شهر | 1674 | | url | string | لینک و یا آدرس | 1675 | | parent | ObjectId | برای انواع CITY و PROVESTATE باید مقدار والد را مشخص کنید | 1676 | 1677 | 1678 | نکته: برای ایجاد کشور مقدار COUNTRY باید وارد شود، برای ایجاد ایالت یا استان باید مقدار PROVESTATE وارد شود و برای ایجاد شهر باید مقدار CITY وارد شود. (فقط حروف بزرگ) 1679 | 1680 | به جز کشور برای استان ها و یا ایالت ها و شهر ها باید مقدار والد آنرا مشخص کنید. مثلا برای ایجاد شهر باید شناسه استان والد آن شهر را وارد کنید و یا برای استان باید شناسه کشور والد آن استان را مشخص کنید. 1681 | 1682 | نمونه ریکوست افزودن کشور: 1683 | 1684 | ``` json 1685 | { 1686 | "type": "COUNTRY", 1687 | "name": "Iran", 1688 | "url": "iran" 1689 | } 1690 | ``` 1691 | نمونه پاسخ: 1692 | 1693 | ``` json 1694 | { 1695 | "status": 200, 1696 | "data": { 1697 | "name": "Iran", 1698 | "url": "iran", 1699 | "_id": "63c503de7940e7743cce895d", 1700 | "__v": 0 1701 | }, 1702 | "message": "Location added." 1703 | } 1704 | ``` 1705 | نمونه ریکوست افزودن استان: 1706 | 1707 | ``` json 1708 | { 1709 | "type": "PROVESTATE", 1710 | "name": "Golestan", 1711 | "url": "golestan", 1712 | "parent": "63c503de7940e7743cce895d" 1713 | } 1714 | ``` 1715 | نمونه پاسخ: 1716 | 1717 | ``` json 1718 | { 1719 | "status": 200, 1720 | "data": { 1721 | "name": "Golestan", 1722 | "url": "golestan", 1723 | "parent": "63c503de7940e7743cce895d", 1724 | "_id": "63c5048e7940e7743cce8961", 1725 | "__v": 0 1726 | }, 1727 | "message": "Location added." 1728 | } 1729 | ``` 1730 | نمونه ریکوست افزودن شهر: 1731 | 1732 | ``` json 1733 | { 1734 | "type": "CITY", 1735 | "name": "Gorgan", 1736 | "url": "gorgan", 1737 | "parent": "63c5048e7940e7743cce8961" 1738 | } 1739 | ``` 1740 | نمونه پاسخ: 1741 | 1742 | ``` json 1743 | { 1744 | "status": 200, 1745 | "data": { 1746 | "name": "Gorgan", 1747 | "url": "gorgan", 1748 | "parent": "63c5048e7940e7743cce8961", 1749 | "_id": "63c505047940e7743cce8965", 1750 | "__v": 0 1751 | }, 1752 | "message": "Location added." 1753 | } 1754 | ``` 1755 | ### فهرست گیری از کشور ها 1756 | 1757 | برای فهرست گرفتن از کلیه کشور ها به آدرس زیر با متد Get ریکوست ارسال کنید: 1758 | 1759 | GET: {{URL}}/loca/list 1760 | 1761 | ``` json 1762 | { 1763 | "status": 200, 1764 | "data": [ 1765 | { 1766 | "_id": "63b7d68507808f0582fe2000", 1767 | "name": "USA", 1768 | "url": "usa", 1769 | "__v": 0 1770 | }, 1771 | { 1772 | "_id": "63c503de7940e7743cce895d", 1773 | "name": "Iran", 1774 | "url": "iran", 1775 | "__v": 0 1776 | } 1777 | ], 1778 | "message": "Country found." 1779 | } 1780 | ``` 1781 | 1782 | ### نمایش مکان 1783 | 1784 | برای دریافت جزئیات و استان ها و شهر های زیر مجموع یک مکان به آدرس زیر با متد Get ریکوست ارسال کنید: 1785 | 1786 | GET: {{URL}}/loca/get/locationId 1787 | 1788 | | فیلد | نوع | توضیحات | 1789 | | :---: | :---: | ---: | 1790 | | type* | string | میتواند: COUNTRY – PROVESTATE - CITY | 1791 | 1792 | نمونه ریکوست: 1793 | 1794 | {{URL}}/loca/get/63c503de7940e7743cce895d 1795 | 1796 | ``` json 1797 | { 1798 | "type": "COUNTRY" 1799 | } 1800 | ``` 1801 | نمونه پاسخ: 1802 | 1803 | ``` json 1804 | { 1805 | "status": 200, 1806 | "data": { 1807 | "country": { 1808 | "_id": "63c503de7940e7743cce895d", 1809 | "name": "Iran", 1810 | "url": "iran", 1811 | "__v": 0 1812 | }, 1813 | "provState": [ 1814 | { 1815 | "_id": "63c5048e7940e7743cce8961", 1816 | "name": "Golestan", 1817 | "url": "golestan", 1818 | "parent": "63c503de7940e7743cce895d", 1819 | "__v": 0 1820 | } 1821 | ] 1822 | }, 1823 | "message": "Country found" 1824 | } 1825 | ``` 1826 | ### بروز رسانی یک مکان 1827 | 1828 | برای ویرایش و بروز رسانی یک مکان به آدرس زیر با متد Patch یک ریکوست ارسال کنید: 1829 | 1830 | PATCH: {{URL}}/loca/edit/locationId 1831 | 1832 | | فیلد | نوع | توضیحات | 1833 | | :---: | :---: | ---: | 1834 | | type* | string | میتواند: COUNTRY – PROVESTATE - CITY | 1835 | | name | string | نام کشور یا استان/ایالت یا شهر | 1836 | | url | string | لینک و یا آدرس | 1837 | | parent | ObjectId | برای انواع CITY و PROVESTATE باید مقدار والد را مشخص کنید | 1838 | 1839 | نمونه ریکوست: 1840 | 1841 | {{URL}}/loca/edit/63c505047940e7743cce8965 1842 | 1843 | ``` json 1844 | { 1845 | "type": "CITY", 1846 | "url": "gorgan", 1847 | "parent": "63c5048e7940e7743cce8961" 1848 | } 1849 | ``` 1850 | نمونه پاسخ: 1851 | 1852 | ``` json 1853 | { 1854 | "status": 200, 1855 | "data": { 1856 | "_id": "63c505047940e7743cce8965", 1857 | "name": "Gorgan", 1858 | "url": "gorgan", 1859 | "parent": "63c5048e7940e7743cce8961", 1860 | "__v": 0 1861 | }, 1862 | "message": "Location updated." 1863 | } 1864 | ``` 1865 | 1866 | ### حذف یک مکان 1867 | 1868 | برای حذف یک مکان به آدرس زیر با متد Delete ریکوست ارسال کنید: 1869 | 1870 | DELETE: {{URL}}/loca/remove/locationId 1871 | 1872 | | فیلد | نوع | توضیحات | 1873 | | :---: | :---: | ---: | 1874 | | type* | string | میتواند: COUNTRY – PROVESTATE - CITY | 1875 | 1876 | نمونه ریکوست: 1877 | 1878 | {{URL}}/loca/remove/63b90bdbe496b54dcbb636f2 1879 | 1880 | ``` json 1881 | { 1882 | "status": 200, 1883 | "data": { 1884 | "_id": "63b90bdbe496b54dcbb636f2", 1885 | "name": "Isfahan", 1886 | "url": "isfahan", 1887 | "parent": "63b7d6bf663b803a8b3c9f79", 1888 | "__v": 0 1889 | }, 1890 | "message": "This location id: 63b90bdbe496b54dcbb636f2 deleted!" 1891 | } 1892 | ``` 1893 | 1894 | ### حذف محصول 1895 | 1896 | برای حذف یک محصول به آدرس زیر با متد Delete ریکوست ارسال کنید: 1897 | 1898 | DELETE: {{URL}}/prod/delete/prodId 1899 | 1900 | نمونه ریکوست: 1901 | 1902 | {{URL}}/prod/delete/63aaa7dc465015f29b89fee1 1903 | 1904 | نمونه پاسخ: 1905 | 1906 | ``` json 1907 | { 1908 | "status": 200, 1909 | "message": "The product deleted!", 1910 | "data": { 1911 | "images": { 1912 | "main": "63bcf356ce90d6dbda7696b0", 1913 | "gallery": [ 1914 | "63aaa7110c832c33351fb79e" 1915 | ] 1916 | }, 1917 | "_id": "63aaa7dc465015f29b89fee1", 1918 | "title": "Backend Dev with Nodejs", 1919 | "excerpt": "You will learn how to use some advanced tools such as Nodejs work", 1920 | "content": "Complete description and detail about Nodejs and backend development", 1921 | "price": 98000, 1922 | "category": [ 1923 | "63b12f89ff0cf3e6dcfd1d83" 1924 | ], 1925 | "tag": [], 1926 | "meta": "63bbcec3ee1416b96dbf8f53", 1927 | "owner": "63aaa69e0c832c33351fb77e", 1928 | "comments": [], 1929 | "createdAt": "2022-12-27T08:07:56.128Z", 1930 | "updatedAt": "2023-01-16T05:28:21.863Z", 1931 | "url": "nodejs-backend-dev", 1932 | "__v": 2 1933 | } 1934 | } 1935 | ``` 1936 | 1937 | ### افزودن به سبد خرید 1938 | 1939 | برای اضافه کردن یک محصول بع سبد خرید باید شناسه محصول را با متد Post به آدرس زیر ارسال کنید: 1940 | 1941 | POST: {{URL}}/cart/add 1942 | 1943 | | فیلد | نوع | توضیحات | 1944 | | :---: | :---: | ---: | 1945 | | prodId | string | شناسه محصول | 1946 | 1947 | نمونه ریکوست: 1948 | ``` json 1949 | { 1950 | "prodId": "63aaa7dc465015f29b89fee1" 1951 | } 1952 | ``` 1953 | نمونه پاسخ: 1954 | 1955 | ``` json 1956 | { 1957 | "status": 200, 1958 | "data": { 1959 | "userId": "63c23094cb76f81556b2df3a", 1960 | "list": [ 1961 | { 1962 | "prodId": "63c4dc6213d8575602ccf5df", 1963 | "quantity": 1, 1964 | "price": 124000, 1965 | "_id": "63c4eef60257c81a33d4269e" 1966 | } 1967 | ], 1968 | "payment": { 1969 | "state": "READY", 1970 | "date": 1673846143351 1971 | }, 1972 | "amount": 124000, 1973 | "_id": "63c4eef60257c81a33d4269d", 1974 | "__v": 0 1975 | }, 1976 | "message": "A new cart created." 1977 | } 1978 | ``` 1979 | نکته: با تکرار ارسال یک ریکوست تکراری فقط تعداد Quantity همان محصول در سبد خرید افزایش پیدا میکند. 1980 | 1981 | ### نمایش محتوای سبد خرید 1982 | 1983 | برای مشاهده محصولات افزوده شده به سبد خرید به آدرس زیر با متد Get ریکوست ارسال کنید: 1984 | 1985 | GET: {{URL}}/cart/get 1986 | 1987 | نمونه پاسخ: 1988 | 1989 | ``` json 1990 | { 1991 | "status": 200, 1992 | "data": { 1993 | "payment": { 1994 | "state": "READY", 1995 | "date": 1673846143351 1996 | }, 1997 | "_id": "63c4eef60257c81a33d4269d", 1998 | "userId": "63c23094cb76f81556b2df3a", 1999 | "list": [ 2000 | { 2001 | "prodId": "63c4dc6213d8575602ccf5df", 2002 | "quantity": 1, 2003 | "price": 124000, 2004 | "_id": "63c4eef60257c81a33d4269e" 2005 | } 2006 | ], 2007 | "amount": 124000, 2008 | "__v": 0 2009 | }, 2010 | "message": "Cart found." 2011 | } 2012 | ``` 2013 | ### بروز رسانی سبد خرید 2014 | 2015 | برای بروز رسانی سبد خرید به آدرس زیر با متد Patch ریکوست ارسال کنید: 2016 | 2017 | PATCH: {{URL}}/cart/update 2018 | 2019 | | فیلد | نوع | توضیحات | 2020 | | :---: | :---: | ---: | 2021 | | list | array | شامل محصولات سبد خرید: شناسه محصول prodId و تعداد محصول quantity | 2022 | 2023 | نکته: ریکوست ارسالی شامل یک آرایه از کلیه محصولات به همراه مقادیر جدول بالا میباشد. 2024 | 2025 | نمونه ریکوست: 2026 | 2027 | ``` json 2028 | { 2029 | "list": [ 2030 | { 2031 | "prodId": "63c4dc6213d8575602ccf5df", 2032 | "quantity": 1 2033 | }, 2034 | { 2035 | "prodId": "63ad11e3d7261f57cfbb4566", 2036 | "quantity": 2 2037 | } 2038 | ] 2039 | } 2040 | ``` 2041 | نمونه پاسخ: 2042 | 2043 | ``` json 2044 | { 2045 | "status": 200, 2046 | "data": { 2047 | "payment": { 2048 | "state": "READY", 2049 | "date": 1673846143351 2050 | }, 2051 | "_id": "63c4eef60257c81a33d4269d", 2052 | "userId": "63c23094cb76f81556b2df3a", 2053 | "list": [ 2054 | { 2055 | "prodId": "63c4dc6213d8575602ccf5df", 2056 | "quantity": 1, 2057 | "price": 124000, 2058 | "_id": "63c4eef60257c81a33d4269e" 2059 | }, 2060 | { 2061 | "prodId": "63ad11e3d7261f57cfbb4566", 2062 | "quantity": 2, 2063 | "price": 98000, 2064 | "_id": "63c4f1870257c81a33d426a9" 2065 | } 2066 | ], 2067 | "amount": 320000, 2068 | "__v": 1 2069 | }, 2070 | "message": "Cart updated." 2071 | } 2072 | ``` 2073 | ### حذف سبد خرید 2074 | 2075 | برای خالی کردن سبد خرید به آدرس زیر با متد Delete ریکوست ارسال کنید: 2076 | 2077 | DELET: {{URL}}/cart/delete 2078 | 2079 | نمونه پاسخ: 2080 | 2081 | ``` json 2082 | { 2083 | "status": 200, 2084 | "message": "Cart removed!" 2085 | } 2086 | ``` 2087 | 2088 | ### پرداخت آنلاین 2089 | 2090 | برای پرداخت آنلاین به آدرس زیر با متد Post ریکوست ارسال کنید: 2091 | 2092 | POST: {{URL}}/paym/payment 2093 | 2094 | نمونه پاسخ: 2095 | 2096 | ``` json 2097 | { 2098 | "status": 200, 2099 | "data": { 2100 | "info": { 2101 | "payment": { 2102 | "authority": "A00000000000000000000000000403047345", 2103 | "code": 100, 2104 | "state": "PEDNDING", 2105 | "date": 1674015932872 2106 | }, 2107 | "_id": "63c62325447dd312c35662fd", 2108 | "userId": "63c23094cb76f81556b2df3a", 2109 | "list": [ 2110 | { 2111 | "prodId": "63ad11e3d7261f57cfbb4566", 2112 | "quantity": 1, 2113 | "price": 98000, 2114 | "_id": "63c62325447dd312c35662fe" 2115 | } 2116 | ], 2117 | "amount": 98000, 2118 | "__v": 0 2119 | }, 2120 | "redirect": "https://www.zarinpal.com/pg/StartPay/A00000000000000000000000000403047345" 2121 | } 2122 | } 2123 | ``` 2124 | 2125 | بعد از دریافت پاسخ، کاربر را به مقدار ریدایرکت منتقل کنید. در صورت موفق بودن تراکنش مقدار Status مساوی OK میشود و در غیر این صورت مقدار آن NOK باز می‌گردد. 2126 | 2127 | نمونه تراکنش موفق که کاربر از طرف درگاه پرداخت به فرانت منتقل شده است: 2128 | 2129 | {{URL}}?Authority=A00000000000000000000000000403047345&Status=OK 2130 | 2131 | نمونه تراکنش ناموفق که کاربر از طرف درگاه پرداخت به فرانت منتقل شده است: 2132 | 2133 | {{URL}}?Authority=A00000000000000000000000000403047345&Status=NOK 2134 | 2135 | نکته: در حال حاضر تنها درگاه پرداخت تنظیم شده زرین پال می‌باشد. 2136 | 2137 | در صورت خالی بودن سبد خرید: 2138 | 2139 | 2140 | تایید پرداخت 2141 | 2142 | برای بررسی پرداخت انجام شده به آدرس زیر ریکوست خود را با متد POST ارسال کنید: 2143 | 2144 | POST: {{URL}}/paym/checkout 2145 | 2146 | | فیلد | نوع | توضیحات | 2147 | | :---: | :---: | ---: | 2148 | | Authority | string | شناسه پرداخت | 2149 | | Status | string | مقدار OK یا NOK | 2150 | 2151 | در صورت دریافت OK مقدار OK را بازگردانید و در صورت دریافت مقدار NOK همان را بازگردانید. 2152 | 2153 | ریکوست نمونه: 2154 | 2155 | ``` json 2156 | { 2157 | "Authority":"A00000000000000000000000000401330946", 2158 | "Status":"OK" 2159 | } 2160 | ``` 2161 | 2162 | نمونه پاسخ: 2163 | 2164 | ``` json 2165 | { 2166 | "status": 200, 2167 | "data": { 2168 | "userId": "63c23094cb76f81556b2df3a", 2169 | "list": [ 2170 | { 2171 | "prodId": "63ad11e3d7261f57cfbb4566", 2172 | "quantity": 1, 2173 | "price": 98000, 2174 | "_id": "63c62325447dd312c35662fe" 2175 | } 2176 | ], 2177 | "payment": { 2178 | "authority": "A00000000000000000000000000403047345", 2179 | "code": 100, 2180 | "state": "SUCCESS", 2181 | "date": 1674015932872 2182 | }, 2183 | "amount": 98000, 2184 | "_id": "63c6289f447dd312c3566309", 2185 | "createdAt": "2023-01-17T04:48:31.741Z", 2186 | "updatedAt": "2023-01-17T04:48:31.741Z", 2187 | "__v": 0 2188 | }, 2189 | "message": "The payment is done." 2190 | } 2191 | ``` 2192 | 2193 | ### فهرست گیری از سفارش ها 2194 | 2195 | برای فهرست گیری از کلیه سفارش های انجام شده به آدرس زیر با متد Get ریکوست ارسال کنید: 2196 | 2197 | GET: {{URL}}/orde/list 2198 | 2199 | نمونه پاسخ: 2200 | 2201 | ``` json 2202 | { 2203 | "status": 200, 2204 | "data": [ 2205 | { 2206 | "payment": { 2207 | "authority": "A00000000000000000000000000398323748", 2208 | "code": 100, 2209 | "state": "SUCCESS" 2210 | }, 2211 | "_id": "63abdcc007c4f8af45cf4a0d", 2212 | "userId": "63aaa69e0c832c33351fb77e", 2213 | "list": [ 2214 | { 2215 | "prodId": "63aaa7dc465015f29b89fee1", 2216 | "quantity": 2, 2217 | "price": 98000, 2218 | "_id": "63aaa87bff14249bda05adfb" 2219 | } 2220 | ], 2221 | "amount": 196000, 2222 | "__v": 0 2223 | }, 2224 | { 2225 | "payment": { 2226 | "authority": "A00000000000000000000000000401330946", 2227 | "code": 100, 2228 | "state": "SUCCESS", 2229 | "date": 1673425068471 2230 | }, 2231 | "_id": "63bd2029c9927249907a709d", 2232 | "userId": "63b51152f7daee2dfbd6d28a", 2233 | "list": [ 2234 | { 2235 | "prodId": "63ad11e3d7261f57cfbb4566", 2236 | "quantity": 1, 2237 | "price": 98000, 2238 | "_id": "63ba6b5bce97ace49c2afa1b" 2239 | }, 2240 | { 2241 | "prodId": "63aaa7dc465015f29b89fee1", 2242 | "quantity": 1, 2243 | "price": 98000, 2244 | "_id": "63ba6b5ece97ace49c2afa22" 2245 | } 2246 | ], 2247 | "amount": 196000, 2248 | "createdAt": "2023-01-10T08:22:01.895Z", 2249 | "updatedAt": "2023-01-10T08:22:01.895Z", 2250 | "__v": 0 2251 | } 2252 | ], 2253 | "message": "Orders found" 2254 | } 2255 | ``` 2256 | 2257 | ### دریافت جزئیات سفارش 2258 | 2259 | برای دریافت جزئیات سفارش به آدرس زیر با متد Get ریکوست ارسال کنید: 2260 | 2261 | GET: {{GET}}/orde/get/orderId 2262 | 2263 | نمونه ریکوست: 2264 | 2265 | {{URL}}/orde/get/63c6289f447dd312c3566309 2266 | 2267 | نمونه پاسخ: 2268 | 2269 | ``` json 2270 | { 2271 | "status": 200, 2272 | "data": { 2273 | "payment": { 2274 | "authority": "A00000000000000000000000000403047345", 2275 | "code": 100, 2276 | "state": "SUCCESS", 2277 | "date": 1674015932872 2278 | }, 2279 | "_id": "63c6289f447dd312c3566309", 2280 | "userId": { 2281 | "_id": "63c23094cb76f81556b2df3a", 2282 | "name": "Mohammad Barghamadi", 2283 | "phone": "9304551004" 2284 | }, 2285 | "list": [ 2286 | { 2287 | "prodId": "63ad11e3d7261f57cfbb4566", 2288 | "quantity": 1, 2289 | "price": 98000, 2290 | "_id": "63c62325447dd312c35662fe" 2291 | } 2292 | ], 2293 | "amount": 98000, 2294 | "createdAt": "2023-01-17T04:48:31.741Z", 2295 | "updatedAt": "2023-01-17T04:48:31.741Z", 2296 | "__v": 0 2297 | }, 2298 | "message": "Order found." 2299 | } 2300 | ``` 2301 | 2302 | ### ایجاد یا افزودن آدرس 2303 | 2304 | برای افزودن آدرس پستی مشتری به آدرس زیر با متد Post ریکوست ارسال کنید: 2305 | 2306 | POST: {{URL}}/addr/add 2307 | 2308 | | فیلد | نوع | توضیحات | 2309 | | :---: | :---: | ---: | 2310 | | country | ObjectId | شناسه کشور انتخاب شده | 2311 | | Status | string | مقدار OK یا NOK | 2312 | | proveState | ObjectId | شناسه استان انتخاب شده | 2313 | | city | ObjectId | شناسه شهر انتخاب شده | 2314 | | address | string | جزئیات آدرس: خیابان – محله – کوچه | 2315 | | postalcode | string | کد پستی دریافت کننده محصول | 2316 | 2317 | نمونه ریکوست: 2318 | 2319 | ``` json 2320 | { 2321 | "country": "63c503de7940e7743cce895d", 2322 | "provState": "63c5048e7940e7743cce8961", 2323 | "city": "63c505047940e7743cce8965", 2324 | "address": "Soroush 6 entehayeh kheyaban", 2325 | "postalcode": "49187245365" 2326 | } 2327 | ``` 2328 | 2329 | نمونه پاسخ: 2330 | 2331 | ``` json 2332 | { 2333 | "status": 200, 2334 | "data": { 2335 | "country": "63c503de7940e7743cce895d", 2336 | "provState": "63c5048e7940e7743cce8961", 2337 | "city": "63c505047940e7743cce8965", 2338 | "address": "Soroush 6 entehayeh kheyaban", 2339 | "postalcode": "49187245365", 2340 | "userId": "63c23094cb76f81556b2df3a", 2341 | "_id": "63c639c0634ef0881f9a3567", 2342 | "__v": 0 2343 | }, 2344 | "message": "Address added." 2345 | } 2346 | ``` 2347 | 2348 | ### فهرست گیری از آدرس‌ها 2349 | 2350 | برای فهرست گیری از آدرس های پستی خریدار به آدرس زیر با متد Get ریکوست ارسال کنید: 2351 | 2352 | GET: {{URL}}/addr/list 2353 | 2354 | نمونه ریکوست: 2355 | 2356 | ``` json 2357 | { 2358 | "status": 200, 2359 | "data": [ 2360 | { 2361 | "_id": "63c639c0634ef0881f9a3567", 2362 | "address": "Soroush 6 entehayeh kheyaban" 2363 | } 2364 | ], 2365 | "message": "Address found." 2366 | } 2367 | ``` 2368 | 2369 | ### دریافت جزئیات آدرس 2370 | 2371 | برای دریافت جزئیات آدرس پستی یک کاربر به ادرس زیر با متد Get ریکوست ارسال کنید: 2372 | 2373 | GET: {{URL}}/addr/get/addressId 2374 | 2375 | نمونه ریکوست: 2376 | 2377 | {{URL}}/addr/get/63c639c0634ef0881f9a3567 2378 | 2379 | نمونه پاسخ: 2380 | 2381 | ``` json 2382 | { 2383 | "status": 200, 2384 | "data": { 2385 | "_id": "63c639c0634ef0881f9a3567", 2386 | "country": { 2387 | "_id": "63c503de7940e7743cce895d", 2388 | "name": "Iran" 2389 | }, 2390 | "provState": { 2391 | "_id": "63c5048e7940e7743cce8961", 2392 | "name": "Golestan" 2393 | }, 2394 | "city": { 2395 | "_id": "63c505047940e7743cce8965", 2396 | "name": "Gorgan" 2397 | }, 2398 | "address": "Soroush 6 entehayeh kheyaban", 2399 | "postalcode": "49187245365", 2400 | "userId": "63c23094cb76f81556b2df3a", 2401 | "__v": 0 2402 | }, 2403 | "message": "Address found." 2404 | } 2405 | ``` 2406 | ### ویرایش آدرس 2407 | 2408 | برای ویرایش آدرس پستی مشتری به آدرس زیر با متد Patch ریکوست ارسال کنید: 2409 | 2410 | PATCH: {{URL}}/addr/edit/addressId 2411 | 2412 | | فیلد | نوع | توضیحات | 2413 | | :---: | :---: | ---: | 2414 | | country | ObjectId | شناسه کشور انتخاب شده | 2415 | | Status | string | مقدار OK یا NOK | 2416 | | proveState | ObjectId | شناسه استان انتخاب شده | 2417 | | city | ObjectId | شناسه شهر انتخاب شده | 2418 | | address | string | جزئیات آدرس: خیابان – محله – کوچه | 2419 | | postalcode | string | کد پستی دریافت کننده محصول | 2420 | 2421 | نمونه ریکوست: 2422 | 2423 | {{URL}}/addr/edit/63c639c0634ef0881f9a3567 2424 | ``` json 2425 | { 2426 | "postalcode": "471665892" 2427 | } 2428 | ``` 2429 | 2430 | نمونه پاسخ: 2431 | 2432 | ``` json 2433 | { 2434 | "status": 200, 2435 | "data": { 2436 | "_id": "63c639c0634ef0881f9a3567", 2437 | "country": "63c503de7940e7743cce895d", 2438 | "provState": "63c5048e7940e7743cce8961", 2439 | "city": "63c505047940e7743cce8965", 2440 | "address": "Soroush 6 entehayeh kheyaban", 2441 | "postalcode": "471665892", 2442 | "userId": "63c23094cb76f81556b2df3a", 2443 | "__v": 0 2444 | }, 2445 | "message": "Address updated." 2446 | } 2447 | ``` 2448 | ### حذف آدرس پستی 2449 | 2450 | برای حذف آدرس پستی مشتری به آدرس زیر با متد Delete ریکوست ارسال کنید: 2451 | 2452 | DELETE: {{URL}}/addr/delete/addressId 2453 | 2454 | نمونه ریکوست: 2455 | 2456 | {{URL}}/addr/delete/63c639c0634ef0881f9a3567 2457 | 2458 | نمونه پاسخ: 2459 | 2460 | ``` json 2461 | { 2462 | "status": 200, 2463 | "data": { 2464 | "_id": "63c639c0634ef0881f9a3567", 2465 | "country": "63c503de7940e7743cce895d", 2466 | "provState": "63c5048e7940e7743cce8961", 2467 | "city": "63c505047940e7743cce8965", 2468 | "address": "Soroush 6 entehayeh kheyaban", 2469 | "postalcode": "471665892", 2470 | "userId": "63c23094cb76f81556b2df3a", 2471 | "__v": 0 2472 | }, 2473 | "message": "Address deleted." 2474 | } 2475 | ``` 2476 | 2477 | ### افزودن به لیست علاقه مندی ها 2478 | 2479 | برای افزودن یک محصول به فهرست علاقه مندی های به آدرس زیر با متد Post ریکوست ارسال کنید: 2480 | 2481 | POST: {{URL}}/favo/add 2482 | 2483 | 2484 | | فیلد | نوع | توضیحات | 2485 | | :---: | :---: | ---: | 2486 | | prodId | ObjectId | شناسه محصول | 2487 | 2488 | نمونه ریکوست: 2489 | 2490 | ``` json 2491 | { 2492 | "prodId": "63ad11e3d7261f57cfbb4566" 2493 | } 2494 | ``` 2495 | 2496 | نمونه پاسخ: 2497 | 2498 | ``` json 2499 | { 2500 | "status": 200, 2501 | "message": "Item added to favorites." 2502 | } 2503 | ``` 2504 | نکته: برای حذف یک محصول از فهرست علاقه مندی ها کافیست همان ریکوست تکرار شود. 2505 | 2506 | ### نمایش فهرست علاقه مندی ها 2507 | 2508 | برای فهرست گرفتن از آیتم های موجود در علاقه مندی ها به آدرس زیر با متد Get ریکوست ارسال کنید: 2509 | 2510 | GET: {{URL}}/favo/list 2511 | 2512 | نمونه پاسخ: 2513 | 2514 | ``` json 2515 | { 2516 | "status": 200, 2517 | "data": { 2518 | "_id": "63c64d3a9294f4f5be73456a", 2519 | "userId": "63c23094cb76f81556b2df3a", 2520 | "list": [ 2521 | { 2522 | "prodId": { 2523 | "images": { 2524 | "main": "63aaa7110c832c33351fb79e" 2525 | }, 2526 | "_id": "63ad11e3d7261f57cfbb4566", 2527 | "title": "Backend dev with Nodejs", 2528 | "price": 98000, 2529 | "url": "backend-dev-with-nodejs-58949-1672286691815" 2530 | }, 2531 | "_id": "63c64d3a9294f4f5be73456b" 2532 | } 2533 | ], 2534 | "__v": 0 2535 | }, 2536 | "message": "Favorite list" 2537 | } 2538 | ``` 2539 | 2540 | ### خالی کردن فهرست علاقه مندی ها 2541 | 2542 | برای خالی نمودن کلیه ایتم های موجود در فهرست علاقه مندی ها به آدرس زیر با متد Delete ریکوست ارسال کنید: 2543 | 2544 | DELETE: {{URL}}/favo/clear 2545 | 2546 | نمونه پاسخ: 2547 | 2548 | ``` json 2549 | { 2550 | "status": 200, 2551 | "data": { 2552 | "_id": "63c64d3a9294f4f5be73456a", 2553 | "userId": "63c23094cb76f81556b2df3a", 2554 | "list": [], 2555 | "__v": 1 2556 | }, 2557 | "message": "Favorite list emptied" 2558 | } 2559 | ``` 2560 | 2561 | ### افزودن یا ثبت دیدگاه 2562 | 2563 | برای ثبت نظر به آدرس زیر با متد Post ریکوست ارسال کنید: 2564 | 2565 | POST: {{URL}}/cmnt/add 2566 | 2567 | | فیلد | نوع | توضیحات | 2568 | | :---: | :---: | ---: | 2569 | | name | string | نام کاربر درصورتیکه وارد برنامه نشده باشد | 2570 | | prodId | ObjectId | شناسه محصول | 2571 | | rating | number | عدد ۱ تا 5 | 2572 | | title | string | عنوان دیدگاه | 2573 | | description | string | توضیحات دیدگاه | 2574 | | email | string | آدرس ایمیل کاربر در صورتیکه وارد برنامه نشده باشد | 2575 | 2576 | نمونه ریکوست: 2577 | ``` json 2578 | { 2579 | "prodId": "63ad11e3d7261f57cfbb4566", 2580 | "rating": 4, 2581 | "title": "Best Nodejs course ever", 2582 | "description": "I don't know how to say thank you, but I can say Thanks so much." 2583 | } 2584 | ``` 2585 | نمونه پاسخ: 2586 | 2587 | ``` json 2588 | { 2589 | "status": 200, 2590 | "data": { 2591 | "name": "Mohammad Barghamadi", 2592 | "email": "mohammadbarghamadi@gmail.com", 2593 | "authorId": "63c23094cb76f81556b2df3a", 2594 | "prodId": "63ad11e3d7261f57cfbb4566", 2595 | "rating": 4, 2596 | "title": "Best Nodejs course ever", 2597 | "description": "I don't know how to say thank you, but I can say Thanks so much.", 2598 | "status": "PENDING", 2599 | "_id": "63c653c5a66636d4f0ff8db1", 2600 | "createdAt": "2023-01-17T07:52:37.158Z", 2601 | "updatedAt": "2023-01-17T07:52:37.158Z", 2602 | "__v": 0 2603 | }, 2604 | "message": "New comment added." 2605 | } 2606 | ``` 2607 | نمونه ریکوست برای زمانیکه کاربر ثبت نام نکرده و یا وارد برنامه نشده باشد: 2608 | 2609 | ``` json 2610 | { 2611 | "name": "Jim Kwik", 2612 | "email": "jimkwik@gmail.com", 2613 | "prodId": "63ad11e3d7261f57cfbb4566", 2614 | "rating": 5, 2615 | "title": "What a tutorial", 2616 | "description": "Best backend development you can find on whole Internet." 2617 | } 2618 | ``` 2619 | نمونه پاسخ: 2620 | 2621 | ``` json 2622 | { 2623 | "status": 200, 2624 | "data": { 2625 | "name": "Jim Kwik", 2626 | "email": "jimkwik@gmail.com", 2627 | "prodId": "63ad11e3d7261f57cfbb4566", 2628 | "rating": 5, 2629 | "title": "What a tutorial", 2630 | "description": "Best backend development you can find on whole Internet.", 2631 | "status": "PENDING", 2632 | "_id": "63c6574f772d2f9ce1268a34", 2633 | "createdAt": "2023-01-17T08:07:43.376Z", 2634 | "updatedAt": "2023-01-17T08:07:43.376Z", 2635 | "__v": 0 2636 | }, 2637 | "message": "New comment added." 2638 | } 2639 | ``` 2640 | ### نمایش دیدگاه‌های یک محصول 2641 | 2642 | برای فهرست گیری و نمایش کلیه دیدگاه های (تایید شده) یک محصول به آدرس زیر با متد Get ریکوست ارسال کنید: 2643 | 2644 | GET: {{URL}}/cmnt/show/prodId 2645 | 2646 | نمونه ریکوست: 2647 | 2648 | {{URL}}/cmnt/show/63ad11e3d7261f57cfbb4566 2649 | 2650 | نمونه پاسخ: 2651 | 2652 | ``` json 2653 | { 2654 | "status": 200, 2655 | "data": [ 2656 | { 2657 | "_id": "63c653c5a66636d4f0ff8db1", 2658 | "name": "Mohammad Barghamadi", 2659 | "email": "mohammadbarghamadi@gmail.com", 2660 | "rating": 4, 2661 | "title": "Best Nodejs course ever", 2662 | "description": "I don't know how to say thank you, but I can say Thanks so much." 2663 | }, 2664 | { 2665 | "_id": "63c6574f772d2f9ce1268a34", 2666 | "name": "Jim Kwik", 2667 | "email": "jimkwik@gmail.com", 2668 | "rating": 5, 2669 | "title": "What a tutorial", 2670 | "description": "Best backend development you can find on whole Internet." 2671 | } 2672 | ], 2673 | "message": "Product comments found." 2674 | } 2675 | ``` 2676 | 2677 | ### فهرست گیری از کلیه دیدگاه‌ها 2678 | 2679 | برای فهرست گیری از کلیه دیدگاه ها ثبت شده در سایت به آدرس زیر با متد Get ریکوست ارسال کنید: 2680 | 2681 | GET: {{URL}}/cmnt/list 2682 | 2683 | | فیلد | نوع | توضیحات | 2684 | | :---: | :---: | ---: | 2685 | | admin | boolean | درصورت true بودن فهرست کلیه دیدگاه ها ارسال میشود در غیر اینصورت فقط دیدگاه های فرد درخواست کنند نمایش داده میشود. | 2686 | 2687 | نمونه ریکوست برای دریافت همه دیدگاه ها به عنوان مدیر برنامه: 2688 | 2689 | ``` json 2690 | { 2691 | "admin":true 2692 | } 2693 | ``` 2694 | 2695 | نمونه پاسخ: 2696 | 2697 | ``` json 2698 | { 2699 | "status": 200, 2700 | "data": [ 2701 | { 2702 | "_id": "63bd17ca36ee070579b89cda", 2703 | "name": "Sharif Mohamed", 2704 | "email": "sharifmohamed@gmail.com", 2705 | "prodId": "63aaa7dc465015f29b89fee1", 2706 | "rating": 4, 2707 | "title": "Best Laptop ever", 2708 | "description": "I don't know how to say thank you, but I can say Thanks so much.", 2709 | "status": "APPROVED", 2710 | "createdAt": "2023-01-10T07:46:18.191Z", 2711 | "updatedAt": "2023-01-10T07:50:34.457Z", 2712 | "__v": 0 2713 | }, 2714 | { 2715 | "_id": "63c653c5a66636d4f0ff8db1", 2716 | "name": "Mohammad Barghamadi", 2717 | "email": "mohammadbarghamadi@gmail.com", 2718 | "authorId": "63c23094cb76f81556b2df3a", 2719 | "prodId": "63ad11e3d7261f57cfbb4566", 2720 | "rating": 4, 2721 | "title": "Best Nodejs course ever", 2722 | "description": "I don't know how to say thank you, but I can say Thanks so much.", 2723 | "status": "PENDING", 2724 | "createdAt": "2023-01-17T07:52:37.158Z", 2725 | "updatedAt": "2023-01-17T07:52:37.158Z", 2726 | "__v": 0 2727 | }, 2728 | { 2729 | "_id": "63c6574f772d2f9ce1268a34", 2730 | "name": "Jim Kwik", 2731 | "email": "jimkwik@gmail.com", 2732 | "prodId": "63ad11e3d7261f57cfbb4566", 2733 | "rating": 5, 2734 | "title": "What a tutorial", 2735 | "description": "Best backend development you can find on whole Internet.", 2736 | "status": "PENDING", 2737 | "createdAt": "2023-01-17T08:07:43.376Z", 2738 | "updatedAt": "2023-01-17T08:07:43.376Z", 2739 | "__v": 0 2740 | } 2741 | ], 2742 | "message": "Comments found." 2743 | } 2744 | ``` 2745 | 2746 | ### مشاهده یک دیدگاه 2747 | 2748 | برای مشاهده یک دیدگاه به آدرس زیر با متد Get ریکوست ارسال کنید: 2749 | 2750 | GET: {{URL}}/cmnt/get/commentId 2751 | 2752 | نمونه ریکوست: 2753 | 2754 | {{URL}}/cmnt/get/63bd15d14a35eb59ad50d235 2755 | 2756 | نمونه پاسخ: 2757 | 2758 | ``` json 2759 | { 2760 | "status": 200, 2761 | "data": { 2762 | "_id": "63bd15d14a35eb59ad50d235", 2763 | "name": "Mohammad Barghamadi", 2764 | "email": "mohammadbarghamadi@gmail.com", 2765 | "authorId": "63b51152f7daee2dfbd6d28a", 2766 | "prodId": "63aaa7dc465015f29b89fee1", 2767 | "rating": 5, 2768 | "title": "Amazing Product", 2769 | "description": "I really appricate for this nice product that you created for us, thank you and keep up good work.", 2770 | "status": "APPROVED", 2771 | "createdAt": "2023-01-10T07:37:53.982Z", 2772 | "updatedAt": "2023-01-10T07:51:31.509Z", 2773 | "__v": 0 2774 | }, 2775 | "message": "comment found." 2776 | } 2777 | ``` 2778 | ویرایش یک دیدگاه 2779 | 2780 | برای ویرایش و تایید و رد نمودن یک دیدگاه به آدرس زیر با متد Patch ریکوست ارسال کنید: 2781 | 2782 | PATCH: {{URL}}/cmnt/edit/commentId 2783 | 2784 | | فیلد | نوع | توضیحات | 2785 | | :---: | :---: | ---: | 2786 | | status | string | میتواند: PENDING – REJECTED - APPROVED | 2787 | | name | string | نام کاربر | 2788 | | prodId | ObjectId | شناسه محصول | 2789 | | rating | number | عدد ۱ تا 5 | 2790 | | title | string | عنوان دیدگاه | 2791 | | description | string | توضیحات دیدگاه | 2792 | | email | string | آدرس ایمیل کاربر | 2793 | 2794 | نمونه ریکوست: 2795 | 2796 | ``` json 2797 | { 2798 | "status":"APPROVED" 2799 | } 2800 | ``` 2801 | 2802 | نمونه پاسخ: 2803 | 2804 | ``` json 2805 | { 2806 | "status": 200, 2807 | "data": { 2808 | "_id": "63c6574f772d2f9ce1268a34", 2809 | "name": "Jim Kwik", 2810 | "email": "jimkwik@gmail.com", 2811 | "prodId": "63ad11e3d7261f57cfbb4566", 2812 | "rating": 5, 2813 | "title": "What a tutorial", 2814 | "description": "Best backend development you can find on whole Internet.", 2815 | "status": "APPROVED", 2816 | "createdAt": "2023-01-17T08:07:43.376Z", 2817 | "updatedAt": "2023-01-17T08:20:14.116Z", 2818 | "__v": 0 2819 | }, 2820 | "message": "Comment updated." 2821 | } 2822 | ``` 2823 | 2824 | ### حذف یک دیدگاه 2825 | 2826 | برای حذف یک دیدگاه به آدرس زیر با متد Delete ریکوست ارسال کنید: 2827 | 2828 | DELETE: {{URL}}/cmnt/delete/63c6574f772d2f9ce1268a34 2829 | 2830 | نمونه ریکوست: 2831 | 2832 | {{URL}}/cmnt/delete/63c6574f772d2f9ce1268a34 2833 | 2834 | نمونه پاسخ: 2835 | 2836 | ``` json 2837 | { 2838 | "status": 200, 2839 | "data": { 2840 | "_id": "63c6574f772d2f9ce1268a34", 2841 | "name": "Jim Kwik", 2842 | "email": "jimkwik@gmail.com", 2843 | "prodId": "63ad11e3d7261f57cfbb4566", 2844 | "rating": 5, 2845 | "title": "What a tutorial", 2846 | "description": "Best backend development you can find on whole Internet.", 2847 | "status": "APPROVED", 2848 | "createdAt": "2023-01-17T08:07:43.376Z", 2849 | "updatedAt": "2023-01-17T08:20:14.116Z", 2850 | "__v": 0 2851 | }, 2852 | "message": "Comment deleted." 2853 | } 2854 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecommerce", 3 | "version": "1.0.0", 4 | "description": "my first typescript usage to make an ecommerce app", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "start": "node dist/app.js", 10 | "dev": "nodemon dist/app.js" 11 | }, 12 | "author": "Mohammad Barghamadi", 13 | "license": "ISC", 14 | "dependencies": { 15 | "axios": "^1.2.1", 16 | "bcrypt": "^5.1.0", 17 | "cookie-parser": "^1.4.6", 18 | "cors": "^2.8.5", 19 | "express": "^4.18.2", 20 | "express-fileupload": "^1.4.0", 21 | "jsonwebtoken": "^8.5.1", 22 | "mongoose": "^6.8.0", 23 | "nodemailer": "^6.8.0" 24 | }, 25 | "devDependencies": { 26 | "@types/bcrypt": "^5.0.0", 27 | "@types/cookie-parser": "^1.4.3", 28 | "@types/cors": "^2.8.13", 29 | "@types/express": "^4.17.14", 30 | "@types/express-fileupload": "^1.4.1", 31 | "@types/jsonwebtoken": "^8.5.9", 32 | "@types/node": "^18.11.14", 33 | "@types/nodemailer": "^6.4.7", 34 | "dotenv": "^16.0.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | // import main modules 2 | import dotenv from 'dotenv' 3 | import express from 'express' 4 | import cors from 'cors' 5 | import cookie from 'cookie-parser' 6 | 7 | // connect to database 8 | import './db/connect.js' 9 | 10 | // import express error handler 11 | import errorHandler from './middlewares/error.js' 12 | import { events } from './utils/logger.js' 13 | 14 | // import main routes 15 | import userRoute from './routes/user.route.js' 16 | import prodRoute from './routes/product.route.js' 17 | import cateRoute from './routes/category.route.js' 18 | import tagsRoute from './routes/tag.route.js' 19 | import fileRoute from './routes/file.route.js' 20 | import cartRoute from './routes/cart.route.js' 21 | import ordeRoute from './routes/order.route.js' 22 | import paymRoute from './routes/payment.route.js' 23 | import favoRoute from './routes/favourite.route.js' 24 | import cmntRoute from './routes/comment.route.js' 25 | import locaRoute from './routes/location.route.js' 26 | import addrRoute from './routes/address.route.js' 27 | 28 | 29 | // configure environment variables 30 | dotenv.config() 31 | 32 | // setup express app 33 | const app = express() 34 | 35 | // Node and express Port setup 36 | const PORT = process.env.PORT || 8200 37 | 38 | // express main middlewares setup 39 | app.use(express.urlencoded({ extended: true })) // to parses urlencoded payloads 40 | app.use(express.json()) // to parses request body as json 41 | app.use(cookie()) // parse incoming cookies from header 42 | app.use(cors()) // let's frontend app to send api request without cors issues 43 | app.use(events) // write event to disk 44 | 45 | 46 | // here is I will import main apies routes 47 | app.use('/api/user', userRoute) 48 | app.use('/api/prod', prodRoute) 49 | app.use('/api/cate', cateRoute) 50 | app.use('/api/tags', tagsRoute) 51 | app.use('/api/file', fileRoute) 52 | app.use('/api/cart', cartRoute) 53 | app.use('/api/orde', ordeRoute) 54 | app.use('/api/paym', paymRoute) 55 | app.use('/api/favo', favoRoute) 56 | app.use('/api/cmnt', cmntRoute) 57 | app.use('/api/loca', locaRoute) 58 | app.use('/api/addr', addrRoute) 59 | 60 | // express error handler middleware 61 | app.use(errorHandler) 62 | 63 | const server = app.listen(PORT, () => console.log(`nodejs server start and running with express on port:${PORT}`)) 64 | process.on("unhandledRejection", (err, promise) => { 65 | console.log("Logged Error" + err) 66 | server.close(() => process.exit(1)) 67 | }) -------------------------------------------------------------------------------- /src/config/cors.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mohammadbarghamadi/ecommerce-api/2ad1da8b5ffb8ff9d00c8b045e64ae7e49b7c9b0/src/config/cors.ts -------------------------------------------------------------------------------- /src/config/regex.ts: -------------------------------------------------------------------------------- 1 | export const isValidEmail:RegExp = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/ 2 | export const isValidURL: RegExp = /^([a-zA-Z0-9-]){1,75}$/ -------------------------------------------------------------------------------- /src/controllers/address.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express"; 2 | import AddressModel from "../db/models/address.model.js"; 3 | import { ROLES } from "../middlewares/role.js"; 4 | import { isValidReq } from "../utils/validate.js"; 5 | 6 | 7 | // add an address /api/addr/add:post 8 | export const addAddressCtr: RequestHandler = async (req, res, next) => { 9 | 10 | const isValidRB = isValidReq(req.body, ['country', 'provState', 'city', 'address', 'postalcode']) 11 | if (!isValidRB) return res.json({ status: 400, message: 'Invalid field!' }) 12 | 13 | try { 14 | const address = new AddressModel({ ...req.body, userId: req.cred.user._id }) 15 | const data = await address.save() 16 | res.json({ status: 200, data, message: 'Address added.' }) 17 | 18 | } catch (e) { next(e) } 19 | 20 | } 21 | 22 | // get an address /api/addr/get:addressId:get 23 | export const getAddressCtr: RequestHandler = async (req, res, next) => { 24 | 25 | const _id = req.params.addressId 26 | 27 | try { 28 | let address 29 | if (req.cred.user.role <= ROLES.Seller) address = await AddressModel.findById(_id).populate({ 30 | path: 'country provState city', 31 | select: 'name' 32 | }) 33 | else address = await AddressModel.findOne({ _id, userId: req.cred.user._id }) 34 | if (!address) return res.status(404).json({ status: 404, message: `This address id:${_id} not found!` }) 35 | res.json({ status: 200, data: address, message: 'Address found.' }) 36 | 37 | } catch (e) { next(e) } 38 | 39 | } 40 | 41 | // get an address /api/addr/list 42 | export const lisAddressCtr: RequestHandler = async (req, res, next) => { 43 | 44 | try { 45 | const addresses = await AddressModel.find({ userId: req.cred.user._id }).select('address') 46 | if (!addresses.length) return res.status(404).json({ status: 404, message: `No addresses found!` }) 47 | res.json({ status: 200, data: addresses, message: 'Address found.' }) 48 | 49 | } catch (e) { next(e) } 50 | 51 | } 52 | 53 | // edit an address /api/addr/edit:addressId:patch 54 | export const ediAddressCtr: RequestHandler = async (req, res, next) => { 55 | 56 | const _id = req.params.addressId 57 | const isValidRB = isValidReq(req.body, ['country', 'provState', 'city', 'street', 'postalcode']) 58 | if (!isValidRB) return res.json({ status: 400, message: 'Invalid field!' }) 59 | 60 | try { 61 | let address: any 62 | if (req.cred.user.role <= ROLES.Seller) address = await AddressModel.findById(_id) 63 | else address = await AddressModel.findOne({ _id, userId: req.cred.user._id }) 64 | Object.keys(req.body).forEach(item => address[item] = req.body[item]) 65 | const data = await address.save() 66 | res.json({status: 200, data, message: 'Address updated.'}) 67 | } catch (e) { next(e) } 68 | 69 | } 70 | 71 | // remove an address /api/addr/remove:addressId:delete 72 | export const remAddressCtr: RequestHandler = async (req, res, next) => { 73 | 74 | const _id = req.params.addressId 75 | 76 | try { 77 | 78 | const address = await AddressModel.findByIdAndDelete(_id) 79 | if (!address) return res.status(404).json({ status: 404, message: `This address id: ${_id} not found!` }) 80 | res.json({ status: 200, data: address, message: 'Address deleted.' }) 81 | 82 | } catch (e) { next(e) } 83 | 84 | } -------------------------------------------------------------------------------- /src/controllers/cart.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express" 2 | import { CartArrayInt } from "../types/types.js" 3 | import CartModel from "../db/models/cart.model.js" 4 | import ProductModel from "../db/models/product.model.js" 5 | import { isValidReq } from "../utils/validate.js" 6 | 7 | 8 | // add to cart /api/cart/add:post 9 | export const addCartCtr: RequestHandler = async (req, res, next) => { 10 | 11 | const isValidRB = isValidReq(req.body, ['prodId', 'quantity']) 12 | if (!isValidRB) return next({ code: 400, message: 'Invalid field!' }) 13 | 14 | let data: any 15 | let message: string = '' 16 | 17 | try { 18 | const oldCart = await CartModel.findOne({ userId: req.cred.user._id }) 19 | 20 | if (!oldCart) { 21 | const product = await ProductModel.findById(req.body.prodId) 22 | if (!product) return next({ code: 404, message: 'No product was found!' }) 23 | const newCart = new CartModel({ list: [{ prodId: product._id, price: product.price, quantity: req.body.quantity }], userId: req.cred.user._id, }) 24 | data = await newCart.save() 25 | message = 'A new cart created.' 26 | } else { 27 | let isSameProdId = false 28 | const product = await ProductModel.findById(req.body.prodId) 29 | if (!product) return next({ code: 404, message: 'No product was found!' }) 30 | 31 | oldCart.list = oldCart.list.filter(item => { 32 | if (product._id.equals(item.prodId)) { 33 | item.quantity++ 34 | isSameProdId = true 35 | } 36 | return item 37 | }) 38 | 39 | if (!isSameProdId) oldCart.list.push({ prodId: product._id, price: product.price, quantity: 1 }) 40 | data = await oldCart.save() 41 | message = 'The product added to cart.' 42 | } 43 | 44 | res.json({ status: 200, data, message }) 45 | } catch (e) { 46 | next(e) 47 | } 48 | 49 | } 50 | 51 | // update cart /api/cart/update:patch 52 | export const updCartCtr: RequestHandler = async (req, res, next) => { 53 | 54 | const isValidRB = isValidReq(req.body, ['list']) 55 | 56 | if (!isValidRB || !Array.isArray(req.body.list)) 57 | return next({ code: 400, message: 'Bad request sent!' }) 58 | 59 | try { 60 | const oldCart = await CartModel.findOne({ userId: req.cred.user._id }) 61 | if (!oldCart) return next({ code: 404, message: 'No cart found!' }) 62 | 63 | oldCart.list.forEach(item => { 64 | req.body.list.forEach((update: CartArrayInt) => { 65 | if (item.prodId.equals(update.prodId)) item.quantity = update.quantity 66 | }) 67 | }) 68 | 69 | oldCart.list = oldCart.list.filter(item => item.quantity !== 0) 70 | const updated = await oldCart.save({ validateBeforeSave: true }) 71 | 72 | res.json({ status: 200, data: updated, message: 'Cart updated.' }) 73 | } catch (e) { 74 | next(e) 75 | } 76 | 77 | } 78 | 79 | // view cart /api/cart/view:view 80 | export const getCartCtr: RequestHandler = async (req, res, next) => { 81 | 82 | try { 83 | const cart = await CartModel.findOne({ userId: req.cred.user._id }) 84 | if (!cart) return next({ code: 404, message: 'No cart found!' }) 85 | res.json({ status: 200, data: cart, message: 'Cart found.' }) 86 | } catch (e) { 87 | next(e) 88 | } 89 | 90 | } 91 | 92 | // delete cart /api/cart/delete:delete 93 | export const delCartCtr: RequestHandler = async (req, res, next) => { 94 | 95 | try { 96 | const cart = await CartModel.findOneAndDelete({ userId: req.cred.user._id }) 97 | if (!cart) return next({ code: 200, message: 'No cart found!' }) 98 | res.json({ status: 200, message: 'Cart removed!' }) 99 | } catch (e) { 100 | next(e) 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /src/controllers/category.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express"; 2 | import CategoryModel from "../db/models/category.model.js"; 3 | import ProductModel from "../db/models/product.model.js"; 4 | import MetaModel from "../db/models/meta.model.js"; 5 | import { isValidReq } from "../utils/validate.js"; 6 | 7 | // add a category /api/cate/add:post 8 | export const addCategoryCtr: RequestHandler = async (req, res, next) => { 9 | 10 | const isValidRB = isValidReq(req.body, ['name', 'url', 'meta', 'category']) 11 | if (!isValidRB) return next({ code: 400, message: 'Invalid request!' }) 12 | 13 | let categoryMeta 14 | if (req.body.meta) categoryMeta = req.body.meta 15 | 16 | try { 17 | delete req.body.meta 18 | const newCategory = new CategoryModel({ ...req.body, category: req.body.category ? req.body.category : undefined }) 19 | const newMeta = new MetaModel({ ...categoryMeta, link: newCategory._id }) 20 | newCategory.meta = newMeta._id 21 | 22 | const category = await newCategory.save() 23 | const meta = await newMeta.save() 24 | if (req.body.category) await CategoryModel.findOneAndUpdate({ _id: req.body.category }, { $push: { children: newCategory._id } }) 25 | res.json({ status: 200, data: { category, meta }, message: 'New category added!' }) 26 | } catch (e) { 27 | next(e) 28 | } 29 | } 30 | 31 | // edit a category /api/cate/edit/:categoryId:patch 32 | export const ediCategoryCtr: RequestHandler = async (req, res, next) => { 33 | 34 | const _id = req.params.categoryId 35 | const isValidRB = isValidReq(req.body, ['name', 'url', 'meta', 'category']) 36 | if (!isValidRB || !_id) return next({ code: 400, message: 'Bad Request!' }) 37 | 38 | let categoryMeta: any 39 | if (req.body.meta) { 40 | categoryMeta = req.body.meta 41 | delete req.body.meta 42 | } 43 | 44 | try { 45 | const data: { saved?: any, meta?: any, removed?: any, assigned?: any } = {} 46 | const category: any = await CategoryModel.findById(_id) 47 | if (!category) return next({ code: 404, message: 'No category was found!' }) 48 | 49 | let meta: any 50 | meta = await MetaModel.findById(category.meta) 51 | 52 | if (categoryMeta && meta) Object.keys(categoryMeta).forEach(item => meta[item] = categoryMeta[item]) 53 | 54 | if (!meta && categoryMeta) { 55 | meta = new MetaModel({ categoryMeta, link: category._id }) 56 | category.meta = meta._id 57 | } 58 | 59 | if (req.body.category || req.body.category === 'none') data.removed = await CategoryModel.findByIdAndUpdate(category.category, { $pull: { children: category._id } }) 60 | if (req.body.category !== 'none') data.assigned = await CategoryModel.findByIdAndUpdate(req.body.category, { $push: { children: category._id } }) 61 | 62 | if (req.body.category === 'none') req.body.category = undefined 63 | Object.keys(req.body).forEach(item => category[item] = req.body[item]) 64 | 65 | data.saved = await category.save() 66 | if (categoryMeta && meta) data.meta = await meta.save() 67 | 68 | res.json({ status: 200, data, message: 'Category updated!' }) 69 | } catch (e) { 70 | next(e) 71 | } 72 | 73 | } 74 | 75 | // view a category /api/cate/view/:categoryId:get 76 | export const getCategoryCtr: RequestHandler = async (req, res, next) => { 77 | 78 | const _id = req.params.categoryId 79 | 80 | try { 81 | const category = await CategoryModel.findById(_id).populate({ 82 | path: 'meta', 83 | select: 'title description keyprase' 84 | }) 85 | if (!category) return next({ code: 404, message: 'No category was found!' }) 86 | const products = await ProductModel.find({ category: { $in: [category._id, ...category.children] } }).select('title images.main url price').populate({ 87 | path: 'images.main', 88 | select: 'filepath name' 89 | }) 90 | res.json({ status: 200, data: { category, products }, message: 'Category and its products' }) 91 | } catch (e) { 92 | next(e) 93 | } 94 | 95 | } 96 | 97 | // delete a category /api/cate/delete/:categoryId:delete 98 | export const delCategoryCtr: RequestHandler = async (req, res, next) => { 99 | 100 | const _id = req.params.categoryId 101 | if (!_id) return next({ code: 400, message: 'Bad request!' }) 102 | 103 | try { 104 | const category = await CategoryModel.findByIdAndDelete(_id) 105 | if (!category) return next({ code: 404, message: 'No category was found!' }) 106 | const meta = await MetaModel.findByIdAndDelete(category.meta) 107 | if (category.category) await CategoryModel.findByIdAndUpdate(category.category, { $pull: { children: category._id } }) 108 | if (category.children.length) await CategoryModel.updateMany({ category: _id }, { $unset: { category: 1 } }) 109 | res.json({ status: 200, data: { category, meta }, message: 'Category deleted' }) 110 | } catch (e) { 111 | next(e) 112 | } 113 | 114 | } 115 | 116 | // list categories /api/cate/list:get 117 | export const LisCategoryCtr: RequestHandler = async (req, res, next) => { 118 | 119 | try { 120 | const categories = await CategoryModel.find() 121 | if (categories.length < 1) return next({ code: 404, message: 'No category was found!' }) 122 | res.json({ status: 200, message: 'Category found', data: categories }) 123 | } catch (e) { 124 | next(e) 125 | } 126 | 127 | } -------------------------------------------------------------------------------- /src/controllers/comment.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express"; 2 | import CommentModel from "../db/models/comment.model.js"; 3 | import { ROLES } from "../middlewares/role.js"; 4 | import { CommentStatus } from "../types/types.js"; 5 | import { queryHandler } from "../utils/filter.js"; 6 | import { isValidReq } from "../utils/validate.js"; 7 | 8 | 9 | // view a single comment /api/cmnt/view/:commentId:get 10 | export const viwCommentCtr: RequestHandler = async (req, res, next) => { 11 | 12 | const _id = req.params.commentId 13 | 14 | try { 15 | const comment = await CommentModel.findById(_id) 16 | if (!comment) return res.status(404).json({ status: 404, message: 'No comment was found!' }) 17 | res.json({ status: 200, data: comment, message: 'comment found.' }) 18 | } catch (e) { next(e) } 19 | 20 | } 21 | 22 | // list comments /api/cmnt/list:get 23 | export const lisCommentCtr: RequestHandler = async (req, res, next) => { 24 | 25 | const { createdAt, limit, skip, keyphrase } = queryHandler(req.query) 26 | 27 | try { 28 | let comments 29 | if (req.cred.user.role <= ROLES.Seller && req.body.admin === true) comments = await CommentModel.find({}).limit(limit).skip(skip).sort({ createdAt }) 30 | else comments = await CommentModel.find({ authorId: req.cred.user._id }).limit(limit).skip(skip).sort({ createdAt }) 31 | if (!comments.length) return res.status(404).json({ status: 404, message: 'No comment was found!' }) 32 | res.json({ status: 200, data: comments, message: 'Comments found.' }) 33 | } catch (e) { next(e) } 34 | 35 | } 36 | 37 | // show comment /api/cmnt/show:commentId:get 38 | export const shwCommentCtr: RequestHandler = async (req, res, next) => { 39 | 40 | const { limit, skip } = queryHandler(req.query) 41 | const _id = req.params.prodId 42 | if (!_id) return res.status(400).json({ status: 400, message: 'Bad request sent!' }) 43 | 44 | try { 45 | const comments = await CommentModel.find({ prodId: _id, status: CommentStatus.Approved }).select('name email replayTo title description rating').limit(limit).skip(skip) 46 | if (!comments.length) return res.status(404).json({ status: 404, message: 'No comment was found!' }) 47 | res.json({ status: 200, data: comments, message: 'Product comments found.' }) 48 | } catch (e) { next(e) } 49 | 50 | } 51 | 52 | // add a comment /api/cmnt/add:post 53 | export const addCommentCtr: RequestHandler = async (req, res, next) => { 54 | 55 | const isValidRB = isValidReq(req.body, ['name', 'email', 'replayTo', 'prodId', 'authorId', 'rating', 'title', 'description']) 56 | if (!isValidRB) return res.status(400).json({ status: 400, message: 'Invalid field!' }) 57 | console.log(req.cred) 58 | try { 59 | let newComment 60 | if (req.cred?.isAuthenticated && req.cred.user) newComment = new CommentModel({ ...req.body, name: req.cred.user.name, email: req.cred.user.email, authorId: req.cred.user._id }) 61 | else newComment = new CommentModel(req.body) 62 | const comment = await newComment.save() 63 | res.json({ status: 200, data: comment, message: 'New comment added.' }) 64 | 65 | } catch (e) { next(e) } 66 | 67 | } 68 | 69 | // edit a comment /api/cmnt/edit/:commentId:patch 70 | export const ediCommentCtr: RequestHandler = async (req, res, next) => { 71 | 72 | const _id = req.params.commentId 73 | const isValidRB = isValidReq(req.body, ['name', 'email', 'prodId', 'replayTo', 'rating', 'title', 'description', 'status']) 74 | if (!isValidRB) return res.json({ status: 400, message: 'Invalid field!' }) 75 | 76 | try { 77 | const comment: any = await CommentModel.findById(_id) 78 | if (!comment) return res.json({ status: 404, message: 'Comment not found.' }) 79 | Object.keys(req.body).forEach(item => comment[item] = req.body[item]) 80 | const data = await comment.save() 81 | res.json({ status: 200, data, message: 'Comment updated.' }) 82 | } catch (e) { next(e) } 83 | 84 | } 85 | 86 | // delete a comment /api/cmnt/delete/:commentId:delete 87 | export const delCommentCtr: RequestHandler = async (req, res, next) => { 88 | 89 | const _id = req.params.commentId 90 | 91 | try { 92 | const comment = await CommentModel.findByIdAndDelete(_id) 93 | if (!comment) return res.status(404).json({ status: 404, message: 'Comment not found!.' }) 94 | res.json({ status: 200, data: comment, message: 'Comment deleted.' }) 95 | } catch (e) { next(e) } 96 | 97 | } -------------------------------------------------------------------------------- /src/controllers/favourite.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express"; 2 | import FavoModel from "../db/models/favourite.model.js"; 3 | 4 | 5 | // add product to favorite list /api/favo/add:post 6 | export const addFavoCtr: RequestHandler = async (req, res, next) => { 7 | 8 | const { prodId } = req.body 9 | let favorite 10 | let message = 'Item added to favorites.' 11 | try { 12 | const favoList = await FavoModel.findOne({ userId: req.cred.user._id }) 13 | 14 | if (!favoList) { 15 | const newFavo = new FavoModel({ userId: req.cred.user._id, list: [{ prodId }] }) 16 | favorite = await newFavo.save() // need fix / doesn't return any message to favorite var 17 | } else if (favoList) { 18 | let prodIdFound = false 19 | favoList.list.find(item => item.prodId.equals(prodId) ? prodIdFound = true : false) 20 | if (!prodIdFound) favoList.list.push({ prodId }) 21 | else favoList.list = favoList.list.filter(item => !item.prodId.equals(prodId)) 22 | { prodIdFound ? message = 'Item removed from favorites.' : message } 23 | } 24 | 25 | favorite = await favoList?.save() 26 | 27 | res.json({ status: 200, data: favorite, message }) 28 | } catch (e) { 29 | next(e) 30 | } 31 | 32 | } 33 | 34 | // clear favorite list /api/favo/clear:delete 35 | export const clsFavoCtr: RequestHandler = async (req, res, next) => { 36 | 37 | try { 38 | const favoList = await FavoModel.findOne({ userId: req.cred.user._id }) 39 | if (!favoList) return next({ code: 404, message: 'No favorite list was found!' }) 40 | favoList.list = [] 41 | const favorite = await favoList.save() 42 | res.json({ status: 200, data: favorite, message: 'Favorite list emptied' }) 43 | } catch (e) { 44 | next(e) 45 | } 46 | 47 | } 48 | 49 | // get favorites list /api/favo/list:get 50 | export const lisFavoCtr: RequestHandler = async (req, res, next) => { 51 | 52 | try { 53 | const favoList = await FavoModel.findOne({ userId: req.cred.user._id }).populate({ 54 | path: 'list.prodId', 55 | select: 'title price url images.main' 56 | }) 57 | if (!favoList) return next({ code: 404, message: 'Your favorite list is empty!' }) 58 | res.json({ status: 200, data: favoList, message: 'Favorite list' }) 59 | } catch (e) { 60 | next(e) 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /src/controllers/file.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express" 2 | import FileModel from "../db/models/file.model.js" 3 | import { ROLES } from "../middlewares/role.js" 4 | import { genFilePath } from "../utils/file.js" 5 | import { isValidReq } from "../utils/validate.js" 6 | 7 | 8 | // file upload /api/file/upload:post 9 | export const fileUploadCtr: RequestHandler = async (req, res, next) => { 10 | try { 11 | const files: any = req.files! 12 | const items: {}[] = [] 13 | 14 | Object.keys(files).forEach(item => { 15 | const { name, size, encoding, mimetype, md5, mv } = files[item] 16 | const filepath = genFilePath(name, mimetype) 17 | 18 | mv(filepath, (err: Error) => { 19 | if (err) return next({ code: 500, message: `Error while wrting file to disk! ${name}` }) 20 | }) 21 | items.push({ name, md5, size, mimetype, encoding, filepath, userId: req.cred.user._id }) 22 | }) 23 | const newFiles = await FileModel.insertMany(items) 24 | 25 | res.json({ status: 200, data: newFiles }) 26 | } catch (e) { 27 | next(e) 28 | } 29 | } 30 | 31 | // file update /api/file/update/:fileId:patch 32 | export const fileUpdateCtr: RequestHandler = async (req, res, next) => { 33 | const _id = req.params.fileId 34 | const element = Object.keys(req.body) 35 | const isValidRB = isValidReq(req.body, ['name','userId']) 36 | if (!isValidRB) return next({ code: 400, message: 'Bad request!' }) 37 | if (req.cred.user.role > ROLES.Admin && req.body.userId) return next({code: 401, message: 'You have insufficient permission!'}) 38 | try { 39 | let file: any 40 | if (req.cred.user.role <= ROLES.Admin) file = await FileModel.findOne({ _id }) 41 | else file = await FileModel.findOne({ _id, userId: req.cred.user._id }) 42 | if (!file) return next({ code: 404, message: 'No file was found!' }) 43 | element.forEach(item => file[item] = req.body[item]) 44 | await file.save() 45 | res.json({ status: 200, data: file, message: 'File updated.' }) 46 | } catch (e) { 47 | next(e) 48 | } 49 | } 50 | 51 | // file delete /api/file/delete/:fileId:delete 52 | export const fileDeleteCtr: RequestHandler = async (req, res, next) => { 53 | const _id = req.params.fileId 54 | 55 | try { 56 | let file 57 | if (req.cred.user.role <= ROLES.Admin) file = await FileModel.findOneAndDelete({ _id }) 58 | else file = await FileModel.findOneAndDelete({ _id, userId: req.cred.user._id }) 59 | if (!file) return next({ code: 400, message: 'No file was found' }) 60 | res.json({ status: 200, data: file, message: 'File deleted!' }) 61 | } catch (e) { 62 | next(e) 63 | } 64 | } 65 | 66 | // file view /api/file/view/:fileId:get 67 | export const fileViewCtr: RequestHandler = async (req, res, next) => { 68 | const _id = req.params.fileId 69 | try { 70 | let file 71 | if (req.cred.user.role <= ROLES.Admin) file = await FileModel.findOne({ _id }) 72 | else file = await FileModel.findOne({ _id, userId: req.cred.user._id }) 73 | if (!file) return next({ code: 404, message: 'File not found!' }) 74 | res.json({ status: 200, data: file, message: 'File found.' }) 75 | } catch (e) { 76 | next(e) 77 | } 78 | } 79 | 80 | // file list /api/file/list/:fileId:get 81 | export const fileListCtr: RequestHandler = async (req, res, next) => { 82 | 83 | try { 84 | let files 85 | if (req.cred.user.role <= ROLES.Admin) files = await FileModel.find({}) 86 | else files = await FileModel.find({ userId: req.cred.user._id }) 87 | if (!files.length) return next({ code: 404, message: 'No file was found!' }) 88 | res.json({ status: 200, data: files, message: 'Files retrieved.' }) 89 | } catch (e) { 90 | next(e) 91 | } 92 | } -------------------------------------------------------------------------------- /src/controllers/location.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express"; 2 | import { CountryModel, CityModel, ProvStateModel } from "../db/models/address.model.js"; 3 | import { LocationType } from "../types/types.js"; 4 | import { isValidReq } from "../utils/validate.js"; 5 | 6 | export const countryListCtr: RequestHandler = async (req, res, next) => { 7 | 8 | try { 9 | const countires = await CountryModel.find({}) 10 | if (!countires) return res.status(404).json({ status: 404, message: 'No country found!' }) 11 | res.json({ status: 200, data: countires, message: 'Country found.' }) 12 | } catch (e) { next(e) } 13 | 14 | } 15 | 16 | // get country,state/province,city /api/loca/get:locationId:get 17 | export const getLocationCtr: RequestHandler = async (req, res, next) => { 18 | 19 | const _id = req.params.locationId 20 | 21 | try { 22 | if (req.body.type === LocationType.Country) { 23 | 24 | const country = await CountryModel.findById(_id) 25 | if (!country) return res.status(404).json({ status: 404, message: `This country id: ${_id} not found!` }) 26 | const provState = await ProvStateModel.find({ parent: _id }) 27 | res.json({ status: 200, data: { country, provState }, message: 'Country found' }) 28 | 29 | } else if (req.body.type === LocationType.ProvState) { 30 | 31 | const provState = await ProvStateModel.findById(_id) 32 | const city = await CityModel.find({ parent: _id }) 33 | if (!provState) return res.status(404).json({ status: 404, message: `This State/Province id: ${_id} not found!` }) 34 | res.json({ status: 200, data: { provState, city }, message: 'Province/State found!' }) 35 | 36 | } else if (req.body.type === LocationType.City) { 37 | 38 | const city = await CityModel.findById(_id) 39 | if (!city) return res.status(404).json({ status: 404, message: `This City id: ${_id} not found!` }) 40 | res.json({ status: 200, data: city, message: 'City found' }) 41 | 42 | } else { 43 | res.status(400).json({ status: 400, message: 'Invalid request!' }) 44 | } 45 | 46 | } catch (e) { next(e) } 47 | 48 | } 49 | 50 | // add country,state/province,city /api/loca/add:post 51 | export const addLocationCtr: RequestHandler = async (req, res, next) => { 52 | 53 | const isValidRB = isValidReq(req.body, ['name', 'url', 'parent', 'type']) 54 | if (!isValidRB) return res.status(400).json({ status: 400, message: 'Invalid field!' }) 55 | if (req.body.type === LocationType.ProvState || req.body.type === LocationType.City) 56 | if (!req.body.parent) 57 | return res.status(400).json({ status: 400, message: 'Parent field is required.' }) 58 | 59 | try { 60 | let location: any 61 | if (req.body.type === LocationType.Country) location = new CountryModel(req.body) 62 | else if (req.body.type === LocationType.ProvState) location = new ProvStateModel(req.body) 63 | else if (req.body.type === LocationType.City) location = new CityModel(req.body) 64 | else return res.status(400).json({ status: 400, message: 'Type not defined!' }) 65 | const data = await location.save() 66 | res.json({ status: 200, data, message: 'Location added.' }) 67 | } catch (e) { next(e) } 68 | 69 | } 70 | 71 | // edit country,state/province,city /api/loca/edit:locationId:patch 72 | export const ediLocationCtr: RequestHandler = async (req, res, next) => { 73 | 74 | const _id = req.params.locationId 75 | 76 | const isValidRB = isValidReq(req.body, ['name', 'url', 'parent', 'type']) 77 | if (!isValidRB) return res.status(400).json({ status: 400, message: 'Invalid field!' }) 78 | if (req.body.type === LocationType.ProvState || req.body.type === LocationType.City) 79 | if (!req.body.parent) 80 | return res.status(400).json({ status: 400, message: 'Parent field is required.' }) 81 | 82 | try { 83 | let location: any 84 | if (req.body.type === LocationType.Country) location = await CountryModel.findById(_id) 85 | else if (req.body.type === LocationType.ProvState) location = await ProvStateModel.findById(_id) 86 | else if (req.body.type === LocationType.City) location = await CityModel.findById(_id) 87 | else return res.status(400).json({ status: 400, message: 'Invalid request!' }) 88 | 89 | if (!location) return res.status(404).json({ status: 404, message: `This location id: ${_id} not found!` }) 90 | Object.keys(req.body).forEach(item => location[item] = req.body[item]) 91 | const data = await location.save() 92 | res.json({ status: 200, data, message: 'Location updated.' }) 93 | 94 | } catch (e) { next(e) } 95 | 96 | } 97 | 98 | // remove country,state/province,city /api/loca/remove:locationId:delete 99 | export const remLocationCtr: RequestHandler = async (req, res, next) => { 100 | 101 | const _id = req.params.locationId 102 | 103 | try { 104 | let location: any 105 | if (req.body.type === LocationType.Country) location = await CountryModel.findByIdAndDelete(_id) 106 | else if (req.body.type === LocationType.ProvState) location = await ProvStateModel.findByIdAndDelete(_id) 107 | else if (req.body.type === LocationType.City) location = await CountryModel.findByIdAndDelete(_id) 108 | else return res.status(400).json({ status: 400, message: 'Invalid request!' }) 109 | if (!location) return res.status(404).json({ status: 404, message: `This location id: ${_id} not found!` }) 110 | res.json({ status: 200, data: location, message: `This location id: ${_id} deleted!` }) 111 | } catch (e) { next(e) } 112 | 113 | } -------------------------------------------------------------------------------- /src/controllers/order.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express"; 2 | import OrderModel from "../db/models/order.model.js"; 3 | import { ROLES } from "../middlewares/role.js"; 4 | 5 | // list orders /api/orde/list:get 6 | export const listOrderCtr: RequestHandler = async (req, res, next) => { 7 | 8 | let orders 9 | 10 | try { 11 | if (req.cred.user.role! <= ROLES.Seller) orders = await OrderModel.find({}) 12 | else orders = await OrderModel.find({ userId: req.cred.user._id }) 13 | if (!orders.length) return next({ code: 404, message: 'No order found!' }) 14 | res.json({ status: 200, data: orders, message: 'Orders found' }) 15 | } catch (e) { 16 | next(e) 17 | } 18 | 19 | } 20 | 21 | // view an order /api/orde/view/:orderId:get 22 | export const getOrderCtr: RequestHandler = async (req, res, next) => { 23 | 24 | const _id = req.params.orderId 25 | let order 26 | 27 | try { 28 | if (req.cred.user.role! <= ROLES.Seller) order = await OrderModel.findOne({_id}).populate({path: 'userId',select: 'name phone'}) 29 | else order = await OrderModel.findOne({_id, userId: req.cred.user._id }).populate({path: 'userId',select: 'name phone'}) 30 | if (!order) return next({ code: 404, message: 'Order not found!' }) 31 | res.json({status: 200, data: order, message: 'Order found.'}) 32 | } catch (e) { 33 | next(e) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/controllers/payment.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express"; 2 | import CartModel from "../db/models/cart.model.js"; 3 | import OrderModel from "../db/models/order.model.js"; 4 | import { ZarinGateway } from "../utils/payment.js"; 5 | import { PaymentState } from "../types/types.js" 6 | 7 | // Payment request /api/paym/payment:post 8 | export const PayRequestCtr: RequestHandler = async (req, res, next) => { 9 | 10 | try { 11 | const cart = await CartModel.findOne({ userId: req.cred.user._id }) 12 | if (!cart) return next({ code: 400, message: 'No cart found' }) 13 | 14 | const payment = { 15 | amount: cart.amount, 16 | description: req.body.description || 'Product payment.', 17 | mobile: req.cred.user.mobile!, 18 | email: req.cred.user.email! 19 | } 20 | 21 | const { authority, code } = await ZarinGateway(payment) 22 | if (code !== 100 && typeof authority !== 'string') return next({ code: 400, message: 'Incomplete payment request!' }) 23 | cart.payment = { authority, code, state: PaymentState.Pending, date: Date.now() + 24 * 60 * 60 * 1000 } 24 | const info = await cart.save() 25 | res.json({ status: 200, data: { info, redirect: `${process.env.ZARIN_PAY_PGSTART}${authority}` } }) 26 | 27 | } catch (e) { 28 | next(e) 29 | } 30 | 31 | } 32 | 33 | // Checkout payment /api/paym/checkout:post 34 | export const CheckoutCtr: RequestHandler = async (req, res, next) => { 35 | 36 | const { Authority, Status } = req.body 37 | 38 | try { 39 | const cart = await CartModel.findOne({ userId: req.cred.user._id, 'payment.authority': Authority, 'payment.state': PaymentState.Pending, 'payment.date': { $gte: Date.now() } }) 40 | 41 | 42 | if (!cart) return next({ code: 404, message: 'Invalid Payment request, no cart found!' }) 43 | 44 | if (Status === 'OK') { 45 | cart.payment.state = PaymentState.Success 46 | const { userId, list, payment, amount } = cart 47 | const newOrder = new OrderModel({ userId, list, payment, amount }) 48 | const order = await newOrder.save() 49 | await cart.delete() 50 | res.json({ status: 200, data: order, message: 'The payment is done.' }) 51 | 52 | } else if (Status === 'NOK') { 53 | cart.payment.state = PaymentState.Cancel 54 | res.json({ status: 200, message: 'The payment is canceled.' }) 55 | } 56 | 57 | } catch (e) { 58 | next(e) 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/controllers/product.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express" 2 | import ProductModel from "../db/models/product.model.js" 3 | import { isValidReq } from "../utils/validate.js" 4 | import MetaModel from "../db/models/meta.model.js" 5 | import { queryHandler } from '../utils/filter.js' 6 | import { ROLES } from "../middlewares/role.js" 7 | 8 | 9 | // view a product /api/prod/view/:productId:get 10 | export const getProdCtr: RequestHandler = async (req, res, next) => { 11 | const _id = req.params.productId 12 | try { 13 | const product = await ProductModel.findById(_id).populate({ 14 | path: 'images.main images.gallery category tag meta' 15 | }) 16 | if (!product) return next({ code: 404, message: "No product was found!" }) 17 | res.json({ status: 200, data: product, message: "Product found." }) 18 | } catch (e) { 19 | next(e) 20 | } 21 | 22 | } 23 | 24 | // products list /api/prod/list:get 25 | export const listProdCtr: RequestHandler = async (req, res, next) => { 26 | 27 | const { createdAt, updatedAt, limit, skip, price } = queryHandler(req.query) 28 | try { 29 | const products = await ProductModel.find().populate({ 30 | path: 'images.main images.gallery category tag', 31 | select: 'filepath name url' 32 | 33 | }).limit(limit).skip(skip).sort({ price }).sort({ createdAt }) 34 | if (!products.length) return next({ code: 404, message: 'No product was found!' }) 35 | res.json({ status: 200, data: products, message: 'Products found' }) 36 | 37 | } catch (e) { 38 | next(e) 39 | } 40 | 41 | } 42 | 43 | // add new product /api/prod/add:post 44 | export const addProdCtr: RequestHandler = async (req, res, next) => { 45 | 46 | const isValidRB = isValidReq(req.body, ['title', 'excerpt', 'content', 'images', 'category', 'tag', 'meta', 'price', 'owner', 'url']) 47 | if (!isValidRB) return next({ code: 400, message: 'Invalid Request' }) 48 | 49 | try { 50 | 51 | const newMeta = new MetaModel({...req.body.meta}) 52 | delete req.body.meta 53 | const newProduct = new ProductModel({ ...req.body, owner: req.cred.user._id, meta: newMeta._id }) 54 | newMeta.link = newProduct._id 55 | 56 | const meta = await newMeta.save() 57 | const product = await newProduct.save() 58 | res.json({ status: 200, message: 'New product added.', data: { meta, product } }) 59 | 60 | } catch (e) { 61 | next(e) 62 | } 63 | 64 | } 65 | 66 | // update a product /api/prod/update/:productId:get 67 | export const updateProdCtr: RequestHandler = async (req, res, next) => { 68 | const _id = req.params.productId 69 | let product: any 70 | const isValidRB = isValidReq(req.body, ['title', 'excerpt', 'content', 'price', 'url', 'images', 'category', 'tag', 'meta', 'owner']) 71 | if (!isValidRB) return next({ code: 400, message: 'Invalid field!' }) 72 | try { 73 | if (req.cred.user.role! <= ROLES.Admin) product = await ProductModel.findOne({ _id }) 74 | else product = await ProductModel.findOne({ _id, owner: req.cred.user._id }) 75 | if (!product) return next({ code: 404, message: 'No product was found!' }) 76 | 77 | let meta: any 78 | meta = await MetaModel.findById(product.meta._id) 79 | 80 | if (meta && req.body.meta) Object.keys(req.body.meta).forEach(item => meta[item] = req.body.meta[item]) 81 | 82 | if (!meta && req.body.meta) { 83 | meta = new MetaModel({...req.body.meta, link: product._id}) 84 | product.meta = meta._id 85 | } 86 | 87 | delete req.body.meta 88 | 89 | Object.keys(req.body).forEach(item => product[item] = req.body[item]) 90 | 91 | await meta.save() 92 | await product.save() 93 | res.json({ status: 200, data: { product, meta }, message: 'Your product updated.' }) 94 | } catch (e) { 95 | next(e) 96 | } 97 | 98 | } 99 | 100 | // delete a product /api/prod/delete/:productId:get 101 | export const deleteProdCtr: RequestHandler = async (req, res, next) => { 102 | const _id = req.params.productId 103 | let product: any 104 | try { 105 | if (req.cred.user.role! <= ROLES.Admin) product = await ProductModel.findOneAndDelete({ _id }) 106 | else product = await ProductModel.findOneAndDelete({ _id, owner: req.cred.user._id }) 107 | if (!product) return next({ code: 404, message: 'No product was found!' }) 108 | res.json({ status: 200, message: 'The product deleted!', data: product }) 109 | } catch (e) { 110 | next(e) 111 | } 112 | 113 | } -------------------------------------------------------------------------------- /src/controllers/tag.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express"; 2 | import TagModel from "../db/models/tag.model.js"; 3 | import ProductModel from "../db/models/product.model.js"; 4 | import { isValidReq } from "../utils/validate.js"; 5 | import MetaModel from "../db/models/meta.model.js"; 6 | 7 | // add a tag /api/tags/add:post 8 | export const addTagCtr: RequestHandler = async (req, res, next) => { 9 | 10 | const isValidRB = isValidReq(req.body, ['name', 'url', 'meta']) 11 | if (!isValidRB) return next({ code: 400, message: 'Invalid request!' }) 12 | 13 | try { 14 | const newTag = new TagModel(req.body) 15 | const newMeta = new MetaModel({ ...req.body.meta, link: newTag._id }) 16 | newTag.meta = newMeta._id 17 | 18 | const tag = await newTag.save() 19 | const meta = await newMeta.save() 20 | res.json({ status: 200, data: { tag, meta }, message: 'New tag added!' }) 21 | } catch (e) { 22 | next(e) 23 | } 24 | 25 | } 26 | 27 | // edit a tag /api/tags/edit/:tagId:get 28 | export const ediTagCtr: RequestHandler = async (req, res, next) => { 29 | 30 | const _id = req.params.tagId 31 | const isValidRB = isValidReq(req.body, ['name', 'url', 'meta']) 32 | if (!isValidRB || !_id) return next({ code: 400, message: 'Bad Request!' }) 33 | 34 | try { 35 | const tag: any = await TagModel.findById(_id) 36 | if (!tag) return next({ code: 404, message: 'No tag was found!' }) 37 | 38 | let meta: any 39 | meta = await MetaModel.findById(tag.meta) 40 | 41 | if (meta && req.body.meta) Object.keys(req.body.meta).forEach(item => meta[item] = req.body.meta[item]) 42 | 43 | if (!meta && req.body.meta) { 44 | meta = new MetaModel({ ...req.body.meta, link: tag._id }) 45 | tag.meta = meta._id 46 | } 47 | 48 | delete req.body.meta 49 | 50 | Object.keys(req.body).forEach(item => tag[item] = req.body[item]) 51 | 52 | meta = await meta.save() 53 | const updatedTag = await tag.save() 54 | res.json({ status: 200, data: { updatedTag, meta }, message: 'Tag updated!' }) 55 | } catch (e) { 56 | next(e) 57 | } 58 | 59 | } 60 | 61 | // view a tag /api/tags/view/:get 62 | export const getTagCtr: RequestHandler = async (req, res, next) => { 63 | 64 | const _id = req.params.tagId 65 | 66 | try { 67 | const tag = await TagModel.findById(_id).populate({ 68 | path: 'meta', 69 | select: 'title description keyphrase' 70 | }) 71 | const products = await ProductModel.find({ tag: _id }).select('title images.main url price').populate({ 72 | path: 'images.main', 73 | select: 'filepath name' 74 | }) 75 | if (!tag) return next({ code: 404, message: 'No tag was found!' }) 76 | res.json({ status: 200, data: tag, products }) 77 | } catch (e) { 78 | next(e) 79 | } 80 | 81 | } 82 | 83 | // delete a tag /api/tags/delete/:tagId:delete 84 | export const delTagCtr: RequestHandler = async (req, res, next) => { 85 | 86 | const _id = req.params.tagId 87 | 88 | try { 89 | const tag = await TagModel.findByIdAndDelete(_id) 90 | if (!tag) return next({ code: 404, message: 'No tag was found!' }) 91 | let meta 92 | if (tag.meta) meta = await MetaModel.findByIdAndRemove(tag.meta) 93 | const products = await ProductModel.updateMany({ tag: tag._id }, { $pull: { tag: tag._id } }) 94 | res.json({ status: 200, data: { tag, meta, products }, message: 'tag deleted' }) 95 | } catch (e) { 96 | next(e) 97 | } 98 | 99 | } 100 | 101 | // list tags /api/tags/list:get 102 | export const LisTagCtr: RequestHandler = async (req, res, next) => { 103 | 104 | try { 105 | const tags = await TagModel.find() 106 | if (tags.length < 1) return next({ code: 404, message: 'No tag was found!' }) 107 | res.json({ status: 200, message: 'Tag found', data: tags }) 108 | } catch (e) { 109 | next(e) 110 | } 111 | 112 | } -------------------------------------------------------------------------------- /src/controllers/user.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express-serve-static-core"; 2 | import UserModel from "../db/models/user.model.js"; 3 | import { queryHandler } from "../utils/filter.js"; 4 | import resetPassTemp from "../templates/emails/forgot.js"; 5 | import sendEmail from "../utils/nodemailer.js"; 6 | import { ROLES } from "../middlewares/role.js"; 7 | import { isValidReq } from "../utils/validate.js"; 8 | 9 | // user signup controller /api/user/signup:post 10 | export const userSignupCtr: RequestHandler = async (req, res, next) => { 11 | 12 | const isValidRB = isValidReq(req.body, ['name', 'username', 'email', 'address', 'phone', 'password']) 13 | if (!isValidRB) return next({ message: 'Invalid field', code: 400 }) 14 | 15 | try { 16 | const newUser = new UserModel(req.body) 17 | const savedUser = await newUser.save() 18 | res.status(200).json({ status: 200, data: savedUser, message: 'New user created!' }) 19 | } catch (e: any) { 20 | next(e) 21 | } 22 | } 23 | 24 | // user signin controller /api/user/signin:post 25 | export const userSigninCtr: RequestHandler = async (req, res, next) => { 26 | 27 | const isValidRB = isValidReq(req.body, ['phone', 'email', 'password']) 28 | if (!isValidRB) return next({ message: 'Invalid field', code: 400 }) 29 | const { email, phone, password } = req.body 30 | 31 | try { 32 | const user = await UserModel.findByCredentials(email, phone, password) 33 | if (user.error) return next({ message: 'Invalid Credentials!', code: 401 }) 34 | const token = await user.genAuthToken() 35 | res.cookie('authToken', token, { httpOnly: true, maxAge: 7 * 24 * 60 * 60 * 1000 }) 36 | res.json({ status: 200, data: user, message: 'User signed in.' }) 37 | } catch (e) { 38 | next(e) 39 | } 40 | } 41 | 42 | // user singout controller /api/user/signout:post 43 | export const userSignoutCtr: RequestHandler = async (req, res, next) => { 44 | 45 | try { 46 | req.cred.user.tokens = req.cred.user.tokens?.filter(item => item.token !== req.cred.token) 47 | await req.cred.user!.save() 48 | res.json({ status: 200, message: 'signout with success!' }) 49 | } catch (e) { 50 | next(e) 51 | } 52 | 53 | } 54 | 55 | // user singout all controller /api/user/signoutall:post 56 | export const userSignoutAllCtr: RequestHandler = async (req, res, next) => { 57 | 58 | try { 59 | req.cred.user.tokens = req.cred.user.tokens?.filter(item => item.token === req.cred.token) 60 | await req.cred.user!.save() 61 | res.json({ status: 200, message: 'Signed out from all devices!' }) 62 | } catch (e) { 63 | next(e) 64 | } 65 | 66 | } 67 | 68 | // user profile controller /api/user/profile:get 69 | export const userProfiletr: RequestHandler = async (req, res, next) => { 70 | 71 | try { 72 | res.json({ status: 200, data: req.cred.user, message: 'User profile' }) 73 | } catch (e) { 74 | next(e) 75 | } 76 | 77 | } 78 | 79 | // user update controller /api/user/update:patch 80 | export const userUpdateCtr: RequestHandler = async (req, res, next) => { 81 | 82 | const element = Object.keys(req.body) 83 | const isValidRB = isValidReq(req.body, ['name', 'email', 'address', 'phone', 'password']) 84 | if (!isValidRB) return next({ code: 400, message: 'Invalid fields!' }) 85 | 86 | try { 87 | const user: any = req.cred.user 88 | element.forEach(item => user[item] = req.body[item]) 89 | const update = await user.save() 90 | res.json({ status: 200, data: update, message: 'User updated!' }) 91 | } catch (e) { 92 | next(e) 93 | } 94 | 95 | } 96 | 97 | // user forgot password controller /api/user/forgot:post 98 | export const userForgetCtr: RequestHandler = async (req, res, next) => { 99 | 100 | if (!req.body.email) return next({ code: 400, message: 'Provide username or email address' }) 101 | const { email } = req.body 102 | try { 103 | const user = await UserModel.findOne({ email }) 104 | if (!user) return next({ code: 404, message: 'No user found!' }) 105 | 106 | const token = await user.genResetToken() 107 | const template = resetPassTemp(token) 108 | const options = { to: email, subject: 'Reset Password', html: template } 109 | const message = await sendEmail(options) 110 | res.json({ status: 200, message }) 111 | 112 | } catch (e) { 113 | next(e) 114 | } 115 | 116 | } 117 | 118 | // user password reset controller /api/user/reset:get 119 | export const userResetCtr: RequestHandler = async (req, res, next) => { 120 | 121 | const token = req.params.resetToken 122 | const { password } = req.body 123 | 124 | if (!token || !password) return next({ code: 400, message: 'Bad request!' }) 125 | try { 126 | const user = await UserModel.resetPassword(token, password) 127 | if (!user) return next({ code: 401, message: 'Invalid request!' }) 128 | res.json({ status: 200, message: 'Your password changed!' }) 129 | } catch (e) { 130 | next(e) 131 | } 132 | 133 | } 134 | 135 | // user delete controller /api/user/delete:delete 136 | export const userDeleteCtr: RequestHandler = async (req, res, next) => { 137 | 138 | try { 139 | const user = req.cred.user.delete() 140 | if (!user) return next({ code: 404, message: 'User not found!' }) 141 | res.json({ status: 200, message: 'Your account removed!' }) 142 | } catch (e) { 143 | next(e) 144 | } 145 | 146 | } 147 | 148 | // get user list by admins /api/user/list:get 149 | export const userListCtr: RequestHandler = async (req, res, next) => { 150 | const { createdAt, updatedAt, limit, skip } = queryHandler(req.query) 151 | try { 152 | const users = await UserModel.find().limit(limit).skip(skip).sort({createdAt}) 153 | res.json({ status: 200, data: users }) 154 | } catch (e) { 155 | next(e) 156 | } 157 | } 158 | 159 | // search between users by admins /api/user/search:get 160 | export const userSearchCtr: RequestHandler = async (req, res, next) => { 161 | const { createdAt, updatedAt, limit, skip, keyphrase } = queryHandler(req.query) 162 | try { 163 | const users = await UserModel.find({ $text: { $search: keyphrase, $caseSensitive: false } }) 164 | .limit(limit).skip(skip).sort({createdAt}) 165 | res.json({ status: 200, data: users }) 166 | } catch (e) { 167 | next(e) 168 | } 169 | } 170 | 171 | 172 | // create user by admins /api/user/create:post 173 | export const userCreateCtr: RequestHandler = async (req, res, next) => { 174 | 175 | const isValidRB = isValidReq(req.body, ['name', 'username', 'email', 'address', 'phone', 'password', 'role']) 176 | if (!isValidRB) return next({ message: 'Invalid field', code: 400 }) 177 | 178 | if (req.cred.user.role !== ROLES.Root) 179 | if (req.cred.user.role >= req.body.role) 180 | return next({ code: 403, message: `You have insufficient permission!` }) 181 | 182 | try { 183 | const newUser = new UserModel(req.body) 184 | const savedUser = await newUser.save() 185 | res.status(200).json({ status: 200, data: savedUser, message: 'New user created!' }) 186 | } catch (e: any) { 187 | next(e) 188 | } 189 | 190 | } 191 | 192 | // edit users by admins /api/user/edit/:userId:patch 193 | export const userEditCtr: RequestHandler = async (req, res, next) => { 194 | 195 | const element = Object.keys(req.body) 196 | const isValidRB = isValidReq(req.body, ['name', 'email', 'address', 'phone', 'password', 'role']) 197 | if (!isValidRB) return next({ code: 400, message: 'Invalid fields!' }) 198 | 199 | try { 200 | const user: any = await UserModel.findById(req.params.userId) 201 | if (!user) return next({ code: 404, message: 'User not found!' }) 202 | 203 | if (req.cred.user.role !== ROLES.Root) 204 | if (req.cred.user.role >= user.role) 205 | return next({ code: 403, message: `You have insufficient permission!` }) 206 | 207 | element.forEach(item => user[item] = req.body[item]) 208 | const update = await user.save() 209 | res.json({ status: 200, data: update, message: 'User updated!' }) 210 | } catch (e) { 211 | next(e) 212 | } 213 | 214 | } 215 | 216 | // remove users by admins /api/user/remove/:userId:delete 217 | export const userRemoveCtr: RequestHandler = async (req, res, next) => { 218 | 219 | const _id = req.params.userId 220 | if (!_id) return next({ code: 400, message: 'Bad request' }) 221 | 222 | try { 223 | const user = await UserModel.findOne({ _id }) 224 | if (!user) return next({ code: 404, message: 'No user found!' }) 225 | 226 | if (req.cred.user.role !== ROLES.Root) 227 | if (req.cred.user.role! >= user.role) 228 | return next({ code: 403, message: `You have insufficient permission!` }) 229 | 230 | await user.remove() 231 | 232 | res.json({ status: 200, data: user, message: 'User removed!' }) 233 | } catch (e) { 234 | next(e) 235 | } 236 | 237 | } -------------------------------------------------------------------------------- /src/db/connect.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv' 2 | import mongoose from 'mongoose' 3 | 4 | dotenv.config() 5 | 6 | const uri:string = process.env.DB_URI! 7 | const dbn:string = process.env.DB_NAM! 8 | 9 | mongoose.set("strictQuery",false) 10 | mongoose.connect(uri+dbn) -------------------------------------------------------------------------------- /src/db/models/address.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | // countires 4 | const countrySchema = new mongoose.Schema({ 5 | name: { 6 | type: String, 7 | required: true 8 | }, 9 | url: { 10 | type: String, 11 | required: true, 12 | unique: true, 13 | trim: true, 14 | lowercase: true 15 | } 16 | }) 17 | 18 | // provinces and states 19 | const provStateSchema = new mongoose.Schema({ 20 | name: { 21 | type: String, 22 | required: true 23 | }, 24 | url: { 25 | type: String, 26 | required: true, 27 | unique: true, 28 | trim: true, 29 | lowercase: true 30 | }, 31 | parent: { 32 | type: mongoose.Schema.Types.ObjectId, 33 | ref: 'countries' 34 | } 35 | }) 36 | 37 | // cities 38 | const citySchema = new mongoose.Schema({ 39 | name: { 40 | type: String, 41 | required: true 42 | }, 43 | url: { 44 | type: String, 45 | required: true, 46 | unique: true, 47 | trim: true, 48 | lowercase: true 49 | }, 50 | parent: { 51 | type: mongoose.Schema.Types.ObjectId, 52 | ref: 'provstates' 53 | } 54 | }) 55 | 56 | // addresses 57 | const addressSchema = new mongoose.Schema({ 58 | country: { 59 | type: mongoose.Schema.Types.ObjectId, 60 | ref: 'countries', 61 | required: true 62 | }, 63 | provState: { 64 | type: mongoose.Schema.Types.ObjectId, 65 | ref: 'provstates', 66 | required: true 67 | }, 68 | city: { 69 | type: mongoose.Schema.Types.ObjectId, 70 | ref:'cities', 71 | required: true 72 | }, 73 | address: { 74 | type: String, 75 | required: true 76 | }, 77 | postalcode: { 78 | type: String, 79 | required: true 80 | }, 81 | userId: { 82 | type: mongoose.Schema.Types.ObjectId, 83 | required: true 84 | } 85 | }) 86 | 87 | export const CountryModel = mongoose.model('countries', countrySchema) 88 | export const ProvStateModel = mongoose.model('provstates', provStateSchema) 89 | export const CityModel = mongoose.model('cities', citySchema) 90 | 91 | const AddressModel = mongoose.model('addresses', addressSchema) 92 | 93 | export default AddressModel -------------------------------------------------------------------------------- /src/db/models/cart.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import { PaymentState } from "../../types/types.js"; 3 | 4 | 5 | export interface CartSchemaInt { 6 | userId: mongoose.Types.ObjectId 7 | list: { 8 | prodId: mongoose.Types.ObjectId 9 | quantity: number 10 | price: number 11 | }[] 12 | payment: { 13 | authority: string 14 | code: number 15 | state: PaymentState 16 | date: number 17 | } 18 | amount: number 19 | } 20 | 21 | const cartSchema = new mongoose.Schema({ 22 | 23 | userId: { 24 | type: mongoose.Schema.Types.ObjectId, 25 | ref: 'users', 26 | required: true 27 | }, 28 | list: [{ 29 | prodId: { 30 | type: mongoose.Schema.Types.ObjectId, 31 | required: true 32 | }, 33 | quantity: { 34 | type: Number, 35 | required: true, 36 | default: 1, 37 | min: 1 38 | }, 39 | price: { 40 | type: Number, 41 | required: true, 42 | default: 0 43 | } 44 | }], 45 | payment: { 46 | authority: { 47 | type: String 48 | }, 49 | code: { 50 | type: Number 51 | }, 52 | state: { 53 | type: String, 54 | required: true, 55 | default: PaymentState.Ready 56 | }, 57 | date: { 58 | type: Number, 59 | required: true, 60 | default: Date.now() 61 | } 62 | }, 63 | amount: { 64 | type: Number, 65 | required: true, 66 | default: 0 67 | } 68 | 69 | }) 70 | 71 | cartSchema.pre('save', function () { 72 | let amount = 0 73 | this.list.forEach(item => amount += item.price * item.quantity) 74 | this.amount = amount 75 | }) 76 | 77 | const CartModel = mongoose.model('carts', cartSchema) 78 | 79 | export default CartModel -------------------------------------------------------------------------------- /src/db/models/category.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Types, Document, Model } from "mongoose" 2 | import { isValidURL } from "../../config/regex.js" 3 | 4 | interface CategoryInt { 5 | name: string 6 | url: string 7 | category: Types.ObjectId 8 | children: Types.ObjectId[] 9 | meta: Types.ObjectId 10 | } 11 | 12 | 13 | interface CategorySchemaInt extends Document, CategoryInt { } 14 | 15 | const categorySchema = new mongoose.Schema({ 16 | name: { 17 | type: String, 18 | required: true 19 | }, 20 | url: { 21 | type: String, 22 | required: true, 23 | match: [isValidURL, 'Enter a valid URL(a-z A-Z 0-9 -)'], 24 | unique: true 25 | }, 26 | category: { 27 | type: mongoose.Schema.Types.ObjectId, 28 | ref: 'categories' 29 | }, 30 | children: [{ 31 | type: mongoose.Schema.Types.ObjectId 32 | }], 33 | meta: { 34 | type: mongoose.Schema.Types.ObjectId, 35 | ref: 'metas' 36 | } 37 | }) 38 | 39 | const CategoryModel = mongoose.model('categories', categorySchema) 40 | 41 | export default CategoryModel -------------------------------------------------------------------------------- /src/db/models/comment.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import { CommentStatus } from "../../types/types.js"; 3 | 4 | 5 | const commentSchema = new mongoose.Schema({ 6 | name: { 7 | type: String, 8 | trim: true, 9 | required: true 10 | }, 11 | email: { 12 | type: String, 13 | required: true 14 | }, 15 | authorId: { 16 | type: mongoose.Schema.Types.ObjectId, 17 | }, 18 | prodId: { 19 | type: mongoose.Schema.Types.ObjectId, 20 | required: true 21 | }, 22 | replayTo: { 23 | type: mongoose.Schema.Types.ObjectId, 24 | }, 25 | rating: { 26 | type: Number, 27 | min: [1,'Min number is 1'], 28 | max: [5, 'max number is 5'] 29 | }, 30 | title: { 31 | type: String, 32 | trim: true, 33 | required: true 34 | }, 35 | description: { 36 | type: String, 37 | required: true 38 | }, 39 | status: { 40 | type: String, 41 | required: true, 42 | default: CommentStatus.Pending 43 | } 44 | }, { timestamps: true }) 45 | 46 | commentSchema.index({ name: 'text', email: 'text', title: 'text' }) 47 | 48 | const CommentModel = mongoose.model('comments', commentSchema) 49 | 50 | export default CommentModel -------------------------------------------------------------------------------- /src/db/models/favourite.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Types, Document, Model } from "mongoose"; 2 | 3 | 4 | interface FavoriteSchemaInt extends Document { 5 | userId: Types.ObjectId 6 | list: { 7 | prodId: Types.ObjectId 8 | }[] 9 | } 10 | 11 | const favoSchema = new mongoose.Schema({ 12 | 13 | userId: { 14 | type: mongoose.Schema.Types.ObjectId, 15 | ref: 'users', 16 | required: true 17 | }, 18 | list: [{ 19 | prodId: { 20 | type: Types.ObjectId, 21 | ref: 'products', 22 | required: true 23 | } 24 | }] 25 | 26 | }) 27 | 28 | const FavoModel = mongoose.model('favourites', favoSchema) 29 | 30 | export default FavoModel -------------------------------------------------------------------------------- /src/db/models/file.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | 4 | const fileSchema = new mongoose.Schema({ 5 | 6 | name: { 7 | type: String, 8 | required: true 9 | }, 10 | encoding: { 11 | type: String, 12 | required: true 13 | }, 14 | size: { 15 | type: String, 16 | required: true 17 | }, 18 | filepath: { 19 | type: String, 20 | required: true 21 | }, 22 | mimetype: { 23 | type: String, 24 | required: true 25 | }, 26 | md5: { 27 | type: String, 28 | required: true 29 | }, 30 | userId: { 31 | type: mongoose.Schema.Types.ObjectId, 32 | ref: 'users', 33 | required: true 34 | } 35 | 36 | }) 37 | 38 | fileSchema.virtual('myFiles', { 39 | ref: 'users', 40 | localField: 'userId', 41 | foreignField: '_id', 42 | }) 43 | 44 | const FileModel = mongoose.model('files', fileSchema) 45 | 46 | export default FileModel -------------------------------------------------------------------------------- /src/db/models/meta.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const metaSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | trim: true 7 | }, 8 | description: { 9 | type: String, 10 | trim: true 11 | }, 12 | keyphrase: [String], 13 | link: { 14 | type: mongoose.Schema.Types.ObjectId, 15 | required: true 16 | } 17 | }) 18 | 19 | const MetaModel = mongoose.model('metas', metaSchema) 20 | 21 | export default MetaModel -------------------------------------------------------------------------------- /src/db/models/order.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Types } from "mongoose"; 2 | import { PaymentState } from "../../types/types.js"; 3 | 4 | 5 | interface OrderSchemaInt { 6 | userId: Types.ObjectId, 7 | list: { 8 | prodId: Types.ObjectId 9 | quantity: number 10 | price: number 11 | }[] 12 | payment: { 13 | authority: string 14 | code: number 15 | state: PaymentState 16 | date: number 17 | } 18 | state: PaymentState 19 | amount: number 20 | } 21 | 22 | const orderSchema = new mongoose.Schema({ 23 | 24 | userId: { 25 | type: mongoose.Schema.Types.ObjectId, 26 | ref: 'users', 27 | required: true 28 | }, 29 | list: [{ 30 | prodId: { 31 | type: mongoose.Schema.Types.ObjectId, 32 | required: true 33 | }, 34 | quantity: { 35 | type: Number, 36 | required: true, 37 | default: 1 38 | }, 39 | price: { 40 | type: Number, 41 | required: true, 42 | default: 0 43 | } 44 | }], 45 | payment: { 46 | authority: { 47 | type: String 48 | }, 49 | code: { 50 | type: Number 51 | }, 52 | state: { 53 | type: String, 54 | required: true, 55 | default: PaymentState.Ready 56 | }, 57 | date: { 58 | type: Number 59 | } 60 | }, 61 | amount: { 62 | type: Number, 63 | required: true, 64 | default: 0 65 | } 66 | 67 | }, { timestamps: true }) 68 | 69 | const OrderModel = mongoose.model('orders', orderSchema) 70 | 71 | export default OrderModel -------------------------------------------------------------------------------- /src/db/models/product.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document, Model, Types } from "mongoose"; 2 | import { isValidURL } from "../../config/regex.js"; 3 | 4 | interface ProductInt { 5 | title: string 6 | excerpt: string 7 | content: string 8 | price: number 9 | url: string 10 | images: { 11 | main: Types.ObjectId, 12 | gallery: Types.ObjectId 13 | } 14 | category: Types.ObjectId[] 15 | tag: Types.ObjectId[] 16 | meta: Types.ObjectId 17 | owner: Types.ObjectId 18 | comments: Types.ObjectId[] 19 | } 20 | 21 | 22 | const productSchema = new mongoose.Schema({ 23 | title: { 24 | type: String, 25 | trim: true, 26 | required: true 27 | }, 28 | excerpt: { 29 | type: String 30 | }, 31 | content: { 32 | type: String 33 | }, 34 | price: { 35 | type: Number, 36 | default: 0, 37 | required: true 38 | }, 39 | url: { 40 | type: String, 41 | match: [isValidURL, 'Enter a valid URL(a-z A-Z 0-9 -)'], 42 | unique: true 43 | }, 44 | images: { 45 | main: { 46 | type: mongoose.Schema.Types.ObjectId, 47 | ref: 'files' 48 | }, 49 | gallery: [{ 50 | type: mongoose.Schema.Types.ObjectId, 51 | ref: 'files' 52 | }] 53 | }, 54 | category: [{ 55 | type: mongoose.Schema.Types.ObjectId, 56 | ref: 'categories' 57 | }], 58 | tag: [{ 59 | type: mongoose.Schema.Types.ObjectId, 60 | ref: 'tags' 61 | }], 62 | meta: { 63 | type: mongoose.Schema.Types.ObjectId, 64 | ref: 'metas' 65 | }, 66 | owner: { 67 | type: mongoose.Schema.Types.ObjectId, 68 | ref: 'users' 69 | } 70 | 71 | }, { 72 | timestamps: true 73 | }) 74 | 75 | productSchema.index({ 76 | title: 'text', 77 | excerpt: 'text', 78 | content: 'text' 79 | }) 80 | 81 | productSchema.pre('save', function () { 82 | if (!this.url) { 83 | this.url = this.title.toLowerCase().trim().replace(/ /g, '-') + '-' + Math.floor(Math.random() * 99999) + '-' + Date.now() 84 | } 85 | }) 86 | 87 | const ProductModel = mongoose.model('products', productSchema) 88 | 89 | export default ProductModel -------------------------------------------------------------------------------- /src/db/models/tag.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose" 2 | import { isValidURL } from "../../config/regex.js" 3 | 4 | const tagSchema = new mongoose.Schema({ 5 | name: { 6 | type: String, 7 | required: true, 8 | trim: true 9 | }, 10 | url: { 11 | type: String, 12 | required: true, 13 | unique: true, 14 | match: [isValidURL, 'Enter a valid URL(a-z A-Z 0-9 -)'], 15 | 16 | }, 17 | meta: { 18 | type: mongoose.Schema.Types.ObjectId, 19 | ref: 'metas' 20 | } 21 | }) 22 | 23 | const TagsModel = mongoose.model('tags',tagSchema) 24 | 25 | export default TagsModel -------------------------------------------------------------------------------- /src/db/models/user.model.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Types, Model, Document, Schema } from "mongoose"; 2 | import crypto from "crypto" 3 | import bcrypt from "bcrypt" 4 | import jwt from 'jsonwebtoken' 5 | import { isValidEmail } from "../../config/regex.js"; 6 | import { ROLES } from "../../middlewares/role.js"; 7 | 8 | 9 | export interface UserInt { 10 | name: string 11 | username: string 12 | email: string 13 | address?: Types.ObjectId 14 | phone?: string 15 | password: string 16 | role: number 17 | cart: Types.ObjectId 18 | order: Types.ObjectId 19 | tokens?: { token: string }[] 20 | resetToken?: string 21 | resetExpire?: string 22 | } 23 | 24 | interface CredentialPromInt { 25 | error: boolean, 26 | data: UserInt 27 | } 28 | 29 | interface UserSchemaInt extends UserInt, CredentialPromInt, Document { 30 | genAuthToken: () => Promise 31 | genResetToken: () => Promise 32 | } 33 | 34 | interface UserModelInt extends Model { 35 | resetPassword: (token: string, password: string) => Promise 36 | findByCredentials: (email: string, phone: string, password: string) => Promise 37 | } 38 | 39 | const userSchema: Schema = new mongoose.Schema({ 40 | name: { 41 | type: String, 42 | required: true, 43 | trim: true 44 | }, 45 | email: { 46 | type: String, 47 | unique: true, 48 | required: true, 49 | match: [isValidEmail, 'Invalid email address!'] 50 | }, 51 | address: { 52 | type: mongoose.Schema.Types.ObjectId, 53 | ref: 'addresses' 54 | }, 55 | phone: { 56 | type: String, 57 | unique: true, 58 | maxLength: 10, 59 | minLength: 10 60 | }, 61 | cart: { 62 | type: mongoose.Schema.Types.ObjectId, 63 | ref: 'carts' 64 | }, 65 | order: { 66 | type: mongoose.Schema.Types.ObjectId, 67 | ref: 'orders' 68 | }, 69 | password: { 70 | type: String, 71 | required: true, 72 | minLength: 7 73 | }, 74 | role: { 75 | type: Number, 76 | required: true, 77 | default: ROLES.Customer 78 | }, 79 | tokens: [{ 80 | token: { 81 | type: String 82 | } 83 | }], 84 | resetToken: { type: String, select: false }, 85 | resetExpire: { type: Date, select: false } 86 | }, { 87 | timestamps: true 88 | }) 89 | 90 | userSchema.index({ name: 'text', email: 'text' }) 91 | 92 | 93 | // remove password and tokens field to prevent user information exposure 94 | userSchema.methods.toJSON = function () { 95 | const user = this.toObject() 96 | delete user.password 97 | delete user.tokens 98 | return user 99 | } 100 | 101 | // generate auth token and saving it into DB 102 | userSchema.methods.genAuthToken = async function () { 103 | const token = jwt.sign({ _id: this._id }, process.env.JWT_SECRET_KEY!, { expiresIn: '7d' }) 104 | this.tokens = this.tokens.concat({ token }) 105 | await this.save() 106 | return token 107 | } 108 | 109 | // check user password with findByCredintial method 110 | userSchema.statics.findByCredentials = async function (email, phone, password) { 111 | let user 112 | if (email) user = await UserModel.findOne({ email }) 113 | if (phone) user = await UserModel.findOne({ phone }) 114 | if (!user) return { error: true } 115 | const isMatch = await bcrypt.compare(password, user.password) 116 | if (!isMatch) return { error: true } 117 | return user 118 | } 119 | 120 | // Hash user password before saving it into DB 121 | userSchema.pre('save', async function () { 122 | if (this.isModified('password')) { 123 | const salt = await bcrypt.genSalt(10) 124 | this.password = await bcrypt.hash(this.password, salt) 125 | } 126 | }) 127 | 128 | // generate reset token 129 | userSchema.methods.genResetToken = async function () { 130 | const data = crypto.randomBytes(40).toString('hex') 131 | const resetToken = crypto.createHash('sha256').update(data).digest('hex') 132 | this.resetToken = resetToken 133 | this.resetExpire = Date.now() + 5 * 60 * 1000 134 | await this.save() 135 | return data 136 | } 137 | 138 | // reset user password with received token 139 | userSchema.statics.resetPassword = async function (token: string, password: string) { 140 | const resetToken = crypto.createHash('sha256').update(token).digest('hex') 141 | const user = await UserModel.findOne({ resetToken, resetExpire: { $gte: Date.now() } }).select('resetToken resetExpire') 142 | if (!user) return false 143 | user.password = password 144 | user.resetToken = '' 145 | user.resetExpire = '' 146 | await user.save() 147 | return true 148 | } 149 | 150 | userSchema.pre('findOneAndRemove', function () { 151 | // I will remove user carts 152 | }) 153 | 154 | const UserModel = mongoose.model('users', userSchema) 155 | 156 | export default UserModel -------------------------------------------------------------------------------- /src/middlewares/auth.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express"; 2 | import UserModel from "../db/models/user.model.js"; 3 | import jwt from "jsonwebtoken"; 4 | 5 | interface JWTInt { 6 | _id: string 7 | iat: number 8 | exp: number 9 | } 10 | 11 | export const NoAuth: RequestHandler = async (req, res, next) => { 12 | try { 13 | const token: string = req.cookies.authToken 14 | if (!token) throw new Error 15 | const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY!) as JWTInt 16 | const user = await UserModel.findOne({ _id: decoded, 'tokens.token': token }) 17 | if (!user) throw new Error 18 | req.cred = {user: user, token: token, isAuthenticated: true} 19 | next() 20 | } catch (e) { 21 | next() 22 | } 23 | } 24 | 25 | export const Auth: RequestHandler = async (req, res, next) => { 26 | 27 | try { 28 | const token: string = req.cookies.authToken 29 | if (!token) return res.status(401).json({ message: 'Please authenticate!' }) 30 | const decoded = jwt.verify(token, process.env.JWT_SECRET_KEY!) as JWTInt 31 | const user = await UserModel.findOne({ _id: decoded, 'tokens.token': token }) 32 | if (!user) return res.status(401).json({ message: 'Please authenticate!' }) 33 | req.cred = {user: user, token: token, isAuthenticated: true} 34 | next() 35 | } catch (e) { 36 | res.status(401).json({ message: 'Please authenticate!' }) 37 | 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/middlewares/error.ts: -------------------------------------------------------------------------------- 1 | import { ErrorRequestHandler } from "express"; 2 | import ErrorResponse from "../utils/error.js"; 3 | import { logger } from "../utils/logger.js"; 4 | 5 | 6 | const errorHandler: ErrorRequestHandler = (err, req, res, next) => { 7 | 8 | const { ip, originalUrl, method } = req 9 | 10 | let error = { ...err } 11 | error.message = err.message 12 | 13 | logger({ ip, originalUrl, method, errorName: err.name, errorMessage: err.message, status: err.code || 500 }) 14 | 15 | console.log(err) 16 | 17 | if (err.code === 11000) { 18 | const message = 'Duplicate field value entered' 19 | error = new ErrorResponse(message, 400) 20 | } 21 | 22 | if (err.error) { 23 | error = new ErrorResponse(err.message, err.code) 24 | } 25 | 26 | res.status(error.statusCode || err.code || 500).json({ 27 | success: false, 28 | error: error.message || 'Server Error' 29 | }) 30 | 31 | } 32 | 33 | export default errorHandler -------------------------------------------------------------------------------- /src/middlewares/role.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express" 2 | export enum ROLES { 3 | Root = 1000, 4 | Admin = 1100, 5 | Seller = 1500, 6 | Customer = 2000 7 | } 8 | 9 | export enum Access { 10 | Just = 0, 11 | Higher = 10, 12 | Lower = 20 13 | } 14 | 15 | export const Role = (role: ROLES, access: Access = Access.Higher) => 16 | (req: Request, res: Response, next: NextFunction) => { 17 | if (access === Access.Higher) if (req.cred.user.role! <= role) return next() 18 | if (access === Access.Lower) if (req.cred.user.role! >= role) return next() 19 | if (access === Access.Just) if (req.cred.user.role! === role) return next() 20 | next({ status: 403, message: 'You have insufficient permission!' }) 21 | } -------------------------------------------------------------------------------- /src/middlewares/upload.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction, RequestHandler } from "express"; 2 | 3 | 4 | type FilePayExists = () => void 5 | type FileExtension = () => void 6 | type FileSizeLimit = () => void 7 | 8 | 9 | // look for file pay exist 10 | export const fpExist: RequestHandler = (req, res, next) => { 11 | 12 | if (!req.files) return res.json({ status: 404, message: 'No file was found!' }) 13 | next() 14 | 15 | } 16 | 17 | // look for file size limit 18 | export const fsLimit = (fileSize: number = 1 * 1024 * 1024) => (req: Request, res: Response, next: NextFunction) => { 19 | 20 | const files: any = req.files! 21 | const limitList: string[] = [] 22 | Object.keys(files).forEach(item => { 23 | if (files[item].size > fileSize) limitList.push(files[item].name) 24 | }) 25 | if (limitList.length) return res.json({ status: 400, message: `File size is too big: ${limitList.toString()}` }) 26 | next() 27 | } 28 | 29 | // look for file extension 30 | export const fxLimit = (extensions: string[] = ['jpeg','jpg','png','pdf']) => (req: Request, res: Response, next: NextFunction) => { 31 | 32 | const files: any = req.files! 33 | let isMatch = true 34 | Object.keys(files).forEach(item => { 35 | if (!extensions.includes(files[item].mimetype.split('/')[1])) return isMatch = false 36 | }) 37 | if (!isMatch) return res.json({ status: 400, message: 'File extensions are not allowed!' }) 38 | next() 39 | } -------------------------------------------------------------------------------- /src/routes/address.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { Auth } from "../middlewares/auth.js"; 3 | 4 | 5 | const router = Router() 6 | 7 | import { 8 | 9 | addAddressCtr, 10 | remAddressCtr, 11 | ediAddressCtr, 12 | getAddressCtr, 13 | lisAddressCtr 14 | 15 | } from '../controllers/address.js' 16 | 17 | 18 | router.route('/add').post(Auth, addAddressCtr) 19 | router.route('/list').get(Auth, lisAddressCtr) 20 | router.route('/get/:addressId').get(Auth, getAddressCtr) 21 | router.route('/edit/:addressId').patch(Auth, ediAddressCtr) 22 | router.route('/delete/:addressId').delete(Auth, remAddressCtr) 23 | 24 | 25 | export default router -------------------------------------------------------------------------------- /src/routes/cart.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { Auth } from "../middlewares/auth.js"; 3 | 4 | const router = Router() 5 | 6 | import { 7 | 8 | addCartCtr, 9 | delCartCtr, 10 | updCartCtr, 11 | getCartCtr 12 | 13 | } from '../controllers/cart.js' 14 | 15 | router.route('/add').post(Auth, addCartCtr) 16 | router.route('/delete').delete(Auth, delCartCtr) 17 | router.route('/update').patch(Auth, updCartCtr) 18 | router.route('/get').get(Auth, getCartCtr) 19 | 20 | 21 | export default router -------------------------------------------------------------------------------- /src/routes/category.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | 3 | import { Auth } from "../middlewares/auth.js"; 4 | import { ROLES, Role, Access } from "../middlewares/role.js"; 5 | 6 | const router = Router() 7 | 8 | import { 9 | addCategoryCtr, 10 | ediCategoryCtr, 11 | getCategoryCtr, 12 | delCategoryCtr, 13 | LisCategoryCtr 14 | } from '../controllers/category.js' 15 | 16 | 17 | router.route('/get/:categoryId').get(getCategoryCtr) 18 | router.route('/list').get(LisCategoryCtr) 19 | router.route('/add').post(Auth, Role(ROLES.Admin, Access.Higher), addCategoryCtr) 20 | router.route('/edit/:categoryId').patch(Auth, Role(ROLES.Admin, Access.Higher), ediCategoryCtr) 21 | router.route('/delete/:categoryId').delete(Auth, Role(ROLES.Admin, Access.Higher), delCategoryCtr) 22 | 23 | export default router -------------------------------------------------------------------------------- /src/routes/comment.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { Auth, NoAuth } from "../middlewares/auth.js"; 3 | import { Role, ROLES, Access } from "../middlewares/role.js"; 4 | 5 | const router = Router() 6 | 7 | import { 8 | viwCommentCtr, 9 | lisCommentCtr, 10 | shwCommentCtr, 11 | addCommentCtr, 12 | ediCommentCtr, 13 | delCommentCtr 14 | } from '../controllers/comment.js' 15 | 16 | 17 | router.route('/get/:commentId').get(Auth, viwCommentCtr) 18 | router.route('/add').post(NoAuth, addCommentCtr) 19 | router.route('/show/:prodId').get(shwCommentCtr) 20 | router.route('/list').get(Auth, Role(ROLES.Customer), lisCommentCtr) 21 | router.route('/edit/:commentId').patch(Auth, Role(ROLES.Customer), ediCommentCtr) 22 | router.route('/delete/:commentId').delete(Auth, Role(ROLES.Customer), delCommentCtr) 23 | 24 | 25 | export default router -------------------------------------------------------------------------------- /src/routes/favourite.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { Auth } from "../middlewares/auth.js"; 3 | 4 | 5 | const router = Router() 6 | 7 | import { 8 | addFavoCtr, 9 | clsFavoCtr, 10 | lisFavoCtr 11 | } from '../controllers/favourite.js' 12 | 13 | 14 | router.route('/list').get(Auth, lisFavoCtr) 15 | router.route('/add').post(Auth, addFavoCtr) 16 | router.route('/clear').delete(Auth, clsFavoCtr) 17 | 18 | 19 | export default router -------------------------------------------------------------------------------- /src/routes/file.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import fileUpload from "express-fileupload"; 3 | import { Auth } from "../middlewares/auth.js"; 4 | import { Role, ROLES, Access } from "../middlewares/role.js"; 5 | import { fsLimit, fpExist, fxLimit } from "../middlewares/upload.js"; 6 | 7 | import { 8 | fileUploadCtr, 9 | fileUpdateCtr, 10 | fileDeleteCtr, 11 | fileViewCtr, 12 | fileListCtr 13 | } from '../controllers/file.js' 14 | 15 | const router = Router() 16 | 17 | router.route('/upload').post( 18 | Auth, 19 | Role(ROLES.Seller, Access.Higher), 20 | fileUpload({ createParentPath: true }), 21 | fpExist, 22 | fsLimit(20 * 1024 * 1024), 23 | fxLimit(['jpg','jpeg','png','mp3','mpeg']), 24 | fileUploadCtr 25 | ) 26 | router.route('/update/:fileId').patch(Auth, Role(ROLES.Seller, Access.Higher), fileUpdateCtr) 27 | router.route('/delete/:fileId').delete(Auth, Role(ROLES.Seller, Access.Higher), fileDeleteCtr) 28 | router.route('/get/:fileId').get(Auth, Role(ROLES.Seller, Access.Higher), fileViewCtr) 29 | router.route('/list').get(Auth, Role(ROLES.Seller, Access.Higher), fileListCtr) 30 | 31 | 32 | export default router -------------------------------------------------------------------------------- /src/routes/location.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { Auth } from "../middlewares/auth.js"; 3 | import { Role, ROLES } from "../middlewares/role.js"; 4 | 5 | const router = Router() 6 | 7 | 8 | import { 9 | 10 | countryListCtr, 11 | addLocationCtr, 12 | remLocationCtr, 13 | ediLocationCtr, 14 | getLocationCtr 15 | 16 | } from '../controllers/location.js' 17 | 18 | router.route('/list').get(countryListCtr) 19 | router.route('/get/:locationId').get(getLocationCtr) 20 | router.route('/add').post(Auth, Role(ROLES.Admin), addLocationCtr) 21 | router.route('/edit/:locationId').patch(Auth, Role(ROLES.Admin), ediLocationCtr) 22 | router.route('/remove/:locationId').delete(Auth, Role(ROLES.Admin), remLocationCtr) 23 | 24 | 25 | 26 | export default router -------------------------------------------------------------------------------- /src/routes/order.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { Auth } from "../middlewares/auth.js"; 3 | 4 | 5 | const router = Router() 6 | 7 | import { 8 | 9 | listOrderCtr, 10 | getOrderCtr 11 | 12 | } from '../controllers/order.js' 13 | 14 | router.route('/list').get(Auth, listOrderCtr) 15 | router.route('/get/:orderId').get(Auth, getOrderCtr) 16 | 17 | 18 | export default router -------------------------------------------------------------------------------- /src/routes/payment.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { Auth } from "../middlewares/auth.js"; 3 | 4 | const router = Router() 5 | 6 | 7 | import { 8 | CheckoutCtr, 9 | PayRequestCtr 10 | } from '../controllers/payment.js' 11 | 12 | router.route('/payment').post(Auth, PayRequestCtr) 13 | router.route('/checkout').post(Auth, CheckoutCtr) 14 | 15 | 16 | export default router -------------------------------------------------------------------------------- /src/routes/product.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { Auth } from "../middlewares/auth.js"; 3 | import { Role, ROLES, Access } from "../middlewares/role.js"; 4 | 5 | const router = Router() 6 | 7 | import { 8 | 9 | addProdCtr, 10 | deleteProdCtr, 11 | updateProdCtr, 12 | getProdCtr, 13 | listProdCtr 14 | 15 | } from "../controllers/product.js" 16 | 17 | 18 | router.route('/add').post(Auth, Role(ROLES.Seller, Access.Higher), addProdCtr) 19 | router.route('/delete/:productId').delete(Auth, deleteProdCtr) 20 | router.route('/update/:productId').patch(Auth, updateProdCtr) 21 | router.route('/get/:productId').get(getProdCtr) 22 | router.route('/list').get(listProdCtr) 23 | 24 | export default router -------------------------------------------------------------------------------- /src/routes/tag.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | 3 | import { Auth } from "../middlewares/auth.js"; 4 | import { ROLES, Role, Access } from "../middlewares/role.js"; 5 | 6 | const router = Router() 7 | 8 | import { 9 | addTagCtr, 10 | ediTagCtr, 11 | getTagCtr, 12 | delTagCtr, 13 | LisTagCtr 14 | } from '../controllers/tag.js' 15 | 16 | 17 | router.route('/get/:tagId').get(getTagCtr) 18 | router.route('/list').get(LisTagCtr) 19 | router.route('/add').post(Auth, Role(ROLES.Admin, Access.Higher), addTagCtr) 20 | router.route('/edit/:tagId').patch(Auth, Role(ROLES.Admin, Access.Higher), ediTagCtr) 21 | router.route('/delete/:tagId').delete(Auth, Role(ROLES.Admin, Access.Higher), delTagCtr) 22 | 23 | export default router -------------------------------------------------------------------------------- /src/routes/user.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import { Auth } from "../middlewares/auth.js"; 3 | import { Role, ROLES, Access } from "../middlewares/role.js"; 4 | import { 5 | 6 | userSignupCtr, 7 | userSigninCtr, 8 | userSignoutCtr, 9 | userSignoutAllCtr, 10 | userProfiletr, 11 | userSearchCtr, 12 | userUpdateCtr, 13 | userForgetCtr, 14 | userResetCtr, 15 | userDeleteCtr, 16 | userCreateCtr, 17 | userListCtr, 18 | userEditCtr, 19 | userRemoveCtr 20 | 21 | } from "../controllers/user.js" 22 | 23 | const router = Router() 24 | 25 | 26 | router.route('/signup').post(userSignupCtr) 27 | router.route('/signin').post(userSigninCtr) 28 | router.route('/signout').post(Auth, userSignoutCtr) 29 | router.route('/signoutall').post(Auth, userSignoutAllCtr) 30 | router.route('/profile').get(Auth, userProfiletr) 31 | router.route('/update').patch(Auth, userUpdateCtr) 32 | router.route('/forgot').post(userForgetCtr) 33 | router.route('/reset/:resetToken').patch(userResetCtr) 34 | router.route('/delete').delete(Auth, userDeleteCtr) 35 | // user management 36 | router.route('/list').get(Auth, Role(ROLES.Admin), userListCtr) 37 | router.route('/search').get(Auth, Role(ROLES.Admin), userSearchCtr) 38 | router.route('/create').post(Auth, Role(ROLES.Admin), userCreateCtr) 39 | router.route('/edit/:userId').patch(Auth, Role(ROLES.Admin), userEditCtr) 40 | router.route('/remove/:userId').delete(Auth, Role(ROLES.Admin), userRemoveCtr) 41 | 42 | 43 | export default router -------------------------------------------------------------------------------- /src/templates/emails/forgot.ts: -------------------------------------------------------------------------------- 1 | const pubStyle = ` 2 | 18 | ` 19 | const resetPassTemp = (token: string) => { 20 | 21 | const template = ` 22 | 23 | 24 | 25 | ${pubStyle} 26 | 27 | 28 |

Reset your password

29 |

If you sent this request and want to reset your password, click the Reset button.

30 | 31 |

If you not asked for this, just ignore this email

32 | 33 | 34 | ` 35 | return template 36 | } 37 | 38 | export default resetPassTemp 39 | -------------------------------------------------------------------------------- /src/types/custom.d.ts: -------------------------------------------------------------------------------- 1 | declare namespace Express { 2 | export interface Request { 3 | cred: { 4 | user: { 5 | role: Number 6 | mobile?: string 7 | email?: string 8 | name?: string 9 | tokens?: { token: string }[] 10 | _id: string 11 | save: () => Promise 12 | delete: () => Promise 13 | }, 14 | isAuthenticated?: boolean 15 | token: string 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/types/types.ts: -------------------------------------------------------------------------------- 1 | export interface FilesNameSchemaInt { 2 | name: string 3 | md5: string 4 | size: number 5 | mimetype: string 6 | encoding: string 7 | filepath: string 8 | userId: string 9 | mv: () => {} 10 | } 11 | 12 | export interface FilesNameErrorInt { 13 | status: boolean 14 | message?: string 15 | } 16 | 17 | export interface CartArrayInt { 18 | prodId: string 19 | quantity: number 20 | price: number 21 | } 22 | 23 | export enum PaymentState { 24 | Ready = 'READY', 25 | Pending = 'PEDNDING', 26 | Success = 'SUCCESS', 27 | Cancel = 'CANCEL' 28 | } 29 | 30 | export enum CommentStatus { 31 | Pending = 'PENDING', 32 | Approved = 'APPROVED', 33 | Rejected = 'REJECTED' 34 | } 35 | 36 | export enum LocationType { 37 | Country = 'COUNTRY', 38 | ProvState = 'PROVESTATE', 39 | City = 'CITY' 40 | } 41 | 42 | export enum CommentRating { 43 | VeryBad = 1, 44 | Bad = 2, 45 | Ok = 3, 46 | Good = 4, 47 | VeryGood = 5 48 | } 49 | 50 | export interface ZarinGatewayReqInt { 51 | merchant_id: string 52 | amount: number 53 | description: string 54 | callback_url: string 55 | mobile?: string 56 | email?: string 57 | } 58 | 59 | export interface PaymentOptionsInt { 60 | amount: number 61 | description: string 62 | mobile?: string 63 | email?: string 64 | } 65 | 66 | export interface LoggerOptions { 67 | ip: string 68 | originalUrl: string 69 | method: string 70 | errorName?: string 71 | errorMessage?: string, 72 | status?: number 73 | } -------------------------------------------------------------------------------- /src/utils/error.ts: -------------------------------------------------------------------------------- 1 | class ErrorResponse extends Error { 2 | constructor(message: string, public statusCode: number) { 3 | super(message) 4 | this.statusCode = statusCode 5 | } 6 | } 7 | export default ErrorResponse -------------------------------------------------------------------------------- /src/utils/file.ts: -------------------------------------------------------------------------------- 1 | import path, { dirname } from 'path' 2 | import { fileURLToPath } from "url" 3 | const __dirname = dirname(fileURLToPath(import.meta.url)) 4 | 5 | type GenerateFilePath = (name: string, mimetype: string) => string 6 | 7 | export const genFilePath: GenerateFilePath = (name: string, mimetype: string) => { 8 | const currDate = new Date() 9 | const datePath = currDate.getFullYear() + '/' + (currDate.getMonth() + 1) + '/' + currDate.getDate() 10 | const randName = Math.floor(Math.random() * 9999999) 11 | const fileName = Date.now() + '-' + randName + name.substring(name.lastIndexOf('.'), name.length) 12 | return path.join(__dirname, `../files/${mimetype.split('/')[0]}s/${datePath}/`, fileName) 13 | } -------------------------------------------------------------------------------- /src/utils/filter.ts: -------------------------------------------------------------------------------- 1 | import { SortOrder } from "mongoose" 2 | 3 | interface QueryInt { 4 | csort?: 'dsc' | 'asc' 5 | usort?: 'dsc' | 'asc' 6 | price?: 'dsc' | 'asc' 7 | limit?: number 8 | skip?: number 9 | keyphrase?: string 10 | match?: {} 11 | } 12 | 13 | 14 | type QueryHandler = (query: QueryInt) => { createdAt: SortOrder, updatedAt: SortOrder, limit: number, skip: number, price: SortOrder, keyphrase: string } 15 | 16 | export const queryHandler: QueryHandler = (query) => { 17 | 18 | let createdAt: SortOrder = 1 19 | let updatedAt: SortOrder = 1 20 | let price: SortOrder = 1 21 | let skip = 0 22 | let limit = 0 23 | let keyphrase: string = '' 24 | 25 | if (query.csort === 'dsc') createdAt = -1 26 | if (query.usort === 'dsc') updatedAt = -1 27 | if (query.price === 'dsc') price = -1 28 | 29 | if (query.csort === 'asc') createdAt = 1 30 | if (query.usort === 'asc') updatedAt = 1 31 | if (query.price === 'asc') price = 1 32 | 33 | if (query.skip) skip = +query.skip 34 | if (query.limit) limit = +query.limit 35 | if (query.keyphrase) keyphrase = query.keyphrase 36 | 37 | return { createdAt, updatedAt, limit, skip, price, keyphrase } 38 | 39 | } -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import { RequestHandler } from "express" 2 | import { LoggerOptions } from "../types/types" 3 | import { existsSync, mkdir, writeFile, appendFile, stat } from 'fs' 4 | import { randomUUID } from "crypto" 5 | import path, { dirname } from "path" 6 | import { fileURLToPath } from "url" 7 | 8 | const __dirname = dirname(fileURLToPath(import.meta.url)) 9 | 10 | type Logger = (options: LoggerOptions) => Promise 11 | 12 | export const logger: Logger = async ({ ip, method, originalUrl, errorName, errorMessage, status }) => { 13 | 14 | let date = new Date().toLocaleString() 15 | let template = `${date} ${randomUUID()} ${ip} ${originalUrl} Method: ${method}` 16 | if (errorName || errorMessage) template = template + ` Status: ${status} ${errorName || 'Error'}: ${errorMessage}` 17 | template = template + '\n' 18 | 19 | const logPath = path.join(__dirname, '../logs') 20 | const logsEvent = path.join(__dirname, '../logs/events.txt') 21 | const errorEvent = path.join(__dirname, '../logs/errors.txt') 22 | 23 | if (!existsSync(logPath)) mkdir(logPath, (err) => { if (err) console.log(err) }) 24 | if (!existsSync(logsEvent)) writeFile(logsEvent, '', (err) => { if (err) console.log(err) }) 25 | if (!existsSync(errorEvent)) writeFile(errorEvent, '', (err) => { if (err) console.log(err) }) 26 | 27 | if (errorName || errorMessage) appendFile(errorEvent, template, (err) => { if (err) console.log(err) }) 28 | else appendFile(logsEvent, template, (err) => { if (err) console.log(err) }) 29 | } 30 | 31 | export const events: RequestHandler = async (req, res, next) => { 32 | const { ip, originalUrl, method } = req 33 | logger({ ip, originalUrl, method }) 34 | next() 35 | } -------------------------------------------------------------------------------- /src/utils/nodemailer.ts: -------------------------------------------------------------------------------- 1 | import nodemailer from "nodemailer" 2 | 3 | export interface NodeMailerOptionsInt { 4 | to: string 5 | subject: string 6 | html: string 7 | } 8 | 9 | export type SendEmailInt = (options: NodeMailerOptionsInt) => Promise 10 | 11 | export const sendEmail:SendEmailInt = async (options: NodeMailerOptionsInt) => { 12 | 13 | const transporter = nodemailer.createTransport({ 14 | host: process.env.EMAIL_SERV, 15 | port: 587, 16 | secure: false, // true for 465, false for other ports 17 | tls: {rejectUnauthorized: false}, 18 | auth: { 19 | user: process.env.EMAIL_USER, 20 | pass: process.env.EMAIL_PASS 21 | }, 22 | }); 23 | 24 | const { to, subject, html } = options 25 | const info = await transporter.sendMail({ from: process.env.EMAIL_USER, to, subject, html }); 26 | 27 | return `Message sent: %s ${info.messageId}`; 28 | } 29 | 30 | export default sendEmail -------------------------------------------------------------------------------- /src/utils/payment.ts: -------------------------------------------------------------------------------- 1 | import { ZarinGatewayReqInt, PaymentOptionsInt } from "../types/types.js" 2 | import axios from "axios" 3 | 4 | type ZarinGatewayReq = (payment: PaymentOptionsInt) => Promise<{code: number, authority: 'string', message: 'string'}> 5 | 6 | // send payment request to zarinpal 7 | export const ZarinGateway: ZarinGatewayReq = async (payment) => { 8 | 9 | let options: ZarinGatewayReqInt = { 10 | merchant_id: process.env.ZARIN_PAY_MERCHANT!, 11 | callback_url: process.env.PAYMENT_CALLBACK_URL!, 12 | amount: payment.amount, 13 | description: payment.description, 14 | mobile: payment.mobile, 15 | email: payment.email 16 | } 17 | 18 | const response = await axios.post(process.env.ZARIN_PAY_ADDRESS!, options) 19 | return response.data.data 20 | } -------------------------------------------------------------------------------- /src/utils/validate.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | type IsValidRequest = (body: {}, list: string[]) => boolean 5 | 6 | export const isValidReq: IsValidRequest = (body, list) => { 7 | const element = Object.keys(body) 8 | const isMatch = element.every(item => list.includes(item)) 9 | return isMatch 10 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "ES6", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "ES2022", /* Specify what module code is generated. */ 29 | "rootDir": "./src", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 36 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 | // "resolveJsonModule": true, /* Enable importing .json files. */ 39 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 40 | 41 | /* JavaScript Support */ 42 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 43 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 44 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 45 | 46 | /* Emit */ 47 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 48 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 49 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 50 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 51 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 52 | "outDir": "./dist", /* Specify an output folder for all emitted files. */ 53 | // "removeComments": true, /* Disable emitting comments. */ 54 | // "noEmit": true, /* Disable emitting files from a compilation. */ 55 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 56 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 57 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 58 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 59 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 60 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 61 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 62 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 63 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 64 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 65 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 66 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 67 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 68 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 69 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 70 | 71 | /* Interop Constraints */ 72 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 75 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 77 | 78 | /* Type Checking */ 79 | "strict": true, /* Enable all strict type-checking options. */ 80 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 83 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 84 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 85 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 86 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 87 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 88 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 89 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 90 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 91 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 92 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 93 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 94 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 95 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 96 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 97 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 98 | 99 | /* Completeness */ 100 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 | }, 103 | "exclude": ["node_modules/","types/"] 104 | } 105 | --------------------------------------------------------------------------------