Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feat/middleware-logger
Browse files Browse the repository at this point in the history
  • Loading branch information
martinyonatann committed Jan 4, 2024
2 parents 96fe42e + 1db53f5 commit b26d1f1
Show file tree
Hide file tree
Showing 38 changed files with 647 additions and 206 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/build_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:

test:
runs-on: ubuntu-latest
env:
ENV: test

steps:
- name: Checkout repository
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
Expand Down
12 changes: 0 additions & 12 deletions .idea/dataSources.xml

This file was deleted.

2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ LABEL description="OnePixel is a simple, self-hosted, one pixel web analytics to


# Copy binary and config files from /build to root folder of scratch container.
# COPY --from=builder ["/build/onepixel", "/build/.env", "/"]
COPY --from=builder ["/build/*.env", "/"]
COPY --from=builder ["/build/bin/onepixel", "/"]

# Command to run when starting the container.
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ build_all: docs

test:
@echo "Running tests..."
@@GOOS=$(OS) GOARCH=$(ARCH) go test -race -covermode=atomic -v -coverpkg=./src/... ./tests/... ./src/...
@@GOOS=$(OS) GOARCH=$(ARCH) ENV=test go test -race -covermode=atomic -v -coverpkg=./src/... ./tests/... ./src/...

clean:
@echo "Cleaning..."
Expand All @@ -52,4 +52,4 @@ clean:

run: build
@echo "Running..."
@./bin/$(BINARY_NAME)-$(OS)-$(ARCH)
@./bin/$(BINARY_NAME)
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# onepixel (1px.li)

Backend code for the 1px.li URL shortener service.
"onepixel" is an API-first URL shortner.

#### Why this name?
A URL shortner should have a short domain name, possibly 2 or 3 letter in length.
"1 pixel" is the smallest unit of a screen.
So `1px.li` stands for `one pixel links` - i.e. smallest possible links :)


