0
0
mirror of https://github.com/rls-moe/nyx synced 2025-10-13 12:04:15 +00:00

MVP, no mod tools or anything but it works

This commit is contained in:
Tim Schuster
2017-03-12 20:37:53 +01:00
parent 70b12c516a
commit 69b0d20825
186 changed files with 44200 additions and 0 deletions

119
http/admin/handler.go Normal file
View File

@@ -0,0 +1,119 @@
package admin
import (
"bytes"
"github.com/GeertJohan/go.rice"
"github.com/icza/session"
"github.com/pressly/chi"
"github.com/tidwall/buntdb"
"go.rls.moe/nyx/http/errw"
"go.rls.moe/nyx/http/middle"
"go.rls.moe/nyx/resources"
"html/template"
"net/http"
"time"
)
var riceConf = rice.Config{
LocateOrder: []rice.LocateMethod{
rice.LocateWorkingDirectory,
rice.LocateEmbedded,
rice.LocateAppended,
},
}
var box = riceConf.MustFindBox("http/admin/res/")
var (
panelTmpl = template.New("admin/panel")
loginTmpl = template.New("admin/login")
)
func init() {
var err error
panelTmpl, err = panelTmpl.Parse(box.MustString("panel.html"))
if err != nil {
panic(err)
}
loginTmpl, err = loginTmpl.Parse(box.MustString("index.html"))
if err != nil {
panic(err)
}
}
// Router sets up the Administration Panel
// It **must** be setup on the /admin/ basepath
func Router(r chi.Router) {
r.Get("/", serveLogin)
r.Get("/index.html", serveLogin)
r.Get("/panel.html", servePanel)
r.Post("/new_board.sh", handleNewBoard)
r.Post("/login.sh", handleLogin)
r.Post("/logout.sh", handleLogout)
}
func serveLogin(w http.ResponseWriter, r *http.Request) {
dat := bytes.NewBuffer([]byte{})
err := loginTmpl.Execute(dat, middle.GetBaseCtx(r))
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
http.ServeContent(w, r, "index.html", time.Now(),
bytes.NewReader(dat.Bytes()))
}
func servePanel(w http.ResponseWriter, r *http.Request) {
sess := middle.GetSession(r)
if sess == nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorized"))
return
}
dat := bytes.NewBuffer([]byte{})
err := panelTmpl.Execute(dat, middle.GetBaseCtx(r))
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
http.ServeContent(w, r, "panel.html", time.Now(),
bytes.NewReader(dat.Bytes()))
}
func handleLogout(w http.ResponseWriter, r *http.Request) {
sess := middle.GetSession(r)
if sess == nil {
http.Redirect(w, r, "/admin/index.html", http.StatusSeeOther)
}
session.Remove(sess, w)
http.Redirect(w, r, "/admin/index.html", http.StatusSeeOther)
}
func handleLogin(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
errw.ErrorWriter(err, w, r)
}
db := middle.GetDB(r)
var admin = &resources.AdminPass{}
err = db.View(func(tx *buntdb.Tx) error {
var err error
admin, err = resources.GetAdmin(tx, r.FormValue("id"))
return err
})
if err != nil {
err = errw.MakeErrorWithTitle("Access Denied", "User or Password Invalid")
errw.ErrorWriter(err, w, r)
}
err = admin.VerifyLogin(r.FormValue("pass"))
if err != nil {
err = errw.MakeErrorWithTitle("Access Denied", "User or Password Invalid")
errw.ErrorWriter(err, w, r)
}
sess := session.NewSessionOptions(&session.SessOptions{
CAttrs: map[string]interface{}{"mode": "admin"},
})
session.Add(sess, w)
http.Redirect(w, r, "/admin/panel.html", http.StatusSeeOther)
}

58
http/admin/newboard.go Normal file
View File

@@ -0,0 +1,58 @@
package admin
import (
"errors"
"github.com/tidwall/buntdb"
"go.rls.moe/nyx/http/errw"
"go.rls.moe/nyx/http/middle"
"go.rls.moe/nyx/resources"
"net/http"
)
func handleNewBoard(w http.ResponseWriter, r *http.Request) {
sess := middle.GetSession(r)
if sess == nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorized"))
return
}
if sess.CAttr("mode") != "admin" {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("Unauthorized"))
return
}
err := r.ParseForm()
if err != nil {
errw.ErrorWriter(err, w, r)
}
db := middle.GetDB(r)
var board = &resources.Board{}
board.ShortName = r.FormValue("shortname")
board.LongName = r.FormValue("longname")
if board.ShortName == "" {
errw.ErrorWriter(errors.New("Need shortname"), w, r)
return
}
if board.ShortName == "admin" && board.ShortName == "@" {
errw.ErrorWriter(errors.New("No"), w, r)
}
if board.LongName == "" && len(board.LongName) < 5 {
errw.ErrorWriter(errors.New("Need 5 characters for long name"), w, r)
return
}
if err = db.Update(func(tx *buntdb.Tx) error {
return resources.NewBoard(tx, r.Host, board)
}); err != nil {
errw.ErrorWriter(err, w, r)
return
}
http.Redirect(w, r, "/admin/panel.html", http.StatusSeeOther)
}

41
http/admin/res/index.html Normal file
View File

@@ -0,0 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Config.Site.Title}} Admin Login</title>
<link rel="stylesheet" href="/@/style.css">
<link rel="stylesheet" href="/@/custom.css">
<link rel="stylesheet" href="/@/admin.css">
</head>
<body>
<div class="admin login">
<form action="/admin/login.sh" method="POST">
<input
type="hidden"
name="csrf_token"
value="{{ .CSRFToken }}" />
<div class="admin form row">
<input
class="admin form input"
type="text"
name="id"
placeholder="admin id"
minlength="3"/>
</div>
<div class="admin form row">
<input
class="admin form input"
type="password"
name="pass"
placeholder="password"
minlength="3"/>
</div>
<div class="admin form row">
<input class="admin form input halfsize" type="submit" value="Login"/>
<input class="admin form input halfsize" type="reset" value="Reset"/>
</div>
</form>
</div>
</body>
</html>

57
http/admin/res/panel.html Normal file
View File

@@ -0,0 +1,57 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Config.Site.Title}} Admin Panel</title>
<link rel="stylesheet" href="/@/style.css">
<link rel="stylesheet" href="/@/custom.css">
<link rel="stylesheet" href="/@/admin.css">
</head>
<body>
<div class="welcome">
Welcome {{.Admin.Id}}<br>
<form class="form logout" method="POST" action="/admin/logout.sh">
<input
type="hidden"
name="csrf_token"
value="{{ .CSRFToken }}" />
<input type="submit" value="Logout" />
</form>
</div>
<div class="panel boardmgr">
<form method="POST" action="/admin/new_board.sh">
<input
type="hidden"
name="csrf_token"
value="{{ .CSRFToken }}" />
<input type="text" placeholder="shortname" name="shortname"/>
<input type="text" placeholder="longname" name="longname"/>
<input type="submit" value="Create Board" />
<input type="reset" value="Reset" />
</form>
</div>
<div class="panel remover post">
<form method="POST" action="/admin/rem_post.sh">
<input
type="hidden"
name="csrf_token"
value="{{ .CSRFToken }}" />
<input type="text" placeholder="post id" name="post id"/>
<input type="submit" value="Remove Post" />
<input type="reset" value="Reset" />
</form>
</div>
<div class="panel remover thread">
<form method="POST" action="/admin/rem_thread.sh">
<input
type="hidden"
name="csrf_token"
value="{{ .CSRFToken }}" />
<input type="text" placeholder="thread id" name="thread id"/>
<input type="submit" value="Remove thread" />
<input type="reset" value="Reset" />
</form>
</div>
</body>
</html>

52
http/board/board.go Normal file
View File

@@ -0,0 +1,52 @@
package board
import (
"bytes"
"github.com/pressly/chi"
"github.com/tidwall/buntdb"
"go.rls.moe/nyx/http/errw"
"go.rls.moe/nyx/http/middle"
"go.rls.moe/nyx/resources"
"log"
"net/http"
"time"
)
func serveBoard(w http.ResponseWriter, r *http.Request) {
dat := bytes.NewBuffer([]byte{})
db := middle.GetDB(r)
ctx := middle.GetBaseCtx(r)
err := db.View(func(tx *buntdb.Tx) error {
bName := chi.URLParam(r, "board")
b, err := resources.GetBoard(tx, r.Host, bName)
if err != nil {
return err
}
ctx["Board"] = b
threads, err := resources.ListThreads(tx, r.Host, bName)
if err != nil {
return err
}
log.Println("Number of Thread on board: ", len(threads))
for k := range threads {
err := resources.FillReplies(tx, r.Host, threads[k])
if err != nil {
return err
}
}
ctx["Threads"] = threads
return nil
})
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
err = boardTmpl.Execute(dat, ctx)
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
http.ServeContent(w, r, "board.html", time.Now(), bytes.NewReader(dat.Bytes()))
}

88
http/board/handler.go Normal file
View File

@@ -0,0 +1,88 @@
package board
import (
"bytes"
"github.com/GeertJohan/go.rice"
"github.com/pressly/chi"
"github.com/tidwall/buntdb"
"go.rls.moe/nyx/http/errw"
"go.rls.moe/nyx/http/middle"
"go.rls.moe/nyx/resources"
"html/template"
"net/http"
"time"
)
var riceConf = rice.Config{
LocateOrder: []rice.LocateMethod{
rice.LocateWorkingDirectory,
rice.LocateEmbedded,
rice.LocateAppended,
},
}
var box = riceConf.MustFindBox("http/board/res/")
var (
dirTmpl = template.New("board/dir")
boardTmpl = template.New("board/board")
threadTmpl = template.New("board/thread")
hdlFMap = template.FuncMap{
"renderText": resources.OperateReplyText,
}
)
func init() {
var err error
dirTmpl, err = dirTmpl.Parse(box.MustString("dir.html"))
if err != nil {
panic(err)
}
boardTmpl, err = boardTmpl.Funcs(hdlFMap).Parse(box.MustString("board.html"))
if err != nil {
panic(err)
}
threadTmpl, err = threadTmpl.Funcs(hdlFMap).Parse(box.MustString("thread.html"))
if err != nil {
panic(err)
}
}
func Router(r chi.Router) {
r.Get("/", serveDir)
r.Get("/dir.html", serveDir)
r.Get("/:board/board.html", serveBoard)
r.Post("/:board/new_thread.sh", handleNewThread)
r.Get("/:board/:thread/thread.html", serveThread)
r.Get("/:board/:thread/:post/post.html", servePost)
r.Post("/:board/:thread/reply.sh", handleNewReply)
}
func servePost(w http.ResponseWriter, r *http.Request) {
return
}
func serveDir(w http.ResponseWriter, r *http.Request) {
dat := bytes.NewBuffer([]byte{})
db := middle.GetDB(r)
ctx := middle.GetBaseCtx(r)
err := db.View(func(tx *buntdb.Tx) error {
bList, err := resources.ListBoards(tx, r.Host)
if err != nil {
return err
}
ctx["Boards"] = bList
return nil
})
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
err = dirTmpl.Execute(dat, ctx)
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
http.ServeContent(w, r, "dir.html", time.Now(), bytes.NewReader(dat.Bytes()))
}

59
http/board/newreply.go Normal file
View File

@@ -0,0 +1,59 @@
package board
import (
"fmt"
"github.com/pressly/chi"
"github.com/tidwall/buntdb"
"go.rls.moe/nyx/http/errw"
"go.rls.moe/nyx/http/middle"
"go.rls.moe/nyx/resources"
"net/http"
"strconv"
)
func handleNewReply(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
var reply = &resources.Reply{}
reply.Board = chi.URLParam(r, "board")
tid, err := strconv.Atoi(chi.URLParam(r, "thread"))
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
reply.Thread = int64(tid)
reply.Text = r.FormValue("text")
if len(reply.Text) > 1000 {
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are too many characters"), w, r)
return
}
if len(reply.Text) < 10 {
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are not enough characters"), w, r)
return
}
reply.Metadata = map[string]string{}
if r.FormValue("tripcode") != "" {
reply.Metadata["trip"] = resources.CalcTripCode(r.FormValue("tripcode"))
} else {
reply.Metadata["trip"] = "Anonymous"
}
db := middle.GetDB(r)
if err = db.Update(func(tx *buntdb.Tx) error {
thread, err := resources.GetThread(tx, r.Host, reply.Board, reply.Thread)
if err != nil {
return err
}
return resources.NewReply(tx, r.Host, reply.Board, thread, reply, false)
}); err != nil {
errw.ErrorWriter(err, w, r)
return
}
http.Redirect(w, r, fmt.Sprintf("/%s/%d/thread.html", chi.URLParam(r, "board"), reply.Thread), http.StatusSeeOther)
}

