Compare commits
25 Commits
weblate-_-
...
8a6f2d82ff
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a6f2d82ff | ||
|
|
bfd75a9d58 | ||
|
|
eef6029c95 | ||
|
|
c60094c778 | ||
|
|
f67bff59c3 | ||
|
|
ec26888487 | ||
|
|
57d76151fd | ||
|
|
c2ee390841 | ||
|
|
d6fc346e70 | ||
|
|
4e977077ba | ||
|
|
f865b2b099 | ||
|
|
d26221de54 | ||
|
|
e91139d3ec | ||
|
|
279da52899 | ||
|
|
5ad01a3304 | ||
|
|
1944502d14 | ||
|
|
2d7261ac83 | ||
|
|
50f2980c10 | ||
|
|
2e68b6893b | ||
|
|
9b68f08c62 | ||
|
|
dfabdb403a | ||
|
|
4da067ab60 | ||
|
|
a8339ff6bd | ||
|
|
7a5cdd1565 | ||
|
|
00dcb53e3a |
8
.github/dependabot.yml
vendored
8
.github/dependabot.yml
vendored
@@ -1,17 +1,9 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
- package-ecosystem: "docker"
|
||||
directory: "/"
|
||||
schedule:
|
||||
|
||||
14
.github/workflows/go.yml
vendored
14
.github/workflows/go.yml
vendored
@@ -83,6 +83,18 @@ jobs:
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
meilisearch:
|
||||
image: getmeili/meilisearch:latest
|
||||
ports:
|
||||
- 47700:7700
|
||||
env:
|
||||
MEILI_NO_ANALYTICS: true
|
||||
MEILI_ENV: development
|
||||
options: >-
|
||||
--health-cmd "curl -sf http://localhost:7700/health"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
@@ -94,6 +106,8 @@ jobs:
|
||||
|
||||
- name: Run tests
|
||||
run: make test TEST_DB_TYPE=${{ matrix.database }}
|
||||
env:
|
||||
OG_TEST_MEILI_HOST: http://localhost:47700
|
||||
|
||||
test:
|
||||
name: Test
|
||||
|
||||
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
run: make all_crosscompile
|
||||
|
||||
- name: Upload Release Assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
files: |
|
||||
build/*.tar.gz
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/thomiceli/opengist
|
||||
@@ -54,26 +54,26 @@ jobs:
|
||||
type=semver,pattern={{version}}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -1,5 +1,28 @@
|
||||
# Changelog
|
||||
|
||||
## [1.12.2](https://github.com/thomiceli/opengist/compare/v1.12.1...v1.12.2) - 2026-03-14
|
||||
See here how to [update](https://opengist.io/docs/update) Opengist.
|
||||
|
||||
### Added
|
||||
- Search all fields (#622)
|
||||
- Display a form to create an Opengist account coming from a OAuth provider (#623)
|
||||
- Rebuild search index in admin options (#647)
|
||||
|
||||
### Fixed
|
||||
- Clean file path names on file creation (#624)
|
||||
- Support UTF-8 on gist download (#625)
|
||||
- CSRF skipper only for GET *.js request (#627)
|
||||
- Async-loaded gist embed scripts (#630)
|
||||
- Make gists username/urls case insensitive in URLS (#641)
|
||||
- Improve code search and index tests (#663)
|
||||
- Translation strings (#659)
|
||||
- Gitea avatar URL on OAuth (#674)
|
||||
|
||||
### [Helm Chart](helm/opengist)
|
||||
- Add environment variables and secrets to statefulset (#644)
|
||||
|
||||
> Admins of Opengist instances may want to run "Rebuild search index" in the admin panel.
|
||||
|
||||
## [1.12.1](https://github.com/thomiceli/opengist/compare/v1.12.0...v1.12.1) - 2026-02-03
|
||||
See here how to [update](https://opengist.io/docs/update) Opengist.
|
||||
|
||||
|
||||
@@ -77,9 +77,9 @@ Download the archive for your system from the release page [here](https://github
|
||||
|
||||
```shell
|
||||
# example for linux amd64
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.12.1/opengist1.12.1-linux-amd64.tar.gz
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.12.2/opengist1.12.2-linux-amd64.tar.gz
|
||||
|
||||
tar xzvf opengist1.12.1-linux-amd64.tar.gz
|
||||
tar xzvf opengist1.12.2-linux-amd64.tar.gz
|
||||
cd opengist
|
||||
chmod +x opengist
|
||||
./opengist # with or without `--config config.yml`
|
||||
|
||||
@@ -32,6 +32,10 @@ index.meili.host:
|
||||
# Set the API key for the Meiliseach server
|
||||
index.meili.api-key:
|
||||
|
||||
# Set the default search fields. Can contain multiple fields (e.g., `content,username`).
|
||||
# Fields: content,user,title,description,filename,extension,language,topic. Default: content
|
||||
search.default: content
|
||||
|
||||
# Default branch name used by Opengist when initializing Git repositories.
|
||||
# If not set, uses the Git default branch name. See https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup#_new_default_branch
|
||||
git.default-branch:
|
||||
|
||||
@@ -85,7 +85,7 @@ export default defineConfig({
|
||||
{icon: 'github', link: 'https://github.com/thomiceli/opengist'}
|
||||
],
|
||||
editLink: {
|
||||
pattern: 'https://github.com/thomiceli/opengist/edit/stable/docs/:path'
|
||||
pattern: 'https://github.com/thomiceli/opengist/edit/v.1.12.2/docs/:path'
|
||||
},
|
||||
// @ts-ignore
|
||||
lastUpdated: true,
|
||||
|
||||
@@ -15,6 +15,7 @@ aside: false
|
||||
| index | OG_INDEX | `bleve` | Define the code indexer (either `bleve`, `meilisearch`, or empty for no index). |
|
||||
| index.meili.host | OG_MEILI_HOST | none | Set the host for the Meiliseach server. |
|
||||
| index.meili.api-key | OG_MEILI_API_KEY | none | Set the API key for the Meiliseach server. |
|
||||
| search.default | OG_SEARCH_DEFAULT | `content` | Set the default search fields. Can contain multiple fields (e.g., `content,username`). Fields: `content,user,title,description,filename,extension,language,topic`. |
|
||||
| git.default-branch | OG_GIT_DEFAULT_BRANCH | none | Default branch name used by Opengist when initializing Git repositories. If not set, uses the Git default branch name. More info [here](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup#_new_default_branch) |
|
||||
| sqlite.journal-mode | OG_SQLITE_JOURNAL_MODE | `WAL` | Set the journal mode for SQLite. More info [here](https://www.sqlite.org/pragma.html#pragma_journal_mode) |
|
||||
| http.host | OG_HTTP_HOST | `0.0.0.0` | The host on which the HTTP server should bind. Use an IP address for network binding. Use a path for Unix socket binding (e.g. /run/opengist.sock) |
|
||||
|
||||
@@ -27,7 +27,7 @@ Usage via command line :
|
||||
./opengist --config /path/to/config.yml
|
||||
```
|
||||
|
||||
You can start by copying and/or modifying the provided [config.yml](https://github.com/thomiceli/opengist/blob/stable/config.yml) file.
|
||||
You can start by copying and/or modifying the provided [config.yml](https://github.com/thomiceli/opengist/blob/master/config.yml) file.
|
||||
|
||||
|
||||
## Configuration via Environment Variables
|
||||
@@ -69,4 +69,4 @@ services:
|
||||
secrets:
|
||||
opengist_secrets:
|
||||
file: ./secrets.env
|
||||
```
|
||||
```
|
||||
|
||||
@@ -4,9 +4,9 @@ Download the archive for your system from the release page [here](https://github
|
||||
|
||||
```shell
|
||||
# example for linux amd64
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.12.1/opengist1.12.1-linux-amd64.tar.gz
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.12.2/opengist1.12.2-linux-amd64.tar.gz
|
||||
|
||||
tar xzvf opengist1.12.1-linux-amd64.tar.gz
|
||||
tar xzvf opengist1.12.2-linux-amd64.tar.gz
|
||||
cd opengist
|
||||
chmod +x opengist
|
||||
./opengist # with or without `--config config.yml`
|
||||
|
||||
@@ -10,7 +10,7 @@ Requirements:
|
||||
git clone https://github.com/thomiceli/opengist
|
||||
cd opengist
|
||||
|
||||
git checkout v1.12.1 # optional, to checkout the latest release
|
||||
git checkout v1.12.2 # optional, to checkout the latest release
|
||||
|
||||
make
|
||||
./opengist
|
||||
|
||||
@@ -27,9 +27,9 @@ Stop the running instance; then like your first installation of Opengist, downlo
|
||||
|
||||
```shell
|
||||
# example for linux amd64
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.12.1/opengist1.12.1-linux-amd64.tar.gz
|
||||
wget https://github.com/thomiceli/opengist/releases/download/v1.12.2/opengist1.12.2-linux-amd64.tar.gz
|
||||
|
||||
tar xzvf opengist1.12.1-linux-amd64.tar.gz
|
||||
tar xzvf opengist1.12.2-linux-amd64.tar.gz
|
||||
cd opengist
|
||||
chmod +x opengist
|
||||
./opengist # with or without `--config config.yml`
|
||||
|
||||
87
go.mod
87
go.mod
@@ -9,28 +9,28 @@ require (
|
||||
github.com/dustin/go-humanize v1.0.1
|
||||
github.com/gabriel-vasile/mimetype v1.4.13
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.12
|
||||
github.com/go-playground/validator/v10 v10.30.1
|
||||
github.com/go-webauthn/webauthn v0.16.0
|
||||
github.com/go-ldap/ldap/v3 v3.4.13
|
||||
github.com/go-playground/validator/v10 v10.30.2
|
||||
github.com/go-webauthn/webauthn v0.17.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/schema v1.4.1
|
||||
github.com/gorilla/securecookie v1.1.2
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
github.com/labstack/echo-contrib v0.17.4
|
||||
github.com/labstack/echo-contrib v0.50.1
|
||||
github.com/labstack/echo/v4 v4.15.1
|
||||
github.com/markbates/goth v1.82.0
|
||||
github.com/meilisearch/meilisearch-go v0.36.1
|
||||
github.com/meilisearch/meilisearch-go v0.36.2
|
||||
github.com/pquerna/otp v1.5.0
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/urfave/cli/v2 v2.27.7
|
||||
github.com/yuin/goldmark v1.7.16
|
||||
github.com/yuin/goldmark v1.8.2
|
||||
github.com/yuin/goldmark-emoji v1.0.6
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||
go.abhg.dev/goldmark/mermaid v0.6.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/text v0.34.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/text v0.36.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/driver/mysql v1.6.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
@@ -38,88 +38,89 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.1.0 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
filippo.io/edwards25519 v1.2.0 // indirect
|
||||
github.com/Azure/go-ntlmssp v0.1.1 // indirect
|
||||
github.com/RoaringBitmap/roaring/v2 v2.18.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.3.1 // indirect
|
||||
github.com/blevesearch/geo v0.2.4 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.27 // indirect
|
||||
github.com/blevesearch/bleve_index_api v1.3.11 // indirect
|
||||
github.com/blevesearch/geo v0.2.5 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.35 // indirect
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
|
||||
github.com/blevesearch/gtreap v0.1.1 // indirect
|
||||
github.com/blevesearch/mmap-go v1.2.0 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.1 // indirect
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 // indirect
|
||||
github.com/blevesearch/segment v0.9.1 // indirect
|
||||
github.com/blevesearch/snowballstem v0.9.0 // indirect
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect
|
||||
github.com/blevesearch/vellum v1.2.0 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.3.0 // indirect
|
||||
github.com/blevesearch/zapx/v11 v11.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v12 v12.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v13 v13.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v14 v14.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v15 v15.4.3 // indirect
|
||||
github.com/blevesearch/zapx/v16 v16.3.4 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clipperhouse/stringish v0.1.1 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.4.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/dlclark/regexp2 v1.12.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.3 // indirect
|
||||
github.com/go-chi/chi/v5 v5.2.5 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/go-webauthn/x v0.2.1 // indirect
|
||||
github.com/go-webauthn/x v0.2.3 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/golang/snappy v1.0.0 // indirect
|
||||
github.com/google/go-tpm v0.9.8 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.8.0 // indirect
|
||||
github.com/jackc/pgx/v5 v5.9.2 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/labstack/gommon v0.5.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.19 // indirect
|
||||
github.com/mattn/go-isatty v0.0.21 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.23 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/mschoch/smat v0.2.0 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.67.5 // indirect
|
||||
github.com/prometheus/procfs v0.19.2 // indirect
|
||||
github.com/prometheus/procfs v0.20.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/tinylib/msgp v1.6.4 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect
|
||||
go.etcd.io/bbolt v1.4.3 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/oauth2 v0.34.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/time v0.14.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.4 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/time v0.15.0 // indirect
|
||||
golang.org/x/tools v0.44.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
modernc.org/libc v1.67.7 // indirect
|
||||
modernc.org/libc v1.72.1 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.44.3 // indirect
|
||||
modernc.org/sqlite v1.50.0 // indirect
|
||||
)
|
||||
|
||||
214
go.sum
214
go.sum
@@ -1,11 +1,11 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/go-ntlmssp v0.1.0 h1:DjFo6YtWzNqNvQdrwEyr/e4nhU3vRiwenz5QX7sFz+A=
|
||||
github.com/Azure/go-ntlmssp v0.1.0/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||
filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo=
|
||||
filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc=
|
||||
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
|
||||
github.com/Azure/go-ntlmssp v0.1.1/go.mod h1:NYqdhxd/8aAct/s4qSYZEerdPuH1liG2/X9DiVTbhpk=
|
||||
github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0=
|
||||
github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4 h1:4aKySrrg9G/5oRtJ3TrZLObVqxgQ9f1znCRBwEwjuVw=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.14.4/go.mod h1:oMvV6omPWr+2ifRdeZvVJyaz+aoEUopyv5iH0u/+wbY=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.18.0 h1:h7sS0VqCkfBMGgcHaudJFB4FE6Td71H6svRB2poRnGY=
|
||||
github.com/RoaringBitmap/roaring/v2 v2.18.0/go.mod h1:eq4wdNXxtJIS/oikeCzdX1rBzek7ANzbth041hrU8Q4=
|
||||
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
@@ -16,28 +16,28 @@ github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs
|
||||
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI=
|
||||
github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4=
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/andybalholm/brotli v1.2.1 h1:R+f5xP285VArJDRgowrfb9DqL18yVK0gKAW/F+eTWro=
|
||||
github.com/andybalholm/brotli v1.2.1/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
|
||||
github.com/blevesearch/bleve/v2 v2.5.7 h1:2d9YrL5zrX5EBBW++GOaEKjE+NPWeZGaX77IM26m1Z8=
|
||||
github.com/blevesearch/bleve/v2 v2.5.7/go.mod h1:yj0NlS7ocGC4VOSAedqDDMktdh2935v2CSWOCDMHdSA=
|
||||
github.com/blevesearch/bleve_index_api v1.3.1 h1:LdH3CQgBbIZ5UI/5Pykz87e0jfeQtVnrdZ2WUBrHHwU=
|
||||
github.com/blevesearch/bleve_index_api v1.3.1/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
|
||||
github.com/blevesearch/geo v0.2.4 h1:ECIGQhw+QALCZaDcogRTNSJYQXRtC8/m8IKiA706cqk=
|
||||
github.com/blevesearch/geo v0.2.4/go.mod h1:K56Q33AzXt2YExVHGObtmRSFYZKYGv0JEN5mdacJJR8=
|
||||
github.com/blevesearch/go-faiss v1.0.27 h1:7cBImYDDQ82WJd5RUZ1ie6zXztCsC73W94ZzwOjkatk=
|
||||
github.com/blevesearch/go-faiss v1.0.27/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/bleve_index_api v1.3.11 h1:x29vbV8OjWfLcrDVd7Lr1q+BkLNS0JWNEig0MCVnKH4=
|
||||
github.com/blevesearch/bleve_index_api v1.3.11/go.mod h1:xvd48t5XMeeioWQ5/jZvgLrV98flT2rdvEJ3l/ki4Ko=
|
||||
github.com/blevesearch/geo v0.2.5 h1:yJg9FX1oRwLnjXSXF+ECHfXFTF4diF02Ca/qUGVjJhE=
|
||||
github.com/blevesearch/geo v0.2.5/go.mod h1:Jhq7WE2K6mJTx1xS44M2pUO6Io+wjCSHh1+co3YOgH4=
|
||||
github.com/blevesearch/go-faiss v1.0.35 h1:DJF8yKgH8/n+Oo4E047Jy4oDeRivOugfMb6Cih//JjI=
|
||||
github.com/blevesearch/go-faiss v1.0.35/go.mod h1:OMGQwOaRRYxrmeNdMrXJPvVx8gBnvE5RYrr0BahNnkk=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3 h1:GtmsqID0aZdCSNiY8SkuPJ12pD4jI+DdXTAn4YRcHCo=
|
||||
github.com/blevesearch/go-porterstemmer v1.0.3/go.mod h1:angGc5Ht+k2xhJdZi511LtmxuEf0OVpvUUNrwmM1P7M=
|
||||
github.com/blevesearch/gtreap v0.1.1 h1:2JWigFrzDMR+42WGIN/V2p0cUvn4UP3C4Q5nmaZGW8Y=
|
||||
github.com/blevesearch/gtreap v0.1.1/go.mod h1:QaQyDRAT51sotthUWAH4Sj08awFSSWzgYICSZ3w0tYk=
|
||||
github.com/blevesearch/mmap-go v1.2.0 h1:l33nNKPFcBjJUMwem6sAYJPUzhUCABoK9FxZDGiFNBI=
|
||||
github.com/blevesearch/mmap-go v1.2.0/go.mod h1:Vd6+20GBhEdwJnU1Xohgt88XCD/CTWcqbCNxkZpyBo0=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.1 h1:os52/JeCSLZ0YUkOuLk/Z7pu0SKUMofDPUg+VnbrRD0=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.1/go.mod h1:zvilBm4BNfbnTRLW7KgCTNgk2R31JaWzwRc2BEcD7Is=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.7 h1:GlMzW08hcsM3DnLUxhyF/1PcDal1qtvvIuytuph5djw=
|
||||
github.com/blevesearch/scorch_segment_api/v2 v2.4.7/go.mod h1://IJ7tG3QCf0cWW/aVSXqy77tc1AvLu3fcJLYEvOAFs=
|
||||
github.com/blevesearch/segment v0.9.1 h1:+dThDy+Lvgj5JMxhmOVlgFfkUtZV2kw49xax4+jTfSU=
|
||||
github.com/blevesearch/segment v0.9.1/go.mod h1:zN21iLm7+GnBHWTao9I+Au/7MBiL8pPFtJBJTsk6kQw=
|
||||
github.com/blevesearch/snowballstem v0.9.0 h1:lMQ189YspGP6sXvZQ4WZ+MLawfV8wOmPoD/iWeNXm8s=
|
||||
@@ -46,18 +46,18 @@ github.com/blevesearch/upsidedown_store_api v1.0.2 h1:U53Q6YoWEARVLd1OYNc9kvhBMG
|
||||
github.com/blevesearch/upsidedown_store_api v1.0.2/go.mod h1:M01mh3Gpfy56Ps/UXHjEO/knbqyQ1Oamg8If49gRwrQ=
|
||||
github.com/blevesearch/vellum v1.2.0 h1:xkDiOEsHc2t3Cp0NsNZZ36pvc130sCzcGKOPMzXe+e0=
|
||||
github.com/blevesearch/vellum v1.2.0/go.mod h1:uEcfBJz7mAOf0Kvq6qoEKQQkLODBF46SINYNkZNae4k=
|
||||
github.com/blevesearch/zapx/v11 v11.4.2 h1:l46SV+b0gFN+Rw3wUI1YdMWdSAVhskYuvxlcgpQFljs=
|
||||
github.com/blevesearch/zapx/v11 v11.4.2/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
|
||||
github.com/blevesearch/zapx/v12 v12.4.2 h1:fzRbhllQmEMUuAQ7zBuMvKRlcPA5ESTgWlDEoB9uQNE=
|
||||
github.com/blevesearch/zapx/v12 v12.4.2/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
|
||||
github.com/blevesearch/zapx/v13 v13.4.2 h1:46PIZCO/ZuKZYgxI8Y7lOJqX3Irkc3N8W82QTK3MVks=
|
||||
github.com/blevesearch/zapx/v13 v13.4.2/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
|
||||
github.com/blevesearch/zapx/v14 v14.4.2 h1:2SGHakVKd+TrtEqpfeq8X+So5PShQ5nW6GNxT7fWYz0=
|
||||
github.com/blevesearch/zapx/v14 v14.4.2/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2 h1:sWxpDE0QQOTjyxYbAVjt3+0ieu8NCE0fDRaFxEsp31k=
|
||||
github.com/blevesearch/zapx/v15 v15.4.2/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.3.0 h1:hF6VlN15E9CB40RMPyqOIhlDw1OOo9RItumhKMQktxw=
|
||||
github.com/blevesearch/zapx/v16 v16.3.0/go.mod h1:zCFjv7McXWm1C8rROL+3mUoD5WYe2RKsZP3ufqcYpLY=
|
||||
github.com/blevesearch/zapx/v11 v11.4.3 h1:PTZOO5loKpHC/x/GzmPZNa9cw7GZIQxd5qRjwij9tHY=
|
||||
github.com/blevesearch/zapx/v11 v11.4.3/go.mod h1:4gdeyy9oGa/lLa6D34R9daXNUvfMPZqUYjPwiLmekwc=
|
||||
github.com/blevesearch/zapx/v12 v12.4.3 h1:eElXvAaAX4m04t//CGBQAtHNPA+Q6A1hHZVrN3LSFYo=
|
||||
github.com/blevesearch/zapx/v12 v12.4.3/go.mod h1:TdFmr7afSz1hFh/SIBCCZvcLfzYvievIH6aEISCte58=
|
||||
github.com/blevesearch/zapx/v13 v13.4.3 h1:qsdhRhaSpVnqDFlRiH9vG5+KJ+dE7KAW9WyZz/KXAiE=
|
||||
github.com/blevesearch/zapx/v13 v13.4.3/go.mod h1:knK8z2NdQHlb5ot/uj8wuvOq5PhDGjNYQQy0QDnopZk=
|
||||
github.com/blevesearch/zapx/v14 v14.4.3 h1:GY4Hecx0C6UTmiNC2pKdeA2rOKiLR5/rwpU9WR51dgM=
|
||||
github.com/blevesearch/zapx/v14 v14.4.3/go.mod h1:rz0XNb/OZSMjNorufDGSpFpjoFKhXmppH9Hi7a877D8=
|
||||
github.com/blevesearch/zapx/v15 v15.4.3 h1:iJiMJOHrz216jyO6lS0m9RTCEkprUnzvqAI2lc/0/CU=
|
||||
github.com/blevesearch/zapx/v15 v15.4.3/go.mod h1:1pssev/59FsuWcgSnTa0OeEpOzmhtmr/0/11H0Z8+Nw=
|
||||
github.com/blevesearch/zapx/v16 v16.3.4 h1:hDAqA8qusZTNbPEL7//w5P65UZ2de6yhSeUaTbp0Po0=
|
||||
github.com/blevesearch/zapx/v16 v16.3.4/go.mod h1:zqkPPqs9GS9FzVWzCO3Wf1X044yWAV17+4zb+FTiEHg=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
@@ -69,11 +69,8 @@ github.com/chromedp/chromedp v0.14.0 h1:/xE5m6wEBwivhalHwlCOyYfBcAJNwg4nLw96QiCf
|
||||
github.com/chromedp/chromedp v0.14.0/go.mod h1:rHzAv60xDE7VNy/MYtTUrYreSc0ujt2O1/C3bzctYBo=
|
||||
github.com/chromedp/sysutil v1.1.0 h1:PUFNv5EcprjqXZD9nJb9b/c9ibAbxiYo4exNWZyipwM=
|
||||
github.com/chromedp/sysutil v1.1.0/go.mod h1:WiThHUdltqCNKGc4gaU50XgYjwjYIhKWoHGPTUfWTJ8=
|
||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
|
||||
github.com/clipperhouse/uax29/v2 v2.4.0 h1:RXqE/l5EiAbA4u97giimKNlmpvkmz+GrBVTelsoXy9g=
|
||||
github.com/clipperhouse/uax29/v2 v2.4.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
@@ -82,12 +79,12 @@ 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/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
|
||||
github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
|
||||
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
@@ -96,35 +93,34 @@ github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GM
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo=
|
||||
github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2 h1:iizUGZ9pEquQS5jTGkh4AqeeHCMbfbjeb0zMt0aEFzs=
|
||||
github.com/go-json-experiment/json v0.0.0-20250725192818-e39067aee2d2/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||
github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4=
|
||||
github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13 h1:+x1nG9h+MZN7h/lUi5Q3UZ0fJ1GyDQYbPvbuH38baDQ=
|
||||
github.com/go-ldap/ldap/v3 v3.4.13/go.mod h1:LxsGZV6vbaK0sIvYfsv47rfh4ca0JXokCoKjZxsszv0=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
|
||||
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/go-webauthn/webauthn v0.16.0 h1:A9BkfYIwWAMPSQCbM2HoWqo6JO5LFI8aqYAzo6nW7AY=
|
||||
github.com/go-webauthn/webauthn v0.16.0/go.mod h1:hm9RS/JNYeUu3KqGbzqlnHClhDGCZzTZlABjathwnN0=
|
||||
github.com/go-webauthn/x v0.2.1 h1:/oB8i0FhSANuoN+YJF5XHMtppa7zGEYaQrrf6ytotjc=
|
||||
github.com/go-webauthn/x v0.2.1/go.mod h1:Wm0X0zXkzznit4gHj4m82GiBZRMEm+TDUIoJWIQLsE4=
|
||||
github.com/go-webauthn/webauthn v0.17.0 h1:8tFdaByIF7EgAg0W849Wt5q+213f1drsV2ggC0t80wM=
|
||||
github.com/go-webauthn/webauthn v0.17.0/go.mod h1:mQC6L0lZ5Kiu35G70zeB2WnrW4+vbHjR8Koq4HdVaMg=
|
||||
github.com/go-webauthn/x v0.2.3 h1:8oArS+Rc1SWFLXhE17KZNx258Z4kUSyaDgsSncCO5RA=
|
||||
github.com/go-webauthn/x v0.2.3/go.mod h1:tM04GF3V6VYq79AZMl7vbj4q6pz9r7L2criWRzbWhPk=
|
||||
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/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
|
||||
@@ -160,8 +156,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
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.8.0 h1:TYPDoleBBme0xGSAX3/+NujXXtpZn9HBONkQC7IEZSo=
|
||||
github.com/jackc/pgx/v5 v5.8.0/go.mod h1:QVeDInX2m9VyzvNeiCJVjCkNFqzsNb43204HshNSZKw=
|
||||
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/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
@@ -190,27 +186,24 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/labstack/echo-contrib v0.17.4 h1:g5mfsrJfJTKv+F5uNKCyrjLK7js+ZW6HTjg4FnDxxgk=
|
||||
github.com/labstack/echo-contrib v0.17.4/go.mod h1:9O7ZPAHUeMGTOAfg80YqQduHzt0CzLak36PZRldYrZ0=
|
||||
github.com/labstack/echo-contrib v0.50.1 h1:W9cZZ9viA4TDdFtm8cuA+XGFwOcnfbjJpl7VgfsRLHE=
|
||||
github.com/labstack/echo-contrib v0.50.1/go.mod h1:8r/++U/Fw/QniApFnzunLanKaviPfBX7fX7/2QX0qOk=
|
||||
github.com/labstack/echo/v4 v4.15.1 h1:S9keusg26gZpjMmPqB5hOEvNKnmd1lNmcHrbbH2lnFs=
|
||||
github.com/labstack/echo/v4 v4.15.1/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/labstack/gommon v0.5.0 h1:6VSQ2NOzsnEJ5W6+84E0RbcaDDmgB6NIAzWCczTEe6c=
|
||||
github.com/labstack/gommon v0.5.0/go.mod h1:Rzlg7HHy1maLfzBYGg9NZcVuz1sA68HHhLjhcEllYE0=
|
||||
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/markbates/goth v1.82.0 h1:8j/c34AjBSTNzO7zTsOyP5IYCQCMBTRBHAbBt/PI0bQ=
|
||||
github.com/markbates/goth v1.82.0/go.mod h1:/DRlcq0pyqkKToyZjsL2KgiA1zbF1HIjE7u2uC79rUk=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/meilisearch/meilisearch-go v0.36.1 h1:mJTCJE5g7tRvaqKco6DfqOuJEjX+rRltDEnkEC02Y0M=
|
||||
github.com/meilisearch/meilisearch-go v0.36.1/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
||||
github.com/mattn/go-isatty v0.0.21 h1:xYae+lCNBP7QuW4PUnNG61ffM4hVIfm+zUzDuSzYLGs=
|
||||
github.com/mattn/go-isatty v0.0.21/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||
github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw=
|
||||
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
||||
github.com/meilisearch/meilisearch-go v0.36.2 h1:MYaMPCpdLh2aYPt+zK+19mLoA4dfBY3S1L7T0FADCjU=
|
||||
github.com/meilisearch/meilisearch-go v0.36.2/go.mod h1:hWcR0MuWLSzHfbz9GGzIr3s9rnXLm1jqkmHkJPbUSvM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -222,7 +215,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
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/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
|
||||
@@ -233,15 +227,14 @@ github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNw
|
||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
|
||||
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
|
||||
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws=
|
||||
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
|
||||
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
|
||||
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY=
|
||||
github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ=
|
||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -249,6 +242,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
||||
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/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ=
|
||||
github.com/tinylib/msgp v1.6.4/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
|
||||
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
@@ -262,8 +257,8 @@ github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBi
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||
github.com/yuin/goldmark-emoji v1.0.6 h1:QWfF2FYaXwL74tfGOW5izeiZepUDroDJfWubQI9HTHs=
|
||||
github.com/yuin/goldmark-emoji v1.0.6/go.mod h1:ukxJDKFpdFb5x0a5HqbdlcKtebh086iJpI31LTKmWuA=
|
||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||
@@ -276,33 +271,28 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||
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/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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/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/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
|
||||
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -317,30 +307,30 @@ gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/cc/v4 v4.28.1 h1:XpLbkYVQ24E8tX5u8+yWGvaxerxkR/S4zqxI8ZoSBuc=
|
||||
modernc.org/cc/v4 v4.28.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.33.0 h1:dspBCm75jsj8Y/ufwAMVfe375L2iYdMyQ2QG/v3hL54=
|
||||
modernc.org/ccgo/v4 v4.33.0/go.mod h1:+RhXBoRYzRwaH21mV/aj6XvQRDtfjcZfAlPMsQo8CR0=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.67.7 h1:H+gYQw2PyidyxwxQsGTwQw6+6H+xUk+plvOKW7+d3TI=
|
||||
modernc.org/libc v1.67.7/go.mod h1:UjCSJFl2sYbJbReVQeVpq/MgzlbmDM4cRHIYFelnaDk=
|
||||
modernc.org/libc v1.72.1 h1:db1xwJ6u1kE3KHTFTTbe2GCrczHPKzlURP0aDC4NGD0=
|
||||
modernc.org/libc v1.72.1/go.mod h1:HRMiC/PhPGLIPM7GzAFCbI+oSgE3dhZ8FWftmRrHVlY=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY=
|
||||
modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
modernc.org/sqlite v1.50.0 h1:eMowQSWLK0MeiQTdmz3lqoF5dqclujdlIKeJA11+7oM=
|
||||
modernc.org/sqlite v1.50.0/go.mod h1:m0w8xhwYUVY3H6pSDwc3gkJ/irZT/0YEXwBlhaxQEew=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Helm Chart Changelog
|
||||
|
||||
# 0.7.0 - 2026-03-14
|
||||
|
||||
- Bump Opengist image to 1.12.2
|
||||
- Add environment variables and secrets to statefulset
|
||||
|
||||
## 0.6.0 - 2026-02-03
|
||||
|
||||
- Bump Opengist image to 1.12.1
|
||||
|
||||
@@ -3,7 +3,7 @@ name: opengist
|
||||
description: Opengist Helm chart for Kubernetes
|
||||
type: application
|
||||
version: 0.6.0
|
||||
appVersion: 1.12.1
|
||||
appVersion: 1.12.2
|
||||
home: https://opengist.io
|
||||
icon: https://raw.githubusercontent.com/thomiceli/opengist/master/public/img/opengist.svg
|
||||
sources:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Opengist Helm Chart
|
||||
|
||||
 
|
||||
 
|
||||
|
||||
Opengist Helm chart for Kubernetes. Check [CHANGELOG.md](CHANGELOG.md) for release notes.
|
||||
|
||||
|
||||
@@ -63,6 +63,19 @@ spec:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- if or .Values.deployment.env .Values.deployment.envFromSecrets }}
|
||||
env:
|
||||
{{- if .Values.deployment.env }}
|
||||
{{- toYaml .Values.deployment.env | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- range .Values.deployment.envFromSecrets }}
|
||||
- name: {{ .name }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .secretName }}
|
||||
key: {{ .secretKey }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.http.port }}
|
||||
|
||||
@@ -131,6 +131,19 @@ spec:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
||||
{{- if or .Values.deployment.env .Values.deployment.envFromSecrets }}
|
||||
env:
|
||||
{{- if .Values.deployment.env }}
|
||||
{{- toYaml .Values.deployment.env | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- range .Values.deployment.envFromSecrets }}
|
||||
- name: {{ .name }}
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .secretName }}
|
||||
key: {{ .secretKey }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.service.http.port }}
|
||||
|
||||
@@ -18,7 +18,7 @@ configExistingSecret: ""
|
||||
image:
|
||||
repository: ghcr.io/thomiceli/opengist
|
||||
pullPolicy: Always
|
||||
tag: "1.12.1"
|
||||
tag: "1.12.2"
|
||||
digest: ""
|
||||
imagePullSecrets: []
|
||||
# - name: "image-pull-secret"
|
||||
@@ -258,6 +258,13 @@ autoscaling:
|
||||
## Additional deployment configuration
|
||||
deployment:
|
||||
env: []
|
||||
## Load environment variables from specific secret keys
|
||||
## Each entry creates an env.valueFrom.secretKeyRef in the container spec
|
||||
## ref: https://kubernetes.io/docs/concepts/configuration/secret/#using-secrets-as-environment-variables
|
||||
envFromSecrets: []
|
||||
# - name: OG_OIDC_SECRET
|
||||
# secretName: opengist-oidc-client-secret
|
||||
# secretKey: client_secret
|
||||
terminationGracePeriodSeconds: 60
|
||||
labels: {}
|
||||
annotations: {}
|
||||
|
||||
@@ -2,16 +2,15 @@ package oauth
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
gojson "encoding/json"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/gothic"
|
||||
"github.com/markbates/goth/providers/gitea"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/config"
|
||||
"github.com/thomiceli/opengist/internal/db"
|
||||
"github.com/thomiceli/opengist/internal/web/context"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type GiteaProvider struct {
|
||||
@@ -80,34 +79,7 @@ func (p *GiteaCallbackProvider) GetProviderUserSSHKeys() ([]string, error) {
|
||||
|
||||
func (p *GiteaCallbackProvider) UpdateUserDB(user *db.User) {
|
||||
user.GiteaID = p.User.UserID
|
||||
|
||||
resp, err := http.Get(urlJoin(config.C.GiteaUrl, "/api/v1/users/", p.User.UserID))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot get user from Gitea")
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot read Gitea response body")
|
||||
return
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
err = gojson.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Cannot unmarshal Gitea response body")
|
||||
return
|
||||
}
|
||||
|
||||
field, ok := result["avatar_url"]
|
||||
if !ok {
|
||||
log.Error().Msg("Field 'avatar_url' not found in Gitea JSON response")
|
||||
return
|
||||
}
|
||||
|
||||
user.AvatarURL = field.(string)
|
||||
user.AvatarURL = p.User.AvatarURL
|
||||
}
|
||||
|
||||
func (p *GiteaCallbackProvider) IsAdmin() bool {
|
||||
|
||||
@@ -38,11 +38,12 @@ type config struct {
|
||||
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"` // deprecated
|
||||
Index string `yaml:"index" env:"OG_INDEX"`
|
||||
BleveDirname string `yaml:"index.dirname" env:"OG_INDEX_DIRNAME"` // deprecated
|
||||
MeiliHost string `yaml:"index.meili.host" env:"OG_MEILI_HOST"`
|
||||
MeiliAPIKey string `yaml:"index.meili.api-key" env:"OG_MEILI_API_KEY"`
|
||||
IndexEnabled bool `yaml:"index.enabled" env:"OG_INDEX_ENABLED"` // deprecated
|
||||
Index string `yaml:"index" env:"OG_INDEX"`
|
||||
BleveDirname string `yaml:"index.dirname" env:"OG_INDEX_DIRNAME"` // deprecated
|
||||
MeiliHost string `yaml:"index.meili.host" env:"OG_MEILI_HOST"`
|
||||
MeiliAPIKey string `yaml:"index.meili.api-key" env:"OG_MEILI_API_KEY"`
|
||||
SearchDefault string `yaml:"search.default" env:"OG_SEARCH_DEFAULT"`
|
||||
|
||||
GitDefaultBranch string `yaml:"git.default-branch" env:"OG_GIT_DEFAULT_BRANCH"`
|
||||
|
||||
@@ -111,6 +112,7 @@ func configWithDefaults() (*config, error) {
|
||||
c.OpengistHome = ""
|
||||
c.DBUri = "opengist.db"
|
||||
c.Index = "bleve"
|
||||
c.SearchDefault = "content"
|
||||
|
||||
c.SqliteJournalMode = "WAL"
|
||||
|
||||
|
||||
@@ -817,18 +817,19 @@ func (gist *Gist) ToIndexedGist() (*index.Gist, error) {
|
||||
}
|
||||
|
||||
indexedGist := &index.Gist{
|
||||
GistID: gist.ID,
|
||||
UserID: gist.UserID,
|
||||
Visibility: gist.Private.Uint(),
|
||||
Username: gist.User.Username,
|
||||
Title: gist.Title,
|
||||
Content: wholeContent,
|
||||
Filenames: fileNames,
|
||||
Extensions: exts,
|
||||
Languages: langs,
|
||||
Topics: topics,
|
||||
CreatedAt: gist.CreatedAt,
|
||||
UpdatedAt: gist.UpdatedAt,
|
||||
GistID: gist.ID,
|
||||
UserID: gist.UserID,
|
||||
Visibility: gist.Private.Uint(),
|
||||
Username: gist.User.Username,
|
||||
Description: gist.Description,
|
||||
Title: gist.Title,
|
||||
Content: wholeContent,
|
||||
Filenames: fileNames,
|
||||
Extensions: exts,
|
||||
Languages: langs,
|
||||
Topics: topics,
|
||||
CreatedAt: gist.CreatedAt,
|
||||
UpdatedAt: gist.UpdatedAt,
|
||||
}
|
||||
|
||||
return indexedGist, nil
|
||||
|
||||
@@ -88,10 +88,12 @@ gist.search.found: gists found
|
||||
gist.search.no-results: No gists found
|
||||
gist.search.help.user: gists created by user
|
||||
gist.search.help.title: gists with given title
|
||||
gist.search.help.description: gists with given description
|
||||
gist.search.help.filename: gists having files with given name
|
||||
gist.search.help.extension: gists having files with given extension
|
||||
gist.search.help.language: gists having files with given language
|
||||
gist.search.help.topic: gists with given topic
|
||||
gist.search.help.all: search all fields
|
||||
gist.search.placeholder.title: Title
|
||||
gist.search.placeholder.visibility: Visibility
|
||||
gist.search.placeholder.public: Public
|
||||
|
||||
@@ -77,7 +77,7 @@ gist.list.all-from: Всі gists від %s
|
||||
gist.search.found: gists знайдено
|
||||
gist.search.no-results: Не знайдено gists
|
||||
gist.search.help.user: gists створені користувачем
|
||||
gist.search.help.title: gists з наданим ім'ям
|
||||
gist.search.help.title: gists з наданим ім'ям
|
||||
gist.search.help.filename: gists мають файли з наданим ім'ям
|
||||
gist.search.help.extension: gists мають файли з наданим розширенням
|
||||
gist.search.help.language: gists мають файли з наданою мовою
|
||||
@@ -266,4 +266,4 @@ validation.should-only-contain-alphanumeric-characters-and-dashes: Поле %s
|
||||
validation.not-enough: Недостатньо %s
|
||||
validation.invalid: Недійсний %s
|
||||
|
||||
html.title.admin-panel: Панель адміністратора
|
||||
html.title.admin-panel: Панель адміністратора
|
||||
|
||||
@@ -5,15 +5,18 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/blevesearch/bleve/v2"
|
||||
"github.com/blevesearch/bleve/v2/analysis/analyzer/custom"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/camelcase"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/length"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/lowercase"
|
||||
"github.com/blevesearch/bleve/v2/analysis/token/unicodenorm"
|
||||
"github.com/blevesearch/bleve/v2/analysis/tokenizer/unicode"
|
||||
"github.com/blevesearch/bleve/v2/search/query"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/config"
|
||||
)
|
||||
|
||||
type BleveIndexer struct {
|
||||
@@ -54,14 +57,9 @@ func (i *BleveIndexer) open() (bleve.Index, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docMapping := bleve.NewDocumentMapping()
|
||||
docMapping.AddFieldMappingsAt("GistID", bleve.NewNumericFieldMapping())
|
||||
docMapping.AddFieldMappingsAt("UserID", bleve.NewNumericFieldMapping())
|
||||
docMapping.AddFieldMappingsAt("Visibility", bleve.NewNumericFieldMapping())
|
||||
docMapping.AddFieldMappingsAt("Content", bleve.NewTextFieldMapping())
|
||||
|
||||
mapping := bleve.NewIndexMapping()
|
||||
|
||||
// Token filters
|
||||
if err = mapping.AddCustomTokenFilter("unicodeNormalize", map[string]any{
|
||||
"type": unicodenorm.Name,
|
||||
"form": unicodenorm.NFC,
|
||||
@@ -69,16 +67,74 @@ func (i *BleveIndexer) open() (bleve.Index, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = mapping.AddCustomAnalyzer("gistAnalyser", map[string]interface{}{
|
||||
"type": custom.Name,
|
||||
"char_filters": []string{},
|
||||
"tokenizer": unicode.Name,
|
||||
"token_filters": []string{"unicodeNormalize", camelcase.Name, lowercase.Name},
|
||||
if err = mapping.AddCustomTokenFilter("lengthMin2", map[string]interface{}{
|
||||
"type": length.Name,
|
||||
"min": 2.0,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
docMapping.DefaultAnalyzer = "gistAnalyser"
|
||||
// Analyzer: split mode (camelCase splitting for partial search)
|
||||
// "CPUCard" -> ["cpu", "card"]
|
||||
if err = mapping.AddCustomAnalyzer("codeSplit", map[string]interface{}{
|
||||
"type": custom.Name,
|
||||
"char_filters": []string{},
|
||||
"tokenizer": unicode.Name,
|
||||
"token_filters": []string{"unicodeNormalize", camelcase.Name, lowercase.Name, "lengthMin2"},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Analyzer: exact mode (no camelCase splitting for full-word search)
|
||||
// "CPUCard" -> ["cpucard"]
|
||||
if err = mapping.AddCustomAnalyzer("codeExact", map[string]interface{}{
|
||||
"type": custom.Name,
|
||||
"char_filters": []string{},
|
||||
"tokenizer": unicode.Name,
|
||||
"token_filters": []string{"unicodeNormalize", lowercase.Name},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Analyzer: keyword with lowercase (for Languages - single token, no splitting)
|
||||
if err = mapping.AddCustomAnalyzer("lowercaseKeyword", map[string]interface{}{
|
||||
"type": custom.Name,
|
||||
"char_filters": []string{},
|
||||
"tokenizer": "single",
|
||||
"token_filters": []string{lowercase.Name},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Document mapping
|
||||
docMapping := bleve.NewDocumentMapping()
|
||||
docMapping.AddFieldMappingsAt("GistID", bleve.NewNumericFieldMapping())
|
||||
docMapping.AddFieldMappingsAt("UserID", bleve.NewNumericFieldMapping())
|
||||
docMapping.AddFieldMappingsAt("Visibility", bleve.NewNumericFieldMapping())
|
||||
|
||||
// Content: dual indexing (exact + split)
|
||||
// "Content" uses the property name so Bleve resolves its analyzer correctly
|
||||
contentExact := bleve.NewTextFieldMapping()
|
||||
contentExact.Name = "Content"
|
||||
contentExact.Analyzer = "codeExact"
|
||||
contentExact.Store = false
|
||||
contentExact.IncludeTermVectors = true
|
||||
|
||||
contentSplit := bleve.NewTextFieldMapping()
|
||||
contentSplit.Name = "ContentSplit"
|
||||
contentSplit.Analyzer = "codeSplit"
|
||||
contentSplit.Store = false
|
||||
contentSplit.IncludeTermVectors = true
|
||||
|
||||
docMapping.AddFieldMappingsAt("Content", contentExact, contentSplit)
|
||||
|
||||
// Languages: keyword analyzer (preserves as single token)
|
||||
languageFieldMapping := bleve.NewTextFieldMapping()
|
||||
languageFieldMapping.Analyzer = "lowercaseKeyword"
|
||||
docMapping.AddFieldMappingsAt("Languages", languageFieldMapping)
|
||||
|
||||
// All other text fields use codeSplit as default
|
||||
docMapping.DefaultAnalyzer = "codeSplit"
|
||||
mapping.DefaultMapping = docMapping
|
||||
|
||||
return bleve.New(i.path, mapping)
|
||||
@@ -116,18 +172,111 @@ func (i *BleveIndexer) Remove(gistID uint) error {
|
||||
return (*atomicIndexer.Load()).(*BleveIndexer).index.Delete(strconv.Itoa(int(gistID)))
|
||||
}
|
||||
|
||||
func (i *BleveIndexer) Search(queryStr string, queryMetadata SearchGistMetadata, userId uint, page int) ([]uint, uint64, map[string]int, error) {
|
||||
// Search returns a list of Gist IDs that match the given search metadata.
|
||||
// The method returns an error if any.
|
||||
//
|
||||
// The queryMetadata parameter is used to filter the search results.
|
||||
// For example, passing a non-empty Username will search for gists whose
|
||||
// username matches the given string.
|
||||
//
|
||||
// If the "All" field in queryMetadata is non-empty, the method will
|
||||
// search across all metadata fields with OR logic. Otherwise, the method
|
||||
// will add each metadata field with AND logic.
|
||||
//
|
||||
// The page parameter is used to paginate the search results.
|
||||
// The method returns the total number of search results in the second return
|
||||
// value.
|
||||
//
|
||||
// The third return value is a map of language counts for the search results.
|
||||
// The language counts are computed by asking Bleve to return the top 10
|
||||
// facets for the "Languages" field.
|
||||
func (i *BleveIndexer) Search(metadata SearchGistMetadata, userId uint, page int) ([]uint, uint64, map[string]int, error) {
|
||||
var err error
|
||||
var indexerQuery query.Query
|
||||
if queryStr != "" {
|
||||
// Use match query with fuzzy matching for more flexible content search
|
||||
contentQuery := bleve.NewMatchQuery(queryStr)
|
||||
contentQuery.SetField("Content")
|
||||
contentQuery.SetFuzziness(2)
|
||||
indexerQuery = contentQuery
|
||||
} else {
|
||||
contentQuery := bleve.NewMatchAllQuery()
|
||||
indexerQuery = contentQuery
|
||||
var indexerQuery query.Query = bleve.NewMatchAllQuery()
|
||||
|
||||
// Query factory
|
||||
factoryQuery := func(field, value string) query.Query {
|
||||
query := bleve.NewMatchPhraseQuery(value)
|
||||
query.SetField(field)
|
||||
return query
|
||||
}
|
||||
|
||||
// Exact search
|
||||
addQuery := func(field, value string) {
|
||||
if value != "" && value != "." {
|
||||
indexerQuery = bleve.NewConjunctionQuery(indexerQuery, factoryQuery(field, value))
|
||||
}
|
||||
}
|
||||
|
||||
// Query factory for text fields: exact match boosted + match query + prefix
|
||||
factoryTextQuery := func(field, value string) query.Query {
|
||||
exact := bleve.NewMatchPhraseQuery(value)
|
||||
exact.SetField(field)
|
||||
exact.SetBoost(2.0)
|
||||
|
||||
fuzzy := bleve.NewMatchQuery(value)
|
||||
fuzzy.SetField(field)
|
||||
fuzzy.SetFuzziness(1)
|
||||
fuzzy.SetOperator(query.MatchQueryOperatorAnd)
|
||||
|
||||
queries := []query.Query{exact, fuzzy}
|
||||
|
||||
if len([]rune(value)) >= 2 {
|
||||
prefix := bleve.NewPrefixQuery(strings.ToLower(value))
|
||||
prefix.SetField(field)
|
||||
prefix.SetBoost(1.5)
|
||||
queries = append(queries, prefix)
|
||||
}
|
||||
|
||||
if len([]rune(value)) >= 4 {
|
||||
wildcard := bleve.NewWildcardQuery("*" + strings.ToLower(value) + "*")
|
||||
wildcard.SetField(field)
|
||||
wildcard.SetBoost(0.5)
|
||||
queries = append(queries, wildcard)
|
||||
}
|
||||
|
||||
return bleve.NewDisjunctionQuery(queries...)
|
||||
}
|
||||
|
||||
// Query factory for Content: searches both exact (Content) and split (ContentSplit) fields
|
||||
factoryContentQuery := func(value string) query.Query {
|
||||
// Exact field (no camelCase split): matches "cpucard"
|
||||
exactMatch := bleve.NewMatchQuery(value)
|
||||
exactMatch.SetField("Content")
|
||||
exactMatch.SetOperator(query.MatchQueryOperatorAnd)
|
||||
exactMatch.SetBoost(2.0)
|
||||
|
||||
// Split field (camelCase split): matches "cpu", "card"
|
||||
splitMatch := bleve.NewMatchQuery(value)
|
||||
splitMatch.SetField("ContentSplit")
|
||||
splitMatch.SetFuzziness(1)
|
||||
splitMatch.SetOperator(query.MatchQueryOperatorAnd)
|
||||
splitMatch.SetBoost(1.0)
|
||||
|
||||
queries := []query.Query{exactMatch, splitMatch}
|
||||
|
||||
if len([]rune(value)) >= 2 {
|
||||
prefix := bleve.NewPrefixQuery(strings.ToLower(value))
|
||||
prefix.SetField("Content")
|
||||
prefix.SetBoost(1.5)
|
||||
queries = append(queries, prefix)
|
||||
}
|
||||
|
||||
if len([]rune(value)) >= 4 {
|
||||
wildcard := bleve.NewWildcardQuery("*" + strings.ToLower(value) + "*")
|
||||
wildcard.SetField("Content")
|
||||
wildcard.SetBoost(0.5)
|
||||
queries = append(queries, wildcard)
|
||||
}
|
||||
|
||||
return bleve.NewDisjunctionQuery(queries...)
|
||||
}
|
||||
|
||||
// Text field search
|
||||
addTextQuery := func(field, value string) {
|
||||
if value != "" && value != "." {
|
||||
indexerQuery = bleve.NewConjunctionQuery(indexerQuery, factoryTextQuery(field, value))
|
||||
}
|
||||
}
|
||||
|
||||
// Visibility filtering: show public gists (Visibility=0) OR user's own gists
|
||||
@@ -143,48 +292,62 @@ func (i *BleveIndexer) Search(queryStr string, queryMetadata SearchGistMetadata,
|
||||
accessQuery := bleve.NewDisjunctionQuery(publicQuery, userIdQuery)
|
||||
indexerQuery = bleve.NewConjunctionQuery(accessQuery, indexerQuery)
|
||||
|
||||
buildFieldQuery := func(field, value string) query.Query {
|
||||
switch field {
|
||||
case "Content":
|
||||
return factoryContentQuery(value)
|
||||
case "Title", "Description", "Filenames":
|
||||
return factoryTextQuery(field, value)
|
||||
case "Extensions":
|
||||
return factoryQuery(field, "."+value)
|
||||
default: // Username, Languages, Topics
|
||||
return factoryQuery(field, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle "All" field - search across all metadata fields with OR logic
|
||||
if queryMetadata.All != "" {
|
||||
allQueries := make([]query.Query, 0)
|
||||
|
||||
// Create match phrase queries for each field
|
||||
fields := []struct {
|
||||
field string
|
||||
value string
|
||||
}{
|
||||
{"Username", queryMetadata.All},
|
||||
{"Title", queryMetadata.All},
|
||||
{"Extensions", "." + queryMetadata.All},
|
||||
{"Filenames", queryMetadata.All},
|
||||
{"Languages", queryMetadata.All},
|
||||
{"Topics", queryMetadata.All},
|
||||
if metadata.All != "" {
|
||||
allQueries := make([]query.Query, 0, len(AllSearchFields))
|
||||
for _, field := range AllSearchFields {
|
||||
allQueries = append(allQueries, buildFieldQuery(field, metadata.All))
|
||||
}
|
||||
|
||||
for _, f := range fields {
|
||||
q := bleve.NewMatchPhraseQuery(f.value)
|
||||
q.FieldVal = f.field
|
||||
allQueries = append(allQueries, q)
|
||||
}
|
||||
|
||||
// Combine all field queries with OR (disjunction)
|
||||
allDisjunction := bleve.NewDisjunctionQuery(allQueries...)
|
||||
indexerQuery = bleve.NewConjunctionQuery(indexerQuery, allDisjunction)
|
||||
indexerQuery = bleve.NewConjunctionQuery(indexerQuery, bleve.NewDisjunctionQuery(allQueries...))
|
||||
} else {
|
||||
// Original behavior: add each metadata field with AND logic
|
||||
addQuery := func(field, value string) {
|
||||
if value != "" && value != "." {
|
||||
q := bleve.NewMatchPhraseQuery(value)
|
||||
q.FieldVal = field
|
||||
indexerQuery = bleve.NewConjunctionQuery(indexerQuery, q)
|
||||
}
|
||||
addQuery("Username", metadata.Username)
|
||||
addTextQuery("Title", metadata.Title)
|
||||
addTextQuery("Description", metadata.Description)
|
||||
addQuery("Extensions", "."+metadata.Extension)
|
||||
addTextQuery("Filenames", metadata.Filename)
|
||||
addQuery("Languages", metadata.Language)
|
||||
addQuery("Topics", metadata.Topic)
|
||||
if metadata.Content != "" {
|
||||
indexerQuery = bleve.NewConjunctionQuery(indexerQuery, factoryContentQuery(metadata.Content))
|
||||
}
|
||||
|
||||
addQuery("Username", queryMetadata.Username)
|
||||
addQuery("Title", queryMetadata.Title)
|
||||
addQuery("Extensions", "."+queryMetadata.Extension)
|
||||
addQuery("Filenames", queryMetadata.Filename)
|
||||
addQuery("Languages", queryMetadata.Language)
|
||||
addQuery("Topics", queryMetadata.Topic)
|
||||
// Handle default search fields from config with OR logic
|
||||
if metadata.Default != "" {
|
||||
var fields []string
|
||||
for _, f := range strings.Split(config.C.SearchDefault, ",") {
|
||||
f = strings.TrimSpace(f)
|
||||
if f == "all" {
|
||||
fields = AllSearchFields
|
||||
break
|
||||
}
|
||||
if indexField, ok := SearchFieldMap[f]; ok {
|
||||
fields = append(fields, indexField)
|
||||
}
|
||||
}
|
||||
if len(fields) == 1 {
|
||||
indexerQuery = bleve.NewConjunctionQuery(indexerQuery, buildFieldQuery(fields[0], metadata.Default))
|
||||
} else if len(fields) > 1 {
|
||||
defaultQueries := make([]query.Query, 0, len(fields))
|
||||
for _, field := range fields {
|
||||
defaultQueries = append(defaultQueries, buildFieldQuery(field, metadata.Default))
|
||||
}
|
||||
indexerQuery = bleve.NewConjunctionQuery(indexerQuery, bleve.NewDisjunctionQuery(defaultQueries...))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
languageFacet := bleve.NewFacetRequest("Languages", 10)
|
||||
@@ -197,6 +360,8 @@ func (i *BleveIndexer) Search(queryStr string, queryMetadata SearchGistMetadata,
|
||||
s.Fields = []string{"GistID"}
|
||||
s.IncludeLocations = false
|
||||
|
||||
log.Debug().Interface("searchRequest", s).Msg("Bleve search request")
|
||||
|
||||
results, err := (*atomicIndexer.Load()).(*BleveIndexer).index.Search(s)
|
||||
if err != nil {
|
||||
return nil, 0, nil, err
|
||||
|
||||
@@ -4,33 +4,31 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// setupBleveIndexer creates a new BleveIndexer for testing
|
||||
func setupBleveIndexer(t *testing.T) (*BleveIndexer, func()) {
|
||||
func setupBleveIndexer(t *testing.T) (Indexer, func()) {
|
||||
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||
t.Helper()
|
||||
|
||||
// Create a temporary directory for the test index
|
||||
tmpDir, err := os.MkdirTemp("", "bleve-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp directory: %v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
indexPath := filepath.Join(tmpDir, "test.index")
|
||||
indexer := NewBleveIndexer(indexPath)
|
||||
|
||||
// Initialize the indexer
|
||||
err = indexer.Init()
|
||||
if err != nil {
|
||||
os.RemoveAll(tmpDir)
|
||||
t.Fatalf("Failed to initialize BleveIndexer: %v", err)
|
||||
}
|
||||
|
||||
// Store in the global atomicIndexer since Add/Remove use it
|
||||
var idx Indexer = indexer
|
||||
atomicIndexer.Store(&idx)
|
||||
|
||||
// Return cleanup function
|
||||
cleanup := func() {
|
||||
atomicIndexer.Store(nil)
|
||||
indexer.Close()
|
||||
@@ -40,123 +38,50 @@ func setupBleveIndexer(t *testing.T) (*BleveIndexer, func()) {
|
||||
return indexer, cleanup
|
||||
}
|
||||
|
||||
func TestBleveIndexerAddGist(t *testing.T) {
|
||||
indexer, cleanup := setupBleveIndexer(t)
|
||||
defer cleanup()
|
||||
func TestBleveAddAndSearch(t *testing.T) { testAddAndSearch(t, setupBleveIndexer) }
|
||||
func TestBleveAccessControl(t *testing.T) { testAccessControl(t, setupBleveIndexer) }
|
||||
func TestBleveMetadataFilters(t *testing.T) { testMetadataFilters(t, setupBleveIndexer) }
|
||||
func TestBleveAllFieldSearch(t *testing.T) { testAllFieldSearch(t, setupBleveIndexer) }
|
||||
func TestBleveFuzzySearch(t *testing.T) { testFuzzySearch(t, setupBleveIndexer) }
|
||||
func TestBleveContentSearch(t *testing.T) { testContentSearch(t, setupBleveIndexer) }
|
||||
func TestBlevePagination(t *testing.T) { testPagination(t, setupBleveIndexer) }
|
||||
func TestBleveLanguageFacets(t *testing.T) { testLanguageFacets(t, setupBleveIndexer) }
|
||||
func TestBleveWildcardSearch(t *testing.T) { testWildcardSearch(t, setupBleveIndexer) }
|
||||
func TestBleveMetadataOnlySearch(t *testing.T) { testMetadataOnlySearch(t, setupBleveIndexer) }
|
||||
func TestBleveTitleFuzzySearch(t *testing.T) { testTitleFuzzySearch(t, setupBleveIndexer) }
|
||||
func TestBleveMultiLanguageFacets(t *testing.T) { testMultiLanguageFacets(t, setupBleveIndexer) }
|
||||
|
||||
testIndexerAddGist(t, indexer)
|
||||
}
|
||||
|
||||
func TestBleveIndexerAllFieldSearch(t *testing.T) {
|
||||
indexer, cleanup := setupBleveIndexer(t)
|
||||
defer cleanup()
|
||||
|
||||
testIndexerAllFieldSearch(t, indexer)
|
||||
}
|
||||
|
||||
func TestBleveIndexerFuzzySearch(t *testing.T) {
|
||||
indexer, cleanup := setupBleveIndexer(t)
|
||||
defer cleanup()
|
||||
|
||||
testIndexerFuzzySearch(t, indexer)
|
||||
}
|
||||
|
||||
func TestBleveIndexerSearchBasic(t *testing.T) {
|
||||
indexer, cleanup := setupBleveIndexer(t)
|
||||
defer cleanup()
|
||||
|
||||
testIndexerSearchBasic(t, indexer)
|
||||
}
|
||||
|
||||
func TestBleveIndexerPagination(t *testing.T) {
|
||||
indexer, cleanup := setupBleveIndexer(t)
|
||||
defer cleanup()
|
||||
|
||||
testIndexerPagination(t, indexer)
|
||||
}
|
||||
|
||||
// TestBleveIndexerInitAndClose tests Bleve-specific initialization and closing
|
||||
func TestBleveIndexerInitAndClose(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "bleve-init-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp directory: %v", err)
|
||||
}
|
||||
func TestBlevePersistence(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "bleve-persist-test-*")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
indexPath := filepath.Join(tmpDir, "test.index")
|
||||
indexer := NewBleveIndexer(indexPath)
|
||||
|
||||
// Test initialization
|
||||
err = indexer.Init()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to initialize BleveIndexer: %v", err)
|
||||
}
|
||||
// Create and populate index
|
||||
indexer1 := NewBleveIndexer(indexPath)
|
||||
require.NoError(t, indexer1.Init())
|
||||
|
||||
if indexer.index == nil {
|
||||
t.Fatal("Expected index to be initialized, got nil")
|
||||
}
|
||||
var idx Indexer = indexer1
|
||||
atomicIndexer.Store(&idx)
|
||||
|
||||
// Test closing
|
||||
indexer.Close()
|
||||
g := newGist(1, 1, 0, "persistent data survives restart")
|
||||
require.NoError(t, indexer1.Add(g))
|
||||
|
||||
// Test reopening the same index
|
||||
indexer1.Close()
|
||||
atomicIndexer.Store(nil)
|
||||
|
||||
// Reopen at same path
|
||||
indexer2 := NewBleveIndexer(indexPath)
|
||||
err = indexer2.Init()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to reopen BleveIndexer: %v", err)
|
||||
}
|
||||
require.NoError(t, indexer2.Init())
|
||||
defer indexer2.Close()
|
||||
|
||||
if indexer2.index == nil {
|
||||
t.Fatal("Expected reopened index to be initialized, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestBleveIndexerUnicodeSearch tests that Unicode content can be indexed and searched
|
||||
func TestBleveIndexerUnicodeSearch(t *testing.T) {
|
||||
indexer, cleanup := setupBleveIndexer(t)
|
||||
defer cleanup()
|
||||
|
||||
// Add a gist with Unicode content
|
||||
gist := &Gist{
|
||||
GistID: 100,
|
||||
UserID: 100,
|
||||
Visibility: 0,
|
||||
Username: "testuser",
|
||||
Title: "Unicode Test",
|
||||
Content: "Hello world with unicode characters: café résumé naïve",
|
||||
Filenames: []string{"test.txt"},
|
||||
Extensions: []string{".txt"},
|
||||
Languages: []string{"Text"},
|
||||
Topics: []string{"unicode"},
|
||||
CreatedAt: 1234567890,
|
||||
UpdatedAt: 1234567890,
|
||||
}
|
||||
|
||||
err := indexer.Add(gist)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add gist: %v", err)
|
||||
}
|
||||
|
||||
// Search for unicode content
|
||||
gistIDs, total, _, err := indexer.Search("café", SearchGistMetadata{}, 100, 1)
|
||||
if err != nil {
|
||||
t.Fatalf("Search failed: %v", err)
|
||||
}
|
||||
|
||||
if total == 0 {
|
||||
t.Skip("Unicode search may require specific index configuration")
|
||||
return
|
||||
}
|
||||
|
||||
found := false
|
||||
for _, id := range gistIDs {
|
||||
if id == 100 {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Log("Unicode gist not found in search results, but other results were returned")
|
||||
}
|
||||
idx = indexer2
|
||||
atomicIndexer.Store(&idx)
|
||||
defer atomicIndexer.Store(nil)
|
||||
|
||||
ids, total, _, err := indexer2.Search(SearchGistMetadata{Content: "persistent"}, 1, 1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, uint64(1), total, "data should survive close+reopen")
|
||||
require.Equal(t, uint(1), ids[0])
|
||||
}
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
package index
|
||||
|
||||
var AllSearchFields = []string{"Username", "Title", "Description", "Filenames", "Extensions", "Languages", "Topics", "Content"}
|
||||
|
||||
var SearchFieldMap = map[string]string{
|
||||
"user": "Username",
|
||||
"title": "Title",
|
||||
"description": "Description",
|
||||
"filename": "Filenames",
|
||||
"extension": "Extensions",
|
||||
"language": "Languages",
|
||||
"topic": "Topics",
|
||||
"content": "Content",
|
||||
}
|
||||
|
||||
type Gist struct {
|
||||
GistID uint
|
||||
UserID uint
|
||||
Visibility uint
|
||||
Username string
|
||||
Title string
|
||||
Content string
|
||||
Filenames []string
|
||||
Extensions []string
|
||||
Languages []string
|
||||
Topics []string
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
GistID uint
|
||||
UserID uint
|
||||
Visibility uint
|
||||
Username string
|
||||
Description string
|
||||
Title string
|
||||
Content string
|
||||
Filenames []string
|
||||
Extensions []string
|
||||
Languages []string
|
||||
Topics []string
|
||||
CreatedAt int64
|
||||
UpdatedAt int64
|
||||
}
|
||||
|
||||
type SearchGistMetadata struct {
|
||||
Username string
|
||||
Title string
|
||||
Filename string
|
||||
Extension string
|
||||
Language string
|
||||
Topic string
|
||||
All string
|
||||
Username string
|
||||
Title string
|
||||
Description string
|
||||
Content string
|
||||
Filename string
|
||||
Extension string
|
||||
Language string
|
||||
Topic string
|
||||
All string
|
||||
Default string
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ type Indexer interface {
|
||||
Reset() error
|
||||
Add(gist *Gist) error
|
||||
Remove(gistID uint) error
|
||||
Search(query string, metadata SearchGistMetadata, userId uint, page int) ([]uint, uint64, map[string]int, error)
|
||||
Search(metadata SearchGistMetadata, userId uint, page int) ([]uint, uint64, map[string]int, error)
|
||||
}
|
||||
|
||||
type IndexerType string
|
||||
@@ -125,7 +125,11 @@ func RemoveFromIndex(gistID uint) error {
|
||||
return (*idx).Remove(gistID)
|
||||
}
|
||||
|
||||
func SearchGists(query string, metadata SearchGistMetadata, userId uint, page int) ([]uint, uint64, map[string]int, error) {
|
||||
// SearchGists returns a list of Gist IDs that match the given search metadata.
|
||||
// If the indexer is not enabled, it returns nil, 0, nil, nil.
|
||||
// If the indexer is not initialized, it returns nil, 0, nil, fmt.Errorf("indexer is not initialized").
|
||||
// The function returns an error if any.
|
||||
func SearchGists(metadata SearchGistMetadata, userId uint, page int) ([]uint, uint64, map[string]int, error) {
|
||||
if !IndexEnabled() {
|
||||
return nil, 0, nil, nil
|
||||
}
|
||||
@@ -135,7 +139,7 @@ func SearchGists(query string, metadata SearchGistMetadata, userId uint, page in
|
||||
return nil, 0, nil, fmt.Errorf("indexer is not initialized")
|
||||
}
|
||||
|
||||
return (*idx).Search(query, metadata, userId, page)
|
||||
return (*idx).Search(metadata, userId, page)
|
||||
}
|
||||
|
||||
func DepreactionIndexDirname() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,9 +6,11 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/config"
|
||||
)
|
||||
|
||||
type MeiliIndexer struct {
|
||||
@@ -50,23 +52,25 @@ func (i *MeiliIndexer) open() (meilisearch.IndexManager, error) {
|
||||
i.client = meilisearch.New(i.host, meilisearch.WithAPIKey(i.apikey))
|
||||
indexResult, err := i.client.GetIndex(i.indexName)
|
||||
|
||||
if indexResult != nil && err == nil {
|
||||
return indexResult.IndexManager, nil
|
||||
}
|
||||
|
||||
_, err = i.client.CreateIndex(&meilisearch.IndexConfig{
|
||||
Uid: i.indexName,
|
||||
PrimaryKey: "GistID",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if indexResult == nil || err != nil {
|
||||
_, err = i.client.CreateIndex(&meilisearch.IndexConfig{
|
||||
Uid: i.indexName,
|
||||
PrimaryKey: "GistID",
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = i.client.Index(i.indexName).UpdateSettings(&meilisearch.Settings{
|
||||
FilterableAttributes: []string{"GistID", "UserID", "Visibility", "Username", "Title", "Filenames", "Extensions", "Languages", "Topics"},
|
||||
DisplayedAttributes: []string{"GistID"},
|
||||
SearchableAttributes: []string{"Content", "Username", "Title", "Filenames", "Extensions", "Languages", "Topics"},
|
||||
RankingRules: []string{"words"},
|
||||
FilterableAttributes: []string{"GistID", "UserID", "Visibility", "Username", "Extensions", "Languages", "Topics"},
|
||||
SearchableAttributes: []string{"Content", "ContentSplit", "Username", "Title", "Description", "Filenames", "Extensions", "Languages", "Topics"},
|
||||
RankingRules: []string{"words", "typo", "proximity", "attribute", "sort", "exactness"},
|
||||
TypoTolerance: &meilisearch.TypoTolerance{
|
||||
Enabled: true,
|
||||
DisableOnNumbers: true,
|
||||
MinWordSizeForTypos: meilisearch.MinWordSizeForTypos{OneTypo: 4, TwoTypos: 10},
|
||||
},
|
||||
})
|
||||
|
||||
return i.client.Index(i.indexName), nil
|
||||
@@ -95,12 +99,21 @@ func (i *MeiliIndexer) Close() {
|
||||
i.client = nil
|
||||
}
|
||||
|
||||
type meiliGist struct {
|
||||
Gist
|
||||
ContentSplit string
|
||||
}
|
||||
|
||||
func (i *MeiliIndexer) Add(gist *Gist) error {
|
||||
if gist == nil {
|
||||
return errors.New("failed to add nil gist to index")
|
||||
}
|
||||
doc := &meiliGist{
|
||||
Gist: *gist,
|
||||
ContentSplit: splitCamelCase(gist.Content),
|
||||
}
|
||||
primaryKey := "GistID"
|
||||
_, err := (*atomicIndexer.Load()).(*MeiliIndexer).index.AddDocuments(gist, &meilisearch.DocumentOptions{PrimaryKey: &primaryKey})
|
||||
_, err := (*atomicIndexer.Load()).(*MeiliIndexer).index.AddDocuments(doc, &meilisearch.DocumentOptions{PrimaryKey: &primaryKey})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -109,13 +122,14 @@ func (i *MeiliIndexer) Remove(gistID uint) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *MeiliIndexer) Search(queryStr string, queryMetadata SearchGistMetadata, userId uint, page int) ([]uint, uint64, map[string]int, error) {
|
||||
func (i *MeiliIndexer) Search(queryMetadata SearchGistMetadata, userId uint, page int) ([]uint, uint64, map[string]int, error) {
|
||||
searchRequest := &meilisearch.SearchRequest{
|
||||
Offset: int64((page - 1) * 10),
|
||||
Limit: 11,
|
||||
AttributesToRetrieve: []string{"GistID", "Languages"},
|
||||
Facets: []string{"Languages"},
|
||||
AttributesToSearchOn: []string{"Content"},
|
||||
AttributesToSearchOn: []string{"Content", "ContentSplit"},
|
||||
MatchingStrategy: meilisearch.All,
|
||||
}
|
||||
|
||||
var filters []string
|
||||
@@ -126,23 +140,83 @@ func (i *MeiliIndexer) Search(queryStr string, queryMetadata SearchGistMetadata,
|
||||
filters = append(filters, fmt.Sprintf("%s = \"%s\"", field, escapeFilterValue(value)))
|
||||
}
|
||||
}
|
||||
addFilter("Username", queryMetadata.Username)
|
||||
addFilter("Title", queryMetadata.Title)
|
||||
addFilter("Filenames", queryMetadata.Filename)
|
||||
addFilter("Extensions", queryMetadata.Extension)
|
||||
addFilter("Languages", queryMetadata.Language)
|
||||
addFilter("Topics", queryMetadata.Topic)
|
||||
var query string
|
||||
if queryMetadata.All != "" {
|
||||
query = queryMetadata.All
|
||||
searchRequest.AttributesToSearchOn = append(AllSearchFields, "ContentSplit")
|
||||
} else {
|
||||
// Exact-match fields stay as filters
|
||||
addFilter("Username", queryMetadata.Username)
|
||||
if queryMetadata.Extension != "" {
|
||||
ext := queryMetadata.Extension
|
||||
if !strings.HasPrefix(ext, ".") {
|
||||
ext = "." + ext
|
||||
}
|
||||
addFilter("Extensions", ext)
|
||||
}
|
||||
addFilter("Languages", queryMetadata.Language)
|
||||
addFilter("Topics", queryMetadata.Topic)
|
||||
|
||||
if queryMetadata.Default != "" {
|
||||
query = queryMetadata.Default
|
||||
var fields []string
|
||||
for _, f := range strings.Split(config.C.SearchDefault, ",") {
|
||||
f = strings.TrimSpace(f)
|
||||
if f == "all" {
|
||||
fields = AllSearchFields
|
||||
break
|
||||
}
|
||||
if indexField, ok := SearchFieldMap[f]; ok {
|
||||
fields = append(fields, indexField)
|
||||
}
|
||||
}
|
||||
if len(fields) > 0 {
|
||||
for _, f := range fields {
|
||||
if f == "Content" {
|
||||
fields = append(fields, "ContentSplit")
|
||||
break
|
||||
}
|
||||
}
|
||||
searchRequest.AttributesToSearchOn = fields
|
||||
}
|
||||
} else {
|
||||
// Fuzzy-matchable fields become part of the query
|
||||
var queryParts []string
|
||||
var searchFields []string
|
||||
|
||||
if queryMetadata.Content != "" {
|
||||
queryParts = append(queryParts, queryMetadata.Content)
|
||||
searchFields = append(searchFields, "Content", "ContentSplit")
|
||||
}
|
||||
if queryMetadata.Title != "" {
|
||||
queryParts = append(queryParts, queryMetadata.Title)
|
||||
searchFields = append(searchFields, "Title")
|
||||
}
|
||||
if queryMetadata.Description != "" {
|
||||
queryParts = append(queryParts, queryMetadata.Description)
|
||||
searchFields = append(searchFields, "Description")
|
||||
}
|
||||
if queryMetadata.Filename != "" {
|
||||
queryParts = append(queryParts, queryMetadata.Filename)
|
||||
searchFields = append(searchFields, "Filenames")
|
||||
}
|
||||
|
||||
query = strings.Join(queryParts, " ")
|
||||
if len(searchFields) > 0 {
|
||||
searchRequest.AttributesToSearchOn = searchFields
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(filters) > 0 {
|
||||
searchRequest.Filter = strings.Join(filters, " AND ")
|
||||
}
|
||||
|
||||
response, err := (*atomicIndexer.Load()).(*MeiliIndexer).index.Search(queryStr, searchRequest)
|
||||
response, err := (*atomicIndexer.Load()).(*MeiliIndexer).index.Search(query, searchRequest)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to search Meilisearch index")
|
||||
return nil, 0, nil, err
|
||||
}
|
||||
|
||||
gistIds := make([]uint, 0, len(response.Hits))
|
||||
for _, hit := range response.Hits {
|
||||
if gistIDRaw, ok := hit["GistID"]; ok {
|
||||
@@ -158,7 +232,9 @@ func (i *MeiliIndexer) Search(queryStr string, queryMetadata SearchGistMetadata,
|
||||
var facetDist map[string]map[string]int
|
||||
if err := json.Unmarshal(response.FacetDistribution, &facetDist); err == nil {
|
||||
if facets, ok := facetDist["Languages"]; ok {
|
||||
languageCounts = facets
|
||||
for lang, count := range facets {
|
||||
languageCounts[strings.ToLower(lang)] += count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,6 +242,30 @@ func (i *MeiliIndexer) Search(queryStr string, queryMetadata SearchGistMetadata,
|
||||
return gistIds, uint64(response.EstimatedTotalHits), languageCounts, nil
|
||||
}
|
||||
|
||||
func splitCamelCase(text string) string {
|
||||
var result strings.Builder
|
||||
runes := []rune(text)
|
||||
for i := 0; i < len(runes); i++ {
|
||||
r := runes[i]
|
||||
if i > 0 {
|
||||
prev := runes[i-1]
|
||||
if unicode.IsUpper(r) {
|
||||
if unicode.IsLower(prev) || unicode.IsDigit(prev) {
|
||||
result.WriteRune(' ')
|
||||
} else if unicode.IsUpper(prev) && i+1 < len(runes) && unicode.IsLower(runes[i+1]) {
|
||||
result.WriteRune(' ')
|
||||
}
|
||||
} else if unicode.IsDigit(r) && !unicode.IsDigit(prev) {
|
||||
result.WriteRune(' ')
|
||||
} else if !unicode.IsDigit(r) && unicode.IsDigit(prev) {
|
||||
result.WriteRune(' ')
|
||||
}
|
||||
}
|
||||
result.WriteRune(r)
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
|
||||
func escapeFilterValue(value string) string {
|
||||
escaped := strings.ReplaceAll(value, "\\", "\\\\")
|
||||
escaped = strings.ReplaceAll(escaped, "\"", "\\\"")
|
||||
|
||||
88
internal/index/meilisearch_test.go
Normal file
88
internal/index/meilisearch_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package index
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/meilisearch/meilisearch-go"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
// syncMeiliIndexer wraps MeiliIndexer to make Add/Remove synchronous for tests.
|
||||
type syncMeiliIndexer struct {
|
||||
*MeiliIndexer
|
||||
}
|
||||
|
||||
func (s *syncMeiliIndexer) Add(gist *Gist) error {
|
||||
if gist == nil {
|
||||
return fmt.Errorf("failed to add nil gist to index")
|
||||
}
|
||||
doc := &meiliGist{
|
||||
Gist: *gist,
|
||||
ContentSplit: splitCamelCase(gist.Content),
|
||||
}
|
||||
primaryKey := "GistID"
|
||||
taskInfo, err := s.index.AddDocuments(doc, &meilisearch.DocumentOptions{PrimaryKey: &primaryKey})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.client.WaitForTask(taskInfo.TaskUID, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *syncMeiliIndexer) Remove(gistID uint) error {
|
||||
taskInfo, err := s.index.DeleteDocument(strconv.Itoa(int(gistID)), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.client.WaitForTask(taskInfo.TaskUID, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
func setupMeiliIndexer(t *testing.T) (Indexer, func()) {
|
||||
zerolog.SetGlobalLevel(zerolog.Disabled)
|
||||
t.Helper()
|
||||
|
||||
host := os.Getenv("OG_TEST_MEILI_HOST")
|
||||
if host == "" {
|
||||
host = "http://localhost:47700"
|
||||
}
|
||||
apiKey := os.Getenv("OG_TEST_MEILI_API_KEY")
|
||||
|
||||
indexName := fmt.Sprintf("test_%d", os.Getpid())
|
||||
|
||||
inner := NewMeiliIndexer(host, apiKey, indexName)
|
||||
err := inner.Init()
|
||||
if err != nil {
|
||||
t.Skipf("MeiliSearch not available at %s: %v", host, err)
|
||||
}
|
||||
|
||||
wrapped := &syncMeiliIndexer{MeiliIndexer: inner}
|
||||
|
||||
// Store the inner MeiliIndexer in atomicIndexer, because MeiliIndexer.Search
|
||||
// type-asserts the global to *MeiliIndexer.
|
||||
var idx Indexer = inner
|
||||
atomicIndexer.Store(&idx)
|
||||
|
||||
cleanup := func() {
|
||||
atomicIndexer.Store(nil)
|
||||
inner.Reset()
|
||||
inner.Close()
|
||||
}
|
||||
|
||||
return wrapped, cleanup
|
||||
}
|
||||
|
||||
func TestMeiliAddAndSearch(t *testing.T) { testAddAndSearch(t, setupMeiliIndexer) }
|
||||
func TestMeiliAccessControl(t *testing.T) { testAccessControl(t, setupMeiliIndexer) }
|
||||
func TestMeiliMetadataFilters(t *testing.T) { testMetadataFilters(t, setupMeiliIndexer) }
|
||||
func TestMeiliAllFieldSearch(t *testing.T) { testAllFieldSearch(t, setupMeiliIndexer) }
|
||||
func TestMeiliFuzzySearch(t *testing.T) { testFuzzySearch(t, setupMeiliIndexer) }
|
||||
func TestMeiliContentSearch(t *testing.T) { testContentSearch(t, setupMeiliIndexer) }
|
||||
func TestMeiliPagination(t *testing.T) { testPagination(t, setupMeiliIndexer) }
|
||||
func TestMeiliLanguageFacets(t *testing.T) { testLanguageFacets(t, setupMeiliIndexer) }
|
||||
func TestMeiliMetadataOnlySearch(t *testing.T) { testMetadataOnlySearch(t, setupMeiliIndexer) }
|
||||
func TestMeiliTitleFuzzySearch(t *testing.T) { testTitleFuzzySearch(t, setupMeiliIndexer) }
|
||||
func TestMeiliMultiLanguageFacets(t *testing.T) { testMultiLanguageFacets(t, setupMeiliIndexer) }
|
||||
@@ -164,6 +164,18 @@ func AllGists(ctx *context.Context) error {
|
||||
return ctx.Html("all.html")
|
||||
}
|
||||
|
||||
// Search handles the search page for gists.
|
||||
//
|
||||
// It takes a query parameter "q" which is a search query in the format:
|
||||
// "user:username title:title description:description filename:filename language:language topic:topic"
|
||||
//
|
||||
// It also takes a page parameter "page" which is the page number to display.
|
||||
//
|
||||
// It returns an error if the search query is invalid or if the page number is invalid.
|
||||
//
|
||||
// It returns the search results as a list of rendered gists, along with the total number of results, the languages found, and the search query.
|
||||
//
|
||||
// The search results are paginated, with 10 results per page.
|
||||
func Search(ctx *context.Context) error {
|
||||
var err error
|
||||
|
||||
@@ -171,7 +183,7 @@ func Search(ctx *context.Context) error {
|
||||
Query: ctx.QueryParam("q"),
|
||||
}
|
||||
|
||||
content, meta := handlers.ParseSearchQueryStr(ctx.QueryParam("q"))
|
||||
metadata := handlers.ParseSearchQueryStr(ctx.QueryParam("q"))
|
||||
pageInt := handlers.GetPage(ctx)
|
||||
|
||||
var currentUserId uint
|
||||
@@ -182,14 +194,18 @@ func Search(ctx *context.Context) error {
|
||||
currentUserId = 0
|
||||
}
|
||||
|
||||
gistsIds, nbHits, langs, err := index.SearchGists(content, index.SearchGistMetadata{
|
||||
Username: meta["user"],
|
||||
Title: meta["title"],
|
||||
Filename: meta["filename"],
|
||||
Extension: meta["extension"],
|
||||
Language: meta["language"],
|
||||
Topic: meta["topic"],
|
||||
All: meta["all"],
|
||||
// Search gists in the index and fetch the gists IDs from the database
|
||||
gistsIds, nbHits, langs, err := index.SearchGists(index.SearchGistMetadata{
|
||||
Username: metadata["user"],
|
||||
Title: metadata["title"],
|
||||
Description: metadata["description"],
|
||||
Filename: metadata["filename"],
|
||||
Extension: metadata["extension"],
|
||||
Language: metadata["language"],
|
||||
Topic: metadata["topic"],
|
||||
Content: metadata["content"],
|
||||
All: metadata["all"],
|
||||
Default: metadata["default"],
|
||||
}, currentUserId, pageInt)
|
||||
if err != nil {
|
||||
return ctx.ErrorRes(500, "Error searching gists", err)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/thomiceli/opengist/internal/web/context"
|
||||
)
|
||||
|
||||
@@ -119,10 +119,16 @@ func Paginate[T any](ctx *context.Context, data []*T, pageInt int, perPage int,
|
||||
return nil
|
||||
}
|
||||
|
||||
func ParseSearchQueryStr(query string) (string, map[string]string) {
|
||||
// ParseSearchQueryStr parses a search query string and returns a map of metadata.
|
||||
// The query string is split into words and each word is checked if it contains a colon (:).
|
||||
// If a word contains a colon, it is split into a key-value pair and added to the metadata map.
|
||||
// If a word does not contain a colon, it is added to an "all" key in the metadata map.
|
||||
// The "all" key is used to search all fields in the index.
|
||||
// The function returns the metadata map.
|
||||
func ParseSearchQueryStr(query string) map[string]string {
|
||||
words := strings.Fields(query)
|
||||
metadata := make(map[string]string)
|
||||
var contentBuilder strings.Builder
|
||||
var allFieldsBuilder strings.Builder
|
||||
|
||||
for _, word := range words {
|
||||
if strings.Contains(word, ":") {
|
||||
@@ -133,10 +139,18 @@ func ParseSearchQueryStr(query string) (string, map[string]string) {
|
||||
metadata[key] = value
|
||||
}
|
||||
} else {
|
||||
contentBuilder.WriteString(word + " ")
|
||||
// Add to content search by default
|
||||
allFieldsBuilder.WriteString(word + " ")
|
||||
}
|
||||
}
|
||||
|
||||
content := strings.TrimSpace(contentBuilder.String())
|
||||
return content, metadata
|
||||
// Set the default search field
|
||||
allContent := strings.TrimSpace(allFieldsBuilder.String())
|
||||
if allContent != "" {
|
||||
metadata["default"] = allContent
|
||||
}
|
||||
|
||||
log.Debug().Msgf("Metadata: %v", metadata)
|
||||
|
||||
return metadata
|
||||
}
|
||||
|
||||
@@ -147,7 +147,10 @@ func (s *Server) setFuncMap() {
|
||||
return dict, nil
|
||||
},
|
||||
"addMetadataToSearchQuery": func(input, key, value string) string {
|
||||
content, metadata := handlers.ParseSearchQueryStr(input)
|
||||
metadata := handlers.ParseSearchQueryStr(input)
|
||||
// extract free-text content (stored under "all") and remove it from metadata
|
||||
content := metadata["all"]
|
||||
delete(metadata, "all")
|
||||
|
||||
metadata[key] = value
|
||||
|
||||
|
||||
1409
package-lock.json
generated
1409
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -12,24 +12,24 @@
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@catppuccin/highlightjs": "^1.0.1",
|
||||
"@codemirror/commands": "^6.10.1",
|
||||
"@codemirror/lang-javascript": "^6.2.4",
|
||||
"@codemirror/language": "^6.12.2",
|
||||
"@codemirror/state": "^6.5.4",
|
||||
"@codemirror/commands": "^6.10.3",
|
||||
"@codemirror/lang-javascript": "^6.2.5",
|
||||
"@codemirror/language": "^6.12.3",
|
||||
"@codemirror/state": "^6.6.0",
|
||||
"@codemirror/text": "^0.19.6",
|
||||
"@codemirror/view": "^6.39.11",
|
||||
"@codemirror/view": "^6.41.1",
|
||||
"@tailwindcss/forms": "^0.5.11",
|
||||
"@tailwindcss/typography": "^0.5.19",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"codemirror": "^6.0.2",
|
||||
"github-markdown-css": "^5.8.1",
|
||||
"github-markdown-css": "^5.9.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"jdenticon": "^3.3.0",
|
||||
"katex": "^0.16.33",
|
||||
"marked": "^17.0.3",
|
||||
"nodemon": "^3.1.11",
|
||||
"katex": "^0.16.45",
|
||||
"marked": "^18.0.2",
|
||||
"nodemon": "^3.1.14",
|
||||
"pdfobject": "^2.3.1",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"vite": "^7.3.1"
|
||||
"tailwindcss": "^4.2.4",
|
||||
"vite": "^8.0.10"
|
||||
}
|
||||
}
|
||||
|
||||
2
templates/base/base_header.html
vendored
2
templates/base/base_header.html
vendored
@@ -116,10 +116,12 @@
|
||||
<div class="p-4 text-xs space-y-1">
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">user:thomas</code> {{ .locale.Tr "gist.search.help.user" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">title:mygist</code> {{ .locale.Tr "gist.search.help.title" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">description:sync</code> {{ .locale.Tr "gist.search.help.description" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">filename:myfile.txt</code> {{ .locale.Tr "gist.search.help.filename" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">extension:yml</code> {{ .locale.Tr "gist.search.help.extension" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">language:go</code> {{ .locale.Tr "gist.search.help.language" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">topic:homelab</code> {{ .locale.Tr "gist.search.help.topic" }}</p>
|
||||
<p class="text-gray-400"><code class="text-slate-800 dark:text-slate-300 pr-1">all:systemctl</code> {{ .locale.Tr "gist.search.help.all" }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
277
test.md
277
test.md
@@ -1,277 +0,0 @@
|
||||
---
|
||||
description: Testing handler and middleware
|
||||
slug: /testing
|
||||
sidebar_position: 13
|
||||
---
|
||||
|
||||
# Testing
|
||||
|
||||
## Testing Handler
|
||||
|
||||
`GET` `/users/:id`
|
||||
|
||||
Handler below retrieves user by id from the database. If user is not found it returns
|
||||
`404` error with a message.
|
||||
|
||||
### CreateUser
|
||||
|
||||
`POST` `/users`
|
||||
|
||||
- Accepts JSON payload
|
||||
- On success `201 - Created`
|
||||
- On error `500 - Internal Server Error`
|
||||
|
||||
### GetUser
|
||||
|
||||
`GET` `/users/:email`
|
||||
|
||||
- On success `200 - OK`
|
||||
- On error `404 - Not Found` if user is not found otherwise `500 - Internal Server Error`
|
||||
|
||||
`handler.go`
|
||||
|
||||
```go
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
)
|
||||
|
||||
type (
|
||||
User struct {
|
||||
Name string `json:"name" form:"name"`
|
||||
Email string `json:"email" form:"email"`
|
||||
}
|
||||
handler struct {
|
||||
db map[string]*User
|
||||
}
|
||||
)
|
||||
|
||||
func (h *handler) createUser(c *echo.Context) error {
|
||||
u := new(User)
|
||||
if err := c.Bind(u); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusCreated, u)
|
||||
}
|
||||
|
||||
func (h *handler) getUser(c *echo.Context) error {
|
||||
email := c.Param("email")
|
||||
user := h.db[email]
|
||||
if user == nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "user not found")
|
||||
}
|
||||
return c.JSON(http.StatusOK, user)
|
||||
}
|
||||
```
|
||||
|
||||
`handler_test.go`
|
||||
|
||||
```go
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/labstack/echo/v5/echotest"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
mockDB = map[string]*User{
|
||||
"jon@labstack.com": &User{"Jon Snow", "jon@labstack.com"},
|
||||
}
|
||||
userJSON = `{"name":"Jon Snow","email":"jon@labstack.com"}`
|
||||
)
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
// Setup
|
||||
e := echo.New()
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON))
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
|
||||
h := &controller{mockDB}
|
||||
|
||||
// Assertions
|
||||
if assert.NoError(t, h.createUser(c)) {
|
||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||
assert.Equal(t, userJSON, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Same test as above but using `echotest` package helpers
|
||||
func TestCreateUserWithEchoTest(t *testing.T) {
|
||||
c, rec := echotest.ContextConfig{
|
||||
Headers: map[string][]string{
|
||||
echo.HeaderContentType: {echo.MIMEApplicationJSON},
|
||||
},
|
||||
JSONBody: []byte(`{"name":"Jon Snow","email":"jon@labstack.com"}`),
|
||||
}.ToContextRecorder(t)
|
||||
|
||||
h := &controller{mockDB}
|
||||
|
||||
// Assertions
|
||||
if assert.NoError(t, h.createUser(c)) {
|
||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||
assert.Equal(t, userJSON+"\n", rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
// Same test as above but even shorter
|
||||
func TestCreateUserWithEchoTest2(t *testing.T) {
|
||||
h := &controller{mockDB}
|
||||
|
||||
rec := echotest.ContextConfig{
|
||||
Headers: map[string][]string{
|
||||
echo.HeaderContentType: {echo.MIMEApplicationJSON},
|
||||
},
|
||||
JSONBody: []byte(`{"name":"Jon Snow","email":"jon@labstack.com"}`),
|
||||
}.ServeWithHandler(t, h.createUser)
|
||||
|
||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||
assert.Equal(t, userJSON+"\n", rec.Body.String())
|
||||
}
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
// Setup
|
||||
e := echo.New()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
|
||||
c.SetPath("/users/:email")
|
||||
c.SetPathValues(echo.PathValues{
|
||||
{Name: "email", Value: "jon@labstack.com"},
|
||||
})
|
||||
h := &controller{mockDB}
|
||||
|
||||
// Assertions
|
||||
if assert.NoError(t, h.getUser(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, userJSON, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUserWithEchoTest(t *testing.T) {
|
||||
c, rec := echotest.ContextConfig{
|
||||
PathValues: echo.PathValues{
|
||||
{Name: "email", Value: "jon@labstack.com"},
|
||||
},
|
||||
Headers: map[string][]string{
|
||||
echo.HeaderContentType: {echo.MIMEApplicationJSON},
|
||||
},
|
||||
JSONBody: []byte(userJSON),
|
||||
}.ToContextRecorder(t)
|
||||
|
||||
h := &controller{mockDB}
|
||||
|
||||
// Assertions
|
||||
if assert.NoError(t, h.getUser(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, userJSON+"\n", rec.Body.String())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Form Payload
|
||||
|
||||
```go
|
||||
// import "net/url"
|
||||
f := make(url.Values)
|
||||
f.Set("name", "Jon Snow")
|
||||
f.Set("email", "jon@labstack.com")
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
|
||||
```
|
||||
|
||||
Multipart form payload:
|
||||
```go
|
||||
func TestContext_MultipartForm(t *testing.T) {
|
||||
testConf := echotest.ContextConfig{
|
||||
MultipartForm: &echotest.MultipartForm{
|
||||
Fields: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
Files: []echotest.MultipartFormFile{
|
||||
{
|
||||
Fieldname: "file",
|
||||
Filename: "test.json",
|
||||
Content: echotest.LoadBytes(t, "testdata/test.json"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
c := testConf.ToContext(t)
|
||||
|
||||
assert.Equal(t, "value", c.FormValue("key"))
|
||||
assert.Equal(t, http.MethodPost, c.Request().Method)
|
||||
assert.Equal(t, true, strings.HasPrefix(c.Request().Header.Get(echo.HeaderContentType), "multipart/form-data; boundary="))
|
||||
|
||||
fv, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, "test.json", fv.Filename)
|
||||
}
|
||||
```
|
||||
|
||||
### Setting Path Params
|
||||
|
||||
```go
|
||||
c.SetPathValues(echo.PathValues{
|
||||
{Name: "id", Value: "1"},
|
||||
{Name: "email", Value: "jon@labstack.com"},
|
||||
})
|
||||
```
|
||||
|
||||
### Setting Query Params
|
||||
|
||||
```go
|
||||
// import "net/url"
|
||||
q := make(url.Values)
|
||||
q.Set("email", "jon@labstack.com")
|
||||
req := httptest.NewRequest(http.MethodGet, "/?"+q.Encode(), nil)
|
||||
```
|
||||
|
||||
## Testing Middleware
|
||||
|
||||
```go
|
||||
func TestCreateUserWithEchoTest2(t *testing.T) {
|
||||
handler := func(c *echo.Context) error {
|
||||
return c.JSON(http.StatusTeapot, fmt.Sprintf("email: %s", c.Param("email")))
|
||||
}
|
||||
middleware := func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c *echo.Context) error {
|
||||
c.Set("user_id", int64(1234))
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
|
||||
c, rec := echotest.ContextConfig{
|
||||
PathValues: echo.PathValues{{Name: "email", Value: "jon@labstack.com"}},
|
||||
}.ToContextRecorder(t)
|
||||
|
||||
err := middleware(handler)(c)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// check that middleware set the value
|
||||
userID, err := echo.ContextGet[int64](c, "user_id")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1234), userID)
|
||||
|
||||
// check that handler returned the correct response
|
||||
assert.Equal(t, http.StatusTeapot, rec.Code)
|
||||
}
|
||||
```
|
||||
|
||||
For now you can look into built-in middleware [test cases](https://github.com/labstack/echo/tree/master/middleware).
|
||||
158
test2.md
158
test2.md
@@ -1,158 +0,0 @@
|
||||
---
|
||||
description: Testing handler and middleware
|
||||
slug: /testing
|
||||
sidebar_position: 13
|
||||
---
|
||||
|
||||
# Testing
|
||||
|
||||
## Testing Handler
|
||||
|
||||
`GET` `/users/:id`
|
||||
|
||||
Handler below retrieves user by id from the database. If user is not found it returns
|
||||
`404` error with a message.
|
||||
|
||||
### CreateUser
|
||||
|
||||
`POST` `/users`
|
||||
|
||||
- Accepts JSON payload
|
||||
- On success `201 - Created`
|
||||
- On error `500 - Internal Server Error`
|
||||
|
||||
### GetUser
|
||||
|
||||
`GET` `/users/:email`
|
||||
|
||||
- On success `200 - OK`
|
||||
- On error `404 - Not Found` if user is not found otherwise `500 - Internal Server Error`
|
||||
|
||||
`handler.go`
|
||||
|
||||
```go
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type (
|
||||
User struct {
|
||||
Name string `json:"name" form:"name"`
|
||||
Email string `json:"email" form:"email"`
|
||||
}
|
||||
handler struct {
|
||||
db map[string]*User
|
||||
}
|
||||
)
|
||||
|
||||
func (h *handler) createUser(c echo.Context) error {
|
||||
u := new(User)
|
||||
if err := c.Bind(u); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusCreated, u)
|
||||
}
|
||||
|
||||
func (h *handler) getUser(c echo.Context) error {
|
||||
email := c.Param("email")
|
||||
user := h.db[email]
|
||||
if user == nil {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "user not found")
|
||||
}
|
||||
return c.JSON(http.StatusOK, user)
|
||||
}
|
||||
```
|
||||
|
||||
`handler_test.go`
|
||||
|
||||
```go
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
mockDB = map[string]*User{
|
||||
"jon@labstack.com": &User{"Jon Snow", "jon@labstack.com"},
|
||||
}
|
||||
userJSON = `{"name":"Jon Snow","email":"jon@labstack.com"}`
|
||||
)
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
// Setup
|
||||
e := echo.New()
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(userJSON))
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
h := &handler{mockDB}
|
||||
|
||||
// Assertions
|
||||
if assert.NoError(t, h.createUser(c)) {
|
||||
assert.Equal(t, http.StatusCreated, rec.Code)
|
||||
assert.Equal(t, userJSON, rec.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
// Setup
|
||||
e := echo.New()
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c := e.NewContext(req, rec)
|
||||
c.SetPath("/users/:email")
|
||||
c.SetParamNames("email")
|
||||
c.SetParamValues("jon@labstack.com")
|
||||
h := &handler{mockDB}
|
||||
|
||||
// Assertions
|
||||
if assert.NoError(t, h.getUser(c)) {
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Equal(t, userJSON, rec.Body.String())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Using Form Payload
|
||||
|
||||
```go
|
||||
// import "net/url"
|
||||
f := make(url.Values)
|
||||
f.Set("name", "Jon Snow")
|
||||
f.Set("email", "jon@labstack.com")
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(f.Encode()))
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationForm)
|
||||
```
|
||||
|
||||
### Setting Path Params
|
||||
|
||||
```go
|
||||
c.SetParamNames("id", "email")
|
||||
c.SetParamValues("1", "jon@labstack.com")
|
||||
```
|
||||
|
||||
### Setting Query Params
|
||||
|
||||
```go
|
||||
// import "net/url"
|
||||
q := make(url.Values)
|
||||
q.Set("email", "jon@labstack.com")
|
||||
req := httptest.NewRequest(http.MethodGet, "/?"+q.Encode(), nil)
|
||||
```
|
||||
|
||||
## Testing Middleware
|
||||
|
||||
*TBD*
|
||||
|
||||
For now you can look into built-in middleware [test cases](https://github.com/labstack/echo/tree/master/middleware).
|
||||
Reference in New Issue
Block a user