first commit

This commit is contained in:
2025-09-12 12:38:11 +02:00
commit 0db2fd0314
46 changed files with 23221 additions and 0 deletions

View 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
View 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
View 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
View 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
View 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"
}