Files
herolib/lib/hero/heromodels/project_issue.v
2025-09-14 15:08:20 +02:00

260 lines
7.1 KiB
V

module heromodels
import freeflowuniverse.herolib.data.encoder
import freeflowuniverse.herolib.data.ourtime
import freeflowuniverse.herolib.hero.db
// ProjectIssue represents a task, story, bug, or question in a project
@[heap]
pub struct ProjectIssue {
db.Base
pub mut:
title string
project_id u32 // Associated project
issue_type IssueType
priority IssuePriority
status IssueStatus
swimlane string // Current swimlane, is string corresponds to name, need to be to_lower and trim_space
assignees []u32 // User IDs
reporter u32 // User ID who created the issue
milestone string // Associated milestone, is string corresponds to name, need to be to_lower and trim_space
deadline i64 // Unix timestamp
estimate int // Story points or hours
fs_files []u32 // IDs of linked files
parent_id u32 // Parent issue ID (for sub-tasks)
children []u32 // Child issue IDs
}
pub enum IssueType {
task
story
bug
question
epic
subtask
}
pub enum IssuePriority {
lowest
low
medium
high
highest
critical
}
pub enum IssueStatus {
open
in_progress
blocked
review
testing
done
closed
}
pub struct DBProjectIssue {
pub mut:
db &db.DB @[skip; str: skip]
}
pub fn (self ProjectIssue) type_name() string {
return 'project_issue'
}
// return example rpc call and result for each methodname
pub fn (self ProjectIssue) description(methodname string) string {
match methodname {
'set' {
return 'Create or update a project issue. Returns the ID of the issue.'
}
'get' {
return 'Retrieve a project issue by ID. Returns the issue object.'
}
'delete' {
return 'Delete a project issue by ID. Returns true if successful.'
}
'exist' {
return 'Check if a project issue exists by ID. Returns true or false.'
}
'list' {
return 'List all project issues. Returns an array of issue objects.'
}
else {
return 'This is generic method for the root object, TODO fill in, ...'
}
}
}
// return example rpc call and result for each methodname
pub fn (self ProjectIssue) example(methodname string) (string, string) {
match methodname {
'set' {
return '{"project_issue": {"title": "Implement new feature", "project_id": 1, "issue_type": "story", "priority": "high", "status": "open", "swimlane": "backlog", "assignees": [1], "reporter": 1, "milestone": "sprint 1", "deadline": "2025-03-01T00:00:00Z", "estimate": 8, "fs_files": [], "parent_id": 0, "children": []}}', '1'
}
'get' {
return '{"id": 1}', '{"title": "Implement new feature", "project_id": 1, "issue_type": "story", "priority": "high", "status": "open", "swimlane": "backlog", "assignees": [1], "reporter": 1, "milestone": "sprint 1", "deadline": "2025-03-01T00:00:00Z", "estimate": 8, "fs_files": [], "parent_id": 0, "children": []}'
}
'delete' {
return '{"id": 1}', 'true'
}
'exist' {
return '{"id": 1}', 'true'
}
'list' {
return '{}', '[{"title": "Implement new feature", "project_id": 1, "issue_type": "story", "priority": "high", "status": "open", "swimlane": "backlog", "assignees": [1], "reporter": 1, "milestone": "sprint 1", "deadline": "2025-03-01T00:00:00Z", "estimate": 8, "fs_files": [], "parent_id": 0, "children": []}]'
}
else {
return '{}', '{}'
}
}
}
pub fn (self ProjectIssue) dump(mut e encoder.Encoder) ! {
e.add_string(self.title)
e.add_u32(self.project_id)
e.add_u8(u8(self.issue_type))
e.add_u8(u8(self.priority))
e.add_u8(u8(self.status))
e.add_string(self.swimlane)
e.add_list_u32(self.assignees)
e.add_u32(self.reporter)
e.add_string(self.milestone)
e.add_i64(self.deadline)
e.add_int(self.estimate)
e.add_list_u32(self.fs_files)
e.add_u32(self.parent_id)
e.add_list_u32(self.children)
}
fn (mut self DBProjectIssue) load(mut o ProjectIssue, mut e encoder.Decoder) ! {
o.title = e.get_string()!
o.project_id = e.get_u32()!
o.issue_type = unsafe { IssueType(e.get_u8()!) }
o.priority = unsafe { IssuePriority(e.get_u8()!) }
o.status = unsafe { IssueStatus(e.get_u8()!) }
o.swimlane = e.get_string()!
o.assignees = e.get_list_u32()!
o.reporter = e.get_u32()!
o.milestone = e.get_string()!
o.deadline = e.get_i64()!
o.estimate = e.get_int()!
o.fs_files = e.get_list_u32()!
o.parent_id = e.get_u32()!
o.children = e.get_list_u32()!
}
@[params]
pub struct ProjectIssueArg {
pub mut:
name string
description string
title string
project_id u32
issue_type IssueType
priority IssuePriority
status IssueStatus
swimlane string
assignees []u32
reporter u32
milestone string
deadline string // Use ourtime module to convert to epoch
estimate int
fs_files []u32
parent_id u32
children []u32
securitypolicy u32
tags []string
comments []db.CommentArg
}
// get new project issue, not from the DB
pub fn (mut self DBProjectIssue) new(args ProjectIssueArg) !ProjectIssue {
mut o := ProjectIssue{
title: args.title
project_id: args.project_id
issue_type: args.issue_type
priority: args.priority
status: args.status
swimlane: args.swimlane.to_lower().trim_space()
assignees: args.assignees
reporter: args.reporter
milestone: args.milestone.to_lower().trim_space()
estimate: args.estimate
fs_files: args.fs_files
parent_id: args.parent_id
children: args.children
}
// Validate that project_id exists
mut db_project := DBProject{
db: self.db
}
if !db_project.exist(args.project_id)! {
return error('Project with ID ${args.project_id} does not exist')
}
// Get the project to validate swimlane and milestone
project_obj := db_project.get(args.project_id)!
// Validate swimlane exists in the project
mut swimlane_exists := false
for swimlane in project_obj.swimlanes {
if swimlane.name == o.swimlane {
swimlane_exists = true
break
}
}
if !swimlane_exists {
return error('Swimlane "${args.swimlane}" does not exist in project "${project_obj.name}"')
}
// Validate milestone exists in the project
mut milestone_exists := false
for milestone in project_obj.milestones {
if milestone.name == o.milestone {
milestone_exists = true
break
}
}
if !milestone_exists {
return error('Milestone "${args.milestone}" does not exist in project "${project_obj.name}"')
}
// Set base fields
o.name = args.name
o.description = args.description
o.securitypolicy = args.securitypolicy
o.tags = self.db.tags_get(args.tags)!
o.comments = self.db.comments_get(args.comments)!
o.updated_at = ourtime.now().unix()
// Convert deadline string to Unix timestamp
mut deadline_obj := ourtime.new(args.deadline)!
o.deadline = deadline_obj.unix()
return o
}
pub fn (mut self DBProjectIssue) set(o ProjectIssue) !u32 {
return self.db.set[ProjectIssue](o)!
}
pub fn (mut self DBProjectIssue) delete(id u32) ! {
self.db.delete[ProjectIssue](id)!
}
pub fn (mut self DBProjectIssue) exist(id u32) !bool {
return self.db.exists[ProjectIssue](id)!
}
pub fn (mut self DBProjectIssue) get(id u32) !ProjectIssue {
mut o, data := self.db.get_data[ProjectIssue](id)!
mut e_decoder := encoder.decoder_new(data)
self.load(mut o, mut e_decoder)!
return o
}
pub fn (mut self DBProjectIssue) list() ![]ProjectIssue {
return self.db.list[ProjectIssue]()!.map(self.get(it)!)
}