#### Status Badges
[![codecov](https://codecov.io/gh/championswimmer/onepixel_backend/graph/badge.svg?token=DL3DR6CS40)](https://codecov.io/gh/championswimmer/onepixel_backend)
Expand All @@ -9,7 +15,7 @@ Backend code for the 1px.li URL shortener service.
## Test Instance
The latest version of the code is automatically deployed via [Railway](https://railway.app)
to
<big><https://api.onepixel.link></big>
<big><https://onepixel.link></big>


You can deploy your own instance too
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/jackc/puddle/v2 v2.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/klauspost/compress v1.17.4 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/klauspost/compress v1.16.3/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
Expand Down
10 changes: 10 additions & 0 deletions onepixel.local.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# The local env is also used for default values
ADMIN_SITE_HOST=127.0.0.1
MAIN_SITE_HOST=0.0.0.0
PORT=3000
DB_LOGGING=error
DB_DIALECT=postgres
DATABASE_URL="host=postgres user=postgres password=postgres dbname=onepixel port=5432 sslmode=disable TimeZone=UTC"
ADMIN_API_KEY="8DC4FCD4-DD71-4C18-B9C0-C38EF6790815"
JWT_SIGNING_KEY="ECkLlR74HWIZTG4GAFsmBFoqO7Nr30Bc"
JWT_DURATION_DAYS=90
2 changes: 2 additions & 0 deletions onepixel.production.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ADMIN_SITE_HOST=onepixel.link
MAIN_SITE_HOST=1px.li
3 changes: 3 additions & 0 deletions onepixel.test.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
DB_LOGGING=info
DB_DIALECT=sqlite
DATABASE_URL="file::memory:?cache=shared"
33 changes: 33 additions & 0 deletions src/config/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package config

import (
"github.com/joho/godotenv"
"github.com/samber/lo"
"onepixel_backend/src/utils/applogger"
"os"
"path"
"runtime"
)

func init() {
if os.Getenv("ENV") == "test" {
// for tests, chdir to the project root
_, filename, _, _ := runtime.Caller(0)
dir := path.Join(path.Dir(filename), "../..") // change to suit test file location
lo.Must0(os.Chdir(dir))
if err := godotenv.Load("onepixel.test.env"); err != nil {
applogger.Error(err)
}
}

if os.Getenv("ENV") == "production" || os.Getenv("RAILWAY_ENVIRONMENT") == "production" {
if err := godotenv.Load("onepixel.production.env"); err != nil {
applogger.Error(err)
}
}

// Use defaults from local.env for all missing vars
if err := godotenv.Load("onepixel.local.env"); err != nil {
applogger.Error(err)
}
}
43 changes: 43 additions & 0 deletions src/config/vars.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package config

import (
"github.com/samber/lo"
"os"
"strconv"
)

var Env string

var DBLogging string
var DBDialect string
var DBUrl string

var Port string
var MainHost string
var AdminHost string

var AdminApiKey string

var JwtSigningKey string
var JwtDurationDays int

// should run after env.go#init as this `vars` is alphabetically after `env`
func init() {
Env, _ = lo.Coalesce(
os.Getenv("RAILWAY_ENVIRONMENT"),
os.Getenv("ENV"),
"local",
)
DBLogging = os.Getenv("DB_LOGGING")
DBDialect = os.Getenv("DB_DIALECT")
DBUrl, _ = lo.Coalesce(
os.Getenv("DATABASE_PRIVATE_URL"),
os.Getenv("DATABASE_URL"),
)
Port = os.Getenv("PORT")
MainHost = os.Getenv("MAIN_SITE_HOST")
AdminHost = os.Getenv("ADMIN_SITE_HOST")
AdminApiKey = os.Getenv("ADMIN_API_KEY")
JwtSigningKey = os.Getenv("JWT_SIGNING_KEY")
JwtDurationDays, _ = strconv.Atoi(os.Getenv("JWT_DURATION_DAYS"))
}
28 changes: 25 additions & 3 deletions src/controllers/urls.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ import (
"gorm.io/gorm"
"math"
"math/rand"
"onepixel_backend/src/models"
"onepixel_backend/src/db/models"
"onepixel_backend/src/utils"
"onepixel_backend/src/utils/applogger"
)

// the current max length of the short url
// when we generate URLs randomly
// can be increase future if we run out of this space
// - 6: 64^6 = 68,719,476,736
const _currentMaxUrlLength = 6
const _defaultUrlGroupId = 0

var _randMax = int(math.Pow(64, _currentMaxUrlLength))

Expand Down Expand Up @@ -58,13 +60,33 @@ func CreateUrlsController(db *gorm.DB) *UrlsController {
}
}

func (c *UrlsController) InitDefaultUrlGroup() {
defaultUrlGroup := &models.UrlGroup{
ID: _defaultUrlGroupId,
Name: lo.Must(utils.Radix64Encode(_defaultUrlGroupId)), // "0",
CreatorID: 0,
}

res := c.db.Save([]models.UrlGroup{*(defaultUrlGroup)})
if res.Error != nil {
if errors.Is(res.Error, gorm.ErrDuplicatedKey) {
applogger.Warn("Default url group already exists")
return
}
applogger.Error("Failed to create default user")
applogger.Panic(res.Error)
} else {
applogger.Info("Default url group created")
}
}

func (c *UrlsController) CreateSpecificShortUrl(shortUrl string, longUrl string, userId uint64) (url *models.Url, err error) {
url = &models.Url{
ID: lo.Must(utils.Radix64Decode(shortUrl)),
ShortURL: shortUrl,
LongURL: longUrl,
CreatorID: userId,
UrlGroupID: nil,
UrlGroupID: 0,
}

res := c.db.Create(url)
Expand All @@ -88,7 +110,7 @@ func (c *UrlsController) CreateRandomShortUrl(longUrl string, userId uint64) (ur
ShortURL: newShortUrl,
LongURL: longUrl,
CreatorID: userId,
UrlGroupID: nil,
UrlGroupID: 0,
}
res := c.db.Create(url)
if res.Error != nil {
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/urls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"testing"
)

var urlsController = CreateUrlsController(lo.Must(db.GetTestDB()))
var urlsController = CreateUrlsController(lo.Must(db.GetDB()))

func TestUrlsController_CreateSpecificShortUrl(t *testing.T) {
user, _, _ := userController.Create("[email protected]", "123456")
Expand Down
27 changes: 26 additions & 1 deletion src/controllers/users.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package controllers

import (
"onepixel_backend/src/models"
"errors"
"github.com/google/uuid"
"onepixel_backend/src/db/models"
"onepixel_backend/src/security"
"onepixel_backend/src/utils/applogger"

"gorm.io/gorm"
)
Expand Down Expand Up @@ -32,6 +35,28 @@ func CreateUsersController(db *gorm.DB) *UsersController {
}
}

func (c *UsersController) InitDefaultUser() {
defaultUser := &models.User{
Email: "[email protected]",
Password: security.HashPassword(uuid.New().String()),
ID: 0,
}

// this doesn't really work, passing ID=0 to gorm is borked
// the code here is just for documentation purposes
res := c.db.Save([]models.User{*(defaultUser)})
if res.Error != nil {
if errors.Is(res.Error, gorm.ErrDuplicatedKey) {
applogger.Warn("Default user already exists")
return
}
applogger.Error("Failed to create default user")
applogger.Panic(res.Error)
} else {
applogger.Info("Default user created")
}
}

// Create new user
func (c *UsersController) Create(email string, password string) (user *models.User, token string, err error) {
user = &models.User{
Expand Down
2 changes: 1 addition & 1 deletion src/controllers/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"testing"
)

var userController = CreateUsersController(lo.Must(db.GetTestDB()))
var userController = CreateUsersController(lo.Must(db.GetDB()))

func TestUsersController_Create(t *testing.T) {
user, token, err := userController.Create("[email protected]", "123456")
Expand Down
52 changes: 24 additions & 28 deletions src/db/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,41 @@ import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"onepixel_backend/src/models"
"onepixel_backend/src/config"
"onepixel_backend/src/db/models"
"onepixel_backend/src/utils/applogger"
"os"
)

var dbSingleton *gorm.DB

func GetTestDB() (*gorm.DB, error) {
applogger.Warn("Using test database")
return getOrInitDB(true)
}

func GetProdDB() (*gorm.DB, error) {
applogger.Warn("Using production database")
return getOrInitDB(false)
}

func getOrInitDB(test bool) (*gorm.DB, error) {
func GetDB() (*gorm.DB, error) {

// TODO: thread safety?
if dbSingleton != nil {
return dbSingleton, nil
}
// TODO: move db config to external YAML config
dsn, _ := lo.Coalesce(
os.Getenv("DATABASE_PRIVATE_URL"),
os.Getenv("DATABASE_URL"),
"host=postgres user=postgres password=postgres dbname=onepixel port=5432 sslmode=disable TimeZone=UTC",
)

config := &gorm.Config{

dbConfig := &gorm.Config{
TranslateError: true,
}

if test {
config.Logger = logger.Default.LogMode(logger.Info)
dbSingleton = lo.Must(gorm.Open(sqlite.Open("file::memory:?cache=shared"), config))
} else {
config.Logger = logger.Default.LogMode(logger.Error)
dbSingleton = lo.Must(gorm.Open(postgres.Open(dsn), config))
var dbLogLevel logger.LogLevel = lo.Switch[string, logger.LogLevel](config.DBLogging).
Case("info", logger.Info).
Case("warn", logger.Warn).
Case("error", logger.Error).
Default(logger.Error)

dbConfig.Logger = logger.Default.LogMode(dbLogLevel)
switch config.DBDialect {
case "sqlite":
applogger.Warn("Using sqlite db")
dbSingleton = lo.Must(gorm.Open(sqlite.Open(config.DBUrl), dbConfig))
break
case "postgres":
applogger.Warn("Using postgres db")
dbSingleton = lo.Must(gorm.Open(postgres.Open(config.DBUrl), dbConfig))
break
default:
panic("Database config incorrect")
}

lo.Must0(dbSingleton.AutoMigrate(&models.User{}))
Expand Down
Loading

0 comments on commit b26d1f1

Please sign in to comment.