1
0

Current state of porting to a Templ template engine. Stage 2.

This commit is contained in:
Alexander Andreev 2024-06-19 01:08:05 +04:00
parent 77aa7a5e63
commit 89de89671a
Signed by: Arav
GPG Key ID: 25969B23DCB5CA34
15 changed files with 543 additions and 249 deletions

View File

@ -37,31 +37,29 @@ func NewHandlers(captchaExpire time.Duration, owner string, gbPageSize int64,
} }
func (h *Handlers) Privacy(w http.ResponseWriter, r *http.Request) { func (h *Handlers) Privacy(w http.ResponseWriter, r *http.Request) {
web.Privacy(r).Render(context.Background(), w)
web.Privacy("/ Privacy statements",
util.GetServiceByHost(r.Host, util.ServiceRadio),
util.GetServiceByHost(r.Host, util.ServiceRadio),
util.GetServiceByHost(r.Host, util.ServiceUpload), w)
} }
func (h *Handlers) Stuff(w http.ResponseWriter, r *http.Request) { func (h *Handlers) Stuff(w http.ResponseWriter, r *http.Request) {
web.Stuff("/ Stuff", util.GetServiceByHost(r.Host, util.ServiceGit), web.Stuff(r).Render(context.Background(), w)
util.GetServiceByHost(r.Host, util.ServiceFiles), web.Metadata, w)
} }
func (h *Handlers) Mindflow(w http.ResponseWriter, r *http.Request) { func (h *Handlers) Mindflow(w http.ResponseWriter, r *http.Request) {
posts, err := h.mindflowDB.Posts() posts, err := h.mindflowDB.Posts()
if err != nil { if err != nil {
log.Println("Cannot load posts.", err)
Error(w, http.StatusInternalServerError, "Cannot load posts.", "", r.Referer())
return return
} }
categories, err := h.mindflowDB.Categories() categories, err := h.mindflowDB.Categories()
if err != nil { if err != nil {
log.Println(err) log.Println("Cannot load categories.", err)
Error(w, http.StatusInternalServerError, "Cannot load categories.", "", r.Referer())
return return
} }
web.Mindflow("/ Mindflow", posts, categories, r, w) web.Mindflow(posts, categories, r).Render(context.Background(), w)
} }
func (h *Handlers) MindflowAdmin(w http.ResponseWriter, r *http.Request) { func (h *Handlers) MindflowAdmin(w http.ResponseWriter, r *http.Request) {
@ -77,7 +75,7 @@ func (h *Handlers) MindflowAdmin(w http.ResponseWriter, r *http.Request) {
return return
} }
web.MindflowAdmin("/ Mindflow Administration", posts, categories, r, w) web.MindflowAdmin(posts, categories, r).Render(context.Background(), w)
} }
func (h *Handlers) About(w http.ResponseWriter, r *http.Request) { func (h *Handlers) About(w http.ResponseWriter, r *http.Request) {
@ -95,18 +93,18 @@ func (h *Handlers) About(w http.ResponseWriter, r *http.Request) {
} }
} }
web.About("/ About", util.GetServiceByHost(r.Host, util.ServiceFiles), lst, w) web.About(util.GetServiceByHost(r.Host, util.ServiceFiles), lst).Render(context.Background(), w)
} }
func (h *Handlers) Article(w http.ResponseWriter, r *http.Request) { func (h *Handlers) Article(w http.ResponseWriter, r *http.Request) {
name := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:] name := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:]
artcl := web.GetArticle(name) artcl := web.GetArticle(name)
if artcl == nil { if artcl == nil {
http.Error(w, "an article doesn't exist", http.StatusNotFound) Error(w, http.StatusNotFound, "", "An article \""+name+"\" doesn't exist.", r.Referer())
return return
} }
web.Article(artcl.Title, artcl.Description, string(artcl.Body), artcl.Date, r.Host, name, r, w) web.Article(artcl.Title, artcl.Description, string(artcl.Body), name, artcl.Date, r).Render(context.Background(), w)
} }
func (h *Handlers) Guestbook(w http.ResponseWriter, r *http.Request) { func (h *Handlers) Guestbook(w http.ResponseWriter, r *http.Request) {
@ -157,7 +155,7 @@ func (h *Handlers) GuestbookAdmin(w http.ResponseWriter, r *http.Request) {
entry.Message = strings.ReplaceAll(entry.Message, "\\n", "\n") entry.Message = strings.ReplaceAll(entry.Message, "\\n", "\n")
} }
web.GuestbookAdmin("/ Guestbook Administration", h.owner, entries, r, w) web.GuestbookAdmin(h.owner, entries, r).Render(context.Background(), w)
} }
func (h *Handlers) RSS(w http.ResponseWriter, r *http.Request) { func (h *Handlers) RSS(w http.ResponseWriter, r *http.Request) {

11
web/about.templ Normal file
View File

@ -0,0 +1,11 @@
package web
import "git.arav.su/Arav/dwelling-home/pkg/servicestat"
templ About(filesSrvc string, services servicestat.ServiceList) {
@base("About", "About me and my home servers.", "about, me, servcies", "/about", "About", aboutHead())
}
templ aboutHead() {
<link rel="stylesheet" href="/assets/css/about.css"/>
}

26
web/article.templ Normal file
View File

@ -0,0 +1,26 @@
package web
import "time"
import "net/http"
import "git.arav.su/Arav/dwelling-home/pkg/util"
templ Article(title, description, body, urlName string, date time.Time, r *http.Request) {
@base(title + " - Articles", description, "", "/stuff/article/"+urlName, "Articles", articleHead()) {
<article>
<header>
<h2>{ title }</h2>
<div class="menu">
<a href="/stuff#articles">Go back to articles list</a>
{{ dctz := util.ToClientTimezone(date, r) }}
<time datetime={ dctz.Format("2006-01-02") }>{ dctz.Format("02 January 2006") }</time>
</div>
</header>
@templ.Raw(body)
</article>
}
}
templ articleHead() {
<link rel="stylesheet" href="/assets/css/articles.css"/>
}

View File

@ -5,3 +5,7 @@
margin: 6rem 0; } margin: 6rem 0; }
#error h1 { font-size: 8rem; } #error h1 { font-size: 8rem; }
#error p {
font-size: 1.1rem;
text-align: center; }

View File

@ -58,6 +58,8 @@ button.refresh:disabled {
text-align: center; text-align: center;
width: 4.5rem; } width: 4.5rem; }
article p { white-space: pre; }
input[type="submit"] { input[type="submit"] {
background-color: var(--primary-color); background-color: var(--primary-color);
border: none; border: none;

View File

@ -1,59 +1,64 @@
package web package web
import "strings" // import "strings"
templ base(title, description, keywords, canonical, heading string, head templ.Component) { templ base(title, description, keywords, canonical, heading string, head templ.Component) {
<!doctype html> <!DOCTYPE html>
<html> <html>
<head> <head>
if title != "" { if title != "" {
<title>{ title } - Arav's dwelling</title> <title>{ title } - Arav's dwelling</title>
} else { } else {
<title>Arav's dwelling</title> <title>Arav's dwelling</title>
} }
<meta charset="utf-8" /> <meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta name="theme-color" content="#cd2682" /> <meta name="theme-color" content="#cd2682"/>
<meta name="description" content={ description } /> <meta name="description" content={ description }/>
<meta name="keywords" content={ keywords } /> <meta name="keywords" content={ keywords }/>
<link rel="icon" href="/assets/img/favicon.svg" sizes="any" type="image/svg+xml" /> <link rel="icon" href="/assets/img/favicon.svg" sizes="any" type="image/svg+xml"/>
<link rel="stylesheet" href="/assets/css/main.css" /> <link rel="stylesheet" href="/assets/css/main.css"/>
<link rel="alternate" href="/rss.xml" type="application/rss+xml" title="Arav's dwelling" /> <link rel="alternate" href="/rss.xml" type="application/rss+xml" title="Arav's dwelling"/>
<link rel="canonical" href={ string(templ.URL(canonical)) }/> if canonical != "" {
@head <link rel="canonical" href={ string(templ.URL(canonical)) }/>
</head> }
<body> if head != nil {
<header> @head
<svg id="logo" viewBox="0 -25 216 40"> }
<text class="logo">Arav's dwelling</text> </head>
<text class="under" y="11">Welcome to my sacred place, wanderer</text> <body>
</svg> <header>
<nav> <svg id="logo" viewBox="0 -25 216 40">
@navigation(heading) <text class="logo">Arav's dwelling</text>
</nav> <text class="under" y="11">Welcome to my sacred place, wanderer</text>
</header> </svg>
{ children... } @navigation(heading)
<footer> </header>
<a href="/rss.xml" title="Stay up to date on what's going on.">RSS feed</a> { children... }
<br> <footer>
&copy; 2017&mdash;2024 Alexander &quot;Arav&quot; Andreev &lt;<a href="mailto:me@arav.su">me@arav.su</a>&gt; <a href="/privacy">Privacy statements</a> <a href="/rss.xml" title="Stay up to date on what's going on.">RSS feed</a>
</footer> <br/>
</body> &copy; 2017&mdash;2024 Alexander &quot;Arav&quot; Andreev &lt;<a href="mailto:me@arav.su">me@arav.su</a>&gt; <a href="/privacy">Privacy statements</a>
</html> </footer>
</body>
</html>
} }
var navElements = []string{"Stuff", "Mindflow", "About", "Guestbook"} var navElements = []string{"Stuff", "Mindflow", "About", "Guestbook"}
templ navigation(heading string) { templ navigation(heading string) {
for _, n := range navElements { <nav>
if n == heading { if heading != "Home" {
continue <a href="/">Home</a>
} }
<a href={ templ.SafeURL("/" + strings.ToLower(n)) }>{ n }</a> <a href="/stuff">Stuff</a>
} <a href="/mindflow">Mindflow</a>
<a href="/about">About</a>
<a href="/guestbook">Guestbook</a>
if heading != "" { if heading != "" && heading != "Home" {
<h1>{ heading }</h1> <h1>{ heading }</h1>
} }
</nav>
} }

View File

@ -4,23 +4,26 @@ import "fmt"
import "net/http" import "net/http"
templ ErrorXXX(errCode int, reason, message, referer string) { templ ErrorXXX(errCode int, reason, message, referer string) {
{{ errText := http.StatusText(errCode) }} {{ errText := http.StatusText(errCode) }}
@base(errText, errText, @base(errText, errText, "", "", errText, errorXXXHead()) {
"", "", errText, nil) { <section id="error">
<section id="error"> <h1>{ fmt.Sprint(errCode) }</h1>
<h1>{ fmt.Sprint(errCode) }</h1> { errText }
{ errText } if reason != "" {
if reason != "" { <p>{ reason }</p>
<p>{ reason }</p> }
} if message != "" {
if message != "" { <p>{ message }</p>
<p>{ message }</p> }
} </section>
</section> if referer != "" {
if referer != "" { <section>
<section> <h2><a href={ templ.URL(referer) }>Go back</a></h2>
<h2><a href={ templ.URL(referer) }>Go back</a></h2> </section>
</section> }
} }
} }
templ errorXXXHead() {
<link rel="stylesheet" href="/assets/css/error.css">
} }

View File

@ -10,79 +10,81 @@ import "git.arav.su/Arav/justguestbook"
import "git.arav.su/Arav/dwelling-home/pkg/util" import "git.arav.su/Arav/dwelling-home/pkg/util"
templ Guestbook(captchaID, owner string, entries []*justguestbook.Entry, pageCount int64, r *http.Request) { templ Guestbook(captchaID, owner string, entries []*justguestbook.Entry, pageCount int64, r *http.Request) {
@base("Guestbook", @base("Guestbook", "This is my guestbook. Welcome.", "guestbook, personal", "/guestbook", "Guestbook", guestbookHead()) {
"This is my guestbook. Welcome.", <form id="new-post" action="/api/guestbook" method="POST">
"guestbook, personal", "/guestbook", "Guestbook", guestbookHead()) { <input type="text" name="name" maxlength="80" placeholder="Name (Anonymous if left blank)"/>
<form id="new-post" action="/api/guestbook" method="POST"> <input type="text" name="website" maxlength="255" placeholder="Website (optional)"/>
<input type="text" name="name" maxlength="80" placeholder="Name (Anonymous if left blank)" /> <textarea name="message" maxlength="4096" placeholder="Your message" required></textarea>
<input type="text" name="website" maxlength="255" placeholder="Website (optional)" /> <span class="checkboxes">
<textarea name="message" maxlength="4096" placeholder="Your message" required></textarea> <input type="checkbox" name="hide_website" id="hide-website" checked/>
<span class="checkboxes"> <label for="hide-website">Hide website <small>(only I can see if set)</small></label>
<input type="checkbox" name="hide_website" id="hide-website" checked /> <br/>
<label for="hide-website">Hide website <small>(only I can see if set)</small></label> <small>Use &gt; to make a quote.</small>
<br> </span>
<small>Use &gt; to make a quote.</small> <span class="captcha">
</span> <input type="hidden" name="captcha_id" value={ captchaID }/>
<span class="captcha"> <img src={ string(templ.URL(fmt.Sprintf("/api/captcha/%s/image", captchaID))) } alt="CAPTCHA" width="160" height="40"/>
<input type="hidden" name="captcha_id" value={ captchaID } /> <input type="text" name="captcha_answer" maxlength="6" placeholder="CAPTCHA" required/>
<img src={ string(templ.URL(fmt.Sprintf("/api/captcha/%s/image", captchaID))) } alt="CAPTCHA" width="160" height="40" /> <small>Valid for <b>10</b> minutes.</small>
<input type="text" name="captcha_answer" maxlength="6" placeholder="CAPTCHA" required /> </span>
<small>Valid for <b>10</b> minutes.</small> <input type="submit" value="Send a post"/>
</span> </form>
<input type="submit" value="Send a post" /> <section id="posts">
</form> for _, entry := range entries {
<section id="posts"> <article>
for _, entry := range entries { <header>
<article> {{ created_tz := util.ToClientTimezone(entry.Created, r).Format("Monday _2 January 2006 15:04:05 -07:00") }}
<header> {{ website := "" }}
{{ created_tz := util.ToClientTimezone(entry.Created, r).Format("Monday _2 January 2006 15:04:05 -07:00") }} if !entry.HideWebsite {
{{ website := "" }} {{ website = entry.Website }}
if !entry.HideWebsite { }
{{ website = entry.Website }} { strconv.FormatInt(entry.ID, 10) } by <span class="highlighted">{ entry.Name }</span> <em>{ website }</em> on <time datetime={ created_tz }>{ created_tz }</time>
} </header>
{ strconv.FormatInt(entry.ID, 10) } by <span class="highlighted">{ entry.Name }</span> <em>{ website }</em> on <time datetime={ created_tz }>{ created_tz }</time> for _, line := range strings.Split(entry.Message, "\n") {
</header> if len(line) > 0 && line[0] == '>' {
for _, line := range strings.Split(entry.Message, "\n") { <p class="quote">@templ.Raw(line)
if len(line) > 0 && line[0] == '>' { </p>
<p class="quote">{ line }</p> } else {
} else { <p>@templ.Raw(line)
<p>{ line }</p> </p>
} }
} }
if entry.Reply != nil { if entry.Reply != nil {
<div class="reply"> <div class="reply">
<header> <header>
{{ reply_created_tz := util.ToClientTimezone(entry.Reply.Created, r).Format("Monday _2 January 2006 15:04:05 -07:00") }} {{ reply_created_tz := util.ToClientTimezone(entry.Reply.Created, r).Format("Monday _2 January 2006 15:04:05 -07:00") }}
Reply by <span class="highlighted">{ owner }</span> in <time datetime={ reply_created_tz }>{ reply_created_tz }</time> Reply by <span class="highlighted">{ owner }</span> in <time datetime={ reply_created_tz }>{ reply_created_tz }</time>
</header> </header>
for _, line := range strings.Split(entry.Reply.Message, "\n") { for _, line := range strings.Split(entry.Reply.Message, "\n") {
if len(line) > 0 && line[0] == '>' { if len(line) > 0 && line[0] == '>' {
<p class="quote">{ line }</p> <p class="quote">@templ.Raw(line)
} else { </p>
<p>{ line }</p> } else {
} <p>@templ.Raw(line)
} </p>
</div> }
} }
</article> </div>
} }
if len(entries) == 0 { </article>
<p class="center">No posts.</p> }
} if len(entries) == 0 {
</section> <p class="center">No posts.</p>
if pageCount > 1 { }
<section id="pagination"> </section>
{{ var n int64 = 1 }} if pageCount > 1 {
for ; n <= pageCount; n++ { <section id="pagination">
{{ strN := strconv.FormatInt(n, 10) }} {{ var n int64 = 1 }}
<a href={ templ.URL("/guestbook?p="+strN) }>{ strN }</a> for ; n <= pageCount; n++ {
} {{ strN := strconv.FormatInt(n, 10) }}
</section> <a href={ templ.URL("/guestbook?p=" + strN) }>{ strN }</a>
} }
} </section>
}
}
} }
templ guestbookHead() { templ guestbookHead() {
<link rel="stylesheet" href="/assets/css/guestbook.css" /> <link rel="stylesheet" href="/assets/css/guestbook.css"/>
<script src="/assets/js/captcha_refresh.js" defer></script> <script src="/assets/js/captcha_refresh.js" defer></script>
} }

View File

@ -0,0 +1,9 @@
package web
import "net/http"
import "git.arav.su/Arav/justguestbook"
templ GuestbookAdmin(owner string, entries []*justguestbook.Entry, r *http.Request) {
@base("Guestbook Administration", "", "", "/guestbook/admin", "Guestbook Administration", nil)
}

View File

@ -1,67 +1,36 @@
package web package web
templ Index() { templ Index() {
@base("", @base("",
"A homepage of a russian guy Alexander aka Arav. Not just homepage, but something more...", "/", "A homepage of a russian guy Alexander aka Arav. Not just homepage, but something more...", "/",
"homepage, personal, blog, services, self-hosting", "", nil) { "homepage, personal, blog, services, self-hosting", "Home", indexHead()) {
<section id="services"> <section id="services">
<span> <span>
<a href="https://arav.su">arav.su</a> <a href="https://arav.su">arav.su</a>.<a href="http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion">onion</a>.<a href="http://[300:a98d:d6d0:8a08::f]">ygg</a>.<a href="http://arav.i2p">i2p</a><sup><a href="http://arav.i2p/?i2paddresshelper=5Kl-DiWbbk6wf7m0v6zBSNHYq3sXlnrWLIWVeGdpPbPyc9CBS~zrzDYpP43rv1fRiIkbVCD5hTEpY6joQGlk-dFkWWD6201qa6ecsDVQMaE3Q7UTYICd0VEBRoqDUSrvsM-P2y5oG4Z-77RmoGKpbcRgNuMVbQ7AGJNqVSGej-lSyscDWTIZT5dCT505lfRwprdD~emZqkwnn22X16Wpj-X4A4ifph4idrThGioz4UW6PrCpa-oebMCo217s0Zyl9VKaU-o9cx5eFUEwnshoUjqwh7VE-S45NDz854J08xldCATM3wwTRVXhc2NUypsJLKFKiV0z3EXN-ApCdxsV60C-eiXUTX5vYcHHH~imA79v8WKFybjnsyUBst5BBEPQIUifTceLUrTmQ9TUpaMV90EsD5SCshmCfOs8R5y2dK6EfQu8iyYAB5VFSH4M1CLiBZUsDTEFiOomn2JGMDnbPho8lMB8ss4SMuwZShb2LlGqLxJ38kRHlvC68VmJO7InBQAEAAcAAA==" title="Address helper">ah</a></sup>
. </span>
<a href="http://moq7aejnf4xk5k2bkaltli3ftkhusy2mbrd3pj23nrca343ku2mgk4yd.onion">onion</a> <a href="https://arav.neocities.org">arav.neocities.org</a>
. <br/>
<a href="http://[300:a98d:d6d0:8a08::f]">ygg</a> <span>
. <a href="https://radio.arav.su">radio</a>.<a href="http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion">onion</a>.<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a>.<a href="http://radio.arav.i2p">i2p</a><sup><a href="http://radio.arav.i2p/?i2paddresshelper=NfCKBu9vjLFiBMEPQGiZT9AzGlhkKHzYrKM66FL-ESeDbnYUY--NzukO9UA28s3WThhDQVge2TmyfYsaZiUw~AjuLsykxS13pebs7lkAVY1jm77La-eFFIAQ22Vtd2YgS0vbhRMzuDxKkCR1vPwNax8R2o6a07xsQvvDml6UQxG4p5vt44JA2geQNvQfm8cEiSa6gNJZJSW3rWuLDg6~1Jy3D70oSVSlNfihmG4JtNV6tVBjJE2h5gUxfhYZACttpGTPM~UNF~lrSujlBQsCqdzvLswdMw~FnvpfGzJcJroeFTerRyH6oUkkDSOK7uWwl0e70vKxrIbFgJjKtjlLWlUCI5N0TnJP4Hzt2pttB~R0hSr2vVl8ky0yJEtN3rwnrJkw7q0ZIH30ngTfxsCTbolAzl6liN9Ez5YF97zDOPnVFmvQ6Eg1PyFdypQO1PiUHqF56SWhx3utGwecUS6jJCvsKIJJVEIMVcD6h7S0z1g1rqQ4jbg5UfXPwFEgFOlzBQAEAAcAAA==" title="Address helper">ah</a></sup>
<a href="http://arav.i2p">i2p</a> </span>
<sup><a href="http://arav.i2p/?i2paddresshelper=5Kl-DiWbbk6wf7m0v6zBSNHYq3sXlnrWLIWVeGdpPbPyc9CBS~zrzDYpP43rv1fRiIkbVCD5hTEpY6joQGlk-dFkWWD6201qa6ecsDVQMaE3Q7UTYICd0VEBRoqDUSrvsM-P2y5oG4Z-77RmoGKpbcRgNuMVbQ7AGJNqVSGej-lSyscDWTIZT5dCT505lfRwprdD~emZqkwnn22X16Wpj-X4A4ifph4idrThGioz4UW6PrCpa-oebMCo217s0Zyl9VKaU-o9cx5eFUEwnshoUjqwh7VE-S45NDz854J08xldCATM3wwTRVXhc2NUypsJLKFKiV0z3EXN-ApCdxsV60C-eiXUTX5vYcHHH~imA79v8WKFybjnsyUBst5BBEPQIUifTceLUrTmQ9TUpaMV90EsD5SCshmCfOs8R5y2dK6EfQu8iyYAB5VFSH4M1CLiBZUsDTEFiOomn2JGMDnbPho8lMB8ss4SMuwZShb2LlGqLxJ38kRHlvC68VmJO7InBQAEAAcAAA==" title="Address helper">ah</a></sup> <span>
</span> <a href="https://files.arav.su">files</a>.<a href="http://qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion">onion</a>.<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a>.<a href="http://files.arav.i2p">i2p</a><sup><a href="http://files.arav.i2p/?i2paddresshelper=48vtYgeVnju7B2FaR0zxUL3MQXN9QjK~Ggya45aANwm86mtpemuEkaskJmEQaFSd4FcDAFIiXHfNpfGqoupLwNmtgBmGRcuVV8xb2W~W6lM0oOhovjB37EUaMWs3AI5aIES84QOqApgwYX-ANIcwa~Kg6AbMuX8D8qnejuhBbuCffYah-TD8e~O0cnyqxzLTmxIGCyk2egdYXwanJyYFDocomIVfcqfJ0MgjIHhFQtkcb0e84bxvDzcAFIpEDrzAo4GVrFn-TCu0Lyf2ccqmVpucFl0UGhuVRxEt19KLd3PxlfwHv2lmzTZtq9CbnfaoPntUPx1sf84QnZDmrXWhVK8p3VvuPZMxjyz9KyhPjrGkO4E0oibDlvKuMsGEm-GkZsKxgXo~CrdcVtN8suAwW6rACAuk8gq2jUMBZBZ12migPZ7miHftEkOFHfgfUiKBwirrw~y9Zi261WX4-EVe2oD4pkhQOrqOKIoI-vv5z9CpQ7PKL531kgkipcBseXybBQAEAAcAAA==" title="Address helper">ah</a></sup>
<a href="https://arav.neocities.org">arav.neocities.org</a> </span>
<br> <span>
<span> <a href="https://upload.arav.su">upload</a>.<a href="http://4usftbmjpfexkr2x5xbp5ukmygpmg4fgrnx2wbifsexqctooz5hmviyd.onion">onion</a>.<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a>.<a href="http://upload.arav.i2p">i2p</a><sup><a href="http://upload.arav.i2p/?i2paddresshelper=b5NWA2vNydWSv6~8KN4e~td2UVGkYsayKPa1PnXI87A3gsg6m978tIehHLVN4XcCfUq4aB-59hqqZicorRnHKfV3lVdx9mdhC8Bhj~bMAcwMgWoXidqZNrWMoFGzWotFsa3nWh4zsRUSfrokecC8u9Y06byfSS1siyak0J6xpsggXRqqgNF0-8ncPeqvzBxHB9NRDXWEVJGS9HSpydWl1UpjgZffcd~NZroxkSAfughHcFAn2OLKkaZRe6WqCJQfJoXTCyz4wkFmYbH1CSddWlddmWaaU7icsbQrZm3XEqKTVKvm86G6ehxmzyHqCumc4GOWswcP0E51UQVOv-WA8R6SWQAj6ZnZhnCoCNFFEfW2lBiDmTnLJbfm-C-AdI6G1~dQ1~3FCH6wXWy-2DebpyoVVt9epzU7l4l2MVeaOUahbf6wcol1UbxPoR0XlGCXDe9700TYePjtpOU9vNkk2B1dQiZ1usgwseYuO26cRogSvbi8poz4BlCNO733HR1XBQAEAAcAAA==" title="Address helper">ah</a></sup>
<a href="https://radio.arav.su">radio</a> </span>
. <span>
<a href="http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion">onion</a> <a href="https://git.arav.su">git</a>.<a href="http://qqitm7qlsbbubwmjos4cqzmvkqidg34rfnbyhuydhalep33fbvh22xyd.onion">onion</a>.<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a>.<a href="http://git.arav.i2p">i2p</a><sup><a href="http://git.arav.i2p/?i2paddresshelper=eFIfcBUv3lHFSnglHfncs5XXtYwm9gCpmAYuio~9CeENBAXKRggPiY1tQC-otCon2hCSpr56WlVBeZk1txKuUnbjHTN7GBFaKW5wJEO2WmKEWPKdcjUDOYZN0D3TwXaYfiBuELD3200lBfDmPEJ01iC2o7B5yvpOqtEKDcaqkIp4vafDuPPumJ~XiCGdUAe~vr52w3Tbuz5x7wbltk-gUELY0-ZAQBos4jOJ6QT1W1lhycHPhAK8qslgwfk94opyIl2pkRyuJhU-2VHc6Fsd621VXC86YAMT1SIfTZlFpoGVCFXDM~BXaLvygFaKf62qardAe0T48Ax6GxosAKXe-yLCVRaiD3KErULfwZXl23kQzRfxM4odG4DWeXawtuvypOmTjHT1skQHU0h52ujye5nT~2bOy14HkCoCnxJ7gSj3MjkmWLd1JhBsPH4ymRmI7jFJR1GYl8Wp5IigMBBzWfJUEEjS7QDHaRo5TCZJ9SXz6sgkGdfh74~r8FWL559gBQAEAAcAAA==" title="Address helper">ah</a></sup>
. </span>
<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a> </section>
. <section>
<a href="http://radio.arav.i2p">i2p</a> <p>
<sup><a href="http://radio.arav.i2p/?i2paddresshelper=NfCKBu9vjLFiBMEPQGiZT9AzGlhkKHzYrKM66FL-ESeDbnYUY--NzukO9UA28s3WThhDQVge2TmyfYsaZiUw~AjuLsykxS13pebs7lkAVY1jm77La-eFFIAQ22Vtd2YgS0vbhRMzuDxKkCR1vPwNax8R2o6a07xsQvvDml6UQxG4p5vt44JA2geQNvQfm8cEiSa6gNJZJSW3rWuLDg6~1Jy3D70oSVSlNfihmG4JtNV6tVBjJE2h5gUxfhYZACttpGTPM~UNF~lrSujlBQsCqdzvLswdMw~FnvpfGzJcJroeFTerRyH6oUkkDSOK7uWwl0e70vKxrIbFgJjKtjlLWlUCI5N0TnJP4Hzt2pttB~R0hSr2vVl8ky0yJEtN3rwnrJkw7q0ZIH30ngTfxsCTbolAzl6liN9Ez5YF97zDOPnVFmvQ6Eg1PyFdypQO1PiUHqF56SWhx3utGwecUS6jJCvsKIJJVEIMVcD6h7S0z1g1rqQ4jbg5UfXPwFEgFOlzBQAEAAcAAA==" title="Address helper">ah</a></sup> Welcome, Anon. I'm Alexander aka Arav, I self-host some services for myself, friends and you. Not much I have to offer, but maybe you'll find something useful for yourself. :)
</span> </p>
<span> </section>
<a href="https://files.arav.su">files</a> }
. }
<a href="http://qf5e43nlhvnrutmikuvbdfj3cmtthokpbaxtkm6mjlslttzvtgm4fxid.onion">onion</a>
. templ indexHead() {
<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a> <link rel="stylesheet" href="assets/css/index.css"/>
.
<a href="http://files.arav.i2p">i2p</a>
<sup><a href="http://files.arav.i2p/?i2paddresshelper=48vtYgeVnju7B2FaR0zxUL3MQXN9QjK~Ggya45aANwm86mtpemuEkaskJmEQaFSd4FcDAFIiXHfNpfGqoupLwNmtgBmGRcuVV8xb2W~W6lM0oOhovjB37EUaMWs3AI5aIES84QOqApgwYX-ANIcwa~Kg6AbMuX8D8qnejuhBbuCffYah-TD8e~O0cnyqxzLTmxIGCyk2egdYXwanJyYFDocomIVfcqfJ0MgjIHhFQtkcb0e84bxvDzcAFIpEDrzAo4GVrFn-TCu0Lyf2ccqmVpucFl0UGhuVRxEt19KLd3PxlfwHv2lmzTZtq9CbnfaoPntUPx1sf84QnZDmrXWhVK8p3VvuPZMxjyz9KyhPjrGkO4E0oibDlvKuMsGEm-GkZsKxgXo~CrdcVtN8suAwW6rACAuk8gq2jUMBZBZ12migPZ7miHftEkOFHfgfUiKBwirrw~y9Zi261WX4-EVe2oD4pkhQOrqOKIoI-vv5z9CpQ7PKL531kgkipcBseXybBQAEAAcAAA==" title="Address helper">ah</a></sup>
</span>
<span>
<a href="https://upload.arav.su">upload</a>
.
<a href="http://4usftbmjpfexkr2x5xbp5ukmygpmg4fgrnx2wbifsexqctooz5hmviyd.onion">onion</a>
.
<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a>
.
<a href="http://upload.arav.i2p">i2p</a>
<sup><a href="http://upload.arav.i2p/?i2paddresshelper=b5NWA2vNydWSv6~8KN4e~td2UVGkYsayKPa1PnXI87A3gsg6m978tIehHLVN4XcCfUq4aB-59hqqZicorRnHKfV3lVdx9mdhC8Bhj~bMAcwMgWoXidqZNrWMoFGzWotFsa3nWh4zsRUSfrokecC8u9Y06byfSS1siyak0J6xpsggXRqqgNF0-8ncPeqvzBxHB9NRDXWEVJGS9HSpydWl1UpjgZffcd~NZroxkSAfughHcFAn2OLKkaZRe6WqCJQfJoXTCyz4wkFmYbH1CSddWlddmWaaU7icsbQrZm3XEqKTVKvm86G6ehxmzyHqCumc4GOWswcP0E51UQVOv-WA8R6SWQAj6ZnZhnCoCNFFEfW2lBiDmTnLJbfm-C-AdI6G1~dQ1~3FCH6wXWy-2DebpyoVVt9epzU7l4l2MVeaOUahbf6wcol1UbxPoR0XlGCXDe9700TYePjtpOU9vNkk2B1dQiZ1usgwseYuO26cRogSvbi8poz4BlCNO733HR1XBQAEAAcAAA==" title="Address helper">ah</a></sup>
</span>
<span>
<a href="https://git.arav.su">git</a>
.
<a href="http://qqitm7qlsbbubwmjos4cqzmvkqidg34rfnbyhuydhalep33fbvh22xyd.onion">onion</a>
.
<a href="http://[300:a98d:d6d0:8a08::e]">ygg</a>
.
<a href="http://git.arav.i2p">i2p</a>
<sup><a href="http://git.arav.i2p/?i2paddresshelper=eFIfcBUv3lHFSnglHfncs5XXtYwm9gCpmAYuio~9CeENBAXKRggPiY1tQC-otCon2hCSpr56WlVBeZk1txKuUnbjHTN7GBFaKW5wJEO2WmKEWPKdcjUDOYZN0D3TwXaYfiBuELD3200lBfDmPEJ01iC2o7B5yvpOqtEKDcaqkIp4vafDuPPumJ~XiCGdUAe~vr52w3Tbuz5x7wbltk-gUELY0-ZAQBos4jOJ6QT1W1lhycHPhAK8qslgwfk94opyIl2pkRyuJhU-2VHc6Fsd621VXC86YAMT1SIfTZlFpoGVCFXDM~BXaLvygFaKf62qardAe0T48Ax6GxosAKXe-yLCVRaiD3KErULfwZXl23kQzRfxM4odG4DWeXawtuvypOmTjHT1skQHU0h52ujye5nT~2bOy14HkCoCnxJ7gSj3MjkmWLd1JhBsPH4ymRmI7jFJR1GYl8Wp5IigMBBzWfJUEEjS7QDHaRo5TCZJ9SXz6sgkGdfh74~r8FWL559gBQAEAAcAAA==" title="Address helper">ah</a></sup>
</span>
</section>
<section>
<p>
Welcome, Anon. I'm Alexander aka Arav, I self-host some services for myself, friends and you. Not much I have to offer, but maybe you'll find something useful for yourself. :)
</p>
</section>
}
} }

56
web/mindflow.templ Normal file
View File

@ -0,0 +1,56 @@
package web
import "net/http"
import "strings"
import "time"
import "git.arav.su/Arav/dwelling-home/pkg/mindflow"
import "git.arav.su/Arav/dwelling-home/pkg/util"
templ Mindflow(posts []mindflow.Post, categories []mindflow.Category, r *http.Request) {
@base("Mindflow", "Updates on my infrastructure, my very important opinions and thoughts.", "updates, thoughts, opinions, blog, diary", "/mindflow", "Mindflow", mindflowHead()) {
<p class="center">Here I post updates on websites and infrastructure, my very important opinions and thoughts no one asked for. If you'd like to subscribe to this bullshittery then <a href="/rss.xml">RSS feed</a> at your service. :)</p>
<section id="filter" class="hidden">
<p class="center">
<button name="all">All</button>
for _, category := range categories {
<button name={ strings.ToLower(category.Name) }>{ category.Name }</button>
}
</p>
</section>
<section>
for _, post := range posts {
<article id={ post.PostID() }>
<header>
<a href={ templ.SafeURL("#" + post.PostID()) }>
<h3>{ post.Category.Name }: { post.Title }</h3>
</a>
</header>
for _, line := range strings.Split(post.Body, "\n") {
if len(line) > 0 && line[0] == '>' {
<p class="quote">{ line }</p>
} else {
<p>{ line }</p>
}
}
<footer>
if post.URL != "" {
<a href={ templ.SafeURL(post.PostURL(r.Host, false)) }>{ post.PostURL(r.Host, false) }</a>
} else {
<span></span>
}
<time datetime={ util.ToClientTimezone(post.Date, r).Format(time.RFC1123) }>{ util.ToClientTimezone(post.Date, r).Format(time.RFC1123) }</time>
</footer>
</article>
}
if len(posts) == 0 {
<p class="center">Nothing? There must be some... Looks like database went down.</p>
}
</section>
}
}
templ mindflowHead() {
<link rel="stylesheet" href="/assets/css/mindflow.css"/>
<script src="/assets/js/mindflow.js" defer></script>
}

9
web/mindflow_admin.templ Normal file
View File

@ -0,0 +1,9 @@
package web
import "net/http"
import "git.arav.su/Arav/dwelling-home/pkg/mindflow"
templ MindflowAdmin(posts []mindflow.Post, categories []mindflow.Category, r *http.Request) {
@base("Mindflow Administration", "", "", "/mindflow/admin", "Mindflow Administration", nil)
}

29
web/privacy.templ Normal file
View File

@ -0,0 +1,29 @@
package web
import "net/http"
import "git.arav.su/Arav/dwelling-home/pkg/util"
templ Privacy(r *http.Request) {
@base("Privacy", "Privacy statements for all of my services.", "privacy statements", "/privacy", "Privacy", nil) {
<section id="privacy">
<h2>Privacy statements</h2>
<h3>General data</h3>
<p>Across all of my Web-services following data is being collected: date of access, IP-address, User-Agent, referer URL, request URL.</p>
<h3>Use of JavaScript</h3>
<p>JS is used on a <a href="/">main website</a> at a guestbook page to refresh CAPTCHA; at mindflow page to filter posts by categories.</p>
<p><a href={ templ.URL(util.GetServiceByHost(r.Host, util.ServiceRadio)) }>Radio service</a> uses JS to update Last N songs list section, and to get current radio statistics.</p>
<p><a href={ templ.URL(util.GetServiceByHost(r.Host, util.ServiceFiles)) }>Files service</a> uses JS to add functionality such as an overlay to view files without the need to leave a site, and implements some keyboard control for convenience.</p>
<h3>Upload service specific</h3>
<p>Every action (upload, download, and delete) is being logged and includes this additional data:</p>
<ol>
<li>File's name it was uploaded/downloaded with;</li>
<li>Unsalted SHA-256 hash of a file;</li>
<li>A salted hash encoded as base64 in raw URL variant that is used to download/delete a file;</li>
<li>File's size.</li>
</ol>
<p>And I will cooperate with law enforcements and provide them with all information (logs and a file itself if it is still present).</p>
<p>As already stated at <a href={ templ.URL(util.GetServiceByHost(r.Host, util.ServiceUpload)) }>Upload service</a>'s page, file's content must comply with law of Russian Federation. Anything like extremist materials, CP, and so on is forbidden.</p>
</section>
}
}

View File

@ -1,38 +1,37 @@
package web package web
import "time"
import "strings"
import "net/http" import "net/http"
import "strings"
import "time"
import "git.arav.su/Arav/dwelling-home/pkg/mindflow" import "git.arav.su/Arav/dwelling-home/pkg/mindflow"
import "git.arav.su/Arav/dwelling-home/pkg/util" import "git.arav.su/Arav/dwelling-home/pkg/util"
templ RSS(host, author string, posts []mindflow.Post, r *http.Request) { templ RSS(host, author string, posts []mindflow.Post, r *http.Request) {
@templ.Raw("<?xml version=\"1.0\" encoding=\"utf-8\" ?>") @templ.Raw("<?xml version=\"1.0\" encoding=\"utf-8\" ?>")
<rss version="2.0"> <rss version="2.0">
<channel> <channel>
<title>Arav's dwelling</title> <title>Arav's dwelling</title>
<description>What's going on with me and my machines.</description> <description>What's going on with me and my machines.</description>
<language>en-gb</language> <language>en-gb</language>
<link>{ host }</link> <link>{ host }</link>
for _, post := range posts {
for _, post := range posts { <item>
<item> <title>{ post.Title }</title>
<title>{ post.Title }</title> <category>{ post.Category.Name }</category>
<category>{ post.Category.Name }</category> <guid>{ post.PostID() }</guid>
<guid>{ post.PostID() }</guid> <pubDate>{ util.ToClientTimezone(post.Date, r).Format(time.RFC1123Z) }</pubDate>
<pubDate>{ util.ToClientTimezone(post.Date, r).Format(time.RFC1123Z) }</pubDate> <link>{ post.PostURL(r.Host, true) }</link>
<link>{ post.PostURL(r.Host, true) }</link> <author>{ author }</author>
<author>{ author }</author> <description>
<description> @templ.Raw("<![CDATA[")
@templ.Raw("<![CDATA[") for _, line := range strings.Split(post.Body, "\n") {
for _, line := range strings.Split(post.Body, "\n") { <p>{ line }</p>
<p>{ line }</p> }
} @templ.Raw("]]>")
@templ.Raw("]]>") </description>
</description> </item>
</item> }
} </channel>
</channel> </rss>
</rss>
} }

172
web/stuff.templ Normal file
View File

@ -0,0 +1,172 @@
package web
import "net/http"
import "git.arav.su/Arav/dwelling-home/pkg/util"
templ Stuff(r *http.Request) {
{{ gitSite := util.GetServiceByHost(r.Host, util.ServiceGit) }}
@base("Stuff", "Here I share my programs, scripts, articles, may be other stuff.", "articles, programs, personal projects, own music", "/stuff", "Stuff", nil) {
<p class="center">Here lies everything I've made that I'm willing to share.</p>
<section id="articles">
<h2>Articles</h2>
<table>
<tr>
<th>Last Update</th>
<th>Article</th>
</tr>
for _, entry := range Metadata {
<tr>
<td>
<time datetime={ entry.Date.Format("2006-01-02") }>{ entry.Date.Format("02 January 2006") }</time>
</td>
<td>
<a href={ templ.URL(entry.URL) }>{ entry.Title }</a>
</td>
</tr>
}
</table>
</section>
<section id="programs-scripts">
<h2>Programs and scripts</h2>
<table>
<tr>
<td><b>mccl</b></td>
<td>0.1.2 (14 December 2023)</td>
<td>Go</td>
<td>GPLv3</td>
<td>
<a href={ templ.URL(gitSite + "/Arav/mccl") }>source</a>
<a href={ templ.URL(gitSite + "/Arav/mccl/releases") }>releases</a>
</td>
</tr>
<tr>
<td colspan="5">
<p>A console Minecraft launcher. Take a look at README.md for explanation.</p>
</td>
</tr>
<tr>
<td><b>httpr</b></td>
<td>0.3.2 (20 September 2023)</td>
<td>Go</td>
<td>MIT</td>
<td>
<a href={ templ.URL(gitSite + "/Arav/httpr") }>source</a>
<a href={ templ.URL(gitSite + "/Arav/httpr/releases") }>releases</a>
</td>
</tr>
<tr>
<td colspan="5">
<p>A simple HTTP router that supports having both regular and parametrised path's parts at the same level, like <code>/assets/*filepath</code> and <code>/:a/:b</code> with the same HTTP method.</p>
<p>Yeah, not as efficient as httprouter is, but in my case no performance loose was noticed (on a small amount of paths). Yet I gained prettiness because it allowed me, for example, to ditch <code>/f/</code> part and leave just <code>/:hash/:name</code> instead of <code>/f/:hash/:name</code> in a dwelling-upload service.</p>
</td>
</tr>
<tr>
<td><b>justguestbook</b></td>
<td>1.3.2 (22 May 2023)</td>
<td>Go</td>
<td>MIT</td>
<td>
<a href={ templ.URL(gitSite + "/Arav/justguestbook") }>source</a>
</td>
</tr>
<tr>
<td colspan="5">
<p>A simple guestbook with owner's replies implementation made into a library.</p>
</td>
</tr>
<tr>
<td><b>justcaptcha</b></td>
<td>2.1.0 (12 August 2023)</td>
<td>Go</td>
<td>MIT</td>
<td>
<a href={ templ.URL(gitSite + "/Arav/justcaptcha") }>source</a>
</td>
</tr>
<tr>
<td colspan="5">
<p>A simple CAPTCHA implementation.</p>
</td>
</tr>
<tr>
<td><b>kwh-cost</b></td>
<td>1.1.3 (21 May 2024)</td>
<td>C</td>
<td>MIT+NIGGER</td>
<td>
<a href={ templ.URL(gitSite + "/Arav/kwh-cost") }>source</a>
</td>
</tr>
<tr>
<td colspan="5">
<p>KWh cost calculator in C.</p>
</td>
</tr>
<tr>
<td><b>httpprocprobed</b></td>
<td>3.1.1 (13 January 2024)</td>
<td>Go</td>
<td>MIT+NIGGER</td>
<td>
<a href={ templ.URL(gitSite + "/Arav/httpprocprobed") }>source</a>
</td>
</tr>
<tr>
<td colspan="5">
<p>It returns a list of processes and if they are running in a JSON format via HTTP GET request on <code>/processes</code> endpoint. Under the hood it searches in a /proc/ directory. It is being used on about page to show wether service's running or not.</p>
</td>
</tr>
<tr>
<td><b>ScrapTheChan</b></td>
<td>0.5.0 (3 May 2021)</td>
<td>Python</td>
<td>MIT</td>
<td>
<a href={ templ.URL(gitSite + "/Arav/ScrapTheChan") }>source</a>
<a href={ templ.URL(gitSite + "/Arav/ScrapTheChan/releases") }>releases</a>
</td>
</tr>
<tr>
<td colspan="5">
<p>Imageboards file scraper using theirs JSON API. Currently supported: 4chan.org, lainchan.org, 2ch.hk and 8kun.top.</p>
</td>
</tr>
<tr>
<td><b>OpenNIC active domains extraction script</b></td>
<td>10 July 2020</td>
<td>Bash</td>
<td>MIT</td>
<td>
<a href={ templ.URL(gitSite + "/Arav/opennic-extract-domains") }>source</a>
</td>
</tr>
<tr>
<td colspan="5">
<p>Bash script for BIND9 that extracts domains from zone files of OpenNIC and forms a list of domains that have an IP-address.</p>
</td>
</tr>
<tr>
<td><b>PiggyBank</b></td>
<td>1.0.0 (8 July 2020)</td>
<td>Python</td>
<td>MIT</td>
<td>
<a href={ templ.URL(gitSite + "/Arav/PiggyBank") }>source</a>
<a href={ templ.URL(gitSite + "/Arav/PiggyBank/releases") }>releases</a>
</td>
</tr>
<tr>
<td colspan="5">
<p>A program to help you to keep track of your piggy bank. A simple script I once wrote that I rewrote and made into a package just to learn how to do it.</p>
</td>
</tr>
</table>
</section>
<section id="music">
<h2>Music</h2>
<p>There was a period in my life when I was playing with audio sequencers. I lost all project files and only 3 tracks survived in mp3 and they <a href={ templ.URL(util.GetServiceByHost(r.Host, util.ServiceFiles) + "/music/My%20tracks,%20that%20survived/") }>can be found here</a>.</p>
</section>
}
}