add license
This commit is contained in:
parent
36ffc88dc7
commit
9bd35593fc
9
LICENSE
Normal file
9
LICENSE
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 vxfemboy
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
132
src/main.go
132
src/main.go
@ -5,53 +5,53 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/BurntSushi/toml"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/prologic/bitcask"
|
"github.com/prologic/bitcask"
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Server struct {
|
Server struct {
|
||||||
Port string
|
Port string
|
||||||
}
|
}
|
||||||
Git struct {
|
Git struct {
|
||||||
UseGit bool
|
UseGit bool
|
||||||
RepoURL string
|
RepoURL string
|
||||||
Branch string
|
Branch string
|
||||||
LocalPath string
|
LocalPath string
|
||||||
}
|
}
|
||||||
Database struct {
|
Database struct {
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var commentsDB *bitcask.Bitcask
|
var commentsDB *bitcask.Bitcask
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var config Config
|
var config Config
|
||||||
if _, err := toml.DecodeFile("config.toml", &config); err != nil {
|
if _, err := toml.DecodeFile("config.toml", &config); err != nil {
|
||||||
log.Fatalf("Failed to load config: %v", err)
|
log.Fatalf("Failed to load config: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Git.UseGit {
|
if config.Git.UseGit {
|
||||||
err := cloneRepository(config.Git.RepoURL, config.Git.LocalPath)
|
err := cloneRepository(config.Git.RepoURL, config.Git.LocalPath)
|
||||||
if err != nil && err != git.ErrRepositoryAlreadyExists {
|
if err != nil && err != git.ErrRepositoryAlreadyExists {
|
||||||
log.Fatalf("Failed to clone repository: %v", err)
|
log.Fatalf("Failed to clone repository: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err := os.Stat(config.Git.LocalPath); os.IsNotExist(err) {
|
if _, err := os.Stat(config.Git.LocalPath); os.IsNotExist(err) {
|
||||||
os.MkdirAll(config.Git.LocalPath, os.ModePerm)
|
os.MkdirAll(config.Git.LocalPath, os.ModePerm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
commentsDB, err = bitcask.Open(config.Database.Path)
|
commentsDB, err = bitcask.Open(config.Database.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -59,13 +59,13 @@ func main() {
|
|||||||
}
|
}
|
||||||
defer commentsDB.Close()
|
defer commentsDB.Close()
|
||||||
|
|
||||||
fs := http.FileServer(http.Dir("./assets"))
|
fs := http.FileServer(http.Dir("./assets"))
|
||||||
http.Handle("/assets/", http.StripPrefix("/assets/", fs))
|
http.Handle("/assets/", http.StripPrefix("/assets/", fs))
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
handler(&config, w, r)
|
handler(&config, w, r)
|
||||||
})
|
})
|
||||||
http.HandleFunc("/submit_comment", func(w http.ResponseWriter, r *http.Request) {
|
http.HandleFunc("/submit_comment", func(w http.ResponseWriter, r *http.Request) {
|
||||||
submitCommentHandler(w, r)
|
submitCommentHandler(w, r)
|
||||||
})
|
})
|
||||||
|
|
||||||
srv := &http.Server{Addr: config.Server.Port}
|
srv := &http.Server{Addr: config.Server.Port}
|
||||||
@ -98,14 +98,14 @@ func handler(config *Config, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Git.UseGit {
|
if config.Git.UseGit {
|
||||||
err := pullRepository(config.Git.LocalPath, config.Git.Branch)
|
err := pullRepository(config.Git.LocalPath, config.Git.Branch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to pull repository: %v", err)
|
log.Printf("Failed to pull repository: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := strings.TrimPrefix(r.URL.Path, "/")
|
filePath := strings.TrimPrefix(r.URL.Path, "/")
|
||||||
if filePath == "" {
|
if filePath == "" {
|
||||||
@ -117,38 +117,38 @@ func handler(config *Config, w http.ResponseWriter, r *http.Request) {
|
|||||||
csp := "default-src 'self'; font-src 'self' data:; frame-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self';"
|
csp := "default-src 'self'; font-src 'self' data:; frame-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self';"
|
||||||
w.Header().Set("Content-Security-Policy", csp)
|
w.Header().Set("Content-Security-Policy", csp)
|
||||||
|
|
||||||
markdownFiles, err := listMarkdownFiles(config.Git.LocalPath)
|
markdownFiles, err := listMarkdownFiles(config.Git.LocalPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error listing markdown files: %v", err)
|
log.Printf("Error listing markdown files: %v", err)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = renderPage(w, r, config, filePath, commentsDB, markdownFiles)
|
err = renderPage(w, r, config, filePath, commentsDB, markdownFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to render page: %v", err)
|
log.Printf("Failed to render page: %v", err)
|
||||||
http.Error(w, "File not found", http.StatusNotFound)
|
http.Error(w, "File not found", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listMarkdownFiles(localPath string) ([]string, error) {
|
func listMarkdownFiles(localPath string) ([]string, error) {
|
||||||
var files []string
|
var files []string
|
||||||
|
|
||||||
err := filepath.Walk(localPath, func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk(localPath, func(path string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !info.IsDir() && strings.HasSuffix(path, ".md") {
|
if !info.IsDir() && strings.HasSuffix(path, ".md") {
|
||||||
relPath, err := filepath.Rel(localPath, path)
|
relPath, err := filepath.Rel(localPath, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
relPath = strings.Replace(relPath, string(os.PathSeparator), "/", -1)
|
relPath = strings.Replace(relPath, string(os.PathSeparator), "/", -1)
|
||||||
files = append(files, relPath)
|
files = append(files, relPath)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return files, err
|
return files, err
|
||||||
}
|
}
|
||||||
|
127
src/render.go
127
src/render.go
@ -11,12 +11,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/prologic/bitcask"
|
"github.com/prologic/bitcask"
|
||||||
|
img64 "github.com/tenkoh/goldmark-img64"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
highlighting "github.com/yuin/goldmark-highlighting"
|
highlighting "github.com/yuin/goldmark-highlighting"
|
||||||
"github.com/yuin/goldmark/extension"
|
"github.com/yuin/goldmark/extension"
|
||||||
"github.com/yuin/goldmark/parser"
|
"github.com/yuin/goldmark/parser"
|
||||||
"github.com/yuin/goldmark/renderer/html"
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
img64 "github.com/tenkoh/goldmark-img64"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Page struct {
|
type Page struct {
|
||||||
@ -27,74 +27,74 @@ type Page struct {
|
|||||||
AuthoredDate *time.Time
|
AuthoredDate *time.Time
|
||||||
LastModifier string
|
LastModifier string
|
||||||
LastModifiedDate *time.Time
|
LastModifiedDate *time.Time
|
||||||
Pages []string
|
Pages []string
|
||||||
UseGit bool
|
UseGit bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderPage(w http.ResponseWriter, r *http.Request, config *Config, filePath string, commentsDB *bitcask.Bitcask, pages []string) error {
|
func renderPage(w http.ResponseWriter, r *http.Request, config *Config, filePath string, commentsDB *bitcask.Bitcask, pages []string) error {
|
||||||
var content []byte
|
var content []byte
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if config.Git.UseGit {
|
if config.Git.UseGit {
|
||||||
content, err = readFileFromRepo(config.Git.LocalPath, filePath)
|
content, err = readFileFromRepo(config.Git.LocalPath, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fullPath := filepath.Join(config.Git.LocalPath, filePath)
|
fullPath := filepath.Join(config.Git.LocalPath, filePath)
|
||||||
content, err = ioutil.ReadFile(fullPath)
|
content, err = ioutil.ReadFile(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ext := filepath.Ext(filePath)
|
ext := filepath.Ext(filePath)
|
||||||
switch ext {
|
switch ext {
|
||||||
case ".md":
|
case ".md":
|
||||||
renderMarkdown(w, r, content, commentsDB, filePath, pages, config)
|
renderMarkdown(w, r, content, commentsDB, filePath, pages, config)
|
||||||
case ".html", ".css":
|
case ".html", ".css":
|
||||||
renderStatic(w, content, ext)
|
renderStatic(w, content, ext)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported file format")
|
return fmt.Errorf("unsupported file format")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderMarkdown(w http.ResponseWriter, r *http.Request, content []byte, commentsDB *bitcask.Bitcask, filePath string, pages []string, config *Config) {
|
func renderMarkdown(w http.ResponseWriter, r *http.Request, content []byte, commentsDB *bitcask.Bitcask, filePath string, pages []string, config *Config) {
|
||||||
|
|
||||||
md := goldmark.New(
|
md := goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
extension.GFM, // images should probably be base64 encoded https://github.com/tenkoh/goldmark-img64 for extra performance
|
extension.GFM, // images should probably be base64 encoded https://github.com/tenkoh/goldmark-img64 for extra performance
|
||||||
extension.Table,
|
extension.Table,
|
||||||
highlighting.NewHighlighting(
|
highlighting.NewHighlighting(
|
||||||
highlighting.WithStyle("monokai"),
|
highlighting.WithStyle("monokai"),
|
||||||
),
|
),
|
||||||
img64.Img64,
|
img64.Img64,
|
||||||
), // does this code below do anything useful?
|
), // does this code below do anything useful?
|
||||||
goldmark.WithParserOptions(
|
goldmark.WithParserOptions(
|
||||||
parser.WithAutoHeadingID(),
|
parser.WithAutoHeadingID(),
|
||||||
),
|
),
|
||||||
goldmark.WithRendererOptions(
|
goldmark.WithRendererOptions(
|
||||||
html.WithXHTML(),
|
html.WithXHTML(),
|
||||||
html.WithHardWraps(),
|
html.WithHardWraps(),
|
||||||
img64.WithParentPath(config.Git.LocalPath),
|
img64.WithParentPath(config.Git.LocalPath),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
var author, lastModifier string
|
var author, lastModifier string
|
||||||
var authoredDate, lastModifiedDate *time.Time
|
var authoredDate, lastModifiedDate *time.Time
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if config.Git.UseGit {
|
if config.Git.UseGit {
|
||||||
var ad, lmd time.Time
|
var ad, lmd time.Time
|
||||||
author, ad, lastModifier, lmd, err = getAuthorAndLastModification(config.Git.LocalPath, filePath)
|
author, ad, lastModifier, lmd, err = getAuthorAndLastModification(config.Git.LocalPath, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Error fetching author and last modification date", http.StatusInternalServerError)
|
http.Error(w, "Error fetching author and last modification date", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
authoredDate = &ad
|
authoredDate = &ad
|
||||||
lastModifiedDate = &lmd
|
lastModifiedDate = &lmd
|
||||||
}
|
}
|
||||||
|
|
||||||
var mdBuf bytes.Buffer
|
var mdBuf bytes.Buffer
|
||||||
err = md.Convert(content, &mdBuf)
|
err = md.Convert(content, &mdBuf)
|
||||||
@ -103,13 +103,12 @@ func renderMarkdown(w http.ResponseWriter, r *http.Request, content []byte, comm
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layout, err := ioutil.ReadFile("assets/_layout.html")
|
||||||
layout, err := ioutil.ReadFile("assets/_layout.html")
|
if err != nil {
|
||||||
if err != nil {
|
log.Printf("Error reading _layout.html: %v", err)
|
||||||
log.Printf("Error reading _layout.html: %v", err)
|
http.Error(w, "Layout not found", http.StatusInternalServerError)
|
||||||
http.Error(w, "Layout not found", http.StatusInternalServerError)
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
|
||||||
comments, err := getComments(commentsDB, r.URL.Path)
|
comments, err := getComments(commentsDB, r.URL.Path)
|
||||||
if err != nil && err != bitcask.ErrKeyNotFound {
|
if err != nil && err != bitcask.ErrKeyNotFound {
|
||||||
@ -125,8 +124,8 @@ func renderMarkdown(w http.ResponseWriter, r *http.Request, content []byte, comm
|
|||||||
AuthoredDate: authoredDate,
|
AuthoredDate: authoredDate,
|
||||||
LastModifier: lastModifier,
|
LastModifier: lastModifier,
|
||||||
LastModifiedDate: lastModifiedDate,
|
LastModifiedDate: lastModifiedDate,
|
||||||
Pages: pages,
|
Pages: pages,
|
||||||
UseGit: config.Git.UseGit,
|
UseGit: config.Git.UseGit,
|
||||||
}
|
}
|
||||||
|
|
||||||
t, err := template.New("layout").Parse(string(layout))
|
t, err := template.New("layout").Parse(string(layout))
|
||||||
@ -138,8 +137,8 @@ func renderMarkdown(w http.ResponseWriter, r *http.Request, content []byte, comm
|
|||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
err = t.Execute(&buf, page)
|
err = t.Execute(&buf, page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error executing template: %v", err)
|
log.Printf("Error executing template: %v", err)
|
||||||
http.Error(w, "Error rendering layout", http.StatusInternalServerError)
|
http.Error(w, "Error rendering layout", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user