-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 91490e9
Showing
62 changed files
with
5,258 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ./... |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
app.env | ||
bin/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" ] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Oops, something went wrong.