48
http/board/newthread.go Normal file
View File

@@ -0,0 +1,48 @@
package board
import (
"fmt"
"github.com/pressly/chi"
"github.com/tidwall/buntdb"
"go.rls.moe/nyx/http/errw"
"go.rls.moe/nyx/http/middle"
"go.rls.moe/nyx/resources"
"net/http"
)
func handleNewThread(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
var thread = &resources.Thread{}
var mainReply = &resources.Reply{}
mainReply.Board = chi.URLParam(r, "board")
thread.Board = chi.URLParam(r, "board")
mainReply.Text = r.FormValue("text")
if len(mainReply.Text) > 1000 {
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are too many characters"), w, r)
return
}
if len(mainReply.Text) < 10 {
errw.ErrorWriter(errw.MakeErrorWithTitle("I'm sorry but I can't do that", "These are not enough characters"), w, r)
return
}
mainReply.Metadata = map[string]string{}
if r.FormValue("tripcode") != "" {
mainReply.Metadata["trip"] = resources.CalcTripCode(r.FormValue("tripcode"))
}
db := middle.GetDB(r)
if err = db.Update(func(tx *buntdb.Tx) error {
return resources.NewThread(tx, r.Host, mainReply.Board, thread, mainReply)
}); err != nil {
errw.ErrorWriter(err, w, r)
return
}
http.Redirect(w, r, fmt.Sprintf("/%s/%d/thread.html", chi.URLParam(r, "board"), thread.ID), http.StatusSeeOther)
}

119
http/board/res/board.html Normal file
View File

@@ -0,0 +1,119 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Config.Site.Title}} - /{{.Board.ShortName}}/</title>
<link rel="stylesheet" href="/@/style.css">
<link rel="stylesheet" href="/@/custom.css">
</head>
<body>
<div class="banner logo">
<div class="site title"><h1><span class="reflink"><a href="/{{.Board.ShortName}}/board.html">/{{.Board.ShortName}}/</a></span></h1></div>
<div class="site description"><h2>{{.Board.LongName}}</h2></div>
</div>
{{ $boardlink := .Board.ShortName }}
<div class="postarea">
<form id="postform" action="/{{$boardlink}}/new_thread.sh" method="POST">
<table>
<tbody>
<tr>
<td class="postblock">
TripCode
</td>
<td>
<input type="text" name="tripcode" size=48 placeholder="Anonymous"/>
<input
type="hidden"
name="csrf_token"
value="{{ .CSRFToken }}" />
</td>
</tr>
<tr>
<td class="postblock">
Comment
</td>
<td>
<textarea
name="text"
placeholder="your comment"
rows="4"
cols="48"
minlength="10"
required
></textarea>
</td>
</tr>
<tr>
<td class="postblock">
Image File
</td>
<td>
<input type="file" name="image" />
</td>
</tr>
{{ if ne .Config.Captcha.Mode "disabled" }}
<tr>
<td class="postblock">
Captcha
</td>
<td>
<input type="text" name="captcha" size=48 />
<input type="hidden"
value="{{.CaptchaToken}}"/>
<img alt=""
src="{{.CaptchaImage}}" />
</td>
</tr>
{{ end }}
<tr>
<td class="postblock">
</td>
<td>
<input type="submit" value="Post" />
</td>
</tr>
</tbody>
</table>
</form>
</div>
<hr />
<div class="postlists">
{{range .Threads}}
{{ $threadrid := .GetReply.ID }}
<label><span class="postertrip">
{{ if .GetReply.Metadata.trip }}
{{.GetReply.Metadata.trip}}
{{ else }}
Anonymous
{{ end }}
</span></label>
<span class="reflink"><a href="/{{$boardlink}}/{{.ID}}/thread.html">No.{{.ID}}</a></span>
<blockquote><blockquote>
{{ renderText .GetReply.Text}}
</blockquote></blockquote>
{{range .GetReplies}}
{{ if ne .ID $threadrid }}
<table><tbody><tr><td class="doubledash">&gt;&gt;</td>
<td class="reply" id="reply{{.ID}}">
<label><span class="postertrip">
{{ if .Metadata.trip }}
{{.Metadata.trip}}
{{ else }}
Anonymous
{{ end }}
</span></label>
<span class="reflink"><a href="/{{$boardlink}}/{{.Thread}}/thread.html">No.{{.ID}}</a></span>
<blockquote><blockquote>
{{ renderText .Text}}
</blockquote></blockquote>
</td>
</tr></tbody></table>
{{end}}
{{end}}
<br clear="left" /><hr />
{{end}}
</div>
</body>
</html>

