func fetchBlog(res *wcg.Response, req *wcg.Request, includePosts bool) (*models.Blog, *supports.Page, error) { // default query for posts. id := req.Param("blog_id") driver := models.NewBlogDriver(gae.NewContext(req), req.Logger) blog, query, err := driver.PostQuery(id) if err != nil { if err == models.ErrBlogNotFound { res.WriteHeader(404) res.WriteString("Not found") res.End() return nil, nil, err } res.RenderInternalError(err.Error()) return nil, nil, err } if !includePosts { return blog, supports.EmptyPage, nil } query = query.Filter("IsNew =", false).Filter("IsDraft =", false).Order("-PostDate").Order("-UpdatedAt") per_page := wcg.ParseInt(req.Query("num"), AppConfig.DefaultPostsFetched, 0, AppConfig.MaxPostsFetched) current := wcg.ParseInt(req.Query("p"), 0, 0, wcg.ParseIntMax) page, err := supports.NewPage(current, per_page, query) if err != nil { res.RenderInternalError("Post pagination error: %v", err) return nil, nil, err } return blog, page, nil }
func (ph *PageHelper) NotFound(res *wcg.Response, req *wcg.Request) { res.TemplatesWithStatus( 404, nil, "404.html", "header.html", "footer.html", ) }
func indexAllMembers(res *wcg.Response, req *wcg.Request, app *App) { var appCtx = lib.NewAppContextFromRequest(req) result := make(map[string][]string) crawler := ameblo.NewCrawler(appCtx.NewHttpClient()) for _, m := range app.Members { req.Logger.Debug("Crawling %s (%s)", m.BlogUrl, m.Name) entries, err := crawler.CrawlEntryList(m.BlogUrl) if err != nil { req.Logger.Error("An error occurred while crawling %s: %v", m.BlogUrl, err) continue } req.Logger.Debug("Found %d entries.", len(entries)) list := make([]string, 0) for _, e := range entries { e.Owner = m.Name list = append(list, e.Url) } if err := updateIndexes(appCtx, entries); err != nil { req.Logger.Error("Failed to update the entry: %v", err) } else { result[m.Name] = list } } // invalidate the cache time.Sleep(10 * time.Second) // TODO: wait for all indexes are updated on datastore. mc := appCtx.NewMemcacheDriver() for _, m := range app.Members { mckey := fmt.Sprintf(MC_KEY_HISTORY, app.Key, m.Name) mc.Delete(mckey) } res.WriteJson(result) }
func getVersion(res *wcg.Response, req *wcg.Request) { stats, _ := runtime.Stats(gae.NewContext(req)) res.WriteJson(map[string]interface{}{ "version": lib.APP_COMMIT, "timestamp": lib.APP_TIMESTAMP, "stats": stats, }) }
func authRedirect(res *wcg.Response, req *wcg.Request) { ref, _ := req.Session.Get(SESSION_KEY_LOGIN_REF) if ref != "" && strings.HasPrefix(ref, "/") { res.Redirect(wcg.AbsoluteUrl(req, ref), http.StatusFound) } else { res.Redirect("/", http.StatusFound) } }
func (ph *PageHelper) Forbidden(res *wcg.Response, req *wcg.Request) { res.SetLocal("Ref", req.URL().RequestURI()) res.TemplatesWithStatus( 403, nil, "403.html", "header.html", "footer.html", ) }
// HTTP 201 Created func (api *ApiHelper) Created(res *wcg.Response, req *wcg.Request, id string) { loc := path.Join(req.URL().Path, fmt.Sprintf("%s.json", id)) res.WriteJsonWithStatus(201, nil, map[string]interface{}{ "ok": true, "id": id, "location": wcg.AbsoluteUrl(req, loc), }) }
func (ph *PageHelper) InternalError(res *wcg.Response, req *wcg.Request, e error) { req.Logger.Error("Internal Server Error: %v", e) res.SetLocal("error", e) res.TemplatesWithStatus( 500, nil, "500.html", "header.html", "footer.html", ) }
func indexSpecifiedMember(res *wcg.Response, req *wcg.Request, app *App) { var appCtx = lib.NewAppContextFromRequest(req) member, ok := app.Members[req.Param("member")] if !ok { lib.NotFound(res, req) return } num := wcg.ParseInt(req.Param("n"), 0, 0, wcg.ParseIntMax) if num == 0 { num = wcg.ParseIntMax } // Crawling crawler := ameblo.NewCrawler(appCtx.NewHttpClient()) prefix := strings.TrimSuffix(member.BlogUrl, ".html") // xxxx.html => xxxx-{num}.html entries := make([]*ameblo.AmebloEntry, 0) for i := 1; i < num; i += 1 { url := fmt.Sprintf("%s-%d.html", prefix, i) req.Logger.Info("Indexing from %s ... ", url) newentries, err := crawler.CrawlEntryList(url) if err != nil { lib.InternalError(res, req, err) return } if len(newentries) > 20 { panic(fmt.Errorf("Unexpected number of entries (%d) are returned during indexing.", len(newentries))) } if len(newentries) == 0 { break } if len(newentries) < 20 { entries = append(entries, newentries...) break } if len(entries) > 0 && entries[len(entries)-1].Url == newentries[len(newentries)-1].Url { break } entries = append(entries, newentries...) } // Save and return resutls results := make([]string, 0) for _, ent := range entries { ent.Owner = member.Name results = append(results, ent.Url) } if err := updateIndexes(appCtx, entries); err != nil { req.Logger.Error("Failed to update the entry: %v", err) lib.InternalError(res, req, err) // stopped. } else { time.Sleep(10 * time.Second) // TODO: wait for all indexes are updated on datastore. mc := appCtx.NewMemcacheDriver() mckey := fmt.Sprintf(MC_KEY_HISTORY, app.Key, member.Name) mc.Delete(mckey) res.WriteJson(results) } }
func createPostHandler(res *wcg.Response, req *wcg.Request) { blog, _, err := fetchBlogForUpdate(res, req, false) if err != nil { return } post := models.NewPost(req.User.Id(), blog.Id) err = models.NewPostDriver(gae.NewContext(req), req.Logger).SavePost(post) if err != nil { res.RenderInternalError(err.Error()) return } res.Redirect(fmt.Sprintf("/admin/blogs/%s/%s/", blog.Id, post.Id), http.StatusSeeOther) }
// HTTP 500 Internal Error func (api *ApiHelper) InternalError(res *wcg.Response, req *wcg.Request, e error) { var msg string if util.IsProduction() { msg = "Sorry for inconvinience. An internal error occurred. Please try again later." } else { msg = fmt.Sprintf("An error occurred: %v", e) } req.Logger.Error("Serve 500: %v", e) res.WriteJsonWithStatus(500, nil, map[string]interface{}{ "error": "internal_server_error", "message": msg, }) }
func Redirect(res *wcg.Response, req *wcg.Request) { urlStr := req.Query("u") if urlobj, err := url.Parse(urlStr); err == nil && validateUrl(urlobj) { // TODO: Check blacklist res.Redirect(urlStr, 302) return } res.TemplatesWithStatus( 404, nil, "404.html", "header.html", "footer.html", ) return }
func deleteBlogHandler(res *wcg.Response, req *wcg.Request) { driver := models.NewBlogDriver(gae.NewContext(req), req.Logger) err := driver.DeleteBlog(req.Param("blog_id"), req.User.Id()) if err != nil { if supports.IsValidationError(err) { res.WriteJsonWithStatus(400, nil, err) } else if err == models.ErrBlogNotOwned { res.WriteJsonWithStatus(403, nil, no_permission) } else if err == models.ErrBlogNotFound { res.WriteJsonWithStatus(404, nil, not_found) } else { res.RenderInternalError(err.Error()) } return } res.WriteJsonWithStatus(200, nil, ok) }
func manageBlogHandler(res *wcg.Response, req *wcg.Request) { blog, posts, err := fetchBlogForUpdate(res, req, true) if err != nil { return } res.SetLocal("blog", blog) res.SetLocal("posts", posts) res.SetLocal("js", "/static/js/admin/blog.js") res.Templates("admin/blog.html", "header.html", "footer.html") }
func crawl(res *wcg.Response, req *wcg.Request, member *ameblo.Member, app *App) { var appCtx = lib.NewAppContextFromRequest(req) var logger = appCtx.Logger var targets []*ameblo.AmebloEntry result := make([]string, 0) d := NewAmebloEntryDriver(appCtx) crawler := ameblo.NewCrawler(appCtx.NewHttpClient()) // prioritize the entries which are not crawled and are posted recently. q := d.NewQuery().Filter("CrawledAt =", time.Time{}).Order("PostAt").Limit(NUM_ENTRIES_TO_CRAWL_PER_CALL) if member != nil { q = q.Filter("Owner =", member.Name) } if _, err := q.GetAll(&targets); err != nil { lib.InternalError(res, req, err) return } // Crawl Contents for _, e := range targets { logger.Info("Crawling %s ... ", e.Url) if e1, err := crawler.CrawlEntry(e.Url); err != nil { logger.Warn("Failed to crawl %s, skipped: %v", e.Url, err) continue } else { if e1 == nil { logger.Warn("CrawlEntry returns nil entry for %s", e.Url) e.Content = "<No Content>" e.CrawledAt = time.Now() } else { logger.Debug("CrawlEntry scraped %d bytes.", len(e1.Content)) e.Content = e1.Content } result = append(result, e.Url) } } if err := updateContents(appCtx, targets, app.MemberList); err != nil { lib.InternalError(res, req, err) return } res.WriteJson(result) }
func showPostHandler(res *wcg.Response, req *wcg.Request) { blog, _, err := fetchBlog(res, req, false) if err != nil { return } post, err := fetchPost(res, req, blog) if err != nil { return } res.SetLocal("Og", &supports.Og{Title: post.Title, Type: "article", Url: wcg.AbsoluteUrl(req, req.URL().Path), SiteName: blog.Title, Description: wcg.Shorten(post.Content, 80), }) res.SetLocal("blog", blog) res.SetLocal("post", post) res.SetLocal("is_owner", post.OwnerId == req.User.Id()) res.SetLocal("js", "/static/js/post.js") res.Templates("post.html", "header.html", "footer.html", "parts/post_part.html") }
func historyInsights(res *wcg.Response, req *wcg.Request, app *App) { var appCtx = lib.NewAppContextFromRequest(req) member, ok := app.Members[req.Param("member")] if !ok { lib.NotFound(res, req) return } var insights amebloHistoryInsights mckey := fmt.Sprintf(MC_KEY_HISTORY, app.Key, member.Name) dent := NewAmebloEntryDriver(appCtx) dref := NewAmebloRefDriver(appCtx) mc := appCtx.NewMemcacheDriver() err := mc.CachedObject(mckey, &insights, func() (interface{}, error) { return getAmebloHistoryInsights(member, dent, dref) }, req.Query("force") == "1") if err != nil { lib.Error(res, req, err) return } res.WriteJson(insights) }
func editPostHandler(res *wcg.Response, req *wcg.Request) { blog, _, err := fetchBlogForUpdate(res, req, false) if err != nil { return } post, err := fetchPostForUpdate(req, res, blog) if err != nil { return } res.SetLocal("blog", blog) res.SetLocal("post", post) res.SetLocal("css", "/static/css/admin/post.css") res.SetLocal("js", "/static/js/admin/post.js") res.Templates("admin/post.html", "header.html", "footer.html") }
func listTvChannels(res *wcg.Response, req *wcg.Request) ([]*tv.TvChannel, error) { var list []*tv.TvChannel app := lib.GetCurrentApp(req) ctx := gae.NewContext(req) d := NewTvChannelDriver(app.Key, ctx, req.Logger) mc := memcache.NewDriver(ctx, req.Logger) err := mc.CachedObject(MC_KEY_CHANNELS, &list, func() (interface{}, error) { return d.AllAsList() }, req.Query("force") == "1") if err != nil { return nil, err } else { if len(list) == 0 { req.Logger.Warn("No channel is defined. Reset the configuraiton.") d.AddChannelList(defaultChannels) mc.Delete(MC_KEY_CHANNELS) mc.Set(MC_KEY_CHANNELS, defaultChannels) res.WriteJson(defaultChannels) return defaultChannels, nil } else { return list, nil } } }
func createBlogHandler(res *wcg.Response, req *wcg.Request) { driver := models.NewBlogDriver(gae.NewContext(req), req.Logger) blog, err := driver.CreateBlog( req.Form("path"), req.Form("title"), req.Form("description"), req.User.Id(), ) if err != nil { if err == models.ErrBlogAlreadyExists { res.WriteJsonWithStatus(409, nil, already_taken) } else if supports.IsValidationError(err) { res.WriteJsonWithStatus(400, nil, err) } else { res.RenderInternalError(err.Error()) } return } res.WriteJsonWithStatus(201, map[string]string{ "location": wcg.AbsoluteUrl(req, "/"+blog.Id), }, ok) }
func deletePostHandler(res *wcg.Response, req *wcg.Request) { blog, _, err := fetchBlogForUpdate(res, req, false) if err != nil { return } post, err := fetchPostForUpdate(req, res, blog) if err != nil { return } err = models.NewPostDriver(gae.NewContext(req), req.Logger).DeletePost(post) if err != nil { if supports.IsValidationError(err) { res.WriteJsonWithStatus(400, nil, err) } else { res.RenderInternalError(err.Error()) } return } res.WriteJsonWithStatus(200, nil, ok) }
// HTTP 200 OK func (cron *CronHelper) Ok(res *wcg.Response, req *wcg.Request) { res.WriteJson(map[string]interface{}{ "ok": true, }) }
func authorized(res *wcg.Response, req *wcg.Request) { res.Redirect("/", http.StatusFound) }
func showBlogHandler(res *wcg.Response, req *wcg.Request) { blog, posts, err := fetchBlog(res, req, true) if err != nil { return } res.SetLocal("Og", &supports.Og{Title: blog.Title, Type: "blog", Url: wcg.AbsoluteUrl(req, req.URL().Path), Description: blog.Description, SiteName: AppConfig.SiteTitle, }) res.SetLocal("blog", blog) res.SetLocal("is_owner", blog.OwnerId == req.User.Id()) res.SetLocal("posts", posts) res.SetLocal("js", "/static/js/blog.js") res.Templates("blog.html", "header.html", "footer.html", "parts/post_part.html") }
func fetchPostForUpdate(req *wcg.Request, res *wcg.Response, blog *models.Blog) (*models.Post, error) { driver := models.NewPostDriver(gae.NewContext(req), req.Logger) id := req.Param("post_id") blogId := req.Param("blog_id") post, err := driver.FindPostById(id, blogId) if err != nil { if err == models.ErrPostNotFound { res.WriteHeader(404) res.WriteString("Not found") res.End() return nil, err } res.RenderInternalError(err.Error()) return nil, err } if blog.Id != post.BlogId { res.WriteHeader(403) res.WriteString("You could not manage that blog.") res.End() } if post.OwnerId != req.User.Id() { res.WriteHeader(403) res.WriteString("You could not manage this post.") res.End() return nil, err } return post, err }
func fetchPost(res *wcg.Response, req *wcg.Request, blog *models.Blog) (*models.Post, error) { driver := models.NewPostDriver(gae.NewContext(req), req.Logger) id := req.Param("post_id") blogId := req.Param("blog_id") post, err := driver.FindPostById(id, blogId) if err != nil { if err == models.ErrPostNotFound { res.WriteHeader(404) res.WriteString("Not found") res.End() return nil, err } res.RenderInternalError(err.Error()) return nil, err } if post.PostState() != models.PostStatePublished { if post.OwnerId != req.User.Id() { res.WriteHeader(404) res.WriteString("Not found") res.End() return nil, err } } return post, err }
func updatePostHandler(res *wcg.Response, req *wcg.Request) { blog, _, err := fetchBlogForUpdate(res, req, false) if err != nil { return } post, err := fetchPostForUpdate(req, res, blog) if err != nil { return } post.Title = req.Form("title") post.Content = req.Form("content") post.IsDraft = req.Form("is_draft") == "true" post.IsNew = false post.PostDate, err = time.Parse(wcg.FormDateFormat, req.Form("post_date")) post.Tags = strings.Split(req.Form("tags"), ",") for i, v := range post.Tags { post.Tags[i] = strings.TrimSpace(v) } if err != nil { res.WriteHeader(400) res.WriteString("Invalid date format.") res.End() } driver := models.NewPostDriver(gae.NewContext(req), req.Logger) if AppConfig.GithubMarkdown { driver.HttpClient = gae.NewHttpClient(req) } err = driver.SavePost(post) if err != nil { if supports.IsValidationError(err) { res.WriteJsonWithStatus(400, nil, err) } else { res.RenderInternalError(err.Error()) } return } req.Logger.Info("A post is created at %s.", post.Id) res.WriteJsonWithStatus(200, nil, ok) }
// HTTP 400 Bad Request func (api *ApiHelper) BadRequest(res *wcg.Response, req *wcg.Request, e error) { res.WriteJsonWithStatus(400, nil, map[string]interface{}{ "error": "bad_request", "message": fmt.Sprintf("%v", e), }) }
// HTTP 400 Bad Request func (api *ApiHelper) NotFound(res *wcg.Response, req *wcg.Request) { res.WriteJsonWithStatus(404, nil, map[string]interface{}{ "error": "not_found", "message": "The requested resource is not found on this server.", }) }
func (api *ApiHelper) Forbidden(res *wcg.Response, req *wcg.Request) { res.WriteJsonWithStatus(403, nil, map[string]interface{}{ "erorr": "forbidden", "message": "You don't have enough permission to access the resource", }) }