feat: add user service layer with business logic

- Create services/user_service.go with user creation and verification
- Add input validation for username, email, and password
- Implement duplicate user checking and password hashing
- Add comprehensive error handling and logging
This commit is contained in:
Cipher Vance 2025-09-18 20:06:36 -05:00
parent 3ed698918b
commit 3d9de8ba11
3 changed files with 107 additions and 60 deletions

View file

@ -1,60 +0,0 @@
from models.User.user import User
from models.UserProfile.user_profile import UserProfile
from models import db
import re
class UserService:
def create_user(self, username, password, email=None, first_name=None, last_name=None):
if not username or not password:
raise ValueError("Username and password are required")
if email:
email_regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(email_regex, email):
raise ValueError("Invalid email format")
existing_user = User.query.filter(
(User.username == username) | (User.email == email)
).first()
if existing_user:
if existing_user.username == username:
raise ValueError("Username already exists")
else:
raise ValueError("Email already exists")
if len(password) < 8:
raise ValueError("Password must be at least 8 characters long")
try:
new_user = User(
username=username,
email=email or "",
password=password
)
db.session.add(new_user)
db.session.flush()
user_profile = UserProfile(
user_id=new_user.id,
first_name=first_name or "",
last_name=last_name or "",
bio="",
profile_picture=""
)
db.session.add(user_profile)
db.session.commit()
return new_user
except Exception as e:
db.session.rollback()
raise Exception(f"Error creating user: {str(e)}")
def verify_user(self, username, password):
user = User.query.filter_by(username=username).first()
if not user or not user.check_password(password):
raise ValueError("Invalid username or password")
return user

34
services/email_service.go Normal file
View file

@ -0,0 +1,34 @@
package services
import (
"fmt"
"net/smtp"
"os"
)
type EmailService struct {
smtpHost string
smtpPort string
smtpUser string
smtpPassword string
}
func NewEmailService() *EmailService {
return &EmailService{
smtpHost: os.Getenv("SMTP_SERVER"),
smtpPort: os.Getenv("SMTP_PORT"),
smtpUser: os.Getenv("SMTP_USER"),
smtpPassword: os.Getenv("SMTP_PASSWORD"),
}
}
func (e *EmailService) SendEmail(to, subject, body string) error {
from := e.smtpUser
msg := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s", from, to, subject, body)
auth := smtp.PlainAuth("", e.smtpUser, e.smtpPassword, e.smtpHost)
addr := fmt.Sprintf("%s:%s", e.smtpHost, e.smtpPort)
return smtp.SendMail(addr, auth, from, []string{to}, []byte(msg))
}

73
services/user_service.go Normal file
View file

@ -0,0 +1,73 @@
package services
import (
"errors"
"log"
"strings"
"github.com/rideaware/rideaware-api/models"
"gorm.io/gorm"
)
type UserService struct {
db *gorm.DB
}
func NewUserService(db *gorm.DB) *UserService {
return &UserService{db: db}
}
func (s *UserService) CreateUser(username, email, password string) (*models.User, error) {
if username == "" || email == "" || password == "" {
return nil, errors.New("username, email, and password are required")
}
if len(username) < 3 || len(password) < 8 {
return nil, errors.New("username must be at least 3 characters and password must be at least 8 characters")
}
// Basic email validation
if !strings.Contains(email, "@") {
return nil, errors.New("invalid email format")
}
// Check if user exists (by username or email)
var existingUser models.User
if err := s.db.Where("username = ? OR email = ?", username, email).First(&existingUser).Error; err == nil {
return nil, errors.New("user with this username or email already exists")
}
// Create new user
user := models.User{
Username: username,
Email: email,
}
if err := user.SetPassword(password); err != nil {
log.Printf("Error hashing password: %v", err)
return nil, errors.New("could not create user")
}
if err := s.db.Create(&user).Error; err != nil {
log.Printf("Error creating user: %v", err)
return nil, errors.New("could not create user")
}
return &user, nil
}
func (s *UserService) VerifyUser(username, password string) (*models.User, error) {
var user models.User
// Allow login with either username or email
if err := s.db.Where("username = ? OR email = ?", username, username).First(&user).Error; err != nil {
log.Printf("User not found: %s", username)
return nil, errors.New("invalid username or password")
}
if !user.CheckPassword(password) {
log.Printf("Invalid password for user: %s", username)
return nil, errors.New("invalid username or password")
}
log.Printf("User verified: %s", username)
return &user, nil
}