Compare commits
2 Commits
90469dd4c5
...
b99ddd2bfb
Author | SHA1 | Date | |
---|---|---|---|
|
b99ddd2bfb | ||
|
00c65f5480 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
data/
|
||||||
|
comments.db/
|
42
README.md
42
README.md
@ -2,6 +2,43 @@ H0wdy!!!
|
|||||||
|
|
||||||
feel free to commit, leave suggestions, issues, or really anything <3
|
feel free to commit, leave suggestions, issues, or really anything <3
|
||||||
|
|
||||||
|
## SETUP
|
||||||
|
**For a normal user you can follow this process:**
|
||||||
|
|
||||||
|
First clone the repo:
|
||||||
|
```bash
|
||||||
|
git clone https://git.tcp.direct/S4D/tcp-wiki.git
|
||||||
|
```
|
||||||
|
Then you have to cd into the repo's folder and run/compile:
|
||||||
|
```bash
|
||||||
|
cd tcp-wiki/src
|
||||||
|
go run .
|
||||||
|
```
|
||||||
|
Then you goto your browser and visit: http://127.0.0.1:8080/
|
||||||
|
|
||||||
|
**For a develeper setup you can follow this process:**
|
||||||
|
|
||||||
|
First clone the repo:
|
||||||
|
```bash
|
||||||
|
git clone ssh://git@git.tcp.direct:2222/S4D/tcp-wiki.git
|
||||||
|
```
|
||||||
|
Then cd and run dev.sh
|
||||||
|
```bash
|
||||||
|
cd tcp-wiki
|
||||||
|
bash dev.sh
|
||||||
|
```
|
||||||
|
Then you goto your browser and visit: http://127.0.0.1:8080/
|
||||||
|
|
||||||
|
This method just adds in some handy symlinks for development purposes
|
||||||
|
|
||||||
|
### Use with your own repo?
|
||||||
|
|
||||||
|
All you have to do is modify the main.go file:
|
||||||
|
```go
|
||||||
|
const repoURL = "https://git.tcp.direct/S4D/tcp-wiki.git"
|
||||||
|
```
|
||||||
|
Change this line to your repo link, and enjoy!
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [ ] MANY FUCKING THINGS
|
- [ ] MANY FUCKING THINGS
|
||||||
@ -13,14 +50,17 @@ feel free to commit, leave suggestions, issues, or really anything <3
|
|||||||
- [ ] last edited
|
- [ ] last edited
|
||||||
- [ ] last editor/commit
|
- [ ] last editor/commit
|
||||||
- [ ] pgp signed intergration
|
- [ ] pgp signed intergration
|
||||||
- [-] comments using bitcast - almost there! - generated in comments.db/
|
- [x] comments using bitcask - generated in comments.db/
|
||||||
- [ ] verification - no login pgp
|
- [ ] verification - no login pgp
|
||||||
- [ ] captcha
|
- [ ] captcha
|
||||||
- [ ] sub rating system
|
- [ ] sub rating system
|
||||||
- [ ] sort by date etc
|
- [ ] sort by date etc
|
||||||
|
- [ ] reply to replies
|
||||||
|
- [ ] set security controls per page
|
||||||
- [ ] dynamically generated links for all avaiable pages
|
- [ ] dynamically generated links for all avaiable pages
|
||||||
- [ ] sitemap
|
- [ ] sitemap
|
||||||
- [ ] anti robot shit here
|
- [ ] anti robot shit here
|
||||||
|
- [ ] acual working pages!?
|
||||||
- [ ] post quantum intergration and verification
|
- [ ] post quantum intergration and verification
|
||||||
- [ ] BUILD UP THAT MARKDOWN SUPPORT
|
- [ ] BUILD UP THAT MARKDOWN SUPPORT
|
||||||
- [ ] fix whatever i did to fuck up design/layout/css???
|
- [ ] fix whatever i did to fuck up design/layout/css???
|
@ -28,20 +28,21 @@
|
|||||||
<h2>Comments</h2>
|
<h2>Comments</h2>
|
||||||
{{ range .Comments }}
|
{{ range .Comments }}
|
||||||
<div class="comment">
|
<div class="comment">
|
||||||
<p>{{ .Body }}</p>
|
<p>{{ .Content }}</p>
|
||||||
<p><small>{{ .Author }} at {{ .CreatedAt }}</small></p>
|
<p><small>{{ .Author }} at {{ .Date }}</small></p>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<form method="POST" action="/submit_comment">
|
<form method="POST" action="/submit_comment">
|
||||||
|
<input type="hidden" name="path" value="{{ .Path }}">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="author">Name:</label>
|
<label for="author">Name:</label>
|
||||||
<input type="text" id="author" name="author">
|
<input type="text" id="author" name="author">
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="body">Comment:</label>
|
<label for="body">Comment:</label>
|
||||||
<textarea id="body" name="body"></textarea>
|
<textarea id="content" name="content"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
14
dev.sh
Normal file
14
dev.sh
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#/bin/bash
|
||||||
|
# this sets up super annoying shit like hard symlinks and whatever else
|
||||||
|
# Eventually front end will be in a seperate branch for production
|
||||||
|
# but for now we can manage by editing via hardlinks.
|
||||||
|
# This sets up your local readme as the main page and the files in assets as public
|
||||||
|
cd src && go run .
|
||||||
|
rm ../data/assets/*
|
||||||
|
ln ../assets/_layout.html ../data/assets/_layout.html
|
||||||
|
ln ../assets/main.css ../data/assets/main.css
|
||||||
|
rm ../data/README.md
|
||||||
|
ln ./README.md ../data/README.md
|
||||||
|
echo "Developer setup ready!"
|
||||||
|
echo "Goto: http://127.0.0.1:8080"
|
||||||
|
|
81
src/comments.go
Normal file
81
src/comments.go
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/prologic/bitcask"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Comment struct {
|
||||||
|
Author string
|
||||||
|
Content string
|
||||||
|
Date time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func getComments(db *bitcask.Bitcask, key string) ([]Comment, error) {
|
||||||
|
data, err := db.Get([]byte(key))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var comments []Comment
|
||||||
|
err = json.Unmarshal(data, &comments)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return comments, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveComment(db *bitcask.Bitcask, key string, comment Comment) error {
|
||||||
|
comments, err := getComments(db, key)
|
||||||
|
if err != nil && err != bitcask.ErrKeyNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
comment.Date = time.Now()
|
||||||
|
comments = append(comments, comment)
|
||||||
|
|
||||||
|
data, err := json.Marshal(comments)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db.Put([]byte(key), data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func submitCommentHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := r.ParseForm()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to parse form", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pagePath := r.FormValue("path")
|
||||||
|
if pagePath == "" {
|
||||||
|
http.Error(w, "Path is required", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comment := Comment{
|
||||||
|
Author: r.FormValue("author"),
|
||||||
|
Content: r.FormValue("content"),
|
||||||
|
}
|
||||||
|
|
||||||
|
err = saveComment(commentsDB, pagePath, comment)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to save comment", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Saved comment: %+v", comment)
|
||||||
|
http.Redirect(w, r, pagePath, http.StatusSeeOther)
|
||||||
|
}
|
73
src/git.go
Normal file
73
src/git.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
//"fmt"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
|
||||||
|
git "github.com/go-git/go-git/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cloneRepository(repoURL, localPath string) error {
|
||||||
|
_, err := git.PlainClone(localPath, false, &git.CloneOptions{
|
||||||
|
URL: repoURL,
|
||||||
|
Progress: os.Stdout,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func pullRepository(localPath string) error {
|
||||||
|
repo, err := git.PlainOpen(localPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
worktree, err := repo.Worktree()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = worktree.Pull(&git.PullOptions{RemoteName: "origin"})
|
||||||
|
if err != nil && err != git.NoErrAlreadyUpToDate {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFileFromRepo(localPath string, filePath string) ([]byte, error) {
|
||||||
|
// Open the local repository
|
||||||
|
repo, err := git.PlainOpen(localPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the head reference
|
||||||
|
ref, err := repo.Head()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the commit object
|
||||||
|
commit, err := repo.CommitObject(ref.Hash())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the file contents from the commit tree
|
||||||
|
tree, err := commit.Tree()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := tree.File(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := file.Contents()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []byte(content), nil
|
||||||
|
}
|
61
src/main.go
Normal file
61
src/main.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-git/go-git/v5"
|
||||||
|
"github.com/prologic/bitcask"
|
||||||
|
)
|
||||||
|
|
||||||
|
const repoURL = "https://git.tcp.direct/S4D/tcp-wiki.git"
|
||||||
|
const localPath = "../data"
|
||||||
|
|
||||||
|
var commentsDB *bitcask.Bitcask
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
err := cloneRepository(repoURL, localPath)
|
||||||
|
if err != nil && err != git.ErrRepositoryAlreadyExists {
|
||||||
|
log.Fatalf("Failed to clone repository: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
commentsDB, err = bitcask.Open("../comments.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open comments database: %v", err)
|
||||||
|
}
|
||||||
|
defer commentsDB.Close()
|
||||||
|
|
||||||
|
http.HandleFunc("/", handler)
|
||||||
|
http.HandleFunc("/submit_comment", submitCommentHandler)
|
||||||
|
log.Fatal(http.ListenAndServe(":8080", nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
//for debugging
|
||||||
|
log.Printf("LOCAL PATH: %q", localPath)
|
||||||
|
|
||||||
|
//...
|
||||||
|
|
||||||
|
if r.URL.Path == "../assets/favicon.ico" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := pullRepository(localPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to pull repository: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := strings.TrimPrefix(r.URL.Path, "/")
|
||||||
|
if filePath == "" {
|
||||||
|
filePath = "README.md"
|
||||||
|
}
|
||||||
|
log.Printf("Rendering file %q from path %q", filePath, r.URL.Path)
|
||||||
|
|
||||||
|
err = renderPage(w, r, localPath, filePath, commentsDB)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Comment loading? %q", commentsDB.Path())
|
||||||
|
|
||||||
|
http.Error(w, "File not found", http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}
|
102
src/render.go
Normal file
102
src/render.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/prologic/bitcask"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
highlighting "github.com/yuin/goldmark-highlighting"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Page struct {
|
||||||
|
Content template.HTML
|
||||||
|
Comments []Comment
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderPage(w http.ResponseWriter, r *http.Request, localPath, filePath string, commentsDB *bitcask.Bitcask) error {
|
||||||
|
content, err := readFileFromRepo(localPath, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
//log.Printf("Read file content: %s", content)
|
||||||
|
|
||||||
|
ext := filepath.Ext(filePath)
|
||||||
|
switch ext {
|
||||||
|
case ".md":
|
||||||
|
renderMarkdown(w, r, content, commentsDB)
|
||||||
|
case ".html", ".css":
|
||||||
|
renderStatic(w, content, ext)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported file format")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderMarkdown(w http.ResponseWriter, r *http.Request, content []byte, commentsDB *bitcask.Bitcask) {
|
||||||
|
md := goldmark.New(
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
extension.GFM, // GitHub Flavored Markdown
|
||||||
|
highlighting.NewHighlighting(
|
||||||
|
highlighting.WithStyle("monokai"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
var mdBuf bytes.Buffer
|
||||||
|
err := md.Convert(content, &mdBuf)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error converting Markdown", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
layout, err := ioutil.ReadFile(filepath.Join(localPath, "assets/_layout.html"))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Layout not found", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
comments, err := getComments(commentsDB, r.URL.Path)
|
||||||
|
if err != nil && err != bitcask.ErrKeyNotFound {
|
||||||
|
http.Error(w, "Error fetching comments", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
page := &Page{Content: template.HTML(mdBuf.String()), Comments: comments, Path: r.URL.Path}
|
||||||
|
t, err := template.New("layout").Parse(string(layout))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error parsing layout", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = t.Execute(&buf, page)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error executing template: %v", err) // Add this line
|
||||||
|
http.Error(w, "Error rendering layout", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.Write(buf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderStatic(w http.ResponseWriter, content []byte, ext string) {
|
||||||
|
contentType := ""
|
||||||
|
switch ext {
|
||||||
|
case ".html":
|
||||||
|
contentType = "text/html; charset=utf-8"
|
||||||
|
case ".css":
|
||||||
|
contentType = "text/css; charset=utf-8"
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", contentType)
|
||||||
|
w.Write(content)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user