Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ed335e6c3 | |||
| 4ed6e0175d | |||
| a86350eebb | |||
| 97ea4fc4d0 | |||
| faef50ec4d | |||
| 9b9e5ae964 | |||
| 82a48d69cc | |||
| 4e5b7c7e65 | |||
| e1ca5cdfc4 | |||
| 704d9d10be | |||
| 549e890cd0 | |||
| 857c51e030 | |||
| d8c4f1efaf | |||
| d9cdaacb77 | |||
| 4a4873c733 | |||
| 633980bd05 | |||
| 4bd6be7935 | |||
| cefff4878c | |||
| 47ac97d284 | |||
| be6f3091af | |||
| aeb36e4312 | |||
| 2135f4c37c | |||
| d7bc3c3879 | |||
| e7e2c5f7a2 | |||
| 45a0338fc3 | |||
| ce5f3570fb | |||
| 5acf10f766 | |||
| e7fd70e0f2 | |||
| 34b1ef6638 | |||
| ce05f9f3c6 | |||
| 6f9d7ca03a | |||
| 1c7d43df38 | |||
| b2b31f6c7b | |||
| 5ba1d0b2e5 | |||
| b762c94a25 | |||
| 6070f7dbd4 |
@@ -1,73 +0,0 @@
|
|||||||
# Populates the go module, build, and golangci-lint caches under the default
|
|
||||||
# branch's cache scope so that PR runs have a warm fallback to restore from.
|
|
||||||
#
|
|
||||||
# GitHub Actions caches are scoped per ref: a PR run can only write to its own
|
|
||||||
# branch's scope, but can read from the base branch's scope as a fallback.
|
|
||||||
# PRs therefore cannot seed main's scope themselves. Running the same cache
|
|
||||||
# steps on push-to-main is the only opportunity to populate that fallback
|
|
||||||
# scope so fresh PR branches start with a useful cache on first run.
|
|
||||||
|
|
||||||
# A PR job's exact key lives in its own PR-scope (empty on first run, filled
|
|
||||||
# by later runs of the same PR); on miss, actions/cache's restore-keys fall
|
|
||||||
# back to prefix matches against entries this seeder saves in main's scope.
|
|
||||||
|
|
||||||
name: cache-seeder
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- "go.sum"
|
|
||||||
- ".golangci.yml"
|
|
||||||
- ".github/actions/go-cache/action.yml"
|
|
||||||
- ".github/workflows/cache-seeder.yml"
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: cache-seeder
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
gobuild:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: seed
|
|
||||||
- run: make deps-backend
|
|
||||||
- run: TAGS="bindata" make backend
|
|
||||||
- run: TAGS="bindata gogit" GOEXPERIMENT="" make backend
|
|
||||||
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- { job: lint-backend, tags: "bindata", target: "lint-backend" }
|
|
||||||
- { job: lint-go-windows, tags: "bindata", target: "lint-go-windows" }
|
|
||||||
- { job: lint-go-gogit, tags: "bindata gogit", target: "lint-go" }
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: ${{ matrix.job }}
|
|
||||||
lint-cache: "true"
|
|
||||||
- run: make deps-backend deps-tools
|
|
||||||
- run: make ${{ matrix.target }}
|
|
||||||
env:
|
|
||||||
TAGS: ${{ matrix.tags }}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
name: cron-licenses
|
|
||||||
|
|
||||||
on:
|
|
||||||
# schedule:
|
|
||||||
# - cron: "7 0 * * 1" # every Monday at 00:07 UTC
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
cron-licenses:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'go-gitea/gitea'
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
- run: make generate-gitignore
|
|
||||||
timeout-minutes: 40
|
|
||||||
- name: push translations to repo
|
|
||||||
uses: appleboy/git-push-action@3b2c8661652360dbf1afe1b319a49dbb739c39f1 # v1.2.0
|
|
||||||
with:
|
|
||||||
author_email: "teabot@gitea.io"
|
|
||||||
author_name: GiteaBot
|
|
||||||
branch: main
|
|
||||||
commit: true
|
|
||||||
commit_message: "[skip ci] Updated licenses and gitignores"
|
|
||||||
remote: "git@github.com:go-gitea/gitea.git"
|
|
||||||
ssh_key: ${{ secrets.DEPLOY_KEY }}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
name: cron-renovate
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "23 * * * *" # hourly at :23
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: cron-renovate
|
|
||||||
|
|
||||||
env:
|
|
||||||
RENOVATE_VERSION: 43.141.5 # renovate: datasource=docker depName=ghcr.io/renovatebot/renovate
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
cron-renovate:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'go-gitea/gitea' # prevent running on forks
|
|
||||||
timeout-minutes: 30
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: renovatebot/github-action@f66d8679fcfcfa051abde6e7a623007173bf5164 # v46.1.12
|
|
||||||
with:
|
|
||||||
renovate-version: ${{ env.RENOVATE_VERSION }}
|
|
||||||
configurationFile: renovate.json5
|
|
||||||
token: ${{ secrets.RENOVATE_TOKEN }}
|
|
||||||
env:
|
|
||||||
RENOVATE_BINARY_SOURCE: install # auto-install go/node toolchains needed by post-upgrade tasks.
|
|
||||||
RENOVATE_ALLOWED_POST_UPGRADE_COMMANDS: '["^make (tidy|svg nolyfill)$"]'
|
|
||||||
RENOVATE_REPOSITORIES: '["go-gitea/gitea"]'
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
name: cron-translations
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: "7 0 * * *" # every day at 00:07 UTC
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
crowdin-pull:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: github.repository == 'go-gitea/gitea'
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: crowdin/github-action@8868a33591d21088edfc398968173a3b98d51706 # v2.16.2
|
|
||||||
with:
|
|
||||||
upload_sources: true
|
|
||||||
upload_translations: false
|
|
||||||
download_sources: false
|
|
||||||
download_translations: true
|
|
||||||
push_translations: false
|
|
||||||
push_sources: false
|
|
||||||
create_pull_request: false
|
|
||||||
config: crowdin.yml
|
|
||||||
env:
|
|
||||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
|
||||||
CROWDIN_KEY: ${{ secrets.CROWDIN_KEY }}
|
|
||||||
- name: update locales
|
|
||||||
run: ./build/update-locales.sh
|
|
||||||
- name: push translations to repo
|
|
||||||
uses: appleboy/git-push-action@3b2c8661652360dbf1afe1b319a49dbb739c39f1 # v1.2.0
|
|
||||||
with:
|
|
||||||
author_email: "teabot@gitea.io"
|
|
||||||
author_name: GiteaBot
|
|
||||||
branch: main
|
|
||||||
commit: true
|
|
||||||
commit_message: "[skip ci] Updated translations via Crowdin"
|
|
||||||
remote: "git@github.com:go-gitea/gitea.git"
|
|
||||||
ssh_key: ${{ secrets.DEPLOY_KEY }}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
name: files-changed
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_call:
|
|
||||||
outputs:
|
|
||||||
backend:
|
|
||||||
value: ${{ jobs.detect.outputs.backend }}
|
|
||||||
frontend:
|
|
||||||
value: ${{ jobs.detect.outputs.frontend }}
|
|
||||||
docs:
|
|
||||||
value: ${{ jobs.detect.outputs.docs }}
|
|
||||||
actions:
|
|
||||||
value: ${{ jobs.detect.outputs.actions }}
|
|
||||||
templates:
|
|
||||||
value: ${{ jobs.detect.outputs.templates }}
|
|
||||||
docker:
|
|
||||||
value: ${{ jobs.detect.outputs.docker }}
|
|
||||||
dockerfile:
|
|
||||||
value: ${{ jobs.detect.outputs.dockerfile }}
|
|
||||||
swagger:
|
|
||||||
value: ${{ jobs.detect.outputs.swagger }}
|
|
||||||
yaml:
|
|
||||||
value: ${{ jobs.detect.outputs.yaml }}
|
|
||||||
json:
|
|
||||||
value: ${{ jobs.detect.outputs.json }}
|
|
||||||
e2e:
|
|
||||||
value: ${{ jobs.detect.outputs.e2e }}
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
detect:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 3
|
|
||||||
outputs:
|
|
||||||
backend: ${{ steps.changes.outputs.backend }}
|
|
||||||
frontend: ${{ steps.changes.outputs.frontend }}
|
|
||||||
docs: ${{ steps.changes.outputs.docs }}
|
|
||||||
actions: ${{ steps.changes.outputs.actions }}
|
|
||||||
templates: ${{ steps.changes.outputs.templates }}
|
|
||||||
docker: ${{ steps.changes.outputs.docker }}
|
|
||||||
dockerfile: ${{ steps.changes.outputs.dockerfile }}
|
|
||||||
swagger: ${{ steps.changes.outputs.swagger }}
|
|
||||||
yaml: ${{ steps.changes.outputs.yaml }}
|
|
||||||
json: ${{ steps.changes.outputs.json }}
|
|
||||||
e2e: ${{ steps.changes.outputs.e2e }}
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
|
||||||
id: changes
|
|
||||||
with:
|
|
||||||
filters: |
|
|
||||||
backend:
|
|
||||||
- "**/*.go"
|
|
||||||
- "templates/**/*.tmpl"
|
|
||||||
- "assets/emoji.json"
|
|
||||||
- "go.mod"
|
|
||||||
- "go.sum"
|
|
||||||
- "Makefile"
|
|
||||||
- ".golangci.yml"
|
|
||||||
- ".editorconfig"
|
|
||||||
- "options/locale/locale_en-US.json"
|
|
||||||
|
|
||||||
frontend:
|
|
||||||
- "*.js"
|
|
||||||
- "*.ts"
|
|
||||||
- "web_src/**"
|
|
||||||
- "tools/*.js"
|
|
||||||
- "tools/*.ts"
|
|
||||||
- "assets/emoji.json"
|
|
||||||
- "package.json"
|
|
||||||
- "pnpm-lock.yaml"
|
|
||||||
- "Makefile"
|
|
||||||
- ".eslintrc.cjs"
|
|
||||||
- ".npmrc"
|
|
||||||
|
|
||||||
docs:
|
|
||||||
- "**/*.md"
|
|
||||||
- ".markdownlint.yaml"
|
|
||||||
- "package.json"
|
|
||||||
- "pnpm-lock.yaml"
|
|
||||||
|
|
||||||
actions:
|
|
||||||
- ".github/workflows/*"
|
|
||||||
- "Makefile"
|
|
||||||
|
|
||||||
templates:
|
|
||||||
- "tools/lint-templates-*.js"
|
|
||||||
- "templates/**/*.tmpl"
|
|
||||||
- "pyproject.toml"
|
|
||||||
- "uv.lock"
|
|
||||||
|
|
||||||
docker:
|
|
||||||
- ".github/workflows/pull-docker-dryrun.yml"
|
|
||||||
- "Dockerfile"
|
|
||||||
- "Dockerfile.rootless"
|
|
||||||
- "docker/**"
|
|
||||||
- "Makefile"
|
|
||||||
|
|
||||||
dockerfile:
|
|
||||||
- "Dockerfile"
|
|
||||||
- "Dockerfile.rootless"
|
|
||||||
|
|
||||||
swagger:
|
|
||||||
- "templates/swagger/v1_json.tmpl"
|
|
||||||
- "templates/swagger/v1_input.json"
|
|
||||||
- "Makefile"
|
|
||||||
- "package.json"
|
|
||||||
- "pnpm-lock.yaml"
|
|
||||||
- ".spectral.yaml"
|
|
||||||
|
|
||||||
yaml:
|
|
||||||
- "**/*.yml"
|
|
||||||
- "**/*.yaml"
|
|
||||||
- ".yamllint.yaml"
|
|
||||||
- "pyproject.toml"
|
|
||||||
|
|
||||||
json:
|
|
||||||
- "**/*.json"
|
|
||||||
|
|
||||||
e2e:
|
|
||||||
- "tests/e2e/**"
|
|
||||||
- "tools/test-e2e.sh"
|
|
||||||
- "playwright.config.ts"
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
name: compliance
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
files-changed:
|
|
||||||
uses: ./.github/workflows/files-changed.yml
|
|
||||||
|
|
||||||
lint-backend:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: lint-backend
|
|
||||||
lint-cache: "true"
|
|
||||||
- run: make deps-backend deps-tools
|
|
||||||
- run: make lint-backend
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
|
|
||||||
lint-on-demand:
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
|
|
||||||
- run: make lint-spell
|
|
||||||
|
|
||||||
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
|
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
|
||||||
- if: needs.files-changed.outputs.templates == 'true' || needs.files-changed.outputs.yaml == 'true'
|
|
||||||
run: uv python install 3.14 && make deps-py lint-templates lint-yaml
|
|
||||||
|
|
||||||
- if: needs.files-changed.outputs.docs == 'true' || needs.files-changed.outputs.swagger == 'true' || needs.files-changed.outputs.json == 'true'
|
|
||||||
run: make deps-frontend lint-md lint-swagger lint-json
|
|
||||||
|
|
||||||
- if: needs.files-changed.outputs.actions == 'true'
|
|
||||||
run: make lint-actions
|
|
||||||
|
|
||||||
lint-go-windows:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: lint-go-windows
|
|
||||||
lint-cache: "true"
|
|
||||||
- run: make deps-backend deps-tools
|
|
||||||
- run: make lint-go-windows
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
GOOS: windows
|
|
||||||
GOARCH: amd64
|
|
||||||
|
|
||||||
lint-go-gogit:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: lint-go-gogit
|
|
||||||
lint-cache: "true"
|
|
||||||
- run: make deps-backend deps-tools
|
|
||||||
- run: make lint-go
|
|
||||||
env:
|
|
||||||
TAGS: bindata gogit
|
|
||||||
|
|
||||||
checks-backend:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: checks-backend
|
|
||||||
build-cache: "false"
|
|
||||||
- run: make deps-backend deps-tools
|
|
||||||
- run: make --always-make checks-backend # ensure the "go-licenses" make target runs
|
|
||||||
|
|
||||||
frontend:
|
|
||||||
if: needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-frontend
|
|
||||||
- run: make lint-frontend
|
|
||||||
- run: make checks-frontend
|
|
||||||
- run: make test-frontend
|
|
||||||
- run: make frontend
|
|
||||||
|
|
||||||
backend:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: compliance-backend
|
|
||||||
- run: make deps-backend generate-go
|
|
||||||
# no frontend build here as backend should be able to build, even without any frontend files
|
|
||||||
# CGO is not used when cross-compile, so these steps also test if the code is compatible with CGO disabled
|
|
||||||
- name: build-backend-arm64
|
|
||||||
run: go build -o gitea_linux_arm64
|
|
||||||
env:
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: arm64
|
|
||||||
TAGS: bindata gogit
|
|
||||||
- name: build-backend-windows
|
|
||||||
run: go build -o gitea_windows
|
|
||||||
env:
|
|
||||||
GOOS: windows
|
|
||||||
GOARCH: amd64
|
|
||||||
TAGS: bindata gogit
|
|
||||||
- name: build-backend-386
|
|
||||||
run: go build -o gitea_linux_386
|
|
||||||
env:
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: 386
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
name: db-tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
files-changed:
|
|
||||||
uses: ./.github/workflows/files-changed.yml
|
|
||||||
|
|
||||||
test-pgsql:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
pgsql:
|
|
||||||
image: postgres:14
|
|
||||||
env:
|
|
||||||
POSTGRES_DB: test
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
ldap:
|
|
||||||
image: gitea/test-openldap:latest
|
|
||||||
ports:
|
|
||||||
- "389:389"
|
|
||||||
- "636:636"
|
|
||||||
minio:
|
|
||||||
# as github actions doesn't support "entrypoint", we need to use a non-official image
|
|
||||||
# that has a custom entrypoint set to "minio server /data"
|
|
||||||
image: bitnamilegacy/minio:2023.12.23
|
|
||||||
env:
|
|
||||||
MINIO_ROOT_USER: 123456
|
|
||||||
MINIO_ROOT_PASSWORD: 12345678
|
|
||||||
ports:
|
|
||||||
- "9000:9000"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: pgsql
|
|
||||||
- name: Add hosts to /etc/hosts
|
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 pgsql ldap minio" | sudo tee -a /etc/hosts'
|
|
||||||
- run: make deps-backend
|
|
||||||
- run: make backend
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- name: run migration tests
|
|
||||||
run: GITEA_TEST_DATABASE=pgsql make test-migration
|
|
||||||
- name: run tests
|
|
||||||
run: GITEA_TEST_DATABASE=pgsql make test-integration
|
|
||||||
timeout-minutes: 50
|
|
||||||
env:
|
|
||||||
# pgsql is chosen to be the unlucky one to run with the slow "race detector", it is about 60% slower.
|
|
||||||
GOTEST_FLAGS: -race -timeout=40m
|
|
||||||
TAGS: bindata gogit
|
|
||||||
TEST_LDAP: 1
|
|
||||||
|
|
||||||
test-sqlite:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: sqlite
|
|
||||||
- run: make deps-backend
|
|
||||||
- run: make backend
|
|
||||||
env:
|
|
||||||
TAGS: bindata gogit
|
|
||||||
GOEXPERIMENT:
|
|
||||||
- name: run migration tests
|
|
||||||
run: GITEA_TEST_DATABASE=sqlite make test-migration
|
|
||||||
env:
|
|
||||||
TAGS: bindata gogit
|
|
||||||
- name: run tests
|
|
||||||
run: GITEA_TEST_DATABASE=sqlite make test-integration
|
|
||||||
timeout-minutes: 50
|
|
||||||
env:
|
|
||||||
# sqlite driver can contain large amount of Golang code, so don't use race detector for it, otherwise, extremely slow
|
|
||||||
GOTEST_FLAGS: -timeout=40m
|
|
||||||
TAGS: bindata gogit
|
|
||||||
GOEXPERIMENT:
|
|
||||||
|
|
||||||
test-unit:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
elasticsearch:
|
|
||||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.14
|
|
||||||
env:
|
|
||||||
discovery.type: single-node
|
|
||||||
xpack.security.enabled: false
|
|
||||||
ports:
|
|
||||||
- "9200:9200"
|
|
||||||
meilisearch:
|
|
||||||
image: getmeili/meilisearch:v1
|
|
||||||
env:
|
|
||||||
MEILI_ENV: development # disable auth
|
|
||||||
ports:
|
|
||||||
- "7700:7700"
|
|
||||||
redis:
|
|
||||||
image: redis
|
|
||||||
options: >- # wait until redis has started
|
|
||||||
--health-cmd "redis-cli ping"
|
|
||||||
--health-interval 5s
|
|
||||||
--health-timeout 3s
|
|
||||||
--health-retries 10
|
|
||||||
ports:
|
|
||||||
- 6379:6379
|
|
||||||
minio:
|
|
||||||
image: bitnamilegacy/minio:2021.12.29
|
|
||||||
env:
|
|
||||||
MINIO_ACCESS_KEY: 123456
|
|
||||||
MINIO_SECRET_KEY: 12345678
|
|
||||||
ports:
|
|
||||||
- "9000:9000"
|
|
||||||
devstoreaccount1.azurite.local: # https://github.com/Azure/Azurite/issues/1583
|
|
||||||
image: mcr.microsoft.com/azure-storage/azurite:latest
|
|
||||||
ports:
|
|
||||||
- 10000:10000
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: unit
|
|
||||||
build-cache-rotate: "true"
|
|
||||||
- name: Add hosts to /etc/hosts
|
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 minio devstoreaccount1.azurite.local mysql elasticsearch meilisearch smtpimap" | sudo tee -a /etc/hosts'
|
|
||||||
- run: make deps-backend
|
|
||||||
- run: make backend
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- name: unit-tests
|
|
||||||
run: make test-backend test-check
|
|
||||||
env:
|
|
||||||
GOTEST_FLAGS: -race -timeout=20m
|
|
||||||
TAGS: bindata
|
|
||||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
|
||||||
- name: unit-tests-gogit
|
|
||||||
run: make test-backend test-check
|
|
||||||
env:
|
|
||||||
GOTEST_FLAGS: -race -timeout=20m
|
|
||||||
TAGS: bindata gogit
|
|
||||||
GOEXPERIMENT:
|
|
||||||
GITHUB_READ_TOKEN: ${{ secrets.GITHUB_READ_TOKEN }}
|
|
||||||
|
|
||||||
test-mysql:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
mysql:
|
|
||||||
# the bitnami mysql image has more options than the official one, it's easier to customize
|
|
||||||
image: bitnamilegacy/mysql:8.4
|
|
||||||
env:
|
|
||||||
ALLOW_EMPTY_PASSWORD: true
|
|
||||||
MYSQL_DATABASE: testgitea
|
|
||||||
ports:
|
|
||||||
- "3306:3306"
|
|
||||||
options: >-
|
|
||||||
--mount type=tmpfs,destination=/bitnami/mysql/data
|
|
||||||
elasticsearch:
|
|
||||||
image: docker.elastic.co/elasticsearch/elasticsearch:8.19.14
|
|
||||||
env:
|
|
||||||
discovery.type: single-node
|
|
||||||
xpack.security.enabled: false
|
|
||||||
ports:
|
|
||||||
- "9200:9200"
|
|
||||||
smtpimap:
|
|
||||||
image: tabascoterrier/docker-imap-devel:latest
|
|
||||||
ports:
|
|
||||||
- "25:25"
|
|
||||||
- "143:143"
|
|
||||||
- "587:587"
|
|
||||||
- "993:993"
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: mysql
|
|
||||||
- name: Add hosts to /etc/hosts
|
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mysql elasticsearch smtpimap" | sudo tee -a /etc/hosts'
|
|
||||||
- run: make deps-backend
|
|
||||||
- run: make backend
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- name: run migration tests
|
|
||||||
run: GITEA_TEST_DATABASE=mysql make test-migration
|
|
||||||
- name: run tests
|
|
||||||
run: GITEA_TEST_DATABASE=mysql make test-integration
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
TEST_INDEXER_CODE_ES_URL: "http://elastic:changeme@elasticsearch:9200"
|
|
||||||
|
|
||||||
test-mssql:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.actions == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
services:
|
|
||||||
mssql:
|
|
||||||
image: mcr.microsoft.com/mssql/server:2019-latest
|
|
||||||
env:
|
|
||||||
ACCEPT_EULA: Y
|
|
||||||
MSSQL_PID: Standard
|
|
||||||
SA_PASSWORD: MwantsaSecurePassword1
|
|
||||||
ports:
|
|
||||||
- "1433:1433"
|
|
||||||
devstoreaccount1.azurite.local: # https://github.com/Azure/Azurite/issues/1583
|
|
||||||
image: mcr.microsoft.com/azure-storage/azurite:latest
|
|
||||||
ports:
|
|
||||||
- 10000:10000
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: mssql
|
|
||||||
- name: Add hosts to /etc/hosts
|
|
||||||
run: '[ -e "/.dockerenv" ] || [ -e "/run/.containerenv" ] || echo "127.0.0.1 mssql devstoreaccount1.azurite.local" | sudo tee -a /etc/hosts'
|
|
||||||
- run: make deps-backend
|
|
||||||
- run: make backend
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- run: GITEA_TEST_DATABASE=mssql make test-migration
|
|
||||||
- name: run tests
|
|
||||||
run: GITEA_TEST_DATABASE=mssql make test-integration
|
|
||||||
timeout-minutes: 50
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
name: docker-dryrun
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
files-changed:
|
|
||||||
uses: ./.github/workflows/files-changed.yml
|
|
||||||
|
|
||||||
# QEMU-based build is slow (40-50 minutes), so run arm64 and riscv64 when dockerfile changes.
|
|
||||||
# Run amd64 when any docker-related files change, which is fast (4 minutes).
|
|
||||||
container-amd64:
|
|
||||||
if: needs.files-changed.outputs.docker == 'true'
|
|
||||||
needs: [files-changed]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: ./.github/actions/docker-dryrun
|
|
||||||
with:
|
|
||||||
platform: linux/amd64
|
|
||||||
|
|
||||||
container-arm64:
|
|
||||||
if: needs.files-changed.outputs.dockerfile == 'true'
|
|
||||||
needs: [files-changed]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: ./.github/actions/docker-dryrun
|
|
||||||
with:
|
|
||||||
platform: linux/arm64
|
|
||||||
|
|
||||||
container-riscv64:
|
|
||||||
if: needs.files-changed.outputs.dockerfile == 'true'
|
|
||||||
needs: [files-changed]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: ./.github/actions/docker-dryrun
|
|
||||||
with:
|
|
||||||
platform: linux/riscv64
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
name: e2e-tests
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
files-changed:
|
|
||||||
uses: ./.github/workflows/files-changed.yml
|
|
||||||
|
|
||||||
test-e2e:
|
|
||||||
if: needs.files-changed.outputs.backend == 'true' || needs.files-changed.outputs.frontend == 'true' || needs.files-changed.outputs.e2e == 'true'
|
|
||||||
needs: files-changed
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
cache: false
|
|
||||||
- uses: ./.github/actions/go-cache
|
|
||||||
with:
|
|
||||||
cache-name: e2e
|
|
||||||
build-cache: "false"
|
|
||||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-frontend
|
|
||||||
- run: make frontend
|
|
||||||
- run: make deps-backend
|
|
||||||
- run: make backend
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- run: make playwright
|
|
||||||
- run: make test-e2e
|
|
||||||
timeout-minutes: 10
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
FORCE_COLOR: 1
|
|
||||||
GITEA_TEST_E2E_DEBUG: 1
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
name: labeler
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
labeler:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
steps:
|
|
||||||
- uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
|
|
||||||
with:
|
|
||||||
sync-labels: true
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
name: pr-title
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types:
|
|
||||||
- opened
|
|
||||||
- edited
|
|
||||||
- reopened
|
|
||||||
- synchronize
|
|
||||||
- ready_for_review
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint-pr-title:
|
|
||||||
if: github.event.pull_request.draft == false
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 5
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
- run: make lint-pr-title
|
|
||||||
env:
|
|
||||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
name: release-nightly
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main, release/v*]
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
nightly-binary:
|
|
||||||
runs-on: namespace-profile-gitea-release-binary
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-frontend deps-backend
|
|
||||||
# xgo build
|
|
||||||
- run: make release
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- name: import gpg key
|
|
||||||
id: import_gpg
|
|
||||||
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
|
||||||
with:
|
|
||||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
|
||||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
|
||||||
- name: sign binaries
|
|
||||||
run: |
|
|
||||||
for f in dist/release/*; do
|
|
||||||
echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f"
|
|
||||||
done
|
|
||||||
# clean branch name to get the folder name in S3
|
|
||||||
- name: Get cleaned branch name
|
|
||||||
id: clean_name
|
|
||||||
run: |
|
|
||||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
|
||||||
echo "Cleaned name is ${REF_NAME}"
|
|
||||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
|
||||||
- name: configure aws
|
|
||||||
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
|
|
||||||
with:
|
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
- name: upload binaries to s3
|
|
||||||
run: |
|
|
||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
|
||||||
|
|
||||||
nightly-container:
|
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write # to publish to ghcr.io
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
|
||||||
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
|
||||||
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
|
||||||
- name: Get cleaned branch name
|
|
||||||
id: clean_name
|
|
||||||
run: |
|
|
||||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\///' -e 's/release\/v//')
|
|
||||||
echo "branch=${REF_NAME}-nightly" >> "$GITHUB_OUTPUT"
|
|
||||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
|
||||||
id: meta
|
|
||||||
with:
|
|
||||||
images: |-
|
|
||||||
gitea/gitea
|
|
||||||
ghcr.io/go-gitea/gitea
|
|
||||||
tags: |
|
|
||||||
type=raw,value=${{ steps.clean_name.outputs.branch }}
|
|
||||||
annotations: |
|
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
|
||||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
|
||||||
id: meta_rootless
|
|
||||||
with:
|
|
||||||
images: |-
|
|
||||||
gitea/gitea
|
|
||||||
ghcr.io/go-gitea/gitea
|
|
||||||
# each tag below will have the suffix of -rootless
|
|
||||||
flavor: |
|
|
||||||
suffix=-rootless
|
|
||||||
tags: |
|
|
||||||
type=raw,value=${{ steps.clean_name.outputs.branch }}
|
|
||||||
annotations: |
|
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Login to GHCR using PAT
|
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: build regular docker image
|
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
|
||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful
|
|
||||||
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootful,mode=max
|
|
||||||
- name: build rootless docker image
|
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
push: true
|
|
||||||
file: Dockerfile.rootless
|
|
||||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
|
||||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
|
||||||
cache-from: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless
|
|
||||||
cache-to: type=registry,ref=ghcr.io/go-gitea/gitea:buildcache-rootless,mode=max
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
name: release-tag-rc
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v1*-rc*"
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
binary:
|
|
||||||
runs-on: namespace-profile-gitea-release-binary
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-frontend deps-backend
|
|
||||||
# xgo build
|
|
||||||
- run: make release
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- name: import gpg key
|
|
||||||
id: import_gpg
|
|
||||||
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
|
||||||
with:
|
|
||||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
|
||||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
|
||||||
- name: sign binaries
|
|
||||||
run: |
|
|
||||||
for f in dist/release/*; do
|
|
||||||
echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f"
|
|
||||||
done
|
|
||||||
# clean branch name to get the folder name in S3
|
|
||||||
- name: Get cleaned branch name
|
|
||||||
id: clean_name
|
|
||||||
run: |
|
|
||||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\/v//' -e 's/release\/v//')
|
|
||||||
echo "Cleaned name is ${REF_NAME}"
|
|
||||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
|
||||||
- name: configure aws
|
|
||||||
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
|
|
||||||
with:
|
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
- name: upload binaries to s3
|
|
||||||
run: |
|
|
||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
|
||||||
- name: Install GH CLI
|
|
||||||
uses: dev-hanz-ops/install-gh-cli-action@af38ce09b1ec248aeb08eea2b16bbecea9e059f8 # v0.2.1
|
|
||||||
with:
|
|
||||||
gh-cli-version: 2.39.1
|
|
||||||
- name: create github release
|
|
||||||
run: |
|
|
||||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --draft --notes-from-tag dist/release/*
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
container:
|
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write # to publish to ghcr.io
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
|
||||||
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
|
||||||
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
|
||||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
|
||||||
id: meta
|
|
||||||
with:
|
|
||||||
images: |-
|
|
||||||
gitea/gitea
|
|
||||||
ghcr.io/go-gitea/gitea
|
|
||||||
flavor: |
|
|
||||||
latest=false
|
|
||||||
# 1.2.3-rc0
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
annotations: |
|
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
|
||||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
|
||||||
id: meta_rootless
|
|
||||||
with:
|
|
||||||
images: |-
|
|
||||||
gitea/gitea
|
|
||||||
ghcr.io/go-gitea/gitea
|
|
||||||
# each tag below will have the suffix of -rootless
|
|
||||||
flavor: |
|
|
||||||
latest=false
|
|
||||||
suffix=-rootless
|
|
||||||
# 1.2.3-rc0
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
annotations: |
|
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Login to GHCR using PAT
|
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: build regular container image
|
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
|
||||||
- name: build rootless container image
|
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
push: true
|
|
||||||
file: Dockerfile.rootless
|
|
||||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
|
||||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
name: release-tag-version
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v1.*"
|
|
||||||
- "!v1*-rc*"
|
|
||||||
- "!v1*-dev"
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
cancel-in-progress: false
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
binary:
|
|
||||||
runs-on: namespace-profile-gitea-release-binary
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write # to publish to ghcr.io
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
|
||||||
with:
|
|
||||||
go-version-file: go.mod
|
|
||||||
check-latest: true
|
|
||||||
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: pnpm
|
|
||||||
cache-dependency-path: pnpm-lock.yaml
|
|
||||||
- run: make deps-frontend deps-backend
|
|
||||||
# xgo build
|
|
||||||
- run: make release
|
|
||||||
env:
|
|
||||||
TAGS: bindata
|
|
||||||
- name: import gpg key
|
|
||||||
id: import_gpg
|
|
||||||
uses: crazy-max/ghaction-import-gpg@2dc316deee8e90f13e1a351ab510b4d5bc0c82cd # v7.0.0
|
|
||||||
with:
|
|
||||||
gpg_private_key: ${{ secrets.GPGSIGN_KEY }}
|
|
||||||
passphrase: ${{ secrets.GPGSIGN_PASSPHRASE }}
|
|
||||||
- name: sign binaries
|
|
||||||
run: |
|
|
||||||
for f in dist/release/*; do
|
|
||||||
echo '${{ secrets.GPGSIGN_PASSPHRASE }}' | gpg --pinentry-mode loopback --passphrase-fd 0 --batch --yes --detach-sign -u ${{ steps.import_gpg.outputs.fingerprint }} --output "$f.asc" "$f"
|
|
||||||
done
|
|
||||||
# clean branch name to get the folder name in S3
|
|
||||||
- name: Get cleaned branch name
|
|
||||||
id: clean_name
|
|
||||||
run: |
|
|
||||||
REF_NAME=$(echo "${{ github.ref }}" | sed -e 's/refs\/heads\///' -e 's/refs\/tags\/v//' -e 's/release\/v//')
|
|
||||||
echo "Cleaned name is ${REF_NAME}"
|
|
||||||
echo "branch=${REF_NAME}" >> "$GITHUB_OUTPUT"
|
|
||||||
- name: configure aws
|
|
||||||
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
|
|
||||||
with:
|
|
||||||
aws-region: ${{ secrets.AWS_REGION }}
|
|
||||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
|
||||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
|
||||||
- name: upload binaries to s3
|
|
||||||
run: |
|
|
||||||
aws s3 sync dist/release s3://${{ secrets.AWS_S3_BUCKET }}/gitea/${{ steps.clean_name.outputs.branch }} --no-progress
|
|
||||||
- name: Install GH CLI
|
|
||||||
uses: dev-hanz-ops/install-gh-cli-action@af38ce09b1ec248aeb08eea2b16bbecea9e059f8 # v0.2.1
|
|
||||||
with:
|
|
||||||
gh-cli-version: 2.39.1
|
|
||||||
- name: create github release
|
|
||||||
run: |
|
|
||||||
gh release create ${{ github.ref_name }} --title ${{ github.ref_name }} --notes-from-tag dist/release/*
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
|
|
||||||
|
|
||||||
container:
|
|
||||||
runs-on: namespace-profile-gitea-release-docker
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
packages: write # to publish to ghcr.io
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
||||||
# fetch all commits instead of only the last as some branches are long lived and could have many between versions
|
|
||||||
# fetch all tags to ensure that "git describe" reports expected Gitea version, eg. v1.21.0-dev-1-g1234567
|
|
||||||
- run: git fetch --unshallow --quiet --tags --force
|
|
||||||
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4.0.0
|
|
||||||
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
|
||||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
|
||||||
id: meta
|
|
||||||
with:
|
|
||||||
images: |-
|
|
||||||
gitea/gitea
|
|
||||||
ghcr.io/go-gitea/gitea
|
|
||||||
# this will generate tags in the following format:
|
|
||||||
# latest
|
|
||||||
# 1
|
|
||||||
# 1.2
|
|
||||||
# 1.2.3
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
annotations: |
|
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
|
||||||
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
|
||||||
id: meta_rootless
|
|
||||||
with:
|
|
||||||
images: |-
|
|
||||||
gitea/gitea
|
|
||||||
ghcr.io/go-gitea/gitea
|
|
||||||
# each tag below will have the suffix of -rootless
|
|
||||||
flavor: |
|
|
||||||
suffix=-rootless,onlatest=true
|
|
||||||
# this will generate tags in the following format (with -rootless suffix added):
|
|
||||||
# latest
|
|
||||||
# 1
|
|
||||||
# 1.2
|
|
||||||
# 1.2.3
|
|
||||||
tags: |
|
|
||||||
type=semver,pattern={{version}}
|
|
||||||
type=semver,pattern={{major}}
|
|
||||||
type=semver,pattern={{major}}.{{minor}}
|
|
||||||
annotations: |
|
|
||||||
org.opencontainers.image.authors="maintainers@gitea.io"
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
- name: Login to GHCR using PAT
|
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
- name: build regular container image
|
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
push: true
|
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
|
||||||
annotations: ${{ steps.meta.outputs.annotations }}
|
|
||||||
- name: build rootless container image
|
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
platforms: linux/amd64,linux/arm64,linux/riscv64
|
|
||||||
push: true
|
|
||||||
file: Dockerfile.rootless
|
|
||||||
tags: ${{ steps.meta_rootless.outputs.tags }}
|
|
||||||
annotations: ${{ steps.meta_rootless.outputs.annotations }}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<name>MokoGitea</name>
|
<name>MokoGitea</name>
|
||||||
<org>MokoConsulting</org>
|
<org>MokoConsulting</org>
|
||||||
<description>Moko fork of Gitea - adding project board REST API endpoints and custom enhancements</description>
|
<description>Moko fork of Gitea - adding project board REST API endpoints and custom enhancements</description>
|
||||||
<version>06.13.00</version>
|
<version>06.14.00</version>
|
||||||
<version-prefix>v1.26.1+MOKO</version-prefix>
|
<version-prefix>v1.26.1+MOKO</version-prefix>
|
||||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||||
</identity>
|
</identity>
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
|
||||||
- name: Delete merged branches
|
- name: Delete merged branches
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Merged Branch Cleanup ==="
|
echo "=== Merged Branch Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
@@ -66,7 +66,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Clean old workflow runs
|
- name: Clean old workflow runs
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Workflow Run Cleanup ==="
|
echo "=== Workflow Run Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|||||||
@@ -42,10 +42,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Setup MokoStandards tools
|
- name: Setup MokoStandards tools
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
|
||||||
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_TOKEN || github.token }}"}}'
|
||||||
run: |
|
run: |
|
||||||
git clone --depth 1 --branch main --quiet \
|
git clone --depth 1 --branch main --quiet \
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
version:
|
version:
|
||||||
description: 'Version tag (e.g. v1.26.1+MOKO06.12.00)'
|
description: 'Version tag'
|
||||||
required: true
|
required: true
|
||||||
default: 'latest'
|
default: 'latest'
|
||||||
environment:
|
environment:
|
||||||
@@ -28,9 +28,9 @@ concurrency:
|
|||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REGISTRY: code.mokoconsulting.tech
|
REGISTRY: git.mokoconsulting.tech
|
||||||
IMAGE: mokoconsulting/mokogitea
|
IMAGE: mokoconsulting/mokogitea
|
||||||
DEPLOY_HOST: code.mokoconsulting.tech
|
DEPLOY_HOST: git.mokoconsulting.tech
|
||||||
DEPLOY_PORT: 2918
|
DEPLOY_PORT: 2918
|
||||||
DEPLOY_USER: mokoconsulting
|
DEPLOY_USER: mokoconsulting
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
@@ -47,8 +47,6 @@ jobs:
|
|||||||
- name: Determine settings
|
- name: Determine settings
|
||||||
id: config
|
id: config
|
||||||
run: |
|
run: |
|
||||||
# On push to main, auto-deploy to production with git-derived version.
|
|
||||||
# On workflow_dispatch, use the provided inputs.
|
|
||||||
if [ "${{ github.event_name }}" = "push" ]; then
|
if [ "${{ github.event_name }}" = "push" ]; then
|
||||||
VERSION=$(git describe --tags --always 2>/dev/null || echo "dev-$(git rev-parse --short HEAD)")
|
VERSION=$(git describe --tags --always 2>/dev/null || echo "dev-$(git rev-parse --short HEAD)")
|
||||||
ENV="production"
|
ENV="production"
|
||||||
@@ -56,217 +54,96 @@ jobs:
|
|||||||
VERSION="${{ github.event.inputs.version }}"
|
VERSION="${{ github.event.inputs.version }}"
|
||||||
ENV="${{ github.event.inputs.environment }}"
|
ENV="${{ github.event.inputs.environment }}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$ENV" = "production" ]; then
|
if [ "$ENV" = "production" ]; then
|
||||||
echo "compose_dir=/opt/gitea" >> $GITHUB_OUTPUT
|
echo "compose_dir=/opt/gitea" >> $GITHUB_OUTPUT
|
||||||
echo "container=mokogitea" >> $GITHUB_OUTPUT
|
echo "container=mokogitea" >> $GITHUB_OUTPUT
|
||||||
echo "source_dir=/opt/gitea/source" >> $GITHUB_OUTPUT
|
echo "source_dir=/opt/gitea/source" >> $GITHUB_OUTPUT
|
||||||
echo "branch=main" >> $GITHUB_OUTPUT
|
echo "branch=main" >> $GITHUB_OUTPUT
|
||||||
echo "tag=${VERSION}" >> $GITHUB_OUTPUT
|
echo "tag=$VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "instance_url=https://code.mokoconsulting.tech" >> $GITHUB_OUTPUT
|
|
||||||
else
|
else
|
||||||
echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT
|
echo "compose_dir=/opt/gitea-dev" >> $GITHUB_OUTPUT
|
||||||
echo "container=mokogitea-dev" >> $GITHUB_OUTPUT
|
echo "container=mokogitea-dev" >> $GITHUB_OUTPUT
|
||||||
echo "source_dir=/opt/gitea-dev/source" >> $GITHUB_OUTPUT
|
echo "source_dir=/opt/gitea-dev/source" >> $GITHUB_OUTPUT
|
||||||
echo "branch=dev" >> $GITHUB_OUTPUT
|
echo "branch=dev" >> $GITHUB_OUTPUT
|
||||||
echo "tag=${VERSION}-dev" >> $GITHUB_OUTPUT
|
echo "tag=$VERSION-dev" >> $GITHUB_OUTPUT
|
||||||
echo "instance_url=https://git.dev.mokoconsulting.tech" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Enable maintenance mode
|
- name: Write deploy key
|
||||||
env:
|
env:
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
INSTANCE_URL: ${{ steps.config.outputs.instance_url }}
|
|
||||||
run: |
|
run: |
|
||||||
echo "Enabling maintenance mode on ${INSTANCE_URL}..."
|
mkdir -p ~/.ssh
|
||||||
curl -sf -X POST \
|
echo "$DEPLOY_KEY" > ~/.ssh/deploy_key
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
chmod 600 ~/.ssh/deploy_key
|
||||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
||||||
"${INSTANCE_URL}/-/admin/config" \
|
|
||||||
-d 'key=instance.maintenance_mode&value={"AdminWebAccessOnly":true}' \
|
|
||||||
|| echo "WARNING: Could not enable maintenance mode (instance may be down)"
|
|
||||||
|
|
||||||
- name: Build and deploy via SSH
|
- name: Build and deploy via SSH
|
||||||
env:
|
env:
|
||||||
SSH_PRIVATE_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
|
REGISTRY_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
TAG: ${{ steps.config.outputs.tag }}
|
TAG: ${{ steps.config.outputs.tag }}
|
||||||
BRANCH: ${{ steps.config.outputs.branch }}
|
BRANCH: ${{ steps.config.outputs.branch }}
|
||||||
SOURCE_DIR: ${{ steps.config.outputs.source_dir }}
|
SOURCE_DIR: ${{ steps.config.outputs.source_dir }}
|
||||||
COMPOSE_DIR: ${{ steps.config.outputs.compose_dir }}
|
COMPOSE_DIR: ${{ steps.config.outputs.compose_dir }}
|
||||||
CONTAINER: ${{ steps.config.outputs.container }}
|
CONTAINER: ${{ steps.config.outputs.container }}
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ~/.ssh
|
HEALTH_FMT='${{ '{{' }}.State.Health.Status${{ '}}' }}'
|
||||||
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
|
IMAGE_FMT='Image: ${{ '{{' }}.Config.Image${{ '}}' }}'
|
||||||
chmod 600 ~/.ssh/deploy_key
|
|
||||||
|
|
||||||
SSH_CMD="ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} -o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }}"
|
ssh -i ~/.ssh/deploy_key -p ${{ env.DEPLOY_PORT }} \
|
||||||
|
-o ConnectTimeout=30 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
||||||
|
-o ServerAliveInterval=30 -o ServerAliveCountMax=10 \
|
||||||
|
${{ env.DEPLOY_USER }}@${{ env.DEPLOY_HOST }} bash -s <<DEPLOY_EOF
|
||||||
|
set -e
|
||||||
|
echo 'SSH connected'
|
||||||
|
|
||||||
$SSH_CMD "echo 'SSH connected'"
|
echo 'Cleaning Docker build cache...'
|
||||||
|
docker builder prune -af 2>/dev/null || true
|
||||||
|
docker image prune -af 2>/dev/null || true
|
||||||
|
sudo swapoff -a && sudo swapon -a 2>/dev/null || true
|
||||||
|
free -m | head -3
|
||||||
|
|
||||||
# Pre-deploy cleanup: free disk and memory for the build
|
echo 'Pulling source...'
|
||||||
$SSH_CMD "
|
if [ ! -d $SOURCE_DIR/.git ]; then
|
||||||
echo 'Cleaning Docker build cache and unused images...'
|
git clone -b $BRANCH https://git.mokoconsulting.tech/MokoConsulting/MokoGitea.git $SOURCE_DIR
|
||||||
docker builder prune -af 2>/dev/null || true
|
fi
|
||||||
docker image prune -af 2>/dev/null || true
|
cd $SOURCE_DIR
|
||||||
echo 'Clearing swap...'
|
git fetch origin $BRANCH
|
||||||
sudo swapoff -a && sudo swapon -a 2>/dev/null || true
|
git reset --hard origin/$BRANCH
|
||||||
echo 'Cleanup complete'
|
|
||||||
free -m | head -3
|
|
||||||
"
|
|
||||||
|
|
||||||
# Pull latest source
|
echo 'Building Docker image...'
|
||||||
$SSH_CMD "
|
docker build --no-cache --build-arg GOFLAGS='-p 1' \
|
||||||
set -e
|
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG \
|
||||||
if [ ! -d ${SOURCE_DIR}/.git ]; then
|
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest \
|
||||||
git clone -b ${BRANCH} https://code.mokoconsulting.tech/MokoConsulting/MokoGitea.git ${SOURCE_DIR}
|
-f Dockerfile .
|
||||||
|
|
||||||
|
echo 'Pushing to registry...'
|
||||||
|
echo '$REGISTRY_TOKEN' | docker login ${{ env.REGISTRY }} -u ${{ env.DEPLOY_USER }} --password-stdin
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:$TAG
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest
|
||||||
|
|
||||||
|
echo 'Restarting container...'
|
||||||
|
cd $COMPOSE_DIR
|
||||||
|
sed -i 's|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:$TAG|' docker-compose.yml
|
||||||
|
docker compose up -d $CONTAINER
|
||||||
|
|
||||||
|
echo 'Health check...'
|
||||||
|
for i in 1 2 3 4 5 6 7 8; do
|
||||||
|
sleep 15
|
||||||
|
if docker inspect --format='$HEALTH_FMT' $CONTAINER 2>/dev/null | grep -q healthy; then
|
||||||
|
echo 'Container healthy!'
|
||||||
|
docker inspect --format='$IMAGE_FMT' $CONTAINER
|
||||||
|
exit 0
|
||||||
fi
|
fi
|
||||||
cd ${SOURCE_DIR}
|
echo "Waiting... (attempt \$i/8)"
|
||||||
git fetch origin ${BRANCH}
|
done
|
||||||
git reset --hard origin/${BRANCH}
|
echo 'Health check failed'
|
||||||
"
|
docker logs $CONTAINER --tail 20
|
||||||
|
exit 1
|
||||||
# Build Docker image
|
DEPLOY_EOF
|
||||||
$SSH_CMD "
|
|
||||||
set -e
|
|
||||||
cd ${SOURCE_DIR}
|
|
||||||
docker build --no-cache --build-arg GOFLAGS='-p 1' \
|
|
||||||
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG} \
|
|
||||||
--tag ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest \
|
|
||||||
-f Dockerfile .
|
|
||||||
"
|
|
||||||
|
|
||||||
# Push to container registry
|
|
||||||
$SSH_CMD "
|
|
||||||
set -e
|
|
||||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${TAG}
|
|
||||||
docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:latest
|
|
||||||
"
|
|
||||||
|
|
||||||
# Update compose and restart
|
|
||||||
$SSH_CMD "
|
|
||||||
set -e
|
|
||||||
cd ${COMPOSE_DIR}
|
|
||||||
sed -i 's|${{ env.IMAGE }}:[^ ]*|${{ env.IMAGE }}:${TAG}|' docker-compose.yml
|
|
||||||
docker compose up -d ${CONTAINER}
|
|
||||||
"
|
|
||||||
|
|
||||||
# Health check
|
|
||||||
$SSH_CMD "
|
|
||||||
for i in 1 2 3 4 5 6 7 8; do
|
|
||||||
sleep 15
|
|
||||||
if docker inspect --format='{{.State.Health.Status}}' ${CONTAINER} 2>/dev/null | grep -q healthy; then
|
|
||||||
echo 'Container healthy!'
|
|
||||||
docker inspect --format='Image: {{.Config.Image}}' ${CONTAINER}
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo \"Waiting... (attempt \$i/8)\"
|
|
||||||
done
|
|
||||||
echo 'Health check failed'
|
|
||||||
docker logs ${CONTAINER} --tail 20
|
|
||||||
exit 1
|
|
||||||
"
|
|
||||||
|
|
||||||
- name: Update updates.xml
|
|
||||||
if: success()
|
|
||||||
env:
|
|
||||||
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
TAG: ${{ steps.config.outputs.tag }}
|
|
||||||
INSTANCE_URL: ${{ steps.config.outputs.instance_url }}
|
|
||||||
DEPLOY_ENV: ${{ github.event.inputs.environment || 'production' }}
|
|
||||||
run: |
|
|
||||||
# Only update updates.xml for production stable releases
|
|
||||||
if [ "$DEPLOY_ENV" != "production" ]; then
|
|
||||||
echo "Skipping updates.xml — dev deployments don't update stable channel"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract project version by stripping the version prefix from the tag.
|
|
||||||
# Reads prefix from manifest API (e.g. "v1.26.1+MOKO"), falls back to legacy pattern.
|
|
||||||
API_BASE="https://${REGISTRY}/api/v1/repos/MokoConsulting/MokoGitea"
|
|
||||||
PREFIX=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
"${API_BASE}/manifest" | python3 -c "import json,sys; print(json.load(sys.stdin).get('version_prefix',''))" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ -n "$PREFIX" ]; then
|
|
||||||
MOKO_VER="${TAG#$PREFIX}"
|
|
||||||
else
|
|
||||||
# Legacy fallback: strip everything up to and including "-moko."
|
|
||||||
MOKO_VER=$(echo "$TAG" | sed -n 's/.*-moko\.\(.*\)/\1/p')
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$MOKO_VER" ]; then
|
|
||||||
echo "Could not extract version from tag: $TAG (prefix: ${PREFIX:-none})"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
RELEASE_URL="https://${REGISTRY}/MokoConsulting/MokoGitea/releases/tag/${TAG}"
|
|
||||||
DOCKER_IMG="${REGISTRY}/${IMAGE}:${TAG}"
|
|
||||||
|
|
||||||
python3 << PYEOF
|
|
||||||
import json, os, re, base64, urllib.request
|
|
||||||
|
|
||||||
token = os.environ["GITEA_TOKEN"]
|
|
||||||
registry = os.environ["REGISTRY"]
|
|
||||||
tag = os.environ["TAG"]
|
|
||||||
moko_ver = os.environ["MOKO_VER"]
|
|
||||||
release_url = os.environ["RELEASE_URL"]
|
|
||||||
docker_img = os.environ["DOCKER_IMG"]
|
|
||||||
api = f"https://{registry}/api/v1/repos/MokoConsulting/MokoGitea"
|
|
||||||
|
|
||||||
# Fetch current updates.xml
|
|
||||||
req = urllib.request.Request(f"{api}/contents/updates.xml?ref=main",
|
|
||||||
headers={"Authorization": f"token {token}"})
|
|
||||||
with urllib.request.urlopen(req) as resp:
|
|
||||||
data = json.loads(resp.read())
|
|
||||||
sha = data["sha"]
|
|
||||||
content = base64.b64decode(data["content"]).decode("utf-8")
|
|
||||||
|
|
||||||
# Update stable channel — match the <update> block containing <tag>stable</tag>
|
|
||||||
def replace_channel(xml, channel, ver, url, docker):
|
|
||||||
pattern = rf"(<update>\s*<name>MokoGitea</name>[\s\S]*?<tags><tag>{channel}</tag></tags>[\s\S]*?</update>)"
|
|
||||||
def replacer(m):
|
|
||||||
block = m.group(1)
|
|
||||||
block = re.sub(r"<version>[^<]*</version>", f"<version>{ver}</version>", block)
|
|
||||||
block = re.sub(r"(<infourl[^>]*>)[^<]*(</infourl>)", rf"\1{url}\2", block)
|
|
||||||
block = re.sub(r"(<downloadurl[^>]*>)[^<]*(</downloadurl>)", rf"\1{docker}\2", block)
|
|
||||||
return block
|
|
||||||
return re.sub(pattern, replacer, xml)
|
|
||||||
|
|
||||||
content = replace_channel(content, "stable", moko_ver, release_url, docker_img)
|
|
||||||
content = re.sub(r"VERSION: [^\n]*", f"VERSION: {moko_ver}", content)
|
|
||||||
|
|
||||||
# Push updated file
|
|
||||||
encoded = base64.b64encode(content.encode()).decode()
|
|
||||||
payload = json.dumps({
|
|
||||||
"message": f"chore(ci): update updates.xml to {moko_ver}",
|
|
||||||
"content": encoded,
|
|
||||||
"sha": sha,
|
|
||||||
"branch": "main",
|
|
||||||
}).encode()
|
|
||||||
req = urllib.request.Request(f"{api}/contents/updates.xml",
|
|
||||||
data=payload, method="PUT",
|
|
||||||
headers={"Authorization": f"token {token}", "Content-Type": "application/json"})
|
|
||||||
with urllib.request.urlopen(req) as resp:
|
|
||||||
print(f"updates.xml updated to {moko_ver}")
|
|
||||||
PYEOF
|
|
||||||
|
|
||||||
- name: Disable maintenance mode
|
|
||||||
if: always()
|
|
||||||
env:
|
|
||||||
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
|
||||||
INSTANCE_URL: ${{ steps.config.outputs.instance_url }}
|
|
||||||
run: |
|
|
||||||
echo "Disabling maintenance mode on ${INSTANCE_URL}..."
|
|
||||||
curl -sf -X POST \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
||||||
"${INSTANCE_URL}/-/admin/config" \
|
|
||||||
-d 'key=instance.maintenance_mode&value={"AdminWebAccessOnly":false}' \
|
|
||||||
|| echo "WARNING: Could not disable maintenance mode"
|
|
||||||
|
|
||||||
- name: Verify
|
- name: Verify
|
||||||
run: |
|
run: |
|
||||||
sleep 5
|
sleep 5
|
||||||
curl -sf https://${{ env.DEPLOY_HOST }}/api/healthz && echo " — API healthy"
|
curl -sf https://${{ env.DEPLOY_HOST }}/api/healthz && echo " API healthy"
|
||||||
|
|
||||||
- name: Notify on failure
|
- name: Notify on failure
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: Gitea.Workflow
|
||||||
# INGROUP: mokoplatform.Automation
|
# INGROUP: mokoplatform.Automation
|
||||||
# VERSION: 06.13.00
|
# VERSION: 06.14.00
|
||||||
# BRIEF: Auto-create feature branch when an issue is opened
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
name: "Universal: Issue Branch"
|
name: "Universal: Issue Branch"
|
||||||
|
|||||||
@@ -48,4 +48,4 @@ jobs:
|
|||||||
working-directory: .mokogitea/mcp
|
working-directory: .mokogitea/mcp
|
||||||
run: |
|
run: |
|
||||||
npm publish --registry ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/npm/ \
|
npm publish --registry ${{ github.server_url }}/api/packages/${{ github.repository_owner }}/npm/ \
|
||||||
--//$(echo "${{ github.server_url }}" | sed 's|https://||')/api/packages/${{ github.repository_owner }}/npm/:_authToken=${{ secrets.GITEA_TOKEN }}
|
--//$(echo "${{ github.server_url }}" | sed 's|https://||')/api/packages/${{ github.repository_owner }}/npm/:_authToken=${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
- name: Sync upstream bugs
|
- name: Sync upstream bugs
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_MIRROR_TOKEN }}
|
||||||
MOKOGITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||||
MOKOGITEA_URL: https://code.mokoconsulting.tech
|
MOKOGITEA_URL: https://code.mokoconsulting.tech
|
||||||
MOKOGITEA_REPO: MokoConsulting/MokoGitea
|
MOKOGITEA_REPO: MokoConsulting/MokoGitea
|
||||||
UPSTREAM_BRANCH: release/v1.26
|
UPSTREAM_BRANCH: release/v1.26
|
||||||
|
|||||||
+3
-34
@@ -1,9 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
|
||||||
All notable changes to MokoGitea are documented here. Versions follow the format
|
All notable changes to MokoGitea are documented here. Versions follow the format
|
||||||
`v{upstream}-moko.{major}.{minor}` (e.g. `v1.26.1-moko.06.03`).
|
`v{upstream}-moko.{major}.{minor}` (e.g. `v1.26.1-moko.06.03`).
|
||||||
|
|
||||||
## [Unreleased]
|
## [06.14.00] --- 2026-06-09
|
||||||
|
|
||||||
* FEATURES
|
* FEATURES
|
||||||
* feat(api): issue status/priority/type exposed in REST API - GET/PATCH on issues now includes status_id, priority_id, type_id with resolved names
|
* feat(api): issue status/priority/type exposed in REST API - GET/PATCH on issues now includes status_id, priority_id, type_id with resolved names
|
||||||
@@ -194,36 +196,3 @@ All notable changes to MokoGitea are documented here. Versions follow the format
|
|||||||
* fix(updates): correct dlid prefix and align XML with Joomla standard
|
* fix(updates): correct dlid prefix and align XML with Joomla standard
|
||||||
* INFRASTRUCTURE
|
* INFRASTRUCTURE
|
||||||
* fix(ci): auto-deploy to production on merge to main (#235)
|
* fix(ci): auto-deploy to production on merge to main (#235)
|
||||||
|
|
||||||
## [v1.26.1-moko.04] - 2026-05-24
|
|
||||||
|
|
||||||
* SECURITY
|
|
||||||
* Backport 12 upstream v1.26.2 security fixes:
|
|
||||||
* golang.org/x/net v0.55.0 security update (#140)
|
|
||||||
* Token scope enforcement on raw/media/attachment downloads (#141)
|
|
||||||
* OAuth PKCE hardening and refresh token replay protection (#142)
|
|
||||||
* Wiki git write and LFS token access enforcement (#143)
|
|
||||||
* Public-only token filtering in API queries (#144)
|
|
||||||
* Artifact signature payload hardening (#146)
|
|
||||||
* AWS credentials encryption (#161)
|
|
||||||
* Mermaid v11.15.0 security update (#162)
|
|
||||||
* Composer package permission check (#164)
|
|
||||||
* BUGFIXES
|
|
||||||
* fix(actions): nil pointer dereference in concurrency during PR creation (#136)
|
|
||||||
* fix(ui): actions runs list broken row layout (#138)
|
|
||||||
* fix: scheduled action panic with null event payload
|
|
||||||
* fix: treat email addresses case-insensitively
|
|
||||||
* fix: .mod lexer panic — removed invalid AMPL mapping
|
|
||||||
* FEATURES
|
|
||||||
* Joomla-style updates.xml with channel selection
|
|
||||||
* Update checker with configurable CHANNEL setting
|
|
||||||
* Admin dashboard update banner with docker pull command
|
|
||||||
* Upstream bug sync workflow — daily automated issue creation
|
|
||||||
* PR RC release workflow — auto-build RC on PR to main
|
|
||||||
* INFRASTRUCTURE
|
|
||||||
* New 3-part versioning: v{upstream}-moko.{major}.{minor}.{patch}
|
|
||||||
* Branding updates: error pages, home page, settings link
|
|
||||||
* Deploy workflow updated for new version format
|
|
||||||
* PROCESS
|
|
||||||
* Created `type: bug` and `upstream` labels for automated issue tracking
|
|
||||||
* Closed 24 upstream bug/security issues after backporting
|
|
||||||
|
|||||||
Submodule mcp-mokogitea-api deleted from c9eb6cfc89
@@ -432,6 +432,7 @@ func prepareMigrationTasks() []*migration {
|
|||||||
newMigration(352, "Add version prefix and element name to repo manifest", v1_27.AddManifestVersionPrefixAndElement),
|
newMigration(352, "Add version prefix and element name to repo manifest", v1_27.AddManifestVersionPrefixAndElement),
|
||||||
newMigration(353, "Add distribution metadata fields to repo manifest", v1_27.AddManifestDistributionFields),
|
newMigration(353, "Add distribution metadata fields to repo manifest", v1_27.AddManifestDistributionFields),
|
||||||
newMigration(354, "Add org wiki settings to user table", v1_27.AddOrgWikiSettings),
|
newMigration(354, "Add org wiki settings to user table", v1_27.AddOrgWikiSettings),
|
||||||
|
newMigration(355, "Migrate update server metadata to repo manifest", v1_27.MigrateUpdateServerFieldsToManifest),
|
||||||
}
|
}
|
||||||
return preparedMigrations
|
return preparedMigrations
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package v1_27
|
||||||
|
|
||||||
|
import "xorm.io/xorm"
|
||||||
|
|
||||||
|
// MigrateUpdateServerFieldsToManifest copies extension metadata from
|
||||||
|
// update_stream_config into repo_manifest where the manifest fields are empty.
|
||||||
|
// This consolidates the source of truth into repo_manifest.
|
||||||
|
func MigrateUpdateServerFieldsToManifest(x *xorm.Engine) error {
|
||||||
|
// Copy display_name from config to manifest where manifest is empty
|
||||||
|
_, err := x.Exec(`
|
||||||
|
UPDATE repo_manifest m
|
||||||
|
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
|
||||||
|
SET m.display_name = c.display_name
|
||||||
|
WHERE (m.display_name IS NULL OR m.display_name = '') AND c.display_name != ''
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy extension_name → element_name
|
||||||
|
_, err = x.Exec(`
|
||||||
|
UPDATE repo_manifest m
|
||||||
|
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
|
||||||
|
SET m.element_name = c.extension_name
|
||||||
|
WHERE (m.element_name IS NULL OR m.element_name = '') AND c.extension_name != ''
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy extension_type → package_type
|
||||||
|
_, err = x.Exec(`
|
||||||
|
UPDATE repo_manifest m
|
||||||
|
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
|
||||||
|
SET m.package_type = c.extension_type
|
||||||
|
WHERE (m.package_type IS NULL OR m.package_type = '') AND c.extension_type != ''
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy target_version
|
||||||
|
_, err = x.Exec(`
|
||||||
|
UPDATE repo_manifest m
|
||||||
|
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
|
||||||
|
SET m.target_version = c.target_version
|
||||||
|
WHERE (m.target_version IS NULL OR m.target_version = '') AND c.target_version != ''
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy maintainer
|
||||||
|
_, err = x.Exec(`
|
||||||
|
UPDATE repo_manifest m
|
||||||
|
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
|
||||||
|
SET m.maintainer = c.maintainer
|
||||||
|
WHERE (m.maintainer IS NULL OR m.maintainer = '') AND c.maintainer != ''
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy maintainer_url
|
||||||
|
_, err = x.Exec(`
|
||||||
|
UPDATE repo_manifest m
|
||||||
|
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
|
||||||
|
SET m.maintainer_url = c.maintainer_url
|
||||||
|
WHERE (m.maintainer_url IS NULL OR m.maintainer_url = '') AND c.maintainer_url != ''
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy info_url
|
||||||
|
_, err = x.Exec(`
|
||||||
|
UPDATE repo_manifest m
|
||||||
|
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
|
||||||
|
SET m.info_url = c.info_url
|
||||||
|
WHERE (m.info_url IS NULL OR m.info_url = '') AND c.info_url != ''
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy php_minimum
|
||||||
|
_, err = x.Exec(`
|
||||||
|
UPDATE repo_manifest m
|
||||||
|
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
|
||||||
|
SET m.php_minimum = c.php_minimum
|
||||||
|
WHERE (m.php_minimum IS NULL OR m.php_minimum = '') AND c.php_minimum != ''
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy platform from config to manifest where manifest platform is empty
|
||||||
|
_, err = x.Exec(`
|
||||||
|
UPDATE repo_manifest m
|
||||||
|
INNER JOIN update_stream_config c ON m.repo_id = c.repo_id
|
||||||
|
SET m.platform = c.platform
|
||||||
|
WHERE (m.platform IS NULL OR m.platform = '') AND c.platform != ''
|
||||||
|
`)
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package licenses
|
package updateserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package licenses
|
package updateserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package licenses
|
package updateserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package licenses
|
package updateserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package licenses
|
package updateserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
package licenses
|
package updateserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -1479,7 +1479,10 @@ func Routes() *web.Router {
|
|||||||
Delete(reqToken(), repo.DeleteTopic)
|
Delete(reqToken(), repo.DeleteTopic)
|
||||||
}, reqAdmin())
|
}, reqAdmin())
|
||||||
}, reqAnyRepoReader())
|
}, reqAnyRepoReader())
|
||||||
m.Combo("/manifest", reqRepoReader(unit.TypeCode)).
|
m.Combo("/metadata", reqRepoReader(unit.TypeCode)).
|
||||||
|
Get(repo.GetRepoManifest).
|
||||||
|
Put(reqToken(), reqAdmin(), repo.UpdateRepoManifest)
|
||||||
|
m.Combo("/manifest", reqRepoReader(unit.TypeCode)). // backward compat
|
||||||
Get(repo.GetRepoManifest).
|
Get(repo.GetRepoManifest).
|
||||||
Put(reqToken(), reqAdmin(), repo.UpdateRepoManifest)
|
Put(reqToken(), reqAdmin(), repo.UpdateRepoManifest)
|
||||||
// MokoGitea badge engine
|
// MokoGitea badge engine
|
||||||
|
|||||||
@@ -756,23 +756,43 @@ func CreateIssue(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set org-level issue metadata (status/priority/type) if provided
|
// Set org-level issue metadata (status/priority/type).
|
||||||
|
// If not provided, auto-assign the org default.
|
||||||
if form.StatusID != nil && *form.StatusID > 0 {
|
if form.StatusID != nil && *form.StatusID > 0 {
|
||||||
if err := issues_model.SetIssueStatusID(ctx, issue.ID, *form.StatusID); err != nil {
|
_ = issues_model.SetIssueStatusID(ctx, issue.ID, *form.StatusID)
|
||||||
ctx.APIErrorInternal(err)
|
} else {
|
||||||
return
|
// Auto-assign first non-closing status.
|
||||||
|
if defs, err := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Repo.Repository.OwnerID); err == nil {
|
||||||
|
for _, d := range defs {
|
||||||
|
if !d.ClosesIssue {
|
||||||
|
_ = issues_model.SetIssueStatusID(ctx, issue.ID, d.ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if form.PriorityID != nil && *form.PriorityID > 0 {
|
if form.PriorityID != nil && *form.PriorityID > 0 {
|
||||||
if err := issues_model.SetIssuePriorityID(ctx, issue.ID, *form.PriorityID); err != nil {
|
_ = issues_model.SetIssuePriorityID(ctx, issue.ID, *form.PriorityID)
|
||||||
ctx.APIErrorInternal(err)
|
} else {
|
||||||
return
|
if defs, err := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Repo.Repository.OwnerID); err == nil {
|
||||||
|
for _, d := range defs {
|
||||||
|
if d.IsDefault {
|
||||||
|
_ = issues_model.SetIssuePriorityID(ctx, issue.ID, d.ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if form.TypeID != nil && *form.TypeID > 0 {
|
if form.TypeID != nil && *form.TypeID > 0 {
|
||||||
if err := issues_model.SetIssueTypeID(ctx, issue.ID, *form.TypeID); err != nil {
|
_ = issues_model.SetIssueTypeID(ctx, issue.ID, *form.TypeID)
|
||||||
ctx.APIErrorInternal(err)
|
} else {
|
||||||
return
|
if defs, err := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Repo.Repository.OwnerID); err == nil {
|
||||||
|
for _, d := range defs {
|
||||||
|
if d.IsDefault {
|
||||||
|
_ = issues_model.SetIssueTypeID(ctx, issue.ID, d.ID)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
|
||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
|
|
||||||
// GetLicenseSettings returns the licensing/update stream settings for a repo.
|
// GetLicenseSettings returns the licensing/update stream settings for a repo.
|
||||||
func GetLicenseSettings(ctx *context.APIContext) {
|
func GetLicenseSettings(ctx *context.APIContext) {
|
||||||
cfg := licenses.GetEffectiveConfig(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
|
cfg := updateserver_model.GetEffectiveConfig(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
ctx.JSON(http.StatusOK, &structs.LicenseSettings{})
|
ctx.JSON(http.StatusOK, &structs.LicenseSettings{})
|
||||||
return
|
return
|
||||||
@@ -42,7 +42,7 @@ func GetLicenseSettings(ctx *context.APIContext) {
|
|||||||
func UpdateLicenseSettings(ctx *context.APIContext) {
|
func UpdateLicenseSettings(ctx *context.APIContext) {
|
||||||
form := web.GetForm(ctx).(*structs.LicenseSettings)
|
form := web.GetForm(ctx).(*structs.LicenseSettings)
|
||||||
|
|
||||||
cfg := &licenses.UpdateStreamConfig{
|
cfg := &updateserver_model.UpdateStreamConfig{
|
||||||
OwnerID: ctx.Repo.Repository.OwnerID,
|
OwnerID: ctx.Repo.Repository.OwnerID,
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
LicensingEnabled: form.LicensingEnabled,
|
LicensingEnabled: form.LicensingEnabled,
|
||||||
@@ -61,7 +61,7 @@ func UpdateLicenseSettings(ctx *context.APIContext) {
|
|||||||
StreamMode: "joomla",
|
StreamMode: "joomla",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.SaveConfig(ctx, cfg); err != nil {
|
if err := updateserver_model.SaveConfig(ctx, cfg); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ func UpdateLicenseSettings(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verifyPackageOwnership checks that a package belongs to the current repo's owner.
|
// verifyPackageOwnership checks that a package belongs to the current repo's owner.
|
||||||
func verifyPackageOwnership(ctx *context.APIContext, pkg *licenses.LicensePackage) bool {
|
func verifyPackageOwnership(ctx *context.APIContext, pkg *updateserver_model.LicensePackage) bool {
|
||||||
if pkg.OwnerID != ctx.Repo.Repository.OwnerID {
|
if pkg.OwnerID != ctx.Repo.Repository.OwnerID {
|
||||||
ctx.APIErrorNotFound(nil)
|
ctx.APIErrorNotFound(nil)
|
||||||
return false
|
return false
|
||||||
@@ -79,7 +79,7 @@ func verifyPackageOwnership(ctx *context.APIContext, pkg *licenses.LicensePackag
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verifyKeyOwnership checks that a key belongs to the current repo's owner.
|
// verifyKeyOwnership checks that a key belongs to the current repo's owner.
|
||||||
func verifyKeyOwnership(ctx *context.APIContext, key *licenses.LicenseKey) bool {
|
func verifyKeyOwnership(ctx *context.APIContext, key *updateserver_model.LicenseKey) bool {
|
||||||
if key.OwnerID != ctx.Repo.Repository.OwnerID {
|
if key.OwnerID != ctx.Repo.Repository.OwnerID {
|
||||||
ctx.APIErrorNotFound(nil)
|
ctx.APIErrorNotFound(nil)
|
||||||
return false
|
return false
|
||||||
@@ -87,7 +87,7 @@ func verifyKeyOwnership(ctx *context.APIContext, key *licenses.LicenseKey) bool
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func toLicensePackageAPI(pkg *licenses.LicensePackage) *structs.LicensePackage {
|
func toLicensePackageAPI(pkg *updateserver_model.LicensePackage) *structs.LicensePackage {
|
||||||
return &structs.LicensePackage{
|
return &structs.LicensePackage{
|
||||||
ID: pkg.ID,
|
ID: pkg.ID,
|
||||||
OwnerID: pkg.OwnerID,
|
OwnerID: pkg.OwnerID,
|
||||||
@@ -103,7 +103,7 @@ func toLicensePackageAPI(pkg *licenses.LicensePackage) *structs.LicensePackage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toLicenseKeyAPI(key *licenses.LicenseKey) *structs.LicenseKey {
|
func toLicenseKeyAPI(key *updateserver_model.LicenseKey) *structs.LicenseKey {
|
||||||
lk := &structs.LicenseKey{
|
lk := &structs.LicenseKey{
|
||||||
ID: key.ID,
|
ID: key.ID,
|
||||||
PackageID: key.PackageID,
|
PackageID: key.PackageID,
|
||||||
@@ -134,7 +134,7 @@ func toLicenseKeyAPI(key *licenses.LicenseKey) *structs.LicenseKey {
|
|||||||
|
|
||||||
// ListLicensePackages lists license packages for the repo owner.
|
// ListLicensePackages lists license packages for the repo owner.
|
||||||
func ListLicensePackages(ctx *context.APIContext) {
|
func ListLicensePackages(ctx *context.APIContext) {
|
||||||
pkgs, err := licenses.ListLicensePackages(ctx, ctx.Repo.Repository.OwnerID)
|
pkgs, err := updateserver_model.ListLicensePackages(ctx, ctx.Repo.Repository.OwnerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -151,7 +151,7 @@ func ListLicensePackages(ctx *context.APIContext) {
|
|||||||
func CreateLicensePackage(ctx *context.APIContext) {
|
func CreateLicensePackage(ctx *context.APIContext) {
|
||||||
form := web.GetForm(ctx).(*structs.CreateLicensePackageOption)
|
form := web.GetForm(ctx).(*structs.CreateLicensePackageOption)
|
||||||
|
|
||||||
pkg := &licenses.LicensePackage{
|
pkg := &updateserver_model.LicensePackage{
|
||||||
OwnerID: ctx.Repo.Repository.OwnerID,
|
OwnerID: ctx.Repo.Repository.OwnerID,
|
||||||
Name: form.Name,
|
Name: form.Name,
|
||||||
Description: form.Description,
|
Description: form.Description,
|
||||||
@@ -165,7 +165,7 @@ func CreateLicensePackage(ctx *context.APIContext) {
|
|||||||
pkg.RepoScope = "all"
|
pkg.RepoScope = "all"
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.CreateLicensePackage(ctx, pkg); err != nil {
|
if err := updateserver_model.CreateLicensePackage(ctx, pkg); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ func EditLicensePackage(ctx *context.APIContext) {
|
|||||||
form := web.GetForm(ctx).(*structs.EditLicensePackageOption)
|
form := web.GetForm(ctx).(*structs.EditLicensePackageOption)
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
|
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
return
|
return
|
||||||
@@ -187,7 +187,7 @@ func EditLicensePackage(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.APIError(http.StatusForbidden, "master package cannot be edited")
|
ctx.APIError(http.StatusForbidden, "master package cannot be edited")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -214,7 +214,7 @@ func EditLicensePackage(ctx *context.APIContext) {
|
|||||||
pkg.IsActive = *form.IsActive
|
pkg.IsActive = *form.IsActive
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.UpdateLicensePackage(ctx, pkg); err != nil {
|
if err := updateserver_model.UpdateLicensePackage(ctx, pkg); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -226,7 +226,7 @@ func EditLicensePackage(ctx *context.APIContext) {
|
|||||||
func DeleteLicensePackage(ctx *context.APIContext) {
|
func DeleteLicensePackage(ctx *context.APIContext) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
|
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
return
|
return
|
||||||
@@ -235,12 +235,12 @@ func DeleteLicensePackage(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.APIError(http.StatusForbidden, "master package cannot be deleted")
|
ctx.APIError(http.StatusForbidden, "master package cannot be deleted")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.DeleteLicensePackage(ctx, pkgID); err != nil {
|
if err := updateserver_model.DeleteLicensePackage(ctx, pkgID); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -252,7 +252,7 @@ func DeleteLicensePackage(ctx *context.APIContext) {
|
|||||||
func ArchiveLicensePackage(ctx *context.APIContext) {
|
func ArchiveLicensePackage(ctx *context.APIContext) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
|
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
return
|
return
|
||||||
@@ -261,12 +261,12 @@ func ArchiveLicensePackage(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.APIError(http.StatusForbidden, "master package cannot be archived")
|
ctx.APIError(http.StatusForbidden, "master package cannot be archived")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.ArchiveLicensePackage(ctx, pkgID); err != nil {
|
if err := updateserver_model.ArchiveLicensePackage(ctx, pkgID); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -277,7 +277,7 @@ func ArchiveLicensePackage(ctx *context.APIContext) {
|
|||||||
// UnarchiveLicensePackage restores an archived license package via API.
|
// UnarchiveLicensePackage restores an archived license package via API.
|
||||||
func UnarchiveLicensePackage(ctx *context.APIContext) {
|
func UnarchiveLicensePackage(ctx *context.APIContext) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
return
|
return
|
||||||
@@ -285,7 +285,7 @@ func UnarchiveLicensePackage(ctx *context.APIContext) {
|
|||||||
if !verifyPackageOwnership(ctx, pkg) {
|
if !verifyPackageOwnership(ctx, pkg) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := licenses.UnarchiveLicensePackage(ctx, pkgID); err != nil {
|
if err := updateserver_model.UnarchiveLicensePackage(ctx, pkgID); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -294,7 +294,7 @@ func UnarchiveLicensePackage(ctx *context.APIContext) {
|
|||||||
|
|
||||||
// ListLicenseKeys lists license keys for the repo owner.
|
// ListLicenseKeys lists license keys for the repo owner.
|
||||||
func ListLicenseKeys(ctx *context.APIContext) {
|
func ListLicenseKeys(ctx *context.APIContext) {
|
||||||
keys, err := licenses.ListLicenseKeys(ctx, ctx.Repo.Repository.OwnerID)
|
keys, err := updateserver_model.ListLicenseKeys(ctx, ctx.Repo.Repository.OwnerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -311,7 +311,7 @@ func ListLicenseKeys(ctx *context.APIContext) {
|
|||||||
func CreateLicenseKey(ctx *context.APIContext) {
|
func CreateLicenseKey(ctx *context.APIContext) {
|
||||||
form := web.GetForm(ctx).(*structs.CreateLicenseKeyOption)
|
form := web.GetForm(ctx).(*structs.CreateLicenseKeyOption)
|
||||||
|
|
||||||
key := &licenses.LicenseKey{
|
key := &updateserver_model.LicenseKey{
|
||||||
PackageID: form.PackageID,
|
PackageID: form.PackageID,
|
||||||
OwnerID: ctx.Repo.Repository.OwnerID,
|
OwnerID: ctx.Repo.Repository.OwnerID,
|
||||||
LicenseeName: form.LicenseeName,
|
LicenseeName: form.LicenseeName,
|
||||||
@@ -329,7 +329,7 @@ func CreateLicenseKey(ctx *context.APIContext) {
|
|||||||
key.ExpiresUnix = timeutil.TimeStamp(form.ExpiresAt.Unix())
|
key.ExpiresUnix = timeutil.TimeStamp(form.ExpiresAt.Unix())
|
||||||
} else {
|
} else {
|
||||||
// Auto-calculate from package duration.
|
// Auto-calculate from package duration.
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, form.PackageID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, form.PackageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -344,7 +344,7 @@ func CreateLicenseKey(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rawKey, err := licenses.CreateLicenseKey(ctx, key)
|
rawKey, err := updateserver_model.CreateLicenseKey(ctx, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -362,7 +362,7 @@ func EditLicenseKey(ctx *context.APIContext) {
|
|||||||
form := web.GetForm(ctx).(*structs.EditLicenseKeyOption)
|
form := web.GetForm(ctx).(*structs.EditLicenseKeyOption)
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
|
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
return
|
return
|
||||||
@@ -395,7 +395,7 @@ func EditLicenseKey(ctx *context.APIContext) {
|
|||||||
key.ExpiresUnix = timeutil.TimeStamp(form.ExpiresAt.Unix())
|
key.ExpiresUnix = timeutil.TimeStamp(form.ExpiresAt.Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.UpdateLicenseKey(ctx, key); err != nil {
|
if err := updateserver_model.UpdateLicenseKey(ctx, key); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -409,7 +409,7 @@ func PurchaseLicenseKey(ctx *context.APIContext) {
|
|||||||
|
|
||||||
// Idempotency check: if payment_ref already exists, return existing key.
|
// Idempotency check: if payment_ref already exists, return existing key.
|
||||||
if form.PaymentRef != "" {
|
if form.PaymentRef != "" {
|
||||||
existing, err := licenses.GetLicenseKeyByPaymentRef(ctx, form.PaymentRef)
|
existing, err := updateserver_model.GetLicenseKeyByPaymentRef(ctx, form.PaymentRef)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
resp := &structs.LicenseKeyCreated{
|
resp := &structs.LicenseKeyCreated{
|
||||||
LicenseKey: *toLicenseKeyAPI(existing),
|
LicenseKey: *toLicenseKeyAPI(existing),
|
||||||
@@ -420,7 +420,7 @@ func PurchaseLicenseKey(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, form.PackageID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, form.PackageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
return
|
return
|
||||||
@@ -429,7 +429,7 @@ func PurchaseLicenseKey(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key := &licenses.LicenseKey{
|
key := &updateserver_model.LicenseKey{
|
||||||
PackageID: form.PackageID,
|
PackageID: form.PackageID,
|
||||||
OwnerID: ctx.Repo.Repository.OwnerID,
|
OwnerID: ctx.Repo.Repository.OwnerID,
|
||||||
LicenseeName: form.LicenseeName,
|
LicenseeName: form.LicenseeName,
|
||||||
@@ -444,7 +444,7 @@ func PurchaseLicenseKey(ctx *context.APIContext) {
|
|||||||
key.ExpiresUnix = timeutil.TimeStamp(expires.Unix())
|
key.ExpiresUnix = timeutil.TimeStamp(expires.Unix())
|
||||||
}
|
}
|
||||||
|
|
||||||
rawKey, err := licenses.CreateLicenseKey(ctx, key)
|
rawKey, err := updateserver_model.CreateLicenseKey(ctx, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -460,7 +460,7 @@ func PurchaseLicenseKey(ctx *context.APIContext) {
|
|||||||
// RenewLicenseKey extends a key's expiration by its package duration.
|
// RenewLicenseKey extends a key's expiration by its package duration.
|
||||||
func RenewLicenseKey(ctx *context.APIContext) {
|
func RenewLicenseKey(ctx *context.APIContext) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
return
|
return
|
||||||
@@ -469,7 +469,7 @@ func RenewLicenseKey(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, key.PackageID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, key.PackageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
return
|
return
|
||||||
@@ -480,20 +480,20 @@ func RenewLicenseKey(ctx *context.APIContext) {
|
|||||||
days = 365
|
days = 365
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.RenewLicenseKey(ctx, keyID, days); err != nil {
|
if err := updateserver_model.RenewLicenseKey(ctx, keyID, days); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reload key to get updated fields.
|
// Reload key to get updated fields.
|
||||||
key, _ = licenses.GetLicenseKeyByID(ctx, keyID)
|
key, _ = updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
ctx.JSON(http.StatusOK, toLicenseKeyAPI(key))
|
ctx.JSON(http.StatusOK, toLicenseKeyAPI(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeLicenseKey deactivates a license key via API.
|
// RevokeLicenseKey deactivates a license key via API.
|
||||||
func RevokeLicenseKey(ctx *context.APIContext) {
|
func RevokeLicenseKey(ctx *context.APIContext) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
return
|
return
|
||||||
@@ -503,7 +503,7 @@ func RevokeLicenseKey(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
key.IsActive = false
|
key.IsActive = false
|
||||||
if err := licenses.UpdateLicenseKey(ctx, key); err != nil {
|
if err := updateserver_model.UpdateLicenseKey(ctx, key); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -514,7 +514,7 @@ func RevokeLicenseKey(ctx *context.APIContext) {
|
|||||||
// DeleteLicenseKey deletes a license key.
|
// DeleteLicenseKey deletes a license key.
|
||||||
func DeleteLicenseKey(ctx *context.APIContext) {
|
func DeleteLicenseKey(ctx *context.APIContext) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
return
|
return
|
||||||
@@ -522,7 +522,7 @@ func DeleteLicenseKey(ctx *context.APIContext) {
|
|||||||
if !verifyKeyOwnership(ctx, key) {
|
if !verifyKeyOwnership(ctx, key) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := licenses.DeleteLicenseKey(ctx, keyID); err != nil {
|
if err := updateserver_model.DeleteLicenseKey(ctx, keyID); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -533,7 +533,7 @@ func DeleteLicenseKey(ctx *context.APIContext) {
|
|||||||
func ValidateLicenseKey(ctx *context.APIContext) {
|
func ValidateLicenseKey(ctx *context.APIContext) {
|
||||||
form := web.GetForm(ctx).(*structs.ValidateLicenseKeyOption)
|
form := web.GetForm(ctx).(*structs.ValidateLicenseKeyOption)
|
||||||
|
|
||||||
key, pkg, err := licenses.ValidateLicenseKey(ctx, form.Key, form.Domain)
|
key, pkg, err := updateserver_model.ValidateLicenseKey(ctx, form.Key, form.Domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusOK, &structs.ValidateLicenseKeyResponse{
|
ctx.JSON(http.StatusOK, &structs.ValidateLicenseKeyResponse{
|
||||||
Valid: false,
|
Valid: false,
|
||||||
@@ -542,7 +542,7 @@ func ValidateLicenseKey(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = licenses.TouchHeartbeat(ctx, key.ID)
|
_ = updateserver_model.TouchHeartbeat(ctx, key.ID)
|
||||||
|
|
||||||
var expiresAt *time.Time
|
var expiresAt *time.Time
|
||||||
if key.ExpiresUnix > 0 {
|
if key.ExpiresUnix > 0 {
|
||||||
@@ -555,7 +555,7 @@ func ValidateLicenseKey(ctx *context.APIContext) {
|
|||||||
maxSites = pkg.MaxSites
|
maxSites = pkg.MaxSites
|
||||||
}
|
}
|
||||||
|
|
||||||
sitesUsed, _ := licenses.CountUniqueDomainsByKey(ctx, key.ID)
|
sitesUsed, _ := updateserver_model.CountUniqueDomainsByKey(ctx, key.ID)
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, &structs.ValidateLicenseKeyResponse{
|
ctx.JSON(http.StatusOK, &structs.ValidateLicenseKeyResponse{
|
||||||
Valid: true,
|
Valid: true,
|
||||||
@@ -569,7 +569,7 @@ func ValidateLicenseKey(ctx *context.APIContext) {
|
|||||||
|
|
||||||
// GetLicenseKeyUsage returns usage logs for a license key.
|
// GetLicenseKeyUsage returns usage logs for a license key.
|
||||||
func GetLicenseKeyUsage(ctx *context.APIContext) {
|
func GetLicenseKeyUsage(ctx *context.APIContext) {
|
||||||
usages, err := licenses.GetRecentUsage(ctx, ctx.PathParamInt64("id"), 100)
|
usages, err := updateserver_model.GetRecentUsage(ctx, ctx.PathParamInt64("id"), 100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
licenses_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/renderhelper"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/renderhelper"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
@@ -109,10 +109,10 @@ func home(ctx *context.Context, viewRepositories bool) {
|
|||||||
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
|
ctx.Data["IsOrganizationMember"] = ctx.Org.IsMember
|
||||||
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
|
ctx.Data["IsOrganizationOwner"] = ctx.Org.IsOwner
|
||||||
|
|
||||||
orgCfg, _ := licenses_model.GetOrgConfig(ctx, ctx.Org.Organization.ID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, ctx.Org.Organization.ID)
|
||||||
ctx.Data["OrgLicensingEnabled"] = orgCfg != nil && orgCfg.LicensingEnabled
|
ctx.Data["OrgLicensingEnabled"] = orgCfg != nil && orgCfg.LicensingEnabled
|
||||||
if orgCfg != nil && orgCfg.LicensingEnabled {
|
if orgCfg != nil && orgCfg.LicensingEnabled {
|
||||||
numPkgs, _ := licenses_model.CountOrgPackages(ctx, ctx.Org.Organization.ID)
|
numPkgs, _ := updateserver_model.CountOrgPackages(ctx, ctx.Org.Organization.ID)
|
||||||
ctx.Data["NumOrgLicensePackages"] = numPkgs
|
ctx.Data["NumOrgLicensePackages"] = numPkgs
|
||||||
}
|
}
|
||||||
ctx.Data["IsPublicMember"] = func(uid int64) bool {
|
ctx.Data["IsPublicMember"] = func(uid int64) bool {
|
||||||
|
|||||||
+49
-49
@@ -9,7 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/perm"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/perm"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
unit_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
unit_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
||||||
@@ -27,7 +27,7 @@ type OrgChannelItem struct {
|
|||||||
Label string
|
Label string
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildOrgChannelItems(streams []licenses.StreamDef) []OrgChannelItem {
|
func buildOrgChannelItems(streams []updateserver_model.StreamDef) []OrgChannelItem {
|
||||||
items := make([]OrgChannelItem, 0, len(streams))
|
items := make([]OrgChannelItem, 0, len(streams))
|
||||||
for _, s := range streams {
|
for _, s := range streams {
|
||||||
label := s.Name
|
label := s.Name
|
||||||
@@ -62,7 +62,7 @@ func parseOrgAllowedChannels(s string) []string {
|
|||||||
|
|
||||||
// LicensePackageDisplay is used in templates.
|
// LicensePackageDisplay is used in templates.
|
||||||
type LicensePackageDisplay struct {
|
type LicensePackageDisplay struct {
|
||||||
*licenses.LicensePackage
|
*updateserver_model.LicensePackage
|
||||||
KeyCount int64
|
KeyCount int64
|
||||||
Created time.Time
|
Created time.Time
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,7 @@ func Licenses(ctx *context.Context) {
|
|||||||
|
|
||||||
// Auto-create master key if has write access.
|
// Auto-create master key if has write access.
|
||||||
if canWriteLicenses {
|
if canWriteLicenses {
|
||||||
newMasterKey, err := licenses.EnsureMasterKey(ctx, ownerID)
|
newMasterKey, err := updateserver_model.EnsureMasterKey(ctx, ownerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("EnsureMasterKey", err)
|
ctx.ServerError("EnsureMasterKey", err)
|
||||||
return
|
return
|
||||||
@@ -89,7 +89,7 @@ func Licenses(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pkgs, err := licenses.ListLicensePackagesWithAncestors(ctx, ownerID)
|
pkgs, err := updateserver_model.ListLicensePackagesWithAncestors(ctx, ownerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ListLicensePackages", err)
|
ctx.ServerError("ListLicensePackages", err)
|
||||||
return
|
return
|
||||||
@@ -97,7 +97,7 @@ func Licenses(ctx *context.Context) {
|
|||||||
|
|
||||||
var display []LicensePackageDisplay
|
var display []LicensePackageDisplay
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
count, _ := licenses.CountKeysByPackage(ctx, pkg.ID)
|
count, _ := updateserver_model.CountKeysByPackage(ctx, pkg.ID)
|
||||||
display = append(display, LicensePackageDisplay{
|
display = append(display, LicensePackageDisplay{
|
||||||
LicensePackage: pkg,
|
LicensePackage: pkg,
|
||||||
KeyCount: count,
|
KeyCount: count,
|
||||||
@@ -110,11 +110,11 @@ func Licenses(ctx *context.Context) {
|
|||||||
searchQuery := strings.TrimSpace(ctx.FormString("q"))
|
searchQuery := strings.TrimSpace(ctx.FormString("q"))
|
||||||
ctx.Data["SearchQuery"] = searchQuery
|
ctx.Data["SearchQuery"] = searchQuery
|
||||||
|
|
||||||
var keys []*licenses.LicenseKey
|
var keys []*updateserver_model.LicenseKey
|
||||||
if searchQuery != "" {
|
if searchQuery != "" {
|
||||||
keys, err = licenses.SearchLicenseKeysWithAncestors(ctx, ownerID, searchQuery)
|
keys, err = updateserver_model.SearchLicenseKeysWithAncestors(ctx, ownerID, searchQuery)
|
||||||
} else {
|
} else {
|
||||||
keys, err = licenses.ListLicenseKeysWithAncestors(ctx, ownerID)
|
keys, err = updateserver_model.ListLicenseKeysWithAncestors(ctx, ownerID)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ListLicenseKeys", err)
|
ctx.ServerError("ListLicenseKeys", err)
|
||||||
@@ -128,10 +128,10 @@ func Licenses(ctx *context.Context) {
|
|||||||
ctx.Data["OrgLicensingEnabled"] = true
|
ctx.Data["OrgLicensingEnabled"] = true
|
||||||
|
|
||||||
// Load archived packages.
|
// Load archived packages.
|
||||||
archivedPkgs, _ := licenses.ListArchivedLicensePackages(ctx, ownerID)
|
archivedPkgs, _ := updateserver_model.ListArchivedLicensePackages(ctx, ownerID)
|
||||||
var archivedDisplay []LicensePackageDisplay
|
var archivedDisplay []LicensePackageDisplay
|
||||||
for _, pkg := range archivedPkgs {
|
for _, pkg := range archivedPkgs {
|
||||||
count, _ := licenses.CountKeysByPackage(ctx, pkg.ID)
|
count, _ := updateserver_model.CountKeysByPackage(ctx, pkg.ID)
|
||||||
archivedDisplay = append(archivedDisplay, LicensePackageDisplay{
|
archivedDisplay = append(archivedDisplay, LicensePackageDisplay{
|
||||||
LicensePackage: pkg,
|
LicensePackage: pkg,
|
||||||
KeyCount: count,
|
KeyCount: count,
|
||||||
@@ -140,12 +140,12 @@ func Licenses(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
ctx.Data["ArchivedPackages"] = archivedDisplay
|
ctx.Data["ArchivedPackages"] = archivedDisplay
|
||||||
|
|
||||||
orgCfg, _ := licenses.GetOrgConfig(ctx, ownerID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, ownerID)
|
||||||
var orgStreams []licenses.StreamDef
|
var orgStreams []updateserver_model.StreamDef
|
||||||
if orgCfg != nil {
|
if orgCfg != nil {
|
||||||
orgStreams = orgCfg.GetActiveStreams()
|
orgStreams = orgCfg.GetActiveStreams()
|
||||||
} else {
|
} else {
|
||||||
orgStreams = licenses.DefaultJoomlaStreams()
|
orgStreams = updateserver_model.DefaultJoomlaStreams()
|
||||||
}
|
}
|
||||||
ctx.Data["AvailableStreams"] = orgStreams
|
ctx.Data["AvailableStreams"] = orgStreams
|
||||||
ctx.Data["ChannelItems"] = buildOrgChannelItems(orgStreams)
|
ctx.Data["ChannelItems"] = buildOrgChannelItems(orgStreams)
|
||||||
@@ -182,7 +182,7 @@ func LicensesCreatePackage(ctx *context.Context) {
|
|||||||
repoScope = "all"
|
repoScope = "all"
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg := &licenses.LicensePackage{
|
pkg := &updateserver_model.LicensePackage{
|
||||||
OwnerID: ctx.Org.Organization.ID,
|
OwnerID: ctx.Org.Organization.ID,
|
||||||
Name: name,
|
Name: name,
|
||||||
Description: ctx.FormString("description"),
|
Description: ctx.FormString("description"),
|
||||||
@@ -194,7 +194,7 @@ func LicensesCreatePackage(ctx *context.Context) {
|
|||||||
IsActive: true,
|
IsActive: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.CreateLicensePackage(ctx, pkg); err != nil {
|
if err := updateserver_model.CreateLicensePackage(ctx, pkg); err != nil {
|
||||||
ctx.ServerError("CreateLicensePackage", err)
|
ctx.ServerError("CreateLicensePackage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -212,13 +212,13 @@ func LicensesGenerateKey(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, packageID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, packageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key := &licenses.LicenseKey{
|
key := &updateserver_model.LicenseKey{
|
||||||
PackageID: packageID,
|
PackageID: packageID,
|
||||||
OwnerID: ctx.Org.Organization.ID,
|
OwnerID: ctx.Org.Organization.ID,
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
@@ -233,13 +233,13 @@ func LicensesGenerateKey(ctx *context.Context) {
|
|||||||
var rawKey string
|
var rawKey string
|
||||||
customKey := strings.TrimSpace(ctx.FormString("custom_key"))
|
customKey := strings.TrimSpace(ctx.FormString("custom_key"))
|
||||||
if customKey != "" && (ctx.IsUserSiteAdmin() || ctx.Org.IsOwner) {
|
if customKey != "" && (ctx.IsUserSiteAdmin() || ctx.Org.IsOwner) {
|
||||||
if err := licenses.CreateLicenseKeyCustom(ctx, key, customKey); err != nil {
|
if err := updateserver_model.CreateLicenseKeyCustom(ctx, key, customKey); err != nil {
|
||||||
ctx.ServerError("CreateLicenseKeyCustom", err)
|
ctx.ServerError("CreateLicenseKeyCustom", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rawKey = customKey
|
rawKey = customKey
|
||||||
} else {
|
} else {
|
||||||
rawKey, err = licenses.CreateLicenseKey(ctx, key)
|
rawKey, err = updateserver_model.CreateLicenseKey(ctx, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("CreateLicenseKey", err)
|
ctx.ServerError("CreateLicenseKey", err)
|
||||||
return
|
return
|
||||||
@@ -253,10 +253,10 @@ func LicensesGenerateKey(ctx *context.Context) {
|
|||||||
ctx.Data["NewKeyCreated"] = rawKey
|
ctx.Data["NewKeyCreated"] = rawKey
|
||||||
|
|
||||||
ownerID := ctx.Org.Organization.ID
|
ownerID := ctx.Org.Organization.ID
|
||||||
pkgs, _ := licenses.ListLicensePackages(ctx, ownerID)
|
pkgs, _ := updateserver_model.ListLicensePackages(ctx, ownerID)
|
||||||
var display []LicensePackageDisplay
|
var display []LicensePackageDisplay
|
||||||
for _, p := range pkgs {
|
for _, p := range pkgs {
|
||||||
count, _ := licenses.CountKeysByPackage(ctx, p.ID)
|
count, _ := updateserver_model.CountKeysByPackage(ctx, p.ID)
|
||||||
display = append(display, LicensePackageDisplay{
|
display = append(display, LicensePackageDisplay{
|
||||||
LicensePackage: p,
|
LicensePackage: p,
|
||||||
KeyCount: count,
|
KeyCount: count,
|
||||||
@@ -264,14 +264,14 @@ func LicensesGenerateKey(ctx *context.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
ctx.Data["LicensePackages"] = display
|
ctx.Data["LicensePackages"] = display
|
||||||
keys, _ := licenses.ListLicenseKeys(ctx, ownerID)
|
keys, _ := updateserver_model.ListLicenseKeys(ctx, ownerID)
|
||||||
ctx.Data["LicenseKeys"] = keys
|
ctx.Data["LicenseKeys"] = keys
|
||||||
|
|
||||||
orgCfg, _ := licenses.GetOrgConfig(ctx, ownerID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, ownerID)
|
||||||
if orgCfg != nil {
|
if orgCfg != nil {
|
||||||
ctx.Data["AvailableStreams"] = orgCfg.GetActiveStreams()
|
ctx.Data["AvailableStreams"] = orgCfg.GetActiveStreams()
|
||||||
} else {
|
} else {
|
||||||
ctx.Data["AvailableStreams"] = licenses.DefaultJoomlaStreams()
|
ctx.Data["AvailableStreams"] = updateserver_model.DefaultJoomlaStreams()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplOrgLicenses)
|
ctx.HTML(http.StatusOK, tplOrgLicenses)
|
||||||
@@ -283,13 +283,13 @@ const tplOrgLicensesEditKey templates.TplName = "repo/licenses_edit_key"
|
|||||||
// LicensesEditPackage shows the edit form for an org license package.
|
// LicensesEditPackage shows the edit form for an org license package.
|
||||||
func LicensesEditPackage(ctx *context.Context) {
|
func LicensesEditPackage(ctx *context.Context) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.Flash.Error("Master package cannot be edited")
|
ctx.Flash.Error("Master package cannot be edited")
|
||||||
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
||||||
return
|
return
|
||||||
@@ -301,12 +301,12 @@ func LicensesEditPackage(ctx *context.Context) {
|
|||||||
selectedChannels := parseOrgAllowedChannels(pkg.AllowedChannels)
|
selectedChannels := parseOrgAllowedChannels(pkg.AllowedChannels)
|
||||||
ctx.Data["SelectedChannels"] = selectedChannels
|
ctx.Data["SelectedChannels"] = selectedChannels
|
||||||
|
|
||||||
orgCfg, _ := licenses.GetOrgConfig(ctx, ctx.Org.Organization.ID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, ctx.Org.Organization.ID)
|
||||||
var editStreams []licenses.StreamDef
|
var editStreams []updateserver_model.StreamDef
|
||||||
if orgCfg != nil {
|
if orgCfg != nil {
|
||||||
editStreams = orgCfg.GetActiveStreams()
|
editStreams = orgCfg.GetActiveStreams()
|
||||||
} else {
|
} else {
|
||||||
editStreams = licenses.DefaultJoomlaStreams()
|
editStreams = updateserver_model.DefaultJoomlaStreams()
|
||||||
}
|
}
|
||||||
ctx.Data["AvailableStreams"] = editStreams
|
ctx.Data["AvailableStreams"] = editStreams
|
||||||
ctx.Data["ChannelItems"] = buildOrgChannelItems(editStreams)
|
ctx.Data["ChannelItems"] = buildOrgChannelItems(editStreams)
|
||||||
@@ -318,13 +318,13 @@ func LicensesEditPackage(ctx *context.Context) {
|
|||||||
// LicensesEditPackagePost saves edits to an org license package.
|
// LicensesEditPackagePost saves edits to an org license package.
|
||||||
func LicensesEditPackagePost(ctx *context.Context) {
|
func LicensesEditPackagePost(ctx *context.Context) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.Flash.Error("Master package cannot be edited")
|
ctx.Flash.Error("Master package cannot be edited")
|
||||||
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
||||||
return
|
return
|
||||||
@@ -349,7 +349,7 @@ func LicensesEditPackagePost(ctx *context.Context) {
|
|||||||
|
|
||||||
pkg.IsActive = ctx.FormString("is_active") == "on"
|
pkg.IsActive = ctx.FormString("is_active") == "on"
|
||||||
|
|
||||||
if err := licenses.UpdateLicensePackage(ctx, pkg); err != nil {
|
if err := updateserver_model.UpdateLicensePackage(ctx, pkg); err != nil {
|
||||||
ctx.ServerError("UpdateLicensePackage", err)
|
ctx.ServerError("UpdateLicensePackage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -366,17 +366,17 @@ func canOrgDeleteLicenses(ctx *context.Context) bool {
|
|||||||
// LicensesArchivePackage archives an org license package.
|
// LicensesArchivePackage archives an org license package.
|
||||||
func LicensesArchivePackage(ctx *context.Context) {
|
func LicensesArchivePackage(ctx *context.Context) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.Flash.Error("Master package cannot be archived")
|
ctx.Flash.Error("Master package cannot be archived")
|
||||||
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := licenses.ArchiveLicensePackage(ctx, pkgID); err != nil {
|
if err := updateserver_model.ArchiveLicensePackage(ctx, pkgID); err != nil {
|
||||||
ctx.ServerError("ArchiveLicensePackage", err)
|
ctx.ServerError("ArchiveLicensePackage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -387,7 +387,7 @@ func LicensesArchivePackage(ctx *context.Context) {
|
|||||||
// LicensesUnarchivePackage restores an archived org license package.
|
// LicensesUnarchivePackage restores an archived org license package.
|
||||||
func LicensesUnarchivePackage(ctx *context.Context) {
|
func LicensesUnarchivePackage(ctx *context.Context) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
if err := licenses.UnarchiveLicensePackage(ctx, pkgID); err != nil {
|
if err := updateserver_model.UnarchiveLicensePackage(ctx, pkgID); err != nil {
|
||||||
ctx.ServerError("UnarchiveLicensePackage", err)
|
ctx.ServerError("UnarchiveLicensePackage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -402,17 +402,17 @@ func LicensesDeletePackage(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.Flash.Error("Master package cannot be deleted")
|
ctx.Flash.Error("Master package cannot be deleted")
|
||||||
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
ctx.Redirect(ctx.Org.OrgLink + "/-/licenses")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := licenses.DeleteLicensePackage(ctx, pkgID); err != nil {
|
if err := updateserver_model.DeleteLicensePackage(ctx, pkgID); err != nil {
|
||||||
ctx.ServerError("DeleteLicensePackage", err)
|
ctx.ServerError("DeleteLicensePackage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -424,7 +424,7 @@ func LicensesDeletePackage(ctx *context.Context) {
|
|||||||
// LicensesEditKey shows the edit form for an org license key.
|
// LicensesEditKey shows the edit form for an org license key.
|
||||||
func LicensesEditKey(ctx *context.Context) {
|
func LicensesEditKey(ctx *context.Context) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicenseKeyByID", err)
|
ctx.ServerError("GetLicenseKeyByID", err)
|
||||||
return
|
return
|
||||||
@@ -452,7 +452,7 @@ func LicensesEditKey(ctx *context.Context) {
|
|||||||
// LicensesEditKeyPost saves edits to an org license key.
|
// LicensesEditKeyPost saves edits to an org license key.
|
||||||
func LicensesEditKeyPost(ctx *context.Context) {
|
func LicensesEditKeyPost(ctx *context.Context) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicenseKeyByID", err)
|
ctx.ServerError("GetLicenseKeyByID", err)
|
||||||
return
|
return
|
||||||
@@ -481,7 +481,7 @@ func LicensesEditKeyPost(ctx *context.Context) {
|
|||||||
key.ExpiresUnix = 0
|
key.ExpiresUnix = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.UpdateLicenseKey(ctx, key); err != nil {
|
if err := updateserver_model.UpdateLicenseKey(ctx, key); err != nil {
|
||||||
ctx.ServerError("UpdateLicenseKey", err)
|
ctx.ServerError("UpdateLicenseKey", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -493,14 +493,14 @@ func LicensesEditKeyPost(ctx *context.Context) {
|
|||||||
// LicensesRevokeKey handles POST to revoke an org license key.
|
// LicensesRevokeKey handles POST to revoke an org license key.
|
||||||
func LicensesRevokeKey(ctx *context.Context) {
|
func LicensesRevokeKey(ctx *context.Context) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicenseKeyByID", err)
|
ctx.ServerError("GetLicenseKeyByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key.IsActive = false
|
key.IsActive = false
|
||||||
if err := licenses.UpdateLicenseKey(ctx, key); err != nil {
|
if err := updateserver_model.UpdateLicenseKey(ctx, key); err != nil {
|
||||||
ctx.ServerError("UpdateLicenseKey", err)
|
ctx.ServerError("UpdateLicenseKey", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -512,13 +512,13 @@ func LicensesRevokeKey(ctx *context.Context) {
|
|||||||
// LicensesRenewKey extends a license key's expiration by the package's duration.
|
// LicensesRenewKey extends a license key's expiration by the package's duration.
|
||||||
func LicensesRenewKey(ctx *context.Context) {
|
func LicensesRenewKey(ctx *context.Context) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicenseKeyByID", err)
|
ctx.ServerError("GetLicenseKeyByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, key.PackageID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, key.PackageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
@@ -529,7 +529,7 @@ func LicensesRenewKey(ctx *context.Context) {
|
|||||||
days = 365 // default to 1 year for lifetime packages
|
days = 365 // default to 1 year for lifetime packages
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.RenewLicenseKey(ctx, keyID, days); err != nil {
|
if err := updateserver_model.RenewLicenseKey(ctx, keyID, days); err != nil {
|
||||||
ctx.ServerError("RenewLicenseKey", err)
|
ctx.ServerError("RenewLicenseKey", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -545,7 +545,7 @@ func LicensesDeleteKey(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
if err := licenses.DeleteLicenseKey(ctx, keyID); err != nil {
|
if err := updateserver_model.DeleteLicenseKey(ctx, keyID); err != nil {
|
||||||
ctx.ServerError("DeleteLicenseKey", err)
|
ctx.ServerError("DeleteLicenseKey", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||||
)
|
)
|
||||||
@@ -22,7 +22,7 @@ func SettingsUpdateStreams(ctx *context.Context) {
|
|||||||
|
|
||||||
orgID := ctx.Org.Organization.ID
|
orgID := ctx.Org.Organization.ID
|
||||||
|
|
||||||
cfg, err := licenses.GetOrgConfig(ctx, orgID)
|
cfg, err := updateserver_model.GetOrgConfig(ctx, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetOrgConfig", err)
|
ctx.ServerError("GetOrgConfig", err)
|
||||||
return
|
return
|
||||||
@@ -37,7 +37,7 @@ func SettingsUpdateStreams(ctx *context.Context) {
|
|||||||
func SettingsUpdateStreamsPost(ctx *context.Context) {
|
func SettingsUpdateStreamsPost(ctx *context.Context) {
|
||||||
orgID := ctx.Org.Organization.ID
|
orgID := ctx.Org.Organization.ID
|
||||||
|
|
||||||
cfg := &licenses.UpdateStreamConfig{
|
cfg := &updateserver_model.UpdateStreamConfig{
|
||||||
OwnerID: orgID,
|
OwnerID: orgID,
|
||||||
RepoID: 0,
|
RepoID: 0,
|
||||||
StreamMode: ctx.FormString("stream_mode"),
|
StreamMode: ctx.FormString("stream_mode"),
|
||||||
@@ -64,7 +64,7 @@ func SettingsUpdateStreamsPost(ctx *context.Context) {
|
|||||||
cfg.StreamMode = "joomla"
|
cfg.StreamMode = "joomla"
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.SaveConfig(ctx, cfg); err != nil {
|
if err := updateserver_model.SaveConfig(ctx, cfg); err != nil {
|
||||||
ctx.ServerError("SaveConfig", err)
|
ctx.ServerError("SaveConfig", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-9
@@ -157,7 +157,8 @@ func Wiki(ctx *context.Context) {
|
|||||||
ctx.HTML(http.StatusOK, tplOrgWiki)
|
ctx.HTML(http.StatusOK, tplOrgWiki)
|
||||||
}
|
}
|
||||||
|
|
||||||
// findOrgWikiCommit locates the convention wiki repo and returns its HEAD commit.
|
// findOrgWikiCommit locates the profile repo's wiki and returns its HEAD commit.
|
||||||
|
// The org wiki lives in the .wiki.git sidecar of the profile repo (e.g. .profile.wiki.git).
|
||||||
func findOrgWikiCommit(ctx *context.Context, orgID int64, repoName string) (*repo_model.Repository, *git.Commit) {
|
func findOrgWikiCommit(ctx *context.Context, orgID int64, repoName string) (*repo_model.Repository, *git.Commit) {
|
||||||
dbRepo, err := repo_model.GetRepositoryByName(ctx, orgID, repoName)
|
dbRepo, err := repo_model.GetRepositoryByName(ctx, orgID, repoName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -167,19 +168,20 @@ func findOrgWikiCommit(ctx *context.Context, orgID int64, repoName string) (*rep
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if dbRepo.IsEmpty {
|
// Open the wiki git repo (.wiki.git sidecar), not the main repo.
|
||||||
|
wikiGitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, dbRepo.WikiStorageRepo())
|
||||||
|
if err != nil {
|
||||||
|
// Wiki repo doesn't exist yet — not an error, just no wiki.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, dbRepo)
|
branch := dbRepo.DefaultWikiBranch
|
||||||
if err != nil {
|
if branch == "" {
|
||||||
log.Error("findOrgWikiCommit: OpenRepository(%s): %v", dbRepo.FullName(), err)
|
branch = "main"
|
||||||
return nil, nil
|
|
||||||
}
|
}
|
||||||
|
commit, err := wikiGitRepo.GetBranchCommit(branch)
|
||||||
commit, err := gitRepo.GetBranchCommit(dbRepo.DefaultBranch)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("findOrgWikiCommit: GetBranchCommit(%s, %s): %v", dbRepo.FullName(), dbRepo.DefaultBranch, err)
|
log.Error("findOrgWikiCommit: GetBranchCommit wiki(%s, %s): %v", dbRepo.FullName(), branch, err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
licenses_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/httplib"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/httplib"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
@@ -87,7 +87,7 @@ func CDNHandler(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
// If the release is assigned to an update stream, CDN is disabled -
|
// If the release is assigned to an update stream, CDN is disabled -
|
||||||
// the update server handles distribution for streamed releases.
|
// the update server handles distribution for streamed releases.
|
||||||
if stream := licenses_model.GetReleaseStream(req.Context(), release.ID); stream != "" {
|
if stream := updateserver_model.GetReleaseStream(req.Context(), release.ID); stream != "" {
|
||||||
http.Error(w, "Forbidden: release is served via update stream", http.StatusForbidden)
|
http.Error(w, "Forbidden: release is served via update stream", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||||
)
|
)
|
||||||
@@ -58,7 +58,7 @@ func ServeChangelogXML(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get extension metadata for element name and type.
|
// Get extension metadata for element name and type.
|
||||||
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
||||||
element := strings.ToLower(repo.Name)
|
element := strings.ToLower(repo.Name)
|
||||||
extType := "component"
|
extType := "component"
|
||||||
if cfg != nil {
|
if cfg != nil {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||||
)
|
)
|
||||||
@@ -20,7 +20,7 @@ func CheckDownloadGating(ctx *context.Context, tagName string) bool {
|
|||||||
repo := ctx.Repo.Repository
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
// Check effective config (repo override → org default).
|
// Check effective config (repo override → org default).
|
||||||
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
||||||
if cfg == nil || !cfg.LicensingEnabled {
|
if cfg == nil || !cfg.LicensingEnabled {
|
||||||
return true // licensing not enabled — allow all downloads
|
return true // licensing not enabled — allow all downloads
|
||||||
}
|
}
|
||||||
@@ -38,8 +38,8 @@ func CheckDownloadGating(ctx *context.Context, tagName string) bool {
|
|||||||
|
|
||||||
// For prerelease-only gating, check if this is a prerelease tag.
|
// For prerelease-only gating, check if this is a prerelease tag.
|
||||||
if gating == "prerelease" && tagName != "" {
|
if gating == "prerelease" && tagName != "" {
|
||||||
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
||||||
matched := licenses.MatchStreamFromTag(tagName, false, streams)
|
matched := updateserver_model.MatchStreamFromTag(tagName, false, streams)
|
||||||
if matched == "stable" {
|
if matched == "stable" {
|
||||||
return true // stable releases are public
|
return true // stable releases are public
|
||||||
}
|
}
|
||||||
@@ -60,14 +60,14 @@ func CheckDownloadGating(ctx *context.Context, tagName string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
domain := ctx.FormString("domain")
|
domain := ctx.FormString("domain")
|
||||||
key, _, err := licenses.ValidateLicenseKeyForRepo(ctx, rawKey, domain, repo.ID)
|
key, _, err := updateserver_model.ValidateLicenseKeyForRepo(ctx, rawKey, domain, repo.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("Download gating: key validation failed: %v", err)
|
log.Debug("Download gating: key validation failed: %v", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record heartbeat on successful download validation.
|
// Record heartbeat on successful download validation.
|
||||||
_ = licenses.TouchHeartbeat(ctx, key.ID)
|
_ = updateserver_model.TouchHeartbeat(ctx, key.ID)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ func GetSupportURL(ctx *context.Context) string {
|
|||||||
if ctx.Repo == nil || ctx.Repo.Repository == nil {
|
if ctx.Repo == nil || ctx.Repo.Repository == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
cfg := licenses.GetEffectiveConfig(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
|
cfg := updateserver_model.GetEffectiveConfig(ctx, ctx.Repo.Repository.OwnerID, ctx.Repo.Repository.ID)
|
||||||
if cfg != nil && cfg.SupportURL != "" {
|
if cfg != nil && cfg.SupportURL != "" {
|
||||||
return cfg.SupportURL
|
return cfg.SupportURL
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,14 @@ func NewIssue(ctx *context.Context) {
|
|||||||
|
|
||||||
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.Permission.CanWrite(unit.TypeIssues)
|
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.Permission.CanWrite(unit.TypeIssues)
|
||||||
|
|
||||||
|
// Load org-level status/priority/type definitions for the new issue sidebar.
|
||||||
|
issueStatusDefs, _ := issues_model.GetIssueStatusDefsByOrg(ctx, ctx.Repo.Repository.OwnerID)
|
||||||
|
ctx.Data["IssueStatusDefs"] = issueStatusDefs
|
||||||
|
issuePriorityDefs, _ := issues_model.GetIssuePriorityDefsByOrg(ctx, ctx.Repo.Repository.OwnerID)
|
||||||
|
ctx.Data["IssuePriorityDefs"] = issuePriorityDefs
|
||||||
|
issueTypeDefs, _ := issues_model.GetIssueTypeDefsByOrg(ctx, ctx.Repo.Repository.OwnerID)
|
||||||
|
ctx.Data["IssueTypeDefs"] = issueTypeDefs
|
||||||
|
|
||||||
// Load org-level issue-scoped custom fields for the new issue sidebar.
|
// Load org-level issue-scoped custom fields for the new issue sidebar.
|
||||||
customFieldDefs, cfErr := issues_model.GetCustomFieldsByOwner(ctx, ctx.Repo.Repository.OwnerID, issues_model.CustomFieldScopeIssue)
|
customFieldDefs, cfErr := issues_model.GetCustomFieldsByOwner(ctx, ctx.Repo.Repository.OwnerID, issues_model.CustomFieldScopeIssue)
|
||||||
if cfErr != nil {
|
if cfErr != nil {
|
||||||
@@ -408,6 +416,17 @@ func NewIssuePost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save status/priority/type from sidebar dropdowns.
|
||||||
|
if statusID := ctx.FormInt64("status_id"); statusID > 0 {
|
||||||
|
_ = issues_model.SetIssueStatusID(ctx, issue.ID, statusID)
|
||||||
|
}
|
||||||
|
if priorityID := ctx.FormInt64("priority_id"); priorityID > 0 {
|
||||||
|
_ = issues_model.SetIssuePriorityID(ctx, issue.ID, priorityID)
|
||||||
|
}
|
||||||
|
if typeID := ctx.FormInt64("type_id"); typeID > 0 {
|
||||||
|
_ = issues_model.SetIssueTypeID(ctx, issue.ID, typeID)
|
||||||
|
}
|
||||||
|
|
||||||
// Save custom field values submitted from the new issue form.
|
// Save custom field values submitted from the new issue form.
|
||||||
saveCustomFieldsFromForm(ctx, repo.OwnerID, issue.ID)
|
saveCustomFieldsFromForm(ctx, repo.OwnerID, issue.ID)
|
||||||
|
|
||||||
@@ -429,6 +448,7 @@ func NewIssuePost(ctx *context.Context) {
|
|||||||
|
|
||||||
// saveCustomFieldsFromForm reads custom field values from the form
|
// saveCustomFieldsFromForm reads custom field values from the form
|
||||||
// (submitted as "custom-field-{fieldID}") and persists them for the issue.
|
// (submitted as "custom-field-{fieldID}") and persists them for the issue.
|
||||||
|
// Returns true if all required fields are satisfied.
|
||||||
func saveCustomFieldsFromForm(ctx *context.Context, ownerID, issueID int64) {
|
func saveCustomFieldsFromForm(ctx *context.Context, ownerID, issueID int64) {
|
||||||
defs, err := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeIssue)
|
defs, err := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeIssue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -443,6 +463,8 @@ func saveCustomFieldsFromForm(ctx *context.Context, ownerID, issueID int64) {
|
|||||||
v := ctx.Req.FormValue(fmt.Sprintf("custom-field-%d", def.ID))
|
v := ctx.Req.FormValue(fmt.Sprintf("custom-field-%d", def.ID))
|
||||||
if v != "" {
|
if v != "" {
|
||||||
vals[def.ID] = v
|
vals[def.ID] = v
|
||||||
|
} else if def.Required {
|
||||||
|
ctx.Flash.Error(fmt.Sprintf("Custom field %q is required", def.Name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(vals) > 0 {
|
if len(vals) > 0 {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
unit_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
unit_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
|
||||||
@@ -48,7 +48,7 @@ type ChannelItem struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildChannelItems converts stream definitions into combolist items.
|
// buildChannelItems converts stream definitions into combolist items.
|
||||||
func buildChannelItems(streams []licenses.StreamDef) []ChannelItem {
|
func buildChannelItems(streams []updateserver_model.StreamDef) []ChannelItem {
|
||||||
items := make([]ChannelItem, 0, len(streams))
|
items := make([]ChannelItem, 0, len(streams))
|
||||||
for _, s := range streams {
|
for _, s := range streams {
|
||||||
label := s.Name
|
label := s.Name
|
||||||
@@ -62,7 +62,7 @@ func buildChannelItems(streams []licenses.StreamDef) []ChannelItem {
|
|||||||
|
|
||||||
// LicensePackageDisplay is used in templates.
|
// LicensePackageDisplay is used in templates.
|
||||||
type LicensePackageDisplay struct {
|
type LicensePackageDisplay struct {
|
||||||
*licenses.LicensePackage
|
*updateserver_model.LicensePackage
|
||||||
KeyCount int64
|
KeyCount int64
|
||||||
Created time.Time
|
Created time.Time
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ func Licenses(ctx *context.Context) {
|
|||||||
|
|
||||||
// Auto-create master package + key if admin and none exist.
|
// Auto-create master package + key if admin and none exist.
|
||||||
if canWriteLicenses {
|
if canWriteLicenses {
|
||||||
newMasterKey, err := licenses.EnsureMasterKey(ctx, ownerID)
|
newMasterKey, err := updateserver_model.EnsureMasterKey(ctx, ownerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("EnsureMasterKey", err)
|
ctx.ServerError("EnsureMasterKey", err)
|
||||||
return
|
return
|
||||||
@@ -91,10 +91,10 @@ func Licenses(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Always load the master key for display (prefix + status).
|
// Always load the master key for display (prefix + status).
|
||||||
masterKey, _ := licenses.GetMasterKey(ctx, ownerID)
|
masterKey, _ := updateserver_model.GetMasterKey(ctx, ownerID)
|
||||||
ctx.Data["MasterKey"] = masterKey
|
ctx.Data["MasterKey"] = masterKey
|
||||||
|
|
||||||
pkgs, err := licenses.ListLicensePackages(ctx, ownerID)
|
pkgs, err := updateserver_model.ListLicensePackages(ctx, ownerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ListLicensePackages", err)
|
ctx.ServerError("ListLicensePackages", err)
|
||||||
return
|
return
|
||||||
@@ -102,7 +102,7 @@ func Licenses(ctx *context.Context) {
|
|||||||
|
|
||||||
var display []LicensePackageDisplay
|
var display []LicensePackageDisplay
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
count, _ := licenses.CountKeysByPackage(ctx, pkg.ID)
|
count, _ := updateserver_model.CountKeysByPackage(ctx, pkg.ID)
|
||||||
display = append(display, LicensePackageDisplay{
|
display = append(display, LicensePackageDisplay{
|
||||||
LicensePackage: pkg,
|
LicensePackage: pkg,
|
||||||
KeyCount: count,
|
KeyCount: count,
|
||||||
@@ -115,11 +115,11 @@ func Licenses(ctx *context.Context) {
|
|||||||
searchQuery := strings.TrimSpace(ctx.FormString("q"))
|
searchQuery := strings.TrimSpace(ctx.FormString("q"))
|
||||||
ctx.Data["SearchQuery"] = searchQuery
|
ctx.Data["SearchQuery"] = searchQuery
|
||||||
|
|
||||||
var keys []*licenses.LicenseKey
|
var keys []*updateserver_model.LicenseKey
|
||||||
if searchQuery != "" {
|
if searchQuery != "" {
|
||||||
keys, err = licenses.SearchLicenseKeys(ctx, ownerID, searchQuery)
|
keys, err = updateserver_model.SearchLicenseKeys(ctx, ownerID, searchQuery)
|
||||||
} else {
|
} else {
|
||||||
keys, err = licenses.ListLicenseKeys(ctx, ownerID)
|
keys, err = updateserver_model.ListLicenseKeys(ctx, ownerID)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ListLicenseKeys", err)
|
ctx.ServerError("ListLicenseKeys", err)
|
||||||
@@ -129,10 +129,10 @@ func Licenses(ctx *context.Context) {
|
|||||||
ctx.Data["CanDelete"] = canDeleteLicenses(ctx)
|
ctx.Data["CanDelete"] = canDeleteLicenses(ctx)
|
||||||
|
|
||||||
// Load archived packages.
|
// Load archived packages.
|
||||||
archivedPkgs, _ := licenses.ListArchivedLicensePackages(ctx, ownerID)
|
archivedPkgs, _ := updateserver_model.ListArchivedLicensePackages(ctx, ownerID)
|
||||||
var archivedDisplay []LicensePackageDisplay
|
var archivedDisplay []LicensePackageDisplay
|
||||||
for _, pkg := range archivedPkgs {
|
for _, pkg := range archivedPkgs {
|
||||||
count, _ := licenses.CountKeysByPackage(ctx, pkg.ID)
|
count, _ := updateserver_model.CountKeysByPackage(ctx, pkg.ID)
|
||||||
archivedDisplay = append(archivedDisplay, LicensePackageDisplay{
|
archivedDisplay = append(archivedDisplay, LicensePackageDisplay{
|
||||||
LicensePackage: pkg,
|
LicensePackage: pkg,
|
||||||
KeyCount: count,
|
KeyCount: count,
|
||||||
@@ -142,12 +142,12 @@ func Licenses(ctx *context.Context) {
|
|||||||
ctx.Data["ArchivedPackages"] = archivedDisplay
|
ctx.Data["ArchivedPackages"] = archivedDisplay
|
||||||
|
|
||||||
// Load available streams for the channels combolist.
|
// Load available streams for the channels combolist.
|
||||||
orgCfg, _ := licenses.GetOrgConfig(ctx, ownerID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, ownerID)
|
||||||
var streams []licenses.StreamDef
|
var streams []updateserver_model.StreamDef
|
||||||
if orgCfg != nil {
|
if orgCfg != nil {
|
||||||
streams = orgCfg.GetActiveStreams()
|
streams = orgCfg.GetActiveStreams()
|
||||||
} else {
|
} else {
|
||||||
streams = licenses.DefaultJoomlaStreams()
|
streams = updateserver_model.DefaultJoomlaStreams()
|
||||||
}
|
}
|
||||||
ctx.Data["AvailableStreams"] = streams
|
ctx.Data["AvailableStreams"] = streams
|
||||||
ctx.Data["ChannelItems"] = buildChannelItems(streams)
|
ctx.Data["ChannelItems"] = buildChannelItems(streams)
|
||||||
@@ -186,7 +186,7 @@ func LicensesCreatePackage(ctx *context.Context) {
|
|||||||
repoScope = "all"
|
repoScope = "all"
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg := &licenses.LicensePackage{
|
pkg := &updateserver_model.LicensePackage{
|
||||||
OwnerID: ctx.Repo.Repository.OwnerID,
|
OwnerID: ctx.Repo.Repository.OwnerID,
|
||||||
Name: name,
|
Name: name,
|
||||||
Description: ctx.FormString("description"),
|
Description: ctx.FormString("description"),
|
||||||
@@ -199,7 +199,7 @@ func LicensesCreatePackage(ctx *context.Context) {
|
|||||||
IsActive: true,
|
IsActive: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.CreateLicensePackage(ctx, pkg); err != nil {
|
if err := updateserver_model.CreateLicensePackage(ctx, pkg); err != nil {
|
||||||
ctx.ServerError("CreateLicensePackage", err)
|
ctx.ServerError("CreateLicensePackage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -213,21 +213,21 @@ func LicensesRegenerateMasterKey(ctx *context.Context) {
|
|||||||
ownerID := ctx.Repo.Repository.OwnerID
|
ownerID := ctx.Repo.Repository.OwnerID
|
||||||
|
|
||||||
// Deactivate the old master key.
|
// Deactivate the old master key.
|
||||||
oldKey, _ := licenses.GetMasterKey(ctx, ownerID)
|
oldKey, _ := updateserver_model.GetMasterKey(ctx, ownerID)
|
||||||
if oldKey != nil {
|
if oldKey != nil {
|
||||||
oldKey.IsActive = false
|
oldKey.IsActive = false
|
||||||
_ = licenses.UpdateLicenseKey(ctx, oldKey)
|
_ = updateserver_model.UpdateLicenseKey(ctx, oldKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the master package.
|
// Find the master package.
|
||||||
pkgs, err := licenses.ListLicensePackages(ctx, ownerID)
|
pkgs, err := updateserver_model.ListLicensePackages(ctx, ownerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ListLicensePackages", err)
|
ctx.ServerError("ListLicensePackages", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var masterPkg *licenses.LicensePackage
|
var masterPkg *updateserver_model.LicensePackage
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
masterPkg = pkg
|
masterPkg = pkg
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -239,13 +239,13 @@ func LicensesRegenerateMasterKey(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new master key.
|
// Create a new master key.
|
||||||
newKey := &licenses.LicenseKey{
|
newKey := &updateserver_model.LicenseKey{
|
||||||
PackageID: masterPkg.ID,
|
PackageID: masterPkg.ID,
|
||||||
OwnerID: ownerID,
|
OwnerID: ownerID,
|
||||||
IsInternal: true,
|
IsInternal: true,
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
}
|
}
|
||||||
rawKey, err := licenses.CreateLicenseKey(ctx, newKey)
|
rawKey, err := updateserver_model.CreateLicenseKey(ctx, newKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("CreateLicenseKey", err)
|
ctx.ServerError("CreateLicenseKey", err)
|
||||||
return
|
return
|
||||||
@@ -270,7 +270,7 @@ func LicensesGenerateKey(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, packageID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, packageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
@@ -282,7 +282,7 @@ func LicensesGenerateKey(ctx *context.Context) {
|
|||||||
domainRestriction = pkg.DomainRestriction
|
domainRestriction = pkg.DomainRestriction
|
||||||
}
|
}
|
||||||
|
|
||||||
key := &licenses.LicenseKey{
|
key := &updateserver_model.LicenseKey{
|
||||||
PackageID: packageID,
|
PackageID: packageID,
|
||||||
OwnerID: ctx.Repo.Repository.OwnerID,
|
OwnerID: ctx.Repo.Repository.OwnerID,
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
@@ -301,13 +301,13 @@ func LicensesGenerateKey(ctx *context.Context) {
|
|||||||
var rawKey string
|
var rawKey string
|
||||||
customKey := strings.TrimSpace(ctx.FormString("custom_key"))
|
customKey := strings.TrimSpace(ctx.FormString("custom_key"))
|
||||||
if customKey != "" && (ctx.IsUserSiteAdmin() || ctx.Repo.Permission.IsOwner()) {
|
if customKey != "" && (ctx.IsUserSiteAdmin() || ctx.Repo.Permission.IsOwner()) {
|
||||||
if err := licenses.CreateLicenseKeyCustom(ctx, key, customKey); err != nil {
|
if err := updateserver_model.CreateLicenseKeyCustom(ctx, key, customKey); err != nil {
|
||||||
ctx.ServerError("CreateLicenseKeyCustom", err)
|
ctx.ServerError("CreateLicenseKeyCustom", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
rawKey = customKey
|
rawKey = customKey
|
||||||
} else {
|
} else {
|
||||||
rawKey, err = licenses.CreateLicenseKey(ctx, key)
|
rawKey, err = updateserver_model.CreateLicenseKey(ctx, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("CreateLicenseKey", err)
|
ctx.ServerError("CreateLicenseKey", err)
|
||||||
return
|
return
|
||||||
@@ -323,10 +323,10 @@ func LicensesGenerateKey(ctx *context.Context) {
|
|||||||
|
|
||||||
// Re-render the page with the new key displayed.
|
// Re-render the page with the new key displayed.
|
||||||
ownerID := ctx.Repo.Repository.OwnerID
|
ownerID := ctx.Repo.Repository.OwnerID
|
||||||
pkgs, _ := licenses.ListLicensePackages(ctx, ownerID)
|
pkgs, _ := updateserver_model.ListLicensePackages(ctx, ownerID)
|
||||||
var display []LicensePackageDisplay
|
var display []LicensePackageDisplay
|
||||||
for _, p := range pkgs {
|
for _, p := range pkgs {
|
||||||
count, _ := licenses.CountKeysByPackage(ctx, p.ID)
|
count, _ := updateserver_model.CountKeysByPackage(ctx, p.ID)
|
||||||
display = append(display, LicensePackageDisplay{
|
display = append(display, LicensePackageDisplay{
|
||||||
LicensePackage: p,
|
LicensePackage: p,
|
||||||
KeyCount: count,
|
KeyCount: count,
|
||||||
@@ -334,15 +334,15 @@ func LicensesGenerateKey(ctx *context.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
ctx.Data["LicensePackages"] = display
|
ctx.Data["LicensePackages"] = display
|
||||||
keys, _ := licenses.ListLicenseKeys(ctx, ownerID)
|
keys, _ := updateserver_model.ListLicenseKeys(ctx, ownerID)
|
||||||
ctx.Data["LicenseKeys"] = keys
|
ctx.Data["LicenseKeys"] = keys
|
||||||
|
|
||||||
orgCfg, _ := licenses.GetOrgConfig(ctx, ownerID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, ownerID)
|
||||||
var genStreams []licenses.StreamDef
|
var genStreams []updateserver_model.StreamDef
|
||||||
if orgCfg != nil {
|
if orgCfg != nil {
|
||||||
genStreams = orgCfg.GetActiveStreams()
|
genStreams = orgCfg.GetActiveStreams()
|
||||||
} else {
|
} else {
|
||||||
genStreams = licenses.DefaultJoomlaStreams()
|
genStreams = updateserver_model.DefaultJoomlaStreams()
|
||||||
}
|
}
|
||||||
ctx.Data["AvailableStreams"] = genStreams
|
ctx.Data["AvailableStreams"] = genStreams
|
||||||
ctx.Data["ChannelItems"] = buildChannelItems(genStreams)
|
ctx.Data["ChannelItems"] = buildChannelItems(genStreams)
|
||||||
@@ -353,14 +353,14 @@ func LicensesGenerateKey(ctx *context.Context) {
|
|||||||
// LicensesRevokeKey handles POST to revoke a license key.
|
// LicensesRevokeKey handles POST to revoke a license key.
|
||||||
func LicensesRevokeKey(ctx *context.Context) {
|
func LicensesRevokeKey(ctx *context.Context) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicenseKeyByID", err)
|
ctx.ServerError("GetLicenseKeyByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
key.IsActive = false
|
key.IsActive = false
|
||||||
if err := licenses.UpdateLicenseKey(ctx, key); err != nil {
|
if err := updateserver_model.UpdateLicenseKey(ctx, key); err != nil {
|
||||||
ctx.ServerError("UpdateLicenseKey", err)
|
ctx.ServerError("UpdateLicenseKey", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -375,7 +375,7 @@ const tplLicensesEditKey templates.TplName = "repo/licenses_edit_key"
|
|||||||
// LicensesEditKey shows the edit form for a license key.
|
// LicensesEditKey shows the edit form for a license key.
|
||||||
func LicensesEditKey(ctx *context.Context) {
|
func LicensesEditKey(ctx *context.Context) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicenseKeyByID", err)
|
ctx.ServerError("GetLicenseKeyByID", err)
|
||||||
return
|
return
|
||||||
@@ -404,7 +404,7 @@ func LicensesEditKey(ctx *context.Context) {
|
|||||||
// LicensesEditKeyPost saves edits to a license key.
|
// LicensesEditKeyPost saves edits to a license key.
|
||||||
func LicensesEditKeyPost(ctx *context.Context) {
|
func LicensesEditKeyPost(ctx *context.Context) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicenseKeyByID", err)
|
ctx.ServerError("GetLicenseKeyByID", err)
|
||||||
return
|
return
|
||||||
@@ -433,7 +433,7 @@ func LicensesEditKeyPost(ctx *context.Context) {
|
|||||||
key.ExpiresUnix = 0
|
key.ExpiresUnix = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.UpdateLicenseKey(ctx, key); err != nil {
|
if err := updateserver_model.UpdateLicenseKey(ctx, key); err != nil {
|
||||||
ctx.ServerError("UpdateLicenseKey", err)
|
ctx.ServerError("UpdateLicenseKey", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -445,13 +445,13 @@ func LicensesEditKeyPost(ctx *context.Context) {
|
|||||||
// LicensesEditPackage shows the edit form for a license package.
|
// LicensesEditPackage shows the edit form for a license package.
|
||||||
func LicensesEditPackage(ctx *context.Context) {
|
func LicensesEditPackage(ctx *context.Context) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.Flash.Error("Master package cannot be edited")
|
ctx.Flash.Error("Master package cannot be edited")
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||||
return
|
return
|
||||||
@@ -465,12 +465,12 @@ func LicensesEditPackage(ctx *context.Context) {
|
|||||||
ctx.Data["SelectedChannels"] = selectedChannels
|
ctx.Data["SelectedChannels"] = selectedChannels
|
||||||
|
|
||||||
ownerID := ctx.Repo.Repository.OwnerID
|
ownerID := ctx.Repo.Repository.OwnerID
|
||||||
orgCfg, _ := licenses.GetOrgConfig(ctx, ownerID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, ownerID)
|
||||||
var editStreams []licenses.StreamDef
|
var editStreams []updateserver_model.StreamDef
|
||||||
if orgCfg != nil {
|
if orgCfg != nil {
|
||||||
editStreams = orgCfg.GetActiveStreams()
|
editStreams = orgCfg.GetActiveStreams()
|
||||||
} else {
|
} else {
|
||||||
editStreams = licenses.DefaultJoomlaStreams()
|
editStreams = updateserver_model.DefaultJoomlaStreams()
|
||||||
}
|
}
|
||||||
ctx.Data["AvailableStreams"] = editStreams
|
ctx.Data["AvailableStreams"] = editStreams
|
||||||
ctx.Data["ChannelItems"] = buildChannelItems(editStreams)
|
ctx.Data["ChannelItems"] = buildChannelItems(editStreams)
|
||||||
@@ -482,13 +482,13 @@ func LicensesEditPackage(ctx *context.Context) {
|
|||||||
// LicensesEditPackagePost saves edits to a license package.
|
// LicensesEditPackagePost saves edits to a license package.
|
||||||
func LicensesEditPackagePost(ctx *context.Context) {
|
func LicensesEditPackagePost(ctx *context.Context) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.Flash.Error("Master package cannot be edited")
|
ctx.Flash.Error("Master package cannot be edited")
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||||
return
|
return
|
||||||
@@ -518,7 +518,7 @@ func LicensesEditPackagePost(ctx *context.Context) {
|
|||||||
pkg.DomainRestriction = strings.TrimSpace(ctx.FormString("domain_restriction"))
|
pkg.DomainRestriction = strings.TrimSpace(ctx.FormString("domain_restriction"))
|
||||||
pkg.IsActive = ctx.FormString("is_active") == "on"
|
pkg.IsActive = ctx.FormString("is_active") == "on"
|
||||||
|
|
||||||
if err := licenses.UpdateLicensePackage(ctx, pkg); err != nil {
|
if err := updateserver_model.UpdateLicensePackage(ctx, pkg); err != nil {
|
||||||
ctx.ServerError("UpdateLicensePackage", err)
|
ctx.ServerError("UpdateLicensePackage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -535,17 +535,17 @@ func canDeleteLicenses(ctx *context.Context) bool {
|
|||||||
// LicensesArchivePackage archives a license package.
|
// LicensesArchivePackage archives a license package.
|
||||||
func LicensesArchivePackage(ctx *context.Context) {
|
func LicensesArchivePackage(ctx *context.Context) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.Flash.Error("Master package cannot be archived")
|
ctx.Flash.Error("Master package cannot be archived")
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := licenses.ArchiveLicensePackage(ctx, pkgID); err != nil {
|
if err := updateserver_model.ArchiveLicensePackage(ctx, pkgID); err != nil {
|
||||||
ctx.ServerError("ArchiveLicensePackage", err)
|
ctx.ServerError("ArchiveLicensePackage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -556,7 +556,7 @@ func LicensesArchivePackage(ctx *context.Context) {
|
|||||||
// LicensesUnarchivePackage removes archive status from a package.
|
// LicensesUnarchivePackage removes archive status from a package.
|
||||||
func LicensesUnarchivePackage(ctx *context.Context) {
|
func LicensesUnarchivePackage(ctx *context.Context) {
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
if err := licenses.UnarchiveLicensePackage(ctx, pkgID); err != nil {
|
if err := updateserver_model.UnarchiveLicensePackage(ctx, pkgID); err != nil {
|
||||||
ctx.ServerError("UnarchiveLicensePackage", err)
|
ctx.ServerError("UnarchiveLicensePackage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -571,17 +571,17 @@ func LicensesDeletePackage(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
pkgID := ctx.PathParamInt64("id")
|
pkgID := ctx.PathParamInt64("id")
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, pkgID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, pkgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if pkg.Name == licenses.MasterPackageName {
|
if pkg.Name == updateserver_model.MasterPackageName {
|
||||||
ctx.Flash.Error("Master package cannot be deleted")
|
ctx.Flash.Error("Master package cannot be deleted")
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := licenses.DeleteLicensePackage(ctx, pkgID); err != nil {
|
if err := updateserver_model.DeleteLicensePackage(ctx, pkgID); err != nil {
|
||||||
ctx.ServerError("DeleteLicensePackage", err)
|
ctx.ServerError("DeleteLicensePackage", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -593,13 +593,13 @@ func LicensesDeletePackage(ctx *context.Context) {
|
|||||||
// LicensesRenewKey extends a license key's expiration by the package's duration.
|
// LicensesRenewKey extends a license key's expiration by the package's duration.
|
||||||
func LicensesRenewKey(ctx *context.Context) {
|
func LicensesRenewKey(ctx *context.Context) {
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
key, err := licenses.GetLicenseKeyByID(ctx, keyID)
|
key, err := updateserver_model.GetLicenseKeyByID(ctx, keyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicenseKeyByID", err)
|
ctx.ServerError("GetLicenseKeyByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pkg, err := licenses.GetLicensePackageByID(ctx, key.PackageID)
|
pkg, err := updateserver_model.GetLicensePackageByID(ctx, key.PackageID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetLicensePackageByID", err)
|
ctx.ServerError("GetLicensePackageByID", err)
|
||||||
return
|
return
|
||||||
@@ -610,7 +610,7 @@ func LicensesRenewKey(ctx *context.Context) {
|
|||||||
days = 365 // default to 1 year for lifetime packages
|
days = 365 // default to 1 year for lifetime packages
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := licenses.RenewLicenseKey(ctx, keyID, days); err != nil {
|
if err := updateserver_model.RenewLicenseKey(ctx, keyID, days); err != nil {
|
||||||
ctx.ServerError("RenewLicenseKey", err)
|
ctx.ServerError("RenewLicenseKey", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -626,7 +626,7 @@ func LicensesDeleteKey(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
keyID := ctx.PathParamInt64("id")
|
keyID := ctx.PathParamInt64("id")
|
||||||
if err := licenses.DeleteLicenseKey(ctx, keyID); err != nil {
|
if err := updateserver_model.DeleteLicenseKey(ctx, keyID); err != nil {
|
||||||
ctx.ServerError("DeleteLicenseKey", err)
|
ctx.ServerError("DeleteLicenseKey", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
|
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
|
||||||
licenses_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/renderhelper"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/renderhelper"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
||||||
@@ -359,11 +359,11 @@ func newReleaseCommon(ctx *context.Context) {
|
|||||||
// Load available streams for the stream selector (when licensing enabled).
|
// Load available streams for the stream selector (when licensing enabled).
|
||||||
if ctx.Data["LicensingEnabled"] == true {
|
if ctx.Data["LicensingEnabled"] == true {
|
||||||
ownerID := ctx.Repo.Repository.OwnerID
|
ownerID := ctx.Repo.Repository.OwnerID
|
||||||
orgCfg, _ := licenses_model.GetOrgConfig(ctx, ownerID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, ownerID)
|
||||||
if orgCfg != nil {
|
if orgCfg != nil {
|
||||||
ctx.Data["AvailableStreams"] = orgCfg.GetActiveStreams()
|
ctx.Data["AvailableStreams"] = orgCfg.GetActiveStreams()
|
||||||
} else {
|
} else {
|
||||||
ctx.Data["AvailableStreams"] = licenses_model.DefaultJoomlaStreams()
|
ctx.Data["AvailableStreams"] = updateserver_model.DefaultJoomlaStreams()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -534,7 +534,7 @@ func NewReleasePost(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
// Save manual stream assignment if specified.
|
// Save manual stream assignment if specified.
|
||||||
if streamName := form.UpdateStream; streamName != "" {
|
if streamName := form.UpdateStream; streamName != "" {
|
||||||
_ = licenses_model.SetReleaseStream(ctx, rel.ID, rel.RepoID, streamName)
|
_ = updateserver_model.SetReleaseStream(ctx, rel.ID, rel.RepoID, streamName)
|
||||||
}
|
}
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
|
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
|
||||||
return
|
return
|
||||||
@@ -596,7 +596,7 @@ func EditRelease(ctx *context.Context) {
|
|||||||
ctx.Data["content"] = rel.Note
|
ctx.Data["content"] = rel.Note
|
||||||
ctx.Data["prerelease"] = rel.IsPrerelease
|
ctx.Data["prerelease"] = rel.IsPrerelease
|
||||||
ctx.Data["IsDraft"] = rel.IsDraft
|
ctx.Data["IsDraft"] = rel.IsDraft
|
||||||
releaseStream := licenses_model.GetReleaseStream(ctx, rel.ID)
|
releaseStream := updateserver_model.GetReleaseStream(ctx, rel.ID)
|
||||||
ctx.Data["ReleaseStream"] = releaseStream
|
ctx.Data["ReleaseStream"] = releaseStream
|
||||||
ctx.Data["ReleaseHasStream"] = releaseStream != ""
|
ctx.Data["ReleaseHasStream"] = releaseStream != ""
|
||||||
ctx.Data["CDNEnabled"] = setting.CDN.Enabled
|
ctx.Data["CDNEnabled"] = setting.CDN.Enabled
|
||||||
@@ -682,9 +682,9 @@ func EditReleasePost(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
// Save manual stream assignment.
|
// Save manual stream assignment.
|
||||||
if streamName := form.UpdateStream; streamName != "" {
|
if streamName := form.UpdateStream; streamName != "" {
|
||||||
_ = licenses_model.SetReleaseStream(ctx, rel.ID, rel.RepoID, streamName)
|
_ = updateserver_model.SetReleaseStream(ctx, rel.ID, rel.RepoID, streamName)
|
||||||
} else {
|
} else {
|
||||||
_ = licenses_model.DeleteReleaseStream(ctx, rel.ID)
|
_ = updateserver_model.DeleteReleaseStream(ctx, rel.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update per-asset CDN visibility flags.
|
// Update per-asset CDN visibility flags.
|
||||||
|
|||||||
@@ -4,68 +4,15 @@
|
|||||||
package setting
|
package setting
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
|
|
||||||
licenses_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
const tplSettingsLicensing templates.TplName = "repo/settings/licensing"
|
// LicensingSettings redirects to the manifest page where licensing is now consolidated.
|
||||||
|
|
||||||
// LicensingSettings displays the licensing settings page.
|
|
||||||
func LicensingSettings(ctx *context.Context) {
|
func LicensingSettings(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.settings.licensing_section")
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/updateserver")
|
||||||
ctx.Data["PageIsSettingsLicensing"] = true
|
|
||||||
|
|
||||||
repoCfg, _ := licenses_model.GetRepoConfig(ctx, ctx.Repo.Repository.ID)
|
|
||||||
ctx.Data["RepoUpdateConfig"] = repoCfg
|
|
||||||
|
|
||||||
ctx.HTML(http.StatusOK, tplSettingsLicensing)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LicensingSettingsPost saves the licensing settings.
|
// LicensingSettingsPost redirects POST to the manifest page.
|
||||||
func LicensingSettingsPost(ctx *context.Context) {
|
func LicensingSettingsPost(ctx *context.Context) {
|
||||||
repo := ctx.Repo.Repository
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/updateserver")
|
||||||
|
|
||||||
updatePlatform := ctx.FormString("update_platform")
|
|
||||||
if updatePlatform == "" {
|
|
||||||
updatePlatform = "joomla"
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled := ctx.FormString("enable_licensing") == "on"
|
|
||||||
|
|
||||||
if !enabled {
|
|
||||||
// Remove repo-level override so org config takes effect
|
|
||||||
if err := licenses_model.DeleteRepoConfig(ctx, repo.ID); err != nil {
|
|
||||||
log.Error("DeleteRepoConfig: %v", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
updateCfg := &licenses_model.UpdateStreamConfig{
|
|
||||||
OwnerID: repo.OwnerID,
|
|
||||||
RepoID: repo.ID,
|
|
||||||
Platform: updatePlatform,
|
|
||||||
LicensingEnabled: true,
|
|
||||||
RequireKey: ctx.FormString("require_update_key") == "on",
|
|
||||||
DownloadGating: ctx.FormString("download_gating"),
|
|
||||||
SupportURL: ctx.FormString("support_url"),
|
|
||||||
ExtensionName: ctx.FormString("extension_name"),
|
|
||||||
DisplayName: ctx.FormString("display_name"),
|
|
||||||
ExtensionType: ctx.FormString("extension_type"),
|
|
||||||
TargetVersion: ctx.FormString("target_version"),
|
|
||||||
Maintainer: ctx.FormString("maintainer"),
|
|
||||||
PHPMinimum: ctx.FormString("php_minimum"),
|
|
||||||
StreamMode: "joomla",
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := licenses_model.SaveConfig(ctx, updateCfg); err != nil {
|
|
||||||
log.Error("SaveConfig: %v", err)
|
|
||||||
ctx.ServerError("SaveConfig", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success"))
|
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/licensing")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,189 +0,0 @@
|
|||||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
package setting
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
const tplSettingsManifest templates.TplName = "repo/settings/manifest"
|
|
||||||
|
|
||||||
// manifestXML mirrors the .mokogitea/manifest.xml schema for XML parsing.
|
|
||||||
type manifestXML struct {
|
|
||||||
XMLName xml.Name `xml:"mokoplatform"`
|
|
||||||
Identity manifestIdentity `xml:"identity"`
|
|
||||||
Governance manifestGovernance `xml:"governance"`
|
|
||||||
Distribution manifestDistribution `xml:"distribution"`
|
|
||||||
Build manifestBuild `xml:"build"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type manifestIdentity struct {
|
|
||||||
Name string `xml:"name"`
|
|
||||||
Org string `xml:"org"`
|
|
||||||
Description string `xml:"description"`
|
|
||||||
Version string `xml:"version"`
|
|
||||||
VersionPrefix string `xml:"version-prefix"`
|
|
||||||
License manifestLicense `xml:"license"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type manifestLicense struct {
|
|
||||||
SPDX string `xml:"spdx,attr"`
|
|
||||||
Name string `xml:",chardata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type manifestGovernance struct {
|
|
||||||
Platform string `xml:"platform"`
|
|
||||||
StandardsVersion string `xml:"standards-version"`
|
|
||||||
StandardsSource string `xml:"standards-source"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type manifestDistribution struct {
|
|
||||||
DisplayName string `xml:"display-name"`
|
|
||||||
Maintainer string `xml:"maintainer"`
|
|
||||||
MaintainerURL string `xml:"maintainer-url"`
|
|
||||||
InfoURL string `xml:"info-url"`
|
|
||||||
TargetVersion string `xml:"target-version"`
|
|
||||||
PHPMinimum string `xml:"php-minimum"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type manifestBuild struct {
|
|
||||||
Language string `xml:"language"`
|
|
||||||
PackageType string `xml:"package-type"`
|
|
||||||
EntryPoint string `xml:"entry-point"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// ManifestSettings displays the repo manifest settings page.
|
|
||||||
// On first visit, if no manifest exists in DB but .mokogitea/manifest.xml
|
|
||||||
// exists in the repo, it auto-migrates the XML values into the database.
|
|
||||||
func ManifestSettings(ctx *context.Context) {
|
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.settings.manifest")
|
|
||||||
ctx.Data["PageIsSettingsManifest"] = true
|
|
||||||
|
|
||||||
repoID := ctx.Repo.Repository.ID
|
|
||||||
manifest, err := repo_model.GetRepoManifest(ctx, repoID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetRepoManifest", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-detect and migrate .mokogitea/manifest.xml if no DB record exists.
|
|
||||||
if manifest == nil {
|
|
||||||
manifest = tryMigrateManifestXML(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if manifest == nil {
|
|
||||||
// No manifest found — provide empty defaults from repo metadata.
|
|
||||||
manifest = &repo_model.RepoManifest{
|
|
||||||
RepoID: repoID,
|
|
||||||
Name: ctx.Repo.Repository.Name,
|
|
||||||
Org: ctx.Repo.Repository.OwnerName,
|
|
||||||
Description: ctx.Repo.Repository.Description,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Data["Manifest"] = manifest
|
|
||||||
ctx.HTML(http.StatusOK, tplSettingsManifest)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ManifestSettingsPost saves manifest settings from the form.
|
|
||||||
func ManifestSettingsPost(ctx *context.Context) {
|
|
||||||
manifest := &repo_model.RepoManifest{
|
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
|
||||||
Name: ctx.FormString("name"),
|
|
||||||
Org: ctx.FormString("org"),
|
|
||||||
Description: ctx.Repo.Repository.Description,
|
|
||||||
Version: ctx.FormString("version"),
|
|
||||||
LicenseSPDX: ctx.FormString("license_spdx"),
|
|
||||||
LicenseName: ctx.FormString("license_name"),
|
|
||||||
VersionPrefix: ctx.FormString("version_prefix"),
|
|
||||||
ElementName: ctx.FormString("element_name"),
|
|
||||||
Platform: ctx.FormString("platform"),
|
|
||||||
StandardsVersion: ctx.FormString("standards_version"),
|
|
||||||
StandardsSource: ctx.FormString("standards_source"),
|
|
||||||
DisplayName: ctx.FormString("display_name"),
|
|
||||||
Maintainer: ctx.FormString("maintainer"),
|
|
||||||
MaintainerURL: ctx.FormString("maintainer_url"),
|
|
||||||
InfoURL: ctx.FormString("info_url"),
|
|
||||||
TargetVersion: ctx.FormString("target_version"),
|
|
||||||
PHPMinimum: ctx.FormString("php_minimum"),
|
|
||||||
Language: ctx.FormString("language"),
|
|
||||||
PackageType: ctx.FormString("package_type"),
|
|
||||||
EntryPoint: ctx.FormString("entry_point"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := repo_model.CreateOrUpdateRepoManifest(ctx, manifest); err != nil {
|
|
||||||
ctx.ServerError("CreateOrUpdateRepoManifest", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.manifest_saved"))
|
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/manifest")
|
|
||||||
}
|
|
||||||
|
|
||||||
// tryMigrateManifestXML reads .mokogitea/manifest.xml from the repo,
|
|
||||||
// parses it, and stores the values in the DB. Returns nil if no file found.
|
|
||||||
func tryMigrateManifestXML(ctx *context.Context) *repo_model.RepoManifest {
|
|
||||||
if ctx.Repo.GitRepo == nil || ctx.Repo.Commit == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(".mokogitea/manifest.xml")
|
|
||||||
if err != nil || entry == nil {
|
|
||||||
return nil // no manifest.xml found — not an error
|
|
||||||
}
|
|
||||||
|
|
||||||
reader, err := entry.Blob().DataAsync()
|
|
||||||
if err != nil {
|
|
||||||
log.Error("ManifestMigrate: read blob: %v", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer reader.Close()
|
|
||||||
|
|
||||||
var mxml manifestXML
|
|
||||||
if err := xml.NewDecoder(reader).Decode(&mxml); err != nil {
|
|
||||||
log.Error("ManifestMigrate: parse XML: %v", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
manifest := &repo_model.RepoManifest{
|
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
|
||||||
Name: mxml.Identity.Name,
|
|
||||||
Org: mxml.Identity.Org,
|
|
||||||
Description: mxml.Identity.Description,
|
|
||||||
Version: mxml.Identity.Version,
|
|
||||||
LicenseSPDX: mxml.Identity.License.SPDX,
|
|
||||||
LicenseName: mxml.Identity.License.Name,
|
|
||||||
VersionPrefix: mxml.Identity.VersionPrefix,
|
|
||||||
Platform: mxml.Governance.Platform,
|
|
||||||
StandardsVersion: mxml.Governance.StandardsVersion,
|
|
||||||
StandardsSource: mxml.Governance.StandardsSource,
|
|
||||||
DisplayName: mxml.Distribution.DisplayName,
|
|
||||||
Maintainer: mxml.Distribution.Maintainer,
|
|
||||||
MaintainerURL: mxml.Distribution.MaintainerURL,
|
|
||||||
InfoURL: mxml.Distribution.InfoURL,
|
|
||||||
TargetVersion: mxml.Distribution.TargetVersion,
|
|
||||||
PHPMinimum: mxml.Distribution.PHPMinimum,
|
|
||||||
Language: mxml.Build.Language,
|
|
||||||
PackageType: mxml.Build.PackageType,
|
|
||||||
EntryPoint: mxml.Build.EntryPoint,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := repo_model.CreateOrUpdateRepoManifest(ctx, manifest); err != nil {
|
|
||||||
log.Error("ManifestMigrate: save to DB: %v", err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("ManifestMigrate: migrated .mokogitea/manifest.xml for repo %s/%s",
|
|
||||||
ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name)
|
|
||||||
|
|
||||||
ctx.Flash.Info(fmt.Sprintf("Manifest settings imported from .mokogitea/manifest.xml. You can now delete the file from the repository."))
|
|
||||||
return manifest
|
|
||||||
}
|
|
||||||
@@ -9,20 +9,39 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
|
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
|
||||||
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
const tplSettingsMetadata templates.TplName = "repo/settings/metadata"
|
const tplSettingsMetadata templates.TplName = "repo/settings/metadata"
|
||||||
|
|
||||||
// Metadata displays the repo metadata page (repo-scoped custom field values).
|
// Metadata displays the consolidated metadata page:
|
||||||
|
// project identity (manifest), update server config, and repo-scoped custom fields.
|
||||||
func Metadata(ctx *context.Context) {
|
func Metadata(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("repo.settings.metadata")
|
ctx.Data["Title"] = "Metadata"
|
||||||
ctx.Data["PageIsSettingsMetadata"] = true
|
ctx.Data["PageIsSettingsMetadata"] = true
|
||||||
|
|
||||||
ownerID := ctx.Repo.Repository.OwnerID
|
|
||||||
repoID := ctx.Repo.Repository.ID
|
repoID := ctx.Repo.Repository.ID
|
||||||
|
ownerID := ctx.Repo.Repository.OwnerID
|
||||||
|
|
||||||
|
// Load manifest (project identity).
|
||||||
|
manifest, err := repo_model.GetRepoManifest(ctx, repoID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetRepoManifest", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if manifest == nil {
|
||||||
|
manifest = &repo_model.RepoManifest{
|
||||||
|
RepoID: repoID,
|
||||||
|
Name: ctx.Repo.Repository.Name,
|
||||||
|
Org: ctx.Repo.Repository.OwnerName,
|
||||||
|
Description: ctx.Repo.Repository.Description,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.Data["Manifest"] = manifest
|
||||||
|
|
||||||
|
// Load repo-scoped custom fields.
|
||||||
fields, _ := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeRepo)
|
fields, _ := issues_model.GetCustomFieldsByOwner(ctx, ownerID, issues_model.CustomFieldScopeRepo)
|
||||||
ctx.Data["CustomFieldDefs"] = fields
|
ctx.Data["CustomFieldDefs"] = fields
|
||||||
|
|
||||||
@@ -45,8 +64,79 @@ func Metadata(ctx *context.Context) {
|
|||||||
ctx.HTML(http.StatusOK, tplSettingsMetadata)
|
ctx.HTML(http.StatusOK, tplSettingsMetadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MetadataPost saves repo-scoped custom field values.
|
// MetadataPost routes to the correct sub-handler based on the action param.
|
||||||
func MetadataPost(ctx *context.Context) {
|
func MetadataPost(ctx *context.Context) {
|
||||||
|
switch ctx.FormString("action") {
|
||||||
|
case "manifest":
|
||||||
|
saveManifest(ctx)
|
||||||
|
case "customfields":
|
||||||
|
saveCustomFields(ctx)
|
||||||
|
default:
|
||||||
|
saveManifest(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// spdxToName maps SPDX identifiers to human-readable license names.
|
||||||
|
var spdxToName = map[string]string{
|
||||||
|
"GPL-3.0-or-later": "GNU General Public License v3 or later",
|
||||||
|
"GPL-2.0-or-later": "GNU General Public License v2 or later",
|
||||||
|
"MIT": "MIT License",
|
||||||
|
"Apache-2.0": "Apache License 2.0",
|
||||||
|
"BSD-3-Clause": "BSD 3-Clause License",
|
||||||
|
"BSD-2-Clause": "BSD 2-Clause License",
|
||||||
|
"LGPL-3.0-or-later": "GNU Lesser General Public License v3 or later",
|
||||||
|
"MPL-2.0": "Mozilla Public License 2.0",
|
||||||
|
"ISC": "ISC License",
|
||||||
|
"AGPL-3.0-or-later": "GNU Affero General Public License v3 or later",
|
||||||
|
"Unlicense": "The Unlicense",
|
||||||
|
"proprietary": "Proprietary",
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveManifest(ctx *context.Context) {
|
||||||
|
spdx := ctx.FormString("license_spdx")
|
||||||
|
licenseName := spdxToName[spdx]
|
||||||
|
|
||||||
|
// Preserve existing values for fields removed from the UI.
|
||||||
|
existing, _ := repo_model.GetRepoManifest(ctx, ctx.Repo.Repository.ID)
|
||||||
|
|
||||||
|
manifest := &repo_model.RepoManifest{
|
||||||
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
|
Name: ctx.FormString("name"),
|
||||||
|
Org: ctx.FormString("org"),
|
||||||
|
Description: ctx.Repo.Repository.Description,
|
||||||
|
Version: ctx.FormString("version"),
|
||||||
|
LicenseSPDX: spdx,
|
||||||
|
LicenseName: licenseName,
|
||||||
|
VersionPrefix: ctx.FormString("version_prefix"),
|
||||||
|
Platform: ctx.FormString("platform"),
|
||||||
|
InfoURL: ctx.FormString("info_url"),
|
||||||
|
TargetVersion: ctx.FormString("target_version"),
|
||||||
|
PHPMinimum: ctx.FormString("php_minimum"),
|
||||||
|
PackageType: ctx.FormString("package_type"),
|
||||||
|
EntryPoint: ctx.FormString("entry_point"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Preserve fields not in the UI but still in the model.
|
||||||
|
if existing != nil {
|
||||||
|
manifest.ElementName = existing.ElementName
|
||||||
|
manifest.StandardsVersion = existing.StandardsVersion
|
||||||
|
manifest.StandardsSource = existing.StandardsSource
|
||||||
|
manifest.DisplayName = existing.DisplayName
|
||||||
|
manifest.Maintainer = existing.Maintainer
|
||||||
|
manifest.MaintainerURL = existing.MaintainerURL
|
||||||
|
manifest.Language = existing.Language
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := repo_model.CreateOrUpdateRepoManifest(ctx, manifest); err != nil {
|
||||||
|
ctx.ServerError("CreateOrUpdateRepoManifest", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success("Project identity saved")
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/metadata")
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveCustomFields(ctx *context.Context) {
|
||||||
repoID := ctx.Repo.Repository.ID
|
repoID := ctx.Repo.Repository.ID
|
||||||
ownerID := ctx.Repo.Repository.OwnerID
|
ownerID := ctx.Repo.Repository.OwnerID
|
||||||
|
|
||||||
@@ -59,6 +149,7 @@ func MetadataPost(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("repo.settings.metadata_saved"))
|
ctx.Flash.Success("Custom fields saved")
|
||||||
ctx.Redirect(ctx.Repo.RepoLink + "/settings/metadata")
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/metadata")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/organization"
|
||||||
licenses_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/perm"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/perm"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
unit_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
unit_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
||||||
@@ -101,7 +101,7 @@ func SettingsCtxData(ctx *context.Context) {
|
|||||||
|
|
||||||
// Settings show a repository's settings page
|
// Settings show a repository's settings page
|
||||||
func Settings(ctx *context.Context) {
|
func Settings(ctx *context.Context) {
|
||||||
repoCfg, _ := licenses_model.GetRepoConfig(ctx, ctx.Repo.Repository.ID)
|
repoCfg, _ := updateserver_model.GetRepoConfig(ctx, ctx.Repo.Repository.ID)
|
||||||
ctx.Data["RepoUpdateConfig"] = repoCfg
|
ctx.Data["RepoUpdateConfig"] = repoCfg
|
||||||
ctx.HTML(http.StatusOK, tplSettingsOptions)
|
ctx.HTML(http.StatusOK, tplSettingsOptions)
|
||||||
}
|
}
|
||||||
@@ -679,7 +679,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
|
|||||||
// so it falls through to org defaults cleanly.
|
// so it falls through to org defaults cleanly.
|
||||||
if !form.EnableLicensing {
|
if !form.EnableLicensing {
|
||||||
// Remove repo-level override so org config takes effect
|
// Remove repo-level override so org config takes effect
|
||||||
if err := licenses_model.DeleteRepoConfig(ctx, repo.ID); err != nil {
|
if err := updateserver_model.DeleteRepoConfig(ctx, repo.ID); err != nil {
|
||||||
log.Error("DeleteRepoConfig: %v", err)
|
log.Error("DeleteRepoConfig: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -687,7 +687,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
|
|||||||
if updatePlatform == "" {
|
if updatePlatform == "" {
|
||||||
updatePlatform = "joomla"
|
updatePlatform = "joomla"
|
||||||
}
|
}
|
||||||
updateCfg := &licenses_model.UpdateStreamConfig{
|
updateCfg := &updateserver_model.UpdateStreamConfig{
|
||||||
OwnerID: repo.OwnerID,
|
OwnerID: repo.OwnerID,
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
Platform: updatePlatform,
|
Platform: updatePlatform,
|
||||||
@@ -703,7 +703,7 @@ func handleSettingsPostAdvanced(ctx *context.Context) {
|
|||||||
PHPMinimum: form.PHPMinimum,
|
PHPMinimum: form.PHPMinimum,
|
||||||
StreamMode: "joomla",
|
StreamMode: "joomla",
|
||||||
}
|
}
|
||||||
if err := licenses_model.SaveConfig(ctx, updateCfg); err != nil {
|
if err := updateserver_model.SaveConfig(ctx, updateCfg); err != nil {
|
||||||
log.Error("SaveConfig: %v", err)
|
log.Error("SaveConfig: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
|
||||||
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
const tplSettingsUpdateServer templates.TplName = "repo/settings/updateserver"
|
||||||
|
|
||||||
|
// UpdateServerSettings displays the update server settings page.
|
||||||
|
func UpdateServerSettings(ctx *context.Context) {
|
||||||
|
ctx.Data["Title"] = "Update Server"
|
||||||
|
ctx.Data["PageIsSettingsUpdateServer"] = true
|
||||||
|
|
||||||
|
repoCfg, _ := updateserver_model.GetRepoConfig(ctx, ctx.Repo.Repository.ID)
|
||||||
|
ctx.Data["RepoUpdateConfig"] = repoCfg
|
||||||
|
|
||||||
|
ctx.HTML(http.StatusOK, tplSettingsUpdateServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateServerSettingsPost saves update server visibility and gating settings.
|
||||||
|
func UpdateServerSettingsPost(ctx *context.Context) {
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
|
enabled := ctx.FormString("enable_licensing") == "on"
|
||||||
|
|
||||||
|
if !enabled {
|
||||||
|
if err := updateserver_model.DeleteRepoConfig(ctx, repo.ID); err != nil {
|
||||||
|
log.Error("DeleteRepoConfig: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Load existing config to preserve platform and other fields.
|
||||||
|
existing, _ := updateserver_model.GetRepoConfig(ctx, repo.ID)
|
||||||
|
platform := "joomla"
|
||||||
|
streamMode := "joomla"
|
||||||
|
supportURL := ""
|
||||||
|
if existing != nil {
|
||||||
|
platform = existing.Platform
|
||||||
|
streamMode = existing.StreamMode
|
||||||
|
supportURL = existing.SupportURL
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCfg := &updateserver_model.UpdateStreamConfig{
|
||||||
|
OwnerID: repo.OwnerID,
|
||||||
|
RepoID: repo.ID,
|
||||||
|
Platform: platform,
|
||||||
|
LicensingEnabled: true,
|
||||||
|
RequireKey: ctx.FormString("require_update_key") == "on",
|
||||||
|
DownloadGating: ctx.FormString("download_gating"),
|
||||||
|
FeedVisibility: ctx.FormString("feed_visibility"),
|
||||||
|
SupportURL: supportURL,
|
||||||
|
StreamMode: streamMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := updateserver_model.SaveConfig(ctx, updateCfg); err != nil {
|
||||||
|
log.Error("SaveConfig: %v", err)
|
||||||
|
ctx.ServerError("SaveConfig", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success("Update server settings saved")
|
||||||
|
ctx.Redirect(ctx.Repo.RepoLink + "/settings/updateserver")
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
|
||||||
@@ -33,15 +33,15 @@ func validateUpdateKey(ctx *context.Context) (allowedChannels []string, ok bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
domain := ctx.FormString("domain")
|
domain := ctx.FormString("domain")
|
||||||
key, pkg, err := licenses.ValidateLicenseKeyForRepo(ctx, rawKey, domain, ctx.Repo.Repository.ID)
|
key, pkg, err := updateserver_model.ValidateLicenseKeyForRepo(ctx, rawKey, domain, ctx.Repo.Repository.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("License key validation failed: %v", err)
|
log.Debug("License key validation failed: %v", err)
|
||||||
return nil, false, false
|
return nil, false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update heartbeat and record usage.
|
// Update heartbeat and record usage.
|
||||||
_ = licenses.TouchHeartbeat(ctx, key.ID)
|
_ = updateserver_model.TouchHeartbeat(ctx, key.ID)
|
||||||
_ = licenses.RecordUsage(ctx, &licenses.LicenseKeyUsage{
|
_ = updateserver_model.RecordUsage(ctx, &updateserver_model.LicenseKeyUsage{
|
||||||
KeyID: key.ID,
|
KeyID: key.ID,
|
||||||
RepoID: ctx.Repo.Repository.ID,
|
RepoID: ctx.Repo.Repository.ID,
|
||||||
Domain: domain,
|
Domain: domain,
|
||||||
@@ -93,7 +93,7 @@ func ServeUpdatesXML(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repoCfg, _ := licenses.GetRepoConfig(ctx, ctx.Repo.Repository.ID)
|
repoCfg, _ := updateserver_model.GetRepoConfig(ctx, ctx.Repo.Repository.ID)
|
||||||
// Show <downloadkey> only when downloads are gated (prerelease or all).
|
// Show <downloadkey> only when downloads are gated (prerelease or all).
|
||||||
// No gating = no license keys needed = no downloadkey element.
|
// No gating = no license keys needed = no downloadkey element.
|
||||||
requireKey := repoCfg != nil && repoCfg.DownloadGating != "" && repoCfg.DownloadGating != "none"
|
requireKey := repoCfg != nil && repoCfg.DownloadGating != "" && repoCfg.DownloadGating != "none"
|
||||||
|
|||||||
@@ -137,8 +137,8 @@ type PrepareOwnerHeaderResult struct {
|
|||||||
const (
|
const (
|
||||||
RepoNameProfilePrivate = ".profile-private"
|
RepoNameProfilePrivate = ".profile-private"
|
||||||
RepoNameProfile = ".profile"
|
RepoNameProfile = ".profile"
|
||||||
RepoNameWikiPublic = "wiki"
|
RepoNameWikiPublic = ".profile"
|
||||||
RepoNameWikiPrivate = "wiki-private"
|
RepoNameWikiPrivate = ".profile-private"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RenderUserOrgHeader(ctx *context.Context) (result *PrepareOwnerHeaderResult, err error) {
|
func RenderUserOrgHeader(ctx *context.Context) (result *PrepareOwnerHeaderResult, err error) {
|
||||||
@@ -209,11 +209,19 @@ func loadHeaderCount(ctx *context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrgWikiRepoExists checks whether a convention wiki repo exists and is non-empty.
|
// OrgWikiRepoExists checks whether a profile repo's wiki exists and has content.
|
||||||
func OrgWikiRepoExists(ctx *context.Context, ownerID int64, repoName string) bool {
|
func OrgWikiRepoExists(ctx *context.Context, ownerID int64, repoName string) bool {
|
||||||
dbRepo, err := repo_model.GetRepositoryByName(ctx, ownerID, repoName)
|
dbRepo, err := repo_model.GetRepositoryByName(ctx, ownerID, repoName)
|
||||||
if err != nil || dbRepo.IsEmpty {
|
if err != nil {
|
||||||
|
log.Trace("OrgWikiRepoExists: repo %s not found for owner %d: %v", repoName, ownerID, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
wikiRepo := dbRepo.WikiStorageRepo()
|
||||||
|
_, err = gitrepo.GetDefaultBranch(ctx, wikiRepo)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("OrgWikiRepoExists: GetDefaultBranch for wiki of %s failed: %v (path: %s)", dbRepo.FullName(), err, wikiRepo.RelativePath())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
log.Trace("OrgWikiRepoExists: wiki found for %s", dbRepo.FullName())
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-2
@@ -1229,9 +1229,10 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
|
|||||||
m.Group("", func() {
|
m.Group("", func() {
|
||||||
m.Combo("/advanced").Get(repo_setting.AdvancedSettings).Post(web.Bind(forms.RepoSettingForm{}), repo_setting.SettingsPost)
|
m.Combo("/advanced").Get(repo_setting.AdvancedSettings).Post(web.Bind(forms.RepoSettingForm{}), repo_setting.SettingsPost)
|
||||||
}, repo_setting.SettingsCtxData)
|
}, repo_setting.SettingsCtxData)
|
||||||
m.Combo("/licensing").Get(repo_setting.LicensingSettings).Post(repo_setting.LicensingSettingsPost)
|
|
||||||
m.Combo("/manifest").Get(repo_setting.ManifestSettings).Post(repo_setting.ManifestSettingsPost)
|
|
||||||
m.Combo("/metadata").Get(repo_setting.Metadata).Post(repo_setting.MetadataPost)
|
m.Combo("/metadata").Get(repo_setting.Metadata).Post(repo_setting.MetadataPost)
|
||||||
|
m.Combo("/updateserver").Get(repo_setting.UpdateServerSettings).Post(repo_setting.UpdateServerSettingsPost)
|
||||||
|
m.Combo("/manifest").Get(repo_setting.LicensingSettings).Post(repo_setting.LicensingSettingsPost) // redirect
|
||||||
|
m.Combo("/licensing").Get(repo_setting.LicensingSettings).Post(repo_setting.LicensingSettingsPost) // redirect
|
||||||
m.Group("/security", func() {
|
m.Group("/security", func() {
|
||||||
m.Combo("").Get(repo_setting.SecuritySettings).Post(repo_setting.SecuritySettingsPost)
|
m.Combo("").Get(repo_setting.SecuritySettings).Post(repo_setting.SecuritySettingsPost)
|
||||||
m.Post("/scan", repo_setting.SecurityScanNow)
|
m.Post("/scan", repo_setting.SecurityScanNow)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
|
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
|
||||||
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
|
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
|
||||||
licenses_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
access_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/perm/access"
|
access_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/perm/access"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
unit_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
unit_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/unit"
|
||||||
@@ -438,8 +438,8 @@ func repoAssignmentLegacy(ctx *Context, data *repoAssignmentPrepareDataStruct) {
|
|||||||
|
|
||||||
// Check if licensing is enabled — licensed repos allow access to
|
// Check if licensing is enabled — licensed repos allow access to
|
||||||
// releases and downloads via license key, even without membership.
|
// releases and downloads via license key, even without membership.
|
||||||
orgCfg, _ := licenses_model.GetOrgConfig(ctx, repo.OwnerID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, repo.OwnerID)
|
||||||
repoCfg, _ := licenses_model.GetRepoConfig(ctx, repo.ID)
|
repoCfg, _ := updateserver_model.GetRepoConfig(ctx, repo.ID)
|
||||||
licensingEnabled := (orgCfg != nil && orgCfg.LicensingEnabled) ||
|
licensingEnabled := (orgCfg != nil && orgCfg.LicensingEnabled) ||
|
||||||
(repoCfg != nil && repoCfg.LicensingEnabled)
|
(repoCfg != nil && repoCfg.LicensingEnabled)
|
||||||
|
|
||||||
@@ -652,12 +652,12 @@ func repoAssignmentPrepareTemplateData(ctx *Context, data *repoAssignmentPrepare
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if licensing is enabled for this repo/org.
|
// Check if licensing is enabled for this repo/org.
|
||||||
orgCfg, _ := licenses_model.GetOrgConfig(ctx, repo.OwnerID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, repo.OwnerID)
|
||||||
repoUpdateCfg, _ := licenses_model.GetRepoConfig(ctx, repo.ID)
|
repoUpdateCfg, _ := updateserver_model.GetRepoConfig(ctx, repo.ID)
|
||||||
licensingEnabled := (orgCfg != nil && orgCfg.LicensingEnabled) ||
|
licensingEnabled := (orgCfg != nil && orgCfg.LicensingEnabled) ||
|
||||||
(repoUpdateCfg != nil && repoUpdateCfg.LicensingEnabled)
|
(repoUpdateCfg != nil && repoUpdateCfg.LicensingEnabled)
|
||||||
|
|
||||||
numLicensePackages, _ := db.Count[licenses_model.LicensePackage](ctx, licenses_model.FindLicensePackageOptions{
|
numLicensePackages, _ := db.Count[updateserver_model.LicensePackage](ctx, updateserver_model.FindLicensePackageOptions{
|
||||||
OwnerID: repo.OwnerID,
|
OwnerID: repo.OwnerID,
|
||||||
})
|
})
|
||||||
ctx.Data["NumLicensePackages"] = numLicensePackages
|
ctx.Data["NumLicensePackages"] = numLicensePackages
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
licenses_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
@@ -43,7 +43,7 @@ func RepoAssignmentPublicFeed() func(ctx *Context) {
|
|||||||
ctx.Repo.Repository = repo
|
ctx.Repo.Repository = repo
|
||||||
|
|
||||||
// Load update config for platform-aware routing.
|
// Load update config for platform-aware routing.
|
||||||
repoUpdateCfg, _ := licenses_model.GetRepoConfig(ctx, repo.ID)
|
repoUpdateCfg, _ := updateserver_model.GetRepoConfig(ctx, repo.ID)
|
||||||
if repoUpdateCfg != nil {
|
if repoUpdateCfg != nil {
|
||||||
ctx.Data["RepoUpdatePlatform"] = repoUpdateCfg.Platform
|
ctx.Data["RepoUpdatePlatform"] = repoUpdateCfg.Platform
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models"
|
||||||
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
|
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
|
||||||
licenses_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/webhook"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/webhook"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git/gitcmd"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/git/gitcmd"
|
||||||
@@ -167,7 +167,7 @@ func registerCleanupExpiredLicenseKeys() {
|
|||||||
Schedule: "@weekly",
|
Schedule: "@weekly",
|
||||||
}, func(ctx context.Context, _ *user_model.User, config Config) error {
|
}, func(ctx context.Context, _ *user_model.User, config Config) error {
|
||||||
// Delete non-internal keys that expired more than 365 days ago.
|
// Delete non-internal keys that expired more than 365 days ago.
|
||||||
deleted, err := licenses_model.DeleteExpiredKeys(ctx, 365)
|
deleted, err := updateserver_model.DeleteExpiredKeys(ctx, 365)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
|
git_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/git"
|
||||||
licenses_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
user_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/container"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/container"
|
||||||
@@ -192,8 +192,8 @@ func validateTagAgainstStreams(ctx context.Context, rel *repo_model.Release) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if licensing is enabled at org or repo level.
|
// Check if licensing is enabled at org or repo level.
|
||||||
orgCfg, _ := licenses_model.GetOrgConfig(ctx, repo.OwnerID)
|
orgCfg, _ := updateserver_model.GetOrgConfig(ctx, repo.OwnerID)
|
||||||
repoCfg, _ := licenses_model.GetRepoConfig(ctx, repo.ID)
|
repoCfg, _ := updateserver_model.GetRepoConfig(ctx, repo.ID)
|
||||||
licensingEnabled := (orgCfg != nil && orgCfg.LicensingEnabled) ||
|
licensingEnabled := (orgCfg != nil && orgCfg.LicensingEnabled) ||
|
||||||
(repoCfg != nil && repoCfg.LicensingEnabled)
|
(repoCfg != nil && repoCfg.LicensingEnabled)
|
||||||
|
|
||||||
@@ -203,7 +203,7 @@ func validateTagAgainstStreams(ctx context.Context, rel *repo_model.Release) err
|
|||||||
|
|
||||||
// Check that the tag contains a stream-compatible suffix.
|
// Check that the tag contains a stream-compatible suffix.
|
||||||
// Any prerelease suffix in the tag must match a configured stream suffix.
|
// Any prerelease suffix in the tag must match a configured stream suffix.
|
||||||
streams := licenses_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
||||||
lower := strings.ToLower(rel.TagName)
|
lower := strings.ToLower(rel.TagName)
|
||||||
for _, s := range streams {
|
for _, s := range streams {
|
||||||
if s.Suffix == "" {
|
if s.Suffix == "" {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
)
|
)
|
||||||
@@ -67,7 +67,7 @@ func GenerateComposerJSON(ctx context.Context, repo *repo_model.Repository, lice
|
|||||||
baseURL := strings.TrimSuffix(setting.AppURL, "/")
|
baseURL := strings.TrimSuffix(setting.AppURL, "/")
|
||||||
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
|
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
|
||||||
|
|
||||||
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
||||||
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
||||||
|
|
||||||
// Composer package name: vendor/package (override with resolved extension name if set)
|
// Composer package name: vendor/package (override with resolved extension name if set)
|
||||||
@@ -91,7 +91,7 @@ func GenerateComposerJSON(ctx context.Context, repo *repo_model.Repository, lice
|
|||||||
phpMin = ">=" + meta.PHPMinimum
|
phpMin = ">=" + meta.PHPMinimum
|
||||||
}
|
}
|
||||||
|
|
||||||
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
||||||
versions := make(map[string]ComposerVersion)
|
versions := make(map[string]ComposerVersion)
|
||||||
|
|
||||||
for _, rel := range releases {
|
for _, rel := range releases {
|
||||||
@@ -99,7 +99,7 @@ func GenerateComposerJSON(ctx context.Context, repo *repo_model.Repository, lice
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := licenses.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
||||||
if ch != "stable" {
|
if ch != "stable" {
|
||||||
continue // Composer only serves stable versions
|
continue // Composer only serves stable versions
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
)
|
)
|
||||||
@@ -59,7 +59,7 @@ func GenerateDolibarrJSON(ctx context.Context, repo *repo_model.Repository, allo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve effective streams.
|
// Resolve effective streams.
|
||||||
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
||||||
|
|
||||||
// Track best release per channel.
|
// Track best release per channel.
|
||||||
bestByChannel := make(map[string]*repo_model.Release)
|
bestByChannel := make(map[string]*repo_model.Release)
|
||||||
@@ -67,7 +67,7 @@ func GenerateDolibarrJSON(ctx context.Context, repo *repo_model.Repository, allo
|
|||||||
if rel.IsDraft || rel.IsTag {
|
if rel.IsDraft || rel.IsTag {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ch := licenses.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
||||||
existing, ok := bestByChannel[ch]
|
existing, ok := bestByChannel[ch]
|
||||||
if !ok || rel.CreatedUnix > existing.CreatedUnix {
|
if !ok || rel.CreatedUnix > existing.CreatedUnix {
|
||||||
bestByChannel[ch] = rel
|
bestByChannel[ch] = rel
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
)
|
)
|
||||||
@@ -65,12 +65,12 @@ func GenerateDrupalXML(ctx context.Context, repo *repo_model.Repository, allowed
|
|||||||
baseURL := strings.TrimSuffix(setting.AppURL, "/")
|
baseURL := strings.TrimSuffix(setting.AppURL, "/")
|
||||||
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
|
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
|
||||||
|
|
||||||
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
||||||
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
||||||
shortName := meta.Element
|
shortName := meta.Element
|
||||||
title := meta.DisplayName
|
title := meta.DisplayName
|
||||||
|
|
||||||
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
||||||
|
|
||||||
channelAllowed := make(map[string]bool)
|
channelAllowed := make(map[string]bool)
|
||||||
if len(allowedChannels) > 0 {
|
if len(allowedChannels) > 0 {
|
||||||
@@ -97,7 +97,7 @@ func GenerateDrupalXML(ctx context.Context, repo *repo_model.Repository, allowed
|
|||||||
if rel.IsDraft || rel.IsTag {
|
if rel.IsDraft || rel.IsTag {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ch := licenses.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
||||||
if len(channelAllowed) > 0 && !channelAllowed[ch] {
|
if len(channelAllowed) > 0 && !channelAllowed[ch] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
issues_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/issues"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/storage"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/storage"
|
||||||
@@ -112,7 +111,7 @@ func channelFromTag(tagName string, isPrerelease bool) string {
|
|||||||
|
|
||||||
// isStreamName checks if a string matches any stream name (indicating the tag
|
// isStreamName checks if a string matches any stream name (indicating the tag
|
||||||
// is a stream name, not a version number).
|
// is a stream name, not a version number).
|
||||||
func isStreamName(s string, streams []licenses.StreamDef) bool {
|
func isStreamName(s string, streams []updateserver_model.StreamDef) bool {
|
||||||
for _, st := range streams {
|
for _, st := range streams {
|
||||||
if strings.EqualFold(st.Name, s) {
|
if strings.EqualFold(st.Name, s) {
|
||||||
return true
|
return true
|
||||||
@@ -163,7 +162,7 @@ func NormalizeChannel(ch string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// extensionMetadata holds resolved metadata for feed generation.
|
// extensionMetadata holds resolved metadata for feed generation.
|
||||||
// Fields are resolved with priority: custom field → config table → default.
|
// Fields are resolved with priority: manifest → config table (gating only) → default.
|
||||||
type extensionMetadata struct {
|
type extensionMetadata struct {
|
||||||
Element string
|
Element string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
@@ -176,9 +175,10 @@ type extensionMetadata struct {
|
|||||||
KeyPrefix string
|
KeyPrefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveExtensionMetadata loads extension metadata with cascading fallback:
|
// resolveExtensionMetadata loads extension metadata from the repo manifest API.
|
||||||
// org-level repo-scoped custom fields → update_stream_config → repo-derived defaults.
|
// The manifest is the single source of truth for extension identity fields.
|
||||||
func resolveExtensionMetadata(ctx context.Context, repo *repo_model.Repository, cfg *licenses.UpdateStreamConfig) extensionMetadata {
|
// The config table is only used for licensing/gating fields not in the manifest.
|
||||||
|
func resolveExtensionMetadata(ctx context.Context, repo *repo_model.Repository, cfg *updateserver_model.UpdateStreamConfig) extensionMetadata {
|
||||||
m := extensionMetadata{
|
m := extensionMetadata{
|
||||||
Element: strings.ToLower(repo.Name),
|
Element: strings.ToLower(repo.Name),
|
||||||
DisplayName: fmt.Sprintf("%s - %s", repo.Owner.Name, repo.Name),
|
DisplayName: fmt.Sprintf("%s - %s", repo.Owner.Name, repo.Name),
|
||||||
@@ -186,91 +186,49 @@ func resolveExtensionMetadata(ctx context.Context, repo *repo_model.Repository,
|
|||||||
TargetVersion: "(5|6)\\..*",
|
TargetVersion: "(5|6)\\..*",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply config table values.
|
// Manifest is the source of truth for extension metadata.
|
||||||
|
manifest, err := repo_model.GetRepoManifest(ctx, repo.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("resolveExtensionMetadata: GetRepoManifest for repo %d: %v", repo.ID, err)
|
||||||
|
}
|
||||||
|
if manifest != nil {
|
||||||
|
if manifest.ElementName != "" {
|
||||||
|
m.Element = manifest.ElementName
|
||||||
|
}
|
||||||
|
if manifest.PackageType != "" {
|
||||||
|
m.ExtType = manifest.PackageType
|
||||||
|
}
|
||||||
|
if manifest.DisplayName != "" {
|
||||||
|
m.DisplayName = manifest.DisplayName
|
||||||
|
}
|
||||||
|
if manifest.TargetVersion != "" {
|
||||||
|
m.TargetVersion = manifest.TargetVersion
|
||||||
|
}
|
||||||
|
if manifest.PHPMinimum != "" {
|
||||||
|
m.PHPMinimum = manifest.PHPMinimum
|
||||||
|
}
|
||||||
|
if manifest.Description != "" {
|
||||||
|
m.Description = manifest.Description
|
||||||
|
}
|
||||||
|
if manifest.InfoURL != "" {
|
||||||
|
m.SupportURL = manifest.InfoURL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config table: only licensing/gating fields (not in manifest).
|
||||||
if cfg != nil {
|
if cfg != nil {
|
||||||
if cfg.ExtensionName != "" {
|
|
||||||
m.Element = cfg.ExtensionName
|
|
||||||
}
|
|
||||||
if cfg.DisplayName != "" {
|
|
||||||
m.DisplayName = cfg.DisplayName
|
|
||||||
}
|
|
||||||
if cfg.ExtensionType != "" {
|
|
||||||
m.ExtType = cfg.ExtensionType
|
|
||||||
}
|
|
||||||
if cfg.TargetVersion != "" {
|
|
||||||
m.TargetVersion = cfg.TargetVersion
|
|
||||||
}
|
|
||||||
if cfg.PHPMinimum != "" {
|
|
||||||
m.PHPMinimum = cfg.PHPMinimum
|
|
||||||
}
|
|
||||||
if cfg.Description != "" {
|
|
||||||
m.Description = cfg.Description
|
|
||||||
}
|
|
||||||
if cfg.SupportURL != "" {
|
|
||||||
m.SupportURL = cfg.SupportURL
|
|
||||||
}
|
|
||||||
if cfg.DownloadGating != "" {
|
if cfg.DownloadGating != "" {
|
||||||
m.DownloadGating = cfg.DownloadGating
|
m.DownloadGating = cfg.DownloadGating
|
||||||
}
|
}
|
||||||
if cfg.KeyPrefix != "" {
|
if cfg.KeyPrefix != "" {
|
||||||
m.KeyPrefix = cfg.KeyPrefix
|
m.KeyPrefix = cfg.KeyPrefix
|
||||||
}
|
}
|
||||||
}
|
// SupportURL from config as fallback if manifest.InfoURL is empty
|
||||||
|
if m.SupportURL == "" && cfg.SupportURL != "" {
|
||||||
// Override with custom field values (highest priority).
|
m.SupportURL = cfg.SupportURL
|
||||||
fields, err := issues_model.GetCustomFieldsByOwner(ctx, repo.OwnerID, issues_model.CustomFieldScopeRepo)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("resolveExtensionMetadata: GetCustomFieldsByOwner for repo %d: %v", repo.ID, err)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
if len(fields) == 0 {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
values, err := issues_model.GetCustomFieldValuesMap(ctx, repo.ID)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("resolveExtensionMetadata: GetCustomFieldValuesMap for repo %d: %v", repo.ID, err)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
if len(values) == 0 {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build name → value map from field definitions + values.
|
|
||||||
named := make(map[string]string, len(fields))
|
|
||||||
for _, f := range fields {
|
|
||||||
if v, ok := values[f.ID]; ok && v != "" {
|
|
||||||
named[f.Name] = v
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if v := named["Extension Name"]; v != "" {
|
|
||||||
m.Element = v
|
|
||||||
}
|
|
||||||
if v := named["Display Name"]; v != "" {
|
|
||||||
m.DisplayName = v
|
|
||||||
}
|
|
||||||
if v := named["Extension Type"]; v != "" {
|
|
||||||
m.ExtType = v
|
|
||||||
}
|
|
||||||
if v := named["Target Version"]; v != "" {
|
|
||||||
m.TargetVersion = v
|
|
||||||
}
|
|
||||||
if v := named["PHP Minimum"]; v != "" {
|
|
||||||
m.PHPMinimum = v
|
|
||||||
}
|
|
||||||
if v := named["Support URL"]; v != "" {
|
|
||||||
m.SupportURL = v
|
|
||||||
}
|
|
||||||
if v := named["Description"]; v != "" {
|
|
||||||
m.Description = v
|
|
||||||
}
|
|
||||||
if v := named["Download Gating"]; v != "" {
|
|
||||||
m.DownloadGating = v
|
|
||||||
}
|
|
||||||
if v := named["Key Prefix"]; v != "" {
|
|
||||||
m.KeyPrefix = v
|
|
||||||
}
|
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +259,7 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
|
|||||||
|
|
||||||
// Load extension metadata with cascading fallback:
|
// Load extension metadata with cascading fallback:
|
||||||
// custom fields → config table → repo-derived defaults.
|
// custom fields → config table → repo-derived defaults.
|
||||||
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
||||||
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
||||||
|
|
||||||
element := meta.Element
|
element := meta.Element
|
||||||
@@ -322,7 +280,7 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve effective streams (repo override → org default → Joomla default).
|
// Resolve effective streams (repo override → org default → Joomla default).
|
||||||
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
||||||
|
|
||||||
// Track best (latest) release per channel to emit one entry per channel.
|
// Track best (latest) release per channel to emit one entry per channel.
|
||||||
bestByChannel := make(map[string]*repo_model.Release)
|
bestByChannel := make(map[string]*repo_model.Release)
|
||||||
@@ -330,7 +288,7 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
|
|||||||
if rel.IsDraft || rel.IsTag {
|
if rel.IsDraft || rel.IsTag {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ch := licenses.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
||||||
existing, ok := bestByChannel[ch]
|
existing, ok := bestByChannel[ch]
|
||||||
if !ok || rel.CreatedUnix > existing.CreatedUnix {
|
if !ok || rel.CreatedUnix > existing.CreatedUnix {
|
||||||
bestByChannel[ch] = rel
|
bestByChannel[ch] = rel
|
||||||
@@ -422,13 +380,13 @@ func GenerateJoomlaXML(ctx context.Context, repo *repo_model.Repository, require
|
|||||||
infoURL = meta.SupportURL
|
infoURL = meta.SupportURL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Joomla <client> element: packages use client_id=0 in #__extensions,
|
// Joomla <client> element: admin-side extensions use "administrator",
|
||||||
// so we must output <client>0</client> for Joomla to match the update
|
// site-side extensions use "site". Packages, components, libraries,
|
||||||
// to the installed extension. Other types default to "site" (client_id=0)
|
// and files are admin-side by default.
|
||||||
// or "administrator" (client_id=1).
|
|
||||||
client := "site"
|
client := "site"
|
||||||
if extType == "package" {
|
switch extType {
|
||||||
client = "0"
|
case "package", "component", "library", "file":
|
||||||
|
client = "administrator"
|
||||||
}
|
}
|
||||||
|
|
||||||
u := xmlUpdate{
|
u := xmlUpdate{
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
)
|
)
|
||||||
@@ -54,7 +54,7 @@ func GeneratePrestaShopXML(ctx context.Context, repo *repo_model.Repository, all
|
|||||||
baseURL := strings.TrimSuffix(setting.AppURL, "/")
|
baseURL := strings.TrimSuffix(setting.AppURL, "/")
|
||||||
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
|
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
|
||||||
|
|
||||||
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
||||||
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
||||||
moduleName := meta.Element
|
moduleName := meta.Element
|
||||||
displayName := meta.DisplayName
|
displayName := meta.DisplayName
|
||||||
@@ -64,7 +64,7 @@ func GeneratePrestaShopXML(ctx context.Context, repo *repo_model.Repository, all
|
|||||||
maintainer = cfg.Maintainer
|
maintainer = cfg.Maintainer
|
||||||
}
|
}
|
||||||
|
|
||||||
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
||||||
|
|
||||||
// Channel filtering.
|
// Channel filtering.
|
||||||
channelAllowed := make(map[string]bool)
|
channelAllowed := make(map[string]bool)
|
||||||
@@ -80,7 +80,7 @@ func GeneratePrestaShopXML(ctx context.Context, repo *repo_model.Repository, all
|
|||||||
if rel.IsDraft || rel.IsTag {
|
if rel.IsDraft || rel.IsTag {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ch := licenses.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
||||||
existing, ok := bestByChannel[ch]
|
existing, ok := bestByChannel[ch]
|
||||||
if !ok || rel.CreatedUnix > existing.CreatedUnix {
|
if !ok || rel.CreatedUnix > existing.CreatedUnix {
|
||||||
bestByChannel[ch] = rel
|
bestByChannel[ch] = rel
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
)
|
)
|
||||||
@@ -49,7 +49,7 @@ func GenerateWHMCSJSON(ctx context.Context, repo *repo_model.Repository, license
|
|||||||
baseURL := strings.TrimSuffix(setting.AppURL, "/")
|
baseURL := strings.TrimSuffix(setting.AppURL, "/")
|
||||||
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
|
repoLink := fmt.Sprintf("%s/%s/%s", baseURL, repo.Owner.Name, repo.Name)
|
||||||
|
|
||||||
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
||||||
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
||||||
displayName := meta.DisplayName
|
displayName := meta.DisplayName
|
||||||
description := meta.Description
|
description := meta.Description
|
||||||
@@ -64,7 +64,7 @@ func GenerateWHMCSJSON(ctx context.Context, repo *repo_model.Repository, license
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
||||||
|
|
||||||
// Find latest stable release.
|
// Find latest stable release.
|
||||||
var latestStable *repo_model.Release
|
var latestStable *repo_model.Release
|
||||||
@@ -72,7 +72,7 @@ func GenerateWHMCSJSON(ctx context.Context, repo *repo_model.Repository, license
|
|||||||
if rel.IsDraft || rel.IsTag {
|
if rel.IsDraft || rel.IsTag {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ch := licenses.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
||||||
if ch == "stable" {
|
if ch == "stable" {
|
||||||
if latestStable == nil || rel.CreatedUnix > latestStable.CreatedUnix {
|
if latestStable == nil || rel.CreatedUnix > latestStable.CreatedUnix {
|
||||||
latestStable = rel
|
latestStable = rel
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
|
updateserver_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/updateserver"
|
||||||
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
repo_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/repo"
|
||||||
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||||
)
|
)
|
||||||
@@ -59,7 +59,7 @@ func GenerateWordPressJSON(ctx context.Context, repo *repo_model.Repository, lic
|
|||||||
|
|
||||||
// Load extension metadata with cascading fallback:
|
// Load extension metadata with cascading fallback:
|
||||||
// custom fields → config table → repo-derived defaults.
|
// custom fields → config table → repo-derived defaults.
|
||||||
cfg := licenses.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
cfg := updateserver_model.GetEffectiveConfig(ctx, repo.OwnerID, repo.ID)
|
||||||
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
meta := resolveExtensionMetadata(ctx, repo, cfg)
|
||||||
|
|
||||||
slug := meta.Element
|
slug := meta.Element
|
||||||
@@ -84,13 +84,13 @@ func GenerateWordPressJSON(ctx context.Context, repo *repo_model.Repository, lic
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Resolve streams and find the latest stable release.
|
// Resolve streams and find the latest stable release.
|
||||||
streams := licenses.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
streams := updateserver_model.GetEffectiveStreams(ctx, repo.OwnerID, repo.ID)
|
||||||
var latestStable *repo_model.Release
|
var latestStable *repo_model.Release
|
||||||
for _, rel := range releases {
|
for _, rel := range releases {
|
||||||
if rel.IsDraft || rel.IsTag {
|
if rel.IsDraft || rel.IsTag {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ch := licenses.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
||||||
if ch == "stable" {
|
if ch == "stable" {
|
||||||
if latestStable == nil || rel.CreatedUnix > latestStable.CreatedUnix {
|
if latestStable == nil || rel.CreatedUnix > latestStable.CreatedUnix {
|
||||||
latestStable = rel
|
latestStable = rel
|
||||||
@@ -171,14 +171,14 @@ func GenerateWordPressJSON(ctx context.Context, repo *repo_model.Repository, lic
|
|||||||
}
|
}
|
||||||
|
|
||||||
// buildWordPressChangelog builds an HTML changelog from multiple releases.
|
// buildWordPressChangelog builds an HTML changelog from multiple releases.
|
||||||
func buildWordPressChangelog(ctx context.Context, releases []*repo_model.Release, streams []licenses.StreamDef) string {
|
func buildWordPressChangelog(ctx context.Context, releases []*repo_model.Release, streams []updateserver_model.StreamDef) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
count := 0
|
count := 0
|
||||||
for _, rel := range releases {
|
for _, rel := range releases {
|
||||||
if rel.IsDraft || rel.IsTag || rel.Note == "" {
|
if rel.IsDraft || rel.IsTag || rel.Note == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ch := licenses.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
ch := updateserver_model.ResolveReleaseStream(ctx, rel.ID, rel.TagName, rel.IsPrerelease, streams)
|
||||||
if ch != "stable" {
|
if ch != "stable" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,11 +68,11 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
<input class="enable-system-radio" name="wiki_mode" type="radio" value="" data-context="#external_wiki_box" data-target="#internal_wiki_box" {{if eq .Org.WikiMode ""}}checked{{end}}>
|
<input class="enable-system-radio" name="wiki_mode" type="radio" value="" data-context="#external_wiki_box" data-target="#internal_wiki_box" {{if eq .Org.WikiMode ""}}checked{{end}}>
|
||||||
<label>Internal wiki (uses <code>wiki</code> / <code>wiki-private</code> repos)</label>
|
<label>Internal wiki (uses <code>.profile</code> / <code>.profile-private</code> repo wikis)</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="internal_wiki_box" class="field tw-pl-4 {{if ne .Org.WikiMode ""}}disabled{{end}}">
|
<div id="internal_wiki_box" class="field tw-pl-4 {{if ne .Org.WikiMode ""}}disabled{{end}}">
|
||||||
<p class="help">Create repos named <code>wiki</code> (public) and/or <code>wiki-private</code> (members-only) under this organization.</p>
|
<p class="help">Enable the wiki on <code>.profile</code> (public) and/or <code>.profile-private</code> (members-only) repos.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui radio checkbox">
|
<div class="ui radio checkbox">
|
||||||
|
|||||||
@@ -11,8 +11,8 @@
|
|||||||
This organization doesn't have a wiki yet.
|
This organization doesn't have a wiki yet.
|
||||||
</div>
|
</div>
|
||||||
<p class="tw-text-center">
|
<p class="tw-text-center">
|
||||||
Create a repository named <code>wiki</code> (public) or <code>wiki-private</code> (members-only)
|
Enable the wiki on the <code>.profile</code> (public) or <code>.profile-private</code> (members-only)
|
||||||
with markdown files to get started.
|
repository to get started.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|||||||
@@ -59,6 +59,52 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{template "repo/issue/sidebar/assignee_list" $.IssuePageMetaData}}
|
{{template "repo/issue/sidebar/assignee_list" $.IssuePageMetaData}}
|
||||||
|
|
||||||
|
{{if .IssueTypeDefs}}
|
||||||
|
<div class="divider"></div>
|
||||||
|
<div class="tw-flex tw-items-center tw-justify-between tw-gap-2">
|
||||||
|
<span class="text grey tw-text-sm">{{ctx.Locale.Tr "repo.issues.type"}} <span class="tw-text-red">*</span></span>
|
||||||
|
<select name="type_id" class="ui compact mini dropdown tw-max-w-48" required>
|
||||||
|
<option value="">—</option>
|
||||||
|
{{range .IssueTypeDefs}}
|
||||||
|
<option value="{{.ID}}" {{if .IsDefault}}selected{{end}}
|
||||||
|
{{if .Color}}style="border-left: 3px solid {{.Color}}"{{end}}>
|
||||||
|
{{.Name}}
|
||||||
|
</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .IssuePriorityDefs}}
|
||||||
|
<div class="tw-flex tw-items-center tw-justify-between tw-gap-2 tw-mt-2">
|
||||||
|
<span class="text grey tw-text-sm">{{ctx.Locale.Tr "repo.issues.priority"}} <span class="tw-text-red">*</span></span>
|
||||||
|
<select name="priority_id" class="ui compact mini dropdown tw-max-w-48" required>
|
||||||
|
<option value="">—</option>
|
||||||
|
{{range .IssuePriorityDefs}}
|
||||||
|
<option value="{{.ID}}" {{if .IsDefault}}selected{{end}}
|
||||||
|
{{if .Color}}style="border-left: 3px solid {{.Color}}"{{end}}>
|
||||||
|
{{.Name}}
|
||||||
|
</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if .IssueStatusDefs}}
|
||||||
|
<div class="tw-flex tw-items-center tw-justify-between tw-gap-2 tw-mt-2">
|
||||||
|
<span class="text grey tw-text-sm">{{ctx.Locale.Tr "repo.issues.status"}} <span class="tw-text-red">*</span></span>
|
||||||
|
<select name="status_id" class="ui compact mini dropdown tw-max-w-48" required>
|
||||||
|
<option value="">—</option>
|
||||||
|
{{range .IssueStatusDefs}}
|
||||||
|
<option value="{{.ID}}" {{if and (eq .SortOrder 1) (not .ClosesIssue)}}selected{{end}}
|
||||||
|
{{if .Color}}style="border-left: 3px solid {{.Color}}"{{end}}>
|
||||||
|
{{.Name}}{{if .ClosesIssue}} ⏻{{end}}
|
||||||
|
</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{if .CustomFieldDefs}}
|
{{if .CustomFieldDefs}}
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="tw-flex tw-flex-col tw-gap-2">
|
<div class="tw-flex tw-flex-col tw-gap-2">
|
||||||
@@ -67,17 +113,17 @@
|
|||||||
{{range .CustomFieldDefs}}
|
{{range .CustomFieldDefs}}
|
||||||
{{$currentVal := index $values .ID}}
|
{{$currentVal := index $values .ID}}
|
||||||
<div class="tw-flex tw-items-center tw-justify-between tw-gap-2">
|
<div class="tw-flex tw-items-center tw-justify-between tw-gap-2">
|
||||||
<span class="text grey tw-text-sm" {{if .Description}}title="{{.Description}}"{{end}}>{{.Name}}</span>
|
<span class="text grey tw-text-sm" {{if .Description}}title="{{.Description}}"{{end}}>{{.Name}}{{if .Required}} <span class="tw-text-red">*</span>{{end}}</span>
|
||||||
{{if ne .Options ""}}
|
{{if ne .Options ""}}
|
||||||
{{$opts := index $fieldOptions .ID}}
|
{{$opts := index $fieldOptions .ID}}
|
||||||
<select name="custom-field-{{.ID}}" class="ui compact mini dropdown tw-max-w-48">
|
<select name="custom-field-{{.ID}}" class="ui compact mini dropdown tw-max-w-48" {{if .Required}}required{{end}}>
|
||||||
<option value="">—</option>
|
<option value="">—</option>
|
||||||
{{range $opts}}
|
{{range $opts}}
|
||||||
<option value="{{.}}" {{if eq . $currentVal}}selected{{end}}>{{.}}</option>
|
<option value="{{.}}" {{if eq . $currentVal}}selected{{end}}>{{.}}</option>
|
||||||
{{end}}
|
{{end}}
|
||||||
</select>
|
</select>
|
||||||
{{else}}
|
{{else}}
|
||||||
<input name="custom-field-{{.ID}}" type="text" class="tw-max-w-48 tw-text-sm" value="{{$currentVal}}" placeholder="—">
|
<input name="custom-field-{{.ID}}" type="text" class="tw-max-w-48 tw-text-sm" value="{{$currentVal}}" placeholder="—" {{if .Required}}required{{end}}>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings manifest")}}
|
|
||||||
<h4 class="ui top attached header">
|
|
||||||
{{ctx.Locale.Tr "repo.settings.manifest"}}
|
|
||||||
</h4>
|
|
||||||
<div class="ui attached segment">
|
|
||||||
<p class="text grey">{{ctx.Locale.Tr "repo.settings.manifest_desc"}}</p>
|
|
||||||
|
|
||||||
<form class="ui form" method="post" action="{{.RepoLink}}/settings/manifest">
|
|
||||||
{{.CsrfTokenHtml}}
|
|
||||||
|
|
||||||
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.manifest_identity"}}</h5>
|
|
||||||
<div class="two fields">
|
|
||||||
<div class="field">
|
|
||||||
{{if eq .Manifest.Platform "joomla"}}
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_element_name"}}</label>
|
|
||||||
<input name="name" value="{{.Manifest.Name}}" placeholder="e.g. mokowaas">
|
|
||||||
<p class="help">{{ctx.Locale.Tr "repo.settings.manifest_element_name_help"}}</p>
|
|
||||||
{{else}}
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_name"}}</label>
|
|
||||||
<input name="name" value="{{.Manifest.Name}}" placeholder="Project name">
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_org"}}</label>
|
|
||||||
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="four fields">
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_version"}}</label>
|
|
||||||
<input name="version" value="{{.Manifest.Version}}" placeholder="e.g. 06.00.00">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_version_prefix"}}</label>
|
|
||||||
<input name="version_prefix" value="{{.Manifest.VersionPrefix}}" placeholder="e.g. v1.26.1-moko.">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_license_spdx"}}</label>
|
|
||||||
<input name="license_spdx" value="{{.Manifest.LicenseSPDX}}" placeholder="e.g. GPL-3.0-or-later">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_license_name"}}</label>
|
|
||||||
<input name="license_name" value="{{.Manifest.LicenseName}}" placeholder="e.g. GNU General Public License v3">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.manifest_governance"}}</h5>
|
|
||||||
<div class="three fields">
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_platform"}}</label>
|
|
||||||
<select name="platform" class="ui dropdown">
|
|
||||||
<option value="">—</option>
|
|
||||||
{{$platform := .Manifest.Platform}}
|
|
||||||
{{range $val := StringUtils.Split "joomla,wordpress,dolibarr,go,mcp,platform,generic" ","}}
|
|
||||||
<option value="{{$val}}" {{if eq $val $platform}}selected{{end}}>{{$val}}</option>
|
|
||||||
{{end}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_standards_version"}}</label>
|
|
||||||
<input name="standards_version" value="{{.Manifest.StandardsVersion}}" placeholder="e.g. 05.00.00">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_standards_source"}}</label>
|
|
||||||
<input name="standards_source" value="{{.Manifest.StandardsSource}}" placeholder="URL to standards repo">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{if or (eq .Manifest.Platform "joomla") (eq .Manifest.Platform "wordpress") (eq .Manifest.Platform "dolibarr")}}
|
|
||||||
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.manifest_distribution"}}</h5>
|
|
||||||
<div class="two fields">
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_display_name"}}</label>
|
|
||||||
<input name="display_name" value="{{.Manifest.DisplayName}}" placeholder="e.g. Package - MokoWaaS">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_info_url"}}</label>
|
|
||||||
<input name="info_url" value="{{.Manifest.InfoURL}}" placeholder="https://mokoconsulting.tech/product/...">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="two fields">
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_maintainer"}}</label>
|
|
||||||
<input name="maintainer" value="{{.Manifest.Maintainer}}" placeholder="Moko Consulting">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_maintainer_url"}}</label>
|
|
||||||
<input name="maintainer_url" value="{{.Manifest.MaintainerURL}}" placeholder="https://mokoconsulting.tech">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{if or (eq .Manifest.Platform "joomla") (eq .Manifest.Platform "wordpress")}}
|
|
||||||
<div class="two fields">
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_target_version"}}</label>
|
|
||||||
<input name="target_version" value="{{.Manifest.TargetVersion}}" placeholder="e.g. (5|6)\..*">
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_php_minimum"}}</label>
|
|
||||||
<input name="php_minimum" value="{{.Manifest.PHPMinimum}}" placeholder="e.g. 8.1">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
|
|
||||||
<h5 class="ui dividing header">{{ctx.Locale.Tr "repo.settings.manifest_build"}}</h5>
|
|
||||||
<div class="three fields">
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_language"}}</label>
|
|
||||||
<select name="language" class="ui dropdown">
|
|
||||||
<option value="">—</option>
|
|
||||||
{{$lang := .Manifest.Language}}
|
|
||||||
{{range $val := StringUtils.Split "Go,PHP,TypeScript,JavaScript,Python,Ruby,Java,C#,Rust,Shell,SQL,CSS,HTML" ","}}
|
|
||||||
<option value="{{$val}}" {{if eq $val $lang}}selected{{end}}>{{$val}}</option>
|
|
||||||
{{end}}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
{{if eq .Manifest.Platform "joomla"}}
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_package_type"}}</label>
|
|
||||||
<select name="package_type" class="ui dropdown">
|
|
||||||
<option value="">—</option>
|
|
||||||
{{$pkgType := .Manifest.PackageType}}
|
|
||||||
{{range $val := StringUtils.Split "component,module,plugin,package,template,library,file" ","}}
|
|
||||||
<option value="{{$val}}" {{if eq $val $pkgType}}selected{{end}}>{{$val}}</option>
|
|
||||||
{{end}}
|
|
||||||
</select>
|
|
||||||
<p class="help">{{ctx.Locale.Tr "repo.settings.manifest_package_type_help"}}</p>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_element_full"}}</label>
|
|
||||||
<input name="element_name" value="{{.Manifest.ElementName}}" placeholder="{{.Manifest.AutoElementName}}">
|
|
||||||
{{if .Manifest.ElementNameMismatch}}
|
|
||||||
<p class="help tw-text-yellow-600">{{ctx.Locale.Tr "repo.settings.manifest_element_mismatch" .Manifest.AutoElementName}}</p>
|
|
||||||
{{else}}
|
|
||||||
<p class="help">{{ctx.Locale.Tr "repo.settings.manifest_element_full_help"}}</p>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
|
||||||
<div class="field">
|
|
||||||
<label>{{ctx.Locale.Tr "repo.settings.manifest_entry_point"}}</label>
|
|
||||||
<input name="entry_point" value="{{.Manifest.EntryPoint}}" placeholder="e.g. ./ or src/index.ts">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "repo.settings.manifest_save"}}</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{{template "repo/settings/layout_footer" .}}
|
|
||||||
@@ -1,49 +1,153 @@
|
|||||||
{{template "repo/settings/layout_head" (dict "pageClass" "repository settings metadata")}}
|
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings metadata")}}
|
||||||
<div class="user-main-content twelve wide column">
|
<h4 class="ui top attached header">
|
||||||
<h4 class="ui top attached header">
|
{{svg "octicon-file-code" 16}} Project Identity
|
||||||
{{svg "octicon-list-unordered" 16}} {{ctx.Locale.Tr "repo.settings.metadata"}}
|
</h4>
|
||||||
</h4>
|
<div class="ui attached segment">
|
||||||
<div class="ui attached segment">
|
<form class="ui form" method="post" action="{{.RepoLink}}/settings/metadata?action=manifest">
|
||||||
{{if .CustomFieldDefs}}
|
{{.CsrfTokenHtml}}
|
||||||
<form class="ui form" method="post" action="{{.RepoLink}}/settings/metadata">
|
|
||||||
{{.CsrfTokenHtml}}
|
<div class="two fields">
|
||||||
{{$values := .CustomFieldValues}}
|
<div class="field">
|
||||||
{{$options := .CustomFieldOptions}}
|
{{if eq .Manifest.Platform "joomla"}}
|
||||||
{{range .CustomFieldDefs}}
|
<label>Element Name</label>
|
||||||
{{$currentVal := index $values .ID}}
|
<input name="name" value="{{.Manifest.Name}}" placeholder="e.g. mokowaas">
|
||||||
<div class="field">
|
{{else}}
|
||||||
<label>{{.Name}}{{if .Description}} <small class="text grey">({{.Description}})</small>{{end}}</label>
|
<label>Project Name</label>
|
||||||
{{if .Options}}
|
<input name="name" value="{{.Manifest.Name}}" placeholder="Project name">
|
||||||
{{$opts := index $options .ID}}
|
|
||||||
<select name="field_{{.ID}}" class="ui dropdown">
|
|
||||||
<option value="">—</option>
|
|
||||||
{{range $opts}}
|
|
||||||
<option value="{{.}}" {{if eq . $currentVal}}selected{{end}}>{{.}}</option>
|
|
||||||
{{end}}
|
|
||||||
</select>
|
|
||||||
{{else if eq (printf "%s" .FieldType) "checkbox"}}
|
|
||||||
<div class="ui checkbox">
|
|
||||||
<input type="checkbox" name="field_{{.ID}}" value="true" {{if eq $currentVal "true"}}checked{{end}}>
|
|
||||||
<label></label>
|
|
||||||
</div>
|
|
||||||
{{else if eq (printf "%s" .FieldType) "number"}}
|
|
||||||
<input type="number" name="field_{{.ID}}" value="{{$currentVal}}">
|
|
||||||
{{else if eq (printf "%s" .FieldType) "url"}}
|
|
||||||
<input type="url" name="field_{{.ID}}" value="{{$currentVal}}" placeholder="https://...">
|
|
||||||
{{else if eq (printf "%s" .FieldType) "date"}}
|
|
||||||
<input type="date" name="field_{{.ID}}" value="{{$currentVal}}">
|
|
||||||
{{else}}
|
|
||||||
<input type="text" name="field_{{.ID}}" value="{{$currentVal}}">
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="field tw-mt-4">
|
</div>
|
||||||
<button class="ui primary button" type="submit">{{ctx.Locale.Tr "save"}}</button>
|
<div class="field">
|
||||||
</div>
|
<label>Organization</label>
|
||||||
</form>
|
<input name="org" value="{{.Manifest.Org}}" placeholder="Organization">
|
||||||
{{else}}
|
</div>
|
||||||
<p class="text grey">{{ctx.Locale.Tr "repo.settings.metadata_empty"}}</p>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="four fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>Version</label>
|
||||||
|
<input name="version" value="{{.Manifest.Version}}" placeholder="e.g. 06.00.00">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Version Prefix</label>
|
||||||
|
<input name="version_prefix" value="{{.Manifest.VersionPrefix}}" placeholder="e.g. v1.26.1-moko.">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>License</label>
|
||||||
|
<select name="license_spdx" class="ui dropdown">
|
||||||
|
<option value="">—</option>
|
||||||
|
{{$lic := .Manifest.LicenseSPDX}}
|
||||||
|
<option value="GPL-3.0-or-later" {{if eq $lic "GPL-3.0-or-later"}}selected{{end}}>GPL-3.0-or-later</option>
|
||||||
|
<option value="GPL-2.0-or-later" {{if eq $lic "GPL-2.0-or-later"}}selected{{end}}>GPL-2.0-or-later</option>
|
||||||
|
<option value="MIT" {{if eq $lic "MIT"}}selected{{end}}>MIT</option>
|
||||||
|
<option value="Apache-2.0" {{if eq $lic "Apache-2.0"}}selected{{end}}>Apache-2.0</option>
|
||||||
|
<option value="BSD-3-Clause" {{if eq $lic "BSD-3-Clause"}}selected{{end}}>BSD-3-Clause</option>
|
||||||
|
<option value="BSD-2-Clause" {{if eq $lic "BSD-2-Clause"}}selected{{end}}>BSD-2-Clause</option>
|
||||||
|
<option value="LGPL-3.0-or-later" {{if eq $lic "LGPL-3.0-or-later"}}selected{{end}}>LGPL-3.0-or-later</option>
|
||||||
|
<option value="MPL-2.0" {{if eq $lic "MPL-2.0"}}selected{{end}}>MPL-2.0</option>
|
||||||
|
<option value="ISC" {{if eq $lic "ISC"}}selected{{end}}>ISC</option>
|
||||||
|
<option value="AGPL-3.0-or-later" {{if eq $lic "AGPL-3.0-or-later"}}selected{{end}}>AGPL-3.0-or-later</option>
|
||||||
|
<option value="Unlicense" {{if eq $lic "Unlicense"}}selected{{end}}>Unlicense</option>
|
||||||
|
<option value="proprietary" {{if eq $lic "proprietary"}}selected{{end}}>Proprietary</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h5 class="ui dividing header">Governance</h5>
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>Platform</label>
|
||||||
|
<select name="platform" class="ui dropdown">
|
||||||
|
<option value="">—</option>
|
||||||
|
{{$platform := .Manifest.Platform}}
|
||||||
|
{{range $val := StringUtils.Split "joomla,wordpress,dolibarr,go,mcp,platform,generic" ","}}
|
||||||
|
<option value="{{$val}}" {{if eq $val $platform}}selected{{end}}>{{$val}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Info URL</label>
|
||||||
|
<input name="info_url" value="{{.Manifest.InfoURL}}" placeholder="https://mokoconsulting.tech/product/...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if or (eq .Manifest.Platform "joomla") (eq .Manifest.Platform "wordpress")}}
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>Target Platform Version</label>
|
||||||
|
<input name="target_version" value="{{.Manifest.TargetVersion}}" placeholder="e.g. (5|6)\..*">
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>PHP Minimum</label>
|
||||||
|
<input name="php_minimum" value="{{.Manifest.PHPMinimum}}" placeholder="e.g. 8.1">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{if eq .Manifest.Platform "joomla"}}
|
||||||
|
<h5 class="ui dividing header">Build</h5>
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>Extension Type</label>
|
||||||
|
<select name="package_type" class="ui dropdown">
|
||||||
|
<option value="">—</option>
|
||||||
|
{{$pkgType := .Manifest.PackageType}}
|
||||||
|
{{range $val := StringUtils.Split "component,module,plugin,package,template,library,file" ","}}
|
||||||
|
<option value="{{$val}}" {{if eq $val $pkgType}}selected{{end}}>{{$val}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="field">
|
||||||
|
<label>Entry Point</label>
|
||||||
|
<input name="entry_point" value="{{.Manifest.EntryPoint}}" placeholder="e.g. ./ or src/index.ts">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<button class="ui primary button" type="submit">Save Project Identity</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .CustomFieldDefs}}
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{svg "octicon-list-unordered" 16}} Custom Fields
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached segment">
|
||||||
|
<form class="ui form" method="post" action="{{.RepoLink}}/settings/metadata?action=customfields">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
{{$values := .CustomFieldValues}}
|
||||||
|
{{$options := .CustomFieldOptions}}
|
||||||
|
{{range .CustomFieldDefs}}
|
||||||
|
{{$currentVal := index $values .ID}}
|
||||||
|
<div class="field">
|
||||||
|
<label>{{.Name}}{{if .Required}} <span class="tw-text-red">*</span>{{end}}{{if .Description}} <small class="text grey">({{.Description}})</small>{{end}}</label>
|
||||||
|
{{if .Options}}
|
||||||
|
{{$opts := index $options .ID}}
|
||||||
|
<select name="field_{{.ID}}" class="ui dropdown" {{if .Required}}required{{end}}>
|
||||||
|
<option value="">—</option>
|
||||||
|
{{range $opts}}
|
||||||
|
<option value="{{.}}" {{if eq . $currentVal}}selected{{end}}>{{.}}</option>
|
||||||
|
{{end}}
|
||||||
|
</select>
|
||||||
|
{{else if eq (printf "%s" .FieldType) "checkbox"}}
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input type="checkbox" name="field_{{.ID}}" value="true" {{if eq $currentVal "true"}}checked{{end}}>
|
||||||
|
<label></label>
|
||||||
|
</div>
|
||||||
|
{{else if eq (printf "%s" .FieldType) "number"}}
|
||||||
|
<input type="number" name="field_{{.ID}}" value="{{$currentVal}}" {{if .Required}}required{{end}}>
|
||||||
|
{{else if eq (printf "%s" .FieldType) "url"}}
|
||||||
|
<input type="url" name="field_{{.ID}}" value="{{$currentVal}}" placeholder="https://..." {{if .Required}}required{{end}}>
|
||||||
|
{{else if eq (printf "%s" .FieldType) "date"}}
|
||||||
|
<input type="date" name="field_{{.ID}}" value="{{$currentVal}}" {{if .Required}}required{{end}}>
|
||||||
|
{{else}}
|
||||||
|
<input type="text" name="field_{{.ID}}" value="{{$currentVal}}" {{if .Required}}required{{end}}>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="field tw-mt-4">
|
||||||
|
<button class="ui primary button" type="submit">Save Custom Fields</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{template "repo/settings/layout_footer" .}}
|
{{template "repo/settings/layout_footer" .}}
|
||||||
|
|||||||
@@ -7,17 +7,14 @@
|
|||||||
<a class="{{if .PageIsSettingsAdvanced}}active {{end}}item" href="{{.RepoLink}}/settings/advanced">
|
<a class="{{if .PageIsSettingsAdvanced}}active {{end}}item" href="{{.RepoLink}}/settings/advanced">
|
||||||
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings.advanced_settings"}}
|
{{svg "octicon-tools"}} {{ctx.Locale.Tr "repo.settings.advanced_settings"}}
|
||||||
</a>
|
</a>
|
||||||
|
<a class="{{if .PageIsSettingsMetadata}}active {{end}}item" href="{{.RepoLink}}/settings/metadata">
|
||||||
|
{{svg "octicon-file-code"}} Metadata
|
||||||
|
</a>
|
||||||
{{if .LicensingEnabled}}
|
{{if .LicensingEnabled}}
|
||||||
<a class="{{if .PageIsSettingsLicensing}}active {{end}}item" href="{{.RepoLink}}/settings/licensing">
|
<a class="{{if .PageIsSettingsUpdateServer}}active {{end}}item" href="{{.RepoLink}}/settings/updateserver">
|
||||||
{{svg "octicon-broadcast"}} {{ctx.Locale.Tr "repo.settings.licensing_section"}}
|
{{svg "octicon-broadcast"}} Update Server
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
<a class="{{if .PageIsSettingsManifest}}active {{end}}item" href="{{.RepoLink}}/settings/manifest">
|
|
||||||
{{svg "octicon-file-code"}} {{ctx.Locale.Tr "repo.settings.manifest"}}
|
|
||||||
</a>
|
|
||||||
<a class="{{if .PageIsSettingsMetadata}}active {{end}}item" href="{{.RepoLink}}/settings/metadata">
|
|
||||||
{{svg "octicon-list-unordered"}} {{ctx.Locale.Tr "repo.settings.metadata"}}
|
|
||||||
</a>
|
|
||||||
<a class="{{if .PageIsSettingsSecurity}}active {{end}}item" href="{{.RepoLink}}/settings/security">
|
<a class="{{if .PageIsSettingsSecurity}}active {{end}}item" href="{{.RepoLink}}/settings/security">
|
||||||
{{svg "octicon-shield"}} {{ctx.Locale.Tr "repo.settings.security"}}
|
{{svg "octicon-shield"}} {{ctx.Locale.Tr "repo.settings.security"}}
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
{{template "repo/settings/layout_head" (dict "pageClass" "repository settings updateserver")}}
|
||||||
|
<div class="user-main-content twelve wide column">
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{svg "octicon-broadcast" 16}} Update Server
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached segment">
|
||||||
|
<form class="ui form" method="post" action="{{.RepoLink}}/settings/updateserver">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
|
||||||
|
<div class="inline field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input name="enable_licensing" type="checkbox" {{if and .RepoUpdateConfig .RepoUpdateConfig.LicensingEnabled}}checked{{end}}>
|
||||||
|
<label><strong>Enable Update Server</strong></label>
|
||||||
|
</div>
|
||||||
|
<p class="help">Serve update feeds from releases and show the Licenses tab for optional key management.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="ui divider"></div>
|
||||||
|
|
||||||
|
<div class="inline field">
|
||||||
|
<label>Feed Visibility</label>
|
||||||
|
<select name="feed_visibility" class="ui dropdown">
|
||||||
|
<option value="public" {{if or (not .RepoUpdateConfig) (eq .RepoUpdateConfig.FeedVisibility "") (eq .RepoUpdateConfig.FeedVisibility "public")}}selected{{end}}>Public (show versions and downloads)</option>
|
||||||
|
<option value="no-download" {{if and .RepoUpdateConfig (eq .RepoUpdateConfig.FeedVisibility "no-download")}}selected{{end}}>No downloads (show versions only)</option>
|
||||||
|
<option value="hidden" {{if and .RepoUpdateConfig (eq .RepoUpdateConfig.FeedVisibility "hidden")}}selected{{end}}>Hidden (require license key)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inline field">
|
||||||
|
<label>Download Gating</label>
|
||||||
|
<select name="download_gating" class="ui dropdown">
|
||||||
|
<option value="none" {{if or (not .RepoUpdateConfig) (eq .RepoUpdateConfig.DownloadGating "") (eq .RepoUpdateConfig.DownloadGating "none")}}selected{{end}}>None</option>
|
||||||
|
<option value="prerelease" {{if and .RepoUpdateConfig (eq .RepoUpdateConfig.DownloadGating "prerelease")}}selected{{end}}>Pre-release only</option>
|
||||||
|
<option value="all" {{if and .RepoUpdateConfig (eq .RepoUpdateConfig.DownloadGating "all")}}selected{{end}}>All releases</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inline field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input name="require_update_key" type="checkbox" {{if and .RepoUpdateConfig .RepoUpdateConfig.RequireKey}}checked{{end}}>
|
||||||
|
<label>Require license key for update feed</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<button class="ui primary button">Save</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "repo/settings/layout_footer" .}}
|
||||||
Reference in New Issue
Block a user