diff --git a/cmd/server/main.go b/cmd/server/main.go index ed7a21c..eeadf9a 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -96,9 +96,11 @@ func main() { // Запуск сервера в отдельной горутине go func() { + log.Println("server started") if err := server.Run(); err != nil { log.Printf("server error: %v", err) } + }() // Ожидание сигнала завершения diff --git a/internal/config/config.go b/internal/config/config.go index 712b2a6..b561a62 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,7 +3,6 @@ package config import ( "github.com/caarlos0/env/v8" "github.com/joho/godotenv" - "log" "time" ) @@ -40,9 +39,5 @@ func Load() (*Config, error) { if err := env.Parse(cfg); err != nil { return nil, err } - - // Для отладки (можно убрать в продакшене) - log.Printf("Config loaded: %+v", cfg) - return cfg, nil } diff --git a/internal/domain/models.go b/internal/domain/models.go index 71d0343..bd7b8e7 100644 --- a/internal/domain/models.go +++ b/internal/domain/models.go @@ -8,6 +8,7 @@ import ( type User struct { ID int `json:"id"` Username string `json:"username"` + Avatar string `json:"avatar"` Email string `json:"email"` EmailConfirmationToken string `json:"-"` EmailConfirmedAt *time.Time `json:"emailConfirmedAt,omitempty"` diff --git a/internal/http/graph/generated.go b/internal/http/graph/generated.go index ccc4adc..b44fd69 100644 --- a/internal/http/graph/generated.go +++ b/internal/http/graph/generated.go @@ -113,7 +113,7 @@ type ComplexityRoot struct { SendMessage func(childComplexity int, receiverID int, content string) int TerminateSession func(childComplexity int, sessionID int) int UnlikePost func(childComplexity int, postID int) int - UpdateProfile func(childComplexity int, username string, email string) int + UpdateProfile func(childComplexity int, username string, email string, avatar string) int } Post struct { @@ -137,6 +137,7 @@ type ComplexityRoot struct { Post func(childComplexity int, id int) int Posts func(childComplexity int) int User func(childComplexity int, id int) int + Users func(childComplexity int) int } Session struct { @@ -160,6 +161,7 @@ type ComplexityRoot struct { } User struct { + Avatar func(childComplexity int) int CreatedAt func(childComplexity int) int Email func(childComplexity int) int EmailConfirmedAt func(childComplexity int) int @@ -197,7 +199,7 @@ type MutationResolver interface { CreateComment(ctx context.Context, postID int, content string) (*domain.Comment, error) LikePost(ctx context.Context, postID int) (*domain.Like, error) UnlikePost(ctx context.Context, postID int) (bool, error) - UpdateProfile(ctx context.Context, username string, email string) (*domain.User, error) + UpdateProfile(ctx context.Context, username string, email string, avatar string) (*domain.User, error) ChangePassword(ctx context.Context, oldPassword string, newPassword string) (bool, error) SendMessage(ctx context.Context, receiverID int, content string) (*domain.Message, error) MarkAsRead(ctx context.Context, messageID int) (bool, error) @@ -222,6 +224,7 @@ type QueryResolver interface { Post(ctx context.Context, id int) (*domain.Post, error) Posts(ctx context.Context) ([]*domain.Post, error) User(ctx context.Context, id int) (*domain.User, error) + Users(ctx context.Context) ([]*domain.User, error) GetChatHistory(ctx context.Context, userID int) ([]*domain.Message, error) GetUserChats(ctx context.Context) ([]*ChatSession, error) MySessions(ctx context.Context) ([]*domain.Session, error) @@ -622,7 +625,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Mutation.UpdateProfile(childComplexity, args["username"].(string), args["email"].(string)), true + return e.complexity.Mutation.UpdateProfile(childComplexity, args["username"].(string), args["email"].(string), args["avatar"].(string)), true case "Post.author": if e.complexity.Post.Author == nil { @@ -758,6 +761,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Query.User(childComplexity, args["id"].(int)), true + case "Query.users": + if e.complexity.Query.Users == nil { + break + } + + return e.complexity.Query.Users(childComplexity), true + case "Session.device": if e.complexity.Session.Device == nil { break @@ -835,6 +845,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Tokens.RefreshTokenExpires(childComplexity), true + case "User.avatar": + if e.complexity.User.Avatar == nil { + break + } + + return e.complexity.User.Avatar(childComplexity), true + case "User.createdAt": if e.complexity.User.CreatedAt == nil { break @@ -1232,6 +1249,11 @@ func (ec *executionContext) field_Mutation_updateProfile_args(ctx context.Contex return nil, err } args["email"] = arg1 + arg2, err := processArgField(ctx, rawArgs, "avatar", ec.unmarshalNString2string) + if err != nil { + return nil, err + } + args["avatar"] = arg2 return args, nil } @@ -1374,6 +1396,8 @@ func (ec *executionContext) fieldContext_ChatSession_user(_ context.Context, fie return ec.fieldContext_User_id(ctx, field) case "username": return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) case "emailConfirmedAt": @@ -1688,6 +1712,8 @@ func (ec *executionContext) fieldContext_Comment_author(_ context.Context, field return ec.fieldContext_User_id(ctx, field) case "username": return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) case "emailConfirmedAt": @@ -2164,6 +2190,8 @@ func (ec *executionContext) fieldContext_Like_user(_ context.Context, field grap return ec.fieldContext_User_id(ctx, field) case "username": return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) case "emailConfirmedAt": @@ -2310,6 +2338,8 @@ func (ec *executionContext) fieldContext_Message_sender(_ context.Context, field return ec.fieldContext_User_id(ctx, field) case "username": return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) case "emailConfirmedAt": @@ -2368,6 +2398,8 @@ func (ec *executionContext) fieldContext_Message_receiver(_ context.Context, fie return ec.fieldContext_User_id(ctx, field) case "username": return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) case "emailConfirmedAt": @@ -2558,6 +2590,8 @@ func (ec *executionContext) fieldContext_Mutation_register(ctx context.Context, return ec.fieldContext_User_id(ctx, field) case "username": return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) case "emailConfirmedAt": @@ -2998,7 +3032,7 @@ func (ec *executionContext) _Mutation_updateProfile(ctx context.Context, field g }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Mutation().UpdateProfile(rctx, fc.Args["username"].(string), fc.Args["email"].(string)) + return ec.resolvers.Mutation().UpdateProfile(rctx, fc.Args["username"].(string), fc.Args["email"].(string), fc.Args["avatar"].(string)) }) if err != nil { ec.Error(ctx, err) @@ -3027,6 +3061,8 @@ func (ec *executionContext) fieldContext_Mutation_updateProfile(ctx context.Cont return ec.fieldContext_User_id(ctx, field) case "username": return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) case "emailConfirmedAt": @@ -3727,6 +3763,8 @@ func (ec *executionContext) fieldContext_Post_author(_ context.Context, field gr return ec.fieldContext_User_id(ctx, field) case "username": return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) case "emailConfirmedAt": @@ -4073,6 +4111,8 @@ func (ec *executionContext) fieldContext_Query_me(_ context.Context, field graph return ec.fieldContext_User_id(ctx, field) case "username": return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) case "emailConfirmedAt": @@ -4274,6 +4314,8 @@ func (ec *executionContext) fieldContext_Query_user(ctx context.Context, field g return ec.fieldContext_User_id(ctx, field) case "username": return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) case "email": return ec.fieldContext_User_email(ctx, field) case "emailConfirmedAt": @@ -4300,6 +4342,66 @@ func (ec *executionContext) fieldContext_Query_user(ctx context.Context, field g return fc, nil } +func (ec *executionContext) _Query_users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_users(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().Users(rctx) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*domain.User) + fc.Result = res + return ec.marshalNUser2ᚕᚖtailly_back_v2ᚋinternalᚋdomainᚐUserᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Query_users(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Query", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_User_id(ctx, field) + case "username": + return ec.fieldContext_User_username(ctx, field) + case "avatar": + return ec.fieldContext_User_avatar(ctx, field) + case "email": + return ec.fieldContext_User_email(ctx, field) + case "emailConfirmedAt": + return ec.fieldContext_User_emailConfirmedAt(ctx, field) + case "createdAt": + return ec.fieldContext_User_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_User_updatedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type User", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _Query_getChatHistory(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Query_getChatHistory(ctx, field) if err != nil { @@ -5220,6 +5322,50 @@ func (ec *executionContext) fieldContext_User_username(_ context.Context, field return fc, nil } +func (ec *executionContext) _User_avatar(ctx context.Context, field graphql.CollectedField, obj *domain.User) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_User_avatar(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Avatar, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_User_avatar(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "User", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _User_email(ctx context.Context, field graphql.CollectedField, obj *domain.User) (ret graphql.Marshaler) { fc, err := ec.fieldContext_User_email(ctx, field) if err != nil { @@ -8626,6 +8772,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) + case "users": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_users(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + rrm := func(ctx context.Context) graphql.Marshaler { + return ec.OperationContext.RootResolverMiddleware(ctx, + func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) }) case "getChatHistory": field := field @@ -9007,6 +9175,11 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "avatar": + out.Values[i] = ec._User_avatar(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "email": out.Values[i] = ec._User_email(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -9905,6 +10078,50 @@ func (ec *executionContext) marshalNUser2tailly_back_v2ᚋinternalᚋdomainᚐUs return ec._User(ctx, sel, &v) } +func (ec *executionContext) marshalNUser2ᚕᚖtailly_back_v2ᚋinternalᚋdomainᚐUserᚄ(ctx context.Context, sel ast.SelectionSet, v []*domain.User) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNUser2ᚖtailly_back_v2ᚋinternalᚋdomainᚐUser(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) marshalNUser2ᚖtailly_back_v2ᚋinternalᚋdomainᚐUser(ctx context.Context, sel ast.SelectionSet, v *domain.User) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { diff --git a/internal/http/graph/schema.graphql b/internal/http/graph/schema.graphql index 54d147e..0b9f0b7 100644 --- a/internal/http/graph/schema.graphql +++ b/internal/http/graph/schema.graphql @@ -2,6 +2,7 @@ type User { id: Int! # Уникальный идентификатор username: String! # Имя пользователя + avatar: String! # Аватар email: String! # Email (уникальный) emailConfirmedAt: String # Дата подтверждения email (может быть null) createdAt: String! # Дата создания @@ -98,6 +99,7 @@ type Query { post(id: Int!): Post! # Получить пост по ID posts: [Post!]! # Получить все посты user(id: Int!): User! # Получить пользователя по ID + users: [User!]! getChatHistory(userId: Int!): [Message!]! getUserChats: [ChatSession!]! mySessions: [Session!]! @@ -125,7 +127,7 @@ type Mutation { # Удаление лайка unlikePost(postId: Int!): Boolean! - updateProfile(username: String!, email: String!): User! + updateProfile(username: String!, email: String!, avatar: String!): User! changePassword(oldPassword: String!, newPassword: String!): Boolean! sendMessage(receiverId: Int!, content: String!): Message! markAsRead(messageId: Int!): Boolean! diff --git a/internal/http/graph/schema.resolvers.go b/internal/http/graph/schema.resolvers.go deleted file mode 100644 index dcf287f..0000000 --- a/internal/http/graph/schema.resolvers.go +++ /dev/null @@ -1,5 +0,0 @@ -package graph - -// This file will be automatically regenerated based on the schema, any resolver implementations -// will be copied through when generating and any unknown code will be moved to the end. -// Code generated by github.com/99designs/gqlgen version v0.17.77 diff --git a/internal/http/graph/user_resolvers.go b/internal/http/graph/user_resolvers.go index 84dd842..3806a73 100644 --- a/internal/http/graph/user_resolvers.go +++ b/internal/http/graph/user_resolvers.go @@ -61,13 +61,13 @@ func (r *userResolver) UpdatedAt(ctx context.Context, obj *domain.User) (string, } // UpdateProfile is the resolver for the updateProfile field. -func (r *mutationResolver) UpdateProfile(ctx context.Context, username string, email string) (*domain.User, error) { +func (r *mutationResolver) UpdateProfile(ctx context.Context, username string, email string, avatar string) (*domain.User, error) { userID, err := getUserIDFromContext(ctx) if err != nil { return nil, err } - user, err := r.Services.User.UpdateProfile(ctx, userID, username, email) + user, err := r.Services.User.UpdateProfile(ctx, userID, username, email, avatar) if err != nil { return nil, fmt.Errorf("failed to update profile: %w", err) } @@ -109,3 +109,12 @@ func (r *mutationResolver) Login(ctx context.Context, input domain.LoginInput) ( } return tokens, nil } + +// Users is the resolver for the users field. +func (r *queryResolver) Users(ctx context.Context) ([]*domain.User, error) { + users, err := r.Services.User.GetAll(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get users: %w", err) + } + return users, nil +} diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index 4e5cb39..4949b2a 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -15,6 +15,7 @@ var ( type UserRepository interface { Create(ctx context.Context, user *domain.User) error GetByID(ctx context.Context, id int) (*domain.User, error) + GetAll(ctx context.Context) ([]*domain.User, error) GetByEmail(ctx context.Context, email string) (*domain.User, error) GetByConfirmationToken(ctx context.Context, token string) (*domain.User, error) Update(ctx context.Context, user *domain.User) error @@ -50,7 +51,7 @@ func (r *userRepository) Create(ctx context.Context, user *domain.User) error { func (r *userRepository) GetByID(ctx context.Context, id int) (*domain.User, error) { query := ` - SELECT id, username, email, password, email_confirmation_token, email_confirmed_at, created_at, updated_at + SELECT id, username, email, password, email_confirmation_token, email_confirmed_at, created_at, updated_at, avatar FROM users WHERE id = $1 ` @@ -67,6 +68,7 @@ func (r *userRepository) GetByID(ctx context.Context, id int) (*domain.User, err &confirmedAt, &user.CreatedAt, &user.UpdatedAt, + &user.Avatar, ) if err == sql.ErrNoRows { @@ -82,7 +84,7 @@ func (r *userRepository) GetByID(ctx context.Context, id int) (*domain.User, err func (r *userRepository) GetByEmail(ctx context.Context, email string) (*domain.User, error) { query := ` - SELECT id, username, email, password, email_confirmation_token, email_confirmed_at, created_at, updated_at + SELECT id, username, email, password, email_confirmation_token, email_confirmed_at, created_at, updated_at, avatar FROM users WHERE email = $1 ` @@ -99,6 +101,7 @@ func (r *userRepository) GetByEmail(ctx context.Context, email string) (*domain. &confirmedAt, &user.CreatedAt, &user.UpdatedAt, + &user.Avatar, ) if err == sql.ErrNoRows { @@ -114,7 +117,7 @@ func (r *userRepository) GetByEmail(ctx context.Context, email string) (*domain. func (r *userRepository) GetByConfirmationToken(ctx context.Context, token string) (*domain.User, error) { query := ` - SELECT id, username, email, password, email_confirmation_token, email_confirmed_at, created_at, updated_at + SELECT id, username, email, password, email_confirmation_token, email_confirmed_at, created_at, updated_at, avatar FROM users WHERE email_confirmation_token = $1 ` @@ -131,6 +134,7 @@ func (r *userRepository) GetByConfirmationToken(ctx context.Context, token strin &confirmedAt, &user.CreatedAt, &user.UpdatedAt, + &user.Avatar, ) if err == sql.ErrNoRows { @@ -152,8 +156,9 @@ func (r *userRepository) Update(ctx context.Context, user *domain.User) error { password = $3, email_confirmation_token = $4, email_confirmed_at = $5, - updated_at = $6 - WHERE id = $7 + avatar = $6, + updated_at = $7 + WHERE id = $8 ` var confirmedAt interface{} @@ -169,6 +174,7 @@ func (r *userRepository) Update(ctx context.Context, user *domain.User) error { user.Password, user.EmailConfirmationToken, confirmedAt, + &user.Avatar, time.Now(), // Обновляем updated_at user.ID, ) @@ -181,3 +187,32 @@ func (r *userRepository) Delete(ctx context.Context, id int) error { _, err := r.db.ExecContext(ctx, query, id) return err } + +func (r *userRepository) GetAll(ctx context.Context) ([]*domain.User, error) { + query := ` + SELECT id, username, avatar + FROM users + ` + + rows, err := r.db.QueryContext(ctx, query) + if err != nil { + return nil, err + } + defer rows.Close() + + var users []*domain.User + for rows.Next() { + user := &domain.User{} + err := rows.Scan( + &user.ID, + &user.Username, + &user.Avatar, + ) + if err != nil { + return nil, err + } + users = append(users, user) + } + + return users, nil +} diff --git a/internal/service/user_service.go b/internal/service/user_service.go index 992bec1..ee86cdd 100644 --- a/internal/service/user_service.go +++ b/internal/service/user_service.go @@ -11,8 +11,9 @@ import ( type UserService interface { GetByID(ctx context.Context, id int) (*domain.User, error) + GetAll(ctx context.Context) ([]*domain.User, error) GetByEmail(ctx context.Context, email string) (*domain.User, error) - UpdateProfile(ctx context.Context, id int, username, email string) (*domain.User, error) + UpdateProfile(ctx context.Context, id int, username, email, avatar string) (*domain.User, error) ChangePassword(ctx context.Context, id int, oldPassword, newPassword string) error } @@ -52,7 +53,7 @@ func (s *userService) GetByID(ctx context.Context, id int) (*domain.User, error) return user, nil } -func (s *userService) UpdateProfile(ctx context.Context, id int, username, email string) (*domain.User, error) { +func (s *userService) UpdateProfile(ctx context.Context, id int, username, email, avatar string) (*domain.User, error) { user, err := s.userRepo.GetByID(ctx, id) if err != nil { return nil, err @@ -67,6 +68,7 @@ func (s *userService) UpdateProfile(ctx context.Context, id int, username, email user.Username = username user.Email = email + user.Avatar = avatar user.UpdatedAt = time.Now() if err := s.userRepo.Update(ctx, user); err != nil { @@ -96,3 +98,17 @@ func (s *userService) ChangePassword(ctx context.Context, id int, oldPassword, n user.Password = hashedPassword return s.userRepo.Update(ctx, user) } + +func (s *userService) GetAll(ctx context.Context) ([]*domain.User, error) { + users, err := s.userRepo.GetAll(ctx) + if err != nil { + return nil, err + } + + // Возвращаем пустой слайс вместо nil + if users == nil { + return []*domain.User{}, nil + } + + return users, nil +} diff --git a/migrations/0001_initial_schema.up.sql b/migrations/0001_initial_schema.up.sql index f154beb..806cf41 100644 --- a/migrations/0001_initial_schema.up.sql +++ b/migrations/0001_initial_schema.up.sql @@ -2,6 +2,7 @@ CREATE TABLE users ( id SERIAL PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, + avatar TEXT NOT NULL DEFAULT 'https://s3.regru.cloud/tailly/default_avatar.jpg', email VARCHAR(255) NOT NULL UNIQUE, email_confirmation_token VARCHAR(255), email_confirmed_at TIMESTAMP WITH TIME ZONE,