WIP: ♻️ Restructure test #36
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"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
type Config struct {
|
||||
host string
|
||||
port string
|
||||
redisAddr string
|
||||
|
@ -17,7 +17,7 @@ type config struct {
|
|||
urlLength int
|
||||
}
|
||||
|
||||
func getConfig() config {
|
||||
func GetConfig() Config {
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("Error loading .env file: %v", err)
|
||||
|
@ -42,7 +42,7 @@ func getConfig() config {
|
|||
log.Fatal("Invalid PLAKKEN_REDIS_URL_LEN")
|
||||
}
|
||||
|
||||
conf := config{
|
||||
return Config{
|
||||
host: os.Getenv("PLAKKEN_INTERFACE"),
|
||||
port: port,
|
||||
redisAddr: redisAddr,
|
||||
|
@ -51,6 +51,4 @@ func getConfig() config {
|
|||
redisDB: redisDB,
|
||||
urlLength: urlLen,
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
|
5
db.go
5
db.go
|
@ -9,7 +9,7 @@ import (
|
|||
|
||||
var ctx = context.Background()
|
||||
|
||||
func connectDB() *redis.Client {
|
||||
func ConnectDB() *redis.Client {
|
||||
localDb := redis.NewClient(&redis.Options{
|
||||
Addr: currentConfig.redisAddr,
|
||||
Username: currentConfig.redisUser,
|
||||
|
@ -40,8 +40,7 @@ func insertPaste(key string, content string, secret string, ttl time.Duration) {
|
|||
}
|
||||
|
||||
func getContent(key string) string {
|
||||
content := db.HGet(ctx, key, "content").Val()
|
||||
return content
|
||||
return db.HGet(ctx, key, "content").Val()
|
||||
}
|
||||
|
||||
func deleteContent(key string) {
|
||||
|
|
37
main.go
37
main.go
|
@ -10,7 +10,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
var currentConfig config
|
||||
var currentConfig Config
|
||||
var db *redis.Client
|
||||
|
||||
type pasteView struct {
|
||||
|
@ -21,16 +21,17 @@ type pasteView struct {
|
|||
func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||
path := r.URL.Path
|
||||
clearPath := strings.ReplaceAll(r.URL.Path, "/raw", "")
|
||||
staticResource := "/static/"
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
if path == "/" {
|
||||
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"))
|
||||
http.Handle("/static/", http.StripPrefix("/static/", fs))
|
||||
http.Handle(staticResource, http.StripPrefix(staticResource, fs))
|
||||
} else {
|
||||
if urlExist(clearPath) {
|
||||
if UrlExist(clearPath) {
|
||||
if strings.HasSuffix(path, "/raw") {
|
||||
pasteContent := getContent(clearPath)
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
|
@ -56,8 +57,8 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
case "POST":
|
||||
if path == "/" {
|
||||
secret := generateSecret()
|
||||
url := "/" + generateUrl()
|
||||
secret := GenerateSecret()
|
||||
url := "/" + GenerateUrl()
|
||||
content := r.FormValue("content")
|
||||
insertPaste(url, content, secret, -1)
|
||||
http.Redirect(w, r, url, http.StatusSeeOther)
|
||||
|
@ -65,28 +66,26 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
}
|
||||
case "DELETE":
|
||||
if strings.HasPrefix(path, "/delete") {
|
||||
urlItem := strings.Split(path, "/")
|
||||
if urlExist("/" + urlItem[2]) {
|
||||
secret := r.URL.Query().Get("secret")
|
||||
if verifySecret("/"+urlItem[2], secret) {
|
||||
deleteContent("/" + urlItem[2])
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
if UrlExist(path) {
|
||||
secret := r.URL.Query().Get("secret")
|
||||
if secret == db.HGet(ctx, path, "secret").Val() {
|
||||
err := db.Del(ctx, path)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
}
|
||||
} else {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
db = connectDB()
|
||||
currentConfig = getConfig()
|
||||
db = ConnectDB()
|
||||
currentConfig = GetConfig()
|
||||
listen := currentConfig.host + ":" + currentConfig.port
|
||||
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">
|
||||
<title>New plak • Plakken</title>
|
||||
<link href="/static/style.css" rel="stylesheet">
|
||||
<script async src="/static/app.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<form method="post">
|
||||
<label for="content"></label>
|
||||
<textarea autofocus id="content" name="content" placeholder="Your paste here"></textarea>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<label for="password">Password?</label>
|
||||
<input id="password" type="text">
|
||||
</li>
|
||||
<li><label for="exp">Expiration?</label>
|
||||
<input id="exp" type="date"></li>
|
||||
<li>
|
||||
<label for="type">Type</label>
|
||||
<select id="type" name="type">
|
||||
<option value="auto">Auto Detect</option>
|
||||
<option value="c">C</option>
|
||||
<option value="cpp">C++</option>
|
||||
<option value="csharp">C#</option>
|
||||
<option value="css">CSS</option>
|
||||
<option value="go">Go</option>
|
||||
<option value="java">Java</option>
|
||||
<option value="js">Javascript</option>
|
||||
<option value="html">HTML</option>
|
||||
<option selected value="plain">Plaintext</option>
|
||||
<option value="python">Python</option>
|
||||
<option value="rb">Ruby</option>
|
||||
<option value="rs">Rust</option>
|
||||
<option value="sh">Shell</option>
|
||||
<option value="sql">SQL</option>
|
||||
<option value="ts">Typescript</option>
|
||||
<option value="xml">XML</option>
|
||||
<option value="yml">YAML</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
<button type="submit" title="Save plak">
|
||||
<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>
|
||||
<label for="lines"></label>
|
||||
<textarea id="lines" readonly wrap="hard">1</textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="content"></label>
|
||||
<textarea autofocus id="content" name="content" placeholder="Your paste here"></textarea>
|
||||
<nav>
|
||||
<ul>
|
||||
<li>
|
||||
<label for="password">Password?</label>
|
||||
<input id="password" type="text">
|
||||
</li>
|
||||
<li><label for="exp">Expiration?</label>
|
||||
<input id="exp" type="date"></li>
|
||||
<li>
|
||||
<label for="type">Type</label>
|
||||
<select id="type" name="type">
|
||||
<option value="auto">Auto Detect</option>
|
||||
<option value="c">C</option>
|
||||
<option value="cpp">C++</option>
|
||||
<option value="csharp">C#</option>
|
||||
<option value="css">CSS</option>
|
||||
<option value="go">Go</option>
|
||||
<option value="java">Java</option>
|
||||
<option value="js">Javascript</option>
|
||||
<option value="html">HTML</option>
|
||||
<option selected value="plain">Plaintext</option>
|
||||
<option value="python">Python</option>
|
||||
<option value="rb">Ruby</option>
|
||||
<option value="rs">Rust</option>
|
||||
<option value="sh">Shell</option>
|
||||
<option value="sql">SQL</option>
|
||||
<option value="ts">Typescript</option>
|
||||
<option value="xml">XML</option>
|
||||
<option value="yml">YAML</option>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
<button title="Save plak" type="submit">
|
||||
<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>
|
||||
</body>
|
||||
</html>
|
|
@ -2,7 +2,7 @@
|
|||
--accent: #be0560;
|
||||
--background: #141414;
|
||||
--border: #333;
|
||||
--text: #e2e2e2;
|
||||
--text: #e9e9e9;
|
||||
}
|
||||
|
||||
body {
|
||||
|
@ -12,19 +12,35 @@ body {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
button, textarea {
|
||||
background-color: inherit;
|
||||
border: none;
|
||||
outline: none;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
textarea {
|
||||
color: var(--text);
|
||||
font: 14px/1.6 "JetBrains Mono", monospace;
|
||||
#lines {
|
||||
color: #999;
|
||||
font: 400 14px/1.6 "JetBrains Mono", monospace;
|
||||
height: calc(100vh - 3rem);
|
||||
outline: none;
|
||||
padding: 1rem;
|
||||
resize: none;
|
||||
width: calc(100vw - 2rem);
|
||||
overflow-y: hidden;
|
||||
padding: 10px 0;
|
||||
text-align: center;
|
||||
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 {
|
||||
|
@ -43,7 +59,7 @@ nav {
|
|||
ul {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
gap: 2.6rem;
|
||||
gap: 36px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0 1.9rem;
|
||||
|
@ -60,7 +76,7 @@ input, select {
|
|||
color: var(--text);
|
||||
font-size: 15px;
|
||||
outline: none;
|
||||
padding: 5px 6px;
|
||||
padding: 6px 8px;
|
||||
transition: border .15s ease;
|
||||
}
|
||||
|
||||
|
@ -86,7 +102,7 @@ input:focus-visible, select:focus-visible {
|
|||
border: 2px solid var(--text);
|
||||
}
|
||||
|
||||
button:focus-visible{
|
||||
button:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
|
16
utils.go
16
utils.go
|
@ -7,7 +7,7 @@ import (
|
|||
mathrand "math/rand"
|
||||
)
|
||||
|
||||
func generateUrl() string {
|
||||
func GenerateUrl() string {
|
||||
listChars := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
b := make([]rune, currentConfig.urlLength)
|
||||
for i := range b {
|
||||
|
@ -17,7 +17,7 @@ func generateUrl() string {
|
|||
return string(b)
|
||||
}
|
||||
|
||||
func generateSecret() string {
|
||||
func GenerateSecret() string {
|
||||
key := make([]byte, 32)
|
||||
_, err := rand.Read(key)
|
||||
if err != nil {
|
||||
|
@ -27,14 +27,10 @@ func generateSecret() string {
|
|||
return hex.EncodeToString(key)
|
||||
}
|
||||
|
||||
func urlExist(url string) bool {
|
||||
exist := db.Exists(ctx, url).Val()
|
||||
return exist == 1
|
||||
func UrlExist(url string) bool {
|
||||
return db.Exists(ctx, url).Val() == 1
|
||||
}
|
||||
|
||||
func verifySecret(url string, secret string) bool {
|
||||
if secret == db.HGet(ctx, url, "secret").Val() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
func VerifySecret(url string, secret string) bool {
|
||||
return secret == db.HGet(ctx, url, "secret").Val()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue