From 9bc0c33a952dc906344964890bce58761d6ce2c7 Mon Sep 17 00:00:00 2001 From: Jacob McDonnell Date: Sun, 19 Apr 2026 19:23:05 -0400 Subject: feat: Initial Commit, Partial Functionality This is the initial commit. The url-shortener is partially functional. URLs can be shortened and stored for the runtime of the program. API keys are not checked, nothing is persistent, and short URLs are size limited to 4 alpha numeric characters. --- go.mod | 3 ++ main.go | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ test.sh | 9 ++++++ 3 files changed, 117 insertions(+) create mode 100644 go.mod create mode 100644 main.go create mode 100755 test.sh diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cbace07 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module mcdonnell.dev/url-shortener + +go 1.26.1 diff --git a/main.go b/main.go new file mode 100644 index 0000000..11d7370 --- /dev/null +++ b/main.go @@ -0,0 +1,105 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "math/rand/v2" + "net/http" + "unicode/utf8" +) + +const charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" +const version = "0.0.1" + +var Table map[string]string = make(map[string]string) + +type Request struct { + URL string +} + +type Response struct { + Version string + Status string + Original string + New string +} + +func NewRequest(r *http.Request) (Request, error) { + var request Request + + err := json.NewDecoder(r.Body).Decode(&request) + + return request, err +} + +func log_header(r *http.Request) { + log.Println("Header {") + for k, v := range r.Header { + log.Printf("\t\"%s\": \"%s\"\n", k, v) + } + log.Println("}") +} + +func redirect(w http.ResponseWriter, r *http.Request) { + log.Println("Request for:", r.URL.String()) + log_header(r) + + _, i := utf8.DecodeRuneInString(r.URL.String()) + url := r.URL.String()[i:] + + original, ok := Table[url] + + if ok { + w.Header().Set("location", original) + http.Error(w, http.StatusText(301), 301) + } else { + http.Error(w, http.StatusText(404), 404) + } +} + +func shorten(w http.ResponseWriter, r *http.Request) { + log.Println("Request to Shorten") + log_header(r) + + request, err := NewRequest(r) + if err != nil { + http.Error(w, http.StatusText(500), 500) + } + + url := GenerateString(4) + _, ok := Table[url] + + for ok { + url = GenerateString(4) + _, ok = Table[url] + } + + Table[url] = request.URL + + response := Response{version, "ok", request.URL, url} + + data, err := json.Marshal(response) + if err != nil { + http.Error(w, http.StatusText(500), 500) + } + + fmt.Fprintf(w, "%s", string(data)) +} + +func GenerateString(size uint) string { + buffer := make([]rune, size) + + for i := uint(0); i < size; i++ { + index := rand.Uint() % uint(len(charSet)) + buffer[i] = []rune(charSet)[index] + } + + return string(buffer) +} + +func main() { + http.HandleFunc("/shorten", shorten) + http.HandleFunc("/", redirect) + log.Fatal(http.ListenAndServe(":8000", nil)) +} diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..c2cc156 --- /dev/null +++ b/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +RES=$(curl -X 'POST' \ + -H 'accept: application/json' \ + -H 'Content-Type: application/json' \ + --data '{ "URL": "https://git.mcdonnell.dev/" }' \ + http://localhost:8000/shorten 2>/dev/null) +printf '%s\n' "$(echo "$RES" | json_pp)" + -- cgit v1.2.3