implement graphQL and init postgres
This commit is contained in:
@@ -44,22 +44,22 @@ type AuditLogCreateRequest struct {
|
||||
|
||||
// AuditLogInfo represents public audit log information
|
||||
type AuditLogInfo struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"userId"`
|
||||
UserEmail string `json:"userEmail,omitempty"`
|
||||
UserName string `json:"userName,omitempty"`
|
||||
Action string `json:"action"`
|
||||
Resource string `json:"resource"`
|
||||
ResourceID string `json:"resourceId"`
|
||||
Details map[string]interface{} `json:"details"`
|
||||
IPAddress string `json:"ipAddress"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
Success bool `json:"success"`
|
||||
ErrorMsg string `json:"errorMsg,omitempty"`
|
||||
Duration int64 `json:"duration,omitempty"`
|
||||
SessionID string `json:"sessionId,omitempty"`
|
||||
RequestID string `json:"requestId,omitempty"`
|
||||
DateCreated string `json:"dateCreated"`
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"userId"`
|
||||
UserEmail string `json:"userEmail,omitempty"`
|
||||
UserName string `json:"userName,omitempty"`
|
||||
Action string `json:"action"`
|
||||
Resource string `json:"resource"`
|
||||
ResourceID string `json:"resourceId"`
|
||||
Details map[string]interface{} `json:"details"`
|
||||
IPAddress string `json:"ipAddress"`
|
||||
UserAgent string `json:"userAgent"`
|
||||
Success bool `json:"success"`
|
||||
ErrorMsg string `json:"errorMsg,omitempty"`
|
||||
Duration int64 `json:"duration,omitempty"`
|
||||
SessionID string `json:"sessionId,omitempty"`
|
||||
RequestID string `json:"requestId,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// BeforeCreate is called before creating an audit log
|
||||
@@ -122,26 +122,26 @@ func (al *AuditLog) GetDetailsAsJSON() (string, error) {
|
||||
// ToAuditLogInfo converts AuditLog to AuditLogInfo (public information)
|
||||
func (al *AuditLog) ToAuditLogInfo() AuditLogInfo {
|
||||
info := AuditLogInfo{
|
||||
ID: al.ID,
|
||||
UserID: al.UserID,
|
||||
Action: al.Action,
|
||||
Resource: al.Resource,
|
||||
ResourceID: al.ResourceID,
|
||||
Details: al.GetDetails(),
|
||||
IPAddress: al.IPAddress,
|
||||
UserAgent: al.UserAgent,
|
||||
Success: al.Success,
|
||||
ErrorMsg: al.ErrorMsg,
|
||||
Duration: al.Duration,
|
||||
SessionID: al.SessionID,
|
||||
RequestID: al.RequestID,
|
||||
DateCreated: al.DateCreated.Format("2006-01-02T15:04:05Z"),
|
||||
ID: al.ID,
|
||||
UserID: al.UserID,
|
||||
Action: al.Action,
|
||||
Resource: al.Resource,
|
||||
ResourceID: al.ResourceID,
|
||||
Details: al.GetDetails(),
|
||||
IPAddress: al.IPAddress,
|
||||
UserAgent: al.UserAgent,
|
||||
Success: al.Success,
|
||||
ErrorMsg: al.ErrorMsg,
|
||||
Duration: al.Duration,
|
||||
SessionID: al.SessionID,
|
||||
RequestID: al.RequestID,
|
||||
CreatedAt: al.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
|
||||
// Include user information if available
|
||||
if al.User != nil {
|
||||
info.UserEmail = al.User.Email
|
||||
info.UserName = al.User.Name
|
||||
info.UserName = al.User.FullName
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
@@ -8,22 +8,22 @@ import (
|
||||
|
||||
// BaseModel provides common fields for all database models
|
||||
type BaseModel struct {
|
||||
ID string `json:"id" gorm:"primary_key;type:varchar(36)"`
|
||||
DateCreated time.Time `json:"dateCreated" gorm:"not null"`
|
||||
DateUpdated time.Time `json:"dateUpdated" gorm:"not null"`
|
||||
ID string `json:"id" gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"not null;default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"not null;default:now()"`
|
||||
}
|
||||
|
||||
// Init initializes base model with DateCreated, DateUpdated, and ID values
|
||||
// Init initializes base model with CreatedAt, UpdatedAt, and ID values
|
||||
func (bm *BaseModel) Init() {
|
||||
now := time.Now().UTC()
|
||||
bm.ID = uuid.NewString()
|
||||
bm.DateCreated = now
|
||||
bm.DateUpdated = now
|
||||
bm.CreatedAt = now
|
||||
bm.UpdatedAt = now
|
||||
}
|
||||
|
||||
// UpdateTimestamp updates the DateUpdated field
|
||||
// UpdateTimestamp updates the UpdatedAt field
|
||||
func (bm *BaseModel) UpdateTimestamp() {
|
||||
bm.DateUpdated = time.Now().UTC()
|
||||
bm.UpdatedAt = time.Now().UTC()
|
||||
}
|
||||
|
||||
// BeforeCreate is a GORM hook that runs before creating a record
|
||||
@@ -76,7 +76,7 @@ func DefaultParams() Params {
|
||||
return Params{
|
||||
Page: 1,
|
||||
Limit: 10,
|
||||
SortBy: "dateCreated",
|
||||
SortBy: "created_at",
|
||||
SortOrder: "desc",
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ func (p *Params) Validate() {
|
||||
p.Limit = 10
|
||||
}
|
||||
if p.SortBy == "" {
|
||||
p.SortBy = "dateCreated"
|
||||
p.SortBy = "created_at"
|
||||
}
|
||||
if p.SortOrder != "asc" && p.SortOrder != "desc" {
|
||||
p.SortOrder = "desc"
|
||||
|
||||
144
local/model/integration.go
Normal file
144
local/model/integration.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Integration represents a third-party integration configuration
|
||||
type Integration struct {
|
||||
BaseModel
|
||||
ProjectID string `json:"project_id" gorm:"not null;type:uuid;index;references:projects(id);onDelete:CASCADE"`
|
||||
Type string `json:"type" gorm:"not null;type:varchar(50)"`
|
||||
Config json.RawMessage `json:"config" gorm:"type:jsonb;not null"`
|
||||
Project Project `json:"project,omitempty" gorm:"foreignKey:ProjectID"`
|
||||
}
|
||||
|
||||
// IntegrationCreateRequest represents the request to create a new integration
|
||||
type IntegrationCreateRequest struct {
|
||||
ProjectID string `json:"project_id" validate:"required,uuid"`
|
||||
Type string `json:"type" validate:"required,min=1,max=50"`
|
||||
Config map[string]interface{} `json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
// IntegrationUpdateRequest represents the request to update an integration
|
||||
type IntegrationUpdateRequest struct {
|
||||
Config map[string]interface{} `json:"config" validate:"required"`
|
||||
}
|
||||
|
||||
// IntegrationInfo represents public integration information
|
||||
type IntegrationInfo struct {
|
||||
ID string `json:"id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
Type string `json:"type"`
|
||||
Config map[string]interface{} `json:"config"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// BeforeCreate is called before creating an integration
|
||||
func (i *Integration) BeforeCreate(tx *gorm.DB) error {
|
||||
i.BaseModel.BeforeCreate()
|
||||
|
||||
// Normalize type
|
||||
i.Type = strings.ToLower(strings.TrimSpace(i.Type))
|
||||
|
||||
return i.Validate()
|
||||
}
|
||||
|
||||
// BeforeUpdate is called before updating an integration
|
||||
func (i *Integration) BeforeUpdate(tx *gorm.DB) error {
|
||||
i.BaseModel.BeforeUpdate()
|
||||
|
||||
// Normalize type if it's being updated
|
||||
if i.Type != "" {
|
||||
i.Type = strings.ToLower(strings.TrimSpace(i.Type))
|
||||
}
|
||||
|
||||
return i.Validate()
|
||||
}
|
||||
|
||||
// Validate validates integration data
|
||||
func (i *Integration) Validate() error {
|
||||
if i.ProjectID == "" {
|
||||
return errors.New("project_id is required")
|
||||
}
|
||||
|
||||
if i.Type == "" {
|
||||
return errors.New("type is required")
|
||||
}
|
||||
|
||||
if len(i.Type) > 50 {
|
||||
return errors.New("type must not exceed 50 characters")
|
||||
}
|
||||
|
||||
if len(i.Config) == 0 {
|
||||
return errors.New("config is required")
|
||||
}
|
||||
|
||||
// Validate that config is valid JSON
|
||||
var configMap map[string]interface{}
|
||||
if err := json.Unmarshal(i.Config, &configMap); err != nil {
|
||||
return errors.New("config must be valid JSON")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToIntegrationInfo converts Integration to IntegrationInfo (public information)
|
||||
func (i *Integration) ToIntegrationInfo() IntegrationInfo {
|
||||
integrationInfo := IntegrationInfo{
|
||||
ID: i.ID,
|
||||
ProjectID: i.ProjectID,
|
||||
Type: i.Type,
|
||||
CreatedAt: i.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
UpdatedAt: i.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
|
||||
// Parse config JSON to map
|
||||
var configMap map[string]interface{}
|
||||
if err := json.Unmarshal(i.Config, &configMap); err == nil {
|
||||
integrationInfo.Config = configMap
|
||||
}
|
||||
|
||||
return integrationInfo
|
||||
}
|
||||
|
||||
// SetConfig sets the configuration from a map
|
||||
func (i *Integration) SetConfig(config map[string]interface{}) error {
|
||||
configJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.Config = configJSON
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfig gets the configuration as a map
|
||||
func (i *Integration) GetConfig() (map[string]interface{}, error) {
|
||||
var configMap map[string]interface{}
|
||||
err := json.Unmarshal(i.Config, &configMap)
|
||||
return configMap, err
|
||||
}
|
||||
|
||||
// GetConfigValue gets a specific configuration value
|
||||
func (i *Integration) GetConfigValue(key string) (interface{}, error) {
|
||||
configMap, err := i.GetConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return configMap[key], nil
|
||||
}
|
||||
|
||||
// SetConfigValue sets a specific configuration value
|
||||
func (i *Integration) SetConfigValue(key string, value interface{}) error {
|
||||
configMap, err := i.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configMap[key] = value
|
||||
return i.SetConfig(configMap)
|
||||
}
|
||||
@@ -156,10 +156,10 @@ func (f *MembershipFilter) GetSorting() (field string, desc bool) {
|
||||
|
||||
// Map common sort fields to database column names
|
||||
switch f.SortBy {
|
||||
case "dateCreated":
|
||||
field = "date_created"
|
||||
case "dateUpdated":
|
||||
field = "date_updated"
|
||||
case "created_at":
|
||||
field = "created_at"
|
||||
case "updated_at":
|
||||
field = "updated_at"
|
||||
case "username":
|
||||
field = "username"
|
||||
case "email":
|
||||
|
||||
@@ -42,7 +42,7 @@ type PermissionInfo struct {
|
||||
Active bool `json:"active"`
|
||||
System bool `json:"system"`
|
||||
RoleCount int64 `json:"roleCount"`
|
||||
DateCreated string `json:"dateCreated"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// BeforeCreate is called before creating a permission
|
||||
@@ -132,7 +132,7 @@ func (p *Permission) ToPermissionInfo() PermissionInfo {
|
||||
Category: p.Category,
|
||||
Active: p.Active,
|
||||
System: p.System,
|
||||
DateCreated: p.DateCreated.Format("2006-01-02T15:04:05Z"),
|
||||
CreatedAt: p.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
148
local/model/project.go
Normal file
148
local/model/project.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Project represents a project in the system
|
||||
type Project struct {
|
||||
BaseModel
|
||||
Name string `json:"name" gorm:"not null;type:varchar(255)"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
OwnerID string `json:"owner_id" gorm:"not null;type:uuid;index;references:users(id)"`
|
||||
TypeID string `json:"type_id" gorm:"not null;type:uuid;index;references:types(id)"`
|
||||
Owner User `json:"owner,omitempty" gorm:"foreignKey:OwnerID"`
|
||||
Type Type `json:"type,omitempty" gorm:"foreignKey:TypeID"`
|
||||
Tasks []Task `json:"tasks,omitempty" gorm:"foreignKey:ProjectID"`
|
||||
Members []User `json:"members,omitempty" gorm:"many2many:project_members;"`
|
||||
}
|
||||
|
||||
// ProjectCreateRequest represents the request to create a new project
|
||||
type ProjectCreateRequest struct {
|
||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||
Description string `json:"description" validate:"max=1000"`
|
||||
OwnerID string `json:"owner_id" validate:"required,uuid"`
|
||||
TypeID string `json:"type_id" validate:"required,uuid"`
|
||||
}
|
||||
|
||||
// ProjectUpdateRequest represents the request to update a project
|
||||
type ProjectUpdateRequest struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Description *string `json:"description,omitempty" validate:"omitempty,max=1000"`
|
||||
TypeID *string `json:"type_id,omitempty" validate:"omitempty,uuid"`
|
||||
}
|
||||
|
||||
// ProjectInfo represents public project information
|
||||
type ProjectInfo struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
OwnerID string `json:"owner_id"`
|
||||
TypeID string `json:"type_id"`
|
||||
Owner UserInfo `json:"owner,omitempty"`
|
||||
Type TypeInfo `json:"type,omitempty"`
|
||||
TaskCount int64 `json:"task_count"`
|
||||
MemberCount int64 `json:"member_count"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ProjectMember represents the many-to-many relationship between projects and users
|
||||
type ProjectMember struct {
|
||||
ProjectID string `json:"project_id" gorm:"type:uuid;primaryKey"`
|
||||
UserID string `json:"user_id" gorm:"type:uuid;primaryKey"`
|
||||
RoleID string `json:"role_id" gorm:"type:uuid;not null;references:roles(id)"`
|
||||
Project Project `json:"project,omitempty" gorm:"foreignKey:ProjectID"`
|
||||
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
|
||||
Role Role `json:"role,omitempty" gorm:"foreignKey:RoleID"`
|
||||
}
|
||||
|
||||
// BeforeCreate is called before creating a project
|
||||
func (p *Project) BeforeCreate(tx *gorm.DB) error {
|
||||
p.BaseModel.BeforeCreate()
|
||||
|
||||
// Normalize name and description
|
||||
p.Name = strings.TrimSpace(p.Name)
|
||||
p.Description = strings.TrimSpace(p.Description)
|
||||
|
||||
return p.Validate()
|
||||
}
|
||||
|
||||
// BeforeUpdate is called before updating a project
|
||||
func (p *Project) BeforeUpdate(tx *gorm.DB) error {
|
||||
p.BaseModel.BeforeUpdate()
|
||||
|
||||
// Normalize fields if they're being updated
|
||||
if p.Name != "" {
|
||||
p.Name = strings.TrimSpace(p.Name)
|
||||
}
|
||||
if p.Description != "" {
|
||||
p.Description = strings.TrimSpace(p.Description)
|
||||
}
|
||||
|
||||
return p.Validate()
|
||||
}
|
||||
|
||||
// Validate validates project data
|
||||
func (p *Project) Validate() error {
|
||||
if p.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
|
||||
if len(p.Name) > 255 {
|
||||
return errors.New("name must not exceed 255 characters")
|
||||
}
|
||||
|
||||
if p.OwnerID == "" {
|
||||
return errors.New("owner_id is required")
|
||||
}
|
||||
|
||||
if p.TypeID == "" {
|
||||
return errors.New("type_id is required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToProjectInfo converts Project to ProjectInfo (public information)
|
||||
func (p *Project) ToProjectInfo() ProjectInfo {
|
||||
projectInfo := ProjectInfo{
|
||||
ID: p.ID,
|
||||
Name: p.Name,
|
||||
Description: p.Description,
|
||||
OwnerID: p.OwnerID,
|
||||
TypeID: p.TypeID,
|
||||
CreatedAt: p.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
UpdatedAt: p.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
|
||||
// Add owner info if loaded
|
||||
if p.Owner.ID != "" {
|
||||
projectInfo.Owner = p.Owner.ToUserInfo()
|
||||
}
|
||||
|
||||
// Add type info if loaded
|
||||
if p.Type.ID != "" {
|
||||
projectInfo.Type = p.Type.ToTypeInfo()
|
||||
}
|
||||
|
||||
return projectInfo
|
||||
}
|
||||
|
||||
// HasMember checks if a user is a member of the project
|
||||
func (p *Project) HasMember(userID string) bool {
|
||||
for _, member := range p.Members {
|
||||
if member.ID == userID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsOwner checks if a user is the owner of the project
|
||||
func (p *Project) IsOwner(userID string) bool {
|
||||
return p.OwnerID == userID
|
||||
}
|
||||
@@ -42,7 +42,7 @@ type RoleInfo struct {
|
||||
System bool `json:"system"`
|
||||
Permissions []PermissionInfo `json:"permissions"`
|
||||
UserCount int64 `json:"userCount"`
|
||||
DateCreated string `json:"dateCreated"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// BeforeCreate is called before creating a role
|
||||
@@ -120,7 +120,7 @@ func (r *Role) ToRoleInfo() RoleInfo {
|
||||
Active: r.Active,
|
||||
System: r.System,
|
||||
Permissions: make([]PermissionInfo, len(r.Permissions)),
|
||||
DateCreated: r.DateCreated.Format("2006-01-02T15:04:05Z"),
|
||||
CreatedAt: r.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
|
||||
// Convert permissions
|
||||
|
||||
@@ -86,7 +86,7 @@ type SecurityEventInfo struct {
|
||||
ResolverName string `json:"resolverName,omitempty"`
|
||||
ResolvedAt *time.Time `json:"resolvedAt,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
DateCreated string `json:"dateCreated"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// BeforeCreate is called before creating a security event
|
||||
@@ -202,19 +202,19 @@ func (se *SecurityEvent) ToSecurityEventInfo() SecurityEventInfo {
|
||||
ResolvedBy: se.ResolvedBy,
|
||||
ResolvedAt: se.ResolvedAt,
|
||||
Notes: se.Notes,
|
||||
DateCreated: se.DateCreated.Format("2006-01-02T15:04:05Z"),
|
||||
CreatedAt: se.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
|
||||
// Include user information if available
|
||||
if se.User != nil {
|
||||
info.UserEmail = se.User.Email
|
||||
info.UserName = se.User.Name
|
||||
info.UserName = se.User.FullName
|
||||
}
|
||||
|
||||
// Include resolver information if available
|
||||
if se.Resolver != nil {
|
||||
info.ResolverEmail = se.Resolver.Email
|
||||
info.ResolverName = se.Resolver.Name
|
||||
info.ResolverName = se.Resolver.FullName
|
||||
}
|
||||
|
||||
return info
|
||||
|
||||
@@ -56,7 +56,7 @@ type SystemConfigInfo struct {
|
||||
DataType string `json:"dataType"`
|
||||
IsEditable bool `json:"isEditable"`
|
||||
IsSecret bool `json:"isSecret"`
|
||||
DateCreated string `json:"dateCreated"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
DateModified string `json:"dateModified"`
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ func (sc *SystemConfig) ToSystemConfigInfo() SystemConfigInfo {
|
||||
DataType: sc.DataType,
|
||||
IsEditable: sc.IsEditable,
|
||||
IsSecret: sc.IsSecret,
|
||||
DateCreated: sc.DateCreated.Format("2006-01-02T15:04:05Z"),
|
||||
CreatedAt: sc.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
DateModified: sc.DateModified,
|
||||
}
|
||||
|
||||
|
||||
213
local/model/task.go
Normal file
213
local/model/task.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TaskStatus represents the status of a task
|
||||
type TaskStatus string
|
||||
|
||||
const (
|
||||
TaskStatusTodo TaskStatus = "todo"
|
||||
TaskStatusInProgress TaskStatus = "in_progress"
|
||||
TaskStatusDone TaskStatus = "done"
|
||||
TaskStatusCanceled TaskStatus = "canceled"
|
||||
)
|
||||
|
||||
// TaskPriority represents the priority of a task
|
||||
type TaskPriority string
|
||||
|
||||
const (
|
||||
TaskPriorityLow TaskPriority = "low"
|
||||
TaskPriorityMedium TaskPriority = "medium"
|
||||
TaskPriorityHigh TaskPriority = "high"
|
||||
)
|
||||
|
||||
// Task represents a task in the system
|
||||
type Task struct {
|
||||
BaseModel
|
||||
Title string `json:"title" gorm:"not null;type:varchar(255)"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
Status TaskStatus `json:"status" gorm:"not null;default:'todo';type:varchar(20)"`
|
||||
Priority TaskPriority `json:"priority" gorm:"not null;default:'medium';type:varchar(20)"`
|
||||
ProjectID string `json:"project_id" gorm:"not null;type:uuid;index;references:projects(id);onDelete:CASCADE"`
|
||||
DueDate *string `json:"due_date" gorm:"type:date"`
|
||||
Project Project `json:"project,omitempty" gorm:"foreignKey:ProjectID"`
|
||||
Assignees []User `json:"assignees,omitempty" gorm:"many2many:task_assignees;"`
|
||||
}
|
||||
|
||||
// TaskCreateRequest represents the request to create a new task
|
||||
type TaskCreateRequest struct {
|
||||
Title string `json:"title" validate:"required,min=1,max=255"`
|
||||
Description string `json:"description" validate:"max=1000"`
|
||||
Status TaskStatus `json:"status" validate:"omitempty,oneof=todo in_progress done canceled"`
|
||||
Priority TaskPriority `json:"priority" validate:"omitempty,oneof=low medium high"`
|
||||
ProjectID string `json:"project_id" validate:"required,uuid"`
|
||||
DueDate *string `json:"due_date"`
|
||||
AssigneeIDs []string `json:"assignee_ids"`
|
||||
}
|
||||
|
||||
// TaskUpdateRequest represents the request to update a task
|
||||
type TaskUpdateRequest struct {
|
||||
Title *string `json:"title,omitempty" validate:"omitempty,min=1,max=255"`
|
||||
Description *string `json:"description,omitempty" validate:"omitempty,max=1000"`
|
||||
Status *TaskStatus `json:"status,omitempty" validate:"omitempty,oneof=todo in_progress done canceled"`
|
||||
Priority *TaskPriority `json:"priority,omitempty" validate:"omitempty,oneof=low medium high"`
|
||||
DueDate *string `json:"due_date,omitempty"`
|
||||
AssigneeIDs []string `json:"assignee_ids,omitempty"`
|
||||
}
|
||||
|
||||
// TaskInfo represents public task information
|
||||
type TaskInfo struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Status TaskStatus `json:"status"`
|
||||
Priority TaskPriority `json:"priority"`
|
||||
ProjectID string `json:"project_id"`
|
||||
DueDate *string `json:"due_date"`
|
||||
Project ProjectInfo `json:"project,omitempty"`
|
||||
Assignees []UserInfo `json:"assignees,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TaskAssignee represents the many-to-many relationship between tasks and users
|
||||
type TaskAssignee struct {
|
||||
TaskID string `json:"task_id" gorm:"type:uuid;primaryKey"`
|
||||
UserID string `json:"user_id" gorm:"type:uuid;primaryKey"`
|
||||
Task Task `json:"task,omitempty" gorm:"foreignKey:TaskID"`
|
||||
User User `json:"user,omitempty" gorm:"foreignKey:UserID"`
|
||||
}
|
||||
|
||||
// BeforeCreate is called before creating a task
|
||||
func (t *Task) BeforeCreate(tx *gorm.DB) error {
|
||||
t.BaseModel.BeforeCreate()
|
||||
|
||||
// Normalize title and description
|
||||
t.Title = strings.TrimSpace(t.Title)
|
||||
t.Description = strings.TrimSpace(t.Description)
|
||||
|
||||
// Set default values
|
||||
if t.Status == "" {
|
||||
t.Status = TaskStatusTodo
|
||||
}
|
||||
if t.Priority == "" {
|
||||
t.Priority = TaskPriorityMedium
|
||||
}
|
||||
|
||||
return t.Validate()
|
||||
}
|
||||
|
||||
// BeforeUpdate is called before updating a task
|
||||
func (t *Task) BeforeUpdate(tx *gorm.DB) error {
|
||||
t.BaseModel.BeforeUpdate()
|
||||
|
||||
// Normalize fields if they're being updated
|
||||
if t.Title != "" {
|
||||
t.Title = strings.TrimSpace(t.Title)
|
||||
}
|
||||
if t.Description != "" {
|
||||
t.Description = strings.TrimSpace(t.Description)
|
||||
}
|
||||
|
||||
return t.Validate()
|
||||
}
|
||||
|
||||
// Validate validates task data
|
||||
func (t *Task) Validate() error {
|
||||
if t.Title == "" {
|
||||
return errors.New("title is required")
|
||||
}
|
||||
|
||||
if len(t.Title) > 255 {
|
||||
return errors.New("title must not exceed 255 characters")
|
||||
}
|
||||
|
||||
if t.ProjectID == "" {
|
||||
return errors.New("project_id is required")
|
||||
}
|
||||
|
||||
// Validate status
|
||||
if t.Status != "" {
|
||||
switch t.Status {
|
||||
case TaskStatusTodo, TaskStatusInProgress, TaskStatusDone, TaskStatusCanceled:
|
||||
// Valid status
|
||||
default:
|
||||
return errors.New("invalid status")
|
||||
}
|
||||
}
|
||||
|
||||
// Validate priority
|
||||
if t.Priority != "" {
|
||||
switch t.Priority {
|
||||
case TaskPriorityLow, TaskPriorityMedium, TaskPriorityHigh:
|
||||
// Valid priority
|
||||
default:
|
||||
return errors.New("invalid priority")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToTaskInfo converts Task to TaskInfo (public information)
|
||||
func (t *Task) ToTaskInfo() TaskInfo {
|
||||
taskInfo := TaskInfo{
|
||||
ID: t.ID,
|
||||
Title: t.Title,
|
||||
Description: t.Description,
|
||||
Status: t.Status,
|
||||
Priority: t.Priority,
|
||||
ProjectID: t.ProjectID,
|
||||
DueDate: t.DueDate,
|
||||
CreatedAt: t.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
UpdatedAt: t.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
Assignees: make([]UserInfo, len(t.Assignees)),
|
||||
}
|
||||
|
||||
// Add project info if loaded
|
||||
if t.Project.ID != "" {
|
||||
taskInfo.Project = t.Project.ToProjectInfo()
|
||||
}
|
||||
|
||||
// Add assignee info if loaded
|
||||
for i, assignee := range t.Assignees {
|
||||
taskInfo.Assignees[i] = assignee.ToUserInfo()
|
||||
}
|
||||
|
||||
return taskInfo
|
||||
}
|
||||
|
||||
// IsAssignedTo checks if a user is assigned to this task
|
||||
func (t *Task) IsAssignedTo(userID string) bool {
|
||||
for _, assignee := range t.Assignees {
|
||||
if assignee.ID == userID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCompleted checks if the task is completed
|
||||
func (t *Task) IsCompleted() bool {
|
||||
return t.Status == TaskStatusDone
|
||||
}
|
||||
|
||||
// IsCanceled checks if the task is canceled
|
||||
func (t *Task) IsCanceled() bool {
|
||||
return t.Status == TaskStatusCanceled
|
||||
}
|
||||
|
||||
// IsInProgress checks if the task is in progress
|
||||
func (t *Task) IsInProgress() bool {
|
||||
return t.Status == TaskStatusInProgress
|
||||
}
|
||||
|
||||
// IsTodo checks if the task is todo
|
||||
func (t *Task) IsTodo() bool {
|
||||
return t.Status == TaskStatusTodo
|
||||
}
|
||||
95
local/model/type.go
Normal file
95
local/model/type.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Type represents a project type in the system
|
||||
type Type struct {
|
||||
BaseModel
|
||||
UserID *string `json:"user_id" gorm:"type:uuid;index;references:users(id);onDelete:SET NULL"`
|
||||
Name string `json:"name" gorm:"not null;type:varchar(100)"`
|
||||
Description string `json:"description" gorm:"type:text"`
|
||||
User *User `json:"user,omitempty" gorm:"foreignKey:UserID"`
|
||||
}
|
||||
|
||||
// TypeCreateRequest represents the request to create a new type
|
||||
type TypeCreateRequest struct {
|
||||
UserID *string `json:"user_id"`
|
||||
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||
Description string `json:"description" validate:"max=1000"`
|
||||
}
|
||||
|
||||
// TypeUpdateRequest represents the request to update a type
|
||||
type TypeUpdateRequest struct {
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=1,max=100"`
|
||||
Description *string `json:"description,omitempty" validate:"omitempty,max=1000"`
|
||||
}
|
||||
|
||||
// TypeInfo represents public type information
|
||||
type TypeInfo struct {
|
||||
ID string `json:"id"`
|
||||
UserID *string `json:"user_id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// BeforeCreate is called before creating a type
|
||||
func (t *Type) BeforeCreate(tx *gorm.DB) error {
|
||||
t.BaseModel.BeforeCreate()
|
||||
|
||||
// Normalize name
|
||||
t.Name = strings.TrimSpace(t.Name)
|
||||
t.Description = strings.TrimSpace(t.Description)
|
||||
|
||||
return t.Validate()
|
||||
}
|
||||
|
||||
// BeforeUpdate is called before updating a type
|
||||
func (t *Type) BeforeUpdate(tx *gorm.DB) error {
|
||||
t.BaseModel.BeforeUpdate()
|
||||
|
||||
// Normalize fields if they're being updated
|
||||
if t.Name != "" {
|
||||
t.Name = strings.TrimSpace(t.Name)
|
||||
}
|
||||
if t.Description != "" {
|
||||
t.Description = strings.TrimSpace(t.Description)
|
||||
}
|
||||
|
||||
return t.Validate()
|
||||
}
|
||||
|
||||
// Validate validates type data
|
||||
func (t *Type) Validate() error {
|
||||
if t.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
|
||||
if len(t.Name) > 100 {
|
||||
return errors.New("name must not exceed 100 characters")
|
||||
}
|
||||
|
||||
if len(t.Description) > 1000 {
|
||||
return errors.New("description must not exceed 1000 characters")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToTypeInfo converts Type to TypeInfo (public information)
|
||||
func (t *Type) ToTypeInfo() TypeInfo {
|
||||
return TypeInfo{
|
||||
ID: t.ID,
|
||||
UserID: t.UserID,
|
||||
Name: t.Name,
|
||||
Description: t.Description,
|
||||
CreatedAt: t.CreatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
UpdatedAt: t.UpdatedAt.Format("2006-01-02T15:04:05Z07:00"),
|
||||
}
|
||||
}
|
||||
@@ -13,42 +13,98 @@ import (
|
||||
// User represents a user in the system
|
||||
type User struct {
|
||||
BaseModel
|
||||
Email string `json:"email" gorm:"unique;not null;type:varchar(255)"`
|
||||
Username string `json:"username" gorm:"unique;not null;type:varchar(100)"`
|
||||
Name string `json:"name" gorm:"not null;type:varchar(255)"`
|
||||
PasswordHash string `json:"-" gorm:"not null;type:text"`
|
||||
Active bool `json:"active" gorm:"default:true"`
|
||||
EmailVerified bool `json:"emailVerified" gorm:"default:false"`
|
||||
EmailVerificationToken string `json:"-" gorm:"type:varchar(255)"`
|
||||
PasswordResetToken string `json:"-" gorm:"type:varchar(255)"`
|
||||
PasswordResetExpires *time.Time `json:"-"`
|
||||
LastLogin *time.Time `json:"lastLogin"`
|
||||
LoginAttempts int `json:"-" gorm:"default:0"`
|
||||
LockedUntil *time.Time `json:"-"`
|
||||
TwoFactorEnabled bool `json:"twoFactorEnabled" gorm:"default:false"`
|
||||
TwoFactorSecret string `json:"-" gorm:"type:varchar(255)"`
|
||||
Roles []Role `json:"roles" gorm:"many2many:user_roles;"`
|
||||
AuditLogs []AuditLog `json:"-" gorm:"foreignKey:UserID"`
|
||||
Email string `json:"email" gorm:"unique;not null;type:varchar(255)"`
|
||||
PasswordHash string `json:"-" gorm:"not null;type:varchar(255)"`
|
||||
FullName string `json:"full_name" gorm:"type:varchar(255)"`
|
||||
Roles []Role `json:"roles" gorm:"many2many:user_roles;"`
|
||||
}
|
||||
|
||||
// UserCreateRequest represents the request to create a new user
|
||||
type UserCreateRequest struct {
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Username string `json:"username" validate:"required,min=3,max=50"`
|
||||
Name string `json:"name" validate:"required,min=2,max=100"`
|
||||
FullName string `json:"full_name" validate:"required,min=2,max=100"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
RoleIDs []string `json:"roleIds"`
|
||||
}
|
||||
|
||||
// ToUser converts UserCreateRequest to User domain model
|
||||
func (req *UserCreateRequest) ToUser() (*User, error) {
|
||||
user := &User{
|
||||
Email: req.Email,
|
||||
FullName: req.FullName,
|
||||
}
|
||||
|
||||
// Handle password hashing
|
||||
if err := user.SetPassword(req.Password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Note: Roles will be set by the service layer after validation
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Validate validates the UserCreateRequest
|
||||
func (req *UserCreateRequest) Validate() error {
|
||||
if req.Email == "" {
|
||||
return errors.New("email is required")
|
||||
}
|
||||
if !isValidEmail(req.Email) {
|
||||
return errors.New("invalid email format")
|
||||
}
|
||||
if len(req.Password) < 8 {
|
||||
return errors.New("password must be at least 8 characters")
|
||||
}
|
||||
if req.FullName == "" {
|
||||
return errors.New("full name is required")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserUpdateRequest represents the request to update a user
|
||||
type UserUpdateRequest struct {
|
||||
Email *string `json:"email,omitempty" validate:"omitempty,email"`
|
||||
Username *string `json:"username,omitempty" validate:"omitempty,min=3,max=50"`
|
||||
Name *string `json:"name,omitempty" validate:"omitempty,min=2,max=100"`
|
||||
Active *bool `json:"active,omitempty"`
|
||||
FullName *string `json:"full_name,omitempty" validate:"omitempty,min=2,max=100"`
|
||||
RoleIDs []string `json:"roleIds,omitempty"`
|
||||
}
|
||||
|
||||
// ApplyToUser applies the UserUpdateRequest to an existing User
|
||||
func (req *UserUpdateRequest) ApplyToUser(user *User) error {
|
||||
if req.Email != nil {
|
||||
user.Email = *req.Email
|
||||
}
|
||||
|
||||
if req.FullName != nil {
|
||||
user.FullName = *req.FullName
|
||||
}
|
||||
|
||||
// Note: Roles will be handled by the service layer
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate validates the UserUpdateRequest
|
||||
func (req *UserUpdateRequest) Validate() error {
|
||||
if req.Email != nil && !isValidEmail(*req.Email) {
|
||||
return errors.New("invalid email format")
|
||||
}
|
||||
if req.FullName != nil && len(*req.FullName) == 0 {
|
||||
return errors.New("full name cannot be empty")
|
||||
}
|
||||
if req.FullName != nil && len(*req.FullName) > 255 {
|
||||
return errors.New("full name must not exceed 255 characters")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserResponse represents the response when returning user data
|
||||
type UserResponse struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
FullName string `json:"fullName"`
|
||||
Roles []string `json:"roles"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
// UserLoginRequest represents a login request
|
||||
type UserLoginRequest struct {
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
@@ -65,16 +121,13 @@ type UserLoginResponse struct {
|
||||
|
||||
// UserInfo represents public user information
|
||||
type UserInfo struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Username string `json:"username"`
|
||||
Name string `json:"name"`
|
||||
Active bool `json:"active"`
|
||||
EmailVerified bool `json:"emailVerified"`
|
||||
LastLogin *time.Time `json:"lastLogin"`
|
||||
Roles []RoleInfo `json:"roles"`
|
||||
Permissions []string `json:"permissions"`
|
||||
DateCreated time.Time `json:"dateCreated"`
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
FullName string `json:"full_name"`
|
||||
Roles []RoleInfo `json:"roles"`
|
||||
Permissions []string `json:"permissions"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ChangePasswordRequest represents a password change request
|
||||
@@ -98,10 +151,9 @@ type ResetPasswordConfirmRequest struct {
|
||||
func (u *User) BeforeCreate(tx *gorm.DB) error {
|
||||
u.BaseModel.BeforeCreate()
|
||||
|
||||
// Normalize email and username
|
||||
// Normalize email and full name
|
||||
u.Email = strings.ToLower(strings.TrimSpace(u.Email))
|
||||
u.Username = strings.ToLower(strings.TrimSpace(u.Username))
|
||||
u.Name = strings.TrimSpace(u.Name)
|
||||
u.FullName = strings.TrimSpace(u.FullName)
|
||||
|
||||
return u.Validate()
|
||||
}
|
||||
@@ -114,11 +166,8 @@ func (u *User) BeforeUpdate(tx *gorm.DB) error {
|
||||
if u.Email != "" {
|
||||
u.Email = strings.ToLower(strings.TrimSpace(u.Email))
|
||||
}
|
||||
if u.Username != "" {
|
||||
u.Username = strings.ToLower(strings.TrimSpace(u.Username))
|
||||
}
|
||||
if u.Name != "" {
|
||||
u.Name = strings.TrimSpace(u.Name)
|
||||
if u.FullName != "" {
|
||||
u.FullName = strings.TrimSpace(u.FullName)
|
||||
}
|
||||
|
||||
return u.Validate()
|
||||
@@ -134,24 +183,8 @@ func (u *User) Validate() error {
|
||||
return errors.New("invalid email format")
|
||||
}
|
||||
|
||||
if u.Username == "" {
|
||||
return errors.New("username is required")
|
||||
}
|
||||
|
||||
if len(u.Username) < 3 || len(u.Username) > 50 {
|
||||
return errors.New("username must be between 3 and 50 characters")
|
||||
}
|
||||
|
||||
if !isValidUsername(u.Username) {
|
||||
return errors.New("username can only contain letters, numbers, underscores, and hyphens")
|
||||
}
|
||||
|
||||
if u.Name == "" {
|
||||
return errors.New("name is required")
|
||||
}
|
||||
|
||||
if len(u.Name) < 2 || len(u.Name) > 100 {
|
||||
return errors.New("name must be between 2 and 100 characters")
|
||||
if u.FullName != "" && len(u.FullName) > 255 {
|
||||
return errors.New("full name must not exceed 255 characters")
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -182,55 +215,16 @@ func (u *User) VerifyPassword(plainPassword string) bool {
|
||||
return u.CheckPassword(plainPassword)
|
||||
}
|
||||
|
||||
// IsLocked checks if the user account is locked
|
||||
func (u *User) IsLocked() bool {
|
||||
if u.LockedUntil == nil {
|
||||
return false
|
||||
}
|
||||
return time.Now().Before(*u.LockedUntil)
|
||||
}
|
||||
|
||||
// Lock locks the user account for the specified duration
|
||||
func (u *User) Lock(duration time.Duration) {
|
||||
lockUntil := time.Now().Add(duration)
|
||||
u.LockedUntil = &lockUntil
|
||||
}
|
||||
|
||||
// Unlock unlocks the user account
|
||||
func (u *User) Unlock() {
|
||||
u.LockedUntil = nil
|
||||
u.LoginAttempts = 0
|
||||
}
|
||||
|
||||
// IncrementLoginAttempts increments the login attempt counter
|
||||
func (u *User) IncrementLoginAttempts() {
|
||||
u.LoginAttempts++
|
||||
}
|
||||
|
||||
// ResetLoginAttempts resets the login attempt counter
|
||||
func (u *User) ResetLoginAttempts() {
|
||||
u.LoginAttempts = 0
|
||||
}
|
||||
|
||||
// UpdateLastLogin updates the last login timestamp
|
||||
func (u *User) UpdateLastLogin() {
|
||||
now := time.Now()
|
||||
u.LastLogin = &now
|
||||
}
|
||||
|
||||
// ToUserInfo converts User to UserInfo (public information)
|
||||
func (u *User) ToUserInfo() UserInfo {
|
||||
userInfo := UserInfo{
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
Username: u.Username,
|
||||
Name: u.Name,
|
||||
Active: u.Active,
|
||||
EmailVerified: u.EmailVerified,
|
||||
LastLogin: u.LastLogin,
|
||||
DateCreated: u.DateCreated,
|
||||
Roles: make([]RoleInfo, len(u.Roles)),
|
||||
Permissions: []string{},
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
FullName: u.FullName,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
Roles: make([]RoleInfo, len(u.Roles)),
|
||||
Permissions: []string{},
|
||||
}
|
||||
|
||||
// Convert roles and collect permissions
|
||||
@@ -250,6 +244,23 @@ func (u *User) ToUserInfo() UserInfo {
|
||||
return userInfo
|
||||
}
|
||||
|
||||
// ToResponse converts User to UserResponse (for API responses)
|
||||
func (u *User) ToResponse() *UserResponse {
|
||||
roleNames := make([]string, len(u.Roles))
|
||||
for i, role := range u.Roles {
|
||||
roleNames[i] = role.Name
|
||||
}
|
||||
|
||||
return &UserResponse{
|
||||
ID: u.ID,
|
||||
Email: u.Email,
|
||||
FullName: u.FullName,
|
||||
Roles: roleNames,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// HasRole checks if the user has a specific role
|
||||
func (u *User) HasRole(roleName string) bool {
|
||||
for _, role := range u.Roles {
|
||||
@@ -278,12 +289,6 @@ func isValidEmail(email string) bool {
|
||||
return emailRegex.MatchString(email)
|
||||
}
|
||||
|
||||
// isValidUsername validates username format
|
||||
func isValidUsername(username string) bool {
|
||||
usernameRegex := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
|
||||
return usernameRegex.MatchString(username)
|
||||
}
|
||||
|
||||
// validatePassword validates password strength
|
||||
func validatePassword(password string) error {
|
||||
if len(password) < 8 {
|
||||
@@ -294,25 +299,5 @@ func validatePassword(password string) error {
|
||||
return errors.New("password must not exceed 128 characters")
|
||||
}
|
||||
|
||||
// Check for at least one lowercase letter
|
||||
if matched, _ := regexp.MatchString(`[a-z]`, password); !matched {
|
||||
return errors.New("password must contain at least one lowercase letter")
|
||||
}
|
||||
|
||||
// Check for at least one uppercase letter
|
||||
if matched, _ := regexp.MatchString(`[A-Z]`, password); !matched {
|
||||
return errors.New("password must contain at least one uppercase letter")
|
||||
}
|
||||
|
||||
// Check for at least one digit
|
||||
if matched, _ := regexp.MatchString(`\d`, password); !matched {
|
||||
return errors.New("password must contain at least one digit")
|
||||
}
|
||||
|
||||
// Check for at least one special character
|
||||
if matched, _ := regexp.MatchString(`[!@#$%^&*(),.?":{}|<>]`, password); !matched {
|
||||
return errors.New("password must contain at least one special character")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user