package links import ( "context" "log" "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) Store(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.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 "", 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) { log.Printf("look up url for short code %s", short) stmt := psql.Select("url").From("urls"). Where(squirrel.Eq{"short": short}) query, args, err := stmt.ToSql() if err != nil { return "", err } row := db.QueryRow(ctx, query, args...) var url string err = row.Scan(&url) if err != nil { return "", err } return url, nil }