summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacob McDonnell <jacob@jacobmcdonnell.com>2026-04-19 19:23:05 -0400
committerJacob McDonnell <jacob@jacobmcdonnell.com>2026-04-19 19:23:05 -0400
commit9bc0c33a952dc906344964890bce58761d6ce2c7 (patch)
treec2ec7bdf4369f97010f3496bd7d85c7da83abb2b
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.
-rw-r--r--go.mod3
-rw-r--r--main.go105
-rwxr-xr-xtest.sh9
3 files changed, 117 insertions, 0 deletions
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)"
+