refactor: Rewrite server in go

This commit is contained in:
Blaster4385 2024-02-19 20:42:57 +05:30
parent 23de613393
commit 86ed77b9cc
12 changed files with 185 additions and 364 deletions

200
server/.gitignore vendored
View file

@ -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

Binary file not shown.

View file

@ -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 };

View file

@ -1,7 +0,0 @@
const healthCheck = async (req, res) => {
return res.json({
uptime: process.uptime(),
});
};
export default { healthCheck };

10
server/go.mod Normal file
View 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
View 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=

View file

@ -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
View 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)
}

View file

@ -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"
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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
}
}