diff --git a/.github/workflows/compile-check.yaml b/.github/workflows/compile-check.yaml index f8586ad..6cc8b55 100644 --- a/.github/workflows/compile-check.yaml +++ b/.github/workflows/compile-check.yaml @@ -26,4 +26,4 @@ jobs: - name: Compile working-directory: backend/ - run: go build ./cmd/sourceflame-server/ + run: go build -tags="sqlite3" ./cmd/sourceflame-server/ diff --git a/.github/workflows/go-tests.yaml b/.github/workflows/go-tests.yaml index 93b4d18..bb59be1 100644 --- a/.github/workflows/go-tests.yaml +++ b/.github/workflows/go-tests.yaml @@ -29,4 +29,4 @@ jobs: - name: Run tests working-directory: backend/ - run: go test ./... -v + run: go test ./... -v -tags="sqlite3" diff --git a/README.md b/README.md index ce98945..d00368f 100644 --- a/README.md +++ b/README.md @@ -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) diff --git a/backend/Dockerfile b/backend/Dockerfile index cf7bb92..2bb4445 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -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" ] diff --git a/backend/cmd/sourceflame-server/connect_database.go b/backend/cmd/sourceflame-server/connect_database.go new file mode 100644 index 0000000..b9ffdf7 --- /dev/null +++ b/backend/cmd/sourceflame-server/connect_database.go @@ -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") +} diff --git a/backend/cmd/sourceflame-server/connect_session_store.go b/backend/cmd/sourceflame-server/connect_session_store.go new file mode 100644 index 0000000..ec0ac6c --- /dev/null +++ b/backend/cmd/sourceflame-server/connect_session_store.go @@ -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") +} diff --git a/backend/cmd/sourceflame-server/env.go b/backend/cmd/sourceflame-server/env.go new file mode 100644 index 0000000..a4b6209 --- /dev/null +++ b/backend/cmd/sourceflame-server/env.go @@ -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()) } +} diff --git a/backend/cmd/sourceflame-server/get_handlers.go b/backend/cmd/sourceflame-server/get_handlers.go new file mode 100644 index 0000000..6eb1144 --- /dev/null +++ b/backend/cmd/sourceflame-server/get_handlers.go @@ -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, + ) +} diff --git a/backend/cmd/sourceflame-server/main.go b/backend/cmd/sourceflame-server/main.go index 66c3f65..3be833b 100644 --- a/backend/cmd/sourceflame-server/main.go +++ b/backend/cmd/sourceflame-server/main.go @@ -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) } diff --git a/backend/go.mod b/backend/go.mod index 77718d2..b4e6c18 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -7,7 +7,11 @@ require ( github.com/go-git/go-git/v6 v6.0.0-20260220113129-c02711164eb8 github.com/golang-jwt/jwt/v5 v5.3.1 github.com/jackc/pgx/v5 v5.8.0 + github.com/jmoiron/sqlx v1.4.0 + github.com/mattn/go-sqlite3 v1.14.44 github.com/redis/go-redis/v9 v9.17.3 + github.com/uptrace/bun/dialect/pgdialect v1.2.18 + github.com/uptrace/bun/dialect/sqlitedialect v1.2.18 golang.org/x/crypto v0.48.0 ) @@ -35,6 +39,7 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/kevinburke/ssh_config v1.5.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect @@ -44,18 +49,23 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/quic-go v0.54.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect + github.com/uptrace/bun v1.2.18 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.20.0 // indirect - golang.org/x/mod v0.32.0 // indirect + golang.org/x/mod v0.33.0 // indirect golang.org/x/net v0.50.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/text v0.34.0 // indirect - golang.org/x/tools v0.41.0 // indirect + golang.org/x/tools v0.42.0 // indirect google.golang.org/protobuf v1.36.9 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index 4562b9a..0db950c 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,3 +1,5 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= @@ -53,6 +55,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= @@ -72,6 +76,10 @@ github.com/jackc/pgx/v5 v5.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo= github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= +github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kevinburke/ssh_config v1.5.0 h1:3cPZmE54xb5j3G5xQCjSvokqNwU2uW+3ry1+PRLSPpA= @@ -83,8 +91,13 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= +github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -95,6 +108,8 @@ github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= +github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= @@ -114,18 +129,30 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/uptrace/bun v1.2.18 h1:3HnRcMfS6OBPMG1eSOzlbFJ/X/AyMEJb7rMxE6VQvDU= +github.com/uptrace/bun v1.2.18/go.mod h1:wNltaKJk4JtOt4SG5I5zmA7v0/Mzjh1+/S906Rayd3Y= +github.com/uptrace/bun/dialect/pgdialect v1.2.18 h1:IZ6nM2+OYrL8lkEAy7UkSEZvoa3vluTAUlZfPtlRB2k= +github.com/uptrace/bun/dialect/pgdialect v1.2.18/go.mod h1:Tqdf4QP1okrGYpXfodXvCOK6Ob1OOTwSaoAzCgBB3IU= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.18 h1:Z33SY/U++XK9uGWqS4h8OZVxfCXguIG+sU9cYq2PGFQ= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.18/go.mod h1:1MVOS/Ncy4FZbkJcgUFH6OqYoQinYNjkEwsmNQEXz2A= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= @@ -137,8 +164,8 @@ golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/backend/internal/apis/login.go b/backend/internal/apis/login.go index 4d9fae9..216e1e4 100644 --- a/backend/internal/apis/login.go +++ b/backend/internal/apis/login.go @@ -4,7 +4,6 @@ import ( a "github.com/Ankumeah/SourceFlameBackend/internal/app" "github.com/Ankumeah/SourceFlameBackend/internal/database" "github.com/Ankumeah/SourceFlameBackend/internal/external_auth" - "github.com/Ankumeah/SourceFlameBackend/internal/jwt" "github.com/Ankumeah/SourceFlameBackend/internal/session_store" "github.com/gin-gonic/gin" @@ -31,7 +30,7 @@ func login(r *gin.RouterGroup, app *a.App) { user_id, err := app.User_db.Get_Id(ctx, request.Username) if err == database.Error_invalid_user { _, err = app.User_db.Add_User(ctx, request.Username, request.Password) - add_session(c, app.Store, request.Username) + add_session(c, app, request.Username) return } else if err != nil { c.JSON(internal_server_error()) @@ -46,7 +45,7 @@ func login(r *gin.RouterGroup, app *a.App) { c.JSON(http.StatusUnauthorized, gin.H { "error": "Invalid password" }) return } else { - add_session(c, app.Store, request.Username) + add_session(c, app, request.Username) } }) @@ -73,11 +72,11 @@ func login(r *gin.RouterGroup, app *a.App) { return } - add_session(c, app.Store, username) + add_session(c, app, username) }) } -func add_session(c *gin.Context, store *session_store.Session_store, username string) bool { +func add_session(c *gin.Context, app *a.App, username string) bool { ctx := c.Request.Context() var wg sync.WaitGroup @@ -87,17 +86,19 @@ func add_session(c *gin.Context, store *session_store.Session_store, username st var jwt_err error wg.Go(func() { - token, token_err = store.Add_Session(ctx, username) + token, token_err = app.Store.Add_Session(ctx, username) if token_err == session_store.Error_too_many_tokens { c.JSON(http.StatusForbidden, gin.H { "error": "Too many refresh tokens" }) + return } else if token_err != nil { c.JSON(internal_server_error()) + return } }) wg.Go(func() { - new_jwt, jwt_err = jwt.Issue_jwt(username) + new_jwt, jwt_err = app.JWT_Handler.Issue_jwt(username) if jwt_err != nil { - store.Delete_Session(ctx, username, token) + app.Store.Delete_Session(ctx, username, token) c.JSON(internal_server_error()) } }) diff --git a/backend/internal/apis/repo.go b/backend/internal/apis/repo.go index 218d903..ad9ee34 100644 --- a/backend/internal/apis/repo.go +++ b/backend/internal/apis/repo.go @@ -14,11 +14,9 @@ import ( "strings" ) -const push_conflict_message = "Push rejected because another client is pushing, please try again in some time" - func repo(r *gin.RouterGroup, app *a.App) { group := r.Group("/repo", - middlewars.Check_Login_Middleware(), + middlewars.Check_Login_Middleware(app.JWT_Handler), middlewars.Check_PAT_Middleware(app), ) @@ -85,7 +83,7 @@ func repo(r *gin.RouterGroup, app *a.App) { } if info.Private && !authed { - c.Header("WWW-Authenticate", auth_challenge_header) + c.Header("WWW-Authenticate", app.Settings.AUTH_CHALLENGE_HEADER) c.JSON(unauthorised_request()) return } else if info.Private && user_id != owner_id { @@ -129,7 +127,7 @@ func repo(r *gin.RouterGroup, app *a.App) { } if info.Private && !authed { - c.Header("WWW-Authenticate", auth_challenge_header) + c.Header("WWW-Authenticate", app.Settings.AUTH_CHALLENGE_HEADER) c.JSON(unauthorised_request()) return } else if info.Private && user_id != owner_id { @@ -167,7 +165,7 @@ func repo(r *gin.RouterGroup, app *a.App) { } if !authed { - c.Header("WWW-Authenticate", auth_challenge_header) + c.Header("WWW-Authenticate", app.Settings.AUTH_CHALLENGE_HEADER) c.JSON(unauthorised_request()) return } else if user_id != owner_id { diff --git a/backend/internal/apis/repos.go b/backend/internal/apis/repos.go index 4a5f9d9..94f67fd 100644 --- a/backend/internal/apis/repos.go +++ b/backend/internal/apis/repos.go @@ -12,7 +12,7 @@ import ( ) func repos(r *gin.RouterGroup, app *a.App) { - group := r.Group("/repos", middlewars.Verify_JWT_Middleware()) + group := r.Group("/repos", middlewars.Verify_JWT_Middleware(app.JWT_Handler)) group.POST("/:repo_name", func (c *gin.Context) { ctx := c.Request.Context() diff --git a/backend/internal/apis/session.go b/backend/internal/apis/session.go index 16df593..e59ab9e 100644 --- a/backend/internal/apis/session.go +++ b/backend/internal/apis/session.go @@ -2,7 +2,6 @@ package apis import ( a "github.com/Ankumeah/SourceFlameBackend/internal/app" - "github.com/Ankumeah/SourceFlameBackend/internal/jwt" "github.com/Ankumeah/SourceFlameBackend/internal/middlewares" "github.com/Ankumeah/SourceFlameBackend/internal/session_store" @@ -17,7 +16,7 @@ func session(r *gin.RouterGroup, app *a.App) { group.POST("/renew_jwt", func (c *gin.Context) { username := c.GetString("username") - token, err := jwt.Issue_jwt(username) + token, err := app.JWT_Handler.Issue_jwt(username) if err != nil { c.JSON(internal_server_error()) return diff --git a/backend/internal/apis/values.go b/backend/internal/apis/values.go deleted file mode 100644 index 0efd5e5..0000000 --- a/backend/internal/apis/values.go +++ /dev/null @@ -1,3 +0,0 @@ -package apis - -const auth_challenge_header = `Basic realm="SourceFlame"` diff --git a/backend/internal/app/types.go b/backend/internal/app/types.go index 31af65e..2f671d9 100644 --- a/backend/internal/app/types.go +++ b/backend/internal/app/types.go @@ -4,6 +4,9 @@ import ( "github.com/Ankumeah/SourceFlameBackend/internal/database" "github.com/Ankumeah/SourceFlameBackend/internal/pat" "github.com/Ankumeah/SourceFlameBackend/internal/session_store" + "github.com/Ankumeah/SourceFlameBackend/internal/jwt" + + "time" ) type App struct { @@ -12,4 +15,37 @@ type App struct { PAT_db *database.PAT_db Store *session_store.Session_store PAT_Handler *pat.PAT_Handler + JWT_Handler *jwt.JWT_Handler + Settings *Settings +} + +type Settings struct { + API_VERSION string + BACKEND_PORT string + + SESSION_STORE_SESSIONS_USERNAME string + SESSION_STORE_SESSIONS_PASSWORD string + SESSION_STORE_HOSTNAME string + SESSION_STORE_PORT string + SESSION_STORE_TYPE string + + DATABASE_URL string + DATABASE_MAX_CONNS int + DATABASE_MAX_IDLE_CONNS int + DATABASE_MAX_LIFETIME time.Duration + DATABASE_MAX_IDLE_TIME time.Duration + + JWT_KEY []byte + JWT_LIFESPAN time.Duration + JWT_LEEWAY time.Duration + + TOKEN_TIMEOUT time.Duration + TOKEN_LIMIT int + TOKEN_LENGTH int + TOKEN_NAMESPACE string + + PAT_PREFIX string + PAT_LENGTH uint8 + + AUTH_CHALLENGE_HEADER string } diff --git a/backend/internal/database/get_connection_pool.go b/backend/internal/database/get_connection_pool.go deleted file mode 100644 index 733c060..0000000 --- a/backend/internal/database/get_connection_pool.go +++ /dev/null @@ -1,42 +0,0 @@ -package database - -import ( - "github.com/jackc/pgx/v5/pgxpool" - - "context" - "fmt" - "log" -) - -func Get_Connection_Pool( - ctx context.Context, - conn_config Connection_Config, -) (*pgxpool.Pool, error) { - url_string := fmt.Sprintf( - "user=%v password=%v host=%v port=%v dbname=%v %v", - conn_config.Username, - conn_config.Password, - conn_config.Hostname, - conn_config.Port, - conn_config.Db_name, - conn_config.Db_config, - ) - config, err := pgxpool.ParseConfig(url_string) - if err != nil { - log.Printf("Error while parseing DB config: %v\n", err.Error()) - return nil, err - } - - conn, err := pgxpool.NewWithConfig(ctx, config) - if err != nil { - log.Printf("Error while connecting to DB config: %v\n", err.Error()) - return nil, err - } - - err = conn.Ping(ctx) - if err != nil { - return nil, err - } - - return conn, nil -} diff --git a/backend/internal/database/get_sql_connection.go b/backend/internal/database/get_sql_connection.go new file mode 100644 index 0000000..9474e2e --- /dev/null +++ b/backend/internal/database/get_sql_connection.go @@ -0,0 +1,41 @@ +package database + +import ( + "github.com/jmoiron/sqlx" + + "time" + "context" +) + +type Sql_Config struct { + max_conns int + max_idle int + max_lifetime time.Duration + max_idle_time time.Duration +} +func New_Sql_Config( + max_conn int, + max_idle int, + max_lifetime time.Duration, + max_idle_time time.Duration, +) Sql_Config { + return Sql_Config { + max_conns: max_conn, + max_idle: max_idle, + max_lifetime: max_lifetime, + max_idle_time: max_idle_time, + } +} + +func Get_DB_Connection(ctx context.Context, url string, config Sql_Config) (*sqlx.DB, error) { + db, err := sqlx.Open(driver_name, url) + if err != nil { return nil, err } + db.SetMaxOpenConns(config.max_conns) + db.SetMaxIdleConns(config.max_idle) + db.SetConnMaxLifetime(config.max_lifetime) + db.SetConnMaxIdleTime(config.max_idle_time) + + if err := db.Ping(); err != nil { return nil, err } + + return db, nil +} diff --git a/backend/internal/database/git_postgres_driver.go b/backend/internal/database/git_postgres_driver.go deleted file mode 100644 index 6ea31ab..0000000 --- a/backend/internal/database/git_postgres_driver.go +++ /dev/null @@ -1,123 +0,0 @@ -package database - -import ( - "github.com/jackc/pgx/v5/pgxpool" - "github.com/jackc/pgx/v5" - - "context" - "time" -) - -type git_postgres_driver struct { - db *pgxpool.Pool -} - -func Git_Postgres_Driver( pool *pgxpool.Pool) *Git_db { - return &Git_db { - &git_postgres_driver { pool }, - } -} - -func (p *git_postgres_driver) Create_Repo( - ctx context.Context, - owner_id uint64, - repo_name string, - private bool, -) (uint64, error) { - query := ` - INSERT INTO repos (name, owner_id, private, created_at, stars) - VALUES ($1, $2, $3, $4, 0) - RETURNING id; - ` - - var repo_id uint64 - now := time.Now().Unix() - err := p.db.QueryRow(ctx, query, repo_name, owner_id, private, now).Scan(&repo_id) - - return repo_id, err -} - -func (p *git_postgres_driver) Get_Id( - ctx context.Context, - owner_id uint64, - repo_name string, -) (uint64, error) { - query := ` - SELECT id FROM repos - WHERE (owner_id = $1 AND name = $2); - ` - - var repo_id uint64 - err := p.db.QueryRow(ctx, query, owner_id, repo_name).Scan(&repo_id) - if err == pgx.ErrNoRows { - return 0, Error_invalid_repo - } - - return repo_id, err -} - -func (p *git_postgres_driver) Delete_Repo( - ctx context.Context, - repo_id uint64, -) error { - query := ` - DELETE FROM repos - WHERE (id = $1); - ` - - tag, err := p.db.Exec(ctx, query, repo_id) - if tag.RowsAffected() == 0 { - return Error_invalid_repo - } - return err -} - -func (p *git_postgres_driver) Get_Repos( - ctx context.Context, - user_id uint64, - all bool, - limit uint8, - offset uint64, -) ([]string, error) { - query_all := ` - SELECT name FROM repos - WHERE (owner_id = $1) - LIMIT $2 OFFSET $3; - ` - query := ` - SELECT name FROM repos - WHERE (NOT private AND owner_id = $1) - LIMIT $2 OFFSET $3 - ` - if all { query = query_all } - - rows, _ := p.db.Query(ctx, query, user_id, limit, offset) - repos, err := pgx.CollectRows(rows, pgx.RowTo[string]) - if err != nil { - return nil, err - } - - return repos, nil -} - -func (p *git_postgres_driver) Info( - ctx context.Context, - repo_id uint64, -) (*Repo_Info, error) { - query := ` - SELECT repos.created_at, repos.stars, repos.private, users.username - FROM repos - INNER JOIN users ON repos.owner_id = users.id - WHERE repos.id = $1; - ` - - var info Repo_Info - err := p.db.QueryRow(ctx, query, repo_id).Scan( - &info.Creation, &info.Stars, &info.Private, &info.Owner, - ) - if err == pgx.ErrNoRows { - return nil, Error_invalid_repo - } - - return &info, err -} diff --git a/backend/internal/database/git_sql_driver.go b/backend/internal/database/git_sql_driver.go new file mode 100644 index 0000000..9e86f7d --- /dev/null +++ b/backend/internal/database/git_sql_driver.go @@ -0,0 +1,130 @@ +package database + +import ( + "github.com/jmoiron/sqlx" + + "database/sql" + "context" + "time" +) + +type git_sqlx_driver struct { db *sqlx.DB } +func Git_Sql_Driver(db *sqlx.DB) *Git_db { + return &Git_db { + &git_sqlx_driver { db }, + } +} + +func (s *git_sqlx_driver) Create_Repo( + ctx context.Context, + owner_id uint64, + repo_name string, + private bool, +) (uint64, error) { + query := s.db.Rebind(` + INSERT INTO repos (name, owner_id, private, created_at, stars) + VALUES (?, ?, ?, ?, 0) + RETURNING id; + `) + + var repo_id uint64 + now := time.Now().Unix() + err := s.db.QueryRowContext(ctx, query, repo_name, owner_id, private, now).Scan(&repo_id) + + return repo_id, err +} + +func (s *git_sqlx_driver) Get_Id( + ctx context.Context, + owner_id uint64, + repo_name string, +) (uint64, error) { + query := s.db.Rebind(` + SELECT id FROM repos + WHERE (owner_id = ? AND name = ?); + `) + + var repo_id uint64 + err := s.db.QueryRowContext(ctx, query, owner_id, repo_name).Scan(&repo_id) + if err == sql.ErrNoRows { + return 0, Error_invalid_repo + } + + return repo_id, err +} + +func (s *git_sqlx_driver) Delete_Repo( + ctx context.Context, + repo_id uint64, +) error { + query := s.db.Rebind(` + DELETE FROM repos + WHERE (id = ?); + `) + + tag, err := s.db.ExecContext(ctx, query, repo_id) + if err != nil { return err } + + effected, err := tag.RowsAffected() + if effected == 0 { return Error_invalid_repo } + + return err +} + +func (s *git_sqlx_driver) Get_Repos( + ctx context.Context, + user_id uint64, + all bool, + limit uint8, + offset uint64, +) ([]string, error) { + query_all := ` + SELECT name FROM repos + WHERE (owner_id = ?) + LIMIT ? OFFSET ?; + ` + _query := ` + SELECT name FROM repos + WHERE (NOT private AND owner_id = ?) + LIMIT ? OFFSET ? + ` + if all { _query = query_all } + query := s.db.Rebind(_query) + + rows, err := s.db.QueryContext(ctx, query, user_id, limit, offset) + if err != nil { return nil, err } + defer rows.Close() + + var repos []string + for rows.Next() { + var repo string + err := rows.Scan(&repo) + if err != nil { return nil, err } + repos = append(repos, repo) + } + if rows.Err() != nil { return nil, err } + + return repos, nil +} + +func (s *git_sqlx_driver) Info( + ctx context.Context, + repo_id uint64, +) (*Repo_Info, error) { + query := s.db.Rebind(` + SELECT repos.created_at, repos.stars, repos.private, users.username + FROM repos + INNER JOIN users ON repos.owner_id = users.id + WHERE repos.id = ?; + `) + + var info Repo_Info + err := s.db.QueryRowContext(ctx, query, repo_id).Scan( + &info.Creation, &info.Stars, &info.Private, &info.Owner, + ) + if err == sql.ErrNoRows { + return nil, Error_invalid_repo + } + + return &info, err +} diff --git a/backend/internal/database/pat_postgres_driver.go b/backend/internal/database/pat_postgres_driver.go deleted file mode 100644 index c679d22..0000000 --- a/backend/internal/database/pat_postgres_driver.go +++ /dev/null @@ -1,100 +0,0 @@ -package database - -import ( - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" - - "context" - "errors" - "time" -) - -type pat_postgres_driver struct { - db *pgxpool.Pool -} - -func PAT_Postgres_Driver( pool *pgxpool.Pool) *PAT_db { - return &PAT_db { - &pat_postgres_driver { pool }, - } -} - -func (p *pat_postgres_driver) Add_PAT(ctx context.Context, owner_id uint64, hash string, pat_name string) (uint64, error) { - query := ` - INSERT INTO pats (name, hash, owner_id, created_at) - VALUES ($1, $2, $3, $4) - RETURNING id; - ` - - var pat_id uint64 - now := time.Now().Unix() - err := p.db.QueryRow(ctx, query, pat_name, hash, owner_id, now).Scan(&pat_id) - - return pat_id, err -} - -func (p *pat_postgres_driver) Validate_PAT(ctx context.Context, owner_id uint64, hash string) (uint64, error) { - query := ` - SELECT id FROM pats - WHERE (owner_id = $1 AND hash = $2) - ` - - var pat_id uint64 - err := p.db.QueryRow(ctx, query, owner_id, hash).Scan(&pat_id) - if errors.Is(err, pgx.ErrNoRows) { - err = Error_invalid_pat - } - - return pat_id, err -} - -func (p *pat_postgres_driver) Get_Id(ctx context.Context, owner_id uint64, pat_name string) (uint64, error) { - query := ` - SELECT id FROM pats - WHERE (owner_id = $1 AND name = $2); - ` - - var pat_id uint64 - err := p.db.QueryRow(ctx, query, owner_id, pat_name).Scan(&pat_id) - if errors.Is(err, pgx.ErrNoRows) { - err = Error_invalid_pat - } - - return pat_id, err -} - -func (p *pat_postgres_driver) Delete_PAT(ctx context.Context, pat_id uint64) error { - query := ` - DELETE FROM pats - WHERE (id = $1); - ` - - tag, err := p.db.Exec(ctx, query, pat_id) - if tag.RowsAffected() == 0 { - return Error_invalid_pat - } - - return err -} -func (p *pat_postgres_driver) Get_PATs(ctx context.Context, user_id uint64) ([]string, error) { - query := ` - SELECT name FROM pats - WHERE (owner_id = $1); - ` - - rows, _ := p.db.Query(ctx, query, user_id) - pats, err := pgx.CollectRows(rows, pgx.RowTo[string]) - - return pats, err -} -func (p *pat_postgres_driver) Info(ctx context.Context, pat_id uint64) (*PAT_Info, error) { - query := ` - SELECT name, created_at, last_used FROM pats - WHERE (id = $1); - ` - - var info PAT_Info - err := p.db.QueryRow(ctx, query, pat_id).Scan(&info.Name, &info.Creation, &info.Last_Used) - - return &info, err -} diff --git a/backend/internal/database/pat_sql_driver.go b/backend/internal/database/pat_sql_driver.go new file mode 100644 index 0000000..36ae1b6 --- /dev/null +++ b/backend/internal/database/pat_sql_driver.go @@ -0,0 +1,108 @@ +package database + +import ( + "github.com/jmoiron/sqlx" + + "database/sql" + "context" + "errors" + "time" +) + +type pat_sqlx_driver struct { db *sqlx.DB } +func PAT_Sql_Driver(pool *sqlx.DB) *PAT_db { + return &PAT_db { + &pat_sqlx_driver { pool }, + } +} + +func (s *pat_sqlx_driver) Add_PAT(ctx context.Context, owner_id uint64, hash string, pat_name string) (uint64, error) { + query := s.db.Rebind(` + INSERT INTO pats (name, hash, owner_id, created_at) + VALUES (?, ?, ?, ?) + RETURNING id; + `) + + var pat_id uint64 + now := time.Now().Unix() + err := s.db.QueryRowContext(ctx, query, pat_name, hash, owner_id, now).Scan(&pat_id) + + return pat_id, err +} + +func (s *pat_sqlx_driver) Validate_PAT(ctx context.Context, owner_id uint64, hash string) (uint64, error) { + query := s.db.Rebind(` + SELECT id FROM pats + WHERE (owner_id = ? AND hash = ?) + `) + + var pat_id uint64 + err := s.db.QueryRowContext(ctx, query, owner_id, hash).Scan(&pat_id) + if errors.Is(err, sql.ErrNoRows) { + err = Error_invalid_pat + } + + return pat_id, err +} + +func (s *pat_sqlx_driver) Get_Id(ctx context.Context, owner_id uint64, pat_name string) (uint64, error) { + query := s.db.Rebind(` + SELECT id FROM pats + WHERE (owner_id = ? AND name = ?); + `) + + var pat_id uint64 + err := s.db.QueryRowContext(ctx, query, owner_id, pat_name).Scan(&pat_id) + if errors.Is(err, sql.ErrNoRows) { + err = Error_invalid_pat + } + + return pat_id, err +} + +func (s *pat_sqlx_driver) Delete_PAT(ctx context.Context, pat_id uint64) error { + query := s.db.Rebind(` + DELETE FROM pats + WHERE (id = ?); + `) + + tag, err := s.db.Exec(query, pat_id) + if err != nil { return err } + + effected, err := tag.RowsAffected() + if effected == 0 { return Error_invalid_pat } + + return err +} +func (s *pat_sqlx_driver) Get_PATs(ctx context.Context, user_id uint64) ([]string, error) { + query := s.db.Rebind(` + SELECT name FROM pats + WHERE (owner_id = ?); + `) + + rows, err := s.db.Query(query, user_id) + if err != nil { return nil, err } + defer rows.Close() + + var pats []string + for rows.Next() { + var pat string + err := rows.Scan(&pat) + if err != nil { return nil, err } + pats = append(pats, pat) + } + if rows.Err() != nil { return nil, err } + + return pats, nil +} +func (s *pat_sqlx_driver) Info(ctx context.Context, pat_id uint64) (*PAT_Info, error) { + query := s.db.Rebind(` + SELECT name, created_at, last_used FROM pats + WHERE (id = ?); + `) + + var info PAT_Info + err := s.db.QueryRowContext(ctx, query, pat_id).Scan(&info.Name, &info.Creation, &info.Last_Used) + + return &info, err +} diff --git a/backend/internal/database/postgres_options.go b/backend/internal/database/postgres_options.go new file mode 100644 index 0000000..09c3674 --- /dev/null +++ b/backend/internal/database/postgres_options.go @@ -0,0 +1,10 @@ +//go:build postgres +package database + +import ( + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/uptrace/bun/dialect/pgdialect" +) + +const driver_name = "pgx" +var dia = pgdialect.New() diff --git a/backend/internal/database/sqlite_options.go b/backend/internal/database/sqlite_options.go new file mode 100644 index 0000000..753b2c8 --- /dev/null +++ b/backend/internal/database/sqlite_options.go @@ -0,0 +1,10 @@ +//go:build sqlite3 +package database + +import ( + _ "github.com/mattn/go-sqlite3" + "github.com/uptrace/bun/dialect/sqlitedialect" +) + +const driver_name = "sqlite3" +var dia = sqlitedialect.New() diff --git a/backend/internal/database/types.go b/backend/internal/database/types.go index 0940843..5d64c77 100644 --- a/backend/internal/database/types.go +++ b/backend/internal/database/types.go @@ -51,12 +51,3 @@ type PAT_Info struct { Creation uint64 `json:"creation"` Last_Used *uint64 `json:"last_used"` } - -type Connection_Config struct { - Username string - Password string - Hostname string - Port string - Db_name string - Db_config string -} diff --git a/backend/internal/database/user_postgres_driver.go b/backend/internal/database/user_sql_driver.go similarity index 54% rename from backend/internal/database/user_postgres_driver.go rename to backend/internal/database/user_sql_driver.go index 863cb2c..53f8ca9 100644 --- a/backend/internal/database/user_postgres_driver.go +++ b/backend/internal/database/user_sql_driver.go @@ -3,36 +3,33 @@ package database import ( "github.com/Ankumeah/SourceFlameBackend/internal/hash" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" + "github.com/jmoiron/sqlx" + "database/sql" "context" "time" ) -type user_postgres_driver struct { - db *pgxpool.Pool -} - -func User_Postgres_Driver( pool *pgxpool.Pool) *User_db { +type user_sqlx_driver struct { db *sqlx.DB } +func User_Sql_Driver(pool *sqlx.DB) *User_db { return &User_db { - &user_postgres_driver { pool }, + &user_sqlx_driver { pool }, } } -func (p *user_postgres_driver) Add_User( +func (s *user_sqlx_driver) Add_User( ctx context.Context, username string, password_hash *hash.Hash, ) (uint64, error) { - query := ` + query := s.db.Rebind(` INSERT INTO users (username, created_at, password_hash, salt) - VALUES ($1, $2, $3, $4) RETURNING (id); - ` + VALUES (?, ?, ?, ?) RETURNING (id); + `) var user_id uint64 now := time.Now().Unix() - if err := p.db.QueryRow(ctx, query, + if err := s.db.QueryRowContext(ctx, query, username, now, password_hash.Hash, password_hash.Salt, ).Scan(&user_id); err != nil { return 0, err @@ -41,26 +38,26 @@ func (p *user_postgres_driver) Add_User( return user_id, nil } -func (p *user_postgres_driver) Delete_User( +func (s *user_sqlx_driver) Delete_User( ctx context.Context, user_id uint64, ) error { - query := "DELETE FROM users WHERE (id = $1);" + query := s.db.Rebind("DELETE FROM users WHERE (id = ?);") - _, err := p.db.Exec(ctx, query, user_id) + _, err := s.db.Exec(query, user_id) return err } -func (p *user_postgres_driver) Get_Hash( +func (s *user_sqlx_driver) Get_Hash( ctx context.Context, user_id uint64, ) (*hash.Hash, error) { - query := "SELECT password_hash, salt FROM users WHERE (id = $1);" + query := s.db.Rebind("SELECT password_hash, salt FROM users WHERE (id = ?);") var password_hash []byte var salt []byte - err := p.db.QueryRow(ctx, query, user_id).Scan(&password_hash, &salt) - if err == pgx.ErrNoRows { + err := s.db.QueryRowContext(ctx, query, user_id).Scan(&password_hash, &salt) + if err == sql.ErrNoRows { return nil, Error_invalid_user } else if err != nil { return nil, err @@ -72,15 +69,15 @@ func (p *user_postgres_driver) Get_Hash( } } -func (p *user_postgres_driver) Get_Id( +func (s *user_sqlx_driver) Get_Id( ctx context.Context, username string, ) (uint64, error) { - query := "SELECT id FROM users WHERE (username = $1);" + query := s.db.Rebind("SELECT id FROM users WHERE (username = ?);") var id uint64 - err := p.db.QueryRow(ctx, query, username).Scan(&id) - if err == pgx.ErrNoRows { + err := s.db.QueryRowContext(ctx, query, username).Scan(&id) + if err == sql.ErrNoRows { return 0, Error_invalid_user } else if err != nil { return 0, err @@ -89,15 +86,15 @@ func (p *user_postgres_driver) Get_Id( } } -func (p *user_postgres_driver) Info( +func (s *user_sqlx_driver) Info( ctx context.Context, user_id uint64, ) (*User_Info, error) { - query := "SELECT created_at FROM users WHERE (id = $1);" + query := s.db.Rebind("SELECT created_at FROM users WHERE (id = ?);") var creation uint64 - err := p.db.QueryRow(ctx, query, user_id).Scan(&creation) - if err == pgx.ErrNoRows { + err := s.db.QueryRowContext(ctx, query, user_id).Scan(&creation) + if err == sql.ErrNoRows { return nil, Error_invalid_user } else if err != nil { return nil, err diff --git a/backend/internal/git/types.go b/backend/internal/git/types.go index 462338b..6760438 100644 --- a/backend/internal/git/types.go +++ b/backend/internal/git/types.go @@ -1,15 +1,20 @@ package git -import ( - "path" -) +import "path" const max_blob_size = 2097152 var base_path string func init() { - base_path = path.Join("srv", "git") + // This breaks on windows, + // Im sorry if you are running this on bare + // metal on windows but if you are then you are + // kinda asking for it. + // The fact that you are reading this means you + // can probably change this on your own. + // Good luck and apologies from my side + base_path = path.Join("/srv", "git") } type File struct { diff --git a/backend/internal/jwt/jwt.go b/backend/internal/jwt/jwt.go index fc0c736..0e95e8e 100644 --- a/backend/internal/jwt/jwt.go +++ b/backend/internal/jwt/jwt.go @@ -3,42 +3,22 @@ package jwt import ( "github.com/golang-jwt/jwt/v5" - "os" "time" "log" - "strconv" ) -const jwt_leeway = 10 * time.Second - -var jwt_key []byte -var jwt_lifespan time.Duration - -func init() { - key, ok := os.LookupEnv("JWT_KEY") - if !ok { log.Fatalln("Unset env var: JWT_KEY") } - jwt_key = []byte(key) - - lifespan, ok := os.LookupEnv("JWT_LIFESPAN_MINS") - if !ok { log.Fatalln("Unset env var: JWT_LIFESPAN_MINS") } - - val, err := strconv.Atoi(lifespan) - if err != nil { log.Fatalf("Error converting JWT_LIFESPAN_MINS to int: %v\n", err.Error()) } - jwt_lifespan = time.Duration(val) * time.Minute -} - -func Issue_jwt(username string) (string, error) { +func (h *JWT_Handler) Issue_jwt(username string) (string, error) { now := time.Now() t := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims { Subject: username, IssuedAt: jwt.NewNumericDate(now), - ExpiresAt: jwt.NewNumericDate(now.Add(jwt_lifespan)), + ExpiresAt: jwt.NewNumericDate(now.Add(h.lifespan)), }, ) - signed_jwt, err := t.SignedString(jwt_key) + signed_jwt, err := t.SignedString(h.key) if err != nil { log.Println(err.Error()) return "", err @@ -47,16 +27,14 @@ func Issue_jwt(username string) (string, error) { return signed_jwt, nil } -func Validate_jwt(target_jwt string) (string, error) { +func (h *JWT_Handler) Validate_jwt(target_jwt string) (string, error) { claims := &jwt.RegisteredClaims{} token, err := jwt.ParseWithClaims(target_jwt, claims, - func(t *jwt.Token) (any, error) { - return jwt_key, nil - }, + func(t *jwt.Token) (any, error) { return h.key, nil }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}), jwt.WithJSONNumber(), jwt.WithIssuedAt(), - jwt.WithLeeway(jwt_leeway), + jwt.WithLeeway(h.leeway), ) if err != nil { return "", err diff --git a/backend/internal/jwt/jwt_test.go b/backend/internal/jwt/jwt_test.go index 5158a19..f310c22 100644 --- a/backend/internal/jwt/jwt_test.go +++ b/backend/internal/jwt/jwt_test.go @@ -4,6 +4,7 @@ import ( "github.com/Ankumeah/SourceFlameBackend/internal/jwt" "testing" + "time" ) func Test_Issue_jwt(t *testing.T) { @@ -12,9 +13,10 @@ func Test_Issue_jwt(t *testing.T) { } { { "beta" }, } + handler := jwt.Get_JWT_Handler(time.Second, time.Minute, []byte("key")) for _, test := range tests { - jwt, err := jwt.Issue_jwt(test.username) + jwt, err := handler.Issue_jwt(test.username) if err != nil { t.Errorf("Error: %v\n", err.Error()) } else if len(jwt) <= 0 { @@ -31,14 +33,15 @@ func Test_Validate_jwt(t *testing.T) { } { { "beta" }, } + handler := jwt.Get_JWT_Handler(time.Second, time.Minute, []byte("key")) for _, test := range tests { - token, err := jwt.Issue_jwt(test.username) + token, err := handler.Issue_jwt(test.username) if err != nil { t.Error("Error while issueing JWT") } - username, err := jwt.Validate_jwt(token) + username, err := handler.Validate_jwt(token) if err == jwt.Error_invalid_JWT { t.Errorf("JWT was invalid") } else if err != nil { diff --git a/backend/internal/jwt/types.go b/backend/internal/jwt/types.go new file mode 100644 index 0000000..1a540ce --- /dev/null +++ b/backend/internal/jwt/types.go @@ -0,0 +1,20 @@ +package jwt + +import "time" + +type JWT_Handler struct { + leeway time.Duration + lifespan time.Duration + key []byte +} +func Get_JWT_Handler( + leeway time.Duration, + lifespan time.Duration, + key []byte, +) *JWT_Handler { + return &JWT_Handler { + leeway: leeway, + lifespan: lifespan, + key: key, + } +} diff --git a/backend/internal/middlewares/check_PAT.go b/backend/internal/middlewares/check_PAT.go index 5bdb189..51514a4 100644 --- a/backend/internal/middlewares/check_PAT.go +++ b/backend/internal/middlewares/check_PAT.go @@ -1,13 +1,12 @@ package middlewars import ( - "log" - a "github.com/Ankumeah/SourceFlameBackend/internal/app" "github.com/Ankumeah/SourceFlameBackend/internal/database" "github.com/gin-gonic/gin" + "log" "errors" "net/http" "strings" diff --git a/backend/internal/middlewares/check_login.go b/backend/internal/middlewares/check_login.go index 003fa8a..80cdff3 100644 --- a/backend/internal/middlewares/check_login.go +++ b/backend/internal/middlewares/check_login.go @@ -8,7 +8,7 @@ import ( "net/http" ) -func Check_Login_Middleware() gin.HandlerFunc { +func Check_Login_Middleware(handler *jwt.JWT_Handler) gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("X-Authorization") if token == "" { @@ -16,7 +16,7 @@ func Check_Login_Middleware() gin.HandlerFunc { return } - username, err := jwt.Validate_jwt(token) + username, err := handler.Validate_jwt(token) if err == jwt.Error_invalid_JWT { c.JSON(http.StatusUnauthorized, gin.H { "error": "Invalid JWT" }) c.Abort() diff --git a/backend/internal/middlewares/verify_JWT.go b/backend/internal/middlewares/verify_JWT.go index f33e2dc..bef2f0b 100644 --- a/backend/internal/middlewares/verify_JWT.go +++ b/backend/internal/middlewares/verify_JWT.go @@ -8,11 +8,11 @@ import ( "net/http" ) -func Verify_JWT_Middleware() gin.HandlerFunc { +func Verify_JWT_Middleware(handler *jwt.JWT_Handler) gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("X-Authorization") - username, err := jwt.Validate_jwt(token) + username, err := handler.Validate_jwt(token) if err == jwt.Error_invalid_JWT { c.JSON(http.StatusUnauthorized, gin.H { "error": "Invalid JWT" }) c.Abort() diff --git a/backend/internal/session_store/fake_driver.go b/backend/internal/session_store/fake_driver.go index 22dc6ee..9dea0cf 100644 --- a/backend/internal/session_store/fake_driver.go +++ b/backend/internal/session_store/fake_driver.go @@ -10,7 +10,11 @@ type fake_driver struct { } func Get_Fake_Driver() (*Session_store) { - return &Session_store { &fake_driver{ map[string]string{} } } + return &Session_store { + &fake_driver{ map[string]string{} }, + time.Minute, + 2, 8, "fake:", + } } func (d *fake_driver) Add_Session(ctx context.Context, username string, token string, timeout time.Duration) error { @@ -34,6 +38,6 @@ func (d *fake_driver) Delete_Session(ctx context.Context, username string, token return nil } -func (d *fake_driver) Get_Session_Count(ctx context.Context, username string) (uint8, error) { +func (d *fake_driver) Get_Session_Count(ctx context.Context, username string) (int, error) { return 1, nil } diff --git a/backend/internal/session_store/session_store.go b/backend/internal/session_store/session_store.go index d413e7e..ef7f899 100644 --- a/backend/internal/session_store/session_store.go +++ b/backend/internal/session_store/session_store.go @@ -4,53 +4,28 @@ import ( "context" "crypto/rand" "encoding/base64" - "strconv" - "time" "log" - "os" "sync" ) -const token_length = 64 -const token_namespace = "refresh:" - -var token_timeout time.Duration -var token_limit uint8 - -func init() { - timeout, ok := os.LookupEnv("TOKEN_TIMEOUT_DAYS") - if !ok { log.Fatalln("Unset env var: TOKEN_TIMEOUT_DAYS") } - - val, err := strconv.Atoi(timeout) - if err != nil { log.Fatalf("Error while converting TOKEN_TIMEOUT_DAYS to int: %v\n", err.Error()) } - token_timeout = time.Duration(val) * 24 * time.Hour - - limit, ok := os.LookupEnv("TOKEN_LIMIT") - if !ok { log.Fatalf("Unset env var: TOKEN_LIMIT") } - - val, err = strconv.Atoi(limit) - if err != nil { log.Fatalf("Error while converting TOKEN_LIMIT to int: %v\n", err.Error()) } - token_limit = uint8(val) -} - func (d *Session_store) Add_Session(ctx context.Context, username string) (string, error) { var wg sync.WaitGroup - var count uint8 + var count int var count_err error var token_err error var token string wg.Go(func() { - count, count_err = d.db.Get_Session_Count(ctx, token_namespace + username) + count, count_err = d.db.Get_Session_Count(ctx, d.token_namespace + username) if count_err != nil { log.Printf("Error while getting session count: %v\n", count_err.Error()) - } else if count >= token_limit { + } else if count >= d.token_limit { count_err = Error_too_many_tokens } }) wg.Go(func() { - _token := make([]byte, token_length) + _token := make([]byte, d.token_length) if _, token_err = rand.Read(_token); token_err != nil { log.Printf("Error while genrating token: %v\n", token_err.Error()) return @@ -61,7 +36,12 @@ func (d *Session_store) Add_Session(ctx context.Context, username string) (strin if count_err != nil { return "", count_err } if token_err != nil { return "", token_err } - if err := d.db.Add_Session(ctx, token_namespace + username, token, token_timeout); err != nil { + if err := d.db.Add_Session( + ctx, + d.token_namespace + username, + token, + d.token_timeout, + ); err != nil { log.Printf("Error while adding session: %v\n", err.Error()) return "", err } @@ -70,7 +50,7 @@ func (d *Session_store) Add_Session(ctx context.Context, username string) (strin } func (d *Session_store) Validate_Session(ctx context.Context, username string, token string) (bool, error) { - valid, err := d.db.Validate_Session(ctx, token_namespace + username, token) + valid, err := d.db.Validate_Session(ctx, d.token_namespace + username, token) if err != nil { log.Printf("Error while validateing session: %v\n", err) return false, err @@ -80,7 +60,7 @@ func (d *Session_store) Validate_Session(ctx context.Context, username string, t } func (d *Session_store) Delete_Session(ctx context.Context, username string, token string) error { - if err := d.db.Delete_Session(ctx, token_namespace + username, token); err != nil { + if err := d.db.Delete_Session(ctx, d.token_namespace + username, token); err != nil { log.Printf("Error while deleting session: %v\n", err.Error()) } diff --git a/backend/internal/session_store/sessions_universal_redis_driver.go b/backend/internal/session_store/sessions_universal_redis_driver.go index ce37b0e..9df9c55 100644 --- a/backend/internal/session_store/sessions_universal_redis_driver.go +++ b/backend/internal/session_store/sessions_universal_redis_driver.go @@ -15,9 +15,19 @@ type Redis_client interface { ZCount(ctx context.Context, key string, min string, max string) *redis.IntCmd } type sessions_uinversal_redis_driver struct { rdb Redis_client } -func Get_Sessions_Uinversal_Redis_Driver(client Redis_client) *Session_store { +func Get_Sessions_Uinversal_Redis_Driver( + client Redis_client, + token_timeout time.Duration, + token_limit int, + token_length int, + token_namespace string, +) *Session_store { return &Session_store { &sessions_uinversal_redis_driver { client }, + token_timeout, + token_limit, + token_length, + token_namespace, } } @@ -45,12 +55,12 @@ func (r *sessions_uinversal_redis_driver) Delete_Session(ctx context.Context, us return r.rdb.ZRem(ctx, username, token).Err() } -func (r *sessions_uinversal_redis_driver) Get_Session_Count(ctx context.Context, username string) (uint8, error) { +func (r *sessions_uinversal_redis_driver) Get_Session_Count(ctx context.Context, username string) (int, error) { now := time.Now().Unix() count , err := r.rdb.ZCount(ctx, username, strconv.FormatInt(now, 10), "inf").Result() if err != nil { return 0, err } else { - return uint8(count), nil + return int(count), nil } } diff --git a/backend/internal/session_store/types.go b/backend/internal/session_store/types.go index 9fbaa04..99bafb6 100644 --- a/backend/internal/session_store/types.go +++ b/backend/internal/session_store/types.go @@ -9,7 +9,13 @@ type sessions_driver interface { Add_Session(ctx context.Context, username string, token string, timeout time.Duration) error Validate_Session(ctx context.Context, username string, token string) (bool, error) Delete_Session(ctx context.Context, username string, token string) error - Get_Session_Count(ctx context.Context, username string) (uint8, error) + Get_Session_Count(ctx context.Context, username string) (int, error) } -type Session_store struct { db sessions_driver } +type Session_store struct { + db sessions_driver + token_timeout time.Duration + token_limit int + token_length int + token_namespace string +} diff --git a/database/Dockerfile b/database/Dockerfile index 109ba62..7a75d18 100644 --- a/database/Dockerfile +++ b/database/Dockerfile @@ -1,8 +1,5 @@ FROM postgres:alpine -ARG version=alpha-01 -COPY ${version}.sql /docker-entrypoint-initdb.d/ - COPY . /opt/postgres/ RUN chmod +x /opt/postgres/run_postgres.sh diff --git a/database/alpha-01.sql b/database/alpha-01.sql deleted file mode 100644 index 84576be..0000000 --- a/database/alpha-01.sql +++ /dev/null @@ -1,25 +0,0 @@ -CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - username TEXT NOT NULL UNIQUE, - created_at BIGINT NOT NULL, - password_hash BYTEA NOT NULL, - salt BYTEA NOT NULL -); - -CREATE TABLE IF NOT EXISTS repos ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - private BOOLEAN NOT NULL, - owner_id INT REFERENCES users(id) NOT NULL, - created_at BIGINT NOT NULL, - stars INT NOT NULL -); - -CREATE TABLE IF NOT EXISTS pats ( - id SERIAL PRIMARY KEY, - name TEXT NOT NULL, - hash BYTEA NOT NULL, - owner_id INT REFERENCES users(id) NOT NULL, - created_at BIGINT NOT NULL, - last_used BIGINT -); diff --git a/database/run_postgres.sh b/database/run_postgres.sh index 3d5db49..a4ce11b 100755 --- a/database/run_postgres.sh +++ b/database/run_postgres.sh @@ -1,8 +1,8 @@ #! /bin/sh -export POSTGRES_USER="${DATABASE_USER}" -export POSTGRES_PASSWORD="${DATABASE_PASSWORD}" -export POSTGRES_DB="${DATABASE_DB}" -export POSTGRES_PORT="${POSTGRES_PORT}" +export POSTGRES_USER=$(echo "$DATABASE_URL" | awk -F/ '{print $3}' | awk -F: '{print $1}') +export POSTGRES_PASSWORD=$(echo "$DATABASE_URL" | awk -F/ '{print $3}' | awk -F: '{print $2}' | awk -F@ '{print $1}') +export POSTGRES_PORT=$(echo "$DATABASE_URL" | awk -F/ '{print $3}' | awk -F: '{print $3}') +export POSTGRES_DB=$(echo "$DATABASE_URL" | awk -F/ '{print $4}' | awk -F\? '{print $1}') exec docker-entrypoint.sh postgres diff --git a/db_init/Dockerfile b/db_init/Dockerfile new file mode 100644 index 0000000..939bf2a --- /dev/null +++ b/db_init/Dockerfile @@ -0,0 +1,19 @@ +FROM golang:alpine AS builder + +WORKDIR /opt/db_init/ + +RUN apk add gcc musl-dev --no-cache + +COPY go.mod go.sum ./ +RUN go mod download + +ARG db_type=sqlite3 + +COPY . . +RUN --mount=type=cache,target=/root/.cache/go-build go build -ldflags="-s -w" -tags=${db_type} -o ./bin/db-init ./ + +FROM alpine + +COPY --from=builder /opt/db_init/bin/db-init /opt/db_init/db-init + +ENTRYPOINT [ "/opt/db_init/db-init" ] diff --git a/db_init/get_conn.go b/db_init/get_conn.go new file mode 100644 index 0000000..99533fa --- /dev/null +++ b/db_init/get_conn.go @@ -0,0 +1,96 @@ +package main + +import ( + "github.com/uptrace/bun" + + "time" + "context" + "database/sql" +) + +type User struct { + bun.BaseModel `bun:"table:users"` + + ID uint64 `bun:"id,pk,autoincrement"` + Username string `bun:"username,notnull,unique"` + Created_At uint64 `bun:"created_at,notnull"` + Password_Hash []byte `bun:"password_hash,notnull"` + Salt []byte `bun:"salt,notnull"` + + Repos []*Repo `bun:"rel:has-many,join:id=owner_id"` + PATs []*PAT `bun:"rel:has-many,join:id=owner_id"` +} +type Repo struct { + bun.BaseModel `bun:"table:repos"` + + ID uint64 `bun:"id,pk,autoincrement"` + Name string `bun:"name,notnull"` + Private bool `bun:"private,notnull"` + OwnerID uint64 `bun:"owner_id,notnull"` + CreatedAt uint64 `bun:"created_at,notnull"` + Stars uint64 `bun:"stars,notnull"` + + Owner *User `bun:"rel:belongs-to,join:owner_id=id"` +} +type PAT struct { + bun.BaseModel `bun:"table:pats"` + + ID uint64 `bun:"id,pk,autoincrement"` + Name string `bun:"name,notnull"` + Hash []byte `bun:"hash,notnull"` + OwnerID uint64 `bun:"owner_id,notnull"` + CreatedAt uint64 `bun:"created_at,notnull"` + LastUsed *int64 `bun:"last_used"` + + Owner *User `bun:"rel:belongs-to,join:owner_id=id"` +} + +type Sql_Config struct { + max_conns int + max_idle int + max_lifetime time.Duration + max_idle_time time.Duration +} +func New_Sql_Config( + max_conn int, + max_idle int, + max_lifetime time.Duration, + max_idle_time time.Duration, +) Sql_Config { + return Sql_Config { + max_conns: max_conn, + max_idle: max_idle, + max_lifetime: max_lifetime, + max_idle_time: max_idle_time, + } +} + +func Get_DB_Connection(ctx context.Context, url string, config Sql_Config) (*sql.DB, error) { + db, err := sql.Open(driver_name, url) + if err != nil { return nil, err } + db.SetMaxOpenConns(config.max_conns) + db.SetMaxIdleConns(config.max_idle) + db.SetConnMaxLifetime(config.max_lifetime) + db.SetConnMaxIdleTime(config.max_idle_time) + + if err := db.Ping(); err != nil { return nil, err } + + return db, nil +} + +func Exec_Schema(ctx context.Context, db *sql.DB) error { + bun_db := bun.NewDB(db, dia) + + tx, err := bun_db.BeginTx(ctx, &sql.TxOptions{}) + if err != nil { return err } + defer tx.Rollback() + + _, err = tx.NewCreateTable().Model((*User)(nil)).IfNotExists().Exec(ctx) + if err != nil { return err } + _, err = tx.NewCreateTable().Model((*Repo)(nil)).IfNotExists().Exec(ctx) + if err != nil { return err } + _, err = tx.NewCreateTable().Model((*PAT)(nil)).IfNotExists().Exec(ctx) + if err != nil { return err } + + return tx.Commit() +} diff --git a/db_init/go.mod b/db_init/go.mod new file mode 100644 index 0000000..4e5e44b --- /dev/null +++ b/db_init/go.mod @@ -0,0 +1,25 @@ +module github.com/Ankumeah/SourceFlameBackend/db_init + +go 1.25.5 + +require ( + github.com/jackc/pgx/v5 v5.9.2 + github.com/mattn/go-sqlite3 v1.14.44 + github.com/uptrace/bun v1.2.18 + github.com/uptrace/bun/dialect/pgdialect v1.2.18 + github.com/uptrace/bun/dialect/sqlitedialect v1.2.18 +) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect + github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.29.0 // indirect +) diff --git a/db_init/go.sum b/db_init/go.sum new file mode 100644 index 0000000..7f6820a --- /dev/null +++ b/db_init/go.sum @@ -0,0 +1,46 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= +github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8= +github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg= +github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo= +github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs= +github.com/uptrace/bun v1.2.18 h1:3HnRcMfS6OBPMG1eSOzlbFJ/X/AyMEJb7rMxE6VQvDU= +github.com/uptrace/bun v1.2.18/go.mod h1:wNltaKJk4JtOt4SG5I5zmA7v0/Mzjh1+/S906Rayd3Y= +github.com/uptrace/bun/dialect/pgdialect v1.2.18 h1:IZ6nM2+OYrL8lkEAy7UkSEZvoa3vluTAUlZfPtlRB2k= +github.com/uptrace/bun/dialect/pgdialect v1.2.18/go.mod h1:Tqdf4QP1okrGYpXfodXvCOK6Ob1OOTwSaoAzCgBB3IU= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.18 h1:Z33SY/U++XK9uGWqS4h8OZVxfCXguIG+sU9cYq2PGFQ= +github.com/uptrace/bun/dialect/sqlitedialect v1.2.18/go.mod h1:1MVOS/Ncy4FZbkJcgUFH6OqYoQinYNjkEwsmNQEXz2A= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/db_init/main.go b/db_init/main.go new file mode 100644 index 0000000..3f29912 --- /dev/null +++ b/db_init/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "context" + "log" + "os" + "time" + "strconv" +) + +var Ctx = context.Background() +var env_vars = map[string]string { + "DATABASE_URL": "", + "DATABASE_MAX_CONNS": "", + "DATABASE_MAX_IDLE_CONNS": "", + "DATABASE_MAX_LIFETIME": "", + "DATABASE_MAX_IDLE_TIME": "", +} + +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 main() { + load_env() + + max_conn, err := strconv.Atoi(env_vars["DATABASE_MAX_CONNS"]) + if err != nil { + log.Fatalf("Error while parsing DATABASE_MAX_CONNS: %v\n", err.Error()) + } + max_idle, err := strconv.Atoi(env_vars["DATABASE_MAX_IDLE_CONNS"]) + if err != nil { + log.Fatalf("Error while parsing DATABASE_MAX_IDLE_CONNS: %v\n", err.Error()) + } + max_lifetime, err := time.ParseDuration(env_vars["DATABASE_MAX_LIFETIME"]) + if err != nil { + log.Fatalf("Error while parsing DATABASE_MAX_LIFETIME: %v\n", err.Error()) + } + max_idle_time, err := time.ParseDuration(env_vars["DATABASE_MAX_IDLE_TIME"]) + if err != nil { + log.Fatalf("Error while parsing DATABASE_MAX_IDLE_TIME: %v\n", err.Error()) + } + config := New_Sql_Config(int(max_conn), int(max_idle), max_lifetime, max_idle_time) + + db, err := Get_DB_Connection(Ctx, env_vars["DATABASE_URL"], config) + if err != nil { + log.Fatalf("Error while connecting to database: %v\n", err.Error()) + } + if err = Exec_Schema(Ctx, db); err != nil { + log.Fatalf("Error while execing schema: %v\n", err.Error()) + } + + log.Println("Connected to database") +} diff --git a/db_init/postgres_options.go b/db_init/postgres_options.go new file mode 100644 index 0000000..f5891ad --- /dev/null +++ b/db_init/postgres_options.go @@ -0,0 +1,10 @@ +//go:build postgres +package main + +import ( + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/uptrace/bun/dialect/pgdialect" +) + +const driver_name = "pgx" +var dia = pgdialect.New() diff --git a/db_init/sqlite_options.go b/db_init/sqlite_options.go new file mode 100644 index 0000000..7685025 --- /dev/null +++ b/db_init/sqlite_options.go @@ -0,0 +1,10 @@ +//go:build sqlite3 +package main + +import ( + _ "github.com/mattn/go-sqlite3" + "github.com/uptrace/bun/dialect/sqlitedialect" +) + +const driver_name = "sqlite3" +var dia = sqlitedialect.New() diff --git a/docker-compose.yaml b/docker-compose.yaml index a8324f2..f1d357a 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,37 +1,60 @@ services: backend: - build: ./backend/ + build: + context: ./backend/ + args: + - "db_type=sqlite3" # Currently supported sqlite3 and postgres env_file: - ./env.d.example/backend - ./env.d.example/session_store - - ./env.d.example/database + - ./env.d.example/database # Comment if useing postgres +# - ./env.d.example/pg_database # Comment if useing sqlite3 volumes: - ./data/git/:/srv/git/ + - ./data/database/:/data/ # Comment if not useing sqlite3 restart: unless-stopped depends_on: - - database + - db_init - session_store - database: - build: ./database/ - env_file: ./env.d.example/database - restart: unless-stopped + + db_init: + build: + context: ./db_init/ + args: + - "db_type=sqlite3" # Currently supported sqlite3 and postgres + env_file: + - ./env.d.example/backend + - ./env.d.example/database # Comment if useing postgres +# - ./env.d.example/pg_database # Comment if useing sqlite3 + restart: on-failure volumes: - - ./data/database/:/var/lib/postgresql/ + - ./data/database/:/data/ +# depends_on: # Comment if useing sqlite3 +# - database # Comment if useing sqlite3 + +# database: # Comment if useing sqlite3 +# build: ./database/ # Comment if useing sqlite3 +# env_file: ./env.d.example/pg_database # Comment if useing sqlite3 +# restart: unless-stopped # Comment if useing sqlite3 +# volumes: # Comment if useing sqlite3 +# - ./data/database/:/var/lib/postgresql/ # Comment if useing sqlite3 + session_store: build: ./session_store/ env_file: ./env.d.example/session_store restart: unless-stopped + reverse_proxy: build: ./reverse_proxy/ env_file: - ./env.d.example/reverse_proxy - - ./env.d.example/session_store - ./env.d.example/backend restart: unless-stopped depends_on: - backend ports: - "8000:80" + session_store_gc: build: ./session_store_gc/ env_file: ./env.d.example/session_store diff --git a/env.d.example/backend b/env.d.example/backend index 5d32aab..3737b14 100644 --- a/env.d.example/backend +++ b/env.d.example/backend @@ -1,7 +1,11 @@ API_VERSION=v1 BACKEND_PORT=5000 JWT_KEY=jwt_key -JWT_LIFESPAN_MINS=15 -TOKEN_TIMEOUT_DAYS=30 +JWT_LIFESPAN=15m +TOKEN_TIMEOUT=720h TOKEN_LIMIT=10 BACKEND_HOSTNAME=backend +DATABASE_MAX_CONNS=2 +DATABASE_MAX_IDLE_CONNS=2 +DATABASE_MAX_LIFETIME=10h +DATABASE_MAX_IDLE_TIME=2h diff --git a/env.d.example/database b/env.d.example/database index 014394f..38b5488 100644 --- a/env.d.example/database +++ b/env.d.example/database @@ -1,6 +1 @@ -DATABASE_HOST=database -DATABASE_PORT=5432 -DATABASE_CONFIG="pool_max_conns=10 pool_min_conns=2 pool_min_idle_conns=2" -DATABASE_DB=db -DATABASE_USER=username -DATABASE_PASSWORD=password +DATABASE_URL="/data/db.sql" diff --git a/env.d.example/pg_database b/env.d.example/pg_database new file mode 100644 index 0000000..229ae05 --- /dev/null +++ b/env.d.example/pg_database @@ -0,0 +1 @@ +DATABASE_URL=postgres://username:password@database:5432/db diff --git a/k8s/base/README.md b/k8s/base/README.md index aa24155..067288a 100644 --- a/k8s/base/README.md +++ b/k8s/base/README.md @@ -1,14 +1,15 @@ # Deplyment 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` +> This project uses k8s hostPath storage by default (/k8s/base/pv.yaml) +want it is recommended to change this to an actual storage provider +of your choice, the yaml resources to do so are not provided in this repo +and are assumed as the users responsibility. This app assumes the presence +of two PVCs by the names `git-storage` and `database-storage` - 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` +- Replace PVCs for your storage with names `git-storage` and `database-storage` (Optional but **HIGHLY** recommended) - 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) diff --git a/k8s/base/backend/deployment.yaml b/k8s/base/backend/deployment.yaml index 04055e3..c808884 100644 --- a/k8s/base/backend/deployment.yaml +++ b/k8s/base/backend/deployment.yaml @@ -32,8 +32,6 @@ spec: name: session-store-config-map - secretRef: name: database-secrets - - configMapRef: - name: database-config-map volumes: - name: git-storage persistentVolumeClaim: diff --git a/k8s/base/config-maps/backend-config-map.yaml b/k8s/base/config-maps/backend-config-map.yaml index 47c08f2..8534d58 100644 --- a/k8s/base/config-maps/backend-config-map.yaml +++ b/k8s/base/config-maps/backend-config-map.yaml @@ -3,9 +3,13 @@ kind: ConfigMap metadata: name: backend-config-map data: - API_VERSION: v1 + API_VERSION: "v1" BACKEND_PORT: "5000" - BACKEND_HOSTNAME: backend-service - JWT_LIFESPAN_MINS: "15" - TOKEN_TIMEOUT_DAYS: "30" + BACKEND_HOSTNAME: "backend-service" + JWT_LIFESPAN: "15m" + TOKEN_TIMEOUT: "720h" TOKEN_LIMIT: "10" + DATABASE_MAX_CONNS: "2" + DATABASE_MAX_IDLE_CONNS: "2" + DATABASE_MAX_LIFETIME: "10h" + DATABASE_MAX_IDLE_TIME: "2h" diff --git a/k8s/base/config-maps/database-config-map.yaml b/k8s/base/config-maps/database-config-map.yaml deleted file mode 100644 index 855db45..0000000 --- a/k8s/base/config-maps/database-config-map.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: database-config-map -data: - DATABASE_HOST: database-service - DATABASE_PORT: "5432" - DATABASE_CONFIG: "pool_max_conns=10 pool_min_conns=2 pool_min_idle_conns=2" diff --git a/k8s/base/config-maps/kustomization.yaml b/k8s/base/config-maps/kustomization.yaml index 02cef6e..96bfe74 100644 --- a/k8s/base/config-maps/kustomization.yaml +++ b/k8s/base/config-maps/kustomization.yaml @@ -2,6 +2,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ./backend-config-map.yaml - - ./database-config-map.yaml - ./session-store-config-map.yaml - ./reverse-proxy-config-map.yaml diff --git a/k8s/base/database/db-init-job.yaml b/k8s/base/database/db-init-job.yaml new file mode 100644 index 0000000..8785d4b --- /dev/null +++ b/k8s/base/database/db-init-job.yaml @@ -0,0 +1,16 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: db-init-job +spec: + template: + spec: + restartPolicy: OnFailure + containers: + - name: db-init + image: sourceflamebackend-db_init:local + envFrom: + - secretRef: + name: database-secrets + - configMapRef: + name: backend-config-map diff --git a/k8s/base/database/kustomization.yaml b/k8s/base/database/kustomization.yaml index 402e403..ab9223a 100644 --- a/k8s/base/database/kustomization.yaml +++ b/k8s/base/database/kustomization.yaml @@ -3,3 +3,4 @@ kind: Kustomization resources: - ./stateful-set.yaml - ./service.yaml + - ./db-init-job.yaml diff --git a/k8s/base/database/stateful-set.yaml b/k8s/base/database/stateful-set.yaml index d36ce57..32839ea 100644 --- a/k8s/base/database/stateful-set.yaml +++ b/k8s/base/database/stateful-set.yaml @@ -25,8 +25,6 @@ spec: envFrom: - secretRef: name: database-secrets - - configMapRef: - name: database-config-map volumes: - name: database-storage persistentVolumeClaim: diff --git a/k8s/base/kustomization.yaml b/k8s/base/kustomization.yaml index eb460e2..dacaa29 100644 --- a/k8s/base/kustomization.yaml +++ b/k8s/base/kustomization.yaml @@ -3,6 +3,7 @@ kind: Kustomization resources: - ./secrets/ - ./config-maps/ + - ./pv.yaml - ./backend/ - ./database/ - ./session-store/ diff --git a/k8s/base/pv.yaml b/k8s/base/pv.yaml new file mode 100644 index 0000000..6c3409c --- /dev/null +++ b/k8s/base/pv.yaml @@ -0,0 +1,57 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: git-storage +spec: + capacity: + storage: 10Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + hostPath: + path: /data/git/ + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: git-storage + namespace: default +spec: + accessModes: + - ReadWriteOnce + storageClassName: "" + resources: + requests: + storage: 10Gi + volumeName: git-storage +--- +apiVersion: v1 +kind: PersistentVolume +metadata: + name: database-storage +spec: + capacity: + storage: 10Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain + storageClassName: "" + hostPath: + path: /data/database/ + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: database-storage + namespace: default +spec: + accessModes: + - ReadWriteOnce + storageClassName: "" + resources: + requests: + storage: 10Gi + volumeName: database-storage diff --git a/k8s/base/reverse-proxy/deployment.yaml b/k8s/base/reverse-proxy/deployment.yaml index 4fc8a33..a025448 100644 --- a/k8s/base/reverse-proxy/deployment.yaml +++ b/k8s/base/reverse-proxy/deployment.yaml @@ -21,7 +21,5 @@ spec: envFrom: - configMapRef: name: reverse-proxy-config-map - - configMapRef: - name: session-store-config-map - configMapRef: name: backend-config-map diff --git a/k8s/base/secrets.example/database-secrets.yaml b/k8s/base/secrets.example/database-secrets.yaml index 9559243..c0699aa 100644 --- a/k8s/base/secrets.example/database-secrets.yaml +++ b/k8s/base/secrets.example/database-secrets.yaml @@ -3,6 +3,4 @@ kind: Secret metadata: name: database-secrets data: - DATABASE_DB: ZGI= # db - DATABASE_USER: dXNlcm5hbWU= # username - DATABASE_PASSWORD: cGFzc3dvcmQ= # password + DATABASE_URL: cG9zdGdyZXNxbDovL3VzZXJuYW1lOnBhc3N3b3JkQGRhdGFiYXNlLXNlcnZpY2U6NTQzMi9kYg== # postgresql://username:password@database-service:5432/db diff --git a/k8s/base/secrets.example/kustomization.yaml b/k8s/base/secrets.example/kustomization.yaml index e552b02..2be316e 100644 --- a/k8s/base/secrets.example/kustomization.yaml +++ b/k8s/base/secrets.example/kustomization.yaml @@ -2,5 +2,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - ./backend-secrets.yaml - - ./session-store-secrets.yaml - ./database-secrets.yaml + - ./session-store-secrets.yaml diff --git a/k8s/base/secrets.example/session-store-secrets.yaml b/k8s/base/secrets.example/session-store-secrets.yaml index fcd2d02..7e0e8bb 100644 --- a/k8s/base/secrets.example/session-store-secrets.yaml +++ b/k8s/base/secrets.example/session-store-secrets.yaml @@ -5,6 +5,5 @@ metadata: data: SESSION_STORE_SESSIONS_USERNAME: c2Vzc2lvbnM= # sessions SESSION_STORE_SESSIONS_PASSWORD: c2Vzc2lvbnM= # sessions - SESSION_STORE_GC_USERNAME: Z2M= # gc SESSION_STORE_GC_PASSWORD: Z2M= # gc