github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/web/intents/intents.go (about) 1 package intents 2 3 import ( 4 "encoding/json" 5 "errors" 6 "net/http" 7 "strings" 8 9 "github.com/cozy/cozy-stack/model/instance" 10 "github.com/cozy/cozy-stack/model/intent" 11 "github.com/cozy/cozy-stack/model/permission" 12 "github.com/cozy/cozy-stack/pkg/consts" 13 "github.com/cozy/cozy-stack/pkg/couchdb" 14 "github.com/cozy/cozy-stack/pkg/jsonapi" 15 "github.com/cozy/cozy-stack/web/middlewares" 16 "github.com/labstack/echo/v4" 17 ) 18 19 type apiIntent struct { 20 doc *intent.Intent 21 ins *instance.Instance 22 } 23 24 func (i *apiIntent) ID() string { return i.doc.ID() } 25 func (i *apiIntent) Rev() string { return i.doc.Rev() } 26 func (i *apiIntent) DocType() string { return consts.Intents } 27 func (i *apiIntent) Clone() couchdb.Doc { return i } 28 func (i *apiIntent) SetID(id string) { i.doc.SetID(id) } 29 func (i *apiIntent) SetRev(rev string) { i.doc.SetRev(rev) } 30 func (i *apiIntent) Relationships() jsonapi.RelationshipMap { return nil } 31 func (i *apiIntent) Included() []jsonapi.Object { return nil } 32 func (i *apiIntent) Links() *jsonapi.LinksList { 33 parts := strings.SplitN(i.doc.Client, "/", 2) 34 if len(parts) < 2 { 35 return nil 36 } 37 perms, err := permission.GetForWebapp(i.ins, parts[1]) 38 if err != nil { 39 return nil 40 } 41 return &jsonapi.LinksList{ 42 Self: "/intents/" + i.ID(), 43 Perms: "/permissions/" + perms.ID(), 44 } 45 } 46 47 // In the JSON-API, the client is the domain of the client-side app that 48 // asked the intent (it is used for postMessage) 49 func (i *apiIntent) MarshalJSON() ([]byte, error) { 50 was := i.doc.Client 51 parts := strings.SplitN(i.doc.Client, "/", 2) 52 if len(parts) < 2 { 53 i.doc.Client = "" 54 } else { 55 u := i.ins.SubDomain(parts[1]) 56 u.Path = "" 57 i.doc.Client = u.String() 58 } 59 res, err := json.Marshal(i.doc) 60 i.doc.Client = was 61 return res, err 62 } 63 64 func createIntent(c echo.Context) error { 65 pdoc, err := middlewares.GetPermission(c) 66 if err != nil { 67 return echo.NewHTTPError(http.StatusForbidden) 68 } 69 instance := middlewares.GetInstance(c) 70 intent := &intent.Intent{} 71 if _, err = jsonapi.Bind(c.Request().Body, intent); err != nil { 72 return jsonapi.BadRequest(err) 73 } 74 if intent.Action == "" { 75 return jsonapi.InvalidParameter("action", errors.New("Action is missing")) 76 } 77 if intent.Type == "" { 78 return jsonapi.InvalidParameter("type", errors.New("Type is missing")) 79 } 80 intent.Client = pdoc.SourceID 81 intent.SetID("") 82 intent.SetRev("") 83 intent.Services = nil 84 if err = intent.Save(instance); err != nil { 85 return wrapIntentsError(err) 86 } 87 if err = intent.FillServices(instance); err != nil { 88 return wrapIntentsError(err) 89 } 90 // Fill available webapps only if there are no services found 91 if len(intent.Services) == 0 { 92 if err = intent.FillAvailableWebapps(instance); err != nil { 93 return wrapIntentsError(err) 94 } 95 } 96 if err = intent.Save(instance); err != nil { 97 return wrapIntentsError(err) 98 } 99 api := &apiIntent{intent, instance} 100 return jsonapi.Data(c, http.StatusOK, api, nil) 101 } 102 103 func getIntent(c echo.Context) error { 104 instance := middlewares.GetInstance(c) 105 intent := &intent.Intent{} 106 id := c.Param("id") 107 pdoc, err := middlewares.GetPermission(c) 108 if err != nil { 109 return echo.NewHTTPError(http.StatusForbidden) 110 } 111 if err = couchdb.GetDoc(instance, consts.Intents, id, intent); err != nil { 112 return wrapIntentsError(err) 113 } 114 allowed := false 115 for _, service := range intent.Services { 116 if pdoc.SourceID == consts.Apps+"/"+service.Slug { 117 allowed = true 118 } 119 } 120 if !allowed { 121 return echo.NewHTTPError(http.StatusForbidden) 122 } 123 api := &apiIntent{intent, instance} 124 return jsonapi.Data(c, http.StatusOK, api, nil) 125 } 126 127 func wrapIntentsError(err error) error { 128 if couchdb.IsNotFoundError(err) { 129 return jsonapi.NotFound(err) 130 } 131 return jsonapi.InternalServerError(err) 132 } 133 134 // Routes sets the routing for the intents service 135 func Routes(router *echo.Group) { 136 router.POST("", createIntent) 137 router.GET("/:id", getIntent) 138 }