Loggest thine Stuff
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

638 lines
15 KiB

package mysql
import (
"context"
"database/sql"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/internal/genutils"
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql/mysqlcore"
"github.com/Masterminds/squirrel"
)
type projectRepository struct {
db *sql.DB
q *mysqlcore.Queries
}
func (r *projectRepository) Find(ctx context.Context, scopeID, projectID int) (*entities.Project, error) {
row, err := r.q.GetProject(ctx, mysqlcore.GetProjectParams{ID: projectID, ScopeID: scopeID})
if err != nil {
if err == sql.ErrNoRows {
return nil, models.NotFoundError("Project")
}
return nil, err
}
tags, err := r.q.ListTagsByObject(ctx, mysqlcore.ListTagsByObjectParams{
ObjectKind: tagObjectKindProject,
ObjectID: row.ID,
})
return &entities.Project{
ID: row.ID,
ScopeID: row.ScopeID,
OwnerID: row.OwnerID,
CreatedTime: row.CreatedTime,
Name: row.Name,
Description: row.Description,
Status: models.Status(row.Status),
Tags: tags,
}, nil
}
func (r *projectRepository) FetchProjects(ctx context.Context, scopeID int, ids ...int) ([]entities.Project, error) {
if len(ids) == 0 {
return []entities.Project{}, nil
} else if len(ids) == 1 && scopeID != -1 {
project, err := r.Find(ctx, scopeID, ids[0])
if err != nil {
return nil, err
}
return []entities.Project{*project}, nil
}
sq := squirrel.Select("id,scope_id,owner_id,name,status,description,created_time").
From("project").
Where(squirrel.Eq{"id": ids})
if scopeID != -1 {
sq = sq.Where(squirrel.Eq{"scope_id": scopeID})
}
query, args, err := sq.ToSql()
if err != nil {
return nil, err
}
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
projects := make([]entities.Project, 0, len(ids))
for rows.Next() {
project := entities.Project{}
if err := rows.Scan(
&project.ID,
&project.ScopeID,
&project.OwnerID,
&project.Name,
&project.Status,
&project.Description,
&project.CreatedTime,
); err != nil {
return nil, err
}
project.Tags = []string{}
projects = append(projects, project)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
// Fill tags
err = fetchTags(ctx, r.db, tagObjectKindProject, ids, func(id int, tag string) {
for i := range projects {
if projects[i].ID == id {
projects[i].Tags = append(projects[i].Tags, tag)
}
}
})
if err != nil {
return nil, err
}
return projects, nil
}
func (r *projectRepository) List(ctx context.Context, scopeID int) ([]entities.Project, error) {
rows, err := r.q.ListProjects(ctx, scopeID)
if err != nil {
if err == sql.ErrNoRows {
return []entities.Project{}, nil
}
return nil, err
}
res := make([]entities.Project, 0, len(rows))
ids := make([]int, 0, len(rows))
for _, row := range rows {
res = append(res, entities.Project{
ID: row.ID,
ScopeID: row.ScopeID,
OwnerID: row.OwnerID,
CreatedTime: row.CreatedTime,
Name: row.Name,
Description: row.Description,
Status: models.Status(row.Status),
Tags: []string{},
})
ids = append(ids, row.ID)
}
// Fill tags
err = fetchTags(ctx, r.db, tagObjectKindProject, ids, func(id int, tag string) {
for i := range res {
if id == res[i].ID {
res[i].Tags = append(res[i].Tags, tag)
}
}
})
if err != nil {
return nil, err
}
return res, nil
}
func (r *projectRepository) ListByTags(ctx context.Context, scopeID int, tags []string) ([]entities.Project, error) {
query, args, err := squirrel.Select("object_id, tag_name").
From("tag").
Where(squirrel.Eq{"tag_name": tags, "object_kind": tagObjectKindProject}).
ToSql()
if err != nil {
return nil, err
}
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
ids := make([]int, 0, 16)
matches := make(map[int]int, 64)
for rows.Next() {
var objectID int
var tagName string
err := rows.Scan(&objectID, &tagName)
if err != nil {
return nil, err
}
if matches[objectID] == 0 {
ids = append(ids, objectID)
}
matches[objectID] += 1
}
err = rows.Close()
if err != nil {
return nil, err
}
ids = genutils.RetainInPlace(ids, func(id int) bool {
return matches[id] == len(tags)
})
return r.FetchProjects(ctx, scopeID, ids...)
}
func (r *projectRepository) Insert(ctx context.Context, project entities.Project) (*entities.Project, error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer tx.Rollback()
q := r.q.WithTx(tx)
res, err := q.InsertProject(ctx, mysqlcore.InsertProjectParams{
ScopeID: project.ScopeID,
OwnerID: project.OwnerID,
Name: project.Name,
Status: int(project.Status),
Description: project.Description,
})
if err != nil {
return nil, err
}
id, err := res.LastInsertId()
if err != nil {
return nil, err
}
project.ID = int(id)
for _, tag := range project.Tags {
err := q.InsertTag(ctx, mysqlcore.InsertTagParams{
ObjectKind: tagObjectKindProject,
ObjectID: project.ID,
TagName: tag,
})
if err != nil {
return nil, err
}
}
err = tx.Commit()
if err != nil {
return nil, err
}
return &project, nil
}
func (r *projectRepository) Update(ctx context.Context, project entities.Project, update models.ProjectUpdate) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
q := r.q.WithTx(tx)
project.Update(update)
err = q.UpdateProject(ctx, mysqlcore.UpdateProjectParams{
OwnerID: project.OwnerID,
Name: project.Name,
Status: int(project.Status),
Description: project.Description,
ID: project.ID,
ScopeID: project.ScopeID,
})
if err != nil {
return err
}
for _, tag := range update.RemoveTags {
err := q.DeleteTag(ctx, mysqlcore.DeleteTagParams{
ObjectKind: tagObjectKindProject,
ObjectID: project.ID,
TagName: tag,
})
if err != nil {
return err
}
}
for _, tag := range update.AddTags {
err = q.InsertTag(ctx, mysqlcore.InsertTagParams{
ObjectKind: tagObjectKindProject,
ObjectID: project.ID,
TagName: tag,
})
if err != nil {
return err
}
}
return tx.Commit()
}
func (r *projectRepository) Delete(ctx context.Context, project entities.Project) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
q := r.q.WithTx(tx)
reqs, err := q.ListProjectRequirements(ctx, project.ID)
if err != nil {
return err
}
for _, req := range reqs {
err = q.ClearItemProjectRequirement(ctx, sql.NullInt32{Valid: true, Int32: int32(req.ID)})
if err != nil {
return err
}
err = q.DeleteAllProjectRequirementStats(ctx, req.ID)
if err != nil {
return err
}
err = q.DeleteTagByObject(ctx, mysqlcore.DeleteTagByObjectParams{
ObjectKind: tagObjectKindRequirement,
ObjectID: req.ID,
})
if err != nil {
return err
}
}
err = q.DeleteTagByObject(ctx, mysqlcore.DeleteTagByObjectParams{
ObjectKind: tagObjectKindProject,
ObjectID: project.ID,
})
if err != nil {
return err
}
err = q.DeleteAllProjectRequirements(ctx, project.ID)
if err != nil {
return err
}
err = q.DeleteProject(ctx, mysqlcore.DeleteProjectParams{ID: project.ID, ScopeID: project.ScopeID})
if err != nil {
return err
}
return tx.Commit()
}
func (r *projectRepository) FetchRequirements(ctx context.Context, scopeID int, requirementIDs ...int) ([]entities.Requirement, []entities.RequirementStat, error) {
if len(requirementIDs) == 0 {
return []entities.Requirement{}, []entities.RequirementStat{}, nil
}
sq := squirrel.Select("id, scope_id, project_id, name, status, description, is_coarse, aggregate_required").
From("project_requirement").
Where(squirrel.Eq{"id": requirementIDs})
if scopeID != -1 {
sq = sq.Where(squirrel.Eq{"scope_id": scopeID})
}
query, args, err := sq.ToSql()
if err != nil {
return nil, nil, err
}
rows, err := r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, nil, err
}
ids := make([]int, 0, 16)
requirements := make([]entities.Requirement, 0, len(requirementIDs))
for rows.Next() {
requirement := entities.Requirement{}
if err := rows.Scan(
&requirement.ID,
&requirement.ScopeID,
&requirement.ProjectID,
&requirement.Name,
&requirement.Status,
&requirement.Description,
&requirement.IsCoarse,
&requirement.AggregateRequired,
); err != nil {
return nil, nil, err
}
requirement.Tags = []string{}
requirements = append(requirements, requirement)
ids = append(ids, requirement.ID)
}
// Fill tags
err = fetchTags(ctx, r.db, tagObjectKindRequirement, ids, func(id int, tag string) {
for i := range requirements {
if id == requirements[i].ID {
requirements[i].Tags = append(requirements[i].Tags, tag)
break
}
}
})
if err != nil {
return nil, nil, err
}
query, args, err = squirrel.Select("project_requirement_id, stat_id, required").
From("project_requirement_stat").
Where(squirrel.Eq{"project_requirement_id": requirementIDs}).
ToSql()
if err != nil {
return nil, nil, err
}
rows, err = r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, nil, err
}
stats := make([]entities.RequirementStat, 0, len(requirementIDs))
for rows.Next() {
stat := entities.RequirementStat{}
if err := rows.Scan(
&stat.RequirementID,
&stat.StatID,
&stat.Required,
); err != nil {
return nil, nil, err
}
stats = append(stats, stat)
}
return requirements, stats, nil
}
func (r *projectRepository) ListRequirementsByScope(ctx context.Context, scopeID int) ([]entities.Requirement, []entities.RequirementStat, error) {
reqRows, err := r.q.ListProjectRequirementsByScopeID(ctx, scopeID)
if err != nil && err != sql.ErrNoRows {
return nil, nil, err
}
statsRows, err := r.q.ListProjectRequirementsStatsByScopeID(ctx, scopeID)
if err != nil && err != sql.ErrNoRows {
return nil, nil, err
}
return r.fillRequirements(ctx, reqRows, statsRows)
}
func (r *projectRepository) ListRequirements(ctx context.Context, projectID int) ([]entities.Requirement, []entities.RequirementStat, error) {
reqRows, err := r.q.ListProjectRequirements(ctx, projectID)
if err != nil && err != sql.ErrNoRows {
return nil, nil, err
}
statsRows, err := r.q.ListProjectRequirementsStats(ctx, projectID)
if err != nil && err != sql.ErrNoRows {
return nil, nil, err
}
return r.fillRequirements(ctx, reqRows, statsRows)
}
func (r *projectRepository) fillRequirements(ctx context.Context, reqRows []mysqlcore.ProjectRequirement, statsRows []mysqlcore.ProjectRequirementStat) ([]entities.Requirement, []entities.RequirementStat, error) {
requirements := make([]entities.Requirement, 0, len(reqRows))
ids := make([]int, 0, len(reqRows))
for _, row := range reqRows {
requirements = append(requirements, entities.Requirement{
ID: row.ID,
ScopeID: row.ScopeID,
ProjectID: row.ProjectID,
Name: row.Name,
Description: row.Description,
IsCoarse: row.IsCoarse,
AggregateRequired: row.AggregateRequired,
Status: models.Status(row.Status),
Tags: []string{},
})
ids = append(ids, row.ID)
}
// Fill tags
err := fetchTags(ctx, r.db, tagObjectKindRequirement, ids, func(id int, tag string) {
for i := range requirements {
if id == requirements[i].ID {
requirements[i].Tags = append(requirements[i].Tags, tag)
break
}
}
})
if err != nil {
return nil, nil, err
}
stats := make([]entities.RequirementStat, 0, len(statsRows))
for _, row := range statsRows {
stats = append(stats, entities.RequirementStat{
RequirementID: row.ProjectRequirementID,
StatID: row.StatID,
Required: row.Required,
})
}
return requirements, stats, nil
}
func (r *projectRepository) CreateRequirement(ctx context.Context, requirement entities.Requirement) (*entities.Requirement, error) {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return nil, err
}
defer tx.Rollback()
q := r.q.WithTx(tx)
res, err := q.InsertProjectRequirement(ctx, mysqlcore.InsertProjectRequirementParams{
ScopeID: requirement.ScopeID,
ProjectID: requirement.ProjectID,
Name: requirement.Name,
Status: int(requirement.Status),
Description: requirement.Description,
IsCoarse: requirement.IsCoarse,
AggregateRequired: requirement.AggregateRequired,
})
if err != nil {
return nil, err
}
id, err := res.LastInsertId()
if err != nil {
return nil, err
}
for _, tag := range requirement.Tags {
err := q.InsertTag(ctx, mysqlcore.InsertTagParams{
ObjectKind: tagObjectKindRequirement,
ObjectID: int(id),
TagName: tag,
})
if err != nil {
return nil, err
}
}
err = tx.Commit()
if err != nil {
return nil, err
}
requirement.ID = int(id)
return &requirement, nil
}
func (r *projectRepository) UpdateRequirement(ctx context.Context, requirement entities.Requirement, update models.RequirementUpdate) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
q := r.q.WithTx(tx)
requirement.Update(update)
_ = q.UpdateProjectRequirement(ctx, mysqlcore.UpdateProjectRequirementParams{
Name: requirement.Name,
Status: int(requirement.Status),
Description: requirement.Description,
IsCoarse: requirement.IsCoarse,
ID: requirement.ID,
ScopeID: requirement.ScopeID,
AggregateRequired: requirement.AggregateRequired,
ProjectID: requirement.ProjectID,
})
for _, tag := range update.RemoveTags {
err := q.DeleteTag(ctx, mysqlcore.DeleteTagParams{
ObjectKind: tagObjectKindRequirement,
ObjectID: requirement.ID,
TagName: tag,
})
if err != nil {
return err
}
}
for _, tag := range update.AddTags {
err = q.InsertTag(ctx, mysqlcore.InsertTagParams{
ObjectKind: tagObjectKindRequirement,
ObjectID: requirement.ID,
TagName: tag,
})
if err != nil {
return err
}
}
return tx.Commit()
}
func (r *projectRepository) DeleteRequirement(ctx context.Context, requirement entities.Requirement) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
q := r.q.WithTx(tx)
err = q.ClearItemProjectRequirement(ctx, sql.NullInt32{Valid: true, Int32: int32(requirement.ID)})
if err != nil {
return err
}
err = q.DeleteAllProjectRequirementStats(ctx, requirement.ID)
if err != nil {
return err
}
err = q.DeleteProjectRequirement(ctx, mysqlcore.DeleteProjectRequirementParams{
ScopeID: requirement.ScopeID,
ID: requirement.ID,
})
if err != nil {
return err
}
err = q.DeleteTagByObject(ctx, mysqlcore.DeleteTagByObjectParams{
ObjectKind: tagObjectKindRequirement,
ObjectID: requirement.ID,
})
if err != nil {
return err
}
return tx.Commit()
}
func (r *projectRepository) UpsertRequirementStat(ctx context.Context, stat entities.RequirementStat) error {
return r.q.ReplaceProjectRequirementStat(ctx, mysqlcore.ReplaceProjectRequirementStatParams{
ProjectRequirementID: stat.RequirementID,
StatID: stat.StatID,
Required: stat.Required,
})
}
func (r *projectRepository) DeleteRequirementStat(ctx context.Context, stat entities.RequirementStat) error {
return r.q.DeleteProjectRequirementStat(ctx, mysqlcore.DeleteProjectRequirementStatParams{
ProjectRequirementID: stat.RequirementID,
StatID: stat.StatID,
})
}