Compare commits

..

No commits in common. "85cf4e4facaa428e156271e85404c8f1dad989ab" and "147ce945c373dafec91ba1671e4d9ae6997bd228" have entirely different histories.

10 changed files with 285 additions and 220 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
data/ data/
comments.db/ comments.db/
notes notes
sass

View File

@ -1,71 +1,70 @@
# TCP-WIKI H0wdy!!!
Feel free to commit, leave suggestions/ideas, issues, or really anything <3 feel free to commit, leave suggestions/ideas, issues, or really anything <3
# What is TCP-WIKI ? # What is TCP.WIKI ?
<center><img src="https://tcp.ac/i/TIZzK" alt="example screenshot" width="100" height="400"></center>
TCP.WIKI is a secure and verifiable wiki platform designed for projects, code, courses, documents, articles, blogs, tutorials, and more. https://tcp.ac/i/IFAZE
### Project Goals
The aim is to provide a secure, minimal, and easily verifiable wiki environment that supports a wide range of content types, from technical documentation, to educational materials, to blogs, and more. ## Project Goals
secure and verifiable wiki for projects, code, courses, documents, articles, tutorials, and more
## Setup ## Setup
**For a normal user you can follow this process:**
First clone this repository: First clone the repo:
```bash ```bash
git clone https://git.tcp.direct/S4D/tcp-wiki.git git clone https://git.tcp.direct/S4D/tcp-wiki.git
``` ```
Then you have to cd into the repo's folder and run/compile: Then you have to cd into the repo's folder and run/compile:
```bash ```bash
cd tcp-wiki cd tcp-wiki/src
go run ./src go run .
``` ```
Then you goto your browser and visit: http://127.0.0.1:8080/ Then you goto your browser and visit: http://127.0.0.1:8080/
## Want to use with your own data? **For a develeper setup you can follow this process:**
All you have to do is modify the following lines in the `config.toml` file: First clone the repo:
```bash
```toml git clone ssh://git@git.tcp.direct:2222/S4D/tcp-wiki.git
[Git]
UseGit = true # Set to false to use LocalPath
RepoURL = "https://git.tcp.direct/S4D/tcp-wiki.git" # Your Repo Here
Branch = "main" # Your Repo Branch Here
LocalPath = "data" # Directory to clone the git repo too
``` ```
Then cd and run dev.sh
```bash
cd tcp-wiki
bash dev.sh
```
Then you just have to execute this to run the server:
```go
cd src && go run .
```
Then you goto your browser and visit: http://127.0.0.1:8080/
Change the `RepoURL` line `https://git.tcp.direct/S4D/tcp-wiki.git` to your repo link, This method just adds in some handy symlinks for development purposes
## Want to use with your own repo?
All you have to do is modify the following lines in the src/main.go file:
```go
const repoURL = "https://git.tcp.direct/S4D/tcp-wiki.git"
```
Change `https://git.tcp.direct/S4D/tcp-wiki.git` to your repo link, and:
```go
const repoBRANCH = "main"
```
change `main` to your specific repo's branch and you should be good to go! change `main` to your specific repo's branch and you should be good to go!
#### Want to use a local directory other then git repo?
To do this you just need to set `UseGit` to `false` and set your directory in config.toml
```toml
[Git]
UseGit = false # Set this to false
RepoURL = "" # Ignored
Branch = "" # Ignored
LocalPath = "/home/crazy/blog" # The directory of your project
```
make sure to also set `LocalPath` to the directory of your project
> ### Want to use your own theme/layout?
>
> Have a look at the `assets/` directory for the templates
## TODO ## TODO
- [x] config file - [ ] config files
- [ ] Webhook support for auto pull on push/update of the git repo - [ ] Webhook support for auto pull on push/update of the git repo
- [x] Git Branch support - [x] Git Branch support
- [ ] add a star/upvote/like feature for pages - [ ] add a star/upvote/like feature for pages
- [x] edit/version tracker - [x] edit/version tracker
- [x] Author - [x] Author
- [x] last edited - [x] last edited
- [x] last editor/commit - maybe working - [ ] last editor/commit [?] maybe working
- [ ] PGP Signed & Verification - [ ] PGP Signed & Verification
- [ ] pgp signed intergration - [ ] pgp signed intergration
- [x] comments using bitcask - generated in comments.db/ - [x] comments using bitcask - generated in comments.db/
@ -77,6 +76,7 @@ make sure to also set `LocalPath` to the directory of your project
- [ ] set security controls per page - [ ] set security controls per page
- [ ] auto refresh on post - [ ] auto refresh on post
- [x] dynamically generated links for all avaiable pages - [x] dynamically generated links for all avaiable pages
- [x] sitemap - kinda - [ ] sitemap
- [x] working pages - [ ] anti robot shit here
- [x] acual working pages!?
- [ ] image support - [ ] image support

