diff options
Diffstat (limited to '')
| -rw-r--r-- | cmd/web/main.go | 20 | ||||
| -rw-r--r-- | go.mod | 3 | ||||
| -rw-r--r-- | go.sum | 6 | ||||
| -rw-r--r-- | internal/db/redis/image.go | 21 | ||||
| -rw-r--r-- | internal/db/redis/redis.go | 2 | ||||
| -rw-r--r-- | internal/fs/fs.go | 18 | ||||
| -rw-r--r-- | internal/image/handler.go | 33 | ||||
| -rw-r--r-- | internal/image/image_test.go | 42 | ||||
| -rw-r--r-- | internal/image/model.go | 1 | ||||
| -rw-r--r-- | internal/image/repo.go | 1 | ||||
| -rw-r--r-- | internal/image/service.go | 46 | ||||
| -rw-r--r-- | static/css/style.css | 818 | ||||
| -rw-r--r-- | templates/pages/image.tmpl | 12 | ||||
| -rw-r--r-- | templates/parts/_layout.tmpl | 30 |
14 files changed, 1026 insertions, 27 deletions
diff --git a/cmd/web/main.go b/cmd/web/main.go index 3f16dec..f215d1e 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -4,29 +4,43 @@ import ( "log" "net/http" "os" + "path" "github.com/go-chi/chi" "git.ofmax.li/iserv/internal/db/redis" + "git.ofmax.li/iserv/internal/fs" "git.ofmax.li/iserv/internal/image" + "go.ofmax.li/tmpl" ) func main() { connPool := redis.CreatePool("localhost:6379") db := redis.NewRedisImageRepo(connPool) + renderer, err := tmpl.NewHTMLTmpl("templates") + if err != nil { + log.Fatal(err) + } + storagePath, err := os.Getwd() if err != nil { log.Fatal("couldn't find directory to write images to") } - imageService := image.NewService(db, storagePath) + // Image + imageService := image.NewService(db, storagePath, renderer) imageHandler := image.NewHandler(imageService) + imageFile := fs.NewHandler(storagePath) + // Static Files + staticFiles := fs.NewHandler(path.Join(storagePath, "static")) r := chi.NewRouter() - r.Get("/i", imageHandler.GetImage) - r.Post("/i", imageHandler.PostImage) + r.Get("/i/{fileName}", imageHandler.GetImage) + r.Post("/u", imageHandler.PostImage) + r.Get("/f/*", imageFile) + r.Get("/static/*", staticFiles) log.Print("starting imageserv") log.Fatal(http.ListenAndServe(":8080", r)) } @@ -11,10 +11,13 @@ require ( github.com/gomodule/redigo v2.0.0+incompatible github.com/matoous/go-nanoid v1.3.0 github.com/muyo/sno v1.1.0 + github.com/pkg/errors v0.9.1 // indirect github.com/rafaeljusto/redigomock v2.3.0+incompatible github.com/speps/go-hashids v2.0.0+incompatible github.com/stretchr/testify v1.5.1 gitlab.com/grumps/environ v0.0.0-20190605051324-730aa37373e1 + go.ofmax.li/environ v0.1.0 + go.ofmax.li/tmpl v0.1.0 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect google.golang.org/api v0.21.0 // indirect ) @@ -53,6 +53,8 @@ github.com/matoous/go-nanoid v1.3.0 h1:ynznZVSo9t0E8BTYLZx9geceRYZr8yLIrkOv3C/CU github.com/matoous/go-nanoid v1.3.0/go.mod h1:fvGBnhcQ+zcrB3qJIG32PAN11J/y1IYkGX2/VeHzuH0= github.com/muyo/sno v1.1.0 h1:WLsHreJnNFtc29Z2grb9Y9A6nXLrDJS0KqpWZzTYGLg= github.com/muyo/sno v1.1.0/go.mod h1:Hjr5WzLleYD7bJYvQGo5UK7hpE2kSvMbihc0bMWF2bU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -67,6 +69,10 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= gitlab.com/grumps/environ v0.0.0-20190605051324-730aa37373e1 h1:Jjns+kL26DLZhyrxqBZHJn1ymX33jtfnmv2xufSXOaA= gitlab.com/grumps/environ v0.0.0-20190605051324-730aa37373e1/go.mod h1:GPGILReyQ0kLhYL5BcbmvTUGCmHC+upQgUZBQMqvcDY= +go.ofmax.li/environ v0.1.0 h1:O5FEy8lPef4BpdiMNvDJUH7tqQaZmjQvwpDVv6rqmBI= +go.ofmax.li/environ v0.1.0/go.mod h1:MJzTQgo3011WbIjWpEDQiQg2p3dYdIiKGDFvMSOHTKA= +go.ofmax.li/tmpl v0.1.0 h1:itGhtIes5ba/s/0x0i9x+e/6my6MDTkd9KUhwRr/cVc= +go.ofmax.li/tmpl v0.1.0/go.mod h1:gzv8lp+KPxqP6f7FqoBdGl8UwXANmuLOXxyOaKNUaQg= go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/internal/db/redis/image.go b/internal/db/redis/image.go index 6814fb7..1f4e8fa 100644 --- a/internal/db/redis/image.go +++ b/internal/db/redis/image.go @@ -9,7 +9,7 @@ import ( "git.ofmax.li/iserv/internal/image" ) -const V1FilePathFmt = "filepath:up/%s" +const V1FilePathFmt = "v1imagepost:%s" // ImageRepo deps. for storage type ImageRepo struct { @@ -23,13 +23,30 @@ func NewRedisImageRepo(conn *redis.Pool) *ImageRepo { } } +func fileKey(filename, V1FilePathFmt string) string { + return fmt.Sprintf(V1FilePathFmt, filename) +} + func (r *ImageRepo) AddNewFile(filename string, meta *image.PostMeta, timeout int) error { conn := r.db.Get() defer conn.Close() - key := fmt.Sprintf(V1FilePathFmt, filename) + key := fileKey(filename, V1FilePathFmt) _, err := conn.Do("HMSET", redis.Args{}.Add(key).AddFlat(meta)...) if err != nil { log.Fatal(err) } return err } + +func (r *ImageRepo) GetFile(fileUrl string) (*image.PostMeta, error) { + conn := r.db.Get() + defer conn.Close() + imageMeta := &image.PostMeta{} + key := fileKey(fileUrl, V1FilePathFmt) + res, err := redis.Values(conn.Do("HGETALL", key)) + if err != nil { + return &image.PostMeta{}, err + } + err = redis.ScanStruct(res, imageMeta) + return imageMeta, err +} diff --git a/internal/db/redis/redis.go b/internal/db/redis/redis.go index d769bb4..f9147be 100644 --- a/internal/db/redis/redis.go +++ b/internal/db/redis/redis.go @@ -5,7 +5,7 @@ import ( "github.com/gomodule/redigo/redis" - "gitlab.com/grumps/environ" + "go.ofmax.li/environ" ) var redisServer = environ.GetEnv("AUTH_REDIS", "localhost:6379") diff --git a/internal/fs/fs.go b/internal/fs/fs.go new file mode 100644 index 0000000..7b2704b --- /dev/null +++ b/internal/fs/fs.go @@ -0,0 +1,18 @@ +package fs + +import ( + "net/http" + "strings" + + "github.com/go-chi/chi" +) + +func NewHandler(path string) http.HandlerFunc { + filesDir := http.Dir(path) + return func(w http.ResponseWriter, r *http.Request) { + rctx := chi.RouteContext(r.Context()) + pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*") + fs := http.StripPrefix(pathPrefix, http.FileServer(filesDir)) + fs.ServeHTTP(w, r) + } +} diff --git a/internal/image/handler.go b/internal/image/handler.go index 8988a58..2db4c40 100644 --- a/internal/image/handler.go +++ b/internal/image/handler.go @@ -5,6 +5,8 @@ import ( "io/ioutil" "log" "net/http" + + "github.com/go-chi/chi" ) var fileTypes = map[string]string{ @@ -27,9 +29,22 @@ type imageHandler struct { service Servicer } +// GetImage handler for returning an image func (h *imageHandler) GetImage(w http.ResponseWriter, r *http.Request) { - log.Print("serving image") - http.ServeFile(w, r, "foo.jpg") + fileID := chi.URLParam(r, "fileName") + fileMeta, err := h.service.GetFile(fileID) + if err != nil { + w.WriteHeader(400) + log.Printf("error: %+v", err) + w.Write([]byte("WTF Incorrect Content Type")) + } + fileUrl := fmt.Sprintf("/f/%s", fileMeta.FilePath) + data := struct { + ImageUrl string + }{ + fileUrl, + } + h.service.Render(w, "image.tmpl", data) } // PostImage handler for creating an image post @@ -39,6 +54,7 @@ func (h *imageHandler) PostImage(w http.ResponseWriter, r *http.Request) { file, handler, err := r.FormFile("file") if err != nil { log.Printf("%s", err) + return } defer file.Close() fileBytes, err := ioutil.ReadAll(file) @@ -63,9 +79,14 @@ func (h *imageHandler) PostImage(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Incorrect Content Type")) return } - fileID, err := h.service.NewID() - fileName := fmt.Sprintf("%s.%s", fileID, extension) - h.service.AddFile(fileName, fileBytes) + fileName, fileID, err := h.service.AddFile(extension, fileType, fileBytes) + if err != nil { + log.Printf("failed to write file") + w.WriteHeader(500) + w.Write([]byte("An Internal Error Occured")) + return + } w.WriteHeader(201) - w.Write([]byte("ok")) + w.Write([]byte(fmt.Sprintf("%s %s", fileName, fileID))) + return } diff --git a/internal/image/image_test.go b/internal/image/image_test.go index 4a1aea9..3425213 100644 --- a/internal/image/image_test.go +++ b/internal/image/image_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io/ioutil" + "log" "mime/multipart" "net/http" "net/http/httptest" @@ -11,6 +12,7 @@ import ( "os" "path" "path/filepath" + "runtime" "strings" "testing" @@ -20,8 +22,22 @@ import ( db "git.ofmax.li/iserv/internal/db/redis" "git.ofmax.li/iserv/internal/image" + "go.ofmax.li/tmpl" ) +var ( + projectRoot string +) + +func init() { + _, filename, _, _ := runtime.Caller(0) + projectRoot := path.Join(path.Dir(filename), "../..") + err := os.Chdir(projectRoot) + if err != nil { + panic(err) + } +} + // from go lang src https://golang.org/src/mime/multipart/writer.go // there seems to be no way to set the content type. var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") @@ -67,7 +83,7 @@ func prepareRequest(t *testing.T, filename, mimeType string) (*multipart.Writer, return writer, body, err } -func TestImage(t *testing.T) { +func TestPostImage(t *testing.T) { // setup redis for tests conn := redigomock.NewConn() pool := &redis.Pool{ @@ -75,7 +91,7 @@ func TestImage(t *testing.T) { Dial: func() (redis.Conn, error) { return conn, nil }, MaxIdle: 10, } - conn.Command("HMSET", redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData()) + conn.Command("HMSET", redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData(), redigomock.NewAnyData()) repo := db.NewRedisImageRepo(pool) // setup image package @@ -83,13 +99,20 @@ func TestImage(t *testing.T) { if err != nil { t.Fatal(err) } + cwd, _ := os.Getwd() + base := filepath.Join(cwd, "templates") + renderer, err := tmpl.NewHTMLTmpl(base) + if err != nil { + log.Fatal(err) + } defer os.RemoveAll(imageBuildDir) - imageService := image.NewService(repo, imageBuildDir) + imageService := image.NewService(repo, imageBuildDir, renderer) imageHandler := image.NewHandler(imageService) handler := http.HandlerFunc(imageHandler.PostImage) // prep test with png - writer, body, err := prepareRequest(t, "t.png", "image/png") + testImagePath := filepath.Join(projectRoot, "internal/image/t.png") + writer, body, err := prepareRequest(t, testImagePath, "image/png") if err != nil { t.Fatal(err) } @@ -107,7 +130,8 @@ func TestImage(t *testing.T) { } // incorrect mime - writer, body, err = prepareRequest(t, "t.pdf", "application/pdf") + testImagePDF := filepath.Join(projectRoot, "internal/image/t.pdf") + writer, body, err = prepareRequest(t, testImagePDF, "application/pdf") if err != nil { t.Fatal(err) } @@ -124,7 +148,7 @@ func TestImage(t *testing.T) { } // sneaky mimetype check - writer, body, err = prepareRequest(t, "t.png", "image/jpeg") + writer, body, err = prepareRequest(t, testImagePath, "image/jpeg") if err != nil { t.Fatal(err) } @@ -141,7 +165,6 @@ func TestImage(t *testing.T) { } // test image write path && ext - // TODO pngs, err := filepath.Glob(filepath.Join(imageBuildDir)) if err != nil { t.Fatal(err) @@ -149,4 +172,9 @@ func TestImage(t *testing.T) { if len(pngs) != 1 { t.Error("expected only 1 image to be found") } + +} + +func TestGetImage(t *testing.T) { + } diff --git a/internal/image/model.go b/internal/image/model.go index 002ad8a..0d1cd60 100644 --- a/internal/image/model.go +++ b/internal/image/model.go @@ -5,4 +5,5 @@ type PostMeta struct { FilePath string `redis:"file_path"` CreatedAt string `redis:"created_at"` UserID string `redis:"user_id"` + MimeType string `redis:"mime_type"` } diff --git a/internal/image/repo.go b/internal/image/repo.go index a65ad49..39f6a56 100644 --- a/internal/image/repo.go +++ b/internal/image/repo.go @@ -3,4 +3,5 @@ package image // Repo storage interface type Repo interface { AddNewFile(filename string, meta *PostMeta, timeout int) error + GetFile(fileUrl string) (*PostMeta, error) } diff --git a/internal/image/service.go b/internal/image/service.go index dabbfe7..f953bfa 100644 --- a/internal/image/service.go +++ b/internal/image/service.go @@ -1,24 +1,32 @@ package image import ( + "fmt" "io/ioutil" "log" + "net/http" "path" "time" "github.com/matoous/go-nanoid" + "github.com/pkg/errors" + + "go.ofmax.li/tmpl" ) // Servicer image management type Servicer interface { NewID() (string, error) - AddFile(filename string, fileBytes []byte) error + AddFile(extension, fileType string, fileBytes []byte) (string, string, error) + GetFile(fileUrl string) (*PostMeta, error) + Render(w http.ResponseWriter, templateName string, data interface{}) error } // NewService new image service -func NewService(repo Repo, storagePath string) *Service { +func NewService(repo Repo, storagePath string, renderer *tmpl.HTML) *Service { return &Service{repo, storagePath, + renderer, } } @@ -26,6 +34,12 @@ func NewService(repo Repo, storagePath string) *Service { type Service struct { db Repo storagePath string + tmpl *tmpl.HTML +} + +// Render renders templates +func (is *Service) Render(w http.ResponseWriter, templateName string, data interface{}) error { + return is.tmpl.Render(w, templateName, data) } // NewID create an uniqueish ID @@ -34,22 +48,38 @@ func (is *Service) NewID() (string, error) { } // AddFile writes to disk, writes meta to db -func (is *Service) AddFile(fileName string, fileBytes []byte) error { +func (is *Service) AddFile(extension, fileType string, fileBytes []byte) (string, string, error) { + fileID, err := is.NewID() + if err != nil { + return "", "", errors.Wrap(err, "generated id for fileID failed") + } + fileName := fmt.Sprintf("%s.%s", fileID, extension) filePath := path.Join(is.storagePath, fileName) if err := ioutil.WriteFile(filePath, fileBytes, 0750); err != nil { log.Fatal(err) - return err + return "", "", errors.Wrap(err, "couldn't write image file") + } + postID, err := is.NewID() + if err != nil { + return "", "", errors.Wrap(err, "generating postid for uuid") } t := time.Now().UTC() postMeta := &PostMeta{ FilePath: fileName, CreatedAt: t.Format(time.RFC3339), UserID: "1", + MimeType: fileType, } - is.db.AddNewFile(fileName, postMeta, 946080000) - if err := is.db.AddNewFile(fileName, postMeta, 946080000); err != nil { + is.db.AddNewFile(postID, postMeta, 946080000) + if err := is.db.AddNewFile(postID, postMeta, 946080000); err != nil { log.Fatal(err) - return err + return "", "", errors.Wrap(err, "couldn't write to redis") } - return nil + return fileName, postID, nil +} + +// GetFile fetch file from db interface +func (is *Service) GetFile(fileUrl string) (*PostMeta, error) { + result, err := is.db.GetFile(fileUrl) + return result, err } diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 0000000..9075829 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,818 @@ +/* The W3C Core Styles Copyright (c) 1998 W3C (mit, inria, Keio), All Rights +Reserved. W3C liability, trademark, document use and software licensing rules +apply. See http://www.w3.org/Consortium/Legal/ipr-notice.html + +This stylesheet has been served in a form to avoid known bugs in your user +agent's css implementation. Copying and serving this stylesheet to other +agents may lead to unexpected results. Please refer to its address rather than +copy it. */ + + + + + + /* begin body type */ /* 1 - humanist sans a */ + +body { + font-size: 1em; + font-weight: normal; + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + font-family: Verdana, Myriad Web, Syntax, sans-serif; + font-size-adjust: .58; + } + +p, blockquote, ul, ol, dl, dd, li, dir, menu { + /* inherit from body */ + } + +small { + font-size: .92em; + } + +big { + font-size: 1.17em; + } + +pre { + font-family: 'Monotype.com', Courier New, monospace; + } + +ol li { + list-style-type: decimal; + } + +ol ol li { + list-style-type: lower-alpha; + } + +ol ol ol li { + list-style-type: lower-roman; + } + +table, tbody, tr, td { + font-size: 1em; + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + font-family: Verdana, Myriad Web, Syntax, sans-serif; + font-size-adjust: .58; + } + + /* end body type */ + + + + + /* begin header type */ /* 7 - humanist sans b */ + +h1 { + font-family: Trebuchet ms, Verdana, Myriad Web, Syntax, sans-serif; + font-size-adjust: .53; + font-size: 2em; + font-weight: 400; + font-style: normal; + text-decoration: none; + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + } + +h2 { + font-family: Trebuchet ms, Verdana, Myriad Web, Syntax, sans-serif; + font-size-adjust: .53; + font-size: 1.75em; + font-weight: 400; + font-style: normal; + text-decoration: none; + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + } + +h3 { + font-family: Trebuchet ms, Verdana, Myriad Web, Syntax, sans-serif; + font-size-adjust: .53; + font-size: 1.58em; + font-weight: 400; + font-style: normal; + text-decoration: none; + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + } + +h4 { + font-family: Trebuchet ms, Verdana, Myriad Web, Syntax, sans-serif; + font-size-adjust: .53; + font-size: 1.33em; + font-weight: 500; + font-style: normal; + text-decoration: none; + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + } + +h5, dt { + font-family: Trebuchet ms, Verdana, Myriad Web, Syntax, sans-serif; + font-size-adjust: .53; + font-size: 1.17em; + font-weight: 600; + font-style: normal; + text-decoration: none; + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + } + +h6 { + font-family: Trebuchet ms, Verdana, Myriad Web, Syntax, sans-serif; + font-size-adjust: .53; + font-size: 1em; + font-weight: 700; + font-style: normal; + text-decoration: none; + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + } + +tfoot, thead { + font-size: 1em; + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + font-family: Trebuchet ms, Verdana, Myriad Web, Syntax, sans-serif; + font-size-adjust: .53; + } + +th { + vertical-align: baseline; + font-size: 1em; + font-weight: bold; + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + font-family: Trebuchet ms, Verdana, Myriad Web, Syntax, sans-serif; + font-size-adjust: .53; + } + +hr { + visibility: hidden; + } + + /* end header type */ + + + + + /* begin affordances */ /* 3 - mint reverse */ + +a, address, blockquote, body, cite, code, dd, del, dfn, +div, dl, dt, em, form, h1, h2, h3, h4, h5, h6, iframe, img, kbd, +li, object, ol, p, q, samp, small, span, strong, sub, sup, ul, var, +applet, big, center, dir, font, hr, menu, pre, +abbr, acronym, bdo, button, fieldset, ins, label { + word-spacing: normal; + letter-spacing: normal; + text-transform: none; + text-decoration: none; + border-color: #ccc; + border-style: none; + } + +body { + color: #D0FFD0; + background: #212121; + } + +em { + font-style: normal; + font-weight: bold; + color: #D0FFD0; + background: #212121; + } + +strong { + font-style: italic; + background: #212121; + font-weight: bold; + color: #D0FFD0; + } + +em strong, strong em { + text-transform: uppercase; + font-style: normal; + font-weight: bolder; + background: #212121; + color: #D0FFD0; + } + +b { + font-weight: bold; + } + +i { + font-style: italic; + } + +.warning { + text-transform: none; + font-style: normal; + font-weight: bolder; + background: yellow; + color: black; + } + +del { + text-decoration: line-through; + background: #900; + } + +ins { + text-decoration: none; + background: #060; + } + +var, cite, dfn, .note { + font-style: italic; + } + +address { + font-style: normal; + letter-spacing: .1em; + } + +acronym { + font-variant: small-caps; + letter-spacing: 0.1em; + } + +h1, h2, h3, h4, h5, h6, dt, th, thead, tfoot { + color: rgb(245,245,245); + background: #212121; + } + +hr { + color: #ccc; + } + +#colophon { + display: none; + } + +col, colgroup, table, tbody, td, tr { + color: #D0FFD0; + text-decoration: none; + border-color: #ccc; + border-style: none; + background: #212121; + } + +a:link { + text-decoration: none; + font-weight: bold; + color: #F60; + background: #212121; + } + +a:visited { + text-decoration: none; + font-weight: bold; + color: #C96; + background: #212121; + } + +a:active { + text-decoration: none; + font-weight: bold; + color: #ffc; + background: #212121; + } + +a:hover { + text-decoration: none; + color: #FC0; + background: #212121; + } + +a.offsite { + text-decoration: none; + font-weight: normal; + color: #F60; + background: #212121; + } + + /* end affordances */ + + + + + /* begin vertical */ /* 1 - pentatonic a */ + +a, address, blockquote, cite, code, dd, del, dfn, +div, dl, dt, em, form, h1, h2, h3, h4, h5, h6, iframe, img, kbd, +li, object, ol, p, q, samp, small, span, strong, sub, sup, ul, var, +applet, b, big, center, dir, font, hr, i, menu, pre, s, strike, tt, +u, abbr, acronym, bdo, button, fieldset, ins, label { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + vertical-align: baseline; + } + +body { + line-height: 1.58em; + } + +div > p:first-child, body > p:first-child, td > p:first-child { + margin-top: .75em; + margin-bottom: .75em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +h1 + p, h2 + p, h3 + p, h4 + p, h5 + p, h6 + p, div + p, p.initial { + margin-top: .75em; + margin-bottom: .75em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +h1, h2, h3, h4, h5, h6 { + line-height: 1.33em; + } + +h1 { + margin-top: 1.33em; + margin-bottom: .33em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +h2 { + margin-top: 1.75em; + margin-bottom: .33em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +h3 { + margin-top: 1.58em; + margin-bottom: .33em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +h4 { + margin-top: 1.33em; + margin-bottom: .33em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +h5 { + margin-top: 1.17em; + margin-bottom: .33em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +h6 { + margin-top: 1em; + margin-bottom: .33em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +p { + margin-top: .75em; + margin-bottom: .75em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +blockquote, fieldset, form, ul, ol, dl, dir, menu { + margin-top: .75em; + margin-bottom: .75em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +dt { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +dd { + margin-top: 0; + margin-bottom: .75em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +ul ul, ol ol, li address, li dl, li ol, li p, li ul, li dir, li hr, li menu, li pre, li h1, li h2, li h3, li h4, li h5, li h6, dd address, dd dl, dd ol, dd p, dd ul, dd dir, dd hr, dd menu, dd pre, dd h1, dd h2, dd h3, dd h4, dd h5, dd h6 { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +li { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +address { + margin-top: 1.58em; + margin-bottom: 1.58em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +pre { + margin-top: .75em; + margin-bottom: .75em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + line-height: 1.25em; + } + +hr { + margin-top: .75em; + margin-bottom: .75em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + height: 1px; + } + +div, center { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +.subhead { + margin-top: .75em; + margin-bottom: .75em; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +.stb { + margin-top: 2.17em; + margin-bottom: .75em; + padding-top: 2.17em; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + border-style: solid + } + +.mtb { + margin-top: 3.08em; + margin-bottom: .75em; + padding-top: 3.08em; + padding-bottom: 0; + border-top: .1em; + border-bottom: 0; + border-style: solid + } + +.ltb { + margin-top: 4.34em; + margin-bottom: .75em; + padding-top: 4.34em; + padding-bottom: 0; + border-top: .25em; + border-bottom: 0; + border-style: solid + } + +col, colgroup, table, tbody, td, tfoot, th, thead, tr { + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-bottom: 0; + border-top: 0; + border-bottom: 0; + } + +td, th { + line-height: 1.33em; + } + + /* end vertical */ + + + + + /* begin horizontal */ /* 1 - compact */ + +a, address, blockquote, cite, code, dd, del, dfn, +div, dl, dt, em, form, h1, h2, h3, h4, h5, h6, iframe, img, kbd, +li, object, ol, p, q, samp, small, span, strong, sub, sup, ul, var, +applet, b, big, center, dir, font, hr, i, menu, pre, s, strike, tt, +u, abbr, acronym, bdo, button, fieldset, ins, label { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + float: none; + clear: none; + list-style-position: outside; + } + +address, blockquote, dl, fieldset, form, ol, +p, ul, dir, hr, menu, pre { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + } + +div > p:first-child, body > p:first-child, td > p:first-child { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + text-indent: 0; + } + +h1 + p, h2 + p, h3 + p, h4 + p, h5 + p, h6 + p, div + p, p.initial { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + text-indent: 0; + } + +h1 { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + } + +h2 { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + } + +h3 { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + } + +h4 { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + } + +h5 { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + } + +h6 { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + } + +p { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-indent: 0; + } + +blockquote { + margin-left: 1.58em; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + } + +address { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + } + +pre { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + } + +li { + margin-left: 3.16em; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + } + +dt { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + } + +dd { + margin-left: 1.58em; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + } + +ul ul, ol ol, li address, li dl, li ol, li p, li ul, li dir, li hr, li menu, li pre, li h1, li h2, li h3, li h4, li h5, li h6, dd address, dd dl, dd ol, dd p, dd ul, dd dir, dd hr, dd menu, dd pre, dd h1, dd h2, dd h3, dd h4, dd h5, dd h6 { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + } + +hr { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + width: 100%; + } + +center { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + text-align: left; + } + +col, colgroup, table, tbody, td, tfoot, th, thead, tr { + margin-left: 0; + margin-right: 0; + padding-left: 0; + padding-right: 0; + border-left: 0; + border-right: 0; + float: none; + clear: none; + } + +table { + text-align: left; + } + +td { + text-align: left; + } + +th { + text-align: left; + } + +caption { + text-align: left; + } + + /* end horizontal */ + + + + + /* begin body box */ /* 5 - 'ninths symmetric' */ + +body { + margin-top: 1.58em; + margin-left: 11%; + margin-right: 8%; + margin-bottom: 1.58em; + padding-top: 0; + padding-left: 0; + padding-right: 0; + padding-bottom: 0; + border-top: 0; + border-left: 0; + border-bottom: 0; + border-right: 0; + width: auto; + } + /* end body box */ + + + +img { + max-width: 80%; +} diff --git a/templates/pages/image.tmpl b/templates/pages/image.tmpl new file mode 100644 index 0000000..4c9b7b0 --- /dev/null +++ b/templates/pages/image.tmpl @@ -0,0 +1,12 @@ +{{ define "content" }} +<body> + <div> + <h3>Image</h3> + </div> + <div> + <figure> + <img src="http://localhost:8080{{ .ImageUrl }}"> + </figure> + </div> +</body> +{{ end }} diff --git a/templates/parts/_layout.tmpl b/templates/parts/_layout.tmpl new file mode 100644 index 0000000..4098a30 --- /dev/null +++ b/templates/parts/_layout.tmpl @@ -0,0 +1,30 @@ +{{ define "base" }} +<!DOCTYPE html> +<html lang="en"> + <!-- Basic Page Needs + –––––––––––––––––––––––––––––––––––––––––––––––––– --> + <meta charset="utf-8"> + <title>iserv - an image site</title> + <meta name="description" content=""> + <meta name="author" content=""> + + <!-- Mobile Specific Metas + –––––––––––––––––––––––––––––––––––––––––––––––––– --> + <meta name="viewport" content="width=device-width, initial-scale=1"> + + <!-- FONT + <link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css"> + + –––––––––––––––––––––––––––––––––––––––––––––––––– --> + <!-- CSS + <link rel="stylesheet" href="css/normalize.css"> + –––––––––––––––––––––––––––––––––––––––––––––––––– --> + <link rel="stylesheet" href="/static/css/style.css"> + + <!-- Favicon + –––––––––––––––––––––––––––––––––––––––––––––––––– --> + <!-- <link rel="icon" type="image/png" href="images/favicon.png"> --> + +</head> +{{ template "content" . }} +{{ end }} |