Read HTML from a file instead of .tmpl
This commit is contained in:
parent
f2f1a08687
commit
c185d463e6
18 changed files with 369 additions and 591 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
|
@ -12,6 +12,7 @@
|
||||||
"bthub",
|
"bthub",
|
||||||
"chartboost",
|
"chartboost",
|
||||||
"congstar",
|
"congstar",
|
||||||
|
"datetime",
|
||||||
"easybox",
|
"easybox",
|
||||||
"etxr",
|
"etxr",
|
||||||
"feedburner",
|
"feedburner",
|
||||||
|
|
@ -38,11 +39,13 @@
|
||||||
"PGPORT",
|
"PGPORT",
|
||||||
"PGUSER",
|
"PGUSER",
|
||||||
"regexes",
|
"regexes",
|
||||||
|
"Roboto",
|
||||||
"routerlogin",
|
"routerlogin",
|
||||||
"speedport",
|
"speedport",
|
||||||
"steamloopback",
|
"steamloopback",
|
||||||
"stretchr",
|
"stretchr",
|
||||||
"stylesheet",
|
"stylesheet",
|
||||||
|
"tdewolff",
|
||||||
"tmpl",
|
"tmpl",
|
||||||
"tplinkap",
|
"tplinkap",
|
||||||
"tplinkeap",
|
"tplinkeap",
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -5,6 +5,7 @@ go 1.21.6
|
||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.0.11
|
github.com/go-chi/chi/v5 v5.0.11
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.1
|
||||||
|
github.com/tdewolff/minify/v2 v2.20.16
|
||||||
gorm.io/driver/sqlite v1.5.4
|
gorm.io/driver/sqlite v1.5.4
|
||||||
gorm.io/gorm v1.25.6
|
gorm.io/gorm v1.25.6
|
||||||
)
|
)
|
||||||
|
|
@ -15,5 +16,6 @@ require (
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/tdewolff/parse/v2 v2.7.11 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
7
go.sum
7
go.sum
|
|
@ -18,6 +18,13 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/tdewolff/minify/v2 v2.20.16 h1:/C8dtRkxLTIyUlKlBz46gDiktCrE8a6+c1gTrnPFz+U=
|
||||||
|
github.com/tdewolff/minify/v2 v2.20.16/go.mod h1:/FvxV9KaTrFu35J9I2FhRvWSBxcHj8sDSdwBFh5voxM=
|
||||||
|
github.com/tdewolff/parse/v2 v2.7.11 h1:v+W45LnzmjndVlfqPCT5gGjAAZKd1GJGOPJveTIkBY8=
|
||||||
|
github.com/tdewolff/parse/v2 v2.7.11/go.mod h1:3FbJWZp3XT9OWVN3Hmfp0p/a08v4h8J9W1aghka0soA=
|
||||||
|
github.com/tdewolff/test v1.0.11-0.20231101010635-f1265d231d52/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||||
|
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739 h1:IkjBCtQOOjIn03u/dMQK9g+Iw9ewps4mCl1nB8Sscbo=
|
||||||
|
github.com/tdewolff/test v1.0.11-0.20240106005702-7de5f7df4739/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|
|
||||||
209
html.go
Normal file
209
html.go
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/tdewolff/minify/v2"
|
||||||
|
"github.com/tdewolff/minify/v2/css"
|
||||||
|
"github.com/tdewolff/minify/v2/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTMLData struct {
|
||||||
|
Title string
|
||||||
|
Description string
|
||||||
|
Keywords string
|
||||||
|
Author string
|
||||||
|
CanonicalURL string
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
func minifyHTML(h string) string {
|
||||||
|
m := minify.New()
|
||||||
|
m.AddFunc("text/html", html.Minify)
|
||||||
|
minified, err := m.String("text/html", h)
|
||||||
|
if err != nil {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return minified
|
||||||
|
}
|
||||||
|
|
||||||
|
func minifyCSS(h string) string {
|
||||||
|
m := minify.New()
|
||||||
|
m.AddFunc("text/css", css.Minify)
|
||||||
|
minified, err := m.String("text/css", h)
|
||||||
|
if err != nil {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
return minified
|
||||||
|
}
|
||||||
|
|
||||||
|
var style = `
|
||||||
|
html {
|
||||||
|
max-width: 70ch;
|
||||||
|
padding: calc(1vmin + 0.5rem);
|
||||||
|
margin-inline: auto;
|
||||||
|
font-size: clamp(1em, 0.909em + 0.45vmin, 1.25em);
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
color-scheme: light dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 1rem;
|
||||||
|
margin-inline: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leftright {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 10rem;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.messages {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
func fullHTML(h HTMLData, ParseResult []ParseResult) string {
|
||||||
|
var sb strings.Builder
|
||||||
|
var errorBuilder strings.Builder
|
||||||
|
|
||||||
|
FeedCount := 0
|
||||||
|
DatabaseSize := GetDBSize()
|
||||||
|
|
||||||
|
// This is the error message that will be displayed if there are any errors
|
||||||
|
if len(ParseResult) > 0 {
|
||||||
|
errorBuilder.WriteString("<h2>Results</h2><ul>")
|
||||||
|
for _, result := range ParseResult {
|
||||||
|
var listItemClass, statusMsg string
|
||||||
|
if result.IsError {
|
||||||
|
listItemClass = "error"
|
||||||
|
statusMsg = result.Msg
|
||||||
|
} else {
|
||||||
|
listItemClass = "success"
|
||||||
|
statusMsg = result.Msg
|
||||||
|
}
|
||||||
|
errorBuilder.WriteString(fmt.Sprintf(`<li class="%s"><a href="%s">%s</a> - %s</li>`, listItemClass, result.FeedURL, result.FeedURL, statusMsg))
|
||||||
|
}
|
||||||
|
errorBuilder.WriteString("</ul>")
|
||||||
|
}
|
||||||
|
StatusMsg := errorBuilder.String()
|
||||||
|
|
||||||
|
sb.WriteString(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
`)
|
||||||
|
|
||||||
|
if h.Description != "" {
|
||||||
|
sb.WriteString(`<meta name="description" content="` + h.Description + `">`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.Keywords != "" {
|
||||||
|
sb.WriteString(`<meta name="keywords" content="` + h.Keywords + `">`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.Author != "" {
|
||||||
|
sb.WriteString(`<meta name="author" content="` + h.Author + `">`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.CanonicalURL != "" {
|
||||||
|
sb.WriteString(`<link rel="canonical" href="` + h.CanonicalURL + `">`)
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.WriteString(`
|
||||||
|
<title>` + h.Title + `</title>
|
||||||
|
<style>` + minifyCSS(style) + `</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
` + StatusMsg + `
|
||||||
|
<span class="title"><h1><a href="/">FeedVault</a></h1></span>
|
||||||
|
<div class="leftright">
|
||||||
|
<div class="left">
|
||||||
|
<small>Archive of <a href="https://en.wikipedia.org/wiki/Web_feed">web feeds</a>. ` + fmt.Sprintf("%d", FeedCount) + ` feeds. ~` + DatabaseSize + `.</small>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<!-- Search -->
|
||||||
|
<form action="#" method="get">
|
||||||
|
<input type="text" name="q" placeholder="Search">
|
||||||
|
<button type="submit">Search</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<nav>
|
||||||
|
<small>
|
||||||
|
<div class="leftright">
|
||||||
|
<div class="left">
|
||||||
|
<a href="/feeds">Feeds</a> | <a href="/api">API</a> | <a href="/export">Export</a> | <a href="/stats">Stats</a>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<a href="https://github.com/TheLovinator1/FeedVault">GitHub</a> | <a href="https://github.com/sponsors/TheLovinator1">Donate</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</small>
|
||||||
|
</nav>
|
||||||
|
<hr>
|
||||||
|
<main>
|
||||||
|
` + h.Content + `
|
||||||
|
</main>
|
||||||
|
<hr>
|
||||||
|
<footer>
|
||||||
|
<small>
|
||||||
|
<div class="leftright">
|
||||||
|
<div class="left">
|
||||||
|
Made by <a href="">Joakim Hellsén</a>.
|
||||||
|
</div>
|
||||||
|
<div class="right">No rights reserved.</div>
|
||||||
|
</div>
|
||||||
|
<div class="leftright">
|
||||||
|
<div class="left">
|
||||||
|
<a href="mailto:hello@feedvault.se">hello@feedvault.se</a>
|
||||||
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
Web scraping is not a crime.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</small>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>`)
|
||||||
|
|
||||||
|
return minifyHTML(sb.String())
|
||||||
|
|
||||||
|
}
|
||||||
50
html_test.go
Normal file
50
html_test.go
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// returns a minified version of the input HTML string
|
||||||
|
func TestMinifyHTML(t *testing.T) {
|
||||||
|
input := "<html><head><title>Test</title></head><body><h1>Hello, World!</h1></body></html>"
|
||||||
|
expected := "<title>Test</title><h1>Hello, World!</h1>"
|
||||||
|
|
||||||
|
result := minifyHTML(input)
|
||||||
|
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("Expected minified HTML: %s, but got: %s", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinifyCSS(t *testing.T) {
|
||||||
|
cssString := `
|
||||||
|
body {
|
||||||
|
background-color: red;
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
expected := "body{background-color:red;color:blue}"
|
||||||
|
result := minifyCSS(cssString)
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("Expected minified CSS string to be %s, but got %s", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Displays error messages if there are any parse errors
|
||||||
|
func TestErrorMessages(t *testing.T) {
|
||||||
|
// Initialize test data
|
||||||
|
h := HTMLData{}
|
||||||
|
parseResult := []ParseResult{
|
||||||
|
{IsError: true, Msg: "Error 1"},
|
||||||
|
{IsError: true, Msg: "Error 2"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoke function under test
|
||||||
|
result := fullHTML(h, parseResult)
|
||||||
|
|
||||||
|
// Assert that the result contains the error messages
|
||||||
|
if !strings.Contains(result, "Error 1") || !strings.Contains(result, "Error 2") {
|
||||||
|
t.Errorf("Expected error messages, but got: %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
31
main.go
31
main.go
|
|
@ -1,8 +1,6 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
|
@ -53,38 +51,9 @@ func main() {
|
||||||
// Routes
|
// Routes
|
||||||
r.Get("/", IndexHandler)
|
r.Get("/", IndexHandler)
|
||||||
r.Get("/api", ApiHandler)
|
r.Get("/api", ApiHandler)
|
||||||
r.Get("/donate", DonateHandler)
|
|
||||||
r.Get("/feeds", FeedsHandler)
|
r.Get("/feeds", FeedsHandler)
|
||||||
r.Get("/privacy", PrivacyHandler)
|
|
||||||
r.Get("/terms", TermsHandler)
|
|
||||||
r.Post("/add", AddFeedHandler)
|
r.Post("/add", AddFeedHandler)
|
||||||
|
|
||||||
// Static files
|
|
||||||
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
|
||||||
|
|
||||||
// 404 and 405 handlers
|
|
||||||
r.NotFound(NotFoundHandler)
|
|
||||||
r.MethodNotAllowed(MethodNotAllowedHandler)
|
|
||||||
|
|
||||||
log.Println("Listening on http://localhost:8000/ <Ctrl-C> to stop")
|
log.Println("Listening on http://localhost:8000/ <Ctrl-C> to stop")
|
||||||
http.ListenAndServe("127.0.0.1:8000", r)
|
http.ListenAndServe("127.0.0.1:8000", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderPage(w http.ResponseWriter, title, description, keywords, author, url, templateName string) {
|
|
||||||
data := TemplateData{
|
|
||||||
Title: title,
|
|
||||||
Description: description,
|
|
||||||
Keywords: keywords,
|
|
||||||
Author: author,
|
|
||||||
CanonicalURL: url,
|
|
||||||
FeedCount: 0,
|
|
||||||
}
|
|
||||||
data.GetDatabaseSizeAndFeedCount()
|
|
||||||
|
|
||||||
t, err := template.ParseFiles("templates/base.tmpl", fmt.Sprintf("templates/%s.tmpl", templateName))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Internal Server Error: %v", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.ExecuteTemplate(w, "base", data)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,57 +0,0 @@
|
||||||
html {
|
|
||||||
max-width: 70ch;
|
|
||||||
padding: calc(1vmin + 0.5rem);
|
|
||||||
margin-inline: auto;
|
|
||||||
font-size: clamp(1em, 0.909em + 0.45vmin, 1.25em);
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
|
|
||||||
Arial, sans-serif;
|
|
||||||
color-scheme: light dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 1rem;
|
|
||||||
margin-inline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftright {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.left {
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.right {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
height: 10rem;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.success {
|
|
||||||
color: green;
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
{{ define "content" }}
|
|
||||||
<span style="text-align: center;">
|
|
||||||
<h2>404 - Page not found</h2>
|
|
||||||
<p>Sorry, the page you are looking for does not exist.</p>
|
|
||||||
</span>
|
|
||||||
<p>
|
|
||||||
Extra information:
|
|
||||||
<ul>
|
|
||||||
<li>Host: {{ .Request.Host }}</li>
|
|
||||||
<li>Method: {{ .Request.Method }}</li>
|
|
||||||
<li>Proto: {{ .Request.Proto }}</li>
|
|
||||||
<li>RemoteAddr: {{ .Request.RemoteAddr }}</li>
|
|
||||||
<li>RequestURI: {{ .Request.RequestURI }}</li>
|
|
||||||
<li>URL: {{ .Request.URL }}</li>
|
|
||||||
<li>UserAgent: {{ .Request.UserAgent }}</li>
|
|
||||||
</ul>
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
{{ define "content" }}
|
|
||||||
<span style="text-align: center;">
|
|
||||||
<h2>405 - Method Not Allowed</h2>
|
|
||||||
<p>The method ({{ .Request.Method }}) is not allowed for the requested URL.</p>
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Extra information:
|
|
||||||
<ul>
|
|
||||||
<li>Host: {{ .Request.Host }}</li>
|
|
||||||
<li>Method: {{ .Request.Method }}</li>
|
|
||||||
<li>Proto: {{ .Request.Proto }}</li>
|
|
||||||
<li>RemoteAddr: {{ .Request.RemoteAddr }}</li>
|
|
||||||
<li>RequestURI: {{ .Request.RequestURI }}</li>
|
|
||||||
<li>URL: {{ .Request.URL }}</li>
|
|
||||||
<li>UserAgent: {{ .Request.UserAgent }}</li>
|
|
||||||
</ul>
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
{{ define "content" }}
|
|
||||||
<h2>API documentation</h2>
|
|
||||||
<p>Here be dragons.</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
{{ define "base" }}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
{{ if .Description }}<meta name="description" content="{{ .Description }}">{{ end }}
|
|
||||||
{{ if .Keywords }}<meta name="keywords" content="{{ .Keywords }}">{{ end }}
|
|
||||||
{{ if .Author }}<meta name="author" content="{{ .Author }}">{{ end }}
|
|
||||||
{{ if .CanonicalURL }}<link rel="canonical" href="{{ .CanonicalURL }}">{{ end }}
|
|
||||||
<title>{{ .Title }}</title>
|
|
||||||
<link rel="stylesheet" href="/static/style.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
{{ if .ParseErrors }}
|
|
||||||
<h2>Results</h2>
|
|
||||||
<ul>
|
|
||||||
{{ range .ParseErrors }}
|
|
||||||
<li class="{{ if .IsError }}error{{ else }}success{{ end }}"><a href="{{ .FeedURL }}">{{ .FeedURL }}</a> - {{ .Msg }}</li>
|
|
||||||
{{ end }}
|
|
||||||
</ul>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
|
|
||||||
<span class="title">
|
|
||||||
<h1>
|
|
||||||
<a href="/">FeedVault</a>
|
|
||||||
</h1>
|
|
||||||
</span>
|
|
||||||
<div class="leftright">
|
|
||||||
<div class="left">
|
|
||||||
<small>Archive of <a href="https://en.wikipedia.org/wiki/Web_feed">web feeds</a>. {{ .FeedCount }} feeds. ~{{ .DatabaseSize }}</small>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<!-- Search -->
|
|
||||||
<form action="#" method="get">
|
|
||||||
<input type="text" name="q" placeholder="Search">
|
|
||||||
<button type="submit">Search</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nav>
|
|
||||||
<small>
|
|
||||||
<div class="leftright">
|
|
||||||
<div class="left">
|
|
||||||
<a href="/feeds">Feeds</a> | <a href="/api">API</a> | <a href="https://github.com/TheLovinator1/FeedVault">GitHub</a> | <a href="/donate">Donate</a>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<a href="">Register</a> | <a href="">Login</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</small>
|
|
||||||
</nav>
|
|
||||||
<hr>
|
|
||||||
{{ block "content" . }}{{ end }}
|
|
||||||
<hr>
|
|
||||||
<footer>
|
|
||||||
<small>
|
|
||||||
<div class="leftright">
|
|
||||||
<div class="left">
|
|
||||||
Made by <a href="">Joakim Hellsén</a>.
|
|
||||||
</div>
|
|
||||||
<div class="right">No rights reserved.</div>
|
|
||||||
</div>
|
|
||||||
<div class="leftright">
|
|
||||||
<div class="left">
|
|
||||||
<a href="mailto:hello@feedvault.se">hello@feedvault.se</a>
|
|
||||||
</div>
|
|
||||||
<div class="right">
|
|
||||||
<a href="/terms">Terms of Service</a> | <a href="/privacy">Privacy Policy</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</small>
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
{{ define "content" }}
|
|
||||||
<h2>Donate</h2>
|
|
||||||
<p>
|
|
||||||
tl;dr: <a href="https://github.com/sponsors/TheLovinator1">GitHub Sponsors</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
FeedVault is a free service, and will always be free. However, if you wish to support the project, you can do so by donating to the developer.
|
|
||||||
</p>
|
|
||||||
<h3>What will the money be used for?</h3>
|
|
||||||
<p>
|
|
||||||
The money will be used to pay for the server costs, domain name, and other expenses related to running the service.
|
|
||||||
</p>
|
|
||||||
<h3>How much does it cost to run FeedVault?</h3>
|
|
||||||
<p>Domain name: 12 € / 13 $ / 10 £ per year</p>
|
|
||||||
<h3>How can I donate?</h3>
|
|
||||||
<p>
|
|
||||||
The preferred method of donating is through <a href="https://github.com/sponsors/TheLovinator1">GitHub Sponsors</a> due to no fees being taken. However, you can also donate through <a href="https://www.paypal.com/donate/?hosted_button_id=9EU45BXAEUGFC">PayPal</a>.
|
|
||||||
</p>
|
|
||||||
<h3>Crypto</h3>
|
|
||||||
<p>
|
|
||||||
You can also donate through cryptocurrency. The addresses are listed below. If you wish to donate through a cryptocurrency not listed below, please <a href="mailto:hello@feedvault.se">contact me</a>.
|
|
||||||
</p>
|
|
||||||
<h4>Bitcoin</h4>
|
|
||||||
<p>
|
|
||||||
<a href="bitcoin:bc1qdgm09zzxllzh4m2edyyx7khwhw5mjrwe0d2dws?message=FeedVault">bc1qdgm09zzxllzh4m2edyyx7khwhw5mjrwe0d2dws</a>
|
|
||||||
</p>
|
|
||||||
<h4>Monero</h4>
|
|
||||||
<p>
|
|
||||||
<a href="monero:83Hft9zJKPNgMrLAZQ7hhTBeJ6v9bJFJ7P7xfWLRDCiyg4QDwstnjc79Fdn5Y8uY5Hfddj52ok9JF8uisU9UXDJjT4Msevx">83Hft9zJKPNgMrLAZQ7hhTBeJ6v9bJFJ7P7xfWLRDCiyg4QDwstnjc79Fdn5Y8uY5Hfddj52ok9JF8uisU9UXDJjT4Msevx</a>
|
|
||||||
</p>
|
|
||||||
<h3>Thank you!</h3>
|
|
||||||
<p>Thank you for supporting FeedVault!</p>
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
{{ define "content" }}
|
|
||||||
Feeds
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
{{ define "content" }}
|
|
||||||
<h2>Feeds to archive</h2>
|
|
||||||
<p>
|
|
||||||
Input the URLs of the feeds you wish to archive below. You can add as many as needed, and access them through the website or API. Alternatively, include links to .opml files, and the feeds within will be archived.
|
|
||||||
</p>
|
|
||||||
<form action="/add" method="post">
|
|
||||||
<textarea id="urls" name="urls" rows="5" cols="50" required></textarea>
|
|
||||||
<button type="submit">Add feeds</button>
|
|
||||||
</form>
|
|
||||||
<br>
|
|
||||||
<p>You can also upload .opml files containing the feeds you wish to archive:</p>
|
|
||||||
<form enctype="multipart/form-data" method="post" action="/upload_opml">
|
|
||||||
<input type="file" name="file" id="file" accept=".opml" required>
|
|
||||||
<button type="submit">Upload OPML</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<h2>FAQ</h2>
|
|
||||||
<details>
|
|
||||||
<summary>What are web feeds?</summary>
|
|
||||||
<p>
|
|
||||||
Web feeds are a way to distribute content on the web. They allow users to access updates from websites without having to visit them directly. Feeds are typically used for news websites, blogs, and other sites that frequently update content.
|
|
||||||
<br>
|
|
||||||
You can read more about web feeds on <a href="https://en.wikipedia.org/wiki/Web_feed">Wikipedia</a>.
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>What is FeedVault?</summary>
|
|
||||||
<p>
|
|
||||||
FeedVault is a service that archives web feeds. It allows users to access and search for historical content from various websites. The service is designed to preserve the history of the web and provide a reliable source for accessing content that may no longer be available on the original websites.
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>Why archive feeds?</summary>
|
|
||||||
<p>
|
|
||||||
Web feeds are a valuable source of information, and archiving them ensures that the content is preserved for future reference. By archiving feeds, we can ensure that historical content is available for research, analysis, and other purposes. Additionally, archiving feeds can help prevent the loss of valuable information due to website changes, outages, or other issues.
|
|
||||||
</p>
|
|
||||||
<hr>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>How does it work?</summary>
|
|
||||||
<p>
|
|
||||||
FeedVault is written in Go and uses the <a href="https://github.com/mmcdole/gofeed">gofeed</a> library to parse feeds. The service periodically checks for new content in the feeds and stores it in a database. Users can access the archived feeds through the website or API.
|
|
||||||
<hr>
|
|
||||||
</details>
|
|
||||||
<details>
|
|
||||||
<summary>How can I access the archived feeds?</summary>
|
|
||||||
<p>
|
|
||||||
You can access the archived feeds through the website or API. The website provides a user interface for searching and browsing the feeds, while the API allows you to access the feeds programmatically. You can also download the feeds in various formats, such as JSON, XML, or RSS.
|
|
||||||
</p>
|
|
||||||
</details>
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
{{ define "content" }}
|
|
||||||
<article>
|
|
||||||
<header>
|
|
||||||
<h1>Privacy Policy</h1>
|
|
||||||
</header>
|
|
||||||
<p>
|
|
||||||
Last Updated:
|
|
||||||
<time datetime="2024-02-04">
|
|
||||||
February 4, 2024
|
|
||||||
</time>
|
|
||||||
</p>
|
|
||||||
<section>
|
|
||||||
<h2>Information Collection</h2>
|
|
||||||
<p>We gather the following data:</p>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Log Files:</strong> These files contain details about your IP address, browser, and operating system.</li>
|
|
||||||
<ul>
|
|
||||||
<li>This information is collected for debugging purposes and to enhance website performance.</li>
|
|
||||||
<li>Log files are automatically removed after a specific timeframe.</li>
|
|
||||||
<li>They are not linked to any personal information, shared with third parties, or used for marketing purposes.</li>
|
|
||||||
<li>Furthermore, log files are not utilized to track your activity on other websites.</li>
|
|
||||||
</ul>
|
|
||||||
<li><strong>Cloudflare:</strong> We use Cloudflare to secure and optimize our website.</li>
|
|
||||||
<ul>
|
|
||||||
<li>Cloudflare may collect your IP address, cookies, and other data.</li>
|
|
||||||
<li>For more information, please review Cloudflare's <a href="https://www.cloudflare.com/privacypolicy/">privacy policy</a>.</li>
|
|
||||||
</ul>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>User Rights</h2>
|
|
||||||
<p>
|
|
||||||
You have the right to access, correct, or delete your information. Any privacy-related inquiries can be directed to us using the contact information provided at the end of this document.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Changes to the Privacy Policy</h2>
|
|
||||||
<p>
|
|
||||||
This privacy policy may be revised. You can review the revision history of this document on our GitHub repository <a href="https://github.com/TheLovinator1/FeedVault/blob/master/templates/privacy.tmpl">here</a>.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
<h2>Contact Information</h2>
|
|
||||||
<p>
|
|
||||||
For privacy concerns or questions, you can reach us via email at <a href="mailto:hello@feedvault.se">hello@feedvault.se</a> or by creating an issue on our <a href="https://github.com/TheLovinator1/FeedVault/issues">GitHub repository</a>.
|
|
||||||
</p>
|
|
||||||
<p>Cloudflare's contact information can be found <a href="https://www.cloudflare.com/privacypolicy/">here</a>.</p>
|
|
||||||
</section>
|
|
||||||
</article>
|
|
||||||
{{ end }}
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
{{ define "content" }}
|
|
||||||
<article>
|
|
||||||
<header>
|
|
||||||
<h1>Terms of Service</h1>
|
|
||||||
</header>
|
|
||||||
<p>
|
|
||||||
Last Updated:
|
|
||||||
<time datetime="2024-02-04">
|
|
||||||
February 4, 2024
|
|
||||||
</time>
|
|
||||||
</p>
|
|
||||||
<section>
|
|
||||||
<h2>Content Policy</h2>
|
|
||||||
<p>
|
|
||||||
Users are prohibited from uploading any content that is illegal under Swedish law. Any such content found on our platform will be removed. If this happens repeatedly, the user will be banned from using our platform.
|
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
You can report any content that you believe violates our content policy by sending an email to <a href="mailto:hello@feedvault.se">hello@feedvault.se</a>. Please include the URL of the content in question and a brief description of why you believe it violates our content policy.
|
|
||||||
</p>
|
|
||||||
<h2>Copyright Policy</h2>
|
|
||||||
<p>
|
|
||||||
We will remove URLs that are used to share copyrightable information without the necessary permissions or licenses.
|
|
||||||
</p>
|
|
||||||
<h2>Web Scraping</h2>
|
|
||||||
<p>
|
|
||||||
Web scraping is permitted on our platform. We currently do not impose a rate limit on requests.
|
|
||||||
</p>
|
|
||||||
<h2>API Usage</h2>
|
|
||||||
<p>
|
|
||||||
Our API is free to use. We do not impose any rate limits on requests.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</article>
|
|
||||||
{{ end }}
|
|
||||||
155
views.go
155
views.go
|
|
@ -1,66 +1,106 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := TemplateData{
|
|
||||||
Request: r,
|
|
||||||
}
|
|
||||||
data.GetDatabaseSizeAndFeedCount()
|
|
||||||
t, err := template.ParseFiles("templates/base.tmpl", "templates/404.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Internal Server Error: %v", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
t.ExecuteTemplate(w, "base", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func MethodNotAllowedHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := TemplateData{
|
|
||||||
Request: r,
|
|
||||||
}
|
|
||||||
data.GetDatabaseSizeAndFeedCount()
|
|
||||||
t, err := template.ParseFiles("templates/base.tmpl", "templates/405.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Internal Server Error: %v", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
t.ExecuteTemplate(w, "base", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IndexHandler(w http.ResponseWriter, _ *http.Request) {
|
func IndexHandler(w http.ResponseWriter, _ *http.Request) {
|
||||||
renderPage(w, "FeedVault", "FeedVault - A feed archive", "RSS, Atom, Feed, Archive", "TheLovinator", "http://localhost:8000/", "index")
|
|
||||||
|
content := `<h2>Feeds to archive</h2>
|
||||||
|
<p>
|
||||||
|
Input the URLs of the feeds you wish to archive below. You can add as many as needed, and access them through the website or API. Alternatively, include links to .opml files, and the feeds within will be archived.
|
||||||
|
</p>
|
||||||
|
<form action="/add" method="post">
|
||||||
|
<textarea id="urls" name="urls" rows="5" cols="50" required></textarea>
|
||||||
|
<button type="submit">Add feeds</button>
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
<p>You can also upload .opml files containing the feeds you wish to archive:</p>
|
||||||
|
<form enctype="multipart/form-data" method="post" action="/upload_opml">
|
||||||
|
<input type="file" name="file" id="file" accept=".opml" required>
|
||||||
|
<button type="submit">Upload OPML</button>
|
||||||
|
</form>
|
||||||
|
`
|
||||||
|
|
||||||
|
FAQ := `
|
||||||
|
|
||||||
|
<h2>FAQ</h2>
|
||||||
|
<details>
|
||||||
|
<summary>What are web feeds?</summary>
|
||||||
|
<p>
|
||||||
|
Web feeds are a way to distribute content on the web. They allow users to access updates from websites without having to visit them directly. Feeds are typically used for news websites, blogs, and other sites that frequently update content.
|
||||||
|
<br>
|
||||||
|
You can read more about web feeds on <a href="https://en.wikipedia.org/wiki/Web_feed">Wikipedia</a>.
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>What is FeedVault?</summary>
|
||||||
|
<p>
|
||||||
|
FeedVault is a service that archives web feeds. It allows users to access and search for historical content from various websites. The service is designed to preserve the history of the web and provide a reliable source for accessing content that may no longer be available on the original websites.
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>Why archive feeds?</summary>
|
||||||
|
<p>
|
||||||
|
Web feeds are a valuable source of information, and archiving them ensures that the content is preserved for future reference. By archiving feeds, we can ensure that historical content is available for research, analysis, and other purposes. Additionally, archiving feeds can help prevent the loss of valuable information due to website changes, outages, or other issues.
|
||||||
|
</p>
|
||||||
|
<hr>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>How does it work?</summary>
|
||||||
|
<p>
|
||||||
|
FeedVault is written in Go and uses the <a href="https://github.com/mmcdole/gofeed">gofeed</a> library to parse feeds. The service periodically checks for new content in the feeds and stores it in a database. Users can access the archived feeds through the website or API.
|
||||||
|
<hr>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>How can I access the archived feeds?</summary>
|
||||||
|
<p>
|
||||||
|
You can access the archived feeds through the website or API. The website provides a user interface for searching and browsing the feeds, while the API allows you to access the feeds programmatically. You can also download the feeds in various formats, such as JSON, XML, or RSS.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
`
|
||||||
|
|
||||||
|
content += FAQ
|
||||||
|
|
||||||
|
htmlData := HTMLData{
|
||||||
|
Title: "FeedVault",
|
||||||
|
Description: "FeedVault - A feed archive",
|
||||||
|
Keywords: "RSS, Atom, Feed, Archive",
|
||||||
|
Author: "TheLovinator",
|
||||||
|
CanonicalURL: "http://localhost:8000/",
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
html := fullHTML(htmlData, nil)
|
||||||
|
w.Write([]byte(html))
|
||||||
}
|
}
|
||||||
|
|
||||||
func ApiHandler(w http.ResponseWriter, _ *http.Request) {
|
func ApiHandler(w http.ResponseWriter, _ *http.Request) {
|
||||||
renderPage(w, "API", "API Page", "api, page", "TheLovinator", "http://localhost:8000/api", "api")
|
htmlData := HTMLData{
|
||||||
|
Title: "FeedVault API",
|
||||||
|
Description: "FeedVault API - A feed archive",
|
||||||
|
Keywords: "RSS, Atom, Feed, Archive, API",
|
||||||
|
Author: "TheLovinator",
|
||||||
|
CanonicalURL: "http://localhost:8000/api",
|
||||||
|
Content: "<p>Here be dragons.</p>",
|
||||||
}
|
}
|
||||||
func AboutHandler(w http.ResponseWriter, _ *http.Request) {
|
html := fullHTML(htmlData, nil)
|
||||||
renderPage(w, "About", "About Page", "about, page", "TheLovinator", "http://localhost:8000/about", "about")
|
w.Write([]byte(html))
|
||||||
}
|
}
|
||||||
|
|
||||||
func DonateHandler(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
renderPage(w, "Donate", "Donate Page", "donate, page", "TheLovinator", "http://localhost:8000/donate", "donate")
|
|
||||||
}
|
|
||||||
|
|
||||||
func FeedsHandler(w http.ResponseWriter, _ *http.Request) {
|
func FeedsHandler(w http.ResponseWriter, _ *http.Request) {
|
||||||
renderPage(w, "Feeds", "Feeds Page", "feeds, page", "TheLovinator", "http://localhost:8000/feeds", "feeds")
|
htmlData := HTMLData{
|
||||||
|
Title: "FeedVault Feeds",
|
||||||
|
Description: "FeedVault Feeds - A feed archive",
|
||||||
|
Keywords: "RSS, Atom, Feed, Archive",
|
||||||
|
Author: "TheLovinator",
|
||||||
|
CanonicalURL: "http://localhost:8000/feeds",
|
||||||
|
Content: "<p>Here be feeds.</p>",
|
||||||
}
|
}
|
||||||
|
html := fullHTML(htmlData, nil)
|
||||||
func PrivacyHandler(w http.ResponseWriter, _ *http.Request) {
|
w.Write([]byte(html))
|
||||||
renderPage(w, "Privacy", "Privacy Page", "privacy, page", "TheLovinator", "http://localhost:8000/privacy", "privacy")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TermsHandler(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
renderPage(w, "Terms", "Terms and Conditions Page", "terms, page", "TheLovinator", "http://localhost:8000/terms", "terms")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddFeedHandler(w http.ResponseWriter, r *http.Request) {
|
func AddFeedHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -88,20 +128,15 @@ func AddFeedHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Println("Adding feed:", feed_url)
|
log.Println("Adding feed:", feed_url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the index page with the parse errors
|
htmlData := HTMLData{
|
||||||
data := TemplateData{
|
Title: "FeedVault - Add Feeds",
|
||||||
Title: "FeedVault",
|
Description: "FeedVault - Add Feeds",
|
||||||
Description: "FeedVault - A feed archive",
|
|
||||||
Keywords: "RSS, Atom, Feed, Archive",
|
Keywords: "RSS, Atom, Feed, Archive",
|
||||||
ParseErrors: parseErrors,
|
Author: "TheLovinator",
|
||||||
|
CanonicalURL: "http://localhost:8000/add",
|
||||||
|
Content: "<p>Feeds added.</p>",
|
||||||
}
|
}
|
||||||
data.GetDatabaseSizeAndFeedCount()
|
|
||||||
|
|
||||||
t, err := template.ParseFiles("templates/base.tmpl", "templates/index.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("Internal Server Error: %v", err), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.ExecuteTemplate(w, "base", data)
|
|
||||||
|
|
||||||
|
html := fullHTML(htmlData, parseErrors)
|
||||||
|
w.Write([]byte(html))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
152
views_test.go
152
views_test.go
|
|
@ -60,157 +60,7 @@ func TestApiHandler(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the response contains the expected string.
|
// Check the response contains the expected string.
|
||||||
shouldContain := "<p>Here be dragons.</p>"
|
shouldContain := "Here be dragons."
|
||||||
body := rr.Body.String()
|
|
||||||
if !assert.Contains(t, body, shouldContain) {
|
|
||||||
t.Errorf("handler returned unexpected body: got %v want %v",
|
|
||||||
body, shouldContain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTermsHandler(t *testing.T) {
|
|
||||||
// Create a request to pass to our handler.
|
|
||||||
req, err := http.NewRequest("GET", "/terms", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
handler := http.HandlerFunc(TermsHandler)
|
|
||||||
|
|
||||||
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
|
|
||||||
// directly and pass in our Request and ResponseRecorder.
|
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
// Check the status code is what we expect.
|
|
||||||
if status := rr.Code; status != http.StatusOK {
|
|
||||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
|
||||||
status, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the response contains the expected string.
|
|
||||||
shouldContain := "Terms of Service"
|
|
||||||
body := rr.Body.String()
|
|
||||||
if !assert.Contains(t, body, shouldContain) {
|
|
||||||
t.Errorf("handler returned unexpected body: got %v want %v",
|
|
||||||
body, shouldContain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPrivacyHandler(t *testing.T) {
|
|
||||||
// Create a request to pass to our handler.
|
|
||||||
req, err := http.NewRequest("GET", "/privacy", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
handler := http.HandlerFunc(PrivacyHandler)
|
|
||||||
|
|
||||||
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
|
|
||||||
// directly and pass in our Request and ResponseRecorder.
|
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
// Check the status code is what we expect.
|
|
||||||
if status := rr.Code; status != http.StatusOK {
|
|
||||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
|
||||||
status, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the response contains the expected string.
|
|
||||||
shouldContain := "Privacy Policy"
|
|
||||||
body := rr.Body.String()
|
|
||||||
if !assert.Contains(t, body, shouldContain) {
|
|
||||||
t.Errorf("handler returned unexpected body: got %v want %v",
|
|
||||||
body, shouldContain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNotFoundHandler(t *testing.T) {
|
|
||||||
// Create a request to pass to our handler.
|
|
||||||
req, err := http.NewRequest("GET", "/notfound", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
handler := http.HandlerFunc(NotFoundHandler)
|
|
||||||
|
|
||||||
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
|
|
||||||
// directly and pass in our Request and ResponseRecorder.
|
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
// Check the status code is what we expect.
|
|
||||||
if status := rr.Code; status != http.StatusNotFound {
|
|
||||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
|
||||||
status, http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the response contains the expected string.
|
|
||||||
shouldContain := "<h2>404 - Page not found</h2>"
|
|
||||||
body := rr.Body.String()
|
|
||||||
if !assert.Contains(t, body, shouldContain) {
|
|
||||||
t.Errorf("handler returned unexpected body: got %v want %v",
|
|
||||||
body, shouldContain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMethodNotAllowedHandler(t *testing.T) {
|
|
||||||
// Create a request to pass to our handler.
|
|
||||||
req, err := http.NewRequest("GET", "/api", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
handler := http.HandlerFunc(MethodNotAllowedHandler)
|
|
||||||
|
|
||||||
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
|
|
||||||
// directly and pass in our Request and ResponseRecorder.
|
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
// Check the status code is what we expect.
|
|
||||||
if status := rr.Code; status != http.StatusMethodNotAllowed {
|
|
||||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
|
||||||
status, http.StatusMethodNotAllowed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the response contains the expected string.
|
|
||||||
shouldContain := "<h2>405 - Method Not Allowed</h2>"
|
|
||||||
body := rr.Body.String()
|
|
||||||
if !assert.Contains(t, body, shouldContain) {
|
|
||||||
t.Errorf("handler returned unexpected body: got %v want %v",
|
|
||||||
body, shouldContain)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDonateHandler(t *testing.T) {
|
|
||||||
// Create a request to pass to our handler.
|
|
||||||
req, err := http.NewRequest("GET", "/donate", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
handler := http.HandlerFunc(DonateHandler)
|
|
||||||
|
|
||||||
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
|
|
||||||
// directly and pass in our Request and ResponseRecorder.
|
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
// Check the status code is what we expect.
|
|
||||||
if status := rr.Code; status != http.StatusOK {
|
|
||||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
|
||||||
status, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the response contains the expected string.
|
|
||||||
shouldContain := "tl;dr: <a href=\"https://github.com/sponsors/TheLovinator1\">GitHub Sponsors</a>"
|
|
||||||
body := rr.Body.String()
|
body := rr.Body.String()
|
||||||
if !assert.Contains(t, body, shouldContain) {
|
if !assert.Contains(t, body, shouldContain) {
|
||||||
t.Errorf("handler returned unexpected body: got %v want %v",
|
t.Errorf("handler returned unexpected body: got %v want %v",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue