mirror of
https://github.com/mainnika/nikita-tokarch-uk.git
synced 2026-05-25 01:03:35 +00:00
Restructure the go package
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Backend contains backend connection-specific configuration
|
||||
type Backend struct {
|
||||
Addr string `mapstructure:"addr"`
|
||||
Secured bool `mapstructure:"secured"`
|
||||
Headers map[string]string `mapstructure:"headers"`
|
||||
}
|
||||
|
||||
// Content contains content-specific configuration
|
||||
type Content struct {
|
||||
Backend Backend `mapstructure:"backend"`
|
||||
Key string `mapstructure:"key"`
|
||||
Pinned string `mapstructure:"pinned"`
|
||||
PostsPerPage int `mapstructure:"postsPerPage"`
|
||||
}
|
||||
|
||||
// Config contains application configuration
|
||||
type Config struct {
|
||||
Verbose string `mapstructure:"verbose"`
|
||||
Base string `mapstructure:"base"`
|
||||
Addr string `mapstructure:"addr"`
|
||||
Unix string `mapstructure:"unix"`
|
||||
Content Content `mapstructure:"content"`
|
||||
}
|
||||
|
||||
// initialize default values on app-start
|
||||
func init() {
|
||||
|
||||
pflag.BoolP("version", "V", false, "get application version")
|
||||
pflag.BoolP("verbose", "v", false, "enable verbose logging")
|
||||
pflag.StringP("config", "c", string("frontend.yaml"), "config file path")
|
||||
|
||||
pflag.String("addr", "127.0.0.1:8000", "tcp addr to listen")
|
||||
pflag.String("unix", "", "unix socket path to listen")
|
||||
pflag.String("base", "", "http URI prefix")
|
||||
|
||||
pflag.StringToString("content.backend.headers", nil, "map of additional headers to send")
|
||||
pflag.String("content.backend.addr", "demo.ghost.io:443", "ghost backend addr")
|
||||
pflag.Bool("content.backend.secured", true, "is ghost backend secured")
|
||||
pflag.String("content.key", "22444f78447824223cefc48062", "ghost content api key")
|
||||
pflag.String("content.pinned", "contact", "pinned page slug")
|
||||
pflag.Int("content.postsPerPage", 5, "amount of posts per page")
|
||||
|
||||
pflag.Parse()
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.AutomaticEnv()
|
||||
viper.BindPFlags(pflag.CommandLine)
|
||||
|
||||
if viper.GetBool("verbose") {
|
||||
logrus.SetLevel(logrus.DebugLevel)
|
||||
logrus.Debug("Verbose mode")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package content
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/ghost/data"
|
||||
)
|
||||
|
||||
// Blog content data
|
||||
type Blog struct {
|
||||
_ interface{} `template:"blog.go.tmpl"`
|
||||
data.Meta
|
||||
Pinned []data.Post
|
||||
Posts []data.Post
|
||||
}
|
||||
|
||||
// Title returns blog content title
|
||||
func (i Blog) Title() string {
|
||||
return fmt.Sprintf("... %d of %d", i.Meta.Pagination.Page, i.Meta.Pagination.Pages)
|
||||
}
|
||||
|
||||
// Description returns blog content description
|
||||
func (i Blog) Description() string {
|
||||
return "TODO:"
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package content
|
||||
|
||||
// Error content data
|
||||
type Error struct {
|
||||
_ interface{} `template:"error.go.tmpl"`
|
||||
|
||||
Message string
|
||||
}
|
||||
|
||||
// Title returns error title
|
||||
func (e Error) Title() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// Description returns error description
|
||||
func (e Error) Description() string {
|
||||
return e.Message
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package content
|
||||
|
||||
import "code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/ghost/data"
|
||||
|
||||
// Index content data
|
||||
type Index struct {
|
||||
_ interface{} `template:"index.go.tmpl"`
|
||||
data.Meta
|
||||
Pinned []data.Post
|
||||
Posts []data.Post
|
||||
}
|
||||
|
||||
// Title returns index title
|
||||
func (i Index) Title() string {
|
||||
|
||||
if len(i.Pinned) > 0 {
|
||||
return i.Pinned[0].Title
|
||||
}
|
||||
if len(i.Posts) > 0 {
|
||||
return i.Posts[0].Title
|
||||
}
|
||||
|
||||
return "UNKNOWN:"
|
||||
}
|
||||
|
||||
// Description returns index description
|
||||
func (i Index) Description() string {
|
||||
return "TODO:"
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package data
|
||||
|
||||
//go:generate $GOPATH/bin/easyjson -pkg -no_std_marshalers
|
||||
|
||||
import "html/template"
|
||||
|
||||
// Pages are ghost pages data
|
||||
//easyjson:json
|
||||
type Pages struct {
|
||||
Pages []Post `json:"pages"`
|
||||
Meta Meta `json:"meta"`
|
||||
}
|
||||
|
||||
// Post contains ghost post data
|
||||
//easyjson:json
|
||||
type Post struct {
|
||||
ID string `json:"id"`
|
||||
UUID string `json:"uuid"`
|
||||
Title string `json:"title"`
|
||||
HTML template.HTML `json:"html"`
|
||||
FImage template.URL `json:"feature_image"`
|
||||
}
|
||||
|
||||
// Meta contains ghost result metadata
|
||||
//easyjson:json
|
||||
type Meta struct {
|
||||
Pagination Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
// Pagination contains ghost pagination data
|
||||
//easyjson:json
|
||||
type Pagination struct {
|
||||
Page int `json:"page"`
|
||||
Limit int `json:"limit"`
|
||||
Pages int `json:"pages"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
||||
// Posts are ghost posts data
|
||||
//easyjson:json
|
||||
type Posts struct {
|
||||
Posts []Post `json:"posts"`
|
||||
Meta Meta `json:"meta"`
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
// Code generated by easyjson for marshaling/unmarshaling. DO NOT EDIT.
|
||||
|
||||
package data
|
||||
|
||||
import (
|
||||
json "encoding/json"
|
||||
easyjson "github.com/mailru/easyjson"
|
||||
jlexer "github.com/mailru/easyjson/jlexer"
|
||||
jwriter "github.com/mailru/easyjson/jwriter"
|
||||
template "html/template"
|
||||
)
|
||||
|
||||
// suppress unused package warning
|
||||
var (
|
||||
_ *json.RawMessage
|
||||
_ *jlexer.Lexer
|
||||
_ *jwriter.Writer
|
||||
_ easyjson.Marshaler
|
||||
)
|
||||
|
||||
func easyjson794297d0DecodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData(in *jlexer.Lexer, out *Posts) {
|
||||
isTopLevel := in.IsStart()
|
||||
if in.IsNull() {
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeFieldName(false)
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
in.WantComma()
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "posts":
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
out.Posts = nil
|
||||
} else {
|
||||
in.Delim('[')
|
||||
if out.Posts == nil {
|
||||
if !in.IsDelim(']') {
|
||||
out.Posts = make([]Post, 0, 0)
|
||||
} else {
|
||||
out.Posts = []Post{}
|
||||
}
|
||||
} else {
|
||||
out.Posts = (out.Posts)[:0]
|
||||
}
|
||||
for !in.IsDelim(']') {
|
||||
var v1 Post
|
||||
(v1).UnmarshalEasyJSON(in)
|
||||
out.Posts = append(out.Posts, v1)
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim(']')
|
||||
}
|
||||
case "meta":
|
||||
(out.Meta).UnmarshalEasyJSON(in)
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim('}')
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
}
|
||||
func easyjson794297d0EncodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData(out *jwriter.Writer, in Posts) {
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
{
|
||||
const prefix string = ",\"posts\":"
|
||||
out.RawString(prefix[1:])
|
||||
if in.Posts == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
|
||||
out.RawString("null")
|
||||
} else {
|
||||
out.RawByte('[')
|
||||
for v2, v3 := range in.Posts {
|
||||
if v2 > 0 {
|
||||
out.RawByte(',')
|
||||
}
|
||||
(v3).MarshalEasyJSON(out)
|
||||
}
|
||||
out.RawByte(']')
|
||||
}
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"meta\":"
|
||||
out.RawString(prefix)
|
||||
(in.Meta).MarshalEasyJSON(out)
|
||||
}
|
||||
out.RawByte('}')
|
||||
}
|
||||
|
||||
// MarshalEasyJSON supports easyjson.Marshaler interface
|
||||
func (v Posts) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
easyjson794297d0EncodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData(w, v)
|
||||
}
|
||||
|
||||
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
|
||||
func (v *Posts) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
easyjson794297d0DecodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData(l, v)
|
||||
}
|
||||
func easyjson794297d0DecodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData1(in *jlexer.Lexer, out *Post) {
|
||||
isTopLevel := in.IsStart()
|
||||
if in.IsNull() {
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeFieldName(false)
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
in.WantComma()
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "id":
|
||||
out.ID = string(in.String())
|
||||
case "uuid":
|
||||
out.UUID = string(in.String())
|
||||
case "title":
|
||||
out.Title = string(in.String())
|
||||
case "html":
|
||||
out.HTML = template.HTML(in.String())
|
||||
case "feature_image":
|
||||
out.FImage = template.URL(in.String())
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim('}')
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
}
|
||||
func easyjson794297d0EncodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData1(out *jwriter.Writer, in Post) {
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
{
|
||||
const prefix string = ",\"id\":"
|
||||
out.RawString(prefix[1:])
|
||||
out.String(string(in.ID))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"uuid\":"
|
||||
out.RawString(prefix)
|
||||
out.String(string(in.UUID))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"title\":"
|
||||
out.RawString(prefix)
|
||||
out.String(string(in.Title))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"html\":"
|
||||
out.RawString(prefix)
|
||||
out.String(string(in.HTML))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"feature_image\":"
|
||||
out.RawString(prefix)
|
||||
out.String(string(in.FImage))
|
||||
}
|
||||
out.RawByte('}')
|
||||
}
|
||||
|
||||
// MarshalEasyJSON supports easyjson.Marshaler interface
|
||||
func (v Post) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
easyjson794297d0EncodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData1(w, v)
|
||||
}
|
||||
|
||||
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
|
||||
func (v *Post) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
easyjson794297d0DecodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData1(l, v)
|
||||
}
|
||||
func easyjson794297d0DecodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData2(in *jlexer.Lexer, out *Pagination) {
|
||||
isTopLevel := in.IsStart()
|
||||
if in.IsNull() {
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeFieldName(false)
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
in.WantComma()
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "page":
|
||||
out.Page = int(in.Int())
|
||||
case "limit":
|
||||
out.Limit = int(in.Int())
|
||||
case "pages":
|
||||
out.Pages = int(in.Int())
|
||||
case "total":
|
||||
out.Total = int(in.Int())
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim('}')
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
}
|
||||
func easyjson794297d0EncodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData2(out *jwriter.Writer, in Pagination) {
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
{
|
||||
const prefix string = ",\"page\":"
|
||||
out.RawString(prefix[1:])
|
||||
out.Int(int(in.Page))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"limit\":"
|
||||
out.RawString(prefix)
|
||||
out.Int(int(in.Limit))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"pages\":"
|
||||
out.RawString(prefix)
|
||||
out.Int(int(in.Pages))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"total\":"
|
||||
out.RawString(prefix)
|
||||
out.Int(int(in.Total))
|
||||
}
|
||||
out.RawByte('}')
|
||||
}
|
||||
|
||||
// MarshalEasyJSON supports easyjson.Marshaler interface
|
||||
func (v Pagination) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
easyjson794297d0EncodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData2(w, v)
|
||||
}
|
||||
|
||||
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
|
||||
func (v *Pagination) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
easyjson794297d0DecodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData2(l, v)
|
||||
}
|
||||
func easyjson794297d0DecodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData3(in *jlexer.Lexer, out *Pages) {
|
||||
isTopLevel := in.IsStart()
|
||||
if in.IsNull() {
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeFieldName(false)
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
in.WantComma()
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "pages":
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
out.Pages = nil
|
||||
} else {
|
||||
in.Delim('[')
|
||||
if out.Pages == nil {
|
||||
if !in.IsDelim(']') {
|
||||
out.Pages = make([]Post, 0, 0)
|
||||
} else {
|
||||
out.Pages = []Post{}
|
||||
}
|
||||
} else {
|
||||
out.Pages = (out.Pages)[:0]
|
||||
}
|
||||
for !in.IsDelim(']') {
|
||||
var v4 Post
|
||||
(v4).UnmarshalEasyJSON(in)
|
||||
out.Pages = append(out.Pages, v4)
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim(']')
|
||||
}
|
||||
case "meta":
|
||||
(out.Meta).UnmarshalEasyJSON(in)
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim('}')
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
}
|
||||
func easyjson794297d0EncodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData3(out *jwriter.Writer, in Pages) {
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
{
|
||||
const prefix string = ",\"pages\":"
|
||||
out.RawString(prefix[1:])
|
||||
if in.Pages == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
|
||||
out.RawString("null")
|
||||
} else {
|
||||
out.RawByte('[')
|
||||
for v5, v6 := range in.Pages {
|
||||
if v5 > 0 {
|
||||
out.RawByte(',')
|
||||
}
|
||||
(v6).MarshalEasyJSON(out)
|
||||
}
|
||||
out.RawByte(']')
|
||||
}
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"meta\":"
|
||||
out.RawString(prefix)
|
||||
(in.Meta).MarshalEasyJSON(out)
|
||||
}
|
||||
out.RawByte('}')
|
||||
}
|
||||
|
||||
// MarshalEasyJSON supports easyjson.Marshaler interface
|
||||
func (v Pages) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
easyjson794297d0EncodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData3(w, v)
|
||||
}
|
||||
|
||||
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
|
||||
func (v *Pages) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
easyjson794297d0DecodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData3(l, v)
|
||||
}
|
||||
func easyjson794297d0DecodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData4(in *jlexer.Lexer, out *Meta) {
|
||||
isTopLevel := in.IsStart()
|
||||
if in.IsNull() {
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeFieldName(false)
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
in.WantComma()
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "pagination":
|
||||
(out.Pagination).UnmarshalEasyJSON(in)
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim('}')
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
}
|
||||
func easyjson794297d0EncodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData4(out *jwriter.Writer, in Meta) {
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
{
|
||||
const prefix string = ",\"pagination\":"
|
||||
out.RawString(prefix[1:])
|
||||
(in.Pagination).MarshalEasyJSON(out)
|
||||
}
|
||||
out.RawByte('}')
|
||||
}
|
||||
|
||||
// MarshalEasyJSON supports easyjson.Marshaler interface
|
||||
func (v Meta) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
easyjson794297d0EncodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData4(w, v)
|
||||
}
|
||||
|
||||
// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
|
||||
func (v *Meta) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
easyjson794297d0DecodeCodeTokarchUkMainnikaNikitaTokarchUkFrontendGhostData4(l, v)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package ghost
|
||||
|
||||
import (
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/ghost/data"
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/ghost/params"
|
||||
)
|
||||
|
||||
// Client is the ghost backend client
|
||||
type Client interface {
|
||||
// GetPosts returns blog posts according to query params
|
||||
GetPosts(queryParams ...params.Modifier) (posts *data.Posts, err error)
|
||||
// GetPostBySlug returns a single post by its slug title and query params
|
||||
GetPostBySlug(slug string, queryParams ...params.Modifier) (posts *data.Posts, err error)
|
||||
// GetPageBySlug returns a single page by its slug title and query params
|
||||
GetPageBySlug(slug string, queryParams ...params.Modifier) (pages *data.Pages, err error)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package params
|
||||
|
||||
// Params are generics query argument
|
||||
type Params struct {
|
||||
Limit int
|
||||
Page int
|
||||
}
|
||||
|
||||
// Modifier function takes params and makes some changes
|
||||
type Modifier func(params Params) Params
|
||||
|
||||
// Modifiers is a list of modifier
|
||||
type Modifiers []Modifier
|
||||
|
||||
// Apply function modifies params
|
||||
func (ms Modifiers) Apply(params Params) Params {
|
||||
|
||||
for _, m := range ms {
|
||||
params = m(params)
|
||||
}
|
||||
|
||||
return params
|
||||
}
|
||||
|
||||
// WithLimit modifier setups the limit
|
||||
func WithLimit(limit int) Modifier {
|
||||
return func(params Params) Params {
|
||||
params.Limit = limit
|
||||
return params
|
||||
}
|
||||
}
|
||||
|
||||
// WithPage modifier setups the page
|
||||
func WithPage(page int) Modifier {
|
||||
return func(params Params) Params {
|
||||
params.Page = page
|
||||
return params
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/ghost"
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/ghost/data"
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/ghost/params"
|
||||
)
|
||||
|
||||
var _ ghost.Client = (*HTTPClient)(nil)
|
||||
|
||||
// Ghost content data URIs:
|
||||
const (
|
||||
ghostAPIPrefix = "/ghost/api/v3/"
|
||||
ghostAPIGetPosts = ghostAPIPrefix + "content/posts/"
|
||||
ghostAPIGetPostBySlug = ghostAPIPrefix + "content/posts/slug/%s/"
|
||||
ghostAPIGetPageBySlug = ghostAPIPrefix + "content/pages/slug/%s/"
|
||||
)
|
||||
|
||||
// HTTPClient implements the ghost http client
|
||||
type HTTPClient struct {
|
||||
QueryTimeout time.Duration
|
||||
ContentKey string
|
||||
Addr string
|
||||
Secured bool
|
||||
Headers map[string]string
|
||||
|
||||
client *fasthttp.HostClient
|
||||
|
||||
setupClientOnce sync.Once
|
||||
}
|
||||
|
||||
// setupClient creates the default http client
|
||||
func (g *HTTPClient) setupClient() {
|
||||
|
||||
g.client = &fasthttp.HostClient{
|
||||
Addr: g.Addr,
|
||||
IsTLS: g.Secured,
|
||||
|
||||
DisableHeaderNamesNormalizing: true,
|
||||
DisablePathNormalizing: true,
|
||||
}
|
||||
}
|
||||
|
||||
// doQuery does the method and unmarshals the result into the easyjson Unmarshaler
|
||||
func (g *HTTPClient) doQuery(path string, v easyjson.Unmarshaler, params params.Params) (err error) {
|
||||
|
||||
g.setupClientOnce.Do(g.setupClient)
|
||||
|
||||
req := fasthttp.AcquireRequest()
|
||||
res := fasthttp.AcquireResponse()
|
||||
defer func() {
|
||||
fasthttp.ReleaseResponse(res)
|
||||
fasthttp.ReleaseRequest(req)
|
||||
}()
|
||||
|
||||
g.setupRequest(path, req)
|
||||
g.applyParams(params, req)
|
||||
|
||||
err = g.client.DoTimeout(req, res, g.QueryTimeout)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if res.StatusCode() != fasthttp.StatusOK {
|
||||
return fmt.Errorf("non OK status code: %d", res.StatusCode())
|
||||
}
|
||||
|
||||
resBytes := res.Body()
|
||||
if resBytes == nil && v == nil {
|
||||
return fmt.Errorf("nothing to unmarshal")
|
||||
}
|
||||
if resBytes == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = easyjson.Unmarshal(resBytes, v)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// setupRequest does the necessary initial configuration to the http request
|
||||
func (g *HTTPClient) setupRequest(path string, req *fasthttp.Request) {
|
||||
|
||||
uri := req.URI()
|
||||
|
||||
scheme := "http"
|
||||
if g.Secured {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
uri.SetHost(g.Addr)
|
||||
uri.SetPath(path)
|
||||
uri.SetScheme(scheme)
|
||||
|
||||
uri.QueryArgs().Add("key", g.ContentKey)
|
||||
|
||||
for hKey, hValue := range g.Headers {
|
||||
req.Header.Add(hKey, hValue)
|
||||
}
|
||||
}
|
||||
|
||||
// applyParams function additionally configure the http request using params
|
||||
func (g *HTTPClient) applyParams(p params.Params, req *fasthttp.Request) (err error) {
|
||||
|
||||
uri := req.URI()
|
||||
|
||||
limit := p.Limit
|
||||
if limit > 0 {
|
||||
uri.QueryArgs().Add("limit", strconv.Itoa(limit))
|
||||
}
|
||||
|
||||
page := p.Page
|
||||
if page > 1 {
|
||||
uri.QueryArgs().Add("page", strconv.Itoa(page))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetPageBySlug returns the only one page using slug filter
|
||||
func (g *HTTPClient) GetPageBySlug(slug string, queryModifiers ...params.Modifier) (pages *data.Pages, err error) {
|
||||
|
||||
pages = &data.Pages{}
|
||||
defaultParams := params.Params{}
|
||||
method := fmt.Sprintf(ghostAPIGetPageBySlug, slug)
|
||||
|
||||
err = g.doQuery(method, pages, defaultParams)
|
||||
if err != nil {
|
||||
pages = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetPosts returns posts
|
||||
func (g *HTTPClient) GetPosts(queryModifiers ...params.Modifier) (posts *data.Posts, err error) {
|
||||
|
||||
posts = &data.Posts{}
|
||||
defaultParams := params.Params{}
|
||||
combinedParams := params.Modifiers(queryModifiers).Apply(defaultParams)
|
||||
|
||||
err = g.doQuery(ghostAPIGetPosts, posts, combinedParams)
|
||||
if err != nil {
|
||||
posts = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetPostBySlug returns the only one post using slug filter
|
||||
func (g *HTTPClient) GetPostBySlug(slug string, queryModifiers ...params.Modifier) (posts *data.Posts, err error) {
|
||||
|
||||
posts = &data.Posts{}
|
||||
defaultParams := params.Params{}
|
||||
combinedParams := params.Modifiers(queryModifiers).Apply(defaultParams)
|
||||
method := fmt.Sprintf(ghostAPIGetPostBySlug, slug)
|
||||
|
||||
err = g.doQuery(method, posts, combinedParams)
|
||||
if err != nil {
|
||||
posts = nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
routing "github.com/jackwhelpton/fasthttp-routing/v2"
|
||||
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/content"
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/ghost/params"
|
||||
)
|
||||
|
||||
// blog handler renders blog data
|
||||
func (r *Routes) blog(c *routing.Context) (err error) {
|
||||
|
||||
postsPerPage := r.ContentConfig.PostsPerPage
|
||||
currentPage := c.QueryArgs().GetUintOrZero("page")
|
||||
|
||||
latestPosts, err := r.GhostClient.GetPosts(
|
||||
params.WithLimit(postsPerPage),
|
||||
params.WithPage(currentPage),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
blogContent := content.Blog{
|
||||
Meta: latestPosts.Meta,
|
||||
Posts: latestPosts.Posts,
|
||||
}
|
||||
|
||||
return c.Write(blogContent)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
routing "github.com/jackwhelpton/fasthttp-routing/v2"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/content"
|
||||
)
|
||||
|
||||
// errorNotFound renders http error-404 template
|
||||
func (r *Routes) errorNotFound(c *routing.Context) (err error) {
|
||||
|
||||
errorContent := content.Error{Message: "not found"}
|
||||
|
||||
return c.Write(errorContent)
|
||||
}
|
||||
|
||||
// useErrorHandler is the middleware that catch handlers errors and render error template
|
||||
func (r *Routes) useErrorHandler(c *routing.Context) (err error) {
|
||||
|
||||
worker := func() (err error) {
|
||||
|
||||
defer func() {
|
||||
r := recover()
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = routing.NewHTTPError(http.StatusInternalServerError,
|
||||
fmt.Sprintf("panic:\n%v", r))
|
||||
}()
|
||||
|
||||
err = c.Next()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = worker()
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.Abort()
|
||||
|
||||
logrus.Warnf("Cannot process request, %v", err)
|
||||
|
||||
errorContent := content.Error{
|
||||
Message: err.Error(),
|
||||
}
|
||||
|
||||
return c.Write(errorContent)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
routing "github.com/jackwhelpton/fasthttp-routing/v2"
|
||||
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/content"
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/ghost/params"
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/templates"
|
||||
)
|
||||
|
||||
// rootRedirect redirects the root url to the index using http redirect
|
||||
func (r *Routes) rootRedirect(c *routing.Context) (err error) {
|
||||
|
||||
c.Redirect(templates.URLIndex, http.StatusFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// index handler renders index data
|
||||
func (r *Routes) index(c *routing.Context) (err error) {
|
||||
|
||||
pinnedPageSlug := r.ContentConfig.Pinned
|
||||
postsPerPage := r.ContentConfig.PostsPerPage
|
||||
|
||||
pinnedPages, err := r.GhostClient.GetPageBySlug(pinnedPageSlug)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
latestPosts, err := r.GhostClient.GetPosts(params.WithLimit(postsPerPage))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
indexContent := content.Index{
|
||||
Pinned: pinnedPages.Pages,
|
||||
Meta: latestPosts.Meta,
|
||||
Posts: latestPosts.Posts,
|
||||
}
|
||||
|
||||
return c.Write(indexContent)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
routing "github.com/jackwhelpton/fasthttp-routing/v2"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/templates"
|
||||
)
|
||||
|
||||
var _ routing.DataWriter = (*TemplateWriter)(nil)
|
||||
|
||||
// staticWriter is thread-safe static instance of template writer
|
||||
var staticWriter = &TemplateWriter{}
|
||||
|
||||
// TemplateWriter is the fasthttp data writer that loads and executes template using the content
|
||||
type TemplateWriter struct{}
|
||||
|
||||
// SetHeader sets the content type to HTML since all templates are HTML
|
||||
func (tw *TemplateWriter) SetHeader(rh *fasthttp.ResponseHeader) {
|
||||
rh.SetContentType(routing.MIME_HTML)
|
||||
}
|
||||
|
||||
// Write executes the template and writes result to the response writer
|
||||
func (tw *TemplateWriter) Write(w io.Writer, content interface{}) error {
|
||||
|
||||
template := templates.GetTemplateOf(content)
|
||||
|
||||
return template.Execute(w, content)
|
||||
}
|
||||
|
||||
// useTemplateWriter is the routing middleware to set the default data writer
|
||||
func (r *Routes) useTemplateWriter(c *routing.Context) (err error) {
|
||||
|
||||
c.SetDataWriter(staticWriter)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
routing "github.com/jackwhelpton/fasthttp-routing/v2"
|
||||
"github.com/valyala/fasthttp"
|
||||
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/config"
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/ghost"
|
||||
"code.tokarch.uk/mainnika/nikita-tokarch-uk/pkg/templates"
|
||||
)
|
||||
|
||||
// Routes is the main handler that contains all routes handlers
|
||||
type Routes struct {
|
||||
GhostClient ghost.Client
|
||||
ContentConfig config.Content
|
||||
|
||||
Base string
|
||||
|
||||
router *routing.Router
|
||||
handler fasthttp.RequestHandler
|
||||
|
||||
initOnce sync.Once
|
||||
}
|
||||
|
||||
// Handler invokes the lazy once-initializer and then does the request
|
||||
func (r *Routes) Handler(ctx *fasthttp.RequestCtx) {
|
||||
r.initOnce.Do(r.init)
|
||||
r.handler(ctx)
|
||||
}
|
||||
|
||||
// init has the renderer initialization
|
||||
func (r *Routes) init() {
|
||||
|
||||
router := routing.New()
|
||||
|
||||
router.Use(r.useTemplateWriter)
|
||||
router.Use(r.useErrorHandler)
|
||||
router.NotFound(r.errorNotFound)
|
||||
|
||||
root := router.Group(r.Base)
|
||||
root.Get(templates.URLRoot, r.rootRedirect)
|
||||
root.Get(templates.URLIndex, r.index)
|
||||
root.Get(templates.URLBlog, r.blog)
|
||||
|
||||
r.router = router
|
||||
r.handler = router.HandleRequest
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<!doctype html>
|
||||
<html lang="en_us">
|
||||
|
||||
<!-- render head -->
|
||||
{{ template "head.go.tmpl" . }}
|
||||
|
||||
<body>
|
||||
|
||||
<section id="this-is-blog" class="root-container blog-container">
|
||||
|
||||
<!-- render menu -->
|
||||
<div class="menu-container">
|
||||
{{ template "menu.go.tmpl" . }}
|
||||
</div>
|
||||
|
||||
<!-- render top pagination -->
|
||||
{{ if (gt .Pagination.Page 1) }}
|
||||
<div class="pagination-container-top">
|
||||
<ul>
|
||||
|
||||
{{ if (gt .Pagination.Page 1) }}
|
||||
<li>
|
||||
<a href="{{ getBlogURL }}?page={{ sub .Pagination.Page }}">{{ sub .Pagination.Page }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
<li>
|
||||
{{ .Pagination.Page }} ←
|
||||
</li>
|
||||
{{ if (lt .Pagination.Page .Pagination.Total) }}
|
||||
<li>
|
||||
<a href="{{ getBlogURL }}?page={{ add .Pagination.Page }}">{{ add .Pagination.Page }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!-- render posts -->
|
||||
<div class="posts-container">
|
||||
{{ range .Posts }}
|
||||
{{ template "post.go.tmpl" . }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<!-- render bottom pagination -->
|
||||
{{ if (gt .Pagination.Total 1) }}
|
||||
<div class="pagination-container-bottom">
|
||||
<ul>
|
||||
|
||||
{{ if (gt .Pagination.Page 1) }}
|
||||
<li>
|
||||
<a href="{{ getBlogURL }}?page={{ sub .Pagination.Page }}">{{ sub .Pagination.Page }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
<li>
|
||||
{{ .Pagination.Page }} ←
|
||||
</li>
|
||||
{{ if (lt .Pagination.Page .Pagination.Total) }}
|
||||
<li>
|
||||
<a href="{{ getBlogURL }}?page={{ add .Pagination.Page }}">{{ add .Pagination.Page }}</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,82 @@
|
||||
<!doctype html>
|
||||
<html lang="en_us">
|
||||
|
||||
<!-- render head -->
|
||||
{{ template "head.go.tmpl" . }}
|
||||
|
||||
<body>
|
||||
|
||||
<!-- render menu -->
|
||||
<div class="root-container menu-container">
|
||||
{{ template "menu.go.tmpl" . }}
|
||||
</div>
|
||||
|
||||
<!-- render error -->
|
||||
<code class="language-c">
|
||||
/* $NetBSD: yes.c,v 1.5 1997/10/19 14:28:27 mrg Exp $ */
|
||||
|
||||
/*
|
||||
* Copyright (c) 1987, 1993
|
||||
* The Regents of the University of California. All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. All advertising materials mentioning features or use of this software
|
||||
* must display the following acknowledgement:
|
||||
* This product includes software developed by the University of
|
||||
* California, Berkeley and its contributors.
|
||||
* 4. Neither the name of the University nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <sys/cdefs.h>
|
||||
#ifndef lint
|
||||
__COPYRIGHT("@(#) Copyright (c) 1987, 1993\n\
|
||||
The Regents of the University of California. All rights reserved.\n");
|
||||
#endif /* not lint */
|
||||
|
||||
#ifndef lint
|
||||
#if 0
|
||||
static char sccsid[] = "@(#)yes.c 8.1 (Berkeley) 6/6/93";
|
||||
#endif
|
||||
__RCSID("$NetBSD: yes.c,v 1.5 1997/10/19 14:28:27 mrg Exp $");
|
||||
#endif /* not lint */
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int main __P((int, char **));
|
||||
|
||||
int
|
||||
main(argc, argv)
|
||||
int argc;
|
||||
char **argv;
|
||||
{
|
||||
if (argc > 1)
|
||||
for(;;)
|
||||
(void)puts(argv[1]);
|
||||
else for (;;)
|
||||
(void)puts("y");
|
||||
}
|
||||
</code>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,24 @@
|
||||
package templates
|
||||
|
||||
import "html/template"
|
||||
|
||||
// UseFuncs returns a func map with template helpers functions
|
||||
func UseFuncs() template.FuncMap {
|
||||
return template.FuncMap{
|
||||
"add": func(i int) int {
|
||||
return i + 1
|
||||
},
|
||||
"sub": func(i int) int {
|
||||
return i - 1
|
||||
},
|
||||
"getJSAppURL": func() string {
|
||||
return URLJSApp
|
||||
},
|
||||
"getIndexURL": func() string {
|
||||
return URLIndex
|
||||
},
|
||||
"getBlogURL": func() string {
|
||||
return URLBlog
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
|
||||
<meta name="description" content="{{ .Description }}"/>
|
||||
<title>{{ .Title }}</title>
|
||||
|
||||
<script type="text/javascript" src="{{ getJSAppURL }}" async></script>
|
||||
</head>
|
||||
@@ -0,0 +1,57 @@
|
||||
<!doctype html>
|
||||
<html lang="en_us">
|
||||
|
||||
<!-- render head -->
|
||||
{{ template "head.go.tmpl" . }}
|
||||
|
||||
<body>
|
||||
|
||||
<section class="root-container welcome-container">
|
||||
|
||||
<!-- render menu -->
|
||||
<div class="menu-container">
|
||||
{{ template "menu.go.tmpl" . }}
|
||||
</div>
|
||||
|
||||
<!-- render pinned -->
|
||||
<div class="pinned-container">
|
||||
{{ range .Pinned }}
|
||||
<div class="pinned-post">
|
||||
{{ template "post.go.tmpl" . }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="scroll-container">
|
||||
<a href="#this-is-blog"><span></span></a>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="this-is-blog" class="root-container blog-container">
|
||||
|
||||
<!-- render posts -->
|
||||
<div class="posts-container">
|
||||
{{ range .Posts }}
|
||||
{{ template "post.go.tmpl" . }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<!-- render bottom pagination -->
|
||||
{{ if (gt .Pagination.Total 1) }}
|
||||
<div class="pagination-container-bottom">
|
||||
<ul>
|
||||
<li>
|
||||
{{ .Pagination.Page }} ←
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ getBlogURL }}?page={{ add .Pagination.Page }}">{{ add .Pagination.Page }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
<div class="menu">
|
||||
<div class="menu-logo"><a href="{{ getIndexURL }}"><span>nikita</span>.tokarch.uk</a></div>
|
||||
<ul class="menu-nav">
|
||||
<li class="menu-nav-li1"><a href="#this-is-blog"><i class="bi bi-journal-arrow-down"></i> blog</a></li>
|
||||
<li class="menu-nav-li2"><a class="external" target="_blank" href="https://github.com/mainnika"><i class="bi bi-github"></i> github</a></li>
|
||||
<li class="menu-nav-li3"><a class="external" target="_blank" href="https://www.linkedin.com/in/mainnika"><i class="bi bi-linkedin"></i> linkedin</a></li>
|
||||
<li class="menu-nav-li4"><a class="external" target="_blank" href="https://www.instagram.com/mainnika"><i class="bi bi-instagram"></i> instagram</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -0,0 +1,24 @@
|
||||
<div class="blog-post">
|
||||
|
||||
<!-- render post header -->
|
||||
<div class="blog-post-head">
|
||||
<h1>{{ .Title }}</h1>
|
||||
</div>
|
||||
|
||||
{{ if .FImage }}
|
||||
<div class="blog-post-head-image">
|
||||
<img src="{{ .FImage }}"></img>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!-- render post -->
|
||||
<div class="blog-post-content">
|
||||
{{ .HTML }}
|
||||
</div>
|
||||
|
||||
<!-- render post tailer -->
|
||||
<div class="blog-post-tailer">
|
||||
<h3>… … …</h3>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,91 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"reflect"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Static go-templates source
|
||||
//go:embed blog.go.tmpl
|
||||
//go:embed error.go.tmpl
|
||||
//go:embed head.go.tmpl
|
||||
//go:embed index.go.tmpl
|
||||
//go:embed menu.go.tmpl
|
||||
//go:embed post.go.tmpl
|
||||
var content embed.FS
|
||||
|
||||
// List of compiled go-templates
|
||||
var Templates *template.Template
|
||||
|
||||
// Load embeded templates
|
||||
func init() {
|
||||
|
||||
Templates = template.New("")
|
||||
Templates.Funcs(UseFuncs())
|
||||
|
||||
tmplNames, err := fs.Glob(content, "*.go.tmpl")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
for _, name := range tmplNames {
|
||||
|
||||
buf.Reset()
|
||||
|
||||
tmplContent, err := content.Open(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
size, err := buf.ReadFrom(tmplContent)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tmpl, err := Templates.New(name).Parse(buf.String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
logrus.Debugf("Found template: %s, size:%d", tmpl.Name(), size)
|
||||
}
|
||||
|
||||
logrus.Debugf("Templates loading complete%s", Templates.DefinedTemplates())
|
||||
}
|
||||
|
||||
// MustLookup wraps lookup function for the root template namespace
|
||||
func MustLookup(name string) *template.Template {
|
||||
|
||||
tmpl := Templates.Lookup(name)
|
||||
if tmpl == nil {
|
||||
panic(fmt.Errorf("cannot find template %s", name))
|
||||
}
|
||||
|
||||
return tmpl
|
||||
}
|
||||
|
||||
// GetTemplateOf returns template which is mapped to the content data
|
||||
func GetTemplateOf(content interface{}) (template *template.Template) {
|
||||
|
||||
el := reflect.TypeOf(content)
|
||||
numField := el.NumField()
|
||||
|
||||
for i := 0; i < numField; i++ {
|
||||
field := el.Field(i)
|
||||
tag := field.Tag
|
||||
found, ok := tag.Lookup("template")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
return MustLookup(found)
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("content %v does not have a template tag", content))
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package templates
|
||||
|
||||
const (
|
||||
URLRoot = "/"
|
||||
URLIndex = "/index.aspx"
|
||||
URLBlog = "/blog.aspx"
|
||||
URLPost = "/post.aspx"
|
||||
URLJSApp = "/js-bin/app.js"
|
||||
)
|
||||
Reference in New Issue
Block a user