30
http/board/res/dir.html Normal file
View File

@@ -0,0 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Config.Site.Title}} Boards</title>
<link rel="stylesheet" href="/@/style.css">
<link rel="stylesheet" href="/@/custom.css">
</head>
<body>
<div class="banner logo">
<div class="site title"><h1>{{.Config.Site.Title}}</h1></div>
<div class="site description"><h2>{{.Config.Site.Description}}</h2></div>
</div>
<div class="boardlist">
<div class="boardtitle">
<h3>Boards</h3>
</div>
<div class="boardlist">
<ul>
{{range .Boards}}
<li>
<a class="boardlink" href="/{{ .ShortName}}/board.html">{{.ShortName}}: {{.LongName}}</a>
</li>
{{end}}
</ul>
</div>
</div>
</body>
</html>

129
http/board/res/thread.html Normal file
View File

@@ -0,0 +1,129 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Config.Site.Title}} - /{{.Board.ShortName}}/</title>
<link rel="stylesheet" href="/@/style.css">
<link rel="stylesheet" href="/@/custom.css">
</head>
<body>
<div class="banner logo">
<div class="site title"><h1><span class="reflink"><a href="/{{.Board.ShortName}}/board.html">/{{.Board.ShortName}}/</a></span></h1></div>
<div class="site description"><h2>{{.Board.LongName}}</h2></div>
</div>
{{ $boardlink := .Board.ShortName }}
<hr />
<div class="postarea">
<form id="postform" action="/{{.Board.ShortName}}/{{.Thread.ID}}/reply.sh" method="POST">
<table>
<tbody>
<tr>
<td class="postblock">
TripCode
</td>
<td>
<input type="text" name="tripcode" size=48 placeholder="Anonymous"/>
<input
type="hidden"
name="csrf_token"
value="{{ .CSRFToken }}" />
</td>
</tr>
<tr>
<td class="postblock">
Comment
</td>
<td>
<textarea
name="text"
placeholder="your comment"
rows="4"
cols="48"
minlength="10"
required
></textarea>
</td>
</tr>
<tr>
<td class="postblock">
Image File
</td>
<td>
<input type="file" name="image" />
</td>
</tr>
{{ if ne .Config.Captcha.Mode "disabled" }}
<tr>
<td class="postblock">
Captcha
</td>
<td>
<input type="text" name="captcha" size=48 />
<input type="hidden"
value="{{.CaptchaToken}}"/>
<img alt=""
src="{{.CaptchaImage}}" />
</td>
</tr>
{{ end }}
<tr>
<td class="postblock">
</td>
<td>
<input type="submit" value="Post" />
</td>
</tr>
{{ if .Board.Metadata.rules }}
<tr>
<td class="postblock">
Rules
</td>
<td>
{{ .Board.Metadata.rules }}
</td>
</tr>
{{ end }}
</tbody>
</table>
</form>
</div>
<div class="postlists">
{{with .Thread }}
{{ $threadrid := .GetReply.ID }}
<label><span class="postertrip">
{{ if .GetReply.Metadata.trip }}
{{.GetReply.Metadata.trip}}
{{ else }}
Anonymous
{{ end }}
</span></label>
<span class="reflink"><a href="/{{$boardlink}}/{{.ID}}/thread.html">No.{{.ID}}</a></span>
<blockquote><blockquote>
{{ renderText .GetReply.Text}}
</blockquote></blockquote>
{{range .GetReplies}}
{{ if ne .ID $threadrid }}
<table><tbody><tr><td class="doubledash">&gt;&gt;</td>
<td class="reply" id="reply{{.ID}}">
<label><span class="postertrip">
{{ if .Metadata.trip }}
{{.Metadata.trip}}
{{ else }}
Anonymous
{{ end }}
</span></label>
<span class="reflink"><a href="/{{$boardlink}}/{{.Thread}}/thread.html">No.{{.ID}}</a></span>
<blockquote><blockquote>
{{ renderText .Text}}
</blockquote></blockquote>
</td>
</tr></tbody></table>
{{end}}
{{end}}
<br clear="left" /><hr />
{{end}}
</div>
</body>
</html>

