Full Code of melardev/GoGonicEcommerceApi for AI

master 163dd9f2d15b cached
45 files
91.0 KB
25.6k tokens
151 symbols
1 requests
Download .txt
Repository: melardev/GoGonicEcommerceApi
Branch: master
Commit: 163dd9f2d15b
Files: 45
Total size: 91.0 KB

Directory structure:
gitextract_mbl266ph/

├── .gitignore
├── README.md
├── controllers/
│   ├── addresses.go
│   ├── categories.go
│   ├── comments.go
│   ├── orders.go
│   ├── pages.go
│   ├── products.go
│   ├── tags.go
│   └── users.go
├── dtos/
│   ├── addresses.go
│   ├── categories.go
│   ├── comments.go
│   ├── orders.go
│   ├── pages.go
│   ├── products.go
│   ├── shared.go
│   ├── tags.go
│   └── users.go
├── infrastructure/
│   └── db.go
├── main.go
├── middlewares/
│   ├── auth.go
│   ├── benchmark.go
│   └── cors.go
├── models/
│   ├── address.go
│   ├── category.go
│   ├── comment.go
│   ├── file_upload.go
│   ├── order.go
│   ├── order_item.go
│   ├── product.go
│   ├── product_category.go
│   ├── product_tag.go
│   ├── role.go
│   ├── tag.go
│   └── user.go
├── seeds/
│   └── seeder.go
└── services/
    ├── addresses.go
    ├── categories.go
    ├── comments.go
    ├── orders.go
    ├── products.go
    ├── shared.go
    ├── tags.go
    └── users.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/static/**
.env
app.db

# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf

# Generated files
.idea/**/contentModel.xml

# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml

# Gradle
.idea/**/gradle.xml
.idea/**/libraries

# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn.  Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules

# CMake
cmake-build-*/

# Mongo Explorer plugin
.idea/**/mongoSettings.xml

# File-based project format
*.iws

# IntelliJ
out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Cursive Clojure plugin
.idea/replstate.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

# Editor-based Rest Client
.idea/httpRequests

# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

