0
0
Fork 0

better version of the base system

This is a complete redesign of the blog system. This time, we just make
it as simple as possible without layer on layer of abstraction.
This commit is contained in:
Gibheer 2014-12-14 08:08:03 +01:00
parent fce1df3f21
commit e8fcff2cbb
18 changed files with 157 additions and 416 deletions

25
boot.go
View File

@ -1,25 +0,0 @@
package main
import (
"github.com/gibheer/zero-blog/lib"
)
func boot_system() (*lib.Environment, error) {
env := &lib.Environment{}
settings, err := lib.LoadConfiguration()
if err != nil {
return env, err
}
env.Config = settings
env.DB, err = lib.NewDatabase(settings.Connection)
if err != nil {
return env, err
}
env.Template, err = lib.LoadTemplates(settings.Templates)
if err != nil {
return env, err
}
return env, nil
}

View File

@ -1,23 +0,0 @@
package admin
import (
"log"
"github.com/gibheer/zero-blog/lib"
)
func LoginGet(c *lib.Context) error {
c.Env.Template.Lookup("login/index").Execute(c.Response, struct {}{})
return nil
}
func LoginPost(c *lib.Context) error {
username := c.Request.PostFormValue("username")
password := c.Request.PostFormValue("password")
if username == "" && password == "" {
log.Print("There was nothing at all!")
} else {
log.Print(username, password)
}
c.Env.Template.Lookup("login/index").Execute(c.Response, struct {}{})
return nil
}

View File

@ -1,16 +0,0 @@
package controller
import (
"github.com/gibheer/zero-blog/lib"
"github.com/gibheer/zero-blog/middleware"
"github.com/gibheer/zero-blog/controller/admin"
"github.com/gibheer/zero-blog/controller/welcome"
)
func DefineRoutes(router *lib.Router) {
router.Get("/", welcome.Welcome)
router.Get( "/login", admin.LoginGet)
router.Post("/login", admin.LoginPost)
authentication := router.NewGroup("/admin")
authentication.Use(middleware.Authentication)
}

View File

@ -1,11 +0,0 @@
package welcome
import (
"fmt"
"github.com/gibheer/zero-blog/lib"
)
func Welcome(c *lib.Context) error {
fmt.Fprint(c.Response, "Hello World!")
return nil
}

View File

@ -1,31 +0,0 @@
package lib
import (
"errors"
"database/sql"
pq "github.com/lib/pq"
)
type Database struct {
Conn *sql.DB
}
func (d *Database) Query(query string) (*sql.Rows, error) {
res, err := d.Conn.Query(query)
if err != nil {
err := err.(*pq.Error)
return nil, errors.New(err.Code.Name())
}
return res, nil
}
func NewDatabase(connection string) (*Database, error) {
if connection == "" {
return nil, errors.New("No connection string given! Check the config.yml file!")
}
DB, err := sql.Open("postgres", connection)
if err != nil {
return nil, err
}
return &Database{DB}, nil
}

View File

@ -1,34 +0,0 @@
package lib
import (
"io/ioutil"
"text/template"
"gopkg.in/yaml.v1"
)
type Settings struct {
Connection string
Templates string
}
type Environment struct {
// settings from the config file
Config *Settings
// the database connection pool
DB *Database
// the base template system
Template *template.Template
}
func LoadConfiguration() (*Settings, error) {
configfile, err := ioutil.ReadFile("config.yml")
if err != nil {
return nil, err
}
settings := Settings{}
err = yaml.Unmarshal(configfile, &settings)
if err != nil {
return nil, err
}
return &settings, nil
}

View File

