Skip to content

Commit 97f78b5

Browse files
committed
feat(assignment-2): Assignment 2 Implementation
This commit will add assignment 2 implementation
1 parent 2d56c8c commit 97f78b5

21 files changed

+698
-0
lines changed

assignment-2/Dockerfile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM golang:1.18 AS builder
2+
WORKDIR /app
3+
COPY . .
4+
5+
FROM alpine:3.13
6+
WORKDIR /app
7+
COPY --from=builder /app/deploy/hacktiv-assignment-2 .
8+
EXPOSE 8080
9+
10+
ENV APP_NAME=hacktiv-assignment-2
11+
CMD ["./hacktiv-assignment-2"]

assignment-2/Makefile

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.PHONY: migration
2+
migration:
3+
migrate create -ext sql -dir db/migrations/$(module) $(name)
4+
5+
.PHONY: migrate
6+
migrate:
7+
migrate -path db/migrations/$(module) -database "postgres://postgresuser:postgrespassword@127.0.0.1:5432/postgres?sslmode=disable&search_path=public" -verbose up
8+
9+
.PHONY: rollback
10+
rollback:
11+
migrate -path db/migrations/$(module) -database "postgres://postgresuser:postgrespassword@127.0.0.1:5432/postgres?sslmode=disable&search_path=public" -verbose down 1
12+
13+
.PHONY: rollback-all
14+
rollback-all:
15+
migrate -path db/migrations/$(module) -database "postgres://postgresuser:postgrespassword@127.0.0.1:5432/postgres?sslmode=disable&search_path=public" -verbose down -all
16+
17+
.PHONY: force-migrate
18+
force-migrate:
19+
migrate -path db/migrations/$(module) -database "postgres://postgresuser:postgrespassword@127.0.0.1:5432/postgres?sslmode=disable&search_path=public" -verbose force $(version)
20+
21+
.PHONY: compile-server
22+
compile-server:
23+
GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o deploy/hacktiv-assignment-2 main.go
24+
25+
.PHONY: docker-build-server
26+
docker-build-server:
27+
docker build --no-cache -t hacktiv-assignment-2:latest -f Dockerfile .
28+
29+
.PHONY: run
30+
run:
31+
docker-compose up
32+
33+
.PHONY: compile-build-run
34+
compile-build-run: compile-server docker-build-server run

assignment-2/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Assignment 2 Hacktiv
2+
Simple login-logout page with gorilla session stored on postgres db
3+
4+
# Dependencies
5+
- [Docker](https://docs.docker.com/engine/install/ubuntu/)
6+
- [Docker-compose](https://docs.docker.com/compose/install/)
7+
8+
# How To Run
9+
1. Compile server to generate executable file `make compile-server`
10+
2. Build docker image `make docker-build-server`
11+
3. Run docker image `make run`
12+
4. Alternatively, you can use this shortcut to automatically run 3 above steps `make compile-build-run`
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
BEGIN;
2+
3+
DROP TABLE users;
4+
5+
COMMIT;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
BEGIN;
2+
3+
CREATE TABLE users (
4+
id serial not null,
5+
username varchar(50) unique not null,
6+
first_name varchar(200) not null,
7+
last_name varchar(200) not null,
8+
password varchar(120) not null
9+
);
10+
11+
COMMIT;
11.4 MB
Binary file not shown.

assignment-2/docker-compose.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version: '3'
2+
3+
services:
4+
postgres:
5+
image: postgres:13.2-alpine
6+
environment:
7+
- POSTGRES_USER=postgresuser
8+
- POSTGRES_PASSWORD=postgrespassword
9+
- POSTGRES_DB=postgres
10+
ports:
11+
- 5432:5432
12+
# server:
13+
# image: hacktiv-assignment-2:latest
14+
# ports:
15+
# - 8080:8080
16+
# depends_on:
17+
# - postgres

assignment-2/driver/pgx.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package driver
2+
3+
import (
4+
"context"
5+
6+
"github.com/jackc/pgx/v4"
7+
)
8+
9+
func NewPostgresConn(ctx context.Context) (*pgx.Conn, error) {
10+
return pgx.Connect(ctx, POSTGRES_URL)
11+
}

assignment-2/driver/renderer.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package driver
2+
3+
import (
4+
"html/template"
5+
"io"
6+
7+
"github.com/labstack/echo"
8+
)
9+
10+
type Renderer struct {
11+
template *template.Template
12+
debug bool
13+
location string
14+
}
15+
16+
func NewRenderer(location string, debug bool) *Renderer {
17+
tpl := new(Renderer)
18+
tpl.location = location
19+
tpl.debug = debug
20+
21+
tpl.ReloadTemplates()
22+
23+
return tpl
24+
}
25+
26+
func (t *Renderer) ReloadTemplates() {
27+
t.template = template.Must(template.ParseGlob(t.location))
28+
}
29+
30+
func (t *Renderer) Render(
31+
w io.Writer,
32+
name string,
33+
data interface{},
34+
c echo.Context,
35+
) error {
36+
if t.debug {
37+
t.ReloadTemplates()
38+
}
39+
40+
return t.template.ExecuteTemplate(w, name, data)
41+
}

assignment-2/driver/session_store.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package driver
2+
3+
import (
4+
"log"
5+
6+
"github.com/antonlindstrom/pgstore"
7+
"github.com/gorilla/sessions"
8+
)
9+
10+
const (
11+
SESSION_ID = "test-id"
12+
POSTGRES_URL = "postgres://postgresuser:postgrespassword@127.0.0.1:5432/postgres?sslmode=disable"
13+
)
14+
15+
var (
16+
SESSION_AUTH_KEY = []byte("my-auth-key-very-secret")
17+
SESSION_ENCRYPTION_KEY = []byte("my-encryption-key-very-secret123")
18+
)
19+
20+
func NewPostgresStore() *pgstore.PGStore {
21+
store, err := pgstore.NewPGStore(POSTGRES_URL, SESSION_AUTH_KEY, SESSION_ENCRYPTION_KEY)
22+
if err != nil {
23+
log.Fatalln("ERROR", err)
24+
}
25+
26+
return store
27+
}
28+
29+
func NewCookieStore() *sessions.CookieStore {
30+
store := sessions.NewCookieStore(SESSION_AUTH_KEY, SESSION_ENCRYPTION_KEY)
31+
store.Options.Path = "/"
32+
store.Options.MaxAge = 86400 * 7
33+
store.Options.HttpOnly = true
34+
35+
return store
36+
}

assignment-2/entity/user.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package entity
2+
3+
type User struct {
4+
ID int `json:"id"`
5+
Username string `json:"username"`
6+
FirstName string `json:"first_name"`
7+
LastName string `json:"last_name"`
8+
Password string `json:"password"`
9+
}

assignment-2/handler/common.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package handler
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/antonlindstrom/pgstore"
7+
"github.com/ibrahimker/golang-intermediate/assignment-2/driver"
8+
"github.com/labstack/echo"
9+
)
10+
11+
func storeSessionHelper(c echo.Context, pgs *pgstore.PGStore, username string) error {
12+
session, err := pgs.Get(c.Request(), driver.SESSION_ID)
13+
if err != nil {
14+
return c.String(http.StatusInternalServerError, err.Error())
15+
}
16+
session.Values["username"] = username
17+
if err = session.Save(c.Request(), c.Response()); err != nil {
18+
return c.String(http.StatusInternalServerError, err.Error())
19+
}
20+
return nil
21+
}

assignment-2/handler/#_handler.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package handler
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/antonlindstrom/pgstore"
7+
"github.com/gorilla/sessions"
8+
"github.com/ibrahimker/golang-intermediate/assignment-2/driver"
9+
"github.com/ibrahimker/golang-intermediate/assignment-2/entity"
10+
"github.com/ibrahimker/golang-intermediate/assignment-2/repository"
11+
"github.com/labstack/echo"
12+
log "github.com/sirupsen/logrus"
13+
)
14+
15+
type HTMLTemplate struct {
16+
User entity.User
17+
Err string
18+
}
19+
20+
type Login struct {
21+
cs *sessions.CookieStore
22+
pgs *pgstore.PGStore
23+
ur *repository.User
24+
}
25+
26+
func NewLoginHandler(cs *sessions.CookieStore, pgs *pgstore.PGStore, ur *repository.User) *Login {
27+
return &Login{
28+
cs: cs,
29+
pgs: pgs,
30+
ur: ur,
31+
}
32+
}
33+
34+
func (l *Login) LoginHandler(c echo.Context) error {
35+
log.Info("Start Login Handler")
36+
// authenticate in db
37+
user, err := l.ur.GetByUsernamePassword(c.Request().Context(), c.FormValue("username"), c.FormValue("password"))
38+
if err != nil {
39+
log.Warn(err)
40+
return c.Render(http.StatusOK, "login.html", HTMLTemplate{
41+
User: entity.User{},
42+
Err: "Error when login",
43+
})
44+
}
45+
log.Info("Successfully authenticate user", user)
46+
47+
// store username in session
48+
if err = storeSessionHelper(c, l.pgs, user.Username); err != nil {
49+
log.Warn("error when store session")
50+
return c.Render(http.StatusOK, "login.html", HTMLTemplate{
51+
User: entity.User{},
52+
Err: "Error when store session",
53+
})
54+
}
55+
56+
return c.Redirect(http.StatusTemporaryRedirect, "/home")
57+
}
58+
59+
func (l *Login) HomeHandler(c echo.Context) error {
60+
log.Info("Start Home Handler")
61+
62+
session, err := l.pgs.Get(c.Request(), driver.SESSION_ID)
63+
if err != nil {
64+
log.WithError(err).Warn("error when retrieve session")
65+
return c.Redirect(http.StatusTemporaryRedirect, "/#")
66+
}
67+
68+
if len(session.Values) == 0 {
69+
log.Info("no session values available")
70+
return c.Redirect(http.StatusTemporaryRedirect, "/#")
71+
}
72+
73+
log.Info("Session exists, render html")
74+
return c.Render(http.StatusOK, "home.html", HTMLTemplate{
75+
User: entity.User{Username: session.Values["username"].(string)},
76+
Err: "",
77+
})
78+
}
79+
80+
func (l *Login) LogoutHandler(c echo.Context) error {
81+
log.Info("Start Logout Handler")
82+
session, _ := l.pgs.Get(c.Request(), driver.SESSION_ID)
83+
session.Options.MaxAge = -1
84+
session.Save(c.Request(), c.Response())
85+
86+
return c.Redirect(http.StatusTemporaryRedirect, "/home")
87+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package handler
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/antonlindstrom/pgstore"
7+
"github.com/gorilla/sessions"
8+
"github.com/ibrahimker/golang-intermediate/assignment-2/entity"
9+
"github.com/ibrahimker/golang-intermediate/assignment-2/repository"
10+
"github.com/labstack/echo"
11+
log "github.com/sirupsen/logrus"
12+
)
13+
14+
type Register struct {
15+
cs *sessions.CookieStore
16+
pgs *pgstore.PGStore
17+
ur *repository.User
18+
}
19+
20+
func NewRegisterHandler(cs *sessions.CookieStore, pgs *pgstore.PGStore, ur *repository.User) *Register {
21+
return &Register{
22+
cs: cs,
23+
pgs: pgs,
24+
ur: ur,
25+
}
26+
}
27+
28+
func (r *Register) RegisterHandler(c echo.Context) error {
29+
data := "Hello from /html"
30+
u := &entity.User{
31+
Username: c.FormValue("username"),
32+
FirstName: c.FormValue("firstname"),
33+
LastName: c.FormValue("lastname"),
34+
Password: c.FormValue("password"),
35+
}
36+
log.Info("user", u)
37+
if u.Username == "" || u.Password == "" {
38+
log.Warn("username password cannot be empty")
39+
return c.Redirect(http.StatusTemporaryRedirect, "/home")
40+
}
41+
if err := r.ur.Insert(c.Request().Context(), u); err != nil {
42+
return c.Render(http.StatusOK, "login.html", data)
43+
}
44+
45+
// store username in session
46+
if err := storeSessionHelper(c, r.pgs, u.Username); err != nil {
47+
return c.String(http.StatusInternalServerError, err.Error())
48+
}
49+
50+
return c.Redirect(http.StatusTemporaryRedirect, "/home")
51+
}

0 commit comments

Comments
 (0)