github.com/aleksi/gonuts.io@v0.0.0-20130622121132-3b0f2d1999fb/app/gonuts/controllers/nut.go (about) 1 package controllers 2 3 import ( 4 "appengine" 5 "appengine/blobstore" 6 "appengine/datastore" 7 "bytes" 8 "fmt" 9 "html/template" 10 "io/ioutil" 11 "net/http" 12 "strings" 13 "time" 14 15 "gonuts" 16 nutp "gonuts.io/AlekSi/nut" 17 ) 18 19 func nutCreateHandler(c appengine.Context, w http.ResponseWriter, r *http.Request) { 20 d := make(ContentData) 21 ct := r.Header.Get("Content-Type") 22 putNut := ct == "application/zip" 23 24 if !putNut { 25 err := fmt.Errorf(`Unexpected Content-Type %q, should be "application/zip".`, ct) 26 ServeJSONError(w, http.StatusNotAcceptable, err, d) 27 return 28 } 29 30 vendor := r.URL.Query().Get(":vendor") 31 name := r.URL.Query().Get(":name") 32 ver := r.URL.Query().Get(":version") 33 34 if vendor == "" || !nutp.VendorRegexp.MatchString(vendor) || name == "" || (ver != "" && !nutp.VersionRegexp.MatchString(ver)) { 35 err := fmt.Errorf("Invalid vendor %q, name %q or version %q.", vendor, name, ver) 36 ServeJSONError(w, http.StatusBadRequest, err, d) 37 return 38 } 39 40 // extract token from request 41 token := r.URL.Query().Get("token") 42 if token == "" { 43 ServeJSONError(w, http.StatusForbidden, fmt.Errorf("Can't find 'token' in get parameters."), d) 44 return 45 } 46 47 // find user by token 48 q := datastore.NewQuery("User").KeysOnly().Filter("Token=", token) 49 userKeys, err := q.Limit(2).GetAll(c, nil) 50 if err != nil || len(userKeys) != 1 { 51 if err == nil || err == datastore.ErrNoSuchEntity { 52 err = fmt.Errorf("Can't find user with token %q.", token) 53 } 54 ServeJSONError(w, http.StatusForbidden, err, d) 55 return 56 } 57 userID := userKeys[0].StringID() 58 59 // user should belong to vendor 60 v := gonuts.Vendor{} 61 err = datastore.Get(c, gonuts.VendorKey(c, vendor), &v) 62 if err == datastore.ErrNoSuchEntity { 63 err = fmt.Errorf("Can't find vendor %q.", vendor) 64 ServeJSONError(w, http.StatusNotFound, err, d) 65 return 66 } 67 if err != nil { 68 ServeJSONError(w, http.StatusInternalServerError, err, d) 69 return 70 } 71 found := false 72 for _, id := range v.UserStringID { 73 if id == userID { 74 found = true 75 break 76 } 77 } 78 if !found { 79 err = fmt.Errorf("You don't have publish access to vendor %q.", vendor) 80 ServeJSONError(w, http.StatusForbidden, err, d) 81 return 82 } 83 84 // nut version should not exist 85 nutKey := gonuts.NutKey(c, vendor, name) 86 nut := gonuts.Nut{Vendor: vendor, Name: name} 87 versionKey := gonuts.VersionKey(c, vendor, name, ver) 88 version := gonuts.Version{Vendor: vendor, Name: name, Version: ver, CreatedAt: time.Now()} 89 err = datastore.Get(c, versionKey, &version) 90 if err != nil && err != datastore.ErrNoSuchEntity { 91 ServeJSONError(w, http.StatusInternalServerError, err, d) 92 return 93 } 94 if err == nil { 95 ServeJSONError(w, http.StatusConflict, fmt.Errorf("Nut %s/%s version %s already exists.", vendor, name, ver), d) 96 return 97 } 98 99 // read nut from request body 100 nf := new(nutp.NutFile) 101 b, err := ioutil.ReadAll(r.Body) 102 if err == nil { 103 _, err = nf.ReadFrom(bytes.NewReader(b)) 104 } 105 if err != nil { 106 ServeJSONError(w, http.StatusBadRequest, err, d) 107 return 108 } 109 nut.Doc = nf.Doc 110 version.Doc = nf.Doc 111 version.Homepage = nf.Homepage 112 version.VersionNum = nf.Version.Major*1000000 + nf.Version.Minor*1000 + nf.Version.Patch // for sorting 113 114 // check vendor, name and version match 115 if nf.Vendor != vendor || nf.Name != name || nf.Version.String() != ver { 116 err = fmt.Errorf("Nut vendor %q, name %q and version %q from URL don't match found in body: %q %q %q.", 117 vendor, name, ver, nf.Vendor, nf.Name, nf.Version.String()) 118 ServeJSONError(w, http.StatusBadRequest, err, d) 119 return 120 } 121 122 // check nut 123 errors := nf.Check() 124 if len(errors) != 0 { 125 err = fmt.Errorf("%s", strings.Join(errors, "\n")) 126 ServeJSONError(w, http.StatusBadRequest, err, d) 127 return 128 } 129 130 // store nut blob 131 bw, err := blobstore.Create(c, ct) 132 if err == nil { 133 _, err = bw.Write(b) 134 if err == nil { 135 err = bw.Close() 136 } 137 } 138 if err != nil { 139 ServeJSONError(w, http.StatusInternalServerError, err, d) 140 return 141 } 142 143 // store nut version 144 blobKey, err := bw.Key() 145 if err == nil { 146 version.BlobKey = blobKey 147 _, err = datastore.Put(c, versionKey, &version) 148 } 149 if err != nil { 150 ServeJSONError(w, http.StatusInternalServerError, err, d) 151 return 152 } 153 154 // store nut with new doc 155 _, err = datastore.Put(c, nutKey, &nut) 156 if err != nil { 157 ServeJSONError(w, http.StatusInternalServerError, err, d) 158 return 159 } 160 161 // update search index 162 err = gonuts.AddToSearchIndex(c, &nut) 163 gonuts.LogError(c, err) 164 165 // done! 166 d["Message"] = fmt.Sprintf("Nut %s/%s version %s published.", vendor, name, ver) 167 ServeJSON(w, http.StatusCreated, d) 168 return 169 } 170 171 func nutShowHandler(c appengine.Context, w http.ResponseWriter, r *http.Request) { 172 d := make(ContentData) 173 getNut := r.Header.Get("Accept") == "application/zip" 174 175 vendor := r.URL.Query().Get(":vendor") 176 name := r.URL.Query().Get(":name") 177 ver := r.URL.Query().Get(":version") 178 179 if vendor == "" || !nutp.VendorRegexp.MatchString(vendor) || name == "" || (ver != "" && !nutp.VersionRegexp.MatchString(ver)) { 180 err := fmt.Errorf("Invalid vendor %q, name %q or version %q.", vendor, name, ver) 181 ServeJSONError(w, http.StatusBadRequest, err, d) 182 return 183 } 184 185 current := new(gonuts.Version) 186 var err error 187 q := datastore.NewQuery("Version").Filter("Vendor=", vendor).Filter("Name=", name) 188 if ver == "" { 189 _, err = q.Order("-VersionNum").Limit(1).Run(c).Next(current) 190 } else { 191 key := gonuts.VersionKey(c, vendor, name, ver) 192 err = datastore.Get(c, key, current) 193 } 194 gonuts.LogError(c, err) 195 196 var title string 197 if current.BlobKey != "" { 198 // send nut file and exit 199 if getNut { 200 current.Downloads++ 201 key := gonuts.VersionKey(c, current.Vendor, current.Name, current.Version) 202 _, err := datastore.Put(c, key, current) 203 gonuts.LogError(c, err) 204 blobstore.Send(w, current.BlobKey) 205 return 206 } 207 208 // find all versions 209 var all []gonuts.Version 210 _, err = q.Order("-CreatedAt").GetAll(c, &all) 211 gonuts.LogError(c, err) 212 213 // prepare data for template 214 cm := make(map[string]interface{}) 215 cm["Vendor"] = current.Vendor 216 cm["Name"] = current.Name 217 cm["Version"] = current.Version 218 cm["Doc"] = current.Doc 219 cm["Homepage"] = current.Homepage 220 d["Current"] = cm 221 d["All"] = all 222 title = fmt.Sprintf("%s/%s %s", current.Vendor, current.Name, current.Version) 223 } else { 224 w.WriteHeader(http.StatusNotFound) 225 if getNut { 226 return 227 } 228 title = fmt.Sprintf("Nut %s/%s version %s not found", vendor, name, ver) 229 } 230 231 var content bytes.Buffer 232 gonuts.PanicIfErr(Base.ExecuteTemplate(&content, "nut.html", d)) 233 234 bd := BaseData{ 235 Tabtitle: title, 236 Title: title, 237 Content: template.HTML(content.String()), 238 } 239 240 gonuts.PanicIfErr(Base.Execute(w, &bd)) 241 }