diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d6e118 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +pkg/bundled/ +*.sqlite diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1621a8a --- /dev/null +++ b/Makefile @@ -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 diff --git a/cmd/gophertype/main.go b/cmd/gophertype/main.go new file mode 100644 index 0000000..a3f44bb --- /dev/null +++ b/cmd/gophertype/main.go @@ -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) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9508a4a --- /dev/null +++ b/go.mod @@ -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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..22903f6 --- /dev/null +++ b/go.sum @@ -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= diff --git a/pkg/app.go b/pkg/app.go new file mode 100644 index 0000000..facd8fc --- /dev/null +++ b/pkg/app.go @@ -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) +} diff --git a/pkg/controllers/index.go b/pkg/controllers/index.go new file mode 100644 index 0000000..e64cf00 --- /dev/null +++ b/pkg/controllers/index.go @@ -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")) + }, + }) +} diff --git a/pkg/controllers/initial_setup.go b/pkg/controllers/initial_setup.go new file mode 100644 index 0000000..d7174c4 --- /dev/null +++ b/pkg/controllers/initial_setup.go @@ -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) + }, + }) + +} diff --git a/pkg/controllers/static_files.go b/pkg/controllers/static_files.go new file mode 100644 index 0000000..cfe80e1 --- /dev/null +++ b/pkg/controllers/static_files.go @@ -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) +} diff --git a/pkg/glue/controller.go b/pkg/glue/controller.go new file mode 100644 index 0000000..2dc73c4 --- /dev/null +++ b/pkg/glue/controller.go @@ -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]) +} diff --git a/pkg/middleware/example.go b/pkg/middleware/example.go new file mode 100644 index 0000000..9aa60b6 --- /dev/null +++ b/pkg/middleware/example.go @@ -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) +} diff --git a/pkg/models/models.go b/pkg/models/models.go new file mode 100644 index 0000000..72e3d9b --- /dev/null +++ b/pkg/models/models.go @@ -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{}) +} diff --git a/pkg/models/users.go b/pkg/models/users.go new file mode 100644 index 0000000..0ecded1 --- /dev/null +++ b/pkg/models/users.go @@ -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"` +} diff --git a/pkg/responses/errors.go b/pkg/responses/errors.go new file mode 100644 index 0000000..9805600 --- /dev/null +++ b/pkg/responses/errors.go @@ -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)) +} diff --git a/pkg/responses/static_file.go b/pkg/responses/static_file.go new file mode 100644 index 0000000..ed85b7f --- /dev/null +++ b/pkg/responses/static_file.go @@ -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) +} diff --git a/pkg/responses/templates.go b/pkg/responses/templates.go new file mode 100644 index 0000000..f8e9dea --- /dev/null +++ b/pkg/responses/templates.go @@ -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 +} diff --git a/pkg/routes.go b/pkg/routes.go new file mode 100644 index 0000000..3ee330e --- /dev/null +++ b/pkg/routes.go @@ -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 +} diff --git a/pkg/version.go b/pkg/version.go new file mode 100644 index 0000000..e503977 --- /dev/null +++ b/pkg/version.go @@ -0,0 +1,3 @@ +package gophertype + +const Version = "0.0.1" diff --git a/pvt-www/.layout.gohtml b/pvt-www/.layout.gohtml new file mode 100644 index 0000000..8b9dc5d --- /dev/null +++ b/pvt-www/.layout.gohtml @@ -0,0 +1,198 @@ +{{ define "title" }}{{ end }} +{{ define "scripts" }}{{ end }} + +{{ define "layout" }} + + +
+ + + +{{ .Description }}
+Hello, world!
++ Logged in as: {{ .CurrentUser.Username }} +
+ ++ 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. +
+{{ end }}