From d21690e6bbf68de96bc1940f62fb0bd6b241ba7a Mon Sep 17 00:00:00 2001 From: Christophe NINUCCI <36405135+chrnin@users.noreply.github.com> Date: Mon, 21 Feb 2022 20:35:05 +0100 Subject: [PATCH] Feat wekan export comments (#103) * raison sociale before siret in export * export comments * better line feed for comments --- export.go | 4 +- go.mod | 1 + main.go | 2 - wekan.go | 233 +++++++++++++++++++++++++++++++----------------- wekan_config.go | 19 ++-- 5 files changed, 162 insertions(+), 97 deletions(-) diff --git a/export.go b/export.go index 23e69147..649a1dfa 100644 --- a/export.go +++ b/export.go @@ -395,7 +395,7 @@ func (c Card) docx(head ExportHeader) (Docx, error) { fmt.Println(outErr.String()) } return Docx{ - filename: fmt.Sprintf("export-%s-%s.docx", c.dbExport.Siret, strings.Replace(c.dbExport.RaisonSociale, " ", "-", -1)), + filename: fmt.Sprintf("export-%s-%s.docx", strings.Replace(c.dbExport.RaisonSociale, " ", "-", -1), c.dbExport.Siret), data: file, }, nil } @@ -454,7 +454,7 @@ func (c Card) join() WekanExport { if c.WekanCard != nil { we.DateDebutSuivi = dateUrssaf(c.WekanCard.StartAt) - we.DescriptionWekan = c.WekanCard.Description + we.DescriptionWekan = c.WekanCard.Description + "\n\n" + strings.ReplaceAll(strings.Join(c.WekanCard.Comments, "\n\n"), "#export", "") we.Labels = wc.labelForLabelsIDs(c.WekanCard.LabelIds, c.WekanCard.BoardId) if c.WekanCard.EndAt != nil { we.DateFinSuivi = dateUrssaf(*c.WekanCard.EndAt) diff --git a/go.mod b/go.mod index fed77d09..24ccbd1a 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/Nerzal/gocloak/v10 v10.0.1 github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/gin-contrib/cors v1.3.1 github.com/gin-gonic/gin v1.7.4 diff --git a/main.go b/main.go index 5c5b2819..898e8008 100644 --- a/main.go +++ b/main.go @@ -113,8 +113,6 @@ func runAPI() { wekan.POST("/cards/:siret", validSiret, wekanNewCardHandler) wekan.GET("/join/:cardId", wekanJoinCardHandler) wekan.GET("/config", wekanConfigHandler) - // wekan.GET("/allconfig", wekanAllConfigHandler) - // wekan.GET("/reloadConfig", wekanReloadConfigHandler) log.Print("Running API on " + viper.GetString("bind")) err := router.Run(viper.GetString("bind")) diff --git a/wekan.go b/wekan.go index 06feffc5..d2c97e85 100644 --- a/wekan.go +++ b/wekan.go @@ -19,7 +19,6 @@ import ( "github.com/gin-gonic/gin" "github.com/spf13/viper" "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo/options" ) // Token type pour faire persister en mémoire les tokens réutilisables @@ -478,27 +477,6 @@ func createToken(userID string, adminToken string) ([]byte, error) { return ioutil.ReadAll(resp.Body) } -// func getCard(userToken string, boardID string, siretField string, siret string) ([]byte, error) { -// wekanURL := viper.GetString("wekanURL") -// req, err := http.NewRequest("GET", wekanURL+"api/boards/"+boardID+"/cardsByCustomField/"+siretField+"/"+siret, nil) -// if err != nil { -// return nil, err -// } -// req.Header.Add("Accept", "application/json") -// req.Header.Add("Authorization", "Bearer "+userToken) -// client := &http.Client{} -// resp, err := client.Do(req) -// if resp.StatusCode != http.StatusOK { -// body, _ := ioutil.ReadAll(resp.Body) -// err = fmt.Errorf("getCard: unexpected wekan API response: %d\nBody: %s", resp.StatusCode, body) -// } -// if err != nil { -// return nil, err -// } -// defer resp.Body.Close() -// return ioutil.ReadAll(resp.Body) -// } - func createCard(userToken string, boardID string, listID string, creationData map[string]interface{}) ([]byte, error) { wekanURL := viper.GetString("wekanURL") json, err := json.Marshal(creationData) @@ -652,6 +630,7 @@ type WekanCard struct { LabelIds []string `bson:"labelIds"` Archived bool `bson:"archived"` UserID string `bson:"userId"` + Comments []string `bson:"comments"` CustomFields []struct { ID string `bson:"_id"` Value string `bson:"value"` @@ -744,8 +723,28 @@ func selectWekanCardFromSiret(username string, siret string) (*WekanCard, error) return nil, fmt.Errorf("selectWekanCardFromSiret() -> utilisateur wekan inconnu: %s", username) } - var queryOr []bson.M + var wekanCards []WekanCard + + pipeline := cardPipeline(wcu, siret) + cursor, err := mgoDB.Collection("cards").Aggregate(context.Background(), pipeline) + if err != nil { + return nil, err + } + + err = cursor.All(context.Background(), &wekanCards) + if err != nil { + return nil, err + } + if len(wekanCards) > 0 { + return &wekanCards[0], nil + } else { + return nil, nil + } +} + +func cardPipeline(wcu WekanConfig, siret string) bson.A { + var queryOr []bson.M for _, boardConfig := range wcu.Boards { queryOr = append(queryOr, bson.M{ @@ -757,50 +756,93 @@ func selectWekanCardFromSiret(username string, siret string) (*WekanCard, error) }, ) } - fmt.Println(queryOr) - query := bson.M{ - "$or": queryOr, + + pipeline := bson.A{ + bson.M{ + "$match": bson.M{ + "$or": queryOr, + }, + }, + bson.M{ + "$lookup": bson.M{ + "from": "card_comments", + "let": bson.M{"card": "$_id"}, + "pipeline": bson.A{ + bson.M{ + "$match": bson.M{ + "$expr": bson.M{"$eq": bson.A{"$cardId", "$$card"}}, + "text": bson.M{"$regex": "#export"}, + }, + }, bson.M{ + "$sort": bson.M{ + "createdAt": -1, + }, + }, bson.M{ + "$project": bson.M{ + "text": 1, + "_id": 0, + }, + }, + }, + "as": "comments", + }, + }, + bson.M{ + "$sort": bson.D{ + bson.E{Key: "archived", Value: 1}, + bson.E{Key: "startAt", Value: 1}, + }, + }, + bson.M{ + "$project": bson.M{ + "archived": 1, + "title": 1, + "listId": 1, + "boardId": 1, + "members": 1, + "swimlaneId": 1, + "customFields": 1, + "sort": 1, + "labelIds": 1, + "startAt": 1, + "endAt": 1, + "description": 1, + "comments": "$comments.text", + }, + }, } - projection := bson.M{ - "title": 1, - "listId": 1, - "boardId": 1, - "members": 1, - "swimlaneId": 1, - "customFields": 1, - "sort": 1, - "labelIds": 1, - "startAt": 1, - "endAt": 1, - "description": 1, - } - - var wekanCard WekanCard - options := options.FindOne().SetProjection(projection).SetSort(bson.D{ - bson.E{Key: "archived", Value: 1}, - bson.E{Key: "startAt", Value: 1}, - }) + return pipeline +} +func selectWekanCards(username *string, boardIds []string, swimlaneIds []string, listIds []string, labelIds [][]labelID) ([]*WekanCard, error) { + if username != nil { + userID := wekanConfig.userID(*username) + if userID == "" { + return nil, fmt.Errorf("selectWekanCards() -> utilisateur wekan inconnu: %s", *username) + } + } + + pipeline := cardsPipeline(username, boardIds, swimlaneIds, listIds, labelIds) - err := mgoDB.Collection("cards").FindOne(context.Background(), query, options).Decode(&wekanCard) + cardsCursor, err := mgoDB.Collection("cards").Aggregate(context.Background(), pipeline) if err != nil { - return nil, nil + return nil, err } - - return &wekanCard, nil + var wekanCards []*WekanCard + err = cardsCursor.All(context.Background(), &wekanCards) + if err != nil { + return nil, err + } + return wekanCards, nil } -func selectWekanCards(username *string, boardIds []string, swimlaneIds []string, listIds []string, labelIds [][]labelID) ([]*WekanCard, error) { +func cardsPipeline(username *string, boardIds []string, swimlaneIds []string, listIds []string, labelIds [][]labelID) bson.A { query := bson.M{ - "type": "cardType-card", - "archived": false, + "type": "cardType-card", } if username != nil { userID := wekanConfig.userID(*username) - if userID == "" { - return nil, fmt.Errorf("selectWekanCards() -> utilisateur wekan inconnu: %s", *username) - } query["$expr"] = bson.M{"$in": bson.A{userID, "$members"}} } @@ -842,33 +884,58 @@ func selectWekanCards(username *string, boardIds []string, swimlaneIds []string, query["$and"] = labelQueryAnd } - // sélection des champs à retourner et tri - projection := bson.M{ - "title": 1, - "listId": 1, - "boardId": 1, - "members": 1, - "swimlaneId": 1, - "customFields": 1, - "sort": 1, - "labelIds": 1, - "startAt": 1, - "endAt": 1, - "description": 1, - } - options := options.Find().SetProjection(projection).SetSort(bson.D{ - bson.E{Key: "archived", Value: 1}, - bson.E{Key: "startAt", Value: 1}, - }) - - cardsCursor, err := mgoDB.Collection("cards").Find(context.Background(), query, options) - if err != nil { - return nil, err - } - var wekanCards []*WekanCard - err = cardsCursor.All(context.Background(), &wekanCards) - if err != nil { - return nil, err + pipeline := bson.A{ + bson.M{ + "$match": query, + }, + bson.M{ + "$lookup": bson.M{ + "from": "card_comments", + "let": bson.M{"card": "$_id"}, + "pipeline": bson.A{ + bson.M{ + "$match": bson.M{ + "$expr": bson.M{"$eq": bson.A{"$cardId", "$$card"}}, + "text": bson.M{"$regex": "#export"}, + }, + }, bson.M{ + "$sort": bson.M{ + "createdAt": -1, + }, + }, bson.M{ + "$project": bson.M{ + "text": 1, + "_id": 0, + }, + }, + }, + "as": "comments", + }, + }, + bson.M{ + "$sort": bson.D{ + bson.E{Key: "archived", Value: 1}, + bson.E{Key: "startAt", Value: 1}, + }, + }, + bson.M{ + "$project": bson.M{ + "archived": 1, + "title": 1, + "listId": 1, + "boardId": 1, + "members": 1, + "swimlaneId": 1, + "customFields": 1, + "sort": 1, + "labelIds": 1, + "startAt": 1, + "endAt": 1, + "description": 1, + "comments": "$comments.text", + }, + }, } - return wekanCards, nil + + return pipeline } diff --git a/wekan_config.go b/wekan_config.go index c245e4a5..ac056aff 100644 --- a/wekan_config.go +++ b/wekan_config.go @@ -2,7 +2,6 @@ package main import ( "context" - "fmt" "log" "sync" "time" @@ -288,15 +287,15 @@ func buildWekanConfigPipeline() []bson.M { // c.AbortWithStatus(403) // } -func wekanReloadConfigHandler(c *gin.Context) { - var err error - wekanConfig, err = lookupWekanConfig() - if err != nil { - c.JSON(500, fmt.Sprintf("wekanReloadConfig: %s", err)) - return - } - c.JSON(200, true) -} +// func wekanReloadConfigHandler(c *gin.Context) { +// var err error +// wekanConfig, err = lookupWekanConfig() +// if err != nil { +// c.JSON(500, fmt.Sprintf("wekanReloadConfig: %s", err)) +// return +// } +// c.JSON(200, true) +// } func wekanConfigHandler(c *gin.Context) { roles := scopeFromContext(c)