================================================
FILE: README.md
================================================
# GoGonicEcommerceApi
# Table of Contents
- [Introduction](#introduction)
- [Full-stack Applications](#full-stack-applications)
  * [E-commerce (shopping cart)](#e-commerce-shopping-cart)
    + [Server side implementations](#server-side-implementations)
    + [Client side implementations](#client-side-implementations)
  * [Blog/CMS](#blogcms)
    + [Server side implementations](#server-side-implementations-1)
    + [Client side](#client-side)
      - [The next come are](#the-next-come-are)
  * [Simple CRUD(Create, Read, Update, Delete)](#simple-crudcreate-read-update-delete)
    + [Server side implementations](#server-side-implementations-2)
    + [Client side implementations](#client-side-implementations-1)
      - [The next come are](#the-next-come-are-1)
  * [CRUD + Pagination](#crud--pagination)
    + [Server side implementations](#server-side-implementations-3)
      - [The next come are](#the-next-come-are-2)
    + [Client side implementations](#client-side-implementations-2)
      - [The next come are](#the-next-come-are-3)
- [Follow me](#social-media-links)
    
# Introduction
This is one of my E-commerce API app implementations. It is written in Golang using go-gonic web framework..
This is not a finished project by any means, but it has a valid enough shape to be git cloned and studied if you are interested in this topic.
If you are interested in this project take a look at my other server API implementations I have made with:

# Full-stack Applications
## E-commerce (shopping cart)
### Server side implementations
- [Spring Boot + Spring Data Hibernate](https://github.com/melardev/SBootApiEcomMVCHibernate)
- [Spring Boot + JAX-RS Jersey + Spring Data Hibernate](https://github.com/melardev/SpringBootEcommerceApiJersey)
- [Node Js + Sequelize](https://github.com/melardev/ApiEcomSequelizeExpress)
- [Node Js + Bookshelf](https://github.com/melardev/ApiEcomBookshelfExpress)
- [Node Js + Mongoose](https://github.com/melardev/ApiEcomMongooseExpress)
- [Python Django](https://github.com/melardev/DjangoRestShopApy)
- [Flask](https://github.com/melardev/FlaskApiEcommerce)
- [Golang go gonic](https://github.com/melardev/api_shop_gonic)
- [Ruby on Rails](https://github.com/melardev/RailsApiEcommerce)
- [AspNet Core](https://github.com/melardev/ApiAspCoreEcommerce)
- [Laravel](https://github.com/melardev/ApiEcommerceLaravel)

The next to come are:
- Spring Boot + Spring Data Hibernate + Kotlin
- Spring Boot + Jax-RS Jersey + Hibernate + Kotlin
- Spring Boot + mybatis
- Spring Boot + mybatis + Kotlin
- Asp.Net Web Api v2
- Elixir
- Golang + Beego
- Golang + Iris
- Golang + Echo
- Golang + Mux
- Golang + Revel
- Golang + Kit
- Flask + Flask-Restful
- AspNetCore + NHibernate
- AspNetCore + Dapper

### Client side implementations
This client side E-commerce application is also implemented using other client side technologies:
- [React Redux](https://github.com/melardev/ReactReduxEcommerceRestApi)
- [React](https://github.com/melardev/ReactEcommerceRestApi)
- [Vue](https://github.com/melardev/VueEcommerceRestApi)
- [Vue + Vuex](https://github.com/melardev/VueVuexEcommerceRestApi)
- [Angular](https://github.com/melardev/AngularEcommerceRestApi)

## Blog/CMS
### Server side implementations
- [Spring Boot + Spring Data Hibernate](https://github.com/melardev/SpringBootApiBlog)
- [Go + Gin Gonic](https://github.com/melardev/GoGonicBlogApi)
- [NodeJs + Mongoose](https://github.com/melardev/ApiBlogExpressMongoose)
- [Laravel](https://github.com/melardev/LaravelApiBlog)
- [Ruby on Rails + JBuilder](https://github.com/melardev/RailsApiBlog)
- [Django + Rest-Framework](https://github.com/melardev/DjangoApiBlog)
- [Asp.Net Core](https://github.com/melardev/AspCoreApiBlog)
- [Flask + Flask-SQLAlchemy](https://github.com/melardev/FlaskApiBlog)

The next to come are:
- Spring Boot + Spring Data Hibernate + Kotlin
- Spring Boot + Jax-RS Jersey + Hibernate + Kotlin
- Spring Boot + mybatis
- Spring Boot + mybatis + Kotlin
- Asp.Net Web Api v2
- Elixir
- Golang + Beego
- Golang + Iris
- Golang + Echo
- Golang + Mux
- Golang + Revel
- Golang + Kit
- Flask + Flask-Restful
- AspNetCore + NHibernate
- AspNetCore + Dapper

### Client side
- [Vue + Vuex](https://github.com/melardev/VueVuexBlog)
- [Vue](https://github.com/melardev/VueBlog)
- [React + Redux](https://github.com/melardev/ReactReduxBlog)
- [React](https://github.com/melardev/ReactBlog)
- [Angular](https://github.com/melardev/AngularBlog)

The next come are
- Angular NgRx-Store
- Angular + Material
- React + Material
- React + Redux + Material
- Vue + Material
- Vue + Vuex + Material
- Ember

## Simple CRUD(Create, Read, Update, Delete)
### Server side implementations
- [Spring Boot + Spring Data Hibernate](https://github.com/melardev/SpringBootApiJpaCrud)
- [Spring boot + Spring Data Reactive Mongo](https://github.com/melardev/SpringBootApiReactiveMongoCrud)
- [Spring Boot + Spring Data Hibernate + Jersey](https://github.com/melardev/SpringBootApiJerseySpringDataCrud)
- [NodeJs Express + Mongoose](https://github.com/melardev/ExpressMongooseApiCrud)
- [Nodejs Express + Bookshelf](https://github.com/melardev/ExpressBookshelfApiCrud)
- [Nodejs Express + Sequelize](https://github.com/melardev/ExpressSequelizeApiCrud)
- [Go + Gin-Gonic + Gorm](https://github.com/melardev/GoGinGonicApiGormCrud)
- [Ruby On Rails](https://github.com/melardev/RailsApiCrud)
- [Ruby On Rails + JBuilder](https://github.com/melardev/RailsApiJBuilderCrud)
- [Laravel](https://github.com/melardev/LaravelApiCrud)
- [AspNet Core](https://github.com/melardev/AspNetCoreApiCrud)
- [AspNet Web Api 2](https://github.com/melardev/AspNetWebApiCrud)
- [Python + Flask](https://github.com/melardev/FlaskApiCrud)
- [Python + Django](https://github.com/melardev/DjanogApiCrud)
- [Python + Django + Rest Framework](https://github.com/melardev/DjangoRestFrameworkCrud)

### Client side implementations
- [VueJs](https://github.com/melardev/VueAsyncCrud)

#### The next come are
- Angular NgRx-Store
- Angular + Material
- React + Material
- React + Redux + Material
- Vue + Material
- Vue + Vuex + Material
- Ember
- Vanilla javascript

## CRUD + Pagination
### Server side implementations
- [Spring Boot + Spring Data + Jersey](https://github.com/melardev/SpringBootJerseyApiPaginatedCrud)
- [Spring Boot + Spring Data](https://github.com/melardev/SpringBootApiJpaPaginatedCrud)
- [Spring Boot Reactive + Spring Data Reactive](https://github.com/melardev/ApiCrudReactiveMongo)
- [Go with Gin Gonic](https://github.com/melardev/GoGinGonicApiPaginatedCrud)
- [Laravel](https://github.com/melardev/LaravelApiPaginatedCrud)
- [Rails + JBuilder](https://github.com/melardev/RailsJBuilderApiPaginatedCrud)
- [Rails](https://github.com/melardev/RailsApiPaginatedCrud)
- [NodeJs Express + Sequelize](https://github.com/melardev/ExpressSequelizeApiPaginatedCrud)
- [NodeJs Express + Bookshelf](https://github.com/melardev/ExpressBookshelfApiPaginatedCrud)
- [NodeJs Express + Mongoose](https://github.com/melardev/ExpressApiMongoosePaginatedCrud)
- [Python Django](https://github.com/melardev/DjangoApiCrudPaginated)
- [Python Django + Rest Framework](https://github.com/melardev/DjangoRestFrameworkPaginatedCrud)
- [Python Flask](https://github.com/melardev/FlaskApiPaginatedCrud)
- [AspNet Core](https://github.com/melardev/AspNetCoreApiPaginatedCrud)
- [AspNet Web Api 2](https://github.com/melardev/WebApiPaginatedAsyncCrud)

#### The next come are
- NodeJs Express + Knex
- Flask + Flask-Restful
- Laravel + Fractal
- Laravel + ApiResources
- Go with Mux
- AspNet Web Api 2
- Jersey
- Elixir

### Client side implementations
- [Angular](https://github.com/melardev/AngularPaginatedAsyncCrud)
- [React-Redux](https://github.com/melardev/ReactReduxPaginatedAsyncCrud)
- [React](https://github.com/melardev/ReactAsyncPaginatedCrud)
- [Vue + Vuex](https://github.com/melardev/VueVuexPaginatedAsyncCrud)
- [Vue](https://github.com/melardev/VuePaginatedAsyncCrud)


#### The next come are
- Angular NgRx-Store
- Angular + Material
- React + Material
- React + Redux + Material
- Vue + Material
- Vue + Vuex + Material
- Ember
- Vanilla javascript

# Social media links
- [Youtube Channel](https://youtube.com/melardev) I publish videos mainly on programming
- [Blog](http://melardev.com) Sometimes I publish the source code there before Github
- [Twitter](https://twitter.com/@melardev) I share tips on programming

## WARNING
I have mass of projects to deal with so I make some copy/paste around, if something I say is missing or is wrong, then I apologize
and you may let me know opening an issue.

# Getting started
1. go get https://github.com/melardev/ApiEcomGoGonic
1. Change the .env.example as you need(see warning below)
1. Rename .env.example to .env
1. Seed the database passing "create seed" as arguments to the app(read main.go to understand what I mean)

## WARNING
The recommended database to use is Postgresql, the other database backends may not work as expected.
Unfortunately the MySQL does not work as expected, for example the BeforeSave Hook for User is not able to retrieve
the Role model if using MySQL, the same code does work if SQLite, it is weird, because the SQL query generated is valid and it
returns a row, but somehow the driver is not able to map it to the user.

# Features
- Authentication / Authorization
- JWT middleware for authentication
- Multi file upload
- Database seed
- Paging with Limit and Offset using GORM (Golang ORM framework)
- CRUD operations on products, comments, tags, categories, orders
![Fetching products page](./github_images/postman.png)
- Orders, guest users may place an order
![Database diagram](./github_images/db_structure.png)

# What you will learn
- Golang
- Golang Go-Gonic web framework
- JWT
- Controllers
- Middlewares
- JWT Authentication
- Role based authorization
- GORM
    - associations: ManyToMany, OneToMany, ManyToOne
    - virtual fields
    - Select specific columns
    - Eager loading
    - Count related association
    
- seed data
- misc
    - project structure

# Understanding the project
The project is meant to be educational, to learn something beyond the hello world thing we find in a lot, lot of 
tutorials and blog posts. Since its main goal is educational, I try to make as much use as features of APIs, in other
words, I used different code to do the same thing over and over, there is some repeated code but I tried to be as unique
as possible so you can learn different ways of achieving the same goal.

Project structure:
- models: Mvc, it is our domain data.
- dtos: it contains our serializers, they will create the response to be sent as json. They also take care of validating the input(feature incomplete)
- controllers: well this is the mvC, they receive the request from the user, they ask the services to perform an action for them on the database.
- seeds: contains the file that seeds the database.
- static: a folder that will be generated when you create a product or tag or category with images
- services: contains some business logic for each model, and for authorization
- middlewares: it contains middlewares(golang functions) that are triggered before the controller action, for example, a middleware which
reads the request looking for the Jwt token and trying to authenticate the user before forwarding the request to the corresponding controller
action

# TODO
- Add model constraints such as not null
- Refactor the seeding with http://gorm.io/docs/query.html#Select
- Global Application Error handling
- Can't Preload field errors:
    - Get comment details http://127.0.0.1:8080/api/products/:slug/comments/:id triggered in services.FetchCommentById
    - Get My Orders http://localhost:8080/api/orders triggered with services.FetchOrdersPage
- Security, validations, file upload
- Delete FileUpload if associated tag, category or product deleted
- Delete Files if tag, category, product fail to be saved
- Use pointers as function parameters instead of passing them by value as I did in many
- For some reason /api/products does not work on browsers due to CORS issues, /api/home does work, on postman all 
routes work ....
# Resources
- [Go-Gonic](https://github.com/gin-gonic/gin) Awesome golang based web framework
- [GORM]()
- [CORS gin's middleware](https://github.com/gin-contrib/cors)

================================================
FILE: controllers/addresses.go
================================================
package controllers

import (
	"github.com/gin-gonic/gin"
	"github.com/melardev/GoGonicEcommerceApi/dtos"
	"github.com/melardev/GoGonicEcommerceApi/middlewares"
	"github.com/melardev/GoGonicEcommerceApi/models"
	"github.com/melardev/GoGonicEcommerceApi/services"

	"net/http"
	"strconv"
)

func RegisterAddressesRoutes(router *gin.RouterGroup) {

	router.Use(middlewares.EnforceAuthenticatedMiddleware())
	{
		router.GET("/addresses", ListAddresses)
		router.POST("/addresses", CreateAddress)
	}

}

func ListAddresses(c *gin.Context) {

	pageSizeStr := c.Query("page_size")
	pageStr := c.Query("page")

	pageSize, err := strconv.Atoi(pageSizeStr)
	if err != nil {
		pageSize = 5
	}

	page, err := strconv.Atoi(pageStr)
	if err != nil {
		page = 1
	}

	// userId:= c.Keys["currentUserId"].(uint) // or
	userId := c.MustGet("currentUserId").(uint)
	includeUser := false
	addresses, totalCommentCount := services.FetchAddressesPage(userId, page, pageSize, includeUser)

	c.JSON(http.StatusOK, dtos.CreateAddressPagedResponse(c.Request, addresses, page, pageSize, totalCommentCount, includeUser))
}

func CreateAddress(c *gin.Context) {

	user := c.MustGet("currentUser").(models.User)

	var json dtos.CreateAddress
	if err := c.ShouldBindJSON(&json); err != nil {
		c.JSON(http.StatusBadRequest, dtos.CreateBadRequestErrorDto(err))
		return
	}
	firstName := json.FirstName
	lastName := json.LastName
	if firstName == "" {
		firstName = user.FirstName
	}
	if lastName == "" {
		lastName = user.LastName
	}
	address := models.Address{
		FirstName:     firstName,
		LastName:      lastName,
		Country:       json.Country,
		City:          json.City,
		StreetAddress: json.StreetAddress,
		ZipCode:       json.ZipCode,
		User:          user,
		UserId:        user.ID,
	}

	if err := services.SaveOne(&address); err != nil {
		c.JSON(http.StatusUnprocessableEntity, dtos.CreateDetailedErrorDto("database_error", err))
		return
	}

	c.JSON(http.StatusOK, dtos.GetAddressCreatedDto(&address, false))
}


================================================
FILE: controllers/categories.go
================================================
package controllers

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/melardev/GoGonicEcommerceApi/dtos"
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/middlewares"
	"github.com/melardev/GoGonicEcommerceApi/models"
	"github.com/melardev/GoGonicEcommerceApi/services"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
)

func RegisterCategoryRoutes(router *gin.RouterGroup) {
	router.GET("", CategoryList)
	router.Use(middlewares.EnforceAuthenticatedMiddleware())
	{
		router.POST("", CreateCategory)
	}
}

func CategoryList(c *gin.Context) {
	tags, err := services.FetchAllCategories()
	if err != nil {
		c.JSON(http.StatusNotFound, dtos.CreateDetailedErrorDto("fetch_error", err))
		return
	}
	c.JSON(http.StatusOK, dtos.CreateCategoryListMapDto(tags))
}

func CreateCategory(c *gin.Context) {
	user := c.MustGet("currentUser").(models.User)
	if user.IsNotAdmin() {
		c.JSON(http.StatusForbidden, dtos.CreateErrorDtoWithMessage("Permission denied, you must be admin"))
		return
	}
	name := c.PostForm("name")
	description := c.PostForm("description")

	form, err := c.MultipartForm()
	if err != nil {
		c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
		return
	}
	files := form.File["images[]"]
	var categoryImages = make([]models.FileUpload, len(files))
	for index, file := range files {
		fileName := randomString(16) + ".png"

		dirPath := filepath.Join(".", "static", "images", "categories")
		filePath := filepath.Join(dirPath, fileName)
		// Create directory if does not exist
		if _, err = os.Stat(dirPath); os.IsNotExist(err) {
			err = os.MkdirAll(dirPath, os.ModeDir)
			if err != nil {
				c.JSON(http.StatusInternalServerError, dtos.CreateDetailedErrorDto("io_error", err))
				return
			}
		}
		// Create file that will hold the image
		outputFile, err := os.Create(filePath)
		if err != nil {
			log.Fatal(err)
		}
		defer outputFile.Close()

		// Open the temporary file that contains the uploaded image
		inputFile, err := file.Open()
		if err != nil {
			c.JSON(http.StatusOK, dtos.CreateDetailedErrorDto("io_error", err))
		}
		defer inputFile.Close()

		// Copy the temporary image to the permanent location outputFile
		_, err = io.Copy(outputFile, inputFile)
		if err != nil {
			log.Fatal(err)
			c.String(http.StatusBadRequest, fmt.Sprintf("upload file err: %s", err.Error()))
			return
		}

		fileSize := (uint)(file.Size)
		categoryImages[index] = models.FileUpload{Filename: file.Filename, FilePath: string(filepath.Separator) + filePath, FileSize: fileSize}
	}

	database := infrastructure.GetDb()
	category := models.Category{Name: name, Description: description, Images: categoryImages}

	// TODO: Why it is performing a SELECT SQL Query per image?
	// Even worse, it is selecting category_id, why??
	// SELECT "tag_id", "product_id" FROM "file_uploads"  WHERE (id = insertedFileUploadId)
	err = database.Create(&category).Error
	if err != nil {
		c.JSON(http.StatusInternalServerError, dtos.CreateDetailedErrorDto("db_error", err))
	}
	c.JSON(http.StatusOK, dtos.CreateCategoryCreatedDto(category))
}


================================================
FILE: controllers/comments.go
================================================
package controllers

import (
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/melardev/GoGonicEcommerceApi/dtos"
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/middlewares"
	"github.com/melardev/GoGonicEcommerceApi/models"
	"github.com/melardev/GoGonicEcommerceApi/services"

	"net/http"
	"strconv"
)

func RegisterCommentRoutes(router *gin.RouterGroup) {
	router.GET("/products/:slug/comments", ListComments)
	router.GET("/products/:slug/comments/:id", ShowComment)
	router.GET("/comments/:id", ShowComment)

	router.Use(middlewares.EnforceAuthenticatedMiddleware())
	{
		router.POST("/products/:slug/comments", CreateComment)
		router.DELETE("/comments/:id", DeleteComment)
		router.DELETE("/products/:slug/comments/:id", DeleteComment)
	}

}

func ListComments(c *gin.Context) {
	slug := c.Param("slug")
	database := infrastructure.GetDb()
	productId := -1

	err := database.Model(&models.Product{}).Where(&models.Product{Slug: slug}).Select("id").Row().Scan(&productId)
	if err != nil {
		c.JSON(http.StatusNotFound, dtos.CreateDetailedErrorDto("comments", errors.New("invalid slug")))
		return
	}
	pageSizeStr := c.Query("page_size")
	pageStr := c.Query("page")

	pageSize, err := strconv.Atoi(pageSizeStr)
	if err != nil {
		pageSize = 5
	}

	page, err := strconv.Atoi(pageStr)
	if err != nil {
		page = 1
	}
	comments, totalCommentCount := services.FetchCommentsPage(productId, page, pageSize)

	c.JSON(http.StatusOK, dtos.CreateCommentPagedResponse(c.Request, comments, page, pageSize, totalCommentCount, true, false))
}

func CreateComment(c *gin.Context) {
	slug := c.Param("slug")
	if slug == "" {
		c.JSON(http.StatusBadRequest, dtos.CreateErrorDtoWithMessage("You must provide a product slug you want to comment"))
		return
	}

	var json dtos.CreateComment
	if err := c.ShouldBindJSON(&json); err != nil {
		c.JSON(http.StatusBadRequest, dtos.CreateBadRequestErrorDto(err))
		return
	}

	productId, err := services.FetchProductId(slug)
	if err != nil {
		c.JSON(http.StatusNotFound, dtos.CreateDetailedErrorDto("database_error", err))
		return
	}

	comment := models.Comment{
		Content:   json.Content,
		ProductId: productId,
		User:      c.MustGet("currentUser").(models.User),
		UserId:    c.MustGet("currentUserId").(uint),
	}

	if err := services.SaveOne(&comment); err != nil {
		c.JSON(http.StatusUnprocessableEntity, dtos.CreateDetailedErrorDto("database_error", err))
		return
	}

	c.JSON(http.StatusOK, dtos.CreateCommentCreatedDto(&comment))
}

func ShowComment(c *gin.Context) {
	idStr := c.Param("id")
	id, err := strconv.Atoi(idStr)
	if err != nil {
		c.JSON(http.StatusBadRequest, dtos.CreateErrorDtoWithMessage("You must provide a valid comment id"))
	}
	comment := services.FetchCommentById(id, true, true)
	c.JSON(http.StatusOK, dtos.GetCommentDetailsDto(&comment, true, true))
}

func DeleteComment(c *gin.Context) {
	currentUser := c.MustGet("currentUser").(models.User)

	id64, err := strconv.ParseUint(c.Param("id"), 10, 32)
	id := uint(id64)
	database := infrastructure.GetDb()
	var comment models.Comment
	err = database.Select([]string{"id", "user_id"}).Find(&comment, id).Error
	if err != nil || comment.ID == 0 {
		// the comment.ID == is redundat, but shows the other way of checking but it is less readable
		c.JSON(http.StatusNotFound, dtos.CreateDetailedErrorDto("comment", err))
	} else if currentUser.ID == comment.UserId || currentUser.IsAdmin() {
		err = database.Delete(&comment).Error
		if err != nil {
			c.JSON(http.StatusNotFound, dtos.CreateDetailedErrorDto("database_error", err))
			return
		}
		c.JSON(http.StatusOK, dtos.CreateSuccessWithMessageDto("Comment Deleted successfully"))
	} else {
		c.JSON(http.StatusForbidden, dtos.CreateErrorDtoWithMessage("You have to be admin or the owner of this comment to delete it"))
	}
}


================================================
FILE: controllers/orders.go
================================================
package controllers

import (
	"github.com/gin-gonic/gin"
	"github.com/melardev/GoGonicEcommerceApi/dtos"
	"github.com/melardev/GoGonicEcommerceApi/middlewares"
	"github.com/melardev/GoGonicEcommerceApi/models"
	"github.com/melardev/GoGonicEcommerceApi/services"
	"net/http"
	"strconv"
)

func RegisterOrderRoutes(router *gin.RouterGroup) {
	router.POST("", CreateOrder)
	router.Use(middlewares.EnforceAuthenticatedMiddleware())
	{
		router.GET("", ListOrders)
		router.GET("/:id", ShowOrder)
	}
}

func ListOrders(c *gin.Context) {
	pageSizeStr := c.Query("page_size")
	pageStr := c.Query("page")
	pageSize, err := strconv.Atoi(pageSizeStr)
	if err != nil {
		pageSize = 5
	}

	page, err := strconv.Atoi(pageStr)
	if err != nil {
		page = 1
	}
	userId := c.MustGet("currentUserId").(uint)

	orders, totalCommentCount, err := services.FetchOrdersPage(userId, page, pageSize)

	c.JSON(http.StatusOK, dtos.CreateOrderPagedResponse(c.Request, orders, page, pageSize, totalCommentCount, false, false))
}

func ShowOrder(c *gin.Context) {
	orderId, err := strconv.Atoi(c.Param("id"))
	user := c.MustGet("currentUser").(models.User)
	order, err := services.FetchOrderDetails(uint(orderId))
	if err != nil {
		c.JSON(http.StatusInternalServerError, dtos.CreateDetailedErrorDto("db_error", err))
		return
	}

	if order.UserId == user.ID || user.IsAdmin() {
		c.JSON(http.StatusOK, dtos.CreateOrderDetailsDto(&order))
	} else {
		c.JSON(http.StatusForbidden, dtos.CreateErrorDtoWithMessage("Permission denied, you can not view this order"))
		return
	}
}

func CreateOrder(c *gin.Context) {
	var orderRequest dtos.CreateOrderRequestDto
	if err := c.ShouldBind(&orderRequest); err != nil {
		c.JSON(http.StatusBadRequest, dtos.CreateBadRequestErrorDto(err))
		return
	}

	userObj, userLoggedIn := c.Get("currentUser")
	var user models.User
	if userLoggedIn {
		user = (userObj).(models.User)
	}

	var address models.Address
	// Reuse address can only be done by authenticated users
	if orderRequest.AddressId != 0 && userLoggedIn {
		address = services.FetchAddress(orderRequest.AddressId)
		/*if err != nil || address.ID == 0 {
			c.JSON(http.StatusBadRequest, dtos.CreateDetailedErrorDto("db_error", err))
			return
		}*/
		if address.UserId != user.ID {
			c.JSON(http.StatusForbidden, dtos.CreateErrorDtoWithMessage("permission denied"))
			return
		}
	} else if orderRequest.AddressId == 0 {
		address = models.Address{
			FirstName:     orderRequest.FirstName,
			LastName:      orderRequest.LastName,
			City:          orderRequest.City,
			Country:       orderRequest.Country,
			StreetAddress: orderRequest.StreetAddress,
			ZipCode:       orderRequest.ZipCode,
		}
		if userLoggedIn {
			address.UserId = user.ID
		}
		err := services.CreateOne(&address)
		if err != nil {
			c.JSON(http.StatusInternalServerError, err)
			return
		}

	} else {
		c.JSON(http.StatusForbidden, dtos.CreateErrorDtoWithMessage("Operation not supported, what are you trying to do?"))
		return
	}

	order := models.Order{
		TrackingNumber: randomString(16),
		OrderStatus:    0,
		Address:        address,
		AddressId:      address.ID,
	}

	if userLoggedIn {
		order.UserId = user.ID
		order.User = user
	}

	var productIds = make([]uint, len(orderRequest.CartItems))
	for i := 0; i < len(orderRequest.CartItems); i++ {
		productIds[i] = orderRequest.CartItems[i].Id
	}

	products, err := services.FetchProductsIdNameAndPrice(productIds)
	if err != nil {
		c.JSON(http.StatusUnprocessableEntity, dtos.CreateDetailedErrorDto("db_error", err))
		return
	}

	if len(products) != len(orderRequest.CartItems) {
		c.JSON(http.StatusUnprocessableEntity, dtos.CreateErrorDtoWithMessage("make sure all products are still available"))
		return
	}
	orderItems := make([]models.OrderItem, len(products))

	for i := 0; i < len(products); i++ {
		// I am assuming product ids returned are in the same order as the cart_items, TODO: implement a more robust code to ensure
		orderItems[i] = models.OrderItem{
			ProductId:   products[i].ID,
			ProductName: products[i].Name,
			Slug:        products[i].Slug,
			Quantity:    orderRequest.CartItems[i].Quantity,
		}
	}

	order.OrderItems = orderItems
	err = services.CreateOne(&order)
	if err != nil {
		c.JSON(http.StatusInternalServerError, err)
		return
	}
	c.JSON(http.StatusOK, dtos.CreateOrderCreatedDto(&order))

}


================================================
FILE: controllers/pages.go
================================================
package controllers

import (
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/melardev/GoGonicEcommerceApi/dtos"
	"github.com/melardev/GoGonicEcommerceApi/services"
	"net/http"
)

func RegisterPageRoutes(router *gin.RouterGroup) {
	router.GET("", Home)
	router.GET("/home", Home)

}

func Home(c *gin.Context) {

	tags, err := services.FetchAllTags()
	categories, err := services.FetchAllCategories()
	if err != nil {
		c.JSON(http.StatusNotFound, dtos.CreateDetailedErrorDto("comments", errors.New("Somethign went wrong")))
		return
	}

	c.JSON(http.StatusOK, dtos.CreateHomeResponse(tags, categories))
}


================================================
FILE: controllers/products.go
================================================
package controllers

// import "C"
import (
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/gosimple/slug"
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/models"
	"os"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/melardev/GoGonicEcommerceApi/dtos"

	"github.com/melardev/GoGonicEcommerceApi/middlewares"
	"github.com/melardev/GoGonicEcommerceApi/services"

	"net/http"
	"strconv"
)

func RegisterProductRoutes(router *gin.RouterGroup) {
	router.GET("/", ProductList)
	router.GET("/:slug", GetProductDetailsBySlug)

	router.Use(middlewares.EnforceAuthenticatedMiddleware())
	{
		router.POST("/", CreateProduct)
		router.DELETE("/:slug", ProductDelete)
	}
}

func ProductList(c *gin.Context) {

	pageSizeStr := c.Query("page_size")
	pageStr := c.Query("page")

	pageSize, err := strconv.Atoi(pageSizeStr)
	if err != nil {
		pageSize = 5
	}

	page, err := strconv.Atoi(pageStr)
	if err != nil {
		page = 1
	}

	productModels, modelCount, commentsCount, err := services.FetchProductsPage(page, pageSize)
	if err != nil {
		c.JSON(http.StatusNotFound, dtos.CreateDetailedErrorDto("products", errors.New("Invalid param")))
		return
	}

	c.JSON(http.StatusOK, dtos.CreatedProductPagedResponse(c.Request, productModels, page, pageSize, modelCount, commentsCount))
}

func GetProductDetailsBySlug(c *gin.Context) {
	productSlug := c.Param("slug")

	product := services.FetchProductDetails(&models.Product{Slug: productSlug}, true)
	if product.ID == 0 {
		c.JSON(http.StatusNotFound, dtos.CreateDetailedErrorDto("products", errors.New("Invalid slug")))
		return
	}
	c.JSON(http.StatusOK, dtos.CreateProductDetailsDto(product))
}

func CreateProduct(c *gin.Context) {
	// Only admin users can create products
	user := c.Keys["currentUser"].(models.User)
	if user.IsNotAdmin() {
		c.JSON(http.StatusForbidden, dtos.CreateErrorDtoWithMessage("Permission denied, you must be admin"))
		return
	}

	var formDto dtos.CreateProduct
	if err := c.ShouldBind(&formDto); err != nil {
		c.JSON(http.StatusBadRequest, dtos.CreateBadRequestErrorDto(err))
		return
	}

	name := formDto.Name
	description := formDto.Description

	price := formDto.Price
	stock, err := strconv.ParseInt(c.PostForm("stock"), 10, 32)
	form, err := c.MultipartForm()

	tagCount := 0
	catCount := 0
	for key := range form.Value {
		if strings.HasPrefix(key, "tags[") {
			tagCount++
		}
		if strings.HasPrefix(key, "category[") {
			catCount++
		}
	}

	var tags = make([]models.Tag, tagCount)
	var categories = make([]models.Category, catCount)

	var rgx = regexp.MustCompile(`\[(.*?)\]`)
	database := infrastructure.GetDb()
	tagPtr := 0
	catPtr := 0

	for k, v := range form.Value {
		if strings.HasPrefix(k, "tags[") {
			result := rgx.FindStringSubmatch(k)
			var tag models.Tag
			name := result[1]
			description := v[0]
			database.Where(&models.Tag{Slug: slug.Make(name)}).
				Attrs(models.Tag{Name: name, Description: description}).
				FirstOrCreate(&tag)
			tags[tagPtr] = tag
			tagPtr++
		}

		if strings.HasPrefix(k, "category[") {
			result := rgx.FindStringSubmatch(k)
			var category models.Category
			name := result[1]
			description := v[0]
			database.Where(&models.Category{Slug: slug.Make(name)}).
				Attrs(models.Category{Name: name, Description: description}).
				FirstOrCreate(&category)
			categories[catPtr] = category
			catPtr++
		}
	}

	if err != nil {
		c.JSON(http.StatusBadRequest, dtos.CreateDetailedErrorDto("form_error", err))
		return
	}

	files := form.File["images[]"]
	var productImages = make([]models.FileUpload, len(files))

	for index, file := range files {
		fileName := randomString(16) + ".png"

		dirPath := filepath.Join(".", "static", "images", "products")
		filePath := filepath.Join(dirPath, fileName)
		if _, err = os.Stat(dirPath); os.IsNotExist(err) {
			err = os.MkdirAll(dirPath, os.ModeDir)
			if err != nil {
				c.JSON(http.StatusInternalServerError, dtos.CreateDetailedErrorDto("io_error", err))
				return
			}
		}
		if err := c.SaveUploadedFile(file, filePath); err != nil {
			c.JSON(http.StatusBadRequest, dtos.CreateDetailedErrorDto("upload_error", err))
			return
		}
		fileSize := (uint)(file.Size)
		productImages[index] = models.FileUpload{Filename: fileName, OriginalName: file.Filename, FilePath: string(filepath.Separator) + filePath, FileSize: fileSize}
	}

	product := models.Product{
		Name:        name,
		Description: description,
		Tags:        tags,
		Categories:  categories,
		Price:       (int)(price),
		Stock:       (int)(stock),
		Images:      productImages,
	}

	if err := services.CreateOne(&product); err != nil {
		c.JSON(http.StatusUnprocessableEntity, dtos.CreateDetailedErrorDto("database", err))
		return
	}

	c.JSON(http.StatusOK, dtos.CreateProductCreatedDto(product))

}

func ProductDelete(c *gin.Context) {
	slug := c.Param("slug")
	err := services.DeleteProduct(&models.Product{Slug: slug})
	if err != nil {
		c.JSON(http.StatusNotFound, dtos.CreateDetailedErrorDto("products", errors.New("Invalid slug")))
		return
	}
	c.JSON(http.StatusOK, gin.H{"product": "Delete success"})
}


================================================
FILE: controllers/tags.go
================================================
package controllers

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/melardev/GoGonicEcommerceApi/dtos"
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/middlewares"
	"github.com/melardev/GoGonicEcommerceApi/models"
	"github.com/melardev/GoGonicEcommerceApi/services"
	"math/rand"
	"net/http"
	"os"
	"path/filepath"
)

func RegisterTagRoutes(router *gin.RouterGroup) {
	router.GET("", TagList)
	router.Use(middlewares.EnforceAuthenticatedMiddleware())
	{
		router.POST("", CreateTag)
	}
}

func TagList(c *gin.Context) {
	tags, err := services.FetchAllTags()
	if err != nil {
		c.JSON(http.StatusNotFound, dtos.CreateDetailedErrorDto("fetch_error", err))
		return
	}
	c.JSON(http.StatusOK, dtos.CreateTagListMapDto(tags))
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randomString(length int) string {
	b := make([]rune, length)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

func CreateTag(c *gin.Context) {
	user := c.Keys["currentUser"].(models.User)
	if user.IsNotAdmin() {
		c.JSON(http.StatusForbidden, dtos.CreateErrorDtoWithMessage("Permission denied, you must be admin"))
		return
	}
	var createForm dtos.CreateTag
	// name := c.PostForm("name")
	// description := c.PostForm("description")

	// If you wanna know more about how binding is done internally check gin-gonic/bin/binding.formBinding.Bind at form.go
	if err := c.ShouldBind(&createForm); err != nil {
		c.JSON(http.StatusBadRequest, dtos.CreateBadRequestErrorDto(err))
		return
	}

	form, err := c.MultipartForm()
	if err != nil {
		c.String(http.StatusBadRequest, fmt.Sprintf("get form err: %s", err.Error()))
		return
	}

	files := form.File["images[]"]
	var tagImages = make([]models.FileUpload, len(files))
	for index, file := range files {
		fileName := randomString(16) + ".png"

		dirPath := filepath.Join(".", "static", "images", "tags")
		filePath := filepath.Join(dirPath, fileName)
		if _, err = os.Stat(dirPath); os.IsNotExist(err) {
			err = os.MkdirAll(dirPath, os.ModeDir)
			if err != nil {
				c.JSON(http.StatusInternalServerError, dtos.CreateDetailedErrorDto("io_error", err))
				return
			}
		}
		if err := c.SaveUploadedFile(file, filePath); err != nil {
			c.JSON(http.StatusBadRequest, dtos.CreateDetailedErrorDto("upload_error", err))
			return
		}
		fileSize := (uint)(file.Size)
		tagImages[index] = models.FileUpload{Filename: fileName, OriginalName: file.Filename, FilePath: string(filepath.Separator) + filePath, FileSize: fileSize}
	}

	database := infrastructure.GetDb()
	tag := models.Tag{Name: createForm.Name, Description: createForm.Description, Images: tagImages}
	// TODO: Why it is performing a SELECT SQL Query per image?
	// Even worse, it is selecting category_id, why??
	// SELECT "category_id", "product_id" FROM "file_uploads"  WHERE (id = insertedFileUploadId)
	err = database.Create(&tag).Error
	if err != nil {
		c.JSON(http.StatusInternalServerError, dtos.CreateDetailedErrorDto("db_error", err))
	}

	c.JSON(http.StatusOK, dtos.CreateTagCreatedDto(tag))
}


================================================
FILE: controllers/users.go
================================================
package controllers

import (
	"errors"
	"github.com/gin-gonic/gin"
	"github.com/melardev/GoGonicEcommerceApi/dtos"
	"github.com/melardev/GoGonicEcommerceApi/services"

	"github.com/melardev/GoGonicEcommerceApi/models"

	"golang.org/x/crypto/bcrypt"
	"net/http"
)

func RegisterUserRoutes(router *gin.RouterGroup) {
	router.POST("/", UsersRegistration)
	router.POST("/login", UsersLogin)
}

func UsersRegistration(c *gin.Context) {

	var json dtos.RegisterRequestDto
	if err := c.ShouldBindJSON(&json); err != nil {
		c.JSON(http.StatusBadRequest, dtos.CreateBadRequestErrorDto(err))
		return
	}

	password, _ := bcrypt.GenerateFromPassword([]byte(json.Password), bcrypt.DefaultCost)
	if err := services.CreateOne(&models.User{
		Username:  json.Username,
		Password:  string(password),
		FirstName: json.FirstName,
		LastName:  json.LastName,
		Email:     json.Email,
	}); err != nil {
		c.JSON(http.StatusUnprocessableEntity, dtos.CreateDetailedErrorDto("database", err))
		return
	}
	c.JSON(http.StatusCreated, gin.H{
		"success":       true,
		"full_messages": []string{"User created successfully"}})
}

func UsersLogin(c *gin.Context) {

	var json dtos.LoginRequestDto
	if err := c.ShouldBindJSON(&json); err != nil {
		c.JSON(http.StatusBadRequest, dtos.CreateBadRequestErrorDto(err))
		return
	}

	user, err := services.FindOneUser(&models.User{Username: json.Username})

	if err != nil {
		c.JSON(http.StatusForbidden, dtos.CreateDetailedErrorDto("login_error", err))
		return
	}

	if user.IsValidPassword(json.Password) != nil {
		c.JSON(http.StatusForbidden, dtos.CreateDetailedErrorDto("login", errors.New("invalid credentials")))
		return
	}

	c.JSON(http.StatusOK, dtos.CreateLoginSuccessful(&user))

}


================================================
FILE: dtos/addresses.go
================================================
package dtos

import (
	"github.com/melardev/GoGonicEcommerceApi/models"
	"net/http"
)

type CreateAddress struct {
	FirstName     string `form:"first_name" json:"first_name" xml:"first_name"`
	LastName      string `form:"last_name" json:"last_name" xml:"last_name"`
	Country       string `form:"country" json:"country" xml:"country" binding:"required"`
	City          string `form:"city" json:"city" xml:"city" binding:"required"`
	StreetAddress string `form:"address" json:"address" xml:"address" binding:"required"`
	ZipCode       string `form:"zip_code" json:"zip_code" xml:"zip_code" binding:"required"`
}

func CreateAddressPagedResponse(request *http.Request, addresses []models.Address, page, page_size, count int, includeUser bool) map[string]interface{} {
	var resources = make([]interface{}, len(addresses))
	for index, address := range addresses {
		resources[index] = GetAddressDto(&address, includeUser)
	}
	return CreatePagedResponse(request, resources, "addresses", page, page_size, count)
}

func GetAddressDto(address *models.Address, includeUser bool) map[string]interface{} {
	dto := map[string]interface{}{
		"id":         address.ID,
		"first_name": address.FirstName,
		"last_name":  address.LastName,
		"zip_code":   address.ZipCode,
		"country":    address.Country,
		"city":       address.City,
	}

	if includeUser {
		dto["user"] = map[string]interface{}{
			"id":       address.UserId,
			"username": address.User.Username,
		}
	}
	return dto
}

func GetAddressCreatedDto(address *models.Address, includeUser bool) map[string]interface{} {
	return CreateSuccessWithDtoAndMessageDto(GetAddressDto(address, includeUser), "StreetAddress created successfully")
}


================================================
FILE: dtos/categories.go
================================================
package dtos

import (
	"github.com/melardev/GoGonicEcommerceApi/models"
	"strings"
)

func CreateCategoryListMapDto(categories []models.Category) map[string]interface{} {
	result := map[string]interface{}{}
	var t = make([]interface{}, len(categories))
	for i := 0; i < len(categories); i++ {
		t[i] = CreateCategoryDto(categories[i])
	}
	result["categories"] = t
	return CreateSuccessDto(result)
}

func CreateCategoryListDto(categories []models.Category) []interface{} {
	var t = make([]interface{}, len(categories))
	for i := 0; i < len(categories); i++ {
		t[i] = CreateCategoryDto(categories[i])
	}
	return t
}

func CreateCategoryDto(category models.Category) map[string]interface{} {
	var imageUrls = make([]string, len(category.Images))
	replaceAllFlag := -1
	for i := 0; i < len(category.Images); i++ {
		imageUrls[i] = strings.Replace(category.Images[i].FilePath, "\\", "/", replaceAllFlag)
	}
	return map[string]interface{}{
		"id":          category.ID,
		"name":        category.Name,
		"description": category.Description,
		"image_urls":  imageUrls,
	}
}

func CreateCategoryCreatedDto(category models.Category) map[string]interface{} {
	return CreateSuccessWithDtoAndMessageDto(CreateCategoryDto(category), "Category created successfully")
}


================================================
FILE: dtos/comments.go
================================================
package dtos

import (
	"github.com/melardev/GoGonicEcommerceApi/models"
	"net/http"
	"time"
)

type CreateComment struct {
	Content string `form:"content" json:"content" xml:"content"  binding:"required"`
}

func CreateCommentPagedResponse(request *http.Request, comments []models.Comment, page, page_size, count int, bools ...bool) map[string]interface{} {
	var resources = make([]interface{}, len(comments))
	for index, comment := range comments {
		includeUser := false
		if len(bools) > 0 {
			includeUser = bools[0]
		}
		includeProduct := false
		if len(bools) > 1 {
			includeProduct = bools[1]
		}

		resources[index] = GetSummary(&comment, includeUser, includeProduct)
	}
	return CreatePagedResponse(request, resources, "comments", page, page_size, count)
}

func GetCommentDetailsDto(comment *models.Comment, includes ...bool) map[string]interface{} {
	includeUser := false
	if len(includes) > 0 {
		includeUser = includes[0]
	}
	includeProduct := false
	if len(includes) > 1 {
		includeProduct = includes[1]
	}
	return GetSummary(comment, includeUser, includeProduct)
}

func GetSummary(comment *models.Comment, includeUser, includeProduct bool) map[string]interface{} {
	result := map[string]interface{}{
		"id":         comment.ID,
		"content":    comment.Content,
		"created_at": comment.CreatedAt.UTC().Format(time.RFC1123),
		"updated_at": comment.UpdatedAt.UTC().Format(time.RFC1123),
	}
	if includeUser == true {
		result["user"] = map[string]interface{}{
			"id":       comment.User.ID,
			"username": comment.User.Username,
		}
	}
	if includeProduct == true {
		result["product"] = map[string]interface{}{
			"id":   comment.Product.ID,
			"name": comment.Product.Name,
			"slug": comment.Product.Slug,
		}
	}
	return result
}

func CreateCommentCreatedDto(comment *models.Comment, includes ...bool) map[string]interface{} {
	return CreateSuccessWithDtoAndMessageDto(GetCommentDetailsDto(comment, includes...), "Comment created successfully")
}


================================================
FILE: dtos/orders.go
================================================
package dtos

import (
	"github.com/melardev/GoGonicEcommerceApi/models"
	"net/http"
)

type CreateOrderRequestDto struct {
	FirstName     string `form:"first_name" json:"first_name" xml:"first_name"`
	LastName      string `form:"last_name" json:"last_name" xml:"last_name"`
	Country       string `form:"country" json:"country" xml:"country"`
	City          string `form:"city" json:"city" xml:"city"`
	StreetAddress string `form:"street_address" json:"street_address" xml:"street_address" `
	ZipCode       string `form:"zip_code" json:"zip_code" xml:"zip_code" `
	AddressId     uint   `form:"address_id" json:"address_id" xml:"address_id" `
	CartItems     []struct {
		Id       uint `form:"id" json:"id" binding:"required"`
		Quantity int  `form:"quantity" json:"quantity" binding:"required"`
	} `json:"cart_items"`
}

func CreateOrderPagedResponse(request *http.Request, orders []models.Order, page, page_size, totalOrdersCount int, includes ...bool) map[string]interface{} {
	var resources = make([]interface{}, len(orders))
	for index, order := range orders {

		includeAddress, includeOrderItems, includeUser := getIncludeFlags(includes...)

		resources[index] = CreateOrderDto(&order, includeAddress, includeOrderItems, includeUser)
	}
	return CreatePagedResponse(request, resources, "orders", page, page_size, totalOrdersCount)
}

func CreateOrderDto(order *models.Order, includes ...bool) map[string]interface{} {

	includeAddress, includeOrderItems, includeUser := getIncludeFlags(includes...)

	result := map[string]interface{}{
		"id":              order.ID,
		"tracking_number": order.TrackingNumber,
		"order_status":    order.GetOrderStatusAsString(),
	}

	if includeAddress {
		result["address"] = map[string]interface{}{
			"first_name":     order.Address.FirstName,
			"last_name":      order.Address.LastName,
			"street_address": order.Address.StreetAddress,
			"city":           order.Address.City,
			"country":        order.Address.Country,
			"zip_code":       order.Address.ZipCode,
		}
	}

	if includeOrderItems {
		orderItems := make([]map[string]interface{}, len(order.OrderItems))
		for i := 0; i < len(order.OrderItems); i++ {
			oi := order.OrderItems[i]
			orderItems[i] = map[string]interface{}{
				"name":  oi.ProductName,
				"slug":  oi.Slug,
				"price": oi.Price,
			}
		}
		result["order_items"] = orderItems
	} else {
		result["order_items_count"] = order.OrderItemsCount
	}

	if includeUser {
		result["user"] = map[string]interface{}{
			"id":       order.UserId,
			"username": order.User.Username,
		}
	}

	return CreateSuccessDto(result)
}

func CreateOrderDetailsDto(order *models.Order) map[string]interface{} {
	// includeUser -> false
	// includeOrderItems -> true
	// includeUser -> false
	return CreateSuccessDto(CreateOrderDto(order, true, true, false))
}

func getIncludeFlags(includes ...bool) (includeAddress, includeOrderItems, includeUser bool) {

	if len(includes) > 0 {
		includeAddress = includes[0]
	}

	if len(includes) > 1 {
		includeOrderItems = includes[1]
	}

	if len(includes) > 2 {
		includeUser = includes[2]
	}
	return
}

func CreateOrderCreatedDto(order *models.Order) map[string]interface{} {
	return CreateSuccessWithDtoAndMessageDto(CreateOrderDetailsDto(order), "Order created successfully")
}


================================================
FILE: dtos/pages.go
================================================
package dtos

import "github.com/melardev/GoGonicEcommerceApi/models"

func CreateHomeResponse(tags []models.Tag, categories []models.Category) map[string]interface{} {
	return CreateSuccessDto(map[string]interface{}{
		"tags":       CreateTagListDto(tags),
		"categories": CreateCategoryListDto(categories),
	})
}


================================================
FILE: dtos/products.go
================================================
package dtos

import (
	"github.com/melardev/GoGonicEcommerceApi/models"
	"net/http"
	"strings"
	"time"
)

type ManagedModel models.Product

type CreateProduct struct {
	Name        string `form:"name" json:"name" xml:"name" binding:"required"`
	Description string `form:"description" json:"description" xml:"description" binding:"required"`
	Price       int    `form:"price" json:"price" xml:"price" binding:"required"`
	Stock       int    `form:"stock" json:"stock" xml:"stock" binding:"required"`
}

func CreatedProductPagedResponse(request *http.Request, products []models.Product, page, page_size, count int, commentsCount []int) interface{} {
	var resources = make([]interface{}, len(products))
	for index, product := range products {
		resources[index] = CreateProductDto(&product, commentsCount[index])
	}
	return CreatePagedResponse(request, resources, "products", page, page_size, count)
}

func CreateProductDto(product *models.Product, commentCount int) map[string]interface{} {

	var tags = make([]map[string]interface{}, len(product.Tags))
	var categories = make([]map[string]interface{}, len(product.Categories))
	var images = make([]string, len(product.Images))

	for index, tag := range product.Tags {
		tags[index] = map[string]interface{}{
			"id":   tag.ID,
			"name": tag.Name,
			"slug": tag.Slug,
		}
	}

	for index, category := range product.Categories {
		categories[index] = map[string]interface{}{
			"id":   category.ID,
			"name": category.Name,
			"slug": category.Slug,
		}
	}
	replaceAllFlag := -1
	for index, image := range product.Images {
		images[index] = strings.Replace(image.FilePath, "\\", "/", replaceAllFlag)
	}

	for index, tag := range product.Tags {
		tags[index] = map[string]interface{}{
			"id":   tag.ID,
			"name": tag.Name,
			"slug": tag.Slug,
		}
	}

	result := map[string]interface{}{
		"id":         product.ID,
		"name":       product.Name,
		"slug":       product.Slug,
		"price":      product.Price,
		"stock":      product.Stock,
		"tags":       tags,
		"categories": categories,
		"image_urls": images,
		"created_at": product.CreatedAt.UTC().Format("2006-01-02T15:04:05.999Z"),
		"updated_at": product.UpdatedAt.UTC().Format(time.RFC3339Nano),
	}

	if commentCount >= 0 {
		// "comments_count": product.CommentsCount,
		result["comments_count"] = commentCount
	}
	return result
}

func CreateProductDetailsDto(product models.Product) map[string]interface{} {
	result := CreateProductDto(&product, -1)
	result["description"] = product.Description
	comments := make([]map[string]interface{}, len(product.Comments))
	for index, comment := range product.Comments {
		comments[index] = GetSummary(&comment, true, false)
	}

	result["comments"] = comments
	return result
}
func CreateProductCreatedDto(product models.Product) map[string]interface{} {
	return CreateSuccessWithDtoAndMessageDto(CreateProductDetailsDto(product), "Product crated successfully")
}


================================================
FILE: dtos/shared.go
================================================
package dtos

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"gopkg.in/go-playground/validator.v8"
	"math"
	"net/http"
)

type BaseDto struct {
	Success      bool     `json:"success"`
	FullMessages []string `json:"full_messages"`
}

type ErrorDto struct {
	BaseDto
	Errors map[string]interface{} `json:"errors"`
}

func CreatePageMeta(request *http.Request, loadedItemsCount, page, page_size, totalItemsCount int) map[string]interface{} {
	page_meta := map[string]interface{}{}
	page_meta["offset"] = (page - 1) * page_size
	page_meta["requested_page_size"] = page_size
	page_meta["current_page_number"] = page
	page_meta["current_items_count"] = loadedItemsCount

	page_meta["prev_page_number"] = 1
	total_pages_count := int(math.Ceil(float64(totalItemsCount) / float64(page_size)))
	page_meta["total_pages_count"] = total_pages_count

	if page < total_pages_count {
		page_meta["has_next_page"] = true
		page_meta["next_page_number"] = page + 1
	} else {
		page_meta["has_next_page"] = false
		page_meta["next_page_number"] = 1
	}
	if page > 1 {
		page_meta["prev_page_number"] = page - 1
	} else {
		page_meta["has_prev_page"] = false
		page_meta["prev_page_number"] = 1
	}

	page_meta["next_page_url"] = fmt.Sprintf("%v?page=%d&page_size=%d", request.URL.Path, page_meta["next_page_number"], page_meta["requested_page_size"])
	page_meta["prev_page_url"] = fmt.Sprintf("%s?page=%d&page_size=%d", request.URL.Path, page_meta["prev_page_number"], page_meta["requested_page_size"])

	response := gin.H{
		"success":   true,
		"page_meta": page_meta,
	}

	return response
}

func CreatePagedResponse(request *http.Request, resources []interface{}, resource_name string, page, page_size, totalItemsCount int) map[string]interface{} {

	response := CreatePageMeta(request, len(resources), page, page_size, totalItemsCount)
	response[resource_name] = resources
	return response
}

func CreateDetailedErrorDto(key string, err error) map[string]interface{} {
	return map[string]interface{}{
		"success":       false,
		"full_messages": []string{fmt.Sprintf("s -> %v", key, err.Error())},
		"errors":        err,
	}
}

func CreateErrorDtoWithMessage(message string) map[string]interface{} {
	return map[string]interface{}{
		"success":       false,
		"full_messages": []string{message},
	}
}

// This should only be called when we have an Error that is returned from a ShouldBind which contains a lot of information
// other kind of errors should use other functions such as CreateDetailedErrorDto
func CreateBadRequestErrorDto(err error) ErrorDto {
	res := ErrorDto{}
	res.Errors = make(map[string]interface{})
	errs := err.(validator.ValidationErrors)
	res.FullMessages = make([]string, len(errs))
	count := 0
	for _, v := range errs {
		if v.ActualTag == "required" {
			var message = fmt.Sprintf("%v is required", v.Field)
			res.Errors[v.Field] = message
			res.FullMessages[count] = message
		} else {
			var message = fmt.Sprintf("%v has to be %v", v.Field, v.ActualTag)
			res.Errors[v.Field] = message
			res.FullMessages = append(res.FullMessages, message)
		}
		count++
	}
	return res
}

func CreateSuccessDto(result map[string]interface{}) map[string]interface{} {
	result["success"] = true
	return result
}

func CreateSuccessWithMessageDto(message string) interface{} {
	return CreateSuccessWithMessagesDto([]string{message})
}

func CreateSuccessWithMessagesDto(messages []string) interface{} {
	return gin.H{
		"success":       true,
		"full_messages": messages,
	}
}

func CreateSuccessWithDtoAndMessagesDto(data map[string]interface{}, messages []string) map[string]interface{} {
	data["success"] = true
	data["full_messages"] = messages
	return data
}
func CreateSuccessWithDtoAndMessageDto(data map[string]interface{}, message string) map[string]interface{} {
	return CreateSuccessWithDtoAndMessagesDto(data, []string{message})
}


================================================
FILE: dtos/tags.go
================================================
package dtos

import (
	"github.com/melardev/GoGonicEcommerceApi/models"
	"strings"
)

type CreateTag struct {
	Name        string `form:"name" binding:"required"`
	Description string `form:"description" binding:"required"`
}

func CreateTagListMapDto(tags []models.Tag) map[string]interface{} {
	result := map[string]interface{}{}
	var t = make([]interface{}, len(tags))
	for i := 0; i < len(tags); i++ {
		t[i] = CreateTagDto(tags[i])
	}
	result["tags"] = t
	return CreateSuccessDto(result)
}

func CreateTagListDto(tags []models.Tag) []interface{} {
	var t = make([]interface{}, len(tags))
	for i := 0; i < len(tags); i++ {
		t[i] = CreateTagDto(tags[i])
	}
	return t
}

func CreateTagDto(tag models.Tag) map[string]interface{} {
	var imageUrls = make([]string, len(tag.Images))
	replaceAllFlag := -1
	for i := 0; i < len(tag.Images); i++ {
		imageUrls[i] = strings.Replace(tag.Images[i].FilePath, "\\", "/", replaceAllFlag)
	}
	return map[string]interface{}{
		"id":          tag.ID,
		"name":        tag.Name,
		"description": tag.Description,
		"image_urls":  imageUrls,
	}
}

func CreateTagCreatedDto(tag models.Tag) map[string]interface{} {
	return CreateSuccessWithDtoAndMessageDto(CreateTagDto(tag), "Tag created successfully")
}


================================================
FILE: dtos/users.go
================================================
package dtos

import (
	"github.com/melardev/GoGonicEcommerceApi/models"
)

type RegisterRequestDto struct {
	Username             string `form:"username" json:"username" xml:"username"  binding:"required"`
	FirstName            string `form:"first_name" json:"first_name" xml:"first_name" binding:"required"`
	LastName             string `form:"last_name" json:"last_name" xml:"last_name" binding:"required"`
	Email                string `form:"email" json:"email" xml:"email" binding:"required"`
	Password             string `form:"password" json:"password" xml:"password" binding:"required"`
	PasswordConfirmation string `form:"password_confirmation" json:"password_confirmation" xml:"password-confirmation" binding:"required"`
}

type LoginRequestDto struct {
	// Username string `form:"username" json:"username" xml:"username" binding:"exists,username"`
	Username string `form:"username" json:"username" xml:"username" binding:"required"`
	Password string `form:"password"json:"password" binding:"exists,min=8,max=255"`

	userModel models.User `json:"-"`
}

func CreateLoginSuccessful(user *models.User) map[string]interface{} {
	var roles = make([]string, len(user.Roles))

	for i := 0; i < len(user.Roles); i++ {
		roles[i] = user.Roles[i].Name
	}

	return map[string]interface{}{
		"success": true,
		"token":   user.GenerateJwtToken(),
		"user": map[string]interface{}{
			"username": user.Username,
			"id":       user.ID,
			"roles":    roles,
		},
	}
}

func GetUserBasicInfo(user models.User) map[string]interface{} {
	return map[string]interface{}{
		"id":       user.ID,
		"username": user.Username,
	}
}


================================================
FILE: infrastructure/db.go
================================================
package infrastructure

import (
	"fmt"
	"github.com/jinzhu/gorm"
	"path"

	_ "github.com/jinzhu/gorm/dialects/mysql"
	_ "github.com/jinzhu/gorm/dialects/postgres"
	_ "github.com/jinzhu/gorm/dialects/sqlite"
	// import _ "github.com/jinzhu/gorm/dialects/mssql"
	"os"
)

type Database struct {
	*gorm.DB
}

var DB *gorm.DB

// Opening a database and save the reference to `Database` struct.
func OpenDbConnection() *gorm.DB {

	dialect := os.Getenv("DB_DIALECT")
	username := os.Getenv("DB_USER")
	password := os.Getenv("DB_PASSWORD")
	dbName := os.Getenv("DB_NAME")
	host := os.Getenv("DB_HOST")
	var db *gorm.DB
	var err error
	if dialect == "sqlite3" {
		db, err = gorm.Open("sqlite3", path.Join(".", "app.db"))
	} else {
		// db, err := gorm.Open("mysql", "root:root@localhost/go_api_shop_gonc?charset=utf8")
		databaseUrl := fmt.Sprintf("host=%s user=%s password=%s dbname=%s sslmode=disable ", host, username, password, dbName)
		db, err = gorm.Open(dialect, databaseUrl)
	}

	if err != nil {
		fmt.Println("db err: ", err)
		os.Exit(-1)
	}

	db.DB().SetMaxIdleConns(10)
	db.LogMode(true)
	DB = db
	return DB
}

// Delete the database after running testing cases.
func RemoveDb(db *gorm.DB) error {
	db.Close()
	err := os.Remove(path.Join(".", "app.db"))
	return err
}

// Using this function to get a connection, you can create your connection pool here.
func GetDb() *gorm.DB {
	return DB
}


================================================
FILE: main.go
================================================
package main

import (
	"fmt"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	"github.com/joho/godotenv"
	"github.com/melardev/GoGonicEcommerceApi/controllers"
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/middlewares"
	"github.com/melardev/GoGonicEcommerceApi/models"
	"github.com/melardev/GoGonicEcommerceApi/seeds"
	"os"
)

func drop(db *gorm.DB) {
	db.DropTableIfExists(
		&models.FileUpload{},
		&models.Comment{},
		&models.OrderItem{}, &models.Order{}, &models.Address{},
		&models.ProductCategory{}, &models.ProductTag{},
		&models.Tag{}, &models.Category{},
		&models.Product{},
		&models.UserRole{}, &models.Role{}, &models.User{})
}

func migrate(database *gorm.DB) {

	database.AutoMigrate(&models.Address{})

	database.AutoMigrate(&models.Category{})
	database.AutoMigrate(&models.Comment{})

	database.AutoMigrate(&models.Order{})
	database.AutoMigrate(&models.OrderItem{})

	database.AutoMigrate(&models.Product{})
	database.AutoMigrate(&models.ProductCategory{})

	database.AutoMigrate(&models.Role{})
	database.AutoMigrate(&models.UserRole{})

	database.AutoMigrate(&models.Tag{})
	database.AutoMigrate(&models.ProductTag{})

	database.AutoMigrate(&models.User{})

	database.AutoMigrate(&models.FileUpload{})
}

func addDbConstraints(database *gorm.DB) {
	// TODO: it is well known GORM does not add foreign keys even after using ForeignKey in struct, but, why manually does not work neither ?

	dialect := database.Dialect().GetName() // mysql, sqlite3
	if dialect != "sqlite3" {
		database.Model(&models.Comment{}).AddForeignKey("product_id", "products(id)", "CASCADE", "CASCADE")
		database.Model(&models.Comment{}).AddForeignKey("user_id", "users(id)", "CASCADE", "CASCADE")

		database.Model(&models.Order{}).AddForeignKey("user_id", "users(id)", "CASCADE", "CASCADE")
		database.Model(&models.Order{}).AddForeignKey("address_id", "addresses(id)", "CASCADE", "CASCADE")
		database.Model(&models.OrderItem{}).AddForeignKey("order_id", "orders(id)", "CASCADE", "CASCADE")
		database.Model(&models.OrderItem{}).AddForeignKey("user_id", "users(id)", "CASCADE", "CASCADE")

		database.Model(&models.Address{}).AddForeignKey("user_id", "users(id)", "CASCADE", "CASCADE")

		database.Model(&models.UserRole{}).AddForeignKey("user_id", "users(id)", "CASCADE", "CASCADE")
		database.Model(&models.UserRole{}).AddForeignKey("role_id", "roles(id)", "CASCADE", "CASCADE")

		database.Table("products_tags").AddForeignKey("product_id", "products(id)", "CASCADE", "CASCADE")
		database.Table("products_tags").AddForeignKey("tag_id", "tags(id)", "CASCADE", "CASCADE")

		database.Model(models.ProductCategory{}).AddForeignKey("product_id", "products(id)", "CASCADE", "CASCADE")
		database.Model(models.ProductCategory{}).AddForeignKey("category_id", "categories(id)", "CASCADE", "CASCADE")
	} else if dialect == "sqlite3" {
		database.Table("comments").AddIndex("comments__idx_product_id", "product_id")
		database.Table("comments").AddIndex("comments__idx_user_id", "user_id")

		database.Table("ratings").AddIndex("ratings__idx_user_id", "user_id")
		database.Table("ratings").AddIndex("ratings__idx_product_id", "product_id")

		database.Model(&models.Comment{}).AddIndex("comments__idx_created_at", "created_at")

	}

	database.Model(&models.UserRole{}).AddIndex("user_roles__idx_user_id", "user_id")
	database.Table("products_tags").AddIndex("products_tags__idx_product_id", "product_id")
}
func create(database *gorm.DB) {
	drop(database)
	migrate(database)
	addDbConstraints(database)
}

func main() {

	e := godotenv.Load() //Load .env file
	if e != nil {
		fmt.Print(e)
	}
	println(os.Getenv("DB_DIALECT"))

	database := infrastructure.OpenDbConnection()

	defer database.Close()
	args := os.Args
	if len(args) > 1 {
		first := args[1]
		second := ""
		if len(args) > 2 {
			second = args[2]
		}

		if first == "create" {
			create(database)
		} else if first == "seed" {
			seeds.Seed()
			os.Exit(0)
		} else if first == "migrate" {
			migrate(database)
		}

		if second == "seed" {
			seeds.Seed()
			os.Exit(0)
		} else if first == "migrate" {
			migrate(database)
		}

		if first != "" && second == "" {
			os.Exit(0)
		}
	}

	migrate(database)

	// gin.New() - new gin Instance with no middlewares
	// goGonicEngine.Use(gin.Logger())
	// goGonicEngine.Use(gin.Recovery())
	goGonicEngine := gin.Default() // gin with the Logger and Recovery Middlewares attached
	// Allow all Origins
	goGonicEngine.Use(cors.Default())

	goGonicEngine.Use(middlewares.Benchmark())

	// goGonicEngine.Use(middlewares.Cors())

	goGonicEngine.Use(middlewares.UserLoaderMiddleware())
	goGonicEngine.Static("/static", "./static")
	apiRouteGroup := goGonicEngine.Group("/api")

	controllers.RegisterUserRoutes(apiRouteGroup.Group("/users"))
	controllers.RegisterProductRoutes(apiRouteGroup.Group("/products"))
	controllers.RegisterCommentRoutes(apiRouteGroup.Group("/"))
	controllers.RegisterPageRoutes(apiRouteGroup.Group("/"))
	controllers.RegisterAddressesRoutes(apiRouteGroup.Group("/users"))
	controllers.RegisterTagRoutes(apiRouteGroup.Group("/tags"))
	controllers.RegisterCategoryRoutes(apiRouteGroup.Group("/categories"))
	controllers.RegisterOrderRoutes(apiRouteGroup.Group("/orders"))

	goGonicEngine.Run(":8080") // listen and serve on 0.0.0.0:8080
}


================================================
FILE: middlewares/auth.go
================================================
package middlewares

import (
	"fmt"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/models"
	"net/http"
	"os"
	"strings"
)

func EnforceAuthenticatedMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		user, exists := c.Get("currentUser")
		if exists && user.(models.User).ID != 0 {
			return
		} else {
			err, _ := c.Get("authErr")
			_ = c.AbortWithError(http.StatusUnauthorized, err.(error))
			return
		}
	}
}

func UserLoaderMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		bearer := c.Request.Header.Get("Authorization")
		if bearer != "" {
			jwtParts := strings.Split(bearer, " ")
			if len(jwtParts) == 2 {
				jwtEncoded := jwtParts[1]

				token, err := jwt.Parse(jwtEncoded, func(token *jwt.Token) (interface{}, error) {
					// Theorically we have also to validate the algorithm
					if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
						return nil, fmt.Errorf("unexpected signin method %v", token.Header["alg"])
					}
					secret := []byte(os.Getenv("JWT_SECRET"))
					return secret, nil
				})

				if err != nil {
					println(err.Error())
					return
				}
				if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
					userId := uint(claims["user_id"].(float64))
					fmt.Printf("[+] Authenticated request, authenticated user id is %d\n", userId)

					var user models.User
					if userId != 0 {
						database := infrastructure.GetDb()
						// We always need the Roles to be loaded to make authorization decisions based on Roles
						database.Preload("Roles").First(&user, userId)
					}

					c.Set("currentUser", user)
					c.Set("currentUserId", user.ID)
				} else {

				}

			}
		}
	}
}


================================================
FILE: middlewares/benchmark.go
================================================
package middlewares

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"math"
	"time"
)

func Benchmark() gin.HandlerFunc {
	return func(c *gin.Context) {
		start := time.Now()
		c.Next()
		elapsed := time.Since(start)
		fmt.Printf("Request took %v milliseconds\n", float64(elapsed.Nanoseconds())/math.Pow(float64(10), float64(6)))
	}
}


================================================
FILE: middlewares/cors.go
================================================
package middlewares

import (
	"github.com/gin-gonic/gin"
)

func Cors() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type,Token")
		c.Next()
	}
}


================================================
FILE: models/address.go
================================================
package models

import "github.com/jinzhu/gorm"

type Address struct {
	gorm.Model
	StreetAddress string `gorm:"not null"`
	City          string `gorm:"not null"`
	Country       string `gorm:"not null"`
	ZipCode       string `gorm:"not null"`
	FirstName     string `gorm:"not null"`
	LastName      string `gorm:"not null"`

	User   User    `gorm:"association_foreignkey:UserId:"`
	UserId uint    `gorm:"default:null"` // Guest users may place an order, so they should be able to create an address with nullable UserId
	Orders []Order `gorm:"foreignKey:AddressId"`
}


================================================
FILE: models/category.go
================================================
package models

import (
	"github.com/gosimple/slug"
	"github.com/jinzhu/gorm"
)

type Category struct {
	gorm.Model
	Name        string       `gorm:"not null"`
	Description string       `gorm:"default:null"`
	Slug        string       `gorm:"unique_index"`
	Products    []Product    `gorm:"many2many:products_categories;"`
	Images      []FileUpload `gorm:"foreignKey:CategoryId"`
	IsNewRecord bool         `gorm:"-;default:false"`
}

func (a *Category) BeforeSave() (err error) {
	a.Slug = slug.Make(a.Name)
	return
}


================================================
FILE: models/comment.go
================================================
package models

import (
	"github.com/jinzhu/gorm"
)

type Comment struct {
	gorm.Model
	Content   string  `gorm:"size:2048"`
	Rating    int     `gorm:"default:null"`
	Product   Product `gorm:"foreignkey:ProductId"`
	ProductId uint    `gorm:"not null"`
	User      User    `gorm:"foreignkey:UserId"`
	UserId    uint    `gorm:"not null"`
}


================================================
FILE: models/file_upload.go
================================================
package models

import "github.com/jinzhu/gorm"

type FileUpload struct {
	gorm.Model
	Filename     string
	FilePath     string
	OriginalName string
	FileSize     uint

	Tag   Tag  `gorm:"association_foreignkey:TagId"`
	TagId uint `gorm:"default:null"`

	Category   Category `gorm:"association_foreignkey:CategoryId"`
	CategoryId uint     `gorm:"default:null"`

	Product   Category `gorm:"association_foreignkey:ProductId"`
	ProductId uint     `gorm:"default:null"`
}

// Scopes, not used
func TagImages(db *gorm.DB) *gorm.DB {
	return db.Where("type = ?", "TagImage")
}

func CategoryImages(db *gorm.DB) *gorm.DB {
	return db.Where("type = ?", "CategoryImage")
}

func ProductImages(db *gorm.DB) *gorm.DB {
	return db.Where("type = ?", "ProductImage")
}

// db.Scopes(CategoryImages, ProductImages).Find(&images)


================================================
FILE: models/order.go
================================================
package models

import "github.com/jinzhu/gorm"

type Order struct {
	gorm.Model
	OrderStatus    int `gorm:"default:0"`
	TrackingNumber string

	OrderItems []OrderItem `gorm:"foreignKey:OrderId"`

	Address   Address `gorm:"association_foreignkey:AddressId:"`
	AddressId uint

	User            User `gorm:"foreignKey:UserId:"`
	UserId          uint `gorm:"default:null"`
	OrderItemsCount int  `gorm:"-"`
}

func (order *Order) GetOrderStatusAsString() string {
	switch order.OrderStatus {
	case 0:
		return "processed"
	case 1:
		return "delivered"
	case 2:
		return "shipped"
	default:
		return "unknown"
	}
}


================================================
FILE: models/order_item.go
================================================
package models

import "github.com/jinzhu/gorm"

type OrderItem struct {
	gorm.Model
	Order   Order
	OrderId uint `gorm:"not null"`

	Product   Product
	ProductId uint `gorm:"not null"`

	Slug        string `gorm:"not null"`
	ProductName string `gorm:"not null"`
	Price       int    `gorm:"not null"`
	Quantity    int    `gorm:"not null"`

	User   User `gorm:"association_foreignkey:UserId:"`
	UserId uint `gorm:"default:null"`
}


================================================
FILE: models/product.go
================================================
package models

import (
	"github.com/gosimple/slug"
	"github.com/jinzhu/gorm"
)

type Product struct {
	gorm.Model
	Name        string       `gorm:"size:280;not null"`
	Description string       `gorm:"not null"`
	Slug        string       `gorm:"unique_index;not null"`
	Price       int          `gorm:"not null"`
	Stock       int          `gorm:"not null"`
	Tags        []Tag        `gorm:"many2many:products_tags;"`
	ProductTags []ProductTag `gorm:"foreignkey:ProductId"`

	Categories        []Category        `gorm:"many2many:products_categories;"`
	ProductCategories []ProductCategory `gorm:"foreignkey:ProductId"`

	Comments      []Comment    `gorm:"foreignKey:ProductId"`
	Images        []FileUpload `gorm:"foreignKey:ProductId"`
	CommentsCount int          `gorm:"-"`
}

func (product *Product) BeforeSave() (err error) {
	product.Slug = slug.Make(product.Name)
	return
}


================================================
FILE: models/product_category.go
================================================
package models

type ProductCategory struct {
	Category   User `gorm:"association_foreignkey:CategoryId"`
	CategoryId uint
	Product    Product `gorm:"association_foreignkey:ProductId"`
	ProductId  uint
}

func (*ProductCategory) TableName() string {
	return "products_categories"
}


================================================
FILE: models/product_tag.go
================================================
package models

type ProductTag struct {
	Tag       User `gorm:"association_foreignkey:TagId"`
	TagId     uint
	Product   Product `gorm:"association_foreignkey:ProductId"`
	ProductId uint
}

func (*ProductTag) TableName() string {
	return "products_tags"
}


================================================
FILE: models/role.go
================================================
package models

import "github.com/jinzhu/gorm"

type Role struct {
	gorm.Model
	Name        string
	Description string
	Users       []User     `gorm:"many2many:users_roles;"`
	UserRoles   []UserRole `gorm:"foreignkey:RoleId"`
}

type UserRole struct {
	User   User `gorm:"association_foreignkey:UserId"`
	UserId uint
	Role   User `gorm:"association_foreignkey:RoleId"`
	RoleId uint
}

func (UserRole) TableName() string {
	return "users_roles"
}

func Any(roles []Role, f func(Role) bool) bool {
	for _, role := range roles {
		if f(role) {
			return true
		}
	}
	return false
}


================================================
FILE: models/tag.go
================================================
package models

import (
	"github.com/gosimple/slug"
	"github.com/jinzhu/gorm"
)

type Tag struct {
	gorm.Model
	Name        string       `gorm:"not null"`
	Description string       `gorm:"default:null"`
	Slug        string       `gorm:"unique_index"`
	Products    []Product    `gorm:"many2many:products_tags;"`
	Images      []FileUpload `gorm:"foreignKey:TagId"`
	IsNewRecord bool         `gorm:"-;default:false"` // Virtual Field, so it is not persisted in the Db. This is used in FirstOrCreate()
}

func (a *Tag) BeforeSave() (err error) {
	a.Slug = slug.Make(a.Name)
	return
}


================================================
FILE: models/user.go
================================================
package models

import (
	"errors"
	"github.com/dgrijalva/jwt-go"
	"github.com/jinzhu/gorm"
	"golang.org/x/crypto/bcrypt"
	"os"
	"time"
)

type User struct {
	gorm.Model
	//Id           uint    `gorm:"primary_key"`
	FirstName string `gorm:"varchar(255);not null"`
	LastName  string `gorm:"varchar(255);not null"`
	Username  string `gorm:"column:username"`
	Email     string `gorm:"column:email;unique_index"`
	Password  string `gorm:"column:password;not null"`

	Comments []Comment `gorm:"foreignkey:UserId"`

	Roles     []Role     `gorm:"many2many:users_roles;"`
	UserRoles []UserRole `gorm:"foreignkey:UserId"`
}

// What's bcrypt? https://en.wikipedia.org/wiki/Bcrypt
// Golang bcrypt doc: https://godoc.org/golang.org/x/crypto/bcrypt
// You can change the value in bcrypt.DefaultCost to adjust the security index.
// 	err := userModel.setPassword("password0")
func (u *User) SetPassword(password string) error {
	if len(password) == 0 {
		return errors.New("password should not be empty")
	}
	bytePassword := []byte(password)
	// Make sure the second param `bcrypt generator cost` between [4, 32)
	passwordHash, _ := bcrypt.GenerateFromPassword(bytePassword, bcrypt.DefaultCost)
	u.Password = string(passwordHash)
	return nil
}

// Database will only save the hashed string, you should check it by util function.
// 	if err := serModel.checkPassword("password0"); err != nil { password error }
func (u *User) IsValidPassword(password string) error {
	bytePassword := []byte(password)
	byteHashedPassword := []byte(u.Password)
	return bcrypt.CompareHashAndPassword(byteHashedPassword, bytePassword)
}

func (user *User) BeforeSave(db *gorm.DB) (err error) {
	if len(user.Roles) == 0 {
		// role := Role{}
		userRole := Role{}
		// db.Model(&role).Where("name = ?", "ROLE_USER").First(&userRole)
		db.Model(&Role{}).Where("name = ?", "ROLE_USER").First(&userRole)
		//db.Where(&models.Role{Name: "ROLE_USER"}).Attrs(models.Role{Description: "For standard Users"}).FirstOrCreate(&userRole)
		user.Roles = append(user.Roles, userRole)
	}
	return
}

// Generate JWT token associated to this user
func (user *User) GenerateJwtToken() string {
	// jwt.New(jwt.GetSigningMethod("HS512"))
	jwt_token := jwt.New(jwt.SigningMethodHS512)

	var roles []string
	for _, role := range user.Roles {
		roles = append(roles, role.Name)
	}

	jwt_token.Claims = jwt.MapClaims{
		"user_id":  user.ID,
		"username": user.Username,
		"roles":    roles,
		"exp":      time.Now().Add(time.Hour * 24 * 90).Unix(),
	}
	// Sign and get the complete encoded token as a string
	token, _ := jwt_token.SignedString([]byte(os.Getenv("JWT_SECRET")))
	return token
}

func (user *User) IsAdmin() bool {
	for _, role := range user.Roles {
		if role.Name == "ROLE_ADMIN" {
			return true
		}
	}
	return false
}
func (user *User) IsNotAdmin() bool {
	return !user.IsAdmin()
}


================================================
FILE: seeds/seeder.go
================================================
package seeds

import (
	"github.com/icrowley/fake"
	"github.com/jinzhu/gorm"
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/models"
	"golang.org/x/crypto/bcrypt"
	"math/rand"
	"time"
)

func randomInt(min, max int) int {

	return rand.Intn(max-min) + min
}

var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")

func randomString(length int) string {
	b := make([]rune, length)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}

func seedAdmin(db *gorm.DB) {
	count := 0
	adminRole := models.Role{Name: "ROLE_ADMIN", Description: "Only for admin"}
	query := db.Model(&models.Role{}).Where("name = ?", "ROLE_ADMIN")
	query.Count(&count)

	if count == 0 {
		db.Create(&adminRole)
	} else {
		query.First(&adminRole)
	}

	adminRoleUsers := 0
	var adminUsers []models.User
	db.Model(&adminRole).Related(&adminUsers, "Users")

	db.Model(&models.User{}).Where("username = ?", "admin").Count(&adminRoleUsers)
	if adminRoleUsers == 0 {

		// query.First(&adminRole) // First would fetch the Role admin because the query status name='ROLE_ADMIN'
		password, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)
		// Approach 1
		user := models.User{FirstName: "AdminFN", LastName: "AdminFN", Email: "admin@golang.com", Username: "admin", Password: string(password)}
		user.Roles = append(user.Roles, adminRole)

		// Do not try to update the adminRole
		db.Set("gorm:association_autoupdate", false).Create(&user)

		// Approach 2
		// user := models.User{FirstName: "AdminFN", LastName: "AdminFN", Email: "admin@golang.com", Username: "admin", Password: "password"}
		// user.Roles = append(user.Roles, adminRole)
		// db.NewRecord(user)
		// db.Set("gorm:association_autoupdate", false).Save(&user)

		if db.Error != nil {
			print(db.Error)
		}
	}
}

func seedUsers(db *gorm.DB) {
	count := 0
	role := models.Role{Name: "ROLE_USER", Description: "Only for standard users"}
	q := db.Model(&models.Role{}).Where("name = ?", "ROLE_USER")
	q.Count(&count)

	if count == 0 {
		db.Create(&role)
	} else {
		q.First(&role)
	}

	var standardUsers []models.User
	db.Model(&role).Related(&standardUsers, "Users")
	usersCount := len(standardUsers)
	usersToSeed := 20
	usersToSeed -= usersCount
	if usersToSeed > 0 {
		for i := 0; i < usersToSeed; i++ {
			password, _ := bcrypt.GenerateFromPassword([]byte("password"), bcrypt.DefaultCost)
			user := models.User{FirstName: fake.FirstName(), LastName: fake.LastName(), Email: fake.EmailAddress(), Username: fake.UserName(),
				Password: string(password)}
			// No need to add the role as we did for seedAdmin, it is added by the BeforeSave hook
			db.Set("gorm:association_autoupdate", false).Create(&user)
		}
	}
}

func seedTags(db *gorm.DB) {
	var tags [3]models.Tag

	db.Where(&models.Tag{Name: "Shoes"}).Attrs(models.Tag{Description: "Shoes for everyone", IsNewRecord: true}).FirstOrCreate(&tags[0])
	db.Where(models.Tag{Name: "Jackets"}).Attrs(models.Tag{Description: "Jackets for everyone", IsNewRecord: true}).FirstOrCreate(&tags[1])
	db.Where(models.Tag{Name: "Jeans"}).Attrs(models.Tag{Description: "Jeans for everyone", IsNewRecord: true}).FirstOrCreate(&tags[2])

	for _, tag := range tags {
		for i := 0; i < randomInt(1, 3); i++ {
			if tag.IsNewRecord {
				db.Create(&models.FileUpload{Filename: randomString(16) + ".png", OriginalName: randomString(16) + ".png",
					FilePath: "/static/images/tags/" + randomString(16) + ".png", FileSize: 2500,
					Tag: tag, TagId: tag.ID})
			}
		}
	}
}

func seedCategories(db *gorm.DB) {
	var categories [3]models.Category
	db.Where(models.Category{Name: "Women"}).Attrs(models.Category{Description: "Clothes for women", IsNewRecord: true}).FirstOrCreate(&categories[0])
	db.Where(models.Category{Name: "Men"}).Attrs(models.Category{Description: "Clothes for men", IsNewRecord: true}).FirstOrCreate(&categories[1])
	db.Where(models.Category{Name: "Kids"}).Attrs(models.Category{Description: "Clothes for kids", IsNewRecord: true}).FirstOrCreate(&categories[2])

	for _, category := range categories {
		for i := 0; i < randomInt(1, 3); i++ {
			if category.IsNewRecord {
				db.Create(&models.FileUpload{Filename: randomString(16) + ".png", OriginalName: randomString(16) + ".png",
					FilePath: "/static/images/categories/" + randomString(16) + ".png", FileSize: 2500,
					Category: category, CategoryId: category.ID})
			}
		}
	}
}

func seedProducts(db *gorm.DB) {
	productsCount := 0
	productsToSeed := 20
	db.Model(&models.Product{}).Count(&productsCount)
	productsToSeed -= productsCount

	if productsToSeed > 0 {
		rand.Seed(time.Now().Unix())
		tags := []models.Tag{}
		categories := []models.Category{}
		db.Find(&tags)
		db.Find(&categories)
		for i := 0; i < productsToSeed; i++ {
			// add a tag and a category for each product
			// faker.RandomInt(0, len(tags))[0]
			tagForProduct := tags[rand.Intn(len(tags))]
			categoryForProduct := categories[rand.Intn(len(categories))]

			product := &models.Product{Name: fake.ProductName(), Description: fake.Paragraph(),
				Stock: randomInt(100, 2000), Price: randomInt(50, 1000),
				Tags: []models.Tag{tagForProduct}, Categories: []models.Category{categoryForProduct}}
			for i := 0; i < randomInt(1, 4); i++ {
				productImage := models.FileUpload{Filename: randomString(16) + ".png", OriginalName: randomString(16) + ".png",
					FilePath: "/static/images/products/" + randomString(16) + ".png", FileSize: uint(randomInt(1000, 23000))}
				product.Images = append(product.Images, productImage)
				db.Set("gorm:association_autoupdate", false).Create(&product)
			}

			/*
				db.Create(&models.FileUpload{Filename: randomString(16) + ".png", OriginalName: randomString(16) + ".png",
					FilePath: "/static/images/tags" + randomString(16) + ".png", FileSize: 2500,
					Tag: tag, TagId: tag.ID})
			*/
		}
	}
}

func seedComments(db *gorm.DB) {
	commentsCount := 0
	commentsToSeed := 20

	allUsers := []models.User{}
	allProducts := []models.Product{}

	db.Model(&models.Comment{}).Count(&commentsCount)
	commentsToSeed -= commentsCount

	if commentsToSeed > 0 {
		rand.Seed(time.Now().Unix())

		db.Find(&allProducts)
		db.Find(&allUsers)

		for i := 0; i < commentsToSeed; i++ {
			userId := allUsers[rand.Intn(len(allUsers))].ID
			productId := allProducts[rand.Intn(len(allProducts))].ID
			sentences := fake.SentencesN(randomInt(2, 6))
			var comment models.Comment

			if rand.Float32() > 0.3 {
				comment = models.Comment{Content: sentences, UserId: userId, ProductId: productId}
			} else {
				// Comment with rating
				comment = models.Comment{Content: sentences, UserId: userId, ProductId: productId, Rating: randomInt(1, 5)}
			}

			db.Set("gorm:association_autoupdate", false).Create(&comment)
		}
	}
}
func seedAddresses(db *gorm.DB) {
	addressesCount := 0
	addressesToSeed := 20

	allUsers := []models.User{}

	db.Model(&models.Address{}).Count(&addressesCount)
	addressesToSeed -= addressesCount

	if addressesToSeed > 0 {
		rand.Seed(time.Now().Unix())
		db.Find(&allUsers)
		var address models.Address

		var city string
		var country string
		var streetAddress string
		var zipCode string
		for i := 0; i < addressesToSeed; i++ {
			city = fake.City()
			country = fake.Country()
			zipCode = fake.Zip()
			streetAddress = fake.StreetAddress()
			address = models.Address{ZipCode: zipCode, StreetAddress: streetAddress, Country: country, City: city}
			if rand.Float32() > 0.4 {
				user := allUsers[rand.Intn(len(allUsers))]
				address.UserId = user.ID
				address.FirstName = user.FirstName
				address.LastName = user.LastName
			} else {
				address.FirstName = fake.FirstName()
				address.LastName = fake.LastName()
			}

			db.Set("gorm:association_autoupdate", false).Create(&address)
		}
	}
}
func seedOrders(db *gorm.DB) {
	ordersCount := 0
	ordersToSeed := 20

	allAddresses := []models.Address{}
	allProducts := []models.Product{}

	db.Model(&models.Order{}).Count(&ordersCount)
	ordersToSeed -= ordersCount

	if ordersToSeed > 0 {
		rand.Seed(time.Now().Unix())
		// Eager load the address's user association
		db.Find(&allAddresses)
		db.Find(&allProducts)

		for i := 0; i < ordersToSeed; i++ {
			address := allAddresses[rand.Intn(len(allAddresses))]

			order := models.Order{TrackingNumber: randomString(16), OrderStatus: randomInt(0, 3), AddressId: address.ID}
			orderItemsForOrder := randomInt(2, 5)
			if rand.Float32() > 0.3 {
				order.UserId = address.UserId
			}
			for j := 0; j < orderItemsForOrder; j++ {
				product := allProducts[rand.Intn(len(allProducts))]
				orderItem := models.OrderItem{ProductName: product.Name, Price: product.Price, Slug: product.Slug,
					ProductId: product.ID,
					UserId:    address.UserId, Quantity: randomInt(1, 8)}

				order.OrderItems = append(order.OrderItems, orderItem)
			}

			db.Set("gorm:association_autoupdate", false).Create(&order)
		}
	}
}

func Seed() {
	db := infrastructure.GetDb()
	rand.Seed(time.Now().UnixNano())
	seedAdmin(db)
	seedUsers(db)
	seedTags(db)
	seedCategories(db)
	seedProducts(db)
	seedComments(db)
	seedAddresses(db)
	seedOrders(db)
}


================================================
FILE: services/addresses.go
================================================
package services

import (
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/models"
)

func FetchAddressesPage(userId uint, page, pageSize int, includeUser bool) ([]models.Address, int) {
	var addresses []models.Address
	var totalAddressesCount int
	database := infrastructure.GetDb()
	database.Model(&models.Address{}).Where(&models.Address{UserId: uint(userId)}).Count(&totalAddressesCount)
	database.Where(&models.Address{UserId: uint(userId)}).
		Offset((page - 1) * pageSize).Limit(pageSize).
		Preload("User").
		Find(&addresses)

	if includeUser {
		var userIds = make([]uint, len(addresses))
		var users []models.User
		for i := 0; i < len(addresses); i++ {
			userIds[i] = addresses[i].UserId
		}
		database.Select([]string{"id", "username"}).Where(userIds).Find(&users)

		// If the user gets deleted and the comment is still in the database we may have less users than addresses
		// Another scenario (the one I run into) is there is a problem with the Comment.User, the Comment.UserId does not get saved automatically
		for i := 0; i < len(addresses); i++ {
			address := addresses[i]
			for j := 0; j < len(users); j++ {
				user := users[j]
				if address.UserId == user.ID {
					addresses[i].User = users[j]
				}
			}
		}
	}
	return addresses, totalAddressesCount
}

func FetchAddress(addressId uint) (address models.Address) {
	database := infrastructure.GetDb()
	database.First(&address, addressId)
	return address
}

func FetchIdsFromAddress(addressId uint) (address models.Address) {
	database := infrastructure.GetDb()
	database.Select("id, user_id").First(&address, addressId)
	return
}


================================================
FILE: services/categories.go
================================================
package services

import (
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/models"
)

func FetchAllCategories() ([]models.Category, error) {
	database := infrastructure.GetDb()
	var categories []models.Category
	err := database.Preload("Images", "category_id IS NOT NULL").Find(&categories).Error
	return categories, err
}


================================================
FILE: services/comments.go
================================================
package services

import (
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/models"
)

func FetchCommentsPage(productId, page int, page_size int) ([]models.Comment, int) {
	// TODO: Why Preload does not load the User? the error is can't preload field User for models.Comment

	var comments []models.Comment
	var totalCommentCount int
	database := infrastructure.GetDb()
	database.Model(&comments).Where(&models.Comment{ProductId: uint(productId)}).Count(&totalCommentCount)
	database.Where(&models.Comment{ProductId: uint(productId)}).
		Offset((page - 1) * page_size).Limit(page_size).
		Preload("User").
		Find(&comments)

	// `Where in` using other columns different than ID
	// database.Where("username in (?)", []string{"admin", "melardev"}).Find(&users)
	var userIds = make([]uint, len(comments))
	var users []models.User
	for i := 0; i < len(comments); i++ {
		userIds[i] = comments[i].UserId
	}
	database.Select("id, username").Where(userIds).Find(&users)

	// If the user gets deleted and the comment is still in the database we may have less users than comments
	// Another scenario (the one I run into) is there is a problem with the Comment.User, the Comment.UserId does not get saved automatically
	for i := 0; i < len(comments); i++ {
		comment := comments[i]
		for j := 0; j < len(users); j++ {
			user := users[j]
			if comment.UserId == user.ID {
				comments[i].User = users[j]
			}
		}

	}
	return comments, totalCommentCount
}

func FetchCommentById(id int, includes ...bool) models.Comment {
	includeUser := false
	if len(includes) > 0 {
		includeUser = includes[0]
	}
	includeProduct := false
	if len(includes) > 1 {
		includeProduct = includes[1]
	}
	database := infrastructure.GetDb()
	var comment models.Comment
	if includeProduct && includeUser {
		database.Preload("User").Preload("Product").Find(&comment, id)
	} else if includeUser {
		database.Preload("User").Find(&comment, id)
	} else if includeProduct {
		database.Preload("Product").Find(&comment, id)
	} else {
		database.Find(&comment, id)
	}
	return comment
}

func DeleteComment(condition interface{}) error {
	database := infrastructure.GetDb()
	err := database.Where(condition).Delete(models.Comment{}).Error
	return err
}


================================================
FILE: services/orders.go
================================================
package services

import (
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/models"
)

func FetchOrdersPage(userId uint, page, pageSize int) (orders []models.Order, totalOrdersCount int, err error) {
	database := infrastructure.GetDb()

	totalOrdersCount = 0

	query := database.Model(&models.Order{}).Where(&models.Order{UserId: userId})
	query.Count(&totalOrdersCount)

	err = query.Offset((page - 1) * pageSize).Limit(pageSize).
		// TODO: Why Preload("Address") does not work?, perhaps OrderItems does
		// Preload("OrderItems").Preload("Address").
		Find(&orders).Error
	if err != nil {
		return
	}

	var orderIds = make([]uint, len(orders))
	for i := 0; i < len(orders); i++ {
		orderIds[i] = orders[i].ID
	}

	var orderItems []models.OrderItem
	if len(orders) > 0 {
		//
		database.Select("id, order_id").Where("order_id in (?)", orderIds).Find(&orderItems)

		for i := 0; i < len(orderItems); i++ {
			oi := orderItems[i]
			for j := 0; j < len(orders); j++ {
				if oi.OrderId == orders[j].ID {
					orders[j].OrderItemsCount = orders[j].OrderItemsCount + 1
				}
			}
		}
	}
	return orders, totalOrdersCount, err
}

func FetchOrderDetails(orderId uint) (order models.Order, err error) {
	database := infrastructure.GetDb()
	err = database.Model(models.Order{}).Preload("OrderItems").First(&order, orderId).Error
	var address models.Address
	database.Model(&order).Related(&address)
	order.Address = address
	return order, err
}


================================================
FILE: services/products.go
================================================
package services

import (
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/models"
)

func FetchProductsPage(page int, page_size int) ([]models.Product, int, []int, error) {
	database := infrastructure.GetDb()
	var products []models.Product
	var count int
	tx := database.Begin()
	database.Model(&products).Count(&count)
	database.Offset((page - 1) * page_size).Limit(page_size).Find(&products)
	tx.Model(&products).
		Preload("Tags").Preload("Categories").Preload("Images").
		Order("created_at desc").Offset((page - 1) * page_size).Limit(page_size).Find(&products)
	commentsCount := make([]int, len(products))

	for index, product := range products {
		commentsCount[index] = tx.Model(&product).Association("Comments").Count()
	}
	err := tx.Commit().Error
	return products, count, commentsCount, err
}

func FetchProductDetails(condition interface{}, optional ...bool) models.Product {
	database := infrastructure.GetDb()
	var product models.Product

	query := database.Where(condition).
		Preload("Tags").Preload("Categories").Preload("Images").Preload("Comments")
	// Unfortunately .Preload("Comments.User") does not work as the doc states ...
	query.First(&product)
	includeUserComment := false

	if len(optional) > 0 {
		includeUserComment = optional[0]
	}

	if includeUserComment {

		for i := 0; i < len(product.Comments); i++ {
			database.Model(&product.Comments[i]).Related(&product.Comments[i].User, "UserId")
		}

		var userIds = make([]uint, len(product.Comments))
		var users []models.User
		for i := 0; i < len(product.Comments); i++ {
			userIds[i] = product.Comments[i].UserId
		}
		// WHERE users.id IN userIds; This will also work: Select([]string{"id", "username"})
		database.Select("id, username").Where(userIds).Find(&users)

		for i := 0; i < len(product.Comments); i++ {
			user := users[i]
			comment := product.Comments[i]
			if comment.UserId == user.ID {
				product.Comments[i].User = users[i]
			}
		}
	}

	return product
}

func FetchProductId(slug string) (uint, error) {
	productId := -1
	database := infrastructure.GetDb()
	err := database.Model(&models.Product{}).Where(&models.Product{Slug: slug}).Select("id").Row().Scan(&productId)
	return uint(productId), err
}

func SetTags(product *models.Product, tags []string) error {
	database := infrastructure.GetDb()
	var tagList []models.Tag
	for _, tag := range tags {
		var tagModel models.Tag
		err := database.FirstOrCreate(&tagModel, models.Tag{Name: tag}).Error
		if err != nil {
			return err
		}
		tagList = append(tagList, tagModel)
	}
	product.Tags = tagList
	return nil
}

func Update(product *models.Product, data interface{}) error {
	database := infrastructure.GetDb()
	err := database.Model(product).Update(data).Error
	return err
}

func DeleteProduct(condition interface{}) error {
	db := infrastructure.GetDb()
	err := db.Where(condition).Delete(models.Product{}).Error
	return err
}

func FetchProductsIdNameAndPrice(productIds []uint) (products []models.Product, err error) {
	database := infrastructure.GetDb()
	err = database.Select([]string{"id", "name", "slug", "price"}).Find(&products, productIds).Error
	return products, err
}


================================================
FILE: services/shared.go
================================================
package services

import (
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
)

func CreateOne(data interface{}) error {
	database := infrastructure.GetDb()
	err := database.Create(data).Error
	return err
}

func SaveOne(data interface{}) error {
	database := infrastructure.GetDb()
	err := database.Save(data).Error
	return err
}


================================================
FILE: services/tags.go
================================================
package services

import (
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/models"
)

func FetchAllTags() ([]models.Tag, error) {
	database := infrastructure.GetDb()
	var tags []models.Tag
	err := database.Preload("Images", "tag_id IS NOT NULL").Find(&tags).Error
	return tags, err
}


================================================
FILE: services/users.go
================================================
package services

import (
	"github.com/melardev/GoGonicEcommerceApi/infrastructure"
	"github.com/melardev/GoGonicEcommerceApi/models"
)

// You could input the conditions and it will return an User in database with error info.
// 	userModel, err := FindOneUser(&User{Username: "username0"})
func FindOneUser(condition interface{}) (models.User, error) {
	database := infrastructure.GetDb()
	var user models.User

	err := database.Where(condition).Preload("Roles").First(&user).Error
	return user, err
}

// You could update properties of an User to database returning with error info.
//  err := db.Model(userModel).Update(User{Username: "wangzitian0"}).Error
func UpdateUser(user models.User, data interface{}) error {
	database := infrastructure.GetDb()
	err := database.Model(user).Update(data).Error
	return err
}
Download .txt
gitextract_mbl266ph/

├── .gitignore
├── README.md
├── controllers/
│   ├── addresses.go
│   ├── categories.go
│   ├── comments.go
│   ├── orders.go
│   ├── pages.go
│   ├── products.go
│   ├── tags.go
│   └── users.go
├── dtos/
│   ├── addresses.go
│   ├── categories.go
│   ├── comments.go
│   ├── orders.go
│   ├── pages.go
│   ├── products.go
│   ├── shared.go
│   ├── tags.go
│   └── users.go
├── infrastructure/
│   └── db.go
├── main.go
├── middlewares/
│   ├── auth.go
│   ├── benchmark.go
│   └── cors.go
├── models/
│   ├── address.go
│   ├── category.go
│   ├── comment.go
│   ├── file_upload.go
│   ├── order.go
│   ├── order_item.go
│   ├── product.go
│   ├── product_category.go
│   ├── product_tag.go
│   ├── role.go
│   ├── tag.go
│   └── user.go
├── seeds/
│   └── seeder.go
└── services/
    ├── addresses.go
    ├── categories.go
    ├── comments.go
    ├── orders.go
    ├── products.go
    ├── shared.go
    ├── tags.go
    └── users.go
Download .txt
SYMBOL INDEX (151 symbols across 43 files)

FILE: controllers/addresses.go
  function RegisterAddressesRoutes (line 14) | func RegisterAddressesRoutes(router *gin.RouterGroup) {
  function ListAddresses (line 24) | func ListAddresses(c *gin.Context) {
  function CreateAddress (line 47) | func CreateAddress(c *gin.Context) {

FILE: controllers/categories.go
  function RegisterCategoryRoutes (line 18) | func RegisterCategoryRoutes(router *gin.RouterGroup) {
  function CategoryList (line 26) | func CategoryList(c *gin.Context) {
  function CreateCategory (line 35) | func CreateCategory(c *gin.Context) {

FILE: controllers/comments.go
  function RegisterCommentRoutes (line 16) | func RegisterCommentRoutes(router *gin.RouterGroup) {
  function ListComments (line 30) | func ListComments(c *gin.Context) {
  function CreateComment (line 57) | func CreateComment(c *gin.Context) {
  function ShowComment (line 91) | func ShowComment(c *gin.Context) {
  function DeleteComment (line 101) | func DeleteComment(c *gin.Context) {

FILE: controllers/orders.go
  function RegisterOrderRoutes (line 13) | func RegisterOrderRoutes(router *gin.RouterGroup) {
  function ListOrders (line 22) | func ListOrders(c *gin.Context) {
  function ShowOrder (line 41) | func ShowOrder(c *gin.Context) {
  function CreateOrder (line 58) | func CreateOrder(c *gin.Context) {

FILE: controllers/pages.go
  function RegisterPageRoutes (line 11) | func RegisterPageRoutes(router *gin.RouterGroup) {
  function Home (line 17) | func Home(c *gin.Context) {

FILE: controllers/products.go
  function RegisterProductRoutes (line 24) | func RegisterProductRoutes(router *gin.RouterGroup) {
  function ProductList (line 35) | func ProductList(c *gin.Context) {
  function GetProductDetailsBySlug (line 59) | func GetProductDetailsBySlug(c *gin.Context) {
  function CreateProduct (line 70) | func CreateProduct(c *gin.Context) {
  function ProductDelete (line 183) | func ProductDelete(c *gin.Context) {

FILE: controllers/tags.go
  function RegisterTagRoutes (line 17) | func RegisterTagRoutes(router *gin.RouterGroup) {
  function TagList (line 25) | func TagList(c *gin.Context) {
  function randomString (line 36) | func randomString(length int) string {
  function CreateTag (line 44) | func CreateTag(c *gin.Context) {

FILE: controllers/users.go
  function RegisterUserRoutes (line 15) | func RegisterUserRoutes(router *gin.RouterGroup) {
  function UsersRegistration (line 20) | func UsersRegistration(c *gin.Context) {
  function UsersLogin (line 44) | func UsersLogin(c *gin.Context) {

FILE: dtos/addresses.go
  type CreateAddress (line 8) | type CreateAddress struct
  function CreateAddressPagedResponse (line 17) | func CreateAddressPagedResponse(request *http.Request, addresses []model...
  function GetAddressDto (line 25) | func GetAddressDto(address *models.Address, includeUser bool) map[string...
  function GetAddressCreatedDto (line 44) | func GetAddressCreatedDto(address *models.Address, includeUser bool) map...

FILE: dtos/categories.go
  function CreateCategoryListMapDto (line 8) | func CreateCategoryListMapDto(categories []models.Category) map[string]i...
  function CreateCategoryListDto (line 18) | func CreateCategoryListDto(categories []models.Category) []interface{} {
  function CreateCategoryDto (line 26) | func CreateCategoryDto(category models.Category) map[string]interface{} {
  function CreateCategoryCreatedDto (line 40) | func CreateCategoryCreatedDto(category models.Category) map[string]inter...

FILE: dtos/comments.go
  type CreateComment (line 9) | type CreateComment struct
  function CreateCommentPagedResponse (line 13) | func CreateCommentPagedResponse(request *http.Request, comments []models...
  function GetCommentDetailsDto (line 30) | func GetCommentDetailsDto(comment *models.Comment, includes ...bool) map...
  function GetSummary (line 42) | func GetSummary(comment *models.Comment, includeUser, includeProduct boo...
  function CreateCommentCreatedDto (line 65) | func CreateCommentCreatedDto(comment *models.Comment, includes ...bool) ...

FILE: dtos/orders.go
  type CreateOrderRequestDto (line 8) | type CreateOrderRequestDto struct
  function CreateOrderPagedResponse (line 22) | func CreateOrderPagedResponse(request *http.Request, orders []models.Ord...
  function CreateOrderDto (line 33) | func CreateOrderDto(order *models.Order, includes ...bool) map[string]in...
  function CreateOrderDetailsDto (line 79) | func CreateOrderDetailsDto(order *models.Order) map[string]interface{} {
  function getIncludeFlags (line 86) | func getIncludeFlags(includes ...bool) (includeAddress, includeOrderItem...
  function CreateOrderCreatedDto (line 102) | func CreateOrderCreatedDto(order *models.Order) map[string]interface{} {

FILE: dtos/pages.go
  function CreateHomeResponse (line 5) | func CreateHomeResponse(tags []models.Tag, categories []models.Category)...

FILE: dtos/products.go
  type ManagedModel (line 10) | type ManagedModel
  type CreateProduct (line 12) | type CreateProduct struct
  function CreatedProductPagedResponse (line 19) | func CreatedProductPagedResponse(request *http.Request, products []model...
  function CreateProductDto (line 27) | func CreateProductDto(product *models.Product, commentCount int) map[str...
  function CreateProductDetailsDto (line 81) | func CreateProductDetailsDto(product models.Product) map[string]interfac...
  function CreateProductCreatedDto (line 92) | func CreateProductCreatedDto(product models.Product) map[string]interfac...

FILE: dtos/shared.go
  type BaseDto (line 11) | type BaseDto struct
  type ErrorDto (line 16) | type ErrorDto struct
  function CreatePageMeta (line 21) | func CreatePageMeta(request *http.Request, loadedItemsCount, page, page_...
  function CreatePagedResponse (line 57) | func CreatePagedResponse(request *http.Request, resources []interface{},...
  function CreateDetailedErrorDto (line 64) | func CreateDetailedErrorDto(key string, err error) map[string]interface{} {
  function CreateErrorDtoWithMessage (line 72) | func CreateErrorDtoWithMessage(message string) map[string]interface{} {
  function CreateBadRequestErrorDto (line 81) | func CreateBadRequestErrorDto(err error) ErrorDto {
  function CreateSuccessDto (line 102) | func CreateSuccessDto(result map[string]interface{}) map[string]interfac...
  function CreateSuccessWithMessageDto (line 107) | func CreateSuccessWithMessageDto(message string) interface{} {
  function CreateSuccessWithMessagesDto (line 111) | func CreateSuccessWithMessagesDto(messages []string) interface{} {
  function CreateSuccessWithDtoAndMessagesDto (line 118) | func CreateSuccessWithDtoAndMessagesDto(data map[string]interface{}, mes...
  function CreateSuccessWithDtoAndMessageDto (line 123) | func CreateSuccessWithDtoAndMessageDto(data map[string]interface{}, mess...

FILE: dtos/tags.go
  type CreateTag (line 8) | type CreateTag struct
  function CreateTagListMapDto (line 13) | func CreateTagListMapDto(tags []models.Tag) map[string]interface{} {
  function CreateTagListDto (line 23) | func CreateTagListDto(tags []models.Tag) []interface{} {
  function CreateTagDto (line 31) | func CreateTagDto(tag models.Tag) map[string]interface{} {
  function CreateTagCreatedDto (line 45) | func CreateTagCreatedDto(tag models.Tag) map[string]interface{} {

FILE: dtos/users.go
  type RegisterRequestDto (line 7) | type RegisterRequestDto struct
  type LoginRequestDto (line 16) | type LoginRequestDto struct
  function CreateLoginSuccessful (line 24) | func CreateLoginSuccessful(user *models.User) map[string]interface{} {
  function GetUserBasicInfo (line 42) | func GetUserBasicInfo(user models.User) map[string]interface{} {

FILE: infrastructure/db.go
  type Database (line 15) | type Database struct
  function OpenDbConnection (line 22) | func OpenDbConnection() *gorm.DB {
  function RemoveDb (line 51) | func RemoveDb(db *gorm.DB) error {
  function GetDb (line 58) | func GetDb() *gorm.DB {

FILE: main.go
  function drop (line 17) | func drop(db *gorm.DB) {
  function migrate (line 28) | func migrate(database *gorm.DB) {
  function addDbConstraints (line 52) | func addDbConstraints(database *gorm.DB) {
  function create (line 89) | func create(database *gorm.DB) {
  function main (line 95) | func main() {

FILE: middlewares/auth.go
  function EnforceAuthenticatedMiddleware (line 14) | func EnforceAuthenticatedMiddleware() gin.HandlerFunc {
  function UserLoaderMiddleware (line 27) | func UserLoaderMiddleware() gin.HandlerFunc {

FILE: middlewares/benchmark.go
  function Benchmark (line 10) | func Benchmark() gin.HandlerFunc {

FILE: middlewares/cors.go
  function Cors (line 7) | func Cors() gin.HandlerFunc {

FILE: models/address.go
  type Address (line 5) | type Address struct

FILE: models/category.go
  type Category (line 8) | type Category struct
    method BeforeSave (line 18) | func (a *Category) BeforeSave() (err error) {

FILE: models/comment.go
  type Comment (line 7) | type Comment struct

FILE: models/file_upload.go
  type FileUpload (line 5) | type FileUpload struct
  function TagImages (line 23) | func TagImages(db *gorm.DB) *gorm.DB {
  function CategoryImages (line 27) | func CategoryImages(db *gorm.DB) *gorm.DB {
  function ProductImages (line 31) | func ProductImages(db *gorm.DB) *gorm.DB {

FILE: models/order.go
  type Order (line 5) | type Order struct
    method GetOrderStatusAsString (line 20) | func (order *Order) GetOrderStatusAsString() string {

FILE: models/order_item.go
  type OrderItem (line 5) | type OrderItem struct

FILE: models/product.go
  type Product (line 8) | type Product struct
    method BeforeSave (line 26) | func (product *Product) BeforeSave() (err error) {

FILE: models/product_category.go
  type ProductCategory (line 3) | type ProductCategory struct
    method TableName (line 10) | func (*ProductCategory) TableName() string {

FILE: models/product_tag.go
  type ProductTag (line 3) | type ProductTag struct
    method TableName (line 10) | func (*ProductTag) TableName() string {

FILE: models/role.go
  type Role (line 5) | type Role struct
  type UserRole (line 13) | type UserRole struct
    method TableName (line 20) | func (UserRole) TableName() string {
  function Any (line 24) | func Any(roles []Role, f func(Role) bool) bool {

FILE: models/tag.go
  type Tag (line 8) | type Tag struct
    method BeforeSave (line 18) | func (a *Tag) BeforeSave() (err error) {

FILE: models/user.go
  type User (line 12) | type User struct
    method SetPassword (line 31) | func (u *User) SetPassword(password string) error {
    method IsValidPassword (line 44) | func (u *User) IsValidPassword(password string) error {
    method BeforeSave (line 50) | func (user *User) BeforeSave(db *gorm.DB) (err error) {
    method GenerateJwtToken (line 63) | func (user *User) GenerateJwtToken() string {
    method IsAdmin (line 83) | func (user *User) IsAdmin() bool {
    method IsNotAdmin (line 91) | func (user *User) IsNotAdmin() bool {

FILE: seeds/seeder.go
  function randomInt (line 13) | func randomInt(min, max int) int {
  function randomString (line 20) | func randomString(length int) string {
  function seedAdmin (line 28) | func seedAdmin(db *gorm.DB) {
  function seedUsers (line 68) | func seedUsers(db *gorm.DB) {
  function seedTags (line 96) | func seedTags(db *gorm.DB) {
  function seedCategories (line 114) | func seedCategories(db *gorm.DB) {
  function seedProducts (line 131) | func seedProducts(db *gorm.DB) {
  function seedComments (line 168) | func seedComments(db *gorm.DB) {
  function seedAddresses (line 201) | func seedAddresses(db *gorm.DB) {
  function seedOrders (line 239) | func seedOrders(db *gorm.DB) {
  function Seed (line 277) | func Seed() {

FILE: services/addresses.go
  function FetchAddressesPage (line 8) | func FetchAddressesPage(userId uint, page, pageSize int, includeUser boo...
  function FetchAddress (line 41) | func FetchAddress(addressId uint) (address models.Address) {
  function FetchIdsFromAddress (line 47) | func FetchIdsFromAddress(addressId uint) (address models.Address) {

FILE: services/categories.go
  function FetchAllCategories (line 8) | func FetchAllCategories() ([]models.Category, error) {

FILE: services/comments.go
  function FetchCommentsPage (line 8) | func FetchCommentsPage(productId, page int, page_size int) ([]models.Com...
  function FetchCommentById (line 44) | func FetchCommentById(id int, includes ...bool) models.Comment {
  function DeleteComment (line 67) | func DeleteComment(condition interface{}) error {

FILE: services/orders.go
  function FetchOrdersPage (line 8) | func FetchOrdersPage(userId uint, page, pageSize int) (orders []models.O...
  function FetchOrderDetails (line 46) | func FetchOrderDetails(orderId uint) (order models.Order, err error) {

FILE: services/products.go
  function FetchProductsPage (line 8) | func FetchProductsPage(page int, page_size int) ([]models.Product, int, ...
  function FetchProductDetails (line 27) | func FetchProductDetails(condition interface{}, optional ...bool) models...
  function FetchProductId (line 67) | func FetchProductId(slug string) (uint, error) {
  function SetTags (line 74) | func SetTags(product *models.Product, tags []string) error {
  function Update (line 89) | func Update(product *models.Product, data interface{}) error {
  function DeleteProduct (line 95) | func DeleteProduct(condition interface{}) error {
  function FetchProductsIdNameAndPrice (line 101) | func FetchProductsIdNameAndPrice(productIds []uint) (products []models.P...

FILE: services/shared.go
  function CreateOne (line 7) | func CreateOne(data interface{}) error {
  function SaveOne (line 13) | func SaveOne(data interface{}) error {

FILE: services/tags.go
  function FetchAllTags (line 8) | func FetchAllTags() ([]models.Tag, error) {

FILE: services/users.go
  function FindOneUser (line 10) | func FindOneUser(condition interface{}) (models.User, error) {
  function UpdateUser (line 20) | func UpdateUser(user models.User, data interface{}) error {
Condensed preview — 45 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (103K chars).
[
  {
    "path": ".gitignore",
    "chars": 1450,
    "preview": "/static/**\n.env\napp.db\n\n# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio a"
  },
  {
    "path": "README.md",
    "chars": 12300,
    "preview": "# GoGonicEcommerceApi\n# Table of Contents\n- [Introduction](#introduction)\n- [Full-stack Applications](#full-stack-applic"
  },
  {
    "path": "controllers/addresses.go",
    "chars": 1993,
    "preview": "package controllers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/melardev/GoGonicEcommerceApi/dtos\"\n\t\"github.com/m"
  },
  {
    "path": "controllers/categories.go",
    "chars": 3121,
    "preview": "package controllers\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/melardev/GoGonicEcommerceApi/dtos\"\n\t\"githu"
  },
  {
    "path": "controllers/comments.go",
    "chars": 3840,
    "preview": "package controllers\n\nimport (\n\t\"errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/melardev/GoGonicEcommerceApi/dtos\"\n\t\"gi"
  },
  {
    "path": "controllers/orders.go",
    "chars": 4333,
    "preview": "package controllers\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/melardev/GoGonicEcommerceApi/dtos\"\n\t\"github.com/m"
  },
  {
    "path": "controllers/pages.go",
    "chars": 609,
    "preview": "package controllers\n\nimport (\n\t\"errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/melardev/GoGonicEcommerceApi/dtos\"\n\t\"gi"
  },
  {
    "path": "controllers/products.go",
    "chars": 5098,
    "preview": "package controllers\n\n// import \"C\"\nimport (\n\t\"errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/gosimple/slug\"\n\t\"github.c"
  },
  {
    "path": "controllers/tags.go",
    "chars": 3128,
    "preview": "package controllers\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/melardev/GoGonicEcommerceApi/dtos\"\n\t\"githu"
  },
  {
    "path": "controllers/users.go",
    "chars": 1716,
    "preview": "package controllers\n\nimport (\n\t\"errors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/melardev/GoGonicEcommerceApi/dtos\"\n\t\"gi"
  },
  {
    "path": "dtos/addresses.go",
    "chars": 1687,
    "preview": "package dtos\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/models\"\n\t\"net/http\"\n)\n\ntype CreateAddress struct {\n\tFir"
  },
  {
    "path": "dtos/categories.go",
    "chars": 1259,
    "preview": "package dtos\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/models\"\n\t\"strings\"\n)\n\nfunc CreateCategoryListMapDto(cat"
  },
  {
    "path": "dtos/comments.go",
    "chars": 1966,
    "preview": "package dtos\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/models\"\n\t\"net/http\"\n\t\"time\"\n)\n\ntype CreateComment struc"
  },
  {
    "path": "dtos/orders.go",
    "chars": 3269,
    "preview": "package dtos\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/models\"\n\t\"net/http\"\n)\n\ntype CreateOrderRequestDto struc"
  },
  {
    "path": "dtos/pages.go",
    "chars": 315,
    "preview": "package dtos\n\nimport \"github.com/melardev/GoGonicEcommerceApi/models\"\n\nfunc CreateHomeResponse(tags []models.Tag, catego"
  },
  {
    "path": "dtos/products.go",
    "chars": 2915,
    "preview": "package dtos\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/models\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype ManagedM"
  },
  {
    "path": "dtos/shared.go",
    "chars": 3841,
    "preview": "package dtos\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"gopkg.in/go-playground/validator.v8\"\n\t\"math\"\n\t\"net/http\"\n)\n\n"
  },
  {
    "path": "dtos/tags.go",
    "chars": 1240,
    "preview": "package dtos\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/models\"\n\t\"strings\"\n)\n\ntype CreateTag struct {\n\tName    "
  },
  {
    "path": "dtos/users.go",
    "chars": 1620,
    "preview": "package dtos\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/models\"\n)\n\ntype RegisterRequestDto struct {\n\tUsername  "
  },
  {
    "path": "infrastructure/db.go",
    "chars": 1398,
    "preview": "package infrastructure\n\nimport (\n\t\"fmt\"\n\t\"github.com/jinzhu/gorm\"\n\t\"path\"\n\n\t_ \"github.com/jinzhu/gorm/dialects/mysql\"\n\t_"
  },
  {
    "path": "main.go",
    "chars": 5352,
    "preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-contrib/cors\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/jinzhu/gorm\"\n\t\"git"
  },
  {
    "path": "middlewares/auth.go",
    "chars": 1777,
    "preview": "package middlewares\n\nimport (\n\t\"fmt\"\n\t\"github.com/dgrijalva/jwt-go\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/melardev/Go"
  },
  {
    "path": "middlewares/benchmark.go",
    "chars": 332,
    "preview": "package middlewares\n\nimport (\n\t\"fmt\"\n\t\"github.com/gin-gonic/gin\"\n\t\"math\"\n\t\"time\"\n)\n\nfunc Benchmark() gin.HandlerFunc {\n\t"
  },
  {
    "path": "middlewares/cors.go",
    "chars": 276,
    "preview": "package middlewares\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n)\n\nfunc Cors() gin.HandlerFunc {\n\treturn func(c *gin.Context) "
  },
  {
    "path": "models/address.go",
    "chars": 566,
    "preview": "package models\n\nimport \"github.com/jinzhu/gorm\"\n\ntype Address struct {\n\tgorm.Model\n\tStreetAddress string `gorm:\"not null"
  },
  {
    "path": "models/category.go",
    "chars": 518,
    "preview": "package models\n\nimport (\n\t\"github.com/gosimple/slug\"\n\t\"github.com/jinzhu/gorm\"\n)\n\ntype Category struct {\n\tgorm.Model\n\tNa"
  },
  {
    "path": "models/comment.go",
    "chars": 338,
    "preview": "package models\n\nimport (\n\t\"github.com/jinzhu/gorm\"\n)\n\ntype Comment struct {\n\tgorm.Model\n\tContent   string  `gorm:\"size:2"
  },
  {
    "path": "models/file_upload.go",
    "chars": 814,
    "preview": "package models\n\nimport \"github.com/jinzhu/gorm\"\n\ntype FileUpload struct {\n\tgorm.Model\n\tFilename     string\n\tFilePath    "
  },
  {
    "path": "models/order.go",
    "chars": 610,
    "preview": "package models\n\nimport \"github.com/jinzhu/gorm\"\n\ntype Order struct {\n\tgorm.Model\n\tOrderStatus    int `gorm:\"default:0\"`\n"
  },
  {
    "path": "models/order_item.go",
    "chars": 430,
    "preview": "package models\n\nimport \"github.com/jinzhu/gorm\"\n\ntype OrderItem struct {\n\tgorm.Model\n\tOrder   Order\n\tOrderId uint `gorm:"
  },
  {
    "path": "models/product.go",
    "chars": 879,
    "preview": "package models\n\nimport (\n\t\"github.com/gosimple/slug\"\n\t\"github.com/jinzhu/gorm\"\n)\n\ntype Product struct {\n\tgorm.Model\n\tNam"
  },
  {
    "path": "models/product_category.go",
    "chars": 282,
    "preview": "package models\n\ntype ProductCategory struct {\n\tCategory   User `gorm:\"association_foreignkey:CategoryId\"`\n\tCategoryId ui"
  },
  {
    "path": "models/product_tag.go",
    "chars": 257,
    "preview": "package models\n\ntype ProductTag struct {\n\tTag       User `gorm:\"association_foreignkey:TagId\"`\n\tTagId     uint\n\tProduct "
  },
  {
    "path": "models/role.go",
    "chars": 580,
    "preview": "package models\n\nimport \"github.com/jinzhu/gorm\"\n\ntype Role struct {\n\tgorm.Model\n\tName        string\n\tDescription string\n"
  },
  {
    "path": "models/tag.go",
    "chars": 581,
    "preview": "package models\n\nimport (\n\t\"github.com/gosimple/slug\"\n\t\"github.com/jinzhu/gorm\"\n)\n\ntype Tag struct {\n\tgorm.Model\n\tName   "
  },
  {
    "path": "models/user.go",
    "chars": 2841,
    "preview": "package models\n\nimport (\n\t\"errors\"\n\t\"github.com/dgrijalva/jwt-go\"\n\t\"github.com/jinzhu/gorm\"\n\t\"golang.org/x/crypto/bcrypt"
  },
  {
    "path": "seeds/seeder.go",
    "chars": 9154,
    "preview": "package seeds\n\nimport (\n\t\"github.com/icrowley/fake\"\n\t\"github.com/jinzhu/gorm\"\n\t\"github.com/melardev/GoGonicEcommerceApi/"
  },
  {
    "path": "services/addresses.go",
    "chars": 1661,
    "preview": "package services\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/infrastructure\"\n\t\"github.com/melardev/GoGonicEcomme"
  },
  {
    "path": "services/categories.go",
    "chars": 375,
    "preview": "package services\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/infrastructure\"\n\t\"github.com/melardev/GoGonicEcomme"
  },
  {
    "path": "services/comments.go",
    "chars": 2264,
    "preview": "package services\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/infrastructure\"\n\t\"github.com/melardev/GoGonicEcomme"
  },
  {
    "path": "services/orders.go",
    "chars": 1489,
    "preview": "package services\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/infrastructure\"\n\t\"github.com/melardev/GoGonicEcomme"
  },
  {
    "path": "services/products.go",
    "chars": 3191,
    "preview": "package services\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/infrastructure\"\n\t\"github.com/melardev/GoGonicEcomme"
  },
  {
    "path": "services/shared.go",
    "chars": 339,
    "preview": "package services\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/infrastructure\"\n)\n\nfunc CreateOne(data interface{})"
  },
  {
    "path": "services/tags.go",
    "chars": 336,
    "preview": "package services\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/infrastructure\"\n\t\"github.com/melardev/GoGonicEcomme"
  },
  {
    "path": "services/users.go",
    "chars": 819,
    "preview": "package services\n\nimport (\n\t\"github.com/melardev/GoGonicEcommerceApi/infrastructure\"\n\t\"github.com/melardev/GoGonicEcomme"
  }
]

About this extraction

This page contains the full source code of the melardev/GoGonicEcommerceApi GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 45 files (91.0 KB), approximately 25.6k tokens, and a symbol index with 151 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!