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) {

37
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,26 +65,28 @@ 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") {
secret := r.URL.Query().Get("secret") urlItem := strings.Split(path, "/")
if secret == db.HGet(ctx, path, "secret").Val() { if urlExist("/" + urlItem[2]) {
err := db.Del(ctx, path) secret := r.URL.Query().Get("secret")
if err != nil { if verifySecret("/"+urlItem[2], secret) {
log.Println(err) deleteContent("/" + urlItem[2])
w.WriteHeader(http.StatusNoContent)
} else {
w.WriteHeader(http.StatusForbidden)
} }
w.WriteHeader(http.StatusNoContent)
} else { } else {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusNotFound)
} }
} else { } else {
w.WriteHeader(http.StatusNotFound) 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,57 +9,50 @@
<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="content"></label>
<label for="lines"></label> <textarea autofocus id="content" name="content" placeholder="Your paste here"></textarea>
<textarea id="lines" readonly wrap="hard">1</textarea> <nav>
</div> <ul>
<div> <li>
<label for="content"></label> <label for="password">Password?</label>
<textarea autofocus id="content" name="content" placeholder="Your paste here"></textarea> <input id="password" type="text">
<nav> </li>
<ul> <li><label for="exp">Expiration?</label>
<li> <input id="exp" type="date"></li>
<label for="password">Password?</label> <li>
<input id="password" type="text"> <label for="type">Type</label>
</li> <select id="type" name="type">
<li><label for="exp">Expiration?</label> <option value="auto">Auto Detect</option>
<input id="exp" type="date"></li> <option value="c">C</option>
<li> <option value="cpp">C++</option>
<label for="type">Type</label> <option value="csharp">C#</option>
<select id="type" name="type"> <option value="css">CSS</option>
<option value="auto">Auto Detect</option> <option value="go">Go</option>
<option value="c">C</option> <option value="java">Java</option>
<option value="cpp">C++</option> <option value="js">Javascript</option>
<option value="csharp">C#</option> <option value="html">HTML</option>
<option value="css">CSS</option> <option selected value="plain">Plaintext</option>
<option value="go">Go</option> <option value="python">Python</option>
<option value="java">Java</option> <option value="rb">Ruby</option>
<option value="js">Javascript</option> <option value="rs">Rust</option>
<option value="html">HTML</option> <option value="sh">Shell</option>
<option selected value="plain">Plaintext</option> <option value="sql">SQL</option>
<option value="python">Python</option> <option value="ts">Typescript</option>
<option value="rb">Ruby</option> <option value="xml">XML</option>
<option value="rs">Rust</option> <option value="yml">YAML</option>
<option value="sh">Shell</option> </select>
<option value="sql">SQL</option> </li>
<option value="ts">Typescript</option> </ul>
<option value="xml">XML</option> <button type="submit" title="Save plak">
<option value="yml">YAML</option> <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
</select> <polyline points="9 11 12 14 22 4"></polyline>
</li> <path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
</ul> </svg>
<button title="Save plak" type="submit"> </button>
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> </nav>
<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>
</svg>
</button>
</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
} }