Browse Source

Initial Prototype

master
Noah Petherbridge 9 months ago
parent
commit
f8bb08d2f5
23 changed files with 1136 additions and 0 deletions
  1. +3
    -0
      .gitignore
  2. +54
    -0
      Makefile
  3. +81
    -0
      cmd/gophertype/main.go
  4. +10
    -0
      go.mod
  5. +166
    -0
      go.sum
  6. +46
    -0
      pkg/app.go
  7. +42
    -0
      pkg/controllers/index.go
  8. +23
    -0
      pkg/controllers/initial_setup.go
  9. +39
    -0
      pkg/controllers/static_files.go
  10. +73
    -0
      pkg/glue/controller.go
  11. +16
    -0
      pkg/middleware/example.go
  12. +12
    -0
      pkg/models/models.go
  13. +11
    -0
      pkg/models/users.go
  14. +9
    -0
      pkg/responses/errors.go
  15. +26
    -0
      pkg/responses/static_file.go
  16. +121
    -0
      pkg/responses/templates.go
  17. +50
    -0
      pkg/routes.go
  18. +3
    -0
      pkg/version.go
  19. +198
    -0
      pvt-www/.layout.gohtml
  20. +40
    -0
      pvt-www/_builtin/blog-core.css
  21. +5
    -0
      pvt-www/_builtin/initial_setup.gohtml
  22. +99
    -0
      pvt-www/_builtin/theme/theme.css
  23. +9
    -0
      pvt-www/index.gohtml

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
bin/
pkg/bundled/
*.sqlite

+ 54
- 0
Makefile View File

@@ -0,0 +1,54 @@
SHELL := /bin/bash

VERSION=$(shell grep -e 'Version =' pkg/version.go | head -n 1 | cut -d '"' -f 2)
BUILD=$(shell git describe --always)
BUILD_DATE=$(shell date +"%Y-%m-%dT%H:%M:%S%z")
CURDIR=$(shell curdir)

# Inject the build version (commit hash) into the executable.
LDFLAGS := -ldflags "-X main.Build=$(BUILD) -X main.BuildDate=$(BUILD_DATE)"

# `make setup` to set up a new environment, pull dependencies, etc.
.PHONY: setup
setup: clean
go get -u github.com/go-bindata/go-bindata/...
go get ./...

# `make build` to build the binary.
.PHONY: build
build:
go build $(LDFLAGS) -i -o bin/gophertype cmd/gophertype/main.go

# `make buildall` to run all build steps including doodads and bindata.
.PHONY: buildall
buildall: bindata build

# `make bindata` generates the embedded binary assets package.
.PHONY: bindata
bindata:
go-bindata -pkg bundled -o pkg/bundled/bindata.go -prefix pvt-www pvt-www/...

# `make bindata-dev` generates the debug version of bindata package.
.PHONY: bindata-dev
bindata-dev:
go-bindata -debug -pkg bundled -o pkg/bundled/bindata.go -prefix pvt-www pvt-www/...

# `make install` to install the Go binaries to your GOPATH.
.PHONY: install
install:
go install git.kirsle.net/apps/gophertype/cmd/gophertype

# `make run` to run it in debug mode.
.PHONY: run
run:
go run cmd/gophertype/main.go -debug

# `make test` to run unit tests.
.PHONY: test
test:
go test ./...

# `make clean` cleans everything up.
.PHONY: clean
clean:
rm -rf bin

+ 81
- 0
cmd/gophertype/main.go View File

@@ -0,0 +1,81 @@
//go:generate go-bindata -pkg bundled -o pkg/bundled/bindata.go pvt-www/...
//go:generate echo hello world
package main

import (
"flag"
"fmt"
"os"

"git.kirsle.net/apps/gophertype/pkg"
_ "git.kirsle.net/apps/gophertype/pkg/controllers"
_ "github.com/jinzhu/gorm/dialects/mysql"
_ "github.com/jinzhu/gorm/dialects/postgres"
_ "github.com/jinzhu/gorm/dialects/sqlite"
)

// Build parameters.
var (
Build string
BuildDate string
)

// Command-line flags.
var (
optDebug bool
optBind string

// Database option flags.
optSQLite string
optPostgres string
optMySQL string

// Chosen DB options.
dbDriver string
dbPath string
)

func init() {
flag.BoolVar(&optDebug, "debug", false, "Debug level logging")
flag.StringVar(&optBind, "bind", ":8000", "Bind address for HTTP server")

// Database driver. Choose one.
flag.StringVar(&optSQLite, "sqlite3", "", "Use SQLite database, default 'database.sqlite'")
flag.StringVar(&optPostgres, "postgres", "",
"Use Postgres database, format: "+
"host=myhost port=myport user=gorm dbname=gorm password=mypassword")
flag.StringVar(&optMySQL, "mysql", "",
"Use MySQL database, format: "+
"user:password@/dbname?charset=utf8&parseTime=True&loc=Local")
}

func main() {
flag.Parse()

// Validate the choice of database.
if optSQLite != "" {
dbDriver = "sqlite3"
dbPath = optSQLite
} else if optPostgres != "" {
dbDriver = "postgres"
dbPath = optPostgres
} else if optMySQL != "" {
dbDriver = "mysql"
dbPath = optMySQL
} else {
fmt.Print(
"Choose a database to use when running this command. Examples:\n" +
"gophertype -sqlite3 database.sqlite\n" +
"gophertype -postgres host=myhost port=myport user=gorm dbname=gorm password=mypassword\n" +
"gophertype -mysql user:password@/dbname?charset=utf8&parseTime=True&loc=Local",
)
os.Exit(1)
}

fmt.Println("Hello world")

app := gophertype.NewSite()
app.UseDB(dbDriver, dbPath)
app.SetupRouter()
app.ListenAndServe(optBind)
}

+ 10
- 0
go.mod View File

@@ -0,0 +1,10 @@
module git.kirsle.net/apps/gophertype

go 1.13

require (
github.com/gorilla/mux v1.7.3
github.com/jinzhu/gorm v1.9.11
github.com/kirsle/blog v0.0.0-20191022175051-d78814b9c99b
github.com/urfave/negroni v1.0.0
)

+ 166
- 0
go.sum View File

@@ -0,0 +1,166 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edwvee/exiffix v0.0.0-20180602190213-b57537c92a6b/go.mod h1:KoE3Ti1qbQXCb3s/XGj0yApHnbnNnn1bXTtB5Auq/Vc=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jinzhu/gorm v1.9.9/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY=
github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE=
github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kirsle/blog v0.0.0-20191022175051-d78814b9c99b h1:bzQ/gogFWD2RNLW/vEjAsiBqeIwzLXWqH58wAJlP8W0=
github.com/kirsle/blog v0.0.0-20191022175051-d78814b9c99b/go.mod h1:D6I6jquqhLewuhrVt9Q3p97r2YCC7CA4DgNv14+WS8k=
github.com/kirsle/golog v0.0.0-20180411020913-51290b4f9292 h1:Ihk5qKHfi9W/1B9A9GuxKgriCiieK03xp0XN6YYXOtw=
github.com/kirsle/golog v0.0.0-20180411020913-51290b4f9292/go.mod h1:0KaOvOX8s5YINMREeyTILsuU0wkmnKQQTy99e/2oDGc=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
github.com/shurcooL/highlight_go v0.0.0-20181215221002-9d8641ddf2e1/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
github.com/shurcooL/octicon v0.0.0-20181222203144-9ff1a4cf27f4/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10=
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e/go.mod h1:fSIW/szJHsRts/4U8wlMPhs+YqJC+7NYR+Qqb1uJVpA=
github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU=
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 46
- 0
pkg/app.go View File

@@ -0,0 +1,46 @@
package gophertype

