fix(labels): allow link shares to add existing labels to a task

Resolves https://github.com/go-vikunja/vikunja/issues/252
This commit is contained in:
kolaente 2024-04-21 15:12:27 +02:00
parent 1074a8d916
commit 574c7f218e
No known key found for this signature in database
GPG Key ID: F40E70337AB24C9B
7 changed files with 45 additions and 40 deletions

View File

@ -34,6 +34,7 @@ import {useBaseStore} from '@/stores/base'
import Logo from '@/components/home/Logo.vue' import Logo from '@/components/home/Logo.vue'
import PoweredByLink from './PoweredByLink.vue' import PoweredByLink from './PoweredByLink.vue'
import {useProjectStore} from '@/stores/projects' import {useProjectStore} from '@/stores/projects'
import {useLabelStore} from '@/stores/labels'
const baseStore = useBaseStore() const baseStore = useBaseStore()
const currentProject = computed(() => baseStore.currentProject) const currentProject = computed(() => baseStore.currentProject)
@ -42,6 +43,9 @@ const logoVisible = computed(() => baseStore.logoVisible)
const projectStore = useProjectStore() const projectStore = useProjectStore()
projectStore.loadAllProjects() projectStore.loadAllProjects()
const labelStore = useLabelStore()
labelStore.loadAllLabels()
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -160,7 +160,6 @@ export const useLabelStore = defineStore('label', () => {
deleteLabel, deleteLabel,
updateLabel, updateLabel,
createLabel, createLabel,
} }
}) })

View File

@ -309,6 +309,7 @@
v-model="task.labels" v-model="task.labels"
:disabled="!canWrite" :disabled="!canWrite"
:task-id="taskId" :task-id="taskId"
:creatable="!authStore.isLinkShareAuth"
/> />
</div> </div>
@ -653,6 +654,7 @@ const projectStore = useProjectStore()
const attachmentStore = useAttachmentStore() const attachmentStore = useAttachmentStore()
const taskStore = useTaskStore() const taskStore = useTaskStore()
const kanbanStore = useKanbanStore() const kanbanStore = useKanbanStore()
const authStore = useAuthStore()
const task = ref<ITask>(new TaskModel()) const task = ref<ITask>(new TaskModel())
const taskTitle = computed(() => task.value.title) const taskTitle = computed(() => task.value.title)
@ -877,7 +879,7 @@ function toggleTaskDone() {
done: !task.value.done, done: !task.value.done,
} }
if (newTask.done && useAuthStore().settings.frontendSettings.playSoundWhenDone) { if (newTask.done && authStore.settings.frontendSettings.playSoundWhenDone) {
playPopSound() playPopSound()
} }

View File

@ -148,23 +148,14 @@ func (l *Label) Delete(s *xorm.Session, _ web.Auth) (err error) {
// @Failure 500 {object} models.Message "Internal error" // @Failure 500 {object} models.Message "Internal error"
// @Router /labels [get] // @Router /labels [get]
func (l *Label) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (ls interface{}, resultCount int, numberOfEntries int64, err error) { func (l *Label) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (ls interface{}, resultCount int, numberOfEntries int64, err error) {
if _, is := a.(*LinkSharing); is {
return nil, 0, 0, ErrGenericForbidden{}
}
u, err := user.GetUserByID(s, a.GetID())
if err != nil {
return nil, 0, 0, err
}
return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{ return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
Search: []string{search}, Search: []string{search},
User: u, User: a,
GetForUser: u.ID,
Page: page, Page: page,
PerPage: perPage, PerPage: perPage,
GetUnusedLabels: true, GetUnusedLabels: true,
GroupByLabelIDsOnly: true, GroupByLabelIDsOnly: true,
GetForUser: true,
}) })
} }

View File

@ -17,7 +17,6 @@
package models package models
import ( import (
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/web" "code.vikunja.io/web"
"xorm.io/builder" "xorm.io/builder"
"xorm.io/xorm" "xorm.io/xorm"
@ -64,27 +63,29 @@ func (l *Label) isLabelOwner(s *xorm.Session, a web.Auth) (bool, error) {
// Helper method to check if a user can see a specific label // Helper method to check if a user can see a specific label
func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxRight int, err error) { func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxRight int, err error) {
if _, is := a.(*LinkSharing); is { linkShare, isLinkShare := a.(*LinkSharing)
return false, 0, nil
}
u, err := user.GetUserByID(s, a.GetID()) var where builder.Cond
if err != nil { var createdByID int64
return false, 0, err if isLinkShare {
where = builder.Eq{"project_id": linkShare.ProjectID}
} else {
where = builder.In("project_id", getUserProjectsStatement(a.GetID(), "", false).Select("l.id"))
createdByID = a.GetID()
} }
cond := builder.In("label_tasks.task_id", cond := builder.In("label_tasks.task_id",
builder. builder.
Select("id"). Select("id").
From("tasks"). From("tasks").
Where(builder.In("project_id", getUserProjectsStatement(u.ID, "", false).Select("l.id"))), Where(where),
) )
ll := &LabelTask{} ll := &LabelTask{}
has, err = s.Table("labels"). has, err = s.Table("labels").
Select("label_tasks.*"). Select("label_tasks.*").
Join("LEFT", "label_tasks", "label_tasks.label_id = labels.id"). Join("LEFT", "label_tasks", "label_tasks.label_id = labels.id").
Where("label_tasks.label_id is not null OR labels.created_by_id = ?", u.ID). Where("label_tasks.label_id is not null OR labels.created_by_id = ?", createdByID).
Or(cond). Or(cond).
And("labels.id = ?", l.ID). And("labels.id = ?", l.ID).
Exist(ll) Exist(ll)

View File

@ -144,7 +144,7 @@ func (lt *LabelTask) ReadAll(s *xorm.Session, a web.Auth, search string, page in
} }
return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{ return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
User: &user.User{ID: a.GetID()}, User: a,
Search: []string{search}, Search: []string{search},
Page: page, Page: page,
TaskIDs: []int64{lt.TaskID}, TaskIDs: []int64{lt.TaskID},
@ -159,23 +159,26 @@ type LabelWithTaskID struct {
// LabelByTaskIDsOptions is a struct to not clutter the function with too many optional parameters. // LabelByTaskIDsOptions is a struct to not clutter the function with too many optional parameters.
type LabelByTaskIDsOptions struct { type LabelByTaskIDsOptions struct {
User *user.User User web.Auth
Search []string Search []string
Page int Page int
PerPage int PerPage int
TaskIDs []int64 TaskIDs []int64
GetUnusedLabels bool GetUnusedLabels bool
GroupByLabelIDsOnly bool GroupByLabelIDsOnly bool
GetForUser int64 GetForUser bool
} }
// GetLabelsByTaskIDs is a helper function to get all labels for a set of tasks // GetLabelsByTaskIDs is a helper function to get all labels for a set of tasks
// Used when getting all labels for one task as well when getting all lables // Used when getting all labels for one task as well when getting all lables
func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*LabelWithTaskID, resultCount int, totalEntries int64, err error) { func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*LabelWithTaskID, resultCount int, totalEntries int64, err error) {
linkShare, isLinkShareAuth := opts.User.(*LinkSharing)
// We still need the task ID when we want to get all labels for a task, but because of this, we get the same label // We still need the task ID when we want to get all labels for a task, but because of this, we get the same label
// multiple times when it is associated to more than one task. // multiple times when it is associated to more than one task.
// Because of this whole thing, we need this extra switch here to only group by Task IDs if needed. // Because of this whole thing, we need this extra switch here to only group by Task IDs if needed.
// Probably not the most ideal solution. // Probably not the most ideataskdetaill solution.
var groupBy = "labels.id,label_tasks.task_id" var groupBy = "labels.id,label_tasks.task_id"
var selectStmt = "labels.*, label_tasks.task_id" var selectStmt = "labels.*, label_tasks.task_id"
if opts.GroupByLabelIDsOnly { if opts.GroupByLabelIDsOnly {
@ -186,20 +189,25 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
// Get all labels associated with these tasks // Get all labels associated with these tasks
var labels []*LabelWithTaskID var labels []*LabelWithTaskID
cond := builder.And(builder.NotNull{"label_tasks.label_id"}) cond := builder.And(builder.NotNull{"label_tasks.label_id"})
if len(opts.TaskIDs) > 0 && opts.GetForUser == 0 { if len(opts.TaskIDs) > 0 && !opts.GetForUser {
cond = builder.And(builder.In("label_tasks.task_id", opts.TaskIDs), cond) cond = builder.And(builder.In("label_tasks.task_id", opts.TaskIDs), cond)
} }
if opts.GetForUser != 0 { if opts.GetForUser {
projects, _, _, err := getRawProjectsForUser(s, &projectOptions{ var projectIDs []int64
user: &user.User{ID: opts.GetForUser}, if isLinkShareAuth {
}) projectIDs = []int64{linkShare.ProjectID}
if err != nil { } else {
return nil, 0, 0, err projects, _, _, err := getRawProjectsForUser(s, &projectOptions{
} user: &user.User{ID: opts.User.GetID()},
projectIDs := make([]int64, 0, len(projects)) })
for _, project := range projects { if err != nil {
projectIDs = append(projectIDs, project.ID) return nil, 0, 0, err
}
projectIDs = make([]int64, 0, len(projects))
for _, project := range projects {
projectIDs = append(projectIDs, project.ID)
}
} }
cond = builder.And(builder.In("label_tasks.task_id", cond = builder.And(builder.In("label_tasks.task_id",
@ -209,8 +217,8 @@ func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*Lab
Where(builder.In("project_id", projectIDs)), Where(builder.In("project_id", projectIDs)),
), cond) ), cond)
} }
if opts.GetUnusedLabels { if opts.GetUnusedLabels && !isLinkShareAuth {
cond = builder.Or(cond, builder.Eq{"labels.created_by_id": opts.User.ID}) cond = builder.Or(cond, builder.Eq{"labels.created_by_id": opts.User.GetID()})
} }
ids := []int64{} ids := []int64{}

View File

@ -419,7 +419,7 @@ func persistLabels(s *xorm.Session, a web.Auth, task *models.Task, labels []*mod
existingLabels, _, _, err := models.GetLabelsByTaskIDs(s, &models.LabelByTaskIDsOptions{ existingLabels, _, _, err := models.GetLabelsByTaskIDs(s, &models.LabelByTaskIDsOptions{
Search: labelTitles, Search: labelTitles,
User: u, User: u,
GetForUser: u.ID, GetForUser: true,
GetUnusedLabels: true, GetUnusedLabels: true,
GroupByLabelIDsOnly: true, GroupByLabelIDsOnly: true,
}) })