View File

@ -7,78 +7,60 @@
<title>TCP.WIKI</title> <title>TCP.WIKI</title>
<link rel="stylesheet" href="/assets/main.css"> <link rel="stylesheet" href="/assets/main.css">
</head> </head>
<body> <body>
<header> <header>
<div class="navbar bg-base-200"> <h1>TCP.WIKI</h1>
<div class="flex-1">
<h1 class="btn btn-ghost text-xl">TCP.WIKI</h1>
</div>
<nav> <nav>
<div class="flex-none"> <ul>
<ul class="menu menu-horizontal px-1"> <li><a href="/">Home</a></li>
<li><a href="/">Home</a></li>
<!-- Add more links to your menubar here --> <!-- Add more links to your menubar here -->
<li><a href="/what">What</a></li> <li><a href="/what">What</a></li>
<li><a href="/who">Who</a></li> <li><a href="/who">Who</a></li>
<li class="dropdown dropdown-bottom dropdown-end">
<details>
<summary>
Pages
</summary>
<div class="dropdown-content">
<ul class="z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
{{ range .Pages }}
<li><a href="/{{ . }}">{{ . }}</a></li>
{{ end }}
</ul>
</div>
</details>
</li>
</ul> </ul>
</div>
</nav> </nav>
</div>
</header> </header>
<main class="bg-base-300"> <main>
<div class="prose content-center"> <div class="content">
{{ .Content }} {{ .Content }}
</div>
{{ if .UseGit }}
<footer class="footer px-10 py-4 border-t bg-base-200 text-base-content border-base-300">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-info shrink-0 w-6 h-6"><path stroke="oklch(var(--s))" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
<div class="md:justify-self-end text-secondary">
{{- if eq .Author .LastModifier -}}
Authored: {{ .Author }} @ {{ .AuthoredDate.Format "2006/01/02" }}
{{- else -}}
Authored: {{ .Author }} @ {{ .AuthoredDate.Format "2006/01/02" }} - Modified: {{ .LastModifier }} @ {{ .LastModifiedDate.Format "2006/01/02" }}
{{- end -}}
</div>
</footer>
{{ end }}
<div class="card bg-base-100 shadow-xl bg-base-250 text-neutral-content">
<div class="card-body">
<h2 class="card-title">Comments</h2>
{{ range .Comments }}
<div class="chat chat-start">
<p class="chat-header">{{ .Author }}</p>
<p class="chat-bubble">{{ .Content }}</p>
<time class="chat-footer text-xs opacity-50">Sent at {{ .Date }}<time>
</div>
{{ end }}
<form method="POST" action="/submit_comment">
<input type="hidden" name="path" value="{{ .Path }}"></input>
<div class="form-control">
<label for="author" class="Label">Name:</label>
<input type="text" id="author" name="author" class="input input-bordered max-w-md" placeholder="Your Name" required></input>
</div>
<div class="form-control space-y-0.5">
<label for="content" class="label">Comment:</label>
<textarea id="content" name="content" class="textarea textarea-bordered max-w-md" required placeholder="Your Comment Here"></textarea>
<button class="btn btn-secondary sm:btn-sm md:btn-md lg:btn-l max-w-xs" type="submit">Submit</button>
</div>
</form>
</div> </div>
</div> <hr>
<div class="page-info">
{{- if eq .Author .LastModifier -}}
Authored: {{ .Author }} @ {{ .AuthoredDate.Format "2006/01/02" }}
{{- else -}}
Authored: {{ .Author }} @ {{ .AuthoredDate.Format "2006/01/02" }} - Modified: {{ .LastModifier }} @ {{ .LastModifiedDate.Format "2006/01/02" }}
{{- end -}}
</div>
<div class="comments">
<h2>Comments</h2>
{{ range .Comments }}
<div class="comment">
<p>{{ .Content }}</p>
<p><small>{{ .Author }} at {{ .Date }}</small></p>
</div>
{{ end }}
</div>
</main> </main>
</body> <form method="POST" action="/submit_comment">
<input type="hidden" name="path" value="{{ .Path }}">
<div class="form-group">
<label for="author">Name:</label>
<input type="text" id="author" name="author">
</div>
<div class="form-group">
<label for="body">Comment:</label>
<textarea id="content" name="content"></textarea>
</div>
<button type="submit">Submit</button>
</form>
<div class="sidebar">
<h2>Pages</h2>
<ul>
{{ range .Pages }}
<li><a href="/{{ . }}">{{ . }}</a></li>
{{ end }}
</ul>
</div>
</body>
</html> </html>