import (
"log"
"net/http"

"git.kirsle.net/apps/gophertype/pkg/models"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
"github.com/urfave/negroni"
)

// Site is the master struct for the Gophertype server.
type Site struct {
n *negroni.Negroni
mux *mux.Router
}

// NewSite initializes the Site.
func NewSite() *Site {
site := &Site{}

n := negroni.New()
n.Use(negroni.NewRecovery())
n.Use(negroni.NewLogger())
site.n = n

return site
}

// UseDB specifies the database to use.
func (s *Site) UseDB(driver string, path string) error {
db, err := gorm.Open(driver, path)
if err != nil {
return err
}

log.Printf("Using database driver '%s'", driver)
models.UseDB(db)
return nil
}

// ListenAndServe starts the HTTP service.
func (s *Site) ListenAndServe(addr string) error {
return http.ListenAndServe(addr, s.n)
}

+ 42
- 0
pkg/controllers/index.go View File

@@ -0,0 +1,42 @@
package controllers

import (
"net/http"

"git.kirsle.net/apps/gophertype/pkg/glue"
"git.kirsle.net/apps/gophertype/pkg/middleware"
"github.com/gorilla/mux"
)

func init() {
glue.Register(glue.Endpoint{
Path: "/about",
Middleware: []mux.MiddlewareFunc{
middleware.ExampleMiddleware,
},
Handler: func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("About Site"))
},
})

glue.Register(glue.Endpoint{
Path: "/admin",
Middleware: []mux.MiddlewareFunc{
middleware.ExampleMiddleware,
},
Handler: func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Admin index"))
},
})

glue.Register(glue.Endpoint{
Path: "/admin/users",
Methods: []string{"GET", "POST"},
Middleware: []mux.MiddlewareFunc{
middleware.ExampleMiddleware,
},
Handler: func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Admin users page"))
},
})
}

+ 23
- 0
pkg/controllers/initial_setup.go View File

@@ -0,0 +1,23 @@
package controllers

import (
"net/http"

"git.kirsle.net/apps/gophertype/pkg/glue"
"git.kirsle.net/apps/gophertype/pkg/middleware"
"git.kirsle.net/apps/gophertype/pkg/responses"
"github.com/gorilla/mux"
)

func init() {
glue.Register(glue.Endpoint{
Path: "/admin/setup",
Middleware: []mux.MiddlewareFunc{
middleware.ExampleMiddleware,
},
Handler: func(w http.ResponseWriter, r *http.Request) {
responses.RenderTemplate(w, "_builtin/initial_setup.gohtml", nil)
},
})

}

+ 39
- 0
pkg/controllers/static_files.go View File

@@ -0,0 +1,39 @@
package controllers

import (
"log"
"net/http"
"strings"

"git.kirsle.net/apps/gophertype/pkg/responses"
)

// CatchAllHandler handles the "/" and wildcard routes that are not
// picked up by any other controllers. It serves static files and Go
// templates from the builtin web root or the user root.
func CatchAllHandler(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path
log.Printf("Wildcard path: %s", path)

// Resolve the target path.
filepath, err := responses.ResolveFile(path)
if err != nil {
responses.Panic(w, http.StatusNotFound, "ResolveFile: "+err.Error())
return
}

// Files under /_builtin can only be taken literally.
if strings.HasPrefix(filepath, "_builtin/") {
responses.SendFile(w, r, filepath)
return
}

// Is it a Go template?
if strings.HasSuffix(filepath, ".gohtml") {
log.Printf("Resolved to Go Template path %s", filepath)
responses.RenderTemplate(w, filepath, nil)
return
}

http.ServeFile(w, r, "pvt-www/"+filepath)
}

+ 73
- 0
pkg/glue/controller.go View File

@@ -0,0 +1,73 @@
package glue

import (
"fmt"
"log"
"net/http"
"sort"
"sync"

"github.com/gorilla/mux"
)

