├── .env.example ├── .gitignore ├── README.md ├── controllers ├── authController.go ├── categoryController.go ├── currencyController.go ├── productController.go ├── storeController.go └── userController.go ├── database ├── database.go └── seeder │ └── seeder.go ├── front ├── .env.example ├── .eslintrc.json ├── .gitignore ├── README.md ├── components │ ├── cart.tsx │ └── layout │ │ ├── base.tsx │ │ └── nav.tsx ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages │ ├── _app.tsx │ ├── index.tsx │ ├── product │ │ └── [id].tsx │ ├── shop.tsx │ ├── signin.tsx │ └── signup.tsx ├── postcss.config.js ├── public │ ├── favicon.ico │ └── vercel.svg ├── server.js ├── store │ ├── cart.ts │ ├── products.ts │ ├── store.ts │ ├── toggleCart.tsx │ └── userAuth.ts ├── styles │ ├── Home.module.css │ └── globals.css ├── tailwind.config.js ├── tsconfig.json └── util │ ├── axios.ts │ └── storage.ts ├── go.mod ├── go.sum ├── main.go ├── middlewares └── auth.go ├── models ├── cart.go ├── cartitems.go ├── categories.go ├── coupons.go ├── currency.go ├── order.go ├── product.go ├── product_sold.go ├── reviews.go ├── store.go ├── user.go └── user_types.go ├── routes └── routes.go ├── runner.conf └── util └── helper.go /.env.example: -------------------------------------------------------------------------------- 1 | PORT=3000 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/go,vs,nextjs,react,dotenv 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=go,vs,nextjs,react,dotenv 4 | 5 | ### dotenv ### 6 | .env 7 | 8 | ### pnpm ### 9 | front/pnpm-lock.yaml 10 | 11 | ### Intellij ### 12 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 13 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 14 | /**/.idea/* 15 | # User-specific stuff: 16 | .idea/workspace.xml 17 | .idea/tasks.xml 18 | .idea/dictionaries 19 | .idea/vcs.xml 20 | .idea/jsLibraryMappings.xml 21 | 22 | # Sensitive or high-churn files: 23 | .idea/dataSources.ids 24 | .idea/dataSources.xml 25 | .idea/dataSources.local.xml 26 | .idea/sqlDataSources.xml 27 | .idea/dynamic.xml 28 | .idea/uiDesigner.xml 29 | 30 | # Gradle: 31 | .idea/gradle.xml 32 | .idea/libraries 33 | 34 | # Mongo Explorer plugin: 35 | .idea/mongoSettings.xml 36 | 37 | ## File-based project format: 38 | *.iws 39 | 40 | ## Plugin-specific files: 41 | 42 | # IntelliJ 43 | /out/ 44 | 45 | # mpeltonen/sbt-idea plugin 46 | .idea_modules/ 47 | 48 | # JIRA plugin 49 | atlassian-ide-plugin.xml 50 | 51 | # Crashlytics plugin (for Android Studio and IntelliJ) 52 | com_crashlytics_export_strings.xml 53 | crashlytics.properties 54 | crashlytics-build.properties 55 | fabric.properties 56 | 57 | ### Intellij Patch ### 58 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 59 | 60 | # *.iml 61 | # modules.xml 62 | 63 | ### Go ### 64 | # If you prefer the allow list template instead of the deny list, see community template: 65 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 66 | # 67 | # Binaries for programs and plugins 68 | *.exe 69 | *.exe~ 70 | *.dll 71 | *.so 72 | *.dylib 73 | 74 | # Test binary, built with `go test -c` 75 | *.test 76 | 77 | # Output of the go coverage tool, specifically when used with LiteIDE 78 | *.out 79 | 80 | # Dependency directories (remove the comment below to include it) 81 | # vendor/ 82 | 83 | # Go workspace file 84 | go.work 85 | 86 | ### Go Patch ### 87 | /vendor/ 88 | /Godeps/ 89 | 90 | ### NextJS ### 91 | # Next build dir 92 | .next/ 93 | 94 | ### react ### 95 | .DS_* 96 | *.log 97 | logs 98 | **/*.backup.* 99 | **/*.back.* 100 | 101 | node_modules 102 | bower_components 103 | 104 | *.sublime* 105 | 106 | psd 107 | thumb 108 | sketch 109 | 110 | ### vs ### 111 | ## Ignore Visual Studio temporary files, build results, and 112 | ## files generated by popular Visual Studio add-ons. 113 | ## 114 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 115 | 116 | # User-specific files 117 | *.rsuser 118 | *.suo 119 | *.user 120 | *.userosscache 121 | *.sln.docstates 122 | 123 | # User-specific files (MonoDevelop/Xamarin Studio) 124 | *.userprefs 125 | 126 | # Mono auto generated files 127 | mono_crash.* 128 | 129 | # Build results 130 | [Dd]ebug/ 131 | [Dd]ebugPublic/ 132 | [Rr]elease/ 133 | [Rr]eleases/ 134 | x64/ 135 | x86/ 136 | [Aa][Rr][Mm]/ 137 | [Aa][Rr][Mm]64/ 138 | bld/ 139 | [Bb]in/ 140 | [Oo]bj/ 141 | [Ll]og/ 142 | [Ll]ogs/ 143 | 144 | # Visual Studio 2015/2017 cache/options directory 145 | .vs/ 146 | # Uncomment if you have tasks that create the project's static files in wwwroot 147 | #wwwroot/ 148 | 149 | # Visual Studio 2017 auto generated files 150 | Generated\ Files/ 151 | 152 | # MSTest test Results 153 | [Tt]est[Rr]esult*/ 154 | [Bb]uild[Ll]og.* 155 | 156 | # NUnit 157 | *.VisualState.xml 158 | TestResult.xml 159 | nunit-*.xml 160 | 161 | # Build Results of an ATL Project 162 | [Dd]ebugPS/ 163 | [Rr]eleasePS/ 164 | dlldata.c 165 | 166 | # Benchmark Results 167 | BenchmarkDotNet.Artifacts/ 168 | 169 | # .NET Core 170 | project.lock.json 171 | project.fragment.lock.json 172 | artifacts/ 173 | 174 | # StyleCop 175 | StyleCopReport.xml 176 | 177 | # Files built by Visual Studio 178 | *_i.c 179 | *_p.c 180 | *_h.h 181 | *.ilk 182 | *.meta 183 | *.obj 184 | *.iobj 185 | *.pch 186 | *.pdb 187 | *.ipdb 188 | *.pgc 189 | *.pgd 190 | *.rsp 191 | *.sbr 192 | *.tlb 193 | *.tli 194 | *.tlh 195 | *.tmp 196 | *.tmp_proj 197 | *_wpftmp.csproj 198 | *.vspscc 199 | *.vssscc 200 | .builds 201 | *.pidb 202 | *.svclog 203 | *.scc 204 | 205 | # Chutzpah Test files 206 | _Chutzpah* 207 | 208 | # Visual C++ cache files 209 | ipch/ 210 | *.aps 211 | *.ncb 212 | *.opendb 213 | *.opensdf 214 | *.sdf 215 | *.cachefile 216 | *.VC.db 217 | *.VC.VC.opendb 218 | 219 | # Visual Studio profiler 220 | *.psess 221 | *.vsp 222 | *.vspx 223 | *.sap 224 | 225 | # Visual Studio Trace Files 226 | *.e2e 227 | 228 | # TFS 2012 Local Workspace 229 | $tf/ 230 | 231 | # Guidance Automation Toolkit 232 | *.gpState 233 | 234 | # ReSharper is a .NET coding add-in 235 | _ReSharper*/ 236 | *.[Rr]e[Ss]harper 237 | *.DotSettings.user 238 | 239 | # TeamCity is a build add-in 240 | _TeamCity* 241 | 242 | # DotCover is a Code Coverage Tool 243 | *.dotCover 244 | 245 | # AxoCover is a Code Coverage Tool 246 | .axoCover/* 247 | !.axoCover/settings.json 248 | 249 | # Coverlet is a free, cross platform Code Coverage Tool 250 | coverage*[.json, .xml, .info] 251 | 252 | # Visual Studio code coverage results 253 | *.coverage 254 | *.coveragexml 255 | 256 | # NCrunch 257 | _NCrunch_* 258 | .*crunch*.local.xml 259 | nCrunchTemp_* 260 | 261 | # MightyMoose 262 | *.mm.* 263 | AutoTest.Net/ 264 | 265 | # Web workbench (sass) 266 | .sass-cache/ 267 | 268 | # Installshield output folder 269 | [Ee]xpress/ 270 | 271 | # DocProject is a documentation generator add-in 272 | DocProject/buildhelp/ 273 | DocProject/Help/*.HxT 274 | DocProject/Help/*.HxC 275 | DocProject/Help/*.hhc 276 | DocProject/Help/*.hhk 277 | DocProject/Help/*.hhp 278 | DocProject/Help/Html2 279 | DocProject/Help/html 280 | 281 | # Click-Once directory 282 | publish/ 283 | 284 | # Publish Web Output 285 | *.[Pp]ublish.xml 286 | *.azurePubxml 287 | # Note: Comment the next line if you want to checkin your web deploy settings, 288 | # but database connection strings (with potential passwords) will be unencrypted 289 | *.pubxml 290 | *.publishproj 291 | 292 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 293 | # checkin your Azure Web App publish settings, but sensitive information contained 294 | # in these scripts will be unencrypted 295 | PublishScripts/ 296 | 297 | # NuGet Packages 298 | *.nupkg 299 | # NuGet Symbol Packages 300 | *.snupkg 301 | # The packages folder can be ignored because of Package Restore 302 | **/[Pp]ackages/* 303 | # except build/, which is used as an MSBuild target. 304 | !**/[Pp]ackages/build/ 305 | # Uncomment if necessary however generally it will be regenerated when needed 306 | #!**/[Pp]ackages/repositories.config 307 | # NuGet v3's project.json files produces more ignorable files 308 | *.nuget.props 309 | *.nuget.targets 310 | 311 | # Microsoft Azure Build Output 312 | csx/ 313 | *.build.csdef 314 | 315 | # Microsoft Azure Emulator 316 | ecf/ 317 | rcf/ 318 | 319 | # Windows Store app package directories and files 320 | AppPackages/ 321 | BundleArtifacts/ 322 | Package.StoreAssociation.xml 323 | _pkginfo.txt 324 | *.appx 325 | *.appxbundle 326 | *.appxupload 327 | 328 | # Visual Studio cache files 329 | # files ending in .cache can be ignored 330 | *.[Cc]ache 331 | # but keep track of directories ending in .cache 332 | !?*.[Cc]ache/ 333 | 334 | # Others 335 | ClientBin/ 336 | ~$* 337 | *~ 338 | *.dbmdl 339 | *.dbproj.schemaview 340 | *.jfm 341 | *.pfx 342 | *.publishsettings 343 | orleans.codegen.cs 344 | 345 | # Including strong name files can present a security risk 346 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 347 | #*.snk 348 | 349 | # Since there are multiple workflows, uncomment next line to ignore bower_components 350 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 351 | #bower_components/ 352 | 353 | # RIA/Silverlight projects 354 | Generated_Code/ 355 | 356 | # Backup & report files from converting an old project file 357 | # to a newer Visual Studio version. Backup files are not needed, 358 | # because we have git ;-) 359 | _UpgradeReport_Files/ 360 | Backup*/ 361 | UpgradeLog*.XML 362 | UpgradeLog*.htm 363 | ServiceFabricBackup/ 364 | *.rptproj.bak 365 | 366 | # SQL Server files 367 | *.mdf 368 | *.ldf 369 | *.ndf 370 | 371 | # Business Intelligence projects 372 | *.rdl.data 373 | *.bim.layout 374 | *.bim_*.settings 375 | *.rptproj.rsuser 376 | *- [Bb]ackup.rdl 377 | *- [Bb]ackup ([0-9]).rdl 378 | *- [Bb]ackup ([0-9][0-9]).rdl 379 | 380 | # Microsoft Fakes 381 | FakesAssemblies/ 382 | 383 | # GhostDoc plugin setting file 384 | *.GhostDoc.xml 385 | 386 | # Node.js Tools for Visual Studio 387 | .ntvs_analysis.dat 388 | node_modules/ 389 | 390 | # Visual Studio 6 build log 391 | *.plg 392 | 393 | # Visual Studio 6 workspace options file 394 | *.opt 395 | 396 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 397 | *.vbw 398 | 399 | # Visual Studio LightSwitch build output 400 | **/*.HTMLClient/GeneratedArtifacts 401 | **/*.DesktopClient/GeneratedArtifacts 402 | **/*.DesktopClient/ModelManifest.xml 403 | **/*.Server/GeneratedArtifacts 404 | **/*.Server/ModelManifest.xml 405 | _Pvt_Extensions 406 | 407 | # Paket dependency manager 408 | .paket/paket.exe 409 | paket-files/ 410 | 411 | # FAKE - F# Make 412 | .fake/ 413 | 414 | # CodeRush personal settings 415 | .cr/personal 416 | 417 | # Python Tools for Visual Studio (PTVS) 418 | __pycache__/ 419 | *.pyc 420 | 421 | # Cake - Uncomment if you are using it 422 | # tools/** 423 | # !tools/packages.config 424 | 425 | # Tabs Studio 426 | *.tss 427 | 428 | # Telerik's JustMock configuration file 429 | *.jmconfig 430 | 431 | # BizTalk build output 432 | *.btp.cs 433 | *.btm.cs 434 | *.odx.cs 435 | *.xsd.cs 436 | 437 | # OpenCover UI analysis results 438 | OpenCover/ 439 | 440 | # Azure Stream Analytics local run output 441 | ASALocalRun/ 442 | 443 | # MSBuild Binary and Structured Log 444 | *.binlog 445 | 446 | # NVidia Nsight GPU debugger configuration file 447 | *.nvuser 448 | 449 | # MFractors (Xamarin productivity tool) working folder 450 | .mfractor/ 451 | 452 | # Local History for Visual Studio 453 | .localhistory/ 454 | 455 | # BeatPulse healthcheck temp database 456 | healthchecksdb 457 | 458 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 459 | MigrationBackup/ 460 | 461 | # Ionide (cross platform F# VS Code tools) working folder 462 | .ionide/ 463 | 464 | # End of https://www.toptal.com/developers/gitignore/api/go,vs,nextjs,react,dotenv 465 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ecom-Golang-Fiber-Nextjs 2 | >### Development Mode 3 | >### Backend: 4 | >#### Step 1 => go run main.go Database migrations is done in this step. 5 | >#### Step 2 => go run database/seeder/seeder.go Seed the database. 6 | >#### Step 3 => go run main.go Run the backend in Dev mode. 7 | >### Frontend: 8 | >#### Step 1 => cd front Change current directory to front. 9 | >#### Step 2 => npm i Install npm dependencies. 10 | >#### Step 3 => npm run dev Run the frontend in Dev mode. 11 | 12 | >### Production Mode 13 | >### Backend: 14 | >#### Step 1 => go run main.go Migrations is done in this step. 15 | >#### Step 2 => go build To Build the app. 16 | >#### Step 3 => ./'NameOfTheApp' Run the backend in Prod mode. 17 | >### Frontend: 18 | >#### Step 1 => cd front Change current directory to front. 19 | >#### Step 2 => npm i Install npm dependencies. 20 | >#### Step 3 => npm run build Build frontend. 21 | >#### Step 4 => npm run start Run the frontend in Prod mode. 22 | -------------------------------------------------------------------------------- /controllers/authController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "fmt" 5 | "github.com/go-playground/validator/v10" 6 | "gonextjs/database" 7 | "gonextjs/middlewares" 8 | "gonextjs/models" 9 | "strconv" 10 | "time" 11 | 12 | "github.com/dgrijalva/jwt-go" 13 | "github.com/gofiber/fiber/v2" 14 | "gorm.io/gorm" 15 | ) 16 | 17 | func Register(c *fiber.Ctx) error { 18 | var body models.NewUser 19 | if err := c.BodyParser(&body); err != nil { 20 | return err 21 | } 22 | 23 | validate := validator.New() 24 | err := validate.Struct(body) 25 | if err != nil { 26 | for _, err := range err.(validator.ValidationErrors) { 27 | switch err.Tag() { 28 | case "email": 29 | return c.Status(422).JSON(fiber.Map{ 30 | "message": fmt.Sprintf("%s address is invalid", err.Field()), 31 | }) 32 | case "required": 33 | return c.Status(422).JSON(fiber.Map{ 34 | "message": fmt.Sprintf("%s can not be empty", err.Field()), 35 | }) 36 | default: 37 | return err 38 | } 39 | } 40 | } 41 | if body.Password != body.PasswordConfirm { 42 | return c.Status(500).JSON(fiber.Map{ 43 | "message": "passwords do not match", 44 | }) 45 | } 46 | var userType models.UserTypes 47 | userType.UserType = "Customer" 48 | database.DBConn.Find(&userType) 49 | user := models.User{ 50 | FirstName: body.FirstName, 51 | LastName: body.LastName, 52 | Email: body.Email, 53 | TypeId: userType.ID, 54 | } 55 | user.SetPassword(body.Password) 56 | if err := database.DBConn.Preload("UserTypes").Create(&user).Error; err != nil { 57 | return c.Status(500).JSON(fiber.Map{ 58 | "message": "Email is already registered!", 59 | }) 60 | } 61 | 62 | payload := jwt.StandardClaims{ 63 | Subject: strconv.Itoa(int(user.ID)), 64 | ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), 65 | } 66 | token, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, payload).SignedString([]byte("secret")) 67 | cookie := fiber.Cookie{ 68 | Name: "jwt", 69 | Value: token, 70 | Expires: time.Now().Add(time.Hour * 24), 71 | HTTPOnly: true, 72 | } 73 | c.Cookie(&cookie) 74 | return c.JSON(user) 75 | } 76 | 77 | func Login(c *fiber.Ctx) error { 78 | var body models.Login 79 | if err := c.BodyParser(&body); err != nil { 80 | return err 81 | } 82 | 83 | validate := validator.New() 84 | err := validate.Struct(body) 85 | if err != nil { 86 | for _, err := range err.(validator.ValidationErrors) { 87 | switch err.Tag() { 88 | case "email": 89 | return c.Status(422).JSON(fiber.Map{ 90 | "message": fmt.Sprintf("%s address is invalid", err.Field()), 91 | }) 92 | case "required": 93 | return c.Status(422).JSON(fiber.Map{ 94 | "message": fmt.Sprintf("%s can not be empty", err.Field()), 95 | }) 96 | case "gte": 97 | return c.Status(422).JSON(fiber.Map{ 98 | "message": fmt.Sprintf("%s can not be less then %s", err.Field(), err.Param()), 99 | }) 100 | default: 101 | return err 102 | } 103 | } 104 | } 105 | 106 | var user models.User 107 | database.DBConn.Where("email = ?", body.Email).First(&user) 108 | if user.ID == 0 { 109 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 110 | "message": "Invalid Credentials", 111 | }) 112 | } 113 | if err := user.ComparePasswords(body.Password); err != nil { 114 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 115 | "message": "Invalid Credentials", 116 | }) 117 | } 118 | 119 | payload := jwt.StandardClaims{ 120 | Subject: strconv.Itoa(int(user.ID)), 121 | ExpiresAt: time.Now().Add(time.Hour * 24).Unix(), 122 | } 123 | token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, payload).SignedString([]byte("secret")) 124 | if err != nil { 125 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 126 | "message": "Invalid Credentials", 127 | }) 128 | } 129 | 130 | cookie := fiber.Cookie{ 131 | Name: "jwt", 132 | Value: token, 133 | Expires: time.Now().Add(time.Hour * 24), 134 | HTTPOnly: true, 135 | } 136 | c.Cookie(&cookie) 137 | return c.JSON(fiber.Map{ 138 | "message": "success", 139 | }) 140 | } 141 | 142 | func User(c *fiber.Ctx) error { 143 | id, _ := middlewares.GetUserId(c) 144 | var user models.User 145 | database.DBConn.Preload("UserTypes").Where("id = ?", id).First(&user) 146 | return c.JSON(user) 147 | } 148 | 149 | func Logout(c *fiber.Ctx) error { 150 | cookie := fiber.Cookie{ 151 | Name: "jwt", 152 | Value: "", 153 | Expires: time.Now().Add(-time.Hour), 154 | HTTPOnly: true, 155 | } 156 | 157 | c.Cookie(&cookie) 158 | return c.JSON(fiber.Map{ 159 | "message": "success", 160 | }) 161 | } 162 | 163 | func UpdateInfo(c *fiber.Ctx) error { 164 | var body models.UpdateUser 165 | if err := c.BodyParser(&body); err != nil { 166 | return err 167 | } 168 | id, _ := middlewares.GetUserId(c) 169 | 170 | validate := validator.New() 171 | err := validate.Struct(body) 172 | if err != nil { 173 | for _, err := range err.(validator.ValidationErrors) { 174 | switch err.Tag() { 175 | case "email": 176 | if !(body.Email == "") { 177 | return c.Status(422).JSON(fiber.Map{ 178 | "message": fmt.Sprintf("%s address is invalid", err.Field()), 179 | }) 180 | } 181 | case "required": 182 | return c.Status(422).JSON(fiber.Map{ 183 | "message": fmt.Sprintf("%s can not be empty", err.Field()), 184 | }) 185 | default: 186 | return err 187 | } 188 | } 189 | } 190 | 191 | user := models.User{Model: gorm.Model{ID: id}} 192 | if body.FirstName != "" { 193 | user.FirstName = body.FirstName 194 | } 195 | 196 | if body.LastName != "" { 197 | user.LastName = body.LastName 198 | } 199 | 200 | if body.Email != "" { 201 | user.Email = body.Email 202 | } 203 | if err := database.DBConn.Model(&user).Updates(&user).Error; err != nil { 204 | return c.Status(500).JSON(fiber.Map{ 205 | "message": "Email is already in use! try another email", 206 | }) 207 | } 208 | return c.JSON(user) 209 | } 210 | 211 | func UpdatePassword(c *fiber.Ctx) error { 212 | id, _ := middlewares.GetUserId(c) 213 | var body models.UserPassword 214 | if err := c.BodyParser(&body); err != nil { 215 | return err 216 | } 217 | 218 | validate := validator.New() 219 | err := validate.Struct(body) 220 | if err != nil { 221 | for _, err := range err.(validator.ValidationErrors) { 222 | switch err.Tag() { 223 | case "required": 224 | return c.Status(422).JSON(fiber.Map{ 225 | "message": fmt.Sprintf("%s can not be empty", err.Field()), 226 | }) 227 | case "gte": 228 | return c.Status(422).JSON(fiber.Map{ 229 | "message": fmt.Sprintf("%s can not be less then %s", err.Field(), err.Param()), 230 | }) 231 | default: 232 | return err 233 | } 234 | } 235 | } 236 | var user models.User 237 | database.DBConn.Where("id = ?", id).First(&user) 238 | if err := user.ComparePasswords(body.CurrentPassword); err != nil { 239 | return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ 240 | "message": "Invalid Credentials", 241 | }) 242 | } 243 | if body.Password != body.PasswordConfirm { 244 | return c.Status(500).JSON(fiber.Map{ 245 | "message": "passwords did not match", 246 | }) 247 | } 248 | 249 | updateUser := models.User{ 250 | Model: gorm.Model{ID: id}, 251 | } 252 | updateUser.SetPassword(body.Password) 253 | database.DBConn.Model(&updateUser).Updates(&updateUser) 254 | return c.JSON(fiber.Map{ 255 | "message": "Password Updated", 256 | }) 257 | } 258 | -------------------------------------------------------------------------------- /controllers/categoryController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "gonextjs/database" 6 | "gonextjs/models" 7 | ) 8 | 9 | func AddCategory(c *fiber.Ctx) error { 10 | var data map[string]string 11 | if err := c.BodyParser(&data); err != nil { 12 | return err 13 | } 14 | if data["description"] == "" { 15 | return c.Status(500).JSON(fiber.Map{ 16 | "message": "description is required!", 17 | }) 18 | } 19 | category := models.Categories{ 20 | Description: data["description"], 21 | } 22 | if err := database.DBConn.Create(&category).Error; err != nil { 23 | return c.Status(500).JSON(fiber.Map{ 24 | "message": "Error", 25 | }) 26 | } 27 | 28 | return c.JSON(category) 29 | } 30 | 31 | func GetAllCategories(c *fiber.Ctx) error { 32 | var categories []models.Categories 33 | database.DBConn.Find(&categories) 34 | return c.JSON(categories) 35 | } 36 | -------------------------------------------------------------------------------- /controllers/currencyController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "gonextjs/database" 6 | "gonextjs/models" 7 | ) 8 | 9 | func AddCurrency(c *fiber.Ctx) error { 10 | var data map[string]string 11 | if err := c.BodyParser(&data); err != nil { 12 | return err 13 | } 14 | if data["title"] == "" { 15 | return c.Status(500).JSON(fiber.Map{ 16 | "message": "title is required!", 17 | }) 18 | } 19 | if data["code"] == "" { 20 | return c.Status(500).JSON(fiber.Map{ 21 | "message": "code is required!", 22 | }) 23 | } 24 | currency := models.Currency{ 25 | Title: data["title"], 26 | Code: data["code"], 27 | } 28 | if err := database.DBConn.Create(¤cy).Error; err != nil { 29 | return c.Status(500).JSON(fiber.Map{ 30 | "message": "Error", 31 | }) 32 | } 33 | 34 | return c.JSON(currency) 35 | } 36 | 37 | func GetAllCurrencies(c *fiber.Ctx) error { 38 | var currencies []models.Currency 39 | database.DBConn.Find(¤cies) 40 | return c.JSON(currencies) 41 | } 42 | -------------------------------------------------------------------------------- /controllers/productController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "gonextjs/database" 6 | "gonextjs/models" 7 | "gonextjs/util" 8 | "math" 9 | "strconv" 10 | ) 11 | 12 | type ProductsList struct { 13 | Products []models.Product 14 | FirstPage int 15 | CurrentPage int 16 | LastPage int 17 | } 18 | 19 | func GetAllProducts(c *fiber.Ctx) error { 20 | page, _ := strconv.Atoi(c.Query("page", "1")) 21 | pageSize, _ := strconv.Atoi(c.Query("pageSize", "10")) 22 | switch { 23 | case pageSize > 20: 24 | pageSize = 20 25 | case pageSize <= 0: 26 | pageSize = 10 27 | } 28 | offset := (page - 1) * pageSize 29 | 30 | var products ProductsList 31 | var totalRows int64 32 | var totalPage int 33 | 34 | database.DBConn.Model(models.Product{}).Count(&totalRows) 35 | totalPage = int(math.Ceil(float64(totalRows) / float64(pageSize))) 36 | 37 | products.FirstPage = 1 38 | products.CurrentPage = page 39 | products.LastPage = totalPage 40 | 41 | database.DBConn.Offset(offset).Limit(pageSize).Preload("User").Preload("Categories").Preload("Store").Preload("Currency").Find(&products.Products) 42 | return c.JSON(products) 43 | } 44 | 45 | func GetProduct(c *fiber.Ctx) error { 46 | id, _ := c.ParamsInt("id") 47 | var product models.Product 48 | product.ID = uint(id) 49 | database.DBConn.Preload("User").Preload("Categories").Preload("Store").Preload("Currency").Find(&product) 50 | return c.JSON(product) 51 | } 52 | 53 | func UpdateProduct(c *fiber.Ctx) error { 54 | id, _ := c.ParamsInt("id") 55 | product := models.Product{} 56 | if err := c.BodyParser(&product); err != nil { 57 | return err 58 | } 59 | product.ID = uint(id) 60 | database.DBConn.Model(&product).Updates(&product) 61 | return c.JSON(product) 62 | } 63 | 64 | func DeleteProduct(c *fiber.Ctx) error { 65 | id, _ := c.ParamsInt("id") 66 | var product models.Product 67 | product.ID = uint(id) 68 | //database.DBConn.Model(&product).Delete(&product) 69 | database.DBConn.Delete(&product) 70 | return nil 71 | } 72 | 73 | func AddProducts(c *fiber.Ctx) error { 74 | var data map[string]string 75 | if err := c.BodyParser(&data); err != nil { 76 | return err 77 | } 78 | if data["title"] == "" { 79 | return c.Status(500).JSON(fiber.Map{ 80 | "message": "title is required!", 81 | }) 82 | } 83 | if data["description"] == "" { 84 | return c.Status(500).JSON(fiber.Map{ 85 | "message": "description is required!", 86 | }) 87 | } 88 | if data["weight"] == "" { 89 | return c.Status(500).JSON(fiber.Map{ 90 | "message": "weight is required!", 91 | }) 92 | } 93 | if data["length"] == "" { 94 | return c.Status(500).JSON(fiber.Map{ 95 | "message": "length is required!", 96 | }) 97 | } 98 | if data["width"] == "" { 99 | return c.Status(500).JSON(fiber.Map{ 100 | "message": "width is required!", 101 | }) 102 | } 103 | if data["height"] == "" { 104 | return c.Status(500).JSON(fiber.Map{ 105 | "message": "height is required!", 106 | }) 107 | } 108 | if data["price"] == "" { 109 | return c.Status(500).JSON(fiber.Map{ 110 | "message": "price is required!", 111 | }) 112 | } 113 | if data["additional_price"] == "" { 114 | return c.Status(500).JSON(fiber.Map{ 115 | "message": "additional_price is required!", 116 | }) 117 | } 118 | if data["qty"] == "" { 119 | return c.Status(500).JSON(fiber.Map{ 120 | "message": "qty is required!", 121 | }) 122 | } 123 | if data["user_id"] == "" { 124 | return c.Status(500).JSON(fiber.Map{ 125 | "message": "user_id is required!", 126 | }) 127 | } 128 | if data["store_id"] == "" { 129 | return c.Status(500).JSON(fiber.Map{ 130 | "message": "store_id is required!", 131 | }) 132 | } 133 | if data["categories_id"] == "" { 134 | return c.Status(500).JSON(fiber.Map{ 135 | "message": "categories_id is required!", 136 | }) 137 | } 138 | if data["currency_id"] == "" { 139 | return c.Status(500).JSON(fiber.Map{ 140 | "message": "currency_id is required!", 141 | }) 142 | } 143 | product := models.Product{ 144 | Title: data["title"], 145 | Description: data["description"], 146 | Weight: util.ParseFloat32(data["weight"]), 147 | Length: util.ParseFloat32(data["length"]), 148 | Width: util.ParseFloat32(data["width"]), 149 | Height: util.ParseFloat32(data["height"]), 150 | Price: util.ParseFloat32(data["price"]), 151 | AdditionalPrice: util.ParseFloat32(data["additional_price"]), 152 | QTY: util.ParseInt(data["qty"]), 153 | UserID: uint(util.ParseInt(data["user_id"])), 154 | StoreID: uint(util.ParseInt(data["store_id"])), 155 | CategoriesID: uint(util.ParseInt(data["categories_id"])), 156 | CurrencyID: uint(util.ParseInt(data["currency_id"])), 157 | } 158 | if err := database.DBConn.Create(&product).Error; err != nil { 159 | return c.Status(500).JSON(fiber.Map{ 160 | "message": err, 161 | }) 162 | } 163 | 164 | return c.JSON(product) 165 | } 166 | -------------------------------------------------------------------------------- /controllers/storeController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "github.com/gofiber/fiber/v2" 5 | "gonextjs/database" 6 | "gonextjs/models" 7 | ) 8 | 9 | func AddStore(c *fiber.Ctx) error { 10 | var data map[string]string 11 | if err := c.BodyParser(&data); err != nil { 12 | return err 13 | } 14 | if data["name"] == "" { 15 | return c.Status(500).JSON(fiber.Map{ 16 | "message": "name is required!", 17 | }) 18 | } 19 | if data["location"] == "" { 20 | return c.Status(500).JSON(fiber.Map{ 21 | "message": "location is required!", 22 | }) 23 | } 24 | store := models.Store{ 25 | Name: data["name"], 26 | Location: data["location"], 27 | } 28 | if err := database.DBConn.Create(&store).Error; err != nil { 29 | return c.Status(500).JSON(fiber.Map{ 30 | "message": "Error", 31 | }) 32 | } 33 | 34 | return c.JSON(store) 35 | } 36 | 37 | func GetAllStores(c *fiber.Ctx) error { 38 | var stores []models.Store 39 | database.DBConn.Find(&stores) 40 | return c.JSON(stores) 41 | } 42 | -------------------------------------------------------------------------------- /controllers/userController.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | -------------------------------------------------------------------------------- /database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "fmt" 5 | "gonextjs/models" 6 | 7 | "gorm.io/driver/postgres" 8 | "gorm.io/gorm" 9 | ) 10 | 11 | var ( 12 | DBConn *gorm.DB 13 | ) 14 | 15 | func Connect() { 16 | dsn := "host=localhost user=postgres password=root dbname=nextlearn port=5432 sslmode=disable TimeZone=Asia/Shanghai" 17 | var err error 18 | DBConn, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ 19 | SkipDefaultTransaction: true, 20 | }) 21 | if err != nil { 22 | panic("Failed to connect to database") 23 | } 24 | fmt.Println("Database connection successfully opened") 25 | } 26 | func AutoMigrate() { 27 | err := DBConn.AutoMigrate(models.UserTypes{}, models.User{}, models.Store{}, models.Currency{}, models.Categories{}, models.Product{}, models.ProductsSold{}, models.Order{}, models.Cart{}, models.CartItems{}, models.Coupons{}, models.Reviews{}) 28 | if err != nil { 29 | return 30 | } 31 | fmt.Println("Database Migrated🎉") 32 | } 33 | -------------------------------------------------------------------------------- /database/seeder/seeder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/bxcodec/faker/v3" 5 | "gonextjs/database" 6 | "gonextjs/models" 7 | "math/rand" 8 | ) 9 | 10 | func main() { 11 | database.Connect() 12 | var users []models.User 13 | database.DBConn.Find(&users) 14 | if len(users) == 0 { 15 | for i := 0; i < 2; i++ { 16 | store := models.Store{ 17 | Name: faker.Name(), 18 | Location: faker.Sentence(), 19 | } 20 | database.DBConn.Create(&store) 21 | } 22 | 23 | UserType0 := models.UserTypes{ 24 | UserType: "Customer", 25 | } 26 | UserType1 := models.UserTypes{ 27 | UserType: "Seller", 28 | } 29 | UserType2 := models.UserTypes{ 30 | UserType: "Admin", 31 | } 32 | database.DBConn.Create(&UserType0) 33 | database.DBConn.Create(&UserType1) 34 | database.DBConn.Create(&UserType2) 35 | 36 | for i := 0; i < 2; i++ { 37 | categories := models.Categories{ 38 | Description: faker.Sentence(), 39 | } 40 | database.DBConn.Create(&categories) 41 | } 42 | currency := models.Currency{ 43 | Title: "USD", 44 | Code: "$", 45 | } 46 | database.DBConn.Create(¤cy) 47 | 48 | var userTypes models.UserTypes 49 | userTypes.UserType = "Customer" 50 | database.DBConn.Find(&userTypes) 51 | for i := 0; i < 100; i++ { 52 | user := models.User{ 53 | FirstName: faker.FirstName(), 54 | LastName: faker.LastName(), 55 | Email: faker.Email(), 56 | TypeId: userTypes.ID, 57 | } 58 | user.SetPassword("password") 59 | database.DBConn.Create(&user) 60 | var stores []models.Store 61 | var categories []models.Store 62 | var currencies []models.Currency 63 | database.DBConn.Find(&stores) 64 | database.DBConn.Find(&categories) 65 | database.DBConn.Find(¤cies) 66 | product := models.Product{ 67 | Title: faker.Name(), 68 | Description: faker.Paragraph(), 69 | Width: rand.Float32(), 70 | Length: rand.Float32(), 71 | Height: rand.Float32(), 72 | Image: faker.URL(), 73 | Price: float32(rand.Intn(90) + 10), 74 | AdditionalPrice: float32(rand.Intn(90) + 10), 75 | QTY: 3, 76 | UserID: user.ID, 77 | StoreID: stores[0].ID, 78 | CategoriesID: categories[0].ID, 79 | CurrencyID: currencies[0].ID, 80 | } 81 | database.DBConn.Create(&product) 82 | for i := 0; i < 2; i++ { 83 | review := models.Reviews{ 84 | Ratting: 5, 85 | Body: faker.Paragraph(), 86 | UserID: user.ID, 87 | ProductID: product.ID, 88 | } 89 | database.DBConn.Create(&review) 90 | } 91 | product2 := models.Product{ 92 | Title: faker.Name(), 93 | Description: faker.Paragraph(), 94 | Width: rand.Float32(), 95 | Length: rand.Float32(), 96 | Height: rand.Float32(), 97 | Image: faker.URL(), 98 | Price: float32(rand.Intn(90) + 10), 99 | AdditionalPrice: float32(rand.Intn(90) + 10), 100 | QTY: 3, 101 | UserID: user.ID, 102 | StoreID: stores[1].ID, 103 | CategoriesID: categories[1].ID, 104 | CurrencyID: currencies[0].ID, 105 | } 106 | database.DBConn.Create(&product2) 107 | for i := 0; i < 2; i++ { 108 | review := models.Reviews{ 109 | Ratting: 5, 110 | Body: faker.Paragraph(), 111 | UserID: user.ID, 112 | ProductID: product2.ID, 113 | } 114 | database.DBConn.Create(&review) 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /front/.env.example: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | NODE_ENV="development" 3 | NEXT_PUBLIC_BASE="/ecom_fiber" 4 | NEXT_PUBLIC_BASE_API="http://localhost:3000/api/" -------------------------------------------------------------------------------- /front/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /front/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | 40 | # Package-lock.json & yarn.lock 41 | package-lock.json 42 | yarn.lock -------------------------------------------------------------------------------- /front/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | ``` 12 | 13 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 14 | 15 | You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file. 16 | 17 | [API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`. 18 | 19 | The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /front/components/cart.tsx: -------------------------------------------------------------------------------- 1 | import { useDispatch, useSelector } from "react-redux"; 2 | import { RootState } from "../store/store"; 3 | import { FormEvent, useEffect, useState } from "react"; 4 | import axios from "../util/axios"; 5 | import { useRouter } from "next/router"; 6 | import { showCart } from "../store/toggleCart"; 7 | import { ProductState } from "../store/products"; 8 | import { 9 | addItemToCart, 10 | deleteItemFromCart, 11 | removeItemFromCart, 12 | } from "../store/cart"; 13 | import { MinusIcon, PlusIcon } from "@heroicons/react/solid"; 14 | 15 | export default function Cart() { 16 | const router = useRouter(); 17 | const items = useSelector((state: RootState) => state.cart.items); 18 | const show = useSelector((state: RootState) => state.toggleCart.Toggle); 19 | const total = useSelector((state: RootState) => state.cart.total); 20 | const dispatch = useDispatch(); 21 | const [products, setProducts]: [any, any] = useState([]); 22 | useEffect(() => { 23 | if (!router.isReady) return; 24 | setProducts([]); 25 | items.map((item) => { 26 | axios 27 | .get(`products/${item?.ID}`) 28 | .then((res) => { 29 | let userData: ProductState = res.data; 30 | // @ts-ignore 31 | userData.QTY = item.QTY; 32 | setProducts((product: any) => [...product, userData]); 33 | }) 34 | .catch((error) => error); 35 | }); 36 | }, [items, router.isReady]); 37 | const addToCart = (event: FormEvent, id: number, price: number) => { 38 | event.preventDefault(); 39 | dispatch(addItemToCart({ ID: id, QTY: 1, price })); 40 | }; 41 | 42 | const removeFromCart = (event: FormEvent, id: number) => { 43 | event.preventDefault(); 44 | dispatch(removeItemFromCart(id)); 45 | }; 46 | 47 | const deleteItem = (event: FormEvent, id: number) => { 48 | event.preventDefault(); 49 | dispatch(deleteItemFromCart(id)); 50 | }; 51 | 52 | return ( 53 | <> 54 | {show && ( 55 |
59 |
63 |
64 |
68 |
dispatch(showCart(false))} 71 | > 72 | 84 | 85 | 86 | 87 |