File diff suppressed because one or more lines are too long

View File

@ -1,12 +0,0 @@
[Server]
Port = ":8080"
[Git]
UseGit = true
RepoURL = "https://git.tcp.direct/S4D/tcp-wiki.git"
Branch = "main"
LocalPath = "data"
[Database]
Path = "comments.db"

22
dev.sh Normal file
View File

@ -0,0 +1,22 @@
#/bin/bash
# this sets up super annoying shit like hard symlinks and whatever else
# 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
# Clone the repository
echo "Press Control+C when prompted"
go run ./src
# Set up hard links
# !!! for main branch only !!!
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 "Go ahead and run go run src/"
echo "And Go to: http://127.0.0.1:8080"

1
go.mod
View File

@ -3,7 +3,6 @@ module git.tcp.direct/S4D/tcp-wiki
go 1.20 go 1.20
require ( require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/Microsoft/go-winio v0.6.0 // indirect github.com/Microsoft/go-winio v0.6.0 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230331115716-d34776aa93ec // indirect github.com/ProtonMail/go-crypto v0.0.0-20230331115716-d34776aa93ec // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect

2
go.sum
View File

@ -12,8 +12,6 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg=

View File

@ -14,46 +14,21 @@ import (
"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 { const repoURL = "https://git.tcp.direct/S4D/tcp-wiki.git"
Server struct { const repoBRANCH = "main"
Port string const localPath = "data"
}
Git struct {
UseGit bool
RepoURL string
Branch string
LocalPath string
}
Database struct {
Path string
}
}
var commentsDB *bitcask.Bitcask var commentsDB *bitcask.Bitcask
func main() { func main() {
var config Config err := cloneRepository(repoURL, localPath)
if _, err := toml.DecodeFile("config.toml", &config); err != nil { if err != nil && err != git.ErrRepositoryAlreadyExists {
log.Fatalf("Failed to load config: %v", err) log.Fatalf("Failed to clone repository: %v", err)
} }
if config.Git.UseGit { commentsDB, err = bitcask.Open("comments.db")
err := cloneRepository(config.Git.RepoURL, config.Git.LocalPath)
if err != nil && err != git.ErrRepositoryAlreadyExists {
log.Fatalf("Failed to clone repository: %v", err)
}
} else {
if _, err := os.Stat(config.Git.LocalPath); os.IsNotExist(err) {
os.MkdirAll(config.Git.LocalPath, os.ModePerm)
}
}
var err error
commentsDB, err = bitcask.Open(config.Database.Path)
if err != nil { if err != nil {
log.Fatalf("Failed to open comments database: %v", err) log.Fatalf("Failed to open comments database: %v", err)
} }
@ -61,14 +36,10 @@ func main() {
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("/", handler)
handler(&config, w, r) http.HandleFunc("/submit_comment", submitCommentHandler)
})
http.HandleFunc("/submit_comment", func(w http.ResponseWriter, r *http.Request) {
submitCommentHandler(w, r)
})
srv := &http.Server{Addr: config.Server.Port} srv := &http.Server{Addr: ":8080"}
go func() { go func() {
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("ListenAndServe() failed: %v", err) log.Fatalf("ListenAndServe() failed: %v", err)
@ -78,10 +49,12 @@ func main() {
fmt.Println("Server running at http://127.0.0.1:8080") fmt.Println("Server running at http://127.0.0.1:8080")
fmt.Println("Press Ctrl-C to stop the server") fmt.Println("Press Ctrl-C to stop the server")
// Wait for interrupt signal to stop the server
c := make(chan os.Signal, 1) c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM) signal.Notify(c, os.Interrupt, syscall.SIGTERM)
<-c <-c
// Shutdown the server gracefully
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel() defer cancel()
if err := srv.Shutdown(ctx); err != nil { if err := srv.Shutdown(ctx); err != nil {
@ -90,22 +63,18 @@ func main() {
fmt.Println("Server stopped") fmt.Println("Server stopped")
} }
func handler(config *Config, w http.ResponseWriter, r *http.Request) { func handler(w http.ResponseWriter, r *http.Request) {
// For debugging // For debugging
log.Printf("Local Path: %q", config.Git.LocalPath) log.Printf("LOCAL PATH: %q", localPath)
if r.URL.Path == "./assets/favicon.ico" { if r.URL.Path == "./assets/favicon.ico" {
return return
} }
if config.Git.UseGit { err := pullRepository(localPath, repoBRANCH)
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)
return
}
}
filePath := strings.TrimPrefix(r.URL.Path, "/") filePath := strings.TrimPrefix(r.URL.Path, "/")
if filePath == "" { if filePath == "" {
@ -117,34 +86,33 @@ func handler(config *Config, w http.ResponseWriter, r *http.Request) {
csp := "default-src 'self'; img-src 'self'; script-src 'self'; style-src 'self';" csp := "default-src 'self'; img-src 'self'; script-src 'self'; style-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(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, localPath, 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
} }
// Ensure the path uses web-friendly slashes
relPath = strings.Replace(relPath, string(os.PathSeparator), "/", -1) relPath = strings.Replace(relPath, string(os.PathSeparator), "/", -1)
files = append(files, relPath) files = append(files, relPath)
} }

View File

@ -21,34 +21,22 @@ type Page struct {
Comments []Comment Comments []Comment
Path string Path string
Author string Author string
AuthoredDate *time.Time AuthoredDate time.Time
LastModifier string LastModifier string
LastModifiedDate *time.Time LastModifiedDate time.Time
Pages []string Pages []string
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, localPath, filePath string, commentsDB *bitcask.Bitcask, pages []string) error {
var content []byte content, err := readFileFromRepo(localPath, filePath)
var err error if err != nil {
if config.Git.UseGit {
content, err = readFileFromRepo(config.Git.LocalPath, filePath)
if err != nil {
return err return err
}
} else {
fullPath := filepath.Join(config.Git.LocalPath, filePath)
content, err = ioutil.ReadFile(fullPath)
if err != nil {
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, localPath, filePath, pages) // Now correctly includes `pages`
case ".html", ".css": case ".html", ".css":
renderStatic(w, content, ext) renderStatic(w, content, ext)
default: default:
@ -57,31 +45,23 @@ func renderPage(w http.ResponseWriter, r *http.Request, config *Config, filePath
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, localPath, filePath string, pages []string) {
md := goldmark.New( md := goldmark.New(
goldmark.WithExtensions( goldmark.WithExtensions(
extension.GFM, extension.GFM, // GitHub Flavored Markdown
highlighting.NewHighlighting( highlighting.NewHighlighting(
highlighting.WithStyle("monokai"), highlighting.WithStyle("monokai"),
), ),
), ),
) )
var author, lastModifier string author, authoredDate, lastModifier, lastModifiedDate, err := getAuthorAndLastModification(localPath, filePath)
var authoredDate, lastModifiedDate *time.Time if err != nil {
var err error http.Error(w, "Error fetching author and last modification date", http.StatusInternalServerError)
return
if config.Git.UseGit { }
var ad, lmd time.Time
author, ad, lastModifier, lmd, err = getAuthorAndLastModification(config.Git.LocalPath, filePath)
if err != nil {
http.Error(w, "Error fetching author and last modification date", http.StatusInternalServerError)
return
}
authoredDate = &ad
lastModifiedDate = &lmd
}
var mdBuf bytes.Buffer var mdBuf bytes.Buffer
err = md.Convert(content, &mdBuf) err = md.Convert(content, &mdBuf)
@ -113,9 +93,7 @@ func renderMarkdown(w http.ResponseWriter, r *http.Request, content []byte, comm
LastModifier: lastModifier, LastModifier: lastModifier,
LastModifiedDate: lastModifiedDate, LastModifiedDate: lastModifiedDate,
Pages: pages, Pages: pages,
UseGit: config.Git.UseGit,
} }
t, err := template.New("layout").Parse(string(layout)) t, err := template.New("layout").Parse(string(layout))
if err != nil { if err != nil {
http.Error(w, "Error parsing layout", http.StatusInternalServerError) http.Error(w, "Error parsing layout", http.StatusInternalServerError)
@ -125,8 +103,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) // Add this line
http.Error(w, "Error rendering layout", http.StatusInternalServerError) http.Error(w, "Error rendering layout", http.StatusInternalServerError)
return return
} }