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: ` | ||||||
|  | H4sIAAAAAAAC/7RXW2vcOBR+n18hCIWkVK4nzTSJA2GhF/Zh96mPpQ+yJdsismRkeTJpyX9fdLMlW54u | ||||||
|  | CwsJjHUuOpfvXNSqjoFfOwA6ymFLaNOqAuzz/M3D7nW3KwV+MdRKMCELcHF7e/uwxYwMpyInBTGphESK | ||||||
|  | Cl4ALjgxdEyPWSW4IlwZzh5hTHlTgP2hP6W1TheXDFVPk5YWsdqoeKZYtQU4OAuyliBMpPUHyYZyyEit | ||||||
|  | CpBbco3gtSHWgis40J+kANeks8Re0g7J2N08v70jtxEdlo1hKVH11EgxcgxT3ERKISNdnz/neZ4H1DOa | ||||||
|  | Al7tr+h1KBGrKWE48G62fkd5P6qspozEsc0OpAPZR80GAKZDz9BLAShnlBNYMqGjCkApJCayAPv+BAbB | ||||||
|  | KAYXVVVZygkOLcLiWUsNRIHccH3oT+ACYzxLQ4kwHYcC3OhsvnqTJOJPlDfQfMVJs1lfSB/C08iiT58+ | ||||||
|  | PXiAIUYbXoCKcEWkvS3rxJFAWgluwz7KQceSD1ASnWrDddERPoZWeJx53N04q3oxUItfSRhS9EgeolQV | ||||||
|  | 4GJ/v7/b3+njZ1I+UQXFkciaiWc4VFIwZuKvxFi1wdVZP0oC9U+owUq5xcBGZlJiXGiWp1RZxlnfL5I+ | ||||||
|  | ZXujRAF4bqkicOhRRfThs0R9aIO5vhyVciFeGB2HDZWDYKMyp0r0BXCBlTbO7stVqWHYx2fSNwJ76lw1 | ||||||
|  | Jj6c7TPOTqGU6KJULxBnL53w5TQv3NUNC1HumsrksY9Y2t/YR3OPx1Wi2D2Otm5+HNlWnwhETV8Ukv7U | ||||||
|  | YsxB0MhNsDwVwB7bgumaTLVjV3JELWPQe30mTquGrM+i2tERn/RsxWvC3lJdwqkvX74EYLZpus5dU8nc | ||||||
|  | cdzlDp5qsBt1yJgEB4rJsB4QhxT0FqJHIhWtEAuljXGxsAVeIM2QbMhy7OzvPLmkDVybvZ8c1gbOk9U1 | ||||||
|  | Pn2oyZkxdUU2p4ZuO+SKIWicjgUiB5cUGwDe+TXFW4xGJYxChUpG/MVKrxCPCp+zwAr4xDreVW4NF1C1 | ||||||
|  | ENbfdBXbKWGAojum7VZwnsZnhu3cTr9+/brWoUTTMAJrKoelARYw0wiL24zFQ4rlNXlBRzF2U3wxGuP+ | ||||||
|  | aIELo3npDtfhSF3E0NoRLX/ejxTHf7EoMqnAdNDpxVs5crPff97f37t+KTBicFpTFyW499hxfLoTMmRZ | ||||||
|  | j3SgJWVUvRSgpRgTvt3P3RIZtHP3e2OJ8N/pUvoJKcfkZNjydA+UTYku83fA/WWHq4Qbj5geFz3jjf4P | ||||||
|  | DLvzPdsz5P3JFWoyzAb4yeXLmxoj0g/n1Sq/UemYHi9IV9KmIZzgb5UkhH9rXUH7ja0X1Idqy8Qzqh5p | ||||||
|  | 1/yK5lQ4uVyGpsmlp2Y9MjYY+eUUfJPI8e83nBgtSQiEPXPKh8fFPs/3QYXUQnbAbM/ffZX8KLhQl9/V | ||||||
|  | S09+XGX+VNcSNLD27WMzwf7zcDjom+ymbuaUDDemm9xPoT86gikCl/qJNhFJd2W456V6vWut4xmGsKYn | ||||||
|  | Yp4Qr7tJz2rNTKxdK/Zp7VgIUE4VRewhss0ZYpSslsWUCwmDF2nWlWF7jjuwyoP36KJLTtoNo4EhQ0Pr | ||||||
|  | WMP36c1ZK0LgpT2cdAdv0t/MwWgSOsgsdA1jVZFh2NZ2fX2P6vttba+73fu34Fun1w5bvQN4+94DzdUj | ||||||
|  | 4hhczpX84aAfNldutTza+tCpl4JBfX8PGCoJy/yZ+XI2rtcnbQYAmeDsBc5LWhJunm3o/JoUsGFSo5HN | ||||||
|  | ChVBHewQRw3pCFcOy8PajsiMDanHcE7GSLKrj33lTQFVSCq9RiFlg/sOzK+CrQDPRf0/RjgZujnCIR7+ | ||||||
|  | nC1WAvxNMB27f2/7AjTg5s54BOwNTttvIBe0ubuEzo83ocq/bONcqwz1BCL/BAAA///ACu/RdBMAAA== | ||||||
|  | `, | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	"/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) | 		userError(w) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	_, err = m.GetComic(pts[0], pts[1]) | 	//_, err = m.GetComic(pts[0], pts[1]) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		userError(w) | 		userError(w) | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -8,31 +8,30 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func handleRequest(w http.ResponseWriter, req *http.Request) { | func handleRequest(w http.ResponseWriter, req *http.Request) { | ||||||
| 	vars := mux.Vars(req) | 	page := initPageData(w, req) | ||||||
|  |  | ||||||
| 	var fOk, uidOk bool | 	//vars := mux.Vars(req) | ||||||
| 	var f, uid string | 	//var fOk, uidOk bool | ||||||
| 	f, fOk = vars["function"] | 	//var f, uid string | ||||||
| 	uid, uidOk = vars["uid"] | 	//f, fOk = vars["function"] | ||||||
| 	if !fOk || !uidOk { | 	//uid, uidOk = vars[""] | ||||||
| 		// Not sure what you want me to do here, Hoss. | 	if !page.LoggedIn { | ||||||
| 		http.Error(w, "You did a bad", 400) | 		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 | 		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") | 	w.Header().Set("Content-Type", "application/xml") | ||||||
|  |  | ||||||
| 	v, err := buildRssFeed(uid) | 	v, err := buildRssFeed(uid) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		http.Error(w, err.Error(), 400) | 		userError(w) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	fmt.Fprint(w, v) | 	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 | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" |  | ||||||
| 	"net/http" | 	"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 { | func addStringIfUnique(st string, sl []string) []string { | ||||||
| 	for i := range sl { | 	for i := range sl { | ||||||
| 		if sl[i] == st { | 		if sl[i] == st { | ||||||
|   | |||||||
							
								
								
									
										233
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										233
									
								
								main.go
									
									
									
									
									
								
							| @@ -1,7 +1,11 @@ | |||||||
| package main | package main | ||||||
|  |  | ||||||
|  | //go:generate esc -o assets.go assets templates | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"bufio" | ||||||
| 	"fmt" | 	"fmt" | ||||||
|  | 	"html/template" | ||||||
| 	"log" | 	"log" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"os" | 	"os" | ||||||
| @@ -9,17 +13,42 @@ import ( | |||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 	"syscall" | 	"syscall" | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/gorilla/handlers" | 	"github.com/gorilla/handlers" | ||||||
| 	"github.com/gorilla/mux" | 	"github.com/gorilla/mux" | ||||||
| 	"github.com/gorilla/sessions" | 	"github.com/gorilla/sessions" | ||||||
| 	"github.com/justinas/alice" | 	"github.com/justinas/alice" | ||||||
|  | 	"golang.org/x/crypto/ssh/terminal" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const AppName = "ribbit" | const AppName = "ribbit" | ||||||
| const DbName = AppName + ".db" | 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 sessionStore *sessions.CookieStore | ||||||
| var r *mux.Router | var r *mux.Router | ||||||
| var m *model | var m *model | ||||||
| @@ -32,72 +61,24 @@ func main() { | |||||||
| 		errorExit("Unable to initialize Model: " + err.Error()) | 		errorExit("Unable to initialize Model: " + err.Error()) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(os.Args) > 2 { | 	initialize() | ||||||
| 		key, val := os.Args[1], os.Args[2] | 	sessionStore = sessions.NewCookieStore([]byte(m.Site.sessionSecret)) | ||||||
| 		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.") |  | ||||||
|  |  | ||||||
| 		default: | 	for _, arg := range os.Args { | ||||||
| 			errorExit("Unknown argument") | 		switch arg { | ||||||
|  | 		case "-dev": | ||||||
|  | 			m.Site.DevMode = true | ||||||
|  | 			fmt.Println("Running in Dev Mode") | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	r = mux.NewRouter() | 	r = mux.NewRouter() | ||||||
| 	r.StrictSlash(true) | 	r.StrictSlash(true) | ||||||
| 	//r.PathPrefix("/assets/").Handler(http.FileServer()) | 	r.PathPrefix("/assets/").Handler(http.FileServer(FS(m.Site.DevMode))) | ||||||
|  |  | ||||||
| 	pub := r.PathPrefix("/").Subrouter() | 	pub := r.PathPrefix("/").Subrouter() | ||||||
| 	pub.HandleFunc("/", handleRequest) | 	pub.HandleFunc("/", handleUserRequest) | ||||||
|  | 	/* | ||||||
| 		pub.HandleFunc("/api", handleApiCall) | 		pub.HandleFunc("/api", handleApiCall) | ||||||
| 		pub.HandleFunc("/api/users", handleApiUsersCall) | 		pub.HandleFunc("/api/users", handleApiUsersCall) | ||||||
| 		pub.HandleFunc("/api/users/{uid}", handleApiUsersCall) | 		pub.HandleFunc("/api/users/{uid}", handleApiUsersCall) | ||||||
| @@ -106,15 +87,15 @@ func main() { | |||||||
| 		pub.HandleFunc("/api/comics", handleApiComicsCall) | 		pub.HandleFunc("/api/comics", handleApiComicsCall) | ||||||
| 		pub.HandleFunc("/api/comics/{cid}", handleApiComicsCall) | 		pub.HandleFunc("/api/comics/{cid}", handleApiComicsCall) | ||||||
| 		pub.HandleFunc("/api/comics/{cid}/{function}", handleApiComicsCall) | 		pub.HandleFunc("/api/comics/{cid}/{function}", handleApiComicsCall) | ||||||
| 	pub.HandleFunc("/{function}", handleRequest) | 	*/ | ||||||
| 	pub.HandleFunc("/{function}/{uid}", handleRequest) | 	pub.HandleFunc("/rss/{uid}", handleRssFeed) | ||||||
| 	pub.HandleFunc("/{function}/{uid}/{subfunc}", handleRequest) | 	pub.HandleFunc("/user/{function}", handleUserRequest) | ||||||
| 	pub.HandleFunc("/{function}/{uid}/{subfunc}/{slug}", handleRequest) |  | ||||||
|  |  | ||||||
| 	http.Handle("/", r) | 	http.Handle("/", r) | ||||||
| 	chain := alice.New(loggingHandler).Then(r) | 	chain := alice.New(loggingHandler).Then(r) | ||||||
|  |  | ||||||
| 	// Refresh the DB at 2 AM | 	// Refresh the DB at 2 AM | ||||||
|  | 	/* | ||||||
| 		go func() { | 		go func() { | ||||||
| 			for { | 			for { | ||||||
| 				if m.Site.LastSave.IsZero() || (time.Now().Day() != m.Site.LastSave.Day() && time.Now().Hour() == 2) { | 				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) | 				time.Sleep(time.Minute) | ||||||
| 			} | 			} | ||||||
| 		}() | 		}() | ||||||
|  | 	*/ | ||||||
|  |  | ||||||
| 	// Set up a channel to intercept Ctrl+C for graceful shutdowns | 	// Set up a channel to intercept Ctrl+C for graceful shutdowns | ||||||
| 	c := make(chan os.Signal, 1) | 	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)) | 	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 { | func loggingHandler(h http.Handler) http.Handler { | ||||||
| 	return handlers.LoggingHandler(os.Stdout, h) | 	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() { | func done() { | ||||||
| 	os.Exit(0) | 	os.Exit(0) | ||||||
| } | } | ||||||
| @@ -166,3 +235,41 @@ func assertError(err error) { | |||||||
| 		panic(err) | 		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 | 	dbFileName string | ||||||
|  |  | ||||||
| 	Users []User | 	Users []User | ||||||
| 	Comics []Comic | 	Feeds []Feed | ||||||
|  |  | ||||||
| 	Site *SiteData | 	Site *SiteData | ||||||
| } | } | ||||||
| @@ -31,11 +31,14 @@ func NewModel() (*model, error) { | |||||||
| 	if err = m.initDB(); err != nil { | 	if err = m.initDB(); err != nil { | ||||||
| 		return nil, errors.New("Unable to initialzie DB: " + err.Error()) | 		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 { | 	if err = m.LoadUsers(); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	if err = m.LoadComics(); err != nil { | 	if err = m.LoadFeeds(); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| @@ -55,16 +58,15 @@ func (m *model) initDB() error { | |||||||
| 	if err = m.bolt.MkBucketPath([]string{"users"}); err != nil { | 	if err = m.bolt.MkBucketPath([]string{"users"}); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	if err = m.bolt.MkBucketPath([]string{"comics"}); err != nil { | 	if err = m.bolt.MkBucketPath([]string{"feeds"}); err != nil { | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func (m *model) saveChanges() { | func (m *model) saveChanges() { | ||||||
| 	m.Site.LastSave = time.Now() | 	m.Site.SaveToDB() | ||||||
| 	m.SaveSite() | 	//m.SaveAllFeeds(m.Feeds) | ||||||
| 	//m.SaveAllComics(m.Comics) |  | ||||||
| 	m.SaveAllUsers(m.Users) | 	m.SaveAllUsers(m.Users) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -76,14 +78,14 @@ func (m *model) UpdateAllUserFeeds() { | |||||||
| 			allSubs = addStringIfUnique(sub, allSubs) | 			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 { | 	for _, sub := range allSubs { | ||||||
| 		fmt.Println("Updating Comic: " + sub) | 		fmt.Println("Updating Feed: " + sub) | ||||||
| 		pts := strings.Split(sub, ";") | 		pts := strings.Split(sub, ";") | ||||||
| 		if len(pts) != 2 { | 		if len(pts) != 2 { | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		c, err := m.GetComic(pts[0], pts[1]) | 		c, err := m.GetFeed(pts[0], pts[1]) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			fmt.Println(sub, ":", err) | 			fmt.Println(sub, ":", err) | ||||||
| 			continue | 			continue | ||||||
| @@ -92,7 +94,7 @@ func (m *model) UpdateAllUserFeeds() { | |||||||
| 			fmt.Println(sub, ":", err.Error()) | 			fmt.Println(sub, ":", err.Error()) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if err = m.SaveComic(c); err != nil { | 		if err = m.SaveFeed(c); err != nil { | ||||||
| 			fmt.Println(sub, ":", err.Error()) | 			fmt.Println(sub, ":", err.Error()) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| @@ -100,7 +102,7 @@ func (m *model) UpdateAllUserFeeds() { | |||||||
| } | } | ||||||
|  |  | ||||||
| type Source interface { | type Source interface { | ||||||
| 	downloadList() []Comic | 	downloadList() []Feed | ||||||
| 	getRssItem(slug string) (string, error) | 	getRssItem(slug string) (string, error) | ||||||
| 	getUrl(slug string, dt time.Time) (string, error) | 	getUrl(slug string, dt time.Time) (string, error) | ||||||
| 	getDescription(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" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/pborman/uuid" | 	"github.com/pborman/uuid" | ||||||
|  | 	"golang.org/x/crypto/bcrypt" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| type User struct { | type User struct { | ||||||
| 	Username string   `json:username` | 	Username string   `json:username` | ||||||
| 	Uuid     string   `json:uuid` | 	Uuid     string   `json:uuid` | ||||||
| 	SubSlugs []string `json:subs` | 	SubSlugs []string `json:subs` | ||||||
|  | 	IsAdmin  bool     `json:admin` | ||||||
| } | } | ||||||
|  |  | ||||||
| func NewUser(un string) *User { | func NewUser(un string) *User { | ||||||
| @@ -21,6 +23,7 @@ func NewUser(un string) *User { | |||||||
| 	return u | 	return u | ||||||
| } | } | ||||||
|  |  | ||||||
|  | /* | ||||||
| func (u *User) UpdateFeed() error { | func (u *User) UpdateFeed() error { | ||||||
| 	for _, slug := range u.SubSlugs { | 	for _, slug := range u.SubSlugs { | ||||||
| 		pts := strings.Split(slug, ";") | 		pts := strings.Split(slug, ";") | ||||||
| @@ -33,6 +36,7 @@ func (u *User) UpdateFeed() error { | |||||||
| 	} | 	} | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  | */ | ||||||
|  |  | ||||||
| func (m *model) SaveUser(u *User) error { | func (m *model) SaveUser(u *User) error { | ||||||
| 	var err 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 { | 	if err = m.bolt.SetValue(bkt, "username", u.Username); err != nil { | ||||||
| 		return err | 		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 | 	var newSubs []string | ||||||
| 	for _, v := range u.SubSlugs { | 	for _, v := range u.SubSlugs { | ||||||
| 		if strings.TrimSpace(v) != "" { | 		if strings.TrimSpace(v) != "" { | ||||||
| @@ -60,7 +71,39 @@ func (m *model) SaveUser(u *User) error { | |||||||
| 	return nil | 	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) { | func (m *model) GetUser(uid string) (*User, error) { | ||||||
|  | 	if uid == "" { | ||||||
|  | 		return nil, errors.New("No user id given") | ||||||
|  | 	} | ||||||
| 	var err error | 	var err error | ||||||
| 	if err = m.bolt.OpenDB(); err != nil { | 	if err = m.bolt.OpenDB(); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -73,6 +116,8 @@ func (m *model) GetUser(uid string) (*User, error) { | |||||||
| 		fmt.Println("Error getting username value:", err.Error()) | 		fmt.Println("Error getting username value:", err.Error()) | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  | 	admin, _ := m.bolt.GetValue(bkt, "admin") | ||||||
|  | 	ret.IsAdmin = (admin == "true") | ||||||
| 	var subs string | 	var subs string | ||||||
| 	if subs, err = m.bolt.GetValue(bkt, "subs"); err != nil { | 	if subs, err = m.bolt.GetValue(bkt, "subs"); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -143,3 +188,50 @@ func (m *model) GetUserIdList() []string { | |||||||
| 	ret, _ = m.bolt.GetBucketList(bkt) | 	ret, _ = m.bolt.GetBucketList(bkt) | ||||||
| 	return ret | 	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 | package main | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  |  | ||||||
| @@ -16,6 +17,9 @@ type pageSession struct { | |||||||
| } | } | ||||||
|  |  | ||||||
| func (p *pageSession) getStringValue(key string) (string, error) { | func (p *pageSession) getStringValue(key string) (string, error) { | ||||||
|  | 	if p.session == nil { | ||||||
|  | 		return "", errors.New("Session is nil") | ||||||
|  | 	} | ||||||
| 	val := p.session.Values[key] | 	val := p.session.Values[key] | ||||||
| 	var retVal string | 	var retVal string | ||||||
| 	var ok bool | 	var ok bool | ||||||
|   | |||||||
| @@ -1,5 +1,8 @@ | |||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
|  | import ( | ||||||
|  | 	"C" | ||||||
|  | ) | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| @@ -9,33 +12,33 @@ import ( | |||||||
| 	"github.com/PuerkitoBio/goquery" | 	"github.com/PuerkitoBio/goquery" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func downloadDilbertList() []Comic { | func getFeedList() []Feed { | ||||||
| 	var ret []Comic | 	var ret []Feed | ||||||
| 	ret = append(ret, *NewComic("dilbert", "Dilbert", "Scott Adams", "dilbert")) | 	ret = append(ret, *NewFeed("dilbert", "Dilbert", "Scott Adams", "dilbert")) | ||||||
| 	return ret | 	return ret | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getDilbertRssItem(slug string) (string, error) { | func getRssItem(slug string) (string, error) { | ||||||
| 	desc, err := getDilbertFeedDesc(time.Now()) | 	desc, err := getFeedDesc(time.Now()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| 	comic, err := m.GetComic(SRC_DILBERT, slug) | 	feed, err := m.GetFeed(SRC_DILBERT, slug) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| 	desc = "<![CDATA[" + desc + "]]>" | 	desc = "<![CDATA[" + desc + "]]>" | ||||||
| 	ret := "    <item>\n" | 	ret := "    <item>\n" | ||||||
| 	ret += "      <title>" + comic.Name + "</title>\n" | 	ret += "      <title>" + feed.Name + "</title>\n" | ||||||
| 	ret += "      <pubDate>" + comic.LastUpdate.Format(time.RFC1123Z) + "</pubDate>\n" | 	ret += "      <pubDate>" + feed.LastUpdate.Format(time.RFC1123Z) + "</pubDate>\n" | ||||||
| 	ret += "      <guid>dilbert;" + slug + ";" + comic.LastUpdate.Format(time.RFC1123Z) + "</guid>\n" | 	ret += "      <guid>dilbert;" + slug + ";" + feed.LastUpdate.Format(time.RFC1123Z) + "</guid>\n" | ||||||
| 	ret += "      <link>" + getDilbertComicUrl(time.Now()) + "</link>\n" | 	ret += "      <link>" + getFeedUrl(time.Now()) + "</link>\n" | ||||||
| 	ret += "      <description>" + desc + "</description>\n" | 	ret += "      <description>" + desc + "</description>\n" | ||||||
| 	ret += "    </item>\n" | 	ret += "    </item>\n" | ||||||
| 	return ret, nil | 	return ret, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getDilbertComicUrl(date time.Time) string { | func getFeedUrl(date time.Time) string { | ||||||
| 	return fmt.Sprintf( | 	return fmt.Sprintf( | ||||||
| 		"http://dilbert.com/strip/%4d-%02d-%02d", | 		"http://dilbert.com/strip/%4d-%02d-%02d", | ||||||
| 		date.Year(), | 		date.Year(), | ||||||
| @@ -44,8 +47,8 @@ func getDilbertComicUrl(date time.Time) string { | |||||||
| 	) | 	) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getDilbertFeedDesc(date time.Time) (string, error) { | func getFeedDesc(date time.Time) (string, error) { | ||||||
| 	res, err := http.Get(getDilbertComicUrl(date)) | 	res, err := http.Get(getFeedUrl(date)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
|  | 	"C" | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| @@ -10,8 +11,8 @@ import ( | |||||||
| 	"github.com/PuerkitoBio/goquery" | 	"github.com/PuerkitoBio/goquery" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func downloadGoComicsList() []Comic { | func downloadList() { | ||||||
| 	var ret []Comic | 	var ret []Feed | ||||||
| 	lstUrl := "http://www.gocomics.com/comics/a-to-z" | 	lstUrl := "http://www.gocomics.com/comics/a-to-z" | ||||||
| 	res, err := http.Get(lstUrl) | 	res, err := http.Get(lstUrl) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -47,14 +48,14 @@ func downloadGoComicsList() []Comic { | |||||||
| 			} | 			} | ||||||
| 			author = strings.TrimPrefix(author, "By ") | 			author = strings.TrimPrefix(author, "By ") | ||||||
| 			author = strings.Replace(author, "\u0026", "&", -1) | 			author = strings.Replace(author, "\u0026", "&", -1) | ||||||
| 			ret = append(ret, *NewComic(slug, name, author, "gocomics")) | 			ret = append(ret, *NewFeed(slug, name, author, "gocomics")) | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
| 	return ret | 	return ret | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getGoComicsRssItem(slug string) (string, error) { | func getRssItem(slug string) (string, error) { | ||||||
| 	desc, err := getGoComicsFeedDesc(slug, time.Now()) | 	desc, err := getFeedDesc(slug, time.Now()) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| @@ -73,7 +74,7 @@ func getGoComicsRssItem(slug string) (string, error) { | |||||||
| 	return ret, nil | 	return ret, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getGoComicsComicUrl(slug string, date time.Time) string { | func getFeedUrl(slug string, date time.Time) string { | ||||||
| 	return fmt.Sprintf( | 	return fmt.Sprintf( | ||||||
| 		"http://www.gocomics.com/%s/%04d/%02d/%02d", | 		"http://www.gocomics.com/%s/%04d/%02d/%02d", | ||||||
| 		slug, | 		slug, | ||||||
| @@ -83,8 +84,8 @@ func getGoComicsComicUrl(slug string, date time.Time) string { | |||||||
| 	) | 	) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func getGoComicsFeedDesc(slug string, date time.Time) (string, error) { | func getFeedDesc(slug string, date time.Time) (string, error) { | ||||||
| 	res, err := http.Get(getGoComicsComicUrl(slug, date)) | 	res, err := http.Get(getFeedUrl(slug, date)) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		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