Skip to content

Commit

Permalink
GO-0 version 1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
mukhriddin-dev committed Dec 16, 2023
0 parents commit 91490e9
Show file tree
Hide file tree
Showing 62 changed files with 5,258 additions and 0 deletions.
59 changes: 59 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: Deploy to production

on:
push:
branches: [ master]

jobs:

build:
name: Build image
runs-on: ubuntu-latest
steps:

- name: Check out code
uses: actions/checkout@v2
with:
fetch-depth: 0

- name: Set up Date
id: date
run: |
echo "::set-output name=date::$(date +'%m/%d/%Y:%H:%M')"
- name: Set up API versioning
id: ghd
uses: proudust/[email protected]

- name: Setup Environment Variables
run: |
echo "SERVER_PORT=${{ secrets.SERVER_PORT }}" >> .env
echo "DB_DSN=${{ secrets.DB_DSN }}" >> .env
echo "SMTP_HOST=${{ secrets.SMTP_HOST }}" >> .env
echo "SMTP_PORT=${{ secrets.SMTP_PORT }}" >> .env
echo "SMTP_USERNAME=${{ secrets.SMTP_USERNAME }}" >> .env
echo "SMTP_PASSWORD=${{ secrets.SMTP_PASSWORD }}" >> .env
echo "SMTP_SENDER=${{ secrets.SMTP_SENDER }}" >> .env
- name: Build and tag Docker image
run: |
docker build --build-arg VERSION=${{ steps.ghd.outputs.describe }} --build-arg CURRENT_TIME=${{ steps.date.outputs.date }} -t gcr.io/${{ secrets.GCP_PROJECT_ID }}/blogpost:${{ steps.ghd.outputs.describe }} -f app.dockerfile .
- name: Configure Google Cloud credentials
uses: google-github-actions/[email protected]
with:
service_account_key: ${{ secrets.GCP_SA_KEY }}
project_id: ${{ secrets.GCP_PROJECT_ID }}
export_default_credentials: true

- name: Login to Google GCR
run: |
echo ${{ secrets.GCP_SA_KEY }} | base64 --decode | docker login -u _json_key --password-stdin https://gcr.io
- name: Push Docker image to Google GCR
run: |
docker push gcr.io/${{ secrets.GCP_PROJECT_ID }}/blogpost:${{ steps.ghd.outputs.describe }}
- name: Deploy to Google Cloud Run
run: |
gcloud run deploy blogpost --image gcr.io/${{ secrets.GCP_PROJECT_ID }}/blogpost:${{ steps.ghd.outputs.describe }} --region asia-southeast1 --platform managed --allow-unauthenticated --set-env-vars DB_DSN=${{ secrets.DB_DSN }}
24 changes: 24 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Run unit tests

on:
push:
branches: [main]
pull_request:
branches: [main]

jobs:
test:
name: Test
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.18
id: go

- name: Check out code into the Go module directory
uses: actions/checkout@v2

- name: Test
run: go test -v ./...
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
app.env
bin/
146 changes: 146 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
include app.env
SHELL := powershell.exe
.SHELLFLAGS := -NoProfile -Command

# ==================================================================================== #
# HELPERS
# ==================================================================================== #

## help: print this help message
.PHONY: help
help:
@echo 'Usage:'
@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'

.PHONY: confirm
confirm:
@echo -n 'Are you sure? [y/N] ' && read ans && [ $${ans:-N} = y ]






# ==================================================================================== #
# DEVELOPMENT
# ==================================================================================== #

## run/api: run the cmd/api application
.PHONY: run/api
run/api:
go run ./cmd/api

## db/psql: connect to the database using psql
.PHONY: db/psql
db/psql:
psql ${DB_DSN}

## db/migrations/new name=$1: create a new database migration
.PHONY: db/migrations/new
db/migrations/new:
@echo 'Creating migration files for ${name}...'
migrate create -seq -ext=.sql -dir=./migrations ${name}

