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)
|
||||
}
|
||||
36
backend/internal/api/handlers/articles.go
Normal file
36
backend/internal/api/handlers/articles.go
Normal file
@ -0,0 +1,36 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tradarr/backend/internal/httputil"
|
||||
)
|
||||
|
||||
func (h *Handler) ListArticles(c *gin.Context) {
|
||||
symbol := c.Query("symbol")
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50"))
|
||||
offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0"))
|
||||
if limit > 100 {
|
||||
limit = 100
|
||||
}
|
||||
articles, err := h.repo.ListArticles(symbol, limit, offset)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, articles)
|
||||
}
|
||||
|
||||
func (h *Handler) GetArticle(c *gin.Context) {
|
||||
article, err := h.repo.GetArticleByID(c.Param("id"))
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
if article == nil {
|
||||
httputil.NotFound(c)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, article)
|
||||
}
|
||||
64
backend/internal/api/handlers/auth.go
Normal file
64
backend/internal/api/handlers/auth.go
Normal file
@ -0,0 +1,64 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tradarr/backend/internal/auth"
|
||||
"github.com/tradarr/backend/internal/httputil"
|
||||
"github.com/tradarr/backend/internal/models"
|
||||
)
|
||||
|
||||
type loginRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
type registerRequest struct {
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
}
|
||||
|
||||
func (h *Handler) Login(c *gin.Context) {
|
||||
var req loginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httputil.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
user, err := h.repo.GetUserByEmail(req.Email)
|
||||
if err != nil || user == nil || !auth.CheckPassword(user.PasswordHash, req.Password) {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid credentials"})
|
||||
return
|
||||
}
|
||||
token, err := auth.GenerateToken(user.ID, user.Email, string(user.Role), h.cfg.JWTSecret)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, gin.H{"token": token, "user": user})
|
||||
}
|
||||
|
||||
func (h *Handler) Register(c *gin.Context) {
|
||||
var req registerRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httputil.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
existing, _ := h.repo.GetUserByEmail(req.Email)
|
||||
if existing != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": "email already in use"})
|
||||
return
|
||||
}
|
||||
hash, err := auth.HashPassword(req.Password)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
user, err := h.repo.CreateUser(req.Email, hash, models.RoleUser)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
token, _ := auth.GenerateToken(user.ID, user.Email, string(user.Role), h.cfg.JWTSecret)
|
||||
httputil.Created(c, gin.H{"token": token, "user": user})
|
||||
}
|
||||
33
backend/internal/api/handlers/handler.go
Normal file
33
backend/internal/api/handlers/handler.go
Normal file
@ -0,0 +1,33 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/tradarr/backend/internal/ai"
|
||||
"github.com/tradarr/backend/internal/config"
|
||||
"github.com/tradarr/backend/internal/crypto"
|
||||
"github.com/tradarr/backend/internal/models"
|
||||
"github.com/tradarr/backend/internal/scraper"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
repo *models.Repository
|
||||
cfg *config.Config
|
||||
enc *crypto.Encryptor
|
||||
registry *scraper.Registry
|
||||
pipeline *ai.Pipeline
|
||||
}
|
||||
|
||||
func New(
|
||||
repo *models.Repository,
|
||||
cfg *config.Config,
|
||||
enc *crypto.Encryptor,
|
||||
registry *scraper.Registry,
|
||||
pipeline *ai.Pipeline,
|
||||
) *Handler {
|
||||
return &Handler{
|
||||
repo: repo,
|
||||
cfg: cfg,
|
||||
enc: enc,
|
||||
registry: registry,
|
||||
pipeline: pipeline,
|
||||
}
|
||||
}
|
||||
33
backend/internal/api/handlers/summaries.go
Normal file
33
backend/internal/api/handlers/summaries.go
Normal file
@ -0,0 +1,33 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tradarr/backend/internal/httputil"
|
||||
)
|
||||
|
||||
func (h *Handler) ListSummaries(c *gin.Context) {
|
||||
userID := c.GetString("userID")
|
||||
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
|
||||
summaries, err := h.repo.ListSummaries(userID, limit)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, summaries)
|
||||
}
|
||||
|
||||
func (h *Handler) GenerateSummary(c *gin.Context) {
|
||||
userID := c.GetString("userID")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Hour)
|
||||
defer cancel()
|
||||
summary, err := h.pipeline.GenerateForUser(ctx, userID)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.Created(c, summary)
|
||||
}
|
||||
56
backend/internal/api/handlers/user.go
Normal file
56
backend/internal/api/handlers/user.go
Normal file
@ -0,0 +1,56 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tradarr/backend/internal/httputil"
|
||||
)
|
||||
|
||||
func (h *Handler) GetMe(c *gin.Context) {
|
||||
userID := c.GetString("userID")
|
||||
user, err := h.repo.GetUserByID(userID)
|
||||
if err != nil || user == nil {
|
||||
httputil.NotFound(c)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, user)
|
||||
}
|
||||
|
||||
func (h *Handler) GetMyAssets(c *gin.Context) {
|
||||
userID := c.GetString("userID")
|
||||
assets, err := h.repo.GetUserAssets(userID)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.OK(c, assets)
|
||||
}
|
||||
|
||||
type addAssetRequest struct {
|
||||
Symbol string `json:"symbol" binding:"required"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (h *Handler) AddMyAsset(c *gin.Context) {
|
||||
userID := c.GetString("userID")
|
||||
var req addAssetRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
httputil.BadRequest(c, err)
|
||||
return
|
||||
}
|
||||
asset, err := h.repo.AddUserAsset(userID, req.Symbol, req.Name)
|
||||
if err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.Created(c, asset)
|
||||
}
|
||||
|
||||
func (h *Handler) RemoveMyAsset(c *gin.Context) {
|
||||
userID := c.GetString("userID")
|
||||
symbol := c.Param("symbol")
|
||||
if err := h.repo.RemoveUserAsset(userID, symbol); err != nil {
|
||||
httputil.InternalError(c, err)
|
||||
return
|
||||
}
|
||||
httputil.NoContent(c)
|
||||
}
|
||||
73
backend/internal/api/router.go
Normal file
73
backend/internal/api/router.go
Normal file
@ -0,0 +1,73 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/tradarr/backend/internal/api/handlers"
|
||||
"github.com/tradarr/backend/internal/auth"
|
||||
)
|
||||
|
||||
func SetupRouter(h *handlers.Handler, jwtSecret string) *gin.Engine {
|
||||
r := gin.Default()
|
||||
|
||||
r.Use(func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
|
||||
c.Header("Access-Control-Allow-Headers", "Authorization,Content-Type")
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
})
|
||||
|
||||
api := r.Group("/api")
|
||||
|
||||
// Auth public
|
||||
api.POST("/auth/login", h.Login)
|
||||
api.POST("/auth/register", h.Register)
|
||||
|
||||
// Routes authentifiées
|
||||
authed := api.Group("/")
|
||||
authed.Use(auth.Middleware(jwtSecret))
|
||||
|
||||
authed.GET("/me", h.GetMe)
|
||||
authed.GET("/me/assets", h.GetMyAssets)
|
||||
authed.POST("/me/assets", h.AddMyAsset)
|
||||
authed.DELETE("/me/assets/:symbol", h.RemoveMyAsset)
|
||||
|
||||
authed.GET("/articles", h.ListArticles)
|
||||
authed.GET("/articles/:id", h.GetArticle)
|
||||
|
||||
authed.GET("/summaries", h.ListSummaries)
|
||||
authed.POST("/summaries/generate", h.GenerateSummary)
|
||||
|
||||
// Admin
|
||||
admin := authed.Group("/admin")
|
||||
admin.Use(auth.AdminOnly())
|
||||
|
||||
admin.GET("/credentials", h.GetCredentials)
|
||||
admin.PUT("/credentials", h.UpsertCredentials)
|
||||
|
||||
admin.GET("/ai-providers", h.ListAIProviders)
|
||||
admin.POST("/ai-providers", h.CreateAIProvider)
|
||||
admin.PUT("/ai-providers/:id", h.UpdateAIProvider)
|
||||
admin.POST("/ai-providers/:id/activate", h.SetActiveAIProvider)
|
||||
admin.DELETE("/ai-providers/:id", h.DeleteAIProvider)
|
||||
admin.GET("/ai-providers/:id/models", h.ListAIModels)
|
||||
|
||||
admin.GET("/sources", h.ListSources)
|
||||
admin.PUT("/sources/:id", h.UpdateSource)
|
||||
|
||||
admin.GET("/scrape-jobs", h.ListScrapeJobs)
|
||||
admin.POST("/scrape-jobs/trigger", h.TriggerScrapeJob)
|
||||
|
||||
admin.GET("/settings", h.ListSettings)
|
||||
admin.PUT("/settings", h.UpdateSettings)
|
||||
admin.GET("/settings/default-prompt", h.GetDefaultSystemPrompt)
|
||||
|
||||
admin.GET("/users", h.ListUsers)
|
||||
admin.PUT("/users/:id", h.UpdateAdminUser)
|
||||
admin.DELETE("/users/:id", h.DeleteAdminUser)
|
||||
|
||||
return r
|
||||
}
|
||||
Reference in New Issue
Block a user