✨ Implement basic POST request for curl #30
11 changed files with 135 additions and 119 deletions
6
.idea/.gitignore
vendored
6
.idea/.gitignore
vendored
|
@ -1,6 +0,0 @@
|
||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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
5
db.go
|
@ -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) {
|
||||||
|
|
37
main.go
37
main.go
|
@ -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,28 +66,26 @@ 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, "/")
|
secret := r.URL.Query().Get("secret")
|
||||||
if urlExist("/" + urlItem[2]) {
|
if secret == db.HGet(ctx, path, "secret").Val() {
|
||||||
secret := r.URL.Query().Get("secret")
|
err := db.Del(ctx, path)
|
||||||
if verifySecret("/"+urlItem[2], secret) {
|
if err != nil {
|
||||||
deleteContent("/" + urlItem[2])
|
log.Println(err)
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
} else {
|
|
||||||
w.WriteHeader(http.StatusForbidden)
|
|
||||||
}
|
}
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
@ -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();
|
|
@ -9,50 +9,57 @@
|
||||||
<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">
|
||||||
<label for="content"></label>
|
<div>
|
||||||
<textarea autofocus id="content" name="content" placeholder="Your paste here"></textarea>
|
<label for="lines"></label>
|
||||||
<nav>
|
<textarea id="lines" readonly wrap="hard">1</textarea>
|
||||||
<ul>
|
</div>
|
||||||
<li>
|
<div>
|
||||||
<label for="password">Password?</label>
|
<label for="content"></label>
|
||||||
<input id="password" type="text">
|
<textarea autofocus id="content" name="content" placeholder="Your paste here"></textarea>
|
||||||
</li>
|
<nav>
|
||||||
<li><label for="exp">Expiration?</label>
|
<ul>
|
||||||
<input id="exp" type="date"></li>
|
<li>
|
||||||
<li>
|
<label for="password">Password?</label>
|
||||||
<label for="type">Type</label>
|
<input id="password" type="text">
|
||||||
<select id="type" name="type">
|
</li>
|
||||||
<option value="auto">Auto Detect</option>
|
<li><label for="exp">Expiration?</label>
|
||||||
<option value="c">C</option>
|
<input id="exp" type="date"></li>
|
||||||
<option value="cpp">C++</option>
|
<li>
|
||||||
<option value="csharp">C#</option>
|
<label for="type">Type</label>
|
||||||
<option value="css">CSS</option>
|
<select id="type" name="type">
|
||||||
<option value="go">Go</option>
|
<option value="auto">Auto Detect</option>
|
||||||
<option value="java">Java</option>
|
<option value="c">C</option>
|
||||||
<option value="js">Javascript</option>
|
<option value="cpp">C++</option>
|
||||||
<option value="html">HTML</option>
|
<option value="csharp">C#</option>
|
||||||
<option selected value="plain">Plaintext</option>
|
<option value="css">CSS</option>
|
||||||
<option value="python">Python</option>
|
<option value="go">Go</option>
|
||||||
<option value="rb">Ruby</option>
|
<option value="java">Java</option>
|
||||||
<option value="rs">Rust</option>
|
<option value="js">Javascript</option>
|
||||||
<option value="sh">Shell</option>
|
<option value="html">HTML</option>
|
||||||
<option value="sql">SQL</option>
|
<option selected value="plain">Plaintext</option>
|
||||||
<option value="ts">Typescript</option>
|
<option value="python">Python</option>
|
||||||
<option value="xml">XML</option>
|
<option value="rb">Ruby</option>
|
||||||
<option value="yml">YAML</option>
|
<option value="rs">Rust</option>
|
||||||
</select>
|
<option value="sh">Shell</option>
|
||||||
</li>
|
<option value="sql">SQL</option>
|
||||||
</ul>
|
<option value="ts">Typescript</option>
|
||||||
<button type="submit" title="Save plak">
|
<option value="xml">XML</option>
|
||||||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<option value="yml">YAML</option>
|
||||||
<polyline points="9 11 12 14 22 4"></polyline>
|
</select>
|
||||||
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"></path>
|
</li>
|
||||||
</svg>
|
</ul>
|
||||||
</button>
|
<button title="Save plak" type="submit">
|
||||||
</nav>
|
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<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>
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
16
utils.go
16
utils.go
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue