Started Plugin Rewrite
This commit is contained in:
		
							
								
								
									
										428
									
								
								assets.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										428
									
								
								assets.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,428 @@ | ||||
| // Code generated by "esc -o assets.go assets templates"; DO NOT EDIT. | ||||
|  | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"compress/gzip" | ||||
| 	"encoding/base64" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"sync" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type _escLocalFS struct{} | ||||
|  | ||||
| var _escLocal _escLocalFS | ||||
|  | ||||
| type _escStaticFS struct{} | ||||
|  | ||||
| var _escStatic _escStaticFS | ||||
|  | ||||
| type _escDirectory struct { | ||||
| 	fs   http.FileSystem | ||||
| 	name string | ||||
| } | ||||
|  | ||||
| type _escFile struct { | ||||
| 	compressed string | ||||
| 	size       int64 | ||||
| 	modtime    int64 | ||||
| 	local      string | ||||
| 	isDir      bool | ||||
|  | ||||
| 	once sync.Once | ||||
| 	data []byte | ||||
| 	name string | ||||
| } | ||||
|  | ||||
| func (_escLocalFS) Open(name string) (http.File, error) { | ||||
| 	f, present := _escData[path.Clean(name)] | ||||
| 	if !present { | ||||
| 		return nil, os.ErrNotExist | ||||
| 	} | ||||
| 	return os.Open(f.local) | ||||
| } | ||||
|  | ||||
| func (_escStaticFS) prepare(name string) (*_escFile, error) { | ||||
| 	f, present := _escData[path.Clean(name)] | ||||
| 	if !present { | ||||
| 		return nil, os.ErrNotExist | ||||
| 	} | ||||
| 	var err error | ||||
| 	f.once.Do(func() { | ||||
| 		f.name = path.Base(name) | ||||
| 		if f.size == 0 { | ||||
| 			return | ||||
| 		} | ||||
| 		var gr *gzip.Reader | ||||
| 		b64 := base64.NewDecoder(base64.StdEncoding, bytes.NewBufferString(f.compressed)) | ||||
| 		gr, err = gzip.NewReader(b64) | ||||
| 		if err != nil { | ||||
| 			return | ||||
| 		} | ||||
| 		f.data, err = ioutil.ReadAll(gr) | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return f, nil | ||||
| } | ||||
|  | ||||
| func (fs _escStaticFS) Open(name string) (http.File, error) { | ||||
| 	f, err := fs.prepare(name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return f.File() | ||||
| } | ||||
|  | ||||
| func (dir _escDirectory) Open(name string) (http.File, error) { | ||||
| 	return dir.fs.Open(dir.name + name) | ||||
| } | ||||
|  | ||||
| func (f *_escFile) File() (http.File, error) { | ||||
| 	type httpFile struct { | ||||
| 		*bytes.Reader | ||||
| 		*_escFile | ||||
| 	} | ||||
| 	return &httpFile{ | ||||
| 		Reader:   bytes.NewReader(f.data), | ||||
| 		_escFile: f, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (f *_escFile) Close() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *_escFile) Readdir(count int) ([]os.FileInfo, error) { | ||||
| 	return nil, nil | ||||
| } | ||||
|  | ||||
| func (f *_escFile) Stat() (os.FileInfo, error) { | ||||
| 	return f, nil | ||||
| } | ||||
|  | ||||
| func (f *_escFile) Name() string { | ||||
| 	return f.name | ||||
| } | ||||
|  | ||||
| func (f *_escFile) Size() int64 { | ||||
| 	return f.size | ||||
| } | ||||
|  | ||||
| func (f *_escFile) Mode() os.FileMode { | ||||
| 	return 0 | ||||
| } | ||||
|  | ||||
| func (f *_escFile) ModTime() time.Time { | ||||
| 	return time.Unix(f.modtime, 0) | ||||
| } | ||||
|  | ||||
| func (f *_escFile) IsDir() bool { | ||||
| 	return f.isDir | ||||
| } | ||||
|  | ||||
| func (f *_escFile) Sys() interface{} { | ||||
| 	return f | ||||
| } | ||||
|  | ||||
| // FS returns a http.Filesystem for the embedded assets. If useLocal is true, | ||||
| // the filesystem's contents are instead used. | ||||
| func FS(useLocal bool) http.FileSystem { | ||||
| 	if useLocal { | ||||
| 		return _escLocal | ||||
| 	} | ||||
| 	return _escStatic | ||||
| } | ||||
|  | ||||
| // Dir returns a http.Filesystem for the embedded assets on a given prefix dir. | ||||
| // If useLocal is true, the filesystem's contents are instead used. | ||||
| func Dir(useLocal bool, name string) http.FileSystem { | ||||
| 	if useLocal { | ||||
| 		return _escDirectory{fs: _escLocal, name: name} | ||||
| 	} | ||||
| 	return _escDirectory{fs: _escStatic, name: name} | ||||
| } | ||||
|  | ||||
| // FSByte returns the named file from the embedded assets. If useLocal is | ||||
| // true, the filesystem's contents are instead used. | ||||
| func FSByte(useLocal bool, name string) ([]byte, error) { | ||||
| 	if useLocal { | ||||
| 		f, err := _escLocal.Open(name) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		b, err := ioutil.ReadAll(f) | ||||
| 		_ = f.Close() | ||||
| 		return b, err | ||||
| 	} | ||||
| 	f, err := _escStatic.prepare(name) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return f.data, nil | ||||
| } | ||||
|  | ||||
| // FSMustByte is the same as FSByte, but panics if name is not present. | ||||
| func FSMustByte(useLocal bool, name string) []byte { | ||||
| 	b, err := FSByte(useLocal, name) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	return b | ||||
| } | ||||
|  | ||||
| // FSString is the string version of FSByte. | ||||
| func FSString(useLocal bool, name string) (string, error) { | ||||
| 	b, err := FSByte(useLocal, name) | ||||
| 	return string(b), err | ||||
| } | ||||
|  | ||||
| // FSMustString is the string version of FSMustByte. | ||||
| func FSMustString(useLocal bool, name string) string { | ||||
| 	return string(FSMustByte(useLocal, name)) | ||||
| } | ||||
|  | ||||
| var _escData = map[string]*_escFile{ | ||||
|  | ||||
| 	"/assets/css/main.css": { | ||||
| 		local:   "assets/css/main.css", | ||||
| 		size:    4980, | ||||
| 		modtime: 1547133837, | ||||
| 		compressed: ` | ||||
| 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 | ||||
| `, | ||||
| 	}, | ||||
|  | ||||
| 	"/assets/vendor/css/grids-responsive-min.css": { | ||||
| 		local:   "assets/vendor/css/grids-responsive-min.css", | ||||
| 		size:    8032, | ||||
| 		modtime: 1547133812, | ||||
| 		compressed: ` | ||||
| H4sIAAAAAAAC/8SVT4/bRg/G7/4UziHA+wa1LM9oRrL3UjTNoUBQFMipR68trIXqjyHLSbaL/e6FIc1G | ||||
| fEjOIqc9PhQ1Esnh81t/eLf469qXy69p4hOz+NidH/vq4TQsTbqxy7/3p657t/hcHcr2Uh6X1/ZY9svh | ||||
| VC5/+/L7cgoni9MwnC+79fqhGk7X++TQNevH25vr87Uv1/d1d79u9peh7Nef//j46c8vn5LmuPiwXvza | ||||
| lMdqv7wc+rJsl/v2uPxfU7Wrb9VxOO2sS1zZ/P8puR2yuq4uzWrzy1wwaahGmVFtqYSnjkpPZUFkimez | ||||
| f2FfNxiwGMgw4DDgMZBjoMDAFgKGaUslaYPBQg3WZdiBWJfBuizTIB2VpPV4WEazHcwBW+gw3VNJvoXt | ||||
| zuHwnD0nr+MsbqN4OlaXc71/3FVtXbXl6r7uDv/c/dt1zW5zV5fDUPary3l/qNqHXdv1zb6++9b1R4x9 | ||||
| LfuhOuzr1b6uHtrd0J3vhvL7sOrL27reEvfXoXuGbXga1yxLNt7n758jq2R+ZBeJtdZCdoHznJI3JnGQ | ||||
| 6nF6IdUnnv+Gm56alDxwP94zaVLwH8pwbiGb/k4+e7KV2mBxgFO2tbwNcDO3s+wc2mBeCstoYcxJbhc4 | ||||
| pG6EDs3n6KROGOY6U7qDL8/G5qQrkQm3P+QXUjdCiR4+xHewCJnsunhuTyFXui+zeebSvSi41YR0+PBs | ||||
| erlwM7KX6gpaneED9CFTuDPMP/OXZhR4aRhUzGyaW+FymNlIt9LliMCUubR7qXiTpu+fn6Pkzoo5t5vj | ||||
| 7OybYNJQjTKj2lIJTx2VnsqCyBTPZv/Cvm4wYDGQYcBhwGMgx0CBgS0EDNOWStIGg4UarMuwA7Eug3VZ | ||||
| pkE6Kknr8bCMZjuYA7bQYbqnknwL253D4Tl7Tl7HWbwdt6dtULgtrFKE23wbVG7zPYpxe1xCgdvTGDVu | ||||
| 811WuD1NTOM2t4gYt/nNVLk9rpXAbclJYtwOvqJxW/BAhdvBfzRuS3YU4/a4tgK3JRvTuC05XITbwf80 | ||||
| bkt2KHM7+KTG7dFmBG5LDhnhtuSfGrclqES5HaxX4/YrMGUu/VPc9tmc2/XD7OybYNJQjTKj2lIJTx2V | ||||
| nsqCyBTPZv/Cvm4wYDGQYcBhwGMgx0CBgS0EDNOWStIGg4UarMuwA7Eug3VZpkE6Kknr8bCMZjuYA7bQ | ||||
| YbqnknwL253D4Tl7Tl7HWbwdt6dtULgtrFKE23wbVG7zPYpxe1xCgdvTGDVu811WuD1NTOM2t4gYt/nN | ||||
| VLk9rpXAbclJYtwOvqJxW/BAhdvBfzRuS3YU4/a4tgK3JRvTuC05XITbwf80bkt2KHM7+KTG7dFmBG5L | ||||
| DhnhtuSfGrclqES5HaxX4/YrMGUu/VPcLtI5t7/Xs7NvgklDNcqMakslPHVUeioLIlM8m/0L+7rBgMVA | ||||
| hgGHAY+BHAMFBrYQMExbKkkbDBZqsC7DDsS6DNZlmQbpqCStx8Mymu1gDthCh+meSvItbHcOh+fsOXkd | ||||
| Z/F23J62QeG2sEoRbvNtULnN9yjG7XEJBW5PY9S4zXdZ4fY0MY3b3CJi3OY3U+X2uFYCtyUniXE7+IrG | ||||
| bcEDFW4H/9G4LdlRjNvj2grclmxM47bkcBFuB//TuC3Zoczt4JMat0ebEbgtOWSE25J/atyWoBLldrBe | ||||
| jduvwJS5NHL7vwAAAP//TZMIY2AfAAA= | ||||
| `, | ||||
| 	}, | ||||
|  | ||||
| 	"/assets/vendor/css/pure-min.css": { | ||||
| 		local:   "assets/vendor/css/pure-min.css", | ||||
| 		size:    16449, | ||||
| 		modtime: 1547133812, | ||||
| 		compressed: ` | ||||
| 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 | ||||
| `, | ||||
| 	}, | ||||
|  | ||||
| 	"/templates/footer.html": { | ||||
| 		local:   "templates/footer.html", | ||||
| 		size:    228, | ||||
| 		modtime: 1547128347, | ||||
| 		compressed: ` | ||||
| H4sIAAAAAAAC/1zPwQqDMAzG8btPEXIX0XPsu9SlYCFbh0kLvv3Yplh76uH/ox+hgWNxHXEsEHnGZ2Iv | ||||
| fSphE7+j6wDoBwAAaB0rY9EkoKNhHc88VVnzconpEPeVJfH+zddA07NZeikesXEP8aozbkGD9e8sUn11 | ||||
| XvV/PgEAAP///4iBjuQAAAA= | ||||
| `, | ||||
| 	}, | ||||
|  | ||||
| 	"/templates/header.html": { | ||||
| 		local:   "templates/header.html", | ||||
| 		size:    200, | ||||
| 		modtime: 1547136888, | ||||
| 		compressed: ` | ||||
| H4sIAAAAAAAC/0yNsQrDIBRFd7/ikj0JZLYuhW6d2h+w+lIFayGaLPL+PRgkZHzvcM6V1m8wQad06+ag | ||||
| k4OhmGlBKcOj3vfKmDslcP6elJL+ErOQo/WbEteK+cdMMbdOr0NoLvyM4bV+3j4HArMArp4jbWnpfxTX | ||||
| QwCkm1Qpp8IsRzdV1FaPKEVbW3sAAAD//7XiV5HIAAAA | ||||
| `, | ||||
| 	}, | ||||
|  | ||||
| 	"/templates/htmlfooter.html": { | ||||
| 		local:   "templates/htmlfooter.html", | ||||
| 		size:    115, | ||||
| 		modtime: 1547128255, | ||||
| 		compressed: ` | ||||
| H4sIAAAAAAAC/1JQUFCw0U/JLLPjAjGrqxWKEvPSUxVUMnUUVMoUrGwV9IKTizILSooVamvBSmyKwXyF | ||||
| 4qJkW6XqapCq2lolOxt9iDjcnNS8FIgWG/2k/JRKOy4b/YyS3Bw7LkAAAAD//zPK1EBzAAAA | ||||
| `, | ||||
| 	}, | ||||
|  | ||||
| 	"/templates/htmlheader.html": { | ||||
| 		local:   "templates/htmlheader.html", | ||||
| 		size:    801, | ||||
| 		modtime: 1547128244, | ||||
| 		compressed: ` | ||||
| H4sIAAAAAAAC/5yST2/UMBDF7/spplaPdQxCCKjivZRKcAGk9oKqHrz2ZD2LYwd7krKK8t1R9g+70J44 | ||||
| JeOZ95uXF9cXH7/e3H//dgue27Bc1PsHQO3RuPkFoG6RDVhvckHWoudGvhfnrWha1GIgfOpSZgE2RcbI | ||||
| WjyRY68dDmRR7ooroEhMJshiTUD9unr1AsphsZk6phTPaH8NeuZO4s+eBi1ujPUo57mcwpngS5K71lEY | ||||
| KP6AjEEL03UBJafeekl23uIzNlooUwpyUdSuVWOGuVV1cS2Atx1qQa1Zo5oPnhGLT5ltz/CfuD2PiQMu | ||||
| x7G6I8bqfq6mCSTMJ/3qUNdqP7ZY1BdSPlADgeHzLXx4PLjahwclWy3mnK6Vsi5uSmVD6l0TTMbKplaZ | ||||
| jfmlAq2Kmv/62+JpUG+qd6eq2hSxrNWet1zUFw8YHTWPUh4MjyNkE9cIl3QFlwNca6jueBuweEQuME3P | ||||
| cvrTPYY0jrNymg6ZjiNgdEflSws+oXGY73amTivOP/qEPJn/l12r4w2vV8ltD9E5GoCcFsFsU89iufgd | ||||
| AAD//1puW/YhAwAA | ||||
| `, | ||||
| 	}, | ||||
|  | ||||
| 	"/templates/login.html": { | ||||
| 		local:   "templates/login.html", | ||||
| 		size:    812, | ||||
| 		modtime: 1547136935, | ||||
| 		compressed: ` | ||||
| H4sIAAAAAAAC/6SSTW7jMAyF9zkFwb3hC9gGZjHLwQSTuYBs0bFQSRQoKU1uX8RW/LNogaIr88mPTx8F | ||||
| NtrcYLAqxhYnZUcYyCeSSlmL3QmgGVncyxCyUDUfrFWlrLl60ghqSIZ9i3WOJLXlq/EIjtLEusXz38v/ | ||||
| Oe+ZaMjqSGmRAHuEOXdgn4RtdRXOAV82gMaqniyMLC2SU8Zi9/v5gV9aC8XY1LNh12B8yAmMfvnBK0er | ||||
| SI9ALSa6J4Rg1UATW03S4iEVQeXEIw85rsS1Nrfu9BP+oGJ8Z9HYnUv1Ff3qLgNseplh04c5zusl3wKP | ||||
| nzALOXI9CR6bJhreer7vmg7sW9fCvumFfeuHf+UX/KEN4PgsZYIi+pwS+xIUc+9MOsIVw66ughin5IHd | ||||
| ZfY39XJelrPeb2dTP1e8O5VLPwIAAP//0xnP9ywDAAA= | ||||
| `, | ||||
| 	}, | ||||
|  | ||||
| 	"/templates/user_dashboard.html": { | ||||
| 		local:   "templates/user_dashboard.html", | ||||
| 		size:    0, | ||||
| 		modtime: 1547138339, | ||||
| 		compressed: ` | ||||
| H4sIAAAAAAAC/wEAAP//AAAAAAAAAAA= | ||||
| `, | ||||
| 	}, | ||||
|  | ||||
| 	"/": { | ||||
| 		isDir: true, | ||||
| 		local: "", | ||||
| 	}, | ||||
|  | ||||
| 	"/assets": { | ||||
| 		isDir: true, | ||||
| 		local: "assets", | ||||
| 	}, | ||||
|  | ||||
| 	"/assets/css": { | ||||
| 		isDir: true, | ||||
| 		local: "assets/css", | ||||
| 	}, | ||||
|  | ||||
| 	"/assets/vendor": { | ||||
| 		isDir: true, | ||||
| 		local: "assets/vendor", | ||||
| 	}, | ||||
|  | ||||
| 	"/assets/vendor/css": { | ||||
| 		isDir: true, | ||||
| 		local: "assets/vendor/css", | ||||
| 	}, | ||||
|  | ||||
| 	"/templates": { | ||||
| 		isDir: true, | ||||
| 		local: "templates", | ||||
| 	}, | ||||
| } | ||||
							
								
								
									
										355
									
								
								assets/css/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										355
									
								
								assets/css/main.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,355 @@ | ||||
| html { | ||||
|   min-height: 100%; | ||||
| } | ||||
|  | ||||
| body { | ||||
|   color: #777; | ||||
|   min-height: 100%; | ||||
| } | ||||
|  | ||||
| a { | ||||
|   text-decoration: none; | ||||
| } | ||||
|  | ||||
| div.content { | ||||
|   padding: 15px; | ||||
|   min-height: 100%; | ||||
|   color: black; | ||||
| } | ||||
|  | ||||
| div.half { | ||||
|   width: 50%; | ||||
| } | ||||
|  | ||||
| .header { | ||||
|   margin-left: 0; | ||||
| } | ||||
|  | ||||
| .fa-2 { | ||||
|   font-size: 2em; | ||||
| } | ||||
|  | ||||
| .primary { | ||||
|   color: #0078e7; | ||||
| } | ||||
|  | ||||
| .primary-bg { | ||||
|   background-color: #0078e7; | ||||
| } | ||||
|  | ||||
| .error { | ||||
|   color: #DD0000; | ||||
| } | ||||
|  | ||||
| .error-bg { | ||||
|   background-color: #DD0000; | ||||
| } | ||||
|  | ||||
| div.optionalfield { | ||||
|   margin: 2em; | ||||
| } | ||||
|  | ||||
|  | ||||
| input.file { | ||||
|   padding: .5em .6em; | ||||
|   display: inline-block; | ||||
|   border: 1px solid #ccc; | ||||
|   box-shadow: inset 0 1px 3px #ddd; | ||||
|   border-radius: 4px; | ||||
| } | ||||
|  | ||||
| input.ranking-input { | ||||
|   width: 50px; | ||||
|   border-radius: 5px; | ||||
|   border: 1px solid #CCC; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| i.move-icon { | ||||
|   cursor: ns-resize; | ||||
| } | ||||
|  | ||||
| #menu { | ||||
|   width: 100%; | ||||
|   height: 40px; | ||||
|   position: relative; | ||||
|   background: #191818; | ||||
|   webkit-overflow-scrolling: touch; | ||||
| } | ||||
|  | ||||
| #menu .pure-menu-heading { | ||||
|   display: inline-block; | ||||
| } | ||||
|  | ||||
| #menu .pure-menu-nonlink { | ||||
|   color: #777; | ||||
|   padding: .5em 1em; | ||||
|   display: block; | ||||
|   text-decoration: none; | ||||
|   white-space: nowrap; | ||||
| } | ||||
|  | ||||
| #menu .menu-button { | ||||
|   display: inline; | ||||
|   position: absolute; | ||||
|   top: 0px; | ||||
|   right: 0px; | ||||
|   margin-top: 10px; | ||||
|   margin-right: 10px; | ||||
|   color: white; | ||||
|   text-decoration: none; | ||||
| } | ||||
|  | ||||
| .menu-bottom { | ||||
|   width: 150px; | ||||
|   border-top: 1px solid white; | ||||
| } | ||||
|  | ||||
| #menu .menu-container { | ||||
|   display: none; | ||||
|   position: absolute; | ||||
|   right: 0px; | ||||
|   top: 40px; | ||||
|   background-color: #191818; | ||||
| } | ||||
|  | ||||
| #menu .menu-container>ul { | ||||
|   background-color: #191818; | ||||
| } | ||||
|  | ||||
| div.horizontal-scroll { | ||||
|   overflow-x: scroll; | ||||
| } | ||||
|  | ||||
| img.thumbnail { | ||||
|   height: 100px; | ||||
|   max-height: 100%; | ||||
|   max-width: 100%; | ||||
| } | ||||
|  | ||||
| .thumbnail-container { | ||||
|   display: block; | ||||
|   height: 100%; | ||||
|   background-color: #EEE; | ||||
|   padding-top: 20px; | ||||
| } | ||||
|  | ||||
| .padding { | ||||
|   padding: 5px; | ||||
| } | ||||
|  | ||||
| .space { | ||||
|   margin: 5px; | ||||
| } | ||||
|  | ||||
| .space-sides { | ||||
|   margin-left: 5px; | ||||
|   margin-right: 5px; | ||||
| } | ||||
|  | ||||
| .space-vertical { | ||||
|   margin-top: 5px; | ||||
|   margin-bottom: 5px; | ||||
| } | ||||
|  | ||||
| .large { | ||||
|   font-size: 18px; | ||||
| } | ||||
|  | ||||
| .big-space { | ||||
|   margin: 10px; | ||||
| } | ||||
|  | ||||
| .left { | ||||
|   text-align: left; | ||||
| } | ||||
| .right { | ||||
|   text-align: right; | ||||
| } | ||||
| .center { | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| .center-all { | ||||
|   text-align: center; | ||||
|   vertical-align: center; | ||||
|   margin: auto; | ||||
| } | ||||
|  | ||||
| table.center tbody>td { | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| table.padding td { | ||||
|   padding: 5px; | ||||
| } | ||||
|  | ||||
| table tfoot { | ||||
|   border-top: 1px solid #CCC; | ||||
| } | ||||
|  | ||||
| .pure-button-error { | ||||
|   background-color: #DD0000; | ||||
|   color: #FFF; | ||||
| } | ||||
|  | ||||
| .pure-button-toggle-first { | ||||
|   border-top-right-radius: 0px; | ||||
|   border-bottom-right-radius: 0px; | ||||
| } | ||||
| .pure-button-toggle-middle { | ||||
|   border-radius: 0px; | ||||
|   margin-left: -5px; | ||||
|   border-left: 1px solid #CCC; | ||||
| } | ||||
| .pure-button-toggle-last { | ||||
|   border-top-left-radius: 0px; | ||||
|   border-bottom-left-radius: 0px; | ||||
|   margin-left: -5px; | ||||
|   border-left: 1px solid #CCC; | ||||
| } | ||||
|  | ||||
| .pure-button:disabled { | ||||
|   background-color: #CCC; | ||||
|   color: #999; | ||||
| } | ||||
|  | ||||
| #modal-body { | ||||
|   margin-bottom: 15px; | ||||
| } | ||||
|  | ||||
| #modal-overlay { | ||||
|   visibility: hidden; | ||||
|   position: absolute; | ||||
|   left: 0px; | ||||
|   top: 0px; | ||||
|   width: 100%; | ||||
|   height: 100%; | ||||
|   text-align: center; | ||||
|   z-index: 1000; | ||||
|   background-color: rgba(0, 0, 0, 0.5); | ||||
| } | ||||
|  | ||||
| #modal-overlay>div { | ||||
|   margin: 10% 10%; | ||||
|   width: 80%; | ||||
|   margin: 100px auto; | ||||
|   background-color: #FFF; | ||||
|   border: 1px solid #000; | ||||
|   border-radius: 10px; | ||||
|   padding: 15px; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| div#embiggenedScreenShot { | ||||
|   cursor: pointer; | ||||
|   background-color: #FFF; | ||||
| } | ||||
|  | ||||
| div#embiggenedScreenShot>img{ | ||||
|   max-width:100%; | ||||
|   max-height:100%; | ||||
| } | ||||
|  | ||||
| div.fullscreen { | ||||
|   height: 100%; | ||||
|   width: 100%; | ||||
|   position: absolute; | ||||
|   top: 0px; | ||||
|   left: 0px; | ||||
|   text-align: center; | ||||
|   margin: auto auto; | ||||
|   z-index:1001; | ||||
| } | ||||
|  | ||||
| .pure-form input[disabled]:not([type]).disabled-but-visible { | ||||
|   background-color: #FFF; | ||||
|   color: #555; | ||||
| } | ||||
| input.larger { | ||||
|   width: 400px; | ||||
| } | ||||
|  | ||||
| @media (min-width: 40em) { | ||||
|   #menu { | ||||
|     width: 150px; | ||||
|     height: 100%; | ||||
|     position: fixed; | ||||
|   } | ||||
|  | ||||
|   #menu .menu-button { | ||||
|     display: none; | ||||
|   } | ||||
|  | ||||
|   #menu .menu-container{ | ||||
|     display: initial; | ||||
|     width: 100%; | ||||
|   } | ||||
|  | ||||
|   .menu-bottom { | ||||
|     width: 150px; | ||||
|     position: fixed; | ||||
|     left: 0px; | ||||
|     bottom: 0px; | ||||
|   } | ||||
|  | ||||
|   .header { | ||||
|     margin-left: 150px; | ||||
|   } | ||||
|  | ||||
|   div.flash { | ||||
|     font-size: 24px; | ||||
|     position: fixed; | ||||
|     top: 0px; | ||||
|     width: 100%; | ||||
|   } | ||||
|  | ||||
|   div.flash.error { | ||||
|     background-color: #DD0000; | ||||
|     color: #FFFFFF; | ||||
|   } | ||||
|  | ||||
|   div.flash.success { | ||||
|     background-color: #229af9; | ||||
|     color: #FFFFFF; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* Small Screens */ | ||||
| @media screen and (max-width:35.5em) { | ||||
|   div.pure-control-group label.control-label { | ||||
|     text-align: left; | ||||
|   } | ||||
|   .only-large { | ||||
|     display: none; | ||||
|   } | ||||
|   .only-small { | ||||
|     display: default; | ||||
|   } | ||||
|   .team-management-buttons { | ||||
|     text-align:left; | ||||
|   } | ||||
|   .team-management-buttons>.pure-button { | ||||
|     margin-top: 1em; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* Starting at Small, horizontal */ | ||||
| @media screen and (min-width: 35.5em) { | ||||
|   div.pure-control-group label.control-label { | ||||
|     text-align: left; | ||||
|   } | ||||
|   .only-small { | ||||
|     display: none; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* Small Horizontal to Medium */ | ||||
| @media screen and (min-width: 35.5em) and (max-width: 48em) { } | ||||
|  | ||||
| /* Medium Screens */ | ||||
| @media screen and (min-width: 48em) and (max-width:64em) { } | ||||
|  | ||||
| /* Larger Screens */ | ||||
| @media (min-width: 64em) { } | ||||
|  | ||||
							
								
								
									
										7
									
								
								assets/vendor/css/grids-responsive-min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								assets/vendor/css/grids-responsive-min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										11
									
								
								assets/vendor/css/pure-min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								assets/vendor/css/pure-min.css
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										8
									
								
								buildplugins.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										8
									
								
								buildplugins.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| cd plugin_src | ||||
| for i in `ls`; do | ||||
|   go build -buildmode=plugin $i | ||||
| done | ||||
| cd .. | ||||
| mv plugin_src/*.so plugins/ | ||||
| @@ -1,91 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/gorilla/mux" | ||||
| ) | ||||
|  | ||||
| func handleApiComicsCall(w http.ResponseWriter, req *http.Request) { | ||||
| 	w.Header().Set("Content-Type", "application/json") | ||||
|  | ||||
| 	vars := mux.Vars(req) | ||||
| 	cid, cidok := vars["cid"] | ||||
| 	fn, fnok := vars["function"] | ||||
| 	switch req.Method { | ||||
| 	case "GET": | ||||
| 		if !cidok { | ||||
| 			handleApiListComics(w) | ||||
| 			return | ||||
| 		} | ||||
| 		if !fnok { | ||||
| 			handleApiShowComic(cid, w) | ||||
| 			return | ||||
| 		} | ||||
| 		switch fn { | ||||
| 		case "search": | ||||
| 			handleApiSearchComic(cid, w) | ||||
| 			return | ||||
|  | ||||
| 		default: | ||||
| 			userError(w) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 	default: | ||||
| 		userError(w) | ||||
| 		return | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func handleApiListComics(w http.ResponseWriter) { | ||||
| 	var res []byte | ||||
| 	var err error | ||||
| 	if res, err = json.Marshal(m.Comics); err != nil { | ||||
| 		serverError(w) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Fprint(w, string(res)) | ||||
| } | ||||
|  | ||||
| func handleApiSearchComic(nm string, w http.ResponseWriter) { | ||||
| 	nm = strings.ToLower(nm) | ||||
| 	var cs []Comic | ||||
| 	for _, c := range m.Comics { | ||||
| 		if strings.Contains(strings.ToLower(c.Name), nm) { | ||||
| 			cs = append(cs, c) | ||||
| 		} | ||||
| 	} | ||||
| 	var res []byte | ||||
| 	var err error | ||||
| 	if res, err = json.Marshal(cs); err != nil { | ||||
| 		serverError(w) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Fprint(w, string(res)) | ||||
| } | ||||
|  | ||||
| func handleApiShowComic(cid string, w http.ResponseWriter) { | ||||
| 	var err error | ||||
| 	var c *Comic | ||||
| 	pts := strings.Split(cid, ";") | ||||
| 	if len(pts) != 2 { | ||||
| 		userError(w) | ||||
| 		return | ||||
| 	} | ||||
| 	if c, err = m.GetComic(pts[0], pts[1]); err != nil { | ||||
| 		userError(w) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	var res []byte | ||||
| 	if res, err = json.Marshal(c); err != nil { | ||||
| 		userError(w) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Fprint(w, string(res)) | ||||
| 	return | ||||
| } | ||||
| @@ -125,7 +125,7 @@ func handleApiSubUser(uid string, slug string, w http.ResponseWriter) { | ||||
| 		userError(w) | ||||
| 		return | ||||
| 	} | ||||
| 	_, err = m.GetComic(pts[0], pts[1]) | ||||
| 	//_, err = m.GetComic(pts[0], pts[1]) | ||||
| 	if err != nil { | ||||
| 		userError(w) | ||||
| 		return | ||||
|   | ||||
| @@ -8,31 +8,30 @@ import ( | ||||
| ) | ||||
|  | ||||
| func handleRequest(w http.ResponseWriter, req *http.Request) { | ||||
| 	vars := mux.Vars(req) | ||||
| 	page := initPageData(w, req) | ||||
|  | ||||
| 	var fOk, uidOk bool | ||||
| 	var f, uid string | ||||
| 	f, fOk = vars["function"] | ||||
| 	uid, uidOk = vars["uid"] | ||||
| 	if !fOk || !uidOk { | ||||
| 		// Not sure what you want me to do here, Hoss. | ||||
| 		http.Error(w, "You did a bad", 400) | ||||
| 	//vars := mux.Vars(req) | ||||
| 	//var fOk, uidOk bool | ||||
| 	//var f, uid string | ||||
| 	//f, fOk = vars["function"] | ||||
| 	//uid, uidOk = vars[""] | ||||
| 	if !page.LoggedIn { | ||||
| 		handleUserLoginForm(page) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func handleRssFeed(w http.ResponseWriter, req *http.Request) { | ||||
| 	vars := mux.Vars(req) | ||||
| 	var uid string | ||||
| 	var uidOk bool | ||||
| 	if uid, uidOk = vars["uid"]; !uidOk { | ||||
| 		userError(w) | ||||
| 		return | ||||
| 	} | ||||
| 	switch f { | ||||
| 	case "rss": | ||||
| 		handleRssFeed(uid, w) | ||||
| 	default: | ||||
| 		http.Error(w, "You did a bad", 400) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func handleRssFeed(uid string, w http.ResponseWriter) { | ||||
| 	w.Header().Set("Content-Type", "application/xml") | ||||
|  | ||||
| 	v, err := buildRssFeed(uid) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 400) | ||||
| 		userError(w) | ||||
| 		return | ||||
| 	} | ||||
| 	fmt.Fprint(w, v) | ||||
|   | ||||
							
								
								
									
										69
									
								
								endpoints_user.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								endpoints_user.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/gorilla/mux" | ||||
| ) | ||||
|  | ||||
| func handleUserRequest(w http.ResponseWriter, req *http.Request) { | ||||
| 	page := initPageData(w, req) | ||||
|  | ||||
| 	vars := mux.Vars(req) | ||||
| 	f, fOk := vars["function"] | ||||
| 	if fOk && f == "login" { | ||||
| 		handleUserLogin(page) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if !page.LoggedIn { | ||||
| 		page.SubTitle = "ribbit login" | ||||
| 		page.show("login.html", w) | ||||
| 		return | ||||
| 	} | ||||
| 	page.SubTitle = "dashboard" | ||||
| 	page.show("user_dashboard.html", w) | ||||
| } | ||||
|  | ||||
| func handleUserLoginForm(page *pageData) { | ||||
| 	page.SubTitle = "ribbit login" | ||||
| 	page.show("login.html", page.session.w) | ||||
| } | ||||
|  | ||||
| func handleUserLogin(page *pageData) { | ||||
| 	var err error | ||||
| 	var u *User | ||||
| 	if page.LoggedIn { | ||||
| 		redirect("/", page.session.w, page.session.req) | ||||
| 	} | ||||
| 	email := page.session.req.FormValue("email") | ||||
|  | ||||
| 	if u, err = m.GetUserByEmail(email); err != nil { | ||||
| 		page.session.setFlashMessage("Invalid Login", "error") | ||||
| 		redirect("/", page.session.w, page.session.req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	password := page.session.req.FormValue("password") | ||||
| 	fmt.Println("Hitting func") | ||||
| 	if err := doLogin(u.Uuid, password); err != nil { | ||||
| 		fmt.Println("Login Failure: ", err.Error()) | ||||
| 		page.session.setFlashMessage("Invalid Login", "error") | ||||
| 	} else { | ||||
| 		page.session.setStringValue("id", u.Uuid) | ||||
| 	} | ||||
| 	redirect("/", page.session.w, page.session.req) | ||||
| } | ||||
|  | ||||
| // doLogin attempts to log in with the given email/password | ||||
| // If it can't, it returns an error | ||||
| func doLogin(uid, password string) error { | ||||
| 	fmt.Println("Doing Login", uid, password) | ||||
| 	if strings.TrimSpace(uid) != "" && strings.TrimSpace(password) != "" { | ||||
| 		return m.checkCredentials(uid, password) | ||||
| 	} | ||||
| 	return errors.New("Invalid Credentials") | ||||
| } | ||||
| @@ -1,74 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/PuerkitoBio/goquery" | ||||
| ) | ||||
|  | ||||
| func downloadXKCDList() []Comic { | ||||
| 	var ret []Comic | ||||
| 	ret = append(ret, *NewComic("xkcd", "XKCD", "Randall Munroe", "xkcd")) | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func getXKCDRssItem(slug string) (string, error) { | ||||
| 	desc, err := getXKCDFeedDesc(time.Now()) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	comic, err := m.GetComic(SRC_XKCD, slug) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	desc = "<![CDATA[" + desc + "]]>" | ||||
| 	ret := "    <item>\n" | ||||
| 	ret += "      <title>" + comic.Name + "</title>\n" | ||||
| 	ret += "      <pubDate>" + comic.LastUpdate.Format(time.RFC1123Z) + "</pubDate>\n" | ||||
| 	ret += "      <guid>xkcd;" + slug + ";" + comic.LastUpdate.Format(time.RFC1123Z) + "</guid>\n" | ||||
| 	ret += "      <link>" + getXKCDComicUrl(time.Now()) + "</link>\n" | ||||
| 	ret += "      <description>" + desc + "</description>\n" | ||||
| 	ret += "    </item>\n" | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| func getXKCDComicUrl(date time.Time) string { | ||||
| 	// TODO: Actually make this work correctly | ||||
| 	// Get the previous comic number | ||||
| 	// and find the next one | ||||
| 	return fmt.Sprintf( | ||||
| 		"http://xkcd.com/", | ||||
| 	) | ||||
| } | ||||
|  | ||||
| func getXKCDFeedDesc(date time.Time) (string, error) { | ||||
| 	res, err := http.Get(getXKCDComicUrl(date)) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	defer res.Body.Close() | ||||
| 	if res.StatusCode != 200 { | ||||
| 		return "", errors.New(fmt.Sprintf("Status code error: %d %s", res.StatusCode, res.Status)) | ||||
| 	} | ||||
|  | ||||
| 	// Load the HTML document | ||||
| 	doc, err := goquery.NewDocumentFromReader(res.Body) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	// Find the Picture | ||||
| 	sel := doc.Find("div#comic>img") | ||||
| 	src, exists := sel.Attr("src") | ||||
| 	if !exists { | ||||
| 		return "", errors.New("Couldn't find image source") | ||||
| 	} | ||||
| 	src = "https:" + src | ||||
| 	title, exists := sel.Attr("title") | ||||
| 	if !exists { | ||||
| 		title = "" | ||||
| 	} | ||||
| 	return "<img src=\"" + src + "\" /><p>" + title + "</p>", nil | ||||
| } | ||||
							
								
								
									
										89
									
								
								helpers.go
									
									
									
									
									
								
							
							
						
						
									
										89
									
								
								helpers.go
									
									
									
									
									
								
							| @@ -1,98 +1,9 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	SRC_GOCOMICS = "gocomics" | ||||
| 	SRC_DILBERT  = "dilbert" | ||||
| 	SRC_XKCD     = "xkcd" | ||||
| ) | ||||
|  | ||||
| func downloadComicsList() []Comic { | ||||
| 	var ret []Comic | ||||
| 	ret = append(ret, downloadGoComicsList()...) | ||||
| 	ret = append(ret, downloadDilbertList()...) | ||||
| 	ret = append(ret, downloadXKCDList()...) | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func getRssItem(source, slug string) (string, error) { | ||||
| 	switch source { | ||||
| 	case SRC_GOCOMICS: | ||||
| 		return getGoComicsRssItem(slug) | ||||
| 	case SRC_DILBERT: | ||||
| 		return getDilbertRssItem(slug) | ||||
| 	case SRC_XKCD: | ||||
| 		return getXKCDRssItem(slug) | ||||
| 	} | ||||
| 	return "", errors.New("Invalid source") | ||||
| } | ||||
|  | ||||
| func getComicUrl(source, slug string, dt time.Time) (string, error) { | ||||
| 	switch source { | ||||
| 	case SRC_GOCOMICS: | ||||
| 		return getGoComicsComicUrl(slug, dt), nil | ||||
| 	case SRC_DILBERT: | ||||
| 		return getDilbertComicUrl(dt), nil | ||||
| 	case SRC_XKCD: | ||||
| 		return getXKCDComicUrl(dt), nil | ||||
| 	} | ||||
| 	return "", errors.New("Invalid source") | ||||
| } | ||||
|  | ||||
| func getComicDesc(source, slug string, dt time.Time) (string, error) { | ||||
| 	switch source { | ||||
| 	case SRC_GOCOMICS: | ||||
| 		return getGoComicsFeedDesc(slug, dt) | ||||
| 	case SRC_DILBERT: | ||||
| 		return getDilbertFeedDesc(dt) | ||||
| 	case SRC_XKCD: | ||||
| 		return getXKCDFeedDesc(dt) | ||||
| 	} | ||||
| 	return "", errors.New("Unknown Comic Source") | ||||
| } | ||||
|  | ||||
| func buildRssFeed(uid string) (string, error) { | ||||
| 	var usr *User | ||||
| 	var err error | ||||
| 	if usr, err = m.GetUser(uid); err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	output := []string{ | ||||
| 		"<?xml version=\"1.0\"?>", | ||||
| 		"<rss version=\"2.0\">", | ||||
| 		"  <channel>", | ||||
| 		"    <title>BCW Comic Feed</title>", | ||||
| 		"    <link>http://ribbit.bullercodeworks.com/edit/" + uid + "</link>", | ||||
| 		"    <description>Comic feed for " + usr.Username + "</description>", | ||||
| 		"    <language>en-us</language>", | ||||
| 		"    <lastBuildDate>" + time.Now().Format(time.RFC1123) + "</lastBuildDate>", | ||||
| 		"    <ttl>40</ttl>", | ||||
| 	} | ||||
|  | ||||
| 	//date := time.Now() | ||||
| 	for _, slug := range usr.SubSlugs { | ||||
| 		pts := strings.Split(slug, ";") | ||||
| 		if len(pts) != 2 { | ||||
| 			continue | ||||
| 		} | ||||
| 		if comic, err := m.GetComic(pts[0], pts[1]); err == nil { | ||||
| 			output = append(output, comic.GetRssItem()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	output = append(output, []string{ | ||||
| 		"  </channel>", | ||||
| 		"</rss>", | ||||
| 	}...) | ||||
| 	return strings.Join(output, "\n"), nil | ||||
| } | ||||
|  | ||||
| func addStringIfUnique(st string, sl []string) []string { | ||||
| 	for i := range sl { | ||||
| 		if sl[i] == st { | ||||
|   | ||||
							
								
								
									
										233
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										233
									
								
								main.go
									
									
									
									
									
								
							| @@ -1,7 +1,11 @@ | ||||
| package main | ||||
|  | ||||
| //go:generate esc -o assets.go assets templates | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"fmt" | ||||
| 	"html/template" | ||||
| 	"log" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| @@ -9,17 +13,42 @@ import ( | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/gorilla/handlers" | ||||
| 	"github.com/gorilla/mux" | ||||
| 	"github.com/gorilla/sessions" | ||||
| 	"github.com/justinas/alice" | ||||
| 	"golang.org/x/crypto/ssh/terminal" | ||||
| ) | ||||
|  | ||||
| const AppName = "ribbit" | ||||
| const DbName = AppName + ".db" | ||||
|  | ||||
| // pageData is stuff that changes per request | ||||
| type pageData struct { | ||||
| 	Site          *SiteData | ||||
| 	Title         string | ||||
| 	SubTitle      string | ||||
| 	Stylesheets   []string | ||||
| 	HeaderScripts []string | ||||
| 	Scripts       []string | ||||
| 	FlashMessage  string | ||||
| 	FlashClass    string | ||||
| 	LoggedIn      bool | ||||
| 	IsAdmin       bool | ||||
| 	Menu          []menuItem | ||||
| 	BottomMenu    []menuItem | ||||
| 	session       *pageSession | ||||
|  | ||||
| 	TemplateData interface{} | ||||
| } | ||||
|  | ||||
| type menuItem struct { | ||||
| 	Label    string | ||||
| 	Location string | ||||
| 	Icon     string | ||||
| } | ||||
|  | ||||
| var sessionStore *sessions.CookieStore | ||||
| var r *mux.Router | ||||
| var m *model | ||||
| @@ -32,72 +61,24 @@ func main() { | ||||
| 		errorExit("Unable to initialize Model: " + err.Error()) | ||||
| 	} | ||||
|  | ||||
| 	if len(os.Args) > 2 { | ||||
| 		key, val := os.Args[1], os.Args[2] | ||||
| 		switch key { | ||||
| 		case "--add-user-sub": | ||||
| 			if len(os.Args) < 3 { | ||||
| 				errorExit("Usage: --add-user-sub <username> <comic-slug>\nFor a list of slugs, use --list-comics") | ||||
| 			} | ||||
| 			slug := os.Args[3] | ||||
| 			var u *User | ||||
| 			if u, err = m.GetUserByName(val); err != nil { | ||||
| 				errorExit("Couldn't find a user with the username " + val) | ||||
| 			} | ||||
| 			pts := strings.Split(slug, ";") | ||||
| 			if len(pts) != 2 { | ||||
| 				errorExit("Invalid slug given: " + slug) | ||||
| 			} | ||||
| 			_, err := m.GetComic(pts[0], pts[1]) | ||||
| 			if err != nil { | ||||
| 				errorExit("Couldn't find comic with slug: " + slug) | ||||
| 			} | ||||
| 			fmt.Println(u.SubSlugs) | ||||
| 			fmt.Println(slug) | ||||
| 			u.SubSlugs = append(u.SubSlugs, slug) | ||||
| 			fmt.Println(u.SubSlugs) | ||||
| 			m.SaveUser(u) | ||||
| 			done() | ||||
| 		default: | ||||
| 			errorExit("Unknown argument") | ||||
| 		} | ||||
| 	} else if len(os.Args) > 1 { | ||||
| 		switch os.Args[1] { | ||||
| 		case "--test": | ||||
| 			fmt.Println(buildRssFeed("30f14e57-6500-443c-8c77-f352788eacb0")) | ||||
| 			done() | ||||
| 		case "--list-comics": | ||||
| 			comics := m.GetAllComics() | ||||
| 			for _, c := range comics { | ||||
| 				fmt.Printf("[ %s;%s ] %s\n", c.Source, c.Slug, c.Name) | ||||
| 			} | ||||
| 			done() | ||||
| 		case "--update-feeds": | ||||
| 			fmt.Println("Updating User Feeds...") | ||||
| 			m.UpdateAllUserFeeds() | ||||
| 			fmt.Println("Done.") | ||||
| 			done() | ||||
| 		case "--update-comics": | ||||
| 			fmt.Println("Updating the Comics List...") | ||||
| 			comics := downloadComicsList() | ||||
| 			for _, c := range comics { | ||||
| 				fmt.Printf("Updating [ %s - %s, %s ]\n", c.Slug, c.Name, c.Artist) | ||||
| 				m.SaveComic(&c) | ||||
| 			} | ||||
| 			m.saveChanges() | ||||
| 			fmt.Println("Done.") | ||||
| 	initialize() | ||||
| 	sessionStore = sessions.NewCookieStore([]byte(m.Site.sessionSecret)) | ||||
|  | ||||
| 		default: | ||||
| 			errorExit("Unknown argument") | ||||
| 	for _, arg := range os.Args { | ||||
| 		switch arg { | ||||
| 		case "-dev": | ||||
| 			m.Site.DevMode = true | ||||
| 			fmt.Println("Running in Dev Mode") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	r = mux.NewRouter() | ||||
| 	r.StrictSlash(true) | ||||
| 	//r.PathPrefix("/assets/").Handler(http.FileServer()) | ||||
| 	r.PathPrefix("/assets/").Handler(http.FileServer(FS(m.Site.DevMode))) | ||||
|  | ||||
| 	pub := r.PathPrefix("/").Subrouter() | ||||
| 	pub.HandleFunc("/", handleRequest) | ||||
| 	pub.HandleFunc("/", handleUserRequest) | ||||
| 	/* | ||||
| 		pub.HandleFunc("/api", handleApiCall) | ||||
| 		pub.HandleFunc("/api/users", handleApiUsersCall) | ||||
| 		pub.HandleFunc("/api/users/{uid}", handleApiUsersCall) | ||||
| @@ -106,15 +87,15 @@ func main() { | ||||
| 		pub.HandleFunc("/api/comics", handleApiComicsCall) | ||||
| 		pub.HandleFunc("/api/comics/{cid}", handleApiComicsCall) | ||||
| 		pub.HandleFunc("/api/comics/{cid}/{function}", handleApiComicsCall) | ||||
| 	pub.HandleFunc("/{function}", handleRequest) | ||||
| 	pub.HandleFunc("/{function}/{uid}", handleRequest) | ||||
| 	pub.HandleFunc("/{function}/{uid}/{subfunc}", handleRequest) | ||||
| 	pub.HandleFunc("/{function}/{uid}/{subfunc}/{slug}", handleRequest) | ||||
| 	*/ | ||||
| 	pub.HandleFunc("/rss/{uid}", handleRssFeed) | ||||
| 	pub.HandleFunc("/user/{function}", handleUserRequest) | ||||
|  | ||||
| 	http.Handle("/", r) | ||||
| 	chain := alice.New(loggingHandler).Then(r) | ||||
|  | ||||
| 	// Refresh the DB at 2 AM | ||||
| 	/* | ||||
| 		go func() { | ||||
| 			for { | ||||
| 				if m.Site.LastSave.IsZero() || (time.Now().Day() != m.Site.LastSave.Day() && time.Now().Hour() == 2) { | ||||
| @@ -132,6 +113,7 @@ func main() { | ||||
| 				time.Sleep(time.Minute) | ||||
| 			} | ||||
| 		}() | ||||
| 	*/ | ||||
|  | ||||
| 	// Set up a channel to intercept Ctrl+C for graceful shutdowns | ||||
| 	c := make(chan os.Signal, 1) | ||||
| @@ -148,10 +130,97 @@ func main() { | ||||
| 	log.Fatal(http.ListenAndServe("127.0.0.1:"+strconv.Itoa(m.Site.Port), chain)) | ||||
| } | ||||
|  | ||||
| func buildRssFeed(uid string) (string, error) { | ||||
| 	return uid, nil | ||||
| } | ||||
|  | ||||
| func loggingHandler(h http.Handler) http.Handler { | ||||
| 	return handlers.LoggingHandler(os.Stdout, h) | ||||
| } | ||||
|  | ||||
| func initPageData(w http.ResponseWriter, req *http.Request) *pageData { | ||||
| 	if m.Site.DevMode { | ||||
| 		w.Header().Set("Cache-Control", "no-cache") | ||||
| 	} | ||||
| 	p := new(pageData) | ||||
|  | ||||
| 	// Get session | ||||
| 	var err error | ||||
| 	var s *sessions.Session | ||||
| 	if s, err = sessionStore.Get(req, m.Site.sessionSecret); err != nil { | ||||
| 		fmt.Println("Session error... Recreating.") | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 	} | ||||
| 	p.session = new(pageSession) | ||||
| 	p.session.session = s | ||||
| 	p.session.req = req | ||||
| 	p.session.w = w | ||||
|  | ||||
| 	// First check if we're logged in | ||||
| 	userId, _ := p.session.getStringValue("id") | ||||
| 	// With a valid account | ||||
| 	user, err := m.GetUser(userId) | ||||
| 	if err == nil { | ||||
| 		p.LoggedIn = true | ||||
| 		p.IsAdmin = user.IsAdmin | ||||
| 	} | ||||
|  | ||||
| 	p.Site = m.Site | ||||
| 	p.SubTitle = "ribbit" | ||||
| 	p.Stylesheets = make([]string, 0, 0) | ||||
| 	p.Stylesheets = append(p.Stylesheets, "/assets/vendor/css/pure-min.css") | ||||
| 	p.Stylesheets = append(p.Stylesheets, "/assets/vendor/css/grids-responsive-min.css") | ||||
| 	p.Stylesheets = append(p.Stylesheets, "/assets/css/main.css") | ||||
|  | ||||
| 	p.HeaderScripts = make([]string, 0, 0) | ||||
|  | ||||
| 	p.Scripts = make([]string, 0, 0) | ||||
|  | ||||
| 	p.FlashMessage, p.FlashClass = p.session.getFlashMessage() | ||||
| 	if p.FlashClass == "" { | ||||
| 		p.FlashClass = "hidden" | ||||
| 	} | ||||
|  | ||||
| 	// Build the menu | ||||
| 	if p.LoggedIn { | ||||
| 		p.BottomMenu = append(p.BottomMenu, menuItem{"Logout", "/user/logout", "fa-sign-out"}) | ||||
| 	} else { | ||||
| 		if p.IsAdmin { | ||||
| 			p.BottomMenu = append(p.BottomMenu, menuItem{"Admin", "/admin", "fa-sign-in"}) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| func (p *pageData) show(tmplName string, w http.ResponseWriter) error { | ||||
| 	for _, tmpl := range []string{ | ||||
| 		"htmlheader.html", | ||||
| 		"header.html", | ||||
| 		tmplName, | ||||
| 		"footer.html", | ||||
| 		"htmlfooter.html", | ||||
| 	} { | ||||
| 		if err := outputTemplate(tmpl, p, w); err != nil { | ||||
| 			fmt.Printf("%s\n", err) | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func outputTemplate(tmplName string, tmplData interface{}, w http.ResponseWriter) error { | ||||
| 	n := "/templates/" + tmplName | ||||
| 	l := template.Must(template.New("layout").Parse(FSMustString(m.Site.DevMode, n))) | ||||
| 	t := template.Must(l.Parse(FSMustString(m.Site.DevMode, n))) | ||||
| 	return t.Execute(w, tmplData) | ||||
| } | ||||
|  | ||||
| // redirect can be used only for GET redirects | ||||
| func redirect(url string, w http.ResponseWriter, req *http.Request) { | ||||
| 	http.Redirect(w, req, url, 303) | ||||
| } | ||||
|  | ||||
| func done() { | ||||
| 	os.Exit(0) | ||||
| } | ||||
| @@ -166,3 +235,41 @@ func assertError(err error) { | ||||
| 		panic(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func initialize() { | ||||
| 	// Check if we have an admin user already | ||||
| 	if !m.hasAdminUser() { | ||||
| 		// Nope, create one | ||||
| 		reader := bufio.NewReader(os.Stdin) | ||||
| 		fmt.Println("Create new Admin user") | ||||
| 		fmt.Print("Email: ") | ||||
| 		email, _ := reader.ReadString('\n') | ||||
| 		email = strings.TrimSpace(email) | ||||
| 		u := NewUser(email) | ||||
| 		var pw1, pw2 []byte | ||||
| 		for string(pw1) != string(pw2) || string(pw1) == "" { | ||||
| 			fmt.Print("Password: ") | ||||
| 			pw1, _ = terminal.ReadPassword(0) | ||||
| 			fmt.Println("") | ||||
| 			fmt.Print("Repeat Password: ") | ||||
| 			pw2, _ = terminal.ReadPassword(0) | ||||
| 			fmt.Println("") | ||||
| 			if string(pw1) != string(pw2) { | ||||
| 				fmt.Println("Entered Passwords don't match!") | ||||
| 			} | ||||
| 		} | ||||
| 		u.IsAdmin = true | ||||
| 		m.SaveUser(u) | ||||
| 		assertError(m.updateUserPassword(u.Uuid, string(pw1))) | ||||
| 	} | ||||
|  | ||||
| 	if m.Site.sessionSecret == "" { | ||||
| 		reader := bufio.NewReader(os.Stdin) | ||||
| 		fmt.Println("A good session secret is like a good password") | ||||
| 		fmt.Print("Create New Session Secret: ") | ||||
| 		sessSc, _ := reader.ReadString('\n') | ||||
| 		sessSc = strings.TrimSpace(sessSc) | ||||
| 		m.Site.sessionSecret = sessSc | ||||
| 		assertError(m.Site.SaveToDB()) | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										26
									
								
								model.go
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								model.go
									
									
									
									
									
								
							| @@ -14,7 +14,7 @@ type model struct { | ||||
| 	dbFileName string | ||||
|  | ||||
| 	Users []User | ||||
| 	Comics []Comic | ||||
| 	Feeds []Feed | ||||
|  | ||||
| 	Site *SiteData | ||||
| } | ||||
| @@ -31,11 +31,14 @@ func NewModel() (*model, error) { | ||||
| 	if err = m.initDB(); err != nil { | ||||
| 		return nil, errors.New("Unable to initialzie DB: " + err.Error()) | ||||
| 	} | ||||
| 	m.LoadSiteData() | ||||
| 	m.Site = NewSiteData(m) | ||||
| 	if err = m.Site.LoadFromDB(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if err = m.LoadUsers(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if err = m.LoadComics(); err != nil { | ||||
| 	if err = m.LoadFeeds(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| @@ -55,16 +58,15 @@ func (m *model) initDB() error { | ||||
| 	if err = m.bolt.MkBucketPath([]string{"users"}); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.MkBucketPath([]string{"comics"}); err != nil { | ||||
| 	if err = m.bolt.MkBucketPath([]string{"feeds"}); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *model) saveChanges() { | ||||
| 	m.Site.LastSave = time.Now() | ||||
| 	m.SaveSite() | ||||
| 	//m.SaveAllComics(m.Comics) | ||||
| 	m.Site.SaveToDB() | ||||
| 	//m.SaveAllFeeds(m.Feeds) | ||||
| 	m.SaveAllUsers(m.Users) | ||||
| } | ||||
|  | ||||
| @@ -76,14 +78,14 @@ func (m *model) UpdateAllUserFeeds() { | ||||
| 			allSubs = addStringIfUnique(sub, allSubs) | ||||
| 		} | ||||
| 	} | ||||
| 	// So we have allSubs which contains all subscribed comics for all users | ||||
| 	// So we have allSubs which contains all subscribed feeds for all users | ||||
| 	for _, sub := range allSubs { | ||||
| 		fmt.Println("Updating Comic: " + sub) | ||||
| 		fmt.Println("Updating Feed: " + sub) | ||||
| 		pts := strings.Split(sub, ";") | ||||
| 		if len(pts) != 2 { | ||||
| 			continue | ||||
| 		} | ||||
| 		c, err := m.GetComic(pts[0], pts[1]) | ||||
| 		c, err := m.GetFeed(pts[0], pts[1]) | ||||
| 		if err != nil { | ||||
| 			fmt.Println(sub, ":", err) | ||||
| 			continue | ||||
| @@ -92,7 +94,7 @@ func (m *model) UpdateAllUserFeeds() { | ||||
| 			fmt.Println(sub, ":", err.Error()) | ||||
| 			continue | ||||
| 		} | ||||
| 		if err = m.SaveComic(c); err != nil { | ||||
| 		if err = m.SaveFeed(c); err != nil { | ||||
| 			fmt.Println(sub, ":", err.Error()) | ||||
| 			continue | ||||
| 		} | ||||
| @@ -100,7 +102,7 @@ func (m *model) UpdateAllUserFeeds() { | ||||
| } | ||||
|  | ||||
| type Source interface { | ||||
| 	downloadList() []Comic | ||||
| 	downloadList() []Feed | ||||
| 	getRssItem(slug string) (string, error) | ||||
| 	getUrl(slug string, dt time.Time) (string, error) | ||||
| 	getDescription(slug string, dt time.Time) (string, error) | ||||
|   | ||||
							
								
								
									
										202
									
								
								model_comics.go
									
									
									
									
									
								
							
							
						
						
									
										202
									
								
								model_comics.go
									
									
									
									
									
								
							| @@ -1,202 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type Comic struct { | ||||
| 	Name       string | ||||
| 	Artist     string | ||||
| 	Slug       string | ||||
| 	Source     string | ||||
| 	Desc       string | ||||
| 	LastUpdate time.Time | ||||
| } | ||||
|  | ||||
| func NewComic(s, n, a, source string) *Comic { | ||||
| 	return &Comic{ | ||||
| 		Name:   n, | ||||
| 		Artist: a, | ||||
| 		Slug:   s, | ||||
| 		Source: source, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (c *Comic) GetBucket() []string { | ||||
| 	return []string{"comics", c.Source, c.Slug} | ||||
| } | ||||
|  | ||||
| func (c *Comic) Update() error { | ||||
| 	dt := time.Now() | ||||
| 	desc, err := getComicDesc(c.Source, c.Slug, dt) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if desc == c.Desc { | ||||
| 		return errors.New("Comic didn't change") | ||||
| 	} | ||||
| 	c.Desc = desc | ||||
| 	c.LastUpdate = dt | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (c *Comic) GetUrl(dt time.Time) string { | ||||
| 	var v string | ||||
| 	var e error | ||||
| 	if v, e = getComicUrl(c.Source, c.Slug, dt); e != nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (c *Comic) GetDesc(dt time.Time) string { | ||||
| 	var v string | ||||
| 	var e error | ||||
| 	if v, e = getComicDesc(c.Source, c.Slug, dt); e != nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (c *Comic) GetRssItem() string { | ||||
| 	var v string | ||||
| 	var e error | ||||
| 	if v, e = getRssItem(c.Source, c.Slug); e != nil { | ||||
| 		return "" | ||||
| 	} | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // DB Function to save a comic | ||||
| func (m *model) SaveComic(c *Comic) error { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	bkt := c.GetBucket() | ||||
| 	if err = m.bolt.MkBucketPath(bkt); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetValue(bkt, "name", c.Name); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetValue(bkt, "artist", c.Artist); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetValue(bkt, "desc", c.Desc); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetTimestamp(bkt, "lastupdate", c.LastUpdate); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// Add it to the cached list | ||||
| 	for i, v := range m.Comics { | ||||
| 		if v.Source == c.Source && v.Slug == c.Slug { | ||||
| 			m.Comics[i] = *c | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	m.Comics = append(m.Comics, *c) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DB Function to get a comic | ||||
| func (m *model) GetComic(source, slug string) (*Comic, error) { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	ret := new(Comic) | ||||
| 	ret.Source = source | ||||
| 	ret.Slug = slug | ||||
| 	bkt := ret.GetBucket() | ||||
| 	if ret.Name, err = m.bolt.GetValue(bkt, "name"); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if ret.Artist, err = m.bolt.GetValue(bkt, "artist"); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if ret.Desc, err = m.bolt.GetValue(bkt, "desc"); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if ret.LastUpdate, err = m.bolt.GetTimestamp(bkt, "lastupdate"); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| // Load all comics into the model | ||||
| func (m *model) LoadComics() error { | ||||
| 	m.Comics = m.GetAllComics() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Save all comics to the DB | ||||
| func (m *model) SaveAllComics(comics []Comic) { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	for i := range comics { | ||||
| 		m.SaveComic(&comics[i]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Get all comics from the db | ||||
| func (m *model) GetAllComics() []Comic { | ||||
| 	var ret []Comic | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return ret | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	var srcs []string | ||||
| 	bkt := []string{"comics"} | ||||
| 	if srcs, err = m.bolt.GetBucketList(bkt); err != nil { | ||||
| 		return ret | ||||
| 	} | ||||
| 	for _, src := range srcs { | ||||
| 		srcBkt := append(bkt, src) | ||||
| 		var slugs []string | ||||
| 		if slugs, err = m.bolt.GetBucketList(srcBkt); err != nil { | ||||
| 			return ret | ||||
| 		} | ||||
| 		for _, slg := range slugs { | ||||
| 			c, err := m.GetComic(src, slg) | ||||
| 			if err == nil { | ||||
| 				ret = append(ret, *c) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| // Delete a comic from the DB | ||||
| func (m *model) DeleteComic(slug string) error { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
|  | ||||
| 	return m.bolt.DeleteBucket([]string{"comics"}, slug) | ||||
| } | ||||
|  | ||||
| func (m *model) RemoveMissingComics(comics []Comic) { | ||||
| 	for _, c := range m.Comics { | ||||
| 		var fnd bool | ||||
| 		for _, nc := range comics { | ||||
| 			if nc.Slug == c.Slug { | ||||
| 				fnd = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if !fnd { | ||||
| 			m.DeleteComic(c.Slug) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										209
									
								
								model_feeds.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								model_feeds.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,209 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type Feed struct { | ||||
| 	Name       string | ||||
| 	Author     string | ||||
| 	Slug       string | ||||
| 	Source     string | ||||
| 	Desc       string | ||||
| 	LastUpdate time.Time | ||||
| } | ||||
|  | ||||
| func NewFeed(s, n, a, source string) *Feed { | ||||
| 	return &Feed{ | ||||
| 		Name:   n, | ||||
| 		Author: a, | ||||
| 		Slug:   s, | ||||
| 		Source: source, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (f *Feed) GetBucket() []string { | ||||
| 	return []string{"feed", f.Source, f.Slug} | ||||
| } | ||||
|  | ||||
| func (f *Feed) Update() error { | ||||
| 	/* | ||||
| 		dt := time.Now() | ||||
| 		desc, err := getFeedDesc(f.Source, f.Slug, dt) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		if desc == f.Desc { | ||||
| 			return errors.New("Feed didn't change") | ||||
| 		} | ||||
| 		f.Desc = desc | ||||
| 		f.LastUpdate = dt | ||||
| 	*/ | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (f *Feed) GetUrl(dt time.Time) string { | ||||
| 	var v string | ||||
| 	/* | ||||
| 		var e error | ||||
| 		if v, e = getFeedUrl(f.Source, f.Slug, dt); e != nil { | ||||
| 			return "" | ||||
| 		} | ||||
| 	*/ | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (f *Feed) GetDesc(dt time.Time) string { | ||||
| 	var v string | ||||
| 	/* | ||||
| 		var e error | ||||
| 		if v, e = getFeedDesc(f.Source, f.Slug, dt); e != nil { | ||||
| 			return "" | ||||
| 		} | ||||
| 	*/ | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| func (f *Feed) GetRssItem() string { | ||||
| 	var v string | ||||
| 	/* | ||||
| 		var e error | ||||
| 		if v, e = getRssItem(f.Source, f.Slug); e != nil { | ||||
| 			return "" | ||||
| 		} | ||||
| 	*/ | ||||
| 	return v | ||||
| } | ||||
|  | ||||
| // DB Function to save a feed | ||||
| func (m *model) SaveFeed(f *Feed) error { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	bkt := f.GetBucket() | ||||
| 	if err = m.bolt.MkBucketPath(bkt); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetValue(bkt, "name", f.Name); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetValue(bkt, "author", f.Author); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetValue(bkt, "desc", f.Desc); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetTimestamp(bkt, "lastupdate", f.LastUpdate); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	// Add it to the cached list | ||||
| 	for i, v := range m.Feeds { | ||||
| 		if v.Source == f.Source && v.Slug == f.Slug { | ||||
| 			m.Feeds[i] = *f | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	m.Feeds = append(m.Feeds, *f) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // DB Function to get a feed | ||||
| func (m *model) GetFeed(source, slug string) (*Feed, error) { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	ret := new(Feed) | ||||
| 	ret.Source = source | ||||
| 	ret.Slug = slug | ||||
| 	bkt := ret.GetBucket() | ||||
| 	if ret.Name, err = m.bolt.GetValue(bkt, "name"); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if ret.Author, err = m.bolt.GetValue(bkt, "author"); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if ret.Desc, err = m.bolt.GetValue(bkt, "desc"); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if ret.LastUpdate, err = m.bolt.GetTimestamp(bkt, "lastupdate"); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| // Load all feeds into the model | ||||
| func (m *model) LoadFeeds() error { | ||||
| 	m.Feeds = m.GetAllFeeds() | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Save all feeds to the DB | ||||
| func (m *model) SaveAllFeeds(feeds []Feed) { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	for i := range feeds { | ||||
| 		m.SaveFeed(&feeds[i]) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Get all feeds from the db | ||||
| func (m *model) GetAllFeeds() []Feed { | ||||
| 	var ret []Feed | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return ret | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	var srcs []string | ||||
| 	bkt := []string{"feeds"} | ||||
| 	if srcs, err = m.bolt.GetBucketList(bkt); err != nil { | ||||
| 		return ret | ||||
| 	} | ||||
| 	for _, src := range srcs { | ||||
| 		srcBkt := append(bkt, src) | ||||
| 		var slugs []string | ||||
| 		if slugs, err = m.bolt.GetBucketList(srcBkt); err != nil { | ||||
| 			return ret | ||||
| 		} | ||||
| 		for _, slg := range slugs { | ||||
| 			c, err := m.GetFeed(src, slg) | ||||
| 			if err == nil { | ||||
| 				ret = append(ret, *c) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| // Delete a feed from the DB | ||||
| func (m *model) DeleteFeed(slug string) error { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
|  | ||||
| 	return m.bolt.DeleteBucket([]string{"feeds"}, slug) | ||||
| } | ||||
|  | ||||
| func (m *model) RemoveMissingFeeds(feeds []Feed) { | ||||
| 	for _, c := range m.Feeds { | ||||
| 		var fnd bool | ||||
| 		for _, nc := range feeds { | ||||
| 			if nc.Slug == c.Slug { | ||||
| 				fnd = true | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 		if !fnd { | ||||
| 			m.DeleteFeed(c.Slug) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,86 +0,0 @@ | ||||
| package main | ||||
|  | ||||
| import "time" | ||||
|  | ||||
| type SiteData struct { | ||||
| 	Title         string | ||||
| 	Port          int | ||||
| 	SessionName   string | ||||
| 	ServerDir     string | ||||
| 	SessionSecret string | ||||
|  | ||||
| 	LastSave time.Time | ||||
| } | ||||
|  | ||||
| func NewSiteData() *SiteData { | ||||
| 	ret := new(SiteData) | ||||
| 	ret.Title = "BCW Comic Feed" | ||||
| 	ret.Port = 8080 | ||||
| 	ret.SessionName = "bcw-comic-feed" | ||||
| 	ret.ServerDir = "./" | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func (m *model) LoadSiteData() { | ||||
| 	m.Site = m.GetSite() | ||||
| } | ||||
|  | ||||
| func (m *model) SaveSite() error { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	bkt := []string{"site"} | ||||
| 	if err = m.bolt.SetValue(bkt, "title", m.Site.Title); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetInt(bkt, "port", m.Site.Port); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetValue(bkt, "session-name", m.Site.SessionName); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetValue(bkt, "session-secret", m.Site.SessionSecret); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetValue(bkt, "server-dir", m.Site.ServerDir); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = m.bolt.SetTimestamp(bkt, "last-save", m.Site.LastSave); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *model) GetSite() *SiteData { | ||||
| 	s := NewSiteData() | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return s | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	bkt := []string{"site"} | ||||
| 	var wrkStr string | ||||
| 	var wrkInt int | ||||
| 	var wrkTm time.Time | ||||
| 	if wrkStr, err = m.bolt.GetValue(bkt, "title"); err == nil { | ||||
| 		s.Title = wrkStr | ||||
| 	} | ||||
| 	if wrkInt, err = m.bolt.GetInt(bkt, "port"); err == nil { | ||||
| 		s.Port = wrkInt | ||||
| 	} | ||||
| 	if wrkStr, err = m.bolt.GetValue(bkt, "session-name"); err == nil { | ||||
| 		s.SessionName = wrkStr | ||||
| 	} | ||||
| 	if wrkStr, err = m.bolt.GetValue(bkt, "session-secret"); err == nil { | ||||
| 		s.SessionSecret = wrkStr | ||||
| 	} | ||||
| 	if wrkStr, err = m.bolt.GetValue(bkt, "server-dir"); err == nil { | ||||
| 		s.ServerDir = wrkStr | ||||
| 	} | ||||
| 	if wrkTm, err = m.bolt.GetTimestamp(bkt, "last-save"); err == nil { | ||||
| 		s.LastSave = wrkTm | ||||
| 	} | ||||
| 	return s | ||||
| } | ||||
							
								
								
									
										106
									
								
								model_sitedata.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								model_sitedata.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| /** | ||||
|  * SiteData | ||||
|  * Contains configuration for the website | ||||
|  */ | ||||
| type SiteData struct { | ||||
| 	Title       string | ||||
| 	Ip          string | ||||
| 	Port        int | ||||
| 	SessionName string | ||||
| 	ServerDir   string | ||||
|  | ||||
| 	DevMode bool | ||||
|  | ||||
| 	m        *model | ||||
| 	mPath    []string // The path in the db to this site data | ||||
| 	changed  bool | ||||
| 	lastSave time.Time | ||||
|  | ||||
| 	sessionSecret string | ||||
| } | ||||
|  | ||||
| // NewSiteData returns a SiteData object with the default values | ||||
| func NewSiteData(m *model) *SiteData { | ||||
| 	ret := new(SiteData) | ||||
| 	ret.Title = "ribbit" | ||||
| 	ret.Ip = "127.0.0.1" | ||||
| 	ret.Port = 8080 | ||||
| 	ret.SessionName = "good-ol-ribbit" | ||||
| 	ret.ServerDir = "./" | ||||
| 	ret.mPath = []string{"site"} | ||||
| 	ret.m = m | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| // load the site data out of the database | ||||
| // If fields don't exist in the DB, don't clobber what is already in s | ||||
| func (s *SiteData) LoadFromDB() error { | ||||
| 	if err := s.m.bolt.OpenDB(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer s.m.bolt.CloseDB() | ||||
|  | ||||
| 	if title, _ := s.m.bolt.GetValue(s.mPath, "title"); strings.TrimSpace(title) != "" { | ||||
| 		s.Title = title | ||||
| 	} | ||||
| 	if ip, err := s.m.bolt.GetValue(s.mPath, "ip"); err == nil { | ||||
| 		s.Ip = ip | ||||
| 	} | ||||
| 	if port, err := s.m.bolt.GetInt(s.mPath, "port"); err == nil { | ||||
| 		s.Port = port | ||||
| 	} | ||||
| 	if sessionName, _ := s.m.bolt.GetValue(s.mPath, "session-name"); strings.TrimSpace(sessionName) != "" { | ||||
| 		s.SessionName = sessionName | ||||
| 	} | ||||
| 	if serverDir, _ := s.m.bolt.GetValue(s.mPath, "server-dir"); strings.TrimSpace(serverDir) != "" { | ||||
| 		s.ServerDir = serverDir | ||||
| 	} | ||||
| 	s.changed = false | ||||
| 	if secret, _ := s.m.bolt.GetValue(s.mPath, "session-secret"); strings.TrimSpace(secret) != "" { | ||||
| 		s.sessionSecret = secret | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Return if the site data in memory has changed | ||||
| func (s *SiteData) NeedsSave() bool { | ||||
| 	return s.changed | ||||
| } | ||||
|  | ||||
| // Save the site data into the DB | ||||
| func (s *SiteData) SaveToDB() error { | ||||
| 	s.lastSave = time.Now() | ||||
| 	var err error | ||||
| 	if err = s.m.bolt.OpenDB(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer s.m.bolt.CloseDB() | ||||
|  | ||||
| 	if err = s.m.bolt.SetValue(s.mPath, "title", s.Title); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = s.m.bolt.SetValue(s.mPath, "ip", s.Ip); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = s.m.bolt.SetInt(s.mPath, "port", s.Port); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = s.m.bolt.SetValue(s.mPath, "session-name", s.SessionName); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err = s.m.bolt.SetValue(s.mPath, "server-dir", s.ServerDir); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	s.changed = false | ||||
| 	if err = s.m.bolt.SetValue(s.mPath, "session-secret", s.sessionSecret); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| @@ -6,12 +6,14 @@ import ( | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/pborman/uuid" | ||||
| 	"golang.org/x/crypto/bcrypt" | ||||
| ) | ||||
|  | ||||
| type User struct { | ||||
| 	Username string   `json:username` | ||||
| 	Uuid     string   `json:uuid` | ||||
| 	SubSlugs []string `json:subs` | ||||
| 	IsAdmin  bool     `json:admin` | ||||
| } | ||||
|  | ||||
| func NewUser(un string) *User { | ||||
| @@ -21,6 +23,7 @@ func NewUser(un string) *User { | ||||
| 	return u | ||||
| } | ||||
|  | ||||
| /* | ||||
| func (u *User) UpdateFeed() error { | ||||
| 	for _, slug := range u.SubSlugs { | ||||
| 		pts := strings.Split(slug, ";") | ||||
| @@ -33,6 +36,7 @@ func (u *User) UpdateFeed() error { | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| */ | ||||
|  | ||||
| func (m *model) SaveUser(u *User) error { | ||||
| 	var err error | ||||
| @@ -47,6 +51,13 @@ func (m *model) SaveUser(u *User) error { | ||||
| 	if err = m.bolt.SetValue(bkt, "username", u.Username); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	var adminVal = "false" | ||||
| 	if u.IsAdmin { | ||||
| 		adminVal = "true" | ||||
| 	} | ||||
| 	if err = m.bolt.SetValue(bkt, "admin", adminVal); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	var newSubs []string | ||||
| 	for _, v := range u.SubSlugs { | ||||
| 		if strings.TrimSpace(v) != "" { | ||||
| @@ -60,7 +71,39 @@ func (m *model) SaveUser(u *User) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (m *model) isValidUser(uid string) bool { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return false | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
| 	ret := new(User) | ||||
| 	bkt := []string{"users", uid} | ||||
| 	ret.Uuid = uid | ||||
| 	if ret.Username, err = m.bolt.GetValue(bkt, "username"); err != nil { | ||||
| 		fmt.Println("Error getting username value:", err.Error()) | ||||
| 		return false | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| func (m *model) GetUserByEmail(email string) (*User, error) { | ||||
| 	if email == "" { | ||||
| 		return nil, errors.New("No email given") | ||||
| 	} | ||||
| 	u := m.GetAllUsers() | ||||
| 	for i := range u { | ||||
| 		if u[i].Username == email { | ||||
| 			return &u[i], nil | ||||
| 		} | ||||
| 	} | ||||
| 	return nil, errors.New("No user found") | ||||
| } | ||||
|  | ||||
| func (m *model) GetUser(uid string) (*User, error) { | ||||
| 	if uid == "" { | ||||
| 		return nil, errors.New("No user id given") | ||||
| 	} | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return nil, err | ||||
| @@ -73,6 +116,8 @@ func (m *model) GetUser(uid string) (*User, error) { | ||||
| 		fmt.Println("Error getting username value:", err.Error()) | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	admin, _ := m.bolt.GetValue(bkt, "admin") | ||||
| 	ret.IsAdmin = (admin == "true") | ||||
| 	var subs string | ||||
| 	if subs, err = m.bolt.GetValue(bkt, "subs"); err != nil { | ||||
| 		return nil, err | ||||
| @@ -143,3 +188,50 @@ func (m *model) GetUserIdList() []string { | ||||
| 	ret, _ = m.bolt.GetBucketList(bkt) | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| // updateUserPassword | ||||
| // Takes a user id and a password | ||||
| // Fails if the user doesn't exist | ||||
| func (m *model) updateUserPassword(uid, password string) error { | ||||
| 	cryptPw, cryptError := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) | ||||
| 	if cryptError != nil { | ||||
| 		return cryptError | ||||
| 	} | ||||
| 	if _, err := m.GetUser(uid); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := m.bolt.OpenDB(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
|  | ||||
| 	usrPath := []string{"users", uid} | ||||
| 	return m.bolt.SetValue(usrPath, "password", string(cryptPw)) | ||||
| } | ||||
|  | ||||
| // Is the uid and pw given valid? | ||||
| func (m *model) checkCredentials(uid, pw string) error { | ||||
| 	var err error | ||||
| 	if err = m.bolt.OpenDB(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer m.bolt.CloseDB() | ||||
|  | ||||
| 	var uPw string | ||||
| 	usrPath := []string{"users", uid} | ||||
| 	if uPw, err = m.bolt.GetValue(usrPath, "password"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return bcrypt.CompareHashAndPassword([]byte(uPw), []byte(pw)) | ||||
| } | ||||
|  | ||||
| func (m *model) hasAdminUser() bool { | ||||
| 	users := m.GetAllUsers() | ||||
| 	for i := range users { | ||||
| 		if users[i].IsAdmin { | ||||
| 			return true | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
|  | ||||
| @@ -16,6 +17,9 @@ type pageSession struct { | ||||
| } | ||||
|  | ||||
| func (p *pageSession) getStringValue(key string) (string, error) { | ||||
| 	if p.session == nil { | ||||
| 		return "", errors.New("Session is nil") | ||||
| 	} | ||||
| 	val := p.session.Values[key] | ||||
| 	var retVal string | ||||
| 	var ok bool | ||||
|   | ||||
| @@ -1,5 +1,8 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"C" | ||||
| ) | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| @@ -9,33 +12,33 @@ import ( | ||||
| 	"github.com/PuerkitoBio/goquery" | ||||
| ) | ||||
| 
 | ||||
| func downloadDilbertList() []Comic { | ||||
| 	var ret []Comic | ||||
| 	ret = append(ret, *NewComic("dilbert", "Dilbert", "Scott Adams", "dilbert")) | ||||
| func getFeedList() []Feed { | ||||
| 	var ret []Feed | ||||
| 	ret = append(ret, *NewFeed("dilbert", "Dilbert", "Scott Adams", "dilbert")) | ||||
| 	return ret | ||||
| } | ||||
| 
 | ||||
| func getDilbertRssItem(slug string) (string, error) { | ||||
| 	desc, err := getDilbertFeedDesc(time.Now()) | ||||
| func getRssItem(slug string) (string, error) { | ||||
| 	desc, err := getFeedDesc(time.Now()) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	comic, err := m.GetComic(SRC_DILBERT, slug) | ||||
| 	feed, err := m.GetFeed(SRC_DILBERT, slug) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	desc = "<![CDATA[" + desc + "]]>" | ||||
| 	ret := "    <item>\n" | ||||
| 	ret += "      <title>" + comic.Name + "</title>\n" | ||||
| 	ret += "      <pubDate>" + comic.LastUpdate.Format(time.RFC1123Z) + "</pubDate>\n" | ||||
| 	ret += "      <guid>dilbert;" + slug + ";" + comic.LastUpdate.Format(time.RFC1123Z) + "</guid>\n" | ||||
| 	ret += "      <link>" + getDilbertComicUrl(time.Now()) + "</link>\n" | ||||
| 	ret += "      <title>" + feed.Name + "</title>\n" | ||||
| 	ret += "      <pubDate>" + feed.LastUpdate.Format(time.RFC1123Z) + "</pubDate>\n" | ||||
| 	ret += "      <guid>dilbert;" + slug + ";" + feed.LastUpdate.Format(time.RFC1123Z) + "</guid>\n" | ||||
| 	ret += "      <link>" + getFeedUrl(time.Now()) + "</link>\n" | ||||
| 	ret += "      <description>" + desc + "</description>\n" | ||||
| 	ret += "    </item>\n" | ||||
| 	return ret, nil | ||||
| } | ||||
| 
 | ||||
| func getDilbertComicUrl(date time.Time) string { | ||||
| func getFeedUrl(date time.Time) string { | ||||
| 	return fmt.Sprintf( | ||||
| 		"http://dilbert.com/strip/%4d-%02d-%02d", | ||||
| 		date.Year(), | ||||
| @@ -44,8 +47,8 @@ func getDilbertComicUrl(date time.Time) string { | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| func getDilbertFeedDesc(date time.Time) (string, error) { | ||||
| 	res, err := http.Get(getDilbertComicUrl(date)) | ||||
| func getFeedDesc(date time.Time) (string, error) { | ||||
| 	res, err := http.Get(getFeedUrl(date)) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| @@ -1,6 +1,7 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"C" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| @@ -10,8 +11,8 @@ import ( | ||||
| 	"github.com/PuerkitoBio/goquery" | ||||
| ) | ||||
| 
 | ||||
| func downloadGoComicsList() []Comic { | ||||
| 	var ret []Comic | ||||
| func downloadList() { | ||||
| 	var ret []Feed | ||||
| 	lstUrl := "http://www.gocomics.com/comics/a-to-z" | ||||
| 	res, err := http.Get(lstUrl) | ||||
| 	if err != nil { | ||||
| @@ -47,14 +48,14 @@ func downloadGoComicsList() []Comic { | ||||
| 			} | ||||
| 			author = strings.TrimPrefix(author, "By ") | ||||
| 			author = strings.Replace(author, "\u0026", "&", -1) | ||||
| 			ret = append(ret, *NewComic(slug, name, author, "gocomics")) | ||||
| 			ret = append(ret, *NewFeed(slug, name, author, "gocomics")) | ||||
| 		} | ||||
| 	}) | ||||
| 	return ret | ||||
| } | ||||
| 
 | ||||
| func getGoComicsRssItem(slug string) (string, error) { | ||||
| 	desc, err := getGoComicsFeedDesc(slug, time.Now()) | ||||
| func getRssItem(slug string) (string, error) { | ||||
| 	desc, err := getFeedDesc(slug, time.Now()) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| @@ -73,7 +74,7 @@ func getGoComicsRssItem(slug string) (string, error) { | ||||
| 	return ret, nil | ||||
| } | ||||
| 
 | ||||
| func getGoComicsComicUrl(slug string, date time.Time) string { | ||||
| func getFeedUrl(slug string, date time.Time) string { | ||||
| 	return fmt.Sprintf( | ||||
| 		"http://www.gocomics.com/%s/%04d/%02d/%02d", | ||||
| 		slug, | ||||
| @@ -83,8 +84,8 @@ func getGoComicsComicUrl(slug string, date time.Time) string { | ||||
| 	) | ||||
| } | ||||
| 
 | ||||
| func getGoComicsFeedDesc(slug string, date time.Time) (string, error) { | ||||
| 	res, err := http.Get(getGoComicsComicUrl(slug, date)) | ||||
| func getFeedDesc(slug string, date time.Time) (string, error) { | ||||
| 	res, err := http.Get(getFeedUrl(slug, date)) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
							
								
								
									
										84
									
								
								plugin_src/plugin_xkcd.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								plugin_src/plugin_xkcd.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"C" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/PuerkitoBio/goquery" | ||||
| ) | ||||
|  | ||||
| func getFeedList() []Feed { | ||||
| 	var ret []Feed | ||||
| 	ret = append(ret, *NewFeed("xkcd", "XKCD", "Randall Munroe", "xkcd")) | ||||
| 	return ret | ||||
| } | ||||
|  | ||||
| func getRssItem(slug string) (string, error) { | ||||
| 	desc, err := getFeedDesc(time.Now()) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	feed, err := m.GetFeed(SRC_XKCD, slug) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	desc = "<![CDATA[" + desc + "]]>" | ||||
| 	ret := "    <item>\n" | ||||
| 	ret += "      <title>" + feed.Name + "</title>\n" | ||||
| 	ret += "      <pubDate>" + feed.LastUpdate.Format(time.RFC1123Z) + "</pubDate>\n" | ||||
| 	ret += "      <guid>xkcd;" + slug + ";" + feed.LastUpdate.Format(time.RFC1123Z) + "</guid>\n" | ||||
| 	ret += "      <link>" + getUrl(time.Now()) + "</link>\n" | ||||
| 	ret += "      <description>" + desc + "</description>\n" | ||||
| 	ret += "    </item>\n" | ||||
| 	return ret, nil | ||||
| } | ||||
|  | ||||
| func getFeedUrl(date time.Time) (string, error) { | ||||
| 	var isComicDay = func(dt time.Time) bool { | ||||
| 		return dt.Weekday() == time.Monday || dt.Weekday() == time.Wednesday || dt.Weekday() == time.Friday | ||||
| 	} | ||||
| 	if !isComicDay(dt) { | ||||
| 		return "", errors.New("No URL for the given day") | ||||
| 	} | ||||
| 	var num int | ||||
| 	wrkDate := time.Date(2005, time.August, 19, 0, 0, 0, 0, time.UTC) | ||||
| 	for wrkDate.Before(dt) { | ||||
| 		if isComicDay(wrkDate) { | ||||
| 			num++ | ||||
| 		} | ||||
| 		wrkDate = wrkDate.Add(time.Hour * 24) | ||||
| 	} | ||||
| 	return fmt.Sprintf("https://xkcd.com/%d", num), nil | ||||
| } | ||||
|  | ||||
| func getFeedDesc(date time.Time) (string, error) { | ||||
| 	res, err := http.Get(getUrl(date)) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	defer res.Body.Close() | ||||
| 	if res.StatusCode != 200 { | ||||
| 		return "", errors.New(fmt.Sprintf("Status code error: %d %s", res.StatusCode, res.Status)) | ||||
| 	} | ||||
|  | ||||
| 	// Load the HTML document | ||||
| 	doc, err := goquery.NewDocumentFromReader(res.Body) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	// Find the Picture | ||||
| 	sel := doc.Find("div#comic>img") | ||||
| 	src, exists := sel.Attr("src") | ||||
| 	if !exists { | ||||
| 		return "", errors.New("Couldn't find image source") | ||||
| 	} | ||||
| 	src = "https:" + src | ||||
| 	title, exists := sel.Attr("title") | ||||
| 	if !exists { | ||||
| 		title = "" | ||||
| 	} | ||||
| 	return "<img src=\"" + src + "\" /><p>" + title + "</p>", nil | ||||
| } | ||||
							
								
								
									
										11
									
								
								templates/footer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								templates/footer.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| </div> | ||||
| <div id="modal-overlay"> | ||||
|   <div> | ||||
|     <h1 id="modal-title"></h1> | ||||
|     <h2 id="modal-subtitle"></h2> | ||||
|     <div id="modal-body"></div> | ||||
|     <div id="modal-buttons"> | ||||
|     </div> | ||||
|     <div class="reset-pull"></div> | ||||
|   </div> | ||||
| </div> | ||||
							
								
								
									
										9
									
								
								templates/header.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								templates/header.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| <div class="flash center {{.FlashClass}}"> | ||||
|   {{.FlashMessage}} | ||||
| </div> | ||||
| <div class="content center-all"> | ||||
|   {{ if .SubTitle }} | ||||
|   <div class="header-menu"> | ||||
|     <h2>{{.SubTitle}}</h2> | ||||
|   </div> | ||||
|   {{ end }} | ||||
							
								
								
									
										6
									
								
								templates/htmlfooter.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								templates/htmlfooter.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
|     </div> | ||||
|     {{ range $i, $v := .Scripts }} | ||||
|     <script src="{{ $v }}"></script> | ||||
|     {{ end }} | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										25
									
								
								templates/htmlheader.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								templates/htmlheader.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <meta name="description" content=""> | ||||
|     <meta http-equiv="Cache-control" content="No-Cache"> | ||||
|     <link rel="apple-touch-icon" href="/assets/img/favicon.png" type="image/png"> | ||||
|     <link rel="shortcut icon" href="/assets/img/favicon.png" type="image/png"> | ||||
|  | ||||
|     <title>{{.Site.Title}} - {{.SubTitle}}</title> | ||||
|  | ||||
| <!--[if lt IE 9]> | ||||
|     <script src="http://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7/html5shiv.js"></script> | ||||
| <![endif]--> | ||||
|  | ||||
|     {{ range $i, $v := .Stylesheets }} | ||||
|     <link rel="stylesheet" href="{{ $v }}"> | ||||
|     {{ end }} | ||||
|     {{ range $i, $v := .HeaderScripts }} | ||||
|     <script src="{{ $v }}"></script> | ||||
|     {{ end }} | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="layout"> | ||||
							
								
								
									
										22
									
								
								templates/login.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								templates/login.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| <div class="half center-all"> | ||||
|   <form class="pure-form pure-form-aligned" action="/user/login" method="POST"> | ||||
|     <fieldset> | ||||
|       <div class="pure-control-group"> | ||||
|         <label for="email">Email Address</label> | ||||
|         <input id="email" name="email" type="text" placeholder="Email Address" autofocus> | ||||
|       </div> | ||||
|  | ||||
|       <div class="pure-control-group"> | ||||
|         <label for="password">Password</label> | ||||
|         <input id="password" name="password" type="password" placeholder="Password"> | ||||
|       </div> | ||||
|  | ||||
|       <div class="pure-controls"> | ||||
|         <label for="remember" class="pure-checkbox"> | ||||
|           <input id="remember" name="remember" type="checkbox"> Remember Me | ||||
|         </label> | ||||
|       </div> | ||||
|       <button type="submit" class="pure-button pure-button-primary">Submit</button> | ||||
|     </fieldset> | ||||
|   </form> | ||||
| </div> | ||||
							
								
								
									
										0
									
								
								templates/user_dashboard.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								templates/user_dashboard.html
									
									
									
									
									
										Normal file
									
								
							
		Reference in New Issue
	
	Block a user