// ConvertSpace converts between internal and external REST representation func ConvertSpace(request *goa.RequestData, p *space.Space, additional ...SpaceConvertFunc) *app.Space { selfURL := rest.AbsoluteURL(request, app.SpaceHref(p.ID)) relatedIterationList := rest.AbsoluteURL(request, fmt.Sprintf("/api/spaces/%s/iterations", p.ID.String())) relatedAreaList := rest.AbsoluteURL(request, fmt.Sprintf("/api/spaces/%s/areas", p.ID.String())) return &app.Space{ ID: &p.ID, Type: "spaces", Attributes: &app.SpaceAttributes{ Name: &p.Name, Description: &p.Description, CreatedAt: &p.CreatedAt, UpdatedAt: &p.UpdatedAt, Version: &p.Version, }, Links: &app.GenericLinks{ Self: &selfURL, }, Relationships: &app.SpaceRelationships{ Iterations: &app.RelationGeneric{ Links: &app.GenericLinks{ Related: &relatedIterationList, }, }, Areas: &app.RelationGeneric{ Links: &app.GenericLinks{ Related: &relatedAreaList, }, }, }, } }
// CreateCommentsRelationLinks returns a RelationGeneric object representing the links for a workitem to comment relation func CreateCommentsRelationLinks(request *goa.RequestData, wi *app.WorkItem) *app.GenericLinks { commentsSelf := rest.AbsoluteURL(request, app.WorkitemHref(wi.ID)) + "/relationships/comments" commentsRelated := rest.AbsoluteURL(request, app.WorkitemHref(wi.ID)) + "/comments" return &app.GenericLinks{ Self: &commentsSelf, Related: &commentsRelated, } }
// ConvertIteration converts between internal and external REST representation func ConvertIteration(request *goa.RequestData, itr *iteration.Iteration, additional ...IterationConvertFunc) *app.Iteration { iterationType := iteration.APIStringTypeIteration spaceType := "spaces" spaceID := itr.SpaceID.String() selfURL := rest.AbsoluteURL(request, app.IterationHref(itr.ID)) spaceSelfURL := rest.AbsoluteURL(request, app.SpaceHref(spaceID)) workitemsRelatedURL := rest.AbsoluteURL(request, app.WorkitemHref("?filter[iteration]="+itr.ID.String())) i := &app.Iteration{ Type: iterationType, ID: &itr.ID, Attributes: &app.IterationAttributes{ Name: &itr.Name, StartAt: itr.StartAt, EndAt: itr.EndAt, Description: itr.Description, State: &itr.State, }, Relationships: &app.IterationRelations{ Space: &app.RelationGeneric{ Data: &app.GenericData{ Type: &spaceType, ID: &spaceID, }, Links: &app.GenericLinks{ Self: &spaceSelfURL, }, }, Workitems: &app.RelationGeneric{ Links: &app.GenericLinks{ Related: &workitemsRelatedURL, }, }, }, Links: &app.GenericLinks{ Self: &selfURL, }, } if itr.ParentID != uuid.Nil { parentSelfURL := rest.AbsoluteURL(request, app.IterationHref(itr.ParentID)) parentID := itr.ParentID.String() i.Relationships.Parent = &app.RelationGeneric{ Data: &app.GenericData{ Type: &iterationType, ID: &parentID, }, Links: &app.GenericLinks{ Self: &parentSelfURL, }, } } for _, add := range additional { add(request, itr, i) } return i }
// ConvertComment converts between internal and external REST representation func ConvertComment(request *goa.RequestData, comment *comment.Comment, additional ...CommentConvertFunc) *app.Comment { selfURL := rest.AbsoluteURL(request, app.CommentsHref(comment.ID)) markup := rendering.NilSafeGetMarkup(&comment.Markup) bodyRendered := rendering.RenderMarkupToHTML(html.EscapeString(comment.Body), comment.Markup) c := &app.Comment{ Type: "comments", ID: &comment.ID, Attributes: &app.CommentAttributes{ Body: &comment.Body, BodyRendered: &bodyRendered, Markup: &markup, CreatedAt: &comment.CreatedAt, }, Relationships: &app.CommentRelations{ CreatedBy: &app.CommentCreatedBy{ Data: &app.IdentityRelationData{ Type: "identities", ID: &comment.CreatedBy, }, }, }, Links: &app.GenericLinks{ Self: &selfURL, }, } for _, add := range additional { add(request, comment, c) } return c }
// Create runs the create action. func (c *SpaceController) Create(ctx *app.CreateSpaceContext) error { _, err := login.ContextIdentity(ctx) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrUnauthorized(err.Error())) } err = validateCreateSpace(ctx) if err != nil { return jsonapi.JSONErrorResponse(ctx, err) } return application.Transactional(c.db, func(appl application.Application) error { reqSpace := ctx.Payload.Data newSpace := space.Space{ Name: *reqSpace.Attributes.Name, } if reqSpace.Attributes.Description != nil { newSpace.Description = *reqSpace.Attributes.Description } space, err := appl.Spaces().Create(ctx, &newSpace) if err != nil { return jsonapi.JSONErrorResponse(ctx, err) } res := &app.SpaceSingle{ Data: ConvertSpace(ctx.RequestData, space), } ctx.ResponseData.Header().Set("Location", rest.AbsoluteURL(ctx.RequestData, app.SpaceHref(res.Data.ID))) return ctx.Created(res) }) }
// enrichLinkCategorySingle includes related resources in the single's "included" array func enrichLinkCategorySingle(ctx *workItemLinkContext, single *app.WorkItemLinkCategorySingle) error { // Add "links" element selfURL := rest.AbsoluteURL(ctx.RequestData, ctx.LinkFunc(*single.Data.ID)) single.Data.Links = &app.GenericLinks{ Self: &selfURL, } return nil }
// enrichLinkCategoryList includes related resources in the list's "included" array func enrichLinkCategoryList(ctx *workItemLinkContext, list *app.WorkItemLinkCategoryList) error { // Add "links" element for _, data := range list.Data { selfURL := rest.AbsoluteURL(ctx.RequestData, ctx.LinkFunc(*data.ID)) data.Links = &app.GenericLinks{ Self: &selfURL, } } return nil }
// enrichLinkSingle includes related resources in the link's "included" array func enrichLinkSingle(ctx *workItemLinkContext, link *app.WorkItemLinkSingle) error { // include link type linkType, err := ctx.Application.WorkItemLinkTypes().Load(ctx.Context, link.Data.Relationships.LinkType.Data.ID) if err != nil { return errs.WithStack(err) } link.Included = append(link.Included, linkType.Data) // include link category linkCat, err := ctx.Application.WorkItemLinkCategories().Load(ctx.Context, linkType.Data.Relationships.LinkCategory.Data.ID) if err != nil { return errs.WithStack(err) } link.Included = append(link.Included, linkCat.Data) // TODO(kwk): include source work item type (once #559 is merged) // sourceWit, err := appl.WorkItemTypes().Load(ctx, linkType.Data.Relationships.SourceType.Data.ID) // if err != nil { // return errs.WithStack(err) // } // link.Included = append(link.Included, sourceWit.Data) // TODO(kwk): include target work item type (once #559 is merged) // targetWit, err := appl.WorkItemTypes().Load(ctx, linkType.Data.Relationships.TargetType.Data.ID) // if err != nil { // return errs.WithStack(err) // } // link.Included = append(link.Included, targetWit.Data) // TODO(kwk): include source work item sourceWi, err := ctx.Application.WorkItems().Load(ctx.Context, link.Data.Relationships.Source.Data.ID) if err != nil { return errs.WithStack(err) } link.Included = append(link.Included, ConvertWorkItem(ctx.RequestData, sourceWi)) // TODO(kwk): include target work item targetWi, err := ctx.Application.WorkItems().Load(ctx.Context, link.Data.Relationships.Target.Data.ID) if err != nil { return errs.WithStack(err) } link.Included = append(link.Included, ConvertWorkItem(ctx.RequestData, targetWi)) // Add links to individual link data element selfURL := rest.AbsoluteURL(ctx.RequestData, ctx.LinkFunc(*link.Data.ID)) link.Data.Links = &app.GenericLinks{ Self: &selfURL, } return nil }
// CommentIncludeParent adds the "parent" relationship to this Comment func CommentIncludeParent(request *goa.RequestData, comment *comment.Comment, data *app.Comment, ref HrefFunc, parentType string) { parentSelf := rest.AbsoluteURL(request, ref(comment.ParentID)) data.Relationships.Parent = &app.RelationGeneric{ Data: &app.GenericData{ Type: &parentType, ID: &comment.ParentID, }, Links: &app.GenericLinks{ Self: &parentSelf, }, } }
// enrichLinkList includes related resources in the linkArr's "included" element func enrichLinkList(ctx *workItemLinkContext, linkArr *app.WorkItemLinkList) error { // include link types typeDataArr, err := getTypesOfLinks(ctx, linkArr.Data) if err != nil { return errs.WithStack(err) } // Convert slice of objects to slice of interface (see https://golang.org/doc/faq#convert_slice_of_interface) interfaceArr := make([]interface{}, len(typeDataArr)) for i, v := range typeDataArr { interfaceArr[i] = v } linkArr.Included = append(linkArr.Included, interfaceArr...) // include link categories catDataArr, err := getCategoriesOfLinkTypes(ctx, typeDataArr) if err != nil { return errs.WithStack(err) } // Convert slice of objects to slice of interface (see https://golang.org/doc/faq#convert_slice_of_interface) interfaceArr = make([]interface{}, len(catDataArr)) for i, v := range catDataArr { interfaceArr[i] = v } linkArr.Included = append(linkArr.Included, interfaceArr...) // TODO(kwk): Include WIs from source and target workItemDataArr, err := getWorkItemsOfLinks(ctx, linkArr.Data) if err != nil { return errs.WithStack(err) } // Convert slice of objects to slice of interface (see https://golang.org/doc/faq#convert_slice_of_interface) interfaceArr = make([]interface{}, len(workItemDataArr)) for i, v := range workItemDataArr { interfaceArr[i] = v } linkArr.Included = append(linkArr.Included, interfaceArr...) // TODO(kwk): Include WITs (once #559 is merged) // Add links to individual link data element for _, link := range linkArr.Data { selfURL := rest.AbsoluteURL(ctx.RequestData, ctx.LinkFunc(*link.ID)) link.Links = &app.GenericLinks{ Self: &selfURL, } } return nil }
// Create runs the create action. func (c *SpaceIterationsController) Create(ctx *app.CreateSpaceIterationsContext) error { _, err := login.ContextIdentity(ctx) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrUnauthorized(err.Error())) } spaceID, err := uuid.FromString(ctx.ID) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrNotFound(err.Error())) } // Validate Request if ctx.Payload.Data == nil { return jsonapi.JSONErrorResponse(ctx, errors.NewBadParameterError("data", nil).Expected("not nil")) } reqIter := ctx.Payload.Data if reqIter.Attributes.Name == nil { return jsonapi.JSONErrorResponse(ctx, errors.NewBadParameterError("data.attributes.name", nil).Expected("not nil")) } return application.Transactional(c.db, func(appl application.Application) error { _, err = appl.Spaces().Load(ctx, spaceID) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrNotFound(err.Error())) } newItr := iteration.Iteration{ SpaceID: spaceID, Name: *reqIter.Attributes.Name, StartAt: reqIter.Attributes.StartAt, EndAt: reqIter.Attributes.EndAt, } if reqIter.Attributes.Description != nil { newItr.Description = reqIter.Attributes.Description } err = appl.Iterations().Create(ctx, &newItr) if err != nil { return jsonapi.JSONErrorResponse(ctx, err) } res := &app.IterationSingle{ Data: ConvertIteration(ctx.RequestData, &newItr), } ctx.ResponseData.Header().Set("Location", rest.AbsoluteURL(ctx.RequestData, app.IterationHref(res.Data.ID))) return ctx.Created(res) }) }
// enrichLinkTypeSingle includes related resources in the single's "included" array func enrichLinkTypeSingle(ctx *workItemLinkContext, single *app.WorkItemLinkTypeSingle) error { // Add "links" element selfURL := rest.AbsoluteURL(ctx.RequestData, ctx.LinkFunc(*single.Data.ID)) single.Data.Links = &app.GenericLinks{ Self: &selfURL, } // Now include the optional link category data in the work item link type "included" array linkCat, err := ctx.Application.WorkItemLinkCategories().Load(ctx.Context, single.Data.Relationships.LinkCategory.Data.ID) if err != nil { jerrors, httpStatusCode := jsonapi.ErrorToJSONAPIErrors(err) return ctx.ResponseData.Service.Send(ctx.Context, httpStatusCode, jerrors) } single.Included = append(single.Included, linkCat.Data) return nil }
// CreateChild runs the create-child action. func (c *AreaController) CreateChild(ctx *app.CreateChildAreaContext) error { _, err := login.ContextIdentity(ctx) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrUnauthorized(err.Error())) } parentID, err := uuid.FromString(ctx.ID) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrNotFound(err.Error())) } return application.Transactional(c.db, func(appl application.Application) error { parent, err := appl.Areas().Load(ctx, parentID) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrNotFound(err.Error())) } reqArea := ctx.Payload.Data if reqArea.Attributes.Name == nil { return jsonapi.JSONErrorResponse(ctx, errors.NewBadParameterError("data.attributes.name", nil).Expected("not nil")) } childPath := area.ConvertToLtreeFormat(parentID.String()) if parent.Path != "" { childPath = parent.Path + pathSepInDatabase + childPath } newArea := area.Area{ SpaceID: parent.SpaceID, Path: childPath, Name: *reqArea.Attributes.Name, } err = appl.Areas().Create(ctx, &newArea) if err != nil { return jsonapi.JSONErrorResponse(ctx, err) } res := &app.AreaSingle{ Data: ConvertArea(appl, ctx.RequestData, &newArea, addResolvedPath), } ctx.ResponseData.Header().Set("Location", rest.AbsoluteURL(ctx.RequestData, app.AreaHref(res.Data.ID))) return ctx.Created(res) }) }
// Create runs the create action. func (c *SpaceAreasController) Create(ctx *app.CreateSpaceAreasContext) error { _, err := login.ContextIdentity(ctx) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrUnauthorized(err.Error())) } spaceID, err := uuid.FromString(ctx.ID) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrNotFound(err.Error())) } // Validate Request if ctx.Payload.Data == nil { return jsonapi.JSONErrorResponse(ctx, errors.NewBadParameterError("data", nil).Expected("not nil")) } reqIter := ctx.Payload.Data if reqIter.Attributes.Name == nil { return jsonapi.JSONErrorResponse(ctx, errors.NewBadParameterError("data.attributes.name", nil).Expected("not nil")) } return application.Transactional(c.db, func(appl application.Application) error { _, err = appl.Spaces().Load(ctx, spaceID) if err != nil { return jsonapi.JSONErrorResponse(ctx, goa.ErrNotFound(err.Error())) } newArea := area.Area{ SpaceID: spaceID, Name: *reqIter.Attributes.Name, } err = appl.Areas().Create(ctx, &newArea) if err != nil { return jsonapi.JSONErrorResponse(ctx, err) } res := &app.AreaSingle{ Data: ConvertArea(appl, ctx.RequestData, &newArea, addResolvedPath), } ctx.ResponseData.Header().Set("Location", rest.AbsoluteURL(ctx.RequestData, app.AreaHref(res.Data.ID))) return ctx.Created(res) }) }
// enrichLinkTypeList includes related resources in the list's "included" array func enrichLinkTypeList(ctx *workItemLinkContext, list *app.WorkItemLinkTypeList) error { // Add "links" element for _, data := range list.Data { selfURL := rest.AbsoluteURL(ctx.RequestData, ctx.LinkFunc(*data.ID)) data.Links = &app.GenericLinks{ Self: &selfURL, } } // Build our "set" of distinct category IDs already converted as strings categoryIDMap := map[string]bool{} for _, typeData := range list.Data { categoryIDMap[typeData.Relationships.LinkCategory.Data.ID] = true } // Now include the optional link category data in the work item link type "included" array for categoryID := range categoryIDMap { linkCat, err := ctx.Application.WorkItemLinkCategories().Load(ctx.Context, categoryID) if err != nil { jerrors, httpStatusCode := jsonapi.ErrorToJSONAPIErrors(err) return ctx.ResponseData.Service.Send(ctx.Context, httpStatusCode, jerrors) } list.Included = append(list.Included, linkCat.Data) } return nil }
func createAreaLinks(request *goa.RequestData, id interface{}) *app.GenericLinks { selfURL := rest.AbsoluteURL(request, app.AreaHref(id)) return &app.GenericLinks{ Self: &selfURL, } }
// ConvertArea converts between internal and external REST representation func ConvertArea(appl application.Application, request *goa.RequestData, ar *area.Area, additional ...AreaConvertFunc) *app.Area { areaType := area.APIStringTypeAreas spaceType := "spaces" spaceID := ar.SpaceID.String() selfURL := rest.AbsoluteURL(request, app.AreaHref(ar.ID)) childURL := rest.AbsoluteURL(request, app.AreaHref(ar.ID)+"/children") spaceSelfURL := rest.AbsoluteURL(request, app.SpaceHref(spaceID)) pathToTopMostParent := pathSepInService + area.ConvertFromLtreeFormat(ar.Path) // /uuid1/uuid2/uuid3s i := &app.Area{ Type: areaType, ID: &ar.ID, Attributes: &app.AreaAttributes{ Name: &ar.Name, CreatedAt: &ar.CreatedAt, Version: &ar.Version, ParentPath: &pathToTopMostParent, }, Relationships: &app.AreaRelations{ Space: &app.RelationGeneric{ Data: &app.GenericData{ Type: &spaceType, ID: &spaceID, }, Links: &app.GenericLinks{ Self: &spaceSelfURL, }, }, Children: &app.RelationGeneric{ Links: &app.GenericLinks{ Self: &childURL, }, }, }, Links: &app.GenericLinks{ Self: &selfURL, }, } // Now check the path, if the path is empty, then this is the topmost area // in a specific space. if ar.Path != "" { allParents := strings.Split(area.ConvertFromLtreeFormat(ar.Path), pathSepInService) parentID := allParents[len(allParents)-1] // Only the immediate parent's URL. parentSelfURL := rest.AbsoluteURL(request, app.AreaHref(parentID)) i.Relationships.Parent = &app.RelationGeneric{ Data: &app.GenericData{ Type: &areaType, ID: &parentID, }, Links: &app.GenericLinks{ Self: &parentSelfURL, }, } } for _, add := range additional { add(appl, request, ar, i) } return i }
// Perform performs authenticatin func (keycloak *KeycloakOAuthProvider) Perform(ctx *app.AuthorizeLoginContext) error { state := ctx.Params.Get("state") code := ctx.Params.Get("code") referer := ctx.RequestData.Header.Get("Referer") if code != "" { // After redirect from oauth provider // validate known state var knownReferer string defer func() { delete(stateReferer, state) }() knownReferer = stateReferer[state] if state == "" || knownReferer == "" { log.Error(ctx, map[string]interface{}{ "state": state, "referer": knownReferer, }, "state or known referer was empty") jerrors, _ := jsonapi.ErrorToJSONAPIErrors(goa.ErrUnauthorized("State or known referer was empty")) return ctx.Unauthorized(jerrors) } keycloakToken, err := keycloak.config.Exchange(ctx, code) if err != nil || keycloakToken.AccessToken == "" { log.Error(ctx, map[string]interface{}{ "code": code, "err": err, }, "keycloak exchange operation failed") return redirectWithError(ctx, knownReferer, InvalidCodeError) } _, _, err = keycloak.CreateKeycloakUser(keycloakToken.AccessToken, ctx) if err != nil { log.Error(ctx, map[string]interface{}{ "token": keycloakToken.AccessToken, "err": err, }, "failed to create a user and KeyCloak identity using the access token") return redirectWithError(ctx, knownReferer, err.Error()) } referelURL, err := url.Parse(knownReferer) if err != nil { return redirectWithError(ctx, knownReferer, err.Error()) } err = encodeToken(referelURL, keycloakToken) if err != nil { return redirectWithError(ctx, knownReferer, err.Error()) } ctx.ResponseData.Header().Set("Location", referelURL.String()) return ctx.TemporaryRedirect() } // First time access, redirect to oauth provider // store referer id to state for redirect later log.Info(ctx, map[string]interface{}{ "pkg": "login", "referer": referer, }, "Got Request from!") state = uuid.NewV4().String() mapLock.Lock() defer mapLock.Unlock() stateReferer[state] = referer keycloak.config.RedirectURL = rest.AbsoluteURL(ctx.RequestData, "/api/login/authorize") redirectURL := keycloak.config.AuthCodeURL(state, oauth2.AccessTypeOnline) ctx.ResponseData.Header().Set("Location", redirectURL) return ctx.TemporaryRedirect() }
func buildAbsoluteURL(req *goa.RequestData) string { return rest.AbsoluteURL(req, req.URL.Path) }
// ConvertWorkItem is responsible for converting given WorkItem model object into a // response resource object by jsonapi.org specifications func ConvertWorkItem(request *goa.RequestData, wi *app.WorkItem, additional ...WorkItemConvertFunc) *app.WorkItem2 { // construct default values from input WI selfURL := rest.AbsoluteURL(request, app.WorkitemHref(wi.ID)) sourceLinkTypesURL := rest.AbsoluteURL(request, app.WorkitemtypeHref(wi.Type)+sourceLinkTypesRouteEnd) targetLinkTypesURL := rest.AbsoluteURL(request, app.WorkitemtypeHref(wi.Type)+targetLinkTypesRouteEnd) op := &app.WorkItem2{ ID: &wi.ID, Type: APIStringTypeWorkItem, Attributes: map[string]interface{}{ "version": wi.Version, }, Relationships: &app.WorkItemRelationships{ BaseType: &app.RelationBaseType{ Data: &app.BaseTypeData{ ID: wi.Type, Type: APIStringTypeWorkItemType, }, }, }, Links: &app.GenericLinksForWorkItem{ Self: &selfURL, SourceLinkTypes: &sourceLinkTypesURL, TargetLinkTypes: &targetLinkTypesURL, }, } // Move fields into Relationships or Attributes as needed // TODO: Loop based on WorKItemType and match against Field.Type instead of directly to field value for name, val := range wi.Fields { switch name { case workitem.SystemAssignees: if val != nil { valArr := val.([]interface{}) op.Relationships.Assignees = &app.RelationGenericList{ Data: ConvertUsersSimple(request, valArr), } } case workitem.SystemCreator: if val != nil { valStr := val.(string) op.Relationships.Creator = &app.RelationGeneric{ Data: ConvertUserSimple(request, valStr), } } case workitem.SystemIteration: if val != nil { valStr := val.(string) op.Relationships.Iteration = &app.RelationGeneric{ Data: ConvertIterationSimple(request, valStr), } } case workitem.SystemArea: if val != nil { valStr := val.(string) op.Relationships.Area = &app.RelationGeneric{ Data: ConvertAreaSimple(request, valStr), } } case workitem.SystemTitle: // 'HTML escape' the title to prevent script injection op.Attributes[name] = html.EscapeString(val.(string)) case workitem.SystemDescription: description := rendering.NewMarkupContentFromValue(val) if description != nil { op.Attributes[name] = (*description).Content op.Attributes[workitem.SystemDescriptionMarkup] = (*description).Markup // let's include the rendered description while 'HTML escaping' it to prevent script injection op.Attributes[workitem.SystemDescriptionRendered] = rendering.RenderMarkupToHTML(html.EscapeString((*description).Content), (*description).Markup) } default: op.Attributes[name] = val } } if op.Relationships.Assignees == nil { op.Relationships.Assignees = &app.RelationGenericList{Data: nil} } if op.Relationships.Iteration == nil { op.Relationships.Iteration = &app.RelationGeneric{Data: nil} } if op.Relationships.Area == nil { op.Relationships.Area = &app.RelationGeneric{Data: nil} } // Always include Comments Link, but optionally use WorkItemIncludeCommentsAndTotal WorkItemIncludeComments(request, wi, op) for _, add := range additional { add(request, wi, op) } return op }