diff --git a/backend/internal/adapter/database/turso/file_repository.go b/backend/internal/adapter/database/turso/file_repository.go index 443ce08..5c3092c 100644 --- a/backend/internal/adapter/database/turso/file_repository.go +++ b/backend/internal/adapter/database/turso/file_repository.go @@ -59,7 +59,6 @@ func (fr *TursoFileRepository) Upload(reader io.Reader, path string, name string ContentType: contentType, VersionId: utility.GenID(), DateCreated: time.Now(), - DateUpdated: time.Now(), Content: bytes, } diff --git a/backend/internal/adapter/database/turso/user_repository.go b/backend/internal/adapter/database/turso/user_repository.go index baa8832..94b8a53 100644 --- a/backend/internal/adapter/database/turso/user_repository.go +++ b/backend/internal/adapter/database/turso/user_repository.go @@ -131,13 +131,16 @@ func (ur *TursoUserRepository) ListUserAddresses(id string) ([]*domain.UserAddre return ur.AddressTable.SelectWhere("user_id=$1", id) } -func (ur *TursoUserRepository) CreateUserAddress(userid string, addr *domain.UserAddress) (*domain.UserAddress, error) { - addr.UserID = userid +func (ur *TursoUserRepository) GetUserAddress(addrID string) (*domain.UserAddress, error) { + return ur.AddressTable.Get(addrID) +} + +func (ur *TursoUserRepository) CreateUserAddress(addr *domain.UserAddress) (*domain.UserAddress, error) { 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) } -func (ur *TursoUserRepository) DeleteUserAddress(userid string, addrID string) error { - return ur.AddressTable.DeleteWhere("id=$1 and user_id=$2", addrID, userid) +func (ur *TursoUserRepository) DeleteUserAddress(addrID string) error { + return ur.AddressTable.DeleteWhere("id=$1", addrID) } diff --git a/backend/internal/adapter/security/jwt.go b/backend/internal/adapter/security/jwt.go index 16d2d89..34bd948 100644 --- a/backend/internal/adapter/security/jwt.go +++ b/backend/internal/adapter/security/jwt.go @@ -117,8 +117,8 @@ func (tm *JwtTokenManager) TokenMiddleware(next echo.HandlerFunc) echo.HandlerFu // Rend les infos disponibles aux handlers suivants via c.Get(...) c.Set(auth.ContextUserIDKey, claims.ID) - c.Set(auth.ContextEmailKey, claims.Email) - c.Set(auth.ContextNameKey, claims.Firstname+" "+claims.Lastname) + //c.Set(auth.ContextEmailKey, claims.Email) + //c.Set(auth.ContextNameKey, claims.Firstname+" "+claims.Lastname) return next(c) } diff --git a/backend/internal/domain/file.go b/backend/internal/domain/file.go index bb9788f..b9a075b 100644 --- a/backend/internal/domain/file.go +++ b/backend/internal/domain/file.go @@ -6,15 +6,15 @@ import ( ) type File struct { - ID string `db:"id" json:"id"` - Name string `db:"name" json:"name"` - ContentType string `db:"content_type" json:"contentType"` - StoragePath string `db:"storage_path" json:"-"` - StorageFilename string `db:"storage_filename" json:"-"` - Content []byte `db:"content" json:"-"` - DateCreated time.Time `db:"date_created" json:"-"` - DateUpdated time.Time `db:"date_updated" json:"-"` - VersionId string `db:"_version" json:"-"` + ID string `db:"id" json:"id"` + Name string `db:"name" json:"name"` + ContentType string `db:"content_type" json:"contentType"` + StoragePath string `db:"storage_path" json:"-"` + StorageFilename string `db:"storage_filename" json:"-"` + Content []byte `db:"content" json:"-"` + DateCreated time.Time `db:"date_created" json:"-"` + DateUpdated *time.Time `db:"date_updated" json:"-"` + VersionId string `db:"_version" json:"-"` } type FileRepository interface { diff --git a/backend/internal/domain/settings.go b/backend/internal/domain/settings.go index ecfc611..baa0c8c 100644 --- a/backend/internal/domain/settings.go +++ b/backend/internal/domain/settings.go @@ -7,7 +7,7 @@ type Settings struct { DefaultLang string `db:"default_lang"` RegisterEnabled bool `db:"register_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"` VersionId string `db:"_version" json:"_version"` } diff --git a/backend/internal/domain/user.go b/backend/internal/domain/user.go index 4abeb58..840cf90 100644 --- a/backend/internal/domain/user.go +++ b/backend/internal/domain/user.go @@ -5,25 +5,25 @@ import ( ) type User struct { - ID string `db:"id" json:"id"` - Firstname string `db:"firstname" json:"firstname,omitempty"` - Lastname string `db:"lastname" json:"lastname,omitempty"` - Password string `db:"password" json:"-"` - Email string `db:"email" json:"email,omitempty"` - PictureID string `db:"picture" json:"picture,omitempty"` - Enabled bool `db:"enabled" json:"enabled"` - Theme string `db:"theme" json:"theme"` - Lang string `db:"lang" json:"lang"` - DateCreated time.Time `db:"_date_created" json:"_date_created"` - DateUpdated time.Time `db:"_date_updated" json:"_date_updated"` - VersionId string `db:"_version" json:"_version"` + ID string `db:"id" json:"id"` + Firstname string `db:"firstname" json:"firstname,omitempty"` + Lastname string `db:"lastname" json:"lastname,omitempty"` + Password string `db:"password" json:"-"` + Email string `db:"email" json:"email,omitempty"` + PictureID string `db:"picture" json:"picture,omitempty"` + Enabled bool `db:"enabled" json:"enabled"` + Theme string `db:"theme" json:"theme"` + Lang string `db:"lang" json:"lang"` + 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 Team struct { ID string `db:"id" json:"id"` Label string `db:"label" json:"label"` DefaultModelID *string `db:"default_model_id" json:"default_model_id"` - DateCreated *time.Time `db:"_date_created" json:"_date_created"` + DateCreated time.Time `db:"_date_created" json:"_date_created"` DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"` VersionId string `db:"_version" json:"_version"` } @@ -33,7 +33,7 @@ type UserTeam struct { UserID string `db:"user_id" json:"user_id"` TeamID string `db:"team_id" json:"team_id"` Administrator bool `db:"administrator" json:"administrator"` - 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"` VersionId string `db:"_version" json:"_version"` } @@ -42,8 +42,8 @@ type UserAddress struct { ID string `db:"id" json:"id"` UserID string `db:"user_id" json:"user_id"` Type string `db:"type" json:"type"` - Address string `db:"address_id" json:"address"` - DateCreated *time.Time `db:"_date_created" json:"_date_created"` + Address string `db:"address" json:"address"` + DateCreated time.Time `db:"_date_created" json:"_date_created"` DateUpdated *time.Time `db:"_date_updated" json:"_date_updated"` VersionId string `db:"_version" json:"_version"` } @@ -58,6 +58,15 @@ type UserWithTeams struct { Teams []*TeamRelation `json:"teams"` } +func AddressTypes() []string { + return []string{ + "email", + "telegram", + "whatsapp", + "signal", + } +} + type UserRepository interface { FindUser(id string) (*User, error) FindUserByEmail(email string) (*User, error) @@ -81,7 +90,8 @@ type UserRepository interface { PopulateUserWithTeams(user *User) (*UserWithTeams, error) ListUserAddresses(userid string) ([]*UserAddress, error) - CreateUserAddress(userid string, addr *UserAddress) (*UserAddress, error) - UpdateUserAddress(userid string, addr *UserAddress) (*UserAddress, error) - DeleteUserAddress(userid string, addrID string) error + GetUserAddress(addrID string) (*UserAddress, error) + CreateUserAddress(addr *UserAddress) (*UserAddress, error) + UpdateUserAddress(addr *UserAddress) (*UserAddress, error) + DeleteUserAddress(addrID string) error } diff --git a/backend/internal/http/handlers/user.go b/backend/internal/http/handlers/user.go index f4f21d9..d2395d2 100644 --- a/backend/internal/http/handlers/user.go +++ b/backend/internal/http/handlers/user.go @@ -17,6 +17,10 @@ func NewUserGroup(prefix string, e *echo.Group, service *user.Service, middlewar auth := e.Group(prefix, middlewares...) auth.GET("/me", userHandler.Me) 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 } @@ -43,15 +47,86 @@ func (h UserHandler) Update(c *echo.Context) error { } updUser.ID = id - isAdmin := false // TODO : check if caller is admin - if !(isAdmin || userID == id) { + if !(userID == id) { 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 { return c.String(http.StatusBadRequest, err.Error()) } 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") +} diff --git a/backend/internal/service/auth/authenticator.go b/backend/internal/service/auth/authenticator.go index 844150d..9767d8a 100644 --- a/backend/internal/service/auth/authenticator.go +++ b/backend/internal/service/auth/authenticator.go @@ -31,8 +31,10 @@ type Session struct { } const ContextUserIDKey = "userID" -const ContextEmailKey = "userEmail" -const ContextNameKey = "userName" +const ContextUserAdminKey = "userAdmin" + +//const ContextEmailKey = "userEmail" +//const ContextNameKey = "userName" // Interface définie ici car c'est un port propre au cas d'usage "auth" type Authenticator interface { diff --git a/backend/internal/service/auth/service.go b/backend/internal/service/auth/service.go index 5f4e3e0..1d449ec 100644 --- a/backend/internal/service/auth/service.go +++ b/backend/internal/service/auth/service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "time" "trankilou.fr/lassistanoque/backend/internal/domain" ) @@ -53,8 +54,10 @@ func (s *Service) Register(registration Registration) (*Session, error) { if err != nil { return nil, err } + now := time.Now() _, err = s.userRepository.CreateTeam(session.User.ID, &domain.Team{ - Label: "Espace personnel", + Label: "Espace personnel", + DateCreated: now, }) if err != nil { return nil, err diff --git a/backend/internal/service/user/service.go b/backend/internal/service/user/service.go index 707e1ce..af6d2b5 100644 --- a/backend/internal/service/user/service.go +++ b/backend/internal/service/user/service.go @@ -45,3 +45,38 @@ func (s *Service) UpdateUser(updUser *domain.User, fromAdmin bool) (*domain.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) +} diff --git a/backend/lassistanoque.db-wal b/backend/lassistanoque.db-wal index 72f0172..04358d9 100644 Binary files a/backend/lassistanoque.db-wal and b/backend/lassistanoque.db-wal differ diff --git a/backend/web/src/lib/api/endpoints/user.ts b/backend/web/src/lib/api/endpoints/user.ts index 1152593..e4a8250 100644 --- a/backend/web/src/lib/api/endpoints/user.ts +++ b/backend/web/src/lib/api/endpoints/user.ts @@ -1,7 +1,12 @@ import { api } from '../client'; -import { type User } from "$lib/types/api" +import type { User, Address } from "$lib/types/api" export const userApi = { me: () => api.get('/user/me'), update: (id: string, data: Partial) => api.put(`/user/${id}`, data), + + listAddresses: (id: string) => api.get(`/user/${id}/address`), + createAddress: (id: string, data: Partial
) => api.post
(`/user/${id}/address`, data), + updateAddress: (id: string, data: Partial
) => api.put
(`/user/${id}/address`, data), + deleteAddress: (id:string, addrid: string) => api.delete(`/user/${id}/address/${addrid}`), }; diff --git a/backend/web/src/lib/components/Sidebar.svelte b/backend/web/src/lib/components/Sidebar.svelte index 6bf50af..faf0a97 100644 --- a/backend/web/src/lib/components/Sidebar.svelte +++ b/backend/web/src/lib/components/Sidebar.svelte @@ -1,6 +1,5 @@
@@ -106,4 +128,30 @@

Canaux de communication

+ + {#each addresses as address (address.id) } +
+
+ {address.type} : + {address.address} + +
+
+ {/each} +
+
+ +
+
+ +
+
+ +
+
+
diff --git a/backend/web/src/routes/(public)/register/+page.svelte b/backend/web/src/routes/(public)/register/+page.svelte index 8867e36..2a18a35 100644 --- a/backend/web/src/routes/(public)/register/+page.svelte +++ b/backend/web/src/routes/(public)/register/+page.svelte @@ -31,6 +31,12 @@ if (!validateEmail(form.email)) { 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) { errors.set("verifPassword","Les mots de pass ne correspondent pas.") } diff --git a/backend/web/src/routes/layout.css b/backend/web/src/routes/layout.css index 73d2e33..0a4a317 100644 --- a/backend/web/src/routes/layout.css +++ b/backend/web/src/routes/layout.css @@ -28,7 +28,7 @@ h2 { @apply cursor-pointer; } .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; + @apply cursor-pointer rounded bg-stone-200 px-4 py-1 hover:bg-stone-300 dark:bg-stone-800 dark:hover:bg-stone-700; } .menuitem { @apply text-stone-800 text-stone-950 dark:text-stone-300 hover:dark:text-stone-50;