@ -1,135 +0,0 @@
package lib
import (
"log"
"net/http"
"github.com/julienschmidt/httprouter"
"github.com/gibheer/zero-blog/repository"
)
// the following is inspired by the gin framework
// this router defines each action to take for an URI including the middlewares
type Router struct {
// the environment of the application with settings and database connection
env *Environment
// path that this engine takes care of relative to the parent
path string
// the list of functions to run on a request
funcList []ContextFunc
// the router to use as the main entity
router *httprouter.Router
// the parent router, if any
parent *Router
}
// create a new router with the specific environment
func NewRouter(env *Environment) *Router {
return &Router{env, "", make([]ContextFunc, 0), httprouter.New(), nil}
}
// Bundle all parameters into the context to make it easier to push important
// data into functions.
type Context struct {
// the current request
Request *http.Request
// the response
Response http.ResponseWriter
// parameters provided by the router
Params httprouter.Params
// current session
Session *repository.Session
// the list of functions to run
funcList []ContextFunc
// the current position in the function list
current int
// a direct link to the environment
Env *Environment
}
// the func type for internal routes
type ContextFunc func(*Context) error
func (r *Router) fullpath(path string) string {
if r.parent != nil {
return r.parent.fullpath(r.path + path)
}
return r.path + path
}
func (r *Router) fullFuncList() []ContextFunc {
if r.parent != nil {
return append(r.parent.fullFuncList(), r.funcList...)
}
return r.funcList
}
func (r *Router) Get(path string, target ContextFunc) {
r.addRoute("GET", path, target)
}
func (r *Router) Post(path string, target ContextFunc) {
r.addRoute("POST", path, target)
}
func (r *Router) Put(path string, target ContextFunc) {
r.addRoute("PUT", path, target)
}
func (r *Router) Delete(path string, target ContextFunc) {
r.addRoute("DELETE", path, target)
}
func (router *Router) addRoute(method, path string, target ContextFunc) {
router.router.Handle(
method,
router.fullpath(path),
router.createHandleFunction(target),
)
}
func (r *Router) createHandleFunction(target ContextFunc) httprouter.Handle {
return func(
response http.ResponseWriter,
request *http.Request,
params httprouter.Params) {
ctx := &Context{
Request: request,
Response: response,
Params: params,
funcList: append(r.fullFuncList(), target),
current: 0,
Env: r.env,
}
for !ctx.Aborted() && ctx.current < len(ctx.funcList) {
ctx.funcList[ctx.current](ctx)
ctx.current++
}
}
}
func (r *Router) Use(middleware ContextFunc) {
r.funcList = append(r.funcList, middleware)
}
func (r *Router) NewGroup(path string) *Router {
return &Router{r.env, path, make([]ContextFunc, 0), r.router, r}
}
func (r *Router) Start() {
log.Print("Starting to listen for incoming requests ...")
log.Fatal(http.ListenAndServe(":9292", r.router))
}
func (c *Context) Aborted() bool {
if c.current < 0 {
return true
}
return false
}
func (c *Context) Abort() {
c.current = -1
}

View File

@ -1,50 +0,0 @@
package lib
import (
"errors"
"log"
"io/ioutil"
"os"
"path/filepath"
"text/template"
)
type fileList struct {
len_base int
t *template.Template
}
// load all templates found as childs of the path
func LoadTemplates(path string) (*template.Template, error) {
// TODO add better check for template directory
if path == "" { return nil, errors.New("template path empty") }
f := &fileList{len(path), &template.Template{}}
err := filepath.Walk(path, f.scanFile)
if err != nil {
return nil, err
}
return f.t, nil
}
func (f *fileList) scanFile(path string, info os.FileInfo, err error) error {
if err != nil { log.Println(`Error with file:`, path, `-`, err) }
if info.IsDir() {
log.Print(`Scanning '`, path, `' for templates`)
}
if !info.IsDir() && path[len(path) - 5:] == `.tmpl` {
name := path[f.len_base + 1 : len(path) - 5]
log.Println(`Adding:`, name)
f.t.New(name).Parse(read_file_content(path))
}
return nil
}
func read_file_content(path string) string {
content, err := ioutil.ReadFile(path)
if err != nil {
return ""
}
return string(content)
}

71
main.go
View File

@ -1,20 +1,77 @@
package main
import (
"errors"
"flag"
"fmt"
"net/http"
"log"
"io/ioutil"
"os"
"github.com/gibheer/zero-blog/lib"
"github.com/gibheer/zero-blog/controller"
"time"
"github.com/julienschmidt/httprouter"
"gopkg.in/yaml.v2"
)
type (
Config struct {
Address string // which address should be listened on
Port int // the port to listen for the admin panel
Connection string // the URL to the database
TemplatePath string // path to the templates
}
Logger struct {
router *httprouter.Router
}
)
// logger works as a middleware here
func (l Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
start := time.Now()
w.Header()["Content-Type"] = []string{"text/html"}
l.router.ServeHTTP(w, r)
log.Print(r.URL.Path, ";", time.Since(start))
}
func main() {
environment, err := boot_system()
var config_path string
flag.StringVar(&config_path, "config", "config.yml", "path to the config file")
flag.Parse()
config, err := readConfig(config_path)
if err != nil {
log.Fatal("Boot crashed with message: ", err)
log.Println("Error with config file: ", err)
os.Exit(1)
}
if err := templates_load(config.TemplatePath); err != nil {
log.Printf("Error when loading the templates: %v", err)
os.Exit(1)
}
router := lib.NewRouter(environment)
controller.DefineRoutes(router)
router.Start()
router := httprouter.New()
define_routes(router)
start_server(config, router)
}
// Reads the config from config_path and returns the settings.
func readConfig(config_path string) (*Config, error) {
if config_path == "" {
return nil, errors.New("No config path given!")
}
file, err := ioutil.ReadFile(config_path)
if err != nil { return nil, err }
config := Config{}
if err := yaml.Unmarshal(file, &config); err != nil {
return nil, err
}
return &config, nil
}
// start the server with access log
func start_server(config *Config, router *httprouter.Router) {
logger := Logger{router}
log.Print(http.ListenAndServe(fmt.Sprintf("%v:%v", config.Address, config.Port), logger))
}

