diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json
index ce83b50bc5..3b02f2bf72 100644
--- a/options/locale/locale_en-US.json
+++ b/options/locale/locale_en-US.json
@@ -2632,6 +2632,18 @@
"repo.licenses.licensee": "Licensee",
"repo.licenses.expires": "Expires",
"repo.licenses.never": "Never",
+ "repo.licenses.new_package": "New Package",
+ "repo.licenses.description": "Description",
+ "repo.licenses.max_sites": "Max Sites",
+ "repo.licenses.channels_help": "Comma-separated channel names (e.g. stable,release-candidate). Leave empty for all channels.",
+ "repo.licenses.create_package": "Create Package",
+ "repo.licenses.package_created": "License package created successfully.",
+ "repo.licenses.generate_key": "Generate Key",
+ "repo.licenses.key_created": "License Key Created",
+ "repo.licenses.key_created_copy": "Copy this key now. It will not be shown again.",
+ "repo.licenses.revoke": "Revoke",
+ "repo.licenses.key_revoked": "License key revoked.",
+ "repo.licenses.update_feeds": "Update Feed URLs",
"repo.release.draft": "Draft",
"repo.release.prerelease": "Pre-Release",
"repo.release.stable": "Stable",
diff --git a/routers/web/repo/licenses.go b/routers/web/repo/licenses.go
index c9d4592364..052865bbca 100644
--- a/routers/web/repo/licenses.go
+++ b/routers/web/repo/licenses.go
@@ -5,10 +5,12 @@ package repo
import (
"net/http"
+ "strconv"
"time"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licenses"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
+ "git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
)
@@ -26,6 +28,7 @@ func Licenses(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.licenses")
ctx.Data["PageIsLicenses"] = true
ctx.Data["IsLicensesPage"] = true
+ ctx.Data["IsRepoAdmin"] = ctx.Repo.IsAdmin()
ownerID := ctx.Repo.Repository.OwnerID
@@ -35,7 +38,6 @@ func Licenses(ctx *context.Context) {
return
}
- // Build display list with key counts.
var display []LicensePackageDisplay
for _, pkg := range pkgs {
count, _ := licenses.CountKeysByPackage(ctx, pkg.ID)
@@ -47,7 +49,6 @@ func Licenses(ctx *context.Context) {
}
ctx.Data["LicensePackages"] = display
- // List all keys for the owner.
keys, err := licenses.ListLicenseKeys(ctx, ownerID)
if err != nil {
ctx.ServerError("ListLicenseKeys", err)
@@ -57,3 +58,112 @@ func Licenses(ctx *context.Context) {
ctx.HTML(http.StatusOK, tplLicenses)
}
+
+// LicensesCreatePackage handles POST to create a new license package.
+func LicensesCreatePackage(ctx *context.Context) {
+ name := ctx.FormString("name")
+ if name == "" {
+ ctx.Flash.Error("Package name is required")
+ ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
+ return
+ }
+
+ durationDays, _ := strconv.Atoi(ctx.FormString("duration_days"))
+ maxSites, _ := strconv.Atoi(ctx.FormString("max_sites"))
+
+ pkg := &licenses.LicensePackage{
+ OwnerID: ctx.Repo.Repository.OwnerID,
+ Name: name,
+ Description: ctx.FormString("description"),
+ DurationDays: durationDays,
+ MaxSites: maxSites,
+ AllowedChannels: ctx.FormString("allowed_channels"),
+ RepoScope: "all",
+ IsActive: true,
+ }
+
+ if err := licenses.CreateLicensePackage(ctx, pkg); err != nil {
+ ctx.ServerError("CreateLicensePackage", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("repo.licenses.package_created"))
+ ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
+}
+
+// LicensesGenerateKey handles POST to generate a new key from a package.
+func LicensesGenerateKey(ctx *context.Context) {
+ packageID, _ := strconv.ParseInt(ctx.FormString("package_id"), 10, 64)
+ if packageID == 0 {
+ ctx.Flash.Error("Invalid package")
+ ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
+ return
+ }
+
+ pkg, err := licenses.GetLicensePackageByID(ctx, packageID)
+ if err != nil {
+ ctx.ServerError("GetLicensePackageByID", err)
+ return
+ }
+
+ key := &licenses.LicenseKey{
+ PackageID: packageID,
+ OwnerID: ctx.Repo.Repository.OwnerID,
+ IsActive: true,
+ }
+
+ // Auto-calculate expiry from package duration.
+ if pkg.DurationDays > 0 {
+ expires := time.Now().AddDate(0, 0, pkg.DurationDays)
+ key.ExpiresUnix = timeutil.TimeStamp(expires.Unix())
+ }
+
+ rawKey, err := licenses.CreateLicenseKey(ctx, key)
+ if err != nil {
+ ctx.ServerError("CreateLicenseKey", err)
+ return
+ }
+
+ ctx.Data["Title"] = ctx.Tr("repo.licenses")
+ ctx.Data["PageIsLicenses"] = true
+ ctx.Data["IsLicensesPage"] = true
+ ctx.Data["IsRepoAdmin"] = ctx.Repo.IsAdmin()
+ ctx.Data["NewKeyCreated"] = rawKey
+
+ // Re-render the page with the new key displayed.
+ ownerID := ctx.Repo.Repository.OwnerID
+ pkgs, _ := licenses.ListLicensePackages(ctx, ownerID)
+ var display []LicensePackageDisplay
+ for _, p := range pkgs {
+ count, _ := licenses.CountKeysByPackage(ctx, p.ID)
+ display = append(display, LicensePackageDisplay{
+ LicensePackage: p,
+ KeyCount: count,
+ Created: time.Unix(int64(p.CreatedUnix), 0),
+ })
+ }
+ ctx.Data["LicensePackages"] = display
+ keys, _ := licenses.ListLicenseKeys(ctx, ownerID)
+ ctx.Data["LicenseKeys"] = keys
+
+ ctx.HTML(http.StatusOK, tplLicenses)
+}
+
+// LicensesRevokeKey handles POST to revoke a license key.
+func LicensesRevokeKey(ctx *context.Context) {
+ keyID := ctx.PathParamInt64("id")
+ key, err := licenses.GetLicenseKeyByID(ctx, keyID)
+ if err != nil {
+ ctx.ServerError("GetLicenseKeyByID", err)
+ return
+ }
+
+ key.IsActive = false
+ if err := licenses.UpdateLicenseKey(ctx, key); err != nil {
+ ctx.ServerError("UpdateLicenseKey", err)
+ return
+ }
+
+ ctx.Flash.Success(ctx.Tr("repo.licenses.key_revoked"))
+ ctx.Redirect(ctx.Repo.RepoLink + "/licenses")
+}
diff --git a/routers/web/web.go b/routers/web/web.go
index 481c35c647..2719d9a09d 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -1502,8 +1502,11 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) {
// end "/{username}/{reponame}": update server
// "/{username}/{reponame}": licenses page
- m.Group("/{username}/{reponame}", func() {
- m.Get("/licenses", repo.Licenses)
+ m.Group("/{username}/{reponame}/licenses", func() {
+ m.Get("", repo.Licenses)
+ m.Post("/packages", repo.LicensesCreatePackage)
+ m.Post("/keys/generate", repo.LicensesGenerateKey)
+ m.Post("/keys/{id}/revoke", repo.LicensesRevokeKey)
}, optSignIn, context.RepoAssignment)
// end "/{username}/{reponame}": licenses
diff --git a/templates/repo/licenses.tmpl b/templates/repo/licenses.tmpl
index 99ebb61641..c5d12a9cd5 100644
--- a/templates/repo/licenses.tmpl
+++ b/templates/repo/licenses.tmpl
@@ -2,12 +2,61 @@
{{template "repo/header" .}}
-