mirror of
https://github.com/arkorty/Reduce.git
synced 2026-03-17 16:41:42 +00:00
overhaul
This commit is contained in:
11
backend/.gitignore
vendored
Normal file
11
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# Go
|
||||
bin/
|
||||
pkg/
|
||||
*.exe
|
||||
*.dll
|
||||
*.so
|
||||
*.test
|
||||
*.prof
|
||||
.env
|
||||
# Local database (if applicable)
|
||||
reduce.db
|
||||
@@ -1,23 +1,23 @@
|
||||
# Use the official Golang image
|
||||
FROM golang:1.20-alpine
|
||||
FROM golang:1.21-alpine AS builder
|
||||
|
||||
RUN apk add --no-cache gcc musl-dev
|
||||
|
||||
# Set the Current Working Directory inside the container
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod and sum files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
|
||||
RUN go mod download
|
||||
|
||||
# Copy the source code into the container
|
||||
COPY . .
|
||||
|
||||
# Build the Go app
|
||||
RUN go build -o main .
|
||||
RUN CGO_ENABLED=1 \
|
||||
CGO_CFLAGS="-D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE" \
|
||||
CGO_LDFLAGS="-lm" \
|
||||
go build -ldflags="-s -w" .
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
COPY --from=builder /app/reduce /reduce
|
||||
|
||||
# Expose port 8080 to the outside world
|
||||
EXPOSE 8080
|
||||
|
||||
# Command to run the executable
|
||||
CMD ["./main"]
|
||||
CMD ["/reduce"]
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
module url-shortener-backend
|
||||
module reduce
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/jinzhu/gorm v1.9.16
|
||||
github.com/labstack/echo v3.3.10+incompatible
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/jinzhu/gorm v1.9.16 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/labstack/echo v3.3.10+incompatible // indirect
|
||||
github.com/labstack/echo/v4 v4.12.0 // indirect
|
||||
github.com/labstack/gommon v0.4.2 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
golang.org/x/crypto v0.22.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/sys v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
|
||||
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
||||
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
||||
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
@@ -29,9 +29,10 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
@@ -57,5 +58,4 @@ golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
|
||||
@@ -4,61 +4,18 @@ import (
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
"fmt"
|
||||
"os"
|
||||
"errors"
|
||||
|
||||
"strconv"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/postgres"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
|
||||
"github.com/labstack/echo"
|
||||
"github.com/labstack/echo/middleware"
|
||||
)
|
||||
|
||||
var db *gorm.DB
|
||||
|
||||
type URL struct {
|
||||
ID string `gorm:"primary_key" json:"id"`
|
||||
LongURL string `json:"long_url"`
|
||||
ShortURL string `json:"short_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
dbHost := os.Getenv("DB_HOST")
|
||||
dbPort := os.Getenv("DB_PORT")
|
||||
dbUser := os.Getenv("DB_USER")
|
||||
dbName := os.Getenv("DB_NAME")
|
||||
dbPassword := os.Getenv("DB_PASSWORD")
|
||||
|
||||
dsn := "host=" + dbHost + " port=" + dbPort + " user=" + dbUser + " dbname=" + dbName + " password=" + dbPassword + " sslmode=disable"
|
||||
|
||||
maxRetries := 5
|
||||
retryDelay := 2 * time.Second
|
||||
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
db, err = gorm.Open("postgres", dsn)
|
||||
if err == nil {
|
||||
// Connection successful, break out of the loop
|
||||
break
|
||||
}
|
||||
|
||||
// Log the error and wait before retrying
|
||||
fmt.Printf("Failed to connect to database (attempt %d/%d): %v\n", i+1, maxRetries, err)
|
||||
time.Sleep(retryDelay)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Panic if the final attempt also fails
|
||||
panic(fmt.Sprintf("Failed to connect to database after %d attempts: %v", maxRetries, err))
|
||||
}
|
||||
|
||||
// Auto-migrate database
|
||||
db.AutoMigrate(&URL{})
|
||||
}
|
||||
|
||||
func generateRandomString(length int) string {
|
||||
func codegen(length int) string {
|
||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
|
||||
bytes := make([]byte, length)
|
||||
for i := range bytes {
|
||||
@@ -70,7 +27,7 @@ func generateRandomString(length int) string {
|
||||
func shortenURL(c echo.Context) error {
|
||||
// Define a struct for binding the request body
|
||||
type RequestBody struct {
|
||||
LongURL string `json:"long_url"`
|
||||
LURL string `json:"lurl"`
|
||||
BaseURL string `json:"base_url"` // Expect base URL in the request
|
||||
}
|
||||
|
||||
@@ -90,23 +47,31 @@ func shortenURL(c echo.Context) error {
|
||||
}
|
||||
|
||||
// Check if the long URL already exists in the database
|
||||
var existingURL URL
|
||||
if err := db.Where("long_url = ?", reqBody.LongURL).First(&existingURL).Error; err == nil {
|
||||
var existingURL CodeURLMap
|
||||
if err := db.Where("lurl = ?", reqBody.LURL).First(&existingURL).Error; err == nil {
|
||||
// If the long URL exists, return the existing short URL
|
||||
return c.JSON(http.StatusOK, existingURL)
|
||||
return c.JSON(http.StatusOK, map[string]string{
|
||||
"surl": reqBody.BaseURL + "/" + existingURL.Code,
|
||||
})
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
// If there's an error other than record not found, return an error
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to check existing URL")
|
||||
}
|
||||
|
||||
// Generate a unique ID
|
||||
id := generateRandomString(6)
|
||||
// Generate a unique code
|
||||
codelen := 6
|
||||
if os.Getenv("CODE_LENGTH") != "" && os.Getenv("CODE_LENGTH") != "0" {
|
||||
t, err := strconv.Atoi(os.Getenv("CODE_LENGTH"))
|
||||
if err == nil {
|
||||
codelen = t
|
||||
}
|
||||
}
|
||||
code := codegen(codelen)
|
||||
|
||||
// Create URL record
|
||||
url := &URL{
|
||||
ID: id,
|
||||
LongURL: reqBody.LongURL,
|
||||
ShortURL: reqBody.BaseURL + "/" + id,
|
||||
url := &CodeURLMap{
|
||||
Code: code,
|
||||
LURL: reqBody.LURL,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
@@ -115,17 +80,19 @@ func shortenURL(c echo.Context) error {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create URL record")
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusCreated, url)
|
||||
return c.JSON(http.StatusCreated, map[string]string{
|
||||
"surl": reqBody.BaseURL + "/" + code,
|
||||
})
|
||||
}
|
||||
|
||||
func fetchLongURL(c echo.Context) error {
|
||||
id := c.Param("id")
|
||||
var url URL
|
||||
if err := db.Where("id = ?", id).First(&url).Error; err != nil {
|
||||
func fetchLURL(c echo.Context) error {
|
||||
code := c.Param("code")
|
||||
var url CodeURLMap
|
||||
if err := db.Where("code = ?", code).First(&url).Error; err != nil {
|
||||
log.Println("Error retrieving URL:", err)
|
||||
return echo.NewHTTPError(http.StatusNotFound, "URL not found")
|
||||
}
|
||||
return c.JSON(http.StatusOK, map[string]string{"long_url": url.LongURL})
|
||||
return c.JSON(http.StatusOK, map[string]string{"lurl": url.LURL})
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +115,7 @@ func main() {
|
||||
})
|
||||
|
||||
e.POST("/reduce/shorten", shortenURL)
|
||||
e.GET("/reduce/:id", fetchLongURL)
|
||||
e.GET("/reduce/:code", fetchLURL)
|
||||
|
||||
e.Logger.Fatal(e.Start(":8080"))
|
||||
}
|
||||
|
||||
34
backend/store.go
Normal file
34
backend/store.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
|
||||
"os"
|
||||
)
|
||||
|
||||
type CodeURLMap struct {
|
||||
Code string `gorm:"primary_key" json:"code"`
|
||||
LURL string `json:"lurl" gorm:"column:lurl"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
var db *gorm.DB
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
dbPath := os.Getenv("DB_PATH")
|
||||
if dbPath == "" {
|
||||
dbPath = "reduce.db"
|
||||
}
|
||||
|
||||
db, err = gorm.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to connect to database: %v", err))
|
||||
}
|
||||
|
||||
// Auto-migrate database
|
||||
db.AutoMigrate(&CodeURLMap{})
|
||||
}
|
||||
Reference in New Issue
Block a user