Compare commits

..

No commits in common. "83b4f5fe041d11caca936b12ad1ca5540dfc3f19" and "6567a7c0cd3fbe3f28da8a383dfe2bce4b983c1b" have entirely different histories.

11 changed files with 118 additions and 134 deletions

6
.idea/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

8
.idea/modules.xml Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/plakken.iml" filepath="$PROJECT_DIR$/.idea/plakken.iml" />
</modules>
</component>
</project>

10
.idea/plakken.iml Normal file
View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="highlight.js" level="application" />
</component>
</module>

6
.idea/vcs.xml Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View file

@ -7,7 +7,7 @@ import (
"strconv" "strconv"
) )
type Config struct { type config struct {
host string host string
port string port string
redisAddr string redisAddr string
@ -17,7 +17,7 @@ type Config struct {
urlLength int urlLength int
} }
func GetConfig() Config { func getConfig() config {
err := godotenv.Load() err := godotenv.Load()
if err != nil { if err != nil {
log.Fatalf("Error loading .env file: %v", err) log.Fatalf("Error loading .env file: %v", err)
@ -42,7 +42,7 @@ func GetConfig() Config {
log.Fatal("Invalid PLAKKEN_REDIS_URL_LEN") log.Fatal("Invalid PLAKKEN_REDIS_URL_LEN")
} }
return Config{ conf := config{
host: os.Getenv("PLAKKEN_INTERFACE"), host: os.Getenv("PLAKKEN_INTERFACE"),
port: port, port: port,
redisAddr: redisAddr, redisAddr: redisAddr,
@ -51,4 +51,6 @@ func GetConfig() Config {
redisDB: redisDB, redisDB: redisDB,
urlLength: urlLen, urlLength: urlLen,
} }
return conf
} }

5
db.go
View file

@ -9,7 +9,7 @@ import (
var ctx = context.Background() var ctx = context.Background()
func ConnectDB() *redis.Client { func connectDB() *redis.Client {
localDb := redis.NewClient(&redis.Options{ localDb := redis.NewClient(&redis.Options{
Addr: currentConfig.redisAddr, Addr: currentConfig.redisAddr,
Username: currentConfig.redisUser, Username: currentConfig.redisUser,
@ -40,7 +40,8 @@ func insertPaste(key string, content string, secret string, ttl time.Duration) {
} }
func getContent(key string) string { func getContent(key string) string {
return db.HGet(ctx, key, "content").Val() content := db.HGet(ctx, key, "content").Val()
return content
} }
func deleteContent(key string) { func deleteContent(key string) {

31
main.go
View file

@ -10,7 +10,7 @@ import (
"strings" "strings"
) )
var currentConfig Config var currentConfig config
var db *redis.Client var db *redis.Client
type pasteView struct { type pasteView struct {
@ -21,17 +21,16 @@ type pasteView struct {
func handleRequest(w http.ResponseWriter, r *http.Request) { func handleRequest(w http.ResponseWriter, r *http.Request) {
path := r.URL.Path path := r.URL.Path
clearPath := strings.ReplaceAll(r.URL.Path, "/raw", "") clearPath := strings.ReplaceAll(r.URL.Path, "/raw", "")
staticResource := "/static/"
switch r.Method { switch r.Method {
case "GET": case "GET":
if path == "/" { if path == "/" {
http.ServeFile(w, r, "./static/index.html") http.ServeFile(w, r, "./static/index.html")
} else if strings.HasPrefix(path, staticResource) { } else if strings.HasPrefix(path, "/static/") {
fs := http.FileServer(http.Dir("./static")) fs := http.FileServer(http.Dir("./static"))
http.Handle(staticResource, http.StripPrefix(staticResource, fs)) http.Handle("/static/", http.StripPrefix("/static/", fs))
} else { } else {
if UrlExist(clearPath) { if urlExist(clearPath) {
if strings.HasSuffix(path, "/raw") { if strings.HasSuffix(path, "/raw") {
pasteContent := getContent(clearPath) pasteContent := getContent(clearPath)
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
@ -57,8 +56,8 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
} }
case "POST": case "POST":
if path == "/" { if path == "/" {
secret := GenerateSecret() secret := generateSecret()
url := "/" + GenerateUrl() url := "/" + generateUrl()
content := r.FormValue("content") content := r.FormValue("content")
insertPaste(url, content, secret, -1) insertPaste(url, content, secret, -1)
http.Redirect(w, r, url, http.StatusSeeOther) http.Redirect(w, r, url, http.StatusSeeOther)
@ -66,13 +65,12 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
} }
case "DELETE": case "DELETE":
if UrlExist(path) { if strings.HasPrefix(path, "/delete") {
urlItem := strings.Split(path, "/")
if urlExist("/" + urlItem[2]) {
secret := r.URL.Query().Get("secret") secret := r.URL.Query().Get("secret")
if secret == db.HGet(ctx, path, "secret").Val() { if verifySecret("/"+urlItem[2], secret) {
err := db.Del(ctx, path) deleteContent("/" + urlItem[2])
if err != nil {
log.Println(err)
}
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} else { } else {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
@ -80,12 +78,15 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
} else { } else {
w.WriteHeader(http.StatusNotFound) w.WriteHeader(http.StatusNotFound)
} }
} else {
w.WriteHeader(http.StatusMethodNotAllowed)
}
} }
} }
func main() { func main() {
db = ConnectDB() db = connectDB()
currentConfig = GetConfig() currentConfig = getConfig()
listen := currentConfig.host + ":" + currentConfig.port listen := currentConfig.host + ":" + currentConfig.port
http.HandleFunc("/", handleRequest) http.HandleFunc("/", handleRequest)

View file

@ -1,31 +0,0 @@
const codeEditor = document.getElementById('content');
const lineCounter = document.getElementById('lines');
let lineCountCache = 0;
// Update line counter
function updateLineCounter() {
const lineCount = codeEditor.value.split('\n').length;
if (lineCountCache !== lineCount) {
const outarr = Array.from({length: lineCount}, (_, index) => index + 1);
lineCounter.value = outarr.join('\n');
}
lineCountCache = lineCount;
}
codeEditor.addEventListener('input', updateLineCounter);
codeEditor.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
e.preventDefault();
const {value, selectionStart, selectionEnd} = codeEditor;
codeEditor.value = `${value.slice(0, selectionStart)}\t${value.slice(selectionEnd)}`;
codeEditor.setSelectionRange(selectionStart + 1, selectionStart + 1);
updateLineCounter();
}
});
updateLineCounter();

View file

@ -9,15 +9,9 @@
<meta content="width=device-width, initial-scale=1.0" name="viewport"> <meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>New plak • Plakken</title> <title>New plak • Plakken</title>
<link href="/static/style.css" rel="stylesheet"> <link href="/static/style.css" rel="stylesheet">
<script async src="/static/app.js"></script>
</head> </head>
<body> <body>
<form method="post"> <form method="post">
<div>
<label for="lines"></label>
<textarea id="lines" readonly wrap="hard">1</textarea>
</div>
<div>
<label for="content"></label> <label for="content"></label>
<textarea autofocus id="content" name="content" placeholder="Your paste here"></textarea> <textarea autofocus id="content" name="content" placeholder="Your paste here"></textarea>
<nav> <nav>
@ -52,14 +46,13 @@
</select> </select>
</li> </li>
</ul> </ul>
<button title="Save plak" type="submit"> <button type="submit" title="Save plak">
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<polyline points="9 11 12 14 22 4"></polyline> <polyline points="9 11 12 14 22 4"></polyline>
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path> <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
</svg> </svg>
</button> </button>
</nav> </nav>
</div>
</form> </form>
</body> </body>
</html> </html>

View file

@ -2,7 +2,7 @@
--accent: #be0560; --accent: #be0560;
--background: #141414; --background: #141414;
--border: #333; --border: #333;
--text: #e9e9e9; --text: #e2e2e2;
} }
body { body {
@ -12,35 +12,19 @@ body {
margin: 0; margin: 0;
} }
form {
display: flex;
flex-flow: row wrap;
}
button, textarea { button, textarea {
background-color: inherit; background-color: inherit;
border: none; border: none;
outline: none;
resize: none;
} }
#lines { textarea {
color: #999;
font: 400 14px/1.6 "JetBrains Mono", monospace;
height: calc(100vh - 3rem);
overflow-y: hidden;
padding: 10px 0;
text-align: center;
user-select: none;
width: 26px;
}
#content {
color: var(--text); color: var(--text);
font: 400 14px/1.6 "JetBrains Mono", monospace; font: 14px/1.6 "JetBrains Mono", monospace;
height: calc(100vh - 3rem); height: calc(100vh - 3rem);
padding: 10px 10px 10px 6px; outline: none;
width: calc(100vw - 42px); padding: 1rem;
resize: none;
width: calc(100vw - 2rem);
} }
pre { pre {
@ -59,7 +43,7 @@ nav {
ul { ul {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
gap: 36px; gap: 2.6rem;
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0 1.9rem; padding: 0 1.9rem;
@ -76,7 +60,7 @@ input, select {
color: var(--text); color: var(--text);
font-size: 15px; font-size: 15px;
outline: none; outline: none;
padding: 6px 8px; padding: 5px 6px;
transition: border .15s ease; transition: border .15s ease;
} }
@ -102,7 +86,7 @@ input:focus-visible, select:focus-visible {
border: 2px solid var(--text); border: 2px solid var(--text);
} }
button:focus-visible { button:focus-visible{
outline: none; outline: none;
} }

View file

@ -7,7 +7,7 @@ import (
mathrand "math/rand" mathrand "math/rand"
) )
func GenerateUrl() string { func generateUrl() string {
listChars := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") listChars := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
b := make([]rune, currentConfig.urlLength) b := make([]rune, currentConfig.urlLength)
for i := range b { for i := range b {
@ -17,7 +17,7 @@ func GenerateUrl() string {
return string(b) return string(b)
} }
func GenerateSecret() string { func generateSecret() string {
key := make([]byte, 32) key := make([]byte, 32)
_, err := rand.Read(key) _, err := rand.Read(key)
if err != nil { if err != nil {
@ -27,10 +27,14 @@ func GenerateSecret() string {
return hex.EncodeToString(key) return hex.EncodeToString(key)
} }
func UrlExist(url string) bool { func urlExist(url string) bool {
return db.Exists(ctx, url).Val() == 1 exist := db.Exists(ctx, url).Val()
return exist == 1
} }
func VerifySecret(url string, secret string) bool { func verifySecret(url string, secret string) bool {
return secret == db.HGet(ctx, url, "secret").Val() if secret == db.HGet(ctx, url, "secret").Val() {
return true
}
return false
} }