fixed comments, added documentation
This commit is contained in:
parent
b99ddd2bfb
commit
98d69d440c
79
comments.go
79
comments.go
@ -1,79 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, pagePath, http.StatusSeeOther)
|
|
||||||
}
|
|
73
git.go
73
git.go
@ -1,73 +0,0 @@
|
|||||||
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
main.go
61
main.go
@ -1,61 +0,0 @@
|
|||||||
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 == "/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)
|
|
||||||
}
|
|
||||||
}
|
|
100
render.go
100
render.go
@ -1,100 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
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}
|
|
||||||
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 {
|
|
||||||
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