diff --git a/dao/connection.go b/dao/connection.go index d06a9f6..148400a 100644 --- a/dao/connection.go +++ b/dao/connection.go @@ -40,7 +40,8 @@ var ( AvatarCollection *vmdb.Collection // PoolRoleCollection represents the database collection of the PoolRole Collection. - PoolRoleCollection *vmdb.Collection + PoolRoleCollection *vmdb.Collection + PoolRoleHistoryCollection *vmdb.Collection MailboxCollection *vmdb.Collection MessageCollection *vmdb.Collection @@ -103,6 +104,7 @@ func InitialDatabase() { // PoolRoleCollection represents the database collection of the PoolRole Collection. PoolRoleCollection = Database.Collection(models.PoolRoleCollection).CreateIndex("user_id", false).CreateMultiIndex(bson.D{{Key: "name", Value: 1}, {Key: "user_id", Value: 1}}, true) + PoolRoleHistoryCollection = Database.Collection(models.PoolRoleHistoryCollection).CreateIndex("user_id", false).CreateIndex("crew_id", false) // MailboxCollection = Database.Collection(models.MailboxCollection) diff --git a/dao/crew.go b/dao/crew.go index 2f2053d..50ab8db 100644 --- a/dao/crew.go +++ b/dao/crew.go @@ -3,7 +3,9 @@ package dao import ( "context" "pool-backend/models" + "sort" + "github.com/Viva-con-Agua/vcago" "github.com/Viva-con-Agua/vcago/vmdb" "github.com/Viva-con-Agua/vcapool" "go.mongodb.org/mongo-driver/bson" @@ -72,6 +74,20 @@ func CrewUpdate(ctx context.Context, i *models.CrewUpdate, token *vcapool.Access return } filter := i.PermittedFilter(token) + crew := new(models.Crew) + if err = CrewsCollection.FindOne(ctx, filter, &crew); err != nil { + return + } + // Its not allowed to set the asp_selection to "selected" manually + if crew.AspSelection != "selected" && i.AspSelection == "selected" { + return nil, vcago.NewBadRequest(models.CrewCollection, "It is not allowed to set the asp selection state to selected manually!") + } + strings := []string{"active", "inactive"} + sort.Strings(strings) + match := sort.SearchStrings(strings, i.AspSelection) + if crew.AspSelection == "selected" && match < len(strings) && strings[match] == i.AspSelection { + RoleHistoryDelete(ctx, &models.RoleHistoryRequest{CrewID: i.ID, Confirmed: false}, token) + } if !token.Roles.Validate("employee;admin") { if err = CrewsCollection.UpdateOne(ctx, filter, vmdb.UpdateSet(i.ToCrewUpdateASP()), &result); err != nil { return @@ -84,6 +100,22 @@ func CrewUpdate(ctx context.Context, i *models.CrewUpdate, token *vcapool.Access return } +func CrewUpdateAspSelection(ctx context.Context, i *models.CrewParam, value string, token *vcapool.AccessToken) (result *models.Crew, err error) { + if err = models.CrewUpdatePermission(token); err != nil { + return + } + filter := i.PermittedFilter(token) + crew := new(models.CrewUpdate) + if err = CrewsCollection.FindOne(ctx, filter, &crew); err != nil { + return + } + crew.AspSelection = value + if err = CrewsCollection.UpdateOne(ctx, filter, vmdb.UpdateSet(crew), &result); err != nil { + return + } + return +} + func CrewDelete(ctx context.Context, i *models.CrewParam, token *vcapool.AccessToken) (err error) { if err = models.CrewPermission(token); err != nil { return diff --git a/dao/role.go b/dao/role.go index 05fe970..67d3d63 100644 --- a/dao/role.go +++ b/dao/role.go @@ -4,8 +4,10 @@ import ( "context" "log" "pool-backend/models" + "time" "github.com/Viva-con-Agua/vcago" + "github.com/Viva-con-Agua/vcago/vmdb" "github.com/Viva-con-Agua/vcago/vmod" "github.com/Viva-con-Agua/vcapool" "go.mongodb.org/mongo-driver/bson" @@ -30,6 +32,9 @@ func RoleInsert(ctx context.Context, i *models.RoleRequest, token *vcapool.Acces if err = PoolRoleCollection.InsertOne(ctx, result); err != nil { return } + if err = PoolRoleHistoryCollection.InsertOne(ctx, models.NewRoleHistory(result, user)); err != nil { + return + } return } @@ -72,7 +77,9 @@ func RoleBulkUpdate(ctx context.Context, i *models.RoleBulkRequest, token *vcapo if err = PoolRoleCollection.InsertOne(ctx, createdRole); err != nil { return } - + if err = PoolRoleHistoryCollection.InsertOne(ctx, models.NewRoleRequestHistory(&role, user)); err != nil { + return + } if token.ID != role.UserID { if userRolesMap[role.UserID] == nil { userRolesMap[role.UserID] = &models.BulkUserRoles{} @@ -106,6 +113,18 @@ func RoleBulkUpdate(ctx context.Context, i *models.RoleBulkRequest, token *vcapo if err = PoolRoleCollection.DeleteOne(ctx, role.Filter()); err != nil { return } + history := new(models.RoleHistoryUpdate) + if err = PoolRoleHistoryCollection.FindOne( + ctx, + role.FilterHistory(), + &history, + ); err != nil { + return + } + history.EndDate = time.Now().Unix() + if err = PoolRoleHistoryCollection.UpdateOne(ctx, role.FilterHistory(), vmdb.UpdateSet(history), history); err != nil { + return + } if token.ID != role.UserID { if userRolesMap[role.UserID] == nil { userRolesMap[role.UserID] = &models.BulkUserRoles{} @@ -117,6 +136,90 @@ func RoleBulkUpdate(ctx context.Context, i *models.RoleBulkRequest, token *vcapo return } +func RoleBulkConfirm(ctx context.Context, i *[]models.RoleHistory, crew_id string, token *vcapool.AccessToken) (result *models.RoleBulkExport, userRolesMap map[string]*models.AspBulkUserRoles, err error) { + if err = models.RolesAdminPermission(token); err != nil { + return + } + + userRolesMap = make(map[string]*models.AspBulkUserRoles) + + role_filter := bson.D{{Key: "crew.crew_id", Value: crew_id}, {Key: "pool_roles", Value: bson.D{{Key: "$exists", Value: true}, {Key: "$ne", Value: "[]"}}}} + deleted_roles_users := new([]models.User) + UserViewCollection.Find(ctx, role_filter, deleted_roles_users) + deleted_roles := new([]vmod.Role) + for _, user := range *deleted_roles_users { + *deleted_roles = append(*deleted_roles, user.PoolRoles...) + + for _, role := range user.PoolRoles { + if err = PoolRoleCollection.DeleteOne(ctx, bson.D{{Key: "_id", Value: role.ID}}); err != nil { + return + } + } + } + + result = new(models.RoleBulkExport) + for _, role := range *i { + filter := role.MatchUser() + user := new(models.User) + if err = UserCollection.AggregateOne( + ctx, + models.UserPipeline(false).Match(filter).Pipe, + user, + ); err != nil { + return + } + + if err = models.RolesPermission(role.Role, user, token); err != nil { + return + } + userRole := new(models.RoleDatabase) + result.Users = append(result.Users, models.ExportRole{UserID: user.ID, Role: role.Role}) + + if err = PoolRoleCollection.FindOne(ctx, role.FilterRole(), userRole); err != nil { + + createdRole := new(vmod.Role) + if createdRole, err = role.NewRole(); err != nil { + return + } + if err = PoolRoleCollection.InsertOne(ctx, createdRole); err != nil { + return + } + + if token.ID != role.UserID { + if userRolesMap[role.UserID] == nil { + userRolesMap[role.UserID] = &models.AspBulkUserRoles{} + } + if index := getIndex(createdRole, *deleted_roles); index >= 0 { + userRolesMap[role.UserID].UnchangedRoles = append(userRolesMap[role.UserID].UnchangedRoles, createdRole.Label) + *deleted_roles = (*deleted_roles)[:index+copy((*deleted_roles)[index:], (*deleted_roles)[index+1:])] + } else { + if userRolesMap[role.UserID] == nil { + userRolesMap[role.UserID] = &models.AspBulkUserRoles{} + } + userRolesMap[role.UserID].AddedRoles = append(userRolesMap[role.UserID].AddedRoles, createdRole.Label) + } + } + } + } + for _, role := range *deleted_roles { + if token.ID != role.UserID { + if userRolesMap[role.UserID] == nil { + userRolesMap[role.UserID] = &models.AspBulkUserRoles{} + } + userRolesMap[role.UserID].DeletedRoles = append(userRolesMap[role.UserID].DeletedRoles, role.Label) + } + } + result.CrewID = crew_id + return +} +func getIndex(role *vmod.Role, data []vmod.Role) (index int) { + for index, search := range data { + if search.Name == role.Name && search.UserID == role.UserID { + return index + } + } + return -1 +} func RoleDelete(ctx context.Context, i *models.RoleRequest, token *vcapool.AccessToken) (result *vmod.Role, err error) { filter := i.MatchUser() user := new(models.User) @@ -140,7 +243,18 @@ func RoleDelete(ctx context.Context, i *models.RoleRequest, token *vcapool.Acces if err = PoolRoleCollection.DeleteOne(ctx, i.Filter()); err != nil { return } - + history := new(models.RoleHistory) + if err = PoolRoleHistoryCollection.FindOne( + ctx, + i.Filter(), + &history, + ); err != nil { + return + } + history.EndDate = time.Now().Unix() + if err = PoolRoleHistoryCollection.UpdateOne(ctx, history.Filter(), vmdb.UpdateSet(history), history); err != nil { + return + } return } @@ -162,10 +276,26 @@ func RoleNotification(ctx context.Context, i map[string]*models.BulkUserRoles) ( return } +func AspRoleNotification(ctx context.Context, i map[string]*models.AspBulkUserRoles) (err error) { + for index, role := range i { + user := new(models.User) + if err = UserCollection.FindOne( + ctx, + bson.D{{Key: "_id", Value: index}}, + user, + ); err != nil { + return + } + mail := vcago.NewMailData(user.Email, "pool-backend", "asp_role_update", "pool", user.Country) + mail.AddUser(user.User()) + mail.AddContent(user.AspRoleContent(role)) + vcago.Nats.Publish("system.mail.job", mail) + } + return +} + func RoleAdminNotification(ctx context.Context, crewID *models.CrewParam) (err error) { crew := new(models.Crew) - if err = CrewsCollection.FindOne(ctx, crewID.Match(), crew); err != nil { - } mail := vcago.NewMailData("netzwerk@vivaconagua.org", "pool-backend", "role_network", "pool", "de") mail.AddContent(models.RoleAdminContent(crew)) vcago.Nats.Publish("system.mail.job", mail) diff --git a/dao/role_history.go b/dao/role_history.go new file mode 100644 index 0000000..ff34184 --- /dev/null +++ b/dao/role_history.go @@ -0,0 +1,145 @@ +package dao + +import ( + "context" + "log" + "pool-backend/models" + "time" + + "github.com/Viva-con-Agua/vcago" + "github.com/Viva-con-Agua/vcago/vmdb" + "github.com/Viva-con-Agua/vcago/vmod" + "github.com/Viva-con-Agua/vcapool" + "go.mongodb.org/mongo-driver/bson" +) + +func RoleHistoryInsert(ctx context.Context, i *models.RoleHistoryCreate, token *vcapool.AccessToken) (result *models.RoleHistory, err error) { + if err = models.RolesHistoryAdminPermission(token); err != nil { + return + } + if result = i.NewRoleHistory(); err != nil { + return + } + if err = PoolRoleHistoryCollection.InsertOne(ctx, result); err != nil { + return + } + return +} + +func RoleHistoryBulkInsert(ctx context.Context, i *models.RoleHistoryBulkRequest, token *vcapool.AccessToken) (result *models.RoleBulkExport, err error) { + if err = models.RolesBulkPermission(token); err != nil { + return + } + + if token.Roles.Validate("admin;employee") { + RoleHistoryDelete(ctx, &models.RoleHistoryRequest{CrewID: i.CrewID, Confirmed: false}, token) + } + result = new(models.RoleBulkExport) + for _, role := range i.AddedRoles { + filter := role.MatchUser() + user := new(models.User) + if err = UserCollection.AggregateOne( + ctx, + models.UserPipeline(false).Match(filter).Pipe, + user, + ); err != nil { + return + } + + if err = models.RolesHistoryPermission(user, token); err != nil { + return + } + userRoleHistory := new(models.RoleHistoryDatabase) + result.Users = append(result.Users, models.ExportRole{UserID: user.ID, Role: role.Role}) + history_filter := bson.D{{Key: "user_id", Value: user.ID}, {Key: "role", Value: role.Role}, {Key: "end_date", Value: int64(0)}, {Key: "crew_id", Value: i.CrewID}, {Key: "confirmed", Value: false}} + + if err = PoolRoleHistoryCollection.FindOne(ctx, history_filter, userRoleHistory); err != nil { + if err = PoolRoleHistoryCollection.InsertOne(ctx, role.NewRoleHistory(user)); err != nil { + return + } + } + + } + result.CrewID = i.CrewID + return +} + +func RoleHistoryGet(ctx context.Context, i *models.RoleHistoryRequest, token *vcapool.AccessToken) (result *[]models.RoleHistory, list_size int64, err error) { + result = new([]models.RoleHistory) + pipeline := models.RolesHistoryPermittedPipeline() + if err = PoolRoleHistoryCollection.Aggregate( + ctx, + pipeline.Match(i.PermittedFilter(token)).Pipe, + result, + ); err != nil { + return + } + list_size = int64(len(*result)) + return +} + +func RoleHistoryConfirm(ctx context.Context, i *models.RoleHistoryRequest, token *vcapool.AccessToken) (result *[]models.RoleHistory, err error) { + if err = models.RolesHistoryAdminPermission(token); err != nil { + return + } + PoolRoleHistoryCollection.UpdateMany(ctx, i.PermittedFilter(token), vmdb.UpdateSet(bson.D{{Key: "end_date", Value: time.Now().Unix()}})) + + i.Confirmed = false + result = new([]models.RoleHistory) + if err = PoolRoleHistoryCollection.Find(ctx, i.PermittedFilter(token), result); err != nil { + return + } + if err = PoolRoleHistoryCollection.UpdateMany(ctx, i.PermittedFilter(token), vmdb.UpdateSet(bson.D{{Key: "confirmed", Value: true}})); err != nil { + return + } + return +} + +func RoleHistoryDelete(ctx context.Context, i *models.RoleHistoryRequest, token *vcapool.AccessToken) (result *models.RoleHistory, err error) { + if err = models.RolesHistoryAdminPermission(token); err != nil { + return + } + if err = PoolRoleHistoryCollection.FindOne( + ctx, + i.Filter(), + &result, + ); err != nil { + return + } + if err = PoolRoleHistoryCollection.DeleteMany(ctx, i.Filter()); err != nil { + return + } + + return +} + +func RoleHistoryFromRoles(ctx context.Context) (err error) { + roles := new([]vmod.Role) + PoolRoleCollection.Find(ctx, bson.D{{}}, roles) + + for _, role := range *roles { + user := new(models.User) + if err = UserCollection.AggregateOne( + ctx, + models.UserPipeline(false).Match(bson.D{{Key: "_id", Value: role.UserID}}).Pipe, + user, + ); err != nil { + log.Print(err) + } + if err = PoolRoleHistoryCollection.InsertOne(ctx, models.NewRoleHistory(&role, user)); err != nil { + log.Print(err) + } + } + return +} + +func RoleHistoryAdminNotification(ctx context.Context, crewID *models.CrewParam) (err error) { + crew := new(models.Crew) + if err = CrewsCollection.FindOne(ctx, crewID.Match(), crew); err != nil { + log.Print("No crew found") + } + mail := vcago.NewMailData("netzwerk@vivaconagua.org", "pool-backend", "asp_selection_network", "pool", "de") + mail.AddContent(models.RoleAdminContent(crew)) + vcago.Nats.Publish("system.mail.job", mail) + return +} diff --git a/dao/taking.go b/dao/taking.go index de42eed..842623e 100644 --- a/dao/taking.go +++ b/dao/taking.go @@ -4,6 +4,7 @@ import ( "context" "log" "pool-backend/models" + "time" "github.com/Viva-con-Agua/vcago/vmdb" "github.com/Viva-con-Agua/vcapool" @@ -106,7 +107,7 @@ func TakingUpdate(ctx context.Context, i *models.TakingUpdate, token *vcapool.Ac err = nil } - if event.ID != "" { + if event.ID != "" && event.EndAt < time.Now().Unix() { event.EventState.OldState = event.EventState.State event.EventState.State = "closed" e := new(models.Event) diff --git a/dao/update_ticker.go b/dao/update_ticker.go index 3a3f2b8..1fafdaa 100644 --- a/dao/update_ticker.go +++ b/dao/update_ticker.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "pool-backend/models" "time" "github.com/Viva-con-Agua/vcago/vmdb" @@ -19,6 +20,7 @@ func UpdateTicker() { select { case <-ticker.C: EventStateUpdateTicker() + EventStateNoIncome() case <-quit: ticker.Stop() return @@ -36,3 +38,21 @@ func EventStateUpdateTicker() { log.Print(err) } } +func EventStateNoIncome() { + filter := vmdb.NewFilter() + filter.EqualBool("no_income", "true") + filter.EqualString("event.event_state.state", "finished") + pipeline := models.TakingPipelineTicker().Match(filter.Bson()).Pipe + takings := []models.Taking{} + if err := TakingCollection.Aggregate(context.Background(), pipeline, takings); err != nil { + log.Print(err) + } + for i := range takings { + updateFilter := bson.D{{Key: "_id", Value: takings[i].Event.ID}} + update := bson.D{{Key: "event_state.state", Value: "closed"}} + if err := TakingCollection.UpdateOne(context.Background(), updateFilter, vmdb.UpdateSet(update), nil); err != nil { + log.Print(err) + } + + } +} diff --git a/handlers/token/role_history.go b/handlers/token/role_history.go new file mode 100644 index 0000000..a2987a4 --- /dev/null +++ b/handlers/token/role_history.go @@ -0,0 +1,138 @@ +package token + +import ( + "log" + "pool-backend/dao" + "pool-backend/models" + + "github.com/Viva-con-Agua/vcago" + "github.com/Viva-con-Agua/vcapool" + "github.com/labstack/echo/v4" +) + +type RoleHistoryHandler struct { + vcago.Handler +} + +var RoleHistory = &RoleHistoryHandler{*vcago.NewHandler("role_history")} + +func (i *RoleHistoryHandler) Routes(group *echo.Group) { + group.Use(i.Context) + group.POST("", i.Create, accessCookie) + group.POST("/bulk", i.CreateBulk, accessCookie) + group.POST("/confirm", i.ConfirmSelection, accessCookie) + group.GET("", i.Get, accessCookie) + group.DELETE("", i.Delete, accessCookie) +} + +func (i *RoleHistoryHandler) Create(cc echo.Context) (err error) { + c := cc.(vcago.Context) + body := new(models.RoleHistoryCreate) + if c.BindAndValidate(body); err != nil { + return + } + token := new(vcapool.AccessToken) + if err = c.AccessToken(token); err != nil { + return + } + result := new(models.RoleHistory) + if result, err = dao.RoleHistoryInsert(c.Ctx(), body, token); err != nil { + return + } + return c.Created(result) +} + +func (i *RoleHistoryHandler) CreateBulk(cc echo.Context) (err error) { + c := cc.(vcago.Context) + body := new(models.RoleHistoryBulkRequest) + if c.BindAndValidate(body); err != nil { + return + } + token := new(vcapool.AccessToken) + if err = c.AccessToken(token); err != nil { + return + } + result := new(models.RoleBulkExport) + if result, err = dao.RoleHistoryBulkInsert(c.Ctx(), body, token); err != nil { + return + } + if _, err = dao.CrewUpdateAspSelection(c.Ctx(), &models.CrewParam{ID: body.CrewID}, "selected", token); err != nil { + return + } + dao.RoleHistoryAdminNotification(c.Ctx(), &models.CrewParam{ID: body.CrewID}) + return c.Created(result) +} + +func (i *RoleHistoryHandler) ConfirmSelection(cc echo.Context) (err error) { + c := cc.(vcago.Context) + body := new(models.RoleHistoryRequest) + if c.BindAndValidate(body); err != nil { + return + } + token := new(vcapool.AccessToken) + if err = c.AccessToken(token); err != nil { + return + } + body.Confirmed = true + history := new([]models.RoleHistory) + if history, err = dao.RoleHistoryConfirm(c.Ctx(), body, token); err != nil { + return + } + result := new(models.RoleBulkExport) + userRolesMap := make(map[string]*models.AspBulkUserRoles) + if result, userRolesMap, err = dao.RoleBulkConfirm(c.Ctx(), history, body.CrewID, token); err != nil { + return + } + if _, err = dao.CrewUpdateAspSelection(c.Ctx(), &models.CrewParam{ID: body.CrewID}, "inactive", token); err != nil { + return + } + go func() { + if err = dao.IDjango.Post(result, "/v1/pool/asps/"); err != nil { + log.Print(err) + } + }() + if err = dao.AspRoleNotification(c.Ctx(), userRolesMap); err != nil { + return + } + return c.Created(result) +} + +func (i *RoleHistoryHandler) Get(cc echo.Context) (err error) { + c := cc.(vcago.Context) + body := new(models.RoleHistoryRequest) + if err = c.BindAndValidate(body); err != nil { + return + } + token := new(vcapool.AccessToken) + if err = c.AccessToken(token); err != nil { + return + } + result := new([]models.RoleHistory) + var listSize int64 + if result, listSize, err = dao.RoleHistoryGet(c.Ctx(), body, token); err != nil { + return + } + return c.Listed(result, listSize) +} + +func (i *RoleHistoryHandler) Delete(cc echo.Context) (err error) { + c := cc.(vcago.Context) + body := new(models.RoleHistoryRequest) + if c.BindAndValidate(body); err != nil { + return + } + token := new(vcapool.AccessToken) + if err = c.AccessToken(token); err != nil { + return + } + result := new(models.RoleHistory) + if result, err = dao.RoleHistoryDelete(c.Ctx(), body, token); err != nil { + return + } + go func() { + if err = dao.IDjango.Post(result, "/v1/pool/crew/asp/"); err != nil { + log.Print(err) + } + }() + return c.Deleted(body) +} diff --git a/models/artist.go b/models/artist.go index 5ac89ce..b27b59f 100644 --- a/models/artist.go +++ b/models/artist.go @@ -38,7 +38,7 @@ type ( var ArtistCollection = "artists" func ArtistPermission(token *vcapool.AccessToken) (err error) { - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { return vcago.NewPermissionDenied(ArtistCollection) } return diff --git a/models/crew.go b/models/crew.go index cdc4f2b..7e71892 100644 --- a/models/crew.go +++ b/models/crew.go @@ -18,6 +18,7 @@ type ( Additional string `json:"additional" bson:"additional"` Cities []City `json:"cities" bson:"cities"` Status string `json:"status" bson:"status"` + AspSelection string `json:"asp_selection" bson:"asp_selection"` } CrewUpdate struct { ID string `json:"id,omitempty" bson:"_id"` @@ -27,6 +28,7 @@ type ( Mattermost string `bson:"mattermost_username" json:"mattermost_username"` Additional string `json:"additional" bson:"additional"` Status string `json:"status" bson:"status"` + AspSelection string `json:"asp_selection" bson:"asp_selection"` Cities []City `json:"cities" bson:"cities"` } CrewUpdateASP struct { @@ -44,6 +46,7 @@ type ( MailboxID string `json:"mailbox_id" bson:"mailbox_id"` Cities []City `json:"cities" bson:"cities"` Status string `json:"status" bson:"status"` + AspSelection string `json:"asp_selection" bson:"asp_selection"` Modified vmod.Modified `json:"modified" bson:"modified"` } CrewPublic struct { @@ -90,7 +93,7 @@ func CrewPermission(token *vcapool.AccessToken) (err error) { } func CrewUpdatePermission(token *vcapool.AccessToken) (err error) { - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("asp;network;education;finance;operation;awareness;socialmedia;other")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPRole)) { return vcago.NewPermissionDenied(CrewCollection) } return diff --git a/models/deposit.go b/models/deposit.go index 076245c..edbb12b 100644 --- a/models/deposit.go +++ b/models/deposit.go @@ -224,7 +224,7 @@ func (i *DepositUpdate) DepositDatabase(current *Deposit) (r *DepositUpdate, cre } } currency := "EUR" - if i.DepositUnit != nil { + if i.DepositUnit != nil && len(i.DepositUnit) != 0 { currency = i.DepositUnit[0].Money.Currency } r = i diff --git a/models/event.go b/models/event.go index 52d607b..d69847a 100644 --- a/models/event.go +++ b/models/event.go @@ -383,7 +383,7 @@ func EventPipeline(token *vcapool.AccessToken) (pipe *vmdb.Pipeline) { pipe.LookupUnwind(OrganizerCollection, "organizer_id", "_id", "organizer") if token.Roles.Validate("employee;admin") { pipe.Lookup(ParticipationCollection, "_id", "event_id", "participations") - } else if token.PoolRoles.Validate("network;operation;education") { + } else if token.PoolRoles.Validate(ASPEventRole) { pipe.LookupMatch(ParticipationEventView, "_id", "event_id", "participations", bson.D{{Key: "event.crew_id", Value: token.CrewID}}) } else { pipe.LookupMatch(ParticipationEventView, "_id", "event_id", "participations", bson.D{{Key: "event.event_asp_id", Value: token.ID}}) @@ -422,14 +422,14 @@ func EventRolePipeline() *vmdb.Pipeline { } func EventPermission(token *vcapool.AccessToken) (err error) { - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { return vcago.NewPermissionDenied(EventCollection) } return } func EventDeletePermission(token *vcapool.AccessToken) (err error) { - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { return vcago.NewPermissionDenied(EventCollection) } return @@ -472,7 +472,7 @@ func (i *EventUpdate) Match() bson.D { func (i *EventUpdate) PermittedFilter(token *vcapool.AccessToken) bson.D { filter := vmdb.NewFilter() filter.EqualString("_id", i.ID) - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { filter.EqualString("event_asp_id", token.ID) filter.EqualString("crew_id", token.CrewID) } else if !token.Roles.Validate("employee;admin") { @@ -492,7 +492,7 @@ func (i *Event) FilterCrew() bson.D { func (i *EventParam) PermittedFilter(token *vcapool.AccessToken) bson.D { filter := vmdb.NewFilter() filter.EqualString("_id", i.ID) - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { filter.EqualString("event_asp_id", token.ID) } else if !token.Roles.Validate("employee;admin") { filter.EqualString("crew_id", token.CrewID) @@ -522,7 +522,7 @@ func (i *EventParam) PublicFilter() bson.D { func (i *EventQuery) FilterAsp(token *vcapool.AccessToken) bson.D { filter := vmdb.NewFilter() - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { filter.EqualString("event_asp_id", token.ID) } else if !token.Roles.Validate("employee;admin") { filter.EqualString("crew_id", token.CrewID) @@ -532,7 +532,7 @@ func (i *EventQuery) FilterAsp(token *vcapool.AccessToken) bson.D { func (i *EventQuery) PermittedFilter(token *vcapool.AccessToken) bson.D { filter := vmdb.NewFilter() - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { filter.EqualStringList("event_state.state", []string{"published", "finished", "closed"}) } else if !token.Roles.Validate("employee;admin") { noCrewMatch := vmdb.NewFilter() @@ -556,7 +556,7 @@ func (i *EventQuery) PermittedFilter(token *vcapool.AccessToken) bson.D { func (i *EventQuery) FilterEmailEvents(token *vcapool.AccessToken) bson.D { filter := vmdb.NewFilter() - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { filter.EqualString("event_asp_id", token.ID) filter.EqualString("crew_id", token.CrewID) } else if !token.Roles.Validate("employee;admin") { diff --git a/models/mailbox.go b/models/mailbox.go index 5ea7825..efb2eba 100644 --- a/models/mailbox.go +++ b/models/mailbox.go @@ -38,7 +38,7 @@ func MailboxPipeline(token *vcapool.AccessToken) *vmdb.Pipeline { pipe := vmdb.NewPipeline() pipe.LookupUnwind(UserCollection, "_id", "mailbox_id", "user") pipe.LookupUnwind(CrewCollection, "_id", "mailbox_id", "crew") - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("operation;network;finance;education;socialmedia;awareness;other")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPRole)) { inboxMatch := vmdb.NewFilter() inboxMatch.EqualString("type", "inbox") inboxMatch.EqualString("user_id", token.ID) diff --git a/models/message.go b/models/message.go index 49cfbb2..cbbdf6b 100644 --- a/models/message.go +++ b/models/message.go @@ -151,7 +151,7 @@ func (i *Message) MessageUpdate() *MessageUpdate { func (i *MessageParam) PermittedFilter(token *vcapool.AccessToken, crew *Crew) bson.D { filter := vmdb.NewFilter() filter.EqualString("_id", i.ID) - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { filter.EqualStringList("mailbox_id", []string{token.MailboxID, crew.MailboxID}) filter.EqualString("user_id", token.ID) } else { @@ -163,7 +163,7 @@ func (i *MessageParam) PermittedFilter(token *vcapool.AccessToken, crew *Crew) b func (i *MessageUpdate) PermittedFilter(token *vcapool.AccessToken, crew *Crew) bson.D { filter := vmdb.NewFilter() filter.EqualString("_id", i.ID) - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { filter.EqualStringList("mailbox_id", []string{token.MailboxID, crew.MailboxID}) filter.EqualString("user_id", token.ID) } else { @@ -200,7 +200,7 @@ func PermittedMessageCreate(token *vcapool.AccessToken, i *Message, crew *Crew, // IF IS EVENT ASP -> Force Mailbox and From to CrewMailbox and CrewEmail message.MailboxID = crew.MailboxID message.From = crew.Email - } else if token.PoolRoles.Validate("network;operation;education") { + } else if token.PoolRoles.Validate(ASPEventRole) { // IF IS CREW ASP -> Force Mailbox and From to CrewMailbox and CrewEmail message.MailboxID = crew.MailboxID message.From = crew.Email diff --git a/models/organizer.go b/models/organizer.go index eb3c3b6..ccde8c4 100644 --- a/models/organizer.go +++ b/models/organizer.go @@ -38,7 +38,7 @@ type ( var OrganizerCollection = "organizers" func OrganizerPermission(token *vcapool.AccessToken) (err error) { - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { return vcago.NewPermissionDenied(OrganizerCollection) } return diff --git a/models/participation.go b/models/participation.go index 2ea6bc2..a92bfc5 100644 --- a/models/participation.go +++ b/models/participation.go @@ -101,7 +101,7 @@ var ParticipationCollection = "participations" var ParticipationEventView = "participations_event" func ParticipationPermission(token *vcapool.AccessToken) (err error) { - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { return vcago.NewPermissionDenied(ParticipationCollection) } return @@ -121,7 +121,7 @@ func (i *ParticipationUpdate) ParticipationUpdatePermission(token *vcapool.Acces return vcago.NewPermissionDenied(ParticipationCollection) } case "confirmed", "rejected": - if !token.Roles.Validate("employee;admin") && !token.PoolRoles.Validate("network;operation;education") && token.ID != participation.Event.EventASPID { + if !token.Roles.Validate("employee;admin") && !token.PoolRoles.Validate(ASPEventRole) && token.ID != participation.Event.EventASPID { return vcago.NewPermissionDenied(ParticipationCollection) } } @@ -255,7 +255,7 @@ func (i *ParticipationQuery) FilterAspInformation(token *vcapool.AccessToken) bs func (i *EventParam) FilterEvent(token *vcapool.AccessToken) bson.D { filter := vmdb.NewFilter() filter.EqualString("event_id", i.ID) - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { filter.EqualString("event.event_asp_id", token.ID) } else if !token.Roles.Validate("employee;admin") { filter.EqualString("event.crew_id", token.CrewID) @@ -293,7 +293,7 @@ func (i *ParticipationUpdate) PermittedFilter(token *vcapool.AccessToken) bson.D func (i *ParticipationParam) PermittedFilter(token *vcapool.AccessToken) bson.D { filter := vmdb.NewFilter() filter.EqualString("_id", i.ID) - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("network;operation;education")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPEventRole)) { filter.EqualString("user_id", token.ID) } else if !token.Roles.Validate("employee;admin") { filter.EqualString("crew_id", token.CrewID) diff --git a/models/role.go b/models/role.go index a2761d9..54252c7 100644 --- a/models/role.go +++ b/models/role.go @@ -1,6 +1,8 @@ package models import ( + "time" + "github.com/Viva-con-Agua/vcago" "github.com/Viva-con-Agua/vcago/vmdb" "github.com/Viva-con-Agua/vcago/vmod" @@ -52,6 +54,12 @@ type BulkUserRoles struct { DeletedRoles []string `bson:"deleted" json:"deleted"` } +type AspBulkUserRoles struct { + AddedRoles []string `bson:"created" json:"created"` + DeletedRoles []string `bson:"deleted" json:"deleted"` + UnchangedRoles []string `bson:"unchanged" json:"unchanged"` +} + var PoolRoleCollection = "pool_roles" func (i *RoleRequest) NewRole() (r *vmod.Role, err error) { @@ -76,8 +84,43 @@ func (i *RoleRequest) NewRole() (r *vmod.Role, err error) { return nil, vcago.NewValidationError("role not supported: " + i.Role) } } +func (i *RoleRequest) NewRoleHistory(user *User) (r *RoleHistoryDatabase) { + return &RoleHistoryDatabase{ + ID: uuid.NewString(), + Role: i.Role, + UserID: user.ID, + CrewID: user.Crew.CrewID, + StartDate: time.Now().Unix(), + Confirmed: false, + Modified: vmod.NewModified(), + } +} + +func NewRoleHistory(i *vmod.Role, user *User) (r *RoleHistoryDatabase) { + return &RoleHistoryDatabase{ + ID: uuid.NewString(), + Role: i.Name, + UserID: user.ID, + CrewID: user.Crew.CrewID, + StartDate: time.Now().Unix(), + Confirmed: true, + Modified: vmod.NewModified(), + } +} +func NewRoleRequestHistory(i *RoleRequest, user *User) (r *RoleHistoryDatabase) { + return &RoleHistoryDatabase{ + ID: uuid.NewString(), + Role: i.Role, + UserID: user.ID, + CrewID: user.Crew.CrewID, + StartDate: time.Now().Unix(), + Confirmed: true, + Modified: vmod.NewModified(), + } +} -var ASPRole = "asp;finance;operation;education;network;socialmedia;awareness" +var ASPRole = "other;asp;finance;operation;education;network;socialmedia;awareness" +var ASPEventRole = "network;operation;education" func RolesPermission(role string, user *User, token *vcapool.AccessToken) (err error) { if user.NVM.Status != "confirmed" { @@ -90,7 +133,7 @@ func RolesPermission(role string, user *User, token *vcapool.AccessToken) (err e } func RolesBulkPermission(token *vcapool.AccessToken) (err error) { - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("finance;operation;education;network;socialmedia;awareness;other")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPRole)) { return vcago.NewPermissionDenied(PoolRoleCollection) } return @@ -103,6 +146,13 @@ func RolesDeletePermission(role string, token *vcapool.AccessToken) (err error) return } +func RolesAdminPermission(token *vcapool.AccessToken) (err error) { + if !token.Roles.Validate("employee;admin") { + return vcago.NewPermissionDenied(PoolRoleCollection) + } + return +} + func RoleASP(userID string) *vmod.Role { return &vmod.Role{ ID: uuid.NewString(), @@ -201,6 +251,12 @@ func (i *RoleRequest) Filter() bson.D { filter.EqualString("user_id", i.UserID) return filter.Bson() } +func (i *RoleRequest) FilterHistory() bson.D { + filter := vmdb.NewFilter() + filter.EqualString("role", i.Role) + filter.EqualString("user_id", i.UserID) + return filter.Bson() +} func (i *RoleBulkRequest) PermittedFilter(token *vcapool.AccessToken) bson.D { filter := vmdb.NewFilter() @@ -209,7 +265,7 @@ func (i *RoleBulkRequest) PermittedFilter(token *vcapool.AccessToken) bson.D { filter.ElemMatchList("pool_roles", "name", token.PoolRoles) } else { filter.EqualString("crew.crew_id", i.CrewID) - filter.ElemMatchList("pool_roles", "name", []string{"network", "education", "finance", "operation", "awareness", "socialmedia", "other"}) + filter.ElemMatchList("pool_roles", "name", []string{"network", "education", "finance", "operation", "awareness", "socialmedia", "other", "asp"}) } return filter.Bson() } diff --git a/models/role_history.go b/models/role_history.go new file mode 100644 index 0000000..33f3d6e --- /dev/null +++ b/models/role_history.go @@ -0,0 +1,192 @@ +package models + +import ( + "strconv" + + "github.com/Viva-con-Agua/vcago" + "github.com/Viva-con-Agua/vcago/vmdb" + "github.com/Viva-con-Agua/vcago/vmod" + "github.com/Viva-con-Agua/vcapool" + "github.com/google/uuid" + "go.mongodb.org/mongo-driver/bson" +) + +type RoleHistoryCreate struct { + UserID string `json:"user_id"` + Role string `json:"role"` + CrewID string `json:"crew_id"` + Confirmed bool `json:"confirmed" bson:"confirmed"` + StartDate int64 `json:"start_date" bson:"start_date"` + EndDate int64 `json:"end_date" bson:"end_date"` + Modified vmod.Modified `json:"modified" bson:"modified"` +} + +type RoleHistoryUpdate struct { + ID string `bson:"_id" json:"id"` + UserID string `json:"user_id" bson:"user_id"` + Role string `json:"role" bson:"role"` + CrewID string `json:"crew_id" bson:"crew_id"` + Confirmed bool `json:"confirmed" bson:"confirmed"` + StartDate int64 `json:"start_date" bson:"start_date"` + EndDate int64 `json:"end_date" bson:"end_date"` +} +type RoleHistoryRequest struct { + UserID string `json:"user_id"` + Role string `json:"role"` + CrewID string `json:"crew_id"` + Confirmed bool `json:"confirmed" bson:"confirmed"` + StartDate int64 `json:"start_date" bson:"start_date"` + EndDate int64 `json:"end_date" bson:"end_date"` + Modified vmod.Modified `json:"modified" bson:"modified"` +} + +type RoleHistoryBulkRequest struct { + CrewID string `json:"crew_id"` + AddedRoles []RoleRequest `json:"created"` +} + +type RoleHistory struct { + ID string `bson:"_id" json:"id"` + UserID string `json:"user_id" bson:"user_id"` + Role string `json:"role" bson:"role"` + CrewID string `json:"crew_id" bson:"crew_id"` + Crew UserCrewMinimal `json:"crew" bson:"crew"` + Profile ProfileMinimal `json:"profile" bson:"profile"` + User UserMinimal `json:"user" bson:"user"` + Confirmed bool `json:"confirmed" bson:"confirmed"` + StartDate int64 `json:"start_date" bson:"start_date"` + EndDate int64 `json:"end_date" bson:"end_date"` + Modified vmod.Modified `json:"modified" bson:"modified"` +} + +type RoleHistoryDatabase struct { + ID string `bson:"_id" json:"id"` + UserID string `json:"user_id" bson:"user_id"` + Role string `json:"role" bson:"role"` + CrewID string `json:"crew_id" bson:"crew_id"` + Confirmed bool `json:"confirmed" bson:"confirmed"` + StartDate int64 `json:"start_date" bson:"start_date"` + EndDate int64 `json:"end_date" bson:"end_date"` + Modified vmod.Modified `json:"modified" bson:"modified"` +} + +var PoolRoleHistoryCollection = "pool_roles_history" + +func RolesHistoryPermittedPipeline() (pipe *vmdb.Pipeline) { + pipe = vmdb.NewPipeline() + pipe.LookupUnwind(UserCrewCollection, "user_id", "user_id", "crew") + pipe.LookupUnwind(ProfileCollection, "user_id", "user_id", "profile") + pipe.LookupUnwind(UserCollection, "user_id", "_id", "user") + return +} + +func RolesHistoryPermission(user *User, token *vcapool.AccessToken) (err error) { + if user.NVM.Status != "confirmed" { + return vcago.NewBadRequest(PoolRoleHistoryCollection, "nvm required", nil) + } + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPRole)) { + return vcago.NewPermissionDenied(PoolRoleHistoryCollection) + } + return +} + +func RolesHistoryAdminPermission(token *vcapool.AccessToken) (err error) { + if !token.Roles.Validate("employee;admin") { + return vcago.NewPermissionDenied(PoolRoleHistoryCollection) + } + return +} + +func (i *RoleHistory) NewRole() (r *vmod.Role, err error) { + switch i.Role { + case "asp": + return RoleASP(i.UserID), err + case "finance": + return RoleFinance(i.UserID), err + case "operation": + return RoleAction(i.UserID), err + case "education": + return RoleEducation(i.UserID), err + case "network": + return RoleNetwork(i.UserID), err + case "socialmedia": + return RoleSocialMedia(i.UserID), err + case "awareness": + return RoleAwareness(i.UserID), err + case "other": + return RoleOther(i.UserID), err + default: + return nil, vcago.NewValidationError("role not supported: " + i.Role) + } +} +func (i *RoleHistoryCreate) NewRoleHistory() *RoleHistory { + return &RoleHistory{ + ID: uuid.NewString(), + Role: i.Role, + UserID: i.UserID, + CrewID: i.CrewID, + Confirmed: i.Confirmed, + StartDate: i.StartDate, + EndDate: i.EndDate, + Modified: vmod.NewModified(), + } +} + +func (i *RoleHistory) MatchUser() bson.D { + filter := vmdb.NewFilter() + filter.EqualString("_id", i.UserID) + return filter.Bson() +} + +func (i *RoleHistory) FilterRole() bson.D { + filter := vmdb.NewFilter() + filter.EqualString("name", i.Role) + filter.EqualString("user_id", i.UserID) + return filter.Bson() +} + +func (i *RoleHistory) Filter() bson.D { + filter := vmdb.NewFilter() + filter.EqualString("role", i.Role) + filter.EqualString("user_id", i.UserID) + return filter.Bson() +} + +func (i *RoleHistoryRequest) Filter() bson.D { + filter := vmdb.NewFilter() + filter.EqualString("role", i.Role) + filter.EqualBool("confirmed", strconv.FormatBool(i.Confirmed)) + filter.EqualString("crew_id", i.CrewID) + filter.EqualString("user_id", i.UserID) + return filter.Bson() +} + +func (i *RoleHistoryDatabase) Filter() bson.D { + filter := vmdb.NewFilter() + filter.EqualString("role", i.Role) + filter.EqualString("user_id", i.UserID) + filter.EqualInt("end_date", "0") + return filter.Bson() +} + +func (i *RoleHistoryBulkRequest) PermittedFilter(token *vcapool.AccessToken) bson.D { + filter := vmdb.NewFilter() + if !token.Roles.Validate("employee;admin") { + filter.EqualString("crew.crew_id", token.CrewID) + } else { + filter.EqualString("crew.crew_id", i.CrewID) + } + filter.ElemMatchList("pool_roles", "name", []string{"network", "education", "finance", "operation", "awareness", "socialmedia", "other"}) + return filter.Bson() +} + +func (i *RoleHistoryRequest) PermittedFilter(token *vcapool.AccessToken) bson.D { + filter := vmdb.NewFilter() + if !token.Roles.Validate("employee;admin") { + filter.EqualString("crew_id", token.CrewID) + } else { + filter.EqualString("crew_id", i.CrewID) + } + filter.EqualBool("confirmed", strconv.FormatBool(i.Confirmed)) + return filter.Bson() +} diff --git a/models/taking.go b/models/taking.go index 75ba198..c83ebd3 100644 --- a/models/taking.go +++ b/models/taking.go @@ -130,6 +130,12 @@ func TakingPipeline() *vmdb.Pipeline { return pipe } +func TakingPipelineTicker() *vmdb.Pipeline { + pipe := vmdb.NewPipeline() + pipe.LookupUnwind(EventCollection, "_id", "taking_id", "event") + return pipe +} + func (i *TakingCreate) TakingDatabase() *TakingDatabase { return &TakingDatabase{ ID: uuid.NewString(), diff --git a/models/user.go b/models/user.go index 3a67022..a05824f 100644 --- a/models/user.go +++ b/models/user.go @@ -341,7 +341,7 @@ func (i *ProfileUpdate) ToUserUpdate() *ProfileUpdate { } func UsersPermission(token *vcapool.AccessToken) (err error) { - if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate("asp;network;education;finance;operation;awareness;socialmedia;other")) { + if !(token.Roles.Validate("employee;admin") || token.PoolRoles.Validate(ASPRole)) { return vcago.NewPermissionDenied(UserCollection) } return @@ -453,6 +453,16 @@ func (i *User) RoleContent(roles *BulkUserRoles) *vmod.Content { return content } +func (i *User) AspRoleContent(roles *AspBulkUserRoles) *vmod.Content { + content := &vmod.Content{ + Fields: make(map[string]interface{}), + } + content.Fields["AddedRoles"] = strings.Join(roles.AddedRoles, ", ") + content.Fields["DeletedRoles"] = strings.Join(roles.DeletedRoles, ", ") + content.Fields["UnchangedRoles"] = strings.Join(roles.UnchangedRoles, ", ") + return content +} + func RoleAdminContent(crew *Crew) *vmod.Content { content := &vmod.Content{ Fields: make(map[string]interface{}), diff --git a/server.go b/server.go index d6603ce..81ee64e 100644 --- a/server.go +++ b/server.go @@ -31,6 +31,7 @@ func main() { token.Profile.Routes(tokenUser.Group("/profile")) token.UserCrew.Routes(tokenUser.Group("/crew")) token.Role.Routes(tokenUser.Group("/role")) + token.RoleHistory.Routes(tokenUser.Group("/role_history")) token.Active.Routes(tokenUser.Group("/active")) token.NVM.Routes(tokenUser.Group("/nvm")) token.Address.Routes(tokenUser.Group("/address"))