first commit
This commit is contained in:
51
backend/models/database.go
Normal file
51
backend/models/database.go
Normal file
@@ -0,0 +1,51 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var db *gorm.DB
|
||||
|
||||
// InitDB initializes the database connection
|
||||
func InitDB() {
|
||||
var err error
|
||||
db, err = gorm.Open(sqlite.Open("project_dashboard.db"), &gorm.Config{})
|
||||
if err != nil {
|
||||
log.Fatal("Failed to connect to database:", err)
|
||||
}
|
||||
|
||||
// Auto-migrate the schema
|
||||
err = db.AutoMigrate(
|
||||
&User{},
|
||||
&Project{},
|
||||
&ProjectMember{},
|
||||
&Task{},
|
||||
&Subtask{},
|
||||
&Label{},
|
||||
&Comment{},
|
||||
&FileUpload{},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to migrate database:", err)
|
||||
}
|
||||
|
||||
log.Println("Database connected and migrated successfully")
|
||||
}
|
||||
|
||||
// GetDB returns the database instance
|
||||
func GetDB() *gorm.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
// CloseDB closes the database connection
|
||||
func CloseDB() {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
log.Printf("Error getting database instance: %v", err)
|
||||
return
|
||||
}
|
||||
sqlDB.Close()
|
||||
}
|
102
backend/models/file.go
Normal file
102
backend/models/file.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type FileUpload struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
|
||||
FileName string `json:"file_name" gorm:"not null"`
|
||||
OriginalName string `json:"original_name" gorm:"not null"`
|
||||
FilePath string `json:"file_path" gorm:"not null"`
|
||||
FileSize int64 `json:"file_size" gorm:"not null"`
|
||||
MimeType string `json:"mime_type" gorm:"not null"`
|
||||
Description string `json:"description"`
|
||||
|
||||
// Foreign Keys
|
||||
UploadedByID uint `json:"uploaded_by_id" gorm:"not null"`
|
||||
ProjectID *uint `json:"project_id"`
|
||||
TaskID *uint `json:"task_id"`
|
||||
|
||||
// Relationships
|
||||
UploadedBy User `json:"uploaded_by" gorm:"foreignKey:UploadedByID"`
|
||||
Project *Project `json:"project" gorm:"foreignKey:ProjectID"`
|
||||
Task *Task `json:"task" gorm:"foreignKey:TaskID"`
|
||||
}
|
||||
|
||||
// GetFileExtension returns the file extension
|
||||
func (f *FileUpload) GetFileExtension() string {
|
||||
if len(f.OriginalName) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
lastDot := -1
|
||||
for i := len(f.OriginalName) - 1; i >= 0; i-- {
|
||||
if f.OriginalName[i] == '.' {
|
||||
lastDot = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if lastDot == -1 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return f.OriginalName[lastDot:]
|
||||
}
|
||||
|
||||
// IsImage checks if the file is an image
|
||||
func (f *FileUpload) IsImage() bool {
|
||||
imageTypes := []string{
|
||||
"image/jpeg", "image/jpg", "image/png", "image/gif",
|
||||
"image/webp", "image/svg+xml", "image/bmp", "image/tiff",
|
||||
}
|
||||
|
||||
for _, imgType := range imageTypes {
|
||||
if f.MimeType == imgType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsDocument checks if the file is a document
|
||||
func (f *FileUpload) IsDocument() bool {
|
||||
docTypes := []string{
|
||||
"application/pdf", "application/msword",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"application/vnd.ms-excel",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.ms-powerpoint",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"text/plain", "text/csv",
|
||||
}
|
||||
|
||||
for _, docType := range docTypes {
|
||||
if f.MimeType == docType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// FormatFileSize returns a human-readable file size
|
||||
func (f *FileUpload) FormatFileSize() string {
|
||||
const unit = 1024
|
||||
if f.FileSize < unit {
|
||||
return fmt.Sprintf("%d B", f.FileSize)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := f.FileSize / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(f.FileSize)/float64(div), "KMGTPE"[exp])
|
||||
}
|
79
backend/models/project.go
Normal file
79
backend/models/project.go
Normal file
@@ -0,0 +1,79 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Project struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status" gorm:"default:'active'"` // active, completed, archived, on_hold
|
||||
StartDate *time.Time `json:"start_date"`
|
||||
EndDate *time.Time `json:"end_date"`
|
||||
Color string `json:"color" gorm:"default:'#3b82f6'"` // Hex color for UI
|
||||
|
||||
// Foreign Keys
|
||||
OwnerID uint `json:"owner_id" gorm:"not null"`
|
||||
|
||||
// Relationships
|
||||
Owner User `json:"owner" gorm:"foreignKey:OwnerID"`
|
||||
Members []User `json:"members" gorm:"many2many:project_members"`
|
||||
Tasks []Task `json:"tasks" gorm:"foreignKey:ProjectID"`
|
||||
Files []FileUpload `json:"files" gorm:"foreignKey:ProjectID"`
|
||||
}
|
||||
|
||||
type ProjectMember struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
|
||||
ProjectID uint `json:"project_id" gorm:"not null"`
|
||||
UserID uint `json:"user_id" gorm:"not null"`
|
||||
Role string `json:"role" gorm:"default:'member'"` // owner, manager, member
|
||||
|
||||
// Relationships
|
||||
Project Project `json:"project" gorm:"foreignKey:ProjectID"`
|
||||
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||
}
|
||||
|
||||
// IsOwner checks if the given user is the owner of this project
|
||||
func (p *Project) IsOwner(userID uint) bool {
|
||||
return p.OwnerID == userID
|
||||
}
|
||||
|
||||
// HasMember checks if the given user is a member of this project
|
||||
func (p *Project) HasMember(userID uint) bool {
|
||||
for _, member := range p.Members {
|
||||
if member.ID == userID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetMemberRole returns the role of a member in this project
|
||||
func (p *Project) GetMemberRole(userID uint) string {
|
||||
if p.OwnerID == userID {
|
||||
return "owner"
|
||||
}
|
||||
|
||||
for _, member := range p.Members {
|
||||
if member.ID == userID {
|
||||
// Get role from ProjectMember table
|
||||
var pm ProjectMember
|
||||
if err := db.Where("project_id = ? AND user_id = ?", p.ID, userID).First(&pm).Error; err == nil {
|
||||
return pm.Role
|
||||
}
|
||||
return "member"
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
125
backend/models/task.go
Normal file
125
backend/models/task.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
|
||||
Title string `json:"title" gorm:"not null"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status" gorm:"default:'todo'"` // todo, in_progress, review, done
|
||||
Priority string `json:"priority" gorm:"default:'medium'"` // low, medium, high, urgent
|
||||
DueDate *time.Time `json:"due_date"`
|
||||
CompletedAt *time.Time `json:"completed_at"`
|
||||
Position int `json:"position" gorm:"default:0"` // For ordering in kanban board
|
||||
|
||||
// Foreign Keys
|
||||
ProjectID uint `json:"project_id" gorm:"not null"`
|
||||
AssignedToID *uint `json:"assigned_to_id"`
|
||||
CreatedByID uint `json:"created_by_id" gorm:"not null"`
|
||||
|
||||
// Relationships
|
||||
Project Project `json:"project" gorm:"foreignKey:ProjectID"`
|
||||
AssignedTo *User `json:"assigned_to" gorm:"foreignKey:AssignedToID"`
|
||||
CreatedBy User `json:"created_by" gorm:"foreignKey:CreatedByID"`
|
||||
Comments []Comment `json:"comments" gorm:"foreignKey:TaskID"`
|
||||
Files []FileUpload `json:"files" gorm:"foreignKey:TaskID"`
|
||||
Subtasks []Subtask `json:"subtasks" gorm:"foreignKey:ParentTaskID"`
|
||||
Labels []Label `json:"labels" gorm:"many2many:task_labels"`
|
||||
}
|
||||
|
||||
type Subtask struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
|
||||
Title string `json:"title" gorm:"not null"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status" gorm:"default:'todo'"` // todo, in_progress, done
|
||||
Position int `json:"position" gorm:"default:0"`
|
||||
CompletedAt *time.Time `json:"completed_at"`
|
||||
|
||||
// Foreign Keys
|
||||
ParentTaskID uint `json:"parent_task_id" gorm:"not null"`
|
||||
AssignedToID *uint `json:"assigned_to_id"`
|
||||
|
||||
// Relationships
|
||||
ParentTask Task `json:"parent_task" gorm:"foreignKey:ParentTaskID"`
|
||||
AssignedTo *User `json:"assigned_to" gorm:"foreignKey:AssignedToID"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
|
||||
Name string `json:"name" gorm:"not null"`
|
||||
Color string `json:"color" gorm:"default:'#6b7280'"` // Hex color
|
||||
|
||||
// Foreign Keys
|
||||
ProjectID uint `json:"project_id" gorm:"not null"`
|
||||
|
||||
// Relationships
|
||||
Project Project `json:"project" gorm:"foreignKey:ProjectID"`
|
||||
Tasks []Task `json:"tasks" gorm:"many2many:task_labels"`
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
|
||||
Content string `json:"content" gorm:"not null"`
|
||||
|
||||
// Foreign Keys
|
||||
TaskID uint `json:"task_id" gorm:"not null"`
|
||||
UserID uint `json:"user_id" gorm:"not null"`
|
||||
|
||||
// Relationships
|
||||
Task Task `json:"task" gorm:"foreignKey:TaskID"`
|
||||
User User `json:"user" gorm:"foreignKey:UserID"`
|
||||
}
|
||||
|
||||
// IsOverdue checks if the task is overdue
|
||||
func (t *Task) IsOverdue() bool {
|
||||
if t.DueDate == nil {
|
||||
return false
|
||||
}
|
||||
return t.Status != "done" && t.DueDate.Before(time.Now())
|
||||
}
|
||||
|
||||
// GetCompletionPercentage returns the completion percentage based on subtasks
|
||||
func (t *Task) GetCompletionPercentage() int {
|
||||
if len(t.Subtasks) == 0 {
|
||||
if t.Status == "done" {
|
||||
return 100
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
completed := 0
|
||||
for _, subtask := range t.Subtasks {
|
||||
if subtask.Status == "done" {
|
||||
completed++
|
||||
}
|
||||
}
|
||||
|
||||
return (completed * 100) / len(t.Subtasks)
|
||||
}
|
||||
|
||||
// MarkAsCompleted marks the task as completed
|
||||
func (t *Task) MarkAsCompleted() {
|
||||
t.Status = "done"
|
||||
now := time.Now()
|
||||
t.CompletedAt = &now
|
||||
}
|
60
backend/models/user.go
Normal file
60
backend/models/user.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
|
||||
Email string `json:"email" gorm:"uniqueIndex;not null"`
|
||||
Password string `json:"-" gorm:"not null"` // Hidden from JSON
|
||||
FirstName string `json:"first_name" gorm:"not null"`
|
||||
LastName string `json:"last_name" gorm:"not null"`
|
||||
Avatar string `json:"avatar"`
|
||||
Role string `json:"role" gorm:"default:'member'"` // admin, manager, member
|
||||
|
||||
// Relationships
|
||||
Projects []Project `json:"projects" gorm:"many2many:project_members"`
|
||||
OwnedProjects []Project `json:"owned_projects" gorm:"foreignKey:OwnerID"`
|
||||
Tasks []Task `json:"tasks" gorm:"foreignKey:AssignedToID"`
|
||||
Comments []Comment `json:"comments" gorm:"foreignKey:UserID"`
|
||||
FileUploads []FileUpload `json:"file_uploads" gorm:"foreignKey:UploadedByID"`
|
||||
}
|
||||
|
||||
// HashPassword hashes the user's password
|
||||
func (u *User) HashPassword(password string) error {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Password = string(hashedPassword)
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPassword checks if the provided password matches the user's password
|
||||
func (u *User) CheckPassword(password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// GetFullName returns the user's full name
|
||||
func (u *User) GetFullName() string {
|
||||
return u.FirstName + " " + u.LastName
|
||||
}
|
||||
|
||||
// IsAdmin checks if the user has admin role
|
||||
func (u *User) IsAdmin() bool {
|
||||
return u.Role == "admin"
|
||||
}
|
||||
|
||||
// IsManager checks if the user has manager role or higher
|
||||
func (u *User) IsManager() bool {
|
||||
return u.Role == "manager" || u.Role == "admin"
|
||||
}
|
Reference in New Issue
Block a user