From 94f996c10d5c157a5b0124f8f8eedc10e8cc3a93 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Sat, 19 Mar 2022 17:47:01 -0700 Subject: [PATCH] progress --- handler.go | 11 +++++ links/db.go | 49 +++++++++++++++++++ links/url.go | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 189 insertions(+) create mode 100644 handler.go create mode 100644 links/db.go create mode 100644 links/url.go diff --git a/handler.go b/handler.go new file mode 100644 index 0000000..db9a674 --- /dev/null +++ b/handler.go @@ -0,0 +1,11 @@ +package main + +import "net/http" + +func Path(w http.ResponseWriter, r *http.Request) { + +} + +func Index(w http.ResponseWriter, r *http.Request) { + +} diff --git a/links/db.go b/links/db.go new file mode 100644 index 0000000..dcd31d1 --- /dev/null +++ b/links/db.go @@ -0,0 +1,49 @@ +package links + +import ( + "context" + "fmt" + + "git.sr.ht/~kisom/goutils/config" + "github.com/jackc/pgx/v4/pgxpool" +) + +const ( + // The keys are all set up as constants to avoid typos and + // runtime surprises. I've prefixed the names with k, which + // perhaps counterintuitively is not for 'kyle' but for 'key'. + kDriver = "DB_ENGINE" + kName = "DB_NAME" + kUser = "DB_USER" + kPass = "DB_PASSWORD" + kHost = "DB_HOST" + kPort = "DB_PORT" + + defaultDriver = "postgres" + defaultName = "kls" + defaultUser = "kls" + defaultPort = "5432" +) + +func connString(user, pass, host, name, port string) string { + return fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=verify-full", + user, pass, host, port, name) +} + +// Connect will try to open a connection to the database using the +// standard configuration vars, etc. +func Connect(ctx context.Context) (*pgxpool.Pool, error) { + driver := config.GetDefault(kDriver, defaultDriver) + if driver != defaultDriver { + return nil, errors.F("database: unsupported driver %s", driver) + } + + user := config.GetDefault(kUser, defaultUser) + pass := config.Get(kPass) + host := config.Get(kHost) + name := config.GetDefault(kName, defaultName) + port := config.GetDefault(kPort, defaultPort) + cstr := connString(user, pass, host, name, port) + + return pgxpool.Connect(ctx, cstr) +} diff --git a/links/url.go b/links/url.go new file mode 100644 index 0000000..4ea6a19 --- /dev/null +++ b/links/url.go @@ -0,0 +1,129 @@ +package links + +import ( + "context" + "net/url" + "time" + + "github.com/Masterminds/squirrel" + "github.com/google/uuid" + "github.com/jackc/pgx/v4" + "github.com/jackc/pgx/v4/pgxpool" +) + +var psql = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar) + +type URL struct { + ID string + URL string + NURL string // normalized URL + Short string + CreatedAt time.Time +} + +func (u *URL) StoreURL(ctx context.Context, db *pgxpool.Pool) error { + stmt := psql.Insert("urls"). + Columns("id", "url", "nurl", "short", "created_at"). + Values(u.ID, u.URL, u.NURL, u.Short, u.Short, u.CreatedAt) + query, args, err := stmt.ToSql() + if err != nil { + return err + } + + _, err := db.Exec(ctx, query, args...) + return err +} + +// Normalize cleans the URL to only the parts we care about +func Normalize(u *url.URL) *url.URL { + norm := &url.URL{ + Scheme: u.Scheme, + Host: u.Host, + Path: u.Path, + RawPath: u.RawPath, + } + return norm +} + +func NormalizeString(s string) (string, error) { + u, err := url.Parse(s) + if err != nil { + return "", err + } + + u = Normalize(u) + return u.String(), nil +} + +func New(u *url.URL) *URL { + link := URL{ + ID: uuid.NewString(), + URL: u.String(), + NURL: Normalize(u).String(), + Short: GenCode(), + CreatedAt: time.Now(), + } + + return link +} + +func FromString(s string) (*URL, error) { + u, err := url.Parse(s) + if err != nil { + return nil, err + } + return New(u), nil +} + +func Lookup(ctx context.Context, db *pgxpool.Pool, s string) (string, error) { + u, err := url.Parse(s) + if err != nil { + return "", nil + } + + u = Normalize(u) + + stmt := psql.Select("short").From("urls"). + Where(squirrel.Eq{"nurl": u.String()}) + query, args, err := stmt.ToSql() + if err != nil { + return "", err + } + + row := db.QueryRow(ctx, query, args...) + + var short string + err := row.Scan(&short) + if err != nil { + return nil, err + } + + return short, nil +} + +func StoreURL(ctx context.Context, db *pgxpool.Pool, s string) (string, error) { + short, err := Lookup(ctx, db, s) + if err == nil { + return short, nil + } + + if err != pgx.ErrNoRows { + return "", err + } + + u, err := FromString(s) + if err != nil { + return "", err + } + + err = u.Store(ctx, db) + if err != nil { + return "", err + } + + return u.Short, nil +} + +func RetrieveURL(ctx context.Context, db *pgxpool.Pool, short string) (string, error) { + +}