I am batman

This commit is contained in:
2026-07-12 20:26:16 -04:00
commit 5a3a6357cf
37 changed files with 2938 additions and 0 deletions

View File

@@ -0,0 +1,396 @@
package handlers
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
"OpsMastery.v5/internal/database"
"OpsMastery.v5/internal/models"
"OpsMastery.v5/internal/utils"
"github.com/gofiber/adaptor/v2"
"github.com/gofiber/fiber/v2"
"github.com/markbates/goth/gothic"
"golang.org/x/crypto/bcrypt"
)
func SignUp(c *fiber.Ctx) error {
var user models.User
var input struct {
Email string `json:"email"`
Password string `json:"password"`
Name string `json:"name"`
Role string `json:"role"`
Username string `json:"username"`
}
if err := c.BodyParser(&input); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
user.Email = strings.ToLower(input.Email)
user.Password = input.Password
user.Name = strings.ToLower(input.Name)
user.Role = input.Role
user.Username = strings.ToLower(input.Username)
user.Active = false
user.VerificationToken = utils.GenerateRandomToken()
storedHash, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to hash password"})
}
user.Password = string(storedHash)
log.Printf("Hashed password for user %s: %s\n", user.Email, user.Password)
var existingUser models.User
if err := database.DB().Where("email = ? OR username = ?", user.Email, user.Username).First(&existingUser).Error; err == nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Email or username already exists"})
}
if err := database.DB().Create(&user).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
if err := utils.SendVerificationEmail(user.Email, user.VerificationToken); err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send verification email"})
}
return c.Status(http.StatusCreated).JSON(fiber.Map{
"message": "Registration successful. Please check your email to verify your account.",
})
}
func SignIn(c *fiber.Ctx) error {
var userInput struct {
EmailOrUsername string `json:"emailOrUsername"`
Password string `json:"password"`
}
if err := c.BodyParser(&userInput); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"})
}
// Force to lowercase for case-insensitive match
normalizedInput := strings.ToLower(userInput.EmailOrUsername)
var user models.User
if err := database.DB().Where("LOWER(email) = ? OR LOWER(username) = ?", normalizedInput, normalizedInput).First(&user).Error; err != nil {
return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid email/username or password"})
}
if !user.Active {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
"error": "Please verify your email before signing in",
})
}
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(userInput.Password)); err != nil {
return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid email/username or password"})
}
// Generate JWTs
accessToken, refreshToken, err := utils.GenerateJWT(user)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "Could not generate tokens"})
}
// Set Refresh Token in HttpOnly Cookie
c.Cookie(&fiber.Cookie{
Name: "refresh_token",
Value: refreshToken,
Expires: time.Now().Add(7 * 24 * time.Hour),
HTTPOnly: true,
Secure: false,
SameSite: "Strict",
Path: "/api/v1/auth/refresh", // restrict usage
})
// Return Access Token in response (frontend stores in memory)
return c.Status(http.StatusOK).JSON(fiber.Map{
"message": "Sign in successful",
"access_token": accessToken,
"user": fiber.Map{
"id": user.ID,
"email": user.Email,
"name": user.Name,
"role": user.Role,
"username": user.Username,
"address": user.Address,
"phoneNumber": user.PhoneNumber,
"active": user.Active,
},
})
}
func RefreshToken(c *fiber.Ctx) error {
refreshToken := c.Cookies("refresh_token")
if refreshToken == "" {
return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"error": "Refresh token not found"})
}
claims, err := utils.ValidateJWT(refreshToken, true)
if err != nil {
return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid refresh token"})
}
// Get user from claims
var user models.User
if err := database.DB().First(&user, claims["sub"]).Error; err != nil {
return c.Status(http.StatusUnauthorized).JSON(fiber.Map{"error": "User not found"})
}
// Generate new tokens
accessToken, newRefreshToken, err := utils.GenerateJWT(user)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": "Could not generate tokens"})
}
// Rotate refresh token
c.Cookie(&fiber.Cookie{
Name: "refresh_token",
Value: newRefreshToken,
Expires: time.Now().Add(7 * 24 * time.Hour),
HTTPOnly: true,
Secure: false,
SameSite: "Strict",
Path: "/api/auth/refresh",
})
// Return new Access Token
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"access_token": accessToken,
})
}
func SignOut(c *fiber.Ctx) error {
// Invalidate the refresh token cookie
c.Cookie(&fiber.Cookie{
Name: "refresh_token",
Value: "",
Expires: time.Now().Add(-1 * time.Hour),
HTTPOnly: true,
Secure: false,
SameSite: "Strict",
Path: "/api/auth/refresh",
})
return c.Status(http.StatusOK).JSON(fiber.Map{"message": "Successfully signed out"})
}
// func VerifyEmail(c *fiber.Ctx) error {
// token := c.Params("token")
// var user models.User
// if err := database.DB().Where("verification_token = ?", token).First(&user).Error; err != nil {
// return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
// "error": "Invalid verification token",
// })
// }
// user.Active = true
// user.VerificationToken = "" // Clear the token after verification
// if err := database.DB().Save(&user).Error; err != nil {
// return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
// "error": "Failed to verify email",
// })
// }
// return c.Status(fiber.StatusOK).JSON(fiber.Map{
// "message": "Email verified successfully",
// })
// }
func VerifyEmail(c *fiber.Ctx) error {
var body struct {
Token string `json:"token"`
}
if err := c.BodyParser(&body); err != nil || body.Token == "" {
return c.Redirect("http://localhost:3000/auth/verification-failed", fiber.StatusSeeOther)
}
var user models.User
if err := database.DB().Where("verification_token = ?", body.Token).First(&user).Error; err != nil {
return c.Redirect("http://localhost:3000/auth/verification-failed", fiber.StatusSeeOther)
}
user.Active = true
user.VerificationToken = ""
if err := database.DB().Save(&user).Error; err != nil {
return c.Redirect("http://localhost:3000/auth/verification-failed", fiber.StatusSeeOther)
}
return c.Redirect("http://localhost:3000/auth/verification-success", fiber.StatusSeeOther)
}
func RequestPasswordReset(c *fiber.Ctx) error {
var input struct {
Email string `json:"email"`
}
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"})
}
var user models.User
if err := database.DB().Where("email = ?", input.Email).First(&user).Error; err != nil {
// Don't reveal if email exists or not
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "If your email is registered, you will receive a password reset link",
})
}
resetToken := utils.GenerateRandomToken()
user.ResetToken = resetToken
user.ResetTokenExpiry = time.Now().Add(1 * time.Hour)
// Log the token being set
log.Printf("Setting reset token for user %s: %s", user.Email, resetToken)
if err := database.DB().Save(&user).Error; err != nil {
log.Printf("Error saving user with reset token: %v", err)
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to process password reset",
})
}
// Add this log right before SendPasswordResetEmail
log.Printf("About to send reset email with token: %s", resetToken)
if err := utils.SendPasswordResetEmail(user.Email, resetToken); err != nil {
log.Printf("Error sending reset email: %v", err)
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to send reset email",
})
}
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "If your email is registered, you will receive a password reset link",
})
}
func ResetPassword(c *fiber.Ctx) error {
var body struct {
ResetToken string `json:"reset_token"`
NewPassword string `json:"new_password"`
}
// Log the raw request body
rawBody := string(c.Body())
log.Printf("Received reset password request body: %s", rawBody)
if err := c.BodyParser(&body); err != nil {
log.Printf("Error parsing request body: %v", err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid request body",
})
}
log.Printf("Reset token: %s", body.ResetToken)
// Validate the token
var user models.User
if err := database.DB().Where("reset_token = ?", body.ResetToken).First(&user).Error; err != nil {
log.Printf("No user found with reset token: %s, error: %v", body.ResetToken, err)
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Invalid reset token",
})
}
// Check if token has expired
if user.ResetTokenExpiry.Before(time.Now()) {
log.Printf("Token expired. Expiry: %v, Current time: %v", user.ResetTokenExpiry, time.Now())
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
"error": "Reset token has expired",
})
}
// Hash the new password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(body.NewPassword), bcrypt.DefaultCost)
if err != nil {
log.Printf("Error hashing password: %v", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to hash password",
})
}
// Update user's password and clear reset token
user.Password = string(hashedPassword)
user.ResetToken = ""
user.ResetTokenExpiry = time.Time{}
if err := database.DB().Save(&user).Error; err != nil {
log.Printf("Error saving user: %v", err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Failed to update password",
})
}
log.Printf("Password successfully reset for user: %s", user.Email)
return c.JSON(fiber.Map{
"message": "Password successfully reset",
})
}
func validateResetToken(token string) (bool, error) {
var user models.User
if err := database.DB().Where("reset_token = ?", token).First(&user).Error; err != nil {
log.Printf("No user found with reset token: %s, error: %v", token, err)
return false, err
}
// Check if token has expired
if user.ResetTokenExpiry.Before(time.Now()) {
log.Printf("Token expired. Expiry: %v, Current time: %v", user.ResetTokenExpiry, time.Now())
return false, fmt.Errorf("reset token has expired")
}
log.Printf("Token validated successfully for user: %s", user.Email)
return true, nil
}
// Generic OAuth login handler
func OAuthLogin(c *fiber.Ctx) error {
provider := c.Params("provider")
// Set provider in query for gothic
req := c.Request()
uri := req.URI()
uri.SetQueryString("provider=" + provider)
return adaptor.HTTPHandlerFunc(gothic.BeginAuthHandler)(c)
}
func OAuthCallback(c *fiber.Ctx) error {
provider := c.Params("provider")
req := c.Request()
req.URI().SetQueryString("provider=" + provider)
return adaptor.HTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
user, err := gothic.CompleteUserAuth(w, r)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
// Check if user with this email already exists
var existingUser models.User
email := strings.ToLower(user.Email)
if err := database.DB().Where("LOWER(email) = ?", email).First(&existingUser).Error; err == nil {
// User exists, do not allow OAuth sign up
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(map[string]string{
"error": "This email already exists, please sign in.",
})
return
}
// ...proceed with creating new user from OAuth profile...
// (your existing logic here)
json.NewEncoder(w).Encode(user)
})(c)
}