// Endpoint is a handler attached to a path.
type Endpoint struct {
Path string
Methods []string
Middleware []mux.MiddlewareFunc
Handler func(w http.ResponseWriter, r *http.Request)
}

var (
registry = map[string]Endpoint{}
registryLock sync.RWMutex
)

//////////////////////////

// Register a controller.
func Register(e Endpoint) {
registryLock.Lock()
if _, ok := registry[e.Path]; ok {
panic(fmt.Sprintf("Route Registry: path '%s' already registered", e.Path))
}
registry[e.Path] = e
registryLock.Unlock()
log.Printf("Register: %s", e.Path)
}

// GetControllers returns all the routes and handler functions.
func GetControllers() []Endpoint {
registryLock.RLock()
defer registryLock.RUnlock()

// Sort the endpoints by longest first.
var keys = make([]string, len(registry))
var i int
for key := range registry {
keys[i] = key
i++
}

sort.Sort(byLength(keys))

result := make([]Endpoint, len(registry))
for i, key := range keys {
result[i] = registry[key]
}

return result
}

type byLength []string

func (s byLength) Len() int {
return len(s)
}
func (s byLength) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s byLength) Less(i, j int) bool {
// Sort longest over shortest.
return len(s[j]) < len(s[i])
}

+ 16
- 0
pkg/middleware/example.go View File

@@ -0,0 +1,16 @@
package middleware

import (
"log"
"net/http"
)

// ExampleMiddleware is a test middleware.
func ExampleMiddleware(next http.Handler) http.Handler {
middleware := func(w http.ResponseWriter, r *http.Request) {
log.Printf("ExampleMiddleware called on route %s", r.URL.Path)
next.ServeHTTP(w, r)
}

return http.HandlerFunc(middleware)
}

+ 12
- 0
pkg/models/models.go View File

@@ -0,0 +1,12 @@
package models

import "github.com/jinzhu/gorm"

// DB is the database handle for all the models.
var DB *gorm.DB

// UseDB registers a database driver.
func UseDB(db *gorm.DB) {
DB = db
DB.AutoMigrate(&User{})
}

+ 11
- 0
pkg/models/users.go View File

@@ -0,0 +1,11 @@
package models

// User account for the site.
type User struct {
ID int `json:"id"`
Username string `json:"username" gorm:"unique"`
Password string `json:"-"`
IsAdmin bool `json:"isAdmin"`
Name string `json:"name"`
Email string `json:"email"`
}

+ 9
- 0
pkg/responses/errors.go View File

@@ -0,0 +1,9 @@
package responses

import "net/http"

// Panic gives a simple error with no template or anything fancy.
func Panic(w http.ResponseWriter, code int, message string) {
w.WriteHeader(code)
w.Write([]byte(message))
}

+ 26
- 0
pkg/responses/static_file.go View File

@@ -0,0 +1,26 @@
package responses

import (
"mime"
"net/http"
"path/filepath"

"git.kirsle.net/apps/gophertype/pkg/bundled"
)

// SendFile sends a file from bindata or the user root.
func SendFile(w http.ResponseWriter, r *http.Request, path string) {
mimeType := mime.TypeByExtension(filepath.Ext(path))
if mimeType == "" {
mimeType = "text/plain"
}

w.Header().Set("Content-Type", mimeType)

if b, err := bundled.Asset(path); err == nil {
w.Write(b)
return
}

http.ServeFile(w, r, "pvt-www/"+path)
}

+ 121
- 0
pkg/responses/templates.go View File

@@ -0,0 +1,121 @@
package responses

import (
"errors"
"fmt"
"html/template"
"io/ioutil"
"log"
"net/http"
"os"
"strings"

"git.kirsle.net/apps/gophertype/pkg/bundled"
)

