Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
quockhanh | c36e227133 |
|
@ -15,7 +15,7 @@
|
||||||
**/bin
|
**/bin
|
||||||
**/charts
|
**/charts
|
||||||
**/docker-compose*
|
**/docker-compose*
|
||||||
**/Dockerfile*
|
Dockerfile
|
||||||
**/node_modules
|
**/node_modules
|
||||||
**/npm-debug.log
|
**/npm-debug.log
|
||||||
**/obj
|
**/obj
|
||||||
|
@ -28,7 +28,7 @@ out/
|
||||||
**/.vagrant/
|
**/.vagrant/
|
||||||
.idea/
|
.idea/
|
||||||
local/data/
|
local/data/
|
||||||
.env
|
|
||||||
|
|
||||||
|
.env
|
||||||
.terraform
|
.terraform
|
||||||
*tfstate*
|
*tfstate*
|
6
.env
6
.env
|
@ -1,6 +0,0 @@
|
||||||
APP_ADDRESS = "localhost"
|
|
||||||
APP_PORT = "3000"
|
|
||||||
|
|
||||||
REDIS_ADDRESS="localhost"
|
|
||||||
REDIS_PORT="6379"
|
|
||||||
REDIS_PASSWORD = "110502"
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
FROM golang:1.19.2-alpine3.16 as builder
|
||||||
|
COPY . /app
|
||||||
|
WORKDIR /app
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
|
||||||
|
go build
|
||||||
|
|
||||||
|
FROM scratch
|
||||||
|
|
||||||
|
CMD ["/app"]
|
|
@ -1,4 +0,0 @@
|
||||||
FROM ubuntu:latest
|
|
||||||
LABEL authors="nqkhanh2003"
|
|
||||||
|
|
||||||
ENTRYPOINT ["top", "-b"]
|
|
18
go.mod
18
go.mod
|
@ -3,20 +3,6 @@ module url-shortener
|
||||||
go 1.23.1
|
go 1.23.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0
|
github.com/lib/pq v1.10.9
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/sony/sonyflake v1.2.0
|
||||||
github.com/sirupsen/logrus v1.9.3
|
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
|
|
||||||
google.golang.org/protobuf v1.34.2
|
|
||||||
)
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
|
||||||
golang.org/x/sys v0.21.0 // indirect
|
|
||||||
golang.org/x/text v0.17.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
|
||||||
google.golang.org/grpc v1.64.1 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
50
go.sum
50
go.sum
|
@ -1,46 +1,4 @@
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/sony/sonyflake v1.2.0 h1:Pfr3A+ejSg+0SPqpoAmQgEtNDAhc2G1SUYk205qVMLQ=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/sony/sonyflake v1.2.0/go.mod h1:LORtCywH/cq10ZbyfhKrHYgAUGH7mOBa76enV9txy/Y=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
|
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
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/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
|
||||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
|
||||||
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
|
||||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
|
||||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
|
||||||
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
|
||||||
google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA=
|
|
||||||
google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0=
|
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
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=
|
|
||||||
|
|
71
main.go
71
main.go
|
@ -1,45 +1,80 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/joho/godotenv"
|
"github.com/sony/sonyflake"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ShorterRequest struct {
|
var sf *sonyflake.Sonyflake
|
||||||
Url string
|
|
||||||
}
|
const (
|
||||||
|
base62 = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
envError := godotenv.Load()
|
db := connectDB()
|
||||||
if envError != nil {
|
router := http.NewServeMux()
|
||||||
panic("Error loading .env file")
|
port := os.Getenv("APP_PORT")
|
||||||
|
var st sonyflake.Settings
|
||||||
|
sf = sonyflake.NewSonyflake(st)
|
||||||
|
if sf == nil {
|
||||||
|
panic("Sonyflake not created")
|
||||||
}
|
}
|
||||||
|
|
||||||
router := http.NewServeMux()
|
|
||||||
|
|
||||||
router.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
|
router.HandleFunc("GET /", func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte("Hello World"))
|
path := r.URL.Path
|
||||||
|
code := path[1:]
|
||||||
|
if code == "" {
|
||||||
|
http.Error(w, "Page not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
url, err := getUrlByCode(db, code)
|
||||||
|
if url == "" || err != nil {
|
||||||
|
http.Error(w, "Page not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, url, http.StatusFound)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.HandleFunc("POST /shorten", func(w http.ResponseWriter, r *http.Request) {
|
router.HandleFunc("POST /shorten", func(w http.ResponseWriter, r *http.Request) {
|
||||||
requestData := ShorterRequest{}
|
originalURL := r.FormValue("url")
|
||||||
err := json.NewDecoder(r.Body).Decode(&requestData)
|
if originalURL == "" {
|
||||||
|
http.Error(w, "URL parameter is missing", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
codeExisted, err := checkURLAlreadyExists(db, originalURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
log.Fatalf("Error checking URL exists: %v\n", err)
|
||||||
panic(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Write([]byte(requestData.Url))
|
if codeExisted == "" {
|
||||||
|
code, err := insertURL(db, originalURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Error when insert URL: %v\n", err)
|
||||||
|
}
|
||||||
|
_, writeErr := w.Write([]byte(fmt.Sprintf("%s/%s", r.Host, code)))
|
||||||
|
if writeErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, writeErr := w.Write([]byte(fmt.Sprintf("%s/%s", r.Host, codeExisted)))
|
||||||
|
if writeErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
server := http.Server{Addr: ":3000", Handler: router}
|
server := http.Server{Addr: fmt.Sprintf(":%s", port), Handler: router}
|
||||||
|
|
||||||
fmt.Println("URL Shortener is running on :3000")
|
fmt.Println(fmt.Sprintf("App is running in port: %s", port))
|
||||||
|
|
||||||
err := server.ListenAndServe()
|
err := server.ListenAndServe()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package configs
|
|
||||||
|
|
||||||
type (
|
|
||||||
App struct {
|
|
||||||
Name string `env-required:"true" yaml:"name" env:"APP_NAME"`
|
|
||||||
Version string `env-required:"true" yaml:"version" env:"APP_VERSION"`
|
|
||||||
}
|
|
||||||
|
|
||||||
HTTP struct {
|
|
||||||
Host string `env-required:"true" yaml:"host" env:"HTTP_HOST"`
|
|
||||||
Port int `env-required:"true" yaml:"port" env:"HTTP_PORT"`
|
|
||||||
}
|
|
||||||
|
|
||||||
Log struct {
|
|
||||||
Level string `env-required:"true" yaml:"log_level" env:"LOG_LEVEL"`
|
|
||||||
}
|
|
||||||
)
|
|
|
@ -1 +0,0 @@
|
||||||
package consumer
|
|
|
@ -1 +0,0 @@
|
||||||
package consumer
|
|
|
@ -1 +0,0 @@
|
||||||
package consumer
|
|
|
@ -1 +0,0 @@
|
||||||
package kafka
|
|
|
@ -1 +0,0 @@
|
||||||
package producer
|
|
|
@ -1 +0,0 @@
|
||||||
package producer
|
|
|
@ -1 +0,0 @@
|
||||||
package producer
|
|
|
@ -1,79 +0,0 @@
|
||||||
package logger
|
|
||||||
|
|
||||||
// refs:
|
|
||||||
// https://josephwoodward.co.uk/2022/11/slog-structured-logging-proposal
|
|
||||||
// https://thedevelopercafe.com/articles/logging-in-go-with-slog-a7bb489755c2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LogrusHandler struct {
|
|
||||||
logger *logrus.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLogrusHandler(logger *logrus.Logger) *LogrusHandler {
|
|
||||||
return &LogrusHandler{
|
|
||||||
logger: logger,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConvertLogLevel(level string) logrus.Level {
|
|
||||||
var l logrus.Level
|
|
||||||
|
|
||||||
switch strings.ToLower(level) {
|
|
||||||
case "error":
|
|
||||||
l = logrus.ErrorLevel
|
|
||||||
case "warm":
|
|
||||||
l = logrus.WarnLevel
|
|
||||||
case "info":
|
|
||||||
l = logrus.InfoLevel
|
|
||||||
case "debug":
|
|
||||||
l = logrus.DebugLevel
|
|
||||||
default:
|
|
||||||
l = logrus.InfoLevel
|
|
||||||
}
|
|
||||||
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *LogrusHandler) Enabled(_ slog.Level) bool {
|
|
||||||
// support all logging levels
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *LogrusHandler) Handle(rec slog.Record) error {
|
|
||||||
fields := make(map[string]interface{}, rec.NumAttrs())
|
|
||||||
|
|
||||||
rec.Attrs(func(a slog.Attr) {
|
|
||||||
fields[a.Key] = a.Value.Any()
|
|
||||||
})
|
|
||||||
|
|
||||||
entry := h.logger.WithFields(fields)
|
|
||||||
|
|
||||||
switch rec.Level {
|
|
||||||
case slog.DebugLevel:
|
|
||||||
entry.Debug(rec.Message)
|
|
||||||
case slog.InfoLevel.Level():
|
|
||||||
entry.Info(rec.Message)
|
|
||||||
case slog.WarnLevel:
|
|
||||||
entry.Warn(rec.Message)
|
|
||||||
case slog.ErrorLevel:
|
|
||||||
entry.Error(rec.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *LogrusHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
|
||||||
// not implemented for brevity
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *LogrusHandler) WithGroup(name string) slog.Handler {
|
|
||||||
// not implemented for brevity
|
|
||||||
return h
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package postgres
|
|
||||||
|
|
||||||
import "database/sql"
|
|
||||||
|
|
||||||
type DBEngine interface {
|
|
||||||
GetDB() *sql.DB
|
|
||||||
Configure(...Option) DBEngine
|
|
||||||
Close()
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package postgres
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type Option func(*postgres)
|
|
||||||
|
|
||||||
func ConnAttempts(attempts int) Option {
|
|
||||||
return func(p *postgres) {
|
|
||||||
p.connAttempts = attempts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ConnTimeout(timeout time.Duration) Option {
|
|
||||||
return func(p *postgres) {
|
|
||||||
p.connTimeout = timeout
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/exp/slog"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
_defaultConnAttempts = 3
|
|
||||||
_defaultConnTimeout = time.Second
|
|
||||||
)
|
|
||||||
|
|
||||||
type DBConnString string
|
|
||||||
|
|
||||||
type postgres struct {
|
|
||||||
connAttempts int
|
|
||||||
connTimeout time.Duration
|
|
||||||
|
|
||||||
db *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ DBEngine = (*postgres)(nil)
|
|
||||||
|
|
||||||
func NewPostgresDB(url DBConnString) (DBEngine, error) {
|
|
||||||
slog.Info("CONN", "connect string", url)
|
|
||||||
|
|
||||||
pg := &postgres{
|
|
||||||
connAttempts: _defaultConnAttempts,
|
|
||||||
connTimeout: _defaultConnTimeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
for pg.connAttempts > 0 {
|
|
||||||
pg.db, err = sql.Open("postgres", string(url))
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Postgres is trying to connect, attempts left: %d", pg.connAttempts)
|
|
||||||
|
|
||||||
time.Sleep(pg.connTimeout)
|
|
||||||
|
|
||||||
pg.connAttempts--
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.Info("📰 connected to postgresdb 🎉")
|
|
||||||
|
|
||||||
return pg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgres) Configure(opts ...Option) DBEngine {
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgres) GetDB() *sql.DB {
|
|
||||||
return p.db
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgres) Close() {
|
|
||||||
if p.db != nil {
|
|
||||||
p.db.Close()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package utils
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
|
|
||||||
func IsRunningInContainer() bool {
|
|
||||||
if _, err := os.Stat("/.dockerenv"); err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DbConfig struct {
|
||||||
|
dbName string
|
||||||
|
dbUser string
|
||||||
|
dbPassword string
|
||||||
|
dbHost string
|
||||||
|
dbPort string
|
||||||
|
}
|
||||||
|
|
||||||
|
func connectDB() *sql.DB {
|
||||||
|
dbConfig := &DbConfig{
|
||||||
|
dbName: os.Getenv("DB_NAME"),
|
||||||
|
dbUser: os.Getenv("DB_USER"),
|
||||||
|
dbHost: os.Getenv("DB_HOST"),
|
||||||
|
dbPort: os.Getenv("DB_PORT"),
|
||||||
|
dbPassword: os.Getenv("DB_PASSWORD"),
|
||||||
|
}
|
||||||
|
|
||||||
|
connectStr := fmt.Sprintf("user=%s password=%s dbname=%s host=%s port=%s sslmode=disable", dbConfig.dbUser, dbConfig.dbPassword, dbConfig.dbName, dbConfig.dbHost, dbConfig.dbPort)
|
||||||
|
|
||||||
|
db, err := sql.Open("postgres", connectStr)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error connecting to database", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to connect to the database:", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Connected to the database")
|
||||||
|
|
||||||
|
_, tbErr := db.Exec("CREATE TABLE IF NOT EXISTS urls (id bigint NOT NULL PRIMARY KEY , url TEXT NOT NULL, code varchar(18) NULL, createdAt TIMESTAMP DEFAULT NOW(), updatedAt TIMESTAMP DEFAULT NOW())")
|
||||||
|
if tbErr != nil {
|
||||||
|
log.Fatal("Error creating table:", tbErr)
|
||||||
|
}
|
||||||
|
fmt.Printf("Table '%s' created successfully.\n", "urls")
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
func insertURL(db *sql.DB, url string) (string, error) {
|
||||||
|
id := generateNewID()
|
||||||
|
code := intToBase62(id)
|
||||||
|
_, err := db.Exec("INSERT INTO urls(id, url, code) VALUES ($1, $2, $3)", id, url, code)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error inserting url:", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return code, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkURLAlreadyExists(db *sql.DB, url string) (string, error) {
|
||||||
|
var code string
|
||||||
|
rows, err := db.Query(`SELECT code from urls where url=$1`, url)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Error querying rows:", err)
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
for rows.Next() {
|
||||||
|
err := rows.Scan(&code)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return code, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUrlByCode(db *sql.DB, code string) (string, error) {
|
||||||
|
var url string
|
||||||
|
rows, err := db.Query("SELECT url FROM urls WHERE code=$1", code)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
err = rows.Scan(&url)
|
||||||
|
return url, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", errors.New("code not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateNewID() uint64 {
|
||||||
|
id, err := sf.NextID()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func intToBase62(n uint64) string {
|
||||||
|
if n == 0 {
|
||||||
|
return string(base62[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []byte
|
||||||
|
for n > 0 {
|
||||||
|
result = append(result, base62[n%62])
|
||||||
|
n /= 62
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
result[i], result[j] = result[j], result[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(result)
|
||||||
|
}
|
|
@ -1,7 +0,0 @@
|
||||||
package tools
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
|
|
||||||
_ "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
|
|
||||||
_ "google.golang.org/protobuf/cmd/protoc-gen-go"
|
|
||||||
)
|
|
Loading…
Reference in New Issue