## db/migrations/up: apply all up database migrations
.PHONY: db/migrations/up
db/migrations/up: confirm
@echo 'Running up migrations...'
migrate -path ./migrations -database ${DB_DSN} up





# ==================================================================================== #
# QUALITY CONTROL
# ==================================================================================== #

## audit: tidy dependencies and format, vet and test all code
.PHONY: audit
audit: verify
@echo 'Formatting code...'
go fmt ./...
@echo 'Vetting code...'
go vet ./...
staticcheck ./...
@echo 'Running tests...'
go test -race -vet=off ./...

## vendor: tidy and verify dependencies
.PHONY: verify
verify:
@echo 'Tidying and verifying module dependencies...'
go mod tidy
go mod verify





# ==================================================================================== #
# BUILD
# ==================================================================================== #

current_time = $(shell Get-Date -Format "MM/dd/yyyy:HH:mm")
git_description = $(shell git describe --always --dirty --tags --long)
linker_flags = '-s -X main.buildTime=${current_time} -X main.version=${git_description}'


## build/api: build the cmd/api application
.PHONY: build/api
build/api:
@echo 'Building cmd/api...'
go build -ldflags=${linker_flags} -o=./bin/api ./cmd/api
GOOS=linux GOARCH=amd64 go build -ldflags=${linker_flags} -o=./bin/linux_amd64/api ./cmd/api

## dockercompose: run dockercompose up
.PHONY: dockerup
dockerup:
@echo 'Running dockercompose up'
docker-compose build --build-arg VERSION=${git_description} --build-arg CURRENT_TIME=${current_time}
docker compose up

## dockercompose: run dockercompose down
.PHONY: dockerdown
dockerdown:
@echo 'Running dockercompose down'
docker compose down




# ==================================================================================== #
# TEST
# ==================================================================================== #

.PHONY: test/api
test/api:
@echo 'testing cmd/api...'
go test -v ./cmd/api/

.PHONY: test/api/race
test/api/race:
@echo 'testing with race detector cmd/api...'
go test -v -race ./cmd/api/




# ==================================================================================== #
# Windows
# ==================================================================================== #
.PHONY: db/up/win
db/up/win:
@echo 'running db/migrate/up...'
migrate -path ./migrations -database ${DB_DSN} -verbose up

.PHONY: db/down/win
db/down/win:
@echo 'running db/migrate/down...'
migrate -path ./migrations -database ${DB_DSN} down

.PHONY: db/downversion/win
db/downversion/win:
@echo 'running db/migrate/force...'
migrate -path ./migrations -database ${DB_DSN} force 1

18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# My Blog Posts Platform (Back-End)

In my Golang Back-End, I executed advanced SQL queries, ensured data normalization, db migrations, and established proper indexes. Developed a suite of middlewares for, secureHeaders and client-based rate limiting, to manage request traffic. I handled race conditions using mutual exclusion locks in app and a versioning system in DB. Established a graceful shutdown process for the server, systematically formatted JSON responses, ensured proper error handling, and implemented automated API versioning using git.

Tests are written using Go and API is tested end-to-end using Postman. Application metrics are displayed and load-tested properly. The codebase is maintained on GitHub with dependency injection and third-party dependency security ensured. The CI/CD pipeline is implemented with GitHub workflow and Google Cloud Run. Mailtrap is used as the SMTP server for email dispatching.

Adopted multi-stage Docker file, and leveraged AWS ECR and RDS services. Implemented a stateful authentication process using tokens. Viper and cmd-flags are employed for app configuration. Load balancing is handled in Google Cloud Run, with support for HTTPS and TLS certificates.

## Technologies Used
Golang, Google Cloud, AWS, PostgreSQL, Docker, Postman

## Available Scripts
All the available commands are mentioned in Makefile in the root directory.

## Link to Front-End source code
https://github.com/mukhriddin-dev/Go-blog


