Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
6a36c0ed76 |
8
Makefile
8
Makefile
|
@ -18,6 +18,14 @@ build:
|
|||
gofmt -w .
|
||||
go build $(LDFLAGS) -i -o bin/blog cmd/blog/main.go
|
||||
|
||||
# `make bindata` to make the bindata module.
|
||||
# `make bindata-dev` for debug mode module for editing files locally.
|
||||
.PHONY: bindata bindata-dev
|
||||
bindata:
|
||||
go-bindata -pkg root -prefix root/ -o src/root/bundle.go root/...
|
||||
bindata-dev:
|
||||
go-bindata -debug -pkg root -prefix root/ -o src/root/bundle.go root/...
|
||||
|
||||
# `make run` to run it in debug mode.
|
||||
.PHONY: run
|
||||
run:
|
||||
|
|
2
blog.go
2
blog.go
|
@ -29,7 +29,6 @@ import (
|
|||
"github.com/kirsle/blog/src/middleware"
|
||||
"github.com/kirsle/blog/src/middleware/auth"
|
||||
"github.com/kirsle/blog/src/models"
|
||||
"github.com/kirsle/blog/src/ratelimit"
|
||||
"github.com/kirsle/blog/src/render"
|
||||
"github.com/kirsle/blog/src/responses"
|
||||
"github.com/kirsle/blog/src/sessions"
|
||||
|
@ -124,7 +123,6 @@ func (b *Blog) Configure() {
|
|||
b.Cache = cache
|
||||
b.jsonDB.Cache = cache
|
||||
markdown.Cache = cache
|
||||
ratelimit.Cache = cache
|
||||
}
|
||||
}
|
||||
|
||||
|
|
27
go.mod
27
go.mod
|
@ -1,8 +1,9 @@
|
|||
module github.com/kirsle/blog
|
||||
|
||||
go 1.22
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/disintegration/imaging v1.6.0 // indirect
|
||||
github.com/edwvee/exiffix v0.0.0-20180602190213-b57537c92a6b
|
||||
github.com/garyburd/redigo v1.6.0
|
||||
github.com/google/uuid v1.1.1
|
||||
|
@ -13,24 +14,9 @@ require (
|
|||
github.com/kirsle/golog v0.0.0-20180411020913-51290b4f9292
|
||||
github.com/microcosm-cc/bluemonday v1.0.2
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470
|
||||
github.com/urfave/negroni v1.0.0
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/disintegration/imaging v1.6.0 // indirect
|
||||
github.com/gorilla/context v1.1.1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.10.0 // indirect
|
||||
github.com/russross/blackfriday v1.5.2 // indirect
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd // indirect
|
||||
github.com/sergi/go-diff v1.0.0 // indirect
|
||||
github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a // indirect
|
||||
github.com/shurcooL/go-goon v1.0.0 // indirect
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 // indirect
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181215221002-9d8641ddf2e1 // indirect
|
||||
github.com/shurcooL/octicon v0.0.0-20181222203144-9ff1a4cf27f4 // indirect
|
||||
|
@ -38,8 +24,7 @@ require (
|
|||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d // indirect
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e // indirect
|
||||
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e // indirect
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 // indirect
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 // indirect
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
github.com/urfave/negroni v1.0.0
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
)
|
||||
|
|
24
go.sum
24
go.sum
|
@ -1,6 +1,5 @@
|
|||
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 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
|
||||
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=
|
||||
|
@ -10,10 +9,7 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF
|
|||
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/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
||||
github.com/disintegration/imaging v1.6.0 h1:nVPXRUUQ36Z7MNf0O77UzgnOb1mkMMor7lmJMJXc/mA=
|
||||
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
||||
|
@ -22,14 +18,12 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
|
|||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/edwvee/exiffix v0.0.0-20180602190213-b57537c92a6b h1:6CBzNasH8+bKeFwr5Bt5JtALHLFN4iQp7sf4ShlP/ik=
|
||||
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 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
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 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
|
||||
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=
|
||||
|
@ -63,7 +57,6 @@ github.com/jinzhu/gorm v1.9.9 h1:Gc8bP20O+vroFUzZEXA1r7vNGQZGQ+RKgOnriuNF3ds=
|
|||
github.com/jinzhu/gorm v1.9.9/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY=
|
||||
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 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
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=
|
||||
|
@ -72,11 +65,6 @@ github.com/kirsle/golog v0.0.0-20180411020913-51290b4f9292/go.mod h1:0KaOvOX8s5Y
|
|||
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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
|
@ -91,9 +79,7 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
|
|||
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/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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=
|
||||
|
@ -103,8 +89,6 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
|
|||
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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd h1:CmH9+J6ZSsIjUK3dcGsnCnO41eRBOnY12zwkn5qVwgc=
|
||||
|
@ -113,10 +97,6 @@ github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
|||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470 h1:qb9IthCFBmROJ6YBS31BEMeSYjOscSiG+EO+JVNTz64=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a h1:ZHfoO7ZJhws9NU1kzZhStUnnVQiPtDe1PzpUnc6HirU=
|
||||
github.com/shurcooL/go v0.0.0-20230706063926-5fe729b41b3a/go.mod h1:DNrlr0AR9NsHD/aoc2pPeu4uSBZ/71yCHkR42yrzW3M=
|
||||
github.com/shurcooL/go-goon v1.0.0 h1:BCQPvxGkHHJ4WpBO4m/9FXbITVIsvAm/T66cCcCGI7E=
|
||||
github.com/shurcooL/go-goon v1.0.0/go.mod h1:2wTHMsGo7qnpmqA8ADYZtP4I1DD94JpXGQ3Dxq2YQ5w=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480 h1:KaKXZldeYH73dpQL+Nr38j1r5BgpAYQjYvENOUpIZDQ=
|
||||
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 h1:a6a6gGfBoO2ty+yyHNd7M6gkp37EwE3GIoycUnLo1Oo=
|
||||
|
@ -131,7 +111,6 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh
|
|||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8=
|
||||
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 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
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=
|
||||
|
@ -183,7 +162,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3
|
|||
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 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
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=
|
||||
|
@ -191,8 +169,6 @@ google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRn
|
|||
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/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
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=
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
|
@ -45,16 +44,6 @@ func (r *Redis) Get(key string) ([]byte, error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
// GetJSON gets a JSON value from a Redis key.
|
||||
func (r *Redis) GetJSON(key string, v any) error {
|
||||
val, err := r.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(val, v)
|
||||
}
|
||||
|
||||
// Set a key in Redis.
|
||||
func (r *Redis) Set(key string, v []byte, expires int) error {
|
||||
conn := r.pool.Get()
|
||||
|
@ -66,16 +55,6 @@ func (r *Redis) Set(key string, v []byte, expires int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// SetJSON sets a JSON encoded value into a Redis key.
|
||||
func (r *Redis) SetJSON(key string, v any, expires int) error {
|
||||
bin, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.Set(key, bin, expires)
|
||||
}
|
||||
|
||||
// Delete keys from Redis.
|
||||
func (r *Redis) Delete(key ...string) {
|
||||
conn := r.pool.Get()
|
||||
|
|
42
pages.go
42
pages.go
|
@ -3,7 +3,9 @@ package blog
|
|||
import (
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kirsle/blog/src/controllers/posts"
|
||||
|
@ -11,6 +13,7 @@ import (
|
|||
"github.com/kirsle/blog/src/markdown"
|
||||
"github.com/kirsle/blog/src/render"
|
||||
"github.com/kirsle/blog/src/responses"
|
||||
"github.com/kirsle/blog/src/root"
|
||||
)
|
||||
|
||||
// PageHandler is the catch-all route handler, for serving static web pages.
|
||||
|
@ -31,7 +34,7 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// Search for a file that matches their URL.
|
||||
filepath, err := render.ResolvePath(path)
|
||||
fp, err := render.ResolvePath(path)
|
||||
if err != nil {
|
||||
// See if it resolves as a blog entry.
|
||||
err = postctl.ViewPost(w, r, strings.TrimLeft(path, "/"))
|
||||
|
@ -43,19 +46,32 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// Is it a template file?
|
||||
if strings.HasSuffix(filepath.URI, ".gohtml") {
|
||||
render.Template(w, r, filepath.URI, nil)
|
||||
if strings.HasSuffix(fp.URI, ".gohtml") {
|
||||
render.Template(w, r, fp.URI, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Is it a Markdown file?
|
||||
if strings.HasSuffix(filepath.URI, ".md") || strings.HasSuffix(filepath.URI, ".markdown") {
|
||||
source, err := ioutil.ReadFile(filepath.Absolute)
|
||||
if strings.HasSuffix(fp.URI, ".md") || strings.HasSuffix(fp.URI, ".markdown") {
|
||||
var source []byte
|
||||
if len(fp.BindataKey) > 0 {
|
||||
data, err := root.Asset(fp.BindataKey)
|
||||
if err != nil {
|
||||
responses.Error(w, r, "Couldn't read bindata key: "+fp.BindataKey)
|
||||
return
|
||||
}
|
||||
|
||||
source = data
|
||||
} else {
|
||||
data, err := ioutil.ReadFile(fp.Absolute)
|
||||
if err != nil {
|
||||
responses.Error(w, r, "Couldn't read Markdown source!")
|
||||
return
|
||||
}
|
||||
|
||||
source = data
|
||||
}
|
||||
|
||||
// Render it to HTML and find out its title.
|
||||
body := string(source)
|
||||
html := markdown.RenderTrustedMarkdown(body)
|
||||
|
@ -64,10 +80,22 @@ func (b *Blog) PageHandler(w http.ResponseWriter, r *http.Request) {
|
|||
render.Template(w, r, ".markdown", map[string]interface{}{
|
||||
"Title": title,
|
||||
"HTML": template.HTML(html),
|
||||
"MarkdownPath": filepath.URI,
|
||||
"MarkdownPath": fp.URI,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, filepath.Absolute)
|
||||
// It's a regular static file we can serve directly.
|
||||
{
|
||||
// Check if we have bindata for it.
|
||||
if fp.BindataKey != "" {
|
||||
data, _ := root.Asset(fp.BindataKey)
|
||||
w.Header().Set("Content-Type", mime.TypeByExtension(filepath.Ext(fp.URI)))
|
||||
w.Write(data)
|
||||
return
|
||||
}
|
||||
|
||||
// Try the filesystem.
|
||||
http.ServeFile(w, r, fp.Absolute)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
{{ define "title" }}{{ .Data.Title }}{{ end }}
|
||||
{{ define "content" }}
|
||||
|
||||
<div class="markdown">
|
||||
{{ .Data.HTML }}
|
||||
</div>
|
||||
|
||||
{{ if and .CurrentUser.Admin .Editable }}
|
||||
<p class="mt-4">
|
||||
|
|
|
@ -5,11 +5,10 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/kirsle/blog/models/users"
|
||||
"github.com/kirsle/blog/src/forms"
|
||||
"github.com/kirsle/blog/src/log"
|
||||
"github.com/kirsle/blog/src/middleware/auth"
|
||||
"github.com/kirsle/blog/src/ratelimit"
|
||||
"github.com/kirsle/blog/models/users"
|
||||
"github.com/kirsle/blog/src/render"
|
||||
"github.com/kirsle/blog/src/responses"
|
||||
"github.com/kirsle/blog/src/sessions"
|
||||
|
@ -45,20 +44,6 @@ func loginHandler(w http.ResponseWriter, r *http.Request) {
|
|||
if err != nil {
|
||||
vars["Error"] = err
|
||||
} else {
|
||||
// Rate limit by guessed username.
|
||||
limiter := &ratelimit.Limiter{
|
||||
Namespace: "login",
|
||||
ID: form.Username,
|
||||
Limit: 10,
|
||||
Window: 3600,
|
||||
CooldownAt: 3,
|
||||
Cooldown: 10,
|
||||
}
|
||||
if err := limiter.Ping(); err != nil {
|
||||
responses.FlashAndReload(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Test the login.
|
||||
user, err := users.CheckAuth(form.Username, form.Password)
|
||||
if err != nil {
|
||||
|
|
|
@ -8,11 +8,12 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/kirsle/blog/models/comments"
|
||||
"github.com/kirsle/blog/models/settings"
|
||||
"github.com/kirsle/blog/src/log"
|
||||
"github.com/kirsle/blog/src/markdown"
|
||||
"github.com/kirsle/blog/src/render"
|
||||
"github.com/kirsle/blog/models/comments"
|
||||
"github.com/kirsle/blog/models/settings"
|
||||
"github.com/kirsle/blog/src/root"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
gomail "gopkg.in/gomail.v2"
|
||||
)
|
||||
|
@ -51,10 +52,21 @@ func SendEmail(email Email) {
|
|||
// Render the template to HTML.
|
||||
var html bytes.Buffer
|
||||
t := template.New(tmpl.Basename)
|
||||
t, err = template.ParseFiles(tmpl.Absolute)
|
||||
|
||||
// Load it from bindata or filesystem.
|
||||
if tmpl.BindataKey != "" {
|
||||
log.Debug("Parse %s from bindata", tmpl.BindataKey)
|
||||
asset, _ := root.Asset(tmpl.BindataKey)
|
||||
t, err = t.Parse(string(asset))
|
||||
} else {
|
||||
log.Debug("Parse %s from file", tmpl.Absolute)
|
||||
t, err = t.ParseFiles(tmpl.Absolute)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error("SendEmail: template parsing error: %s", err.Error())
|
||||
}
|
||||
|
||||
err = t.ExecuteTemplate(&html, tmpl.Basename, email)
|
||||
if err != nil {
|
||||
log.Error("SendEmail: template execution error: %s", err.Error())
|
||||
|
|
|
@ -24,11 +24,6 @@ var ageGateSuffixes = []string{
|
|||
".gif",
|
||||
".mp4",
|
||||
".webm",
|
||||
".ttf",
|
||||
".eot",
|
||||
".svg",
|
||||
".woff",
|
||||
".woff2",
|
||||
}
|
||||
|
||||
// AgeGate is a middleware generator that does age verification for NSFW sites.
|
||||
|
@ -54,12 +49,6 @@ func AgeGate(verifyHandler func(http.ResponseWriter, *http.Request)) negroni.Han
|
|||
}
|
||||
}
|
||||
|
||||
// POST requests are allowed.
|
||||
if r.Method == http.MethodPost {
|
||||
next(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// See if they've been cleared.
|
||||
session := sessions.Get(r)
|
||||
if val, _ := session.Values["age-ok"].(bool); !val {
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
package ratelimit
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/kirsle/blog/jsondb/caches/redis"
|
||||
)
|
||||
|
||||
// Limiter implements a Redis-backed rate limit for logins or otherwise.
|
||||
type Limiter struct {
|
||||
Namespace string // kind of rate limiter ("login")
|
||||
ID interface{} // unique ID of the resource being pinged (str or ints)
|
||||
Limit int // how many pings within the window period
|
||||
Window int // the window period/expiration of Redis key
|
||||
CooldownAt int // how many pings before the cooldown is enforced
|
||||
Cooldown int // time to wait between fails
|
||||
}
|
||||
|
||||
// The active Redis cache given by the webapp.
|
||||
var Cache *redis.Redis
|
||||
|
||||
// Redis object behind the rate limiter.
|
||||
type Data struct {
|
||||
Pings int
|
||||
NotBefore time.Time
|
||||
}
|
||||
|
||||
// Ping the rate limiter.
|
||||
func (l *Limiter) Ping() error {
|
||||
if Cache == nil {
|
||||
return errors.New("redis not ready")
|
||||
}
|
||||
|
||||
var (
|
||||
key = l.Key()
|
||||
now = time.Now()
|
||||
)
|
||||
|
||||
// Get stored data from Redis if any.
|
||||
var data Data
|
||||
Cache.GetJSON(key, &data)
|
||||
|
||||
// Are we cooling down?
|
||||
if now.Before(data.NotBefore) {
|
||||
return fmt.Errorf(
|
||||
"You are doing that too often.",
|
||||
)
|
||||
}
|
||||
|
||||
// Increment the ping count.
|
||||
data.Pings++
|
||||
|
||||
// Have we hit the wall?
|
||||
if data.Pings >= l.Limit {
|
||||
return fmt.Errorf(
|
||||
"You have hit the rate limit; come back later.",
|
||||
)
|
||||
}
|
||||
|
||||
// Are we throttled?
|
||||
if l.CooldownAt > 0 && data.Pings > l.CooldownAt {
|
||||
data.NotBefore = now.Add(time.Duration(l.Cooldown) * time.Second)
|
||||
if err := Cache.SetJSON(key, data, l.Window); err != nil {
|
||||
return fmt.Errorf("Couldn't set Redis key for rate limiter: %s", err)
|
||||
}
|
||||
return fmt.Errorf(
|
||||
"Please wait %ds before trying again. You have %d more attempt(s) remaining before you will be locked "+
|
||||
"out for %ds.",
|
||||
l.Cooldown,
|
||||
l.Limit-data.Pings,
|
||||
l.Window,
|
||||
)
|
||||
}
|
||||
|
||||
// Save their ping count to Redis.
|
||||
if err := Cache.SetJSON(key, data, l.Window); err != nil {
|
||||
return fmt.Errorf("Couldn't set Redis key for rate limiter: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Clear the rate limiter, cleaning up the Redis key (e.g., after successful login).
|
||||
func (l *Limiter) Clear() {
|
||||
Cache.Delete(l.Key())
|
||||
}
|
||||
|
||||
// Key formats the Redis key.
|
||||
func (l *Limiter) Key() string {
|
||||
var str string
|
||||
switch t := l.ID.(type) {
|
||||
case int:
|
||||
str = fmt.Sprintf("%d", t)
|
||||
case uint64:
|
||||
str = fmt.Sprintf("%d", t)
|
||||
case int64:
|
||||
str = fmt.Sprintf("%d", t)
|
||||
case uint32:
|
||||
str = fmt.Sprintf("%d", t)
|
||||
case int32:
|
||||
str = fmt.Sprintf("%d", t)
|
||||
default:
|
||||
str = fmt.Sprintf("%s", t)
|
||||
}
|
||||
return fmt.Sprintf("rlimit/%s/%s", l.Namespace, str)
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/kirsle/blog/src/log"
|
||||
"github.com/kirsle/blog/src/root"
|
||||
)
|
||||
|
||||
// Blog configuration bindings.
|
||||
|
@ -37,6 +38,10 @@ type Filepath struct {
|
|||
Basename string
|
||||
Relative string // Relative path including document root (i.e. "root/about.html")
|
||||
Absolute string // Absolute path on disk (i.e. "/opt/blog/root/about.html")
|
||||
|
||||
// If file was resolved to embedded bindata, this is the bindata key name.
|
||||
// Zero value means it resolved to a file on filesystem.
|
||||
BindataKey string
|
||||
}
|
||||
|
||||
func (f Filepath) String() string {
|
||||
|
@ -55,39 +60,78 @@ func ResolvePath(path string) (Filepath, error) {
|
|||
|
||||
// If you need to debug this function, edit this block.
|
||||
debug := func(tmpl string, args ...interface{}) {
|
||||
if false {
|
||||
if true { // edit this to enable
|
||||
log.Debug(tmpl, args...)
|
||||
}
|
||||
}
|
||||
|
||||
debug("Resolving filepath for URI: %s", path)
|
||||
for _, root := range []string{*UserRoot, *DocumentRoot} {
|
||||
if len(root) == 0 {
|
||||
continue
|
||||
}
|
||||
debug("ResolvePath(%s) called", path)
|
||||
|
||||
if len(*UserRoot) > 0 {
|
||||
debug("1. Resolving filepath for URI in user root: %s", path)
|
||||
|
||||
// Resolve the file path.
|
||||
relPath := filepath.Join(root, path)
|
||||
relPath := filepath.Join(*UserRoot, path)
|
||||
absPath, err := filepath.Abs(relPath)
|
||||
basename := filepath.Base(relPath)
|
||||
if err != nil {
|
||||
log.Error("%v", err)
|
||||
}
|
||||
|
||||
debug("Expected filepath: %s", absPath)
|
||||
debug(" Expected filepath: %s", absPath)
|
||||
|
||||
// Found an exact hit?
|
||||
if stat, err := os.Stat(absPath); !os.IsNotExist(err) && !stat.IsDir() {
|
||||
debug("Exact filepath found: %s", absPath)
|
||||
return Filepath{path, basename, relPath, absPath}, nil
|
||||
debug(" + Exact filepath found: %s", absPath)
|
||||
return Filepath{
|
||||
URI: path,
|
||||
Basename: basename,
|
||||
Relative: relPath,
|
||||
Absolute: absPath,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Try some supported suffixes.
|
||||
for _, suffix := range hiddenSuffixes {
|
||||
test := absPath + suffix
|
||||
if stat, err := os.Stat(test); !os.IsNotExist(err) && !stat.IsDir() {
|
||||
debug("Filepath found via suffix %s: %s", suffix, test)
|
||||
return Filepath{path + suffix, basename + suffix, relPath + suffix, test}, nil
|
||||
debug(" + Filepath found via suffix %s: %s", suffix, test)
|
||||
return Filepath{
|
||||
URI: path + suffix,
|
||||
Basename: basename + suffix,
|
||||
Relative: relPath + suffix,
|
||||
Absolute: test,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug("2. Not found in filesystem, checking bindata for: %s", path)
|
||||
{
|
||||
// Exact hit?
|
||||
if _, err := root.Asset(path); err == nil {
|
||||
debug(" Found in bindata as: %s", path)
|
||||
return Filepath{
|
||||
URI: path,
|
||||
Basename: filepath.Base(path),
|
||||
Relative: path,
|
||||
Absolute: path,
|
||||
BindataKey: path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Try some supported suffixes.
|
||||
for _, suffix := range hiddenSuffixes {
|
||||
test := path + suffix
|
||||
if _, err := root.Asset(test); err == nil {
|
||||
debug(" Filepath found via suffix %s: %s", suffix, test)
|
||||
return Filepath{
|
||||
URI: test,
|
||||
Basename: filepath.Base(test),
|
||||
Relative: test,
|
||||
Absolute: test,
|
||||
BindataKey: test,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,13 +8,14 @@ import (
|
|||
"time"
|
||||
|
||||
gorilla "github.com/gorilla/sessions"
|
||||
"github.com/kirsle/blog/models/settings"
|
||||
"github.com/kirsle/blog/models/users"
|
||||
"github.com/kirsle/blog/src/log"
|
||||
"github.com/kirsle/blog/src/middleware"
|
||||
"github.com/kirsle/blog/src/middleware/auth"
|
||||
"github.com/kirsle/blog/src/root"
|
||||
"github.com/kirsle/blog/src/sessions"
|
||||
"github.com/kirsle/blog/src/types"
|
||||
"github.com/kirsle/blog/models/settings"
|
||||
"github.com/kirsle/blog/models/users"
|
||||
)
|
||||
|
||||
// Vars is an interface to implement by the templates to pass their own custom
|
||||
|
@ -113,19 +114,19 @@ func Template(w io.Writer, r *http.Request, path string, data interface{}) error
|
|||
// Get the layout template.
|
||||
if !isPartial {
|
||||
templateName = "layout"
|
||||
layout, err = ResolvePath(".layout")
|
||||
layout, err = ResolvePath(".layout.gohtml")
|
||||
if err != nil {
|
||||
log.Error("RenderTemplate(%s): layout template not found", path)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
templateName = filepath.Basename
|
||||
templateName = filepath.Absolute
|
||||
}
|
||||
|
||||
// The comment entry partial.
|
||||
commentEntry, err := ResolvePath("comments/entry.partial")
|
||||
commentEntry, err := ResolvePath("comments/entry.partial.gohtml")
|
||||
if err != nil {
|
||||
log.Error("RenderTemplate(%s): comments/entry.partial not found")
|
||||
log.Error("RenderTemplate: comments/entry.partial not found")
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -135,17 +136,28 @@ func Template(w io.Writer, r *http.Request, path string, data interface{}) error
|
|||
// and allows the filepath template to set the page title.
|
||||
var templates []string
|
||||
if !isPartial {
|
||||
templates = append(templates, layout.Absolute)
|
||||
templates = append(templates, layout.Absolute, commentEntry.Absolute, filepath.Absolute)
|
||||
}
|
||||
t, err = t.ParseFiles(append(templates, commentEntry.Absolute, filepath.Absolute)...)
|
||||
|
||||
for _, filename := range templates {
|
||||
|
||||
if asset, err2 := root.Asset(filename); err2 == nil {
|
||||
log.Debug("Parse %s from bindata", filename)
|
||||
t, err = t.Parse(string(asset))
|
||||
} else {
|
||||
log.Debug("Parse %s from file", filename)
|
||||
t, err = t.ParseFiles(filename)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = t.ExecuteTemplate(w, templateName, v)
|
||||
if err != nil {
|
||||
log.Error("Template parsing error: %s", err)
|
||||
log.Error("Template parsing error(tmpl name: %s; ): %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user