From d352b138d7d72a6dc7787c9b1d19f4554ef62529 Mon Sep 17 00:00:00 2001 From: logikonline Date: Mon, 19 Jan 2026 17:19:32 -0500 Subject: [PATCH] feat(i18n): add organization license settings and AI template helpers Add organization-level license management with new settings page and locale strings across all languages. Add AI feature detection helpers (IsAIEnabled, IsAICodeReviewEnabled, IsAIIssueTriageEnabled) to template functions. Add license scanning functionality to repository settings. --- modules/templates/helper.go | 15 +++ options/locale/locale_cs-CZ.json | 11 +- options/locale/locale_de-DE.json | 11 +- options/locale/locale_el-GR.json | 11 +- options/locale/locale_en-US.json | 9 ++ options/locale/locale_es-ES.json | 11 +- options/locale/locale_fa-IR.json | 11 +- options/locale/locale_fi-FI.json | 11 +- options/locale/locale_fr-FR.json | 11 +- options/locale/locale_ga-IE.json | 11 +- options/locale/locale_hi-IN.json | 11 +- options/locale/locale_hu-HU.json | 11 +- options/locale/locale_id-ID.json | 11 +- options/locale/locale_is-IS.json | 11 +- options/locale/locale_it-IT.json | 11 +- options/locale/locale_ja-JP.json | 11 +- options/locale/locale_ko-KR.json | 11 +- options/locale/locale_lv-LV.json | 11 +- options/locale/locale_nl-NL.json | 11 +- options/locale/locale_pl-PL.json | 11 +- options/locale/locale_pt-BR.json | 11 +- options/locale/locale_pt-PT.json | 11 +- options/locale/locale_ru-RU.json | 11 +- options/locale/locale_si-LK.json | 11 +- options/locale/locale_sk-SK.json | 11 +- options/locale/locale_sv-SE.json | 11 +- options/locale/locale_tr-TR.json | 11 +- options/locale/locale_uk-UA.json | 11 +- options/locale/locale_zh-CN.json | 11 +- options/locale/locale_zh-TW.json | 11 +- routers/api/v1/repo/ai.go | 19 ++-- routers/web/org/home.go | 88 +++++++++++++++ routers/web/org/setting_license.go | 160 +++++++++++++++++++++++++++ routers/web/repo/ai.go | 29 +---- routers/web/repo/setting/license.go | 89 ++++++++++++++- routers/web/web.go | 7 ++ services/ai/ai.go | 15 ++- templates/org/home.tmpl | 15 +++ templates/org/settings/license.tmpl | 72 ++++++++++++ templates/org/settings/navbar.tmpl | 3 + templates/repo/settings/license.tmpl | 51 +++++++-- 41 files changed, 804 insertions(+), 76 deletions(-) create mode 100644 routers/web/org/setting_license.go create mode 100644 templates/org/settings/license.tmpl diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 6077938447..a884de113a 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "code.gitcaddy.com/server/v3/modules/ai" "code.gitcaddy.com/server/v3/modules/base" "code.gitcaddy.com/server/v3/modules/htmlutil" "code.gitcaddy.com/server/v3/modules/markup" @@ -165,9 +166,23 @@ func NewFuncMap() template.FuncMap { "FilenameIsImage": filenameIsImage, "TabSizeClass": tabSizeClass, + + // ----------------------------------------------------------------- + // AI features + "IsAIEnabled": ai.IsEnabled, + "IsAICodeReviewEnabled": isAICodeReviewEnabled, + "IsAIIssueTriageEnabled": isAIIssueTriageEnabled, } } +func isAICodeReviewEnabled() bool { + return ai.IsEnabled() && setting.AI.EnableCodeReview +} + +func isAIIssueTriageEnabled() bool { + return ai.IsEnabled() && setting.AI.EnableIssueTriage +} + // SanitizeHTML sanitizes the input by default sanitization rules. func SanitizeHTML(s string) template.HTML { return markup.Sanitize(s) diff --git a/options/locale/locale_cs-CZ.json b/options/locale/locale_cs-CZ.json index 2130de93d1..702eba103b 100644 --- a/options/locale/locale_cs-CZ.json +++ b/options/locale/locale_cs-CZ.json @@ -4052,5 +4052,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_de-DE.json b/options/locale/locale_de-DE.json index b29b4cb107..f355005227 100644 --- a/options/locale/locale_de-DE.json +++ b/options/locale/locale_de-DE.json @@ -4251,5 +4251,14 @@ "vault.max_secrets": "Max. Geheimnisse", "vault.max_versions": "Max. Versionen", "vault.audit_retention": "Audit-Aufbewahrung", - "vault.unlimited": "Unbegrenzt" + "vault.unlimited": "Unbegrenzt", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_el-GR.json b/options/locale/locale_el-GR.json index b3d7e4f718..a81c4e4c48 100644 --- a/options/locale/locale_el-GR.json +++ b/options/locale/locale_el-GR.json @@ -3745,5 +3745,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index e521f26af2..c638d1d5ca 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -2823,6 +2823,12 @@ "org.settings.confirm_delete_account": "Confirm Deletion", "org.settings.delete_failed": "Deleting organization failed due to an internal error", "org.settings.delete_successful": "Organization %s has been deleted successfully.", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", "org.settings.homepage_pinning": "Homepage Visibility", "org.settings.pin_to_homepage": "Pin this organization to the homepage", "org.settings.pin_to_homepage_help": "When enabled, this organization will be featured on the public homepage. Only administrators can change this setting.", @@ -3917,6 +3923,9 @@ "repo.settings.license_file_error": "License saved but failed to create LICENSE.md file. You may need to create it manually.", "repo.settings.current_license": "Current License", "repo.settings.view_license_file": "View LICENSE.md file", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository.", "api": "API", "admin.config.api_header_url": "API Header Link", "admin.config.api_header_url_placeholder": "https://example.com/api/docs", diff --git a/options/locale/locale_es-ES.json b/options/locale/locale_es-ES.json index c88c1e1516..1863b2a717 100644 --- a/options/locale/locale_es-ES.json +++ b/options/locale/locale_es-ES.json @@ -3843,5 +3843,14 @@ "vault.max_secrets": "Máx. secretos", "vault.max_versions": "Máx. versiones", "vault.audit_retention": "Retención de auditoría", - "vault.unlimited": "Ilimitado" + "vault.unlimited": "Ilimitado", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_fa-IR.json b/options/locale/locale_fa-IR.json index d0e900d5d7..ef25ca12b8 100644 --- a/options/locale/locale_fa-IR.json +++ b/options/locale/locale_fa-IR.json @@ -2976,5 +2976,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_fi-FI.json b/options/locale/locale_fi-FI.json index 239a467c9f..53c63a4e65 100644 --- a/options/locale/locale_fi-FI.json +++ b/options/locale/locale_fi-FI.json @@ -2243,5 +2243,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_fr-FR.json b/options/locale/locale_fr-FR.json index 2362a63727..fb6843d965 100644 --- a/options/locale/locale_fr-FR.json +++ b/options/locale/locale_fr-FR.json @@ -4189,5 +4189,14 @@ "vault.max_secrets": "Max. secrets", "vault.max_versions": "Max. versions", "vault.audit_retention": "Rétention d'audit", - "vault.unlimited": "Illimité" + "vault.unlimited": "Illimité", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_ga-IE.json b/options/locale/locale_ga-IE.json index e40581c24e..2813c52163 100644 --- a/options/locale/locale_ga-IE.json +++ b/options/locale/locale_ga-IE.json @@ -4125,5 +4125,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_hi-IN.json b/options/locale/locale_hi-IN.json index be0f1334fa..76e676e0d1 100644 --- a/options/locale/locale_hi-IN.json +++ b/options/locale/locale_hi-IN.json @@ -4250,5 +4250,14 @@ "vault.max_secrets": "Max Secrets", "vault.max_versions": "Max Versions", "vault.audit_retention": "Audit Retention", - "vault.unlimited": "Unlimited" + "vault.unlimited": "Unlimited", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_hu-HU.json b/options/locale/locale_hu-HU.json index 3c623a8181..a36bfc1233 100644 --- a/options/locale/locale_hu-HU.json +++ b/options/locale/locale_hu-HU.json @@ -2160,5 +2160,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_id-ID.json b/options/locale/locale_id-ID.json index cba95b5d2b..7cfd2d59f0 100644 --- a/options/locale/locale_id-ID.json +++ b/options/locale/locale_id-ID.json @@ -1984,5 +1984,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_is-IS.json b/options/locale/locale_is-IS.json index 34e84e1299..005952e17d 100644 --- a/options/locale/locale_is-IS.json +++ b/options/locale/locale_is-IS.json @@ -1872,5 +1872,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_it-IT.json b/options/locale/locale_it-IT.json index b8f3af42e0..3a7656b5c9 100644 --- a/options/locale/locale_it-IT.json +++ b/options/locale/locale_it-IT.json @@ -3289,5 +3289,14 @@ "vault.max_secrets": "Max. segreti", "vault.max_versions": "Max. versioni", "vault.audit_retention": "Conservazione audit", - "vault.unlimited": "Illimitato" + "vault.unlimited": "Illimitato", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_ja-JP.json b/options/locale/locale_ja-JP.json index 20eb3ad778..08ba7ccf92 100644 --- a/options/locale/locale_ja-JP.json +++ b/options/locale/locale_ja-JP.json @@ -4232,5 +4232,14 @@ "vault.max_secrets": "最大シークレット数", "vault.max_versions": "最大バージョン数", "vault.audit_retention": "監査保持期間", - "vault.unlimited": "無制限" + "vault.unlimited": "無制限", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_ko-KR.json b/options/locale/locale_ko-KR.json index 8f531adcf5..df166aac78 100644 --- a/options/locale/locale_ko-KR.json +++ b/options/locale/locale_ko-KR.json @@ -2247,5 +2247,14 @@ "vault.max_secrets": "최대 시크릿", "vault.max_versions": "최대 버전", "vault.audit_retention": "감사 보존", - "vault.unlimited": "무제한" + "vault.unlimited": "무제한", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_lv-LV.json b/options/locale/locale_lv-LV.json index 4b1a26a8a5..b8101facbe 100644 --- a/options/locale/locale_lv-LV.json +++ b/options/locale/locale_lv-LV.json @@ -3766,5 +3766,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_nl-NL.json b/options/locale/locale_nl-NL.json index 55fa93e1d6..72092353ce 100644 --- a/options/locale/locale_nl-NL.json +++ b/options/locale/locale_nl-NL.json @@ -2914,5 +2914,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_pl-PL.json b/options/locale/locale_pl-PL.json index 8b4efab62a..3b3f92289a 100644 --- a/options/locale/locale_pl-PL.json +++ b/options/locale/locale_pl-PL.json @@ -2885,5 +2885,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_pt-BR.json b/options/locale/locale_pt-BR.json index c2cd3a7911..c0b79cd1eb 100644 --- a/options/locale/locale_pt-BR.json +++ b/options/locale/locale_pt-BR.json @@ -4030,5 +4030,14 @@ "vault.max_secrets": "Máx. segredos", "vault.max_versions": "Máx. versões", "vault.audit_retention": "Retenção de auditoria", - "vault.unlimited": "Ilimitado" + "vault.unlimited": "Ilimitado", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_pt-PT.json b/options/locale/locale_pt-PT.json index 26069aaaa8..535f51b16b 100644 --- a/options/locale/locale_pt-PT.json +++ b/options/locale/locale_pt-PT.json @@ -4126,5 +4126,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_ru-RU.json b/options/locale/locale_ru-RU.json index 0ca645654f..f06dd79945 100644 --- a/options/locale/locale_ru-RU.json +++ b/options/locale/locale_ru-RU.json @@ -3839,5 +3839,14 @@ "vault.max_secrets": "Макс. секретов", "vault.max_versions": "Макс. версий", "vault.audit_retention": "Хранение аудита", - "vault.unlimited": "Неограниченно" + "vault.unlimited": "Неограниченно", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_si-LK.json b/options/locale/locale_si-LK.json index 436636c8f6..98e432bcea 100644 --- a/options/locale/locale_si-LK.json +++ b/options/locale/locale_si-LK.json @@ -2931,5 +2931,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_sk-SK.json b/options/locale/locale_sk-SK.json index 9b4b8c3afa..e8783c6853 100644 --- a/options/locale/locale_sk-SK.json +++ b/options/locale/locale_sk-SK.json @@ -1956,5 +1956,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_sv-SE.json b/options/locale/locale_sv-SE.json index 1944d4b6d4..b4dfc227e0 100644 --- a/options/locale/locale_sv-SE.json +++ b/options/locale/locale_sv-SE.json @@ -2515,5 +2515,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_tr-TR.json b/options/locale/locale_tr-TR.json index 22f73810cd..8e03b251ce 100644 --- a/options/locale/locale_tr-TR.json +++ b/options/locale/locale_tr-TR.json @@ -4124,5 +4124,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_uk-UA.json b/options/locale/locale_uk-UA.json index ea12def622..a41b6f7b73 100644 --- a/options/locale/locale_uk-UA.json +++ b/options/locale/locale_uk-UA.json @@ -3896,5 +3896,14 @@ "admin.plugins.license_grace": "Grace Period", "admin.plugins.license_invalid": "Invalid", "admin.plugins.license_not_required": "Free", - "admin.plugins.none": "No plugins loaded" + "admin.plugins.none": "No plugins loaded", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_zh-CN.json b/options/locale/locale_zh-CN.json index 8d934d46da..4596dc5f02 100644 --- a/options/locale/locale_zh-CN.json +++ b/options/locale/locale_zh-CN.json @@ -4251,5 +4251,14 @@ "vault.max_secrets": "最大密钥数", "vault.max_versions": "最大版本数", "vault.audit_retention": "审计保留", - "vault.unlimited": "无限制" + "vault.unlimited": "无限制", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/options/locale/locale_zh-TW.json b/options/locale/locale_zh-TW.json index afda8d0131..7545d8f5cf 100644 --- a/options/locale/locale_zh-TW.json +++ b/options/locale/locale_zh-TW.json @@ -4159,5 +4159,14 @@ "vault.max_secrets": "最大密鑰數", "vault.max_versions": "最大版本數", "vault.audit_retention": "稽核保留", - "vault.unlimited": "無限制" + "vault.unlimited": "無限制", + "org.settings.license": "License", + "org.settings.license_type": "Organization License", + "org.settings.license_help": "Set a license for your organization. This will be stored in your .profile repository.", + "org.settings.license_saved": "License updated successfully.", + "org.settings.license_cleared": "License has been cleared.", + "org.settings.license_error": "Failed to update license.", + "repo.settings.license_scan": "Scan", + "repo.settings.license_detected": "License detected", + "repo.settings.license_not_found": "No license file detected in repository." } \ No newline at end of file diff --git a/routers/api/v1/repo/ai.go b/routers/api/v1/repo/ai.go index 93aef65346..24319f45c0 100644 --- a/routers/api/v1/repo/ai.go +++ b/routers/api/v1/repo/ai.go @@ -8,9 +8,10 @@ import ( issues_model "code.gitcaddy.com/server/v3/models/issues" "code.gitcaddy.com/server/v3/modules/ai" + "code.gitcaddy.com/server/v3/modules/json" "code.gitcaddy.com/server/v3/modules/setting" - "code.gitcaddy.com/server/v3/services/context" ai_service "code.gitcaddy.com/server/v3/services/ai" + "code.gitcaddy.com/server/v3/services/context" ) // AIReviewPullRequest performs an AI-powered code review on a pull request @@ -269,7 +270,7 @@ func AIExplainCode(ctx *context.APIContext) { } var req ExplainCodeRequest - if err := ctx.ShouldBindJSON(&req); err != nil { + if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil { ctx.JSON(http.StatusBadRequest, map[string]string{ "error": "Invalid request body", }) @@ -291,9 +292,9 @@ func AIExplainCode(ctx *context.APIContext) { type GenerateDocRequest struct { Code string `json:"code" binding:"Required"` FilePath string `json:"file_path"` - DocType string `json:"doc_type"` // function, class, module, api + DocType string `json:"doc_type"` // function, class, module, api Language string `json:"language"` - Style string `json:"style"` // jsdoc, docstring, xml, markdown + Style string `json:"style"` // jsdoc, docstring, xml, markdown } // AIGenerateDocumentation generates documentation using AI @@ -343,7 +344,7 @@ func AIGenerateDocumentation(ctx *context.APIContext) { } var req GenerateDocRequest - if err := ctx.ShouldBindJSON(&req); err != nil { + if err := json.NewDecoder(ctx.Req.Body).Decode(&req); err != nil { ctx.JSON(http.StatusBadRequest, map[string]string{ "error": "Invalid request body", }) @@ -384,12 +385,12 @@ func AIStatus(ctx *context.APIContext) { // "$ref": "#/responses/AIStatusResponse" status := map[string]any{ - "enabled": ai_service.IsEnabled(), - "code_review_enabled": setting.AI.EnableCodeReview, + "enabled": ai_service.IsEnabled(), + "code_review_enabled": setting.AI.EnableCodeReview, "issue_triage_enabled": setting.AI.EnableIssueTriage, - "doc_gen_enabled": setting.AI.EnableDocGen, + "doc_gen_enabled": setting.AI.EnableDocGen, "explain_code_enabled": setting.AI.EnableExplainCode, - "chat_enabled": setting.AI.EnableChat, + "chat_enabled": setting.AI.EnableChat, } if ai_service.IsEnabled() { diff --git a/routers/web/org/home.go b/routers/web/org/home.go index c083e4860b..9e536a3ec5 100644 --- a/routers/web/org/home.go +++ b/routers/web/org/home.go @@ -14,6 +14,7 @@ import ( "code.gitcaddy.com/server/v3/models/renderhelper" repo_model "code.gitcaddy.com/server/v3/models/repo" "code.gitcaddy.com/server/v3/modules/git" + "code.gitcaddy.com/server/v3/modules/gitrepo" "code.gitcaddy.com/server/v3/modules/log" "code.gitcaddy.com/server/v3/modules/markup/markdown" "code.gitcaddy.com/server/v3/modules/setting" @@ -224,6 +225,14 @@ func home(ctx *context.Context, viewRepositories bool) { } ctx.Data["OrgStats"] = orgStats + // Load organization license from .profile repo + profileRepo, orgLicense := getOrgProfileLicense(ctx, org) + if orgLicense != "" { + ctx.Data["OrgLicense"] = orgLicense + ctx.Data["ProfileRepoLink"] = profileRepo.Link() + ctx.Data["ProfileRepo"] = profileRepo + } + // Always show overview by default for organizations isViewOverview := !viewRepositories // Load profile readme if available @@ -385,3 +394,82 @@ func CreateProfileRepo(ctx *context.Context) { // Redirect to edit the README ctx.Redirect(repo.Link() + "/_edit/main/README.md") } + +// getOrgProfileLicense gets the license from the org's .profile repo +func getOrgProfileLicense(ctx *context.Context, org *organization.Organization) (*repo_model.Repository, string) { + profileRepo, err := repo_model.GetRepositoryByName(ctx, org.ID, ".profile") + if err != nil { + return nil, "" + } + + if profileRepo.IsEmpty { + return profileRepo, "" + } + + gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, profileRepo) + if err != nil { + log.Error("getOrgProfileLicense: failed to open git repo: %v", err) + return profileRepo, "" + } + + commit, err := gitRepo.GetBranchCommit(profileRepo.DefaultBranch) + if err != nil { + log.Error("getOrgProfileLicense: failed to get branch commit: %v", err) + return profileRepo, "" + } + + // Check for LICENSE files + licenseFiles := []string{"LICENSE.md", "LICENSE", "LICENSE.txt", "COPYING"} + for _, filename := range licenseFiles { + entry, err := commit.GetTreeEntryByPath(filename) + if err == nil && !entry.IsDir() { + content, err := entry.Blob().GetBlobContent(10000) + if err == nil { + license := detectOrgLicenseType(content) + if license != "" { + return profileRepo, license + } + } + } + } + + return profileRepo, "" +} + +// detectOrgLicenseType tries to detect the license type from content +func detectOrgLicenseType(content string) string { + content = strings.ToLower(content) + + // Check for common license signatures + licensePatterns := map[string][]string{ + "MIT": {"mit license", "permission is hereby granted, free of charge"}, + "Apache-2.0": {"apache license", "version 2.0"}, + "GPL-3.0": {"gnu general public license", "version 3"}, + "GPL-2.0": {"gnu general public license", "version 2"}, + "BSD-3-Clause": {"redistribution and use in source and binary forms", "neither the name"}, + "BSD-2-Clause": {"redistribution and use in source and binary forms"}, + "LGPL-3.0": {"gnu lesser general public license", "version 3"}, + "LGPL-2.1": {"gnu lesser general public license", "version 2.1"}, + "MPL-2.0": {"mozilla public license", "version 2.0"}, + "AGPL-3.0": {"gnu affero general public license", "version 3"}, + "BSL-1.1": {"business source license", "change date"}, + "BSL-1.0": {"boost software license"}, + "Unlicense": {"this is free and unencumbered software", "unlicense"}, + "CC0-1.0": {"cc0 1.0 universal", "public domain"}, + "SSPL-1.0": {"server side public license"}, + } + + for license, patterns := range licensePatterns { + matchCount := 0 + for _, pattern := range patterns { + if strings.Contains(content, pattern) { + matchCount++ + } + } + if matchCount == len(patterns) { + return license + } + } + + return "" +} diff --git a/routers/web/org/setting_license.go b/routers/web/org/setting_license.go new file mode 100644 index 0000000000..ca6e449651 --- /dev/null +++ b/routers/web/org/setting_license.go @@ -0,0 +1,160 @@ +// Copyright 2024 The Gitea Authors and MarketAlly. All rights reserved. +// SPDX-License-Identifier: MIT + +package org + +import ( + "bytes" + "fmt" + "net/http" + "time" + + "code.gitcaddy.com/server/v3/models/organization" + repo_model "code.gitcaddy.com/server/v3/models/repo" + "code.gitcaddy.com/server/v3/modules/log" + repo_module "code.gitcaddy.com/server/v3/modules/repository" + "code.gitcaddy.com/server/v3/modules/templates" + repo_setting "code.gitcaddy.com/server/v3/routers/web/repo/setting" + shared_user "code.gitcaddy.com/server/v3/routers/web/shared/user" + "code.gitcaddy.com/server/v3/services/context" + repo_service "code.gitcaddy.com/server/v3/services/repository" + files_service "code.gitcaddy.com/server/v3/services/repository/files" +) + +const tplSettingsLicense templates.TplName = "org/settings/license" + +// SettingsLicense shows the organization license settings page +func SettingsLicense(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("org.settings.license") + ctx.Data["PageIsOrgSettings"] = true + ctx.Data["PageIsOrgSettingsLicense"] = true + ctx.Data["LicenseTypes"] = repo_setting.LicenseTypes + + if _, err := shared_user.RenderUserOrgHeader(ctx); err != nil { + ctx.ServerError("RenderUserOrgHeader", err) + return + } + + // Load current license from .profile repo + profileRepo, license := getOrgProfileLicense(ctx, ctx.Org.Organization) + if license != "" { + ctx.Data["CurrentLicense"] = license + ctx.Data["ProfileRepo"] = profileRepo + } + + ctx.HTML(http.StatusOK, tplSettingsLicense) +} + +// SettingsLicensePost handles license settings form submission +func SettingsLicensePost(ctx *context.Context) { + licenseType := ctx.FormString("license_type") + org := ctx.Org.Organization + + ctx.Data["Title"] = ctx.Tr("org.settings.license") + ctx.Data["PageIsOrgSettings"] = true + ctx.Data["PageIsOrgSettingsLicense"] = true + ctx.Data["LicenseTypes"] = repo_setting.LicenseTypes + + // Get or create .profile repo + profileRepo, err := getOrCreateProfileRepo(ctx, org) + if err != nil { + ctx.ServerError("getOrCreateProfileRepo", err) + return + } + + if licenseType == "" { + // Clear license - delete LICENSE.md if it exists + ctx.Flash.Success(ctx.Tr("org.settings.license_cleared")) + ctx.Redirect(ctx.Org.OrgLink + "/settings/license") + return + } + + // Create LICENSE.md in .profile repo + if err := createOrgLicenseFile(ctx, profileRepo, licenseType); err != nil { + log.Error("Failed to create LICENSE.md: %v", err) + ctx.Flash.Error(ctx.Tr("org.settings.license_error")) + } else { + ctx.Flash.Success(ctx.Tr("org.settings.license_saved")) + } + + ctx.Redirect(ctx.Org.OrgLink + "/settings/license") +} + +// SettingsLicenseScan detects existing license in .profile repo +func SettingsLicenseScan(ctx *context.Context) { + profileRepo, license := getOrgProfileLicense(ctx, ctx.Org.Organization) + if license != "" { + ctx.JSON(http.StatusOK, map[string]any{ + "found": true, + "license": license, + "file": "LICENSE.md", + "repo": profileRepo.Name, + }) + return + } + + ctx.JSON(http.StatusOK, map[string]any{"found": false}) +} + +// getOrCreateProfileRepo gets or creates the .profile repo for an org +func getOrCreateProfileRepo(ctx *context.Context, org *organization.Organization) (*repo_model.Repository, error) { + profileRepo, err := repo_model.GetRepositoryByName(ctx, org.ID, ".profile") + if err == nil { + return profileRepo, nil + } + + if !repo_model.IsErrRepoNotExist(err) { + return nil, err + } + + // Create .profile repo + repo, err := repo_service.CreateRepository(ctx, ctx.Doer, org.AsUser(), repo_service.CreateRepoOptions{ + Name: ".profile", + Description: "Organization profile", + AutoInit: true, + Readme: "Default", + DefaultBranch: "main", + IsPrivate: false, + }) + if err != nil { + return nil, err + } + + return repo, nil +} + +// createOrgLicenseFile creates a LICENSE.md file in the profile repo +func createOrgLicenseFile(ctx *context.Context, repo *repo_model.Repository, licenseType string) error { + // Get license content from templates + licenseContent, err := repo_module.GetLicense(licenseType, &repo_module.LicenseValues{ + Owner: repo.OwnerName, + Email: ctx.Doer.Email, + Repo: repo.Name, + Year: time.Now().Format("2006"), + }) + if err != nil { + return fmt.Errorf("GetLicense: %w", err) + } + + // Create/update LICENSE.md using files service + opts := &files_service.ChangeRepoFilesOptions{ + Message: fmt.Sprintf("Add LICENSE.md (%s)", licenseType), + OldBranch: repo.DefaultBranch, + NewBranch: repo.DefaultBranch, + Files: []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: "LICENSE.md", + ContentReader: bytes.NewReader(licenseContent), + }, + }, + } + + _, err = files_service.ChangeRepoFiles(ctx, repo, ctx.Doer, opts) + if err != nil { + // If file already exists, try to update it instead + opts.Files[0].Operation = "update" + _, err = files_service.ChangeRepoFiles(ctx, repo, ctx.Doer, opts) + } + return err +} diff --git a/routers/web/repo/ai.go b/routers/web/repo/ai.go index 941b01a98c..318f04554c 100644 --- a/routers/web/repo/ai.go +++ b/routers/web/repo/ai.go @@ -8,9 +8,8 @@ import ( issues_model "code.gitcaddy.com/server/v3/models/issues" "code.gitcaddy.com/server/v3/modules/setting" - "code.gitcaddy.com/server/v3/modules/templates" - "code.gitcaddy.com/server/v3/services/context" ai_service "code.gitcaddy.com/server/v3/services/ai" + "code.gitcaddy.com/server/v3/services/context" ) // AIReviewPullRequest handles the AI review request for a pull request @@ -34,7 +33,7 @@ func AIReviewPullRequest(ctx *context.Context) { } if !issue.IsPull { - ctx.NotFound("Not a pull request", nil) + ctx.NotFound(nil) return } @@ -96,7 +95,7 @@ func AITriageIssue(ctx *context.Context) { func AISuggestLabels(ctx *context.Context) { if !ai_service.IsEnabled() { ctx.JSON(http.StatusServiceUnavailable, map[string]string{ - "error": ctx.Tr("repo.ai.service_unavailable"), + "error": "AI service is not available", }) return } @@ -119,25 +118,3 @@ func AISuggestLabels(ctx *context.Context) { ctx.JSON(http.StatusOK, suggestions) } - -// IsAIEnabled is a template helper to check if AI is enabled -func IsAIEnabled() bool { - return ai_service.IsEnabled() -} - -// IsAICodeReviewEnabled checks if AI code review is enabled -func IsAICodeReviewEnabled() bool { - return ai_service.IsEnabled() && setting.AI.EnableCodeReview -} - -// IsAIIssueTriageEnabled checks if AI issue triage is enabled -func IsAIIssueTriageEnabled() bool { - return ai_service.IsEnabled() && setting.AI.EnableIssueTriage -} - -func init() { - // Register template functions - templates.RegisterTemplateFunc("IsAIEnabled", IsAIEnabled) - templates.RegisterTemplateFunc("IsAICodeReviewEnabled", IsAICodeReviewEnabled) - templates.RegisterTemplateFunc("IsAIIssueTriageEnabled", IsAIIssueTriageEnabled) -} diff --git a/routers/web/repo/setting/license.go b/routers/web/repo/setting/license.go index 5512029dc2..61a7fa97bc 100644 --- a/routers/web/repo/setting/license.go +++ b/routers/web/repo/setting/license.go @@ -7,9 +7,11 @@ import ( "bytes" "fmt" "net/http" + "strings" "time" repo_model "code.gitcaddy.com/server/v3/models/repo" + "code.gitcaddy.com/server/v3/modules/gitrepo" "code.gitcaddy.com/server/v3/modules/log" repo_module "code.gitcaddy.com/server/v3/modules/repository" "code.gitcaddy.com/server/v3/modules/templates" @@ -67,7 +69,8 @@ var LicenseTypes = []LicenseCategory{ { Category: "Source-Available", Licenses: []LicenseInfo{ - {"BSL-1.0", "Business Source License", "Free to use, becomes open source later"}, + {"BSL-1.1", "Business Source License 1.1", "Commercial use restricted until change date"}, + {"BSL-1.0", "Boost Software License", "Permissive, similar to MIT"}, {"SSPL-1.0", "Server Side Public License", "Requires publishing entire service stack"}, }, }, @@ -153,3 +156,87 @@ func createLicenseFile(ctx *context.Context, repo *repo_model.Repository, licens } return err } + +// LicenseScan detects existing license files in the repository +func LicenseScan(ctx *context.Context) { + repo := ctx.Repo.Repository + + if repo.IsEmpty { + ctx.JSON(http.StatusOK, map[string]any{"found": false}) + return + } + + gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, repo) + if err != nil { + log.Error("LicenseScan: failed to open git repo: %v", err) + ctx.JSON(http.StatusOK, map[string]any{"found": false}) + return + } + + commit, err := gitRepo.GetBranchCommit(repo.DefaultBranch) + if err != nil { + log.Error("LicenseScan: failed to get branch commit: %v", err) + ctx.JSON(http.StatusOK, map[string]any{"found": false}) + return + } + + // Check for LICENSE files + licenseFiles := []string{"LICENSE.md", "LICENSE", "LICENSE.txt", "COPYING"} + for _, filename := range licenseFiles { + entry, err := commit.GetTreeEntryByPath(filename) + if err == nil && !entry.IsDir() { + content, err := entry.Blob().GetBlobContent(10000) + if err == nil { + license := detectLicenseType(content) + if license != "" { + ctx.JSON(http.StatusOK, map[string]any{ + "found": true, + "license": license, + "file": filename, + }) + return + } + } + } + } + + ctx.JSON(http.StatusOK, map[string]any{"found": false}) +} + +// detectLicenseType tries to detect the license type from content +func detectLicenseType(content string) string { + content = strings.ToLower(content) + + // Check for common license signatures + licensePatterns := map[string][]string{ + "MIT": {"mit license", "permission is hereby granted, free of charge"}, + "Apache-2.0": {"apache license", "version 2.0"}, + "GPL-3.0": {"gnu general public license", "version 3"}, + "GPL-2.0": {"gnu general public license", "version 2"}, + "BSD-3-Clause": {"redistribution and use in source and binary forms", "neither the name"}, + "BSD-2-Clause": {"redistribution and use in source and binary forms"}, + "LGPL-3.0": {"gnu lesser general public license", "version 3"}, + "LGPL-2.1": {"gnu lesser general public license", "version 2.1"}, + "MPL-2.0": {"mozilla public license", "version 2.0"}, + "AGPL-3.0": {"gnu affero general public license", "version 3"}, + "BSL-1.1": {"business source license", "change date"}, + "BSL-1.0": {"boost software license"}, + "Unlicense": {"this is free and unencumbered software", "unlicense"}, + "CC0-1.0": {"cc0 1.0 universal", "public domain"}, + "SSPL-1.0": {"server side public license"}, + } + + for license, patterns := range licensePatterns { + matchCount := 0 + for _, pattern := range patterns { + if strings.Contains(content, pattern) { + matchCount++ + } + } + if matchCount == len(patterns) { + return license + } + } + + return "" +} diff --git a/routers/web/web.go b/routers/web/web.go index aee94f9fbe..033f3eca88 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1031,6 +1031,12 @@ func registerWebRoutes(m *web.Router) { m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), org.InitializeLabels) }) + m.Group("/license", func() { + m.Get("", org.SettingsLicense) + m.Post("", org.SettingsLicensePost) + m.Get("/scan", org.SettingsLicenseScan) + }) + m.Group("/actions", func() { m.Get("", org_setting.RedirectToDefaultSetting) addSettingsRunnersRoutes() @@ -1158,6 +1164,7 @@ func registerWebRoutes(m *web.Router) { m.Group("/license", func() { m.Get("", repo_setting.License) m.Post("", repo_setting.LicensePost) + m.Get("/scan", repo_setting.LicenseScan) }) m.Combo("/public_access").Get(repo_setting.PublicAccess).Post(repo_setting.PublicAccessPost) diff --git a/services/ai/ai.go b/services/ai/ai.go index adf900a23f..f19095a03a 100644 --- a/services/ai/ai.go +++ b/services/ai/ai.go @@ -8,10 +8,12 @@ import ( "fmt" "strings" + "code.gitcaddy.com/server/v3/models/db" issues_model "code.gitcaddy.com/server/v3/models/issues" repo_model "code.gitcaddy.com/server/v3/models/repo" "code.gitcaddy.com/server/v3/modules/ai" "code.gitcaddy.com/server/v3/modules/git" + "code.gitcaddy.com/server/v3/modules/gitrepo" "code.gitcaddy.com/server/v3/modules/log" "code.gitcaddy.com/server/v3/modules/setting" "code.gitcaddy.com/server/v3/services/gitdiff" @@ -38,8 +40,15 @@ func ReviewPullRequest(ctx context.Context, pr *issues_model.PullRequest) (*ai.R return nil, fmt.Errorf("failed to load issue: %w", err) } + // Open git repo + gitRepo, err := gitrepo.OpenRepository(ctx, pr.BaseRepo) + if err != nil { + return nil, fmt.Errorf("failed to open git repo: %w", err) + } + defer gitRepo.Close() + // Get the diff - diff, err := gitdiff.GetDiff(ctx, pr.BaseRepo, + diff, err := gitdiff.GetDiffForAPI(ctx, gitRepo, &gitdiff.DiffOptions{ BeforeCommitID: pr.MergeBase, AfterCommitID: pr.HeadCommitID, @@ -115,7 +124,7 @@ func TriageIssue(ctx context.Context, issue *issues_model.Issue) (*ai.TriageIssu } // Get available labels - labels, err := issues_model.GetLabelsByRepoID(ctx, issue.RepoID, "", issues_model.ListOptions{}) + labels, err := issues_model.GetLabelsByRepoID(ctx, issue.RepoID, "", db.ListOptions{}) if err != nil { return nil, fmt.Errorf("failed to get labels: %w", err) } @@ -164,7 +173,7 @@ func SuggestLabels(ctx context.Context, issue *issues_model.Issue) (*ai.SuggestL } // Get available labels - labels, err := issues_model.GetLabelsByRepoID(ctx, issue.RepoID, "", issues_model.ListOptions{}) + labels, err := issues_model.GetLabelsByRepoID(ctx, issue.RepoID, "", db.ListOptions{}) if err != nil { return nil, fmt.Errorf("failed to get labels: %w", err) } diff --git a/templates/org/home.tmpl b/templates/org/home.tmpl index 35fed00c8f..b0370e5c15 100644 --- a/templates/org/home.tmpl +++ b/templates/org/home.tmpl @@ -322,6 +322,21 @@ {{end}} + {{/* Organization License - Sidebar Card */}} + {{if .OrgLicense}} +
+
+ {{svg "octicon-law" 24}} +
+
{{ctx.Locale.Tr "repo.license"}}
+ + {{.OrgLicense}} + +
+
+
+ {{end}} + {{/* Members/Public Members Section */}} {{if .IsOrganizationMember}} {{/* Internal view - show all members */}} diff --git a/templates/org/settings/license.tmpl b/templates/org/settings/license.tmpl new file mode 100644 index 0000000000..5cc69b85d0 --- /dev/null +++ b/templates/org/settings/license.tmpl @@ -0,0 +1,72 @@ +{{template "org/settings/layout_head" (dict "ctxData" . "pageClass" "organization settings license")}} + +
+

+ {{ctx.Locale.Tr "org.settings.license"}} +

+
+
+ {{.CsrfTokenHtml}} +
+ +
+ + +
+
+ {{if .CurrentLicense}} +
+ +

{{.CurrentLicense}}

+ {{if .ProfileRepo}} +

+ {{ctx.Locale.Tr "repo.settings.view_license_file"}} +

+ {{end}} +
+ {{end}} +
{{ctx.Locale.Tr "org.settings.license_help"}}
+ +
+
+
+ + + +{{template "org/settings/layout_footer" .}} diff --git a/templates/org/settings/navbar.tmpl b/templates/org/settings/navbar.tmpl index 58475de7e7..6aaffee48d 100644 --- a/templates/org/settings/navbar.tmpl +++ b/templates/org/settings/navbar.tmpl @@ -12,6 +12,9 @@ {{ctx.Locale.Tr "repo.labels"}} + + {{ctx.Locale.Tr "org.settings.license"}} + {{if .EnableOAuth2}} {{ctx.Locale.Tr "settings.applications"}} diff --git a/templates/repo/settings/license.tmpl b/templates/repo/settings/license.tmpl index 102ec98044..800d162797 100644 --- a/templates/repo/settings/license.tmpl +++ b/templates/repo/settings/license.tmpl @@ -8,16 +8,21 @@ {{.CsrfTokenHtml}}
- + + {{range .LicenseTypes}} + + {{range .Licenses}} + + {{end}} + {{end}} - - {{end}} - + + +
{{if .Repository.LicenseType}}
@@ -33,4 +38,32 @@
+ + + {{template "repo/settings/layout_footer" .}}