Back

88 |
89 |

90 | Cart 91 |

92 | {products.length > 0 && 93 | products.map((product: ProductState, index: number) => ( 94 |
100 |
101 | {"Image"} 106 |
107 |
108 |
109 |

110 | {product.title} 111 |

112 | {/**/} 117 | 151 |
152 |

153 | Height: {product.height} 154 |

155 |

156 | Width: {product.width} 157 |

158 |

159 | Weight: {product.weight} 160 |

161 |
162 | 174 |
175 |

176 | Price: {product.Currency.code + " "} 177 | {product.price + product.additionalPrice} 178 |

179 |

180 | Total: {product.Currency.code + " "} 181 | {(product.price + product.additionalPrice) * 182 | product.QTY} 183 |

184 |
185 |
186 |
187 |
188 | ))} 189 |
190 |
191 |
192 |
193 |

194 | Summary 195 |

196 |
197 |

198 | Subtotal 199 |

200 |

201 | ${total} 202 |

203 |
204 | {/*
*/} 205 | {/*

*/} 206 | {/* Shipping*/} 207 | {/*

*/} 208 | {/*

*/} 209 | {/* $30*/} 210 | {/*

*/} 211 | {/*
*/} 212 | {/*
*/} 213 | {/*

*/} 214 | {/* Tax*/} 215 | {/*

*/} 216 | {/*

*/} 217 | {/* $35*/} 218 | {/*

*/} 219 | {/*
*/} 220 |
221 |
222 |
223 |

224 | Total 225 |

226 |

227 | ${total} 228 |

229 |
230 | 236 |
237 |
238 |
239 |
240 |
241 |
242 | )} 243 | 244 | ); 245 | } 246 | -------------------------------------------------------------------------------- /front/components/layout/base.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Nav from "./nav"; 3 | 4 | interface Props { 5 | children: any 6 | } 7 | 8 | function Base({children}: Props) { 9 | return ( 10 | <> 11 |