You've already forked nyx
mirror of
https://github.com/rls-moe/nyx
synced 2025-08-19 06:18:38 +00:00
MVP, no mod tools or anything but it works
This commit is contained in:
52
http/board/board.go
Normal file
52
http/board/board.go
Normal 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
88
http/board/handler.go
Normal 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
59
http/board/newreply.go
Normal 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
48
http/board/newthread.go
Normal 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
119
http/board/res/board.html
Normal 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">>></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
30
http/board/res/dir.html
Normal 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
129
http/board/res/thread.html
Normal 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">>></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
57
http/board/thread.go
Normal 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()))
|
||||
}
|
Reference in New Issue
Block a user