refactor: Rewrite server in go
This commit is contained in:
parent
23de613393
commit
86ed77b9cc
12 changed files with 185 additions and 364 deletions
200
server/.gitignore
vendored
200
server/.gitignore
vendored
|
@ -1,175 +1,25 @@
|
|||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
minibin
|
||||
|
||||
# Database
|
||||
*.db
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
|
BIN
server/bun.lockb
BIN
server/bun.lockb
Binary file not shown.
|
@ -1,100 +0,0 @@
|
|||
import sqlite3 from "sqlite3";
|
||||
|
||||
const dbPath = "./database/bins.db";
|
||||
|
||||
function createBinTable() {
|
||||
const db = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error("Error connecting to database:", err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
const sql = `CREATE TABLE IF NOT EXISTS bin (
|
||||
id TEXT PRIMARY KEY,
|
||||
html_content TEXT
|
||||
)`;
|
||||
|
||||
db.run(sql, (err) => {
|
||||
if (err) {
|
||||
console.error("Error creating table:", err.message);
|
||||
} else {
|
||||
console.log("`bin` table created successfully");
|
||||
}
|
||||
|
||||
db.close((err) => {
|
||||
if (err) {
|
||||
console.error("Error closing database:", err.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const createBin = (req, res) => {
|
||||
console.log(`createBin: ${req.body}`);
|
||||
const { html_content } = req.body;
|
||||
const id = Math.random().toString(36).substring(7);
|
||||
|
||||
const db = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error("Error connecting to database:", err.message);
|
||||
throw err; // Re-throw for async handling
|
||||
}
|
||||
|
||||
const sql = `INSERT INTO bin (id, html_content) VALUES (?, ?)`;
|
||||
|
||||
db.run(sql, [id, html_content], (err) => {
|
||||
if (err) {
|
||||
console.error("Error creating entry:", err.message);
|
||||
throw err; // Re-throw for async handling
|
||||
} else {
|
||||
res.status(201).json({ id });
|
||||
console.log("Entry created successfully:", id);
|
||||
}
|
||||
|
||||
db.close((err) => {
|
||||
if (err) {
|
||||
console.error("Error closing database:", err.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getBin = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
if (!id) {
|
||||
throw new Error("Missing required parameter: id"); // Throw error for missing ID
|
||||
}
|
||||
|
||||
const db = new sqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error("Error connecting to database:", err.message);
|
||||
throw err; // Re-throw for async handling
|
||||
}
|
||||
|
||||
const sql = `SELECT * FROM bin WHERE id = ?`;
|
||||
|
||||
db.get(sql, [id], (err, row) => {
|
||||
if (err) {
|
||||
console.error("Error retrieving entry:", err.message);
|
||||
throw err; // Re-throw for async handling
|
||||
} else if (!row) {
|
||||
console.log("Entry not found:", id);
|
||||
return null; // Handle case where no entry exists
|
||||
}
|
||||
|
||||
res.status(200).json(row);
|
||||
console.log("Entry retrieved:", id);
|
||||
db.close((err) => {
|
||||
if (err) {
|
||||
console.error("Error closing database:", err.message);
|
||||
}
|
||||
});
|
||||
|
||||
return row;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export default { createBinTable, createBin, getBin };
|
|
@ -1,7 +0,0 @@
|
|||
const healthCheck = async (req, res) => {
|
||||
return res.json({
|
||||
uptime: process.uptime(),
|
||||
});
|
||||
};
|
||||
|
||||
export default { healthCheck };
|
10
server/go.mod
Normal file
10
server/go.mod
Normal file
|
@ -0,0 +1,10 @@
|
|||
module minibin
|
||||
|
||||
go 1.22.0
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/rs/cors v1.10.1 // indirect
|
||||
)
|
8
server/go.sum
Normal file
8
server/go.sum
Normal file
|
@ -0,0 +1,8 @@
|
|||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
|
||||
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
|
@ -1,18 +0,0 @@
|
|||
import Express from "express";
|
||||
import cors from "cors";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
import binRoutes from "./routes/bin.js";
|
||||
import healthRoutes from "./routes/health.js";
|
||||
|
||||
const app = Express();
|
||||
dotenv.config();
|
||||
|
||||
app.use(cors());
|
||||
app.use(Express.json());
|
||||
app.use("/", healthRoutes);
|
||||
app.use("/bin", binRoutes);
|
||||
|
||||
const PORT = process.env.PORT;
|
||||
|
||||
app.listen(PORT, () => console.log(`Server running on PORT: ${PORT}`));
|
142
server/main.go
Normal file
142
server/main.go
Normal file
|
@ -0,0 +1,142 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
var db *sql.DB
|
||||
var port int
|
||||
var dbFilePath string
|
||||
|
||||
type Bin struct {
|
||||
Content string `json:"content"`
|
||||
Language string `json:"language"`
|
||||
}
|
||||
|
||||
const (
|
||||
shortIDCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
shortIDLength = 8
|
||||
)
|
||||
|
||||
func main() {
|
||||
flag.IntVar(&port, "port", 8080, "Port number for the server (default is 8080)")
|
||||
flag.StringVar(&dbFilePath, "db", "./minibin.db", "Database file path")
|
||||
flag.Parse()
|
||||
|
||||
setupServer()
|
||||
}
|
||||
|
||||
func setupServer() {
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/bin", postBin)
|
||||
mux.HandleFunc("/bin/", getBin)
|
||||
handler := cors.Default().Handler(mux)
|
||||
serverAddr := fmt.Sprintf(":%d", port)
|
||||
log.Printf("Server listening on port %d...\n", port)
|
||||
initDatabase()
|
||||
log.Fatal(http.ListenAndServe(serverAddr, handler))
|
||||
}
|
||||
|
||||
func initDatabase() {
|
||||
var err error
|
||||
db, err = sql.Open("sqlite3", dbFilePath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = createTable()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func postBin(w http.ResponseWriter, r *http.Request) {
|
||||
handleRequestMethod(w, r, "POST", func() {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if handleError(w, err, http.StatusInternalServerError) {
|
||||
return
|
||||
}
|
||||
var bin Bin
|
||||
err = json.Unmarshal(body, &bin)
|
||||
if handleError(w, err, http.StatusBadRequest) {
|
||||
return
|
||||
}
|
||||
id := generateShortID()
|
||||
handleError(w, saveBin(id, bin), http.StatusInternalServerError)
|
||||
respondWithJSON(w, http.StatusOK, map[string]string{"id": id, "content": bin.Content, "language": bin.Language})
|
||||
})
|
||||
}
|
||||
|
||||
func getBin(w http.ResponseWriter, r *http.Request) {
|
||||
handleRequestMethod(w, r, "GET", func() {
|
||||
id := strings.TrimPrefix(r.URL.Path, "/bin/")
|
||||
bin, err := getBinById(id)
|
||||
handleError(w, err, http.StatusInternalServerError)
|
||||
respondWithJSON(w, http.StatusOK, bin)
|
||||
})
|
||||
}
|
||||
|
||||
func handleRequestMethod(w http.ResponseWriter, r *http.Request, expectedMethod string, handler func()) {
|
||||
if r.Method != expectedMethod {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
handler()
|
||||
}
|
||||
|
||||
func handleError(w http.ResponseWriter, err error, statusCode int) bool {
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), statusCode)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func respondWithJSON(w http.ResponseWriter, statusCode int, data interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
json.NewEncoder(w).Encode(data)
|
||||
}
|
||||
|
||||
func createTable() error {
|
||||
_, err := db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS bins (
|
||||
id TEXT PRIMARY KEY,
|
||||
content TEXT,
|
||||
language TEXT
|
||||
)
|
||||
`)
|
||||
return err
|
||||
}
|
||||
|
||||
func getBinById(id string) (Bin, error) {
|
||||
var bin Bin
|
||||
err := db.QueryRow("SELECT content, language FROM bins WHERE id = ?", id).Scan(&bin.Content, &bin.Language)
|
||||
return bin, err
|
||||
}
|
||||
|
||||
func saveBin(id string, bin Bin) error {
|
||||
_, err := db.Exec("INSERT INTO bins (id, content, language) VALUES (?, ?, ?)", id, bin.Content, bin.Language)
|
||||
return err
|
||||
}
|
||||
|
||||
func generateShortID() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
id := make([]byte, shortIDLength)
|
||||
for i := range id {
|
||||
id[i] = shortIDCharset[rand.Intn(len(shortIDCharset))]
|
||||
}
|
||||
return string(id)
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"name": "minibun-server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "nodemon index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.4",
|
||||
"nodemon": "^3.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"sqlite3": "^5.1.7"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
import Express from "express";
|
||||
import bin from "../controllers/bin.js";
|
||||
|
||||
const router = Express.Router();
|
||||
|
||||
bin.createBinTable();
|
||||
|
||||
router.get("/:id", bin.getBin);
|
||||
router.post("/", bin.createBin);
|
||||
|
||||
export default router;
|
|
@ -1,8 +0,0 @@
|
|||
import Express from "express";
|
||||
import health from "../controllers/health.js";
|
||||
|
||||
const router = Express.Router();
|
||||
|
||||
router.get("/health", health.healthCheck);
|
||||
|
||||
export default router;
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue