feat: rename lists to projects
This commit is contained in:
@ -89,7 +89,7 @@ func NewLinkShareJWTAuthtoken(share *models.LinkSharing) (token string, err erro
|
||||
claims["type"] = AuthTypeLinkShare
|
||||
claims["id"] = share.ID
|
||||
claims["hash"] = share.Hash
|
||||
claims["list_id"] = share.ListID
|
||||
claims["project_id"] = share.ProjectID
|
||||
claims["right"] = share.Right
|
||||
claims["sharedByID"] = share.SharedByID
|
||||
claims["exp"] = exp
|
||||
|
@ -22,7 +22,7 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// Image represents an image which can be used as a list background
|
||||
// Image represents an image which can be used as a project background
|
||||
type Image struct {
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
@ -32,10 +32,10 @@ type Image struct {
|
||||
Info interface{} `json:"info,omitempty"`
|
||||
}
|
||||
|
||||
// Provider represents something that is able to get a list of images and set one of them as background
|
||||
// Provider represents something that is able to get a project of images and set one of them as background
|
||||
type Provider interface {
|
||||
// Search is used to either return a pre-defined list of Image or let the user search for an image
|
||||
// Search is used to either return a pre-defined project of Image or let the user search for an image
|
||||
Search(s *xorm.Session, search string, page int64) (result []*Image, err error)
|
||||
// Set sets an image which was most likely previously obtained by Search as list background
|
||||
Set(s *xorm.Session, image *Image, list *models.List, auth web.Auth) (err error)
|
||||
// Set sets an image which was most likely previously obtained by Search as project background
|
||||
Set(s *xorm.Session, image *Image, project *models.Project, auth web.Auth) (err error)
|
||||
}
|
||||
|
@ -92,38 +92,38 @@ func (bp *BackgroundProvider) SearchBackgrounds(c echo.Context) error {
|
||||
}
|
||||
|
||||
// This function does all kinds of preparations for setting and uploading a background
|
||||
func (bp *BackgroundProvider) setBackgroundPreparations(s *xorm.Session, c echo.Context) (list *models.List, auth web.Auth, err error) {
|
||||
func (bp *BackgroundProvider) setBackgroundPreparations(s *xorm.Session, c echo.Context) (project *models.Project, auth web.Auth, err error) {
|
||||
auth, err = auth2.GetAuthFromClaims(c)
|
||||
if err != nil {
|
||||
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error())
|
||||
}
|
||||
|
||||
listID, err := strconv.ParseInt(c.Param("list"), 10, 64)
|
||||
projectID, err := strconv.ParseInt(c.Param("project"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid list ID: "+err.Error())
|
||||
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid project ID: "+err.Error())
|
||||
}
|
||||
|
||||
// Check if the user has the right to change the list background
|
||||
list = &models.List{ID: listID}
|
||||
can, err := list.CanUpdate(s, auth)
|
||||
// Check if the user has the right to change the project background
|
||||
project = &models.Project{ID: projectID}
|
||||
can, err := project.CanUpdate(s, auth)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !can {
|
||||
log.Infof("Tried to update list background of list %d while not having the rights for it (User: %v)", listID, auth)
|
||||
return list, auth, models.ErrGenericForbidden{}
|
||||
log.Infof("Tried to update project background of project %d while not having the rights for it (User: %v)", projectID, auth)
|
||||
return project, auth, models.ErrGenericForbidden{}
|
||||
}
|
||||
// Load the list
|
||||
list, err = models.GetListSimpleByID(s, list.ID)
|
||||
// Load the project
|
||||
project, err = models.GetProjectSimpleByID(s, project.ID)
|
||||
return
|
||||
}
|
||||
|
||||
// SetBackground sets an Image as list background
|
||||
// SetBackground sets an Image as project background
|
||||
func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
list, auth, err := bp.setBackgroundPreparations(s, c)
|
||||
project, auth, err := bp.setBackgroundPreparations(s, c)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
@ -138,12 +138,12 @@ func (bp *BackgroundProvider) SetBackground(c echo.Context) error {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No or invalid model provided: "+err.Error())
|
||||
}
|
||||
|
||||
err = p.Set(s, image, list, auth)
|
||||
err = p.Set(s, image, project, auth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
return c.JSON(http.StatusOK, list)
|
||||
return c.JSON(http.StatusOK, project)
|
||||
}
|
||||
|
||||
func CreateBlurHash(srcf io.Reader) (hash string, err error) {
|
||||
@ -163,7 +163,7 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
list, auth, err := bp.setBackgroundPreparations(s, c)
|
||||
project, auth, err := bp.setBackgroundPreparations(s, c)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
@ -193,7 +193,7 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."})
|
||||
}
|
||||
|
||||
err = SaveBackgroundFile(s, auth, list, srcf, file.Filename, uint64(file.Size))
|
||||
err = SaveBackgroundFile(s, auth, project, srcf, file.Filename, uint64(file.Size))
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
if files.IsErrFileIsTooLarge(err) {
|
||||
@ -208,10 +208,10 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, list)
|
||||
return c.JSON(http.StatusOK, project)
|
||||
}
|
||||
|
||||
func SaveBackgroundFile(s *xorm.Session, auth web.Auth, list *models.List, srcf io.ReadSeeker, filename string, filesize uint64) (err error) {
|
||||
func SaveBackgroundFile(s *xorm.Session, auth web.Auth, project *models.Project, srcf io.ReadSeeker, filename string, filesize uint64) (err error) {
|
||||
_, _ = srcf.Seek(0, io.SeekStart)
|
||||
f, err := files.Create(srcf, filename, filesize, auth)
|
||||
if err != nil {
|
||||
@ -220,7 +220,7 @@ func SaveBackgroundFile(s *xorm.Session, auth web.Auth, list *models.List, srcf
|
||||
|
||||
// Generate a blurHash
|
||||
_, _ = srcf.Seek(0, io.SeekStart)
|
||||
list.BackgroundBlurHash, err = CreateBlurHash(srcf)
|
||||
project.BackgroundBlurHash, err = CreateBlurHash(srcf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -228,68 +228,68 @@ func SaveBackgroundFile(s *xorm.Session, auth web.Auth, list *models.List, srcf
|
||||
// Save it
|
||||
p := upload.Provider{}
|
||||
img := &background.Image{ID: strconv.FormatInt(f.ID, 10)}
|
||||
err = p.Set(s, img, list, auth)
|
||||
err = p.Set(s, img, project, auth)
|
||||
return err
|
||||
}
|
||||
|
||||
func checkListBackgroundRights(s *xorm.Session, c echo.Context) (list *models.List, auth web.Auth, err error) {
|
||||
func checkProjectBackgroundRights(s *xorm.Session, c echo.Context) (project *models.Project, auth web.Auth, err error) {
|
||||
auth, err = auth2.GetAuthFromClaims(c)
|
||||
if err != nil {
|
||||
return nil, auth, echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error())
|
||||
}
|
||||
|
||||
listID, err := strconv.ParseInt(c.Param("list"), 10, 64)
|
||||
projectID, err := strconv.ParseInt(c.Param("project"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, auth, echo.NewHTTPError(http.StatusBadRequest, "Invalid list ID: "+err.Error())
|
||||
return nil, auth, echo.NewHTTPError(http.StatusBadRequest, "Invalid project ID: "+err.Error())
|
||||
}
|
||||
|
||||
// Check if a background for this list exists + Rights
|
||||
list = &models.List{ID: listID}
|
||||
can, _, err := list.CanRead(s, auth)
|
||||
// Check if a background for this project exists + Rights
|
||||
project = &models.Project{ID: projectID}
|
||||
can, _, err := project.CanRead(s, auth)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return nil, auth, handler.HandleHTTPError(err, c)
|
||||
}
|
||||
if !can {
|
||||
_ = s.Rollback()
|
||||
log.Infof("Tried to get list background of list %d while not having the rights for it (User: %v)", listID, auth)
|
||||
log.Infof("Tried to get project background of project %d while not having the rights for it (User: %v)", projectID, auth)
|
||||
return nil, auth, echo.NewHTTPError(http.StatusForbidden)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetListBackground serves a previously set background from a list
|
||||
// GetProjectBackground serves a previously set background from a project
|
||||
// It has no knowledge of the provider that was responsible for setting the background.
|
||||
// @Summary Get the list background
|
||||
// @Description Get the list background of a specific list. **Returns json on error.**
|
||||
// @tags list
|
||||
// @Summary Get the project background
|
||||
// @Description Get the project background of a specific project. **Returns json on error.**
|
||||
// @tags project
|
||||
// @Produce octet-stream
|
||||
// @Param id path int true "List ID"
|
||||
// @Param id path int true "Project ID"
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {} string "The list background file."
|
||||
// @Failure 403 {object} models.Message "No access to this list."
|
||||
// @Failure 404 {object} models.Message "The list does not exist."
|
||||
// @Success 200 {} string "The project background file."
|
||||
// @Failure 403 {object} models.Message "No access to this project."
|
||||
// @Failure 404 {object} models.Message "The project does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{id}/background [get]
|
||||
func GetListBackground(c echo.Context) error {
|
||||
// @Router /projects/{id}/background [get]
|
||||
func GetProjectBackground(c echo.Context) error {
|
||||
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
list, _, err := checkListBackgroundRights(s, c)
|
||||
project, _, err := checkProjectBackgroundRights(s, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if list.BackgroundFileID == 0 {
|
||||
if project.BackgroundFileID == 0 {
|
||||
_ = s.Rollback()
|
||||
return echo.NotFoundHandler(c)
|
||||
}
|
||||
|
||||
// Get the file
|
||||
bgFile := &files.File{
|
||||
ID: list.BackgroundFileID,
|
||||
ID: project.BackgroundFileID,
|
||||
}
|
||||
if err := bgFile.LoadFileByID(); err != nil {
|
||||
_ = s.Rollback()
|
||||
@ -320,23 +320,23 @@ func GetListBackground(c echo.Context) error {
|
||||
return c.Stream(http.StatusOK, "image/jpg", bgFile.File)
|
||||
}
|
||||
|
||||
// RemoveListBackground removes a list background, no matter the background provider
|
||||
// @Summary Remove a list background
|
||||
// @Description Removes a previously set list background, regardless of the list provider used to set the background. It does not throw an error if the list does not have a background.
|
||||
// @tags list
|
||||
// RemoveProjectBackground removes a project background, no matter the background provider
|
||||
// @Summary Remove a project background
|
||||
// @Description Removes a previously set project background, regardless of the project provider used to set the background. It does not throw an error if the project does not have a background.
|
||||
// @tags project
|
||||
// @Produce json
|
||||
// @Param id path int true "List ID"
|
||||
// @Param id path int true "Project ID"
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} models.List "The list"
|
||||
// @Failure 403 {object} models.Message "No access to this list."
|
||||
// @Failure 404 {object} models.Message "The list does not exist."
|
||||
// @Success 200 {object} models.Project "The project"
|
||||
// @Failure 403 {object} models.Message "No access to this project."
|
||||
// @Failure 404 {object} models.Message "The project does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{id}/background [delete]
|
||||
func RemoveListBackground(c echo.Context) error {
|
||||
// @Router /projects/{id}/background [delete]
|
||||
func RemoveProjectBackground(c echo.Context) error {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
list, auth, err := checkListBackgroundRights(s, c)
|
||||
project, auth, err := checkProjectBackgroundRights(s, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -346,13 +346,13 @@ func RemoveListBackground(c echo.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
list.BackgroundFileID = 0
|
||||
list.BackgroundInformation = nil
|
||||
list.BackgroundBlurHash = ""
|
||||
err = models.UpdateList(s, list, auth, true)
|
||||
project.BackgroundFileID = 0
|
||||
project.BackgroundInformation = nil
|
||||
project.BackgroundBlurHash = ""
|
||||
err = models.UpdateProject(s, project, auth, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, list)
|
||||
return c.JSON(http.StatusOK, project)
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ func unsplashImage(url string, c echo.Context) error {
|
||||
// ProxyUnsplashImage proxies a thumbnail from unsplash for privacy reasons.
|
||||
// @Summary Get an unsplash image
|
||||
// @Description Get an unsplash image. **Returns json on error.**
|
||||
// @tags list
|
||||
// @tags project
|
||||
// @Produce octet-stream
|
||||
// @Param image path int true "Unsplash Image ID"
|
||||
// @Security JWTKeyAuth
|
||||
@ -65,7 +65,7 @@ func ProxyUnsplashImage(c echo.Context) error {
|
||||
// ProxyUnsplashThumb proxies a thumbnail from unsplash for privacy reasons.
|
||||
// @Summary Get an unsplash thumbnail image
|
||||
// @Description Get an unsplash thumbnail image. The thumbnail is cropped to a max width of 200px. **Returns json on error.**
|
||||
// @tags list
|
||||
// @tags project
|
||||
// @Produce octet-stream
|
||||
// @Param image path int true "Unsplash Image ID"
|
||||
// @Security JWTKeyAuth
|
||||
|
@ -142,8 +142,8 @@ func getUnsplashPhotoInfoByID(photoID string) (photo *Photo, err error) {
|
||||
|
||||
// Search is the implementation to search on unsplash
|
||||
// @Summary Search for a background from unsplash
|
||||
// @Description Search for a list background from unsplash
|
||||
// @tags list
|
||||
// @Description Search for a project background from unsplash
|
||||
// @tags project
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param s query string false "Search backgrounds from unsplash with this search term."
|
||||
@ -232,21 +232,21 @@ func (p *Provider) Search(s *xorm.Session, search string, page int64) (result []
|
||||
return
|
||||
}
|
||||
|
||||
// Set sets an unsplash photo as list background
|
||||
// @Summary Set an unsplash photo as list background
|
||||
// @Description Sets a photo from unsplash as list background.
|
||||
// @tags list
|
||||
// Set sets an unsplash photo as project background
|
||||
// @Summary Set an unsplash photo as project background
|
||||
// @Description Sets a photo from unsplash as project background.
|
||||
// @tags project
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "List ID"
|
||||
// @Param list body background.Image true "The image you want to set as background"
|
||||
// @Success 200 {object} models.List "The background has been successfully set."
|
||||
// @Param id path int true "Project ID"
|
||||
// @Param project body background.Image true "The image you want to set as background"
|
||||
// @Success 200 {object} models.Project "The background has been successfully set."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid image object provided."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the list"
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the project"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{id}/backgrounds/unsplash [post]
|
||||
func (p *Provider) Set(s *xorm.Session, image *background.Image, list *models.List, auth web.Auth) (err error) {
|
||||
// @Router /projects/{id}/backgrounds/unsplash [post]
|
||||
func (p *Provider) Set(s *xorm.Session, image *background.Image, project *models.Project, auth web.Auth) (err error) {
|
||||
|
||||
// Find the photo
|
||||
photo, err := getUnsplashPhotoInfoByID(image.ID)
|
||||
@ -289,13 +289,13 @@ func (p *Provider) Set(s *xorm.Session, image *background.Image, list *models.Li
|
||||
}
|
||||
|
||||
// Remove the old background if one exists
|
||||
if list.BackgroundFileID != 0 {
|
||||
file := files.File{ID: list.BackgroundFileID}
|
||||
if project.BackgroundFileID != 0 {
|
||||
file := files.File{ID: project.BackgroundFileID}
|
||||
if err := file.Delete(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := models.RemoveUnsplashPhoto(s, list.BackgroundFileID); err != nil {
|
||||
if err := models.RemoveUnsplashPhoto(s, project.BackgroundFileID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -313,12 +313,12 @@ func (p *Provider) Set(s *xorm.Session, image *background.Image, list *models.Li
|
||||
}
|
||||
log.Debugf("Saved unsplash photo %s as file %d with new entry %d", image.ID, file.ID, unsplashPhoto.ID)
|
||||
|
||||
// Set the file in the list
|
||||
list.BackgroundFileID = file.ID
|
||||
list.BackgroundInformation = unsplashPhoto
|
||||
// Set the file in the project
|
||||
project.BackgroundFileID = file.ID
|
||||
project.BackgroundInformation = unsplashPhoto
|
||||
|
||||
// Set it as the list background
|
||||
return models.SetListBackground(s, list.ID, file, photo.BlurHash)
|
||||
// Set it as the project background
|
||||
return models.SetProjectBackground(s, project.ID, file, photo.BlurHash)
|
||||
}
|
||||
|
||||
// Pingback pings the unsplash api if an unsplash photo has been accessed.
|
||||
|
@ -37,24 +37,24 @@ func (p *Provider) Search(s *xorm.Session, search string, page int64) (result []
|
||||
}
|
||||
|
||||
// Set handles setting a background through a file upload
|
||||
// @Summary Upload a list background
|
||||
// @Description Upload a list background.
|
||||
// @tags list
|
||||
// @Summary Upload a project background
|
||||
// @Description Upload a project background.
|
||||
// @tags project
|
||||
// @Accept mpfd
|
||||
// @Produce json
|
||||
// @Param id path int true "List ID"
|
||||
// @Param id path int true "Project ID"
|
||||
// @Param background formData string true "The file as single file."
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} models.Message "The background was set successfully."
|
||||
// @Failure 400 {object} models.Message "File is no image."
|
||||
// @Failure 403 {object} models.Message "No access to the list."
|
||||
// @Failure 403 {object} models.Message "No access to the project."
|
||||
// @Failure 403 {object} models.Message "File too large."
|
||||
// @Failure 404 {object} models.Message "The list does not exist."
|
||||
// @Failure 404 {object} models.Message "The project does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{id}/backgrounds/upload [put]
|
||||
func (p *Provider) Set(s *xorm.Session, img *background.Image, list *models.List, auth web.Auth) (err error) {
|
||||
// @Router /projects/{id}/backgrounds/upload [put]
|
||||
func (p *Provider) Set(s *xorm.Session, img *background.Image, project *models.Project, auth web.Auth) (err error) {
|
||||
// Remove the old background if one exists
|
||||
err = list.DeleteBackgroundFileIfExists()
|
||||
err = project.DeleteBackgroundFileIfExists()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -65,7 +65,7 @@ func (p *Provider) Set(s *xorm.Session, img *background.Image, list *models.List
|
||||
return
|
||||
}
|
||||
|
||||
list.BackgroundInformation = &models.ListBackgroundType{Type: models.ListBackgroundUpload}
|
||||
project.BackgroundInformation = &models.ProjectBackgroundType{Type: models.ProjectBackgroundUpload}
|
||||
|
||||
return models.SetListBackground(s, list.ID, file, list.BackgroundBlurHash)
|
||||
return models.SetProjectBackground(s, project.ID, file, project.BackgroundBlurHash)
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import (
|
||||
|
||||
// InsertFromStructure takes a fully nested Vikunja data structure and a user and then creates everything for this user
|
||||
// (Namespaces, tasks, etc. Even attachments and relations.)
|
||||
func InsertFromStructure(str []*models.NamespaceWithListsAndTasks, user *user.User) (err error) {
|
||||
func InsertFromStructure(str []*models.NamespaceWithProjectsAndTasks, user *user.User) (err error) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
@ -45,13 +45,13 @@ func InsertFromStructure(str []*models.NamespaceWithListsAndTasks, user *user.Us
|
||||
return s.Commit()
|
||||
}
|
||||
|
||||
func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTasks, user *user.User) (err error) {
|
||||
func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithProjectsAndTasks, user *user.User) (err error) {
|
||||
|
||||
log.Debugf("[creating structure] Creating %d namespaces", len(str))
|
||||
|
||||
labels := make(map[string]*models.Label)
|
||||
|
||||
archivedLists := []int64{}
|
||||
archivedProjects := []int64{}
|
||||
archivedNamespaces := []int64{}
|
||||
|
||||
// Create all namespaces
|
||||
@ -75,18 +75,18 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
|
||||
}
|
||||
|
||||
log.Debugf("[creating structure] Created namespace %d", n.ID)
|
||||
log.Debugf("[creating structure] Creating %d lists", len(n.Lists))
|
||||
log.Debugf("[creating structure] Creating %d projects", len(n.Projects))
|
||||
|
||||
// Create all lists
|
||||
for _, l := range n.Lists {
|
||||
// The tasks and bucket slices are going to be reset during the creation of the list so we rescue it here
|
||||
// to be able to still loop over them aftere the list was created.
|
||||
// Create all projects
|
||||
for _, l := range n.Projects {
|
||||
// The tasks and bucket slices are going to be reset during the creation of the project so we rescue it here
|
||||
// to be able to still loop over them aftere the project was created.
|
||||
tasks := l.Tasks
|
||||
originalBuckets := l.Buckets
|
||||
originalBackgroundInformation := l.BackgroundInformation
|
||||
needsDefaultBucket := false
|
||||
|
||||
// Saving the archived status to archive the list again after creating it
|
||||
// Saving the archived status to archive the project again after creating it
|
||||
var wasArchived bool
|
||||
if l.IsArchived {
|
||||
wasArchived = true
|
||||
@ -101,24 +101,24 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
|
||||
}
|
||||
|
||||
if wasArchived {
|
||||
archivedLists = append(archivedLists, l.ID)
|
||||
archivedProjects = append(archivedProjects, l.ID)
|
||||
}
|
||||
|
||||
log.Debugf("[creating structure] Created list %d", l.ID)
|
||||
log.Debugf("[creating structure] Created project %d", l.ID)
|
||||
|
||||
bf, is := originalBackgroundInformation.(*bytes.Buffer)
|
||||
if is {
|
||||
|
||||
backgroundFile := bytes.NewReader(bf.Bytes())
|
||||
|
||||
log.Debugf("[creating structure] Creating a background file for list %d", l.ID)
|
||||
log.Debugf("[creating structure] Creating a background file for project %d", l.ID)
|
||||
|
||||
err = handler.SaveBackgroundFile(s, user, &l.List, backgroundFile, "", uint64(backgroundFile.Len()))
|
||||
err = handler.SaveBackgroundFile(s, user, &l.Project, backgroundFile, "", uint64(backgroundFile.Len()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("[creating structure] Created a background file for list %d", l.ID)
|
||||
log.Debugf("[creating structure] Created a background file for project %d", l.ID)
|
||||
}
|
||||
|
||||
// Create all buckets
|
||||
@ -129,7 +129,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
|
||||
for _, bucket := range originalBuckets {
|
||||
oldID := bucket.ID
|
||||
bucket.ID = 0 // We want a new id
|
||||
bucket.ListID = l.ID
|
||||
bucket.ProjectID = l.ID
|
||||
err = bucket.Create(s, user)
|
||||
if err != nil {
|
||||
return
|
||||
@ -157,7 +157,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
|
||||
for _, t := range tasks {
|
||||
setBucketOrDefault(&t.Task)
|
||||
|
||||
t.ListID = l.ID
|
||||
t.ProjectID = l.ID
|
||||
err = t.Create(s, user)
|
||||
if err != nil {
|
||||
return
|
||||
@ -179,7 +179,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
|
||||
// First create the related tasks if they do not exist
|
||||
if rt.ID == 0 {
|
||||
setBucketOrDefault(rt)
|
||||
rt.ListID = t.ListID
|
||||
rt.ProjectID = t.ProjectID
|
||||
err = rt.Create(s, user)
|
||||
if err != nil {
|
||||
return
|
||||
@ -263,7 +263,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
|
||||
|
||||
// All tasks brought their own bucket with them, therefore the newly created default bucket is just extra space
|
||||
if !needsDefaultBucket {
|
||||
b := &models.Bucket{ListID: l.ID}
|
||||
b := &models.Bucket{ProjectID: l.ID}
|
||||
bucketsIn, _, _, err := b.ReadAll(s, user, "", 1, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -280,11 +280,11 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
|
||||
}
|
||||
}
|
||||
|
||||
if len(archivedLists) > 0 {
|
||||
if len(archivedProjects) > 0 {
|
||||
_, err = s.
|
||||
Cols("is_archived").
|
||||
In("id", archivedLists).
|
||||
Update(&models.List{IsArchived: true})
|
||||
In("id", archivedProjects).
|
||||
Update(&models.Project{IsArchived: true})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -32,16 +32,16 @@ func TestInsertFromStructure(t *testing.T) {
|
||||
}
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
testStructure := []*models.NamespaceWithListsAndTasks{
|
||||
testStructure := []*models.NamespaceWithProjectsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Test1",
|
||||
Description: "Lorem Ipsum",
|
||||
},
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
Projects: []*models.ProjectWithTasksAndBuckets{
|
||||
{
|
||||
List: models.List{
|
||||
Title: "Testlist1",
|
||||
Project: models.Project{
|
||||
Title: "Testproject1",
|
||||
Description: "Something",
|
||||
},
|
||||
Buckets: []*models.Bucket{
|
||||
@ -133,19 +133,19 @@ func TestInsertFromStructure(t *testing.T) {
|
||||
"title": testStructure[0].Namespace.Title,
|
||||
"description": testStructure[0].Namespace.Description,
|
||||
}, false)
|
||||
db.AssertExists(t, "lists", map[string]interface{}{
|
||||
"title": testStructure[0].Lists[0].Title,
|
||||
"description": testStructure[0].Lists[0].Description,
|
||||
db.AssertExists(t, "projects", map[string]interface{}{
|
||||
"title": testStructure[0].Projects[0].Title,
|
||||
"description": testStructure[0].Projects[0].Description,
|
||||
}, false)
|
||||
db.AssertExists(t, "tasks", map[string]interface{}{
|
||||
"title": testStructure[0].Lists[0].Tasks[5].Title,
|
||||
"bucket_id": testStructure[0].Lists[0].Buckets[0].ID,
|
||||
"title": testStructure[0].Projects[0].Tasks[5].Title,
|
||||
"bucket_id": testStructure[0].Projects[0].Buckets[0].ID,
|
||||
}, false)
|
||||
db.AssertMissing(t, "tasks", map[string]interface{}{
|
||||
"title": testStructure[0].Lists[0].Tasks[6].Title,
|
||||
"title": testStructure[0].Projects[0].Tasks[6].Title,
|
||||
"bucket_id": 1111, // No task with that bucket should exist
|
||||
})
|
||||
assert.NotEqual(t, 0, testStructure[0].Lists[0].Tasks[0].BucketID) // Should get the default bucket
|
||||
assert.NotEqual(t, 0, testStructure[0].Lists[0].Tasks[6].BucketID) // Should get the default bucket
|
||||
assert.NotEqual(t, 0, testStructure[0].Projects[0].Tasks[0].BucketID) // Should get the default bucket
|
||||
assert.NotEqual(t, 0, testStructure[0].Projects[0].Tasks[6].BucketID) // Should get the default bucket
|
||||
})
|
||||
}
|
||||
|
@ -98,19 +98,19 @@ type tasksResponse struct {
|
||||
Value []*task `json:"value"`
|
||||
}
|
||||
|
||||
type list struct {
|
||||
ID string `json:"id"`
|
||||
OdataEtag string `json:"@odata.etag"`
|
||||
DisplayName string `json:"displayName"`
|
||||
IsOwner bool `json:"isOwner"`
|
||||
IsShared bool `json:"isShared"`
|
||||
WellknownListName string `json:"wellknownListName"`
|
||||
Tasks []*task `json:"-"` // This field does not exist in the api, we're just using it to return a structure with everything at once
|
||||
type project struct {
|
||||
ID string `json:"id"`
|
||||
OdataEtag string `json:"@odata.etag"`
|
||||
DisplayName string `json:"displayName"`
|
||||
IsOwner bool `json:"isOwner"`
|
||||
IsShared bool `json:"isShared"`
|
||||
WellknownProjectName string `json:"wellknownProjectName"`
|
||||
Tasks []*task `json:"-"` // This field does not exist in the api, we're just using it to return a structure with everything at once
|
||||
}
|
||||
|
||||
type listsResponse struct {
|
||||
OdataContext string `json:"@odata.context"`
|
||||
Value []*list `json:"value"`
|
||||
type projectsResponse struct {
|
||||
OdataContext string `json:"@odata.context"`
|
||||
Value []*project `json:"value"`
|
||||
}
|
||||
|
||||
func (dtt *dateTimeTimeZone) toTime() (t time.Time, err error) {
|
||||
@ -213,22 +213,22 @@ func makeAuthenticatedGetRequest(token, urlPart string, v interface{}) error {
|
||||
return json.Unmarshal(buf.Bytes(), v)
|
||||
}
|
||||
|
||||
func getMicrosoftTodoData(token string) (microsoftTodoData []*list, err error) {
|
||||
func getMicrosoftTodoData(token string) (microsoftTodoData []*project, err error) {
|
||||
|
||||
microsoftTodoData = []*list{}
|
||||
microsoftTodoData = []*project{}
|
||||
|
||||
lists := &listsResponse{}
|
||||
err = makeAuthenticatedGetRequest(token, "lists", lists)
|
||||
projects := &projectsResponse{}
|
||||
err = makeAuthenticatedGetRequest(token, "projects", projects)
|
||||
if err != nil {
|
||||
log.Errorf("[Microsoft Todo Migration] Could not get lists: %s", err)
|
||||
log.Errorf("[Microsoft Todo Migration] Could not get projects: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Got %d lists", len(lists.Value))
|
||||
log.Debugf("[Microsoft Todo Migration] Got %d projects", len(projects.Value))
|
||||
|
||||
for _, list := range lists.Value {
|
||||
link := "lists/" + list.ID + "/tasks"
|
||||
list.Tasks = []*task{}
|
||||
for _, project := range projects.Value {
|
||||
link := "projects/" + project.ID + "/tasks"
|
||||
project.Tasks = []*task{}
|
||||
|
||||
// Microsoft's Graph API has pagination, so we're going through all pages to get all tasks
|
||||
for {
|
||||
@ -236,13 +236,13 @@ func getMicrosoftTodoData(token string) (microsoftTodoData []*list, err error) {
|
||||
|
||||
err = makeAuthenticatedGetRequest(token, link, tr)
|
||||
if err != nil {
|
||||
log.Errorf("[Microsoft Todo Migration] Could not get tasks for list %s: %s", list.ID, err)
|
||||
log.Errorf("[Microsoft Todo Migration] Could not get tasks for project %s: %s", project.ID, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Got %d tasks for list %s", len(tr.Value), list.ID)
|
||||
log.Debugf("[Microsoft Todo Migration] Got %d tasks for project %s", len(tr.Value), project.ID)
|
||||
|
||||
list.Tasks = append(list.Tasks, tr.Value...)
|
||||
project.Tasks = append(project.Tasks, tr.Value...)
|
||||
|
||||
if tr.Nextlink == "" {
|
||||
break
|
||||
@ -251,35 +251,35 @@ func getMicrosoftTodoData(token string) (microsoftTodoData []*list, err error) {
|
||||
link = strings.ReplaceAll(tr.Nextlink, apiPrefix, "")
|
||||
}
|
||||
|
||||
microsoftTodoData = append(microsoftTodoData, list)
|
||||
microsoftTodoData = append(microsoftTodoData, project)
|
||||
}
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Got all tasks for %d lists", len(lists.Value))
|
||||
log.Debugf("[Microsoft Todo Migration] Got all tasks for %d projects", len(projects.Value))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.NamespaceWithListsAndTasks, err error) {
|
||||
func convertMicrosoftTodoData(todoData []*project) (vikunjsStructure []*models.NamespaceWithProjectsAndTasks, err error) {
|
||||
|
||||
// One namespace with all lists
|
||||
vikunjsStructure = []*models.NamespaceWithListsAndTasks{
|
||||
// One namespace with all projects
|
||||
vikunjsStructure = []*models.NamespaceWithProjectsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from Microsoft Todo",
|
||||
},
|
||||
Lists: []*models.ListWithTasksAndBuckets{},
|
||||
Projects: []*models.ProjectWithTasksAndBuckets{},
|
||||
},
|
||||
}
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Converting %d lists", len(todoData))
|
||||
log.Debugf("[Microsoft Todo Migration] Converting %d projects", len(todoData))
|
||||
|
||||
for _, l := range todoData {
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Converting list %s", l.ID)
|
||||
log.Debugf("[Microsoft Todo Migration] Converting project %s", l.ID)
|
||||
|
||||
// Lists only with title
|
||||
list := &models.ListWithTasksAndBuckets{
|
||||
List: models.List{
|
||||
// Projects only with title
|
||||
project := &models.ProjectWithTasksAndBuckets{
|
||||
Project: models.Project{
|
||||
Title: l.DisplayName,
|
||||
},
|
||||
}
|
||||
@ -358,19 +358,19 @@ func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.Name
|
||||
}
|
||||
}
|
||||
|
||||
list.Tasks = append(list.Tasks, &models.TaskWithComments{Task: *task})
|
||||
project.Tasks = append(project.Tasks, &models.TaskWithComments{Task: *task})
|
||||
log.Debugf("[Microsoft Todo Migration] Done converted %d tasks", len(l.Tasks))
|
||||
}
|
||||
|
||||
vikunjsStructure[0].Lists = append(vikunjsStructure[0].Lists, list)
|
||||
log.Debugf("[Microsoft Todo Migration] Done converting list %s", l.ID)
|
||||
vikunjsStructure[0].Projects = append(vikunjsStructure[0].Projects, project)
|
||||
log.Debugf("[Microsoft Todo Migration] Done converting project %s", l.ID)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Migrate gets all tasks from Microsoft Todo for a user and puts them into vikunja
|
||||
// @Summary Migrate all lists, tasks etc. from Microsoft Todo
|
||||
// @Summary Migrate all projects, tasks etc. from Microsoft Todo
|
||||
// @Description Migrates all tasklinsts, tasks, notes and reminders from Microsoft Todo to Vikunja.
|
||||
// @tags migration
|
||||
// @Accept json
|
||||
|
@ -35,9 +35,9 @@ func TestConverting(t *testing.T) {
|
||||
testtimeTime, err := time.Parse(time.RFC3339Nano, "2020-12-18T03:00:00.4770000Z")
|
||||
assert.NoError(t, err)
|
||||
|
||||
microsoftTodoData := []*list{
|
||||
microsoftTodoData := []*project{
|
||||
{
|
||||
DisplayName: "List 1",
|
||||
DisplayName: "Project 1",
|
||||
Tasks: []*task{
|
||||
{
|
||||
Title: "Task 1",
|
||||
@ -88,7 +88,7 @@ func TestConverting(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
DisplayName: "List 2",
|
||||
DisplayName: "Project 2",
|
||||
Tasks: []*task{
|
||||
{
|
||||
Title: "Task 1",
|
||||
@ -102,15 +102,15 @@ func TestConverting(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
expectedHierachie := []*models.NamespaceWithListsAndTasks{
|
||||
expectedHierachie := []*models.NamespaceWithProjectsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from Microsoft Todo",
|
||||
},
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
Projects: []*models.ProjectWithTasksAndBuckets{
|
||||
{
|
||||
List: models.List{
|
||||
Title: "List 1",
|
||||
Project: models.Project{
|
||||
Title: "Project 1",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
@ -162,8 +162,8 @@ func TestConverting(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
List: models.List{
|
||||
Title: "List 2",
|
||||
Project: models.Project{
|
||||
Title: "Project 2",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
|
@ -43,7 +43,7 @@ type Migrator interface {
|
||||
// FileMigrator handles importing Vikunja data from a file. The implementation of it determines the format.
|
||||
type FileMigrator interface {
|
||||
MigratorName
|
||||
// Migrate is the interface used to migrate a user's tasks, list and other things from a file to vikunja.
|
||||
// Migrate is the interface used to migrate a user's tasks, project and other things from a file to vikunja.
|
||||
// The user object is the user who's tasks will be migrated.
|
||||
Migrate(user *user.User, file io.ReaderAt, size int64) error
|
||||
}
|
||||
|
@ -105,21 +105,21 @@ func parseDurationPart(value string, unit time.Duration) time.Duration {
|
||||
return 0
|
||||
}
|
||||
|
||||
func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.NamespaceWithListsAndTasks) {
|
||||
namespace := &models.NamespaceWithListsAndTasks{
|
||||
func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.NamespaceWithProjectsAndTasks) {
|
||||
namespace := &models.NamespaceWithProjectsAndTasks{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from TickTick",
|
||||
},
|
||||
Lists: []*models.ListWithTasksAndBuckets{},
|
||||
Projects: []*models.ProjectWithTasksAndBuckets{},
|
||||
}
|
||||
|
||||
lists := make(map[string]*models.ListWithTasksAndBuckets)
|
||||
projects := make(map[string]*models.ProjectWithTasksAndBuckets)
|
||||
for _, t := range tasks {
|
||||
_, has := lists[t.ListName]
|
||||
_, has := projects[t.ProjectName]
|
||||
if !has {
|
||||
lists[t.ListName] = &models.ListWithTasksAndBuckets{
|
||||
List: models.List{
|
||||
Title: t.ListName,
|
||||
projects[t.ProjectName] = &models.ProjectWithTasksAndBuckets{
|
||||
Project: models.Project{
|
||||
Title: t.ProjectName,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -158,18 +158,18 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace
|
||||
}
|
||||
}
|
||||
|
||||
lists[t.ListName].Tasks = append(lists[t.ListName].Tasks, task)
|
||||
projects[t.ProjectName].Tasks = append(projects[t.ProjectName].Tasks, task)
|
||||
}
|
||||
|
||||
for _, l := range lists {
|
||||
namespace.Lists = append(namespace.Lists, l)
|
||||
for _, l := range projects {
|
||||
namespace.Projects = append(namespace.Projects, l)
|
||||
}
|
||||
|
||||
sort.Slice(namespace.Lists, func(i, j int) bool {
|
||||
return namespace.Lists[i].Title < namespace.Lists[j].Title
|
||||
sort.Slice(namespace.Projects, func(i, j int) bool {
|
||||
return namespace.Projects[i].Title < namespace.Projects[j].Title
|
||||
})
|
||||
|
||||
return []*models.NamespaceWithListsAndTasks{namespace}
|
||||
return []*models.NamespaceWithProjectsAndTasks{namespace}
|
||||
}
|
||||
|
||||
// Name is used to get the name of the ticktick migration - we're using the docs here to annotate the status route.
|
||||
@ -202,7 +202,7 @@ func newLineSkipDecoder(r io.Reader, linesToSkip int) gocsv.SimpleDecoder {
|
||||
}
|
||||
|
||||
// Migrate takes a ticktick export, parses it and imports everything in it into Vikunja.
|
||||
// @Summary Import all lists, tasks etc. from a TickTick backup export
|
||||
// @Summary Import all projects, tasks etc. from a TickTick backup export
|
||||
// @Description Imports all projects, tasks, notes, reminders, subtasks and files from a TickTick backup export into Vikunja.
|
||||
// @tags migration
|
||||
// @Accept json
|
||||
|
@ -40,76 +40,76 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
|
||||
|
||||
tickTickTasks := []*tickTickTask{
|
||||
{
|
||||
TaskID: 1,
|
||||
ParentID: 0,
|
||||
ListName: "List 1",
|
||||
Title: "Test task 1",
|
||||
Tags: []string{"label1", "label2"},
|
||||
Content: "Lorem Ipsum Dolor sit amet",
|
||||
StartDate: time1,
|
||||
DueDate: time2,
|
||||
Reminder: duration,
|
||||
Repeat: "FREQ=WEEKLY;INTERVAL=1;UNTIL=20190117T210000Z",
|
||||
Status: "0",
|
||||
Order: -1099511627776,
|
||||
TaskID: 1,
|
||||
ParentID: 0,
|
||||
ProjectName: "Project 1",
|
||||
Title: "Test task 1",
|
||||
Tags: []string{"label1", "label2"},
|
||||
Content: "Lorem Ipsum Dolor sit amet",
|
||||
StartDate: time1,
|
||||
DueDate: time2,
|
||||
Reminder: duration,
|
||||
Repeat: "FREQ=WEEKLY;INTERVAL=1;UNTIL=20190117T210000Z",
|
||||
Status: "0",
|
||||
Order: -1099511627776,
|
||||
},
|
||||
{
|
||||
TaskID: 2,
|
||||
ParentID: 1,
|
||||
ListName: "List 1",
|
||||
ProjectName: "Project 1",
|
||||
Title: "Test task 2",
|
||||
Status: "1",
|
||||
CompletedTime: time3,
|
||||
Order: -1099511626,
|
||||
},
|
||||
{
|
||||
TaskID: 3,
|
||||
ParentID: 0,
|
||||
ListName: "List 1",
|
||||
Title: "Test task 3",
|
||||
Tags: []string{"label1", "label2", "other label"},
|
||||
StartDate: time1,
|
||||
DueDate: time2,
|
||||
Reminder: duration,
|
||||
Status: "0",
|
||||
Order: -109951627776,
|
||||
TaskID: 3,
|
||||
ParentID: 0,
|
||||
ProjectName: "Project 1",
|
||||
Title: "Test task 3",
|
||||
Tags: []string{"label1", "label2", "other label"},
|
||||
StartDate: time1,
|
||||
DueDate: time2,
|
||||
Reminder: duration,
|
||||
Status: "0",
|
||||
Order: -109951627776,
|
||||
},
|
||||
{
|
||||
TaskID: 4,
|
||||
ParentID: 0,
|
||||
ListName: "List 2",
|
||||
Title: "Test task 4",
|
||||
Status: "0",
|
||||
Order: -109951627777,
|
||||
TaskID: 4,
|
||||
ParentID: 0,
|
||||
ProjectName: "Project 2",
|
||||
Title: "Test task 4",
|
||||
Status: "0",
|
||||
Order: -109951627777,
|
||||
},
|
||||
}
|
||||
|
||||
vikunjaTasks := convertTickTickToVikunja(tickTickTasks)
|
||||
|
||||
assert.Len(t, vikunjaTasks, 1)
|
||||
assert.Len(t, vikunjaTasks[0].Lists, 2)
|
||||
assert.Len(t, vikunjaTasks[0].Projects, 2)
|
||||
|
||||
assert.Len(t, vikunjaTasks[0].Lists[0].Tasks, 3)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Title, tickTickTasks[0].ListName)
|
||||
assert.Len(t, vikunjaTasks[0].Projects[0].Tasks, 3)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Title, tickTickTasks[0].ProjectName)
|
||||
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Title, tickTickTasks[0].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Description, tickTickTasks[0].Content)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].StartDate, tickTickTasks[0].StartDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].EndDate, tickTickTasks[0].DueDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].DueDate, tickTickTasks[0].DueDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Labels, []*models.Label{
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Title, tickTickTasks[0].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Description, tickTickTasks[0].Content)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].StartDate, tickTickTasks[0].StartDate)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].EndDate, tickTickTasks[0].DueDate)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].DueDate, tickTickTasks[0].DueDate)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Labels, []*models.Label{
|
||||
{Title: "label1"},
|
||||
{Title: "label2"},
|
||||
})
|
||||
//assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Reminders, tickTickTasks[0].) // TODO
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Position, tickTickTasks[0].Order)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Done, false)
|
||||
//assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Reminders, tickTickTasks[0].) // TODO
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Position, tickTickTasks[0].Order)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Done, false)
|
||||
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Title, tickTickTasks[1].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Position, tickTickTasks[1].Order)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Done, true)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].RelatedTasks, models.RelatedTaskMap{
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[1].Title, tickTickTasks[1].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[1].Position, tickTickTasks[1].Order)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[1].Done, true)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[1].RelatedTasks, models.RelatedTaskMap{
|
||||
models.RelationKindParenttask: []*models.Task{
|
||||
{
|
||||
ID: tickTickTasks[1].ParentID,
|
||||
@ -117,23 +117,23 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
|
||||
},
|
||||
})
|
||||
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Title, tickTickTasks[2].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Description, tickTickTasks[2].Content)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].StartDate, tickTickTasks[2].StartDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].EndDate, tickTickTasks[2].DueDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].DueDate, tickTickTasks[2].DueDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Labels, []*models.Label{
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Title, tickTickTasks[2].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Description, tickTickTasks[2].Content)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].StartDate, tickTickTasks[2].StartDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].EndDate, tickTickTasks[2].DueDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].DueDate, tickTickTasks[2].DueDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Labels, []*models.Label{
|
||||
{Title: "label1"},
|
||||
{Title: "label2"},
|
||||
{Title: "other label"},
|
||||
})
|
||||
//assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Reminders, tickTickTasks[0].) // TODO
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Position, tickTickTasks[2].Order)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Done, false)
|
||||
//assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[0].Reminders, tickTickTasks[0].) // TODO
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Position, tickTickTasks[2].Order)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[0].Tasks[2].Done, false)
|
||||
|
||||
assert.Len(t, vikunjaTasks[0].Lists[1].Tasks, 1)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[1].Title, tickTickTasks[3].ListName)
|
||||
assert.Len(t, vikunjaTasks[0].Projects[1].Tasks, 1)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[1].Title, tickTickTasks[3].ProjectName)
|
||||
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[1].Tasks[0].Title, tickTickTasks[3].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[1].Tasks[0].Position, tickTickTasks[3].Order)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[1].Tasks[0].Title, tickTickTasks[3].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Projects[1].Tasks[0].Position, tickTickTasks[3].Order)
|
||||
}
|
||||
|
@ -363,14 +363,14 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
expectedHierachie := []*models.NamespaceWithListsAndTasks{
|
||||
expectedHierachie := []*models.NamespaceWithProjectsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from todoist",
|
||||
},
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
Projects: []*models.ProjectWithTasksAndBuckets{
|
||||
{
|
||||
List: models.List{
|
||||
Project: models.Project{
|
||||
Title: "Project1",
|
||||
Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3",
|
||||
HexColor: todoistColors["berry_red"],
|
||||
@ -504,7 +504,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
List: models.List{
|
||||
Project: models.Project{
|
||||
Title: "Project2",
|
||||
Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5",
|
||||
HexColor: todoistColors["mint_green"],
|
||||
@ -602,7 +602,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
List: models.List{
|
||||
Project: models.Project{
|
||||
Title: "Project3 - Archived",
|
||||
HexColor: todoistColors["mint_green"],
|
||||
IsArchived: true,
|
||||
|
@ -99,14 +99,14 @@ func getTrelloData(token string) (trelloData []*trello.Board, err error) {
|
||||
log.Debugf("[Trello Migration] Got %d trello boards", len(trelloData))
|
||||
|
||||
for _, board := range trelloData {
|
||||
log.Debugf("[Trello Migration] Getting lists for board %s", board.ID)
|
||||
log.Debugf("[Trello Migration] Getting projects for board %s", board.ID)
|
||||
|
||||
board.Lists, err = board.GetLists(trello.Defaults())
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Trello Migration] Got %d lists for board %s", len(board.Lists), board.ID)
|
||||
log.Debugf("[Trello Migration] Got %d projects for board %s", len(board.Lists), board.ID)
|
||||
|
||||
listMap := make(map[string]*trello.List, len(board.Lists))
|
||||
for _, list := range board.Lists {
|
||||
@ -161,27 +161,27 @@ func getTrelloData(token string) (trelloData []*trello.Board, err error) {
|
||||
}
|
||||
|
||||
// Converts all previously obtained data from trello into the vikunja format.
|
||||
// `trelloData` should contain all boards with their lists and cards respectively.
|
||||
func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
|
||||
// `trelloData` should contain all boards with their projects and cards respectively.
|
||||
func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullVikunjaHierachie []*models.NamespaceWithProjectsAndTasks, err error) {
|
||||
|
||||
log.Debugf("[Trello Migration] ")
|
||||
|
||||
fullVikunjaHierachie = []*models.NamespaceWithListsAndTasks{
|
||||
fullVikunjaHierachie = []*models.NamespaceWithProjectsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Imported from Trello",
|
||||
},
|
||||
Lists: []*models.ListWithTasksAndBuckets{},
|
||||
Projects: []*models.ProjectWithTasksAndBuckets{},
|
||||
},
|
||||
}
|
||||
|
||||
var bucketID int64 = 1
|
||||
|
||||
log.Debugf("[Trello Migration] Converting %d boards to vikunja lists", len(trelloData))
|
||||
log.Debugf("[Trello Migration] Converting %d boards to vikunja projects", len(trelloData))
|
||||
|
||||
for _, board := range trelloData {
|
||||
list := &models.ListWithTasksAndBuckets{
|
||||
List: models.List{
|
||||
project := &models.ProjectWithTasksAndBuckets{
|
||||
Project: models.Project{
|
||||
Title: board.Name,
|
||||
Description: board.Desc,
|
||||
IsArchived: board.Closed,
|
||||
@ -189,7 +189,7 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
|
||||
}
|
||||
|
||||
// Background
|
||||
// We're pretty much abusing the backgroundinformation field here - not sure if this is really better than adding a new property to the list
|
||||
// We're pretty much abusing the backgroundinformation field here - not sure if this is really better than adding a new property to the project
|
||||
if board.Prefs.BackgroundImage != "" {
|
||||
log.Debugf("[Trello Migration] Downloading background %s for board %s", board.Prefs.BackgroundImage, board.ID)
|
||||
buf, err := migration.DownloadFile(board.Prefs.BackgroundImage)
|
||||
@ -197,7 +197,7 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("[Trello Migration] Downloaded background %s for board %s", board.Prefs.BackgroundImage, board.ID)
|
||||
list.BackgroundInformation = buf
|
||||
project.BackgroundInformation = buf
|
||||
} else {
|
||||
log.Debugf("[Trello Migration] Board %s does not have a background image, not copying...", board.ID)
|
||||
}
|
||||
@ -291,23 +291,23 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
|
||||
log.Debugf("[Trello Migration] Downloaded card attachment %s", attachment.ID)
|
||||
}
|
||||
|
||||
list.Tasks = append(list.Tasks, &models.TaskWithComments{Task: *task})
|
||||
project.Tasks = append(project.Tasks, &models.TaskWithComments{Task: *task})
|
||||
}
|
||||
|
||||
list.Buckets = append(list.Buckets, bucket)
|
||||
project.Buckets = append(project.Buckets, bucket)
|
||||
bucketID++
|
||||
}
|
||||
|
||||
log.Debugf("[Trello Migration] Converted all cards to tasks for board %s", board.ID)
|
||||
|
||||
fullVikunjaHierachie[0].Lists = append(fullVikunjaHierachie[0].Lists, list)
|
||||
fullVikunjaHierachie[0].Projects = append(fullVikunjaHierachie[0].Projects, project)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Migrate gets all tasks from trello for a user and puts them into vikunja
|
||||
// @Summary Migrate all lists, tasks etc. from trello
|
||||
// @Summary Migrate all projects, tasks etc. from trello
|
||||
// @Description Migrates all projects, tasks, notes, reminders, subtasks and files from trello to vikunja.
|
||||
// @tags migration
|
||||
// @Accept json
|
||||
|
@ -44,9 +44,9 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
Name: "TestBoard",
|
||||
Desc: "This is a description",
|
||||
Closed: false,
|
||||
Lists: []*trello.List{
|
||||
Projects: []*trello.Project{
|
||||
{
|
||||
Name: "Test List 1",
|
||||
Name: "Test Project 1",
|
||||
Cards: []*trello.Card{
|
||||
{
|
||||
Name: "Test Card 1",
|
||||
@ -77,9 +77,9 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
{
|
||||
Name: "Test Card 2",
|
||||
Pos: 124,
|
||||
Checklists: []*trello.Checklist{
|
||||
Checkprojects: []*trello.Checkproject{
|
||||
{
|
||||
Name: "Checklist 1",
|
||||
Name: "Checkproject 1",
|
||||
CheckItems: []trello.CheckItem{
|
||||
{
|
||||
State: "pending",
|
||||
@ -92,7 +92,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Checklist 2",
|
||||
Name: "Checkproject 2",
|
||||
CheckItems: []trello.CheckItem{
|
||||
{
|
||||
State: "pending",
|
||||
@ -124,7 +124,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "Test List 2",
|
||||
Name: "Test Project 2",
|
||||
Cards: []*trello.Card{
|
||||
{
|
||||
Name: "Test Card 5",
|
||||
@ -157,9 +157,9 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
{
|
||||
Name: "TestBoard 2",
|
||||
Closed: false,
|
||||
Lists: []*trello.List{
|
||||
Projects: []*trello.Project{
|
||||
{
|
||||
Name: "Test List 4",
|
||||
Name: "Test Project 4",
|
||||
Cards: []*trello.Card{
|
||||
{
|
||||
Name: "Test Card 634",
|
||||
@ -172,9 +172,9 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
{
|
||||
Name: "TestBoard Archived",
|
||||
Closed: true,
|
||||
Lists: []*trello.List{
|
||||
Projects: []*trello.Project{
|
||||
{
|
||||
Name: "Test List 5",
|
||||
Name: "Test Project 5",
|
||||
Cards: []*trello.Card{
|
||||
{
|
||||
Name: "Test Card 63423",
|
||||
@ -187,14 +187,14 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
}
|
||||
trelloData[0].Prefs.BackgroundImage = "https://vikunja.io/testimage.jpg" // Using an image which we are hosting, so it'll still be up
|
||||
|
||||
expectedHierachie := []*models.NamespaceWithListsAndTasks{
|
||||
expectedHierachie := []*models.NamespaceWithProjectsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Imported from Trello",
|
||||
},
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
Projects: []*models.ProjectWithTasksAndBuckets{
|
||||
{
|
||||
List: models.List{
|
||||
Project: models.Project{
|
||||
Title: "TestBoard",
|
||||
Description: "This is a description",
|
||||
BackgroundInformation: bytes.NewBuffer(exampleFile),
|
||||
@ -202,11 +202,11 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
Buckets: []*models.Bucket{
|
||||
{
|
||||
ID: 1,
|
||||
Title: "Test List 1",
|
||||
Title: "Test Project 1",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Title: "Test List 2",
|
||||
Title: "Test Project 2",
|
||||
},
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
@ -244,12 +244,12 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
Title: "Test Card 2",
|
||||
Description: `
|
||||
|
||||
## Checklist 1
|
||||
## Checkproject 1
|
||||
|
||||
* [ ] Pending Task
|
||||
* [x] Completed Task
|
||||
|
||||
## Checklist 2
|
||||
## Checkproject 2
|
||||
|
||||
* [ ] Pending Task
|
||||
* [ ] Another Pending Task`,
|
||||
@ -315,13 +315,13 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
List: models.List{
|
||||
Project: models.Project{
|
||||
Title: "TestBoard 2",
|
||||
},
|
||||
Buckets: []*models.Bucket{
|
||||
{
|
||||
ID: 3,
|
||||
Title: "Test List 4",
|
||||
Title: "Test Project 4",
|
||||
},
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
@ -335,14 +335,14 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
List: models.List{
|
||||
Project: models.Project{
|
||||
Title: "TestBoard Archived",
|
||||
IsArchived: true,
|
||||
},
|
||||
Buckets: []*models.Bucket{
|
||||
{
|
||||
ID: 4,
|
||||
Title: "Test List 5",
|
||||
Title: "Test Project 5",
|
||||
},
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
|
@ -51,7 +51,7 @@ func (v *FileMigrator) Name() string {
|
||||
}
|
||||
|
||||
// Migrate takes a vikunja file export, parses it and imports everything in it into Vikunja.
|
||||
// @Summary Import all lists, tasks etc. from a Vikunja data export
|
||||
// @Summary Import all projects, tasks etc. from a Vikunja data export
|
||||
// @Description Imports all projects, tasks, notes, reminders, subtasks and files from a Vikunjda data export into Vikunja.
|
||||
// @tags migration
|
||||
// @Accept json
|
||||
@ -113,21 +113,21 @@ func (v *FileMigrator) Migrate(user *user.User, file io.ReaderAt, size int64) er
|
||||
return fmt.Errorf("could not read data file: %w", err)
|
||||
}
|
||||
|
||||
namespaces := []*models.NamespaceWithListsAndTasks{}
|
||||
namespaces := []*models.NamespaceWithProjectsAndTasks{}
|
||||
if err := json.Unmarshal(bufData.Bytes(), &namespaces); err != nil {
|
||||
return fmt.Errorf("could not read data: %w", err)
|
||||
}
|
||||
|
||||
for _, n := range namespaces {
|
||||
for _, l := range n.Lists {
|
||||
for _, l := range n.Projects {
|
||||
if b, exists := storedFiles[l.BackgroundFileID]; exists {
|
||||
bf, err := b.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open list background file %d for reading: %w", l.BackgroundFileID, err)
|
||||
return fmt.Errorf("could not open project background file %d for reading: %w", l.BackgroundFileID, err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if _, err := buf.ReadFrom(bf); err != nil {
|
||||
return fmt.Errorf("could not read list background file %d: %w", l.BackgroundFileID, err)
|
||||
return fmt.Errorf("could not read project background file %d: %w", l.BackgroundFileID, err)
|
||||
}
|
||||
|
||||
l.BackgroundInformation = &buf
|
||||
|
@ -48,12 +48,12 @@ func TestVikunjaFileMigrator_Migrate(t *testing.T) {
|
||||
"title": "test",
|
||||
"owner_id": u.ID,
|
||||
}, false)
|
||||
db.AssertExists(t, "lists", map[string]interface{}{
|
||||
"title": "Test list",
|
||||
db.AssertExists(t, "projects", map[string]interface{}{
|
||||
"title": "Test project",
|
||||
"owner_id": u.ID,
|
||||
}, false)
|
||||
db.AssertExists(t, "lists", map[string]interface{}{
|
||||
"title": "A list with a background",
|
||||
db.AssertExists(t, "projects", map[string]interface{}{
|
||||
"title": "A project with a background",
|
||||
"owner_id": u.ID,
|
||||
}, false)
|
||||
db.AssertExists(t, "tasks", map[string]interface{}{
|
||||
|
Reference in New Issue
Block a user