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)
|
// 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")
|
response.Error(c, http.StatusUnauthorized, "Please login with Google")
|
||||||
return
|
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")
|
response.Error(c, http.StatusUnauthorized, "Invalid credentials")
|
||||||
return
|
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})
|
response.Success(c, gin.H{"user": user})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,12 +111,14 @@ func (h *handler) Register(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
password := string(hashedPassword)
|
||||||
|
role := "USER"
|
||||||
newUser := &model.User{
|
newUser := &model.User{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
Password: string(hashedPassword),
|
Password: &password,
|
||||||
Username: req.Username,
|
Username: &req.Username,
|
||||||
Role: "USER",
|
Role: &role,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := u.WithContext(c.Request.Context()).Create(newUser); err != nil {
|
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
|
u := query.User
|
||||||
user, err := u.WithContext(c.Request.Context()).Where(u.Email.Eq(googleUser.Email)).First()
|
user, err := u.WithContext(c.Request.Context()).Where(u.Email.Eq(googleUser.Email)).First()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
role := "USER"
|
||||||
user = &model.User{
|
user = &model.User{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
Email: googleUser.Email,
|
Email: googleUser.Email,
|
||||||
Username: googleUser.Name,
|
Username: &googleUser.Name,
|
||||||
GoogleID: googleUser.ID,
|
GoogleID: &googleUser.ID,
|
||||||
Avatar: googleUser.Picture,
|
Avatar: &googleUser.Picture,
|
||||||
Role: "USER",
|
Role: &role,
|
||||||
}
|
}
|
||||||
if err := u.WithContext(c.Request.Context()).Create(user); err != nil {
|
if err := u.WithContext(c.Request.Context()).Create(user); err != nil {
|
||||||
response.Fail(c, "Failed to create user")
|
response.Fail(c, "Failed to create user")
|
||||||
return
|
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)
|
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})
|
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
|
// In a real scenario, we would contact Stripe/PayPal here to create a session
|
||||||
// For now, we just create a "PENDING" payment record.
|
// For now, we just create a "PENDING" payment record.
|
||||||
|
|
||||||
|
status := "PENDING"
|
||||||
|
provider := "STRIPE"
|
||||||
|
|
||||||
payment := &model.Payment{
|
payment := &model.Payment{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
PlanID: req.PlanID,
|
PlanID: &req.PlanID,
|
||||||
Amount: req.Amount,
|
Amount: req.Amount,
|
||||||
Status: "PENDING",
|
Status: &status,
|
||||||
Provider: "STRIPE", // Defaulting to Stripe for this example
|
Provider: &provider, // Defaulting to Stripe for this example
|
||||||
}
|
}
|
||||||
|
|
||||||
p := query.Payment
|
p := query.Payment
|
||||||
|
|||||||
@@ -86,21 +86,41 @@ func (h *Handler) CreateVideo(c *gin.Context) {
|
|||||||
|
|
||||||
userID := c.GetString("userID")
|
userID := c.GetString("userID")
|
||||||
|
|
||||||
|
status := "PUBLIC"
|
||||||
|
storageType := "S3"
|
||||||
|
|
||||||
video := &model.Video{
|
video := &model.Video{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
|
Name: req.Title,
|
||||||
Title: req.Title,
|
Title: req.Title,
|
||||||
Description: req.Description,
|
Description: &req.Description,
|
||||||
URL: req.URL,
|
URL: req.URL,
|
||||||
Size: req.Size,
|
Size: req.Size,
|
||||||
Duration: req.Duration,
|
Duration: req.Duration,
|
||||||
Format: req.Format,
|
Format: req.Format,
|
||||||
Status: "PUBLIC",
|
Status: &status,
|
||||||
StorageType: "S3",
|
StorageType: &storageType,
|
||||||
}
|
}
|
||||||
|
|
||||||
v := query.Video
|
q := query.Q
|
||||||
if err := v.WithContext(c.Request.Context()).Create(video); err != nil {
|
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)
|
h.logger.Error("Failed to create video record", "error", err)
|
||||||
response.Error(c, http.StatusInternalServerError, "Failed to create video")
|
response.Error(c, http.StatusInternalServerError, "Failed to create video")
|
||||||
return
|
return
|
||||||
@@ -156,6 +176,12 @@ func (h *Handler) ListVideos(c *gin.Context) {
|
|||||||
func (h *Handler) GetVideo(c *gin.Context) {
|
func (h *Handler) GetVideo(c *gin.Context) {
|
||||||
id := c.Param("id")
|
id := c.Param("id")
|
||||||
v := query.Video
|
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()
|
video, err := v.WithContext(c.Request.Context()).Where(v.ID.Eq(id)).First()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, http.StatusNotFound, "Video not found")
|
response.Error(c, http.StatusNotFound, "Video not found")
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ func (m *AuthMiddleware) Handle() gin.HandlerFunc {
|
|||||||
// If we want new Access we can regenerate pair and update tokens.
|
// If we want new Access we can regenerate pair and update tokens.
|
||||||
// Updating refresh token extends session (Slide expiration).
|
// Updating refresh token extends session (Slide expiration).
|
||||||
|
|
||||||
newTd, err := m.token.GenerateTokenPair(userID, userObj.Email, userObj.Role)
|
newTd, err := m.token.GenerateTokenPair(userID, userObj.Email, *userObj.Role)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Delete old refresh token from Redis?
|
// Delete old refresh token from Redis?
|
||||||
m.cache.Del(ctx, redisKey)
|
m.cache.Del(ctx, redisKey)
|
||||||
@@ -140,7 +140,7 @@ func (m *AuthMiddleware) Handle() gin.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(user.Role) == "block" {
|
if user.Role != nil && strings.ToLower(*user.Role) == "block" {
|
||||||
response.Error(c, http.StatusForbidden, "Forbidden: User is blocked")
|
response.Error(c, http.StatusForbidden, "Forbidden: User is blocked")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user