View File

@ -1,27 +0,0 @@
package middleware
import (
"net/http"
"github.com/gibheer/zero-blog/lib"
"github.com/gibheer/zero-blog/repository"
)
func Authentication(c *lib.Context) error {
session_id, err := c.Request.Cookie("session")
if err != nil {
redirectToLogin(c)
return err
}
session := repository.GetSession(c.Env.DB.Conn, session_id.Value)
if session == nil {
redirectToLogin(c)
return err
}
c.Session = session
return nil
}
func redirectToLogin(c *lib.Context) {
c.Abort()
http.Redirect(c.Response, c.Request, "/login", 307)
}

View File

@ -1,29 +0,0 @@
package repository
import (
"database/sql"
)
type Account struct {
Id int
Username string
Email string
Password string
Role string
}
func GetAccount(db *sql.DB, account_id string) *Account {
account := &Account{}
err := db.QueryRow(
`select id, username, email from accounts where id = ?`,
account_id).Scan(
&account.Id,
&account.Username,
&account.Email,
)
// TODO do something with the error
if err != nil {
return nil
}
return account
}

View File

@ -1,28 +0,0 @@
package repository
import (
"database/sql"
)
type Session struct {
Id string
Account *Account
}
func GetSession(db *sql.DB, session_id string) *Session {
session := &Session{Account: &Account{}}
err := db.QueryRow(
`select session_id, id, username
from sessions s
join accounts a
on s.account_id = a.id
where session_id = ?
and last_change > now() - interval '1 hour'`,
session_id).Scan(&session.Id, &session.Account.Id, &session.Account.Username)
// TODO do something with the error
if err != nil {
return nil
}
return session
}

23
routes.go Normal file
View File

@ -0,0 +1,23 @@
package main
import (
"net/http"
"github.com/julienschmidt/httprouter"
)
// map URLs to their functions
func define_routes(router *httprouter.Router) {
router.HandlerFunc("GET", "/", Index)
router.GET("/post", Posts)
}
// the function for route /
func Index(w http.ResponseWriter, r *http.Request) {
Templates.ExecuteTemplate(w, "welcome", nil)
}
func Posts(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
posts := []int {0, 1, 2, 3, 4}
Templates.ExecuteTemplate(w, "posts/index", posts)
}

51
template.go Normal file
View File

@ -0,0 +1,51 @@
package main
import (
"errors"
"html/template"
"io/ioutil"
"log"
"path/filepath"
"os"
)
var (
Templates *template.Template // all templates
base_path_len int // the base path len to generate a template name
)
// load all templates in the path
func templates_load(path string) error {
if base_path_len > 0 { return errors.New("Templates already loaded!") }
if len(path) == 0 { return errors.New("Path is empty!") }
base_path_len = len(path)
Templates = template.New("dummy")
if err := filepath.Walk(path, templates_scan_file); err != nil {
return err
}
return nil
}
// scan the file for a useable name and add to template collection
func templates_scan_file(path string, info os.FileInfo, err error) error {
if err != nil { log.Println(`Error with file:`, path, `-`, err) }
if !info.IsDir() && path[len(path) - 5:] == `.tmpl` {
name := path[base_path_len + 1 : len(path) - 5]
template.Must(Templates.New(name).Parse(template_parse(path)))
if err != nil {
return err
}
}
return nil
}
// parse the template or return an error
func template_parse(path string) string {
content, err := ioutil.ReadFile(path)
if err != nil {
return ""
}
return string(content)
}

2
templates/footer.tmpl Normal file
View File

@ -0,0 +1,2 @@
</body>
</html>

7
templates/header.tmpl Normal file
View File

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>foobar</title>
</head>
<body>
<h1>This is working!</h1>

View File

@ -0,0 +1,7 @@
{{ template "header" . }}
<ul>
{{ range . }}
<li>{{ . }}
{{ end }}
</ul>
{{ template "footer" . }}

3
templates/welcome.tmpl Normal file
View File

@ -0,0 +1,3 @@
{{ template "header" . }}
From me too!
{{ template "footer" . }}