Add stargazers endpoint (#1)

This commit is contained in:
Piotr Rogowski 2022-11-06 15:12:45 +01:00 committed by GitHub
parent 450814760c
commit 852c181ac3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 226 additions and 9 deletions

1
.gitignore vendored
View File

@ -32,6 +32,7 @@ go.work
# build # build
cloud-backend cloud-backend
cloud-backend.exe cloud-backend.exe
main
# docker overrides # docker overrides
docker-compose.override.yml docker-compose.override.yml

View File

@ -1,7 +1,9 @@
{ {
"cSpell.words": [ "cSpell.words": [
"daos",
"labstack", "labstack",
"Middlewares", "Middlewares",
"pocketbase" "pocketbase",
"unstar"
] ]
} }

View File

@ -1,9 +1,17 @@
# Upgrade guide # Upgrade guide
## From v1.0.1 to v1.1.0
This version adds stargazers.
1. import new schema first
2. get the new binary or Dockerfile and run `cloud-backend`, make sure everything works as it was before (don't forget to backup `pb_data` data)
3. upgrade frontend to `v1.1.0`
## From v1.0.0 to v1.0.1 ## From v1.0.0 to v1.0.1
This version adds to new custom endpoints for fetching tune and ini file. This version adds to new custom endpoints for fetching tune and ini file.
1. get the new binary and run `cloud-backend`, make sure everything works as it was before (don't forget to backup `pb_data` data) 1. get the new binary or Dockerfile and run `cloud-backend`, make sure everything works as it was before (don't forget to backup `pb_data` data)
2. upgrade frontend to `v1.0.1` 2. upgrade frontend to `v1.0.1`
3. import new schema 3. import new schema

10
go.mod
View File

@ -1,8 +1,12 @@
module hyper-tuner/cloud-backend module main
go 1.19 go 1.19
require github.com/pocketbase/pocketbase v0.8.0-rc2.0.20221105112208-a2abeb872aca require (
github.com/labstack/echo/v5 v5.0.0-20220201181537-ed2888cfa198
github.com/pocketbase/dbx v1.7.0
github.com/pocketbase/pocketbase v0.8.0-rc2.0.20221105112208-a2abeb872aca
)
require ( require (
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
@ -42,12 +46,10 @@ require (
github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/labstack/echo/v5 v5.0.0-20220201181537-ed2888cfa198 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/pocketbase/dbx v1.7.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect github.com/remyoudompheng/bigfft v0.0.0-20220927061507-ef77025ab5aa // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/cobra v1.6.1 // indirect github.com/spf13/cobra v1.6.1 // indirect

138
main.go
View File

@ -4,14 +4,22 @@ import (
"log" "log"
"net/http" "net/http"
_ "main/migrations"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/daos"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
) )
func main() { func main() {
tunesCollectionName := "tunes"
iniFilesCollectionName := "iniFiles"
stargazersCollectionName := "stargazers"
app := pocketbase.New() app := pocketbase.New()
app.OnBeforeServe().Add(func(e *core.ServeEvent) error { app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
@ -19,14 +27,14 @@ func main() {
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/custom/tunes/byTuneId/:tuneId", Path: "/api/custom/tunes/byTuneId/:tuneId",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
record, _err := app.Dao().FindFirstRecordByData("tunes", "tuneId", c.PathParam("tuneId")) record, _err := app.Dao().FindFirstRecordByData(tunesCollectionName, "tuneId", c.PathParam("tuneId"))
if _err != nil { if _err != nil {
return apis.NewNotFoundError("Tune not found", nil) return apis.NewNotFoundError("Tune not found", nil)
} }
_errors := app.Dao().ExpandRecord(record, []string{"author"}, func(relCollection *models.Collection, relIds []string) ([]*models.Record, error) { _errors := app.Dao().ExpandRecord(record, []string{"author"}, func(relCollection *models.Collection, relIds []string) ([]*models.Record, error) {
record, _err := app.Dao().FindFirstRecordByData(relCollection.Name, "id", relIds[0]) record, _err := app.Dao().FindRecordById(relCollection.Name, relIds[0])
return []*models.Record{record}, _err return []*models.Record{record}, _err
}) })
@ -43,7 +51,7 @@ func main() {
Method: http.MethodGet, Method: http.MethodGet,
Path: "/api/custom/iniFiles/bySignature/:signature", Path: "/api/custom/iniFiles/bySignature/:signature",
Handler: func(c echo.Context) error { Handler: func(c echo.Context) error {
record, _err := app.Dao().FindFirstRecordByData("iniFiles", "signature", c.PathParam("signature")) record, _err := app.Dao().FindFirstRecordByData(iniFilesCollectionName, "signature", c.PathParam("signature"))
if _err != nil { if _err != nil {
return c.JSON(http.StatusNotFound, _err) return c.JSON(http.StatusNotFound, _err)
@ -53,6 +61,130 @@ func main() {
}, },
}) })
e.Router.AddRoute(echo.Route{
Method: http.MethodPost,
Path: "/api/custom/stargazers/toggleStar",
Handler: func(c echo.Context) (err error) {
auth := c.Get(apis.ContextAuthRecordKey).(*models.Record)
isStarred := false
type Stargazer struct {
User string
Tune string `json:"tune" form:"tune" query:"tune"`
}
stargazer := new(Stargazer)
if err = c.Bind(stargazer); err != nil {
return c.String(http.StatusBadRequest, "Bad request")
}
stargazer.User = auth.Id
_err := app.Dao().RunInTransaction(func(txDao *daos.Dao) error {
collection, err := app.Dao().FindCollectionByNameOrId(stargazersCollectionName)
if err != nil {
return err
}
stargazerRecord := models.NewRecord(collection)
stargazerRecord.Set("user", stargazer.User)
stargazerRecord.Set("tune", stargazer.Tune)
tune, _err := app.Dao().FindFirstRecordByData(tunesCollectionName, "id", stargazer.Tune)
if _err != nil {
return apis.NewNotFoundError("Tune not found", nil)
}
_err = txDao.SaveRecord(stargazerRecord)
// UNIQUE constraint failed most likely, try to unstar
if _err != nil {
currentStargazerRecords, _err := app.Dao().FindRecordsByExpr(
stargazersCollectionName,
dbx.HashExp{"user": stargazer.User},
dbx.HashExp{"tune": stargazer.Tune},
)
if _err != nil || len(currentStargazerRecords) == 0 {
return _err
}
_err = txDao.DeleteRecord(currentStargazerRecords[0])
if _err != nil {
return _err
}
isStarred = false
tune.Set("stars", tune.Get("stars").(float64)-1)
} else {
isStarred = true
tune.Set("stars", tune.Get("stars").(float64)+1)
}
_err = txDao.SaveRecord(tune)
if _err != nil {
return _err
}
return nil
})
if _err != nil {
return apis.NewNotFoundError("Tune not found or already starred", nil)
}
// fetch again and return current state
tune, _err := app.Dao().FindFirstRecordByData(tunesCollectionName, "id", stargazer.Tune)
return c.JSON(http.StatusOK, map[string]any{
"stars": tune.Get("stars").(float64),
"isStarred": isStarred,
})
},
Middlewares: []echo.MiddlewareFunc{
apis.LoadAuthContext(app.App),
apis.RequireAdminOrRecordAuth("users"),
},
})
e.Router.AddRoute(echo.Route{
Method: http.MethodGet,
Path: "/api/custom/stargazers/starredByMe/:tune",
Handler: func(c echo.Context) (err error) {
auth := c.Get(apis.ContextAuthRecordKey).(*models.Record)
isStarred := true
type Stargazer struct {
User string
Tune string
}
stargazer := new(Stargazer)
stargazer.User = auth.Id
stargazer.Tune = c.PathParam("tune")
stargazerRecords, _err := app.Dao().FindRecordsByExpr(
stargazersCollectionName,
dbx.HashExp{"user": stargazer.User},
dbx.HashExp{"tune": stargazer.Tune},
)
if _err != nil || len(stargazerRecords) == 0 {
isStarred = false
}
return c.JSON(http.StatusOK, map[string]any{
"isStarred": isStarred,
})
},
Middlewares: []echo.MiddlewareFunc{
apis.LoadAuthContext(app.App),
apis.RequireAdminOrRecordAuth("users"),
},
})
return nil return nil
}) })

View File

@ -0,0 +1,20 @@
package migrations
import (
"github.com/pocketbase/dbx"
m "github.com/pocketbase/pocketbase/migrations"
)
func init() {
m.Register(func(db dbx.Builder) error {
// add up queries...
db.CreateUniqueIndex("stargazers", "unique_stargazers_on_user_tune", "user", "tune").Execute()
return nil
}, func(db dbx.Builder) error {
// add down queries...
db.DropIndex("stargazers", "unique_stargazers_on_user_tune").Execute()
return nil
})
}

View File

@ -44,6 +44,18 @@
"pattern": "" "pattern": ""
} }
}, },
{
"id": "lwbwtgmx",
"name": "stars",
"type": "number",
"system": false,
"required": false,
"unique": false,
"options": {
"min": 0,
"max": null
}
},
{ {
"id": "g9b17t9y", "id": "g9b17t9y",
"name": "vehicleName", "name": "vehicleName",
@ -427,5 +439,45 @@
"onlyEmailDomains": null, "onlyEmailDomains": null,
"requireEmail": true "requireEmail": true
} }
},
{
"id": "z8cojwcvlyxxyll",
"name": "stargazers",
"type": "base",
"system": false,
"schema": [
{
"id": "him7pbq2",
"name": "user",
"type": "relation",
"system": false,
"required": true,
"unique": false,
"options": {
"maxSelect": 1,
"collectionId": "systemprofiles0",
"cascadeDelete": false
}
},
{
"id": "ny7akrmn",
"name": "tune",
"type": "relation",
"system": false,
"required": true,
"unique": false,
"options": {
"maxSelect": 1,
"collectionId": "5djmpehuiigg06b",
"cascadeDelete": false
}
}
],
"listRule": null,
"viewRule": null,
"createRule": null,
"updateRule": null,
"deleteRule": null,
"options": {}
} }
] ]