Browse Source

add tags to more stuff.

master
Gisle Aune 1 year ago
parent
commit
c565ca5ea7
  1. 22
      entities/item.go
  2. 16
      entities/project.go
  3. 2
      frontend/src/lib/components/project/ProjectMain.svelte
  4. 5
      frontend/src/lib/components/project/RequirementSection.svelte
  5. 2
      frontend/src/lib/models/project.ts
  6. 24
      internal/genutils/slice.go
  7. 49
      internal/validate/tags.go
  8. 22
      models/project.go
  9. 7
      ports/httpapi/projects.go
  10. 36
      ports/mysql/db.go
  11. 36
      ports/mysql/items.go
  12. 181
      ports/mysql/projects.go
  13. 31
      usecases/items/service.go
  14. 1
      usecases/projects/repository.go
  15. 4
      usecases/projects/result.go
  16. 30
      usecases/projects/service.go

22
entities/item.go

@ -1,6 +1,7 @@
package entities
import (
"git.aiterp.net/stufflog3/stufflog3/internal/genutils"
"git.aiterp.net/stufflog3/stufflog3/models"
"sort"
"strings"
@ -75,23 +76,10 @@ func (item *Item) ApplyUpdate(update models.ItemUpdate) {
if update.ClearAcquiredTime {
item.AcquiredTime = nil
}
if update.RemoveTags != nil || update.AddTags != nil {
item.Tags = append(item.Tags[:0:0], item.Tags...)
for _, tagToRemove := range update.RemoveTags {
for i, tag := range item.Tags {
if strings.EqualFold(tag, tagToRemove) {
{
item.Tags = append(item.Tags[:i], item.Tags[i+1:]...)
break
}
}
}
}
for _, tagToAdd := range update.AddTags {
item.Tags = append(item.Tags, tagToAdd)
}
if len(update.AddTags) > 0 {
sort.Strings(item.Tags)
}
item.Tags = genutils.SliceWithout(item.Tags, update.RemoveTags)
item.Tags = genutils.SliceWithUniques(item.Tags, update.AddTags)
sort.Strings(item.Tags)
}
}

16
entities/project.go

@ -1,7 +1,9 @@
package entities
import (
"git.aiterp.net/stufflog3/stufflog3/internal/genutils"
"git.aiterp.net/stufflog3/stufflog3/models"
"sort"
"time"
)
@ -13,6 +15,7 @@ type Project struct {
Name string `json:"name"`
Description string `json:"description"`
Status models.Status `json:"status"`
Tags []string `json:"tags"`
}
func (project *Project) Update(update models.ProjectUpdate) {
@ -28,6 +31,12 @@ func (project *Project) Update(update models.ProjectUpdate) {
if update.Status != nil {
project.Status = *update.Status
}
if update.RemoveTags != nil || update.AddTags != nil {
project.Tags = genutils.SliceWithout(project.Tags, update.RemoveTags)
project.Tags = genutils.SliceWithUniques(project.Tags, update.AddTags)
sort.Strings(project.Tags)
}
}
type Requirement struct {
@ -39,6 +48,7 @@ type Requirement struct {
Description string `json:"description"`
IsCoarse bool `json:"isCoarse"`
Status models.Status `json:"status"`
Tags []string `json:"tags"`
}
func (requirement *Requirement) Update(update models.RequirementUpdate) {
@ -57,6 +67,12 @@ func (requirement *Requirement) Update(update models.RequirementUpdate) {
if update.AggregateRequired != nil {
requirement.AggregateRequired = *update.AggregateRequired
}
if update.RemoveTags != nil || update.AddTags != nil {
requirement.Tags = genutils.SliceWithout(requirement.Tags, update.RemoveTags)
requirement.Tags = genutils.SliceWithUniques(requirement.Tags, update.AddTags)
sort.Strings(requirement.Tags)
}
}
type RequirementStat struct {

2
frontend/src/lib/components/project/ProjectMain.svelte

@ -4,6 +4,7 @@
import Main from "$lib/components/layout/Main.svelte";
import Option from "$lib/components/layout/Option.svelte";
import OptionsRow from "$lib/components/layout/OptionsRow.svelte";
import TagRow from "../common/TagRow.svelte";
import { getProjectContext } from "../contexts/ProjectContext.svelte";
import Icon from "../layout/Icon.svelte";
import RequirementEntry from "./RequirementSection.svelte";
@ -14,6 +15,7 @@
<Main big title={$project.name}>
<Progress alwaysSmooth titlePercentageOnly thin green status={$project.status} count={$project.totalAcquired} target={$project.totalRequired} />
<Progress alwaysSmooth titlePercentageOnly thinner gray count={$project.totalPlanned} target={$project.totalRequired} />
<TagRow names={$project.tags} />
<Markdown source={$project.description} />
<OptionsRow slot="right">
<Option open={{name: "requirement.create", project: $project}}><Icon name="plus" /></Option>

5
frontend/src/lib/components/project/RequirementSection.svelte

@ -11,8 +11,8 @@
import ItemEntry from "./ItemSubSection.svelte";
import Icon from "../layout/Icon.svelte";
import { projectPrettyId } from "$lib/utils/prettyIds";
import AmountRow from "../common/AmountRow.svelte";
import AggregateAmountRow from "./AggregateAmountRow.svelte";
import AggregateAmountRow from "./AggregateAmountRow.svelte";
import TagRow from "../common/TagRow.svelte";
export let requirement: Requirement;
</script>
@ -21,6 +21,7 @@ import AggregateAmountRow from "./AggregateAmountRow.svelte";
<Section title={requirement.name} icon={STATUS_ICONS[requirement.status]} status={requirement.status}>
<Progress alwaysSmooth titlePercentageOnly thin green status={requirement.status} count={requirement.totalAcquired} target={requirement.totalRequired} />
<Progress alwaysSmooth titlePercentageOnly thinner gray count={requirement.totalPlanned} target={requirement.totalRequired} />
<TagRow names={requirement.tags} />
<Markdown source={requirement.description} />
<OptionsRow slot="right">
<Option open={{name: "item.create", requirement}}><Icon name="plus" /></Option>

2
frontend/src/lib/models/project.ts

@ -19,6 +19,7 @@ export interface ProjectEntry {
name: string
status: number
statusName: string
tags: string[]
}
export interface ProjectInput {
@ -41,6 +42,7 @@ export interface Requirement {
aggregateRequired: number
stats: StatProgressWithPlanned[]
items: Item[]
tags: string[]
}
export interface RequirementInput {

24
internal/genutils/slice.go

@ -0,0 +1,24 @@
package genutils
func SliceWithUniques[T comparable](slice []T, elemsToAdd []T) []T {
newSlice := slice[:len(slice):len(slice)]
addLoop:
for _, elem := range elemsToAdd {
for _, existing := range slice {
if existing == elem {
continue addLoop
}
}
newSlice = append(newSlice, elem)
}
return newSlice
}
func SliceWithout[T comparable](originals []T, elemsToRemove []T) []T {
return Retain(originals, func(v T) bool {
return !Contains(elemsToRemove, v)
})
}

49
internal/validate/tags.go

@ -0,0 +1,49 @@
package validate
import (
"fmt"
"git.aiterp.net/stufflog3/stufflog3/internal/genutils"
"git.aiterp.net/stufflog3/stufflog3/models"
)
func Tags(existingTags, addTags, removeTags []string) error {
for i, tag := range addTags {
if !Tag(tag) {
return models.BadInputError{
Object: "ItemInput",
Field: "addTags",
Problem: fmt.Sprintf("Invalid tag: %s", tag),
Element: tag,
}
}
if genutils.Contains(addTags[:i], tag) {
return models.BadInputError{
Object: "ItemUpdate",
Field: "addTags",
Problem: fmt.Sprintf("The tag %s already exists!", tag),
Element: tag,
}
}
if genutils.Contains(existingTags, tag) {
return models.BadInputError{
Object: "ItemUpdate",
Field: "addTags",
Problem: fmt.Sprintf("The tag %s already exists!", tag),
Element: tag,
}
}
}
for _, tag := range removeTags {
if !genutils.Contains(existingTags, tag) {
return models.BadInputError{
Object: "ItemUpdate",
Field: "removeTags",
Problem: fmt.Sprintf("The tag %s does not exist!", tag),
Element: tag,
}
}
}
return nil
}

22
models/project.go

@ -1,16 +1,20 @@
package models
type ProjectUpdate struct {
Name *string `json:"name,omitempty"`
OwnerID *string `json:"ownerId,omitempty"`
Status *Status `json:"status,omitempty"`
Description *string `json:"description,omitempty"`
Name *string `json:"name,omitempty"`
OwnerID *string `json:"ownerId,omitempty"`
Status *Status `json:"status,omitempty"`
Description *string `json:"description,omitempty"`
AddTags []string `json:"addTags"`
RemoveTags []string `json:"removeTags"`
}
type RequirementUpdate struct {
Name *string `json:"name"`
Description *string `json:"description"`
Status *Status `json:"status"`
IsCoarse *bool `json:"isCoarse"`
AggregateRequired *int `json:"aggregateRequired"`
Name *string `json:"name"`
Description *string `json:"description"`
Status *Status `json:"status"`
IsCoarse *bool `json:"isCoarse"`
AggregateRequired *int `json:"aggregateRequired"`
AddTags []string `json:"addTags"`
RemoveTags []string `json:"removeTags"`
}

7
ports/httpapi/projects.go

@ -5,11 +5,16 @@ import (
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/usecases/projects"
"github.com/gin-gonic/gin"
"strings"
)
func Projects(g *gin.RouterGroup, service *projects.Service) {
g.GET("", handler("projects", func(c *gin.Context) (interface{}, error) {
return service.List(c.Request.Context())
if tagsQuery := c.Query("tags"); tagsQuery != "" {
return service.ListByTags(c.Request.Context(), strings.Split(tagsQuery, ","))
} else {
return service.List(c.Request.Context())
}
}))
g.GET("/:project_id", handler("project", func(c *gin.Context) (interface{}, error) {

36
ports/mysql/db.go

@ -1,6 +1,7 @@
package mysql
import (
"context"
"database/sql"
"encoding/json"
"fmt"
@ -12,6 +13,7 @@ import (
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
"git.aiterp.net/stufflog3/stufflog3/usecases/sprints"
"git.aiterp.net/stufflog3/stufflog3/usecases/stats"
"github.com/Masterminds/squirrel"
"time"
_ "github.com/go-sql-driver/mysql"
@ -135,3 +137,37 @@ const (
tagObjectKindRequirement
tagObjectKindProject
)
func fetchTags(ctx context.Context, db *sql.DB, kind int, ids []int, cb func(id int, tag string)) error {
query, args, err := squirrel.Select("object_id, tag_name").
From("tag").
Where(squirrel.Eq{"object_id": ids, "object_kind": kind}).
OrderBy("tag_name").
ToSql()
if err != nil {
return err
}
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
return err
}
for rows.Next() {
var tagStr string
var objectID int
err := rows.Scan(&objectID, &tagStr)
if err != nil {
return err
}
cb(objectID, tagStr)
}
if err := rows.Close(); err != nil {
return err
}
if err := rows.Err(); err != nil {
return err
}
return nil
}

36
ports/mysql/items.go

@ -221,31 +221,15 @@ func (r *itemRepository) Fetch(ctx context.Context, filter models.ItemFilter) ([
ids := genutils.Map(res, func(i entities.Item) int {
return i.ID
})
query, args, err := squirrel.Select("object_id, tag_name").
From("tag").
Where(squirrel.Eq{"object_id": ids, "object_kind": tagObjectKindItem}).
ToSql()
if err != nil {
return nil, err
}
rows, err = r.db.QueryContext(ctx, query, args...)
if err != nil {
return nil, err
}
for rows.Next() {
var tagStr string
var objectID int
err := rows.Scan(&objectID, &tagStr)
if err != nil {
return nil, err
}
err = fetchTags(ctx, r.db, tagObjectKindItem, ids, func(id int, tag string) {
for i := range res {
if res[i].ID == objectID {
res[i].Tags = append(res[i].Tags, tagStr)
break
if id == res[i].ID {
res[i].Tags = append(res[i].Tags, tag)
}
}
})
if err != nil {
return nil, err
}
sort.Slice(res, func(i, j int) bool {
@ -386,6 +370,14 @@ func (r *itemRepository) Update(ctx context.Context, item entities.Item, update
}
func (r *itemRepository) Delete(ctx context.Context, item entities.Item) error {
err := r.q.DeleteTagByObject(ctx, mysqlcore.DeleteTagByObjectParams{
ObjectKind: tagObjectKindItem,
ObjectID: item.ID,
})
if err != nil {
return err
}
return r.q.DeleteItem(ctx, item.ID)
}

181
ports/mysql/projects.go

@ -4,6 +4,7 @@ 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"
@ -24,6 +25,11 @@ func (r *projectRepository) Find(ctx context.Context, scopeID, projectID int) (*
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,
@ -32,6 +38,7 @@ func (r *projectRepository) Find(ctx context.Context, scopeID, projectID int) (*
Name: row.Name,
Description: row.Description,
Status: models.Status(row.Status),
Tags: tags,
}, nil
}
@ -78,6 +85,8 @@ func (r *projectRepository) FetchProjects(ctx context.Context, scopeID int, ids
return nil, err
}
project.Tags = []string{}
projects = append(projects, project)
}
if err := rows.Close(); err != nil {
@ -87,6 +96,18 @@ func (r *projectRepository) FetchProjects(ctx context.Context, scopeID int, ids
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
}
@ -101,6 +122,7 @@ func (r *projectRepository) List(ctx context.Context, scopeID int) ([]entities.P
}
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,
@ -110,14 +132,74 @@ func (r *projectRepository) List(ctx context.Context, scopeID int) ([]entities.P
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) {
res, err := r.q.InsertProject(ctx, mysqlcore.InsertProjectParams{
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,
@ -134,6 +216,23 @@ func (r *projectRepository) Insert(ctx context.Context, project entities.Project
}
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
}
@ -174,6 +273,22 @@ func (r *projectRepository) Delete(ctx context.Context, project entities.Project
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)
@ -226,10 +341,25 @@ func (r *projectRepository) FetchRequirements(ctx context.Context, scopeID int,
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}).
@ -270,6 +400,7 @@ func (r *projectRepository) ListRequirements(ctx context.Context, projectID int)
}
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,
@ -280,7 +411,22 @@ func (r *projectRepository) ListRequirements(ctx context.Context, projectID int)
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))
@ -296,7 +442,14 @@ func (r *projectRepository) ListRequirements(ctx context.Context, projectID int)
}
func (r *projectRepository) CreateRequirement(ctx context.Context, requirement entities.Requirement) (*entities.Requirement, error) {
res, err := r.q.InsertProjectRequirement(ctx, mysqlcore.InsertProjectRequirementParams{
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,
@ -314,6 +467,22 @@ func (r *projectRepository) CreateRequirement(ctx context.Context, requirement e
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
}
@ -358,6 +527,14 @@ func (r *projectRepository) DeleteRequirement(ctx context.Context, requirement e
return err
}
err = q.DeleteTagByObject(ctx, mysqlcore.DeleteTagByObjectParams{
ObjectKind: tagObjectKindRequirement,
ObjectID: requirement.ID,
})
if err != nil {
return err
}
return tx.Commit()
}

31
usecases/items/service.go

@ -314,34 +314,9 @@ func (s *Service) Update(ctx context.Context, id int, update models.ItemUpdate,
return nil, err
}
for _, tag := range update.AddTags {
if !validate.Tag(tag) {
return nil, models.BadInputError{
Object: "ItemInput",
Field: "addTags",
Problem: fmt.Sprintf("Invalid tag: %s", tag),
Element: tag,
}
}
if item.HasTag(tag) {
return nil, models.BadInputError{
Object: "ItemUpdate",
Field: "addTags",
Problem: fmt.Sprintf("The tag %s already exists!", tag),
Element: tag,
}
}
}
for _, tag := range update.RemoveTags {
if !item.HasTag(tag) {
return nil, models.BadInputError{
Object: "ItemUpdate",
Field: "removeTags",
Problem: fmt.Sprintf("The tag %s does not exist!", tag),
Element: tag,
}
}
err = validate.Tags(item.Tags, update.AddTags, update.RemoveTags)
if err != nil {
return nil, err
}
if update.RequirementID != nil {

1
usecases/projects/repository.go

@ -9,6 +9,7 @@ import (
type Repository interface {
Find(ctx context.Context, scopeID, projectID int) (*entities.Project, error)
List(ctx context.Context, scopeID int) ([]entities.Project, error)
ListByTags(ctx context.Context, scopeID int, tags []string) ([]entities.Project, error)
FetchProjects(ctx context.Context, scopeID int, ids ...int) ([]entities.Project, error)
Insert(ctx context.Context, project entities.Project) (*entities.Project, error)
Update(ctx context.Context, project entities.Project, update models.ProjectUpdate) error

4
usecases/projects/result.go

@ -18,6 +18,7 @@ type Entry struct {
Status models.Status `json:"status"`
StatusName string `json:"statusName"`
OwnerName string `json:"ownerName,omitempty"`
Tags []string `json:"tags"`
}
func generateEntry(project entities.Project, scope scopes.Result) Entry {
@ -27,6 +28,7 @@ func generateEntry(project entities.Project, scope scopes.Result) Entry {
CreatedTime: project.CreatedTime,
Name: project.Name,
Status: project.Status,
Tags: project.Tags,
StatusName: scope.StatusName(project.Status),
OwnerName: scope.MemberName(project.OwnerID),
}
@ -65,6 +67,7 @@ type RequirementResult struct {
AggregateRequired int `json:"aggregateRequired"`
Stats []RequirementResultStat `json:"stats"`
Items []items.Result `json:"items"`
Tags []string `json:"tags"`
Requirement entities.Requirement `json:"-"`
}
@ -171,6 +174,7 @@ func generateRequirementResult(req entities.Requirement, scope scopes.Result, re
AggregateRequired: req.AggregateRequired,
Stats: make([]RequirementResultStat, 0, 8),
Items: make([]items.Result, 0, 8),
Tags: req.Tags,
Requirement: req,
}

30
usecases/projects/service.go

@ -4,6 +4,7 @@ import (
"context"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/internal/genutils"
"git.aiterp.net/stufflog3/stufflog3/internal/validate"
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/usecases/auth"
"git.aiterp.net/stufflog3/stufflog3/usecases/items"
@ -66,6 +67,20 @@ func (s *Service) List(ctx context.Context) ([]Entry, error) {
return entries, nil
}
func (s *Service) ListByTags(ctx context.Context, tags []string) (interface{}, error) {
projects, err := s.Repository.ListByTags(ctx, s.Scopes.Context(ctx).ID, tags)
if err != nil {
return nil, err
}
entries := make([]Entry, 0, len(projects))
for _, project := range projects {
entries = append(entries, generateEntry(project, s.Scopes.Context(ctx).Scope))
}
return entries, nil
}
func (s *Service) FetchRequirements(ctx context.Context, ids ...int) ([]RequirementResult, error) {
sc := s.Scopes.Context(ctx)
@ -111,6 +126,11 @@ func (s *Service) Create(ctx context.Context, project entities.Project) (*Result
}
}
err := validate.Tags(nil, project.Tags, nil)
if err != nil {
return nil, err
}
// Allow importing and scripts to mess with the created time, so only set it to now if it's not set.
if project.CreatedTime.IsZero() {
project.CreatedTime = time.Now()
@ -196,6 +216,11 @@ func (s *Service) CreateRequirement(ctx context.Context, id int, requirement ent
}
}
err := validate.Tags(nil, requirement.Tags, nil)
if err != nil {
return nil, err
}
project, err := s.Find(ctx, id)
if err != nil {
return nil, err
@ -250,6 +275,11 @@ func (s *Service) UpdateRequirement(ctx context.Context, projectID, requirementI
return nil, models.NotFoundError("Requirement")
}
err = validate.Tags(req.Tags, update.AddTags, update.RemoveTags)
if err != nil {
return nil, err
}
err = s.Repository.UpdateRequirement(ctx, req.Requirement, update)
if err != nil {
return nil, err

Loading…
Cancel
Save