Implement basic POST request for curl #30

Closed
ada wants to merge 36 commits from ada/curl-upload into main
11 changed files with 135 additions and 119 deletions
Showing only changes of commit 83b4f5fe04 - Show all commits

6
.idea/.gitignore vendored
View file

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

View file

@ -1,8 +0,0 @@
<?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>

View file

@ -1,10 +0,0 @@
<?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>

View file

@ -1,6 +0,0 @@
<?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")
} }
conf := config{ return Config{
host: os.Getenv("PLAKKEN_INTERFACE"), host: os.Getenv("PLAKKEN_INTERFACE"),
port: port, port: port,
redisAddr: redisAddr, redisAddr: redisAddr,
@ -51,6 +51,4 @@ 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,8 +40,7 @@ func insertPaste(key string, content string, secret string, ttl time.Duration) {
} }
func getContent(key string) string { func getContent(key string) string {
content := db.HGet(ctx, key, "content").Val() return 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,16 +21,17 @@ 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, "/static/") { } else if strings.HasPrefix(path, staticResource) {
fs := http.FileServer(http.Dir("./static")) fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs)) http.Handle(staticResource, http.StripPrefix(staticResource, 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")
@ -56,8 +57,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)
@ -65,12 +66,13 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
} }
case "DELETE": case "DELETE":
if strings.HasPrefix(path, "/delete") { if UrlExist(path) {
urlItem := strings.Split(path, "/")
if urlExist("/" + urlItem[2]) {
secret := r.URL.Query().Get("secret") secret := r.URL.Query().Get("secret")
if verifySecret("/"+urlItem[2], secret) { if secret == db.HGet(ctx, path, "secret").Val() {
deleteContent("/" + urlItem[2]) err := db.Del(ctx, path)
if err != nil {
log.Println(err)
}
w.WriteHeader(http.StatusNoContent) w.WriteHeader(http.StatusNoContent)
} else { } else {
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
@ -78,15 +80,12 @@ 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

@ -0,0 +1,31 @@
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,9 +9,15 @@
<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>
@ -46,13 +52,14 @@
</select> </select>
</li> </li>
</ul> </ul>
<button type="submit" title="Save plak"> <button title="Save plak" type="submit">
<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: #e2e2e2; --text: #e9e9e9;
} }
body { body {
@ -12,19 +12,35 @@ 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;
} }
textarea { #lines {
color: var(--text); color: #999;
font: 14px/1.6 "JetBrains Mono", monospace; font: 400 14px/1.6 "JetBrains Mono", monospace;
height: calc(100vh - 3rem); height: calc(100vh - 3rem);
outline: none; overflow-y: hidden;
padding: 1rem; padding: 10px 0;
resize: none; text-align: center;
width: calc(100vw - 2rem); user-select: none;
width: 26px;
}
#content {
color: var(--text);
font: 400 14px/1.6 "JetBrains Mono", monospace;
height: calc(100vh - 3rem);
padding: 10px 10px 10px 6px;
width: calc(100vw - 42px);
} }
pre { pre {
@ -43,7 +59,7 @@ nav {
ul { ul {
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
gap: 2.6rem; gap: 36px;
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0 1.9rem; padding: 0 1.9rem;
@ -60,7 +76,7 @@ input, select {
color: var(--text); color: var(--text);
font-size: 15px; font-size: 15px;
outline: none; outline: none;
padding: 5px 6px; padding: 6px 8px;
transition: border .15s ease; transition: border .15s ease;
} }
@ -86,7 +102,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,14 +27,10 @@ func generateSecret() string {
return hex.EncodeToString(key) return hex.EncodeToString(key)
} }
func urlExist(url string) bool { func UrlExist(url string) bool {
exist := db.Exists(ctx, url).Val() return db.Exists(ctx, url).Val() == 1
return exist == 1
} }
func verifySecret(url string, secret string) bool { func VerifySecret(url string, secret string) bool {
if secret == db.HGet(ctx, url, "secret").Val() { return secret == db.HGet(ctx, url, "secret").Val()
return true
}
return false
} }