30 changes: 30 additions & 0 deletions app.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Build Stage
FROM golang:1.18-alpine3.16 AS builder
WORKDIR /app
COPY . .
ARG VERSION
ARG CURRENT_TIME
ENV VERSION=$VERSION \
CURRENT_TIME=$CURRENT_TIME
RUN GOOS=linux GOARCH=amd64 go build -ldflags="-s -X main.version=$VERSION -X main.buildTime=$CURRENT_TIME" -o main ./cmd/api
RUN apk add curl
RUN curl -L https://github.com/golang-migrate/migrate/releases/download/v4.15.2/migrate.linux-amd64.tar.gz | tar xvz

# Run Stage
FROM alpine:3.16
WORKDIR /app
ENV DB_DSN=postgres://root:secret@pstogres:5432/blogpost?sslmode=disable
RUN echo DB_DSN
COPY --from=builder /app/main .
COPY --from=builder /app/migrate ./migrate
COPY app.env .
COPY start.sh .
COPY wait-for.sh .
COPY /migrations ./migration

# Import environment variables from the app.env file
RUN set -o allexport; source ./app.env; set +o allexport

EXPOSE 3001
CMD [ "/app/main" ]
ENTRYPOINT [ "/app/main", "--env", "production", "--cors-trusted-origins", "$CORS_TRUSTED_ORIGINS" ]
99 changes: 99 additions & 0 deletions cmd/api/comments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package main

import (
"errors"
"net/http"
"strings"

"github.com/AthfanFasee/blog-post-backend/internal/data"
"github.com/AthfanFasee/blog-post-backend/internal/dto"
"github.com/AthfanFasee/blog-post-backend/internal/validator"
)

func (app *application) showCommentsForPostHandler(w http.ResponseWriter, r *http.Request) {
id, err := app.readIDParam(r)
if err != nil {
app.notFoundResponse(w, r)
return
}

comments, err := app.models.Comments.GetAllForPost(id)
if err != nil {
app.serverErrorResponse(w, r, err)
return
}

err = app.writeJSON(w, http.StatusOK, envelope{"comments": comments}, nil)
if err != nil {
app.serverErrorResponse(w, r, err)
}
}

func (app *application) createCommentHandler(w http.ResponseWriter, r *http.Request) {
var input dto.CommentRequestBody

// Decoding JSON values in to input struct
err := app.readJSON(w, r, &input)
if err != nil {
app.badRequestResponse(w, r, err)
return
}

user := app.contextGetUser(r)

comment := &data.Comment{
Text: strings.TrimSpace(input.Text),
PostID: input.PostID,
CreatedBy: user.ID,
}

v := validator.New()

if data.ValidateComment(v, comment); !v.Valid() {
app.validationFailedResponse(w, r, v.Errors)
return
}

err = app.models.Comments.Insert(comment)
if err != nil {
app.serverErrorResponse(w, r, err)
return
}

CommentResponseBody := dto.CommentResponseBody{
ID: comment.ID,
Text: comment.Text,
CreatedBy: comment.CreatedBy,
PostID: comment.PostID,
UserName: user.Name,
}

err = app.writeJSON(w, http.StatusCreated, envelope{"comment": CommentResponseBody}, nil)
if err != nil {
app.serverErrorResponse(w, r, err)
return
}
}

func (app *application) deleteCommentHandler(w http.ResponseWriter, r *http.Request) {
id, err := app.readIDParam(r)
if err != nil {
app.notFoundResponse(w, r)
}

err = app.models.Comments.Delete(id)
if err != nil {
switch {
case errors.Is(err, data.ErrRecordNotFound):
app.notFoundResponse(w, r)
default:
app.serverErrorResponse(w, r, err)
}
return
}

err = app.writeJSON(w, http.StatusOK, envelope{"message": "comment deleted successfully"}, nil)
if err != nil {
app.serverErrorResponse(w, r, err)
}
}
Loading

0 comments on commit 91490e9

Please sign in to comment.