// GetTemplate returns the template file's data, wherever it is.
// Checks the embedded bindata, then the user root on disk, then error.
// If it can be found, returns the contents or error.
func GetFile(path string) ([]byte, error) {
// Check bindata.
if b, err := bundled.Asset(path); err == nil {
return b, nil
}

// Check the filesystem. TODO
if b, err := ioutil.ReadFile("./pvt-www/" + path); err == nil {
return b, nil
} else {
return []byte{}, err
}
}

// GetFileExists checks if the file exists but doesn't return its data.
func GetFileExists(path string) bool {
// Check bindata.
if _, err := bundled.AssetInfo(path); err == nil {
return true
}

// Check the filesystem. TODO
if _, err := os.Stat(path); err == nil {
return true
}

return false
}

/*
ResolveFile searches for the existence of a file from a fuzzy URL path.

`path` is a request path like "/about"

This function would return e.g. "about.gohtml" as being a file path that is
sure to return data in GetFile().

Path finding rules follow expected behavior from dominant web servers:

- If the exact path is found, return it immediately.
- Try assuming a ".gohtml" or ".md" file extension for the path.
- Try checking if the path is a directory with an "index.gohtml" inside it, etc.
*/
func ResolveFile(path string) (string, error) {
// Ensure the path doesn't begin with a slash.
path = strings.TrimLeft(path, "/")

// Try the exact path.
if GetFileExists(path) {
return path, nil
}

// Try fuzzy file matches.
var tries = []string{
path + ".gohtml",
path + ".md",
path + "/index.gohtml",
path + "/index.html",
}
for _, try := range tries {
path = strings.TrimLeft(try, "/")
if GetFileExists(path) {
return path, nil
}
}

return "", errors.New("not found")
}

// RenderTemplate renders a Go HTML template.
// The io.Writer can be an http.ResponseWriter.
func RenderTemplate(w http.ResponseWriter, tmpl string, vars interface{}) error {
w.Header().Set("Content-Type", "text/html; charset=utf-8")

// Look for the built-in template.
if b, err := GetFile(tmpl); err == nil {
t, err := template.New(tmpl).Parse(string(b))
if err != nil {
return fmt.Errorf("bundled template '%s': %s", tmpl, err)
}

// We found the template. Can we find the layout html?
if layout, err := GetFile(".layout.gohtml"); err == nil {
_, err := t.New("layout").Parse(string(layout))
if err != nil {
fmt.Errorf("RenderTemplate(.layout.gohtml): %s", err)
}
} else {
log.Printf("RenderTemplate: .layout.gohtml not found to wrap %s", tmpl)
}

fmt.Printf("Render Templ: %s", tmpl)
if err := t.ExecuteTemplate(w, "layout", vars); err != nil {
log.Printf("RenderTemplate(%s): %s", tmpl, err)
}
log.Println("Done")
return nil
} else {
Panic(w, http.StatusInternalServerError, err.Error())
}

return nil
}

+ 50
- 0
pkg/routes.go View File

@@ -0,0 +1,50 @@
package gophertype

import (
"fmt"
"log"
"net/http"

"git.kirsle.net/apps/gophertype/pkg/controllers"
"git.kirsle.net/apps/gophertype/pkg/glue"
"github.com/gorilla/mux"
)

// SetupRouter sets up the HTTP router.
func (s *Site) SetupRouter() error {
router := mux.NewRouter()

for _, route := range glue.GetControllers() {
log.Printf("Register: %+v", route)
if len(route.Methods) == 0 {
route.Methods = []string{"GET"}
}

route.Methods = append(route.Methods, "HEAD")

if len(route.Middleware) > 0 {
log.Printf("%+v has middlewares!", route)

handler := route.Middleware[0](http.HandlerFunc(route.Handler))
router.Handle(route.Path, handler).Methods(route.Methods...)
} else {
router.HandleFunc(route.Path, route.Handler).Methods(route.Methods...)
}
}

router.PathPrefix("/").HandlerFunc(controllers.CatchAllHandler)
router.NotFoundHandler = http.HandlerFunc(controllers.CatchAllHandler)

log.Println("Walk the mux.Router:")
router.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
tpl, err1 := route.GetPathTemplate()
met, err2 := route.GetMethods()
fmt.Println(tpl, err1, met, err2)
return nil
})

