diff --git a/cmd/warehost-web/config.yml.example b/cmd/warehost-web/config.yml.example index f7904fc..f190cb0 100644 --- a/cmd/warehost-web/config.yml.example +++ b/cmd/warehost-web/config.yml.example @@ -5,4 +5,4 @@ webroot: ../../web_webroot/ database: "host=localhost user=warehost dbname=warehost password=hallo sslmode=disable" log: path: test.log -databasedebug: false +databasedebug: true diff --git a/cmd/warehost-web/handler.go b/cmd/warehost-web/handler.go index 8e5a54d..80795b1 100644 --- a/cmd/warehost-web/handler.go +++ b/cmd/warehost-web/handler.go @@ -5,11 +5,9 @@ import ( "fmt" "net/http" "os" + "strings" "text/template" - // "github.com/microcosm-cc/bluemonday" - "github.com/russross/blackfriday" - liblog "dev.sum7.eu/sum7/warehost/lib/log" web "dev.sum7.eu/sum7/warehost/modul/web" ) @@ -63,6 +61,7 @@ func handlerfiles(w http.ResponseWriter, r *http.Request) { func handlerfunc(w http.ResponseWriter, r *http.Request) { url := web.FixPath(r.URL.Path[1:]) + urlParts := strings.Split(url, "/") logger := liblog.NewModulLog(r.Host).GetLog(r, url) website, err := getWebsite(r.Host) if err != nil { @@ -82,6 +81,12 @@ func handlerfunc(w http.ResponseWriter, r *http.Request) { if item.Path == "" && urlObj == nil { urlObj = item } + if item.Path == strings.Join(urlParts[0:len(urlParts)-1], "/") && (urlObj == nil || urlObj.Path == "") { + urlObj = item + } + if len(urlParts) > 2 && item.Path == strings.Join(urlParts[0:len(urlParts)-3], "/") && (urlObj == nil || urlObj.Path == "") { + urlObj = item + } if item.Path == url { urlObj = item } @@ -93,20 +98,39 @@ func handlerfunc(w http.ResponseWriter, r *http.Request) { WebsiteID: website.ID, URLID: urlObj.ID, } - dbconnection.Where(page).FirstOrInit(&page) - if page.ID > 0 { - unsafe := blackfriday.MarkdownCommon([]byte(page.Content)) - //page.Content = string(bluemonday.UGCPolicy().SanitizeBytes(unsafe)) - page.Content = string(unsafe) + blog := web.Blog{ + WebsiteID: website.ID, + URLID: urlObj.ID, + } + blogpost := web.BlogPost{} + if !dbconnection.Where(page).First(&page).RecordNotFound() { page.URL = urlObj + } else { + if !dbconnection.Where(blog).Preload("Posts").First(&blog).RecordNotFound() { + blogpost.BlogID = blog.ID + if urlObj.Path != blogpost.Title && ((blog.PostURL == 0 && len(urlParts) > 1) || (blog.PostURL == 1 && len(urlParts) > 2)) { + db := dbconnection.Where(blogpost).Where("LOWER(title) = LOWER(?)", urlParts[len(urlParts)-1]) + if blog.PostURL == 1 { + db = db.Where("to_char(createat,'YYYY-MM') = ?", strings.Join(urlParts[len(urlParts)-3:len(urlParts)-1], "-")) + } + db.First(&blogpost) + blogpost.Blog = &blog + } + for _, post := range blog.Posts { + post.Blog = &blog + } + blog.URL = urlObj + } } i := TemplateInfo{ - Website: website, - Host: r.Host, - URL: url, - Menu: menus, - Page: &page, + Website: website, + Host: r.Host, + URL: url, + Menu: menus, + Page: &page, + Blog: &blog, + BlogPost: &blogpost, } t, err := template.ParseGlob(fmt.Sprintf("%s/%d/%s/%s", config.Webroot, website.ID, "tmpl", "*.tmpl")) logger.Info("done") diff --git a/cmd/warehost-web/model.go b/cmd/warehost-web/model.go index a065df1..d607e7f 100644 --- a/cmd/warehost-web/model.go +++ b/cmd/warehost-web/model.go @@ -4,9 +4,11 @@ import modul "dev.sum7.eu/sum7/warehost/modul/web" //TemplateInfo Information send to Template type TemplateInfo struct { - Host string - URL string - Website *modul.Website - Page *modul.Page - Menu []*modul.Menu + Host string + URL string + Website *modul.Website + Page *modul.Page + Blog *modul.Blog + BlogPost *modul.BlogPost + Menu []*modul.Menu } diff --git a/cmd/warehost/config.yml.example b/cmd/warehost/config.yml.example index 32f39c2..4f2d6ad 100644 --- a/cmd/warehost/config.yml.example +++ b/cmd/warehost/config.yml.example @@ -7,7 +7,7 @@ log: path: test.log webroot: ./webroot/build database: "host=localhost user=warehost dbname=warehost password=hallo sslmode=disable" -databasedebug: false +databasedebug: true modules: web: enabled: true diff --git a/modul/web/api.go b/modul/web/api.go index eaf8538..b115268 100644 --- a/modul/web/api.go +++ b/modul/web/api.go @@ -43,6 +43,14 @@ func BindAPI(db *gorm.DB, router *goji.Mux, prefix string) { router.HandleFunc(pat.Post(prefix+"/website/:websiteid/page"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(pageAdd)))) router.HandleFunc(pat.Patch(prefix+"/website/:websiteid/page/:pageid"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(pageEdit)))) router.HandleFunc(pat.Delete(prefix+"/website/:websiteid/page/:pageid"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(pageDelete)))) + router.HandleFunc(pat.Get(prefix+"/website/:websiteid/blog"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(blogList)))) + router.HandleFunc(pat.Post(prefix+"/website/:websiteid/blog"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(blogAdd)))) + router.HandleFunc(pat.Patch(prefix+"/website/:websiteid/blog/:blogid"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(blogEdit)))) + router.HandleFunc(pat.Delete(prefix+"/website/:websiteid/blog/:blogid"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(blogDelete)))) + router.HandleFunc(pat.Get(prefix+"/website/:websiteid/blog/:blogid"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(blogShow)))) + router.HandleFunc(pat.Post(prefix+"/website/:websiteid/blog/:blogid/post"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(blogpostAdd)))) + router.HandleFunc(pat.Patch(prefix+"/website/:websiteid/blog/:blogid/post/:blogpostid"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(blogpostEdit)))) + router.HandleFunc(pat.Delete(prefix+"/website/:websiteid/blog/:blogid/post/:blogpostid"), libapi.SessionHandler(libsystem.LoginHandler(InvolveWebsiteHandler(blogpostDelete)))) } // Involve to get Website where loggend in user has privilegs diff --git a/modul/web/apiblog.go b/modul/web/apiblog.go new file mode 100644 index 0000000..1aa7134 --- /dev/null +++ b/modul/web/apiblog.go @@ -0,0 +1,143 @@ +package web + +import ( + "net/http" + "strconv" + + "goji.io/pat" + + libapi "dev.sum7.eu/sum7/warehost/lib/api" +) + +func getBlog(w http.ResponseWriter, r *http.Request) (blog Blog, returnerr *libapi.ErrorResult) { + ctx := r.Context() + id, err := strconv.ParseInt(pat.Param(r, "blogid"), 10, 64) + if err != nil { + returnerr = &libapi.ErrorResult{Message: "Internal Request Error"} + w.WriteHeader(http.StatusBadRequest) + return + } + + blog = Blog{} + dbconnection.Where(map[string]interface{}{"id": id, "website": ctx.Value("websiteid").(int64)}).Preload("URL").Preload("Posts").Find(&blog) + + if blog.ID <= 0 { + returnerr = &libapi.ErrorResult{Fields: []string{"blog"}, Message: "not found"} + w.WriteHeader(http.StatusNotFound) + } + return +} + +// blogList give all blogs of a website +func blogList(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := log.GetLog(r, "bloglist") + var blogges []*Blog + dbconnection.Where("website = ?", ctx.Value("websiteid").(int64)).Preload("URL").Find(&blogges) + logger.Info("done") + libapi.JSONWrite(w, r, blogges, nil) +} + +func blogShow(w http.ResponseWriter, r *http.Request) { + logger := log.GetLog(r, "blogshow") + blog, returnerr := getBlog(w, r) + if returnerr != nil { + logger.Info("not found") + libapi.JSONWrite(w, r, false, returnerr) + return + } + logger = logger.WithField("bID", blog.ID) + logger.Info("done") + libapi.JSONWrite(w, r, blog, nil) +} + +// BlogAdd to add a new blog +func blogAdd(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := log.GetLog(r, "blogadd") + var blog Blog + returnerr := libapi.JSONDecoder(w, r, logger, &blog) + if returnerr != nil { + libapi.JSONWrite(w, r, false, returnerr) + return + } + blog.WebsiteID = ctx.Value("websiteid").(int64) + blog.URL = &URL{ + WebsiteID: ctx.Value("websiteid").(int64), + Path: FixPath(blog.URL.Path), + } + + if err := dbconnection.Create(&blog).Error; err != nil { + logger.Error("database: during create blog: ", err) + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Message: "Internal Database Error"}) + return + } + logger.Info("done") + libapi.JSONWrite(w, r, true, nil) +} + +// BlogEdit to edit blog +func blogEdit(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := log.GetLog(r, "blogedit") + var blog Blog + blogid, err := strconv.ParseInt(pat.Param(r, "blogid"), 10, 64) + if err != nil { + logger.Warn("invalid blogid, no integer") + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Fields: []string{"blogid"}, Message: "Not a valid blogid"}) + return + } + logger = logger.WithField("id", blogid) + returnerr := libapi.JSONDecoder(w, r, logger, &blog) + if returnerr != nil { + libapi.JSONWrite(w, r, false, returnerr) + return + } + + blog.ID = blogid + blog.WebsiteID = ctx.Value("websiteid").(int64) + blog.URL.WebsiteID = blog.WebsiteID + blog.URL.Path = FixPath(blog.URL.Path) + + tx := dbconnection.Begin() + if err := tx.Save(blog.URL).Error; err != nil { + logger.Error("database: during save website url: ", err) + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Message: "Internal Database Error"}) + return + } + + blog.URLID = blog.URL.ID + blog.URL = nil + + if err := tx.Save(blog).Error; err != nil { + logger.Error("database: during save website blog: ", err) + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Message: "Internal Database Error"}) + return + } + tx.Commit() + + logger.Info("done") + libapi.JSONWrite(w, r, true, nil) +} + +// BlogDelete to delete blog +func blogDelete(w http.ResponseWriter, r *http.Request) { + logger := log.GetLog(r, "blogdelete") + blogid, err := strconv.ParseInt(pat.Param(r, "blogid"), 10, 64) + if err != nil { + logger.Warn("invalid blogid, no integer") + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Fields: []string{"blogid"}, Message: "Not a valid blogid"}) + return + } + logger = logger.WithField("id", blogid) + blog := &Blog{} + dbconnection.Where("id = ?", blogid).Preload("URL").First(blog) + + if err := dbconnection.Unscoped().Delete(blog.URL).Error; err != nil { + logger.Error("database: during delete website blog: ", err) + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Message: "Internal Database Error"}) + return + } + logger.Info("done") + libapi.JSONWrite(w, r, true, nil) +} diff --git a/modul/web/apiblogpost.go b/modul/web/apiblogpost.go new file mode 100644 index 0000000..a2e6031 --- /dev/null +++ b/modul/web/apiblogpost.go @@ -0,0 +1,114 @@ +package web + +import ( + "net/http" + "strconv" + + "goji.io/pat" + + libapi "dev.sum7.eu/sum7/warehost/lib/api" +) + +// to add a new blog post +func blogpostAdd(w http.ResponseWriter, r *http.Request) { + logger := log.GetLog(r, "blogpostadd") + blog, returnerr := getBlog(w, r) + if returnerr != nil { + logger.Info("not found") + libapi.JSONWrite(w, r, false, returnerr) + return + } + logger = logger.WithField("bID", blog.ID) + var blogpost BlogPost + returnerr = libapi.JSONDecoder(w, r, logger, &blogpost) + if returnerr != nil { + libapi.JSONWrite(w, r, false, returnerr) + return + } + + blogpost.BlogID = blog.ID + + if blogpost.Preview != nil { + blogpost.PreviewID = &blogpost.Preview.ID + blogpost.Preview = nil + } + + if err := dbconnection.Create(&blogpost).Error; err != nil { + logger.Error("database: during create blog: ", err) + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Message: "Internal Database Error"}) + return + } + logger.Info("done") + libapi.JSONWrite(w, r, true, nil) +} + +// to edit a blog post +func blogpostEdit(w http.ResponseWriter, r *http.Request) { + logger := log.GetLog(r, "blogpostedit") + blog, returnerr := getBlog(w, r) + if returnerr != nil { + logger.Info("not found") + libapi.JSONWrite(w, r, false, returnerr) + return + } + logger = logger.WithField("bID", blog.ID) + blogpostid, err := strconv.ParseInt(pat.Param(r, "blogpostid"), 10, 64) + if err != nil { + logger.Warn("invalid blogpostid, no integer") + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Fields: []string{"blogid"}, Message: "Not a valid blogpostid"}) + return + } + logger = logger.WithField("pID", blogpostid) + var blogpost BlogPost + returnerr = libapi.JSONDecoder(w, r, logger, &blogpost) + if returnerr != nil { + libapi.JSONWrite(w, r, false, returnerr) + return + } + + blogpost.BlogID = blog.ID + blogpost.ID = blogpostid + + if blogpost.Preview != nil { + blogpost.PreviewID = &blogpost.Preview.ID + blogpost.Preview = nil + } + + if err := dbconnection.Save(&blogpost).Error; err != nil { + logger.Error("database: during save website blogpost: ", err) + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Message: "Internal Database Error"}) + return + } + + logger.Info("done") + libapi.JSONWrite(w, r, true, nil) +} + +// to delete a blog post +func blogpostDelete(w http.ResponseWriter, r *http.Request) { + logger := log.GetLog(r, "blogpostdelete") + blog, returnerr := getBlog(w, r) + if returnerr != nil { + logger.Info("not found") + libapi.JSONWrite(w, r, false, returnerr) + return + } + logger = logger.WithField("bID", blog.ID) + blogpostid, err := strconv.ParseInt(pat.Param(r, "blogpostid"), 10, 64) + if err != nil { + logger.Warn("invalid blogpostid, no integer") + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Fields: []string{"blogid"}, Message: "Not a valid blogpostid"}) + return + } + logger = logger.WithField("pID", blogpostid) + blogpost := &BlogPost{} + dbconnection.Where("blog = ? and id = ?", blog.ID, blogpostid).First(blogpost) + + if err := dbconnection.Unscoped().Delete(blogpost).Error; err != nil { + logger.Error("database: during delete website blogpost: ", err) + libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Message: "Internal Database Error"}) + return + } + logger.Info("done") + libapi.JSONWrite(w, r, true, nil) +} diff --git a/modul/web/models.go b/modul/web/models.go index 9df8a2c..917c38b 100644 --- a/modul/web/models.go +++ b/modul/web/models.go @@ -1,6 +1,10 @@ package web import ( + "strconv" + "strings" + "time" + "github.com/jinzhu/gorm" system "dev.sum7.eu/sum7/warehost/system" @@ -39,8 +43,8 @@ func (Manager) TableName() string { return "web_manager" } // Media struct type Media struct { ID int64 `gorm:"primary_key"` - WebsiteID int64 `sql:"type:bigint REFERENCES web_website(id) ON UPDATE CASCADE ON DELETE CASCADE;column:website;primary_key" json:"-"` - Path string `gorm:"type:varchar(255);column:path" json:"path"` + WebsiteID int64 `sql:"type:bigint REFERENCES web_website(id) ON UPDATE CASCADE ON DELETE CASCADE;column:website;unique_index:media_website_path" json:"-"` + Path string `gorm:"type:varchar(255);column:path;unique_index:media_website_path" json:"path"` } // TableName of struct @@ -84,6 +88,72 @@ type Page struct { // TableName of struct func (Page) TableName() string { return "web_page" } +// PostURLType of URL Schema on a blog +type PostURLType int + +const ( + // PostURLDate http(s)://DOMAIN/url/year/month/day/posttitle + PostURLDate = 0 + // PostURLTitle http(s)://DOMAIN/url/posttitle + PostURLTitle = 1 +) + +// Blog struct +type Blog struct { + ID int64 `gorm:"primary_key"` + WebsiteID int64 `sql:"type:bigint REFERENCES web_website(id) ON UPDATE CASCADE ON DELETE CASCADE;column:website" json:"-"` + URLID int64 `sql:"type:bigint unique REFERENCES web_url(id) ON UPDATE CASCADE ON DELETE CASCADE;column:url" json:"-"` + URL *URL `gorm:"foreignkey:URL" json:"url"` + PostURL PostURLType `sql:"type:int;column:posturl" json:"posturl"` + PreviewEnable bool `gorm:"column:preview_enable" json:"preview_enable"` + AuthorEnable bool `gorm:"column:author_enable" json:"author_enable"` + CreateAtEnable bool `gorm:"column:createat_enable" json:"createat_enable"` + TimerangeEnable bool `gorm:"column:timerange_enable" json:"timerange_enable"` + Title string `gorm:"type:varchar(255);column:title" json:"title"` + Content string `gorm:"type:text;column:content" json:"content"` + Posts []*BlogPost `gorm:"foreignkey:Blog" json:"posts"` +} + +// TableName of struct +func (Blog) TableName() string { return "web_blog" } + +// BlogPost struct +type BlogPost struct { + ID int64 `gorm:"primary_key"` + BlogID int64 `sql:"type:bigint REFERENCES web_blog(id) ON UPDATE CASCADE ON DELETE CASCADE;column:blog" json:"-"` + Blog *Blog `json:"blog"` + CreateAt time.Time `sql:"default:current_timestamp" gorm:"column:createat" json:"createat"` + Author string `gorm:"type:varchar(255);column:author" json:"author"` + Title string `gorm:"type:varchar(255);column:title" json:"title"` + Location string `gorm:"type:varchar(255);column:location" json:"location"` + Start time.Time `gorm:"column:start" json:"start"` + End time.Time `gorm:"column:end" json:"end"` + Link string `gorm:"type:varchar(255);column:link" json:"link"` + PreviewID *int64 `sql:"type:bigint REFERENCES web_media(id) ON UPDATE CASCADE ON DELETE SET NULL;column:preview" json:"-"` + Preview *Media `gorm:"foreignkey:Media" json:"preview"` + Summary string `gorm:"type:text;column:summary" json:"summary"` + Content string `gorm:"type:text;column:content" json:"content"` +} + +func (bg *BlogPost) GetURL() string { + var parts []string + if blog := bg.Blog; blog != nil { + parts = append(parts, blog.URL.Path) + if blog.PostURL == 1 { + month := strconv.Itoa(int(bg.CreateAt.Month())) + if len(month) == 1 { + month = "0" + month + } + parts = append(parts, strconv.Itoa(bg.CreateAt.Year()), month) + } + } + parts = append(parts, bg.Title) + return "/" + FixPath(strings.Join(parts, "/")) +} + +// TableName of struct +func (BlogPost) TableName() string { return "web_blogpost" } + // BuildMenuTree format a array of Menu to a Tree of Menu (with Children) func BuildMenuTree(items []*Menu) []*Menu { menumap := make(map[int64]*Menu) @@ -111,5 +181,5 @@ func BuildMenuTree(items []*Menu) []*Menu { // SyncModels to verify the database schema func SyncModels(dbconnection *gorm.DB) { - dbconnection.AutoMigrate(&Website{}, &Domain{}, &Manager{}, &URL{}, &Media{}, &Menu{}, &Page{}) + dbconnection.AutoMigrate(&Website{}, &Domain{}, &Manager{}, &URL{}, &Media{}, &Menu{}, &Page{}, &Blog{}, &BlogPost{}) } diff --git a/system/api.go b/system/api.go index cfd2187..ddb6532 100644 --- a/system/api.go +++ b/system/api.go @@ -246,7 +246,7 @@ func loginAdd(w http.ResponseWriter, r *http.Request) { Active: true, } - if err := dbconnection.Create(loginObj).Error; err != nil { + if err := dbconnection.Create(&loginObj).Error; err != nil { logger.Warn("error create login") libapi.JSONWrite(w, r, false, &libapi.ErrorResult{Message: "Username exists already"}) return