feat: add frontend + backend + database to retrieve and compute news from Yahoo
This commit is contained in:
349
backend/internal/api/handlers/admin.go
Normal file
349
backend/internal/api/handlers/admin.go
Normal file
@ -0,0 +1,349 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tradarr/backend/internal/ai"
|
||||
"github.com/tradarr/backend/internal/httputil"
|
||||
"github.com/tradarr/backend/internal/models"
|
||||
)
|
||||
|
||||
// ── Credentials ────────────────────────────────────────────────────────────
|
||||
|
||||
type credentialsRequest struct {
|
||||
SourceID string `json:"source_id" binding:"required"`
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (h *Handler) GetCredentials(c *gin.Context) {
|
||||
sources, err := h.repo.ListSources()
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
type credResponse struct {
|
||||
SourceID string `json:"source_id"`
|
||||
SourceName string `json:"source_name"`
|
||||
Username string `json:"username"`
|
||||
HasPassword bool `json:"has_password"`
|
||||
}
|
||||
var result []credResponse
|
||||
for _, src := range sources {
|
||||
if src.Type != "bloomberg" {
|
||||
continue
|
||||
}
|
||||
cred, _ := h.repo.GetCredentials(src.ID)
|
||||
r := credResponse{SourceID: src.ID, SourceName: src.Name}
|
||||
if cred != nil {
|
||||
r.Username = cred.Username
|
||||
r.HasPassword = cred.PasswordEncrypted != ""
|
||||
}
|
||||
result = append(result, r)
|
||||
}
|
||||
httputil.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) UpsertCredentials(c *gin.Context) {
|
||||
var req credentialsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httputil.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
encPwd := ""
|
||||
if req.Password != "" {
|
||||
var err error
|
||||
encPwd, err = h.enc.Encrypt(req.Password)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := h.repo.UpsertCredentials(req.SourceID, req.Username, encPwd); err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// ── AI Providers ───────────────────────────────────────────────────────────
|
||||
|
||||
type aiProviderRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
APIKey string `json:"api_key"`
|
||||
Model string `json:"model"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
}
|
||||
|
||||
func (h *Handler) ListAIProviders(c *gin.Context) {
|
||||
providers, err := h.repo.ListAIProviders()
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
// Ne pas exposer les clés chiffrées — juste indiquer si elle existe
|
||||
type safeProvider struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Model string `json:"model"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
IsActive bool `json:"is_active"`
|
||||
HasKey bool `json:"has_key"`
|
||||
}
|
||||
var result []safeProvider
|
||||
for _, p := range providers {
|
||||
result = append(result, safeProvider{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
Model: p.Model,
|
||||
Endpoint: p.Endpoint,
|
||||
IsActive: p.IsActive,
|
||||
HasKey: p.APIKeyEncrypted != "",
|
||||
})
|
||||
}
|
||||
httputil.OK(c, result)
|
||||
}
|
||||
|
||||
func (h *Handler) CreateAIProvider(c *gin.Context) {
|
||||
var req aiProviderRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httputil.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
encKey := ""
|
||||
if req.APIKey != "" {
|
||||
var err error
|
||||
encKey, err = h.enc.Encrypt(req.APIKey)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
p, err := h.repo.CreateAIProvider(req.Name, encKey, req.Model, req.Endpoint)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.Created(c, p)
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateAIProvider(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req aiProviderRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httputil.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
existing, err := h.repo.GetAIProviderByID(id)
|
||||
if err != nil || existing == nil {
|
||||
httputil.NotFound(c)
|
||||
return
|
||||
}
|
||||
encKey := existing.APIKeyEncrypted
|
||||
if req.APIKey != "" {
|
||||
encKey, err = h.enc.Encrypt(req.APIKey)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := h.repo.UpdateAIProvider(id, encKey, req.Model, req.Endpoint); err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
func (h *Handler) SetActiveAIProvider(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
if err := h.repo.SetActiveAIProvider(id); err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteAIProvider(c *gin.Context) {
|
||||
if err := h.repo.DeleteAIProvider(c.Param("id")); err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.NoContent(c)
|
||||
}
|
||||
|
||||
func (h *Handler) ListAIModels(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
p, err := h.repo.GetAIProviderByID(id)
|
||||
if err != nil || p == nil {
|
||||
httputil.NotFound(c)
|
||||
return
|
||||
}
|
||||
apiKey := ""
|
||||
if p.APIKeyEncrypted != "" {
|
||||
apiKey, err = h.enc.Decrypt(p.APIKeyEncrypted)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
provider, err := h.pipeline.BuildProvider(p.Name, apiKey, p.Endpoint)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
models, err := provider.ListModels(c.Request.Context())
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, models)
|
||||
}
|
||||
|
||||
// ── Sources ────────────────────────────────────────────────────────────────
|
||||
|
||||
func (h *Handler) ListSources(c *gin.Context) {
|
||||
sources, err := h.repo.ListSources()
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, sources)
|
||||
}
|
||||
|
||||
type updateSourceRequest struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateSource(c *gin.Context) {
|
||||
var req updateSourceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httputil.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
if err := h.repo.UpdateSource(c.Param("id"), req.Enabled); err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
// ── Scrape Jobs ────────────────────────────────────────────────────────────
|
||||
|
||||
func (h *Handler) ListScrapeJobs(c *gin.Context) {
|
||||
jobs, err := h.repo.ListScrapeJobs(100)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, jobs)
|
||||
}
|
||||
|
||||
func (h *Handler) TriggerScrapeJob(c *gin.Context) {
|
||||
type triggerRequest struct {
|
||||
SourceID string `json:"source_id" binding:"required"`
|
||||
}
|
||||
var req triggerRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httputil.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
if err := h.registry.Run(req.SourceID); err != nil {
|
||||
fmt.Printf("scrape job error: %v\n", err)
|
||||
}
|
||||
}()
|
||||
c.JSON(http.StatusAccepted, gin.H{"ok": true, "message": "job started"})
|
||||
}
|
||||
|
||||
// ── Settings ───────────────────────────────────────────────────────────────
|
||||
|
||||
func (h *Handler) ListSettings(c *gin.Context) {
|
||||
settings, err := h.repo.ListSettings()
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, settings)
|
||||
}
|
||||
|
||||
type updateSettingsRequest struct {
|
||||
Settings []models.Setting `json:"settings"`
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateSettings(c *gin.Context) {
|
||||
var req updateSettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httputil.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
for _, s := range req.Settings {
|
||||
if err := h.repo.SetSetting(s.Key, s.Value); err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
httputil.OK(c, gin.H{"ok": true})
|
||||
}
|
||||
|
||||
func (h *Handler) GetDefaultSystemPrompt(c *gin.Context) {
|
||||
httputil.OK(c, gin.H{"prompt": ai.DefaultSystemPrompt})
|
||||
}
|
||||
|
||||
// ── Admin Users ────────────────────────────────────────────────────────────
|
||||
|
||||
func (h *Handler) ListUsers(c *gin.Context) {
|
||||
users, err := h.repo.ListUsers()
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, users)
|
||||
}
|
||||
|
||||
type updateUserRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Role string `json:"role" binding:"required"`
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateAdminUser(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req updateUserRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httputil.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
if req.Role != "admin" && req.Role != "user" {
|
||||
httputil.BadRequest(c, fmt.Errorf("role must be admin or user"))
|
||||
return
|
||||
}
|
||||
user, err := h.repo.UpdateUser(id, req.Email, models.Role(req.Role))
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, user)
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteAdminUser(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
// Empêcher la suppression du dernier admin
|
||||
user, err := h.repo.GetUserByID(id)
|
||||
if err != nil || user == nil {
|
||||
httputil.NotFound(c)
|
||||
return
|
||||
}
|
||||
if user.Role == "admin" {
|
||||
count, _ := h.repo.CountAdmins()
|
||||
if count <= 1 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "cannot delete the last admin"})
|
||||
return
|
||||
}
|
||||
}
|
||||
if err := h.repo.DeleteUser(id); err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.NoContent(c)
|
||||
}
|
||||
Reference in New Issue
Block a user