package main import ( "encoding/json" "errors" "github.com/labstack/echo/v5" "github.com/pocketbase/dbx" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/models" "log" "net/http" "nurl/base58" _import "nurl/import" "os" "time" ) var ( Version string Build string Webhost = "https://nurl.co/" ) type Count struct { Count int `json:"count" db:"count"` } type UrlRequest struct { Url string `json:"url"` } type Data struct { Visits int64 `json:"visits" db:"visits"` LongUrl string `json:"long_url" db:"long_url"` CreatedAt time.Time `json:"created_at,omitempty" db:"created_at"` Id int64 `json:"_id" db:"_id"` } type ShortUrl struct { ShortUrl string `json:"shortUrl"` } func main() { log.Printf("GO-PB-NURL v%+v build %+v\n\n", Version, Build) app := pocketbase.New() app.OnRecordBeforeCreateRequest("urls").Add(func(e *core.RecordCreateEvent) error { log.Println("OnRecordBeforeCreate") count := Count{} err := app.Dao().DB().NewQuery("select count(*) as count from urls").One(&count) if err != nil { log.Println(err) } log.Println("count:", count.Count) count.Count++ e.Record.Set("_id", count.Count) log.Println("Record", e.Record) return nil }) app.OnAfterBootstrap().Add(func(e *core.BootstrapEvent) error { // log.Println(e.App) return nil collection, err := e.App.Dao().FindCollectionByNameOrId("urls") if err != nil { log.Println(err) return err } data := _import.DoImport() // log.Println("Import", data) for _, v := range data { log.Println("v", v) record := models.NewRecord(collection) record.Set("long_url", v.LongUrl) record.Set("visits", v.Visits) record.Set("_id", v.Id) record.Set("created_at", v.CreatedAt) log.Println(record) if err := app.Dao().SaveRecord(record); err != nil { log.Println("ERROR!! postshort save NewRecord") log.Println(err) // return err } } return nil }) // serves static files from the provided public dir (if exists) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS("./pb_public"), false)) e.Router.GET("/build/*", apis.StaticDirectoryHandler(os.DirFS("./pb_public/build"), false)) e.Router.GET("/favicon.png", func(c echo.Context) error { return c.File("/pb_public/favicon.png") }) e.Router.GET("/api/v1/list", func(c echo.Context) error { apis.ActivityLogger(app) getList() return nil }) e.Router.POST("/api/v1/shorten", func(c echo.Context) error { apis.ActivityLogger(app) postShort(c, app) return nil }) e.Router.GET("/:encoded", func(c echo.Context) error { apis.ActivityLogger(app) getEncoded(c, app) return nil }) e.Router.GET("/!/hb", func(c echo.Context) error { apis.ActivityLogger(app) return c.NoContent(200) }) return nil }) if err := app.Start(); err != nil { log.Fatal(err) } else { log.Println("Nurl is running...") } } func getList() { log.Println("Getting list") } func getIdByUrl(url string, app *pocketbase.PocketBase) (int64, error) { recordData := &Data{} log.Println("look for:", url) dberr := app.Dao().DB().Select("_id").From("urls").Where(dbx.NewExp("long_url is {:url}", dbx.Params{"url": url})).One(&recordData) log.Println(recordData) if dberr == nil { log.Println("Url found", recordData.Id) return recordData.Id, nil } else { return -1, dberr } } func saveUrl(url string, app *pocketbase.PocketBase) (string, error) { count := Count{} err := app.Dao().DB().NewQuery("select count(*) as count from urls").One(&count) if err != nil { log.Println(err) } count.Count += 2000 // Base everything from 2000 now count.Count++ collection, err := app.Dao().FindCollectionByNameOrId("urls") if err != nil { log.Println(err) return "", err } record := models.NewRecord(collection) record.Set("long_url", url) record.Set("visits", 0) record.Set("_id", count.Count) record.Set("created_at", time.Now()) if err := app.Dao().SaveRecord(record); err != nil { log.Println("ERROR!! postshort save NewRecord") log.Println(err) return "", err } shortCode := base58.Encode(count.Count) log.Println("Return", shortCode) return shortCode, nil } func postShort(c echo.Context, app *pocketbase.PocketBase) error { log.Println("Posting short") j := GetJSONRawBody(c) url := j.Url log.Println("url", url) if url == "" { return c.JSON(http.StatusBadRequest, "url is null") } id, err := getIdByUrl(url, app) if err != nil { log.Println("post getIdByUrl error", err) } else { log.Println("id:", id) if id != -1 { shortUrl := ShortUrl{} shortUrl.ShortUrl = Webhost + base58.Encode(int(id)) return c.JSON(http.StatusOK, shortUrl) } } // todo: check that the url doesn't already exist in the db and return it if it does // todo: breakout working in other functions. ie: getUrl:done, saveUrl:done shortCode, err := saveUrl(url, app) if err != nil { log.Println("post saveUrl error", err) } else { log.Println("shortCode:", shortCode) if shortCode != "" { shortUrl := ShortUrl{} shortUrl.ShortUrl = Webhost + shortCode return c.JSON(http.StatusOK, shortUrl) } } return c.NoContent(200) } func getEncoded(c echo.Context, app *pocketbase.PocketBase) error { log.Println("Getting encoded") Encoded := c.PathParam("encoded") log.Println("encoded: ", Encoded) if Encoded == "" || len(Encoded) == 0 || len(Encoded) > 3 { return c.NoContent(404) } Decoded := base58.Decode(Encoded) log.Println("decoded: ", Decoded) url, err := findById(Decoded, app) if err != nil { return c.NoContent(404) } return c.Redirect(301, url) } func findById(id int, app *pocketbase.PocketBase) (string, error) { record, recErr := app.Dao().FindRecordsByExpr("urls", dbx.NewExp("_id = {:id} and deleted = false", dbx.Params{"id": id})) if recErr != nil { log.Println(recErr) return "", recErr } if len(record) != 0 { log.Println("-- record", record[0]) rec := record[0] longUrl := rec.GetString("long_url") go func() { err := UpdateReadCount(app, id) if err != nil { log.Println(err) } }() return longUrl, nil } return "", errors.New("record not found") } func GetJSONRawBody(c echo.Context) UrlRequest { urlBody := UrlRequest{} err := json.NewDecoder(c.Request().Body).Decode(&urlBody) if err != nil { log.Println("empty json body") return urlBody } return urlBody } func UpdateReadCount(app *pocketbase.PocketBase, id int) error { log.Println("$$:UpdateReadCount", id) record, recErr := app.Dao().FindRecordsByExpr("urls", dbx.NewExp("_id = {:id}", dbx.Params{"id": id})) if recErr != nil { log.Println(recErr) return recErr } if len(record) == 0 { return errors.New("record not found") } else { log.Println("-- Update url record", record) rec := record[0] visits := rec.GetInt("visits") + 1 rec.Set("visits", visits) if err := app.Dao().SaveRecord(rec); err != nil { log.Println("ERROR!! UpdateReadCount SaveRecord") log.Println(err) return err } } return nil }