57
http/board/thread.go Normal file
View File

@@ -0,0 +1,57 @@
package board
import (
"bytes"
"github.com/pressly/chi"
"github.com/tidwall/buntdb"
"go.rls.moe/nyx/http/errw"
"go.rls.moe/nyx/http/middle"
"go.rls.moe/nyx/resources"
"net/http"
"strconv"
"time"
)
func serveThread(w http.ResponseWriter, r *http.Request) {
dat := bytes.NewBuffer([]byte{})
db := middle.GetDB(r)
ctx := middle.GetBaseCtx(r)
err := db.View(func(tx *buntdb.Tx) error {
bName := chi.URLParam(r, "board")
b, err := resources.GetBoard(tx, r.Host, bName)
if err != nil {
return err
}
ctx["Board"] = b
id, err := strconv.Atoi(chi.URLParam(r, "thread"))
if err != nil {
return err
}
thread, err := resources.GetThread(tx, r.Host, bName, int64(id))
if err != nil {
return err
}
err = resources.FillReplies(tx, r.Host, thread)
if err != nil {
return err
}
if err != nil {
return err
}
ctx["Thread"] = thread
return nil
})
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
err = threadTmpl.Execute(dat, ctx)
if err != nil {
errw.ErrorWriter(err, w, r)
return
}
http.ServeContent(w, r, "board.html", time.Now(), bytes.NewReader(dat.Bytes()))
}

77
http/errw/handler.go Normal file
View File

@@ -0,0 +1,77 @@
package errw
import (
"errors"
"github.com/GeertJohan/go.rice"
"github.com/pressly/chi/middleware"
"go.rls.moe/nyx/http/middle"
"html/template"
"net/http"
)
var riceConf = rice.Config{
LocateOrder: []rice.LocateMethod{
rice.LocateWorkingDirectory,
rice.LocateEmbedded,
rice.LocateAppended,
},
}
var box = riceConf.MustFindBox("http/errw/res/")
var (
errorTmpl = template.New("errw/error")
)
func init() {
var err error
errorTmpl, err = errorTmpl.Parse(box.MustString("error.html"))
if err != nil {
panic(err)
}
}
type ErrorWithTitle interface {
error
ErrorTitle() string
}
type errorWTInt struct {
message, title string
}
func (e errorWTInt) Error() string {
return e.message
}
func (e errorWTInt) ErrorTitle() string {
return e.title
}
func MakeErrorWithTitle(title, message string) ErrorWithTitle {
return errorWTInt{message, title}
}
func ErrorWriter(err error, w http.ResponseWriter, r *http.Request) {
ctx := middle.GetBaseCtx(r)
if err == nil {
ErrorWriter(errors.New("Unknonw Error"), w, r)
}
if errWT, ok := err.(ErrorWithTitle); ok {
ctx["Error"] = map[string]string{
"Code": middleware.GetReqID(r.Context()),
"Description": errWT.Error(),
"Title": errWT.ErrorTitle(),
}
} else {
ctx["Error"] = map[string]string{
"Code": middleware.GetReqID(r.Context()),
"Description": err.Error(),
"Title": "Error",
}
}
errorTmpl.Execute(w, ctx)
return
}

35
http/errw/res/error.html Normal file
View File

