Compare commits

..

2 Commits

Author SHA1 Message Date
jmiller 39e4eb6ec8 Merge pull request 'fix: use ctx.APIError in licensing endpoints (build fix)' (#661) from dev into main
Deploy MokoGitea / deploy (push) Failing after 3m45s
2026-06-21 02:49:52 +00:00
jmiller 78ad2c999b Merge pull request 'feat: licensing API phase 2 — validation, signed downloads, management, tier admin' (#660) from dev into main
Deploy MokoGitea / deploy (push) Failing after 4m18s
2026-06-21 02:37:26 +00:00
3 changed files with 29 additions and 34 deletions
+1 -2
View File
@@ -113,8 +113,7 @@ func ServeDownload(ctx *context.APIContext) {
} }
// Find ZIP attachment // Find ZIP attachment
var attachments []*repo_model.Attachment attachments, err := repo_model.GetAttachmentsByReleaseID(ctx, targetRelease.ID)
err = db.GetEngine(ctx).Where("release_id = ?", targetRelease.ID).Find(&attachments)
if err != nil { if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to get attachments") ctx.APIError(http.StatusInternalServerError, "failed to get attachments")
return return
+21 -24
View File
@@ -9,10 +9,7 @@ import (
"time" "time"
licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing" licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing"
"encoding/json" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
mojo_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/modules/timeutil" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/timeutil"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/services/context"
@@ -31,7 +28,7 @@ type createLicenseRequest struct {
// CreateLicense handles POST /api/v1/licensing/licenses // CreateLicense handles POST /api/v1/licensing/licenses
func CreateLicense(ctx *context.APIContext) { func CreateLicense(ctx *context.APIContext) {
var req createLicenseRequest var req createLicenseRequest
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil { if err := ctx.BindJSON(&req); err != nil {
ctx.APIError(http.StatusBadRequest, "invalid request body") ctx.APIError(http.StatusBadRequest, "invalid request body")
return return
} }
@@ -86,7 +83,7 @@ func ListLicenses(ctx *context.APIContext) {
// For now, get all licenses (pagination via offset) // For now, get all licenses (pagination via offset)
// TODO: add proper pagination to the model layer // TODO: add proper pagination to the model layer
var licenses []*licensing_model.License var licenses []*licensing_model.License
err := db.GetEngine(ctx).Limit(limit, (page-1)*limit).Find(&licenses) err := ctx.Orm().Limit(limit, (page-1)*limit).Find(&licenses)
if err != nil { if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to list licenses") ctx.APIError(http.StatusInternalServerError, "failed to list licenses")
return return
@@ -109,7 +106,7 @@ func GetLicense(ctx *context.APIContext) {
license, err := licensing_model.GetLicenseByID(ctx, id) license, err := licensing_model.GetLicenseByID(ctx, id)
if err != nil || license == nil { if err != nil || license == nil {
ctx.APIErrorNotFound() ctx.NotFound()
return return
} }
@@ -163,12 +160,12 @@ func UpdateLicense(ctx *context.APIContext) {
license, err := licensing_model.GetLicenseByID(ctx, id) license, err := licensing_model.GetLicenseByID(ctx, id)
if err != nil || license == nil { if err != nil || license == nil {
ctx.APIErrorNotFound() ctx.NotFound()
return return
} }
var req updateLicenseRequest var req updateLicenseRequest
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil { if err := ctx.BindJSON(&req); err != nil {
ctx.APIError(http.StatusBadRequest, "invalid request body") ctx.APIError(http.StatusBadRequest, "invalid request body")
return return
} }
@@ -208,7 +205,7 @@ func UpdateLicense(ctx *context.APIContext) {
} }
if len(cols) > 0 { if len(cols) > 0 {
cols = append(cols, "updated_at") cols = append(cols, "updated_at")
db.GetEngine(ctx).ID(id).Cols(cols...).Update(license) ctx.Orm().ID(id).Cols(cols...).Update(license)
} }
ctx.JSON(http.StatusOK, licenseToJSON(ctx, license)) ctx.JSON(http.StatusOK, licenseToJSON(ctx, license))
@@ -257,7 +254,7 @@ func MyLicenseDomains(ctx *context.APIContext) {
license, err := licensing_model.GetLicenseByID(ctx, id) license, err := licensing_model.GetLicenseByID(ctx, id)
if err != nil || license == nil || license.UserID != ctx.Doer.ID { if err != nil || license == nil || license.UserID != ctx.Doer.ID {
ctx.APIErrorNotFound() ctx.NotFound()
return return
} }
@@ -288,7 +285,7 @@ func MyDeactivateDomain(ctx *context.APIContext) {
license, err := licensing_model.GetLicenseByID(ctx, id) license, err := licensing_model.GetLicenseByID(ctx, id)
if err != nil || license == nil || license.UserID != ctx.Doer.ID { if err != nil || license == nil || license.UserID != ctx.Doer.ID {
ctx.APIErrorNotFound() ctx.NotFound()
return return
} }
@@ -329,12 +326,12 @@ type createTierRequest struct {
// CreateTier handles POST /api/v1/licensing/tiers // CreateTier handles POST /api/v1/licensing/tiers
func CreateTier(ctx *context.APIContext) { func CreateTier(ctx *context.APIContext) {
var req createTierRequest var req createTierRequest
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil { if err := ctx.BindJSON(&req); err != nil {
ctx.APIError(http.StatusBadRequest, "invalid request body") ctx.APIError(http.StatusBadRequest, "invalid request body")
return return
} }
reposJSON, _ := mojo_json.Marshal(req.Repos) reposJSON, _ := json.Marshal(req.Repos)
tier := &licensing_model.ProductTier{ tier := &licensing_model.ProductTier{
TierKey: req.TierKey, TierKey: req.TierKey,
TierName: req.TierName, TierName: req.TierName,
@@ -343,7 +340,7 @@ func CreateTier(ctx *context.APIContext) {
SortOrder: req.SortOrder, SortOrder: req.SortOrder,
} }
_, err := db.GetEngine(ctx).Insert(tier) _, err := ctx.Orm().Insert(tier)
if err != nil { if err != nil {
ctx.APIError(http.StatusInternalServerError, "failed to create tier") ctx.APIError(http.StatusInternalServerError, "failed to create tier")
return return
@@ -368,14 +365,14 @@ func UpdateTier(ctx *context.APIContext) {
} }
tier := new(licensing_model.ProductTier) tier := new(licensing_model.ProductTier)
has, err := db.GetEngine(ctx).ID(id).Get(tier) has, err := ctx.Orm().ID(id).Get(tier)
if err != nil || !has { if err != nil || !has {
ctx.APIErrorNotFound() ctx.NotFound()
return return
} }
var req updateTierRequest var req updateTierRequest
if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil { if err := ctx.BindJSON(&req); err != nil {
ctx.APIError(http.StatusBadRequest, "invalid request body") ctx.APIError(http.StatusBadRequest, "invalid request body")
return return
} }
@@ -386,7 +383,7 @@ func UpdateTier(ctx *context.APIContext) {
cols = append(cols, "tier_name") cols = append(cols, "tier_name")
} }
if req.Repos != nil { if req.Repos != nil {
reposJSON, _ := mojo_json.Marshal(req.Repos) reposJSON, _ := json.Marshal(req.Repos)
tier.Repos = string(reposJSON) tier.Repos = string(reposJSON)
cols = append(cols, "repos") cols = append(cols, "repos")
} }
@@ -400,7 +397,7 @@ func UpdateTier(ctx *context.APIContext) {
} }
if len(cols) > 0 { if len(cols) > 0 {
db.GetEngine(ctx).ID(id).Cols(cols...).Update(tier) ctx.Orm().ID(id).Cols(cols...).Update(tier)
} }
ctx.JSON(http.StatusOK, tierToJSON(tier)) ctx.JSON(http.StatusOK, tierToJSON(tier))
@@ -416,19 +413,19 @@ func DeleteTier(ctx *context.APIContext) {
// Check if any licenses use this tier // Check if any licenses use this tier
tier := new(licensing_model.ProductTier) tier := new(licensing_model.ProductTier)
has, _ := db.GetEngine(ctx).ID(id).Get(tier) has, _ := ctx.Orm().ID(id).Get(tier)
if !has { if !has {
ctx.APIErrorNotFound() ctx.NotFound()
return return
} }
count, _ := db.GetEngine(ctx).Where("tier = ?", tier.TierKey).Count(new(licensing_model.License)) count, _ := ctx.Orm().Where("tier = ?", tier.TierKey).Count(new(licensing_model.License))
if count > 0 { if count > 0 {
ctx.APIError(http.StatusConflict, "cannot delete tier with active licenses") ctx.APIError(http.StatusConflict, "cannot delete tier with active licenses")
return return
} }
db.GetEngine(ctx).ID(id).Delete(new(licensing_model.ProductTier)) ctx.Orm().ID(id).Delete(new(licensing_model.ProductTier))
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)
} }
+7 -8
View File
@@ -7,7 +7,6 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/db"
licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing" licensing_model "code.mokoconsulting.tech/MokoConsulting/MokoGitea/models/licensing"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/json"
"code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates" "code.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/templates"
@@ -35,7 +34,7 @@ func LicenseTiers(ctx *context.Context) {
views := make([]tierView, 0, len(tiers)) views := make([]tierView, 0, len(tiers))
for _, t := range tiers { for _, t := range tiers {
count, _ := db.GetEngine(ctx).Where("tier = ?", t.TierKey).Count(new(licensing_model.License)) count, _ := ctx.Orm().Where("tier = ?", t.TierKey).Count(new(licensing_model.License))
views = append(views, tierView{ views = append(views, tierView{
ProductTier: t, ProductTier: t,
Repos: t.RepoList(), Repos: t.RepoList(),
@@ -70,7 +69,7 @@ func LicenseTierCreate(ctx *context.Context) {
SortOrder: sortOrder, SortOrder: sortOrder,
} }
if _, err := db.GetEngine(ctx).Insert(tier); err != nil { if _, err := ctx.Orm().Insert(tier); err != nil {
ctx.Flash.Error("Failed to create tier: " + err.Error()) ctx.Flash.Error("Failed to create tier: " + err.Error())
} else { } else {
ctx.Flash.Success("Tier '" + tierName + "' created") ctx.Flash.Success("Tier '" + tierName + "' created")
@@ -83,7 +82,7 @@ func LicenseTierUpdate(ctx *context.Context) {
id, _ := strconv.ParseInt(ctx.PathParam("id"), 10, 64) id, _ := strconv.ParseInt(ctx.PathParam("id"), 10, 64)
tier := new(licensing_model.ProductTier) tier := new(licensing_model.ProductTier)
has, _ := db.GetEngine(ctx).ID(id).Get(tier) has, _ := ctx.Orm().ID(id).Get(tier)
if !has { if !has {
ctx.NotFound(nil) ctx.NotFound(nil)
return return
@@ -96,7 +95,7 @@ func LicenseTierUpdate(ctx *context.Context) {
tier.MaxDomains, _ = strconv.Atoi(ctx.FormString("max_domains")) tier.MaxDomains, _ = strconv.Atoi(ctx.FormString("max_domains"))
tier.SortOrder, _ = strconv.Atoi(ctx.FormString("sort_order")) tier.SortOrder, _ = strconv.Atoi(ctx.FormString("sort_order"))
if _, err := db.GetEngine(ctx).ID(id).Cols("tier_name", "repos", "max_domains", "sort_order").Update(tier); err != nil { if _, err := ctx.Orm().ID(id).Cols("tier_name", "repos", "max_domains", "sort_order").Update(tier); err != nil {
ctx.Flash.Error("Failed to update tier: " + err.Error()) ctx.Flash.Error("Failed to update tier: " + err.Error())
} else { } else {
ctx.Flash.Success("Tier '" + tier.TierName + "' updated") ctx.Flash.Success("Tier '" + tier.TierName + "' updated")
@@ -109,20 +108,20 @@ func LicenseTierDelete(ctx *context.Context) {
id, _ := strconv.ParseInt(ctx.FormString("id"), 10, 64) id, _ := strconv.ParseInt(ctx.FormString("id"), 10, 64)
tier := new(licensing_model.ProductTier) tier := new(licensing_model.ProductTier)
has, _ := db.GetEngine(ctx).ID(id).Get(tier) has, _ := ctx.Orm().ID(id).Get(tier)
if !has { if !has {
ctx.NotFound(nil) ctx.NotFound(nil)
return return
} }
count, _ := db.GetEngine(ctx).Where("tier = ?", tier.TierKey).Count(new(licensing_model.License)) count, _ := ctx.Orm().Where("tier = ?", tier.TierKey).Count(new(licensing_model.License))
if count > 0 { if count > 0 {
ctx.Flash.Error("Cannot delete tier with active licenses. Reassign licenses first.") ctx.Flash.Error("Cannot delete tier with active licenses. Reassign licenses first.")
ctx.Redirect("/admin/license-tiers") ctx.Redirect("/admin/license-tiers")
return return
} }
db.GetEngine(ctx).ID(id).Delete(new(licensing_model.ProductTier)) ctx.Orm().ID(id).Delete(new(licensing_model.ProductTier))
ctx.Flash.Success("Tier '" + tier.TierName + "' deleted") ctx.Flash.Success("Tier '" + tier.TierName + "' deleted")
ctx.Redirect("/admin/license-tiers") ctx.Redirect("/admin/license-tiers")
} }