mirror of
https://github.com/thomiceli/opengist.git
synced 2024-12-22 20:42:40 +00:00
Add Postgres and MySQL databases support (#335)
This commit is contained in:
parent
4b039b0703
commit
17237713a1
23 changed files with 479 additions and 59 deletions
56
.github/workflows/go.yml
vendored
56
.github/workflows/go.yml
vendored
|
@ -1,15 +1,15 @@
|
||||||
name: "Go CI"
|
name: "Go CI"
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
workflow_dispatch:
|
||||||
- master
|
|
||||||
- 'dev-*'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**.yml'
|
- '**.yml'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
|
name: Lint
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -31,6 +31,7 @@ jobs:
|
||||||
run: make fmt check_changes
|
run: make fmt check_changes
|
||||||
|
|
||||||
check:
|
check:
|
||||||
|
name: Check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -47,12 +48,58 @@ jobs:
|
||||||
- name: Check translations
|
- name: Check translations
|
||||||
run: make check-tr
|
run: make check-tr
|
||||||
|
|
||||||
|
test-db:
|
||||||
|
name: Test
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: ["ubuntu-latest"]
|
||||||
|
go: ["1.22"]
|
||||||
|
database: [postgres, mysql]
|
||||||
|
include:
|
||||||
|
- database: postgres
|
||||||
|
image: postgres:16
|
||||||
|
port: 5432:5432
|
||||||
|
- database: mysql
|
||||||
|
image: mysql:8
|
||||||
|
port: 3306:3306
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
services:
|
||||||
|
database:
|
||||||
|
image: ${{ matrix.image }}
|
||||||
|
ports:
|
||||||
|
- ${{ matrix.port }}
|
||||||
|
env:
|
||||||
|
POSTGRES_PASSWORD: opengist
|
||||||
|
POSTGRES_DB: opengist_test
|
||||||
|
MYSQL_ROOT_PASSWORD: opengist
|
||||||
|
MYSQL_DATABASE: opengist_test
|
||||||
|
options: >-
|
||||||
|
--health-cmd ${{ matrix.database == 'postgres' && 'pg_isready' || '"mysqladmin ping"' }}
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Go ${{ matrix.go }}
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: make test TEST_DB_TYPE=${{ matrix.database }}
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
name: Test
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: ["ubuntu-latest", "macOS-latest", "windows-latest"]
|
os: ["ubuntu-latest", "macOS-latest", "windows-latest"]
|
||||||
go: ["1.22"]
|
go: ["1.22"]
|
||||||
|
database: ["sqlite"]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
@ -64,5 +111,4 @@ jobs:
|
||||||
go-version: ${{ matrix.go }}
|
go-version: ${{ matrix.go }}
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: make test
|
run: make test TEST_DB_TYPE=${{ matrix.database }}
|
||||||
|
|
3
Makefile
3
Makefile
|
@ -4,6 +4,7 @@
|
||||||
BINARY_NAME := opengist
|
BINARY_NAME := opengist
|
||||||
GIT_TAG := $(shell git describe --tags)
|
GIT_TAG := $(shell git describe --tags)
|
||||||
VERSION_PKG := github.com/thomiceli/opengist/internal/config.OpengistVersion
|
VERSION_PKG := github.com/thomiceli/opengist/internal/config.OpengistVersion
|
||||||
|
TEST_DB_TYPE ?= sqlite
|
||||||
|
|
||||||
all: clean install build
|
all: clean install build
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ fmt:
|
||||||
@go fmt ./...
|
@go fmt ./...
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test ./... -p 1
|
@OPENGIST_TEST_DB=$(TEST_DB_TYPE) go test ./... -p 1
|
||||||
|
|
||||||
check-tr:
|
check-tr:
|
||||||
@bash ./scripts/check-translations.sh
|
@bash ./scripts/check-translations.sh
|
|
@ -28,7 +28,7 @@ It is similiar to [GitHub Gist](https://gist.github.com/), but open-source and c
|
||||||
* OAuth2 login with GitHub, GitLab, Gitea, and OpenID Connect
|
* OAuth2 login with GitHub, GitLab, Gitea, and OpenID Connect
|
||||||
* Restrict or unrestrict snippets visibility to anonymous users
|
* Restrict or unrestrict snippets visibility to anonymous users
|
||||||
* Docker support
|
* Docker support
|
||||||
* [More...](/docs/index.md#features)
|
* [More...](/docs/introduction.md#features)
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,11 @@ external-url:
|
||||||
# Directory where Opengist will store its data. Default: ~/.opengist/
|
# Directory where Opengist will store its data. Default: ~/.opengist/
|
||||||
opengist-home:
|
opengist-home:
|
||||||
|
|
||||||
# Name of the SQLite database file. Default: opengist.db
|
# URI of the database. Default: opengist.db (SQLite)
|
||||||
db-filename: opengist.db
|
# SQLite: file name
|
||||||
|
# PostgreSQL: postgres://user:password@host:port/database
|
||||||
|
# MySQL/MariaDB: mysql://user:password@host:port/database
|
||||||
|
db-uri: opengist.db
|
||||||
|
|
||||||
# Enable or disable the code search index (either `true` or `false`). Default: true
|
# Enable or disable the code search index (either `true` or `false`). Default: true
|
||||||
index.enabled: true
|
index.enabled: true
|
||||||
|
@ -29,6 +32,7 @@ git.default-branch:
|
||||||
|
|
||||||
# Set the journal mode for SQLite. Default: WAL
|
# Set the journal mode for SQLite. Default: WAL
|
||||||
# See https://www.sqlite.org/pragma.html#pragma_journal_mode
|
# See https://www.sqlite.org/pragma.html#pragma_journal_mode
|
||||||
|
# For SQLite databases only.
|
||||||
sqlite.journal-mode: WAL
|
sqlite.journal-mode: WAL
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -36,11 +36,17 @@ export default defineConfig({
|
||||||
{
|
{
|
||||||
text: 'Configuration', base: '/docs/configuration', items: [
|
text: 'Configuration', base: '/docs/configuration', items: [
|
||||||
{text: 'Configure Opengist', link: '/configure'},
|
{text: 'Configure Opengist', link: '/configure'},
|
||||||
{text: 'Admin panel', link: '/admin-panel'},
|
{text: 'Databases', items: [
|
||||||
|
{text: 'SQLite', link: '/databases/sqlite'},
|
||||||
|
{text: 'PostgreSQL', link: '/databases/postgresql'},
|
||||||
|
{text: 'MySQL', link: '/databases/mysql'},
|
||||||
|
], collapsed: true
|
||||||
|
},
|
||||||
{text: 'OAuth Providers', link: '/oauth-providers'},
|
{text: 'OAuth Providers', link: '/oauth-providers'},
|
||||||
{text: 'Custom assets', link: '/custom-assets'},
|
{text: 'Custom assets', link: '/custom-assets'},
|
||||||
{text: 'Custom links', link: '/custom-links'},
|
{text: 'Custom links', link: '/custom-links'},
|
||||||
{text: 'Cheat Sheet', link: '/cheat-sheet'},
|
{text: 'Cheat Sheet', link: '/cheat-sheet'},
|
||||||
|
{text: 'Admin panel', link: '/admin-panel'},
|
||||||
], collapsed: false
|
], collapsed: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
49
docs/configuration/databases/mysql.md
Normal file
49
docs/configuration/databases/mysql.md
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Using MySQL/MariaDB
|
||||||
|
|
||||||
|
To use MySQL/MariaDB as the database backend, you need to set the database URI configuration to the connection string of your MySQL/MariaDB database with this format :
|
||||||
|
|
||||||
|
`mysql://<user>:<password>@<host>:<port>/<database>`
|
||||||
|
|
||||||
|
#### YAML
|
||||||
|
```yaml
|
||||||
|
# Example
|
||||||
|
db-uri: mysql://root:passwd@localhost:3306/opengist_db
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Environment variable
|
||||||
|
```sh
|
||||||
|
# Example
|
||||||
|
OG_DB_URI=mysql://root:passwd@localhost:3306/opengist_db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
```yml
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
opengist:
|
||||||
|
image: ghcr.io/thomiceli/opengist:1
|
||||||
|
container_name: opengist
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
ports:
|
||||||
|
- "6157:6157"
|
||||||
|
- "2222:2222"
|
||||||
|
volumes:
|
||||||
|
- "$HOME/.opengist:/opengist"
|
||||||
|
environment:
|
||||||
|
OG_DB_URI: mysql://opengist:secret@mysql:3306/opengist_db
|
||||||
|
# other configuration options
|
||||||
|
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.4
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- "./opengist-database:/var/lib/mysql"
|
||||||
|
environment:
|
||||||
|
MYSQL_USER: opengist
|
||||||
|
MYSQL_PASSWORD: secret
|
||||||
|
MYSQL_DATABASE: opengist_db
|
||||||
|
MYSQL_ROOT_PASSWORD: rootsecret
|
||||||
|
```
|
48
docs/configuration/databases/postgresql.md
Normal file
48
docs/configuration/databases/postgresql.md
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# Using PostgreSQL
|
||||||
|
|
||||||
|
To use PostgreSQL as the database backend, you need to set the database URI configuration to the connection string of your PostgreSQL database with this format :
|
||||||
|
|
||||||
|
`postgres://<user>:<password>@<host>:<port>/<database>`
|
||||||
|
|
||||||
|
#### YAML
|
||||||
|
```yaml
|
||||||
|
# Example
|
||||||
|
db-uri: postgres://postgres:passwd@localhost:5432/opengist_db
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Environment variable
|
||||||
|
```sh
|
||||||
|
# Example
|
||||||
|
OG_DB_URI=postgres://postgres:passwd@localhost:5432/opengist_db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
```yml
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
opengist:
|
||||||
|
image: ghcr.io/thomiceli/opengist:1
|
||||||
|
container_name: opengist
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
ports:
|
||||||
|
- "6157:6157"
|
||||||
|
- "2222:2222"
|
||||||
|
volumes:
|
||||||
|
- "$HOME/.opengist:/opengist"
|
||||||
|
environment:
|
||||||
|
OG_DB_URI: postgres://opengist:secret@postgres:5432/opengist_db
|
||||||
|
# other configuration options
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:16.4
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- "./opengist-database:/var/lib/postgresql/data"
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: opengist
|
||||||
|
POSTGRES_PASSWORD: secret
|
||||||
|
POSTGRES_DB: opengist_db
|
||||||
|
```
|
39
docs/configuration/databases/sqlite.md
Normal file
39
docs/configuration/databases/sqlite.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# Using SQLite
|
||||||
|
|
||||||
|
By default, Opengist uses SQLite as the database backend.
|
||||||
|
|
||||||
|
Because SQLite is a file-based database, there is not much configuration to tweak.
|
||||||
|
|
||||||
|
The configuration `db-uri`/`OG_DB_URI` refers to the path of the SQLite database file relative in the `$opengist-home/` directory (default `opengist.db`),
|
||||||
|
although it can be left untouched.
|
||||||
|
|
||||||
|
The SQLite journal mode is set to [`WAL` (Write-Ahead Logging)](https://www.sqlite.org/pragma.html#pragma_journal_mode) by default and can be changed.
|
||||||
|
|
||||||
|
#### YAML
|
||||||
|
```yaml
|
||||||
|
sqlite.journal-mode: WAL
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Environment variable
|
||||||
|
```sh
|
||||||
|
OG_SQLITE_JOURNAL_MODE=WAL
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
```yml
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
opengist:
|
||||||
|
image: ghcr.io/thomiceli/opengist:1
|
||||||
|
container_name: opengist
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "6157:6157" # HTTP port
|
||||||
|
- "2222:2222" # SSH port, can be removed if you don't use SSH
|
||||||
|
volumes:
|
||||||
|
- "$HOME/.opengist:/opengist"
|
||||||
|
environment:
|
||||||
|
OG_SQLITE_JOURNAL_MODE: WAL
|
||||||
|
# other configuration options
|
||||||
|
```
|
|
@ -31,7 +31,7 @@ Written in [Go](https://go.dev), Opengist aims to be fast and easy to deploy.
|
||||||
* delete users/gists;
|
* delete users/gists;
|
||||||
* clean database/filesystem by syncing gists
|
* clean database/filesystem by syncing gists
|
||||||
* run `git gc` for all repositories
|
* run `git gc` for all repositories
|
||||||
* SQLite database
|
* SQLite/PostgreSQL/MySQL database
|
||||||
* Logging
|
* Logging
|
||||||
* Docker support
|
* Docker support
|
||||||
|
|
||||||
|
|
12
go.mod
12
go.mod
|
@ -7,7 +7,6 @@ require (
|
||||||
github.com/alecthomas/chroma/v2 v2.14.0
|
github.com/alecthomas/chroma/v2 v2.14.0
|
||||||
github.com/blevesearch/bleve/v2 v2.4.0
|
github.com/blevesearch/bleve/v2 v2.4.0
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/glebarez/go-sqlite v1.22.0
|
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
github.com/go-playground/validator/v10 v10.21.0
|
github.com/go-playground/validator/v10 v10.21.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
|
@ -26,7 +25,9 @@ require (
|
||||||
golang.org/x/crypto v0.23.0
|
golang.org/x/crypto v0.23.0
|
||||||
golang.org/x/text v0.15.0
|
golang.org/x/text v0.15.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/gorm v1.25.10
|
gorm.io/driver/mysql v1.5.7
|
||||||
|
gorm.io/driver/postgres v1.5.9
|
||||||
|
gorm.io/gorm v1.25.12
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
@ -53,8 +54,10 @@ require (
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dlclark/regexp2 v1.11.0 // indirect
|
github.com/dlclark/regexp2 v1.11.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
|
||||||
|
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
|
@ -62,6 +65,10 @@ require (
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
|
||||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
@ -85,6 +92,7 @@ require (
|
||||||
go.etcd.io/bbolt v1.3.10 // indirect
|
go.etcd.io/bbolt v1.3.10 // indirect
|
||||||
golang.org/x/net v0.25.0 // indirect
|
golang.org/x/net v0.25.0 // indirect
|
||||||
golang.org/x/oauth2 v0.20.0 // indirect
|
golang.org/x/oauth2 v0.20.0 // indirect
|
||||||
|
golang.org/x/sync v0.5.0 // indirect
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/protobuf v1.34.1 // indirect
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
|
19
go.sum
19
go.sum
|
@ -82,6 +82,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/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
github.com/go-playground/validator/v10 v10.21.0 h1:4fZA11ovvtkdgaeev9RGWPgc1uj3H8W+rNYyH/ySBb0=
|
github.com/go-playground/validator/v10 v10.21.0 h1:4fZA11ovvtkdgaeev9RGWPgc1uj3H8W+rNYyH/ySBb0=
|
||||||
github.com/go-playground/validator/v10 v10.21.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.21.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
|
@ -125,6 +127,14 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP
|
||||||
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
|
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-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||||
|
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
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/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
@ -236,8 +246,13 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||||
|
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
||||||
|
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||||
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
|
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
|
||||||
|
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||||
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
|
modernc.org/cc/v4 v4.21.2 h1:dycHFB/jDc3IyacKipCNSDrjIC0Lm1hyoWOZTRR20Lk=
|
||||||
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
modernc.org/cc/v4 v4.21.2/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||||
modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=
|
modernc.org/ccgo/v4 v4.17.8 h1:yyWBf2ipA0Y9GGz/MmCmi3EFpKgeS7ICrAFes+suEbs=
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var CmdHook = cli.Command{
|
var CmdHook = cli.Command{
|
||||||
|
@ -50,7 +49,8 @@ func initialize(ctx *cli.Context) {
|
||||||
}
|
}
|
||||||
config.InitLog()
|
config.InitLog()
|
||||||
|
|
||||||
if err := db.Setup(filepath.Join(config.GetHomeDir(), config.C.DBFilename), false); err != nil {
|
db.DeprecationDBFilename()
|
||||||
|
if err := db.Setup(config.C.DBUri, false); err != nil {
|
||||||
log.Fatal().Err(err).Msg("Failed to initialize database in hooks")
|
log.Fatal().Err(err).Msg("Failed to initialize database in hooks")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,8 +108,9 @@ func Initialize(ctx *cli.Context) {
|
||||||
if err := os.MkdirAll(filepath.Join(homePath, "custom"), 0755); err != nil {
|
if err := os.MkdirAll(filepath.Join(homePath, "custom"), 0755); err != nil {
|
||||||
log.Fatal().Err(err).Send()
|
log.Fatal().Err(err).Send()
|
||||||
}
|
}
|
||||||
log.Info().Msg("Database file: " + filepath.Join(homePath, config.C.DBFilename))
|
|
||||||
if err := db.Setup(filepath.Join(homePath, config.C.DBFilename), false); err != nil {
|
db.DeprecationDBFilename()
|
||||||
|
if err := db.Setup(config.C.DBUri, false); err != nil {
|
||||||
log.Fatal().Err(err).Msg("Failed to initialize database")
|
log.Fatal().Err(err).Msg("Failed to initialize database")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,10 @@ type config struct {
|
||||||
LogOutput string `yaml:"log-output" env:"OG_LOG_OUTPUT"`
|
LogOutput string `yaml:"log-output" env:"OG_LOG_OUTPUT"`
|
||||||
ExternalUrl string `yaml:"external-url" env:"OG_EXTERNAL_URL"`
|
ExternalUrl string `yaml:"external-url" env:"OG_EXTERNAL_URL"`
|
||||||
OpengistHome string `yaml:"opengist-home" env:"OG_OPENGIST_HOME"`
|
OpengistHome string `yaml:"opengist-home" env:"OG_OPENGIST_HOME"`
|
||||||
DBFilename string `yaml:"db-filename" env:"OG_DB_FILENAME"`
|
|
||||||
|
DBUri string `yaml:"db-uri" env:"OG_DB_URI"`
|
||||||
|
DBFilename string `yaml:"db-filename" env:"OG_DB_FILENAME"` // deprecated
|
||||||
|
|
||||||
IndexEnabled bool `yaml:"index.enabled" env:"OG_INDEX_ENABLED"`
|
IndexEnabled bool `yaml:"index.enabled" env:"OG_INDEX_ENABLED"`
|
||||||
IndexDirname string `yaml:"index.dirname" env:"OG_INDEX_DIRNAME"`
|
IndexDirname string `yaml:"index.dirname" env:"OG_INDEX_DIRNAME"`
|
||||||
|
|
||||||
|
@ -80,7 +83,7 @@ func configWithDefaults() (*config, error) {
|
||||||
c.LogLevel = "warn"
|
c.LogLevel = "warn"
|
||||||
c.LogOutput = "stdout,file"
|
c.LogOutput = "stdout,file"
|
||||||
c.OpengistHome = ""
|
c.OpengistHome = ""
|
||||||
c.DBFilename = "opengist.db"
|
c.DBUri = "opengist.db"
|
||||||
c.IndexEnabled = true
|
c.IndexEnabled = true
|
||||||
c.IndexDirname = "opengist.index"
|
c.IndexDirname = "opengist.index"
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AdminSetting struct {
|
type AdminSetting struct {
|
||||||
Key string `gorm:"uniqueIndex"`
|
Key string `gorm:"index:,unique"`
|
||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ func UpdateSetting(key string, value string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setSetting(key string, value string) error {
|
func setSetting(key string, value string) error {
|
||||||
return db.Create(&AdminSetting{Key: key, Value: value}).Error
|
return db.FirstOrCreate(&AdminSetting{Key: key, Value: value}, &AdminSetting{Key: key}).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
func initAdminSettings(settings map[string]string) error {
|
func initAdminSettings(settings map[string]string) error {
|
||||||
|
@ -64,9 +64,9 @@ func initAdminSettings(settings map[string]string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DBAuthInfo struct{}
|
type AuthInfo struct{}
|
||||||
|
|
||||||
func (auth DBAuthInfo) RequireLogin() (bool, error) {
|
func (auth AuthInfo) RequireLogin() (bool, error) {
|
||||||
s, err := GetSetting(SettingRequireLogin)
|
s, err := GetSetting(SettingRequireLogin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, err
|
return true, err
|
||||||
|
@ -74,7 +74,7 @@ func (auth DBAuthInfo) RequireLogin() (bool, error) {
|
||||||
return s == "1", nil
|
return s == "1", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth DBAuthInfo) AllowGistsWithoutLogin() (bool, error) {
|
func (auth AuthInfo) AllowGistsWithoutLogin() (bool, error) {
|
||||||
s, err := GetSetting(SettingAllowGistsWithoutLogin)
|
s, err := GetSetting(SettingAllowGistsWithoutLogin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
|
@ -2,38 +2,133 @@ package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"net/url"
|
||||||
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
msqlite "github.com/glebarez/go-sqlite"
|
|
||||||
"github.com/glebarez/sqlite"
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/thomiceli/opengist/internal/config"
|
"github.com/thomiceli/opengist/internal/config"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var db *gorm.DB
|
var db *gorm.DB
|
||||||
|
|
||||||
func Setup(dbPath string, sharedCache bool) error {
|
const (
|
||||||
var err error
|
SQLite databaseType = iota
|
||||||
journalMode := strings.ToUpper(config.C.SqliteJournalMode)
|
PostgreSQL
|
||||||
|
MySQL
|
||||||
|
)
|
||||||
|
|
||||||
if !slices.Contains([]string{"DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"}, journalMode) {
|
type databaseType int
|
||||||
log.Warn().Msg("Invalid SQLite journal mode: " + journalMode)
|
|
||||||
|
func (d databaseType) String() string {
|
||||||
|
return [...]string{"SQLite", "PostgreSQL", "MySQL"}[d]
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedCacheStr := ""
|
type databaseInfo struct {
|
||||||
if sharedCache {
|
Type databaseType
|
||||||
sharedCacheStr = "&cache=shared"
|
Host string
|
||||||
|
Port string
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
Database string
|
||||||
}
|
}
|
||||||
|
|
||||||
if db, err = gorm.Open(sqlite.Open(dbPath+"?_fk=true&_journal_mode="+journalMode+sharedCacheStr), &gorm.Config{
|
var DatabaseInfo *databaseInfo
|
||||||
Logger: logger.Default.LogMode(logger.Silent),
|
|
||||||
}); err != nil {
|
func parseDBURI(uri string) (*databaseInfo, error) {
|
||||||
|
info := &databaseInfo{}
|
||||||
|
|
||||||
|
if !strings.Contains(uri, "://") {
|
||||||
|
info.Type = SQLite
|
||||||
|
if uri == "file::memory:" {
|
||||||
|
info.Database = "file::memory:"
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
info.Database = filepath.Join(config.GetHomeDir(), uri)
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid URI: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch u.Scheme {
|
||||||
|
case "postgres", "postgresql":
|
||||||
|
info.Type = PostgreSQL
|
||||||
|
case "mysql", "mariadb":
|
||||||
|
info.Type = MySQL
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Host != "" {
|
||||||
|
host, port, _ := strings.Cut(u.Host, ":")
|
||||||
|
info.Host = host
|
||||||
|
info.Port = port
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.User != nil {
|
||||||
|
info.User = u.User.Username()
|
||||||
|
info.Password, _ = u.User.Password()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch info.Type {
|
||||||
|
case PostgreSQL, MySQL:
|
||||||
|
info.Database = strings.TrimPrefix(u.Path, "/")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Setup(dbUri string, sharedCache bool) error {
|
||||||
|
dbInfo, err := parseDBURI(dbUri)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info().Msgf("Setting up a %s database connection", dbInfo.Type)
|
||||||
|
var setupFunc func(databaseInfo, bool) error
|
||||||
|
switch dbInfo.Type {
|
||||||
|
case SQLite:
|
||||||
|
setupFunc = setupSQLite
|
||||||
|
case PostgreSQL:
|
||||||
|
setupFunc = setupPostgres
|
||||||
|
case MySQL:
|
||||||
|
setupFunc = setupMySQL
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown database type: %v", dbInfo.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxAttempts := 60
|
||||||
|
retryInterval := 1 * time.Second
|
||||||
|
|
||||||
|
for attempt := 1; attempt <= maxAttempts; attempt++ {
|
||||||
|
err = setupFunc(*dbInfo, sharedCache)
|
||||||
|
if err == nil {
|
||||||
|
log.Info().Msg("Database connection established")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if attempt < maxAttempts {
|
||||||
|
log.Warn().Err(err).Msgf("Failed to connect to database (attempt %d), retrying in %v...", attempt, retryInterval)
|
||||||
|
time.Sleep(retryInterval)
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DatabaseInfo = dbInfo
|
||||||
|
|
||||||
if err = db.SetupJoinTable(&Gist{}, "Likes", &Like{}); err != nil {
|
if err = db.SetupJoinTable(&Gist{}, "Likes", &Like{}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -46,7 +141,7 @@ func Setup(dbPath string, sharedCache bool) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = ApplyMigrations(db); err != nil {
|
if err = applyMigrations(db, dbInfo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,11 +170,7 @@ func CountAll(table interface{}) (int64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsUniqueConstraintViolation(err error) bool {
|
func IsUniqueConstraintViolation(err error) bool {
|
||||||
var sqliteErr *msqlite.Error
|
return errors.Is(err, gorm.ErrDuplicatedKey)
|
||||||
if errors.As(err, &sqliteErr) && sqliteErr.Code() == 2067 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Ping() error {
|
func Ping() error {
|
||||||
|
@ -90,3 +181,65 @@ func Ping() error {
|
||||||
|
|
||||||
return sql.Ping()
|
return sql.Ping()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupSQLite(dbInfo databaseInfo, sharedCache bool) error {
|
||||||
|
var err error
|
||||||
|
journalMode := strings.ToUpper(config.C.SqliteJournalMode)
|
||||||
|
|
||||||
|
if !slices.Contains([]string{"DELETE", "TRUNCATE", "PERSIST", "MEMORY", "WAL", "OFF"}, journalMode) {
|
||||||
|
log.Warn().Msg("Invalid SQLite journal mode: " + journalMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
sharedCacheStr := ""
|
||||||
|
if sharedCache {
|
||||||
|
sharedCacheStr = "&cache=shared"
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err = gorm.Open(sqlite.Open(dbInfo.Database+"?_fk=true&_journal_mode="+journalMode+sharedCacheStr), &gorm.Config{
|
||||||
|
Logger: logger.Default.LogMode(logger.Silent),
|
||||||
|
TranslateError: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupPostgres(dbInfo databaseInfo, sharedCache bool) error {
|
||||||
|
var err error
|
||||||
|
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", dbInfo.Host, dbInfo.Port, dbInfo.User, dbInfo.Password, dbInfo.Database)
|
||||||
|
|
||||||
|
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||||
|
Logger: logger.Default.LogMode(logger.Silent),
|
||||||
|
TranslateError: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupMySQL(dbInfo databaseInfo, sharedCache bool) error {
|
||||||
|
var err error
|
||||||
|
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", dbInfo.User, dbInfo.Password, dbInfo.Host, dbInfo.Port, dbInfo.Database)
|
||||||
|
|
||||||
|
db, err = gorm.Open(mysql.New(mysql.Config{
|
||||||
|
DSN: dsn,
|
||||||
|
DontSupportRenameIndex: true,
|
||||||
|
}), &gorm.Config{
|
||||||
|
Logger: logger.Default.LogMode(logger.Silent),
|
||||||
|
TranslateError: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeprecationDBFilename() {
|
||||||
|
if config.C.DBFilename != "" {
|
||||||
|
log.Warn().Msg("The 'db-filename'/'OG_DB_FILENAME' configuration option is deprecated and will be removed in a future version. Please use 'db-uri'/'OG_DB_URI' instead.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.C.DBUri == "" {
|
||||||
|
config.C.DBUri = config.C.DBFilename
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TruncateDatabase() error {
|
||||||
|
return db.Migrator().DropTable("likes", &User{}, "gists", &SSHKey{}, &AdminSetting{}, &Invitation{})
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -15,10 +16,21 @@ type Invitation struct {
|
||||||
|
|
||||||
func GetAllInvitations() ([]*Invitation, error) {
|
func GetAllInvitations() ([]*Invitation, error) {
|
||||||
var invitations []*Invitation
|
var invitations []*Invitation
|
||||||
err := db.
|
dialect := db.Dialector.Name()
|
||||||
Order("(((expires_at >= strftime('%s', 'now')) AND ((nb_max <= 0) OR (nb_used < nb_max)))) desc").
|
query := db.Model(&Invitation{})
|
||||||
Order("id asc").
|
|
||||||
Find(&invitations).Error
|
switch dialect {
|
||||||
|
case "sqlite":
|
||||||
|
query = query.Order("(((expires_at >= strftime('%s', 'now')) AND ((nb_max <= 0) OR (nb_used < nb_max)))) DESC")
|
||||||
|
case "postgres":
|
||||||
|
query = query.Order("(((expires_at >= EXTRACT(EPOCH FROM CURRENT_TIMESTAMP)) AND ((nb_max <= 0) OR (nb_used < nb_max)))) DESC")
|
||||||
|
case "mysql":
|
||||||
|
query = query.Order("(((expires_at >= UNIX_TIMESTAMP()) AND ((nb_max <= 0) OR (nb_used < nb_max)))) DESC")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unsupported database dialect: %s", dialect)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := query.Order("id ASC").Find(&invitations).Error
|
||||||
|
|
||||||
return invitations, err
|
return invitations, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,19 @@ type MigrationVersion struct {
|
||||||
Version uint
|
Version uint
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApplyMigrations(db *gorm.DB) error {
|
func applyMigrations(db *gorm.DB, dbInfo *databaseInfo) error {
|
||||||
|
switch dbInfo.Type {
|
||||||
|
case SQLite:
|
||||||
|
return applySqliteMigrations(db)
|
||||||
|
case PostgreSQL, MySQL:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown database type: %s", dbInfo.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func applySqliteMigrations(db *gorm.DB) error {
|
||||||
// Create migration table if it doesn't exist
|
// Create migration table if it doesn't exist
|
||||||
if err := db.AutoMigrate(&MigrationVersion{}); err != nil {
|
if err := db.AutoMigrate(&MigrationVersion{}); err != nil {
|
||||||
log.Fatal().Err(err).Msg("Error creating migration version table")
|
log.Fatal().Err(err).Msg("Error creating migration version table")
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uint `gorm:"primaryKey"`
|
ID uint `gorm:"primaryKey"`
|
||||||
Username string `gorm:"uniqueIndex"`
|
Username string `gorm:"uniqueIndex,size:191"`
|
||||||
Password string
|
Password string
|
||||||
IsAdmin bool
|
IsAdmin bool
|
||||||
CreatedAt int64
|
CreatedAt int64
|
||||||
|
|
|
@ -39,7 +39,7 @@ func runGitCommand(ch ssh.Channel, gitCmd string, key string, ip string) error {
|
||||||
return errors.New("gist not found")
|
return errors.New("gist not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
allowUnauthenticated, err := auth.ShouldAllowUnauthenticatedGistAccess(db.DBAuthInfo{}, true)
|
allowUnauthenticated, err := auth.ShouldAllowUnauthenticatedGistAccess(db.AuthInfo{}, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("internal server error")
|
return errors.New("internal server error")
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,6 +161,9 @@ func adminConfig(ctx echo.Context) error {
|
||||||
setData(ctx, "htmlTitle", trH(ctx, "admin.configuration")+" - "+trH(ctx, "admin.admin_panel"))
|
setData(ctx, "htmlTitle", trH(ctx, "admin.configuration")+" - "+trH(ctx, "admin.admin_panel"))
|
||||||
setData(ctx, "adminHeaderPage", "config")
|
setData(ctx, "adminHeaderPage", "config")
|
||||||
|
|
||||||
|
setData(ctx, "dbtype", db.DatabaseInfo.Type.String())
|
||||||
|
setData(ctx, "dbname", db.DatabaseInfo.Database)
|
||||||
|
|
||||||
return html(ctx, "admin_config.html")
|
return html(ctx, "admin_config.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,8 @@ import (
|
||||||
"github.com/thomiceli/opengist/internal/web"
|
"github.com/thomiceli/opengist/internal/web"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var databaseType string
|
||||||
|
|
||||||
type testServer struct {
|
type testServer struct {
|
||||||
server *web.Server
|
server *web.Server
|
||||||
sessionCookie string
|
sessionCookie string
|
||||||
|
@ -132,6 +134,17 @@ func structToURLValues(s interface{}) url.Values {
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(t *testing.T) {
|
func setup(t *testing.T) {
|
||||||
|
var databaseDsn string
|
||||||
|
databaseType = os.Getenv("OPENGIST_TEST_DB")
|
||||||
|
switch databaseType {
|
||||||
|
case "sqlite":
|
||||||
|
databaseDsn = "file::memory:"
|
||||||
|
case "postgres":
|
||||||
|
databaseDsn = "postgres://postgres:opengist@localhost:5432/opengist_test"
|
||||||
|
case "mysql":
|
||||||
|
databaseDsn = "mysql://root:opengist@localhost:3306/opengist_test"
|
||||||
|
}
|
||||||
|
|
||||||
_ = os.Setenv("OPENGIST_SKIP_GIT_HOOKS", "1")
|
_ = os.Setenv("OPENGIST_SKIP_GIT_HOOKS", "1")
|
||||||
|
|
||||||
err := config.InitConfig("", io.Discard)
|
err := config.InitConfig("", io.Discard)
|
||||||
|
@ -155,9 +168,13 @@ func setup(t *testing.T) {
|
||||||
err = os.MkdirAll(filepath.Join(homePath, "tmp", "repos"), 0755)
|
err = os.MkdirAll(filepath.Join(homePath, "tmp", "repos"), 0755)
|
||||||
require.NoError(t, err, "Could not create tmp repos directory")
|
require.NoError(t, err, "Could not create tmp repos directory")
|
||||||
|
|
||||||
err = db.Setup("file::memory:", true)
|
err = db.Setup(databaseDsn, true)
|
||||||
require.NoError(t, err, "Could not initialize database")
|
require.NoError(t, err, "Could not initialize database")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal().Err(err).Msg("Could not initialize database")
|
||||||
|
}
|
||||||
|
|
||||||
err = memdb.Setup()
|
err = memdb.Setup()
|
||||||
require.NoError(t, err, "Could not initialize in memory database")
|
require.NoError(t, err, "Could not initialize in memory database")
|
||||||
|
|
||||||
|
@ -168,10 +185,10 @@ func setup(t *testing.T) {
|
||||||
func teardown(t *testing.T, s *testServer) {
|
func teardown(t *testing.T, s *testServer) {
|
||||||
s.stop()
|
s.stop()
|
||||||
|
|
||||||
err := db.Close()
|
//err := db.Close()
|
||||||
require.NoError(t, err, "Could not close database")
|
//require.NoError(t, err, "Could not close database")
|
||||||
|
|
||||||
err = os.RemoveAll(path.Join(config.GetHomeDir(), "tests"))
|
err := os.RemoveAll(path.Join(config.GetHomeDir(), "tests"))
|
||||||
require.NoError(t, err, "Could not remove repos directory")
|
require.NoError(t, err, "Could not remove repos directory")
|
||||||
|
|
||||||
err = os.RemoveAll(path.Join(config.GetHomeDir(), "tmp", "repos"))
|
err = os.RemoveAll(path.Join(config.GetHomeDir(), "tmp", "repos"))
|
||||||
|
@ -180,6 +197,9 @@ func teardown(t *testing.T, s *testServer) {
|
||||||
err = os.RemoveAll(path.Join(config.GetHomeDir(), "tmp", "sessions"))
|
err = os.RemoveAll(path.Join(config.GetHomeDir(), "tmp", "sessions"))
|
||||||
require.NoError(t, err, "Could not remove repos directory")
|
require.NoError(t, err, "Could not remove repos directory")
|
||||||
|
|
||||||
|
err = db.TruncateDatabase()
|
||||||
|
require.NoError(t, err, "Could not truncate database")
|
||||||
|
|
||||||
// err = os.RemoveAll(path.Join(config.C.OpengistHome, "testsindex"))
|
// err = os.RemoveAll(path.Join(config.C.OpengistHome, "testsindex"))
|
||||||
// require.NoError(t, err, "Could not remove repos directory")
|
// require.NoError(t, err, "Could not remove repos directory")
|
||||||
|
|
||||||
|
|
4
templates/pages/admin_config.html
vendored
4
templates/pages/admin_config.html
vendored
|
@ -17,11 +17,11 @@
|
||||||
<dt>Log output</dt><dd>{{ .c.LogOutput }}</dd>
|
<dt>Log output</dt><dd>{{ .c.LogOutput }}</dd>
|
||||||
<dt>External URL</dt><dd>{{ .c.ExternalUrl }}</dd>
|
<dt>External URL</dt><dd>{{ .c.ExternalUrl }}</dd>
|
||||||
<dt>Opengist home</dt><dd>{{ .c.OpengistHome }}</dd>
|
<dt>Opengist home</dt><dd>{{ .c.OpengistHome }}</dd>
|
||||||
<dt>DB filename</dt><dd>{{ .c.DBFilename }}</dd>
|
<dt>Database type</dt><dd>{{ .dbtype }}{{ if eq .dbtype "SQLite" }} ({{ .c.SqliteJournalMode }}){{ end }}</dd>
|
||||||
|
<dt>Database name</dt><dd>{{ .dbname }}</dd>
|
||||||
<dt>Index Enabled</dt><dd>{{ .c.IndexEnabled }}</dd>
|
<dt>Index Enabled</dt><dd>{{ .c.IndexEnabled }}</dd>
|
||||||
<dt>Index Dirname</dt><dd>{{ .c.IndexDirname }}</dd>
|
<dt>Index Dirname</dt><dd>{{ .c.IndexDirname }}</dd>
|
||||||
<dt>Git default branch</dt><dd>{{ .c.GitDefaultBranch }}</dd>
|
<dt>Git default branch</dt><dd>{{ .c.GitDefaultBranch }}</dd>
|
||||||
<dt>SQLite Journal Mode</dt><dd>{{ .c.SqliteJournalMode }}</dd>
|
|
||||||
<div class="relative col-span-3 mt-4">
|
<div class="relative col-span-3 mt-4">
|
||||||
<div class="absolute inset-0 flex items-center" aria-hidden="true">
|
<div class="absolute inset-0 flex items-center" aria-hidden="true">
|
||||||
<div class="w-full border-t border-gray-300"></div>
|
<div class="w-full border-t border-gray-300"></div>
|
||||||
|
|
Loading…
Reference in a new issue