go-nurl/main.go
Martin Donnelly e1d7d78aed Init
2024-09-16 17:06:11 +01:00

335 lines
6.2 KiB
Go

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("/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}", 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")
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
}