diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml
index 8d8e079..4e2c43c 100644
--- a/.github/workflows/docker.yml
+++ b/.github/workflows/docker.yml
@@ -1,15 +1,15 @@
-name: Build and Push to GHCR
+name: Build and Push Docker
on:
push:
branches:
- dev
tags:
- - '*'
+ - "*"
workflow_dispatch:
jobs:
- build:
+ build-and-push:
runs-on: ubuntu-latest
steps:
@@ -17,53 +17,50 @@ jobs:
uses: actions/checkout@v3
- name: Login to GitHub Container Registry
- uses: docker/login-action@v1
+ uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- - name: Set tag name
- id: set_tag
- run: |
- if [[ $GITHUB_REF == refs/heads/* ]]; then
- echo "::set-output name=tag::$(echo $GITHUB_REF | cut -d'/' -f3)"
- else
- echo "::set-output name=tag::${{ github.ref_name }}"
- fi
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKER_HUB_USERNAME }}
+ password: ${{ secrets.DOCKER_HUB_PASSWORD }}
- - name: Check if triggered by tag
- id: check_tag
- run: |
- if [[ "${{ github.ref }}" == refs/tags/* ]]; then
- echo "::set-output name=triggered_by_tag::true"
- else
- echo "::set-output name=triggered_by_tag::false"
- fi
+ - name: Docker meta
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: |
+ ${{ secrets.DOCKER_HUB_USERNAME }}/sub2clash
+ ghcr.io/${{ github.repository }}
- - name: Build and push Docker image for dev branch
- if: steps.check_tag.outputs.triggered_by_tag == 'false'
- uses: docker/build-push-action@v2
+ - name: Prepare args
+ id: prep
+ run: |
+ if [[ "${{ github.event_name }}" == 'workflow_dispatch' ]]; then
+ VERSION="${{ github.sha }}"
+ fi
+ if [[ "$GITHUB_REF" == 'refs/heads/dev' ]]; then
+ VERSION="${{ github.sha }}"
+ fi
+ if [[ "$GITHUB_REF" == 'refs/tags/'* ]]; then
+ VERSION=$TAG_NAME
+ fi
+ echo "version=$VERSION" >> $GITHUB_ENV
+
+
+ - name: Set up Docker buildx
+ uses: docker/setup-buildx-action@v3
+
+ - name: Build and push Docker image to GHCR and Docker Hub
+ uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
- build-args: |
- dev=true
- version=${{ github.sha }}
+ build-args: version=${{ env.version }}
push: true
- tags: ghcr.io/${{ github.repository }}:${{ steps.set_tag.outputs.tag }}
-
- - name: Build and push Docker image for tags
- if: steps.check_tag.outputs.triggered_by_tag == 'true'
- uses: docker/build-push-action@v2
- with:
- context: .
- file: ./Dockerfile
- build-args: |
- dev=false
- version=${{ steps.set_tag.outputs.tag }}
- push: true
- tags: |
- ghcr.io/${{ github.repository }}:${{ steps.set_tag.outputs.tag }}
- ghcr.io/${{ github.repository }}:latest
-
+ platforms: linux/amd64,linux/arm,linux/arm64
+ tags: ${{ steps.prep.outputs.tags }}
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
deleted file mode 100644
index 8c99d22..0000000
--- a/.github/workflows/go.yml
+++ /dev/null
@@ -1,110 +0,0 @@
-# This workflow will build a golang project
-# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
-
-name: Go
-
-on:
- push:
- tags:
- - '*'
- workflow_dispatch:
-
-jobs:
-
- build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
-
- - name: Set up Go
- uses: actions/setup-go@v4
- with:
- go-version: '1.21'
-
- - name: Build
- run: |
- LDFLAGS="-s -w -X config.Version=${{ github.ref_name }}"
-
- # Linux
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="$LDFLAGS" -o output/sub2clash-linux-amd64 main.go
- CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="$LDFLAGS" -o output/sub2clash-linux-arm64 main.go
-
- # Darwin
- CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="$LDFLAGS" -o output/sub2clash-darwin-amd64 main.go
- CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="$LDFLAGS" -o output/sub2clash-darwin-arm64 main.go
-
- # Windows
- CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="$LDFLAGS" -o output/sub2clash-windows-amd64.exe main.go
- CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="$LDFLAGS" -o output/sub2clash-windows-arm64.exe main.go
-
- - name: Create Release
- id: create_release
- uses: actions/create-release@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- tag_name: ${{ github.ref_name }}
- release_name: Release ${{ github.ref_name }}
- draft: false
- prerelease: false
-
- - name: Upload Release Asset (Linux amd64)
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./output/sub2clash-linux-amd64
- asset_name: sub2clash-linux-amd64
- asset_content_type: application/octet-stream
-
- - name: Upload Release Asset (Linux arm64)
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./output/sub2clash-linux-arm64
- asset_name: sub2clash-linux-arm64
- asset_content_type: application/octet-stream
-
- - name: Upload Release Asset (Darwin amd64)
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./output/sub2clash-darwin-amd64
- asset_name: sub2clash-darwin-amd64
- asset_content_type: application/octet-stream
-
- - name: Upload Release Asset (Darwin arm64)
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./output/sub2clash-darwin-arm64
- asset_name: sub2clash-darwin-arm64
- asset_content_type: application/octet-stream
-
- - name: Upload Release Asset (Windows amd64)
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./output/sub2clash-windows-amd64.exe
- asset_name: sub2clash-windows-amd64.exe
- asset_content_type: application/octet-stream
-
- - name: Upload Release Asset (Windows arm64)
- uses: actions/upload-release-asset@v1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- upload_url: ${{ steps.create_release.outputs.upload_url }}
- asset_path: ./output/sub2clash-windows-arm64.exe
- asset_name: sub2clash-windows-arm64.exe
- asset_content_type: application/octet-stream
-
diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
new file mode 100644
index 0000000..1f97cec
--- /dev/null
+++ b/.github/workflows/goreleaser.yml
@@ -0,0 +1,31 @@
+name: Build and Release
+
+on:
+ push:
+ tags:
+ - "*"
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ packages: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Go
+ uses: actions/setup-go@v4
+ with:
+ go-version: 1.21.5
+
+ - name: Run goreleaser
+ uses: goreleaser/goreleaser-action@v5
+ with:
+ distribution: goreleaser
+ version: latest
+ args: release --rm-dist --clean
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 7f8d47b..4d3cfdd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ test
logs
sub2clash.db
.env
+.vscode/settings.json
\ No newline at end of file
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 21794dd..418ff37 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -1,17 +1,18 @@
-#before:
-# hooks:
-# - go mod tidy
+project_name: sub2clash
builds:
- env:
- CGO_ENABLED=0
goos:
- - linux
- windows
+ - linux
- darwin
goarch:
- amd64
- arm64
+ - "386"
ldflags:
- -s -w -X sub2clash/config.Version={{ .Version }}
no_unique_dist_dir: true
binary: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}"
+archives:
+ - format: binary
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 0211933..04e0bd1 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,7 +7,7 @@
"mode": "debug",
"program": "${workspaceFolder}/main.go",
"output": "${workspaceFolder}/dist/main.exe",
- "buildFlags": "-ldflags '-X sub2clash/config.Dev=true -X sub2clash/config.Version=dev'"
+ "buildFlags": "-ldflags '-X sub2clash/config.Version=dev'"
}
]
}
diff --git a/API_README.md b/API_README.md
index 8048273..96af2ed 100644
--- a/API_README.md
+++ b/API_README.md
@@ -1,4 +1,4 @@
-# `/clash`, `/meta`
+# `GET /clash`, `GET /meta`
获取 Clash/Clash.Meta 配置链接
@@ -17,7 +17,7 @@
| remove | string | 否 | - | 通过正则表达式删除节点 |
| nodeList | bool | 否 | `false` | 只输出节点 |
-# `/short`
+# `POST /short`
获取短链,Content-Type 为 `application/json`
具体参考使用可以参考 [api\templates\index.html](api/static/index.html)
@@ -27,7 +27,7 @@
| url | string | 是 | - | 需要转换的 Query 参数部分 |
| password | string | 否 | - | 短链密码 |
-# `/s/:hash`
+# `GET /s/:hash`
短链跳转
`hash` 为动态路由参数,可以通过 `/short` 接口获取
@@ -35,3 +35,13 @@
| Query 参数 | 类型 | 是否必须 | 默认值 | 说明 |
| ---------- | ------ | -------- | ------ | -------- |
| password | string | 否 | - | 短链密码 |
+
+# `PUT /short`
+
+更新短链,Content-Type 为 `application/json`
+
+| Body 参数 | 类型 | 是否必须 | 默认值 | 说明 |
+| --------- | ------ | -------- | ------ | ------------------------- |
+| url | string | 是 | - | 需要转换的 Query 参数部分 |
+| password | string | 否 | - | 短链密码 |
+| hash | string | 是 | - | 短链 hash |
diff --git a/Dockerfile b/Dockerfile
index 755bc46..222775d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -11,10 +11,9 @@ RUN go mod download
# 获取参数
ARG version
-ARG dev
# 使用 -ldflags 参数进行编译
-RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X sub2clash/config.Version=${version} -X sub2clash/config.Dev=${dev}" -o sub2clash main.go
+RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-s -w -X sub2clash/config.Version=${version}" -o sub2clash main.go
FROM alpine:latest
diff --git a/api/controller/clash.go b/api/handler/clash.go
similarity index 98%
rename from api/controller/clash.go
rename to api/handler/clash.go
index fc6da69..e9b2acb 100644
--- a/api/controller/clash.go
+++ b/api/handler/clash.go
@@ -1,4 +1,4 @@
-package controller
+package handler
import (
"net/http"
diff --git a/api/controller/default.go b/api/handler/default.go
similarity index 99%
rename from api/controller/default.go
rename to api/handler/default.go
index 55a9d4e..27619f5 100644
--- a/api/controller/default.go
+++ b/api/handler/default.go
@@ -1,4 +1,4 @@
-package controller
+package handler
import (
"crypto/sha256"
diff --git a/api/controller/meta.go b/api/handler/meta.go
similarity index 98%
rename from api/controller/meta.go
rename to api/handler/meta.go
index 347e200..38ee020 100644
--- a/api/controller/meta.go
+++ b/api/handler/meta.go
@@ -1,4 +1,4 @@
-package controller
+package handler
import (
_ "embed"
diff --git a/api/controller/short_link.go b/api/handler/short_link.go
similarity index 79%
rename from api/controller/short_link.go
rename to api/handler/short_link.go
index 8c30359..1d82b42 100644
--- a/api/controller/short_link.go
+++ b/api/handler/short_link.go
@@ -1,4 +1,4 @@
-package controller
+package handler
import (
"errors"
@@ -107,3 +107,31 @@ func ShortLinkGetHandler(c *gin.Context) {
}
c.String(http.StatusOK, string(all))
}
+
+func ShortLinkUpdateHandler(c *gin.Context) {
+ var params validator.ShortLinkUpdateValidator
+ if err := c.ShouldBind(¶ms); err != nil {
+ c.String(400, "参数错误: "+err.Error())
+ }
+ if strings.TrimSpace(params.Url) == "" {
+ c.String(400, "参数错误")
+ return
+ }
+ var shortLink model.ShortLink
+ result := database.FindShortLinkByHash(params.Hash, &shortLink)
+ if result.Error != nil {
+ c.String(404, "未找到短链接")
+ return
+ }
+ if shortLink.Password == "" {
+ c.String(403, "无法修改无密码短链接")
+ return
+ }
+ if shortLink.Password != params.Password {
+ c.String(403, "密码错误")
+ return
+ }
+ shortLink.Url = params.Url
+ database.SaveShortLink(&shortLink)
+ c.String(200, "更新成功")
+}
diff --git a/api/route.go b/api/route.go
index f398b8a..4c02550 100644
--- a/api/route.go
+++ b/api/route.go
@@ -5,7 +5,7 @@ import (
"html/template"
"log"
"net/http"
- "sub2clash/api/controller"
+ "sub2clash/api/handler"
"sub2clash/config"
"sub2clash/middleware"
@@ -45,22 +45,27 @@ func SetRoute(r *gin.Engine) {
)
r.GET(
"/clash", func(c *gin.Context) {
- controller.SubmodHandler(c)
+ handler.SubmodHandler(c)
},
)
r.GET(
"/meta", func(c *gin.Context) {
- controller.SubHandler(c)
+ handler.SubHandler(c)
},
)
r.POST(
"/short", func(c *gin.Context) {
- controller.ShortLinkGenHandler(c)
+ handler.ShortLinkGenHandler(c)
},
)
r.GET(
"/s/:hash", func(c *gin.Context) {
- controller.ShortLinkGetHandler(c)
+ handler.ShortLinkGetHandler(c)
+ },
+ )
+ r.PUT(
+ "/short", func(c *gin.Context) {
+ handler.ShortLinkUpdateHandler(c)
},
)
}
diff --git a/api/static/index.html b/api/static/index.html
index 367fd4d..68fb48f 100644
--- a/api/static/index.html
+++ b/api/static/index.html
@@ -142,7 +142,7 @@
-
+
@@ -151,11 +151,14 @@
-
+
+
diff --git a/api/static/index.js b/api/static/index.js
index 5e54520..8be5407 100644
--- a/api/static/index.js
+++ b/api/static/index.js
@@ -1,338 +1,332 @@
function clearExistingValues() {
- // 清除简单输入框和复选框的值
- document.getElementById("endpoint").value = "clash";
- document.getElementById("sub").value = "";
- document.getElementById("proxy").value = "";
- document.getElementById("refresh").checked = false;
- document.getElementById("autoTest").checked = false;
- document.getElementById("lazy").checked = false;
- document.getElementById("igcg").checked = false;
- document.getElementById("template").value = "";
- document.getElementById("sort").value = "nameasc";
- document.getElementById("remove").value = "";
- document.getElementById("apiLink").value = "";
- document.getElementById("apiShortLink").value = "";
- document.getElementById("password").value = "";
- document.getElementById("nodeList").checked = false;
+ // 清除简单输入框和复选框的值
+ document.getElementById("endpoint").value = "clash";
+ document.getElementById("sub").value = "";
+ document.getElementById("proxy").value = "";
+ document.getElementById("refresh").checked = false;
+ document.getElementById("autoTest").checked = false;
+ document.getElementById("lazy").checked = false;
+ document.getElementById("igcg").checked = false;
+ document.getElementById("template").value = "";
+ document.getElementById("sort").value = "nameasc";
+ document.getElementById("remove").value = "";
+ document.getElementById("apiLink").value = "";
+ document.getElementById("apiShortLink").value = "";
+ document.getElementById("password").value = "";
+ document.getElementById("nodeList").checked = false;
- // 清除由 createRuleProvider, createReplace, 和 createRule 创建的所有额外输入组
- clearInputGroup("ruleProviderGroup");
- clearInputGroup("replaceGroup");
- clearInputGroup("ruleGroup");
+ // 清除由 createRuleProvider, createReplace, 和 createRule 创建的所有额外输入组
+ clearInputGroup("ruleProviderGroup");
+ clearInputGroup("replaceGroup");
+ clearInputGroup("ruleGroup");
}
function generateURI() {
- const queryParams = [];
+ const queryParams = [];
- // 获取 API Endpoint
- const endpoint = document.getElementById("endpoint").value;
+ // 获取 API Endpoint
+ const endpoint = document.getElementById("endpoint").value;
- // 获取并组合订阅链接
- let subLines = document
- .getElementById("sub")
- .value.split("\n")
- .filter((line) => line.trim() !== "");
- let noSub = false;
- // 去除 subLines 中空元素
- subLines = subLines.map((item) => {
- if (item !== "") {
- return item;
- }
- });
- if (subLines.length > 0) {
- queryParams.push(`sub=${encodeURIComponent(subLines.join(","))}`);
- } else {
- noSub = true;
+ // 获取并组合订阅链接
+ let subLines = document
+ .getElementById("sub")
+ .value.split("\n")
+ .filter((line) => line.trim() !== "");
+ let noSub = false;
+ // 去除 subLines 中空元素
+ subLines = subLines.map((item) => {
+ if (item !== "") {
+ return item;
}
+ });
+ if (subLines.length > 0) {
+ queryParams.push(`sub=${encodeURIComponent(subLines.join(","))}`);
+ } else {
+ noSub = true;
+ }
- // 获取并组合节点分享链接
- let proxyLines = document
- .getElementById("proxy")
- .value.split("\n")
- .filter((line) => line.trim() !== "");
- let noProxy = false;
- // 去除 proxyLines 中空元素
- proxyLines = proxyLines.map((item) => {
- if (item !== "") {
- return item;
- }
- });
- if (proxyLines.length > 0) {
- queryParams.push(`proxy=${encodeURIComponent(proxyLines.join(","))}`);
- } else {
- noProxy = true;
+ // 获取并组合节点分享链接
+ let proxyLines = document
+ .getElementById("proxy")
+ .value.split("\n")
+ .filter((line) => line.trim() !== "");
+ let noProxy = false;
+ // 去除 proxyLines 中空元素
+ proxyLines = proxyLines.map((item) => {
+ if (item !== "") {
+ return item;
}
- if (noSub && noProxy) {
- alert("订阅链接和节点分享链接不能同时为空!");
+ });
+ if (proxyLines.length > 0) {
+ queryParams.push(`proxy=${encodeURIComponent(proxyLines.join(","))}`);
+ } else {
+ noProxy = true;
+ }
+ if (noSub && noProxy) {
+ alert("订阅链接和节点分享链接不能同时为空!");
+ return "";
+ }
+ // 获取复选框的值
+ const refresh = document.getElementById("refresh").checked;
+ queryParams.push(`refresh=${refresh ? "true" : "false"}`);
+ const autoTest = document.getElementById("autoTest").checked;
+ queryParams.push(`autoTest=${autoTest ? "true" : "false"}`);
+ const lazy = document.getElementById("lazy").checked;
+ queryParams.push(`lazy=${lazy ? "true" : "false"}`);
+ const nodeList = document.getElementById("nodeList").checked;
+ queryParams.push(`nodeList=${nodeList ? "true" : "false"}`);
+ const igcg = document.getElementById("igcg").checked;
+ queryParams.push(`ignoreCountryGroup=${igcg ? "true" : "false"}`);
+
+ // 获取模板链接或名称(如果存在)
+ const template = document.getElementById("template").value;
+ if (template.trim() !== "") {
+ queryParams.push(`template=${encodeURIComponent(template)}`);
+ }
+
+ // 获取Rule Provider和规则
+ const ruleProviders = document.getElementsByName("ruleProvider");
+ const rules = document.getElementsByName("rule");
+ let providers = [];
+ for (let i = 0; i < ruleProviders.length / 5; i++) {
+ let baseIndex = i * 5;
+ let behavior = ruleProviders[baseIndex].value;
+ let url = ruleProviders[baseIndex + 1].value;
+ let group = ruleProviders[baseIndex + 2].value;
+ let prepend = ruleProviders[baseIndex + 3].value;
+ let name = ruleProviders[baseIndex + 4].value;
+ // 是否存在空值
+ if (
+ behavior.trim() === "" ||
+ url.trim() === "" ||
+ group.trim() === "" ||
+ prepend.trim() === "" ||
+ name.trim() === ""
+ ) {
+ alert("Rule Provider 中存在空值,请检查后重试!");
+ return "";
+ }
+ providers.push(`[${behavior},${url},${group},${prepend},${name}]`);
+ }
+ queryParams.push(`ruleProvider=${encodeURIComponent(providers.join(","))}`);
+
+ let ruleList = [];
+ for (let i = 0; i < rules.length / 3; i++) {
+ if (rules[i * 3].value.trim() !== "") {
+ let rule = rules[i * 3].value;
+ let prepend = rules[i * 3 + 1].value;
+ let group = rules[i * 3 + 2].value;
+ // 是否存在空值
+ if (rule.trim() === "" || prepend.trim() === "" || group.trim() === "") {
+ alert("Rule 中存在空值,请检查后重试!");
return "";
+ }
+ ruleList.push(`[${rule},${prepend},${group}]`);
}
- // 获取复选框的值
- const refresh = document.getElementById("refresh").checked;
- queryParams.push(`refresh=${refresh ? "true" : "false"}`);
- const autoTest = document.getElementById("autoTest").checked;
- queryParams.push(`autoTest=${autoTest ? "true" : "false"}`);
- const lazy = document.getElementById("lazy").checked;
- queryParams.push(`lazy=${lazy ? "true" : "false"}`);
- const nodeList = document.getElementById("nodeList").checked;
- queryParams.push(`nodeList=${nodeList ? "true" : "false"}`);
- const igcg = document.getElementById("igcg").checked;
- queryParams.push(`ignoreCountryGroup=${igcg ? "true" : "false"}`);
+ }
+ queryParams.push(`rule=${encodeURIComponent(ruleList.join(","))}`);
- // 获取模板链接或名称(如果存在)
- const template = document.getElementById("template").value;
- if (template.trim() !== "") {
- queryParams.push(`template=${encodeURIComponent(template)}`);
+ // 获取排序策略
+ const sort = document.getElementById("sort").value;
+ queryParams.push(`sort=${sort}`);
+
+ // 获取删除节点的正则表达式
+ const remove = document.getElementById("remove").value;
+ if (remove.trim() !== "") {
+ queryParams.push(`remove=${encodeURIComponent(remove)}`);
+ }
+
+ // 获取替换节点名称的正则表达式
+ let replaceList = [];
+ const replaces = document.getElementsByName("replace");
+ for (let i = 0; i < replaces.length / 2; i++) {
+ let replaceStr = `<${replaces[i * 2].value}>`;
+ let replaceTo = `<${replaces[i * 2 + 1].value}>`;
+ if (replaceStr.trim() === "") {
+ alert("重命名设置中存在空值,请检查后重试!");
+ return "";
}
+ replaceList.push(`[${replaceStr},${replaceTo}]`);
+ }
+ queryParams.push(`replace=${encodeURIComponent(replaceList.join(","))}`);
- // 获取Rule Provider和规则
- const ruleProviders = document.getElementsByName("ruleProvider");
- const rules = document.getElementsByName("rule");
- let providers = [];
- for (let i = 0; i < ruleProviders.length / 5; i++) {
- let baseIndex = i * 5;
- let behavior = ruleProviders[baseIndex].value;
- let url = ruleProviders[baseIndex + 1].value;
- let group = ruleProviders[baseIndex + 2].value;
- let prepend = ruleProviders[baseIndex + 3].value;
- let name = ruleProviders[baseIndex + 4].value;
- // 是否存在空值
- if (
- behavior.trim() === "" ||
- url.trim() === "" ||
- group.trim() === "" ||
- prepend.trim() === "" ||
- name.trim() === ""
- ) {
- alert("Rule Provider 中存在空值,请检查后重试!");
- return "";
- }
- providers.push(`[${behavior},${url},${group},${prepend},${name}]`);
- }
- queryParams.push(`ruleProvider=${encodeURIComponent(providers.join(","))}`);
-
- let ruleList = [];
- for (let i = 0; i < rules.length / 3; i++) {
- if (rules[i * 3].value.trim() !== "") {
- let rule = rules[i * 3].value;
- let prepend = rules[i * 3 + 1].value;
- let group = rules[i * 3 + 2].value;
- // 是否存在空值
- if (
- rule.trim() === "" ||
- prepend.trim() === "" ||
- group.trim() === ""
- ) {
- alert("Rule 中存在空值,请检查后重试!");
- return "";
- }
- ruleList.push(`[${rule},${prepend},${group}]`);
- }
- }
- queryParams.push(`rule=${encodeURIComponent(ruleList.join(","))}`);
-
- // 获取排序策略
- const sort = document.getElementById("sort").value;
- queryParams.push(`sort=${sort}`);
-
- // 获取删除节点的正则表达式
- const remove = document.getElementById("remove").value;
- if (remove.trim() !== "") {
- queryParams.push(`remove=${encodeURIComponent(remove)}`);
- }
-
- // 获取替换节点名称的正则表达式
- let replaceList = [];
- const replaces = document.getElementsByName("replace");
- for (let i = 0; i < replaces.length / 2; i++) {
- let replaceStr = `<${replaces[i * 2].value}>`;
- let replaceTo = `<${replaces[i * 2 + 1].value}>`;
- if (replaceStr.trim() === "") {
- alert("重命名设置中存在空值,请检查后重试!");
- return "";
- }
- replaceList.push(`[${replaceStr},${replaceTo}]`);
- }
- queryParams.push(`replace=${encodeURIComponent(replaceList.join(","))}`);
-
- return `${endpoint}?${queryParams.join("&")}`;
+ return `${endpoint}?${queryParams.join("&")}`;
}
// 将输入框中的 URL 解析为参数
function parseInputURL() {
- // 获取输入框中的 URL
- const inputURL = document.getElementById("urlInput").value;
+ // 获取输入框中的 URL
+ const inputURL = document.getElementById("urlInput").value;
- if (!inputURL) {
- alert("请输入有效的链接!");
- return;
- }
+ if (!inputURL) {
+ alert("请输入有效的链接!");
+ return;
+ }
- let url;
- try {
- url = new URL(inputURL);
- } catch (_) {
- alert("无效的链接!");
- return;
- }
+ let url;
+ try {
+ url = new URL(inputURL);
+ } catch (_) {
+ alert("无效的链接!");
+ return;
+ }
- // 清除现有的输入框值
- clearExistingValues();
+ // 清除现有的输入框值
+ clearExistingValues();
- // 获取查询参数
- const params = new URLSearchParams(url.search);
+ // 获取查询参数
+ const params = new URLSearchParams(url.search);
- // 分配值到对应的输入框
- const pathSections = url.pathname.split("/");
- const lastSection = pathSections[pathSections.length - 1];
- const clientTypeSelect = document.getElementById("endpoint");
- switch (lastSection.toLowerCase()) {
- case "meta":
- clientTypeSelect.value = "meta";
- break;
- case "clash":
- default:
- clientTypeSelect.value = "clash";
- break;
- }
+ // 分配值到对应的输入框
+ const pathSections = url.pathname.split("/");
+ const lastSection = pathSections[pathSections.length - 1];
+ const clientTypeSelect = document.getElementById("endpoint");
+ switch (lastSection.toLowerCase()) {
+ case "meta":
+ clientTypeSelect.value = "meta";
+ break;
+ case "clash":
+ default:
+ clientTypeSelect.value = "clash";
+ break;
+ }
- if (params.has("sub")) {
- document.getElementById("sub").value = decodeURIComponent(
- params.get("sub")
- )
- .split(",")
- .join("\n");
- }
+ if (params.has("sub")) {
+ document.getElementById("sub").value = decodeURIComponent(params.get("sub"))
+ .split(",")
+ .join("\n");
+ }
- if (params.has("proxy")) {
- document.getElementById("proxy").value = decodeURIComponent(
- params.get("proxy")
- )
- .split(",")
- .join("\n");
- }
+ if (params.has("proxy")) {
+ document.getElementById("proxy").value = decodeURIComponent(
+ params.get("proxy")
+ )
+ .split(",")
+ .join("\n");
+ }
- if (params.has("refresh")) {
- document.getElementById("refresh").checked =
- params.get("refresh") === "true";
- }
+ if (params.has("refresh")) {
+ document.getElementById("refresh").checked =
+ params.get("refresh") === "true";
+ }
- if (params.has("autoTest")) {
- document.getElementById("autoTest").checked =
- params.get("autoTest") === "true";
- }
+ if (params.has("autoTest")) {
+ document.getElementById("autoTest").checked =
+ params.get("autoTest") === "true";
+ }
- if (params.has("lazy")) {
- document.getElementById("lazy").checked = params.get("lazy") === "true";
- }
+ if (params.has("lazy")) {
+ document.getElementById("lazy").checked = params.get("lazy") === "true";
+ }
- if (params.has("template")) {
- document.getElementById("template").value = decodeURIComponent(
- params.get("template")
- );
- }
+ if (params.has("template")) {
+ document.getElementById("template").value = decodeURIComponent(
+ params.get("template")
+ );
+ }
- if (params.has("sort")) {
- document.getElementById("sort").value = params.get("sort");
- }
+ if (params.has("sort")) {
+ document.getElementById("sort").value = params.get("sort");
+ }
- if (params.has("remove")) {
- document.getElementById("remove").value = decodeURIComponent(
- params.get("remove")
- );
- }
+ if (params.has("remove")) {
+ document.getElementById("remove").value = decodeURIComponent(
+ params.get("remove")
+ );
+ }
- if (params.has("replace")) {
- parseAndFillReplaceParams(decodeURIComponent(params.get("replace")));
- }
+ if (params.has("replace")) {
+ parseAndFillReplaceParams(decodeURIComponent(params.get("replace")));
+ }
- if (params.has("ruleProvider")) {
- parseAndFillRuleProviderParams(
- decodeURIComponent(params.get("ruleProvider"))
- );
- }
+ if (params.has("ruleProvider")) {
+ parseAndFillRuleProviderParams(
+ decodeURIComponent(params.get("ruleProvider"))
+ );
+ }
- if (params.has("rule")) {
- parseAndFillRuleParams(decodeURIComponent(params.get("rule")));
- }
+ if (params.has("rule")) {
+ parseAndFillRuleParams(decodeURIComponent(params.get("rule")));
+ }
- if (params.has("nodeList")) {
- document.getElementById("nodeList").checked =
- params.get("nodeList") === "true";
- }
+ if (params.has("nodeList")) {
+ document.getElementById("nodeList").checked =
+ params.get("nodeList") === "true";
+ }
}
function clearInputGroup(groupId) {
- // 清空第二个之后的child
- const group = document.getElementById(groupId);
- while (group.children.length > 2) {
- group.removeChild(group.lastChild);
- }
+ // 清空第二个之后的child
+ const group = document.getElementById(groupId);
+ while (group.children.length > 2) {
+ group.removeChild(group.lastChild);
+ }
}
function parseAndFillReplaceParams(replaceParams) {
- const replaceGroup = document.getElementById("replaceGroup");
- let matches;
- const regex = /\[(<.*?>),(<.*?>)\]/g;
- const str = decodeURIComponent(replaceParams);
- while ((matches = regex.exec(str)) !== null) {
- const div = createReplace();
- const original = matches[1].slice(1, -1); // Remove < and >
- const replacement = matches[2].slice(1, -1); // Remove < and >
+ const replaceGroup = document.getElementById("replaceGroup");
+ let matches;
+ const regex = /\[(<.*?>),(<.*?>)\]/g;
+ const str = decodeURIComponent(replaceParams);
+ while ((matches = regex.exec(str)) !== null) {
+ const div = createReplace();
+ const original = matches[1].slice(1, -1); // Remove < and >
+ const replacement = matches[2].slice(1, -1); // Remove < and >
- div.children[0].value = original;
- div.children[1].value = replacement;
- replaceGroup.appendChild(div);
- }
+ div.children[0].value = original;
+ div.children[1].value = replacement;
+ replaceGroup.appendChild(div);
+ }
}
function parseAndFillRuleProviderParams(ruleProviderParams) {
- const ruleProviderGroup = document.getElementById("ruleProviderGroup");
- let matches;
- const regex = /\[(.*?),(.*?),(.*?),(.*?),(.*?)\]/g;
- const str = decodeURIComponent(ruleProviderParams);
- while ((matches = regex.exec(str)) !== null) {
- const div = createRuleProvider();
- div.children[0].value = matches[1];
- div.children[1].value = matches[2];
- div.children[2].value = matches[3];
- div.children[3].value = matches[4];
- div.children[4].value = matches[5];
- ruleProviderGroup.appendChild(div);
- }
+ const ruleProviderGroup = document.getElementById("ruleProviderGroup");
+ let matches;
+ const regex = /\[(.*?),(.*?),(.*?),(.*?),(.*?)\]/g;
+ const str = decodeURIComponent(ruleProviderParams);
+ while ((matches = regex.exec(str)) !== null) {
+ const div = createRuleProvider();
+ div.children[0].value = matches[1];
+ div.children[1].value = matches[2];
+ div.children[2].value = matches[3];
+ div.children[3].value = matches[4];
+ div.children[4].value = matches[5];
+ ruleProviderGroup.appendChild(div);
+ }
}
function parseAndFillRuleParams(ruleParams) {
- const ruleGroup = document.getElementById("ruleGroup");
- let matches;
- const regex = /\[(.*?),(.*?),(.*?)\]/g;
- const str = decodeURIComponent(ruleParams);
- while ((matches = regex.exec(str)) !== null) {
- const div = createRule();
- div.children[0].value = matches[1];
- div.children[1].value = matches[2];
- div.children[2].value = matches[3];
- ruleGroup.appendChild(div);
- }
+ const ruleGroup = document.getElementById("ruleGroup");
+ let matches;
+ const regex = /\[(.*?),(.*?),(.*?)\]/g;
+ const str = decodeURIComponent(ruleParams);
+ while ((matches = regex.exec(str)) !== null) {
+ const div = createRule();
+ div.children[0].value = matches[1];
+ div.children[1].value = matches[2];
+ div.children[2].value = matches[3];
+ ruleGroup.appendChild(div);
+ }
}
async function copyToClipboard(elem, e) {
- const apiLinkInput = document.querySelector(`#${elem}`).value;
- try {
- await navigator.clipboard.writeText(apiLinkInput);
- let text = e.textContent;
- e.addEventListener("mouseout", function () {
- e.textContent = text;
- });
- e.textContent = "复制成功";
- } catch (err) {
- console.error("复制到剪贴板失败:", err);
- }
+ const apiLinkInput = document.querySelector(`#${elem}`).value;
+ try {
+ await navigator.clipboard.writeText(apiLinkInput);
+ let text = e.textContent;
+ e.addEventListener("mouseout", function () {
+ e.textContent = text;
+ });
+ e.textContent = "复制成功";
+ } catch (err) {
+ console.error("复制到剪贴板失败:", err);
+ }
}
function createRuleProvider() {
- const div = document.createElement("div");
- div.classList.add("input-group", "mb-2");
- div.innerHTML = `
+ const div = document.createElement("div");
+ div.classList.add("input-group", "mb-2");
+ div.innerHTML = `
@@ -340,85 +334,124 @@ function createRuleProvider() {
`;
- return div;
+ return div;
}
function createReplace() {
- const div = document.createElement("div");
- div.classList.add("input-group", "mb-2");
- div.innerHTML = `
+ const div = document.createElement("div");
+ div.classList.add("input-group", "mb-2");
+ div.innerHTML = `
`;
- return div;
+ return div;
}
function createRule() {
- const div = document.createElement("div");
- div.classList.add("input-group", "mb-2");
- div.innerHTML = `
+ const div = document.createElement("div");
+ div.classList.add("input-group", "mb-2");
+ div.innerHTML = `
`;
- return div;
+ return div;
}
function addRuleProvider() {
- const div = createRuleProvider();
- document.getElementById("ruleProviderGroup").appendChild(div);
+ const div = createRuleProvider();
+ document.getElementById("ruleProviderGroup").appendChild(div);
}
function addRule() {
- const div = createRule();
- document.getElementById("ruleGroup").appendChild(div);
+ const div = createRule();
+ document.getElementById("ruleGroup").appendChild(div);
}
function addReplace() {
- const div = createReplace();
- document.getElementById("replaceGroup").appendChild(div);
+ const div = createReplace();
+ document.getElementById("replaceGroup").appendChild(div);
}
function removeElement(button) {
- button.parentElement.remove();
+ button.parentElement.remove();
}
function generateURL() {
- const apiLink = document.getElementById("apiLink");
- let uri = generateURI();
- if (uri === "") {
- return;
- }
- apiLink.value = `${window.location.origin}${window.location.pathname}${uri}`;
+ const apiLink = document.getElementById("apiLink");
+ let uri = generateURI();
+ if (uri === "") {
+ return;
+ }
+ apiLink.value = `${window.location.origin}${window.location.pathname}${uri}`;
}
function generateShortLink() {
- const apiShortLink = document.getElementById("apiShortLink");
- const password = document.getElementById("password");
- let uri = generateURI();
- if (uri === "") {
- return;
- }
- axios
- .post(
- "./short",
- {
- url: uri,
- password: password.value.trim(),
- },
- {
- headers: {
- "Content-Type": "application/json",
- },
- }
- )
- .then((response) => {
- apiShortLink.value = `${window.location.origin}${window.location.pathname}s/${response.data}`;
- })
- .catch((error) => {
- console.log(error);
- alert("生成短链失败,请重试!");
- });
+ const apiShortLink = document.getElementById("apiShortLink");
+ const password = document.getElementById("password");
+ let uri = generateURI();
+ if (uri === "") {
+ return;
+ }
+ axios
+ .post(
+ "./short",
+ {
+ url: uri,
+ password: password.value.trim(),
+ },
+ {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ )
+ .then((response) => {
+ apiShortLink.value = `${window.location.origin}${window.location.pathname}s/${response.data}`;
+ })
+ .catch((error) => {
+ console.log(error);
+ alert("生成短链失败,请重试!");
+ });
+}
+
+function updateShortLink() {
+ const password = document.getElementById("password");
+ const apiShortLink = document.getElementById("apiShortLink");
+ let hash = apiShortLink.value;
+ if (hash.startsWith("http")) {
+ let u = new URL(hash);
+ hash = u.pathname.replace("/s/", "");
+ }
+ if (password.value.trim() === "") {
+ alert("请输入密码!");
+ return;
+ }
+ let uri = generateURI();
+ if (uri === "") {
+ return;
+ }
+ axios
+ .put(
+ "./short",
+ {
+ hash: hash,
+ url: uri,
+ password: password.value.trim(),
+ },
+ {
+ headers: {
+ "Content-Type": "application/json",
+ },
+ }
+ )
+ .then((response) => {
+ alert("更新短链成功!");
+ })
+ .catch((error) => {
+ console.log(error);
+ alert(error.response.data);
+ });
}
diff --git a/config/config.go b/config/config.go
index 8da3576..d85e4d5 100644
--- a/config/config.go
+++ b/config/config.go
@@ -3,6 +3,7 @@ package config
import (
"errors"
"os"
+ "regexp"
"strconv"
"github.com/joho/godotenv"
@@ -24,6 +25,15 @@ var Default *Config
var Version string
var Dev string
+func init() {
+ reg := regexp.MustCompile(`^v\d+\.\d+\.\d+$`)
+ if reg.MatchString(Version) {
+ Dev = "false"
+ } else {
+ Dev = "true"
+ }
+}
+
func LoadConfig() error {
Default = &Config{
MetaTemplate: "template_meta.yaml",
diff --git a/go.mod b/go.mod
index a68fd39..150bd5e 100644
--- a/go.mod
+++ b/go.mod
@@ -4,48 +4,54 @@ go 1.21
require (
github.com/gin-gonic/gin v1.9.1
- github.com/glebarez/sqlite v1.9.0
+ github.com/glebarez/sqlite v1.10.0
github.com/joho/godotenv v1.5.1
- go.uber.org/zap v1.26.0
- golang.org/x/text v0.13.0
+ go.uber.org/zap v1.27.0
+ golang.org/x/text v0.14.0
gopkg.in/yaml.v3 v3.0.1
- gorm.io/gorm v1.25.4
+ gorm.io/gorm v1.25.7
)
require (
- github.com/bytedance/sonic v1.10.1 // indirect
+ github.com/bytedance/sonic v1.11.3 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
- github.com/chenzhuoyu/iasm v0.9.0 // indirect
+ github.com/chenzhuoyu/iasm v0.9.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
- github.com/glebarez/go-sqlite v1.21.2 // indirect
+ github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
- github.com/go-playground/validator/v10 v10.15.4 // indirect
+ github.com/go-playground/validator/v10 v10.19.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
- github.com/google/go-cmp v0.5.9 // indirect
- github.com/google/uuid v1.3.0 // indirect
+ github.com/google/go-cmp v0.6.0 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/hashicorp/golang-lru/v2 v2.0.7 // 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/klauspost/cpuid/v2 v2.2.5 // indirect
- github.com/leodido/go-urn v1.2.4 // indirect
- github.com/mattn/go-isatty v0.0.19 // indirect
+ github.com/klauspost/cpuid/v2 v2.2.7 // indirect
+ github.com/knz/go-libedit v1.10.1 // indirect
+ github.com/leodido/go-urn v1.4.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
- github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+ github.com/ncruces/go-strftime v0.1.9 // indirect
+ github.com/pelletier/go-toml/v2 v2.1.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
- github.com/ugorji/go/codec v1.2.11 // indirect
+ github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/multierr v1.11.0 // indirect
- golang.org/x/arch v0.5.0 // indirect
- golang.org/x/crypto v0.13.0 // indirect
- golang.org/x/net v0.15.0 // indirect
- golang.org/x/sys v0.12.0 // indirect
- google.golang.org/protobuf v1.31.0 // indirect
- modernc.org/libc v1.22.5 // indirect
- modernc.org/mathutil v1.5.0 // indirect
- modernc.org/memory v1.5.0 // indirect
- modernc.org/sqlite v1.23.1 // indirect
+ golang.org/x/arch v0.7.0 // indirect
+ golang.org/x/crypto v0.21.0 // indirect
+ golang.org/x/net v0.22.0 // indirect
+ golang.org/x/sys v0.18.0 // indirect
+ google.golang.org/protobuf v1.33.0 // indirect
+ modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect
+ modernc.org/libc v1.44.0 // indirect
+ modernc.org/mathutil v1.6.0 // indirect
+ modernc.org/memory v1.7.2 // indirect
+ modernc.org/sqlite v1.29.4 // indirect
+ modernc.org/strutil v1.2.0 // indirect
+ modernc.org/token v1.1.0 // indirect
)
diff --git a/go.sum b/go.sum
index 90f2f48..4656861 100644
--- a/go.sum
+++ b/go.sum
@@ -2,12 +2,16 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
+github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=
+github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
+github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
+github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -15,14 +19,20 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
+github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
+github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
+github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.9.0 h1:Aj6bPA12ZEx5GbSF6XADmCkYXlljPNUY+Zf1EQxynXs=
github.com/glebarez/sqlite v1.9.0/go.mod h1:YBYCoyupOao60lzp1MVBLEjZfgkq0tdB1voAQ09K9zw=
+github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
+github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
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=
@@ -31,17 +41,25 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.15.4 h1:zMXza4EpOdooxPel5xDqXEdXG5r+WggpvnAKMsalBjs=
github.com/go-playground/validator/v10 v10.15.4/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
+github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -53,18 +71,29 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
+github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1 h1:0pHpWtx9vcvC0xGZqEQlQdfSQs7WRlAjuPvk3fOZDCo=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+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/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
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/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=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
+github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI=
+github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
@@ -85,29 +114,46 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
+github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
+golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
+golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
+golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
+golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@@ -115,13 +161,29 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
+gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
+gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
+modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
+modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
+modernc.org/libc v1.44.0 h1:71bbnKgb0mCg7GOOI/PHlzz7Bv6obELGNKnIEeowX8c=
+modernc.org/libc v1.44.0/go.mod h1:RRqfGVjvILF5AdNP3RPCiihj7+Dn2pIBrdlU60lA9vs=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
+modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
+modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
+modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
+modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
+modernc.org/sqlite v1.29.4 h1:mbvQTJ3Tl5Vz+wLA6z8hdBFSeNQ0XXQ+KVwn8NkUliw=
+modernc.org/sqlite v1.29.4/go.mod h1:MjUIBKZ+tU/lqjNLbVAAMjsQPdWdA/ciwdhsT9kBwk8=
+modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
+modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
+modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/validator/short_link.go b/validator/short_link.go
index 8945f0f..45eb3d1 100644
--- a/validator/short_link.go
+++ b/validator/short_link.go
@@ -6,6 +6,12 @@ type ShortLinkGenValidator struct {
}
type ShortLinkGetValidator struct {
- Hash string `form:"hash" binding:"required"`
+ Hash string `form:"hash" binding:"required"` // Hash: 短链接
Password string `form:"password"`
}
+
+type ShortLinkUpdateValidator struct {
+ Hash string `form:"hash" binding:"required"`
+ Url string `form:"url" binding:"required"`
+ Password string `form:"password" binding:"required"`
+}