@@ -0,0 +1,35 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.Config.Site.Title}} Admin Login</title>
<style>
div.error {
border: 1px solid black;
width: 500px;
margin: auto;
margin-top: 100px;
}
div.error h1 {
margin-bottom: 0px;
text-align: center;
}
div.error h2 {
text-align: center;
}
div.error h3 {
margin-top: 0px;
text-align: center;
color: #888;
}
</style>
</head>
<body>
<div class="error">
<h1>{{.Error.Title}}</h1><br/>
<h3>{{.Error.Code}}</h3><br/>
<h2>{{.Error.Description}}</h2>
</div>
</body>
</html>

21
http/middle/base.go Normal file
View File

@@ -0,0 +1,21 @@
package middle
import (
"github.com/justinas/nosurf"
"github.com/pressly/chi/middleware"
"net/http"
)
func GetBaseCtx(r *http.Request) map[string]interface{} {
val := map[string]interface{}{
"Config": GetConfig(r),
"ReqID": middleware.GetReqID(r.Context()),
"CSRFToken": nosurf.Token(r),
}
return val
}
func CSRFProtect(next http.Handler) http.Handler {
return nosurf.New(next)
}

24
http/middle/config.go Normal file
View File

@@ -0,0 +1,24 @@
package middle
import (
"context"
"go.rls.moe/nyx/config"
"net/http"
)
func ConfigCtx(config *config.Config) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(context.WithValue(r.Context(), configKey, config))
next.ServeHTTP(w, r)
})
}
}
func GetConfig(r *http.Request) *config.Config {
val := r.Context().Value(configKey)
if val == nil {
panic("Config Middleware not configured")
}
return val.(*config.Config)
}

9
http/middle/ctx.go Normal file
View File

@@ -0,0 +1,9 @@
package middle
type ctxKey int64
const (
configKey ctxKey = iota
dbCtxKey
sessionKey
)

34
http/middle/db.go Normal file
View File

@@ -0,0 +1,34 @@
package middle
import (
"context"
"github.com/tidwall/buntdb"
"go.rls.moe/nyx/config"
"go.rls.moe/nyx/resources"
"net/http"
)
func Database(c *config.Config) (func(http.Handler) http.Handler, error) {
db, err := buntdb.Open(c.DB.File)
if err != nil {
return nil, err
}
if err = resources.InitialSetup(db); err != nil {
return nil, err
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
r = r.WithContext(context.WithValue(r.Context(),
dbCtxKey, db))
next.ServeHTTP(w, r)
})
}, nil
}
func GetDB(r *http.Request) *buntdb.DB {
val := r.Context().Value(dbCtxKey)
if val == nil {
panic("DB Middleware not configured")
}
return val.(*buntdb.DB)
}

15
http/middle/session.go Normal file
View File

@@ -0,0 +1,15 @@
package middle
import (
"github.com/icza/session"
"net/http"
)
func init() {
session.Global.Close()
session.Global = session.NewCookieManager(session.NewInMemStore())
}
func GetSession(r *http.Request) session.Session {
return session.Get(r)
}

26
http/res/admin.css Normal file
View File

@@ -0,0 +1,26 @@
/* CUSTOM CSS */
div.admin.login {
border: 1px solid black;
width: 500px;
margin: auto;
margin-top: 100px;
}
.admin.form.row {
margin: auto;
padding: 5px;
width: 90%;
height: 22px;
left: 0;
right: 0;
display: flex;
}
.admin.form.input {
font-family: "monospace";
width: 100%;
height: 100%;
padding: 2px;
display: inline;
}
.admin.form.input.halfsize {
width: 50%;
}

21
http/res/custom.css Normal file
View File

@@ -0,0 +1,21 @@
h1 {
font-size: 32px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 16px;
}
div {
display: block;
margin: 0;
padding: 0;
}
blockquote blockquote { max-width: 80%; word-wrap: break-word; white-space: normal; }
.reply blockquote, blockquote :last-child { max-width: 80%; word-wrap: break-word; white-space: normal; }

157
http/res/style.css Normal file
View File

