refactor: Update data models to use pointer fields for optional values and add atomic database operations for video views and user storage.
This commit is contained in:
@@ -60,17 +60,17 @@ func (h *handler) Login(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Verify password (if user has password, google users might not)
|
||||
if user.Password == "" {
|
||||
if user.Password == nil || *user.Password == "" {
|
||||
response.Error(c, http.StatusUnauthorized, "Please login with Google")
|
||||
return
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password)); err != nil {
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(req.Password)); err != nil {
|
||||
response.Error(c, http.StatusUnauthorized, "Invalid credentials")
|
||||
return
|
||||
}
|
||||
|
||||
h.generateAndSetTokens(c, user.ID, user.Email, user.Role)
|
||||
h.generateAndSetTokens(c, user.ID, user.Email, *user.Role)
|
||||
response.Success(c, gin.H{"user": user})
|
||||
}
|
||||
|
||||
@@ -111,12 +111,14 @@ func (h *handler) Register(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
password := string(hashedPassword)
|
||||
role := "USER"
|
||||
newUser := &model.User{
|
||||
ID: uuid.New().String(),
|
||||
Email: req.Email,
|
||||
Password: string(hashedPassword),
|
||||
Username: req.Username,
|
||||
Role: "USER",
|
||||
Password: &password,
|
||||
Username: &req.Username,
|
||||
Role: &role,
|
||||
}
|
||||
|
||||
if err := u.WithContext(c.Request.Context()).Create(newUser); err != nil {
|
||||
@@ -226,23 +228,24 @@ func (h *handler) GoogleCallback(c *gin.Context) {
|
||||
u := query.User
|
||||
user, err := u.WithContext(c.Request.Context()).Where(u.Email.Eq(googleUser.Email)).First()
|
||||
if err != nil {
|
||||
role := "USER"
|
||||
user = &model.User{
|
||||
ID: uuid.New().String(),
|
||||
Email: googleUser.Email,
|
||||
Username: googleUser.Name,
|
||||
GoogleID: googleUser.ID,
|
||||
Avatar: googleUser.Picture,
|
||||
Role: "USER",
|
||||
Username: &googleUser.Name,
|
||||
GoogleID: &googleUser.ID,
|
||||
Avatar: &googleUser.Picture,
|
||||
Role: &role,
|
||||
}
|
||||
if err := u.WithContext(c.Request.Context()).Create(user); err != nil {
|
||||
response.Fail(c, "Failed to create user")
|
||||
return
|
||||
}
|
||||
} else if user.GoogleID == "" {
|
||||
} else if user.GoogleID == nil || *user.GoogleID == "" {
|
||||
u.WithContext(c.Request.Context()).Where(u.ID.Eq(user.ID)).Update(u.GoogleID, googleUser.ID)
|
||||
}
|
||||
|
||||
h.generateAndSetTokens(c, user.ID, user.Email, user.Role)
|
||||
h.generateAndSetTokens(c, user.ID, user.Email, *user.Role)
|
||||
response.Success(c, gin.H{"user": user})
|
||||
}
|
||||
|
||||
|
||||
@@ -52,13 +52,16 @@ func (h *Handler) CreatePayment(c *gin.Context) {
|
||||
// In a real scenario, we would contact Stripe/PayPal here to create a session
|
||||
// For now, we just create a "PENDING" payment record.
|
||||
|
||||
status := "PENDING"
|
||||
provider := "STRIPE"
|
||||
|
||||
payment := &model.Payment{
|
||||
ID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
PlanID: req.PlanID,
|
||||
PlanID: &req.PlanID,
|
||||
Amount: req.Amount,
|
||||
Status: "PENDING",
|
||||
Provider: "STRIPE", // Defaulting to Stripe for this example
|
||||
Status: &status,
|
||||
Provider: &provider, // Defaulting to Stripe for this example
|
||||
}
|
||||
|
||||
p := query.Payment
|
||||
|
||||
@@ -86,21 +86,41 @@ func (h *Handler) CreateVideo(c *gin.Context) {
|
||||
|
||||
userID := c.GetString("userID")
|
||||
|
||||
status := "PUBLIC"
|
||||
storageType := "S3"
|
||||
|
||||
video := &model.Video{
|
||||
ID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
Name: req.Title,
|
||||
Title: req.Title,
|
||||
Description: req.Description,
|
||||
Description: &req.Description,
|
||||
URL: req.URL,
|
||||
Size: req.Size,
|
||||
Duration: req.Duration,
|
||||
Format: req.Format,
|
||||
Status: "PUBLIC",
|
||||
StorageType: "S3",
|
||||
Status: &status,
|
||||
StorageType: &storageType,
|
||||
}
|
||||
|
||||
v := query.Video
|
||||
if err := v.WithContext(c.Request.Context()).Create(video); err != nil {
|
||||
q := query.Q
|
||||
err := q.Transaction(func(tx *query.Query) error {
|
||||
if err := tx.Video.WithContext(c.Request.Context()).Create(video); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Atomic update: StorageUsed = StorageUsed + video.Size
|
||||
// We use UpdateSimple with Add to ensure atomicity at database level: UPDATE users SET storage_used = storage_used + ?
|
||||
if _, err := tx.User.WithContext(c.Request.Context()).
|
||||
Where(tx.User.ID.Eq(userID)).
|
||||
UpdateSimple(tx.User.StorageUsed.Add(video.Size)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to create video record", "error", err)
|
||||
response.Error(c, http.StatusInternalServerError, "Failed to create video")
|
||||
return
|
||||
@@ -156,6 +176,12 @@ func (h *Handler) ListVideos(c *gin.Context) {
|
||||
func (h *Handler) GetVideo(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
v := query.Video
|
||||
|
||||
// Atomically increment views: UPDATE videos SET views = views + 1 WHERE id = ?
|
||||
// We intentionally ignore errors here (like record not found) because the subsequent fetch will handle 404s,
|
||||
// and we don't want to fail the read if writing the view count fails for some transient reason.
|
||||
v.WithContext(c.Request.Context()).Where(v.ID.Eq(id)).UpdateSimple(v.Views.Add(1))
|
||||
|
||||
video, err := v.WithContext(c.Request.Context()).Where(v.ID.Eq(id)).First()
|
||||
if err != nil {
|
||||
response.Error(c, http.StatusNotFound, "Video not found")
|
||||
|
||||
Reference in New Issue
Block a user