feat(ui+backend): base of data persistence
This commit is contained in:
@ -90,7 +90,7 @@ func NewDefault() *Config {
|
||||
Address: ":8081",
|
||||
CookieAuthenticationKey: "",
|
||||
CookieEncryptionKey: "",
|
||||
CookieMaxAge: int((time.Hour * 1).Seconds()), // 1 hour
|
||||
CookieMaxAge: int((time.Hour * 24).Seconds()), // 24 hour
|
||||
TemplateDir: "template",
|
||||
PublicDir: "public",
|
||||
FrontendURL: "http://localhost:8080",
|
||||
|
@ -45,10 +45,7 @@ autobind:
|
||||
models:
|
||||
ID:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.ID
|
||||
- github.com/99designs/gqlgen/graphql.Int
|
||||
- github.com/99designs/gqlgen/graphql.Int64
|
||||
- github.com/99designs/gqlgen/graphql.Int32
|
||||
Int:
|
||||
model:
|
||||
- github.com/99designs/gqlgen/graphql.Int
|
||||
|
49
internal/graph/estimation_handler.go
Normal file
49
internal/graph/estimation_handler.go
Normal file
@ -0,0 +1,49 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"forge.cadoles.com/Cadoles/guesstimate/internal/model"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func handleEstimations(ctx context.Context, task *model.Task) (*model.Estimations, error) {
|
||||
estimations := &model.Estimations{}
|
||||
|
||||
if task.Estimations == nil {
|
||||
return estimations, nil
|
||||
}
|
||||
|
||||
rawOptimistic, exists := task.Estimations[model.EstimationOptimistic]
|
||||
if exists && rawOptimistic != nil {
|
||||
optimistic, err := strconv.ParseFloat(*rawOptimistic, 64)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
estimations.Optimistic = optimistic
|
||||
}
|
||||
|
||||
rawLikely, exists := task.Estimations[model.EstimationLikely]
|
||||
if exists && rawLikely != nil {
|
||||
likely, err := strconv.ParseFloat(*rawLikely, 64)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
estimations.Likely = likely
|
||||
}
|
||||
|
||||
rawPessimistic, exists := task.Estimations[model.EstimationPessimistic]
|
||||
if exists && rawPessimistic != nil {
|
||||
pessimistic, err := strconv.ParseFloat(*rawPessimistic, 64)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
estimations.Pessimistic = pessimistic
|
||||
}
|
||||
|
||||
return estimations, nil
|
||||
}
|
@ -2,6 +2,26 @@ input UserChanges {
|
||||
name: String
|
||||
}
|
||||
|
||||
input CreateProjectChanges {
|
||||
title: String!
|
||||
}
|
||||
|
||||
input ProjectTaskChanges {
|
||||
label: String
|
||||
categoryId: ID
|
||||
estimations: ProjectTaskEstimationsChanges
|
||||
}
|
||||
|
||||
input ProjectTaskEstimationsChanges {
|
||||
optimistic: Float
|
||||
likely: Float
|
||||
pessimistic: Float
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
updateUser(id: ID!, changes: UserChanges!): User!
|
||||
createProject(changes: CreateProjectChanges!): Project!
|
||||
updateProjectTitle(projectId: ID!, title: String!): Project!
|
||||
addProjectTask(projectId: ID!, changes: ProjectTaskChanges): Task!
|
||||
removeProjectTask(projectId: ID!, taskId: ID!): Boolean!
|
||||
}
|
@ -10,10 +10,26 @@ import (
|
||||
"forge.cadoles.com/Cadoles/guesstimate/internal/model"
|
||||
)
|
||||
|
||||
func (r *mutationResolver) UpdateUser(ctx context.Context, id string, changes model.UserChanges) (*model.User, error) {
|
||||
func (r *mutationResolver) UpdateUser(ctx context.Context, id int64, changes model.UserChanges) (*model.User, error) {
|
||||
return handleUpdateUser(ctx, id, changes)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) CreateProject(ctx context.Context, changes model.CreateProjectChanges) (*model.Project, error) {
|
||||
return handleCreateProject(ctx, changes)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) UpdateProjectTitle(ctx context.Context, projectID int64, title string) (*model.Project, error) {
|
||||
return handleUpdateProjectTitle(ctx, projectID, title)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) AddProjectTask(ctx context.Context, projectID int64, changes *model.ProjectTaskChanges) (*model.Task, error) {
|
||||
return handleAddProjectTask(ctx, projectID, changes)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) RemoveProjectTask(ctx context.Context, projectID int64, taskID int64) (bool, error) {
|
||||
return handleRemoveProjectTask(ctx, projectID, taskID)
|
||||
}
|
||||
|
||||
// Mutation returns generated.MutationResolver implementation.
|
||||
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
|
||||
|
||||
|
96
internal/graph/project_handler.go
Normal file
96
internal/graph/project_handler.go
Normal file
@ -0,0 +1,96 @@
|
||||
package graph
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"forge.cadoles.com/Cadoles/guesstimate/internal/model"
|
||||
model1 "forge.cadoles.com/Cadoles/guesstimate/internal/model"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func handleProjects(ctx context.Context, filter *model1.ProjectsFilter) ([]*model1.Project, error) {
|
||||
user, db, err := getSessionUser(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
repo := model.NewProjectRepository(db)
|
||||
|
||||
if filter == nil {
|
||||
filter = &model1.ProjectsFilter{
|
||||
OwnerIds: make([]int64, 0, 1),
|
||||
}
|
||||
}
|
||||
|
||||
filter.OwnerIds = append(filter.OwnerIds, user.ID)
|
||||
|
||||
projects, err := repo.Search(ctx, filter)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return projects, nil
|
||||
}
|
||||
|
||||
func handleCreateProject(ctx context.Context, input model.CreateProjectChanges) (*model.Project, error) {
|
||||
user, db, err := getSessionUser(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
repo := model.NewProjectRepository(db)
|
||||
|
||||
project, err := repo.Create(ctx, input.Title, user.ID)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func handleUpdateProjectTitle(ctx context.Context, projectID int64, title string) (*model.Project, error) {
|
||||
db, err := getDB(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
repo := model.NewProjectRepository(db)
|
||||
|
||||
project, err := repo.UpdateTitle(ctx, projectID, title)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func handleAddProjectTask(ctx context.Context, projectID int64, changes *model.ProjectTaskChanges) (*model.Task, error) {
|
||||
db, err := getDB(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
repo := model.NewProjectRepository(db)
|
||||
|
||||
task, err := repo.AddTask(ctx, projectID, changes)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func handleRemoveProjectTask(ctx context.Context, projectID int64, taskID int64) (bool, error) {
|
||||
db, err := getDB(ctx)
|
||||
if err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
repo := model.NewProjectRepository(db)
|
||||
|
||||
if err := repo.RemoveTask(ctx, projectID, taskID); err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
@ -8,6 +8,60 @@ type User {
|
||||
createdAt: Time!
|
||||
}
|
||||
|
||||
type Project {
|
||||
id: ID!
|
||||
title: String!
|
||||
tasks: [Task]!
|
||||
params: ProjectParams!
|
||||
acl: [Access]!
|
||||
taskCategories: [TaskCategory]!
|
||||
}
|
||||
|
||||
type Task {
|
||||
id: ID!
|
||||
label: String
|
||||
category: TaskCategory
|
||||
estimations: Estimations
|
||||
}
|
||||
|
||||
type TaskCategory {
|
||||
id: ID!
|
||||
label: String!
|
||||
costPerTimeUnit: Float!
|
||||
}
|
||||
|
||||
type Access {
|
||||
id: ID!
|
||||
user: User!
|
||||
level: String!
|
||||
}
|
||||
|
||||
type ProjectParams {
|
||||
timeUnit: TimeUnit
|
||||
currency: String!
|
||||
roundUpEstimations: Boolean!
|
||||
hideFinancialPreviewOnPrint: Boolean!
|
||||
}
|
||||
|
||||
type TimeUnit {
|
||||
label: String!
|
||||
acronym: String!
|
||||
}
|
||||
|
||||
type Estimations {
|
||||
optimistic: Float!
|
||||
likely: Float!
|
||||
pessimistic: Float!
|
||||
}
|
||||
|
||||
input ProjectsFilter {
|
||||
ids: [ID]
|
||||
limit: Int
|
||||
offset: Int
|
||||
search: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
currentUser: User
|
||||
projects(filter: ProjectsFilter): [Project]
|
||||
}
|
||||
|
@ -14,7 +14,19 @@ func (r *queryResolver) CurrentUser(ctx context.Context) (*model1.User, error) {
|
||||
return handleCurrentUser(ctx)
|
||||
}
|
||||
|
||||
func (r *queryResolver) Projects(ctx context.Context, filter *model1.ProjectsFilter) ([]*model1.Project, error) {
|
||||
return handleProjects(ctx, filter)
|
||||
}
|
||||
|
||||
func (r *taskResolver) Estimations(ctx context.Context, obj *model1.Task) (*model1.Estimations, error) {
|
||||
return handleEstimations(ctx, obj)
|
||||
}
|
||||
|
||||
// Query returns generated.QueryResolver implementation.
|
||||
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
||||
|
||||
// Task returns generated.TaskResolver implementation.
|
||||
func (r *Resolver) Task() generated.TaskResolver { return &taskResolver{r} }
|
||||
|
||||
type queryResolver struct{ *Resolver }
|
||||
type taskResolver struct{ *Resolver }
|
||||
|
@ -19,7 +19,7 @@ func handleCurrentUser(ctx context.Context) (*model.User, error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func handleUpdateUser(ctx context.Context, id string, changes model.UserChanges) (*model.User, error) {
|
||||
func handleUpdateUser(ctx context.Context, id int64, changes model.UserChanges) (*model.User, error) {
|
||||
user, db, err := getSessionUser(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
|
21
internal/model/base.go
Normal file
21
internal/model/base.go
Normal file
@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
import "time"
|
||||
|
||||
type Base struct {
|
||||
ID int64 `gorm:"primary_key,AUTO_INCREMENT" json:"id"`
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
func (b *Base) BeforeSave() (err error) {
|
||||
now := time.Now()
|
||||
|
||||
if b.CreatedAt.IsZero() {
|
||||
b.CreatedAt = now
|
||||
}
|
||||
|
||||
b.UpdatedAt = now
|
||||
|
||||
return nil
|
||||
}
|
123
internal/model/project.go
Normal file
123
internal/model/project.go
Normal file
@ -0,0 +1,123 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/jinzhu/gorm/dialects/postgres"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Project struct {
|
||||
Base
|
||||
Title *string `json:"title"`
|
||||
Tasks []*Task `json:"tasks"`
|
||||
Params *ProjectParams `gorm:"-" json:"params"`
|
||||
ParamsJSON postgres.Jsonb `json:"-" gorm:"column:params;"`
|
||||
TaskCategories []*TaskCategory `json:"taskCategories"`
|
||||
ACL []*Access `json:"acl"`
|
||||
}
|
||||
|
||||
func (p *Project) BeforeSave() error {
|
||||
data, err := json.Marshal(p.Params)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
p.ParamsJSON = postgres.Jsonb{RawMessage: data}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Project) AfterFind() error {
|
||||
params := &ProjectParams{}
|
||||
if err := json.Unmarshal(p.ParamsJSON.RawMessage, params); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
p.Params = params
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ProjectParams struct {
|
||||
TimeUnit *TimeUnit `json:"timeUnit"`
|
||||
Currency string `json:"currency"`
|
||||
RoundUpEstimations bool `json:"roundUpEstimations"`
|
||||
HideFinancialPreviewOnPrint bool `json:"hideFinancialPreviewOnPrint"`
|
||||
}
|
||||
|
||||
func NewDefaultProjectParams() *ProjectParams {
|
||||
return &ProjectParams{
|
||||
Currency: "€ H.T.",
|
||||
TimeUnit: &TimeUnit{
|
||||
Acronym: "J/H",
|
||||
Label: "Jour/Homme",
|
||||
},
|
||||
RoundUpEstimations: false,
|
||||
HideFinancialPreviewOnPrint: false,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDefaultTaskCategories() []*TaskCategory {
|
||||
return []*TaskCategory{
|
||||
&TaskCategory{
|
||||
Label: "Développement",
|
||||
CostPerTimeUnit: 500,
|
||||
},
|
||||
&TaskCategory{
|
||||
Label: "Conduite de projet",
|
||||
CostPerTimeUnit: 500,
|
||||
},
|
||||
&TaskCategory{
|
||||
Label: "Recette",
|
||||
CostPerTimeUnit: 500,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
LevelOwner = "owner"
|
||||
LevelReadOnly = "readonly"
|
||||
LevelContributor = "contributor"
|
||||
)
|
||||
|
||||
type Access struct {
|
||||
Base
|
||||
ProjectID int64
|
||||
Project *Project `json:"-"`
|
||||
UserID int64
|
||||
User *User `json:"user"`
|
||||
Level string `json:"level"`
|
||||
}
|
||||
|
||||
const (
|
||||
EstimationPessimistic = "pessimistic"
|
||||
EstimationLikely = "likely"
|
||||
EstimationOptimistic = "optimistic"
|
||||
)
|
||||
|
||||
type Task struct {
|
||||
Base
|
||||
ProjectID int64 `json:"-"`
|
||||
Project *Project `json:"-"`
|
||||
Label *string `json:"label"`
|
||||
CategoryID int64 `json:"-"`
|
||||
Category *TaskCategory `json:"category"`
|
||||
Estimations postgres.Hstore `json:"estimations"`
|
||||
}
|
||||
|
||||
type TaskCategory struct {
|
||||
Base
|
||||
ProjectID int64 `json:"-"`
|
||||
Project *Project `json:"-"`
|
||||
Label string `json:"label"`
|
||||
CostPerTimeUnit float64 `json:"costPerTimeUnit"`
|
||||
}
|
||||
|
||||
type ProjectsFilter struct {
|
||||
Ids []*int64 `json:"ids"`
|
||||
Limit *int `json:"limit"`
|
||||
Offset *int `json:"offset"`
|
||||
Search *string `json:"search"`
|
||||
OwnerIds []int64 `json:"-"`
|
||||
}
|
261
internal/model/project_repository.go
Normal file
261
internal/model/project_repository.go
Normal file
@ -0,0 +1,261 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/jinzhu/gorm/dialects/postgres"
|
||||
|
||||
"forge.cadoles.com/Cadoles/guesstimate/internal/orm"
|
||||
"github.com/jinzhu/gorm"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type ProjectRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func (r *ProjectRepository) Create(ctx context.Context, title string, ownerID int64) (*Project, error) {
|
||||
project := &Project{
|
||||
Title: &title,
|
||||
Tasks: make([]*Task, 0),
|
||||
TaskCategories: NewDefaultTaskCategories(),
|
||||
Params: NewDefaultProjectParams(),
|
||||
}
|
||||
|
||||
err := orm.WithTx(ctx, r.db, func(ctx context.Context, tx *gorm.DB) error {
|
||||
if err := tx.Save(project).Error; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err := tx.Model(project).Association("ACL").Append(&Access{
|
||||
Level: LevelOwner,
|
||||
UserID: ownerID,
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err = tx.Model(project).
|
||||
Preload("ACL").
|
||||
Preload("ACL.User").
|
||||
Preload("Tasks").
|
||||
Preload("Tasks.Category").
|
||||
Preload("TaskCategories").
|
||||
Find(project).
|
||||
Error
|
||||
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not create user")
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (r *ProjectRepository) UpdateTitle(ctx context.Context, projectID int64, title string) (*Project, error) {
|
||||
project := &Project{}
|
||||
project.ID = projectID
|
||||
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(project).Update("title", title).Error; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
err := tx.Model(project).
|
||||
Preload("ACL").
|
||||
Preload("ACL.User").
|
||||
Preload("Tasks").
|
||||
Preload("Tasks.Category").
|
||||
Preload("TaskCategories").
|
||||
First(project, "id = ?", projectID).
|
||||
Error
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (r *ProjectRepository) Search(ctx context.Context, filter *ProjectsFilter) ([]*Project, error) {
|
||||
projects := make([]*Project, 0)
|
||||
|
||||
projectTableName := r.db.NewScope(&Project{}).TableName()
|
||||
|
||||
query := r.db.Table(projectTableName).
|
||||
Preload("ACL").
|
||||
Preload("ACL.User").
|
||||
Preload("Tasks").
|
||||
Preload("Tasks.Category").
|
||||
Preload("TaskCategories")
|
||||
|
||||
if filter != nil {
|
||||
if len(filter.Ids) > 0 {
|
||||
query = query.Where(fmt.Sprintf("%s.id in (?)", projectTableName), filter.Ids)
|
||||
}
|
||||
|
||||
if len(filter.OwnerIds) > 0 {
|
||||
accessTableName := r.db.NewScope(&Access{}).TableName()
|
||||
|
||||
query = query.
|
||||
Joins(
|
||||
fmt.Sprintf(
|
||||
"left join %s on %s.project_id = %s.id",
|
||||
accessTableName, accessTableName, projectTableName,
|
||||
),
|
||||
).
|
||||
Where(fmt.Sprintf("%s.level = ?", accessTableName), LevelOwner).
|
||||
Where(fmt.Sprintf("%s.user_id IN (?)", accessTableName), filter.OwnerIds)
|
||||
}
|
||||
|
||||
if filter.Limit != nil {
|
||||
query = query.Limit(*filter.Limit)
|
||||
}
|
||||
|
||||
if filter.Offset != nil {
|
||||
query = query.Offset(*filter.Offset)
|
||||
}
|
||||
}
|
||||
|
||||
if err := query.Find(&projects).Error; err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return projects, nil
|
||||
}
|
||||
|
||||
func (r *ProjectRepository) AddTask(ctx context.Context, projectID int64, changes *ProjectTaskChanges) (*Task, error) {
|
||||
project := &Project{}
|
||||
project.ID = projectID
|
||||
task := &Task{}
|
||||
|
||||
if changes == nil {
|
||||
return nil, errors.Errorf("changes should not be nil")
|
||||
}
|
||||
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
if changes.Label != nil {
|
||||
task.Label = changes.Label
|
||||
}
|
||||
|
||||
if changes.CategoryID != nil {
|
||||
taskCategory := &TaskCategory{}
|
||||
taskCategory.ID = *changes.CategoryID
|
||||
task.Category = taskCategory
|
||||
}
|
||||
|
||||
if changes.Estimations != nil {
|
||||
if task.Estimations == nil {
|
||||
task.Estimations = postgres.Hstore{}
|
||||
}
|
||||
|
||||
if changes.Estimations.Pessimistic != nil {
|
||||
pessimistic := strconv.FormatFloat(*changes.Estimations.Pessimistic, 'f', 12, 64)
|
||||
task.Estimations[EstimationPessimistic] = &pessimistic
|
||||
}
|
||||
|
||||
if changes.Estimations.Likely != nil {
|
||||
likely := strconv.FormatFloat(*changes.Estimations.Likely, 'f', 12, 64)
|
||||
task.Estimations[EstimationLikely] = &likely
|
||||
}
|
||||
|
||||
if changes.Estimations.Optimistic != nil {
|
||||
optimistic := strconv.FormatFloat(*changes.Estimations.Optimistic, 'f', 12, 64)
|
||||
task.Estimations[EstimationOptimistic] = &optimistic
|
||||
}
|
||||
|
||||
if changes.CategoryID != nil {
|
||||
taskCategory := &TaskCategory{}
|
||||
if err := tx.Find(taskCategory, "id = ?", *changes.CategoryID).Error; err != nil {
|
||||
return errors.Wrap(err, "could not find task category")
|
||||
}
|
||||
|
||||
task.Category = taskCategory
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Save(task).Error; err != nil {
|
||||
return errors.Wrap(err, "could not create task")
|
||||
}
|
||||
|
||||
err := tx.Model(project).Association("Tasks").Append(task).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not add task")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not add task")
|
||||
}
|
||||
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func (r *ProjectRepository) RemoveTask(ctx context.Context, projectID int64, taskID int64) error {
|
||||
project := &Project{}
|
||||
project.ID = projectID
|
||||
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
task := &Task{}
|
||||
task.ID = taskID
|
||||
|
||||
err := tx.Model(project).Association("Tasks").Delete(task).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not remove task relationship")
|
||||
}
|
||||
|
||||
err = tx.Delete(task, "id = ?", taskID).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not delete task")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not remove task")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ProjectRepository) UpdateTaskEstimation(ctx context.Context, projectID, taskID int64, estimation string, value float64) (*Task, error) {
|
||||
err := r.db.Transaction(func(tx *gorm.DB) error {
|
||||
task := &Task{}
|
||||
if err := tx.First(task, "id = ?", taskID).Error; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
strValue := strconv.FormatFloat(value, 'f', 12, 64)
|
||||
task.Estimations[estimation] = &strValue
|
||||
|
||||
if err := tx.Save(task).Error; err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not update task")
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewProjectRepository(db *gorm.DB) *ProjectRepository {
|
||||
return &ProjectRepository{db}
|
||||
}
|
@ -5,11 +5,10 @@ import (
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Base
|
||||
Name *string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
ConnectedAt time.Time `json:"connectedAt"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
type UserChanges struct {
|
||||
|
Reference in New Issue
Block a user