@@ -0,0 +1,157 @@
/* The following CSS is mostly taken from Wakaba, big thanks for the devs there! <3 */
html, body {
background:#FFFFEE;
color:#800000;
}
a {
color:#0000EE;
}
a:hover {
color:#DD0000;
}
.adminbar {
text-align:right;
clear:both;
float:right;
}
.logo {
clear:both;
text-align:center;
font-size:2em;
color:#800000;
width:100%;
}
.theader {
background:#E04000;
text-align:center;
padding:2px;
color:#FFFFFF;
width:100%;
}
.postarea {
}
.rules {
font-size:0.7em;
}
.postblock {
background:#EEAA88;
color:#800000;
font-weight:800;
}
.footer {
text-align:center;
font-size:12px;
font-family:serif;
}
.passvalid {
background:#EEAA88;
text-align:center;
width:100%;
color:#ffffff;
}
.dellist {
font-weight: bold;
text-align:center;
}
.delbuttons {
text-align:center;
padding-bottom:4px;
}
.managehead {
background:#AAAA66;
color:#400000;
padding:0px;
}
.postlists {
background:#FFFFFF;
width:100%;
padding:0px;
color:#800000;
}
.row1 {
background:#EEEECC;
color:#800000;
}
.row2 {
background:#DDDDAA;
color:#800000;
}
.unkfunc {
background:inert;
color:#789922;
}
.filesize {
text-decoration:none;
}
.filetitle {
background:inherit;
font-size:1.2em;
color:#CC1105;
font-weight:800;
}
.postername {
color:#117743;
font-weight:bold;
}
.postertrip {
color:#228854;
}
.oldpost {
color:#CC1105;
font-weight:800;
}
.omittedposts {
color:#707070;
}
.reply {
background:#F0E0D6;
color:#800000;
}
.doubledash {
vertical-align:top;
clear:both;
float:left;
}
.replytitle {
font-size: 1.2em;
color:#CC1105;
font-weight:800;
}
.commentpostername {
color:#117743;
font-weight:800;
}
.thumbnailmsg {
font-size: small;
color:#800000;
}
.abbrev {
color:#707070;
}
.highlight {
background:#F0E0D6;
color:#800000;
border: 2px dashed #EEAA88;
}
/* From pl files */
/* futaba_style.pl */
blockquote blockquote { margin-left: 0em; }
form { margin-bottom: 0px }
form .trap { display:none }
.postarea { text-align: center }
.postarea table { margin: 0px auto; text-align: left }
.thumb { border: none; float: left; margin: 2px 20px }
.nothumb { float: left; background: #eee; border: 2px dashed #aaa; text-align: center; margin: 2px 20px; padding: 1em 0.5em 1em 0.5em; }
.reply blockquote, blockquote :last-child { margin-bottom: 0em; }
.reflink a { color: inherit; text-decoration: none }
.reply .filesize { margin-left: 20px }
.userdelete { float: right; text-align: center; white-space: nowrap }
.replypage .replylink { display: none }

53
http/server.go Normal file
View File

@@ -0,0 +1,53 @@
package http
import (
"fmt"
"github.com/GeertJohan/go.rice"
"github.com/pressly/chi"
"github.com/pressly/chi/middleware"
"go.rls.moe/nyx/config"
"go.rls.moe/nyx/http/admin"
"go.rls.moe/nyx/http/board"
"go.rls.moe/nyx/http/middle"
"net/http"
)
var riceConf = rice.Config{
LocateOrder: []rice.LocateMethod{
rice.LocateWorkingDirectory,
rice.LocateEmbedded,
rice.LocateAppended,
},
}
func Start(config *config.Config) {
r := chi.NewRouter()
fmt.Println("Setting up Router")
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.CloseNotify)
r.Use(middleware.DefaultCompress)
r.Use(middle.ConfigCtx(config))
r.Use(middle.CSRFProtect)
{
mw, err := middle.Database(config)
if err != nil {
panic(err)
}
r.Use(mw)
}
r.Route("/admin/", admin.Router)
{
box := riceConf.MustFindBox("http/res")
atFileServer := http.StripPrefix("/@/", http.FileServer(box.HTTPBox()))
r.Mount("/@/", atFileServer)
}
r.Group(board.Router)
fmt.Println("Setup Complete, Starting Web Server...")
http.ListenAndServe(config.ListenOn, r)
}