refactor: optimized editor, encryption and backend code
This commit is contained in:
parent
4c1b757403
commit
afab9091b1
4 changed files with 142 additions and 179 deletions
|
@ -1,10 +1,16 @@
|
||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, {
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
} from "react";
|
||||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||||
import Prism from "prismjs";
|
import Prism from "prismjs";
|
||||||
import styles from "./Editor.module.css";
|
import styles from "./Editor.module.css";
|
||||||
import "../prism-themes/prism-gruvbox-dark.css";
|
import "../prism-themes/prism-gruvbox-dark.css";
|
||||||
import "../prism-themes/prism-line-numbers.css";
|
import "../prism-themes/prism-line-numbers.css";
|
||||||
import { BASE_URL, URL_REGEX } from "../../utils/constants";
|
import { URL_REGEX } from "../../utils/constants";
|
||||||
import Header from "../Header/Header";
|
import Header from "../Header/Header";
|
||||||
import {
|
import {
|
||||||
generateAESKey,
|
generateAESKey,
|
||||||
|
@ -24,25 +30,29 @@ const Editor = () => {
|
||||||
const [openModal, setOpenModal] = useState(false);
|
const [openModal, setOpenModal] = useState(false);
|
||||||
const textareaRef = useRef(null);
|
const textareaRef = useRef(null);
|
||||||
const lineNumberRef = useRef(null);
|
const lineNumberRef = useRef(null);
|
||||||
const queryParams = new URLSearchParams(location.search);
|
const queryParams = useMemo(
|
||||||
|
() => new URLSearchParams(location.search),
|
||||||
|
[location.search],
|
||||||
|
);
|
||||||
|
const origin = useMemo(() => window.location.origin, []);
|
||||||
|
|
||||||
const handleTextChange = (event) => {
|
const handleTextChange = useCallback((event) => {
|
||||||
setText(event.target.value);
|
setText(event.target.value);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleScroll = () => {
|
const handleScroll = useCallback(() => {
|
||||||
if (textareaRef.current && lineNumberRef.current) {
|
if (textareaRef.current && lineNumberRef.current) {
|
||||||
lineNumberRef.current.scrollTop = textareaRef.current.scrollTop;
|
lineNumberRef.current.scrollTop = textareaRef.current.scrollTop;
|
||||||
}
|
}
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleSaveClick = async () => {
|
const handleSaveClick = useCallback(async () => {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
alert("Please enter some text!");
|
alert("Please enter some text!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (URL_REGEX.test(text)) {
|
if (URL_REGEX.test(text)) {
|
||||||
const response = await fetch(`${BASE_URL}/bin`, {
|
const response = await fetch(`${origin}/bin`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -54,29 +64,19 @@ const Editor = () => {
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
navigator.clipboard
|
const shortURL = `${origin}/r/${data.id}`;
|
||||||
.writeText(`${window.location.origin}/r/${data.id}`)
|
copyToClipboard(shortURL);
|
||||||
.then(
|
alert("Short URL copied to clipboard!");
|
||||||
function () {
|
navigate(`/${data.id}`);
|
||||||
alert("Short URL copied to clipboard!");
|
} else {
|
||||||
},
|
console.error(data);
|
||||||
function () {
|
|
||||||
try {
|
|
||||||
document.execCommand("copy");
|
|
||||||
alert("Short URL copied to clipboard!");
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Oops, unable to copy");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
navigate(`/${data.id}`);
|
} else {
|
||||||
return;
|
setOpenModal(true);
|
||||||
}
|
}
|
||||||
setOpenModal(true);
|
}, [text, language, navigate]);
|
||||||
};
|
|
||||||
|
|
||||||
const handleSuccessClick = async () => {
|
const handleSuccessClick = useCallback(async () => {
|
||||||
setOpenModal(false);
|
setOpenModal(false);
|
||||||
const key = await generateAESKey();
|
const key = await generateAESKey();
|
||||||
const keyString = await keyToString(key);
|
const keyString = await keyToString(key);
|
||||||
|
@ -86,7 +86,7 @@ const Editor = () => {
|
||||||
);
|
);
|
||||||
const ivBase64 = btoa(String.fromCharCode.apply(null, iv));
|
const ivBase64 = btoa(String.fromCharCode.apply(null, iv));
|
||||||
|
|
||||||
const response = await fetch(`${BASE_URL}/bin`, {
|
const response = await fetch(`${origin}/bin`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -99,33 +99,18 @@ const Editor = () => {
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
navigator.clipboard
|
const encryptedURL = `${origin}/${data.id}?key=${keyString}`;
|
||||||
.writeText(`${window.location.origin}/${data.id}?key=${keyString}`)
|
copyToClipboard(encryptedURL);
|
||||||
.then(
|
alert("URL copied to clipboard!");
|
||||||
function () {
|
|
||||||
navigator.clipboard.writeText(
|
|
||||||
`${window.location.origin}/${data.id}?key=${keyString}`,
|
|
||||||
);
|
|
||||||
alert("URL copied to clipboard!");
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
try {
|
|
||||||
document.execCommand("copy");
|
|
||||||
alert("URL copied to clipboard!");
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Oops, unable to copy");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
navigate(`/${data.id}?key=${keyString}`);
|
navigate(`/${data.id}?key=${keyString}`);
|
||||||
} else {
|
} else {
|
||||||
console.error(data);
|
console.error(data);
|
||||||
}
|
}
|
||||||
};
|
}, [text, language, navigate]);
|
||||||
|
|
||||||
const handleCancelClick = async () => {
|
const handleCancelClick = useCallback(async () => {
|
||||||
setOpenModal(false);
|
setOpenModal(false);
|
||||||
const response = await fetch(`${BASE_URL}/bin`, {
|
const response = await fetch(`${origin}/bin`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
@ -137,78 +122,72 @@ const Editor = () => {
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
navigator.clipboard
|
const normalURL = `${origin}/${data.id}`;
|
||||||
.writeText(`${window.location.origin}/${data.id}`)
|
copyToClipboard(normalURL);
|
||||||
.then(
|
alert("URL copied to clipboard!");
|
||||||
function () {
|
|
||||||
navigator.clipboard.writeText(
|
|
||||||
`${window.location.origin}/${data.id}`,
|
|
||||||
);
|
|
||||||
alert("URL copied to clipboard!");
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
try {
|
|
||||||
document.execCommand("copy");
|
|
||||||
alert("URL copied to clip`board!");
|
|
||||||
} catch (err) {
|
|
||||||
console.log("Oops, unable to copy");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
navigate(`/${data.id}`);
|
navigate(`/${data.id}`);
|
||||||
} else {
|
} else {
|
||||||
console.error(data);
|
console.error(data);
|
||||||
}
|
}
|
||||||
};
|
}, [text, language, navigate]);
|
||||||
|
|
||||||
const handleLanguageChange = (value) => {
|
const handleLanguageChange = useCallback((value) => {
|
||||||
setLanguage(value);
|
setLanguage(value);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Prism.highlightAll();
|
Prism.highlightAll();
|
||||||
}, [text, language]);
|
}, [text, language]);
|
||||||
|
|
||||||
useEffect(() => {
|
const fetchData = useCallback(async () => {
|
||||||
const fetchData = async () => {
|
const response = await fetch(`${origin}/bin/${id}`);
|
||||||
const response = await fetch(`${BASE_URL}/bin/${id}`);
|
const data = await response.json();
|
||||||
const data = await response.json();
|
if (response.ok) {
|
||||||
if (response.ok) {
|
if (data.iv) {
|
||||||
if (data.iv) {
|
const keyString = queryParams.get("key");
|
||||||
const keyString = queryParams.get("key");
|
const key = await stringToKey(keyString);
|
||||||
const key = await stringToKey(keyString);
|
const encrypted = new Uint8Array(
|
||||||
const encrypted = new Uint8Array(
|
atob(data.content)
|
||||||
atob(data.content)
|
.split("")
|
||||||
.split("")
|
.map((char) => char.charCodeAt(0)),
|
||||||
.map((char) => char.charCodeAt(0)),
|
).buffer;
|
||||||
).buffer;
|
const ivArray = new Uint8Array(
|
||||||
const ivArray = new Uint8Array(
|
atob(data.iv)
|
||||||
atob(data.iv)
|
.split("")
|
||||||
.split("")
|
.map((char) => char.charCodeAt(0)),
|
||||||
.map((char) => char.charCodeAt(0)),
|
);
|
||||||
);
|
const decryptedContent = await decryptAES(encrypted, key, ivArray);
|
||||||
const decryptedContent = await decryptAES(encrypted, key, ivArray);
|
setLanguage(data.language);
|
||||||
setLanguage(data.language);
|
setText(decryptedContent);
|
||||||
setText(decryptedContent);
|
} else {
|
||||||
|
const isURL = URL_REGEX.test(data.content);
|
||||||
|
if (isURL) {
|
||||||
|
setText(`Your shortened URL: ${origin}/r/${id}`);
|
||||||
} else {
|
} else {
|
||||||
const isURL = URL_REGEX.test(data.content);
|
setLanguage(data.language);
|
||||||
if (isURL) {
|
setText(data.content);
|
||||||
setText(`Your shortened URL: ${window.location.origin}/r/${id}`);
|
|
||||||
} else {
|
|
||||||
setLanguage(data.language);
|
|
||||||
setText(data.content);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
}, [id, queryParams]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
fetchData();
|
fetchData();
|
||||||
} else {
|
} else {
|
||||||
textareaRef.current.value = "";
|
|
||||||
setText("");
|
setText("");
|
||||||
}
|
}
|
||||||
}, [id]);
|
}, [id, fetchData]);
|
||||||
|
|
||||||
|
const copyToClipboard = useCallback((text) => {
|
||||||
|
navigator.clipboard.writeText(text).catch(() => {
|
||||||
|
try {
|
||||||
|
document.execCommand("copy");
|
||||||
|
} catch (err) {
|
||||||
|
console.log("Oops, unable to copy");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -216,22 +195,17 @@ const Editor = () => {
|
||||||
<Modal
|
<Modal
|
||||||
openModal={openModal}
|
openModal={openModal}
|
||||||
setOpenModal={setOpenModal}
|
setOpenModal={setOpenModal}
|
||||||
onSuccessClick={() => {
|
onSuccessClick={handleSuccessClick}
|
||||||
handleSuccessClick();
|
onCancelClick={handleCancelClick}
|
||||||
}}
|
|
||||||
onCancelClick={() => {
|
|
||||||
handleCancelClick();
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
{!id && (
|
{!id && (
|
||||||
<button
|
<button className={styles.btn__save} onClick={handleSaveClick}>
|
||||||
className={styles.btn__save}
|
<img
|
||||||
onClick={() => {
|
src="assets/icons/save.svg"
|
||||||
handleSaveClick();
|
className={styles.btn__icon}
|
||||||
}}
|
alt="Save"
|
||||||
>
|
/>
|
||||||
<img src="assets/icons/save.svg" className={styles.btn__icon} />
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -245,8 +219,9 @@ const Editor = () => {
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
ref={textareaRef}
|
ref={textareaRef}
|
||||||
placeholder="</> Paste, save, share! (Pasting just a URL will shorten it!)"
|
placeholder="</> Paste, save, share! (Pasting just a URL will shorten it!)"
|
||||||
|
value={text}
|
||||||
/>
|
/>
|
||||||
<pre className="line-numbers">
|
<pre className="line-numbers" ref={lineNumberRef}>
|
||||||
<code
|
<code
|
||||||
className={`${styles.codespace__code} language-${language}`}
|
className={`${styles.codespace__code} language-${language}`}
|
||||||
>
|
>
|
||||||
|
@ -261,4 +236,3 @@ const Editor = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Editor;
|
export default Editor;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
export const BASE_URL = window.location.origin;
|
|
||||||
export const URL_REGEX = /^(https?:\/\/)?([\w.-]+\.[a-z]{2,})(\/?[^\s]*)?$/;
|
export const URL_REGEX = /^(https?:\/\/)?([\w.-]+\.[a-z]{2,})(\/?[^\s]*)?$/;
|
||||||
export const SUPPORTED_LANGUAGES = [
|
export const SUPPORTED_LANGUAGES = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
async function generateAESKey() {
|
async function generateAESKey() {
|
||||||
try {
|
try {
|
||||||
const key = await window.crypto.subtle.generateKey(
|
const key = await window.crypto.subtle.generateKey(
|
||||||
{
|
{ name: "AES-GCM", length: 256 },
|
||||||
name: "AES-GCM",
|
|
||||||
length: 256,
|
|
||||||
},
|
|
||||||
true,
|
true,
|
||||||
["encrypt", "decrypt"],
|
["encrypt", "decrypt"],
|
||||||
);
|
);
|
||||||
return key;
|
return key;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error generating AES key:", error);
|
console.error("Error generating AES key:", error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function keyToString(key) {
|
async function keyToString(key) {
|
||||||
try {
|
try {
|
||||||
const exportedKey = await window.crypto.subtle.exportKey("raw", key);
|
const exportedKey = await window.crypto.subtle.exportKey("raw", key);
|
||||||
const keyString = btoa(String.fromCharCode(...new Uint8Array(exportedKey)));
|
return btoa(String.fromCharCode(...new Uint8Array(exportedKey)))
|
||||||
return keyString.replace(/\+/g, "-").replace(/\//g, "_");
|
.replace(/\+/g, "-")
|
||||||
|
.replace(/\//g, "_");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error converting key to string:", error);
|
console.error("Error converting key to string:", error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,17 +31,16 @@ async function stringToKey(keyString) {
|
||||||
c.charCodeAt(0),
|
c.charCodeAt(0),
|
||||||
).buffer;
|
).buffer;
|
||||||
|
|
||||||
const key = await window.crypto.subtle.importKey(
|
return await window.crypto.subtle.importKey(
|
||||||
"raw",
|
"raw",
|
||||||
buffer,
|
buffer,
|
||||||
{ name: "AES-GCM", length: 256 },
|
{ name: "AES-GCM", length: 256 },
|
||||||
true,
|
true,
|
||||||
["encrypt", "decrypt"],
|
["encrypt", "decrypt"],
|
||||||
);
|
);
|
||||||
|
|
||||||
return key;
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error converting string to key:", error);
|
console.error("Error converting string to key:", error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,32 +48,28 @@ async function encryptAES(plaintext, key) {
|
||||||
try {
|
try {
|
||||||
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
const iv = window.crypto.getRandomValues(new Uint8Array(12));
|
||||||
const encrypted = await window.crypto.subtle.encrypt(
|
const encrypted = await window.crypto.subtle.encrypt(
|
||||||
{
|
{ name: "AES-GCM", iv: iv },
|
||||||
name: "AES-GCM",
|
|
||||||
iv: iv,
|
|
||||||
},
|
|
||||||
key,
|
key,
|
||||||
new TextEncoder().encode(plaintext),
|
new TextEncoder().encode(plaintext),
|
||||||
);
|
);
|
||||||
return { encrypted, iv };
|
return { encrypted, iv };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error encrypting:", error);
|
console.error("Error encrypting:", error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function decryptAES(encrypted, key, iv) {
|
async function decryptAES(encrypted, key, iv) {
|
||||||
try {
|
try {
|
||||||
const decrypted = await window.crypto.subtle.decrypt(
|
const decrypted = await window.crypto.subtle.decrypt(
|
||||||
{
|
{ name: "AES-GCM", iv: iv },
|
||||||
name: "AES-GCM",
|
|
||||||
iv: iv,
|
|
||||||
},
|
|
||||||
key,
|
key,
|
||||||
encrypted,
|
encrypted,
|
||||||
);
|
);
|
||||||
return new TextDecoder().decode(decrypted);
|
return new TextDecoder().decode(decrypted);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error decrypting:", error);
|
console.error("Error decrypting:", error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,37 +7,36 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var db *sql.DB
|
var (
|
||||||
var dbFilePath string
|
db *sql.DB
|
||||||
var port string
|
dbFilePath string
|
||||||
|
port string
|
||||||
type Bin struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
Language string `json:"language"`
|
|
||||||
IV string `json:"iv"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
shortIDCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
shortIDCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
shortIDLength = 8
|
shortIDLength = 8
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type Bin struct {
|
||||||
//go:embed all:dist
|
Content string `json:"content"`
|
||||||
dist embed.FS
|
Language string `json:"language"`
|
||||||
|
IV string `json:"iv"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
insertQuery = "INSERT INTO bins (id, content, language, iv) VALUES (?, ?, ?, ?)"
|
||||||
|
selectQuery = "SELECT content, language, iv FROM bins WHERE id = ?"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed all:dist
|
||||||
|
var dist embed.FS
|
||||||
|
|
||||||
func RegisterHandlers(e *echo.Echo) {
|
func RegisterHandlers(e *echo.Echo) {
|
||||||
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
|
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
|
||||||
Skipper: nil,
|
|
||||||
Root: "dist",
|
Root: "dist",
|
||||||
Index: "index.html",
|
Index: "index.html",
|
||||||
HTML5: true,
|
HTML5: true,
|
||||||
|
@ -55,6 +54,7 @@ func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
initDatabase()
|
initDatabase()
|
||||||
|
|
||||||
e := echo.New()
|
e := echo.New()
|
||||||
RegisterHandlers(e)
|
RegisterHandlers(e)
|
||||||
e.Logger.Fatal(e.Start(":" + port))
|
e.Logger.Fatal(e.Start(":" + port))
|
||||||
|
@ -67,47 +67,43 @@ func initDatabase() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = createTable()
|
if err := createTable(); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func postBin(echoContext echo.Context) error {
|
func postBin(c echo.Context) error {
|
||||||
bin := Bin{}
|
var bin Bin
|
||||||
err := echoContext.Bind(&bin)
|
if err := c.Bind(&bin); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
id := generateShortID()
|
id := generateShortID()
|
||||||
err = saveBin(id, bin)
|
if err := saveBin(id, bin); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return echoContext.JSON(http.StatusCreated, echo.Map{
|
|
||||||
"id": id,
|
return c.JSON(http.StatusCreated, echo.Map{"id": id})
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBin(echoContext echo.Context) error {
|
func getBin(c echo.Context) error {
|
||||||
id := echoContext.Param("id")
|
id := c.Param("id")
|
||||||
bin, err := getBinById(id)
|
bin, err := getBinByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return echoContext.JSON(http.StatusOK, bin)
|
return c.JSON(http.StatusOK, bin)
|
||||||
}
|
}
|
||||||
|
|
||||||
func redirectToURL(echoContext echo.Context) error {
|
func redirectToURL(c echo.Context) error {
|
||||||
id := echoContext.Param("id")
|
id := c.Param("id")
|
||||||
bin, err := getBinById(id)
|
bin, err := getBinByID(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
echoContext.Logger().Error(err)
|
c.Logger().Error(err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
url := bin.Content
|
return c.Redirect(http.StatusFound, bin.Content)
|
||||||
return echoContext.Redirect(http.StatusFound, url)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTable() error {
|
func createTable() error {
|
||||||
|
@ -115,20 +111,19 @@ func createTable() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getBinById(id string) (Bin, error) {
|
func getBinByID(id string) (Bin, error) {
|
||||||
row := db.QueryRow("SELECT content, language, iv FROM bins WHERE id = ?", id)
|
var bin Bin
|
||||||
bin := Bin{}
|
row := db.QueryRow(selectQuery, id)
|
||||||
err := row.Scan(&bin.Content, &bin.Language, &bin.IV)
|
err := row.Scan(&bin.Content, &bin.Language, &bin.IV)
|
||||||
return bin, err
|
return bin, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveBin(id string, bin Bin) error {
|
func saveBin(id string, bin Bin) error {
|
||||||
_, err := db.Exec("INSERT INTO bins (id, content, language, iv) VALUES (?, ?, ?, ?)", id, bin.Content, bin.Language, bin.IV)
|
_, err := db.Exec(insertQuery, id, bin.Content, bin.Language, bin.IV)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateShortID() string {
|
func generateShortID() string {
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
id := make([]byte, shortIDLength)
|
id := make([]byte, shortIDLength)
|
||||||
for i := range id {
|
for i := range id {
|
||||||
id[i] = shortIDCharset[rand.Intn(len(shortIDCharset))]
|
id[i] = shortIDCharset[rand.Intn(len(shortIDCharset))]
|
||||||
|
|
Loading…
Reference in a new issue