Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
thisisommore authored Feb 25, 2023
0 parents commit 2762ae8
Show file tree
Hide file tree
Showing 32 changed files with 948 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

tmp
.env
25 changes: 25 additions & 0 deletions .gitpod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
image:
file: .gitpod/Dockerfile
tasks:
- name: live
init: |
go get
command: |
docker run --name="backend" --rm -d -p 5432:5432 \
-e POSTGRES_PASSWORD=backend \
-e POSTGRES_USER=backend \
-e POSTGRES_DB=backend \
postgres -c log_statement=all
sleep 12
gp sync-done db
cp sample.env .env
sed -i '/=$/d' .env
air
- name: postgres shell
command: |
gp sync-await db
docker exec -it backend psql -U backend
vscode:
extensions:
- golang.go
4 changes: 4 additions & 0 deletions .gitpod/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM gitpod/workspace-full

USER gitpod
RUN sudo curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sudo sh -s -- -b $(go env GOPATH)/bin
18 changes: 18 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Package api provide support to create /api group
package api

import (
"template-app/api/auth"
"template-app/api/publish"

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

// ApplyRoutes applies the /api group and v1 routes to given gin Engine
func ApplyRoutes(r *gin.Engine) {
api := r.Group("/api")
{
auth.ApplyRoutes(api)
publish.ApplyRoutes(api)
}
}
75 changes: 75 additions & 0 deletions api/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Package auth provides Api to authenticate user by validating his supabase jwt and giving him paseto
package auth

import (
"errors"
"net/http"
"strings"
"template-app/models/user/usermethods"
"template-app/pkg/paseto"
"template-app/pkg/supabase"

"github.com/TheLazarusNetwork/go-helpers/httpo"
"github.com/TheLazarusNetwork/go-helpers/logo"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)

// ApplyRoutes applies router to gin Router
func ApplyRoutes(r *gin.RouterGroup) {
g := r.Group("/supabase")
{
g.POST("", supabaseAuth)
}
}

func supabaseAuth(c *gin.Context) {
var body AuthRequest
err := c.BindJSON(&body)
if err != nil {
httpo.NewErrorResponse(http.StatusBadRequest, "failed to validate body").
Send(c, http.StatusBadRequest)
return
}
// If unexpected error
sbUser, err := supabase.GetSBUser(body.SupabaseToken)
if err != nil {
logo.Errorf("failed to get supabase user: %s", err)
errMsg := "failed to verify and get paseto"
if strings.Contains(err.Error(), "invalid JWT: unable to parse or verify signature, token is expired by ") {
errMsg += ": supabase JWT token expired"
}
httpo.NewErrorResponse(500, errMsg).Send(c, 500)
return
}

_, err = usermethods.Get(sbUser.Email)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
err = usermethods.Add(sbUser.Email)
if err != nil {
logo.Errorf("failed to add user into database: %s", err)
httpo.NewErrorResponse(500, "failed to verify and get paseto").Send(c, 500)
return
}
} else {
logo.Errorf("failed to check if user exist in database: %s", err)
httpo.NewErrorResponse(500, "failed to verify and get paseto").Send(c, 500)
return
}
}

pasetoToken, err := paseto.GetPasetoForUser(sbUser.Email)
if err != nil {
logo.Errorf("failed to get paseto: %s", err)

httpo.NewErrorResponse(500, "failed to verify and get paseto").Send(c, 500)
return
}

payload := AuthPayload{
Token: pasetoToken,
}
httpo.NewSuccessResponseP(http.StatusOK, "token generated successfully", payload).
SendD(c)
}
9 changes: 9 additions & 0 deletions api/auth/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package auth

type AuthRequest struct {
SupabaseToken string `json:"supabaseToken"`
}

type AuthPayload struct {
Token string `json:"token"`
}
65 changes: 65 additions & 0 deletions api/middleware/auth/paseto/paseto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Package pasetomiddleware defines middleware to verify PASETO token with required claims
package pasetomiddleware

import (
"errors"
"fmt"
"net/http"
"template-app/pkg/paseto"

"github.com/TheLazarusNetwork/go-helpers/httpo"
"github.com/TheLazarusNetwork/go-helpers/logo"
"github.com/vk-rv/pvx"
"gorm.io/gorm"

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

var EmailIdInContext = "EmailId"
var (
ErrAuthHeaderMissing = errors.New("authorization header is required")
)

func PASETO(c *gin.Context) {
var headers GenericAuthHeaders
err := c.BindHeader(&headers)
if err != nil {
err = fmt.Errorf("failed to bind header, %s", err)
logValidationFailed(headers.Authorization, err)
c.AbortWithStatus(http.StatusInternalServerError)
return
}
if headers.Authorization == "" {
logValidationFailed(headers.Authorization, ErrAuthHeaderMissing)
httpo.NewErrorResponse(httpo.AuthHeaderMissing, ErrAuthHeaderMissing.Error()).Send(c, http.StatusBadRequest)
c.Abort()
return
}
emailId, err := paseto.VerifyPaseto(headers.Authorization)
if err != nil {
var validationErr *pvx.ValidationError
if errors.As(err, &validationErr) {
if validationErr.HasExpiredErr() {
err = fmt.Errorf("failed to scan claims for paseto token, %s", err)
logValidationFailed(headers.Authorization, err)
httpo.NewErrorResponse(httpo.TokenExpired, "token expired").Send(c, http.StatusUnauthorized)
c.Abort()
return
}
}
if errors.Is(err, gorm.ErrRecordNotFound) {
c.AbortWithStatus(http.StatusUnauthorized)
return
}

err = fmt.Errorf("failed to scan claims for paseto token, %s", err)
logValidationFailed(headers.Authorization, err)
c.AbortWithStatus(http.StatusUnauthorized)
return
}
c.Set(EmailIdInContext, emailId)
}

func logValidationFailed(token string, err error) {
logo.Warnf("validation failed with token %v and error: %v", token, err)
}
5 changes: 5 additions & 0 deletions api/middleware/auth/paseto/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package pasetomiddleware

type GenericAuthHeaders struct {
Authorization string
}
37 changes: 37 additions & 0 deletions api/publish/publish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package publish

import (
"net/http"
"template-app/models/sitemodel"
"template-app/models/sitemodel/site_model_methods"

"github.com/TheLazarusNetwork/go-helpers/httpo"
"github.com/gin-gonic/gin"
)

// ApplyRoutes applies router to gin Router
func ApplyRoutes(r *gin.RouterGroup) {
g := r.Group("/publish")
{
g.POST("", publish)
}
}

func publish(c *gin.Context) {
var body PublishRequest
err := c.BindJSON(&body)
if err != nil {
httpo.NewErrorResponse(http.StatusBadRequest, err.Error()).
Send(c, http.StatusBadRequest)
return
}

if err = site_model_methods.Add(&sitemodel.Site{Name: body.Name}); err != nil {
httpo.NewErrorResponse(http.StatusInternalServerError, err.Error()).
SendD(c)
return
}
httpo.NewSuccessResponse(http.StatusOK, "site deployed successfully").
SendD(c)

}
34 changes: 34 additions & 0 deletions api/publish/publish_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package publish

import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"template-app/app/stage/appinit"

"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)

func Test_GetServices(t *testing.T) {
appinit.Init()

apiUrl := "/api/v1.0/orgadmin/org-service"
reqBody := PublishRequest{
Name: "maxprerdomf78maaarrrrx",
HtmlString: "<p>Hello Pro</p>",
Components: []string{"3"},
}
jsonBytes, err := json.Marshal(reqBody)
require.Nil(t, err)

rr := httptest.NewRecorder()
req := httptest.NewRequest("POST", apiUrl, bytes.NewBuffer(jsonBytes))
c, _ := gin.CreateTestContext(rr)
c.Request = req
publish(c)
require.Equal(t, http.StatusOK, rr.Result().StatusCode, rr.Body.String())
}
7 changes: 7 additions & 0 deletions api/publish/type.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package publish

type PublishRequest struct {
Name string `json:"name" binding:"required"`
HtmlString string `json:"htmlString"`
Components []string `json:"components"`
}
16 changes: 16 additions & 0 deletions app/stage/appinit/appinit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Package appinit provides method to Init all stages of app
package appinit

import (
"template-app/app/stage/appinit/dbconinit"
"template-app/app/stage/appinit/dbmigrate"
"template-app/app/stage/appinit/envinit"
"template-app/app/stage/appinit/logoinit"
)

func Init() {
envinit.Init()
logoinit.Init()
dbconinit.Init()
dbmigrate.Migrate()
}
48 changes: 48 additions & 0 deletions app/stage/appinit/dbconinit/dbconinit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Package dbconinit provides method to Init database
package dbconinit

import (
"fmt"

"template-app/pkg/envconfig"
"template-app/pkg/store"

"github.com/TheLazarusNetwork/go-helpers/logo"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

func Init() {
var (
host = envconfig.EnvVars.DB_HOST
username = envconfig.EnvVars.DB_USERNAME
password = envconfig.EnvVars.DB_PASSWORD
dbname = envconfig.EnvVars.DB_NAME
port = envconfig.EnvVars.DB_PORT
)

dns := fmt.Sprintf("host=%s user=%s password=%s dbname=%s sslmode=disable port=%d",
host, username, password, dbname, port)

var err error
db, err := gorm.Open(postgres.New(postgres.Config{
DSN: dns,
}))
if err != nil {
logo.Fatal("failed to connect database", err)
}

// Store database in global store
store.DB = db

// Get underlying sql database to ping it
sqlDb, err := db.DB()
if err != nil {
logo.Fatal("failed to ping database", err)
}

// If ping fails then log error and exit
if err = sqlDb.Ping(); err != nil {
logo.Fatal("failed to ping database", err)
}
}
19 changes: 19 additions & 0 deletions app/stage/appinit/dbmigrate/dbmigrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Package dbmigrate provides method to migrate models into database
package dbmigrate

import (
"template-app/models/sitemodel"
"template-app/pkg/store"

"github.com/TheLazarusNetwork/go-helpers/logo"
)

func Migrate() {
db := store.DB
err := db.AutoMigrate(
&sitemodel.Site{},
)
if err != nil {
logo.Fatalf("failed to migrate models into database: %s", err)
}
}
Loading

0 comments on commit 2762ae8

Please sign in to comment.