init
This commit is contained in:
16
internal/core/domain/errors.go
Normal file
16
internal/core/domain/errors.go
Normal file
@ -0,0 +1,16 @@
|
||||
package domain
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrInternal = errors.New("internal error")
|
||||
ErrConflictingData = errors.New("data conflicts with existing data in unique column")
|
||||
ErrDataNotFound = errors.New("data not found")
|
||||
ErrInvalidEmailCredentials = errors.New("invalid email or password")
|
||||
ErrInvalidUsernameCredentials = errors.New("invalid username or password")
|
||||
ErrTokenCreation = errors.New("error creating token")
|
||||
ErrExpiredToken = errors.New("access token has expired")
|
||||
ErrUsernameExists = errors.New("username already exists")
|
||||
ErrEmailExists = errors.New("email already exists")
|
||||
ErrInvalidToken = errors.New("invalid token")
|
||||
)
|
20
internal/core/domain/message.go
Normal file
20
internal/core/domain/message.go
Normal file
@ -0,0 +1,20 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
UserID uuid.UUID
|
||||
ChatID string
|
||||
Content string
|
||||
Type string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
type StreamMessage struct {
|
||||
*Message
|
||||
Commit func() error
|
||||
}
|
7
internal/core/domain/token.go
Normal file
7
internal/core/domain/token.go
Normal file
@ -0,0 +1,7 @@
|
||||
package domain
|
||||
|
||||
import "github.com/google/uuid"
|
||||
|
||||
type AuthPayload struct {
|
||||
UserID uuid.UUID
|
||||
}
|
14
internal/core/domain/user.go
Normal file
14
internal/core/domain/user.go
Normal file
@ -0,0 +1,14 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uuid.UUID
|
||||
Username string
|
||||
Email string
|
||||
Password string
|
||||
// CreatedAt time.Time
|
||||
// UpdatedAt time.Time
|
||||
}
|
17
internal/core/port/auth.go
Normal file
17
internal/core/port/auth.go
Normal file
@ -0,0 +1,17 @@
|
||||
package port
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aykhans/oh-my-chat/internal/core/domain"
|
||||
)
|
||||
|
||||
type TokenService interface {
|
||||
CreateToken(user *domain.User) (string, error)
|
||||
VerifyToken(token string) (*domain.AuthPayload, error)
|
||||
}
|
||||
|
||||
type AuthService interface {
|
||||
LoginByEmail(ctx context.Context, email, password string) (string, error)
|
||||
LoginByUsername(ctx context.Context, username, password string) (string, error)
|
||||
}
|
26
internal/core/port/message.go
Normal file
26
internal/core/port/message.go
Normal file
@ -0,0 +1,26 @@
|
||||
package port
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aykhans/oh-my-chat/internal/core/domain"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type MessageProducer interface {
|
||||
ProduceMessage(ctx context.Context, message *domain.Message) error
|
||||
}
|
||||
|
||||
type MessageConsumer interface {
|
||||
ConsumeMessage(ctx context.Context, uid string, getChats func() []string, message chan<- *domain.StreamMessage) error
|
||||
}
|
||||
|
||||
type MessageRepository interface {
|
||||
CreateMessage(ctx context.Context, message *domain.Message) (*domain.Message, error)
|
||||
}
|
||||
|
||||
type MessageService interface {
|
||||
SendMessage(ctx context.Context, message *domain.Message) error
|
||||
ReceiveMessage(ctx context.Context, userID uuid.UUID, message chan<- *domain.StreamMessage) error
|
||||
CreateMessage(ctx context.Context, message *domain.Message) (*domain.Message, error)
|
||||
}
|
22
internal/core/port/user.go
Normal file
22
internal/core/port/user.go
Normal file
@ -0,0 +1,22 @@
|
||||
package port
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/aykhans/oh-my-chat/internal/core/domain"
|
||||
)
|
||||
|
||||
type UserRepository interface {
|
||||
CreateUser(ctx context.Context, user *domain.User) (*domain.User, error)
|
||||
GetUserByEmail(ctx context.Context, email string) (*domain.User, error)
|
||||
GetUserByUsername(ctx context.Context, username string) (*domain.User, error)
|
||||
IsUsernameExists(ctx context.Context, username string) (bool, error)
|
||||
IsEmailExists(ctx context.Context, email string) (bool, error)
|
||||
// GetUserByID(ctx context.Context, id uint64) (*domain.User, error)
|
||||
// DeleteUser(ctx context.Context, id uint64) error
|
||||
}
|
||||
|
||||
type UserService interface {
|
||||
Register(ctx context.Context, user *domain.User) (*domain.User, error)
|
||||
// GetUser(ctx context.Context, id uint64) (*domain.User, error)
|
||||
// DeleteUser(ctx context.Context, id uint64) error
|
||||
}
|
72
internal/core/service/auth.go
Normal file
72
internal/core/service/auth.go
Normal file
@ -0,0 +1,72 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aykhans/oh-my-chat/internal/core/domain"
|
||||
"github.com/aykhans/oh-my-chat/internal/core/port"
|
||||
"github.com/aykhans/oh-my-chat/internal/core/utils"
|
||||
)
|
||||
|
||||
type AuthService struct {
|
||||
userRepository port.UserRepository
|
||||
tokenService port.TokenService
|
||||
}
|
||||
|
||||
// NewAuthService creates a new auth service instance
|
||||
func NewAuthService(userRepository port.UserRepository, tokenService port.TokenService) *AuthService {
|
||||
return &AuthService{
|
||||
userRepository,
|
||||
tokenService,
|
||||
}
|
||||
}
|
||||
|
||||
func (authService *AuthService) LoginByEmail(
|
||||
ctx context.Context,
|
||||
email, password string,
|
||||
) (string, error) {
|
||||
user, err := authService.userRepository.GetUserByEmail(ctx, email)
|
||||
if err != nil {
|
||||
if err == domain.ErrDataNotFound {
|
||||
return "", domain.ErrInvalidEmailCredentials
|
||||
}
|
||||
return "", domain.ErrInternal
|
||||
}
|
||||
|
||||
err = utils.ComparePassword(password, user.Password)
|
||||
if err != nil {
|
||||
return "", domain.ErrInvalidEmailCredentials
|
||||
}
|
||||
|
||||
accessToken, err := authService.tokenService.CreateToken(user)
|
||||
if err != nil {
|
||||
return "", domain.ErrTokenCreation
|
||||
}
|
||||
|
||||
return accessToken, nil
|
||||
}
|
||||
|
||||
func (authService *AuthService) LoginByUsername(
|
||||
ctx context.Context,
|
||||
username, password string,
|
||||
) (string, error) {
|
||||
user, err := authService.userRepository.GetUserByEmail(ctx, username)
|
||||
if err != nil {
|
||||
if err == domain.ErrDataNotFound {
|
||||
return "", domain.ErrInvalidUsernameCredentials
|
||||
}
|
||||
return "", domain.ErrInternal
|
||||
}
|
||||
|
||||
err = utils.ComparePassword(password, user.Password)
|
||||
if err != nil {
|
||||
return "", domain.ErrInvalidUsernameCredentials
|
||||
}
|
||||
|
||||
accessToken, err := authService.tokenService.CreateToken(user)
|
||||
if err != nil {
|
||||
return "", domain.ErrTokenCreation
|
||||
}
|
||||
|
||||
return accessToken, nil
|
||||
}
|
55
internal/core/service/message.go
Normal file
55
internal/core/service/message.go
Normal file
@ -0,0 +1,55 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aykhans/oh-my-chat/internal/core/domain"
|
||||
"github.com/aykhans/oh-my-chat/internal/core/port"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type MessageService struct {
|
||||
producer port.MessageProducer
|
||||
consumer port.MessageConsumer
|
||||
repo port.MessageRepository
|
||||
}
|
||||
|
||||
func NewMessageService(
|
||||
producerService port.MessageProducer,
|
||||
consumerService port.MessageConsumer,
|
||||
repo port.MessageRepository,
|
||||
) *MessageService {
|
||||
return &MessageService{
|
||||
producerService,
|
||||
consumerService,
|
||||
repo,
|
||||
}
|
||||
}
|
||||
|
||||
func (chatServie *MessageService) SendMessage(
|
||||
ctx context.Context,
|
||||
message *domain.Message,
|
||||
) error {
|
||||
message.ChatID = "chat_" + message.ChatID
|
||||
return chatServie.producer.ProduceMessage(ctx, message)
|
||||
}
|
||||
|
||||
func (chatServie *MessageService) ReceiveMessage(
|
||||
ctx context.Context,
|
||||
userID uuid.UUID,
|
||||
message chan<- *domain.StreamMessage,
|
||||
) error {
|
||||
return chatServie.consumer.ConsumeMessage(
|
||||
ctx,
|
||||
userID.String(),
|
||||
func() []string { return []string{"chat_1", "chat_5", "chat_9"} },
|
||||
message,
|
||||
)
|
||||
}
|
||||
|
||||
func (chatServie *MessageService) CreateMessage(
|
||||
ctx context.Context,
|
||||
message *domain.Message,
|
||||
) (*domain.Message, error) {
|
||||
return chatServie.repo.CreateMessage(ctx, message)
|
||||
}
|
49
internal/core/service/user.go
Normal file
49
internal/core/service/user.go
Normal file
@ -0,0 +1,49 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/aykhans/oh-my-chat/internal/core/domain"
|
||||
"github.com/aykhans/oh-my-chat/internal/core/port"
|
||||
"github.com/aykhans/oh-my-chat/internal/core/utils"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
repo port.UserRepository
|
||||
}
|
||||
|
||||
func NewUserService(repo port.UserRepository) *UserService {
|
||||
return &UserService{repo: repo}
|
||||
}
|
||||
|
||||
func (userService *UserService) Register(
|
||||
ctx context.Context,
|
||||
user *domain.User,
|
||||
) (*domain.User, error) {
|
||||
if exists, err := userService.repo.IsUsernameExists(ctx, user.Username); err != nil {
|
||||
return nil, domain.ErrInternal
|
||||
} else if exists {
|
||||
return nil, domain.ErrUsernameExists
|
||||
}
|
||||
if exists, err := userService.repo.IsEmailExists(ctx, user.Email); err != nil {
|
||||
return nil, domain.ErrInternal
|
||||
} else if exists {
|
||||
return nil, domain.ErrEmailExists
|
||||
}
|
||||
|
||||
hashedPassword, err := utils.HashPassword(user.Password)
|
||||
if err != nil {
|
||||
return nil, domain.ErrInternal
|
||||
}
|
||||
user.Password = hashedPassword
|
||||
|
||||
user, err = userService.repo.CreateUser(ctx, user)
|
||||
if err != nil {
|
||||
if err == domain.ErrConflictingData {
|
||||
return nil, err
|
||||
}
|
||||
return nil, domain.ErrInternal
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
9
internal/core/utils/convert.go
Normal file
9
internal/core/utils/convert.go
Normal file
@ -0,0 +1,9 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Str2StrSlice(value string) []string {
|
||||
return strings.Split(strings.ReplaceAll(value, " ", ""), ",")
|
||||
}
|
15
internal/core/utils/os.go
Normal file
15
internal/core/utils/os.go
Normal file
@ -0,0 +1,15 @@
|
||||
package utils
|
||||
|
||||
import "os"
|
||||
|
||||
func ExitErr() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func GetEnvOrDefault(key, defaultValue string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
26
internal/core/utils/password.go
Normal file
26
internal/core/utils/password.go
Normal file
@ -0,0 +1,26 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// HashPassword hashes password and returns hashed password or error
|
||||
func HashPassword(password string) (string, error) {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword(
|
||||
[]byte(password),
|
||||
bcrypt.DefaultCost,
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(hashedPassword), nil
|
||||
}
|
||||
|
||||
// ComparePassword compares password with hashed password and returns error if they don't match or nil if they do
|
||||
func ComparePassword(password, hashedPassword string) error {
|
||||
return bcrypt.CompareHashAndPassword(
|
||||
[]byte(hashedPassword),
|
||||
[]byte(password),
|
||||
)
|
||||
}
|
7
internal/core/utils/time.go
Normal file
7
internal/core/utils/time.go
Normal file
@ -0,0 +1,7 @@
|
||||
package utils
|
||||
|
||||
import "time"
|
||||
|
||||
func GetNow() time.Time {
|
||||
return time.Now()
|
||||
}
|
Reference in New Issue
Block a user