mirror of
https://github.com/aykhans/oh-my-url.git
synced 2025-04-19 03:09:42 +00:00
🎉 first commit
This commit is contained in:
commit
f59cb4f927
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.env
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
FROM golang:1.21.6-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /ohmyurl
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY ./app ./app
|
||||||
|
|
||||||
|
RUN go build -o ./ohmyurl ./app/main.go
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
COPY --from=builder /ohmyurl/app/templates/ /app/templates/
|
||||||
|
COPY --from=builder /ohmyurl/ohmyurl /ohmyurl
|
||||||
|
|
||||||
|
ENTRYPOINT ["/ohmyurl"]
|
10
app/config/.env.example
Normal file
10
app/config/.env.example
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
DB=postgres
|
||||||
|
POSTGRES_USER=
|
||||||
|
POSTGRES_PASSWORD=
|
||||||
|
POSTGRES_DB=
|
||||||
|
POSTGRES_HOST=ohmyurl-postgresql
|
||||||
|
POSTGRES_PORT=5432
|
||||||
|
LISTEN_PORT_CREATE=8080
|
||||||
|
LISTEN_PORT_FORWARD=8081
|
||||||
|
FORWARD_DOMAIN=http://localhost/
|
||||||
|
CREATE_DOMAIN=http://127.0.0.1
|
78
app/config/settings.go
Normal file
78
app/config/settings.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBName string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Postgres DBName = "postgres"
|
||||||
|
MongoDB DBName = "mongodb"
|
||||||
|
Cassandra DBName = "cassandra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppConfig struct {
|
||||||
|
LISTEN_PORT_CREATE string
|
||||||
|
LISTEN_PORT_FORWARD string
|
||||||
|
FORWARD_DOMAIN string
|
||||||
|
CREATE_DOMAIN string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostgresConfig struct {
|
||||||
|
USER string
|
||||||
|
PASSWORD string
|
||||||
|
HOST string
|
||||||
|
PORT string
|
||||||
|
DBNAME string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAppConfig() *AppConfig {
|
||||||
|
return &AppConfig{
|
||||||
|
LISTEN_PORT_CREATE: GetEnvOrPanic("LISTEN_PORT_CREATE"),
|
||||||
|
LISTEN_PORT_FORWARD: GetEnvOrPanic("LISTEN_PORT_FORWARD"),
|
||||||
|
FORWARD_DOMAIN: GetEnvOrPanic("FORWARD_DOMAIN"),
|
||||||
|
CREATE_DOMAIN: GetEnvOrPanic("CREATE_DOMAIN"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPostgresConfig() *PostgresConfig {
|
||||||
|
return &PostgresConfig{
|
||||||
|
USER: GetEnvOrPanic("POSTGRES_USER"),
|
||||||
|
PASSWORD: GetEnvOrPanic("POSTGRES_PASSWORD"),
|
||||||
|
HOST: GetEnvOrPanic("POSTGRES_HOST"),
|
||||||
|
PORT: GetEnvOrDefault("POSTGRES_PORT", "5432"),
|
||||||
|
DBNAME: GetEnvOrPanic("POSTGRES_DB"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDB() DBName {
|
||||||
|
dbName := strings.ToLower(GetEnvOrPanic("DB"))
|
||||||
|
switch dbName {
|
||||||
|
case "postgres":
|
||||||
|
return Postgres
|
||||||
|
case "mongodb":
|
||||||
|
return MongoDB
|
||||||
|
case "cassandra":
|
||||||
|
return Cassandra
|
||||||
|
default:
|
||||||
|
panic("Unknown database")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEnvOrDefault(key, defaultValue string) string {
|
||||||
|
value := os.Getenv(key)
|
||||||
|
if value == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetEnvOrPanic(key string) string {
|
||||||
|
value := os.Getenv(key)
|
||||||
|
if value == "" {
|
||||||
|
panic("Environment variable " + key + " is not set")
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
48
app/db/base.go
Normal file
48
app/db/base.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/aykhans/oh-my-url/app/config"
|
||||||
|
gormPostgres "gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DB interface {
|
||||||
|
Init()
|
||||||
|
CreateURL(url string) (string, error)
|
||||||
|
GetURL(key string) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDB() DB {
|
||||||
|
db := config.GetDB()
|
||||||
|
switch db {
|
||||||
|
case "postgres":
|
||||||
|
postgresConf := config.GetPostgresConfig()
|
||||||
|
dsn := fmt.Sprintf(
|
||||||
|
"host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=%s",
|
||||||
|
postgresConf.HOST,
|
||||||
|
postgresConf.USER,
|
||||||
|
postgresConf.PASSWORD,
|
||||||
|
postgresConf.DBNAME,
|
||||||
|
postgresConf.PORT,
|
||||||
|
"disable",
|
||||||
|
"UTC",
|
||||||
|
)
|
||||||
|
|
||||||
|
var db *gorm.DB
|
||||||
|
var err error
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
db, err = gorm.Open(gormPostgres.Open(dsn), &gorm.Config{})
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &Postgres{gormDB: db}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
1
app/db/cassandra.go
Normal file
1
app/db/cassandra.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package db
|
1
app/db/mongodb.go
Normal file
1
app/db/mongodb.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package db
|
72
app/db/postgres.go
Normal file
72
app/db/postgres.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "github.com/aykhans/oh-my-url/app/config"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Postgres struct {
|
||||||
|
gormDB *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type url struct {
|
||||||
|
ID uint `gorm:"primaryKey"`
|
||||||
|
Key string `gorm:"unique;not null;size:15;default:null"`
|
||||||
|
URL string `gorm:"not null;default:null"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Postgres) Init() {
|
||||||
|
err := p.gormDB.AutoMigrate(&url{})
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to migrate database")
|
||||||
|
}
|
||||||
|
tx := p.gormDB.Exec(urlKeyCreateTrigger)
|
||||||
|
if tx.Error != nil {
|
||||||
|
panic("failed to create trigger")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Postgres) CreateURL(mainUrl string) (string, error) {
|
||||||
|
url := url{URL: mainUrl}
|
||||||
|
tx := p.gormDB.Create(&url)
|
||||||
|
if tx.Error != nil {
|
||||||
|
return "", tx.Error
|
||||||
|
}
|
||||||
|
return url.Key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Postgres) GetURL(key string) (string, error) {
|
||||||
|
var result url
|
||||||
|
tx := p.gormDB.Where("key = ?", key).First(&result)
|
||||||
|
if tx.Error != nil {
|
||||||
|
return "", tx.Error
|
||||||
|
}
|
||||||
|
return result.URL, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlKeyCreateTrigger = `
|
||||||
|
CREATE OR REPLACE FUNCTION generate_url_key()
|
||||||
|
RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
key_characters TEXT := 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
||||||
|
key TEXT := '';
|
||||||
|
base INT := LENGTH(key_characters);
|
||||||
|
n INT := NEW.id;
|
||||||
|
BEGIN
|
||||||
|
WHILE n > 0 LOOP
|
||||||
|
n := n - 1;
|
||||||
|
key := SUBSTRING(key_characters FROM (n % base) + 1 FOR 1) || key;
|
||||||
|
n := n / base;
|
||||||
|
END LOOP;
|
||||||
|
NEW.key := key;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS trigger_generate_url_key on "public"."urls";
|
||||||
|
|
||||||
|
CREATE TRIGGER trigger_generate_url_key
|
||||||
|
BEFORE INSERT ON urls
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION generate_url_key();
|
||||||
|
`
|
27
app/http_handlers/base.go
Normal file
27
app/http_handlers/base.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package httpHandlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aykhans/oh-my-url/app/db"
|
||||||
|
"github.com/aykhans/oh-my-url/app/utils"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HandlerCreate struct {
|
||||||
|
DB db.DB
|
||||||
|
ForwardDomain string
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandlerForward struct {
|
||||||
|
DB db.DB
|
||||||
|
CreateDomain string
|
||||||
|
}
|
||||||
|
|
||||||
|
func FaviconHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.ServeFile(w, r, utils.GetTemplatePaths("favicon.ico")[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func InternalServerError(w http.ResponseWriter, err error) {
|
||||||
|
log.Fatal(err)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
}
|
75
app/http_handlers/url_create.go
Normal file
75
app/http_handlers/url_create.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package httpHandlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aykhans/oh-my-url/app/utils"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
netUrl "net/url"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateData struct {
|
||||||
|
ShortedURL string
|
||||||
|
MainURL string
|
||||||
|
Error string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (hl *HandlerCreate) UrlCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.ParseFiles(utils.GetTemplatePaths("index.html")...)
|
||||||
|
if err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodGet:
|
||||||
|
err = tmpl.Execute(w, nil)
|
||||||
|
if err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case http.MethodPost:
|
||||||
|
url := r.FormValue("url")
|
||||||
|
urlRegex := regexp.MustCompile(`^(http|https)://[a-zA-Z0-9.-]+(?:\:[0-9]+)?(?:/[^\s]*)?$`)
|
||||||
|
isValidUrl := urlRegex.MatchString(url)
|
||||||
|
if !isValidUrl {
|
||||||
|
data := CreateData{
|
||||||
|
MainURL: url,
|
||||||
|
Error: "Invalid URL",
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key, err := hl.DB.CreateURL(url)
|
||||||
|
if err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shortedURL, err := netUrl.JoinPath(hl.ForwardDomain, key)
|
||||||
|
if err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data := CreateData{
|
||||||
|
ShortedURL: shortedURL,
|
||||||
|
MainURL: url,
|
||||||
|
}
|
||||||
|
err = tmpl.Execute(w, data)
|
||||||
|
if err != nil {
|
||||||
|
InternalServerError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
26
app/http_handlers/url_forward.go
Normal file
26
app/http_handlers/url_forward.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package httpHandlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (hl *HandlerForward) UrlForward(w http.ResponseWriter, r *http.Request) {
|
||||||
|
path := r.URL.Path
|
||||||
|
segments := strings.Split(path, "/")
|
||||||
|
if len(segments) > 2 {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
} else if segments[1] == "" {
|
||||||
|
http.Redirect(w, r, hl.CreateDomain, http.StatusMovedPermanently)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
key := segments[1]
|
||||||
|
url, err := hl.DB.GetURL(key)
|
||||||
|
if err != nil {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, url, http.StatusMovedPermanently)
|
||||||
|
}
|
37
app/main.go
Normal file
37
app/main.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/aykhans/oh-my-url/app/config"
|
||||||
|
"github.com/aykhans/oh-my-url/app/db"
|
||||||
|
"github.com/aykhans/oh-my-url/app/http_handlers"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config := config.GetAppConfig()
|
||||||
|
dbCreate := db.GetDB()
|
||||||
|
dbCreate.Init()
|
||||||
|
handlerCreate := httpHandlers.HandlerCreate{DB: dbCreate, ForwardDomain: config.FORWARD_DOMAIN}
|
||||||
|
urlCreateMux := http.NewServeMux()
|
||||||
|
urlCreateMux.HandleFunc("/", handlerCreate.UrlCreate)
|
||||||
|
urlCreateMux.HandleFunc("/favicon.ico", httpHandlers.FaviconHandler)
|
||||||
|
|
||||||
|
dbRead := db.GetDB()
|
||||||
|
handlerForward := httpHandlers.HandlerForward{DB: dbRead, CreateDomain: config.CREATE_DOMAIN}
|
||||||
|
urlReadMux := http.NewServeMux()
|
||||||
|
urlReadMux.HandleFunc("/", handlerForward.UrlForward)
|
||||||
|
urlReadMux.HandleFunc("/favicon.ico", httpHandlers.FaviconHandler)
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(2)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
panic(http.ListenAndServe(":"+config.LISTEN_PORT_CREATE, urlCreateMux))
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
panic(http.ListenAndServe(":"+config.LISTEN_PORT_FORWARD, urlReadMux))
|
||||||
|
}()
|
||||||
|
wg.Wait()
|
||||||
|
}
|
BIN
app/templates/favicon.ico
Normal file
BIN
app/templates/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
82
app/templates/index.html
Normal file
82
app/templates/index.html
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Oh My URL</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 50px auto;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="url"] {
|
||||||
|
padding: 8px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 15px;
|
||||||
|
width: 20%;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: medium;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #0056b3;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>Oh My URL!</h1>
|
||||||
|
<form action="/" method="post">
|
||||||
|
{{ if .Error }}
|
||||||
|
<label for="urlInput" style="color: red;">{{ .Error }}</label>
|
||||||
|
{{ end }}
|
||||||
|
<input type="url" id="urlInput" name="url" placeholder="https://example.com/" value="{{ if .MainURL }}{{ .MainURL }}{{ end }}" required>
|
||||||
|
{{ if .ShortedURL }}
|
||||||
|
<label for="urlOutput">Shortened URL: <a href="{{.ShortedURL}}" target="_blank">{{.ShortedURL}}</a></label>
|
||||||
|
{{ end }}
|
||||||
|
<button type="submit">Get URL</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
26
app/utils/short_key.go
Normal file
26
app/utils/short_key.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const keyCharacters string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
|
||||||
|
func GenerateKey(n int) string {
|
||||||
|
var result strings.Builder
|
||||||
|
base := len(keyCharacters)
|
||||||
|
|
||||||
|
for n > 0 {
|
||||||
|
n--
|
||||||
|
result.WriteByte(keyCharacters[n%base])
|
||||||
|
n /= base
|
||||||
|
}
|
||||||
|
|
||||||
|
key := result.String()
|
||||||
|
runes := []rune(key)
|
||||||
|
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
runes[i], runes[j] = runes[j], runes[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(runes)
|
||||||
|
}
|
19
app/utils/templates.go
Normal file
19
app/utils/templates.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetTemplatePaths(filenames ...string) []string {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
templatePath := filepath.Join(dir, "app", "templates")
|
||||||
|
for i, filename := range filenames {
|
||||||
|
filenames[i] = filepath.Join(templatePath, filename)
|
||||||
|
}
|
||||||
|
return filenames
|
||||||
|
}
|
31
config/nginx/nginx.conf
Normal file
31
config/nginx/nginx.conf
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
upstream create_server {
|
||||||
|
server ohmyurl-web:8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
upstream forward_server {
|
||||||
|
server ohmyurl-web:8081;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name 127.0.0.1;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://create_server;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_redirect off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://forward_server;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_redirect off;
|
||||||
|
}
|
||||||
|
}
|
3
config/postgresql/.env.example
Normal file
3
config/postgresql/.env.example
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
POSTGRES_USER=
|
||||||
|
POSTGRES_PASSWORD=
|
||||||
|
POSTGRES_DB=
|
41
docker-compose-postgres.yml
Normal file
41
docker-compose-postgres.yml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
|
||||||
|
services:
|
||||||
|
ohmyurl-postgres:
|
||||||
|
container_name: "ohmyurl-postgresql"
|
||||||
|
image: postgres:16.1-alpine
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
env_file:
|
||||||
|
- ./config/postgresql/.env
|
||||||
|
volumes:
|
||||||
|
- ohmyurl_postgresqo_data:/var/lib/postgresql/data
|
||||||
|
init: true
|
||||||
|
|
||||||
|
ohmyurl-web:
|
||||||
|
container_name: "ohmyurl-web"
|
||||||
|
build: .
|
||||||
|
image: ohmyurl-web:1.1
|
||||||
|
env_file:
|
||||||
|
- ./app/config/.env
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "8081:8081"
|
||||||
|
depends_on:
|
||||||
|
- ohmyurl-postgres
|
||||||
|
init: true
|
||||||
|
|
||||||
|
ohmyurl-nginx:
|
||||||
|
container_name: "ohmyurl-nginx"
|
||||||
|
image: nginx:1.25.3-alpine
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
volumes:
|
||||||
|
- ./config/nginx/nginx.conf:/etc/nginx/conf.d/default.conf
|
||||||
|
depends_on:
|
||||||
|
- ohmyurl-web
|
||||||
|
init: true
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
ohmyurl_postgresqo_data:
|
22
go.mod
Normal file
22
go.mod
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
module github.com/aykhans/oh-my-url
|
||||||
|
|
||||||
|
go 1.21.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
gorm.io/driver/postgres v1.5.4
|
||||||
|
gorm.io/gorm v1.25.5
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/buraksezer/consistent v0.10.0 // indirect
|
||||||
|
github.com/cespare/xxhash v1.1.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.5.2 // indirect
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
golang.org/x/crypto v0.18.0 // indirect
|
||||||
|
golang.org/x/sync v0.6.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
)
|
50
go.sum
Normal file
50
go.sum
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
|
github.com/buraksezer/consistent v0.10.0 h1:hqBgz1PvNLC5rkWcEBVAL9dFMBWz6I0VgUCW25rrZlU=
|
||||||
|
github.com/buraksezer/consistent v0.10.0/go.mod h1:6BrVajWq7wbKZlTOUPs/XVfR8c0maujuPowduSpZqmw=
|
||||||
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
|
github.com/jackc/pgx/v5 v5.5.2 h1:iLlpgp4Cp/gC9Xuscl7lFL1PhhW+ZLtXZcrfCt4C3tA=
|
||||||
|
github.com/jackc/pgx/v5 v5.5.2/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||||
|
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
|
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.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||||
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
|
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
|
||||||
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/postgres v1.5.4 h1:Iyrp9Meh3GmbSuyIAGyjkN+n9K+GHX9b9MqsTL4EJCo=
|
||||||
|
gorm.io/driver/postgres v1.5.4/go.mod h1:Bgo89+h0CRcdA33Y6frlaHHVuTdOf87pmyzwW9C/BH0=
|
||||||
|
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||||
|
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
Loading…
x
Reference in New Issue
Block a user