Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/compile-check.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ jobs:

- name: Compile
working-directory: backend/
run: go build ./cmd/sourceflame-server/
run: go build -tags="sqlite3" ./cmd/sourceflame-server/
2 changes: 1 addition & 1 deletion .github/workflows/go-tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ jobs:

- name: Run tests
working-directory: backend/
run: go test ./... -v
run: go test ./... -v -tags="sqlite3"
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ frontend for it, the frontend can be found here [Work in progress]()
> [!NOTE]
> A new dir called data/ will appear in the project containing the data created by the project

> [!NOTE]
> Currently we sship support for postgres and sqlite3 out of the box,
out of these sqlite3 is the default option when running with docker compose,
while for any use case involveing docker copmose, sqlite3 is recommended,
if you wish to switch to postgres have a look at docker-compose.yaml

- Clone and cd in the repo: `git clone https://github.com/Ankumeah/SourceFlameBackend && cd SourceFlameBackend`
- Change the secrets in `./env.d.example/` to your likeing (**VERY IMPORTANT**)
- Start docker compose: `docker compose up -d`

### With kustomize

> [!NOTE]
> This dir does not include the yaml files needed to deploy the required
storage or the PVC used to connect the storage to the app. It is the user's
responsibility to setup and connect the storage. The app assumes the
presence of two PVCs by the name of `git-storage` and `database-storage`
![Read here](https://github.com/Ankumeah/SourceFlameBackend/blob/master/k8s/base/README.md)

- Clone and cd in the repo: `git clone https://github.com/Ankumeah/SourceFlameBackend && cd SourceFlameBackend`
- Change the secrets in `./k8s/base/secrets.example/` to your liking (**VERY IMPORTANT**)
- Move the files to the actual dir: `mv ./k8s/base/secrets.example/ ./k8s/base/secrets/`
- Add PVCs for your storage with names `git-storage` and `database-storage`
- Take a look at the yaml files and patch any values you want (Optional but recommended)
- Apply the files: `kubectl apply -k ./k8s/base/` (Adjust accordingly if patched)
## How to use

![openapi.yaml](https://github.com/Ankumeah/SourceFlameBackend/blob/master/openapi.yaml)
![Quick cheatsheet](https://github.com/Ankumeah/SourceFlameBackend/blob/master/doc.md)
13 changes: 9 additions & 4 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,23 @@ FROM golang:alpine AS builder

WORKDIR /opt/backend/

RUN apk add gcc musl-dev --no-cache

COPY go.mod go.sum ./
RUN go mod download

ARG db_type=sqlite3

COPY . .
#RUN go build -ldflags="-s -w" -o ./bin/sourceflame-server ./cmd/sourceflame-server/
RUN --mount=type=cache,target=/root/.cache/go-build go build -ldflags="-s -w" -o ./bin/sourceflame-server ./cmd/sourceflame-server/
RUN --mount=type=cache,target=/root/.cache/go-build go build -ldflags="-s -w" -tags=${db_type} -o ./bin/sourceflame-server ./cmd/sourceflame-server/

FROM alpine

RUN apk add git --no-cache

WORKDIR /opt/backend/

ENV GIN_MODE=release
COPY --from=builder /opt/backend/bin/sourceflame-server /opt/backend/sourceflame-server
COPY --from=builder /opt/backend/bin/sourceflame-server .

ENTRYPOINT [ "/opt/backend/sourceflame-server" ]
ENTRYPOINT [ "./sourceflame-server" ]
28 changes: 28 additions & 0 deletions backend/cmd/sourceflame-server/connect_database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"github.com/Ankumeah/SourceFlameBackend/internal/database"
a "github.com/Ankumeah/SourceFlameBackend/internal/app"

"log"
)

func connect_database(app *a.App) {
log.Println("Connecting to database")

config := database.New_Sql_Config(
app.Settings.DATABASE_MAX_CONNS,
app.Settings.DATABASE_MAX_IDLE_CONNS,
app.Settings.DATABASE_MAX_LIFETIME,
app.Settings.DATABASE_MAX_IDLE_TIME,
)

db, err := database.Get_DB_Connection(Ctx, app.Settings.DATABASE_URL, config)
if err != nil { log.Fatalf("Error while connecting to database: %v\n", err.Error()) }

app.User_db = database.User_Sql_Driver(db)
app.Git_db = database.Git_Sql_Driver(db)
app.PAT_db = database.PAT_Sql_Driver(db)

log.Println("Connected to database")
}
42 changes: 42 additions & 0 deletions backend/cmd/sourceflame-server/connect_session_store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package main

import (
"github.com/Ankumeah/SourceFlameBackend/internal/session_store"
a "github.com/Ankumeah/SourceFlameBackend/internal/app"

"log"
)

func connect_session_store(app *a.App) {
log.Println("Connecting to session store")

sessions_config := session_store.Universal_Redis_Config {
Username: app.Settings.SESSION_STORE_SESSIONS_USERNAME,
Password: app.Settings.SESSION_STORE_SESSIONS_PASSWORD,
Hostname: app.Settings.SESSION_STORE_HOSTNAME,
Port: app.Settings.SESSION_STORE_PORT,
}
var client session_store.Redis_client
var err error

switch app.Settings.SESSION_STORE_TYPE {
case "redis_standalone":
client, err = session_store.Get_Redis_Client(Ctx, sessions_config)
case "redis_cluster":
client, err = session_store.Get_Redis_Cluster_Client(Ctx, sessions_config)
default:
log.Fatalf("Unsupported session store type: %v\n", app.Settings.SESSION_STORE_TYPE)
}
if err != nil {
log.Panicf("Error while connecting to session store: %v\n", err.Error())
}

app.Store = session_store.Get_Sessions_Uinversal_Redis_Driver(
client,
app.Settings.TOKEN_TIMEOUT,
app.Settings.TOKEN_LIMIT,
app.Settings.TOKEN_LENGTH,
app.Settings.TOKEN_NAMESPACE,
)
log.Println("Connected to session store")
}
82 changes: 82 additions & 0 deletions backend/cmd/sourceflame-server/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package main

import (
a "github.com/Ankumeah/SourceFlameBackend/internal/app"

"os"
"time"
"log"
"strconv"
)

var env_vars = map[string]string {
"API_VERSION": "",
"BACKEND_PORT": "",

"SESSION_STORE_SESSIONS_USERNAME": "",
"SESSION_STORE_SESSIONS_PASSWORD": "",
"SESSION_STORE_HOSTNAME": "",
"SESSION_STORE_PORT": "",
"SESSION_STORE_TYPE": "",

"DATABASE_URL": "",
"DATABASE_MAX_CONNS": "",
"DATABASE_MAX_IDLE_CONNS": "",
"DATABASE_MAX_LIFETIME": "",
"DATABASE_MAX_IDLE_TIME": "",

"JWT_KEY": "",
"JWT_LIFESPAN": "",

"TOKEN_TIMEOUT": "",
"TOKEN_LIMIT": "",
}

func load_env(s *a.Settings) {
log.Println("Loading env")

for env := range env_vars {
_env, ok := os.LookupEnv(env)
if !ok {
log.Fatalf("Unset env var: %v\n", env)
}

env_vars[env] = _env
}

set_settings(s)

log.Println("Loaded env")
}

func set_settings(s *a.Settings) {
s.API_VERSION = env_vars["API_VERSION"]
s.BACKEND_PORT = env_vars["BACKEND_PORT"]

s.SESSION_STORE_SESSIONS_USERNAME = env_vars["SESSION_STORE_SESSIONS_USERNAME"]
s.SESSION_STORE_SESSIONS_PASSWORD = env_vars["SESSION_STORE_SESSIONS_PASSWORD"]
s.SESSION_STORE_HOSTNAME = env_vars["SESSION_STORE_HOSTNAME"]
s.SESSION_STORE_PORT = env_vars["SESSION_STORE_PORT"]
s.SESSION_STORE_TYPE = env_vars["SESSION_STORE_TYPE"]

s.DATABASE_URL = env_vars["DATABASE_URL"]

var err error
s.DATABASE_MAX_CONNS, err = strconv.Atoi(env_vars["DATABASE_MAX_CONNS"])
if err != nil { log.Fatalf("Error while parseing DATABASE_MAX_CONNS: %v\n", err.Error()) }
s.DATABASE_MAX_IDLE_CONNS, err = strconv.Atoi(env_vars["DATABASE_MAX_IDLE_CONNS"])
if err != nil { log.Fatalf("Error while parseing DATABASE_MAX_IDLE_CONNS: %v\n", err.Error()) }
s.DATABASE_MAX_LIFETIME, err = time.ParseDuration(env_vars["DATABASE_MAX_LIFETIME"])
if err != nil { log.Fatalf("Error while parseing DATABASE_MAX_LIFETIME: %v\n", err.Error()) }
s.DATABASE_MAX_IDLE_TIME, err = time.ParseDuration(env_vars["DATABASE_MAX_IDLE_TIME"])
if err != nil { log.Fatalf("Error while parseing DATABASE_MAX_IDLE_TIME: %v\n", err.Error()) }

s.JWT_KEY = []byte(env_vars["JWT_KEY"])
s.JWT_LIFESPAN, err = time.ParseDuration(env_vars["JWT_LIFESPAN"])
if err != nil { log.Fatalf("Error while parseing JWT_LIFESPAN: %v\n", err.Error()) }

s.TOKEN_TIMEOUT, err = time.ParseDuration(env_vars["TOKEN_TIMEOUT"])
if err != nil { log.Fatalf("Error while parseing TOKEN_TIMEOUT: %v\n", err.Error()) }
s.TOKEN_LIMIT, err = strconv.Atoi(env_vars["TOKEN_LIMIT"])
if err != nil { log.Fatalf("Error while parseing TOKEN_LIMIT: %v\n", err.Error()) }
}
19 changes: 19 additions & 0 deletions backend/cmd/sourceflame-server/get_handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package main

import (
a "github.com/Ankumeah/SourceFlameBackend/internal/app"
"github.com/Ankumeah/SourceFlameBackend/internal/pat"
"github.com/Ankumeah/SourceFlameBackend/internal/jwt"
)

func get_handlers(app *a.App) {
app.PAT_Handler = pat.Get_PAT_Handler(
app.Settings.PAT_PREFIX,
app.Settings.PAT_LENGTH,
)
app.JWT_Handler = jwt.Get_JWT_Handler(
app.Settings.JWT_LEEWAY,
app.Settings.JWT_LIFESPAN,
app.Settings.JWT_KEY,
)
}
123 changes: 18 additions & 105 deletions backend/cmd/sourceflame-server/main.go
Original file line number Diff line number Diff line change
@@ -1,133 +1,46 @@
package main

import (
"github.com/Ankumeah/SourceFlameBackend/internal/apis"
a "github.com/Ankumeah/SourceFlameBackend/internal/app"
"github.com/Ankumeah/SourceFlameBackend/internal/database"
"github.com/Ankumeah/SourceFlameBackend/internal/apis"
"github.com/Ankumeah/SourceFlameBackend/internal/middlewares"
"github.com/Ankumeah/SourceFlameBackend/internal/pat"
"github.com/Ankumeah/SourceFlameBackend/internal/session_store"

"github.com/gin-gonic/gin"

"time"
"context"
"log"
"os"
"sync"
)

var Ctx = context.Background()
var app = &a.App{}
const pat_prefix = "gho_"
const pat_length = 32
var env_vars = map[string]string {
"API_VERSION": "",
"BACKEND_PORT": "",
"SESSION_STORE_SESSIONS_USERNAME": "",
"SESSION_STORE_SESSIONS_PASSWORD": "",
"SESSION_STORE_HOSTNAME": "",
"SESSION_STORE_PORT": "",
"SESSION_STORE_TYPE": "",
"DATABASE_USER": "",
"DATABASE_PASSWORD": "",
"DATABASE_HOST": "",
"DATABASE_PORT": "",
"DATABASE_DB": "",
"DATABASE_CONFIG": "",
}

func load_env() {
for env := range env_vars {
_env, ok := os.LookupEnv(env)
if !ok {
log.Fatalf("Unset env var: %v\n", env)
}

env_vars[env] = _env
}
}

func connect_session_store() {
sessions_config := session_store.Universal_Redis_Config {
Username: env_vars["SESSION_STORE_SESSIONS_USERNAME"],
Password: env_vars["SESSION_STORE_SESSIONS_PASSWORD"],
Hostname: env_vars["SESSION_STORE_HOSTNAME"],
Port: env_vars["SESSION_STORE_PORT"],
}
var client session_store.Redis_client
var err error

switch env_vars["SESSION_STORE_TYPE"] {
case "redis_standalone":
client, err = session_store.Get_Redis_Client(Ctx, sessions_config)
case "redis_cluster":
client, err = session_store.Get_Redis_Cluster_Client(Ctx, sessions_config)
default:
log.Fatalf("Unsupported session store type: %v\n", env_vars["SESSION_STORE_TYPE"])
}
if err != nil {
log.Panicf("Error while connecting to session store: %v", err.Error())
}

app.Store = session_store.Get_Sessions_Uinversal_Redis_Driver(client)
log.Println("Connected to session store")
}

func connect_database() {
conn_config := database.Connection_Config {
Username: env_vars["DATABASE_USER"],
Password: env_vars["DATABASE_PASSWORD"],
Hostname: env_vars["DATABASE_HOST"],
Port: env_vars["DATABASE_PORT"],
Db_name: env_vars["DATABASE_DB"],
Db_config: env_vars["DATABASE_CONFIG"],
}

pool, err := database.Get_Connection_Pool(Ctx, conn_config)
if err != nil {
log.Fatalf("Error while connecting to database: %v\n", err.Error())
}

app.User_db = database.User_Postgres_Driver(pool)
app.Git_db = database.Git_Postgres_Driver(pool)
app.PAT_db = database.PAT_Postgres_Driver(pool)

log.Println("Connected to database")
}

func get_pat_handler() {
app.PAT_Handler = pat.Get_PAT_Handler(pat_prefix, pat_length)
log.Println("Got PAT Handler")
}
var app = &a.App{ Settings: &a.Settings{} }

func main() {
log.Println("Loading env")
load_env()
load_env(app.Settings)
app.Settings.PAT_PREFIX = "sf_"
app.Settings.PAT_LENGTH = 32
app.Settings.AUTH_CHALLENGE_HEADER = `Basic realm="SourceFlame"`
app.Settings.JWT_LEEWAY = time.Second * 10
app.Settings.TOKEN_LENGTH = 64
app.Settings.TOKEN_NAMESPACE = "refresh:"

var wg sync.WaitGroup
wg.Go(func() {
log.Println("Connecting to session store")
connect_session_store()
})
wg.Go(func() {
log.Println("Connecting to database")
connect_database()
})
wg.Go(func() {
log.Println("Getting PAT Handler")
get_pat_handler()
})
wg.Go(func() { connect_session_store(app) })
wg.Go(func() { connect_database(app) })
wg.Go(func() { get_handlers(app) })
wg.Wait()

log.Println("Starting http server")
r := gin.Default()
apiGroup := r.Group(
"/api/" + env_vars["API_VERSION"] + "/",
"/api/" + app.Settings.API_VERSION + "/",
middlewars.Log_Middleware(),
)
apis.Apis(apiGroup, app)

log.Println("Running backend on port: " + env_vars["BACKEND_PORT"])
log.Println("API_VERSION: " + env_vars["API_VERSION"])
r.Run(":" + env_vars["BACKEND_PORT"])
log.Println("Running backend on port: " + app.Settings.BACKEND_PORT)
log.Println("API_VERSION: " + app.Settings.API_VERSION)

r.Run(":" + app.Settings.BACKEND_PORT)
}
Loading
Loading