gestion des providers

This commit is contained in:
fabien committed 2026-08-31 21:23:35 +02:00
1 parent 357bf9b41c
commit 4d72952b2c
46 files changed
+995 -150

No files matched your search

@@ -12,6 +12,7 @@ type Database interface {
SettingsRepository() domain.SettingsRepository
UserRepository() domain.UserRepository
FileRepository() domain.FileRepository
ProviderRepository() domain.ProviderRepository
Migrate() error
Close()
}
@@ -67,3 +67,7 @@ func (db *TursoDB) SettingsRepository() domain.SettingsRepository {
func (db *TursoDB) FileRepository() domain.FileRepository {
return NewTursoFileRepository(db.DB)
}
func (db *TursoDB) ProviderRepository() domain.ProviderRepository {
return NewTursoModelRepository(db.DB)
}
@@ -11,4 +11,3 @@ drop table tools;
drop table tasks;
drop table history;
drop table files;
drop table share;
@@ -1,25 +1,16 @@
create table providers (
id text not null primary key,
team_id text not null,
type text not null,
name text not null,
key text,
url text,
models text,
_date_created numeric not null default current_timestamp,
_date_updated numeric,
_version text not null
);
create table models (
id text not null primary key,
team_id text not null,
provider_id text not null,
name text not null default '',
modelname text not null default '',
configuration text not null default '{}',
_date_created numeric not null default current_timestamp,
_date_updated numeric,
_version text not null
);
create table settings (
id text not null primary key,
@@ -150,14 +141,3 @@ create table files (
_date_updated numeric ,
_version text not null
);
create table share (
id text not null primary key,
object_id text not null,
object_type text not null,
team_id text not null,
enabled numeric not null default 0,
_date_created numeric not null default current_timestamp,
_date_updated numeric,
_version text not null
);
@@ -0,0 +1,61 @@
package turso
import (
"database/sql"
"log"
"gitea.trankilou.fr/fabien/lasebuche"
"trankilou.fr/lassistanoque/backend/internal/domain"
)
type TursoModelRepository struct {
db *sql.DB
providerTable lasebuche.Table[domain.Provider]
}
func NewTursoModelRepository(db *sql.DB) *TursoModelRepository {
dialect := lasebuche.NewSqliteDialect()
providerTable, err := lasebuche.NewTable[domain.Provider](db, dialect, "providers")
if err != nil {
log.Fatalf("error creating lasebuche team table")
}
return &TursoModelRepository{
db: db,
providerTable: providerTable,
}
}
func (r *TursoModelRepository) ListProviders(userID string, teamID string) ([]*domain.Provider, error) {
return r.providerTable.SelectWhere(
"team_id=$1 and team_id in (select team_id from user_teams where user_id=$2)",
teamID,
userID,
)
}
func (r *TursoModelRepository) GetProvider(userID string, teamID string, id string) (*domain.Provider, error) {
return r.providerTable.SelectOne(
"id=$1 and team_id=$2 and team_id in (select team_id from user_teams where user_id=$3)",
id,
teamID,
userID,
)
}
func (r *TursoModelRepository) CreateProvider(userID string, provider *domain.Provider) (*domain.Provider, error) {
return r.providerTable.Insert(provider)
}
func (r *TursoModelRepository) UpdateProvider(userID string, provider *domain.Provider) (*domain.Provider, error) {
return r.providerTable.Update(provider)
}
func (r *TursoModelRepository) DeleteProvider(userID string, teamID string, id string) error {
return r.providerTable.DeleteWhere(
"id=$1 and team_id=$2 and team_id in (select team_id from user_teams where user_id=$3)",
id,
teamID,
userID,
)
}
@@ -73,7 +73,7 @@ func (ur *TursoUserRepository) FindTeam(userid string, teamid string) (*domain.T
}
func (ur *TursoUserRepository) ListTeams(userid string) ([]*domain.Team, error) {
return ur.TeamTable.SelectWhere("user_id=$1", userid)
return ur.TeamTable.SelectWhere("id in (select team_id from user_teams where user_id=$1)", userid)
}
func (ur *TursoUserRepository) CreateTeam(userid string, team *domain.Team) (*domain.Team, error) {
@@ -123,10 +123,6 @@ func (ur *TursoUserRepository) DeleteUserTeam(userid string, teamid string) erro
return ur.UserTeamTable.Delete(teamid)
}
func (ur *TursoUserRepository) PopulateUserWithTeams(user *domain.User) (*domain.UserWithTeams, error) {
return nil, nil
}
func (ur *TursoUserRepository) ListUserAddresses(id string) ([]*domain.UserAddress, error) {
return ur.AddressTable.SelectWhere("user_id=$1", id)
}
+76
View File
@@ -0,0 +1,76 @@
package llm
import (
"context"
"fmt"
anyllm "github.com/mozilla-ai/any-llm-go"
"github.com/mozilla-ai/any-llm-go/providers/anthropic"
"github.com/mozilla-ai/any-llm-go/providers/ollama"
"github.com/mozilla-ai/any-llm-go/providers/openai"
"trankilou.fr/lassistanoque/backend/internal/domain"
)
var providerTypes = []domain.Item{
{ID: "anthropic", Text: "Anthropic"},
{ID: "openai", Text: "OpenAI"},
{ID: "ollama", Text: "Ollama"},
{ID: "openaicomp", Text: "OpenAI compatible"},
{ID: "openrouter", Text: "Openrouter"},
}
type AnyLLMEngine struct {
}
func NewAnyLLMEngine() *AnyLLMEngine {
return &AnyLLMEngine{}
}
func (e *AnyLLMEngine) ListProviderTypes() []domain.Item {
return providerTypes
}
func providerFactory(provider *domain.Provider) (anyllm.Provider, error) {
switch provider.Type {
case "ollama":
return ollama.New(
anyllm.WithBaseURL(provider.URL),
)
case "openai":
return openai.New(
anyllm.WithAPIKey(provider.APIKey),
)
case "openaicomp":
return openai.New(
anyllm.WithBaseURL(provider.URL),
anyllm.WithAPIKey(provider.APIKey),
)
case "anthropic":
return anthropic.New(
anyllm.WithAPIKey(provider.APIKey),
)
}
return nil, fmt.Errorf("unknown provider type: %s", provider.Type)
}
func (e *AnyLLMEngine) ListModelsFromProvider(ctx context.Context, provider *domain.Provider) ([]string, error) {
models := make([]string, 0)
prov, err := providerFactory(provider)
if err != nil {
return nil, err
}
if lister, ok := prov.(anyllm.ModelLister); ok {
response, err := lister.ListModels(ctx)
if err != nil {
return nil, err
}
for _, m := range response.Data {
models = append(models, m.ID)
}
} else {
models = append(models, "Default")
}
return models, nil
}
+26
View File
@@ -0,0 +1,26 @@
package domain
import "context"
type LLMModelDetail struct {
ParentModel string `json:"parent_model"`
Format string `json:"format"`
Family string `json:"family"`
Families []string `json:"families"`
ParameterSize string `json:"parameter_size"`
QuantizationLevel string `json:"quantization_level"`
}
type LLMModel struct {
Name string `json:"name"`
Model string `json:"model"`
ModifiedAt int64 `json:"modified_at"`
Size int64 `json:"size"`
Digest string `json:"digest"`
Detail *LLMModelDetail `json:"detail"`
}
type LLMEngine interface {
ListProviderTypes() []Item
ListModelsFromProvider(ctx context.Context, provider *Provider) ([]string, error)
}
+24
View File
@@ -0,0 +1,24 @@
package domain
import "time"
type Provider struct {
ID string `db:"id" json:"id"`
TeamID string `db:"team_id" json:"teamId"`
Type string `db:"type" json:"type"`
Name string `db:"name" json:"name"`
APIKey string `db:"key" json:"apiKey"`
URL string `db:"url" json:"baseUrl"`
Models string `db:"models" json:"models"`
DateCreated time.Time `db:"_date_created" json:"_date_created"`
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
VersionId string `db:"_version" json:"_version"`
}
type ProviderRepository interface {
ListProviders(userID string, teamID string) ([]*Provider, error)
GetProvider(userID string, teamID string, id string) (*Provider, error)
CreateProvider(userID string, provider *Provider) (*Provider, error)
UpdateProvider(userID string, provider *Provider) (*Provider, error)
DeleteProvider(userID string, teamID string, id string) error
}
+6
View File
@@ -0,0 +1,6 @@
package domain
type Item struct {
ID string `json:"id"`
Text string `json:"text"`
}
+14 -15
View File
@@ -15,27 +15,27 @@ type User struct {
Theme string `db:"theme" json:"theme"`
Lang string `db:"lang" json:"lang"`
Timezone string `db:"timezone" json:"timezone"`
DateCreated time.Time `db:"_date_created" json:"_date_created"`
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
DateCreated time.Time `db:"_date_created" json:"_dateCreated"`
DateUpdated *time.Time `db:"_date_updated" json:"_dateUpdated"`
VersionId string `db:"_version" json:"_version"`
}
type Team struct {
ID string `db:"id" json:"id"`
Label string `db:"label" json:"label"`
DefaultModelID *string `db:"default_model_id" json:"default_model_id"`
DateCreated time.Time `db:"_date_created" json:"_date_created"`
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
DefaultModelID *string `db:"default_model_id" json:"defaultModelId"`
DateCreated time.Time `db:"_date_created" json:"_dateCreated"`
DateUpdated *time.Time `db:"_date_updated" json:"_dateUpdated"`
VersionId string `db:"_version" json:"_version"`
}
type UserTeam struct {
ID string `db:"id" json:"id"`
UserID string `db:"user_id" json:"user_id"`
TeamID string `db:"team_id" json:"team_id"`
UserID string `db:"user_id" json:"userId"`
TeamID string `db:"team_id" json:"teamId"`
Administrator bool `db:"administrator" json:"administrator"`
DateCreated time.Time `db:"_date_created" json:"_date_created"`
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
DateCreated time.Time `db:"_date_created" json:"_dateCreated"`
DateUpdated *time.Time `db:"_date_updated" json:"_dateUpdated"`
VersionId string `db:"_version" json:"_version"`
}
@@ -44,8 +44,8 @@ type UserAddress struct {
UserID string `db:"user_id" json:"user_id"`
Type string `db:"type" json:"type"`
Address string `db:"address" json:"address"`
DateCreated time.Time `db:"_date_created" json:"_date_created"`
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
DateCreated time.Time `db:"_date_created" json:"_dateCreated"`
DateUpdated *time.Time `db:"_date_updated" json:"_dateUpdated"`
VersionId string `db:"_version" json:"_version"`
}
@@ -55,8 +55,9 @@ type TeamRelation struct {
}
type UserWithTeams struct {
User *User `json:"user"`
Teams []*TeamRelation `json:"teams"`
User *User `json:"user"`
UserTeams []*UserTeam `json:"user_teams"`
Teams []*Team `json:"teams"`
}
func AddressTypes() []string {
@@ -88,8 +89,6 @@ type UserRepository interface {
UpdateUserTeam(userid string, team *UserTeam) (*UserTeam, error)
DeleteUserTeam(userid string, teamid string) error
PopulateUserWithTeams(user *User) (*UserWithTeams, error)
ListUserAddresses(userid string) ([]*UserAddress, error)
GetUserAddress(addrID string) (*UserAddress, error)
CreateUserAddress(addr *UserAddress) (*UserAddress, error)
+123
View File
@@ -0,0 +1,123 @@
package handlers
import (
"net/http"
"github.com/labstack/echo/v5"
"trankilou.fr/lassistanoque/backend/internal/domain"
"trankilou.fr/lassistanoque/backend/internal/service/auth"
"trankilou.fr/lassistanoque/backend/internal/service/provider"
)
func NewModelGroup(prefix string, e *echo.Group, service *provider.Service, middlewares ...echo.MiddlewareFunc) *echo.Group {
modelHandler := &ModelHandler{
providerService: service,
}
model := e.Group(prefix, middlewares...)
model.GET("/providerTypes", modelHandler.ListProviderTypes)
model.GET("/:space", modelHandler.ListProviders)
model.POST("/:space/avalable-models", modelHandler.ListAvailableModels)
model.GET("/:space/:provider", modelHandler.GetProvider)
model.PUT("/:space", modelHandler.UpdateProvider)
model.POST("/:space", modelHandler.CreateProvider)
model.DELETE("/:space/:provider", modelHandler.DeleteProvider)
return model
}
type ModelHandler struct {
providerService *provider.Service
}
func (h *ModelHandler) ListProviderTypes(c *echo.Context) error {
return c.JSON(http.StatusOK, h.providerService.ListProviderTypes())
}
func (h *ModelHandler) ListProviders(c *echo.Context) error {
userID := c.Get(auth.ContextUserIDKey).(string)
teamID := c.Param("space")
providers, err := h.providerService.ListProviders(userID, teamID)
if err != nil {
c.Logger().Error("error listing providers: %s", err)
return echo.NewHTTPError(http.StatusBadRequest, "error listing providers")
}
return c.JSON(http.StatusOK, providers)
}
func (h *ModelHandler) GetProvider(c *echo.Context) error {
userID := c.Get(auth.ContextUserIDKey).(string)
teamID := c.Param("space")
providerID := c.Param("provider")
provider, err := h.providerService.GetProvider(userID, teamID, providerID)
if err != nil {
c.Logger().Error("error getting provider: %s", err)
return echo.NewHTTPError(http.StatusBadRequest, "error getting provider")
}
return c.JSON(http.StatusOK, provider)
}
func (h *ModelHandler) CreateProvider(c *echo.Context) error {
userID := c.Get(auth.ContextUserIDKey).(string)
teamID := c.Param("space")
var provider domain.Provider
err := c.Bind(&provider)
if err != nil {
c.Logger().Error("error binding provider: %s", err)
return echo.NewHTTPError(http.StatusBadRequest, "error binding provider")
}
provider.TeamID = teamID
updprovider, err := h.providerService.CreateProvider(userID, &provider)
if err != nil {
c.Logger().Error("error creating provider: %s", err)
return echo.NewHTTPError(http.StatusBadRequest, "error creating provider")
}
return c.JSON(http.StatusOK, updprovider)
}
func (h *ModelHandler) UpdateProvider(c *echo.Context) error {
userID := c.Get(auth.ContextUserIDKey).(string)
teamID := c.Param("space")
var provider domain.Provider
err := c.Bind(&provider)
if err != nil {
c.Logger().Error("error binding provider: %s", err)
return echo.NewHTTPError(http.StatusBadRequest, "error binding provider")
}
provider.TeamID = teamID
updprovider, err := h.providerService.UpdateProvider(userID, &provider)
if err != nil {
c.Logger().Error("error updating provider: %s", err)
return echo.NewHTTPError(http.StatusBadRequest, "error creating provider")
}
return c.JSON(http.StatusOK, updprovider)
}
func (h *ModelHandler) DeleteProvider(c *echo.Context) error {
userID := c.Get(auth.ContextUserIDKey).(string)
teamID := c.Param("space")
providerID := c.Param("provider")
err := h.providerService.DeleteProvider(userID, teamID, providerID)
if err != nil {
c.Logger().Error("error creating provider: %s", err)
return echo.NewHTTPError(http.StatusBadRequest, "error creating provider")
}
return c.JSON(http.StatusOK, providerID)
}
func (h *ModelHandler) ListAvailableModels(c *echo.Context) error {
teamID := c.Param("space")
var provider domain.Provider
err := c.Bind(&provider)
if err != nil {
c.Logger().Error("error binding provider: %s", err)
return echo.NewHTTPError(http.StatusBadRequest, "error binding provider")
}
provider.TeamID = teamID
list, err := h.providerService.ListAvailableModels(c.Request().Context(), &provider)
if err != nil {
c.Logger().Error("error listing models: %s", err)
return echo.NewHTTPError(http.StatusBadRequest, "error creating provider")
}
return c.JSON(http.StatusOK, list)
}
+1 -2
View File
@@ -13,7 +13,6 @@ func NewUserGroup(prefix string, e *echo.Group, service *user.Service, middlewar
userHandler := &UserHandler{
userService: service,
}
_ = userHandler
auth := e.Group(prefix, middlewares...)
auth.GET("/me", userHandler.Me)
auth.PUT("/:id", userHandler.Update)
@@ -30,7 +29,7 @@ type UserHandler struct {
func (h UserHandler) Me(c *echo.Context) error {
userID := c.Get(auth.ContextUserIDKey).(string)
user, err := h.userService.GetUser(userID)
user, err := h.userService.GetUserWithTeams(userID)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
}
+7 -4
View File
@@ -11,6 +11,7 @@ import (
"trankilou.fr/lassistanoque/backend/internal/config"
"trankilou.fr/lassistanoque/backend/internal/http/handlers"
"trankilou.fr/lassistanoque/backend/internal/service/auth"
"trankilou.fr/lassistanoque/backend/internal/service/provider"
"trankilou.fr/lassistanoque/backend/internal/service/storage"
"trankilou.fr/lassistanoque/backend/internal/service/user"
)
@@ -32,10 +33,11 @@ type Router struct {
}
type Dependencies struct {
StorageService *storage.Service
AuthService *auth.Service
UserService *user.Service
TokenManager auth.TokenManager
StorageService *storage.Service
AuthService *auth.Service
UserService *user.Service
ProviderService *provider.Service
TokenManager auth.TokenManager
}
func NewRouter(deps Dependencies) *Router {
@@ -71,6 +73,7 @@ func NewRouter(deps Dependencies) *Router {
_ = handlers.NewAuthGroup("/auth", api, deps.AuthService)
_ = handlers.NewUserGroup("/user", api, deps.UserService, deps.TokenManager.TokenMiddleware)
_ = handlers.NewMiscGroup("/misc", api, deps.TokenManager.TokenMiddleware)
_ = handlers.NewModelGroup("/provider", api, deps.ProviderService, deps.TokenManager.TokenMiddleware)
return &Router{
echo: e,
@@ -0,0 +1,76 @@
package provider
import (
"context"
"fmt"
"trankilou.fr/lassistanoque/backend/internal/domain"
)
type Service struct {
repo domain.ProviderRepository
repoUser domain.UserRepository
llmengine domain.LLMEngine
}
func NewService(
repo domain.ProviderRepository,
repoUser domain.UserRepository,
llmengine domain.LLMEngine,
) *Service {
return &Service{
repo,
repoUser,
llmengine,
}
}
func (s *Service) ListProviderTypes() []domain.Item {
return s.llmengine.ListProviderTypes()
}
func (s *Service) ListProviders(userID string, teamID string) ([]*domain.Provider, error) {
return s.repo.ListProviders(userID, teamID)
}
func (s *Service) GetProvider(userID string, teamID string, id string) (*domain.Provider, error) {
return s.repo.GetProvider(userID, teamID, id)
}
func (s *Service) CreateProvider(userID string, provider *domain.Provider) (*domain.Provider, error) {
if _, err := s.repoUser.FindUserTeam(userID, provider.TeamID); err != nil {
return nil, fmt.Errorf("error finding user in team: %s", err)
}
return s.repo.CreateProvider(userID, provider)
}
func (s *Service) UpdateProvider(userID string, provider *domain.Provider) (*domain.Provider, error) {
if _, err := s.repoUser.FindUserTeam(userID, provider.TeamID); err != nil {
return nil, fmt.Errorf("error finding user in team: %s", err)
}
if _, err := s.repo.GetProvider(userID, provider.TeamID, provider.ID); err != nil {
return nil, fmt.Errorf("error finding provider: %s", err)
}
return s.repo.UpdateProvider(userID, provider)
}
func (s *Service) DeleteProvider(userID string, teamID string, id string) error {
if _, err := s.repoUser.FindUserTeam(userID, teamID); err != nil {
return fmt.Errorf("error finding user in team: %s", err)
}
if _, err := s.repo.GetProvider(userID, teamID, id); err != nil {
return fmt.Errorf("error finding provider: %s", err)
}
return s.repo.DeleteProvider(userID, teamID, id)
}
func (s *Service) ListAvailableModels(ctx context.Context, provider *domain.Provider) ([]string, error) {
return s.llmengine.ListModelsFromProvider(ctx, provider)
}
+24
View File
@@ -27,6 +27,30 @@ func (s *Service) GetUser(id string) (*domain.User, error) {
return user, nil
}
func (s *Service) GetUserWithTeams(id string) (*domain.UserWithTeams, error) {
u, err := s.GetUser(id)
if err != nil {
return nil, err
}
teams, err := s.repo.ListTeams(u.ID)
if err != nil {
return nil, err
}
userTeams, err := s.repo.ListUserTeams(u.ID)
if err != nil {
return nil, err
}
ut := &domain.UserWithTeams{
User: u,
UserTeams: userTeams,
Teams: teams,
}
return ut, nil
}
func (s *Service) UpdateUser(updUser *domain.User, fromAdmin bool) (*domain.User, error) {
user, err := s.repo.FindUser(updUser.ID)
if err != nil {