func AddSubscription(c mpg.Context, w http.ResponseWriter, r *http.Request) { backupOPML(c) cu := user.Current(c) url := r.FormValue("url") o := &OpmlOutline{ Outline: []*OpmlOutline{ &OpmlOutline{XmlUrl: url}, }, } if err := addFeed(c, cu.ID, o); err != nil { c.Errorf("add sub error (%s): %s", url, err.Error()) serveError(w, err) return } gn := goon.FromContext(c) ud := UserData{Id: "data", Parent: gn.Key(&User{Id: cu.ID})} gn.Get(&ud) if err := mergeUserOpml(c, &ud, o); err != nil { c.Errorf("add sub error opml (%v): %v", url, err) serveError(w, err) return } gn.PutMany(&ud, &Log{ Parent: ud.Parent, Id: time.Now().UnixNano(), Text: fmt.Sprintf("add sub: %v", url), }) if r.Method == "GET" { http.Redirect(w, r, routeUrl("main"), http.StatusFound) } backupOPML(c) }
func Charge(c mpg.Context, w http.ResponseWriter, r *http.Request) { cu := user.Current(c) gn := goon.FromContext(c) u := User{Id: cu.ID} uc := UserCharge{Id: 1, Parent: gn.Key(&u)} if err := gn.Get(&u); err != nil { serveError(w, err) return } else if u.Account != AFree { serveError(w, fmt.Errorf("You're already subscribed.")) return } if err := gn.Get(&uc); err == nil && len(uc.Customer) > 0 { serveError(w, fmt.Errorf("You're already subscribed.")) return } else if err != datastore.ErrNoSuchEntity { serveError(w, err) return } resp, err := stripe(c, "POST", "customers", url.Values{ "email": {u.Email}, "description": {u.Id}, "card": {r.FormValue("stripeToken")}, "plan": {STRIPE_PLAN}, }.Encode()) if err != nil { serveError(w, err) return } else if resp.StatusCode != http.StatusOK { c.Errorf("%s", resp.Body) serveError(w, fmt.Errorf("Error")) return } defer resp.Body.Close() b, _ := ioutil.ReadAll(resp.Body) var sc StripeCustomer if err := json.Unmarshal(b, &sc); err != nil { serveError(w, err) return } if err := gn.RunInTransaction(func(gn *goon.Goon) error { if err := gn.Get(&u); err != nil && err != datastore.ErrNoSuchEntity { return err } if err := gn.Get(&uc); err != nil && err != datastore.ErrNoSuchEntity { return err } u.Account = APaid uc.Customer = sc.Id uc.Last4 = sc.Card.Last4 uc.Created = time.Unix(sc.Created, 0) _, err := gn.PutMany(&u, &uc) return err }, nil); err != nil { serveError(w, err) return } b, _ = json.Marshal(&uc) w.Write(b) }
func Donate(c mpg.Context, w http.ResponseWriter, r *http.Request) { cu := user.Current(c) gn := goon.FromContext(c) u := User{Id: cu.ID} if err := gn.Get(&u); err != nil { serveError(w, err) return } amount, err := strconv.Atoi(r.FormValue("amount")) if err != nil || amount < 200 { serveError(w, fmt.Errorf("bad amount: %v", r.FormValue("amount"))) return } resp, err := stripe(c, "POST", "charges", url.Values{ "amount": {r.FormValue("amount")}, "description": {fmt.Sprintf("%v - %v", u.Id, u.Email)}, "card": {r.FormValue("stripeToken")}, "currency": {"usd"}, }.Encode()) if err != nil { serveError(w, err) return } else if resp.StatusCode != http.StatusOK { c.Errorf("%s", resp.Body) serveError(w, fmt.Errorf("Error")) return } }
func doUncheckout(c mpg.Context) (*UserCharge, error) { cu := user.Current(c) gn := goon.FromContext(c) u := User{Id: cu.ID} uc := UserCharge{Id: 1, Parent: gn.Key(&u)} if err := gn.Get(&u); err != nil { return nil, err } if err := gn.Get(&uc); err != nil || len(uc.Customer) == 0 { return nil, err } resp, err := stripe(c, "DELETE", "customers/"+uc.Customer, "") if err != nil { return nil, err } else if resp.StatusCode != http.StatusOK { c.Errorf("%s", resp.Body) c.Errorf("stripe delete error, but proceeding") } if err := gn.RunInTransaction(func(gn *goon.Goon) error { if err := gn.Get(&u); err != nil && err != datastore.ErrNoSuchEntity { return err } u.Account = AFree u.Until = uc.Next if err := gn.Delete(gn.Key(&uc)); err != nil { return err } _, err := gn.Put(&u) return err }, nil); err != nil { return nil, err } return &uc, nil }
func UploadOpml(c mpg.Context, w http.ResponseWriter, r *http.Request) { opml := Opml{} if err := json.Unmarshal([]byte(r.FormValue("opml")), &opml.Outline); err != nil { serveError(w, err) return } backupOPML(c) cu := user.Current(c) gn := goon.FromContext(c) u := User{Id: cu.ID} ud := UserData{Id: "data", Parent: gn.Key(&u)} if err := gn.Get(&ud); err != nil { serveError(w, err) c.Errorf("get err: %v", err) return } if b, err := json.Marshal(&opml); err != nil { serveError(w, err) c.Errorf("json err: %v", err) return } else { l := Log{ Parent: ud.Parent, Id: time.Now().UnixNano(), Text: fmt.Sprintf("upload opml: %v -> %v", len(ud.Opml), len(b)), } ud.Opml = b if _, err := gn.PutMany(&ud, &l); err != nil { serveError(w, err) return } backupOPML(c) } }
func includes(c mpg.Context, r *http.Request) *Includes { i := &Includes{ Angular: Angular, BootstrapCss: BootstrapCss, BootstrapJs: BootstrapJs, Jquery: Jquery, Underscore: Underscore, MiniProfiler: c.Includes(r), GoogleAnalyticsId: GOOGLE_ANALYTICS_ID, GoogleAnalyticsHost: GOOGLE_ANALYTICS_HOST, IsDev: isDevServer, } if cu := user.Current(c); cu != nil { gn := goon.FromContext(c) user := &User{Id: cu.ID} if err := gn.Get(user); err == nil { i.User = user i.IsAdmin = cu.Admin if len(user.Messages) > 0 { i.Messages = user.Messages user.Messages = nil gn.Put(user) } } } return i }
func UploadOpml(c mpg.Context, w http.ResponseWriter, r *http.Request) { opml := Opml{} if err := json.Unmarshal([]byte(r.FormValue("opml")), &opml.Outline); err != nil { serveError(w, err) return } backupOPML(c) cu := user.Current(c) gn := goon.FromContext(c) u := User{Id: cu.ID} ud := UserData{Id: "data", Parent: gn.Key(&u)} if err := gn.Get(&ud); err != nil { serveError(w, err) c.Errorf("get err: %v", err) return } if b, err := json.Marshal(&opml); err != nil { saveError(c, fmt.Sprintf("%v", opml), err) serveError(w, err) c.Errorf("json err: %v", err) return } else { ud.Opml = b } gn.Put(&ud) backupOPML(c) }
func UpdateFeed(c mpg.Context, w http.ResponseWriter, r *http.Request) { gn := goon.FromContext(appengine.Timeout(c, time.Minute)) url := r.FormValue("feed") if url == "" { c.Errorf("empty update feed") return } c.Debugf("update feed %s", url) last := len(r.FormValue("last")) > 0 f := Feed{Url: url} s := "" defer func() { gn.Put(&Log{ Parent: gn.Key(&f), Id: time.Now().UnixNano(), Text: "UpdateFeed - " + s, }) }() if err := gn.Get(&f); err == datastore.ErrNoSuchEntity { c.Errorf("no such entity - " + url) s += "NSE" return } else if err != nil { s += "err - " + err.Error() return } else if last { // noop } if time.Now().Before(f.NextUpdate) { c.Errorf("feed %v already updated: %v", url, f.NextUpdate) s += "already updated" return } feedError := func(err error) { s += "feed err - " + err.Error() f.Errors++ v := f.Errors + 1 const max = 24 * 7 if v > max { v = max } else if f.Errors == 1 { v = 0 } f.NextUpdate = time.Now().Add(time.Hour * time.Duration(v)) gn.Put(&f) c.Warningf("error with %v (%v), bump next update to %v, %v", url, f.Errors, f.NextUpdate, err) } if feed, stories, err := fetchFeed(c, f.Url, f.Url); err == nil { if err := updateFeed(c, f.Url, feed, stories, false, false, last); err != nil { feedError(err) } else { s += "success" } } else { feedError(err) } f.Subscribe(c) }
func UpdateFeeds(c mpg.Context, w http.ResponseWriter, r *http.Request) { q := datastore.NewQuery("F").KeysOnly().Filter("n <=", time.Now()) q = q.Limit(10 * 60 * 2) // 10/s queue, 2 min cron it := q.Run(appengine.Timeout(c, time.Minute)) tc := make(chan *taskqueue.Task) done := make(chan bool) i := 0 u := routeUrl("update-feed") go taskSender(c, "update-feed", tc, done) for { k, err := it.Next(nil) if err == datastore.Done { break } else if err != nil { c.Errorf("next error: %v", err.Error()) break } tc <- taskqueue.NewPOSTTask(u, url.Values{ "feed": {k.StringID()}, }) i++ } close(tc) <-done c.Infof("updating %d feeds", i) }
func DeleteOldFeed(c mpg.Context, w http.ResponseWriter, r *http.Request) { g := goon.FromContext(c) oldDate := time.Now().Add(-time.Hour * 24 * 90) feed := Feed{Url: r.FormValue("f")} if err := g.Get(&feed); err != nil { c.Criticalf("err: %v", err) return } if feed.LastViewed.After(oldDate) { return } q := datastore.NewQuery(g.Kind(&Story{})).Ancestor(g.Key(&feed)).KeysOnly() keys, err := q.GetAll(c, nil) if err != nil { c.Criticalf("err: %v", err) return } q = datastore.NewQuery(g.Kind(&StoryContent{})).Ancestor(g.Key(&feed)).KeysOnly() sckeys, err := q.GetAll(c, nil) if err != nil { c.Criticalf("err: %v", err) return } keys = append(keys, sckeys...) c.Infof("delete: %v - %v", feed.Url, len(keys)) err = g.DeleteMulti(keys) if err != nil { c.Criticalf("err: %v", err) } }
func SubscribeFeed(c mpg.Context, w http.ResponseWriter, r *http.Request) { gn := goon.FromContext(c) f := Feed{Url: r.FormValue("feed")} if err := gn.Get(&f); err != nil { c.Errorf("%v: %v", err, f.Url) serveError(w, err) return } else if f.IsSubscribed() { return } u := url.Values{} u.Add("hub.callback", f.PubSubURL()) u.Add("hub.mode", "subscribe") u.Add("hub.verify", "sync") fu, _ := url.Parse(f.Url) fu.Fragment = "" u.Add("hub.topic", fu.String()) req, err := http.NewRequest("POST", PUBSUBHUBBUB_HUB, strings.NewReader(u.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") cl := urlfetch.Client(c) resp, err := cl.Do(req) if err != nil { c.Errorf("req error: %v", err) } else if resp.StatusCode != 204 { c.Errorf("resp: %v - %v", f.Url, resp.Status) c.Errorf("%s", resp.Body) } else { c.Infof("subscribed: %v", f.Url) } }
func FeedHistory(c mpg.Context, w http.ResponseWriter, r *http.Request) { cu := user.Current(c) gn := goon.FromContext(c) u := User{Id: cu.ID} uk := gn.Key(&u) if v := r.FormValue("v"); len(v) == 0 { q := datastore.NewQuery(gn.Key(&UserOpml{}).Kind()).Ancestor(uk).KeysOnly() keys, err := gn.GetAll(q, nil) if err != nil { serveError(w, err) return } times := make([]string, len(keys)) for i, k := range keys { times[i] = strconv.FormatInt(k.IntID(), 10) } b, _ := json.Marshal(×) w.Write(b) } else { a, _ := strconv.ParseInt(v, 10, 64) uo := UserOpml{Id: a, Parent: uk} c.Errorf("k: %v", uo) if err := gn.Get(&uo); err != nil { serveError(w, err) return } downloadOpml(w, uo.opml(), cu.Email) } }
func Main(c mpg.Context, w http.ResponseWriter, r *http.Request) { if err := templates.ExecuteTemplate(w, "base.html", includes(c, w, r)); err != nil { c.Errorf("%v", err) serveError(w, err) return } }
func GetFeed(c mpg.Context, w http.ResponseWriter, r *http.Request) { gn := goon.FromContext(c) f := Feed{Url: r.FormValue("f")} var stars []string wg := sync.WaitGroup{} fk := gn.Key(&f) q := datastore.NewQuery(gn.Kind(&Story{})).Ancestor(fk).KeysOnly() q = q.Order("-" + IDX_COL) if cur := r.FormValue("c"); cur != "" { if dc, err := datastore.DecodeCursor(cur); err == nil { q = q.Start(dc) } } else { // grab the stars list on the first run wg.Add(1) go c.Step("stars", func(c mpg.Context) { gn := goon.FromContext(c) usk := starKey(c, f.Url, "") q := datastore.NewQuery(gn.Kind(&UserStar{})).Ancestor(gn.Key(usk).Parent()).KeysOnly() keys, _ := gn.GetAll(q, nil) stars = make([]string, len(keys)) for i, key := range keys { stars[i] = starID(key) } wg.Done() }) } iter := gn.Run(q) var stories []*Story for i := 0; i < 20; i++ { if k, err := iter.Next(nil); err == nil { stories = append(stories, &Story{ Id: k.StringID(), Parent: k.Parent(), }) } else if err == datastore.Done { break } else { serveError(w, err) return } } cursor := "" if ic, err := iter.Cursor(); err == nil { cursor = ic.String() } gn.GetMulti(&stories) wg.Wait() b, _ := json.Marshal(struct { Cursor string Stories []*Story Stars []string `json:",omitempty"` }{ Cursor: cursor, Stories: stories, Stars: stars, }) w.Write(b) }
func ImportOpml(c mpg.Context, w http.ResponseWriter, r *http.Request) { cu := user.Current(c) gn := goon.FromContext(c) u := User{Id: cu.ID} if err := gn.Get(&u); err != nil { serveError(w, err) return } backupOPML(c) if file, _, err := r.FormFile("file"); err == nil { if fdata, err := ioutil.ReadAll(file); err == nil { buf := bytes.NewReader(fdata) // attempt to extract from google reader takeout zip if zb, zerr := zip.NewReader(buf, int64(len(fdata))); zerr == nil { for _, f := range zb.File { if strings.HasSuffix(f.FileHeader.Name, "Reader/subscriptions.xml") { if rc, rerr := f.Open(); rerr == nil { if fb, ferr := ioutil.ReadAll(rc); ferr == nil { fdata = fb break } } } } } // Preflight the OPML, so we can report any errors. d := xml.NewDecoder(bytes.NewReader(fdata)) d.CharsetReader = charset.NewReader d.Strict = false opml := Opml{} if err := d.Decode(&opml); err != nil { serveError(w, err) c.Errorf("opml error: %v", err.Error()) return } var b bytes.Buffer enc := gob.NewEncoder(&b) err := enc.Encode(&opml) if err != nil { serveError(w, err) return } bk, err := saveFile(c, b.Bytes()) if err != nil { serveError(w, err) return } task := taskqueue.NewPOSTTask(routeUrl("import-opml-task"), url.Values{ "key": {string(bk)}, "user": {cu.ID}, }) taskqueue.Add(c, task, "import-reader") } } }
func UpdateFeeds(c mpg.Context, w http.ResponseWriter, r *http.Request) { gn := goon.FromContext(c) q := datastore.NewQuery(gn.Key(&Feed{}).Kind()).KeysOnly() q = q.Filter("n <=", time.Now()) q = q.Limit(1000) it := gn.Run(q) var keys []*datastore.Key var del []*datastore.Key for { k, err := it.Next(nil) if err == datastore.Done { break } else if err != nil { c.Errorf("next error: %v", err.Error()) break } else if len(k.StringID()) == 0 { del = append(del, k) continue } keys = append(keys, k) } tasks := make([]*taskqueue.Task, len(keys)) for i, k := range keys { tasks[i] = taskqueue.NewPOSTTask(routeUrl("update-feed"), url.Values{ "feed": {k.StringID()}, }) } var ts []*taskqueue.Task const taskLimit = 100 for len(tasks) > 0 { if len(tasks) > taskLimit { ts = tasks[:taskLimit] tasks = tasks[taskLimit:] } else { ts = tasks tasks = tasks[0:0] } if _, err := taskqueue.AddMulti(c, ts, "update-feed"); err != nil { c.Errorf("taskqueue error: %v", err.Error()) } } c.Infof("updating %d feeds", len(keys)) if len(del) > 0 { c.Errorf("attempt to delete %v feeds", len(del)) if err := gn.DeleteMulti(del); err != nil { c.Errorf("delete error: %v", err.Error()) } fmt.Fprintf(w, `<html><head><meta http-equiv="refresh" content="0"></head></html>`) fmt.Fprintf(w, "attempt to delete %v feeds", len(del)) for _, k := range del { fmt.Fprintf(w, "\n<br>%v", k) } } fmt.Fprintf(w, "updating %d feeds", len(keys)) }
func ClearFeeds(c mpg.Context, w http.ResponseWriter, r *http.Request) { if !isDevServer { return } cu := user.Current(c) gn := goon.FromContext(c) done := make(chan bool) go func() { u := &User{Id: cu.ID} defer func() { done <- true }() ud := &UserData{Id: "data", Parent: gn.Key(u)} if err := gn.Get(u); err != nil { c.Errorf("user del err: %v", err.Error()) return } gn.Get(ud) u.Read = time.Time{} ud.Read = nil ud.Opml = nil gn.PutMany(u, ud) c.Infof("%v cleared", u.Email) }() del := func(kind string) { defer func() { done <- true }() q := datastore.NewQuery(kind).KeysOnly() keys, err := gn.GetAll(q, nil) if err != nil { c.Errorf("err: %v", err.Error()) return } if err := gn.DeleteMulti(keys); err != nil { c.Errorf("err: %v", err.Error()) return } c.Infof("%v deleted", kind) } types := []interface{}{ &Feed{}, &Story{}, &StoryContent{}, &DateFormat{}, &Log{}, &UserOpml{}, } for _, i := range types { k := gn.Key(i).Kind() go del(k) } for i := 0; i < len(types); i++ { <-done } http.Redirect(w, r, routeUrl("main"), http.StatusFound) }
func fetchFeed(c mpg.Context, origUrl, fetchUrl string) (*Feed, []*Story, error) { u, err := url.Parse(fetchUrl) if err != nil { return nil, nil, err } if u.Host == "" { u.Host = u.Path u.Path = "" } const clURL = "craigslist.org" if strings.HasSuffix(u.Host, clURL) || u.Host == clURL { return nil, nil, fmt.Errorf("Craigslist blocks our server host: not possible to subscribe") } if u.Scheme == "" { u.Scheme = "http" origUrl = u.String() fetchUrl = origUrl if origUrl == "" { return nil, nil, fmt.Errorf("bad URL") } } cl := &http.Client{ Transport: &urlfetch.Transport{ Context: c, Deadline: time.Minute, }, } if resp, err := cl.Get(fetchUrl); err == nil && resp.StatusCode == http.StatusOK { reader := io.LimitReader(resp.Body, 1<<21) defer resp.Body.Close() b, _ := ioutil.ReadAll(reader) if autoUrl, err := Autodiscover(b); err == nil && origUrl == fetchUrl { if autoU, err := url.Parse(autoUrl); err == nil { if autoU.Scheme == "" { autoU.Scheme = u.Scheme } if autoU.Host == "" { autoU.Host = u.Host } autoUrl = autoU.String() } if autoUrl != fetchUrl { return fetchFeed(c, origUrl, autoUrl) } } return ParseFeed(c, origUrl, fetchUrl, b) } else if err != nil { c.Warningf("fetch feed error: %v", err) return nil, nil, fmt.Errorf("Could not fetch feed") } else { c.Warningf("fetch feed error: status code: %s", resp.Status) return nil, nil, fmt.Errorf("Bad response code from server") } }
func UpdateFeedLast(c mpg.Context, w http.ResponseWriter, r *http.Request) { gn := goon.FromContext(c) url := r.FormValue("feed") c.Debugf("update feed last %s", url) f := Feed{Url: url} if err := gn.Get(&f); err != nil { return } f.LastViewed = time.Now() gn.Put(&f) }
// Task used to subscribe a feed to push. func SubscribeFeed(c mpg.Context, w http.ResponseWriter, r *http.Request) { start := time.Now() gn := goon.FromContext(c) f := Feed{Url: r.FormValue("feed")} fk := gn.Key(&f) s := "" defer func() { gn.Put(&Log{ Parent: fk, Id: time.Now().UnixNano(), Text: "SubscribeFeed - start " + start.String() + " - f.sub " + f.Subscribed.String() + " - " + s, }) }() if err := gn.Get(&f); err != nil { c.Errorf("%v: %v", err, f.Url) serveError(w, err) s += "err" return } else if f.IsSubscribed() { s += "is subscribed" return } u := url.Values{} u.Add("hub.callback", f.PubSubURL()) u.Add("hub.mode", "subscribe") u.Add("hub.verify", "sync") fu, _ := url.Parse(f.Url) fu.Fragment = "" u.Add("hub.topic", fu.String()) req, err := http.NewRequest("POST", f.Hub, strings.NewReader(u.Encode())) req.Header.Set("Content-Type", "application/x-www-form-urlencoded") cl := &http.Client{ Transport: &urlfetch.Transport{ Context: c, Deadline: time.Minute, }, } resp, err := cl.Do(req) if err != nil { c.Errorf("req error: %v", err) } else if resp.StatusCode != http.StatusNoContent { f.Subscribed = time.Now().Add(time.Hour * 48) gn.Put(&f) if resp.StatusCode != http.StatusConflict { c.Errorf("resp: %v - %v", f.Url, resp.Status) c.Errorf("%s", resp.Body) } s += "resp err" } else { c.Infof("subscribed: %v", f.Url) s += "success" } }
func includes(c mpg.Context, w http.ResponseWriter, r *http.Request) *Includes { i := &Includes{ Angular: Angular, BootstrapCss: BootstrapCss, BootstrapJs: BootstrapJs, FontAwesome: FontAwesome, Jquery: Jquery, JqueryUI: JqueryUI, Underscore: Underscore, MiniProfiler: c.Includes(r), GoogleAnalyticsId: GOOGLE_ANALYTICS_ID, GoogleAnalyticsHost: GOOGLE_ANALYTICS_HOST, IsDev: isDevServer, StripeKey: STRIPE_KEY, StripePlans: STRIPE_PLANS, GoogleAd: GOOGLE_AD, GoogleAdT: GOOGLE_AD_TALL, } if cu := user.Current(c); cu != nil { gn := goon.FromContext(c) user := &User{Id: cu.ID} if err := gn.Get(user); err == nil { i.User = user i.IsAdmin = cu.Admin if len(user.Messages) > 0 { i.Messages = user.Messages user.Messages = nil gn.Put(user) } if user.Account != AFree { i.GoogleAd = "" i.GoogleAdT = "" } /* if _, err := r.Cookie("update-bug"); err != nil { i.Messages = append(i.Messages, "Go Read had some problems updating feeds. It may take a while for new stories to appear again. Sorry about that.") http.SetCookie(w, &http.Cookie{ Name: "update-bug", Value: "done", Expires: time.Now().Add(time.Hour * 24 * 7), }) } */ } } return i }
func SitemapFeed(c mpg.Context, w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) feed := vars["feed"] fk, err := datastore.DecodeKey(feed) if err != nil { serveError(w, err) return } bf := base64.URLEncoding.EncodeToString([]byte(fk.StringID())) q := datastore.NewQuery("S").KeysOnly().Ancestor(fk) q = q.Limit(Limit) cs := r.FormValue("c") if len(cs) > 0 { if cur, err := datastore.DecodeCursor(cs); err == nil { q = q.Start(cur) } } stories := make(map[string]string) it := q.Run(c) for { k, err := it.Next(nil) if err == datastore.Done { break } else if err != nil { c.Errorf("next error: %v", err) break } stories[k.StringID()] = base64.URLEncoding.EncodeToString([]byte(k.StringID())) } cs = "" if len(stories) == Limit { if cur, err := it.Cursor(); err == nil { cs = cur.String() } } if err := templates.ExecuteTemplate(w, "sitemap-feed.html", struct { Feed, Feed64 string Stories map[string]string Cursor string }{ Feed: feed, Feed64: bf, Stories: stories, Cursor: cs, }); err != nil { c.Errorf("%v", err) serveError(w, err) return } }
func UpdateFeeds(c mpg.Context, w http.ResponseWriter, r *http.Request) { gn := goon.FromContext(c) q := datastore.NewQuery(gn.Key(&Feed{}).Kind()).KeysOnly() q = q.Filter("n <=", time.Now()) keys, _ := gn.GetAll(q, nil) for _, k := range keys { t := taskqueue.NewPOSTTask(routeUrl("update-feed"), url.Values{ "feed": {k.StringID()}, }) if _, err := taskqueue.Add(c, t, "update-feed"); err != nil { c.Errorf("taskqueue error: %v", err.Error()) } } c.Infof("updating %d feeds", len(keys)) }
func Charge(c mpg.Context, w http.ResponseWriter, r *http.Request) { cu := user.Current(c) gn := goon.FromContext(c) u := User{Id: cu.ID} uc := &UserCharge{Id: 1, Parent: gn.Key(&u)} if err := gn.Get(&u); err != nil { serveError(w, err) return } else if u.Account != AFree { serveError(w, fmt.Errorf("You're already subscribed.")) return } if err := gn.Get(uc); err == nil && len(uc.Customer) > 0 { serveError(w, fmt.Errorf("You're already subscribed.")) return } else if err != datastore.ErrNoSuchEntity { serveError(w, err) return } resp, err := stripe(c, "POST", "customers", url.Values{ "email": {u.Email}, "description": {u.Id}, "card": {r.FormValue("token")}, "plan": {r.FormValue("plan")}, }.Encode()) if err != nil { serveError(w, err) return } else if resp.StatusCode != http.StatusOK { var se StripeError defer resp.Body.Close() b, _ := ioutil.ReadAll(resp.Body) if err := json.Unmarshal(b, &se); err == nil { serveError(w, fmt.Errorf(se.Error.Message)) } else { serveError(w, fmt.Errorf("Error")) } c.Errorf("status: %v, %s", resp.StatusCode, b) return } uc, err = setCharge(c, resp) if err != nil { serveError(w, err) return } b, _ := json.Marshal(&uc) w.Write(b) }
func UpdateFeeds(c mpg.Context, w http.ResponseWriter, r *http.Request) { q := datastore.NewQuery("F").KeysOnly() q = q.Filter("n <=", time.Now()) q = q.Limit(3000) var keys []*datastore.Key it := q.Run(c) retry := 0 for { k, err := it.Next(nil) if err != nil { c.Errorf("next error: %v, retry: %v", err.Error(), retry) if retry == 5 { break } retry++ it = q.Run(c) } else { keys = append(keys, k) } } if len(keys) == 0 { c.Errorf("giving up") return } tasks := make([]*taskqueue.Task, len(keys)) for i, k := range keys { c.Infof("task: %v, %v", i, k) tasks[i] = taskqueue.NewPOSTTask(routeUrl("update-feed"), url.Values{ "feed": {k.StringID()}, }) } var ts []*taskqueue.Task const taskLimit = 100 for len(tasks) > 0 { if len(tasks) > taskLimit { ts = tasks[:taskLimit] tasks = tasks[taskLimit:] } else { ts = tasks tasks = tasks[0:0] } if _, err := taskqueue.AddMulti(c, ts, "update-feed"); err != nil { c.Errorf("taskqueue error: %v", err.Error()) } } c.Infof("updating %d feeds", len(keys)) }
func DeleteAccount(c mpg.Context, w http.ResponseWriter, r *http.Request) { if _, err := doUncheckout(c); err != nil { c.Errorf("uncheckout err: %v", err) } cu := user.Current(c) gn := goon.FromContext(c) u := User{Id: cu.ID} ud := UserData{Id: "data", Parent: gn.Key(&u)} if err := gn.Get(&u); err != nil { serveError(w, err) return } gn.Delete(gn.Key(&ud)) gn.Delete(ud.Parent) http.Redirect(w, r, routeUrl("logout"), http.StatusFound) }
func AdminUser(c mpg.Context, w http.ResponseWriter, r *http.Request) { gn := goon.FromContext(c) q := datastore.NewQuery(gn.Kind(&User{})).Limit(1) q = q.Filter("e =", r.FormValue("u")) it := gn.Run(q) var u User ud := UserData{Id: "data"} var h []Log k, err := it.Next(&u) if err != nil { serveError(w, err) return } ud.Parent = gn.Key(&u) gn.Get(&ud) until := r.FormValue("until") if d, err := time.Parse("2006-01-02", until); err == nil { u.Until = d gn.Put(&u) } if o := []byte(r.FormValue("opml")); len(o) > 0 { opml := Opml{} if err := json.Unmarshal(o, &opml); err != nil { serveError(w, err) return } ud.Opml = o if _, err := gn.Put(&ud); err != nil { serveError(w, err) return } c.Infof("opml updated") } q = datastore.NewQuery(gn.Kind(&Log{})).Ancestor(k) _, err = gn.GetAll(q, &h) if err := templates.ExecuteTemplate(w, "admin-user.html", struct { User User Data UserData Log []Log }{ u, ud, h, }); err != nil { serveError(w, err) } }
func addFeed(c mpg.Context, userid string, outline *OpmlOutline) error { gn := goon.FromContext(c) o := outline.Outline[0] c.Infof("adding feed %v to user %s", o.XmlUrl, userid) fu, ferr := url.Parse(o.XmlUrl) if ferr != nil { return ferr } fu.Fragment = "" o.XmlUrl = fu.String() f := Feed{Url: o.XmlUrl} if err := gn.Get(&f); err == datastore.ErrNoSuchEntity { if feed, stories, err := fetchFeed(c, o.XmlUrl, o.XmlUrl); err != nil { return fmt.Errorf("could not add feed %s: %v", o.XmlUrl, err) } else { f = *feed f.Updated = time.Time{} f.Checked = f.Updated f.NextUpdate = f.Updated f.LastViewed = time.Now() gn.Put(&f) for _, s := range stories { s.Created = s.Published } if err := updateFeed(c, f.Url, feed, stories, false, false, false); err != nil { return err } o.XmlUrl = feed.Url o.HtmlUrl = feed.Link if o.Title == "" { o.Title = feed.Title } } } else if err != nil { return err } else { o.HtmlUrl = f.Link if o.Title == "" { o.Title = f.Title } } o.Text = "" return nil }
func fetchFeed(c mpg.Context, origUrl, fetchUrl string) (*Feed, []*Story) { u, err := url.Parse(fetchUrl) if u.Host == "" { u.Host = u.Path u.Path = "" } if err == nil && u.Scheme == "" { u.Scheme = "http" origUrl = u.String() fetchUrl = origUrl if origUrl == "" { return nil, nil } } cl := &http.Client{ Transport: &urlfetch.Transport{ Context: c, Deadline: time.Minute, }, } if resp, err := cl.Get(fetchUrl); err == nil && resp.StatusCode == http.StatusOK { defer resp.Body.Close() b, _ := ioutil.ReadAll(resp.Body) if autoUrl, err := Autodiscover(b); err == nil && origUrl == fetchUrl { if autoU, err := url.Parse(autoUrl); err == nil { if autoU.Scheme == "" { autoU.Scheme = u.Scheme } if autoU.Host == "" { autoU.Host = u.Host } autoUrl = autoU.String() } if autoUrl != fetchUrl { return fetchFeed(c, origUrl, autoUrl) } } return ParseFeed(c, origUrl, b) } else if err != nil { c.Warningf("fetch feed error: %s", err.Error()) } else { c.Warningf("fetch feed error: status code: %s", resp.Status) } return nil, nil }
func taskSender(c mpg.Context, queue string, tc chan *taskqueue.Task, done chan bool) { const taskLimit = 100 tasks := make([]*taskqueue.Task, 0, taskLimit) send := func() { taskqueue.AddMulti(c, tasks, queue) c.Infof("added %v tasks", len(tasks)) tasks = tasks[0:0] } for t := range tc { tasks = append(tasks, t) if len(tasks) == taskLimit { send() } } if len(tasks) > 0 { send() } done <- true }