mirror of
https://github.com/schollz/cowyo.git
synced 2023-08-10 21:13:00 +03:00
Compare commits
168 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d3a301497 | ||
|
|
a3e1d6f6d1 | ||
|
|
8318b5afd8 | ||
|
|
82b16ec121 | ||
|
|
8e6b798be5 | ||
|
|
57cda99027 | ||
|
|
e8959d8923 | ||
|
|
9d9918966a | ||
|
|
05f43196c5 | ||
|
|
c35ab4eb4a | ||
|
|
5fab2749f9 | ||
|
|
da2dcee3fd | ||
|
|
e11baae47c | ||
|
|
035cd7b6d9 | ||
|
|
7b1ce17506 | ||
|
|
14f563572d | ||
|
|
5d273c037a | ||
|
|
dbd1be938e | ||
|
|
3f98cb7e70 | ||
|
|
f09cf1b3e9 | ||
|
|
c9b9032dc9 | ||
|
|
674b39b49a | ||
|
|
e066ad23cc | ||
|
|
11b671423c | ||
|
|
82c24f2413 | ||
|
|
fa7ae60d59 | ||
|
|
20dd8d4f20 | ||
|
|
bd3b9c96e1 | ||
|
|
a78468e086 | ||
|
|
97bfc15692 | ||
|
|
61b8d275be | ||
|
|
3b7e35f9ef | ||
|
|
bbf55f539f | ||
|
|
28d70b6148 | ||
|
|
3b0169212c | ||
|
|
90faae9e85 | ||
|
|
e9e513171c | ||
|
|
bd6ed8d3c0 | ||
|
|
c1b9b71669 | ||
|
|
edcd2e7d09 | ||
|
|
3fd886e625 | ||
|
|
4709654c4c | ||
|
|
cc04599b6f | ||
|
|
d247846934 | ||
|
|
79768ce9be | ||
|
|
a16fd6f8fb | ||
|
|
62c9e6ee1b | ||
|
|
3a265a0979 | ||
|
|
39ebd6c125 | ||
|
|
bd85204eff | ||
|
|
322294b2ac | ||
|
|
1686409cf7 | ||
|
|
0109a94cbb | ||
|
|
17bf3c3a4c | ||
|
|
cbede33317 | ||
|
|
dbeea86d6a | ||
|
|
accc3b5f62 | ||
|
|
12d447ed74 | ||
|
|
328e177b66 | ||
|
|
38e0943df3 | ||
|
|
c66c312fdb | ||
|
|
c3ba228288 | ||
|
|
3a463449fb | ||
|
|
bca008f315 | ||
|
|
6347038563 | ||
|
|
a203fcc32f | ||
|
|
538fa425b0 | ||
|
|
b4fecbfaa2 | ||
|
|
fb5ee8d09e | ||
|
|
522c4c6283 | ||
|
|
f42c0f411b | ||
|
|
59284f2601 | ||
|
|
d46fe9cb6a | ||
|
|
64b3fdb0d4 | ||
|
|
6031590d87 | ||
|
|
4d3385b3f1 | ||
|
|
3ba5537c1e | ||
|
|
8a7803250f | ||
|
|
97edbd73a8 | ||
|
|
79422d1fb3 | ||
|
|
0be3c91823 | ||
|
|
04f1cc87e9 | ||
|
|
706e4e6fdf | ||
|
|
5e1ac31d13 | ||
|
|
7d878a18b4 | ||
|
|
95401434a2 | ||
|
|
074b776984 | ||
|
|
4ac089780e | ||
|
|
d0791f3b2e | ||
|
|
18d002abf4 | ||
|
|
4276e4bec3 | ||
|
|
eca269696f | ||
|
|
a3a881b65e | ||
|
|
09b09f3dbc | ||
|
|
dba80e26c7 | ||
|
|
7d6cb4910c | ||
|
|
6b5a4d0061 | ||
|
|
629a2a026a | ||
|
|
465b0b3f84 | ||
|
|
f79e031bbc | ||
|
|
9570d182e3 | ||
|
|
64d589df44 | ||
|
|
7709b6317d | ||
|
|
f705f8ee87 | ||
|
|
5d97c4651a | ||
|
|
f89c71f013 | ||
|
|
1ee8269267 | ||
|
|
c0d4c099d0 | ||
|
|
6f5400eaa1 | ||
|
|
24ce00aff9 | ||
|
|
91c6266664 | ||
|
|
2a97331c59 | ||
|
|
684d7fbe38 | ||
|
|
06222774a0 | ||
|
|
1febbd1bd9 | ||
|
|
f61590de70 | ||
|
|
d511eb5371 | ||
|
|
a6e96a5446 | ||
|
|
b443193386 | ||
|
|
e1a9243a1e | ||
|
|
d1904b6158 | ||
|
|
640e715515 | ||
|
|
382f53678f | ||
|
|
e0ba778263 | ||
|
|
6fd69965ed | ||
|
|
56b43600b1 | ||
|
|
bd80123a74 | ||
|
|
1a8d796e98 | ||
|
|
0dacd669c8 | ||
|
|
6adb82b7d7 | ||
|
|
e56e2d725f | ||
|
|
c35c2ea1ce | ||
|
|
53729454e0 | ||
|
|
61dddac837 | ||
|
|
9ee72fddf6 | ||
|
|
cb0e6276e5 | ||
|
|
7077950bb4 | ||
|
|
e142daa0f4 | ||
|
|
616b3cea68 | ||
|
|
0f34d342d8 | ||
|
|
380707cbe6 | ||
|
|
041d674ddc | ||
|
|
8ebe02f432 | ||
|
|
b4e3628008 | ||
|
|
0f31132a8a | ||
|
|
0a364fd68a | ||
|
|
279bacc9e8 | ||
|
|
44c2dcec7c | ||
|
|
1c64394639 | ||
|
|
50382c85b8 | ||
|
|
749d455fb4 | ||
|
|
c058bc09f1 | ||
|
|
4b6189f831 | ||
|
|
57771c742c | ||
|
|
f82b1d4369 | ||
|
|
8ff9589dde | ||
|
|
59955afa78 | ||
|
|
f5b10f8c2e | ||
|
|
5d3c81cda0 | ||
|
|
d8d7509aa8 | ||
|
|
5b57696333 | ||
|
|
7225e2a8fb | ||
|
|
27de2fb3b2 | ||
|
|
6eae8f49e1 | ||
|
|
d04f57e7a7 | ||
|
|
c48d175c02 | ||
|
|
2a0c9efa18 | ||
|
|
b6ce06037d |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
static/* linguist-vendored
|
||||
8
.travis.yml
Normal file
8
.travis.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- go get -u github.com/schollz/cowyo
|
||||
- go get -u github.com/jteeuwen/go-bindata/...
|
||||
4
LICENSE
4
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Zack
|
||||
Copyright (c) 2017 Zack
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
72
Makefile
72
Makefile
@@ -1,51 +1,35 @@
|
||||
ADDRESS = awwkoala.com
|
||||
PORT = 8003
|
||||
# Make a release with
|
||||
# make -j4 release
|
||||
|
||||
CUR_DIR = $(shell bash -c 'pwd')
|
||||
USERCUR = $(shell bash -c 'whoami')
|
||||
VERSION=$(shell git describe)
|
||||
LDFLAGS=-ldflags "-s -w -X main.version=${VERSION}"
|
||||
|
||||
make:
|
||||
.PHONY: build
|
||||
build:
|
||||
go-bindata static/... templates/...
|
||||
go build
|
||||
|
||||
install:
|
||||
rm -rf jinstall
|
||||
mkdir jinstall
|
||||
cp install/awwkoala.nginx jinstall/awwkoala.nginx
|
||||
sed -i 's/PORT/$(PORT)/g' jinstall/awwkoala.nginx
|
||||
sed -i 's/ADDRESS/$(ADDRESS)/g' jinstall/awwkoala.nginx
|
||||
sed -i 's^CUR_DIR^$(CUR_DIR)^g' jinstall/awwkoala.nginx
|
||||
cp install/awwkoala.init jinstall/awwkoala.init
|
||||
sed -i 's/EXT_ADDRESS/$(ADDRESS)/g' jinstall/awwkoala.init
|
||||
sed -i 's^CUR_DIR^$(CUR_DIR)^g' jinstall/awwkoala.init
|
||||
sed -i 's^USERCUR^$(USERCUR)^g' jinstall/awwkoala.init
|
||||
sed -i 's^PORT^$(PORT)^g' jinstall/awwkoala.init
|
||||
cp jinstall/awwkoala.init /etc/init.d/awwkoala.init
|
||||
chmod +x /etc/init.d/awwkoala.init
|
||||
cp jinstall/awwkoala.nginx /etc/nginx/sites-available/awwkoala.nginx
|
||||
ln -fs /etc/nginx/sites-available/awwkoala.nginx /etc/nginx/sites-enabled/awwkoala.nginx
|
||||
/etc/init.d/nginx reload
|
||||
/etc/init.d/nginx restart
|
||||
/etc/init.d/awwkoala.init restart
|
||||
rm -rf jinstall
|
||||
.PHONY: linuxarm
|
||||
linuxarm:
|
||||
env GOOS=linux GOARCH=arm go build ${LDFLAGS} -o dist/cowyo_linux_arm
|
||||
cd dist && upx --brute cowyo_linux_arm
|
||||
|
||||
binaries:
|
||||
rm -rf binaries
|
||||
rm -f awwkoala
|
||||
mkdir binaries
|
||||
env GOOS=linux GOARCH=amd64 go build -o awwkoala -v *.go
|
||||
zip -9 -r awwkoala-linux-64bit.zip awwkoala static/* templates/*
|
||||
rm -f awwkoala
|
||||
env GOOS=windows GOARCH=amd64 go build -o awwkoala.exe -v *.go
|
||||
zip -9 -r awwkoala-windows-64bit.zip awwkoala.exe static/* templates/*
|
||||
rm -f awwkoala.exe
|
||||
env GOOS=linux GOARCH=arm go build -o awwkoala -v *.go
|
||||
zip -9 -r awwkoala-raspberrypi.zip awwkoala static/* templates/*
|
||||
rm -f awwkoala
|
||||
env GOOS=darwin GOARCH=amd64 go build -o awwkoala -v *.go
|
||||
zip -9 -r awwkoala-macosx-64bit.zip awwkoala static/* templates/*
|
||||
rm -f awwkoala
|
||||
mv *.zip binaries/
|
||||
.PHONY: linux64
|
||||
linux64:
|
||||
env GOOS=linux GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_linux_amd64
|
||||
cd dist && upx --brute cowyo_linux_amd64
|
||||
|
||||
.PHONY: windows
|
||||
windows:
|
||||
env GOOS=windows GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_windows_amd64.exe
|
||||
cd dist && upx --brute cowyo_windows_amd64.exe
|
||||
|
||||
.PHONY: osx
|
||||
osx:
|
||||
env GOOS=darwin GOARCH=amd64 go build ${LDFLAGS} -o dist/cowyo_osx_amd64
|
||||
cd dist && upx --brute cowyo_osx_amd64
|
||||
|
||||
.PHONY: release
|
||||
release: osx windows linux64 linuxarm
|
||||
|
||||
|
||||
.PHONY: install
|
||||
.PHONY: binaries
|
||||
157
README.md
157
README.md
@@ -1,129 +1,78 @@
|
||||

|
||||
|
||||
# AwwKoala - [Demo](http://awwkoala.com/)
|
||||
## A Websocket Wiki and Kind Of A List Application
|
||||
[]() [](https://goreportcard.com/report/github.com/schollz/AwwKoala) [](https://gitter.im/schollz/AwwKoala?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
This is a self-contained wiki webserver that makes sharing easy and _fast_. The most important feature here is *simplicity*. There are many other features as well including versioning, page locking, self-destructing messages, encryption, math support, and listifying. Read on to learn more about the features.
|
||||
|
||||
# Features
|
||||
**Simplicity**. The philosophy here is to *just type*. To jot a note, simply load the page at [`/`](http://AwwKoala.com/) and just start typing. No need to press edit, the browser will already be focused on the text. No need to press save - it will automatically save when you stop writing. The URL at [`/`](http://AwwKoala.com/) will redirect to an easy-to-remember name that you can use to reload the page at anytime, anywhere. But, you can also use any URL you want, e.g. [`/AnythingYouWant`](http://AwwKoala.com/AnythingYouWant). All pages can be rendered into HTML by adding `/view`. For example, the page [`/AnythingYouWant`](http://AwwKoala.com/AnythingYouWant) is rendered at [`/AnythingYouWant/view`](http://AwwKoala.com/AnythingYouWant/view). You can write in HTML or [Markdown](https://daringfireball.net/projects/markdown/) for page rendering. To quickly link to `/view` pages, just use `[[AnythingYouWnat]]`.
|
||||
|
||||

|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
src="/static/img/logo.png"
|
||||
width="260" height="80" border="0" alt="linkcrawler">
|
||||
<br>
|
||||
<a href="https://travis-ci.org/schollz/cowyo"><img src="https://img.shields.io/travis/schollz/cowyo.svg?style=flat-square" alt="Build Status"></a>
|
||||
<a href="https://github.com/schollz/cowyo/releases/latest"><img src="https://img.shields.io/badge/version-2.1.0-brightgreen.svg?style=flat-square" alt="Version"></a>
|
||||
</p>
|
||||
|
||||
**Listifying**. If you are writing a list and you want to tick off things really easily, just add `/list`. For example, after editing [`/grocery`](http://AwwKoala.com/grocery), goto [`/grocery/list`](http://AwwKoala.com/grocery/list). In this page, whatever you click on will be struck through and moved to the end. This is helpful if you write a grocery list and then want to easily delete things from it.
|
||||
<p align="center">A feature-rich wiki for minimalists</a></p>
|
||||
|
||||

|
||||
*cowyo* is a self-contained wiki server that makes jotting notes easy and _fast_. The most important feature here is _simplicity_. Other features include versioning, page locking, self-destructing messages, encryption, and listifying. You can [download *cowyo* as a single executable](https://github.com/schollz/cowyo/releases/latest) or install it with Go. Try it out at https://cowyo.com.
|
||||
|
||||
<br>
|
||||
Getting Started
|
||||
===============
|
||||
|
||||
**Automatic versioning**. All previous versions of all notes are stored and can be accessed by adding `?version=X` onto `/view` or `/edit`. If you are on the `/view` or `/edit` pages the menu below will show the most substantial changes in the history. Note, only the _current_ version can be edited (no branching allowed, yet).
|
||||
## Install
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
|
||||
**Self-destructing messages**. You can write a message that will delete itself when a user loads it (in any view). Useful for transmitting sensitive information. To use, simply add a line somewhere that says only "`self-destruct`".
|
||||
|
||||

|
||||
|
||||
|
||||
<br>
|
||||
|
||||
**Security**. HTTPS support is provided and everything is sanitized to prevent XSS attacks. Though all URLs are publicly accessible, you are free to obfuscate your website by using an obscure/random address (read: the site is still publicly accessible, just hard to find!). In addition to TLS support, you can PGP-encrypt your messages using a passphrase (_Note: This will delete the version tree_).
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
|
||||
**Page locking**. You can apply a password to a page to allow further edits from being available. The whole version tree will still be available. _Note_: This is not available for list mode.
|
||||
|
||||
**Keyboard Shortcuts**. Quickly transition between Edit/View/List by using `Ctl+Shift+E` to Edit, `Ctl+Shift+Z` to View, and `Ctl+Shift+L` to Listify.
|
||||
|
||||
**Admin controls**. The Admin can view/delete all the documents by setting the `-a YourAdminKey` when starting the program. Then the admin has access to the `/ls/YourAdminKey` to view and delete any of the pages.
|
||||
|
||||
**Math support**. Math is supported with [Katex](https://github.com/Khan/KaTeX) using `$\frac{1}{2}$` for inline equations and `$$\frac{1}{2}$$` for regular equations.
|
||||
|
||||
|
||||
|
||||
# Install
|
||||
|
||||
First [install Go](https://golang.org/doc/install). Then continue.
|
||||
|
||||
## To access locally...
|
||||
|
||||
Then, if you want to host on your local network just do:
|
||||
If you have go
|
||||
|
||||
```
|
||||
git clone https://github.com/schollz/awwkoala.git
|
||||
cd awwkoala
|
||||
make
|
||||
./awwkoala -p :8001 LOCALIPADDRESS
|
||||
go get -u -v github.com/schollz/cowyo
|
||||
```
|
||||
|
||||
and then goto the address `http://LOCALIPADDRESS:8001/`
|
||||
or just download from the [latest releases](https://github.com/schollz/cowyo/releases/latest).
|
||||
|
||||
## To access from anywhere...
|
||||
## Run
|
||||
|
||||
For this you need to forward port 80 and [get a DNS for your external address](https://www.duckdns.org/). I recommend using `NGINX` as middleware, as it will do caching of the static files for you.
|
||||
|
||||
```bash
|
||||
sudo apt-get install nginx
|
||||
```
|
||||
|
||||
There is an example `NGINX` block in `install/`. If you want to use SSL instead, follow the instructions in `letsencrypt/README.md`. To automatically install, on Raspberry Pi / Ubuntu / Debian system use:
|
||||
To run just double click or from the command line:
|
||||
|
||||
```
|
||||
git clone https://github.com/schollz/awwkoala.git
|
||||
cd awwkoala
|
||||
nano Makefile <--- EDIT this Makefile to include YOUR EXTERNAL ADDRESS
|
||||
make && sudo make install
|
||||
cowyo
|
||||
```
|
||||
|
||||
Now the program starts and stops with
|
||||
## Usage
|
||||
|
||||
```
|
||||
sudo /etc/init.d/AwwKoala start|stop|restart
|
||||
```
|
||||
*cowyo* is straightforward to use. Here are some of the basic features:
|
||||
|
||||
Edit your crontab (`sudo crontab -e`) to start on boot:
|
||||
### Editing
|
||||
|
||||
```
|
||||
@reboot /etc/init.d/AwwKoala start
|
||||
```
|
||||
When you open a document you'll be directed to an alliterative animal (which is supposed to be easy to remember). You can write in Markdown. Saving is performed as soon as you stop writing.
|
||||
|
||||
# Usage
|
||||

|
||||
|
||||
```
|
||||
$ awwkoala --help
|
||||
awwkoala: A Websocket Wiki and Kind Of A List Application
|
||||
run this to start the server and then visit localhost at the port you specify
|
||||
(see parameters).
|
||||
Example: 'awwkoala localhost'
|
||||
Example: 'awwkoala -p :8080 localhost:8080'
|
||||
Example: 'awwkoala -db /var/lib/awwkoala/db.bolt localhost:8003'
|
||||
Example: 'awwkoala -p :8080 -crt ssl/server.crt -key ssl/server.key localhost:8080'
|
||||
Options:
|
||||
-a string
|
||||
key to access admin priveleges (default no admin priveleges)
|
||||
-crt string
|
||||
location of ssl crt
|
||||
-db string
|
||||
location of database file (default "/home/mu/awwkoala/data.db")
|
||||
-httptest.serve string
|
||||
if non-empty, httptest.NewServer serves on this address and blocks
|
||||
-key string
|
||||
location of ssl key
|
||||
-p string
|
||||
port to bind (default ":8003")
|
||||
```
|
||||
### History
|
||||
|
||||
If you set the admin flag, `-a` you can access a list of all the current files by going to `/ls/WhateverYouSetTheFlagTo`.
|
||||
You can easily see previous versions of your documents.
|
||||
|
||||
# Contact
|
||||
If you'd like help, go ahead and clone and send a pull request. If you find a bug, please submit [an issue](https://github.com/schollz/AwwKoala/issues). Any other comments, questions or anything at all, just <a href="https://twitter.com/intent/tweet?screen_name=zack_118" class="twitter-mention-button" data-related="zack_118">tweet me @zack_118</a>
|
||||

|
||||
|
||||
# Contributors
|
||||
Thanks to [tscholl2](https://github.com/tscholl2).
|
||||
### Lists
|
||||
|
||||
You can easily make lists and check them off.
|
||||
|
||||

|
||||
|
||||
### Locking
|
||||
|
||||
Locking prevents other users from editing your pages without a passphrase.
|
||||
|
||||

|
||||
|
||||
### Encryption
|
||||
|
||||
Encryption is performed using AES-256.
|
||||
|
||||

|
||||
|
||||
### Self-destructing pages
|
||||
|
||||
Just like in mission impossible.
|
||||
|
||||

|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
|
||||
1
bindata.go.REMOVED.git-id
Normal file
1
bindata.go.REMOVED.git-id
Normal file
@@ -0,0 +1 @@
|
||||
0689ef697703381e56db9e318921ec13f34f56f5
|
||||
222
db.go
222
db.go
@@ -1,222 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
)
|
||||
|
||||
var db *bolt.DB
|
||||
var open bool
|
||||
|
||||
// Open to create the database and open
|
||||
func Open(filename string) error {
|
||||
var err error
|
||||
config := &bolt.Options{Timeout: 30 * time.Second}
|
||||
db, err = bolt.Open(filename, 0600, config)
|
||||
if err != nil {
|
||||
fmt.Println("Opening BoltDB timed out")
|
||||
log.Fatal(err)
|
||||
}
|
||||
open = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close database
|
||||
func Close() {
|
||||
open = false
|
||||
db.Close()
|
||||
}
|
||||
|
||||
// WikiData is data for storing in DB
|
||||
type WikiData struct {
|
||||
Title string
|
||||
CurrentText string
|
||||
Diffs []string
|
||||
Timestamps []string
|
||||
Encrypted bool
|
||||
Locked string
|
||||
}
|
||||
|
||||
func hasPassword(title string) (bool, error) {
|
||||
title = strings.ToLower(title)
|
||||
if !open {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
}
|
||||
hasPassword := false
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
var err error
|
||||
b := tx.Bucket([]byte("datas"))
|
||||
if b == nil {
|
||||
return fmt.Errorf("db must be opened before loading")
|
||||
}
|
||||
k := []byte(title)
|
||||
val := b.Get(k)
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
var p WikiData
|
||||
err = p.decode(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, line := range strings.Split(p.CurrentText, "\n") {
|
||||
if strings.Contains(line, "<") == true && strings.Contains(line, ">") == true && strings.Contains(line, "user") == true && strings.Contains(line, "password") == true && strings.Contains(line, "public") == true {
|
||||
hasPassword = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Could not get WikiData: %s", err)
|
||||
return false, err
|
||||
}
|
||||
return hasPassword, nil
|
||||
}
|
||||
|
||||
func getCurrentText(title string, version int) (string, []versionsInfo, bool, time.Duration, bool, string) {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
title = strings.ToLower(title)
|
||||
var vi []versionsInfo
|
||||
totalTime := time.Now().Sub(time.Now())
|
||||
isCurrent := true
|
||||
currentText := ""
|
||||
encrypted := false
|
||||
locked := ""
|
||||
if !open {
|
||||
return currentText, vi, isCurrent, totalTime, encrypted, locked
|
||||
}
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
var err error
|
||||
b := tx.Bucket([]byte("datas"))
|
||||
if b == nil {
|
||||
return fmt.Errorf("db must be opened before loading")
|
||||
}
|
||||
k := []byte(title)
|
||||
val := b.Get(k)
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
var p WikiData
|
||||
err = p.decode(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentText = p.CurrentText
|
||||
encrypted = p.Encrypted
|
||||
locked = p.Locked
|
||||
if version > -1 && version < len(p.Diffs) {
|
||||
// get that version of text instead
|
||||
currentText = rebuildTextsToDiffN(p, version)
|
||||
isCurrent = false
|
||||
}
|
||||
vi, totalTime = getImportantVersions(p)
|
||||
log.Println(totalTime)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Could not get WikiData: %s", err)
|
||||
}
|
||||
return currentText, vi, isCurrent, totalTime, encrypted, locked
|
||||
}
|
||||
|
||||
func (p *WikiData) load(title string) error {
|
||||
title = strings.ToLower(title)
|
||||
if !open {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
}
|
||||
err := db.View(func(tx *bolt.Tx) error {
|
||||
var err error
|
||||
b := tx.Bucket([]byte("datas"))
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
k := []byte(title)
|
||||
val := b.Get(k)
|
||||
if val == nil {
|
||||
// make new one
|
||||
p.Title = title
|
||||
p.CurrentText = ""
|
||||
p.Diffs = []string{}
|
||||
p.Timestamps = []string{}
|
||||
return nil
|
||||
}
|
||||
err = p.decode(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Could not get WikiData: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *WikiData) save(newText string) error {
|
||||
if !open {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
}
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
bucket, err := tx.CreateBucketIfNotExists([]byte("datas"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create bucket: %s", err)
|
||||
}
|
||||
// find diffs
|
||||
dmp := diffmatchpatch.New()
|
||||
diffs := dmp.DiffMain(p.CurrentText, newText, true)
|
||||
delta := dmp.DiffToDelta(diffs)
|
||||
p.CurrentText = newText
|
||||
p.Timestamps = append(p.Timestamps, time.Now().Format(time.ANSIC))
|
||||
p.Diffs = append(p.Diffs, delta)
|
||||
enc, err := p.encode()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not encode WikiData: %s", err)
|
||||
}
|
||||
p.Title = strings.ToLower(p.Title)
|
||||
err = bucket.Put([]byte(p.Title), enc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could add to bucket: %s", err)
|
||||
}
|
||||
return err
|
||||
})
|
||||
// // Add the new name to the programdata so its not randomly generated
|
||||
// if err == nil && len(p.Timestamps) > 0 && len(p.CurrentText) > 0 {
|
||||
// err2 := db.Update(func(tx *bolt.Tx) error {
|
||||
// b := tx.Bucket([]byte("programdata"))
|
||||
// id, _ := b.NextSequence()
|
||||
// idInt := int(id)
|
||||
// return b.Put(itob(idInt), []byte(p.Title))
|
||||
// })
|
||||
// if err2 != nil {
|
||||
// return fmt.Errorf("could not add to programdata: %s", err)
|
||||
// }
|
||||
// }
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *WikiData) encode() ([]byte, error) {
|
||||
enc, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return enc, nil
|
||||
}
|
||||
|
||||
func (p *WikiData) decode(data []byte) error {
|
||||
err := json.Unmarshal(data, &p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"golang.org/x/crypto/openpgp"
|
||||
"golang.org/x/crypto/openpgp/armor"
|
||||
)
|
||||
|
||||
var encryptionType string
|
||||
|
||||
func init() {
|
||||
encryptionType = "PGP SIGNATURE"
|
||||
}
|
||||
|
||||
func encryptString(encryptionText string, encryptionPassphraseString string) string {
|
||||
encryptionPassphrase := []byte(encryptionPassphraseString)
|
||||
encbuf := bytes.NewBuffer(nil)
|
||||
w, err := armor.Encode(encbuf, encryptionType, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
plaintext, err := openpgp.SymmetricallyEncrypt(w, encryptionPassphrase, nil, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
message := []byte(encryptionText)
|
||||
_, err = plaintext.Write(message)
|
||||
|
||||
plaintext.Close()
|
||||
w.Close()
|
||||
return encbuf.String()
|
||||
}
|
||||
|
||||
func decryptString(decryptionString string, encryptionPassphraseString string) (string, error) {
|
||||
encryptionPassphrase := []byte(encryptionPassphraseString)
|
||||
decbuf := bytes.NewBuffer([]byte(decryptionString))
|
||||
result, err := armor.Decode(decbuf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
alreadyPrompted := false
|
||||
md, err := openpgp.ReadMessage(result.Body, nil, func(keys []openpgp.Key, symmetric bool) ([]byte, error) {
|
||||
if alreadyPrompted {
|
||||
return nil, errors.New("Could not decrypt using passphrase")
|
||||
} else {
|
||||
alreadyPrompted = true
|
||||
}
|
||||
return encryptionPassphrase, nil
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadAll(md.UnverifiedBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bytes), nil
|
||||
}
|
||||
|
||||
// func main() {
|
||||
// test := encryptString("This is some string", "golang")
|
||||
// fmt.Println(test)
|
||||
// testD := decryptString(test, "golang")
|
||||
// fmt.Println(testD)
|
||||
//
|
||||
// }
|
||||
376
handlers.go
Executable file
376
handlers.go
Executable file
@@ -0,0 +1,376 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
// "github.com/gin-contrib/static"
|
||||
"github.com/gin-contrib/multitemplate"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func serve(port string) {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
router := gin.Default()
|
||||
router.HTMLRender = loadTemplates("index.tmpl")
|
||||
// router.Use(static.Serve("/static/", static.LocalFile("./static", true)))
|
||||
router.GET("/", func(c *gin.Context) {
|
||||
c.Redirect(302, "/"+randomAlliterateCombo())
|
||||
})
|
||||
router.GET("/:page", func(c *gin.Context) {
|
||||
page := c.Param("page")
|
||||
c.Redirect(302, "/"+page+"/edit")
|
||||
})
|
||||
router.GET("/:page/*command", handlePageRequest)
|
||||
router.POST("/update", handlePageUpdate)
|
||||
router.POST("/prime", handlePrime)
|
||||
router.POST("/lock", handleLock)
|
||||
router.POST("/encrypt", handleEncrypt)
|
||||
router.DELETE("/oldlist", handleClearOldListItems)
|
||||
router.DELETE("/listitem", deleteListItem)
|
||||
|
||||
router.Run(":" + port)
|
||||
}
|
||||
|
||||
func loadTemplates(list ...string) multitemplate.Render {
|
||||
r := multitemplate.New()
|
||||
|
||||
for _, x := range list {
|
||||
templateString, err := Asset("templates/" + x)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tmplMessage, err := template.New(x).Parse(string(templateString))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
r.Add(x, tmplMessage)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func handlePageRequest(c *gin.Context) {
|
||||
page := c.Param("page")
|
||||
command := c.Param("command")
|
||||
if len(command) < 2 {
|
||||
command = "/ "
|
||||
}
|
||||
// Serve static content from memory
|
||||
if page == "static" {
|
||||
filename := page + command
|
||||
data, err := Asset(filename)
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Could not find data")
|
||||
}
|
||||
c.Data(http.StatusOK, contentType(filename), data)
|
||||
return
|
||||
}
|
||||
|
||||
version := c.DefaultQuery("version", "ajksldfjl")
|
||||
p := Open(page)
|
||||
|
||||
// Disallow anything but viewing locked/encrypted pages
|
||||
if (p.IsEncrypted || p.IsLocked) &&
|
||||
(command[0:2] != "/v" && command[0:2] != "/r") {
|
||||
c.Redirect(302, "/"+page+"/view")
|
||||
return
|
||||
}
|
||||
|
||||
// Destroy page if it is opened and primed
|
||||
if p.IsPrimedForSelfDestruct && !p.IsLocked && !p.IsEncrypted {
|
||||
p.Update("*This page has self-destructed. You can not return to it.*\n\n" + p.Text.GetCurrent())
|
||||
p.Erase()
|
||||
}
|
||||
if command == "/erase" {
|
||||
if !p.IsLocked && !p.IsEncrypted {
|
||||
p.Erase()
|
||||
c.Redirect(302, "/"+page+"/edit")
|
||||
} else {
|
||||
c.Redirect(302, "/"+page+"/view")
|
||||
}
|
||||
return
|
||||
}
|
||||
rawText := p.Text.GetCurrent()
|
||||
rawHTML := p.RenderedPage
|
||||
|
||||
// Check to see if an old version is requested
|
||||
versionInt, versionErr := strconv.Atoi(version)
|
||||
if versionErr == nil && versionInt > 0 {
|
||||
versionText, err := p.Text.GetPreviousByTimestamp(int64(versionInt))
|
||||
if err == nil {
|
||||
rawText = versionText
|
||||
rawHTML = GithubMarkdownToHTML(rawText)
|
||||
}
|
||||
}
|
||||
|
||||
// Get history
|
||||
var versionsInt64 []int64
|
||||
var versionsChangeSums []int
|
||||
var versionsText []string
|
||||
if command[0:2] == "/h" {
|
||||
versionsInt64, versionsChangeSums = p.Text.GetMajorSnapshotsAndChangeSums(60) // get snapshots 60 seconds apart
|
||||
versionsText = make([]string, len(versionsInt64))
|
||||
for i, v := range versionsInt64 {
|
||||
versionsText[i] = time.Unix(v/1000000000, 0).Format("Mon Jan 2 15:04:05 MST 2006")
|
||||
}
|
||||
versionsText = reverseSliceString(versionsText)
|
||||
versionsInt64 = reverseSliceInt64(versionsInt64)
|
||||
versionsChangeSums = reverseSliceInt(versionsChangeSums)
|
||||
}
|
||||
|
||||
if command[0:2] == "/r" {
|
||||
c.Writer.Header().Set("Content-Type", contentType(p.Name))
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
c.Writer.Header().Set("Access-Control-Max-Age", "86400")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-Max")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Data(200, contentType(p.Name), []byte(rawText))
|
||||
return
|
||||
}
|
||||
log.Debug(command)
|
||||
log.Debug("%v", command[0:2] != "/e" &&
|
||||
command[0:2] != "/v" &&
|
||||
command[0:2] != "/l" &&
|
||||
command[0:2] != "/h")
|
||||
|
||||
var FileNames, FileLastEdited []string
|
||||
var FileSizes, FileNumChanges []int
|
||||
if page == "ls" {
|
||||
command = "/view"
|
||||
FileNames, FileSizes, FileNumChanges, FileLastEdited = DirectoryList()
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"EditPage": command[0:2] == "/e", // /edit
|
||||
"ViewPage": command[0:2] == "/v", // /view
|
||||
"ListPage": command[0:2] == "/l", // /list
|
||||
"HistoryPage": command[0:2] == "/h", // /history
|
||||
"DontKnowPage": command[0:2] != "/e" &&
|
||||
command[0:2] != "/v" &&
|
||||
command[0:2] != "/l" &&
|
||||
command[0:2] != "/h",
|
||||
"DirectoryPage": page == "ls",
|
||||
"FileNames": FileNames,
|
||||
"FileSizes": FileSizes,
|
||||
"FileNumChanges": FileNumChanges,
|
||||
"FileLastEdited": FileLastEdited,
|
||||
"Page": page,
|
||||
"RenderedPage": template.HTML([]byte(rawHTML)),
|
||||
"RawPage": rawText,
|
||||
"Versions": versionsInt64,
|
||||
"VersionsText": versionsText,
|
||||
"VersionsChangeSums": versionsChangeSums,
|
||||
"IsLocked": p.IsLocked,
|
||||
"IsEncrypted": p.IsEncrypted,
|
||||
"ListItems": renderList(rawText),
|
||||
"Route": "/" + page + command,
|
||||
"HasDotInName": strings.Contains(page, "."),
|
||||
})
|
||||
}
|
||||
|
||||
func handlePageUpdate(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
NewText string `json:"new_text"`
|
||||
}
|
||||
var json QueryJSON
|
||||
if c.BindJSON(&json) != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong JSON"})
|
||||
return
|
||||
}
|
||||
if len(json.NewText) > 100000 {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Too much"})
|
||||
return
|
||||
}
|
||||
log.Trace("Update: %v", json)
|
||||
p := Open(json.Page)
|
||||
var message string
|
||||
if p.IsLocked {
|
||||
message = "Locked"
|
||||
} else if p.IsEncrypted {
|
||||
message = "Encrypted"
|
||||
} else {
|
||||
p.Update(json.NewText)
|
||||
p.Save()
|
||||
message = "Saved"
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
|
||||
}
|
||||
|
||||
func handlePrime(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
}
|
||||
var json QueryJSON
|
||||
if c.BindJSON(&json) != nil {
|
||||
c.String(http.StatusBadRequest, "Problem binding keys")
|
||||
return
|
||||
}
|
||||
log.Trace("Update: %v", json)
|
||||
p := Open(json.Page)
|
||||
if p.IsLocked {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
|
||||
return
|
||||
} else if p.IsEncrypted {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
|
||||
return
|
||||
}
|
||||
p.IsPrimedForSelfDestruct = true
|
||||
p.Save()
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Primed"})
|
||||
}
|
||||
|
||||
func handleLock(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
Passphrase string `json:"passphrase"`
|
||||
}
|
||||
|
||||
var json QueryJSON
|
||||
if c.BindJSON(&json) != nil {
|
||||
c.String(http.StatusBadRequest, "Problem binding keys")
|
||||
return
|
||||
}
|
||||
p := Open(json.Page)
|
||||
if p.IsEncrypted {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
|
||||
return
|
||||
}
|
||||
var message string
|
||||
if p.IsLocked {
|
||||
err2 := CheckPasswordHash(json.Passphrase, p.PassphraseToUnlock)
|
||||
if err2 != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Can't unlock"})
|
||||
return
|
||||
}
|
||||
p.IsLocked = false
|
||||
message = "Unlocked"
|
||||
} else {
|
||||
p.IsLocked = true
|
||||
p.PassphraseToUnlock = HashPassword(json.Passphrase)
|
||||
message = "Locked"
|
||||
}
|
||||
p.Save()
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
|
||||
}
|
||||
|
||||
func handleEncrypt(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
Passphrase string `json:"passphrase"`
|
||||
}
|
||||
|
||||
var json QueryJSON
|
||||
if c.BindJSON(&json) != nil {
|
||||
c.String(http.StatusBadRequest, "Problem binding keys")
|
||||
return
|
||||
}
|
||||
p := Open(json.Page)
|
||||
if p.IsLocked {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
|
||||
return
|
||||
}
|
||||
q := Open(json.Page)
|
||||
var message string
|
||||
if p.IsEncrypted {
|
||||
decrypted, err2 := DecryptString(p.Text.GetCurrent(), json.Passphrase)
|
||||
if err2 != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Wrong password"})
|
||||
return
|
||||
}
|
||||
q.Erase()
|
||||
q = Open(json.Page)
|
||||
q.Update(decrypted)
|
||||
q.IsEncrypted = false
|
||||
q.IsLocked = p.IsLocked
|
||||
q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
|
||||
message = "Decrypted"
|
||||
} else {
|
||||
currentText := p.Text.GetCurrent()
|
||||
encrypted, _ := EncryptString(currentText, json.Passphrase)
|
||||
q.Erase()
|
||||
q = Open(json.Page)
|
||||
q.Update(encrypted)
|
||||
q.IsEncrypted = true
|
||||
q.IsLocked = p.IsLocked
|
||||
q.IsPrimedForSelfDestruct = p.IsPrimedForSelfDestruct
|
||||
message = "Encrypted"
|
||||
}
|
||||
q.Save()
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": message})
|
||||
}
|
||||
|
||||
func deleteListItem(c *gin.Context) {
|
||||
lineNum, err := strconv.Atoi(c.DefaultQuery("lineNum", "None"))
|
||||
page := c.Query("page") // shortcut for c.Request.URL.Query().Get("lastname")
|
||||
if err == nil {
|
||||
p := Open(page)
|
||||
|
||||
_, listItems := reorderList(p.Text.GetCurrent())
|
||||
newText := p.Text.GetCurrent()
|
||||
for i, lineString := range listItems {
|
||||
// fmt.Println(i, lineString, lineNum)
|
||||
if i+1 == lineNum {
|
||||
// fmt.Println("MATCHED")
|
||||
if strings.Contains(lineString, "~~") == false {
|
||||
// fmt.Println(p.Text, "("+lineString[2:]+"\n"+")", "~~"+lineString[2:]+"~~"+"\n")
|
||||
newText = strings.Replace(newText+"\n", lineString[2:]+"\n", "~~"+strings.TrimSpace(lineString[2:])+"~~"+"\n", 1)
|
||||
} else {
|
||||
newText = strings.Replace(newText+"\n", lineString[2:]+"\n", lineString[4:len(lineString)-2]+"\n", 1)
|
||||
}
|
||||
p.Update(newText)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"success": true,
|
||||
"message": "Done.",
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"success": false,
|
||||
"message": err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func handleClearOldListItems(c *gin.Context) {
|
||||
type QueryJSON struct {
|
||||
Page string `json:"page"`
|
||||
}
|
||||
|
||||
var json QueryJSON
|
||||
if c.BindJSON(&json) != nil {
|
||||
c.String(http.StatusBadRequest, "Problem binding keys")
|
||||
return
|
||||
}
|
||||
p := Open(json.Page)
|
||||
if p.IsEncrypted {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Encrypted"})
|
||||
return
|
||||
}
|
||||
if p.IsLocked {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "message": "Locked"})
|
||||
return
|
||||
}
|
||||
lines := strings.Split(p.Text.GetCurrent(), "\n")
|
||||
newLines := make([]string, len(lines))
|
||||
newLinesI := 0
|
||||
for _, line := range lines {
|
||||
if strings.Count(line, "~~") != 2 {
|
||||
newLines[newLinesI] = line
|
||||
newLinesI++
|
||||
}
|
||||
}
|
||||
p.Update(strings.Join(newLines[0:newLinesI], "\n"))
|
||||
p.Save()
|
||||
c.JSON(http.StatusOK, gin.H{"success": true, "message": "Cleared"})
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: awwkoala
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Start daemon at boot time
|
||||
# Description: Enable service provided by daemon.
|
||||
### END INIT INFO
|
||||
|
||||
# This should be placed in /etc/init.d directory
|
||||
# start with
|
||||
# sudo /etc/init.d/awwkoala start
|
||||
# stop with
|
||||
# sudo /etc/init.d/awwkoala start
|
||||
|
||||
dir="CUR_DIR"
|
||||
user="USERCUR"
|
||||
cmd="./awwkoala -p :PORT EXT_ADDRESS"
|
||||
|
||||
name="awwkoala"
|
||||
pid_file="/var/run/$name.pid"
|
||||
stdout_log="/var/log/$name.log"
|
||||
stderr_log="/var/log/$name.err"
|
||||
|
||||
get_pid() {
|
||||
cat "$pid_file"
|
||||
}
|
||||
|
||||
is_running() {
|
||||
[ -f "$pid_file" ] && ps `get_pid` > /dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if is_running; then
|
||||
echo "Already started"
|
||||
else
|
||||
echo "Starting $name"
|
||||
cd "$dir"
|
||||
sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" &
|
||||
echo $! > "$pid_file"
|
||||
if ! is_running; then
|
||||
echo "Unable to start, see $stdout_log and $stderr_log"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if is_running; then
|
||||
echo -n "Stopping $name.."
|
||||
kill `get_pid`
|
||||
for i in {1..10}
|
||||
do
|
||||
if ! is_running; then
|
||||
break
|
||||
fi
|
||||
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
|
||||
if is_running; then
|
||||
echo "Not stopped; may still be shutting down or shutdown may have failed"
|
||||
exit 1
|
||||
else
|
||||
echo "Stopped"
|
||||
if [ -f "$pid_file" ]; then
|
||||
rm "$pid_file"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Not running"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
if is_running; then
|
||||
echo "Unable to stop, will not attempt to start"
|
||||
exit 1
|
||||
fi
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
if is_running; then
|
||||
echo "Running"
|
||||
else
|
||||
echo "Stopped"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -1,41 +0,0 @@
|
||||
server {
|
||||
# SERVER BLOCK FOR awwkoala
|
||||
listen 80; ## listen for ipv4; this line is default and implied
|
||||
|
||||
access_log /etc/nginx/logs/access-awwkoala.log;
|
||||
error_log /etc/nginx/logs/error-awwkoala.log info;
|
||||
root CUR_DIR;
|
||||
server_name ADDRESS;
|
||||
|
||||
# Media: images, icons, video, audio, HTC
|
||||
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
|
||||
expires 1M;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# CSS and Javascript
|
||||
location ~* \.(?:css|js)$ {
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
location ^~ /static {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location ~ ^/ {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
|
||||
proxy_pass http://127.0.0.1:PORT;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
ADDRESS = yourserver.com
|
||||
PORT = 8003
|
||||
|
||||
CUR_DIR = $(shell bash -c 'pwd')
|
||||
USERCUR = $(shell bash -c 'whoami')
|
||||
|
||||
make:
|
||||
go build
|
||||
|
||||
install:
|
||||
rm -rf jinstall
|
||||
mkdir jinstall
|
||||
cp awwkoala.ssl.nginx awwkoala.ssl.nginx.temp
|
||||
sed -i 's/PORT/$(PORT)/g' awwkoala.ssl.nginx.temp
|
||||
sed -i 's/ADDRESS/$(ADDRESS)/g' awwkoala.ssl.nginx.temp
|
||||
sed -i 's^CUR_DIR^$(CUR_DIR)^g' awwkoala.ssl.nginx.temp
|
||||
cp awwkoala.init awwkoala.init.temp
|
||||
sed -i 's/EXT_ADDRESS/$(ADDRESS)/g' awwkoala.init.temp
|
||||
sed -i 's^CUR_DIR^$(CUR_DIR)^g' awwkoala.init.temp
|
||||
sed -i 's^USERCUR^$(USERCUR)^g' awwkoala.init.temp
|
||||
sed -i 's^PORT^$(PORT)^g' awwkoala.init.temp
|
||||
cp awwkoala.init.temp /etc/init.d/awwkoala.init
|
||||
chmod +x /etc/init.d/awwkoala.init
|
||||
cp awwkoala.ssl.nginx.temp /etc/nginx/sites-available/awwkoala.nginx
|
||||
ln -fs /etc/nginx/sites-available/awwkoala.nginx /etc/nginx/sites-enabled/awwkoala.nginx
|
||||
/etc/init.d/nginx reload
|
||||
/etc/init.d/nginx restart
|
||||
/etc/init.d/awwkoala.init restart
|
||||
rm -rf *.temp
|
||||
|
||||
.PHONY: install
|
||||
@@ -1,17 +0,0 @@
|
||||
First install the NGINX block in this directory. (There is an experimental Makefile that will do this, just try `sudo make install`.
|
||||
|
||||
To use letsencrypt follow these steps:
|
||||
|
||||
```
|
||||
git clone https://github.com/letsencrypt/letsencrypt
|
||||
cd letsencrypt
|
||||
sudo service nginx stop
|
||||
sudo ./letsencrypt-auto certonly --standalone --email youremail@somewhere.com -d yourserver.com
|
||||
sudo service nginx start
|
||||
```
|
||||
|
||||
Then startup `awwkoala` with
|
||||
|
||||
```bash
|
||||
sudo ./awwkoala -p :8001 -key /etc/letsencrypt/live/yourserver.com/privkey.pem -crt /etc/letsencrypt/live/yourserver.com/cert.pem yourserver.com
|
||||
```
|
||||
@@ -1,101 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: awwkoala
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: Start daemon at boot time
|
||||
# Description: Enable service provided by daemon.
|
||||
### END INIT INFO
|
||||
|
||||
# This should be placed in /etc/init.d directory
|
||||
# start with
|
||||
# sudo /etc/init.d/awwkoala start
|
||||
# stop with
|
||||
# sudo /etc/init.d/awwkoala start
|
||||
|
||||
dir="CUR_DIR"
|
||||
user="USERCUR"
|
||||
cmd="./awwkoala -p :PORT -key /etc/letsencrypt/live/EXT_ADDRESS/privkey.pem -crt /etc/letsencrypt/live/EXT_ADDRESS/cert.pem yourserver.com./awwkoala EXT_ADDRESS"
|
||||
|
||||
name="awwkoala"
|
||||
pid_file="/var/run/$name.pid"
|
||||
stdout_log="/var/log/$name.log"
|
||||
stderr_log="/var/log/$name.err"
|
||||
|
||||
get_pid() {
|
||||
cat "$pid_file"
|
||||
}
|
||||
|
||||
is_running() {
|
||||
[ -f "$pid_file" ] && ps `get_pid` > /dev/null 2>&1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
if is_running; then
|
||||
echo "Already started"
|
||||
else
|
||||
echo "Starting $name"
|
||||
cd "$dir"
|
||||
sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" &
|
||||
echo $! > "$pid_file"
|
||||
if ! is_running; then
|
||||
echo "Unable to start, see $stdout_log and $stderr_log"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
if is_running; then
|
||||
echo -n "Stopping $name.."
|
||||
kill `get_pid`
|
||||
for i in {1..10}
|
||||
do
|
||||
if ! is_running; then
|
||||
break
|
||||
fi
|
||||
|
||||
echo -n "."
|
||||
sleep 1
|
||||
done
|
||||
echo
|
||||
|
||||
if is_running; then
|
||||
echo "Not stopped; may still be shutting down or shutdown may have failed"
|
||||
exit 1
|
||||
else
|
||||
echo "Stopped"
|
||||
if [ -f "$pid_file" ]; then
|
||||
rm "$pid_file"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "Not running"
|
||||
fi
|
||||
;;
|
||||
restart)
|
||||
$0 stop
|
||||
if is_running; then
|
||||
echo "Unable to stop, will not attempt to start"
|
||||
exit 1
|
||||
fi
|
||||
$0 start
|
||||
;;
|
||||
status)
|
||||
if is_running; then
|
||||
echo "Running"
|
||||
else
|
||||
echo "Stopped"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart|status}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@@ -1,50 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name ADDRESS;
|
||||
rewrite ^ https://$server_name$request_uri? permanent;
|
||||
}
|
||||
|
||||
server {
|
||||
# SERVER BLOCK FOR ADDRESS
|
||||
listen 443 ssl;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_certificate /etc/letsencrypt/live/ADDRESS/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/ADDRESS/privkey.pem;
|
||||
|
||||
access_log /etc/nginx/logs/access-ADDRESS.log;
|
||||
error_log /etc/nginx/logs/error-ADDRESS.log info;
|
||||
root CUR_DIR;
|
||||
server_name ADDRESS;
|
||||
|
||||
# Media: images, icons, video, audio, HTC
|
||||
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
|
||||
expires 1M;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
# CSS and Javascript
|
||||
location ~* \.(?:css|js)$ {
|
||||
expires 1y;
|
||||
access_log off;
|
||||
add_header Cache-Control "public";
|
||||
}
|
||||
|
||||
location ^~ /static {
|
||||
try_files $uri $uri/ =404;
|
||||
}
|
||||
|
||||
location ~ ^/ {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-NginX-Proxy true;
|
||||
|
||||
proxy_pass https://127.0.0.1:PORT;
|
||||
proxy_redirect off;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
62
listify.go
Normal file
62
listify.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func reorderList(text string) ([]template.HTML, []string) {
|
||||
listItemsString := ""
|
||||
for _, lineString := range strings.Split(text, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if string(lineString[0]) != "-" {
|
||||
listItemsString += "- " + lineString + "\n"
|
||||
} else {
|
||||
listItemsString += lineString + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get ordering of template.HTML for rendering
|
||||
renderedListString := MarkdownToHtml(listItemsString)
|
||||
listItems := []template.HTML{}
|
||||
endItems := []template.HTML{}
|
||||
for _, lineString := range strings.Split(renderedListString, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if strings.Contains(lineString, "<del>") || strings.Contains(lineString, "</ul>") {
|
||||
endItems = append(endItems, template.HTML(lineString))
|
||||
} else {
|
||||
listItems = append(listItems, template.HTML(lineString))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get ordering of strings for deleting
|
||||
listItemsStringArray := []string{}
|
||||
endItemsStringArray := []string{}
|
||||
for _, lineString := range strings.Split(listItemsString, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if strings.Contains(lineString, "~~") {
|
||||
endItemsStringArray = append(endItemsStringArray, lineString)
|
||||
} else {
|
||||
listItemsStringArray = append(listItemsStringArray, lineString)
|
||||
}
|
||||
}
|
||||
}
|
||||
return append(listItems, endItems...), append(listItemsStringArray, endItemsStringArray...)
|
||||
}
|
||||
|
||||
func renderList(currentRawText string) []template.HTML {
|
||||
listItems, _ := reorderList(currentRawText)
|
||||
for i := range listItems {
|
||||
newHTML := strings.Replace(string(listItems[i]), "</a>", "</a>"+`<span id="`+strconv.Itoa(i)+`" class="deletable">`, -1)
|
||||
newHTML = strings.Replace(newHTML, "<a href=", "</span><a href=", -1)
|
||||
newHTML = strings.Replace(newHTML, "<li>", "<li>"+`<span id="`+strconv.Itoa(i)+`" class="deletable">`, -1)
|
||||
newHTML = strings.Replace(newHTML, "</li>", "</span></li>", -1)
|
||||
newHTML = strings.Replace(newHTML, "<li>"+`<span id="`+strconv.Itoa(i)+`" class="deletable"><del>`, "<li><del>"+`<span id="`+strconv.Itoa(i)+`" class="deletable">`, -1)
|
||||
newHTML = strings.Replace(newHTML, "</del></span></li>", "</span></del></li>", -1)
|
||||
listItems[i] = template.HTML([]byte(newHTML))
|
||||
}
|
||||
return listItems
|
||||
}
|
||||
173
main.go
Normal file → Executable file
173
main.go
Normal file → Executable file
@@ -1,122 +1,79 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
// AllowedIPs is a white/black list of
|
||||
// IP addresses allowed to access awwkoala
|
||||
var AllowedIPs = map[string]bool{
|
||||
"192.168.1.13": true,
|
||||
"192.168.1.12": true,
|
||||
"192.168.1.2": true,
|
||||
}
|
||||
|
||||
// RuntimeArgs contains all runtime
|
||||
// arguments available
|
||||
var RuntimeArgs struct {
|
||||
WikiName string
|
||||
ExternalIP string
|
||||
Port string
|
||||
DatabaseLocation string
|
||||
ServerCRT string
|
||||
ServerKey string
|
||||
SourcePath string
|
||||
AdminKey string
|
||||
Socket string
|
||||
}
|
||||
var VersionNum string
|
||||
var version string
|
||||
var pathToData string
|
||||
|
||||
func main() {
|
||||
VersionNum = "0.94"
|
||||
// _, executableFile, _, _ := runtime.Caller(0) // get full path of this file
|
||||
cwd, _ := os.Getwd()
|
||||
databaseFile := path.Join(cwd, "data.db")
|
||||
flag.StringVar(&RuntimeArgs.Port, "p", ":8003", "port to bind")
|
||||
flag.StringVar(&RuntimeArgs.DatabaseLocation, "db", databaseFile, "location of database file")
|
||||
flag.StringVar(&RuntimeArgs.AdminKey, "a", RandStringBytesMaskImprSrc(50), "key to access admin priveleges")
|
||||
flag.StringVar(&RuntimeArgs.ServerCRT, "crt", "", "location of ssl crt")
|
||||
flag.StringVar(&RuntimeArgs.ServerKey, "key", "", "location of ssl key")
|
||||
flag.StringVar(&RuntimeArgs.WikiName, "w", "AwwKoala", "custom name for wiki")
|
||||
dumpDataset := flag.Bool("dump", false, "flag to dump all data to 'dump' directory")
|
||||
flag.CommandLine.Usage = func() {
|
||||
fmt.Println(`AwwKoala (version ` + VersionNum + `): A Websocket Wiki and Kind Of A List Application
|
||||
run this to start the server and then visit localhost at the port you specify
|
||||
(see parameters).
|
||||
Example: 'awwkoala yourserver.com'
|
||||
Example: 'awwkoala -p :8080 localhost:8080'
|
||||
Example: 'awwkoala -db /var/lib/awwkoala/db.bolt localhost:8003'
|
||||
Example: 'awwkoala -p :8080 -crt ssl/server.crt -key ssl/server.key localhost:8080'
|
||||
Options:`)
|
||||
flag.CommandLine.PrintDefaults()
|
||||
}
|
||||
flag.Parse()
|
||||
|
||||
if *dumpDataset {
|
||||
fmt.Println("Dumping data to 'dump' folder...")
|
||||
dumpEverything()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
RuntimeArgs.ExternalIP = flag.Arg(0)
|
||||
if RuntimeArgs.ExternalIP == "" {
|
||||
RuntimeArgs.ExternalIP = GetLocalIP() + RuntimeArgs.Port
|
||||
}
|
||||
RuntimeArgs.SourcePath = cwd
|
||||
|
||||
// create programdata bucket
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
|
||||
err := db.Update(func(tx *bolt.Tx) error {
|
||||
_, err := tx.CreateBucketIfNotExists([]byte("programdata"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("create bucket: %s", err)
|
||||
app := cli.NewApp()
|
||||
app.Name = "cowyo"
|
||||
app.Usage = "a simple wiki"
|
||||
app.Version = version
|
||||
app.Compiled = time.Now()
|
||||
app.Action = func(c *cli.Context) error {
|
||||
if !c.GlobalBool("debug") {
|
||||
turnOffDebugger()
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
pathToData = c.GlobalString("data")
|
||||
os.MkdirAll(pathToData, 0755)
|
||||
fmt.Printf("\nRunning cowyo server (version %s) at http://%s:%s\n\n", version, GetLocalIP(), c.GlobalString("port"))
|
||||
serve(c.GlobalString("port"))
|
||||
return nil
|
||||
}
|
||||
Close()
|
||||
|
||||
// Default page
|
||||
aboutFile, _ := ioutil.ReadFile(path.Join(RuntimeArgs.SourcePath, "templates/aboutpage.md"))
|
||||
p := WikiData{"help", "", []string{}, []string{}, false, "zzz"}
|
||||
p.save(string(aboutFile))
|
||||
|
||||
// var q WikiData
|
||||
// q.load("about")
|
||||
// fmt.Println(getImportantVersions(q))
|
||||
|
||||
r := gin.Default()
|
||||
r.LoadHTMLGlob(path.Join(RuntimeArgs.SourcePath, "templates/*"))
|
||||
r.GET("/", newNote)
|
||||
r.HEAD("/", func(c *gin.Context) { c.Status(200) })
|
||||
r.GET("/:title", editNote)
|
||||
r.GET("/:title/*option", everythingElse)
|
||||
r.POST("/:title/*option", encryptionRoute)
|
||||
r.DELETE("/listitem", deleteListItem)
|
||||
r.DELETE("/deletepage", deletePage)
|
||||
if RuntimeArgs.ServerCRT != "" && RuntimeArgs.ServerKey != "" {
|
||||
RuntimeArgs.Socket = "wss"
|
||||
fmt.Println("--------------------------")
|
||||
fmt.Println("AwwKoala (version " + VersionNum + ") is up and running on https://" + RuntimeArgs.ExternalIP)
|
||||
fmt.Println("Admin key: " + RuntimeArgs.AdminKey)
|
||||
fmt.Println("--------------------------")
|
||||
r.RunTLS(RuntimeArgs.Port, RuntimeArgs.ServerCRT, RuntimeArgs.ServerKey)
|
||||
} else {
|
||||
RuntimeArgs.Socket = "ws"
|
||||
fmt.Println("--------------------------")
|
||||
fmt.Println("AwwKoala (version " + VersionNum + ") is up and running on http://" + RuntimeArgs.ExternalIP)
|
||||
fmt.Println("Admin key: " + RuntimeArgs.AdminKey)
|
||||
fmt.Println("--------------------------")
|
||||
r.Run(RuntimeArgs.Port)
|
||||
app.Flags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "data",
|
||||
Value: "data",
|
||||
Usage: "data folder to use",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "olddata",
|
||||
Value: "",
|
||||
Usage: "data folder for migrating",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "port,p",
|
||||
Value: "8050",
|
||||
Usage: "port to use",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "debug, d",
|
||||
Usage: "turn on debugging",
|
||||
},
|
||||
}
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "migrate",
|
||||
Aliases: []string{"m"},
|
||||
Usage: "migrate from the old cowyo",
|
||||
Action: func(c *cli.Context) error {
|
||||
if !c.GlobalBool("debug") {
|
||||
turnOffDebugger()
|
||||
}
|
||||
pathToData = c.GlobalString("data")
|
||||
pathToOldData := c.GlobalString("olddata")
|
||||
if len(pathToOldData) == 0 {
|
||||
fmt.Printf("You need to specify folder with -olddata")
|
||||
return nil
|
||||
}
|
||||
os.MkdirAll(pathToData, 0755)
|
||||
if !exists(pathToOldData) {
|
||||
fmt.Printf("Can not find '%s', does it exist?", pathToOldData)
|
||||
return nil
|
||||
}
|
||||
migrate(pathToOldData, pathToData)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
app.Run(os.Args)
|
||||
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
import os
|
||||
|
||||
"""DEFUNCT
|
||||
darwin arm
|
||||
darwin arm64
|
||||
dragonfly amd64
|
||||
freebsd 386
|
||||
freebsd amd64
|
||||
freebsd arm
|
||||
linux 386
|
||||
linux arm64
|
||||
linux ppc64le
|
||||
netbsd 386
|
||||
netbsd amd64
|
||||
netbsd arm
|
||||
openbsd 386
|
||||
openbsd amd64
|
||||
openbsd arm
|
||||
plan9 386
|
||||
plan9 amd64
|
||||
solaris amd64
|
||||
windows 386
|
||||
darwin 386
|
||||
darwin amd64
|
||||
linux arm
|
||||
linux ppc64
|
||||
windows amd64"""
|
||||
|
||||
arches = """linux amd64
|
||||
windows amd64
|
||||
linux arm
|
||||
darwin amd64"""
|
||||
|
||||
arches = arches.split("\n")
|
||||
version = "1.0"
|
||||
programName = "awwkoala"
|
||||
try:
|
||||
os.system("rm -rf builds")
|
||||
except:
|
||||
pass
|
||||
os.mkdir("builds")
|
||||
|
||||
for arch in arches:
|
||||
goos = arch.split()[0]
|
||||
goarch = arch.split()[1]
|
||||
exe = ""
|
||||
if "windows" in goos:
|
||||
exe = ".exe"
|
||||
cmd1 = 'env GOOS=%(goos)s GOARCH=%(goarch)s go build -o builds/%(programName)s%(exe)s' % {'goos':goos,'goarch':goarch,'exe':exe,'programName':programName}
|
||||
cmd2 = 'zip -r %(programName)s-%(version)s-%(goos)s-%(goarch)s.zip %(programName)s%(exe)s ../templates ../static' % {'goos':goos,'goarch':goarch,'exe':exe,'version':version,'programName':programName}
|
||||
print(cmd1)
|
||||
os.system(cmd1)
|
||||
os.chdir("builds")
|
||||
print(cmd2)
|
||||
os.system(cmd2)
|
||||
cmd3 = 'rm %(programName)s%(exe)s' % {'exe':exe,'programName':programName}
|
||||
print(cmd3)
|
||||
os.system(cmd3)
|
||||
os.chdir("../")
|
||||
25
migrate.go
Executable file
25
migrate.go
Executable file
@@ -0,0 +1,25 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path"
|
||||
)
|
||||
|
||||
func migrate(pathToOldData, pathToData string) error {
|
||||
files, _ := ioutil.ReadDir(pathToOldData)
|
||||
for _, f := range files {
|
||||
fmt.Printf("Migrating %s", f.Name())
|
||||
p := Open(f.Name())
|
||||
bData, err := ioutil.ReadFile(path.Join(pathToOldData, f.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = p.Update(string(bData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Save()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
96
page.go
Executable file
96
page.go
Executable file
@@ -0,0 +1,96 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/schollz/versionedtext"
|
||||
)
|
||||
|
||||
// Page is the basic struct
|
||||
type Page struct {
|
||||
Name string
|
||||
Text versionedtext.VersionedText
|
||||
RenderedPage string
|
||||
IsLocked bool
|
||||
PassphraseToUnlock string
|
||||
IsEncrypted bool
|
||||
IsPrimedForSelfDestruct bool
|
||||
}
|
||||
|
||||
func Open(name string) (p *Page) {
|
||||
p = new(Page)
|
||||
p.Name = name
|
||||
p.Text = versionedtext.NewVersionedText("")
|
||||
p.Render()
|
||||
bJSON, err := ioutil.ReadFile(path.Join(pathToData, encodeToBase32(strings.ToLower(name))+".json"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(bJSON, &p)
|
||||
if err != nil {
|
||||
p = new(Page)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func DirectoryList() (names []string, lengths []int, numchanges []int, lastEdited []string) {
|
||||
files, _ := ioutil.ReadDir(pathToData)
|
||||
names = make([]string, len(files))
|
||||
lengths = make([]int, len(files))
|
||||
numchanges = make([]int, len(files))
|
||||
lastEdited = make([]string, len(files))
|
||||
for i, f := range files {
|
||||
names[i] = DecodeFileName(f.Name())
|
||||
p := Open(names[i])
|
||||
lengths[i] = len(p.Text.GetCurrent())
|
||||
numchanges[i] = p.Text.NumEdits()
|
||||
lastEdited[i] = time.Unix(p.Text.LastEditTime()/1000000000, 0).Format("Mon Jan 2 15:04:05 MST 2006")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func DecodeFileName(s string) string {
|
||||
s2, _ := decodeFromBase32(strings.Split(s, ".")[0])
|
||||
return s2
|
||||
}
|
||||
|
||||
func (p *Page) Update(newText string) error {
|
||||
p.Text.Update(newText)
|
||||
p.Render()
|
||||
return p.Save()
|
||||
}
|
||||
|
||||
func (p *Page) Render() {
|
||||
if p.IsEncrypted {
|
||||
p.RenderedPage = "<code>" + p.Text.GetCurrent() + "</code>"
|
||||
return
|
||||
}
|
||||
|
||||
// Convert [[page]] to [page](/page/view)
|
||||
r, _ := regexp.Compile("\\[\\[(.*?)\\]\\]")
|
||||
currentText := p.Text.GetCurrent()
|
||||
for _, s := range r.FindAllString(currentText, -1) {
|
||||
currentText = strings.Replace(currentText, s, "["+s[2:len(s)-2]+"](/"+s[2:len(s)-2]+"/view)", 1)
|
||||
}
|
||||
p.Text.Update(currentText)
|
||||
p.RenderedPage = MarkdownToHtml(p.Text.GetCurrent())
|
||||
}
|
||||
|
||||
func (p *Page) Save() error {
|
||||
bJSON, err := json.MarshalIndent(p, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(path.Join(pathToData, encodeToBase32(strings.ToLower(p.Name))+".json"), bJSON, 0755)
|
||||
}
|
||||
|
||||
func (p *Page) Erase() error {
|
||||
log.Trace("Erasing " + p.Name)
|
||||
return os.Remove(path.Join(pathToData, encodeToBase32(strings.ToLower(p.Name))+".json"))
|
||||
}
|
||||
49
page_test.go
Executable file
49
page_test.go
Executable file
@@ -0,0 +1,49 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
// "fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestListFiles(t *testing.T) {
|
||||
pathToData = "testdata"
|
||||
os.MkdirAll(pathToData, 0755)
|
||||
defer os.RemoveAll(pathToData)
|
||||
p := Open("testpage")
|
||||
p.Update("Some data")
|
||||
p = Open("testpage2")
|
||||
p.Update("A different bunch of data")
|
||||
p = Open("testpage3")
|
||||
p.Update("Not much else")
|
||||
n, l := DirectoryList()
|
||||
if strings.Join(n, " ") != "testpage testpage2 testpage3" {
|
||||
t.Errorf("Names: %s, Lengths: %d", n, l)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneral(t *testing.T) {
|
||||
pathToData = "testdata"
|
||||
os.MkdirAll(pathToData, 0755)
|
||||
defer os.RemoveAll(pathToData)
|
||||
p := Open("testpage")
|
||||
err := p.Update("**bold**")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if strings.TrimSpace(p.RenderedPage) != "<p><strong>bold</strong></p>" {
|
||||
t.Errorf("Did not render: '%s'", p.RenderedPage)
|
||||
}
|
||||
err = p.Update("**bold** and *italic*")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
p.Save()
|
||||
|
||||
p2 := Open("testpage")
|
||||
if strings.TrimSpace(p2.RenderedPage) != "<p><strong>bold</strong> and <em>italic</em></p>" {
|
||||
t.Errorf("Did not render: '%s'", p2.RenderedPage)
|
||||
}
|
||||
|
||||
}
|
||||
485
routes.go
485
routes.go
@@ -1,485 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
"github.com/russross/blackfriday"
|
||||
)
|
||||
|
||||
type EncryptionPost struct {
|
||||
Text string `form:"text" json:"text" binding:"required"`
|
||||
Password string `form:"password" json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
func encryptionRoute(c *gin.Context) {
|
||||
title := c.Param("title")
|
||||
option := c.Param("option")
|
||||
fmt.Println(option, title)
|
||||
var jsonLoad EncryptionPost
|
||||
if option == "/decrypt" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
var err error
|
||||
currentText, _, _, _, encrypted, _ := getCurrentText(title, -1)
|
||||
if encrypted == true {
|
||||
currentText, err = decryptString(currentText, jsonLoad.Password)
|
||||
if err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Inorrect passphrase.",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
} else {
|
||||
p := WikiData{strings.ToLower(title), "", []string{}, []string{}, false, ""}
|
||||
p.save(currentText)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Could not bind",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
if option == "/encrypt" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
p := WikiData{strings.ToLower(title), "", []string{}, []string{}, true, ""}
|
||||
p.save(encryptString(jsonLoad.Text, jsonLoad.Password))
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
if option == "/lock" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
var p WikiData
|
||||
err := p.load(strings.ToLower(title))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
p.Locked = jsonLoad.Password
|
||||
p.save(p.CurrentText)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
if option == "/unlock" {
|
||||
if c.BindJSON(&jsonLoad) == nil {
|
||||
var p WikiData
|
||||
err := p.load(strings.ToLower(title))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(p.Locked) > 0 && p.Locked == jsonLoad.Password {
|
||||
p.Locked = ""
|
||||
p.save(p.CurrentText)
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Unlocked!",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": true,
|
||||
})
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "Incorrect password!",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
c.JSON(200, gin.H{
|
||||
"status": "posted",
|
||||
"title": title,
|
||||
"option": option,
|
||||
"success": false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func newNote(c *gin.Context) {
|
||||
title := randomAlliterateCombo()
|
||||
c.Redirect(302, "/"+title)
|
||||
}
|
||||
|
||||
func editNote(c *gin.Context) {
|
||||
title := c.Param("title")
|
||||
if title == "ws" {
|
||||
wshandler(c.Writer, c.Request)
|
||||
} else if title == "robots.txt" {
|
||||
c.Data(200, "text/plain", []byte(robotsTxt))
|
||||
} else if strings.ToLower(title) == "help" { //}&& strings.Contains(AllowedIPs, c.ClientIP()) != true {
|
||||
c.Redirect(302, "/Help/view")
|
||||
} else {
|
||||
locked, _ := hasPassword(title)
|
||||
if locked {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
} else {
|
||||
version := c.DefaultQuery("version", "-1")
|
||||
versionNum, _ := strconv.Atoi(version)
|
||||
currentText, versions, currentVersion, totalTime, encrypted, locked := getCurrentText(title, versionNum)
|
||||
if encrypted || len(locked) > 0 {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
}
|
||||
if strings.Contains(currentText, "self-destruct\n") || strings.Contains(currentText, "\nself-destruct") {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
}
|
||||
numRows := len(strings.Split(currentText, "\n")) + 10
|
||||
totalTimeString := totalTime.String()
|
||||
if totalTime.Seconds() < 1 {
|
||||
totalTimeString = "< 1 s"
|
||||
}
|
||||
c.HTML(http.StatusOK, "index.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"ExternalIP": RuntimeArgs.ExternalIP,
|
||||
"CurrentText": currentText,
|
||||
"NumRows": numRows,
|
||||
"Versions": versions,
|
||||
"TotalTime": totalTimeString,
|
||||
"SocketType": RuntimeArgs.Socket,
|
||||
"NoEdit": !currentVersion,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func everythingElse(c *gin.Context) {
|
||||
option := c.Param("option")
|
||||
title := c.Param("title")
|
||||
if option == "/view" {
|
||||
version := c.DefaultQuery("version", "-1")
|
||||
noprompt := c.DefaultQuery("noprompt", "-1")
|
||||
versionNum, _ := strconv.Atoi(version)
|
||||
if strings.ToLower(title) == "help" {
|
||||
versionNum = -1
|
||||
}
|
||||
currentText, versions, _, totalTime, encrypted, locked := getCurrentText(title, versionNum)
|
||||
if (strings.Contains(currentText, "self-destruct\n") || strings.Contains(currentText, "\nself-destruct")) && strings.ToLower(title) != "help" {
|
||||
currentText = strings.Replace(currentText, "self-destruct\n", `> *This page has been deleted, you cannot return after closing.*`+"\n", 1)
|
||||
currentText = strings.Replace(currentText, "\nself-destruct", "\n"+`> *This page has been deleted, you cannot return after closing.*`, 1)
|
||||
p := WikiData{strings.ToLower(title), "", []string{}, []string{}, false, ""}
|
||||
p.save("")
|
||||
}
|
||||
renderMarkdown(c, currentText, title, versions, "", totalTime, encrypted, noprompt == "-1", len(locked) > 0)
|
||||
} else if title == "ls" && option == "/"+RuntimeArgs.AdminKey && len(RuntimeArgs.AdminKey) > 1 {
|
||||
renderMarkdown(c, listEverything(), "ls", nil, RuntimeArgs.AdminKey, time.Now().Sub(time.Now()), false, false, false)
|
||||
} else if option == "/list" {
|
||||
renderList(c, title)
|
||||
} else if title == "static" {
|
||||
serveStaticFile(c, option)
|
||||
} else {
|
||||
c.Redirect(302, "/"+title)
|
||||
}
|
||||
}
|
||||
|
||||
func serveStaticFile(c *gin.Context, option string) {
|
||||
staticFile, err := ioutil.ReadFile(path.Join(RuntimeArgs.SourcePath, "static") + option)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(404)
|
||||
} else {
|
||||
c.Data(200, contentType(option), []byte(staticFile))
|
||||
}
|
||||
}
|
||||
|
||||
func renderMarkdown(c *gin.Context, currentText string, title string, versions []versionsInfo, AdminKey string, totalTime time.Duration, encrypted bool, noprompt bool, locked bool) {
|
||||
r, _ := regexp.Compile("\\[\\[(.*?)\\]\\]")
|
||||
for _, s := range r.FindAllString(currentText, -1) {
|
||||
currentText = strings.Replace(currentText, s, "["+s[2:len(s)-2]+"](/"+s[2:len(s)-2]+"/view)", 1)
|
||||
}
|
||||
unsafe := blackfriday.MarkdownCommon([]byte(currentText))
|
||||
pClean := bluemonday.UGCPolicy()
|
||||
pClean.AllowElements("img")
|
||||
pClean.AllowAttrs("alt").OnElements("img")
|
||||
pClean.AllowAttrs("src").OnElements("img")
|
||||
pClean.AllowAttrs("class").OnElements("a")
|
||||
pClean.AllowAttrs("href").OnElements("a")
|
||||
pClean.AllowAttrs("id").OnElements("a")
|
||||
pClean.AllowDataURIImages()
|
||||
html := pClean.SanitizeBytes(unsafe)
|
||||
html2 := string(html)
|
||||
r, _ = regexp.Compile("\\$\\$(.*?)\\$\\$")
|
||||
for _, s := range r.FindAllString(html2, -1) {
|
||||
html2 = strings.Replace(html2, s, "<span class='texp' data-expr='"+s[2:len(s)-2]+"'></span>", 1)
|
||||
}
|
||||
r, _ = regexp.Compile("\\$(.*?)\\$")
|
||||
for _, s := range r.FindAllString(html2, -1) {
|
||||
html2 = strings.Replace(html2, s, "<span class='texi' data-expr='"+s[1:len(s)-1]+"'></span>", 1)
|
||||
}
|
||||
|
||||
html2 = strings.Replace(html2, "&#36;", "$", -1)
|
||||
html2 = strings.Replace(html2, "&#91;", "[", -1)
|
||||
html2 = strings.Replace(html2, "&#93;", "]", -1)
|
||||
html2 = strings.Replace(html2, "&35;", "#", -1)
|
||||
totalTimeString := totalTime.String()
|
||||
if totalTime.Seconds() < 1 {
|
||||
totalTimeString = "< 1 s"
|
||||
}
|
||||
c.HTML(http.StatusOK, "view.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"Body": template.HTML([]byte(html2)),
|
||||
"Versions": versions,
|
||||
"TotalTime": totalTimeString,
|
||||
"AdminKey": AdminKey,
|
||||
"Encrypted": encrypted,
|
||||
"Locked": locked,
|
||||
"Prompt": noprompt,
|
||||
"LockedOrEncrypted": locked || encrypted,
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func reorderList(text string) ([]template.HTML, []string) {
|
||||
listItemsString := ""
|
||||
for _, lineString := range strings.Split(text, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if string(lineString[0]) != "-" {
|
||||
listItemsString += "- " + lineString + "\n"
|
||||
} else {
|
||||
listItemsString += lineString + "\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get ordering of template.HTML for rendering
|
||||
renderedListString := string(blackfriday.MarkdownCommon([]byte(listItemsString)))
|
||||
listItems := []template.HTML{}
|
||||
endItems := []template.HTML{}
|
||||
for _, lineString := range strings.Split(renderedListString, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if strings.Contains(lineString, "<del>") || strings.Contains(lineString, "</ul>") {
|
||||
endItems = append(endItems, template.HTML(lineString))
|
||||
} else {
|
||||
listItems = append(listItems, template.HTML(lineString))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get ordering of strings for deleting
|
||||
listItemsStringArray := []string{}
|
||||
endItemsStringArray := []string{}
|
||||
for _, lineString := range strings.Split(listItemsString, "\n") {
|
||||
if len(lineString) > 1 {
|
||||
if strings.Contains(lineString, "~~") {
|
||||
endItemsStringArray = append(endItemsStringArray, lineString)
|
||||
} else {
|
||||
listItemsStringArray = append(listItemsStringArray, lineString)
|
||||
}
|
||||
}
|
||||
}
|
||||
return append(listItems, endItems...), append(listItemsStringArray, endItemsStringArray...)
|
||||
}
|
||||
|
||||
func renderList(c *gin.Context, title string) {
|
||||
if strings.ToLower(title) == "help" { //}&& strings.Contains(AllowedIPs, c.ClientIP()) != true {
|
||||
c.Redirect(302, "/Help/view")
|
||||
}
|
||||
var p WikiData
|
||||
err := p.load(strings.ToLower(title))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
currentText := p.CurrentText
|
||||
if strings.Contains(currentText, "self-destruct\n") || strings.Contains(currentText, "\nself-destruct") {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
}
|
||||
if p.Encrypted || len(p.Locked) > 0 {
|
||||
c.Redirect(302, "/"+title+"/view")
|
||||
}
|
||||
|
||||
pClean := bluemonday.UGCPolicy()
|
||||
pClean.AllowElements("img")
|
||||
pClean.AllowAttrs("alt").OnElements("img")
|
||||
pClean.AllowAttrs("src").OnElements("img")
|
||||
pClean.AllowAttrs("class").OnElements("a")
|
||||
pClean.AllowAttrs("href").OnElements("a")
|
||||
pClean.AllowAttrs("id").OnElements("a")
|
||||
pClean.AllowDataURIImages()
|
||||
text := pClean.SanitizeBytes([]byte(p.CurrentText))
|
||||
listItems, _ := reorderList(string(text))
|
||||
fmt.Println(string(text))
|
||||
c.HTML(http.StatusOK, "list.tmpl", gin.H{
|
||||
"Title": title,
|
||||
"WikiName": RuntimeArgs.WikiName,
|
||||
"ListItems": listItems,
|
||||
})
|
||||
}
|
||||
|
||||
func deleteListItem(c *gin.Context) {
|
||||
lineNum, err := strconv.Atoi(c.DefaultQuery("lineNum", "None"))
|
||||
title := c.Query("title") // shortcut for c.Request.URL.Query().Get("lastname")
|
||||
if err == nil {
|
||||
var p WikiData
|
||||
err := p.load(strings.ToLower(title))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, listItems := reorderList(p.CurrentText)
|
||||
newText := p.CurrentText
|
||||
for i, lineString := range listItems {
|
||||
// fmt.Println(i, lineString, lineNum)
|
||||
if i+1 == lineNum {
|
||||
// fmt.Println("MATCHED")
|
||||
if strings.Contains(lineString, "~~") == false {
|
||||
// fmt.Println(p.Text, "("+lineString[2:]+"\n"+")", "~~"+lineString[2:]+"~~"+"\n")
|
||||
newText = strings.Replace(newText+"\n", lineString[2:]+"\n", "~~"+strings.TrimSpace(lineString[2:])+"~~"+"\n", 1)
|
||||
} else {
|
||||
newText = strings.Replace(newText+"\n", lineString[2:]+"\n", lineString[4:len(lineString)-2]+"\n", 1)
|
||||
}
|
||||
p.save(newText)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"message": "Done.",
|
||||
})
|
||||
} else {
|
||||
c.JSON(404, gin.H{
|
||||
"message": "?",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func deletePage(c *gin.Context) {
|
||||
deleteName := c.DefaultQuery("DeleteName", "None")
|
||||
adminKey := c.DefaultQuery("AdminKey", "None")
|
||||
fmt.Println(adminKey)
|
||||
fmt.Println(deleteName)
|
||||
// if adminKey == RuntimeArgs.AdminKey || true == true {
|
||||
if strings.ToLower(deleteName) != "help" {
|
||||
p := WikiData{strings.ToLower(deleteName), "", []string{}, []string{}, false, ""}
|
||||
p.save("")
|
||||
}
|
||||
// // remove from program data
|
||||
// var deleteKey []byte
|
||||
// foundKey := false
|
||||
// err := db.View(func(tx *bolt.Tx) error {
|
||||
// b := tx.Bucket([]byte("programdata"))
|
||||
// c := b.Cursor()
|
||||
// for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
// if strings.ToLower(string(v)) == strings.ToLower(deleteName) {
|
||||
// fmt.Println("FOUND " + string(v))
|
||||
// deleteKey = k
|
||||
// foundKey = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// return nil
|
||||
// })
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// if foundKey == true {
|
||||
// fmt.Println(len([]string{}))
|
||||
// fmt.Println(deleteKey)
|
||||
// db.View(func(tx *bolt.Tx) error {
|
||||
// b := tx.Bucket([]byte("programdata"))
|
||||
// err := b.Delete(deleteKey)
|
||||
// return err
|
||||
// })
|
||||
// }
|
||||
|
||||
// return OKAY
|
||||
c.JSON(200, gin.H{
|
||||
"message": "Done.",
|
||||
})
|
||||
// } else {
|
||||
// c.JSON(404, gin.H{
|
||||
// "message": "?",
|
||||
// })
|
||||
// }
|
||||
}
|
||||
|
||||
func listEverything() string {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
everything := `| Title | Current size | Changes | Total Size | |
|
||||
| --------- |-------------| -----| ------------- | ------------- |
|
||||
`
|
||||
db.View(func(tx *bolt.Tx) error {
|
||||
// Assume bucket exists and has keys
|
||||
b := tx.Bucket([]byte("datas"))
|
||||
c := b.Cursor()
|
||||
for k, v := c.First(); k != nil; k, v = c.Next() {
|
||||
var p WikiData
|
||||
p.load(string(k))
|
||||
if len(p.CurrentText) > 1 {
|
||||
contentSize := strconv.Itoa(len(p.CurrentText))
|
||||
numChanges := strconv.Itoa(len(p.Diffs))
|
||||
totalSize := strconv.Itoa(len(v))
|
||||
everything += "| [" + p.Title + "](/" + p.Title + "/view) | " + contentSize + " | " + numChanges + " | " + totalSize + ` | <a class="deleteable" id="` + p.Title + `">Delete</a> | ` + "\n"
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return everything
|
||||
}
|
||||
|
||||
func dumpEverything() {
|
||||
Open(RuntimeArgs.DatabaseLocation)
|
||||
defer Close()
|
||||
err := os.MkdirAll("dump", 0777)
|
||||
if err != nil {
|
||||
fmt.Println("Already exists")
|
||||
}
|
||||
db.View(func(tx *bolt.Tx) error {
|
||||
// Assume bucket exists and has keys
|
||||
b := tx.Bucket([]byte("datas"))
|
||||
c := b.Cursor()
|
||||
for k, _ := c.First(); k != nil; k, _ = c.Next() {
|
||||
var p WikiData
|
||||
p.load(string(k))
|
||||
fmt.Println(string(k), len(p.CurrentText))
|
||||
ioutil.WriteFile(path.Join("dump", string(k)+".md"), []byte(p.CurrentText), 0644)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
11
static/css/base-min.css
vendored
Normal file
11
static/css/base-min.css
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
/*!
|
||||
Pure v0.6.2
|
||||
Copyright 2013 Yahoo!
|
||||
Licensed under the BSD License.
|
||||
https://github.com/yahoo/pure/blob/master/LICENSE.md
|
||||
*/
|
||||
/*!
|
||||
normalize.css v^3.0 | MIT License | git.io/normalize
|
||||
Copyright (c) Nicolas Gallagher and Jonathan Neal
|
||||
*/
|
||||
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */img,legend{border:0}legend,td,th{padding:0}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,optgroup,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre,textarea{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}table{border-collapse:collapse;border-spacing:0}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}
|
||||
6584
static/css/bootstrap.css
vendored
6584
static/css/bootstrap.css
vendored
File diff suppressed because it is too large
Load Diff
5
static/css/bootstrap.min.css
vendored
5
static/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user