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  }