Compare commits

...

20 Commits

Author SHA1 Message Date
gitea-actions[bot] 0eb81f9c1a chore(release): build 05.08.00 [skip ci] 2026-05-30 21:49:04 +00:00
jmiller 6498459e49 Merge pull request 'chore: merge dev into main — all build fixes' (#256) from dev into main
Deploy MokoGitea / deploy (push) Has been cancelled
chore: merge dev into main — all build fixes (#256)
2026-05-30 21:47:57 +00:00
jmiller 2b82312b4e Merge branch 'main' into dev
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
Universal: Build & Release / Promote to RC (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
2026-05-30 21:47:41 +00:00
jmiller 8808dfc3ce Merge pull request 'fix(api): fix route registration for license key API' (#255) from fix/api-route-registration into dev
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been cancelled
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Universal: Build & Release / Promote to RC (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
fix(api): fix route registration for license key API (#255)
2026-05-30 21:44:19 +00:00
Jonathan Miller 470364e50c fix(api): fix route registration — reqAdmin and m.Delete pattern
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Fix two compilation errors in license key API routes:
- reqRepoAdmin() does not exist, use reqAdmin() instead
- m.Delete(handler) needs route pattern: m.Delete("", handler)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 16:42:22 -05:00
Jonathan Miller 69ad436ebb fix(api): fix route registration errors in license key API
Deploy MokoGitea / deploy (push) Has been cancelled
Fix reqRepoAdmin() -> reqAdmin() and m.Delete(handler) -> m.Delete("", handler)
to match the Gitea API route registration patterns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 16:17:37 -05:00
jmiller 65c5e3d213 Merge pull request 'fix(api): fix compilation errors in license key handlers' (#254) from fix/api-build-errors into dev
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Deploy MokoGitea / deploy (push) Has been cancelled
2026-05-30 21:08:18 +00:00
Jonathan Miller d40c8e1b85 fix(api): fix compilation errors in license key API handlers
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Replace ctx.Bind/ctx.APIErrorValidation (which don't exist on
APIContext) with web.GetForm() pattern used by all other API handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 15:56:54 -05:00
jmiller 39c373975e Merge branch 'main' into dev
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
Universal: Build & Release / Promote to RC (pull_request) Has been cancelled
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been cancelled
Deploy MokoGitea / deploy (push) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
2026-05-30 20:16:29 +00:00
jmiller b14fcb11f9 Merge pull request 'fix(ci): pre-deploy cleanup for memory' (#252) from fix/deploy-cleanup into dev
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been cancelled
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Universal: Build & Release / Promote to RC (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
2026-05-30 20:16:18 +00:00
Jonathan Miller 60a686ce63 fix(ci): pre-deploy cleanup — clear Docker cache and swap before build
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Has been cancelled
Add automatic cleanup step before Docker build in deploy workflow:
- Prune Docker build cache (can accumulate 30+ GB)
- Remove unused Docker images
- Clear swap to prevent thrashing during Go compilation

Fixes deploy failures caused by server memory exhaustion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 14:30:44 -05:00
jmiller 17ac356100 Merge pull request 'feat(updates): license key API + all remaining phases (Phase 4-6)' (#251) from feat/update-server-phase4-6 into dev
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
feat(updates): license key API (Phase 4) (#251)
2026-05-30 19:09:05 +00:00
Jonathan Miller 68845abd59 feat(updates): license key management API endpoints (Phase 4)
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Add REST API for managing license packages and keys:

- GET/POST /api/v1/repos/{owner}/{repo}/license-packages
- GET/POST /api/v1/repos/{owner}/{repo}/license-keys
- DELETE /api/v1/repos/{owner}/{repo}/license-keys/{id}
- GET /api/v1/repos/{owner}/{repo}/license-keys/{id}/usage

API structs for create/edit/response, with raw key only returned on
creation. Requires repo admin permissions.

Ref #239

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 14:08:35 -05:00
gitea-actions[bot] ba0fdf3df1 chore(release): build 05.07.00 [skip ci] 2026-05-30 18:59:33 +00:00
jmiller ba0b17d9b5 Merge pull request 'chore: merge dev into main - startup fix' (#250) from dev into main
Deploy MokoGitea / deploy (push) Has been cancelled
chore: merge dev into main - startup fix (#250)
2026-05-30 18:57:42 +00:00
jmiller 29341b2b9b Merge branch 'main' into dev
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Universal: Build & Release / Promote to RC (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
2026-05-30 18:57:17 +00:00
jmiller eef72a5b00 Merge pull request 'fix(config): remove WithFileConfig from LandingPage option' (#249) from fix/startup-crash into dev
Universal: Auto Version Bump / Version Bump (push) Has been cancelled
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
fix(config): remove WithFileConfig from LandingPage (#249)
2026-05-30 18:56:43 +00:00
Jonathan Miller 530cfc91b1 fix(config): remove WithFileConfig from LandingPage option
Branch Policy Check / Verify merge target (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Has been cancelled
PR RC Release / Build RC Release (pull_request) Has been cancelled
Universal: PR Check / Validate PR (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Has been cancelled
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || 'development' }}) (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
The WithFileConfig for LandingPage tried to parse the app.ini
LANDING_PAGE string value (e.g. "home") as JSON into a struct,
causing a startup hang. The landing page setting should only be
managed through the dynamic config system (admin UI).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-30 13:55:33 -05:00
gitea-actions[bot] 39249dd0e7 chore(release): build 05.06.00 [skip ci] 2026-05-30 18:12:57 +00:00
jmiller aee484780b Merge pull request 'chore: merge dev into main' (#248) from dev into main
Deploy MokoGitea / deploy (push) Has been cancelled
chore: merge dev into main - license keys + dolibarr (#248)
2026-05-30 18:12:24 +00:00
8 changed files with 327 additions and 4 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
<name>MokoGitea</name>
<org>MokoConsulting</org>
<description>Moko fork of Gitea — adding project board REST API endpoints and custom enhancements</description>
<version>05.05.00</version>
<version>05.08.00</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
+11
View File
@@ -103,6 +103,17 @@ jobs:
$SSH_CMD "echo 'SSH connected'"
# Pre-deploy cleanup: free disk and memory for the build
$SSH_CMD "
echo 'Cleaning Docker build cache and unused images...'
docker builder prune -af 2>/dev/null || true
docker image prune -af 2>/dev/null || true
echo 'Clearing swap...'
sudo swapoff -a && sudo swapon -a 2>/dev/null || true
echo 'Cleanup complete'
free -m | head -3
"
# Pull latest source
$SSH_CMD "
set -e
+1 -1
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# VERSION: 05.05.00
# VERSION: 05.08.00
# BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch"
+1 -1
View File
@@ -81,7 +81,7 @@ func initDefaultConfig() {
Instance: &InstanceStruct{
WebBanner: config.NewOption[WebBannerType]("instance.web_banner"),
MaintenanceMode: config.NewOption[MaintenanceModeType]("instance.maintenance_mode"),
LandingPage: config.NewOption[LandingPageType]("instance.landing_page").WithFileConfig(config.CfgSecKey{Sec: "server", Key: "LANDING_PAGE"}),
LandingPage: config.NewOption[LandingPageType]("instance.landing_page"),
},
}
}
+107
View File
@@ -0,0 +1,107 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package structs
import "time"
// LicensePackage represents a license package (subscription tier).
type LicensePackage struct {
ID int64 `json:"id"`
OwnerID int64 `json:"owner_id"`
Name string `json:"name"`
Description string `json:"description"`
DurationDays int `json:"duration_days"`
MaxSites int `json:"max_sites"`
RepoScope string `json:"repo_scope"`
AllowedChannels string `json:"allowed_channels"`
IsActive bool `json:"is_active"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
// swagger:strfmt date-time
Updated time.Time `json:"updated_at"`
}
// CreateLicensePackageOption options for creating a license package.
type CreateLicensePackageOption struct {
Name string `json:"name" binding:"Required"`
Description string `json:"description"`
DurationDays int `json:"duration_days"`
MaxSites int `json:"max_sites"`
RepoScope string `json:"repo_scope"`
AllowedChannels string `json:"allowed_channels"`
}
// EditLicensePackageOption options for editing a license package.
type EditLicensePackageOption struct {
Name *string `json:"name"`
Description *string `json:"description"`
DurationDays *int `json:"duration_days"`
MaxSites *int `json:"max_sites"`
RepoScope *string `json:"repo_scope"`
AllowedChannels *string `json:"allowed_channels"`
IsActive *bool `json:"is_active"`
}
// LicenseKey represents a license key (response — never includes raw key except on creation).
type LicenseKey struct {
ID int64 `json:"id"`
PackageID int64 `json:"package_id"`
OwnerID int64 `json:"owner_id"`
KeyPrefix string `json:"key_prefix"`
LicenseeName string `json:"licensee_name"`
LicenseeEmail string `json:"licensee_email"`
DomainRestriction string `json:"domain_restriction"`
MaxSites int `json:"max_sites"`
IsInternal bool `json:"is_internal"`
IsActive bool `json:"is_active"`
// swagger:strfmt date-time
StartsAt *time.Time `json:"starts_at"`
// swagger:strfmt date-time
ExpiresAt *time.Time `json:"expires_at"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
}
// LicenseKeyCreated is the response when a key is first created (includes raw key).
type LicenseKeyCreated struct {
LicenseKey
// RawKey is the full license key string. Only returned on creation.
RawKey string `json:"raw_key"`
}
// CreateLicenseKeyOption options for creating a license key.
type CreateLicenseKeyOption struct {
PackageID int64 `json:"package_id" binding:"Required"`
LicenseeName string `json:"licensee_name"`
LicenseeEmail string `json:"licensee_email"`
DomainRestriction string `json:"domain_restriction"`
MaxSites int `json:"max_sites"`
// StartsAt is optional; defaults to now.
StartsAt *time.Time `json:"starts_at"`
// ExpiresAt is optional; auto-calculated from package duration if not set.
ExpiresAt *time.Time `json:"expires_at"`
}
// EditLicenseKeyOption options for editing a license key.
type EditLicenseKeyOption struct {
LicenseeName *string `json:"licensee_name"`
LicenseeEmail *string `json:"licensee_email"`
DomainRestriction *string `json:"domain_restriction"`
MaxSites *int `json:"max_sites"`
IsActive *bool `json:"is_active"`
ExpiresAt *time.Time `json:"expires_at"`
}
// LicenseKeyUsage represents a usage tracking entry.
type LicenseKeyUsage struct {
ID int64 `json:"id"`
KeyID int64 `json:"key_id"`
RepoID int64 `json:"repo_id"`
Domain string `json:"domain"`
IPAddress string `json:"ip_address"`
UserAgent string `json:"user_agent"`
VersionFrom string `json:"version_from"`
// swagger:strfmt date-time
Created time.Time `json:"created_at"`
}
+12
View File
@@ -1347,6 +1347,18 @@ func Routes() *web.Router {
Delete(reqToken(), reqRepoWriter(unit.TypeReleases), repo.DeleteReleaseByTag)
})
}, reqRepoReader(unit.TypeReleases))
m.Group("/license-packages", func() {
m.Combo("").Get(repo.ListLicensePackages).
Post(bind(api.CreateLicensePackageOption{}), repo.CreateLicensePackage)
}, reqToken(), reqAdmin())
m.Group("/license-keys", func() {
m.Combo("").Get(repo.ListLicenseKeys).
Post(bind(api.CreateLicenseKeyOption{}), repo.CreateLicenseKey)
m.Group("/{id}", func() {
m.Delete("", repo.DeleteLicenseKey)
m.Get("/usage", repo.GetLicenseKeyUsage)
})
}, reqToken(), reqAdmin())
m.Post("/mirror-sync", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.MirrorSync)
m.Post("/push_mirrors-sync", reqAdmin(), reqToken(), mustNotBeArchived, repo.PushMirrorSync)
m.Group("/push_mirrors", func() {
+193
View File
@@ -0,0 +1,193 @@
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
// SPDX-License-Identifier: GPL-3.0-or-later
package repo
import (
"net/http"
"time"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/structs"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/web"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
func toLicensePackageAPI(pkg *licenses.LicensePackage) *structs.LicensePackage {
return &structs.LicensePackage{
ID: pkg.ID,
OwnerID: pkg.OwnerID,
Name: pkg.Name,
Description: pkg.Description,
DurationDays: pkg.DurationDays,
MaxSites: pkg.MaxSites,
RepoScope: pkg.RepoScope,
AllowedChannels: pkg.AllowedChannels,
IsActive: pkg.IsActive,
Created: time.Unix(int64(pkg.CreatedUnix), 0),
Updated: time.Unix(int64(pkg.UpdatedUnix), 0),
}
}
func toLicenseKeyAPI(key *licenses.LicenseKey) *structs.LicenseKey {
lk := &structs.LicenseKey{
ID: key.ID,
PackageID: key.PackageID,
OwnerID: key.OwnerID,
KeyPrefix: key.KeyPrefix,
LicenseeName: key.LicenseeName,
LicenseeEmail: key.LicenseeEmail,
DomainRestriction: key.DomainRestriction,
MaxSites: key.MaxSites,
IsInternal: key.IsInternal,
IsActive: key.IsActive,
Created: time.Unix(int64(key.CreatedUnix), 0),
}
if key.StartsUnix > 0 {
t := time.Unix(int64(key.StartsUnix), 0)
lk.StartsAt = &t
}
if key.ExpiresUnix > 0 {
t := time.Unix(int64(key.ExpiresUnix), 0)
lk.ExpiresAt = &t
}
return lk
}
// ListLicensePackages lists license packages for the repo owner.
func ListLicensePackages(ctx *context.APIContext) {
pkgs, err := licenses.ListLicensePackages(ctx, ctx.Repo.Repository.OwnerID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make([]*structs.LicensePackage, len(pkgs))
for i, pkg := range pkgs {
result[i] = toLicensePackageAPI(pkg)
}
ctx.JSON(http.StatusOK, result)
}
// CreateLicensePackage creates a new license package.
func CreateLicensePackage(ctx *context.APIContext) {
form := web.GetForm(ctx).(*structs.CreateLicensePackageOption)
pkg := &licenses.LicensePackage{
OwnerID: ctx.Repo.Repository.OwnerID,
Name: form.Name,
Description: form.Description,
DurationDays: form.DurationDays,
MaxSites: form.MaxSites,
RepoScope: form.RepoScope,
AllowedChannels: form.AllowedChannels,
}
if pkg.RepoScope == "" {
pkg.RepoScope = "all"
}
if err := licenses.CreateLicensePackage(ctx, pkg); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.JSON(http.StatusCreated, toLicensePackageAPI(pkg))
}
// ListLicenseKeys lists license keys for the repo owner.
func ListLicenseKeys(ctx *context.APIContext) {
keys, err := licenses.ListLicenseKeys(ctx, ctx.Repo.Repository.OwnerID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make([]*structs.LicenseKey, len(keys))
for i, key := range keys {
result[i] = toLicenseKeyAPI(key)
}
ctx.JSON(http.StatusOK, result)
}
// CreateLicenseKey creates a new license key.
func CreateLicenseKey(ctx *context.APIContext) {
form := web.GetForm(ctx).(*structs.CreateLicenseKeyOption)
key := &licenses.LicenseKey{
PackageID: form.PackageID,
OwnerID: ctx.Repo.Repository.OwnerID,
LicenseeName: form.LicenseeName,
LicenseeEmail: form.LicenseeEmail,
DomainRestriction: form.DomainRestriction,
MaxSites: form.MaxSites,
}
if form.StartsAt != nil {
key.StartsUnix = timeutil.TimeStamp(form.StartsAt.Unix())
}
if form.ExpiresAt != nil {
key.ExpiresUnix = timeutil.TimeStamp(form.ExpiresAt.Unix())
} else {
// Auto-calculate from package duration.
pkg, err := licenses.GetLicensePackageByID(ctx, form.PackageID)
if err != nil {
ctx.APIErrorInternal(err)
return
}
if pkg.DurationDays > 0 {
start := time.Now()
if form.StartsAt != nil {
start = *form.StartsAt
}
expires := start.AddDate(0, 0, pkg.DurationDays)
key.ExpiresUnix = timeutil.TimeStamp(expires.Unix())
}
}
rawKey, err := licenses.CreateLicenseKey(ctx, key)
if err != nil {
ctx.APIErrorInternal(err)
return
}
resp := &structs.LicenseKeyCreated{
LicenseKey: *toLicenseKeyAPI(key),
RawKey: rawKey,
}
ctx.JSON(http.StatusCreated, resp)
}
// DeleteLicenseKey deletes a license key.
func DeleteLicenseKey(ctx *context.APIContext) {
if err := licenses.DeleteLicenseKey(ctx, ctx.PathParamInt64("id")); err != nil {
ctx.APIErrorInternal(err)
return
}
ctx.Status(http.StatusNoContent)
}
// GetLicenseKeyUsage returns usage logs for a license key.
func GetLicenseKeyUsage(ctx *context.APIContext) {
usages, err := licenses.GetRecentUsage(ctx, ctx.PathParamInt64("id"), 100)
if err != nil {
ctx.APIErrorInternal(err)
return
}
result := make([]*structs.LicenseKeyUsage, len(usages))
for i, u := range usages {
result[i] = &structs.LicenseKeyUsage{
ID: u.ID,
KeyID: u.KeyID,
RepoID: u.RepoID,
Domain: u.Domain,
IPAddress: u.IPAddress,
UserAgent: u.UserAgent,
VersionFrom: u.VersionFrom,
Created: time.Unix(int64(u.CreatedUnix), 0),
}
}
ctx.JSON(http.StatusOK, result)
}
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='UTF-8'?>
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
VERSION: 05.05.00
VERSION: 05.08.00
-->
<updates>