View File

@@ -0,0 +1,224 @@
// // filepath: /home/cody/OpsMastery.v5/internal/handlers/chatHandler.go
package handlers
import (
"fmt"
"sync"
"time"
"OpsMastery.v5/internal/database"
"OpsMastery.v5/internal/models"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/websocket/v2"
)
var (
clients = make(map[*websocket.Conn]uint) // map connection to user ID
broadcast = make(chan Message)
mu sync.Mutex
chatClients = make(map[uint]map[*websocket.Conn]bool) // chatID -> set of connections
)
type Message struct {
SenderID uint `json:"sender_id"`
RecipientID uint `json:"recipient_id"`
Content string `json:"content"`
}
type Chat struct {
ID uint `json:"id"`
Name string `json:"name"`
TicketID *uint `json:"ticket_id,omitempty"`
IsPrivate bool `json:"is_private"`
Users []models.User `json:"users"`
Messages []models.ChatMessage `json:"messages"`
}
// WebSocket chat endpoint
func ChatWebSocket(c *fiber.Ctx) error {
// Upgrade to WebSocket
if websocket.IsWebSocketUpgrade(c) {
return c.Next()
}
return fiber.ErrUpgradeRequired
}
func HandleChat(c *websocket.Conn, claims map[string]interface{}) {
senderID, _ := claims["userID"].(uint)
// Register connection
if chatClients[0] == nil {
chatClients[0] = make(map[*websocket.Conn]bool)
}
chatClients[0][c] = true
defer func() {
delete(chatClients[0], c)
c.Close()
}()
for {
var msg struct {
Content string `json:"content"`
}
if err := c.ReadJSON(&msg); err != nil {
break
}
// Save message to DB
chatMsg := models.ChatMessage{
ChatID: 0,
SenderID: senderID,
Content: msg.Content,
SentAt: time.Now(),
}
database.DB().Create(&chatMsg)
// Broadcast to all clients in the chat
for client := range chatClients[0] {
client.WriteJSON(chatMsg)
}
}
}
// Broadcast messages to all clients
func StartBroadcast() {
for {
msg := <-broadcast
mu.Lock()
for conn := range clients {
if err := conn.WriteJSON(msg); err != nil {
fmt.Println("Error broadcasting:", err)
}
}
mu.Unlock()
}
}
// GET /api/v1/chats?user1=username1&user2=username2
func GetChatHistory(c *fiber.Ctx) error {
user1 := c.Query("user1")
user2 := c.Query("user2")
var messages []models.ChatMessage
database.DB().Where(
"(sender_id = ? AND recipient_id = ?) OR (sender_id = ? AND recipient_id = ?)",
user1, user2, user2, user1,
).Order("created_at asc").Find(&messages)
return c.JSON(messages) // Returns [] if no messages exist
}
// GET /api/v1/chat_partners?user_id=some_user_id
func GetChatPartners(c *fiber.Ctx) error {
userID := c.Query("user_id")
var partners []models.User
database.DB().Raw(`
SELECT DISTINCT u.*
FROM users u
JOIN chat_messages cm
ON (cm.sender_id = u.id OR cm.recipient_id = u.id)
WHERE (cm.sender_id = ? OR cm.recipient_id = ?) AND u.id != ?
`, userID, userID, userID).Scan(&partners)
return c.JSON(partners)
}
// DELETE /api/v1/chats/delete?user1=username1&user2=username2
func DeleteChatBetweenUsers(c *fiber.Ctx) error {
user1 := c.Query("user1")
user2 := c.Query("user2")
result := database.DB().Where(
"(sender_id = ? AND recipient_id = ?) OR (sender_id = ? AND recipient_id = ?)",
user1, user2, user2, user1,
).Delete(&models.ChatMessage{})
if result.Error != nil {
return c.Status(500).JSON(fiber.Map{"error": result.Error.Error()})
}
return c.JSON(fiber.Map{"deleted": result.RowsAffected})
}
// Create a new chat (group or direct)
func CreateChat(c *fiber.Ctx) error {
var input struct {
Name string `json:"name"`
UserIDs []uint `json:"user_ids"`
IsPrivate bool `json:"is_private"`
TicketID *uint `json:"ticket_id"`
}
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"})
}
chat := models.Chat{
Name: input.Name,
IsPrivate: input.IsPrivate,
TicketID: input.TicketID,
}
if err := database.DB().Create(&chat).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not create chat"})
}
if len(input.UserIDs) > 0 {
var users []models.User
if err := database.DB().Where("id IN ?", input.UserIDs).Find(&users).Error; err == nil {
database.DB().Model(&chat).Association("Users").Append(users)
}
}
return c.Status(fiber.StatusCreated).JSON(chat)
}
// Add users to an existing chat
func AddUsersToChat(c *fiber.Ctx) error {
chatID := c.Params("chatId")
var input struct {
UserIDs []uint `json:"user_ids"`
}
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"})
}
var chat models.Chat
if err := database.DB().First(&chat, chatID).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Chat not found"})
}
var users []models.User
if err := database.DB().Where("id IN ?", input.UserIDs).Find(&users).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Users not found"})
}
database.DB().Model(&chat).Association("Users").Append(users)
return c.JSON(fiber.Map{"added": len(users)})
}
// Get all chats for a user (for sidebar)
func GetChatsForUser(c *fiber.Ctx) error {
userID := c.Params("userId")
var chats []models.Chat
if err := database.DB().Joins("JOIN chat_users ON chat_users.chat_id = chats.id").
Where("chat_users.user_id = ?", userID).
Preload("Users").
Preload("Messages").
Find(&chats).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not fetch chats"})
}
return c.JSON(chats)
}
// Get all messages for a chat
func GetChatMessages(c *fiber.Ctx) error {
chatID := c.Params("chatId")
var messages []models.ChatMessage
if err := database.DB().Where("chat_id = ?", chatID).Order("sent_at asc").Find(&messages).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not fetch messages"})
}
return c.JSON(messages)
}
// Delete a chat (and its messages)
func DeleteChat(c *fiber.Ctx) error {
chatID := c.Params("chatId")
if err := database.DB().Where("chat_id = ?", chatID).Delete(&models.ChatMessage{}).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not delete messages"})
}
if err := database.DB().Delete(&models.Chat{}, chatID).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not delete chat"})
}
return c.JSON(fiber.Map{"deleted": chatID})
}

View File

@@ -0,0 +1,327 @@
package handlers
import (
"net/http"
"strconv"
"strings"
"OpsMastery.v5/internal/database"
"OpsMastery.v5/internal/models"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)
type sectionPayload struct {
Title string `json:"title"`
TitleAlt string `json:"Title"`
Position *int `json:"position"`
PositionAlt *int `json:"Position"`
}
type taskPayload struct {
Title string `json:"title"`
TitleAlt string `json:"Title"`
Description string `json:"description"`
DescriptionAlt string `json:"Description"`
Position *int `json:"position"`
PositionAlt *int `json:"Position"`
SectionID *uint `json:"section_id"`
SectionIDAlt *uint `json:"SectionID"`
}
func (p sectionPayload) normalizedTitle() string {
title := strings.TrimSpace(p.Title)
if title == "" {
title = strings.TrimSpace(p.TitleAlt)
}
return title
}
func (p sectionPayload) normalizedPosition() int {
if p.Position != nil {
return *p.Position
}
if p.PositionAlt != nil {
return *p.PositionAlt
}
return 0
}
func (p taskPayload) normalizedTask() models.Tasks {
title := strings.TrimSpace(p.Title)
if title == "" {
title = strings.TrimSpace(p.TitleAlt)
}
description := p.Description
if description == "" {
description = p.DescriptionAlt
}
position := 0
if p.Position != nil {
position = *p.Position
} else if p.PositionAlt != nil {
position = *p.PositionAlt
}
sectionID := uint(0)
if p.SectionID != nil {
sectionID = *p.SectionID
} else if p.SectionIDAlt != nil {
sectionID = *p.SectionIDAlt
}
return models.Tasks{
Title: title,
Description: description,
Position: position,
SectionID: sectionID,
}
}
func sectionExists(sectionID uint) (bool, error) {
var count int64
err := database.DB().Model(&models.TaskSection{}).Where("id = ?", sectionID).Count(&count).Error
if err != nil {
return false, err
}
return count > 0, nil
}
func CreateTaskSection(c *fiber.Ctx) error {
var payload sectionPayload
if err := c.BodyParser(&payload); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
section := models.TaskSection{
Title: payload.normalizedTitle(),
Position: payload.normalizedPosition(),
}
if section.Title == "" {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "title is required"})
}
if err := database.DB().Create(&section).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.Status(http.StatusCreated).JSON(section)
}
func ListTaskSections(c *fiber.Ctx) error {
var sections []models.TaskSection
err := database.DB().
Preload("Tasks", func(db *gorm.DB) *gorm.DB {
return db.Order("position asc, id asc")
}).
Order("position asc, id asc").
Find(&sections).Error
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(sections)
}
func UpdateTaskSectionByID(c *fiber.Ctx) error {
id := c.Params("id")
sectionID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid section ID"})
}
var payload sectionPayload
if err := c.BodyParser(&payload); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
updates := map[string]any{}
if title := payload.normalizedTitle(); title != "" {
updates["title"] = title
}
if payload.Position != nil || payload.PositionAlt != nil {
updates["position"] = payload.normalizedPosition()
}
if len(updates) == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "at least one field is required"})
}
result := database.DB().Model(&models.TaskSection{}).Where("id = ?", sectionID).Updates(updates)
if result.Error != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": result.Error.Error()})
}
if result.RowsAffected == 0 {
return c.Status(http.StatusNotFound).JSON(fiber.Map{"error": "Section not found"})
}
var section models.TaskSection
if err := database.DB().First(&section, sectionID).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(section)
}
func DeleteTaskSectionByID(c *fiber.Ctx) error {
id := c.Params("id")
sectionID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid section ID"})
}
result := database.DB().Unscoped().Delete(&models.TaskSection{}, sectionID)
if result.RowsAffected == 0 {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Section not found"})
}
if result.Error != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": result.Error.Error()})
}
return c.Status(http.StatusOK).JSON(fiber.Map{"message": "Section deleted successfully"})
}
func CreateTask(c *fiber.Ctx) error {
var payload taskPayload
if err := c.BodyParser(&payload); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
task := payload.normalizedTask()
if task.Title == "" || task.SectionID == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "title and section_id are required"})
}
exists, err := sectionExists(task.SectionID)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
if !exists {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "section not found"})
}
if err := database.DB().Create(&task).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.Status(http.StatusCreated).JSON(task)
}
func ListTasks(c *fiber.Ctx) error {
var tasks []models.Tasks
err := database.DB().
Preload("Section").
Order("position asc, id asc").
Find(&tasks).Error
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(tasks)
}
func GetTaskByID(c *fiber.Ctx) error {
id := c.Params("id")
var task models.Tasks
taskID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid task ID"})
}
if err := database.DB().Preload("Section").First(&task, taskID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Task not found"})
}
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(task)
}
func DeleteTaskByID(c *fiber.Ctx) error {
id := c.Params("id")
taskID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid task ID"})
}
result := database.DB().Unscoped().Delete(&models.Tasks{}, taskID)
if result.RowsAffected == 0 {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Task not found"})
}
if result.Error != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": result.Error.Error()})
}
return c.Status(http.StatusOK).JSON(fiber.Map{"message": "Task deleted successfully"})
}
func UpdateTaskByID(c *fiber.Ctx) error {
id := c.Params("id")
taskID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid task ID"})
}
var payload taskPayload
if err := c.BodyParser(&payload); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
updates := map[string]any{}
title := strings.TrimSpace(payload.Title)
if title == "" {
title = strings.TrimSpace(payload.TitleAlt)
}
if title != "" {
updates["title"] = title
}
if payload.Description != "" || payload.DescriptionAlt != "" {
description := payload.Description
if description == "" {
description = payload.DescriptionAlt
}
updates["description"] = description
}
if payload.Position != nil {
updates["position"] = *payload.Position
} else if payload.PositionAlt != nil {
updates["position"] = *payload.PositionAlt
}
sectionID := uint(0)
if payload.SectionID != nil {
sectionID = *payload.SectionID
} else if payload.SectionIDAlt != nil {
sectionID = *payload.SectionIDAlt
}
if sectionID != 0 {
exists, err := sectionExists(sectionID)
if err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
if !exists {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "section not found"})
}
updates["section_id"] = sectionID
}
if len(updates) == 0 {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "at least one field is required"})
}
result := database.DB().Model(&models.Tasks{}).Where("id = ?", taskID).Updates(updates)
if result.Error != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": result.Error.Error()})
}
if result.RowsAffected == 0 {
return c.Status(http.StatusNotFound).JSON(fiber.Map{"error": "Task not found"})
}
var task models.Tasks
if err := database.DB().Preload("Section").First(&task, taskID).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(task)
}

View File

@@ -0,0 +1,84 @@
package handlers
import (
"net/http"
"strconv"
"OpsMastery.v5/internal/database"
"OpsMastery.v5/internal/models"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)
func CreateTicket(c *fiber.Ctx) error {
var ticket models.Ticket
if err := c.BodyParser(&ticket); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
if err := database.DB().Create(&ticket).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.Status(http.StatusCreated).JSON(ticket)
}
func ListTickets(c *fiber.Ctx) error {
var tickets []models.Ticket
if err := database.DB().Find(&tickets).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(tickets)
}
func GetTicketByID(c *fiber.Ctx) error {
id := c.Params("id")
var ticket models.Ticket
ticketID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid ticket ID"})
}
if err := database.DB().First(&ticket, ticketID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Ticket not found"})
}
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(ticket)
}
func DeleteTicketByID(c *fiber.Ctx) error {
id := c.Params("id")
ticketID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid ticket ID"})
}
result := database.DB().Unscoped().Delete(&models.Ticket{}, ticketID)
if result.RowsAffected == 0 {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Ticket not found"})
}
if result.Error != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": result.Error.Error()})
}
return c.Status(http.StatusOK).JSON(fiber.Map{"message": "Ticket deleted successfully"})
}
func UpdateTicketByID(c *fiber.Ctx) error {
id := c.Params("id")
ticketID, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid ticket ID"})
}
var ticket models.Ticket
if err := c.BodyParser(&ticket); err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
}
if err := database.DB().Model(&models.Ticket{}).Where("id = ?", ticketID).Updates(ticket).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.Status(http.StatusOK).JSON(fiber.Map{"message": "Ticket updated successfully"})
}

View File

@@ -0,0 +1,191 @@
package handlers
import (
"net/http"
"strconv"
"strings"
"OpsMastery.v5/internal/database"
"OpsMastery.v5/internal/models"
"github.com/gofiber/fiber/v2"
)
func ListUsers(c *fiber.Ctx) error {
var users []models.User
if err := database.DB().Unscoped().Find(&users).Error; err != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
}
return c.JSON(users)
}
func GetUserByID(c *fiber.Ctx) error {
id := c.Params("id")
var user models.User
if err := database.DB().First(&user, id).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
return c.JSON(user)
}
func DeleteUserByID(c *fiber.Ctx) error {
id := c.Params("id")
userIDParsed, err := strconv.ParseUint(id, 10, 32)
if err != nil {
return c.Status(http.StatusBadRequest).JSON(fiber.Map{"error": "Invalid user ID"})
}
result := database.DB().Unscoped().Delete(&models.User{}, userIDParsed)
if result.RowsAffected == 0 {
return c.Status(http.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
if result.Error != nil {
return c.Status(http.StatusInternalServerError).JSON(fiber.Map{"error": result.Error.Error()})
}
return c.Status(http.StatusOK).JSON(fiber.Map{"message": "User deleted successfully"})
}
func UpdateUserByID(c *fiber.Ctx) error {
id := c.Params("id")
var user models.User
if err := database.DB().First(&user, id).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
var input models.User
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"})
}
// Only update fields if present in input
if input.Name != "" {
user.Name = input.Name
}
if input.Username != "" {
user.Username = input.Username
}
if input.Address != "" {
user.Address = input.Address
}
if input.PhoneNumber != "" {
user.PhoneNumber = input.PhoneNumber
}
if input.Role != "" {
user.Role = input.Role
}
if input.Email != "" {
user.Email = input.Email
}
if input.Password != "" {
user.Password = input.Password // Consider hashing here!
}
// ...handle other fields as needed...
if err := database.DB().Save(&user).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not update user"})
}
return c.JSON(user)
}
func SetUserRole(c *fiber.Ctx) error {
id := c.Params("id")
var user models.User
if err := database.DB().First(&user, id).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
var input struct {
Role string `json:"role"`
}
if err := c.BodyParser(&input); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid input"})
}
user.Role = input.Role
if err := database.DB().Save(&user).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not update user role"})
}
return c.JSON(user)
}
func GetCurrentUser(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "User ID not found"})
}
var user models.User
if err := database.DB().First(&user, userID).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
return c.JSON(user)
}
func UpdateCurrentUser(c *fiber.Ctx) error {
userID, ok := c.Locals("userID").(uint)
if !ok {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "User ID not found"})
}
var user models.User
if err := database.DB().First(&user, userID).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
// Parse form fields using Fiber's API
name := c.FormValue("name")
email := c.FormValue("email")
// Handle file upload (if needed)
fileHeader, err := c.FormFile("avatar")
if err == nil {
// Process the uploaded file
file, err := fileHeader.Open()
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Failed to open uploaded file"})
}
defer file.Close()
// ...handle file...
}
// Update user fields
user.Name = name
user.Email = email
if err := database.DB().Save(&user).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not update user"})
}
return c.JSON(user)
}
func GetUserProfilePhoto(c *fiber.Ctx) error {
id := c.Params("id")
var user models.User
if err := database.DB().First(&user, id).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
if len(user.ProfilePhoto) == 0 {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Profile photo not found"})
}
// You may want to detect the image type; here we default to jpeg
c.Set("Content-Type", "image/jpeg")
return c.Send(user.ProfilePhoto)
}
func SearchUsers(c *fiber.Ctx) error {
q := c.Query("q")
if q == "" {
return c.JSON([]models.User{})
}
normalized := strings.ToLower(q)
var users []models.User
database.DB().Debug().Where(
"LOWER(username) LIKE ? OR LOWER(email) LIKE ? OR LOWER(name) LIKE ?",
"%"+normalized+"%", "%"+normalized+"%", "%"+normalized+"%",
).Find(&users)
return c.JSON(users)
}