Compare commits
4
Commits
50d1ba630e
...
4d72952b2c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d72952b2c | ||
|
|
357bf9b41c | ||
|
|
21e96702a0 | ||
|
|
d2a153536f |
No files matched your search
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
@@ -8,9 +8,11 @@ import (
|
|||||||
"trankilou.fr/lassistanoque/backend/internal/adapter/auth/password"
|
"trankilou.fr/lassistanoque/backend/internal/adapter/auth/password"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/adapter/database"
|
"trankilou.fr/lassistanoque/backend/internal/adapter/database"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/adapter/file"
|
"trankilou.fr/lassistanoque/backend/internal/adapter/file"
|
||||||
|
"trankilou.fr/lassistanoque/backend/internal/adapter/llm"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/adapter/security"
|
"trankilou.fr/lassistanoque/backend/internal/adapter/security"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/http"
|
"trankilou.fr/lassistanoque/backend/internal/http"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/auth"
|
"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/storage"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/user"
|
"trankilou.fr/lassistanoque/backend/internal/service/user"
|
||||||
)
|
)
|
||||||
@@ -56,6 +58,7 @@ func runServe() {
|
|||||||
// Adapters
|
// Adapters
|
||||||
tokenManager := security.NewJwtTokenManager(12*time.Hour, 7*24*time.Hour, "lassistanoque")
|
tokenManager := security.NewJwtTokenManager(12*time.Hour, 7*24*time.Hour, "lassistanoque")
|
||||||
pwdAuth := password.NewPasswordAuthenticator(tokenManager, db.UserRepository())
|
pwdAuth := password.NewPasswordAuthenticator(tokenManager, db.UserRepository())
|
||||||
|
llmEngine := llm.NewAnyLLMEngine()
|
||||||
|
|
||||||
// services
|
// services
|
||||||
authService := auth.NewService(
|
authService := auth.NewService(
|
||||||
@@ -67,6 +70,7 @@ func runServe() {
|
|||||||
)
|
)
|
||||||
userService := user.NewService(db.UserRepository())
|
userService := user.NewService(db.UserRepository())
|
||||||
storageService := storage.NewService(store)
|
storageService := storage.NewService(store)
|
||||||
|
providerService := provider.NewService(db.ProviderRepository(), db.UserRepository(), llmEngine)
|
||||||
|
|
||||||
// http server
|
// http server
|
||||||
router := http.NewRouter(http.Dependencies{
|
router := http.NewRouter(http.Dependencies{
|
||||||
@@ -74,6 +78,7 @@ func runServe() {
|
|||||||
AuthService: authService,
|
AuthService: authService,
|
||||||
UserService: userService,
|
UserService: userService,
|
||||||
TokenManager: tokenManager,
|
TokenManager: tokenManager,
|
||||||
|
ProviderService: providerService,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err := router.Start(); err != nil {
|
if err := router.Start(); err != nil {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type Database interface {
|
|||||||
SettingsRepository() domain.SettingsRepository
|
SettingsRepository() domain.SettingsRepository
|
||||||
UserRepository() domain.UserRepository
|
UserRepository() domain.UserRepository
|
||||||
FileRepository() domain.FileRepository
|
FileRepository() domain.FileRepository
|
||||||
|
ProviderRepository() domain.ProviderRepository
|
||||||
Migrate() error
|
Migrate() error
|
||||||
Close()
|
Close()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,3 +67,7 @@ func (db *TursoDB) SettingsRepository() domain.SettingsRepository {
|
|||||||
func (db *TursoDB) FileRepository() domain.FileRepository {
|
func (db *TursoDB) FileRepository() domain.FileRepository {
|
||||||
return NewTursoFileRepository(db.DB)
|
return NewTursoFileRepository(db.DB)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *TursoDB) ProviderRepository() domain.ProviderRepository {
|
||||||
|
return NewTursoModelRepository(db.DB)
|
||||||
|
}
|
||||||
@@ -59,7 +59,6 @@ func (fr *TursoFileRepository) Upload(reader io.Reader, path string, name string
|
|||||||
ContentType: contentType,
|
ContentType: contentType,
|
||||||
VersionId: utility.GenID(),
|
VersionId: utility.GenID(),
|
||||||
DateCreated: time.Now(),
|
DateCreated: time.Now(),
|
||||||
DateUpdated: time.Now(),
|
|
||||||
Content: bytes,
|
Content: bytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,4 +11,3 @@ drop table tools;
|
|||||||
drop table tasks;
|
drop table tasks;
|
||||||
drop table history;
|
drop table history;
|
||||||
drop table files;
|
drop table files;
|
||||||
drop table share;
|
|
||||||
@@ -1,25 +1,16 @@
|
|||||||
create table providers (
|
create table providers (
|
||||||
id text not null primary key,
|
id text not null primary key,
|
||||||
team_id text not null,
|
team_id text not null,
|
||||||
|
type text not null,
|
||||||
name text not null,
|
name text not null,
|
||||||
key text,
|
key text,
|
||||||
url text,
|
url text,
|
||||||
|
models text,
|
||||||
_date_created numeric not null default current_timestamp,
|
_date_created numeric not null default current_timestamp,
|
||||||
_date_updated numeric,
|
_date_updated numeric,
|
||||||
_version text not null
|
_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 (
|
create table settings (
|
||||||
id text not null primary key,
|
id text not null primary key,
|
||||||
@@ -52,7 +43,8 @@ create table users (
|
|||||||
picture text not null default '',
|
picture text not null default '',
|
||||||
enabled numeric default true,
|
enabled numeric default true,
|
||||||
theme text default 'auto',
|
theme text default 'auto',
|
||||||
lang text default 'en',
|
lang text default 'fr',
|
||||||
|
timezone text default 'Europe/Paris',
|
||||||
_date_created numeric not null default current_timestamp,
|
_date_created numeric not null default current_timestamp,
|
||||||
_date_updated numeric,
|
_date_updated numeric,
|
||||||
_version text not null
|
_version text not null
|
||||||
@@ -149,14 +141,3 @@ create table files (
|
|||||||
_date_updated numeric ,
|
_date_updated numeric ,
|
||||||
_version text not null
|
_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) {
|
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) {
|
func (ur *TursoUserRepository) CreateTeam(userid string, team *domain.Team) (*domain.Team, error) {
|
||||||
@@ -123,21 +123,20 @@ func (ur *TursoUserRepository) DeleteUserTeam(userid string, teamid string) erro
|
|||||||
return ur.UserTeamTable.Delete(teamid)
|
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) {
|
func (ur *TursoUserRepository) ListUserAddresses(id string) ([]*domain.UserAddress, error) {
|
||||||
return ur.AddressTable.SelectWhere("user_id=$1", id)
|
return ur.AddressTable.SelectWhere("user_id=$1", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ur *TursoUserRepository) CreateUserAddress(userid string, addr *domain.UserAddress) (*domain.UserAddress, error) {
|
func (ur *TursoUserRepository) GetUserAddress(addrID string) (*domain.UserAddress, error) {
|
||||||
addr.UserID = userid
|
return ur.AddressTable.Get(addrID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ur *TursoUserRepository) CreateUserAddress(addr *domain.UserAddress) (*domain.UserAddress, error) {
|
||||||
return ur.AddressTable.Insert(addr)
|
return ur.AddressTable.Insert(addr)
|
||||||
}
|
}
|
||||||
func (ur *TursoUserRepository) UpdateUserAddress(userid string, addr *domain.UserAddress) (*domain.UserAddress, error) {
|
func (ur *TursoUserRepository) UpdateUserAddress(addr *domain.UserAddress) (*domain.UserAddress, error) {
|
||||||
return ur.AddressTable.Update(addr)
|
return ur.AddressTable.Update(addr)
|
||||||
}
|
}
|
||||||
func (ur *TursoUserRepository) DeleteUserAddress(userid string, addrID string) error {
|
func (ur *TursoUserRepository) DeleteUserAddress(addrID string) error {
|
||||||
return ur.AddressTable.DeleteWhere("id=$1 and user_id=$2", addrID, userid)
|
return ur.AddressTable.DeleteWhere("id=$1", addrID)
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -117,8 +117,8 @@ func (tm *JwtTokenManager) TokenMiddleware(next echo.HandlerFunc) echo.HandlerFu
|
|||||||
|
|
||||||
// Rend les infos disponibles aux handlers suivants via c.Get(...)
|
// Rend les infos disponibles aux handlers suivants via c.Get(...)
|
||||||
c.Set(auth.ContextUserIDKey, claims.ID)
|
c.Set(auth.ContextUserIDKey, claims.ID)
|
||||||
c.Set(auth.ContextEmailKey, claims.Email)
|
//c.Set(auth.ContextEmailKey, claims.Email)
|
||||||
c.Set(auth.ContextNameKey, claims.Firstname+" "+claims.Lastname)
|
//c.Set(auth.ContextNameKey, claims.Firstname+" "+claims.Lastname)
|
||||||
|
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ type File struct {
|
|||||||
StorageFilename string `db:"storage_filename" json:"-"`
|
StorageFilename string `db:"storage_filename" json:"-"`
|
||||||
Content []byte `db:"content" json:"-"`
|
Content []byte `db:"content" json:"-"`
|
||||||
DateCreated time.Time `db:"date_created" json:"-"`
|
DateCreated time.Time `db:"date_created" json:"-"`
|
||||||
DateUpdated time.Time `db:"date_updated" json:"-"`
|
DateUpdated *time.Time `db:"date_updated" json:"-"`
|
||||||
VersionId string `db:"_version" json:"-"`
|
VersionId string `db:"_version" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type Item struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ type Settings struct {
|
|||||||
DefaultLang string `db:"default_lang"`
|
DefaultLang string `db:"default_lang"`
|
||||||
RegisterEnabled bool `db:"register_enabled"`
|
RegisterEnabled bool `db:"register_enabled"`
|
||||||
PasswordEnabled bool `db:"password_enabled"`
|
PasswordEnabled bool `db:"password_enabled"`
|
||||||
DateCreated *time.Time `db:"_date_created" json:"_date_created"`
|
DateCreated time.Time `db:"_date_created" json:"_date_created"`
|
||||||
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
|
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
|
||||||
VersionId string `db:"_version" json:"_version"`
|
VersionId string `db:"_version" json:"_version"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,27 +14,28 @@ type User struct {
|
|||||||
Enabled bool `db:"enabled" json:"enabled"`
|
Enabled bool `db:"enabled" json:"enabled"`
|
||||||
Theme string `db:"theme" json:"theme"`
|
Theme string `db:"theme" json:"theme"`
|
||||||
Lang string `db:"lang" json:"lang"`
|
Lang string `db:"lang" json:"lang"`
|
||||||
DateCreated time.Time `db:"_date_created" json:"_date_created"`
|
Timezone string `db:"timezone" json:"timezone"`
|
||||||
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"`
|
VersionId string `db:"_version" json:"_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Team struct {
|
type Team struct {
|
||||||
ID string `db:"id" json:"id"`
|
ID string `db:"id" json:"id"`
|
||||||
Label string `db:"label" json:"label"`
|
Label string `db:"label" json:"label"`
|
||||||
DefaultModelID *string `db:"default_model_id" json:"default_model_id"`
|
DefaultModelID *string `db:"default_model_id" json:"defaultModelId"`
|
||||||
DateCreated *time.Time `db:"_date_created" json:"_date_created"`
|
DateCreated time.Time `db:"_date_created" json:"_dateCreated"`
|
||||||
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
|
DateUpdated *time.Time `db:"_date_updated" json:"_dateUpdated"`
|
||||||
VersionId string `db:"_version" json:"_version"`
|
VersionId string `db:"_version" json:"_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserTeam struct {
|
type UserTeam struct {
|
||||||
ID string `db:"id" json:"id"`
|
ID string `db:"id" json:"id"`
|
||||||
UserID string `db:"user_id" json:"user_id"`
|
UserID string `db:"user_id" json:"userId"`
|
||||||
TeamID string `db:"team_id" json:"team_id"`
|
TeamID string `db:"team_id" json:"teamId"`
|
||||||
Administrator bool `db:"administrator" json:"administrator"`
|
Administrator bool `db:"administrator" json:"administrator"`
|
||||||
DateCreated *time.Time `db:"_date_created" json:"_date_created"`
|
DateCreated time.Time `db:"_date_created" json:"_dateCreated"`
|
||||||
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
|
DateUpdated *time.Time `db:"_date_updated" json:"_dateUpdated"`
|
||||||
VersionId string `db:"_version" json:"_version"`
|
VersionId string `db:"_version" json:"_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,9 +43,9 @@ type UserAddress struct {
|
|||||||
ID string `db:"id" json:"id"`
|
ID string `db:"id" json:"id"`
|
||||||
UserID string `db:"user_id" json:"user_id"`
|
UserID string `db:"user_id" json:"user_id"`
|
||||||
Type string `db:"type" json:"type"`
|
Type string `db:"type" json:"type"`
|
||||||
Address string `db:"address_id" json:"address"`
|
Address string `db:"address" json:"address"`
|
||||||
DateCreated *time.Time `db:"_date_created" json:"_date_created"`
|
DateCreated time.Time `db:"_date_created" json:"_dateCreated"`
|
||||||
DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"`
|
DateUpdated *time.Time `db:"_date_updated" json:"_dateUpdated"`
|
||||||
VersionId string `db:"_version" json:"_version"`
|
VersionId string `db:"_version" json:"_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +56,17 @@ type TeamRelation struct {
|
|||||||
|
|
||||||
type UserWithTeams struct {
|
type UserWithTeams struct {
|
||||||
User *User `json:"user"`
|
User *User `json:"user"`
|
||||||
Teams []*TeamRelation `json:"teams"`
|
UserTeams []*UserTeam `json:"user_teams"`
|
||||||
|
Teams []*Team `json:"teams"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddressTypes() []string {
|
||||||
|
return []string{
|
||||||
|
"email",
|
||||||
|
"telegram",
|
||||||
|
"whatsapp",
|
||||||
|
"signal",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserRepository interface {
|
type UserRepository interface {
|
||||||
@@ -78,10 +89,9 @@ type UserRepository interface {
|
|||||||
UpdateUserTeam(userid string, team *UserTeam) (*UserTeam, error)
|
UpdateUserTeam(userid string, team *UserTeam) (*UserTeam, error)
|
||||||
DeleteUserTeam(userid string, teamid string) error
|
DeleteUserTeam(userid string, teamid string) error
|
||||||
|
|
||||||
PopulateUserWithTeams(user *User) (*UserWithTeams, error)
|
|
||||||
|
|
||||||
ListUserAddresses(userid string) ([]*UserAddress, error)
|
ListUserAddresses(userid string) ([]*UserAddress, error)
|
||||||
CreateUserAddress(userid string, addr *UserAddress) (*UserAddress, error)
|
GetUserAddress(addrID string) (*UserAddress, error)
|
||||||
UpdateUserAddress(userid string, addr *UserAddress) (*UserAddress, error)
|
CreateUserAddress(addr *UserAddress) (*UserAddress, error)
|
||||||
DeleteUserAddress(userid string, addrID string) error
|
UpdateUserAddress(addr *UserAddress) (*UserAddress, error)
|
||||||
|
DeleteUserAddress(addrID string) error
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,623 @@
|
|||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MiscHandler struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMiscGroup(prefix string, e *echo.Group, middlewares ...echo.MiddlewareFunc) *echo.Group {
|
||||||
|
miscHandler := &MiscHandler{}
|
||||||
|
misc := e.Group(prefix, middlewares...)
|
||||||
|
misc.GET("/timezones", miscHandler.Timezones)
|
||||||
|
return misc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h MiscHandler) Timezones(c *echo.Context) error {
|
||||||
|
|
||||||
|
tz := []string{
|
||||||
|
"Africa/Abidjan",
|
||||||
|
"Africa/Accra",
|
||||||
|
"Africa/Addis_Ababa",
|
||||||
|
"Africa/Algiers",
|
||||||
|
"Africa/Asmara",
|
||||||
|
"Africa/Asmera",
|
||||||
|
"Africa/Bamako",
|
||||||
|
"Africa/Bangui",
|
||||||
|
"Africa/Banjul",
|
||||||
|
"Africa/Bissau",
|
||||||
|
"Africa/Blantyre",
|
||||||
|
"Africa/Brazzaville",
|
||||||
|
"Africa/Bujumbura",
|
||||||
|
"Africa/Cairo",
|
||||||
|
"Africa/Casablanca",
|
||||||
|
"Africa/Ceuta",
|
||||||
|
"Africa/Conakry",
|
||||||
|
"Africa/Dakar",
|
||||||
|
"Africa/Dar_es_Salaam",
|
||||||
|
"Africa/Djibouti",
|
||||||
|
"Africa/Douala",
|
||||||
|
"Africa/El_Aaiun",
|
||||||
|
"Africa/Freetown",
|
||||||
|
"Africa/Gaborone",
|
||||||
|
"Africa/Harare",
|
||||||
|
"Africa/Johannesburg",
|
||||||
|
"Africa/Juba",
|
||||||
|
"Africa/Kampala",
|
||||||
|
"Africa/Khartoum",
|
||||||
|
"Africa/Kigali",
|
||||||
|
"Africa/Kinshasa",
|
||||||
|
"Africa/Lagos",
|
||||||
|
"Africa/Libreville",
|
||||||
|
"Africa/Lome",
|
||||||
|
"Africa/Luanda",
|
||||||
|
"Africa/Lubumbashi",
|
||||||
|
"Africa/Lusaka",
|
||||||
|
"Africa/Malabo",
|
||||||
|
"Africa/Maputo",
|
||||||
|
"Africa/Maseru",
|
||||||
|
"Africa/Mbabane",
|
||||||
|
"Africa/Mogadishu",
|
||||||
|
"Africa/Monrovia",
|
||||||
|
"Africa/Nairobi",
|
||||||
|
"Africa/Ndjamena",
|
||||||
|
"Africa/Niamey",
|
||||||
|
"Africa/Nouakchott",
|
||||||
|
"Africa/Ouagadougou",
|
||||||
|
"Africa/Porto-Novo",
|
||||||
|
"Africa/Sao_Tome",
|
||||||
|
"Africa/Timbuktu",
|
||||||
|
"Africa/Tripoli",
|
||||||
|
"Africa/Tunis",
|
||||||
|
"Africa/Windhoek",
|
||||||
|
"America/Adak",
|
||||||
|
"America/Anchorage",
|
||||||
|
"America/Anguilla",
|
||||||
|
"America/Antigua",
|
||||||
|
"America/Araguaina",
|
||||||
|
"America/Argentina/Buenos_Aires",
|
||||||
|
"America/Argentina/Catamarca",
|
||||||
|
"America/Argentina/ComodRivadavia",
|
||||||
|
"America/Argentina/Cordoba",
|
||||||
|
"America/Argentina/Jujuy",
|
||||||
|
"America/Argentina/La_Rioja",
|
||||||
|
"America/Argentina/Mendoza",
|
||||||
|
"America/Argentina/Rio_Gallegos",
|
||||||
|
"America/Argentina/Salta",
|
||||||
|
"America/Argentina/San_Juan",
|
||||||
|
"America/Argentina/San_Luis",
|
||||||
|
"America/Argentina/Tucuman",
|
||||||
|
"America/Argentina/Ushuaia",
|
||||||
|
"America/Aruba",
|
||||||
|
"America/Asuncion",
|
||||||
|
"America/Atikokan",
|
||||||
|
"America/Atka",
|
||||||
|
"America/Bahia",
|
||||||
|
"America/Bahia_Banderas",
|
||||||
|
"America/Barbados",
|
||||||
|
"America/Belem",
|
||||||
|
"America/Belize",
|
||||||
|
"America/Blanc-Sablon",
|
||||||
|
"America/Boa_Vista",
|
||||||
|
"America/Bogota",
|
||||||
|
"America/Boise",
|
||||||
|
"America/Buenos_Aires",
|
||||||
|
"America/Cambridge_Bay",
|
||||||
|
"America/Campo_Grande",
|
||||||
|
"America/Cancun",
|
||||||
|
"America/Caracas",
|
||||||
|
"America/Catamarca",
|
||||||
|
"America/Cayenne",
|
||||||
|
"America/Cayman",
|
||||||
|
"America/Chicago",
|
||||||
|
"America/Chihuahua",
|
||||||
|
"America/Ciudad_Juarez",
|
||||||
|
"America/Coral_Harbour",
|
||||||
|
"America/Cordoba",
|
||||||
|
"America/Costa_Rica",
|
||||||
|
"America/Coyhaique",
|
||||||
|
"America/Creston",
|
||||||
|
"America/Cuiaba",
|
||||||
|
"America/Curacao",
|
||||||
|
"America/Danmarkshavn",
|
||||||
|
"America/Dawson",
|
||||||
|
"America/Dawson_Creek",
|
||||||
|
"America/Denver",
|
||||||
|
"America/Detroit",
|
||||||
|
"America/Dominica",
|
||||||
|
"America/Edmonton",
|
||||||
|
"America/Eirunepe",
|
||||||
|
"America/El_Salvador",
|
||||||
|
"America/Ensenada",
|
||||||
|
"America/Fort_Nelson",
|
||||||
|
"America/Fort_Wayne",
|
||||||
|
"America/Fortaleza",
|
||||||
|
"America/Glace_Bay",
|
||||||
|
"America/Godthab",
|
||||||
|
"America/Goose_Bay",
|
||||||
|
"America/Grand_Turk",
|
||||||
|
"America/Grenada",
|
||||||
|
"America/Guadeloupe",
|
||||||
|
"America/Guatemala",
|
||||||
|
"America/Guayaquil",
|
||||||
|
"America/Guyana",
|
||||||
|
"America/Halifax",
|
||||||
|
"America/Havana",
|
||||||
|
"America/Hermosillo",
|
||||||
|
"America/Indiana/Indianapolis",
|
||||||
|
"America/Indiana/Knox",
|
||||||
|
"America/Indiana/Marengo",
|
||||||
|
"America/Indiana/Petersburg",
|
||||||
|
"America/Indiana/Tell_City",
|
||||||
|
"America/Indiana/Vevay",
|
||||||
|
"America/Indiana/Vincennes",
|
||||||
|
"America/Indiana/Winamac",
|
||||||
|
"America/Indianapolis",
|
||||||
|
"America/Inuvik",
|
||||||
|
"America/Iqaluit",
|
||||||
|
"America/Jamaica",
|
||||||
|
"America/Jujuy",
|
||||||
|
"America/Juneau",
|
||||||
|
"America/Kentucky/Louisville",
|
||||||
|
"America/Kentucky/Monticello",
|
||||||
|
"America/Knox_IN",
|
||||||
|
"America/Kralendijk",
|
||||||
|
"America/La_Paz",
|
||||||
|
"America/Lima",
|
||||||
|
"America/Los_Angeles",
|
||||||
|
"America/Louisville",
|
||||||
|
"America/Lower_Princes",
|
||||||
|
"America/Maceio",
|
||||||
|
"America/Managua",
|
||||||
|
"America/Manaus",
|
||||||
|
"America/Marigot",
|
||||||
|
"America/Martinique",
|
||||||
|
"America/Matamoros",
|
||||||
|
"America/Mazatlan",
|
||||||
|
"America/Mendoza",
|
||||||
|
"America/Menominee",
|
||||||
|
"America/Merida",
|
||||||
|
"America/Metlakatla",
|
||||||
|
"America/Mexico_City",
|
||||||
|
"America/Miquelon",
|
||||||
|
"America/Moncton",
|
||||||
|
"America/Monterrey",
|
||||||
|
"America/Montevideo",
|
||||||
|
"America/Montreal",
|
||||||
|
"America/Montserrat",
|
||||||
|
"America/Nassau",
|
||||||
|
"America/New_York",
|
||||||
|
"America/Nipigon",
|
||||||
|
"America/Nome",
|
||||||
|
"America/Noronha",
|
||||||
|
"America/North_Dakota/Beulah",
|
||||||
|
"America/North_Dakota/Center",
|
||||||
|
"America/North_Dakota/New_Salem",
|
||||||
|
"America/Nuuk",
|
||||||
|
"America/Ojinaga",
|
||||||
|
"America/Panama",
|
||||||
|
"America/Pangnirtung",
|
||||||
|
"America/Paramaribo",
|
||||||
|
"America/Phoenix",
|
||||||
|
"America/Port-au-Prince",
|
||||||
|
"America/Port_of_Spain",
|
||||||
|
"America/Porto_Acre",
|
||||||
|
"America/Porto_Velho",
|
||||||
|
"America/Puerto_Rico",
|
||||||
|
"America/Punta_Arenas",
|
||||||
|
"America/Rainy_River",
|
||||||
|
"America/Rankin_Inlet",
|
||||||
|
"America/Recife",
|
||||||
|
"America/Regina",
|
||||||
|
"America/Resolute",
|
||||||
|
"America/Rio_Branco",
|
||||||
|
"America/Rosario",
|
||||||
|
"America/Santa_Isabel",
|
||||||
|
"America/Santarem",
|
||||||
|
"America/Santiago",
|
||||||
|
"America/Santo_Domingo",
|
||||||
|
"America/Sao_Paulo",
|
||||||
|
"America/Scoresbysund",
|
||||||
|
"America/Shiprock",
|
||||||
|
"America/Sitka",
|
||||||
|
"America/St_Barthelemy",
|
||||||
|
"America/St_Johns",
|
||||||
|
"America/St_Kitts",
|
||||||
|
"America/St_Lucia",
|
||||||
|
"America/St_Thomas",
|
||||||
|
"America/St_Vincent",
|
||||||
|
"America/Swift_Current",
|
||||||
|
"America/Tegucigalpa",
|
||||||
|
"America/Thule",
|
||||||
|
"America/Thunder_Bay",
|
||||||
|
"America/Tijuana",
|
||||||
|
"America/Toronto",
|
||||||
|
"America/Tortola",
|
||||||
|
"America/Vancouver",
|
||||||
|
"America/Virgin",
|
||||||
|
"America/Whitehorse",
|
||||||
|
"America/Winnipeg",
|
||||||
|
"America/Yakutat",
|
||||||
|
"America/Yellowknife",
|
||||||
|
"Antarctica/Casey",
|
||||||
|
"Antarctica/Davis",
|
||||||
|
"Antarctica/DumontDUrville",
|
||||||
|
"Antarctica/Macquarie",
|
||||||
|
"Antarctica/Mawson",
|
||||||
|
"Antarctica/McMurdo",
|
||||||
|
"Antarctica/Palmer",
|
||||||
|
"Antarctica/Rothera",
|
||||||
|
"Antarctica/South_Pole",
|
||||||
|
"Antarctica/Syowa",
|
||||||
|
"Antarctica/Troll",
|
||||||
|
"Antarctica/Vostok",
|
||||||
|
"Arctic/Longyearbyen",
|
||||||
|
"Asia/Aden",
|
||||||
|
"Asia/Almaty",
|
||||||
|
"Asia/Amman",
|
||||||
|
"Asia/Anadyr",
|
||||||
|
"Asia/Aqtau",
|
||||||
|
"Asia/Aqtobe",
|
||||||
|
"Asia/Ashgabat",
|
||||||
|
"Asia/Ashkhabad",
|
||||||
|
"Asia/Atyrau",
|
||||||
|
"Asia/Baghdad",
|
||||||
|
"Asia/Bahrain",
|
||||||
|
"Asia/Baku",
|
||||||
|
"Asia/Bangkok",
|
||||||
|
"Asia/Barnaul",
|
||||||
|
"Asia/Beirut",
|
||||||
|
"Asia/Bishkek",
|
||||||
|
"Asia/Brunei",
|
||||||
|
"Asia/Calcutta",
|
||||||
|
"Asia/Chita",
|
||||||
|
"Asia/Choibalsan",
|
||||||
|
"Asia/Chongqing",
|
||||||
|
"Asia/Chungking",
|
||||||
|
"Asia/Colombo",
|
||||||
|
"Asia/Dacca",
|
||||||
|
"Asia/Damascus",
|
||||||
|
"Asia/Dhaka",
|
||||||
|
"Asia/Dili",
|
||||||
|
"Asia/Dubai",
|
||||||
|
"Asia/Dushanbe",
|
||||||
|
"Asia/Famagusta",
|
||||||
|
"Asia/Gaza",
|
||||||
|
"Asia/Harbin",
|
||||||
|
"Asia/Hebron",
|
||||||
|
"Asia/Ho_Chi_Minh",
|
||||||
|
"Asia/Hong_Kong",
|
||||||
|
"Asia/Hovd",
|
||||||
|
"Asia/Irkutsk",
|
||||||
|
"Asia/Istanbul",
|
||||||
|
"Asia/Jakarta",
|
||||||
|
"Asia/Jayapura",
|
||||||
|
"Asia/Jerusalem",
|
||||||
|
"Asia/Kabul",
|
||||||
|
"Asia/Kamchatka",
|
||||||
|
"Asia/Karachi",
|
||||||
|
"Asia/Kashgar",
|
||||||
|
"Asia/Kathmandu",
|
||||||
|
"Asia/Katmandu",
|
||||||
|
"Asia/Khandyga",
|
||||||
|
"Asia/Kolkata",
|
||||||
|
"Asia/Krasnoyarsk",
|
||||||
|
"Asia/Kuala_Lumpur",
|
||||||
|
"Asia/Kuching",
|
||||||
|
"Asia/Kuwait",
|
||||||
|
"Asia/Macao",
|
||||||
|
"Asia/Macau",
|
||||||
|
"Asia/Magadan",
|
||||||
|
"Asia/Makassar",
|
||||||
|
"Asia/Manila",
|
||||||
|
"Asia/Muscat",
|
||||||
|
"Asia/Nicosia",
|
||||||
|
"Asia/Novokuznetsk",
|
||||||
|
"Asia/Novosibirsk",
|
||||||
|
"Asia/Omsk",
|
||||||
|
"Asia/Oral",
|
||||||
|
"Asia/Phnom_Penh",
|
||||||
|
"Asia/Pontianak",
|
||||||
|
"Asia/Pyongyang",
|
||||||
|
"Asia/Qatar",
|
||||||
|
"Asia/Qostanay",
|
||||||
|
"Asia/Qyzylorda",
|
||||||
|
"Asia/Rangoon",
|
||||||
|
"Asia/Riyadh",
|
||||||
|
"Asia/Saigon",
|
||||||
|
"Asia/Sakhalin",
|
||||||
|
"Asia/Samarkand",
|
||||||
|
"Asia/Seoul",
|
||||||
|
"Asia/Shanghai",
|
||||||
|
"Asia/Singapore",
|
||||||
|
"Asia/Srednekolymsk",
|
||||||
|
"Asia/Taipei",
|
||||||
|
"Asia/Tashkent",
|
||||||
|
"Asia/Tbilisi",
|
||||||
|
"Asia/Tehran",
|
||||||
|
"Asia/Tel_Aviv",
|
||||||
|
"Asia/Thimbu",
|
||||||
|
"Asia/Thimphu",
|
||||||
|
"Asia/Tokyo",
|
||||||
|
"Asia/Tomsk",
|
||||||
|
"Asia/Ujung_Pandang",
|
||||||
|
"Asia/Ulaanbaatar",
|
||||||
|
"Asia/Ulan_Bator",
|
||||||
|
"Asia/Urumqi",
|
||||||
|
"Asia/Ust-Nera",
|
||||||
|
"Asia/Vientiane",
|
||||||
|
"Asia/Vladivostok",
|
||||||
|
"Asia/Yakutsk",
|
||||||
|
"Asia/Yangon",
|
||||||
|
"Asia/Yekaterinburg",
|
||||||
|
"Asia/Yerevan",
|
||||||
|
"Atlantic/Azores",
|
||||||
|
"Atlantic/Bermuda",
|
||||||
|
"Atlantic/Canary",
|
||||||
|
"Atlantic/Cape_Verde",
|
||||||
|
"Atlantic/Faeroe",
|
||||||
|
"Atlantic/Faroe",
|
||||||
|
"Atlantic/Jan_Mayen",
|
||||||
|
"Atlantic/Madeira",
|
||||||
|
"Atlantic/Reykjavik",
|
||||||
|
"Atlantic/South_Georgia",
|
||||||
|
"Atlantic/St_Helena",
|
||||||
|
"Atlantic/Stanley",
|
||||||
|
"Australia/ACT",
|
||||||
|
"Australia/Adelaide",
|
||||||
|
"Australia/Brisbane",
|
||||||
|
"Australia/Broken_Hill",
|
||||||
|
"Australia/Canberra",
|
||||||
|
"Australia/Currie",
|
||||||
|
"Australia/Darwin",
|
||||||
|
"Australia/Eucla",
|
||||||
|
"Australia/Hobart",
|
||||||
|
"Australia/LHI",
|
||||||
|
"Australia/Lindeman",
|
||||||
|
"Australia/Lord_Howe",
|
||||||
|
"Australia/Melbourne",
|
||||||
|
"Australia/NSW",
|
||||||
|
"Australia/North",
|
||||||
|
"Australia/Perth",
|
||||||
|
"Australia/Queensland",
|
||||||
|
"Australia/South",
|
||||||
|
"Australia/Sydney",
|
||||||
|
"Australia/Tasmania",
|
||||||
|
"Australia/Victoria",
|
||||||
|
"Australia/West",
|
||||||
|
"Australia/Yancowinna",
|
||||||
|
"Brazil/Acre",
|
||||||
|
"Brazil/DeNoronha",
|
||||||
|
"Brazil/East",
|
||||||
|
"Brazil/West",
|
||||||
|
"CET",
|
||||||
|
"CST6CDT",
|
||||||
|
"Canada/Atlantic",
|
||||||
|
"Canada/Central",
|
||||||
|
"Canada/Eastern",
|
||||||
|
"Canada/Mountain",
|
||||||
|
"Canada/Newfoundland",
|
||||||
|
"Canada/Pacific",
|
||||||
|
"Canada/Saskatchewan",
|
||||||
|
"Canada/Yukon",
|
||||||
|
"Chile/Continental",
|
||||||
|
"Chile/EasterIsland",
|
||||||
|
"Cuba",
|
||||||
|
"EET",
|
||||||
|
"EST",
|
||||||
|
"EST5EDT",
|
||||||
|
"Egypt",
|
||||||
|
"Eire",
|
||||||
|
"Etc/GMT",
|
||||||
|
"Etc/GMT+0",
|
||||||
|
"Etc/GMT+1",
|
||||||
|
"Etc/GMT+10",
|
||||||
|
"Etc/GMT+11",
|
||||||
|
"Etc/GMT+12",
|
||||||
|
"Etc/GMT+2",
|
||||||
|
"Etc/GMT+3",
|
||||||
|
"Etc/GMT+4",
|
||||||
|
"Etc/GMT+5",
|
||||||
|
"Etc/GMT+6",
|
||||||
|
"Etc/GMT+7",
|
||||||
|
"Etc/GMT+8",
|
||||||
|
"Etc/GMT+9",
|
||||||
|
"Etc/GMT-0",
|
||||||
|
"Etc/GMT-1",
|
||||||
|
"Etc/GMT-10",
|
||||||
|
"Etc/GMT-11",
|
||||||
|
"Etc/GMT-12",
|
||||||
|
"Etc/GMT-13",
|
||||||
|
"Etc/GMT-14",
|
||||||
|
"Etc/GMT-2",
|
||||||
|
"Etc/GMT-3",
|
||||||
|
"Etc/GMT-4",
|
||||||
|
"Etc/GMT-5",
|
||||||
|
"Etc/GMT-6",
|
||||||
|
"Etc/GMT-7",
|
||||||
|
"Etc/GMT-8",
|
||||||
|
"Etc/GMT-9",
|
||||||
|
"Etc/GMT0",
|
||||||
|
"Etc/Greenwich",
|
||||||
|
"Etc/UCT",
|
||||||
|
"Etc/UTC",
|
||||||
|
"Etc/Universal",
|
||||||
|
"Etc/Zulu",
|
||||||
|
"Europe/Amsterdam",
|
||||||
|
"Europe/Andorra",
|
||||||
|
"Europe/Astrakhan",
|
||||||
|
"Europe/Athens",
|
||||||
|
"Europe/Belfast",
|
||||||
|
"Europe/Belgrade",
|
||||||
|
"Europe/Berlin",
|
||||||
|
"Europe/Bratislava",
|
||||||
|
"Europe/Brussels",
|
||||||
|
"Europe/Bucharest",
|
||||||
|
"Europe/Budapest",
|
||||||
|
"Europe/Busingen",
|
||||||
|
"Europe/Chisinau",
|
||||||
|
"Europe/Copenhagen",
|
||||||
|
"Europe/Dublin",
|
||||||
|
"Europe/Gibraltar",
|
||||||
|
"Europe/Guernsey",
|
||||||
|
"Europe/Helsinki",
|
||||||
|
"Europe/Isle_of_Man",
|
||||||
|
"Europe/Istanbul",
|
||||||
|
"Europe/Jersey",
|
||||||
|
"Europe/Kaliningrad",
|
||||||
|
"Europe/Kiev",
|
||||||
|
"Europe/Kirov",
|
||||||
|
"Europe/Kyiv",
|
||||||
|
"Europe/Lisbon",
|
||||||
|
"Europe/Ljubljana",
|
||||||
|
"Europe/London",
|
||||||
|
"Europe/Luxembourg",
|
||||||
|
"Europe/Madrid",
|
||||||
|
"Europe/Malta",
|
||||||
|
"Europe/Mariehamn",
|
||||||
|
"Europe/Minsk",
|
||||||
|
"Europe/Monaco",
|
||||||
|
"Europe/Moscow",
|
||||||
|
"Europe/Nicosia",
|
||||||
|
"Europe/Oslo",
|
||||||
|
"Europe/Paris",
|
||||||
|
"Europe/Podgorica",
|
||||||
|
"Europe/Prague",
|
||||||
|
"Europe/Riga",
|
||||||
|
"Europe/Rome",
|
||||||
|
"Europe/Samara",
|
||||||
|
"Europe/San_Marino",
|
||||||
|
"Europe/Sarajevo",
|
||||||
|
"Europe/Saratov",
|
||||||
|
"Europe/Simferopol",
|
||||||
|
"Europe/Skopje",
|
||||||
|
"Europe/Sofia",
|
||||||
|
"Europe/Stockholm",
|
||||||
|
"Europe/Tallinn",
|
||||||
|
"Europe/Tirane",
|
||||||
|
"Europe/Tiraspol",
|
||||||
|
"Europe/Ulyanovsk",
|
||||||
|
"Europe/Uzhgorod",
|
||||||
|
"Europe/Vaduz",
|
||||||
|
"Europe/Vatican",
|
||||||
|
"Europe/Vienna",
|
||||||
|
"Europe/Vilnius",
|
||||||
|
"Europe/Volgograd",
|
||||||
|
"Europe/Warsaw",
|
||||||
|
"Europe/Zagreb",
|
||||||
|
"Europe/Zaporozhye",
|
||||||
|
"Europe/Zurich",
|
||||||
|
"Factory",
|
||||||
|
"GB",
|
||||||
|
"GB-Eire",
|
||||||
|
"GMT",
|
||||||
|
"GMT+0",
|
||||||
|
"GMT-0",
|
||||||
|
"GMT0",
|
||||||
|
"Greenwich",
|
||||||
|
"HST",
|
||||||
|
"Hongkong",
|
||||||
|
"Iceland",
|
||||||
|
"Indian/Antananarivo",
|
||||||
|
"Indian/Chagos",
|
||||||
|
"Indian/Christmas",
|
||||||
|
"Indian/Cocos",
|
||||||
|
"Indian/Comoro",
|
||||||
|
"Indian/Kerguelen",
|
||||||
|
"Indian/Mahe",
|
||||||
|
"Indian/Maldives",
|
||||||
|
"Indian/Mauritius",
|
||||||
|
"Indian/Mayotte",
|
||||||
|
"Indian/Reunion",
|
||||||
|
"Iran",
|
||||||
|
"Israel",
|
||||||
|
"Jamaica",
|
||||||
|
"Japan",
|
||||||
|
"Kwajalein",
|
||||||
|
"Libya",
|
||||||
|
"MET",
|
||||||
|
"MST",
|
||||||
|
"MST7MDT",
|
||||||
|
"Mexico/BajaNorte",
|
||||||
|
"Mexico/BajaSur",
|
||||||
|
"Mexico/General",
|
||||||
|
"NZ",
|
||||||
|
"NZ-CHAT",
|
||||||
|
"Navajo",
|
||||||
|
"PRC",
|
||||||
|
"PST8PDT",
|
||||||
|
"Pacific/Apia",
|
||||||
|
"Pacific/Auckland",
|
||||||
|
"Pacific/Bougainville",
|
||||||
|
"Pacific/Chatham",
|
||||||
|
"Pacific/Chuuk",
|
||||||
|
"Pacific/Easter",
|
||||||
|
"Pacific/Efate",
|
||||||
|
"Pacific/Enderbury",
|
||||||
|
"Pacific/Fakaofo",
|
||||||
|
"Pacific/Fiji",
|
||||||
|
"Pacific/Funafuti",
|
||||||
|
"Pacific/Galapagos",
|
||||||
|
"Pacific/Gambier",
|
||||||
|
"Pacific/Guadalcanal",
|
||||||
|
"Pacific/Guam",
|
||||||
|
"Pacific/Honolulu",
|
||||||
|
"Pacific/Johnston",
|
||||||
|
"Pacific/Kanton",
|
||||||
|
"Pacific/Kiritimati",
|
||||||
|
"Pacific/Kosrae",
|
||||||
|
"Pacific/Kwajalein",
|
||||||
|
"Pacific/Majuro",
|
||||||
|
"Pacific/Marquesas",
|
||||||
|
"Pacific/Midway",
|
||||||
|
"Pacific/Nauru",
|
||||||
|
"Pacific/Niue",
|
||||||
|
"Pacific/Norfolk",
|
||||||
|
"Pacific/Noumea",
|
||||||
|
"Pacific/Pago_Pago",
|
||||||
|
"Pacific/Palau",
|
||||||
|
"Pacific/Pitcairn",
|
||||||
|
"Pacific/Pohnpei",
|
||||||
|
"Pacific/Ponape",
|
||||||
|
"Pacific/Port_Moresby",
|
||||||
|
"Pacific/Rarotonga",
|
||||||
|
"Pacific/Saipan",
|
||||||
|
"Pacific/Samoa",
|
||||||
|
"Pacific/Tahiti",
|
||||||
|
"Pacific/Tarawa",
|
||||||
|
"Pacific/Tongatapu",
|
||||||
|
"Pacific/Truk",
|
||||||
|
"Pacific/Wake",
|
||||||
|
"Pacific/Wallis",
|
||||||
|
"Pacific/Yap",
|
||||||
|
"Poland",
|
||||||
|
"Portugal",
|
||||||
|
"ROC",
|
||||||
|
"ROK",
|
||||||
|
"Singapore",
|
||||||
|
"Turkey",
|
||||||
|
"UCT",
|
||||||
|
"US/Alaska",
|
||||||
|
"US/Aleutian",
|
||||||
|
"US/Arizona",
|
||||||
|
"US/Central",
|
||||||
|
"US/East-Indiana",
|
||||||
|
"US/Eastern",
|
||||||
|
"US/Hawaii",
|
||||||
|
"US/Indiana-Starke",
|
||||||
|
"US/Michigan",
|
||||||
|
"US/Mountain",
|
||||||
|
"US/Pacific",
|
||||||
|
"US/Samoa",
|
||||||
|
"UTC",
|
||||||
|
"Universal",
|
||||||
|
"W-SU",
|
||||||
|
"WET",
|
||||||
|
"Zulu",
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, tz)
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -13,10 +13,13 @@ func NewUserGroup(prefix string, e *echo.Group, service *user.Service, middlewar
|
|||||||
userHandler := &UserHandler{
|
userHandler := &UserHandler{
|
||||||
userService: service,
|
userService: service,
|
||||||
}
|
}
|
||||||
_ = userHandler
|
|
||||||
auth := e.Group(prefix, middlewares...)
|
auth := e.Group(prefix, middlewares...)
|
||||||
auth.GET("/me", userHandler.Me)
|
auth.GET("/me", userHandler.Me)
|
||||||
auth.PUT("/:id", userHandler.Update)
|
auth.PUT("/:id", userHandler.Update)
|
||||||
|
auth.GET("/:id/address", userHandler.Addresses)
|
||||||
|
auth.POST("/:id/address", userHandler.CreateAddress)
|
||||||
|
auth.PUT("/:id/address", userHandler.UpdateAddress)
|
||||||
|
auth.DELETE("/:id/address/:address", userHandler.DeleteAddress)
|
||||||
return auth
|
return auth
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,7 +29,7 @@ type UserHandler struct {
|
|||||||
|
|
||||||
func (h UserHandler) Me(c *echo.Context) error {
|
func (h UserHandler) Me(c *echo.Context) error {
|
||||||
userID := c.Get(auth.ContextUserIDKey).(string)
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
user, err := h.userService.GetUser(userID)
|
user, err := h.userService.GetUserWithTeams(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
|
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
|
||||||
}
|
}
|
||||||
@@ -43,15 +46,86 @@ func (h UserHandler) Update(c *echo.Context) error {
|
|||||||
}
|
}
|
||||||
updUser.ID = id
|
updUser.ID = id
|
||||||
|
|
||||||
isAdmin := false // TODO : check if caller is admin
|
if !(userID == id) {
|
||||||
if !(isAdmin || userID == id) {
|
|
||||||
return echo.NewHTTPError(http.StatusForbidden, "unauthorized to update user")
|
return echo.NewHTTPError(http.StatusForbidden, "unauthorized to update user")
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := h.userService.UpdateUser(&updUser, isAdmin)
|
user, err := h.userService.UpdateUser(&updUser, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.String(http.StatusBadRequest, err.Error())
|
return c.String(http.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, user)
|
return c.JSON(http.StatusOK, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h UserHandler) Addresses(c *echo.Context) error {
|
||||||
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
|
id := c.Param("id")
|
||||||
|
if !(userID == id) {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "forbidden")
|
||||||
|
}
|
||||||
|
addresses, err := h.userService.ListAdresses(userID)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
|
||||||
|
}
|
||||||
|
return c.JSON(http.StatusOK, addresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h UserHandler) CreateAddress(c *echo.Context) error {
|
||||||
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
|
id := c.Param("id")
|
||||||
|
if !(userID == id) {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
var address domain.UserAddress
|
||||||
|
if err := c.Bind(&address); err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, "bad request")
|
||||||
|
}
|
||||||
|
address.UserID = userID
|
||||||
|
|
||||||
|
newaddress, err := h.userService.AddAddress(userID, address.Type, address.Address)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, "internal error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, newaddress)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h UserHandler) UpdateAddress(c *echo.Context) error {
|
||||||
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
|
id := c.Param("id")
|
||||||
|
if !(userID == id) {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
var address domain.UserAddress
|
||||||
|
if err := c.Bind(&address); err != nil {
|
||||||
|
return c.String(http.StatusBadRequest, "bad request")
|
||||||
|
}
|
||||||
|
address.UserID = userID
|
||||||
|
|
||||||
|
newaddress, err := h.userService.UpdateAddress(userID, &address)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, "internal error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, newaddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h UserHandler) DeleteAddress(c *echo.Context) error {
|
||||||
|
userID := c.Get(auth.ContextUserIDKey).(string)
|
||||||
|
id := c.Param("id")
|
||||||
|
addressID := c.Param("address")
|
||||||
|
if !(userID == id) {
|
||||||
|
return echo.NewHTTPError(http.StatusForbidden, "forbidden")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.userService.DeleteAddress(userID, addressID)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, "internal error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, "deleted")
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"trankilou.fr/lassistanoque/backend/internal/config"
|
"trankilou.fr/lassistanoque/backend/internal/config"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/http/handlers"
|
"trankilou.fr/lassistanoque/backend/internal/http/handlers"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/auth"
|
"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/storage"
|
||||||
"trankilou.fr/lassistanoque/backend/internal/service/user"
|
"trankilou.fr/lassistanoque/backend/internal/service/user"
|
||||||
)
|
)
|
||||||
@@ -35,6 +36,7 @@ type Dependencies struct {
|
|||||||
StorageService *storage.Service
|
StorageService *storage.Service
|
||||||
AuthService *auth.Service
|
AuthService *auth.Service
|
||||||
UserService *user.Service
|
UserService *user.Service
|
||||||
|
ProviderService *provider.Service
|
||||||
TokenManager auth.TokenManager
|
TokenManager auth.TokenManager
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +72,8 @@ func NewRouter(deps Dependencies) *Router {
|
|||||||
api := e.Group("/api")
|
api := e.Group("/api")
|
||||||
_ = handlers.NewAuthGroup("/auth", api, deps.AuthService)
|
_ = handlers.NewAuthGroup("/auth", api, deps.AuthService)
|
||||||
_ = handlers.NewUserGroup("/user", api, deps.UserService, deps.TokenManager.TokenMiddleware)
|
_ = 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{
|
return &Router{
|
||||||
echo: e,
|
echo: e,
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ type Session struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ContextUserIDKey = "userID"
|
const ContextUserIDKey = "userID"
|
||||||
const ContextEmailKey = "userEmail"
|
const ContextUserAdminKey = "userAdmin"
|
||||||
const ContextNameKey = "userName"
|
|
||||||
|
//const ContextEmailKey = "userEmail"
|
||||||
|
//const ContextNameKey = "userName"
|
||||||
|
|
||||||
// Interface définie ici car c'est un port propre au cas d'usage "auth"
|
// Interface définie ici car c'est un port propre au cas d'usage "auth"
|
||||||
type Authenticator interface {
|
type Authenticator interface {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||||
)
|
)
|
||||||
@@ -53,8 +54,10 @@ func (s *Service) Register(registration Registration) (*Session, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
now := time.Now()
|
||||||
_, err = s.userRepository.CreateTeam(session.User.ID, &domain.Team{
|
_, err = s.userRepository.CreateTeam(session.User.ID, &domain.Team{
|
||||||
Label: "Espace personnel",
|
Label: "Espace personnel",
|
||||||
|
DateCreated: now,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -27,6 +27,30 @@ func (s *Service) GetUser(id string) (*domain.User, error) {
|
|||||||
return user, nil
|
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) {
|
func (s *Service) UpdateUser(updUser *domain.User, fromAdmin bool) (*domain.User, error) {
|
||||||
user, err := s.repo.FindUser(updUser.ID)
|
user, err := s.repo.FindUser(updUser.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -41,7 +65,43 @@ func (s *Service) UpdateUser(updUser *domain.User, fromAdmin bool) (*domain.User
|
|||||||
user.Lastname = updUser.Lastname
|
user.Lastname = updUser.Lastname
|
||||||
user.Theme = updUser.Theme
|
user.Theme = updUser.Theme
|
||||||
user.Lang = updUser.Lang
|
user.Lang = updUser.Lang
|
||||||
|
user.Timezone = updUser.Timezone
|
||||||
|
|
||||||
return s.repo.UpdateUser(user)
|
return s.repo.UpdateUser(user)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListAdresses(userId string) ([]*domain.UserAddress, error) {
|
||||||
|
return s.repo.ListUserAddresses(userId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) AddAddress(userId string, typ string, address string) (*domain.UserAddress, error) {
|
||||||
|
addr := &domain.UserAddress{
|
||||||
|
UserID: userId,
|
||||||
|
Type: typ,
|
||||||
|
Address: address,
|
||||||
|
}
|
||||||
|
return s.repo.CreateUserAddress(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateAddress(userId string, address *domain.UserAddress) (*domain.UserAddress, error) {
|
||||||
|
old, err := s.repo.GetUserAddress(address.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if old.UserID != userId {
|
||||||
|
return nil, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
return s.repo.UpdateUserAddress(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) DeleteAddress(userId string, addressId string) error {
|
||||||
|
old, err := s.repo.GetUserAddress(addressId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if old.UserID != userId {
|
||||||
|
return fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
return s.repo.DeleteUserAddress(addressId)
|
||||||
|
}
|
||||||
Binary file not shown.
Generated
+53
-25
@@ -8,7 +8,8 @@
|
|||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"oidc-client-ts": "^3.5.0"
|
"oidc-client-ts": "^3.5.0",
|
||||||
|
"svelte-select": "^6.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
@@ -198,6 +199,31 @@
|
|||||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@floating-ui/core": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/utils": "^0.2.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/dom": {
|
||||||
|
"version": "1.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
|
||||||
|
"integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/core": "^1.8.0",
|
||||||
|
"@floating-ui/utils": "^0.2.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@floating-ui/utils": {
|
||||||
|
"version": "0.2.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
|
||||||
|
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@humanfs/core": {
|
"node_modules/@humanfs/core": {
|
||||||
"version": "0.19.2",
|
"version": "0.19.2",
|
||||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
|
||||||
@@ -268,7 +294,6 @@
|
|||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||||
@@ -279,7 +304,6 @@
|
|||||||
"version": "2.3.5",
|
"version": "2.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
||||||
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.5",
|
"@jridgewell/gen-mapping": "^0.3.5",
|
||||||
@@ -290,7 +314,6 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -300,14 +323,12 @@
|
|||||||
"version": "1.5.5",
|
"version": "1.5.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.31",
|
"version": "0.3.31",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
||||||
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
@@ -822,7 +843,6 @@
|
|||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.11.tgz",
|
||||||
"integrity": "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==",
|
"integrity": "sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"acorn": "^8.9.0"
|
"acorn": "^8.9.0"
|
||||||
@@ -1259,7 +1279,6 @@
|
|||||||
"version": "1.0.9",
|
"version": "1.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
|
||||||
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
"integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
@@ -1283,7 +1302,6 @@
|
|||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
@@ -1446,7 +1464,7 @@
|
|||||||
"version": "8.65.0",
|
"version": "8.65.0",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz",
|
||||||
"integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==",
|
"integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||||
@@ -1530,7 +1548,6 @@
|
|||||||
"version": "8.18.0",
|
"version": "8.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
|
||||||
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"acorn": "bin/acorn"
|
"acorn": "bin/acorn"
|
||||||
@@ -1570,7 +1587,6 @@
|
|||||||
"version": "5.3.1",
|
"version": "5.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz",
|
||||||
"integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
|
"integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -1580,7 +1596,6 @@
|
|||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -1613,7 +1628,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
@@ -1706,7 +1720,6 @@
|
|||||||
"version": "5.9.0",
|
"version": "5.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.0.tgz",
|
||||||
"integrity": "sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==",
|
"integrity": "sha512-RWrqdArjvPbsATEhOPUo6Wndc/iWnkWKlhIrdlF3zMMYo/c3CVtoaVAyLtWxz5h8nSlkHzxnzV2uLydPXmtF+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/enhanced-resolve": {
|
"node_modules/enhanced-resolve": {
|
||||||
@@ -1894,7 +1907,6 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||||
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/espree": {
|
"node_modules/espree": {
|
||||||
@@ -1932,7 +1944,6 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/esrap/-/esrap-2.3.0.tgz",
|
||||||
"integrity": "sha512-GQ/7RN8uOtEfNpzZzBMTzW9JBcX42oaSVtPzdF+6cEL8pqIL094iUpr9jzYGn4O4P/1S60dJ6izyT8F4LYARng==",
|
"integrity": "sha512-GQ/7RN8uOtEfNpzZzBMTzW9JBcX42oaSVtPzdF+6cEL8pqIL094iUpr9jzYGn4O4P/1S60dJ6izyT8F4LYARng==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||||
@@ -2164,7 +2175,6 @@
|
|||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||||
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/estree": "^1.0.6"
|
"@types/estree": "^1.0.6"
|
||||||
@@ -2595,7 +2605,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
@@ -2618,7 +2627,6 @@
|
|||||||
"version": "0.30.21",
|
"version": "0.30.21",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
||||||
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||||
@@ -2678,9 +2686,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.16",
|
"version": "3.3.18",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
|
||||||
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
|
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -3211,7 +3219,6 @@
|
|||||||
"version": "5.56.8",
|
"version": "5.56.8",
|
||||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.8.tgz",
|
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.56.8.tgz",
|
||||||
"integrity": "sha512-PY8LOw7xP6c8IOiVqdo0sbbZVYhXRSfklOQLAUyGBKqjTX0wx/z4l/9J+PmBpmlLnxzEb1NqltxQ5/wZme/Cmg==",
|
"integrity": "sha512-PY8LOw7xP6c8IOiVqdo0sbbZVYhXRSfklOQLAUyGBKqjTX0wx/z4l/9J+PmBpmlLnxzEb1NqltxQ5/wZme/Cmg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/remapping": "^2.3.4",
|
"@jridgewell/remapping": "^2.3.4",
|
||||||
@@ -3383,6 +3390,28 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/svelte-floating-ui": {
|
||||||
|
"version": "1.5.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/svelte-floating-ui/-/svelte-floating-ui-1.5.8.tgz",
|
||||||
|
"integrity": "sha512-dVvJhZ2bT+kQDHlE4Lep8t+sgEc0XD96fXLzAi2DDI2bsaegBbClxXVNMma0C2WsG+n9GJSYx292dTvA8CYRtw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@floating-ui/core": "^1.5.0",
|
||||||
|
"@floating-ui/dom": "^1.5.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/svelte-select": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/svelte-select/-/svelte-select-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-WJ1UDw0a1Z3I7oVFgaCS7G9D2rCSmYAPpufNEj/DNuk5Oyb2a5dju6F36o0urBVw9bTE3WkM9cZkI0x+Sfo8Bg==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"svelte-floating-ui": "1.5.8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"svelte": ">=5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tailwindcss": {
|
"node_modules/tailwindcss": {
|
||||||
"version": "4.3.3",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
|
||||||
@@ -3973,7 +4002,6 @@
|
|||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz",
|
||||||
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
"integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@
|
|||||||
"esbuild": "^0.25.0"
|
"esbuild": "^0.25.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"oidc-client-ts": "^3.5.0"
|
"oidc-client-ts": "^3.5.0",
|
||||||
|
"svelte-select": "^6.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { api } from '../client';
|
||||||
|
|
||||||
|
export const miscApi = {
|
||||||
|
listTimezones: () => api.get<string[]>(`/misc/timezones`),
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { api } from '../client';
|
||||||
|
import type { Provider, Item } from "$lib/types/api"
|
||||||
|
import { space } from "$lib/state/space.svelte"
|
||||||
|
|
||||||
|
export const providerApi = {
|
||||||
|
providerTypes: () => api.get<Item[]>('/provider/providerTypes'),
|
||||||
|
getProviders: () => api.get<Provider[]>(`/provider/${space.id}`),
|
||||||
|
getProvider: (id: string) => api.get<Provider>(`/provider/${space.id}/${id}`),
|
||||||
|
createProvider: (data: Partial<Provider>) => api.post<Provider>(`/provider/${space.id}`, data),
|
||||||
|
updateProvider: (data: Partial<Provider>) => api.put<Provider>(`/provider/${space.id}`, data),
|
||||||
|
deleteProvider: (id: string) => api.delete(`/provider/${space.id}/${id}`),
|
||||||
|
getAvailableModels: (data: Partial<Provider>) => api.post<string[]>(`/provider/${space.id}/avalable-models`, data),
|
||||||
|
};
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
import { api } from '../client';
|
import { api } from '../client';
|
||||||
import { type User } from "$lib/types/api"
|
import type { User, Address, UserWithTeams } from "$lib/types/api"
|
||||||
|
|
||||||
export const userApi = {
|
export const userApi = {
|
||||||
me: () => api.get<User>('/user/me'),
|
me: () => api.get<UserWithTeams>('/user/me'),
|
||||||
update: (id: string, data: Partial<User>) => api.put<User>(`/user/${id}`, data),
|
update: (id: string, data: Partial<User>) => api.put<User>(`/user/${id}`, data),
|
||||||
|
|
||||||
|
listAddresses: (id: string) => api.get<Address[]>(`/user/${id}/address`),
|
||||||
|
createAddress: (id: string, data: Partial<Address>) => api.post<Address>(`/user/${id}/address`, data),
|
||||||
|
updateAddress: (id: string, data: Partial<Address>) => api.put<Address>(`/user/${id}/address`, data),
|
||||||
|
deleteAddress: (id:string, addrid: string) => api.delete<Address[]>(`/user/${id}/address/${addrid}`),
|
||||||
};
|
};
|
||||||
@@ -8,6 +8,4 @@
|
|||||||
|
|
||||||
{#if errors.has(key)}
|
{#if errors.has(key)}
|
||||||
<span class="text-red-700 dark:text-red-400 text-xs">{errors.get(key)}</span>
|
<span class="text-red-700 dark:text-red-400 text-xs">{errors.get(key)}</span>
|
||||||
{:else}
|
|
||||||
<span class="text-xs"> </span>
|
|
||||||
{/if}
|
{/if}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { uiState } from "$lib/state/ui.svelte"
|
import { uiState } from "$lib/state/ui.svelte"
|
||||||
import { auth } from "$lib/state/auth.svelte"
|
|
||||||
import Menu from "$lib/components/Menu.svelte"
|
import Menu from "$lib/components/Menu.svelte"
|
||||||
import { logout } from "$lib/state/auth.svelte"
|
import { logout } from "$lib/state/auth.svelte"
|
||||||
import logo from "$lib/assets/logo-txt.png"
|
import logo from "$lib/assets/logo-txt.png"
|
||||||
|
import { space } from '$lib/state/space.svelte'
|
||||||
|
|
||||||
let {handleMenuDesktop, handleMenuMobile} = $props();
|
let {handleMenuDesktop, handleMenuMobile} = $props();
|
||||||
|
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex flex-row italic mb-4">
|
<div class="flex flex-row italic mb-2">
|
||||||
{#if uiState.menuContentVisible}
|
{#if uiState.menuContentVisible}
|
||||||
<div class="flex-1 p-3 font openonly">
|
<div class="flex-1 p-3 font openonly">
|
||||||
<img src={logo} class="h-5" alt="logo"/>
|
<img src={logo} class="h-5" alt="logo"/>
|
||||||
@@ -26,26 +26,38 @@
|
|||||||
<i class="icon-menu pointer block p-2"></i>
|
<i class="icon-menu pointer block p-2"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{#if uiState.menuContentVisible}
|
||||||
|
<div class="m-2">
|
||||||
|
<div class="formelement">
|
||||||
|
<select class="textinput">
|
||||||
|
{#each space.spaces as s (s.id)}
|
||||||
|
<option value={s.id}>{s.label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
<div id="menu" class="flex-1 overflow-y-scroll">
|
<div id="menu" class="flex-1 overflow-y-scroll">
|
||||||
<Menu label="Conversation" icon="icon-chat" link="/"/>
|
<Menu label="Nouveau chat" icon="icon-plus" link="/"/>
|
||||||
<Menu label="Tâches" icon="icon-tasks" link="/tasks"/>
|
<Menu label="Conversations" icon="icon-chat" link="/history"/>
|
||||||
|
<Menu label="Planification" icon="icon-tasks" link="/tasks"/>
|
||||||
<Menu label="Connaissances" icon="icon-graduation-cap" link="/kb"/>
|
<Menu label="Connaissances" icon="icon-graduation-cap" link="/kb"/>
|
||||||
<Menu label="Historique" icon="icon-history" link="/history"/>
|
<Menu label="Réglages" icon="icon-cog" link="/settings"/>
|
||||||
{#if auth.user?.administrator}
|
|
||||||
<Menu label="Paramétrage" icon="icon-cog" link="/settings"/>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="profile" class="w-full flex flex-row items-center border-t border-stone-200 dark:border-stone-800">
|
<div id="profile" class="w-full items-center border-t border-stone-200 dark:border-stone-800">
|
||||||
{#if uiState.menuContentVisible}
|
{#if uiState.menuContentVisible}
|
||||||
<div class="p-4 flex-1 profile">
|
<div class="flex flex-row gap-2 p-2 ">
|
||||||
<div>Fabien Masson</div>
|
<div class="py-1 flex-1 flex flex-row text-xs">
|
||||||
<div class="flex">
|
<div class="w-6 h-6 rounded-full bg-cyan-600 text-white flex items-center justify-center font-medium">
|
||||||
<a href="/profile" class="text-xs flex-1">Modifier</a>
|
FM
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex-1 ml-2 inline-flex items-center">Fabien Masson</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2">
|
<a href="/profile" class="bt small inline-flex items-center justify-center" title="Edition profil">
|
||||||
<button class="w-full bt" onclick={handleLogout} title="Déconecter">
|
<i class="icon-edit"></i>
|
||||||
|
</a>
|
||||||
|
<button class="bt small" onclick={handleLogout} title="Déconnecter">
|
||||||
<i class="icon-logout"></i>
|
<i class="icon-logout"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import { type User } from "$lib/types/api"
|
import type { User } from "$lib/types/api"
|
||||||
|
|
||||||
type AuthState = {
|
type AuthState = {
|
||||||
user: User | null;
|
user: User | null
|
||||||
status: 'loading' | 'authenticated' | 'unauthenticated';
|
status: 'loading' | 'authenticated' | 'unauthenticated'
|
||||||
//accessToken: string | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const auth: AuthState = $state({
|
export const auth: AuthState = $state({
|
||||||
user: null,
|
user: null,
|
||||||
status: 'loading',
|
status: 'loading',
|
||||||
//accessToken: null
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const logout = () => {
|
export const logout = () => {
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import type { Team } from "$lib/types/api"
|
||||||
|
|
||||||
|
export const space = $state({
|
||||||
|
spaces: [] as Team[],
|
||||||
|
id: "",
|
||||||
|
});
|
||||||
@@ -1,10 +1,67 @@
|
|||||||
|
export type Item = {
|
||||||
|
id: string
|
||||||
|
text: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string
|
id: string
|
||||||
firstname: string
|
firstname: string
|
||||||
lastname: string
|
lastname: string
|
||||||
email: string
|
email: string
|
||||||
administrator: boolean
|
|
||||||
theme: string
|
theme: string
|
||||||
lang: string
|
lang: string
|
||||||
|
timezone: string
|
||||||
|
_version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserTeam {
|
||||||
|
id: string
|
||||||
|
userId: string
|
||||||
|
teamId: string
|
||||||
|
administrator: boolean
|
||||||
|
_version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Team {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
defaultModelId: string
|
||||||
|
_version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UserWithTeams {
|
||||||
|
user: User
|
||||||
|
userTeams: UserTeam[]
|
||||||
|
teams: Team[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Address {
|
||||||
|
id: string
|
||||||
|
type: string
|
||||||
|
address: string
|
||||||
|
_version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Provider {
|
||||||
|
id: string
|
||||||
|
teamId: string
|
||||||
|
type: string
|
||||||
|
name: string
|
||||||
|
apiKey: string
|
||||||
|
baseUrl: string
|
||||||
|
models: string
|
||||||
|
_date_created: string
|
||||||
|
_date_updated: string
|
||||||
|
_version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Model {
|
||||||
|
id: string
|
||||||
|
teamId: string
|
||||||
|
providerId: string
|
||||||
|
modelName: string
|
||||||
|
configuration: object
|
||||||
|
_date_created: string
|
||||||
|
_date_updated: string
|
||||||
_version: string
|
_version: string
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,10 @@
|
|||||||
const emailRegex: RegExp = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
const emailRegex: RegExp = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||||
|
const urlRegex: RegExp = /^https?:\/\/([a-zA-Z0-9.-]+|\[[0-9a-fA-F:.]+\])(:\d+)?(\/[^\s]*)?$/;
|
||||||
|
|
||||||
export function validateEmail(input: string): boolean {
|
export function validateEmail(input: string): boolean {
|
||||||
return emailRegex.test(input);
|
return emailRegex.test(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateUrl(input: string): boolean {
|
||||||
|
return urlRegex.test(input);
|
||||||
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
<div class="container">
|
||||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
||||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
||||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
||||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
||||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
||||||
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Asperiores, reiciendis quo. Quos animi nisi modi cumque hic illo doloremque totam! Distinctio molestias sint magnam illum nostrum eos, fugit aliquid consequuntur?
|
||||||
|
</div>
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import {uiState} from "$lib/state/ui.svelte"
|
import {uiState} from "$lib/state/ui.svelte"
|
||||||
import { auth } from '$lib/state/auth.svelte'
|
import { auth } from '$lib/state/auth.svelte'
|
||||||
|
import { space } from '$lib/state/space.svelte'
|
||||||
import Sidebar from "$lib/components/Sidebar.svelte"
|
import Sidebar from "$lib/components/Sidebar.svelte"
|
||||||
import { goto } from '$app/navigation'
|
import { goto } from '$app/navigation'
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
@@ -39,7 +40,9 @@
|
|||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
handleResize()
|
handleResize()
|
||||||
if (data.me) {
|
if (data.me) {
|
||||||
auth.user = data.me
|
auth.user = data.me.user
|
||||||
|
space.spaces = data.me.teams
|
||||||
|
space.id = data.me.teams[0].id
|
||||||
themeToLocalStorage(auth.user.theme)
|
themeToLocalStorage(auth.user.theme)
|
||||||
applyTheme()
|
applyTheme()
|
||||||
auth.status = 'authenticated'
|
auth.status = 'authenticated'
|
||||||
@@ -64,7 +67,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<div class="flex flex-col h-screen ">
|
<div class="flex flex-col h-screen ">
|
||||||
<div class={["header", page.data.theme]}>
|
<div class={["header", "container" , page.data.theme]}>
|
||||||
<h1 class="flex">
|
<h1 class="flex">
|
||||||
<span class="flex-1">{page.data.title}</span>
|
<span class="flex-1">{page.data.title}</span>
|
||||||
{#if page.data.back}
|
{#if page.data.back}
|
||||||
|
|||||||
@@ -2,17 +2,28 @@
|
|||||||
|
|
||||||
import { SvelteMap } from 'svelte/reactivity';
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
import type { PageProps } from './$types';
|
import type { PageProps } from './$types';
|
||||||
import { type User } from '$lib/types/api'
|
import type { User, Address, Item } from '$lib/types/api'
|
||||||
import { userApi } from '$lib/api/endpoints/user'
|
import { userApi } from '$lib/api/endpoints/user'
|
||||||
|
import { miscApi } from '$lib/api/endpoints/misc'
|
||||||
import FormError from '$lib/components/FormError.svelte';
|
import FormError from '$lib/components/FormError.svelte';
|
||||||
import { applyTheme, themeToLocalStorage } from '$lib/ui/themeswitcher';
|
import { applyTheme, themeToLocalStorage } from '$lib/ui/themeswitcher';
|
||||||
import { validateEmail } from '$lib/utilities/validators'
|
import { validateEmail } from '$lib/utilities/validators'
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import Select from 'svelte-select/no-styles';
|
||||||
|
|
||||||
let { data }: PageProps = $props();
|
let { data }: PageProps = $props();
|
||||||
|
|
||||||
let form : User = $state(data.me)
|
let form : User = $state(data.me.user)
|
||||||
|
let addresses : Address[] = $state([] as Address[])
|
||||||
|
let newAddress : Address = $state({} as Address)
|
||||||
|
let timezones = $state([] as Item[])
|
||||||
let errors = new SvelteMap<string,string>()
|
let errors = new SvelteMap<string,string>()
|
||||||
|
|
||||||
|
onMount(async ()=> {
|
||||||
|
addresses = await userApi.listAddresses(form.id)
|
||||||
|
timezones = (await miscApi.listTimezones()).map((it)=>{return {id:it, text:it}})
|
||||||
|
})
|
||||||
|
|
||||||
$effect(()=>{
|
$effect(()=>{
|
||||||
themeToLocalStorage(form.theme)
|
themeToLocalStorage(form.theme)
|
||||||
applyTheme()
|
applyTheme()
|
||||||
@@ -36,14 +47,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleAddAddress = () => {
|
||||||
|
userApi.createAddress(form.id, newAddress)
|
||||||
|
.then((data)=>{
|
||||||
|
addresses.push(data)
|
||||||
|
newAddress = {} as Address
|
||||||
|
})
|
||||||
|
.catch((error)=>{ console.log(error) })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteAddress = (id: string) => {
|
||||||
|
userApi.deleteAddress(form.id,id).then(()=>{
|
||||||
|
addresses = addresses.filter((a)=> a.id!=id)
|
||||||
|
}).catch((error)=>{console.log(error)})
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mb-4 formgroup">
|
<div class="mb-4 formgroup container">
|
||||||
<h2>Mon compte</h2>
|
<h2>Mon compte</h2>
|
||||||
|
|
||||||
<form onsubmit={submitUser} onchange={()=>{errors.clear()}}>
|
<form onsubmit={submitUser} onchange={()=>{errors.clear()}}>
|
||||||
|
|
||||||
<label for="email" class="block my-1 flex-1">Email</label>
|
<div class="formelement">
|
||||||
|
<label for="email" class="my-1 flex-1">Email</label>
|
||||||
<input
|
<input
|
||||||
id="email"
|
id="email"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -52,10 +79,11 @@
|
|||||||
autofocus
|
autofocus
|
||||||
/>
|
/>
|
||||||
<FormError key="email" errors={errors} />
|
<FormError key="email" errors={errors} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="grid grid-cols-1 gap-x-4 md:grid-cols-2">
|
||||||
<div class="flex-1 mr-2">
|
<div class="formelement">
|
||||||
<label for="firstname" class="block my-1 flex-1">Prénom</label>
|
<label for="firstname" class=" my-1 flex-1">Prénom</label>
|
||||||
<input
|
<input
|
||||||
id="firstname"
|
id="firstname"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -65,8 +93,8 @@
|
|||||||
<FormError key="firstname" errors={errors} />
|
<FormError key="firstname" errors={errors} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-1 ml-2">
|
<div class="formelement">
|
||||||
<label for="lastname" class="block my-1 flex-1">Nom</label>
|
<label for="lastname" class="my-1 flex-1">Nom</label>
|
||||||
<input
|
<input
|
||||||
id="lastname"
|
id="lastname"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -77,18 +105,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex">
|
<div class="grid grid-cols-1 gap-x-4 md:grid-cols-2">
|
||||||
<div class="flex-1 mr-2">
|
|
||||||
<label for="theme" class="block my-1 flex-1">Thème</label>
|
|
||||||
<select bind:value={form.theme} class="textinput">
|
|
||||||
<option value="auto">Automatic</option>
|
|
||||||
<option value="dark">Sombre</option>
|
|
||||||
<option value="light">Clair</option>
|
|
||||||
</select>
|
|
||||||
<FormError key="theme" errors={errors} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-1 ml-2">
|
<div class="formelement">
|
||||||
<label for="lang" class="block my-1 flex-1">Langue</label>
|
<label for="lang" class="block my-1 flex-1">Langue</label>
|
||||||
<select bind:value={form.lang} class="textinput">
|
<select bind:value={form.lang} class="textinput">
|
||||||
<option value="en">English</option>
|
<option value="en">English</option>
|
||||||
@@ -96,6 +115,22 @@
|
|||||||
</select>
|
</select>
|
||||||
<FormError key="lang" errors={errors} />
|
<FormError key="lang" errors={errors} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="formelement">
|
||||||
|
<label for="timezone" class="block my-1 flex-1">Timezone</label>
|
||||||
|
<Select items={timezones} bind:value={form.timezone}/>
|
||||||
|
<FormError key="timezone" errors={errors} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="formelement">
|
||||||
|
<label for="theme" class="block my-1 flex-1">Thème</label>
|
||||||
|
<select bind:value={form.theme} class="textinput">
|
||||||
|
<option value="auto">Automatique</option>
|
||||||
|
<option value="dark">Sombre</option>
|
||||||
|
<option value="light">Clair</option>
|
||||||
|
</select>
|
||||||
|
<FormError key="theme" errors={errors} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="bt w-full">Enregistrer</button>
|
<button class="bt w-full">Enregistrer</button>
|
||||||
@@ -104,6 +139,35 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-4 formgroup">
|
<div class="mb-4 formgroup container">
|
||||||
<h2>Canaux de communication</h2>
|
<h2>Canaux de communication</h2>
|
||||||
|
|
||||||
|
{#each addresses as address (address.id) }
|
||||||
|
<div class="m-2">
|
||||||
|
<div class="dark:bg-stone-700 bg-stone-300 rounded inline px-2 py-1 text-sm">
|
||||||
|
<span class="inline-block w-20">{address.type} : </span>
|
||||||
|
<span class="">{address.address}</span>
|
||||||
|
<button class="ml-4 pointer" onclick={()=>{handleDeleteAddress(address.id)}}>
|
||||||
|
<i class="icon-cancel"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
<div class="flex gap-2 mt-6">
|
||||||
|
<div>
|
||||||
|
<select bind:value={newAddress.type} class="textinput">
|
||||||
|
<option value="email">Email</option>
|
||||||
|
<option value="signal">Signal</option>
|
||||||
|
<option value="telegram">Telegram</option>
|
||||||
|
<option value="whatsapp">Whatsapp</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<input type="text" bind:value={newAddress.address} class="textinput" />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="bt" onclick={handleAddAddress}>Ajouter</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4">
|
<div class="container grid grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-4">
|
||||||
|
|
||||||
<a class="bt p-5 text-center"
|
<a class="bt p-5 text-center"
|
||||||
title="Modèles"
|
title="Modèles"
|
||||||
|
|||||||
@@ -1 +1,157 @@
|
|||||||
<div>Modèles</div>
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
|
|
||||||
|
import type { Provider} from '$lib/types/api'
|
||||||
|
import type { PageProps } from './$types';
|
||||||
|
import { providerApi } from "$lib/api/endpoints/provider"
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
|
import { validateUrl } from '$lib/utilities/validators'
|
||||||
|
import ProviderForm from './ProviderForm.svelte'
|
||||||
|
|
||||||
|
let { data }: PageProps = $props();
|
||||||
|
|
||||||
|
let providers = $state([] as Provider[])
|
||||||
|
let addingProvider = $state(false)
|
||||||
|
let editingProvider = $state(null as string|null)
|
||||||
|
let providerForm = $state({} as Provider)
|
||||||
|
let errors = new SvelteMap<string,string>()
|
||||||
|
|
||||||
|
|
||||||
|
onMount(async ()=>{
|
||||||
|
providers = await providerApi.getProviders()
|
||||||
|
})
|
||||||
|
|
||||||
|
const enterAddProviderHandler = () => {
|
||||||
|
if (editingProvider) return
|
||||||
|
errors.clear()
|
||||||
|
providerForm = {} as Provider
|
||||||
|
providerForm.models = ""
|
||||||
|
addingProvider= true
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelAddProviderHandler = () => {
|
||||||
|
addingProvider= false
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitAddProvider = (p: Provider) => {
|
||||||
|
|
||||||
|
if (!p.name) {
|
||||||
|
errors.set("name","Le nom est obligatoire !")
|
||||||
|
}
|
||||||
|
if (!p.type) {
|
||||||
|
errors.set("type","Le type est obligatoire !")
|
||||||
|
}
|
||||||
|
if (p.baseUrl && !validateUrl(p.baseUrl)) {
|
||||||
|
errors.set("baseUrl","Mauvaise URL !")
|
||||||
|
}
|
||||||
|
if (errors.size === 0) {
|
||||||
|
providerApi.createProvider(p)
|
||||||
|
.then((p)=>{
|
||||||
|
providers.push(p)
|
||||||
|
addingProvider= false
|
||||||
|
providerForm = {} as Provider
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelEditProviderHandler = () => {
|
||||||
|
editingProvider = null
|
||||||
|
providerForm = {} as Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
const submitEditProvider = (p: Provider) => {
|
||||||
|
|
||||||
|
if (!p.name) {
|
||||||
|
errors.set("name","Le nom est obligatoire !")
|
||||||
|
}
|
||||||
|
if (!p.type) {
|
||||||
|
errors.set("type","Le type est obligatoire !")
|
||||||
|
}
|
||||||
|
if (p.baseUrl && !validateUrl(p.baseUrl)) {
|
||||||
|
errors.set("baseUrl","Mauvaise URL !")
|
||||||
|
}
|
||||||
|
if (errors.size === 0) {
|
||||||
|
providerApi.updateProvider(p)
|
||||||
|
.then((p)=>{
|
||||||
|
providers = providers.map((it)=>{return p.id===it.id ? p : it})
|
||||||
|
editingProvider = null
|
||||||
|
providerForm = {} as Provider
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
console.log(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEditProvider = async (id: string) => {
|
||||||
|
errors.clear()
|
||||||
|
if (addingProvider) return
|
||||||
|
providerApi.getProvider(id)
|
||||||
|
.then((data)=>{
|
||||||
|
providers = providers.map((it)=>it.id===id ? data : it)
|
||||||
|
editingProvider = id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDeleteProvider = (id: string) => {
|
||||||
|
providerApi.deleteProvider(id)
|
||||||
|
.then(() => {
|
||||||
|
providers = providers.filter((it)=>it.id!==id)
|
||||||
|
})
|
||||||
|
.catch((err) => {console.log(err)})
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
{#if addingProvider}
|
||||||
|
{#key "new"}
|
||||||
|
<ProviderForm
|
||||||
|
item={providerForm}
|
||||||
|
errors={errors}
|
||||||
|
providerTypes={data.providerTypes}
|
||||||
|
onSave={submitAddProvider}
|
||||||
|
onCancel={cancelAddProviderHandler}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
{:else}
|
||||||
|
<div class="rounded border border-dashed p-2 border-stone-500 cursor-pointer" onclick={enterAddProviderHandler} tabindex="0">
|
||||||
|
<i class="icon-plus"></i>
|
||||||
|
Ajouter un fournisseur
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#each providers as p (p.id) }
|
||||||
|
{#if editingProvider===p.id}
|
||||||
|
{#key p.id}
|
||||||
|
<ProviderForm
|
||||||
|
item={p}
|
||||||
|
errors={errors}
|
||||||
|
providerTypes={data.providerTypes}
|
||||||
|
onSave={submitEditProvider}
|
||||||
|
onCancel={cancelEditProviderHandler}
|
||||||
|
/>
|
||||||
|
{/key}
|
||||||
|
{:else}
|
||||||
|
<div class="rounded border p-2 my-2 border-stone-500 flex">
|
||||||
|
<div class="flex-1">
|
||||||
|
<span>{ p.name }</span>
|
||||||
|
<span class="text-stone-500">({ p.type })</span>
|
||||||
|
</div>
|
||||||
|
<button class="bt small mx-1" onclick={()=>handleEditProvider(p.id)}>
|
||||||
|
<i class="icon-edit"></i>
|
||||||
|
</button>
|
||||||
|
<button class="bt small mx-1" onclick={()=>handleDeleteProvider(p.id)}>
|
||||||
|
<i class="icon-trash-empty"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
export function load() {
|
import { providerApi } from "$lib/api/endpoints/provider"
|
||||||
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
|
export const load: PageLoad = async () => {
|
||||||
return {
|
return {
|
||||||
title: 'Paramétrage : Modèles',
|
title: 'Paramétrage : Modèles',
|
||||||
back: "/settings",
|
back: "/settings",
|
||||||
theme: "settings"
|
theme: "settings",
|
||||||
|
providerTypes: await providerApi.providerTypes(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
|
||||||
|
import { SvelteMap } from 'svelte/reactivity';
|
||||||
|
import FormError from '$lib/components/FormError.svelte';
|
||||||
|
import Select from 'svelte-select/no-styles';
|
||||||
|
import type { Provider, Item } from "$lib/types/api"
|
||||||
|
import { providerApi } from "$lib/api/endpoints/provider"
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
item: Provider
|
||||||
|
errors: Map<string,string>
|
||||||
|
providerTypes: Item[]
|
||||||
|
onSave: (item: Provider) => void
|
||||||
|
onCancel: () => void
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
item,
|
||||||
|
errors,
|
||||||
|
providerTypes,
|
||||||
|
onSave,
|
||||||
|
onCancel,
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
type Selectable = {
|
||||||
|
value: string
|
||||||
|
selected: boolean
|
||||||
|
}
|
||||||
|
let selectedModels = $state(item.models.split(";"))
|
||||||
|
let availableModels : Selectable[]= $state(item.models.split(";").map((it)=> {return {value:it, selected:true}}))
|
||||||
|
let modelsErrors = new SvelteMap<string,string>()
|
||||||
|
|
||||||
|
const autofocus = (node: HTMLElement) => {
|
||||||
|
node.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSave = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
item.models = selectedModels.join(";")
|
||||||
|
onSave(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
onCancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchModels = (e: Event) => {
|
||||||
|
e.preventDefault()
|
||||||
|
modelsErrors.clear()
|
||||||
|
providerApi.getAvailableModels(item)
|
||||||
|
.then((data) => {
|
||||||
|
availableModels = data.map((it)=>{
|
||||||
|
return { value: it, selected: selectedModels.includes(it)}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => { modelsErrors.set("models",err) })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleModelChange = () => {
|
||||||
|
selectedModels = availableModels.filter((it)=>it.selected).map((it)=>it.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="formgroup my-2">
|
||||||
|
<form onsubmit={handleSave} onchange={()=>{errors.clear()}}>
|
||||||
|
<div class="formelement">
|
||||||
|
<label for="name">Nom</label>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
type="text"
|
||||||
|
bind:value={item.name}
|
||||||
|
class="textinput"
|
||||||
|
placeholder="nom affiché"
|
||||||
|
use:autofocus
|
||||||
|
/>
|
||||||
|
<FormError key="name" errors={errors} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="formelement">
|
||||||
|
<label for="type" class="block my-1 flex-1">Type</label>
|
||||||
|
<Select items={providerTypes}
|
||||||
|
valueMode="id" itemId="id" label="text"
|
||||||
|
bind:value={item.type}
|
||||||
|
/>
|
||||||
|
<FormError key="type" errors={errors} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="formelement">
|
||||||
|
<label for="baseUrl">URL</label>
|
||||||
|
<input
|
||||||
|
id="baseUrl"
|
||||||
|
type="text"
|
||||||
|
bind:value={item.baseUrl}
|
||||||
|
class="textinput"
|
||||||
|
placeholder="http://127.0.0.1:8080/v1"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
<FormError key="baseUrl" errors={errors} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="formelement">
|
||||||
|
<label for="apiKey">Clé API</label>
|
||||||
|
<input
|
||||||
|
id="apiKey"
|
||||||
|
type="password"
|
||||||
|
bind:value={item.apiKey}
|
||||||
|
class="textinput"
|
||||||
|
placeholder="clé API (optionnel)"
|
||||||
|
autocomplete="new-password"
|
||||||
|
/>
|
||||||
|
<FormError key="apiKey" errors={errors} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="formelement">
|
||||||
|
<label for="apiKey">Models</label>
|
||||||
|
<button class="bt small" onclick={fetchModels}>
|
||||||
|
Afficher les modèles disponibles
|
||||||
|
</button>
|
||||||
|
<ol class="ml-2">
|
||||||
|
{#each availableModels as model (model.value)}
|
||||||
|
{#if model.value}
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" bind:checked={model.selected} onchange={handleModelChange}/>
|
||||||
|
{model.value}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</ol>
|
||||||
|
<div><FormError key="models" errors={modelsErrors} /></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-right">
|
||||||
|
<button type="button" class="bt" onclick={handleCancel}>Annuler</button>
|
||||||
|
<button type="submit" class="bt mx-4">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -31,6 +31,12 @@
|
|||||||
if (!validateEmail(form.email)) {
|
if (!validateEmail(form.email)) {
|
||||||
errors.set("email","Adresse email invalide !")
|
errors.set("email","Adresse email invalide !")
|
||||||
}
|
}
|
||||||
|
if (!form.firstname) {
|
||||||
|
errors.set("firstname","Le prénom est obligatoire !")
|
||||||
|
}
|
||||||
|
if (!form.lastname) {
|
||||||
|
errors.set("lastname","Le nom est obligatoire !")
|
||||||
|
}
|
||||||
if (form.password!==form.verifPassword) {
|
if (form.password!==form.verifPassword) {
|
||||||
errors.set("verifPassword","Les mots de pass ne correspondent pas.")
|
errors.set("verifPassword","Les mots de pass ne correspondent pas.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,38 +8,45 @@ html {
|
|||||||
@apply h-full;
|
@apply h-full;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply h-full bg-stone-100 text-stone-900 dark:bg-stone-900 dark:text-stone-200;
|
@apply h-full bg-stone-100 text-stone-950 dark:bg-stone-950 dark:text-stone-100;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
.formelement > label {
|
||||||
@apply text-xs text-stone-600 uppercase dark:text-stone-400;
|
@apply text-xs text-stone-600 uppercase dark:text-stone-400;
|
||||||
}
|
}
|
||||||
hr {
|
hr {
|
||||||
@apply border-stone-200 dark:border-stone-800;
|
@apply border-stone-200 dark:border-stone-800;
|
||||||
}
|
}
|
||||||
h2 {
|
h2 {
|
||||||
@apply mb-4 font-bold;
|
@apply mb-4 text-base font-bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.textinput {
|
.container {
|
||||||
@apply w-full rounded bg-white p-1 text-stone-900 dark:bg-stone-700 dark:text-stone-100;
|
@apply mx-auto max-w-3xl;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pointer {
|
.pointer {
|
||||||
@apply cursor-pointer;
|
@apply cursor-pointer;
|
||||||
}
|
}
|
||||||
.bt {
|
.bt {
|
||||||
@apply cursor-pointer rounded bg-stone-200 px-4 py-2 hover:bg-stone-300 dark:bg-stone-800 dark:hover:bg-stone-700;
|
appearance: button;
|
||||||
|
@apply cursor-pointer rounded bg-stone-300 px-4 py-1 font-bold hover:bg-stone-200 dark:bg-stone-800 dark:hover:bg-stone-700;
|
||||||
|
&.small {
|
||||||
|
@apply px-2 py-1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.menuitem {
|
.menuitem {
|
||||||
@apply text-stone-800 text-stone-950 dark:text-stone-300 hover:dark:text-stone-50;
|
@apply text-stone-950 dark:text-stone-300 hover:dark:text-stone-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
@apply p-2 text-lg;
|
@apply py-2 text-lg font-bold;
|
||||||
|
|
||||||
|
/*
|
||||||
h1 {
|
h1 {
|
||||||
@apply rounded bg-stone-200 px-2 font-bold text-sky-600 dark:bg-stone-800 dark:text-sky-200;
|
@apply rounded bg-stone-200 px-2 font-bold uppercase dark:bg-stone-800;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
&.settings {
|
&.settings {
|
||||||
h1 {
|
h1 {
|
||||||
@@ -56,10 +63,6 @@ h2 {
|
|||||||
@apply text-sky-500;
|
@apply text-sky-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.formgroup {
|
|
||||||
@apply rounded border border-stone-300 bg-stone-100 p-2 dark:border-stone-700 dark:bg-stone-900;
|
|
||||||
}
|
|
||||||
|
|
||||||
#outer {
|
#outer {
|
||||||
@apply flex h-full flex-row;
|
@apply flex h-full flex-row;
|
||||||
}
|
}
|
||||||
@@ -97,7 +100,7 @@ h2 {
|
|||||||
@apply ml-10;
|
@apply ml-10;
|
||||||
}
|
}
|
||||||
#sidebar {
|
#sidebar {
|
||||||
@apply fixed left-0 h-full w-full bg-stone-100 dark:bg-stone-900;
|
@apply fixed left-0 h-full w-full bg-stone-100 dark:bg-stone-950;
|
||||||
}
|
}
|
||||||
.mobileMenuClosed {
|
.mobileMenuClosed {
|
||||||
#sidebar {
|
#sidebar {
|
||||||
@@ -105,3 +108,148 @@ h2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* form */
|
||||||
|
|
||||||
|
.formgroup {
|
||||||
|
@apply rounded border border-stone-300 bg-stone-100 p-2 text-sm dark:border-stone-700 dark:bg-stone-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formelement {
|
||||||
|
@apply mb-3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textinput {
|
||||||
|
@apply h-8 w-full rounded border-stone-700 bg-stone-50 px-2 py-1 text-sm text-stone-900 dark:bg-stone-950 dark:text-stone-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* svelte select */
|
||||||
|
|
||||||
|
.svelte-select {
|
||||||
|
@apply relative flex h-8 w-full border-none text-stone-900 dark:bg-stone-950 dark:text-stone-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select .value-container {
|
||||||
|
@apply relative flex-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select input {
|
||||||
|
@apply absolute top-0 left-0 m-0 h-8 w-full cursor-default rounded border-stone-700 bg-transparent px-2 py-0 text-sm leading-10 text-stone-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select.focused {
|
||||||
|
/* @apply border-blue-600; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select.disabled {
|
||||||
|
@apply border-gray-200 bg-gray-200 text-gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select.disabled input {
|
||||||
|
@apply placeholder:text-gray-400 placeholder:opacity-100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select > .value-container > .selected-item {
|
||||||
|
@apply h-8 overflow-x-hidden px-4 leading-8 focus:outline-none;
|
||||||
|
}
|
||||||
|
.svelte-select .hide-selected-item {
|
||||||
|
@apply hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select .indicators {
|
||||||
|
@apply pointer-events-none absolute top-0 right-0 bottom-0 flex h-8 translate-y-0 items-center text-gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select .icons > * {
|
||||||
|
@apply transition-colors duration-200 ease-in-out focus:outline-none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select .clear-select {
|
||||||
|
@apply pointer-events-auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select.focused .icons,
|
||||||
|
.svelte-select .chevron:hover,
|
||||||
|
.svelte-select .clear-select:hover {
|
||||||
|
@apply text-gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select .clear-select {
|
||||||
|
@apply h-5 w-9 flex-none px-2 text-gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select .chevron {
|
||||||
|
@apply flex h-5 w-9 border-l-2 pt-0 pr-2 pl-2 text-gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select.multi {
|
||||||
|
@apply h-auto flex-wrap items-stretch pr-9 pl-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select.multi input {
|
||||||
|
@apply relative m-0 p-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select.error {
|
||||||
|
@apply border-red-500 bg-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.a11y-text {
|
||||||
|
@apply sr-only;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select-list {
|
||||||
|
@apply absolute right-0 left-0 z-10 max-h-64 w-full overflow-y-auto rounded-sm border-none bg-stone-50 px-4 shadow-md dark:bg-stone-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select-list .list-group-title {
|
||||||
|
@apply h-8 cursor-default px-5 text-sm leading-10 font-medium text-ellipsis whitespace-nowrap text-gray-400 uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select-list .empty {
|
||||||
|
@apply py-5 text-center text-gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svelte-select-item {
|
||||||
|
@apply h-8 cursor-default overflow-hidden px-5 leading-10 text-ellipsis whitespace-nowrap text-gray-800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.group-item {
|
||||||
|
@apply px-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.active {
|
||||||
|
@apply bg-blue-600 text-white dark:bg-blue-900;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.not-selectable {
|
||||||
|
@apply text-gray-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.first {
|
||||||
|
@apply rounded-t-sm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item.hover:not(.active) {
|
||||||
|
@apply bg-blue-100 dark:bg-stone-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi input {
|
||||||
|
flex: 1 1 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-item {
|
||||||
|
@apply mt-1 mr-1 flex h-8 max-w-full cursor-default items-center overflow-hidden rounded-sm border border-gray-200 bg-gray-100 pr-1 pl-1 leading-8 text-ellipsis whitespace-nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-item.disabled {
|
||||||
|
@apply hover:bg-gray-300 hover:text-gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-item-clear {
|
||||||
|
@apply flex w-5 items-center justify-center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-item {
|
||||||
|
@apply list-none;
|
||||||
|
}
|
||||||
@@ -113,6 +113,24 @@
|
|||||||
"css": "left-big",
|
"css": "left-big",
|
||||||
"code": 59400,
|
"code": 59400,
|
||||||
"src": "fontawesome"
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "5211af474d3a9848f67f945e2ccaf143",
|
||||||
|
"css": "cancel",
|
||||||
|
"code": 59401,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "44e04715aecbca7f266a17d5a7863c68",
|
||||||
|
"css": "plus",
|
||||||
|
"code": 59402,
|
||||||
|
"src": "fontawesome"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
|
||||||
|
"css": "trash-empty",
|
||||||
|
"code": 59403,
|
||||||
|
"src": "fontawesome"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,9 @@
|
|||||||
.icon-right-big:before { content: '\e806'; } /* '' */
|
.icon-right-big:before { content: '\e806'; } /* '' */
|
||||||
.icon-logout:before { content: '\e807'; } /* '' */
|
.icon-logout:before { content: '\e807'; } /* '' */
|
||||||
.icon-left-big:before { content: '\e808'; } /* '' */
|
.icon-left-big:before { content: '\e808'; } /* '' */
|
||||||
|
.icon-cancel:before { content: '\e809'; } /* '' */
|
||||||
|
.icon-plus:before { content: '\e80a'; } /* '' */
|
||||||
|
.icon-trash-empty:before { content: '\e80b'; } /* '' */
|
||||||
.icon-facebook:before { content: '\f09a'; } /* '' */
|
.icon-facebook:before { content: '\f09a'; } /* '' */
|
||||||
.icon-tasks:before { content: '\f0ae'; } /* '' */
|
.icon-tasks:before { content: '\f0ae'; } /* '' */
|
||||||
.icon-menu:before { content: '\f0c9'; } /* '' */
|
.icon-menu:before { content: '\f0c9'; } /* '' */
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'fontello';
|
font-family: 'fontello';
|
||||||
src: url('../font/fontello.eot?40016728');
|
src: url('../font/fontello.eot?20532310');
|
||||||
src: url('../font/fontello.eot?40016728#iefix') format('embedded-opentype'),
|
src: url('../font/fontello.eot?20532310#iefix') format('embedded-opentype'),
|
||||||
url('../font/fontello.svg?40016728#fontello') format('svg');
|
url('../font/fontello.svg?20532310#fontello') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'fontello';
|
font-family: 'fontello';
|
||||||
src: url('data:application/octet-stream;base64,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 truncated
|
src: url('data:application/octet-stream;base64,d09GRgABAAAAABtwAA8AAAAALBgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABWAAAADsAAABUIIslek9TLzIAAAGUAAAARAAAAGA+I1PFY21hcAAAAdgAAADdAAAC0G9nGq1jdnQgAAACuAAAAAsAAAAOAAAAAGZwZ20AAALEAAAG7QAADgxiLvl6Z2FzcAAACbQAAAAIAAAACAAAABBnbHlmAAAJvAAADjQAABQUeuV0NWhlYWQAABfwAAAAMwAAADYxnQn3aGhlYQAAGCQAAAAgAAAAJAhaBINobXR4AAAYRAAAADUAAABYTP3//WxvY2EAABh8AAAALgAAAC48eDhibWF4cAAAGKwAAAAgAAAAIAFODsduYW1lAAAYzAAAAXUAAALNzZ0dHnBvc3QAABpEAAAAsAAAAO6/M3V0cHJlcAAAGvQAAAB6AAAAnH62O7Z4nGNgZGBg4GIwYLBjYHJx8wlh4MtJLMljkGJgYYAAkDwymzEnMz2RgQPGA8qxgGkOIGaDiAIAJjsFSAB4nGNgYW5gnMDAysDAVMW0h4GBoQdCMz5gMGRkAooysDIzYAUBaa4pDAdeMHy8xRz0P4shinkNwzSgMCOKIiYAkDgNb3ic5ZLLbcMwEAWHsaJ8rHxsRanCDbqNHA0YcAUuIadc3IHhCvbIrcB5a+4t6iArjAA+EFyCs8A9sBAb0UH5oRD1rbTc8gXPt7zjS+tP3pX0tqy7eqwnH33rez/42S/XKxjz+UwVnbX580V+pw6dbtbzwCNP6r9k4IVX3tR9xZqRDyZt7WdP/l81xK8MuZrCQSP8WaI3xZLwbUk4tyRmwRK9PZbIApbIB5bIDJbEjFgiW1gSt6u7hgxSjw25pJ4asoqPDfnFtw2ZxvcNOccPDWLvuaE5wC8Npl/P3VtNAAAAeJxjYEAGAAAOAAEAeJytV2tbG8cVntUNjAEDQtjNuu4oY1GXHckkcRxiKw7ZZVEcJanAuN11brtIuE2TXpLe6DW9X5Q/c1a0T51v+Wl5z8xKAQfcp89TPui8M/POnOucWUhoSeJ+FMZSdh+J+Z0uVe49iOiGS9fi5KEc3o+o0Eg/mxbTot9X+269TiImEaitkXBEkPhNcjTJ5GGTClrVVb1JRS0HR8XlmvADqgYySfyssBz4WaMYUCHYO5Q0qwCCdECl3uGoUCjgGKofXK7z7Gi+5viXJaDyR1WnijVFohcdxKMVp2AUljQVPaoFEeujlSDICa4cSPq8R6XVB6NrzlwQ9kOqhFGdio14960IZHcYSer1MLUJNm0w2ohjmVk2LLqGqXwkaZ3X15n5eS+SiMYwlTTTixLMSF6bYXST0c3ETeI4dhEtmg36JHYjEl0m1zF2u3SF0ZVu+mhB9JnxqCz243iQxuR4cZx7EMsB/FF+3KSylrCg1Ejh01TQi2hK+TStfGQAW5ImVUy4EQk5yKb2fcmL7K5rzedfEknYp/JaHYuBHMohdGXr5QYitBMlPTfdjSMV12NJm/cirLkcl9yUJk1pOhd4I1GwaZ7GUPkK5aL8lAr7D8npwxCaWmvSOS3Z2nm4VRL7kk+gzSRmSrJlrJ3Ro3PzIgj9tfqkcM7rk4U0a09xPJgQwPVEhkOVclJNsIXLCSHpwsixlUitSresirkzttNV7BLul64d3zSvjUNHc7OiGEKLq+rxGor4gs4KhZAG6VaTFjSoUtKF4DU+AAAZogUe7WK0YPK1iIMWTFAkYtCHZloMEjlMJC0ibE1a0t29KCsNtuKrNHegDptU1d2dqHvPTrp1zFfN/LLOxFJwP8qWlgJyUp8WPb5yKC0/u8A/C/ghZwW5KDZ6Ucbhg7/+EBmG2oW1usK2MXbtOm/BTeaZGJ50YH8HsyeTdUYKMyGqCvFCQd0ZOY5jslXTIhOFcC+iJeXLkOZRfnOIcOLL5D+XLjliUVSF7/scgWWsOWm2PO3Rp577NMK1Ah9rXpMu6sxheQnxZvk1nRVZPqWzEktXZ2WWl3VWYfl1nU2xvKKzaZbf0Nk5lp5W4/hTJUGklWyR8w7flibpY4srk8WP7GLz2OLqZPFjuyi1oAvemX7CqX9bV9nP4/7V4Z+EXU/DP5YK/rG8Cv9YNuAfy1X4x/Kb8I/lNfjH8lvwj+Ua/GPZ0rJtCva6htpLiUTTc5LApBSXsMU1u67pukfXcR+fwVXoyDOyqdINxY39iQyXvX92nOJsvhJyxdEza1nZqYURmiJ7+dyx8JzFuaHl88by53Ga5YRf1Ylre6otPC9W/iX4b+uO2shuODX29SbiAQdOtx+XJd1o0gu6dbHdpI3/RkVh90F/ESkSKw3Zkh1uCQjt3eGwozroIREePnRdvEgbjlNbRoRvoXet0EXQSminDUPLZoVP5wPvYNhSUraHOPP2SZps2fOoovwxW1LCPWVzJzoqybJ0j0qr5adinzvtDJq2MjvUdkKV4PHrmnC3s69SKUgGisp4VLFcClIXOOFO9/ieFKah/6tt5FhBwza/WDOB0YLzTlGibE+toIkgGWUUXPkrp+JENqLBRhTxm3fSL3WhENrjWEjMllfzWKg2wvTSZIlmzPq26rBSzuKdSQjZGRtpEntRS7bxoLP1+aRku/JUUKWB0d3j3y42iadVe54txSX/8jFLgnG6Ev7AedzlcYo30T9aHMVtuhhEPRdvqmzHrWzdWca9feXE6q7bO7Hqn7r3STsCTbe8Jync0nTbG8I2rjE4dSYVCW3ROnaExmWuz1Ub+RQfaL51nQtU4fq0cPPs+ds6m8FbM97yP5Z05/9VxewT97G2Qqs6Vi/1OLezgwZ8yxtH5VWMbnt1lccl92YSgrsIQc1ee3yN4IZXW3QTt/y1M+a7OM5ZrtILwK9rehHiDY5iiHDLbTy842i9qbmg6Q3Ab+uRENsAPQCHwY4eOWZmF8DM3GNOB2CPOQzuM4fBd5jD4Lv6CL0wAIqAHINifeTYuQdAdu4t5jmM3maeQe8wz6B3mWfQe6wzBEhYJ4OUdTLYZ50M+sx5FWDAHAYHzGHwkDkMvmfs2gL6vrGL0fvGLkY/MHYx+sDYxehDYxejHxq7GP3I2MXox4hxe5LAn5gRbQJ+ZOErgB9z0M3Ix+ineGtzzs8sZM7PDcfJOb/A5pcmp/7SjMyOQwt5x68sZPqvcU5O+I2FTPithUz4Hbh3Juf93owM/RMLmf4HC5n+R+zMCX+ykAl/tpAJfwH35cl5fzUjQ/+bhUz/u4VM/wd25oR/WsiEoYVM+FSPzpsvW6q4o1KhGOKfJrTB2Pdo+oCKV3uH48e6+QUl2gFBAAAAAAEAAf//AA94nJ1YXWwb2XW+5975J8Xf+aEkihY5/BMlUTJJkbYkU7R+aYmWZFmWTO2ubLf2ypWNrnfXdRzDcZ2s5cQvduFuWjQNEjsrJwGyCdYLIwGKNi0apw/75KKBtw9NH4o+OAgKFylQwMaa7rlDOvGi3aaoJM7MvXNneM8533fOd0QoIc8/YI/oT0iCzJGxyui+MCUwBoz0AGWspgGDuRzQfQQIbBHCyBZhlLI6YYweI5TRA+OV8qgdswXJ6gXdA3YsJcmSbMeSQ8lCcWiMFuVSmZaykCqUIRcBS5fkCOwAcwfTZQ+kkvgr4UNmPlcslXFVKgIssHnv1GBu+pDRSSU3UBAYg7Do8QhT87B57+G9zYeLk6Jb6VQFUWBANTmsr0znBv/0TLd/bXt8qgZt1WW4s/9KTd1piYImgyAAk/AlETEU8KXPTsxfqdWu/NPEuZTH1KIuJkoaUEUDQbR2qrMsN1C4OtubSZ0n0vPnzzeFAbZE/CRHRsgkWSFHyBuV0wsetB1qflBd6hZxKa4tHyhU2fKi2+iWDCI6TITLbo2CREC6RCQB/y4RQbjiQe8SVscTI8faAH16YP21tfrK8tLi3Oy+mYnxsfLw7nZDby/ZwagvIO3oBcefpUIe/VfKlSRDB12yo7Ek+lTESVrGkBVLuYhg4Y0Y+j1n4kUWytQUdSllJ1PFoQJ3cRmsXOrXS+bWhuf6KzAlZMajyQSj7yxNNEKTiyC4fd3J4agUz1YXp9t7fEpsV7Lb54Fnf7d8bhn/4NqlDy7Ti/cuwEQ5O7d7rZ8lEtHJtDA107q/zib7+n5qdkCb4ZtvvDI+Pz8e2TW+q5A0Q+EOavo6NGomC7vGw/TGCH9gufG4fole+OF56co/9GZhku2d9xltoRC0bqOPyPNz7BZzEYXoJEvylcEkoiKdCjAqEEAn1ggT2BYRqLBFKKGIVUCsApwL2/2hgCh19pYKSQ+NQDEBUmwAkoUSRCDhYJC7o5QzLVP3gmEV0Zc586Ee0WmoIwSvN94xhs0Rw4CL5jL8WVvnlxZO3rx5snu6XVXfO00zc1Gv1uUxaaA7IHT8Z+MdXd+jj5hwsbT8czMxtw43H/wR1X1SQF7/wiht79c1gj+cd3fZmmPPJpmqjJ+o1/YKRBhBvJBCutOHsEaTREHckgDnX5jDgPMP7aT03KuvHDywr9qbiXUHA7IU6oVCMuYBM1dMGLrkRcxwgxzzBoBTUuoFJCXSTZZinHYFpFspOQBIzaECN7rUmsznduAAf0cgZyJTrVIxZ7VeJuNEBOjI0vklunJ2BcKKfFJzBdOS6F1sk+X97R2qLPguKm5fp7Ug+aRpUxCVtOZVXpcV0MSTisdKNNcq+0MdqsL8F2U3eMPWguiVq7ogqM3FGqw3Q3+e3/dFjM6c5JGMRRBH25Ra2KfJG6p7VJQqEdEjuXPecKcX3LKztr2ju192y/riS0tdI6I4EW4t7fCB+0UM6EfMTQwSIYlKDAkrbjEAAT6FI3IuaPn9Vk6Swr2JJu+GCmVmmTlkHGPIpiwU0ScfTe9sxHdOa6FMuX/fR7OZ8WRYuYEkEb70/pWp0cOHRweXV0bTUK0my8sr8DeHLzkkIkTEfTxh/4pYkIiX7CZlUgW5YlLine truncated
|
||||||
url('data:application/octet-stream;base64,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 truncated
|
url('data:application/octet-stream;base64,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 truncated
|
||||||
}
|
}
|
||||||
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
|
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
|
||||||
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
|
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'fontello';
|
font-family: 'fontello';
|
||||||
src: url('../font/fontello.svg?40016728#fontello') format('svg');
|
src: url('../font/fontello.svg?20532310#fontello') format('svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
@@ -66,6 +66,9 @@
|
|||||||
.icon-right-big:before { content: '\e806'; } /* '' */
|
.icon-right-big:before { content: '\e806'; } /* '' */
|
||||||
.icon-logout:before { content: '\e807'; } /* '' */
|
.icon-logout:before { content: '\e807'; } /* '' */
|
||||||
.icon-left-big:before { content: '\e808'; } /* '' */
|
.icon-left-big:before { content: '\e808'; } /* '' */
|
||||||
|
.icon-cancel:before { content: '\e809'; } /* '' */
|
||||||
|
.icon-plus:before { content: '\e80a'; } /* '' */
|
||||||
|
.icon-trash-empty:before { content: '\e80b'; } /* '' */
|
||||||
.icon-facebook:before { content: '\f09a'; } /* '' */
|
.icon-facebook:before { content: '\f09a'; } /* '' */
|
||||||
.icon-tasks:before { content: '\f0ae'; } /* '' */
|
.icon-tasks:before { content: '\f0ae'; } /* '' */
|
||||||
.icon-menu:before { content: '\f0c9'; } /* '' */
|
.icon-menu:before { content: '\f0c9'; } /* '' */
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
.icon-right-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-right-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.icon-left-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-left-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
.icon-trash-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.icon-facebook { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-facebook { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.icon-tasks { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-tasks { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
.icon-right-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-right-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-logout { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.icon-left-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-left-big { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
.icon-trash-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.icon-facebook { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-facebook { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.icon-tasks { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-tasks { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
.icon-menu { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); }
|
||||||
|
|||||||
+10
-7
@@ -1,11 +1,11 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'fontello';
|
font-family: 'fontello';
|
||||||
src: url('../font/fontello.eot?64785462');
|
src: url('../font/fontello.eot?1806785');
|
||||||
src: url('../font/fontello.eot?64785462#iefix') format('embedded-opentype'),
|
src: url('../font/fontello.eot?1806785#iefix') format('embedded-opentype'),
|
||||||
url('../font/fontello.woff2?64785462') format('woff2'),
|
url('../font/fontello.woff2?1806785') format('woff2'),
|
||||||
url('../font/fontello.woff?64785462') format('woff'),
|
url('../font/fontello.woff?1806785') format('woff'),
|
||||||
url('../font/fontello.ttf?64785462') format('truetype'),
|
url('../font/fontello.ttf?1806785') format('truetype'),
|
||||||
url('../font/fontello.svg?64785462#fontello') format('svg');
|
url('../font/fontello.svg?1806785#fontello') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
@media screen and (-webkit-min-device-pixel-ratio:0) {
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'fontello';
|
font-family: 'fontello';
|
||||||
src: url('../font/fontello.svg?64785462#fontello') format('svg');
|
src: url('../font/fontello.svg?1806785#fontello') format('svg');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
@@ -63,6 +63,9 @@
|
|||||||
.icon-right-big:before { content: '\e806'; } /* '' */
|
.icon-right-big:before { content: '\e806'; } /* '' */
|
||||||
.icon-logout:before { content: '\e807'; } /* '' */
|
.icon-logout:before { content: '\e807'; } /* '' */
|
||||||
.icon-left-big:before { content: '\e808'; } /* '' */
|
.icon-left-big:before { content: '\e808'; } /* '' */
|
||||||
|
.icon-cancel:before { content: '\e809'; } /* '' */
|
||||||
|
.icon-plus:before { content: '\e80a'; } /* '' */
|
||||||
|
.icon-trash-empty:before { content: '\e80b'; } /* '' */
|
||||||
.icon-facebook:before { content: '\f09a'; } /* '' */
|
.icon-facebook:before { content: '\f09a'; } /* '' */
|
||||||
.icon-tasks:before { content: '\f0ae'; } /* '' */
|
.icon-tasks:before { content: '\f0ae'; } /* '' */
|
||||||
.icon-menu:before { content: '\f0c9'; } /* '' */
|
.icon-menu:before { content: '\f0c9'; } /* '' */
|
||||||
|
|||||||
@@ -146,11 +146,11 @@
|
|||||||
}
|
}
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'fontello';
|
font-family: 'fontello';
|
||||||
src: url('./font/fontello.eot?72761720');
|
src: url('./font/fontello.eot?13627011');
|
||||||
src: url('./font/fontello.eot?72761720#iefix') format('embedded-opentype'),
|
src: url('./font/fontello.eot?13627011#iefix') format('embedded-opentype'),
|
||||||
url('./font/fontello.woff?72761720') format('woff'),
|
url('./font/fontello.woff?13627011') format('woff'),
|
||||||
url('./font/fontello.ttf?72761720') format('truetype'),
|
url('./font/fontello.ttf?13627011') format('truetype'),
|
||||||
url('./font/fontello.svg?72761720#fontello') format('svg');
|
url('./font/fontello.svg?13627011#fontello') format('svg');
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
}
|
}
|
||||||
@@ -242,6 +242,17 @@
|
|||||||
<div class="span3" title="Code: 0xe808">
|
<div class="span3" title="Code: 0xe808">
|
||||||
<i class="demo-icon icon-left-big"></i> <span class="i-name">icon-left-big</span><span class="i-code">0xe808</span>
|
<i class="demo-icon icon-left-big"></i> <span class="i-name">icon-left-big</span><span class="i-code">0xe808</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="span3" title="Code: 0xe809">
|
||||||
|
<i class="demo-icon icon-cancel"></i> <span class="i-name">icon-cancel</span><span class="i-code">0xe809</span>
|
||||||
|
</div>
|
||||||
|
<div class="span3" title="Code: 0xe80a">
|
||||||
|
<i class="demo-icon icon-plus"></i> <span class="i-name">icon-plus</span><span class="i-code">0xe80a</span>
|
||||||
|
</div>
|
||||||
|
<div class="span3" title="Code: 0xe80b">
|
||||||
|
<i class="demo-icon icon-trash-empty"></i> <span class="i-name">icon-trash-empty</span><span class="i-code">0xe80b</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
<div class="span3" title="Code: 0xf09a">
|
<div class="span3" title="Code: 0xf09a">
|
||||||
<i class="demo-icon icon-facebook"></i> <span class="i-name">icon-facebook</span><span class="i-code">0xf09a</span>
|
<i class="demo-icon icon-facebook"></i> <span class="i-name">icon-facebook</span><span class="i-code">0xf09a</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -251,11 +262,11 @@
|
|||||||
<div class="span3" title="Code: 0xf0c9">
|
<div class="span3" title="Code: 0xf0c9">
|
||||||
<i class="demo-icon icon-menu"></i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span>
|
<i class="demo-icon icon-menu"></i> <span class="i-name">icon-menu</span><span class="i-code">0xf0c9</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="span3" title="Code: 0xf113">
|
<div class="span3" title="Code: 0xf113">
|
||||||
<i class="demo-icon icon-github"></i> <span class="i-name">icon-github</span><span class="i-code">0xf113</span>
|
<i class="demo-icon icon-github"></i> <span class="i-name">icon-github</span><span class="i-code">0xf113</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
<div class="span3" title="Code: 0xf179">
|
<div class="span3" title="Code: 0xf179">
|
||||||
<i class="demo-icon icon-apple"></i> <span class="i-name">icon-apple</span><span class="i-code">0xf179</span>
|
<i class="demo-icon icon-apple"></i> <span class="i-name">icon-apple</span><span class="i-code">0xf179</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -265,11 +276,11 @@
|
|||||||
<div class="span3" title="Code: 0xf1a0">
|
<div class="span3" title="Code: 0xf1a0">
|
||||||
<i class="demo-icon icon-google"></i> <span class="i-name">icon-google</span><span class="i-code">0xf1a0</span>
|
<i class="demo-icon icon-google"></i> <span class="i-name">icon-google</span><span class="i-code">0xf1a0</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<div class="span3" title="Code: 0xf1d8">
|
<div class="span3" title="Code: 0xf1d8">
|
||||||
<i class="demo-icon icon-paper-plane"></i> <span class="i-name">icon-paper-plane</span><span class="i-code">0xf1d8</span>
|
<i class="demo-icon icon-paper-plane"></i> <span class="i-name">icon-paper-plane</span><span class="i-code">0xf1d8</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
<div class="span3" title="Code: 0xf1da">
|
<div class="span3" title="Code: 0xf1da">
|
||||||
<i class="demo-icon icon-history"></i> <span class="i-name">icon-history</span><span class="i-code">0xf1da</span>
|
<i class="demo-icon icon-history"></i> <span class="i-name">icon-history</span><span class="i-code">0xf1da</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Binary file not shown.
@@ -24,6 +24,12 @@
|
|||||||
|
|
||||||
<glyph glyph-name="left-big" unicode="" d="M857 350v-71q0-30-18-51t-47-21h-393l164-164q21-20 21-50t-21-50l-42-43q-21-20-51-20-29 0-50 20l-364 364q-20 21-20 50 0 29 20 51l364 363q21 21 50 21 29 0 51-21l42-41q21-22 21-51t-21-51l-164-164h393q29 0 47-20t18-51z" horiz-adv-x="857.1" />
|
<glyph glyph-name="left-big" unicode="" d="M857 350v-71q0-30-18-51t-47-21h-393l164-164q21-20 21-50t-21-50l-42-43q-21-20-51-20-29 0-50 20l-364 364q-20 21-20 50 0 29 20 51l364 363q21 21 50 21 29 0 51-21l42-41q21-22 21-51t-21-51l-164-164h393q29 0 47-20t18-51z" horiz-adv-x="857.1" />
|
||||||
|
|
||||||
|
<glyph glyph-name="cancel" unicode="" d="M724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="plus" unicode="" d="M786 439v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q23 0 38-16t16-38z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
|
<glyph glyph-name="trash-empty" unicode="" d="M286 439v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m143 0v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m142 0v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q7 0 12-5t5-13z m72-404v529h-500v-529q0-12 4-22t8-15 6-5h464q2 0 6 5t8 15 4 22z m-375 601h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q23 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" />
|
||||||
|
|
||||||
<glyph glyph-name="facebook" unicode="" d="M535 843v-147h-87q-48 0-65-20t-17-60v-106h164l-22-165h-142v-424h-171v424h-142v165h142v122q0 104 58 161t155 57q82 0 127-7z" horiz-adv-x="571.4" />
|
<glyph glyph-name="facebook" unicode="" d="M535 843v-147h-87q-48 0-65-20t-17-60v-106h164l-22-165h-142v-424h-171v424h-142v165h142v122q0 104 58 161t155 57q82 0 127-7z" horiz-adv-x="571.4" />
|
||||||
|
|
||||||
<glyph glyph-name="tasks" unicode="" d="M571 64h358v72h-358v-72z m-214 286h572v71h-572v-71z m357 286h215v71h-215v-71z m286-465v-142q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v142q0 15 11 26t25 10h928q15 0 25-10t11-26z m0 286v-143q0-14-11-25t-25-10h-928q-15 0-25 10t-11 25v143q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 286v-143q0-14-11-25t-25-11h-928q-15 0-25 11t-11 25v143q0 14 11 25t25 11h928q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
<glyph glyph-name="tasks" unicode="" d="M571 64h358v72h-358v-72z m-214 286h572v71h-572v-71z m357 286h215v71h-215v-71z m286-465v-142q0-15-11-25t-25-11h-928q-15 0-25 11t-11 25v142q0 15 11 26t25 10h928q15 0 25-10t11-26z m0 286v-143q0-14-11-25t-25-10h-928q-15 0-25 10t-11 25v143q0 15 11 25t25 11h928q15 0 25-11t11-25z m0 286v-143q0-14-11-25t-25-11h-928q-15 0-25 11t-11 25v143q0 14 11 25t25 11h928q15 0 25-11t11-25z" horiz-adv-x="1000" />
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user