first draft of categories #2
This commit is contained in:
parent
9bcc96d845
commit
f017bd8f40
|
@ -53,7 +53,6 @@ TODOs
|
|||
* Revisit Makefile (v0.3)
|
||||
* Review async mails
|
||||
* Review preview of articles and sites / error handling
|
||||
* Categories?
|
||||
* Roles?
|
||||
|
||||
Licence
|
||||
|
|
|
@ -91,7 +91,7 @@ func GetArticleByIDHandler(ctx *middleware.AppContext, w http.ResponseWriter, r
|
|||
func ListArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
page := getPageParam(r)
|
||||
|
||||
t, err := ctx.ArticleService.CountArticles(nil, models.OnlyPublished)
|
||||
t, err := ctx.ArticleService.CountArticles(nil, nil, models.OnlyPublished)
|
||||
|
||||
p := &models.Pagination{
|
||||
Total: t,
|
||||
|
@ -108,7 +108,7 @@ func ListArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *h
|
|||
}
|
||||
}
|
||||
|
||||
a, err := ctx.ArticleService.ListArticles(nil, p, models.OnlyPublished)
|
||||
a, err := ctx.ArticleService.ListArticles(nil, nil, p, models.OnlyPublished)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
|
@ -139,9 +139,73 @@ func ListArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *h
|
|||
}
|
||||
}
|
||||
|
||||
//ListArticlesHandler returns the template which contains all published articles
|
||||
func ListArticlesCategoryHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
page := getPageParam(r)
|
||||
|
||||
category := getVar(r, "category")
|
||||
|
||||
c, err := ctx.CategoryService.GetCategoryBySlug(category)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
Name: tplArticles,
|
||||
Active: "articles",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
t, err := ctx.ArticleService.CountArticles(nil, c, models.OnlyPublished)
|
||||
|
||||
p := &models.Pagination{
|
||||
Total: t,
|
||||
Limit: ctx.ConfigService.ArticlesPerPage,
|
||||
CurrentPage: page,
|
||||
RelURL: "articles/page",
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
Name: tplArticles,
|
||||
Active: "articles",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
a, err := ctx.ArticleService.ListArticles(nil, c, p, models.OnlyPublished)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
Name: tplArticles,
|
||||
Active: "articles",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
cs, err := ctx.CategoryService.ListCategories(models.CategoriesWithArticles)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
Name: tplArticles,
|
||||
Active: "articles",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return &middleware.Template{
|
||||
Name: tplArticles,
|
||||
Active: "articles",
|
||||
Data: map[string]interface{}{
|
||||
"articles": a,
|
||||
"categories": cs,
|
||||
"pagination": p,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
//IndexArticlesHandler returns the template information for the index page
|
||||
func IndexArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
a, err := ctx.ArticleService.IndexArticles(nil, nil, models.OnlyPublished)
|
||||
a, err := ctx.ArticleService.IndexArticles(nil, nil, nil, models.OnlyPublished)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
|
@ -183,7 +247,7 @@ func RSSFeed(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request)
|
|||
func AdminListArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter, r *http.Request) *middleware.Template {
|
||||
u, _ := middleware.User(r)
|
||||
|
||||
t, err := ctx.ArticleService.CountArticles(u, models.All)
|
||||
t, err := ctx.ArticleService.CountArticles(u, nil, models.All)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
|
@ -200,7 +264,7 @@ func AdminListArticlesHandler(ctx *middleware.AppContext, w http.ResponseWriter,
|
|||
RelURL: "admin/articles/page",
|
||||
}
|
||||
|
||||
a, err := ctx.ArticleService.ListArticles(u, p, models.All)
|
||||
a, err := ctx.ArticleService.ListArticles(u, nil, p, models.All)
|
||||
|
||||
if err != nil {
|
||||
return &middleware.Template{
|
||||
|
|
|
@ -35,8 +35,8 @@ type Article struct {
|
|||
//ArticleDatasourceService defines an interface for CRUD operations of articles
|
||||
type ArticleDatasourceService interface {
|
||||
Create(a *Article) (int, error)
|
||||
List(u *User, p *Pagination, pc PublishedCriteria) ([]Article, error)
|
||||
Count(u *User, pc PublishedCriteria) (int, error)
|
||||
List(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]Article, error)
|
||||
Count(u *User, c *Category, pc PublishedCriteria) (int, error)
|
||||
Get(articleID int, u *User, pc PublishedCriteria) (*Article, error)
|
||||
GetBySlug(slug string, u *User, pc PublishedCriteria) (*Article, error)
|
||||
Publish(a *Article) error
|
||||
|
@ -220,14 +220,14 @@ func (as ArticleService) GetArticleByID(id int, u *User, pc PublishedCriteria) (
|
|||
|
||||
// CountArticles returns the number of articles.
|
||||
// The publishedCriteria defines whether the published and/or unpublished articles should be considered
|
||||
func (as ArticleService) CountArticles(u *User, pc PublishedCriteria) (int, error) {
|
||||
return as.Datasource.Count(u, pc)
|
||||
func (as ArticleService) CountArticles(u *User, c *Category, pc PublishedCriteria) (int, error) {
|
||||
return as.Datasource.Count(u, c, pc)
|
||||
}
|
||||
|
||||
// ListArticles returns all article by the slug.
|
||||
// The publishedCriteria defines whether the published and/or unpublished articles should be considered
|
||||
func (as ArticleService) ListArticles(u *User, p *Pagination, pc PublishedCriteria) ([]Article, error) {
|
||||
return as.Datasource.List(u, p, pc)
|
||||
func (as ArticleService) ListArticles(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]Article, error) {
|
||||
return as.Datasource.List(u, c, p, pc)
|
||||
}
|
||||
|
||||
// RSSFeed receives a specified number of articles in RSS
|
||||
|
@ -239,7 +239,8 @@ func (as ArticleService) RSSFeed(p *Pagination, pc PublishedCriteria) (RSS, erro
|
|||
Language: as.AppConfig.Language,
|
||||
}
|
||||
|
||||
articles, err := as.Datasource.List(nil, p, pc)
|
||||
//TODO: categories in rss feeds
|
||||
articles, err := as.Datasource.List(nil, nil, p, pc)
|
||||
|
||||
if err != nil {
|
||||
return RSS{}, err
|
||||
|
@ -274,8 +275,8 @@ type IndexArticle struct {
|
|||
Articles []Article
|
||||
}
|
||||
|
||||
func (as ArticleService) IndexArticles(u *User, p *Pagination, pc PublishedCriteria) ([]IndexArticle, error) {
|
||||
arts, err := as.Datasource.List(u, p, pc)
|
||||
func (as ArticleService) IndexArticles(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]IndexArticle, error) {
|
||||
arts, err := as.Datasource.List(u, c, p, pc)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -40,8 +40,8 @@ func (rdb SQLiteArticleDatasource) Create(a *Article) (int, error) {
|
|||
|
||||
//List returns a slice of articles; if the user is not nil the number of articles for this explcit user is returned
|
||||
//the PublishedCritera specifies which articles should be considered
|
||||
func (rdb SQLiteArticleDatasource) List(u *User, p *Pagination, pc PublishedCriteria) ([]Article, error) {
|
||||
rows, err := selectSQLiteArticlesStmt(rdb.SQLConn, u, p, pc)
|
||||
func (rdb SQLiteArticleDatasource) List(u *User, c *Category, p *Pagination, pc PublishedCriteria) ([]Article, error) {
|
||||
rows, err := selectArticlesStmt(rdb.SQLConn, u, c, p, pc)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -53,10 +53,11 @@ func (rdb SQLiteArticleDatasource) List(u *User, p *Pagination, pc PublishedCrit
|
|||
|
||||
for rows.Next() {
|
||||
var a Article
|
||||
var c Category
|
||||
var ru User
|
||||
|
||||
if err := rows.Scan(&a.ID, &a.Headline, &a.Teaser, &a.Content, &a.Published, &a.PublishedOn, &a.Slug, &a.LastModified, &ru.ID, &ru.DisplayName,
|
||||
&ru.Email, &ru.Username, &ru.IsAdmin); err != nil {
|
||||
&ru.Email, &ru.Username, &ru.IsAdmin, &c.ID, &c.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -74,27 +75,40 @@ func (rdb SQLiteArticleDatasource) List(u *User, p *Pagination, pc PublishedCrit
|
|||
|
||||
//Count returns the number of article found; if the user is not nil the number of articles for this explcit user is returned
|
||||
//the PublishedCritera specifies which articles should be considered
|
||||
func (rdb SQLiteArticleDatasource) Count(u *User, pc PublishedCriteria) (int, error) {
|
||||
func (rdb SQLiteArticleDatasource) Count(u *User, c *Category, pc PublishedCriteria) (int, error) {
|
||||
var total int
|
||||
|
||||
var args []interface{}
|
||||
|
||||
var stmt bytes.Buffer
|
||||
stmt.WriteString("SELECT count(id) FROM article WHERE ")
|
||||
stmt.WriteString("SELECT count(a.id) FROM article a ")
|
||||
|
||||
if c != nil {
|
||||
stmt.WriteString("INNER JOIN category c ON (c.id = a.category_id) ")
|
||||
} else {
|
||||
stmt.WriteString("LEFT JOIN category c ON (c.id = a.category_id) ")
|
||||
}
|
||||
|
||||
stmt.WriteString("WHERE ")
|
||||
|
||||
if c != nil {
|
||||
stmt.WriteString("c.name = ? AND ")
|
||||
args = append(args, c.Name)
|
||||
}
|
||||
|
||||
if u != nil {
|
||||
if !u.IsAdmin {
|
||||
stmt.WriteString("user_id=? AND ")
|
||||
stmt.WriteString("a.user_id=? AND ")
|
||||
args = append(args, u.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if pc == NotPublished {
|
||||
stmt.WriteString("published = '0' ")
|
||||
stmt.WriteString("a.published = '0' ")
|
||||
} else if pc == All {
|
||||
stmt.WriteString("(published='0' OR published='1') ")
|
||||
stmt.WriteString("(a.published='0' OR a.published='1') ")
|
||||
} else {
|
||||
stmt.WriteString("published = '1' ")
|
||||
stmt.WriteString("a.published = '1' ")
|
||||
}
|
||||
|
||||
if err := rdb.SQLConn.QueryRow(stmt.String(), args...).Scan(&total); err != nil {
|
||||
|
@ -110,7 +124,7 @@ func (rdb SQLiteArticleDatasource) Get(articleID int, u *User, pc PublishedCrite
|
|||
var a Article
|
||||
var ru User
|
||||
|
||||
if err := selectSQLiteArticleStmt(rdb.SQLConn, articleID, "", u, pc).Scan(&a.ID, &a.Headline, &a.PublishedOn, &a.Published, &a.Slug, &a.Teaser, &a.Content,
|
||||
if err := selectArticleStmt(rdb.SQLConn, articleID, "", u, pc).Scan(&a.ID, &a.Headline, &a.PublishedOn, &a.Published, &a.Slug, &a.Teaser, &a.Content,
|
||||
&a.LastModified, &ru.ID, &ru.DisplayName, &ru.Email, &ru.Username, &ru.IsAdmin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -126,7 +140,7 @@ func (rdb SQLiteArticleDatasource) GetBySlug(slug string, u *User, pc PublishedC
|
|||
var a Article
|
||||
var ru User
|
||||
|
||||
if err := selectSQLiteArticleStmt(rdb.SQLConn, -1, slug, u, pc).Scan(&a.ID, &a.Headline, &a.PublishedOn, &a.Published, &a.Slug, &a.Teaser, &a.Content,
|
||||
if err := selectArticleStmt(rdb.SQLConn, -1, slug, u, pc).Scan(&a.ID, &a.Headline, &a.PublishedOn, &a.Published, &a.Slug, &a.Teaser, &a.Content,
|
||||
&a.LastModified, &ru.ID, &ru.DisplayName, &ru.Email, &ru.Username, &ru.IsAdmin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -170,7 +184,7 @@ func (rdb SQLiteArticleDatasource) Delete(articleID int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func selectSQLiteArticleStmt(db *sql.DB, articleID int, slug string, u *User, pc PublishedCriteria) *sql.Row {
|
||||
func selectArticleStmt(db *sql.DB, articleID int, slug string, u *User, pc PublishedCriteria) *sql.Row {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
var args []interface{}
|
||||
|
@ -206,17 +220,30 @@ func selectSQLiteArticleStmt(db *sql.DB, articleID int, slug string, u *User, pc
|
|||
return db.QueryRow(stmt.String(), args...)
|
||||
}
|
||||
|
||||
func selectSQLiteArticlesStmt(db *sql.DB, u *User, p *Pagination, pc PublishedCriteria) (*sql.Rows, error) {
|
||||
func selectArticlesStmt(db *sql.DB, u *User, c *Category, p *Pagination, pc PublishedCriteria) (*sql.Rows, error) {
|
||||
var stmt bytes.Buffer
|
||||
|
||||
var args []interface{}
|
||||
|
||||
stmt.WriteString("SELECT a.id, a.headline, a.teaser, a.content, a.published, a.published_on, a.slug, a.last_modified, ")
|
||||
stmt.WriteString("u.id, u.display_name, u.email, u.username, u.is_admin ")
|
||||
stmt.WriteString("u.id, u.display_name, u.email, u.username, u.is_admin, ")
|
||||
stmt.WriteString("c.id, c.name ")
|
||||
stmt.WriteString("FROM article a ")
|
||||
stmt.WriteString("INNER JOIN user u ON (a.user_id = u.id) ")
|
||||
|
||||
if c != nil {
|
||||
stmt.WriteString("INNER JOIN category c ON (c.id = a.category_id) ")
|
||||
} else {
|
||||
stmt.WriteString("LEFT JOIN category c ON (c.id = a.category_id) ")
|
||||
}
|
||||
|
||||
stmt.WriteString("WHERE ")
|
||||
|
||||
if c != nil {
|
||||
stmt.WriteString("c.name = ? AND ")
|
||||
args = append(args, c.Name)
|
||||
}
|
||||
|
||||
if u != nil {
|
||||
if !u.IsAdmin {
|
||||
stmt.WriteString("a.user_id=? AND ")
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -56,6 +57,11 @@ type CategoryService struct {
|
|||
Datasource CategoryDatasourceService
|
||||
}
|
||||
|
||||
//SlugEscape escapes the slug for use in URLs
|
||||
func (c Category) SlugEscape() string {
|
||||
return url.PathEscape(c.Slug)
|
||||
}
|
||||
|
||||
func (cs CategoryService) GetCategoryBySlug(s string) (*Category, error) {
|
||||
c, err := cs.Datasource.GetBySlug(s)
|
||||
|
||||
|
@ -116,7 +122,7 @@ func (cs CategoryService) CreateCategory(c *Category) (int, error) {
|
|||
return cid, nil
|
||||
}
|
||||
|
||||
//UpdateArticle updates a category
|
||||
//UpdateCategory updates a category
|
||||
func (cs CategoryService) UpdateCategory(c *Category) error {
|
||||
if err := c.validate(); err != nil {
|
||||
return err
|
||||
|
|
|
@ -141,6 +141,9 @@ func publicRoutes(ctx *m.AppContext, router *mux.Router, chain alice.Chain) {
|
|||
router.Handle("/", chain.Then(useTemplateHandler(ctx, c.ListArticlesHandler))).Methods("GET")
|
||||
router.Handle("/index", chain.Then(useTemplateHandler(ctx, c.IndexArticlesHandler))).Methods("GET")
|
||||
|
||||
router.Handle("/articles/category/{categorySlug}", chain.Then(useTemplateHandler(ctx, c.ListArticlesCategoryHandler))).Methods("GET")
|
||||
router.Handle("/articles/category/{categorySlug}/{page}", chain.Then(useTemplateHandler(ctx, c.ListArticlesCategoryHandler))).Methods("GET")
|
||||
|
||||
router.Handle("/articles/page/{page}", chain.Then(useTemplateHandler(ctx, c.ListArticlesHandler))).Methods("GET")
|
||||
router.Handle("/article/{year}/{month}/{slug}", chain.Then(useTemplateHandler(ctx, c.GetArticleHandler))).Methods("GET")
|
||||
router.Handle("/article/by-id/{articleID}", chain.Then(useTemplateHandler(ctx, c.GetArticleByIDHandler))).Methods("GET")
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
{{template "front/navigation" .}}
|
||||
|
||||
<main>
|
||||
{{template "skel/flash" .}}
|
||||
{{template "skel/flash" .}}
|
||||
<article>
|
||||
{{with .article}}
|
||||
<h2 class="article_link">{{.Headline}}</h2>
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<ul>
|
||||
{{range .categories}}
|
||||
<li>
|
||||
<a href="#" href="/">{{.Name}}</a>
|
||||
<a href="/articles/category/{{.SlugEscape}}">{{.Name}}</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
|
|
|
@ -3,16 +3,12 @@
|
|||
{{template "front/head" .}}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{{template "front/title"}}
|
||||
|
||||
<body>
|
||||
{{template "front/navigation" .}}
|
||||
|
||||
|
||||
<main>
|
||||
<section class="max--width">
|
||||
<h2>Error</h2>
|
||||
|
||||
{{template "skel/flash" .}}
|
||||
</section>
|
||||
<h2>Error</h2>
|
||||
|
||||
{{template "skel/flash" .}}
|
||||
</main>
|
||||
{{end}}
|
||||
|
|
Loading…
Reference in New Issue