Changeset 0.23.2 (#155)

This commit is contained in:
2026-03-06 23:17:03 +00:00
parent 4c6555cb06
commit 2dc4514a57
36 changed files with 2784 additions and 192 deletions

View File

@@ -333,6 +333,14 @@ func (h *AdminHandler) PublicSettings(c *gin.Context) {
hasAdminPrompt = true
}
// Channel retention mode (v0.23.2 — flexible or retain)
retentionMode := "flexible"
if retCfg, err := h.stores.GlobalConfig.Get(c.Request.Context(), "channel_retention"); err == nil {
if mode, ok := retCfg["mode"].(string); ok && mode != "" {
retentionMode = mode
}
}
c.JSON(http.StatusOK, gin.H{
"banner": banner,
"branding": branding,
@@ -340,9 +348,10 @@ func (h *AdminHandler) PublicSettings(c *gin.Context) {
"storage_configured": storageConfigured,
"paste_to_file_chars": pasteChars,
"policies": gin.H{
"allow_registration": policies["allow_registration"],
"allow_user_byok": policies["allow_user_byok"],
"allow_user_personas": policies["allow_user_personas"],
"allow_registration": policies["allow_registration"],
"allow_user_byok": policies["allow_user_byok"],
"allow_user_personas": policies["allow_user_personas"],
"channel_retention_mode": retentionMode,
},
})
}
@@ -724,3 +733,88 @@ func (h *AdminHandler) auditLog(c *gin.Context, action, resourceType, resourceID
log.Printf("audit log error: %v", err)
}
}
// ── Archived Channels (v0.23.2) ──────────────
func (h *AdminHandler) ListArchivedChannels(c *gin.Context) {
rows, err := database.DB.QueryContext(c.Request.Context(), database.Q(`
SELECT c.id, c.title, c.type, c.user_id,
COALESCE(u.username, '') AS owner_name,
c.updated_at,
(SELECT COUNT(*) FROM messages WHERE channel_id = c.id) AS message_count
FROM channels c
LEFT JOIN users u ON u.id = c.user_id
WHERE c.is_archived = true
ORDER BY c.updated_at DESC
LIMIT 100
`))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "query failed"})
return
}
defer rows.Close()
type archivedChannel struct {
ID string `json:"id"`
Title string `json:"title"`
Type string `json:"type"`
OwnerName string `json:"owner_name"`
UpdatedAt string `json:"updated_at"`
MessageCount int `json:"message_count"`
}
channels := []archivedChannel{}
for rows.Next() {
var ch archivedChannel
var userID string
if rows.Scan(&ch.ID, &ch.Title, &ch.Type, &userID, &ch.OwnerName, &ch.UpdatedAt, &ch.MessageCount) == nil {
channels = append(channels, ch)
}
}
// Retention config
retentionMode := "flexible"
if retCfg, err := h.stores.GlobalConfig.Get(c.Request.Context(), "channel_retention"); err == nil {
if mode, ok := retCfg["mode"].(string); ok {
retentionMode = mode
}
}
c.JSON(http.StatusOK, gin.H{
"channels": channels,
"retention_mode": retentionMode,
})
}
func (h *AdminHandler) PurgeChannel(c *gin.Context) {
channelID := c.Param("id")
// Verify the channel is actually archived
var isArchived bool
err := database.DB.QueryRowContext(c.Request.Context(), database.Q(`
SELECT is_archived FROM channels WHERE id = $1
`), channelID).Scan(&isArchived)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "channel not found"})
return
}
if !isArchived {
c.JSON(http.StatusConflict, gin.H{"error": "channel must be archived before purging"})
return
}
// Hard delete (CASCADE removes messages, participants, models)
_, err = database.DB.ExecContext(c.Request.Context(), database.Q(`
DELETE FROM channels WHERE id = $1
`), channelID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "purge failed"})
return
}
// Audit log
uid := getUserID(c)
h.auditLog(c, uid, "channel.purged", "channel", channelID)
c.JSON(http.StatusOK, gin.H{"ok": true})
}