From 79309d31baa71ce6ab0292fd7301ed99b1ee0ef2 Mon Sep 17 00:00:00 2001 From: KostLinux Date: Mon, 3 Jun 2024 08:22:03 +0300 Subject: [PATCH 1/5] Added installer --- bin/installer/install.sh | 17 +++++++++++++++ bin/installer/src/common.sh | 22 +++++++++++++++++++ bin/installer/src/macos.sh | 42 +++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 bin/installer/install.sh create mode 100644 bin/installer/src/common.sh create mode 100644 bin/installer/src/macos.sh diff --git a/bin/installer/install.sh b/bin/installer/install.sh new file mode 100644 index 0000000..702c35f --- /dev/null +++ b/bin/installer/install.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +source src/*.sh + +OS=$(uname -s) +VERSION=$1 + +if [[ -z $VERSION ]]; then + echo "No version specified. Please specify the version to install." + echo "Available versions can be tag or branch name. Example: v1.0.0 or master" + exit 1 +fi + +if [[ $OS == "Darwin" ]]; then + install_on_macos + install_goblitz +fi \ No newline at end of file diff --git a/bin/installer/src/common.sh b/bin/installer/src/common.sh new file mode 100644 index 0000000..c915033 --- /dev/null +++ b/bin/installer/src/common.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +function install_goblitz() { + if ! [ -d "$(pwd)/GoBlitz" ]; then + echo "GoBlitz directory does not exist. Pulling GoBlitz..." + git clone git@github.com:LookinLabs/GoBlitz.git --depth 1 --branch $VERSION + fi +} + +function update_goblitz() { + if `pwd | grep -q "GoBlitz"` && [ "$VERSION" == "master" ]; then + echo "GoBlitz directory found. Pulling updates..." + git pull origin master + fi + + if `pwd | grep -q "GoBlitz"` && [ "$VERSION" != "master" ]; then + echo "GoBlitz directory found. Checking out $VERSION..." + git fetch + git checkout $VERSION + git pull + fi +} \ No newline at end of file diff --git a/bin/installer/src/macos.sh b/bin/installer/src/macos.sh new file mode 100644 index 0000000..0ddcbdc --- /dev/null +++ b/bin/installer/src/macos.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +function install_macos_utilities() { + + if ! command -v brew &> /dev/null; then + echo "Homebrew is not installed. Installing Homebrew..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi + + for common_utils in curl git go; do + if ! command -v $common_utils &> /dev/null; then + echo "$common_utils is not installed. Installing $common_utils..." + brew install $common_utils + fi + done + + for linter_utils in golangci-lint gosec; do + if ! command -v $linter_utils &> /dev/null; then + echo "$linter_utils is not installed. Installing $linter_utils..." + brew install $linter_utils + fi + done + + for migration_utils in goose; do + if ! command -v $migration_utils &> /dev/null; then + echo "$migration_utils is not installed. Installing $migration_utils..." + brew install $migration_utils + fi + done + + if ! command -v air &> /dev/null; then + echo "air is not installed. Installing air..." + curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s + fi + + + if ! command -v docker &> /dev/null; then + echo "Docker is not installed. Please install Docker Desktop." + # Open the Docker Desktop download page + open https://hub.docker.com/editions/community/docker-ce-desktop-mac/ + fi +} \ No newline at end of file From 22916fd98880eee93f9b7c03aa0151763ae459e8 Mon Sep 17 00:00:00 2001 From: KostLinux Date: Mon, 3 Jun 2024 08:32:28 +0300 Subject: [PATCH 2/5] Separated install and added update --- bin/installer/install.sh | 30 +++++++++++++++++++++++------- bin/installer/src/common.sh | 9 +++++---- bin/installer/src/macos.sh | 0 3 files changed, 28 insertions(+), 11 deletions(-) mode change 100644 => 100755 bin/installer/install.sh mode change 100644 => 100755 bin/installer/src/common.sh mode change 100644 => 100755 bin/installer/src/macos.sh diff --git a/bin/installer/install.sh b/bin/installer/install.sh old mode 100644 new mode 100755 index 702c35f..5d1def9 --- a/bin/installer/install.sh +++ b/bin/installer/install.sh @@ -1,17 +1,33 @@ #!/bin/bash -source src/*.sh +for i in $(pwd)/bin/installer/src/*.sh; do + source $i +done OS=$(uname -s) -VERSION=$1 +OPTION=$1 +VERSION=$2 if [[ -z $VERSION ]]; then - echo "No version specified. Please specify the version to install." + echo "No version specified. Please specify the version to install or update." echo "Available versions can be tag or branch name. Example: v1.0.0 or master" exit 1 fi -if [[ $OS == "Darwin" ]]; then - install_on_macos - install_goblitz -fi \ No newline at end of file +case $OPTION in + --install) + if [[ $OS == "Darwin" ]]; then + install_macos_utilities $VERSION + install_goblitz $VERSION + fi + ;; + --update) + if [[ $OS == "Darwin" ]]; then + update_goblitz $VERSION + fi + ;; + *) + echo "Invalid option. Please use --install or --update." + exit 1 + ;; +esac \ No newline at end of file diff --git a/bin/installer/src/common.sh b/bin/installer/src/common.sh old mode 100644 new mode 100755 index c915033..9f60cc9 --- a/bin/installer/src/common.sh +++ b/bin/installer/src/common.sh @@ -3,20 +3,21 @@ function install_goblitz() { if ! [ -d "$(pwd)/GoBlitz" ]; then echo "GoBlitz directory does not exist. Pulling GoBlitz..." - git clone git@github.com:LookinLabs/GoBlitz.git --depth 1 --branch $VERSION + git config --global advice.detachedHead false + git clone git@github.com:LookinLabs/GoBlitz.git --branch $VERSION + rm -rf GoBlitz/.git fi } function update_goblitz() { if `pwd | grep -q "GoBlitz"` && [ "$VERSION" == "master" ]; then echo "GoBlitz directory found. Pulling updates..." - git pull origin master + git merge origin/master fi if `pwd | grep -q "GoBlitz"` && [ "$VERSION" != "master" ]; then echo "GoBlitz directory found. Checking out $VERSION..." git fetch - git checkout $VERSION - git pull + git merge $VERSION fi } \ No newline at end of file diff --git a/bin/installer/src/macos.sh b/bin/installer/src/macos.sh old mode 100644 new mode 100755 From e5f2108a2214ff840b25e8fd5f2afae30c167722 Mon Sep 17 00:00:00 2001 From: KostLinux Date: Mon, 3 Jun 2024 09:02:46 +0300 Subject: [PATCH 3/5] Migrating the installer into go --- .version.txt | 1 + bin/installer/go/go.mod | 3 ++ bin/installer/go/helper/file.go | 14 +++++++ bin/installer/go/main.go | 38 +++++++++++++++++ bin/installer/go/src/common.go | 25 +++++++++++ bin/installer/go/src/macos.go | 74 +++++++++++++++++++++++++++++++++ 6 files changed, 155 insertions(+) create mode 100644 .version.txt create mode 100644 bin/installer/go/go.mod create mode 100644 bin/installer/go/helper/file.go create mode 100644 bin/installer/go/main.go create mode 100644 bin/installer/go/src/common.go create mode 100644 bin/installer/go/src/macos.go diff --git a/.version.txt b/.version.txt new file mode 100644 index 0000000..60453e6 --- /dev/null +++ b/.version.txt @@ -0,0 +1 @@ +v1.0.0 \ No newline at end of file diff --git a/bin/installer/go/go.mod b/bin/installer/go/go.mod new file mode 100644 index 0000000..0d4b6c4 --- /dev/null +++ b/bin/installer/go/go.mod @@ -0,0 +1,3 @@ +module goblitz-installer + +go 1.22.3 diff --git a/bin/installer/go/helper/file.go b/bin/installer/go/helper/file.go new file mode 100644 index 0000000..6c75330 --- /dev/null +++ b/bin/installer/go/helper/file.go @@ -0,0 +1,14 @@ +package helper + +import "os" + +func CheckIfFileExists(path string) (bool, error) { + _, err := os.Stat(path) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} diff --git a/bin/installer/go/main.go b/bin/installer/go/main.go new file mode 100644 index 0000000..0246efa --- /dev/null +++ b/bin/installer/go/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "flag" + "goblitz-installer/src" + "log" + "os" + "runtime" +) + +var version string +var install bool +var update bool + +func init() { + flag.StringVar(&version, "version", "", "Specify the version to install or update.") + flag.BoolVar(&install, "install", false, "Install GoBlitz.") + flag.BoolVar(&update, "update", false, "Update GoBlitz.") + flag.Parse() +} + +func main() { + if version == "" { + log.Println("No version specified. Please specify the version to install or update.") + log.Println("Available versions can be tag or branch name. Example: v1.0.0 or master") + os.Exit(1) + } + + switch { + case install && runtime.GOOS == "darwin": + src.InstallMacOSUtilities() + case update && runtime.GOOS == "darwin": + // Implement the update functionality here + default: + log.Println("Invalid option. Please use --install or --update.") + os.Exit(1) + } +} diff --git a/bin/installer/go/src/common.go b/bin/installer/go/src/common.go new file mode 100644 index 0000000..9a44c57 --- /dev/null +++ b/bin/installer/go/src/common.go @@ -0,0 +1,25 @@ +package src + +import ( + "goblitz-installer/helper" + "log" + "os" +) + +func InstallGoBlitz(version string) { + exists, err := helper.CheckIfFileExists("GoBlitz") + if err != nil { + log.Fatalf("Failed to check if GoBlitz directory exists: %s", err) + } + if !exists { + log.Println("GoBlitz directory does not exist. Pulling GoBlitz...") + runCommand("git", "config", "--global", "advice.detachedHead", "false") + runCommand("git", "clone", "--branch", version, "git@github.com:LookinLabs/GoBlitz.git") + os.RemoveAll("GoBlitz/.git") + } + + err = os.WriteFile(".version.txt", []byte(version), 0644) + if err != nil { + log.Fatalf("Failed to write version to .version.txt: %s", err) + } +} diff --git a/bin/installer/go/src/macos.go b/bin/installer/go/src/macos.go new file mode 100644 index 0000000..80fa937 --- /dev/null +++ b/bin/installer/go/src/macos.go @@ -0,0 +1,74 @@ +package src + +import ( + "io" + "log" + "os/exec" +) + +func InstallMacOSUtilities() { + // List of utilities to install + commonUtils := []string{"curl", "git", "go"} + linterUtils := []string{"golangci-lint", "gosec"} + migrationUtils := []string{"goose"} + + // Check if Homebrew is installed + if !isCommandAvailable("brew") { + log.Println("Homebrew is not installed. Installing Homebrew...") + runCommand("/bin/bash", "-c", `$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)`) + } + + // Install common utilities + for _, util := range commonUtils { + if !isCommandAvailable(util) { + log.Printf("%s is not installed. Installing %s...\n", util, util) + runCommand("brew", "install", util) + } + } + + // Install linter utilities + for _, util := range linterUtils { + if !isCommandAvailable(util) { + log.Printf("%s is not installed. Installing %s...\n", util, util) + runCommand("brew", "install", util) + } + } + + // Install migration utilities + for _, util := range migrationUtils { + if !isCommandAvailable(util) { + log.Printf("%s is not installed. Installing %s...\n", util, util) + runCommand("brew", "install", util) + } + } + + // Install air + if !isCommandAvailable("air") { + log.Println("air is not installed. Installing air...") + runCommand("/bin/bash", "-c", `$(curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh)`) + } + + // Check if Docker is installed + if !isCommandAvailable("docker") { + log.Println("Docker is not installed. Please install Docker Desktop.") + // Open the Docker Desktop download page + runCommand("open", "https://hub.docker.com/editions/community/docker-ce-desktop-mac/") + } +} + +func isCommandAvailable(name string) bool { + cmd := exec.Command("/bin/sh", "-c", "command -v "+name) + if err := cmd.Run(); err != nil { + return false + } + return true +} + +func runCommand(name string, arg ...string) { + cmd := exec.Command(name, arg...) + cmd.Stdout = io.Discard + cmd.Stderr = io.Discard + if err := cmd.Run(); err != nil { + log.Fatalf("Failed to execute command: %s", err) + } +} From 6f027c65c9af60a7db84f94a5c7fe3814987a2fd Mon Sep 17 00:00:00 2001 From: KostLinux Date: Tue, 4 Jun 2024 19:39:41 +0300 Subject: [PATCH 4/5] Updated changes right now --- bin/installer/go/helper/file.go | 2 +- bin/installer/go/main.go | 1 + bin/installer/go/src/common.go | 35 ++++++++++++++++++++++++--------- bin/installer/go/src/macos.go | 3 ++- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/bin/installer/go/helper/file.go b/bin/installer/go/helper/file.go index 6c75330..8f05cb5 100644 --- a/bin/installer/go/helper/file.go +++ b/bin/installer/go/helper/file.go @@ -2,7 +2,7 @@ package helper import "os" -func CheckIfFileExists(path string) (bool, error) { +func CheckIfPathExists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil diff --git a/bin/installer/go/main.go b/bin/installer/go/main.go index 0246efa..a0c9c9b 100644 --- a/bin/installer/go/main.go +++ b/bin/installer/go/main.go @@ -29,6 +29,7 @@ func main() { switch { case install && runtime.GOOS == "darwin": src.InstallMacOSUtilities() + src.InstallGoBlitz(version) case update && runtime.GOOS == "darwin": // Implement the update functionality here default: diff --git a/bin/installer/go/src/common.go b/bin/installer/go/src/common.go index 9a44c57..9931366 100644 --- a/bin/installer/go/src/common.go +++ b/bin/installer/go/src/common.go @@ -2,24 +2,41 @@ package src import ( "goblitz-installer/helper" + "io" "log" + "net/http" "os" ) func InstallGoBlitz(version string) { - exists, err := helper.CheckIfFileExists("GoBlitz") + checkFile, err := helper.CheckIfPathExists("GoBlitz") if err != nil { log.Fatalf("Failed to check if GoBlitz directory exists: %s", err) } - if !exists { + + if !checkFile { log.Println("GoBlitz directory does not exist. Pulling GoBlitz...") - runCommand("git", "config", "--global", "advice.detachedHead", "false") - runCommand("git", "clone", "--branch", version, "git@github.com:LookinLabs/GoBlitz.git") - os.RemoveAll("GoBlitz/.git") - } + releaseUri := "https://github.com/LookinLabs/GoBlitz/archive/refs/tags/" + version + ".zip" + + uriResponse, err := http.Get(releaseUri) + if err != nil { + log.Fatalf("Failed to download the zip file: %s", err) + } + + defer uriResponse.Body.Close() + + fileOutput, err := os.Create("/tmp/GoBlitz.zip") + if err != nil { + log.Fatalf("Failed to create the zip file: %s", err) + } + + defer fileOutput.Close() + + _, err = io.Copy(fileOutput, uriResponse.Body) + if err != nil { + log.Fatalf("Failed to write the zip file: %s", err) + } - err = os.WriteFile(".version.txt", []byte(version), 0644) - if err != nil { - log.Fatalf("Failed to write version to .version.txt: %s", err) } + } diff --git a/bin/installer/go/src/macos.go b/bin/installer/go/src/macos.go index 80fa937..2ca51a4 100644 --- a/bin/installer/go/src/macos.go +++ b/bin/installer/go/src/macos.go @@ -11,7 +11,7 @@ func InstallMacOSUtilities() { commonUtils := []string{"curl", "git", "go"} linterUtils := []string{"golangci-lint", "gosec"} migrationUtils := []string{"goose"} - + log.Println("Checking MacOS utilities...") // Check if Homebrew is installed if !isCommandAvailable("brew") { log.Println("Homebrew is not installed. Installing Homebrew...") @@ -54,6 +54,7 @@ func InstallMacOSUtilities() { // Open the Docker Desktop download page runCommand("open", "https://hub.docker.com/editions/community/docker-ce-desktop-mac/") } + log.Println("MacOS utilities installed successfully.") } func isCommandAvailable(name string) bool { From bfb4084630c21d0bbe282fd55e612333dfc144a0 Mon Sep 17 00:00:00 2001 From: KostLinux Date: Sun, 30 Jun 2024 18:48:49 +0300 Subject: [PATCH 5/5] feat: Updated contributions --- .version.txt | 1 - CONTRIBUTE.md | 642 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 642 insertions(+), 1 deletion(-) delete mode 100644 .version.txt create mode 100644 CONTRIBUTE.md diff --git a/.version.txt b/.version.txt deleted file mode 100644 index 60453e6..0000000 --- a/.version.txt +++ /dev/null @@ -1 +0,0 @@ -v1.0.0 \ No newline at end of file diff --git a/CONTRIBUTE.md b/CONTRIBUTE.md new file mode 100644 index 0000000..b25fa52 --- /dev/null +++ b/CONTRIBUTE.md @@ -0,0 +1,642 @@ +# Contribute Guidelines + +1. Clone the repository to your local machine. +2. Create a branch (`/feature`, `/bugfix`, or `/hotfix`) based on your needs. +3. Make the necessary changes. +4. Push your changes to the Git repository. +5. Create a pull request. +6. Add appropriate labels to the pull request. + +You can also use the provided `docker-compose` file for local testing. More information about local environment setup can be found [here](/doc/local-env.md/). + +**Note!** Don't forget to validate your code via make validate (for GO only) or make validate_codebase (for whole CodeBase) + +# GoBlitz - Practices and Coding Conventions + +## Introduction + +This document outlines the coding conventions and practices for the **Travelis** project, specifically tailored for GoLang development using Gin Gonic. Adhering to these standards ensures code consistency, readability, and maintainability across the project. + +## Table of Contents + +1. [General Coding Standards](#general-coding-standards) + - [Naming Conventions](#naming-conventions) + - - [Variables and Functions](#variables-and-functions) + - - [Structs](#structs) + - - [Interfaces](#interfaces) + - - [Constants](#constants) + - [Code Structure](#code-structure) + - - [Packages](#packages) + - - [File Naming](#file-naming) + - - [Folder Structure](#folder-structure) + - - [Test Files](#test-files) + - [Formatting](#formatting) + - - [Newlines](#newlines) + - - [Commenting](#commenting) + - [Error Handling](#error-handling) + - - [Error return](#error-return) + - - [Error Messages](#error-messages) + - - [Error shorthand](#error-shorthand) + - [Miscellaneous](#miscellaneous) + - - [One-letter variable names](#one-letter-variable-names) + - - [Enumerations](#enumerations) +2. [Layer-Specific Guidelines](#layer-specific-guidelines) + - [Router (using Gin Gonic)](#router-using-gin-gonic) + - [Controller](#controller) + - [Service](#service) + - [Repository](#repository) +3. [Best Practices for PRs](#best-practices-for-prs) +4. [Commit Messages](#commit-messages) +5. [Conclusion](#conclusion) + +--- + +## General Coding Standards + +### Naming Conventions + +#### **Variables and Functions**: + +Use `camelCase` for variable. + +- Additional words can be added to disambiguate similar names, for example userCount and projectCount. +- Do not simply drop letters to save typing. For example `sandbox` is preferred over `Sbx`, particularly for exported names. +- Omit types and type-like words from most variable names. +- - For a number, `userCount` is a better name than `numUsers` or `usersInt`. +- - For a slice, `users` is a better name than `userSlice`. +- Omit words that are clear from the surrounding context. For example, in the implementation of a `UserCount` method, a local variable called `userCount` is probably redundant; `count`, `users` are just as readable. +- Use `camelCase` for function names if they are not exported, and `PascalCase` if they are exported. For example, `getUserByID` is a private function, and `GetUserByID` is a public function. All that is not exported should be added after the // private comment. + +#### **Structs**: + +Use `PascalCase` for struct names. + +**Good Example**: + +```go +type User struct { + ID int + Name string +} + +users := []User{ + {ID: 1, Name: "Alice"}, + {ID: 2, Name: "Bob"}, +} + +var userCount int + +func GetUserByID(userID int) { + // Implementation +} + +// private +func getUserByID(userID int) { + // Implementation +} +``` + +**Bad Example**: + +```go +type UserStruct struct { + UserID int + UserName string +} + +userSlice := []UserStruct{ + {UserID: 1, UserName: "Alice"}, + {UserID: 2, UserName: "Bob"}, +} + +var user_count int + +func getUserByID(userID int) { + // Implementation +} + +// private +func GetUserByID(userID int) { + // Implementation +} +``` + +--- + +#### **Interfaces**: + +- Interfaces should be prefixed with `I` (e.g., `IService`) to indicate it's an interface. + +Good Example: + +```go +type IUserService interface { + GetUserByID(id int) (*User, error) +} +``` + +Bad Example: + +```go +type UserService interface { + GetUserByID(id int) (*User, error) +} +``` + +--- + +#### **Constants**: + +Use MixedCaps for constants. + +- **MixedCaps**: For example, a constant is MaxLength (not MAX_LENGTH) if exported and maxLength (not max_length) if unexported. + +**Good Example:** + +```go +const MaxLength = 10 +const minPasswordLength = 8 +``` + +**Bad Example:** + +```go +const MAX_LENGTH = 10 +const MIN_PASSWORD_LENGTH = 8 +``` + +--- + +### Code Structure + +#### **Packages**: + +Go package names should be short and contain only lowercase letters. A package name composed of multiple words should be left unbroken in all lowercase. For example, the package tabwriter is not named `tabWriter`, `TabWriter`, or `tab_writer` + +#### **File Naming**: + +Use descriptive names, and separate words with underscores (e.g., `user.go`, `user_validations.go`). + +#### **Folder Structure**: + +Organize code into meaningful packages and folders (e.g., `controller/`, `service/`, `repository/`, `model/`) + +#### **Test Files**: + +Name test files with `_test.go` suffix (e.g., `user_test.go`). + +**Good Example:** + +```go +package user +package uservalidations +``` + +``` +travelis/ + ├── controller/ + │ ├── user.go + | ├── user_test.go + │ └── ... + ├── service/ + │ ├── user.go + | ├── user_test.go + │ └── ... + ├── repository/ + │ ├── user.go + | ├── user_test.go + │ └── ... + |── model/ + | ├── user.go + └── main.go +``` + +**Bad Example:** + +```go +package User +package user_validations +``` + +``` +travelis/ + ├── controllers/ + │ ├── controller_user.go + | ├── controller_userTest.go + │ └── ... + ├── services/ + │ ├── userService.go + | ├── userService_test.go + │ └── ... + ├── repositories/ + │ ├── userRepository.go + | ├── userRepositorytest.go + │ └── ... + |── models/ + | ├── user_model.go + └── main.go +``` + +--- + +### Formatting + +#### **Newlines**: + +Ensure there is a newline after `}` when there is a `return` or new `var` and before `var` or any other line following a closing brace. Also, include a newline after each `case` in a `switch` statement. + +**Good Example:** + +```go +func example() { + if condition { + return + } + + var x int + x = 10 + if x > 5 { + fmt.Println("x is greater than 5") + } + + switch x { + case 1: + fmt.Println("x is 1") + + case 2: + fmt.Println("x is 2") + + default: + fmt.Println("x is neither 1 nor 2") + } + + return x +} +``` + +**Bad Example:** + +```go +func example() { + if condition { + return + } + var x int + x = 10 + if x > 5 { + fmt.Println("x is greater than 5") + } + switch x { + case 1: + fmt.Println("x is 1") + case 2: + fmt.Println("x is 2") + default: + fmt.Println("x is neither 1 nor 2") + } + return x +} +``` + +#### **Commenting**: + +Add comments to explain complex logic or non-obvious code. + +--- + +### Error Handling + +#### **Error return**: + +Prefer returning errors explicitly instead of using panic. + +**Good Example:** + +```go +func getUserByID(userID int) (*User, error) { + user, err := userRepository.FindByID(userID) + if err != nil { + return nil, fmt.Errorf("finding user by ID failed: %v", err) + } + + return user, nil +} +``` + +**Bad Example:** + +```go +func getUserByID(userID int) *User { + user, err := userRepository.FindByID(userID) + if err != nil { + panic(err) + } + + return user +} +``` + +--- + +#### **Error Messages**: + +Provide meaningful error messages when returning errors. + +**Good Example:** + +```go +func getUserByID(userID int) (*User, error) { + user, err := userRepository.FindByID(userID) + if err != nil { + return nil, fmt.Errorf("finding user by ID failed: %v", err) + } + + return user, nil +} +``` + +**Bad Example:** + +```go +func getUserByID(userID int) (*User, error) { + user, err := userRepository.FindByID(userID) + if err != nil { + return nil, err + } + + return user, nil +} +``` + +--- + +#### **Error shorthand**: + +Use the `err` shorthand for error variables. + +**Good Example:** + +```go +if err := someFunction(); err != nil { + return err +} +``` + +**Bad Example:** + +```go +err := someFunction() +if err != nil { + return err +} +``` + +--- + +### Miscellaneous + +#### One-letter variable names: + +Avoid using one-letter variable names except in cases like loop indices (`i`, `j`, `k`). + +**Good Example:** + +```go +func calculateArea(length, width int) int { + return length * width +} +``` + +**Bad Example:** + +```go +func calculateArea(l, w int) int { + return l * w +} +``` + +#### Enumerations: + +Enums should be defined within the `model` package with their respective constants. + +**Good Example:** + +```go +package enum + +type Coverage string + +const ( + CoverageMostSegments Coverage = "MOST_SEGMENTS" + CoverageAtLeastOneSegment Coverage = "AT_LEAST_ONE_SEGMENT" + CoverageAllSegments Coverage = "ALL_SEGMENTS" +) + +var Coverages = []string{ + string(CoverageMostSegments), + string(CoverageAtLeastOneSegment), + string(CoverageAllSegments), +} +``` + +**Bad Example:** + +```go +package enum + +const ( + MostSegments = "MOST_SEGMENTS" + AtLeastOneSegment = "AT_LEAST_ONE_SEGMENT" + AllSegments = "ALL_SEGMENTS" +) + +var Coverages = []string{ + MostSegments, + AtLeastOneSegment, + AllSegments, +} +``` + +--- + +## Layer-Specific Guidelines + +The software architecture that we are using is "Layered Architecture" or "Layered Design Pattern." In this architecture, the different components of the system are organized into separate layers, each with its own specific responsibility. + +### Router (using Gin Gonic) + +- Define routes using Gin Gonic's Router. +- Use meaningful route paths and HTTP methods. + +#### Example: Gin Router Setup + +```go +package main + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +func main() { + r := gin.Default() + + // Example route with handler + r.GET("/users/:id", getUserHandler) + + r.Run(":8080") +} + +func getUserHandler(c *gin.Context) { + // Implementation to fetch user by ID + userID := c.Param("id") + + // Call service layer to fetch user details + user, err := userService.GetUserByID(userID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, user) +} +``` + +### Controller + +- Controllers should handle HTTP request/response lifecycle. +- Validate request, query, path parameters, and call service methods. + +Example: **UserController** + +```go +package controllers + +import ( + "github.com/gin-gonic/gin" + "net/http" + "strconv" +) + +// UserController handles user-related requests +type UserController struct { + userService IUserService +} + +// NewUserController creates a new UserController +func NewUserController(us IUserService) *UserController { + return &UserController{userService: us} +} + +// GetUserByID retrieves a user by ID +func (uc *UserController) GetUserByID(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.Atoi(idStr) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id parameter"}) + return + } + + user, err := uc.userService.GetUser(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, user) +} +``` + +### Service + +- Business logic should reside here. +- Follow Single Responsibility Principle (SRP) for service methods. + +Example: **UserService** + +```go +package services + +import ( + "errors" +) + +// UserService provides user-related operations +type UserService struct { + // Service dependencies if any +} + +// NewUserService creates a new instance of UserService +func NewUserService() *UserService { + return &UserService{} +} + +// GetUser retrieves a user by ID +func (us *UserService) GetUser(id int) (*User, error) { + // Example logic to fetch user from repository layer + user, err := userRepository.FindByID(id) + if err != nil { + return nil, errors.New("failed to fetch user") + } + + return user, nil +} +``` + +### Repository + +- Data access layer for interacting with databases or external services. +- Implement data persistence logic. + +Example: **UserRepository** + +```go +package repositories + +// UserRepository provides data access operations for users +type UserRepository struct { + // Repository dependencies if any +} + +// NewUserRepository creates a new instance of UserRepository +func NewUserRepository() *UserRepository { + return &UserRepository{} +} + +// FindByID retrieves a user by ID from the database +func (ur *UserRepository) FindByID(id int) (*User, error) { + // Example implementation to fetch user from database or external service + // Placeholder code for demonstration purposes + user := &User{ + ID: id, + Name: "John Doe", + Age: 30, + } + + return user, nil +} +``` + +## Best Practices for PRs + +- Consistency: Ensure adherence to coding standards and guidelines. +- - Review your code before creating a PR to catch any errors or issues. +- Code Reviews: All PRs must be reviewed by at least one team member. +- Formatting: Check for proper indentation, brace placement, and comment usage. + +## Commit Messages + +[Conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) are used for commit messages. + +Commit messages should be clear and descriptive. They should follow the format `type: subject` where type is one of the following: + +- `feat`: a new feature +- `fix`: a bug fix +- `docs`: changes to documentation +- `style`: formatting, missing semi colons, etc; no code change +- `refactor`: refactoring production code +- `test`: adding tests, refactoring test; no production code change + +## Conclusion + +By following these coding conventions and best practices, we aim to maintain a high standard of code quality, readability, and maintainability across the **Travelis** project. Consistency in coding style and structure will help streamline development and collaboration among team members. For any questions or suggestions regarding these practices, please reach out to the team. \ No newline at end of file