Skip to content

Commit 1e291f4

Browse files
committed
Fix Batch 2: HiTechCloud Software License & Commerce Platform. Subscriptions, perpetual, trials, floating licenses, usage metering, Stripe, VNPay, ZaloPay, Pay2s and More payments, team seats, and admin dashboard. Self-hosted alternative to Keygen & Cryptlex. One binary, one database.
1 parent f8d6781 commit 1e291f4

11 files changed

Lines changed: 33 additions & 45 deletions

File tree

‎.github/FUNDING.yml‎

Lines changed: 0 additions & 3 deletions
This file was deleted.

‎internal/handler/webhook_admin.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func (h *WebhookAdminHandler) GetDelivery(c *gin.Context) {
231231
// the receiver was down / mis-configured and the admin wants to
232232
// replay an event without trying to recreate the original action.
233233
// The replay carries the SAME payload bytes (so receiver-side
234-
// idempotency keys still match) but a fresh X-Keygate-Delivery and
234+
// idempotency keys still match) but a fresh X-HiTechCloud-Delivery and
235235
// a new row in the deliveries table so retry counters are clean.
236236
func (h *WebhookAdminHandler) ResendDelivery(c *gin.Context) {
237237
wh, ok := h.checkWebhookScope(c, c.Param("id"))

‎internal/middleware/metrics.go‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ import (
1212
var (
1313
httpRequestsTotal = promauto.NewCounterVec(
1414
prometheus.CounterOpts{
15-
Name: "keygate_http_requests_total",
15+
Name: "hitechcloud_http_requests_total",
1616
Help: "Total number of HTTP requests",
1717
},
1818
[]string{"method", "path", "status"},
1919
)
2020

2121
httpRequestDuration = promauto.NewHistogramVec(
2222
prometheus.HistogramOpts{
23-
Name: "keygate_http_request_duration_seconds",
23+
Name: "hitechcloud_http_request_duration_seconds",
2424
Help: "HTTP request duration in seconds",
2525
Buckets: prometheus.DefBuckets,
2626
},
@@ -29,54 +29,54 @@ var (
2929

3030
httpRequestsInFlight = promauto.NewGauge(
3131
prometheus.GaugeOpts{
32-
Name: "keygate_http_requests_in_flight",
32+
Name: "hitechcloud_http_requests_in_flight",
3333
Help: "Number of HTTP requests currently being processed",
3434
},
3535
)
3636

3737
// Business metrics
3838
LicenseActivations = promauto.NewCounterVec(
3939
prometheus.CounterOpts{
40-
Name: "keygate_license_activations_total",
40+
Name: "hitechcloud_license_activations_total",
4141
Help: "Total license activations",
4242
},
4343
[]string{"product_id", "status"}, // status: "activated", "already_activated", "failed"
4444
)
4545

4646
LicenseVerifications = promauto.NewCounterVec(
4747
prometheus.CounterOpts{
48-
Name: "keygate_license_verifications_total",
48+
Name: "hitechcloud_license_verifications_total",
4949
Help: "Total license verifications",
5050
},
5151
[]string{"product_id", "result"}, // result: "valid", "expired", "not_found", "not_activated"
5252
)
5353

5454
WebhookDeliveries = promauto.NewCounterVec(
5555
prometheus.CounterOpts{
56-
Name: "keygate_webhook_deliveries_total",
56+
Name: "hitechcloud_webhook_deliveries_total",
5757
Help: "Total webhook delivery attempts",
5858
},
5959
[]string{"status"}, // "delivered", "failed", "retrying"
6060
)
6161

6262
EmailDeliveries = promauto.NewCounterVec(
6363
prometheus.CounterOpts{
64-
Name: "keygate_email_deliveries_total",
64+
Name: "hitechcloud_email_deliveries_total",
6565
Help: "Total email delivery attempts",
6666
},
6767
[]string{"status"}, // "sent", "failed", "queued"
6868
)
6969

7070
ActiveLicenses = promauto.NewGauge(
7171
prometheus.GaugeOpts{
72-
Name: "keygate_active_licenses",
72+
Name: "hitechcloud_active_licenses",
7373
Help: "Current number of active licenses",
7474
},
7575
)
7676

7777
BruteForceBlocks = promauto.NewCounter(
7878
prometheus.CounterOpts{
79-
Name: "keygate_brute_force_blocks_total",
79+
Name: "hitechcloud_brute_force_blocks_total",
8080
Help: "Total brute force lockouts triggered",
8181
},
8282
)

‎internal/service/webhook.go‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,7 @@ func (s *WebhookService) DeliverTest(ctx context.Context, wh *model.Webhook) (*m
476476
"timestamp": time.Now().UTC().Format(time.RFC3339),
477477
"data": map[string]any{
478478
"webhook_id": wh.ID,
479-
"message": "This is a test delivery from Keygate.",
479+
"message": "This is a test delivery from HiTechCloud.",
480480
},
481481
},
482482
}
@@ -506,9 +506,9 @@ func (s *WebhookService) deliver(ctx context.Context, wh *model.Webhook, deliver
506506
return
507507
}
508508
req.Header.Set("Content-Type", "application/json")
509-
req.Header.Set("X-Keygate-Event", delivery.Event)
510-
req.Header.Set("X-Keygate-Signature", "sha256="+sig)
511-
req.Header.Set("X-Keygate-Delivery", delivery.ID)
509+
req.Header.Set("X-HiTechCloud-Event", delivery.Event)
510+
req.Header.Set("X-HiTechCloud-Signature", "sha256="+sig)
511+
req.Header.Set("X-HiTechCloud-Delivery", delivery.ID)
512512

513513
resp, err := s.send(ctx, req)
514514
if err != nil {
@@ -567,7 +567,7 @@ var ErrWebhookInactive = errors.New("webhook is disabled")
567567
// Redispatch fires a fresh delivery using the payload of an existing
568568
// one. Industry-standard "resend" behaviour: the receiver sees the
569569
// SAME `data` (so its idempotency dedup still works) but a new
570-
// `X-Keygate-Delivery` header and a new row in the deliveries table
570+
// `X-HiTechCloud-Delivery` header and a new row in the deliveries table
571571
// — so retries, response codes, and timestamps are tracked
572572
// independently of the original attempt.
573573
//

‎internal/store/metrics.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import (
1616
// Alert when this counter increments. Phase C deployment is unsafe while
1717
// any failures are recorded.
1818
var LicenseKeyDecryptFailures = promauto.NewCounter(prometheus.CounterOpts{
19-
Name: "keygate_license_key_decrypt_failures_total",
19+
Name: "hitechcloud_license_key_decrypt_failures_total",
2020
Help: "Cumulative count of license_key_encrypted decrypt failures.",
2121
})
2222

@@ -28,7 +28,7 @@ var LicenseKeyDecryptFailures = promauto.NewCounter(prometheus.CounterOpts{
2828
// Updated on each backfill batch + once at startup; used as the deciding
2929
// metric for "is it safe to enable Phase B?".
3030
var LicenseKeysUnencrypted = promauto.NewGauge(prometheus.GaugeOpts{
31-
Name: "keygate_license_keys_unencrypted",
31+
Name: "hitechcloud_license_keys_unencrypted",
3232
Help: "Number of license rows still requiring license_key_encrypted backfill.",
3333
})
3434

‎web/src/components/layout.tsx‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
Blocks,
44
ChevronDown,
55
FileKey2,
6-
Heart,
76
Key,
87
Layers,
98
LayoutDashboard,
@@ -158,12 +157,6 @@ export function AdminLayout() {
158157
</nav>
159158
{/* Settings — fixed at bottom above user menu */}
160159
<div className="px-2">{renderNavItem(settingsItem)}</div>
161-
<a href="https://keygate.app/sponsorships" target="_blank" rel="noopener noreferrer" className="block px-2 pb-1">
162-
<div className="flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-pink-50 hover:text-pink-700 transition-colors">
163-
<Heart className="h-4 w-4 text-pink-500 fill-pink-500" />
164-
{t("nav.sponsor")}
165-
</div>
166-
</a>
167160
<Separator />
168161
<div className="p-3">
169162
<DropdownMenu>

‎web/src/hooks/use-site-config.tsx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ export function SiteConfigProvider({ children }: { children: ReactNode }) {
5959
document.title = data.site_name
6060
}
6161
// Set default language if user hasn't explicitly chosen one
62-
if (data.language && !localStorage.getItem("keygate_locale")) {
63-
localStorage.setItem("keygate_locale", data.language)
62+
if (data.language && !localStorage.getItem("hitechcloud_locale")) {
63+
localStorage.setItem("hitechcloud_locale", data.language)
6464
document.documentElement.lang = data.language
6565
}
6666
})

‎web/src/i18n/index.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ interface I18nContextType {
1616

1717
const I18nContext = createContext<I18nContextType | null>(null)
1818

19-
const STORAGE_KEY = "keygate_locale"
19+
const STORAGE_KEY = "hitechcloud_locale"
2020

2121
export function I18nProvider({ children }: { children: ReactNode }) {
2222
const [locale, setLocaleState] = useState<Locale>(() => {

‎web/src/i18n/locales/en.ts‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ const en = {
6464
"nav.insights": "Insights",
6565
"nav.customers": "Customers",
6666
"nav.settings": "Settings",
67-
"nav.sponsor": "Sponsor Us",
6867
"nav.menu": "Menu",
6968
"nav.portal": "User Portal",
7069
"nav.logout": "Logout",
@@ -78,7 +77,7 @@ const en = {
7877
"dashboard.subtitle": "Overview of your license management system.",
7978
"dashboard.statusDistribution": "License Status Distribution",
8079
"dashboard.recentLicenses": "Recent Licenses",
81-
"dashboard.updateAvailable": "Keygate {version} is available.",
80+
"dashboard.updateAvailable": "HiTechCloud {version} is available.",
8281
"dashboard.viewUpdate": "View Details",
8382
"dashboard.releaseNotes": "Release Notes",
8483

@@ -174,7 +173,7 @@ const en = {
174173
"licenses.unlink": "Unlink",
175174
"licenses.unlinkTitle": "Unlink this Stripe subscription?",
176175
"licenses.unlinkConfirm":
177-
"Keygate asks Stripe first and refuses while the subscription is still there. Once unlinked, this license is managed here again — its plan and expiry become editable — and a refund of that subscription’s last invoice will no longer find it by subscription id.",
176+
"HiTechCloud asks Stripe first and refuses while the subscription is still there. Once unlinked, this license is managed here again — its plan and expiry become editable — and a refund of that subscription’s last invoice will no longer find it by subscription id.",
178177
"licenses.unlinkDone": "Subscription unlinked.",
179178
"licenses.unlinkNotLinked": "This license has no Stripe subscription.",
180179
"licenses.changePlan": "Change Plan",
@@ -367,7 +366,7 @@ const en = {
367366
"settings.brandColor": "Brand Color",
368367
"settings.brandColorDesc": "Primary color used throughout the interface. Leave empty for default.",
369368
"settings.logoUrl": "Custom Logo URL",
370-
"settings.logoUrlDesc": "URL to your logo (SVG recommended). Leave empty for default Keygate logo.",
369+
"settings.logoUrlDesc": "URL to your logo (SVG recommended). Leave empty for default HiTechCloud logo.",
371370
"settings.emailConfigured": "Outgoing email is configured.",
372371
"settings.emailNotConfigured":
373372
"Outgoing email is not configured — customers will not receive license or invite emails.",
@@ -437,7 +436,7 @@ const en = {
437436
"settings.reminderDaysDefaultTag": "(default)",
438437
"settings.autoEmails": "Automated emails",
439438
"settings.autoEmailsDesc":
440-
"Choose which emails Keygate sends to customers on its own. Turn off any you'd rather send yourself; those marked webhook also fire a webhook event your system can act on.",
439+
"Choose which emails HiTechCloud sends to customers on its own. Turn off any you'd rather send yourself; those marked webhook also fire a webhook event your system can act on.",
441440
"settings.autoEmailsAlways":
442441
"Always sent: login codes, invitations, license delivery emails, and anything you send by hand (such as Resend).",
443442
"settings.autoEmailsGroup.reminders": "Reminders",
@@ -489,7 +488,7 @@ const en = {
489488
"pagination.rows": "Rows",
490489

491490
// Login
492-
"setup.title": "Set up Keygate",
491+
"setup.title": "Set up HiTechCloud",
493492
"setup.subtitle": "Create the owner account and your first product.",
494493
"setup.you": "You",
495494
"setup.emailHint": "You sign in with a one time code sent to this address.",
@@ -844,7 +843,7 @@ const en = {
844843
"updateSettings.channelHint":
845844
"For the Sparkle and Tauri addresses. A channel also receives the more stable ones, so beta apps get stable releases too.",
846845
"updateSettings.baseUrlWarning":
847-
"BASE_URL is set to {url}, an address only this machine can reach, so apps elsewhere cannot use these URLs. Set BASE_URL to the address your users' apps reach this server at, then restart Keygate.",
846+
"BASE_URL is set to {url}, an address only this machine can reach, so apps elsewhere cannot use these URLs. Set BASE_URL to the address your users' apps reach this server at, then restart HiTechCloud.",
848847
"updateSettings.sparkleField": "SUFeedURL in Info.plist",
849848
"updateSettings.sparkleHint": "Set {field} to the version you publish here, such as 1.2.0.",
850849
"updateSettings.tauriField": "Endpoint in tauri.conf.json",

‎web/src/i18n/locales/zh.ts‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ const zh = {
6363
"nav.insights": "数据洞察",
6464
"nav.customers": "客户管理",
6565
"nav.settings": "系统设置",
66-
"nav.sponsor": "赞助我们",
6766
"nav.menu": "菜单",
6867
"nav.portal": "用户门户",
6968
"nav.logout": "退出登录",
@@ -77,7 +76,7 @@ const zh = {
7776
"dashboard.subtitle": "许可证管理系统概览。",
7877
"dashboard.statusDistribution": "许可证状态分布",
7978
"dashboard.recentLicenses": "最近许可证",
80-
"dashboard.updateAvailable": "Keygate {version} 新版本可用。",
79+
"dashboard.updateAvailable": "HiTechCloud {version} 新版本可用。",
8180
"dashboard.viewUpdate": "查看详情",
8281
"dashboard.releaseNotes": "更新日志",
8382

@@ -169,7 +168,7 @@ const zh = {
169168
"licenses.unlink": "解绑",
170169
"licenses.unlinkTitle": "解绑这个 Stripe 订阅?",
171170
"licenses.unlinkConfirm":
172-
"Keygate 会先向 Stripe 确认,订阅还在就会拒绝。解绑后这张授权重新由本地管理(方案和到期日可以改),而该订阅最后一张发票的退款将不再能通过订阅 id 找到它。",
171+
"HiTechCloud 会先向 Stripe 确认,订阅还在就会拒绝。解绑后这张授权重新由本地管理(方案和到期日可以改),而该订阅最后一张发票的退款将不再能通过订阅 id 找到它。",
173172
"licenses.unlinkDone": "订阅已解绑。",
174173
"licenses.unlinkNotLinked": "这张授权没有绑定 Stripe 订阅。",
175174
"licenses.changePlan": "更换方案",
@@ -359,7 +358,7 @@ const zh = {
359358
"settings.brandColor": "品牌颜色",
360359
"settings.brandColorDesc": "整个界面使用的主色调。留空则使用默认紫色。",
361360
"settings.logoUrl": "自定义 Logo URL",
362-
"settings.logoUrlDesc": "Logo 图片的 URL(推荐 SVG 格式)。留空则使用默认 Keygate Logo。",
361+
"settings.logoUrlDesc": "Logo 图片的 URL(推荐 SVG 格式)。留空则使用默认 HiTechCloud Logo。",
363362
"settings.emailConfigured": "外发邮件已配置。",
364363
"settings.emailNotConfigured": "外发邮件未配置,客户不会收到授权和邀请邮件。",
365364
"settings.emailEnvHint":
@@ -425,7 +424,7 @@ const zh = {
425424
"settings.reminderDaysDefaultTag": "(默认)",
426425
"settings.autoEmails": "自动邮件",
427426
"settings.autoEmailsDesc":
428-
"选择 Keygate 自动发给客户的邮件。可以关闭想自己发送的邮件;标有 webhook 的邮件同时会触发一个 webhook 事件,供你的系统处理。",
427+
"选择 HiTechCloud 自动发给客户的邮件。可以关闭想自己发送的邮件;标有 webhook 的邮件同时会触发一个 webhook 事件,供你的系统处理。",
429428
"settings.autoEmailsAlways": "以下邮件始终发送:登录验证码、邀请、许可证发放邮件,以及手动发送的邮件(例如“重发”)。",
430429
"settings.autoEmailsGroup.reminders": "提醒",
431430
"settings.autoEmailsGroup.status": "许可证状态",
@@ -475,7 +474,7 @@ const zh = {
475474
"pagination.rows": "每页",
476475

477476
// Login
478-
"setup.title": "设置 Keygate",
477+
"setup.title": "设置 HiTechCloud",
479478
"setup.subtitle": "创建所有者账号和第一个产品。",
480479
"setup.you": "你的信息",
481480
"setup.emailHint": "登录时会向这个邮箱发送一次性验证码。",
@@ -824,7 +823,7 @@ const zh = {
824823
"updateSettings.channelHint":
825824
"用于 Sparkle 和 Tauri 的地址。一个渠道也会收到更稳定渠道的版本,所以 beta 应用也能收到 stable 版本。",
826825
"updateSettings.baseUrlWarning":
827-
"BASE_URL 设置为 {url},只有本机才能访问这个地址,其他机器上的应用无法使用下面这些地址。请把 BASE_URL 设成用户的应用访问这台服务器所用的地址,然后重启 Keygate。",
826+
"BASE_URL 设置为 {url},只有本机才能访问这个地址,其他机器上的应用无法使用下面这些地址。请把 BASE_URL 设成用户的应用访问这台服务器所用的地址,然后重启 HiTechCloud。",
828827
"updateSettings.sparkleField": "Info.plist 中的 SUFeedURL",
829828
"updateSettings.sparkleHint": "把 {field} 设成你在这里发布的版本号,例如 1.2.0。",
830829
"updateSettings.tauriField": "tauri.conf.json 中的 endpoints",

0 commit comments

Comments
 (0)