fixed comments, added documentation

This commit is contained in:
legitnull 2023-04-12 06:26:32 -06:00
parent 90469dd4c5
commit 00c65f5480
4 changed files with 317 additions and 0 deletions

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

@ -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

@ -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

@ -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)
}