s.mux = router
s.n.UseHandler(router)

return nil
}

+ 3
- 0
pkg/version.go View File

@@ -0,0 +1,3 @@
package gophertype

const Version = "0.0.1"

+ 198
- 0
pvt-www/.layout.gohtml View File

@@ -0,0 +1,198 @@
{{ define "title" }}{{ end }}
{{ define "scripts" }}{{ end }}

{{ define "layout" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

<title>{{ template "title" . }} - {{ .Title }}</title>

<!-- Bootstrap core CSS -->
<link rel="stylesheet" href="/_builtin/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/_builtin/theme/theme.css">

<link rel="stylesheet" href="/_builtin/blog-core.css">
<!-- <link rel="stylesheet" href="/css/gfm.css"> -->
</head>
<body>

<nav class="navbar navbar-expand-md fixed-top bluez-navbar">
<a href="#" class="navbar-brand">{{ .Title }}</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse" aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarCollapse">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a href="/" class="nav-link">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a href="/about" class="nav-link">About</a>
</li>
<li class="nav-item">
<a href="/archive" class="nav-link">Archive</a>
</li>
<li class="nav-item">
<a href="/contact" class="nav-link">Contact Me</a>
</li>
</ul>

<form class="form-inline mt-2 mt-md-0">
<input class="form-control mr-sm-2" type="text" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>

<div class="bluez-header">
<div class="container">
<h1 class="bluez-title">{{ .Title }}</h1>
<p class="lead bluez-description">{{ .Description }}</p>
</div>
</div>

<div class="container mb-5">
<div class="row">
<div class="col-9">
{{ if .SetupNeeded }}
<div class="alert alert-success">
Your web blog needs to be set up!
Please <a href="/initial-setup">click here</a> to
configure your blog.
</div>
{{ end }}

{{ range .Flashes }}
<div class="alert alert-success">
{{ . }}
</div>
{{ end }}

{{ if .Error }}
<div class="alert alert-danger">
<strong>Error:</strong> {{ .Error }}
</div>
{{ end }}

{{ template "content" . }}
</div>
<div class="col-3">

<div class="card mb-4">
<div class="card-body">
<h4 class="card-title">About</h4>

<p>Hello, world!</p>
</div>
</div>

{{ if .LoggedIn }}
<div class="card mb-4">
<div class="card-body">
<h4 class="cart-title">Control Center</h4>

<p>
Logged in as: <a href="/account">{{ .CurrentUser.Username }}</a>
</p>

<ul class="list-unstyled">
{{ if .CurrentUser.Admin }}
<li class="list-item"><a href="/admin">Admin Center</a></li>
{{ end }}
<li class="list-item"><a href="/logout">Log out</a></li>
</ul>

<h5>Manage Blog</h5>

<ul class="list-unstyled">
<li class="list-item"><a href="/blog/edit">Post Blog Entry</a></li>
<li class="list-item"><a href="/blog/drafts">View Drafts</a></li>
<li class="list-item"><a href="/blog/private">View Private</a></li>
</ul>
</div>
</div>
{{ end }}

<div class="card mb-4">
<div class="card-body">
<h4 class="card-title">Archives</h4>

<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="#">March 2018</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">February 2018</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">January 2018</a>
</li>
</ul>
</div>
</div>

<div class="card">
<div class="card-body">
<h4 class="card-title">Elsewhere</h4>

<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link" href="#">Facebook</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Twitter</a>
</li>
</ul>
</div>
</div>

</div>
</div>
</div>

<footer class="bluez-footer">
<div class="container">
<div class="row">
<div class="col-8">
<ul class="nav">
<li class="nav-item">
<a class="nav-link" href="#">RSS</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Random</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Archive</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Ask me anything</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Back to top</a>
</li>
{{ if .LoggedIn }}
<li class="nav-item">
<a class="nav-link" href="/logout">Log out</a>
</li>
{{ else }}
<li class="nav-item">
<a class="nav-link" href="/login">Log in</a>
</li>
{{ end }}
</ul>
</div>
<div class="col-4">
Copyright 2017
</div>
</div>
</div>
</footer>

{{ template "scripts" or "" }}

</body>
</html>
{{ end }}

+ 40
- 0
pvt-www/_builtin/blog-core.css View File

@@ -0,0 +1,40 @@
/*
* Generally useful blog styles.
*/

/* Styles for the blog title <h1> */
.blog-title {
display: block;
}
a.blog-title {
text-decoration: underline;
}

/* The blog entry publish date */
.blog-meta {
font-style: italic;
font-size: smaller;
margin-bottom: 2rem;
}
.blog-meta .blog-private, .blog-meta .blog-unlisted {
color: #F00;
}
.blog-meta .blog-draft {
color: #909;
}
.blog-meta .blog-sticky {
color: #F0F;
}

/* Comment metadata line */
.comment-meta {
font-style: italic;
font-size: smaller;
margin-bottom: 1rem;
}

/* Address formatting */
address {
white-space: pre-line;
font-style: italic;
}

+ 5
- 0
pvt-www/_builtin/initial_setup.gohtml View File

@@ -0,0 +1,5 @@
{{ define "title" }}Initial Setup{{ end }}

{{ define "content" }}
<h1>Initial Setup</h1>
{{ end }}

+ 99
- 0
pvt-www/_builtin/theme/theme.css View File

@@ -0,0 +1,99 @@
/*
* Globals
*/

body {
font-family: Verdana, Helvetica, Arial, sans-serif;
color: #555;
margin-top: 80px;
background-color: #DEF;
}

img {
max-width: 100%;
height: auto;
}

h1, .h1,
h2, .h2,
h3, .h3,
h4, .h4,
h5, .h5,
h6, .h6 {
font-family: "Trebuchet MS", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: normal;
color: #333;
}

blockquote {
border-left: 2px solid #FF0000;
padding: 0 10px;
margin: 4px 6px;
}
blockquote blockquote {
border-left-color: #FF9900;
}
blockquote blockquote blockquote {
border-left-color: #CCCC00;
}

/*
* Bootstrap tweaks and overrides
*/
.form-group label {
font-weight: bold;
}
label.form-check-label {
font-weight: normal;
}
button {
cursor: pointer;
}
.text-muted a {
color: #868e96 !important;
text-decoration: underline;
}

/*
* Top nav
*/
.bluez-navbar {
background-color: rgba(0, 75, 153, 0.8);
}
.bluez-navbar a {
color: #DDD;
}
.bluez-navbar a:hover, .bluez-navbar a:active {
color: #FFF !important;
}
.bluez-navbar .active a {
color: #FFF;
}

/* Blog title and description */
.bluez-header {
padding-bottom: 1.25rem;
margin-bottom: 2rem;
border-bottom: .05rem solid #CCC;
}
.bluez-title {
margin-bottom: 0;
font-size: 2rem;
font-weight: 400;
}
.bluez-description {
font-size: 1.1rem;
color: #999;
}

/* Page footer */
.bluez-footer {
padding: 2.5rem 0;
color: #999;
text-align: center;
background-color: #EEF;
border-top: .05rem solid #CCC;
}
.bluez-footer p:last-child {
margin-bottom: 0;
}

+ 9
- 0
pvt-www/index.gohtml View File

@@ -0,0 +1,9 @@
{{ define "title" }}Welcome{{ end }}
{{ define "content" }}
<h1>Welcome to "Blog!"</h1>

<p>
This is your index page. You can edit it and put whatever you want here.
By default, the blog index is also embedded on the website's index page.
</p>
{{ end }}

Loading…
Cancel
Save