initial public commit
This commit is contained in:
281
daos/user.go
Normal file
281
daos/user.go
Normal file
@@ -0,0 +1,281 @@
|
||||
package daos
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/models/schema"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
|
||||
// UserQuery returns a new User model select query.
|
||||
func (dao *Dao) UserQuery() *dbx.SelectQuery {
|
||||
return dao.ModelQuery(&models.User{})
|
||||
}
|
||||
|
||||
// LoadProfile loads the profile record associated to the provided user.
|
||||
func (dao *Dao) LoadProfile(user *models.User) error {
|
||||
collection, err := dao.FindCollectionByNameOrId(models.ProfileCollectionName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
profile, err := dao.FindFirstRecordByData(collection, models.ProfileCollectionUserFieldName, user.Id)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
|
||||
user.Profile = profile
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadProfiles loads the profile records associated to the provied users list.
|
||||
func (dao *Dao) LoadProfiles(users []*models.User) error {
|
||||
collection, err := dao.FindCollectionByNameOrId(models.ProfileCollectionName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// extract user ids
|
||||
ids := []string{}
|
||||
usersMap := map[string]*models.User{}
|
||||
for _, user := range users {
|
||||
ids = append(ids, user.Id)
|
||||
usersMap[user.Id] = user
|
||||
}
|
||||
|
||||
profiles, err := dao.FindRecordsByExpr(collection, dbx.HashExp{
|
||||
models.ProfileCollectionUserFieldName: list.ToInterfaceSlice(ids),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// populate each user.Profile member
|
||||
for _, profile := range profiles {
|
||||
userId := profile.GetStringDataValue(models.ProfileCollectionUserFieldName)
|
||||
user, ok := usersMap[userId]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
user.Profile = profile
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindUserById finds a single User model by its id.
|
||||
//
|
||||
// This method also auto loads the related user profile record
|
||||
// into the found model.
|
||||
func (dao *Dao) FindUserById(id string) (*models.User, error) {
|
||||
model := &models.User{}
|
||||
|
||||
err := dao.UserQuery().
|
||||
AndWhere(dbx.HashExp{"id": id}).
|
||||
Limit(1).
|
||||
One(model)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// try to load the user profile (if exist)
|
||||
if err := dao.LoadProfile(model); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
||||
|
||||
// FindUserByEmail finds a single User model by its email address.
|
||||
//
|
||||
// This method also auto loads the related user profile record
|
||||
// into the found model.
|
||||
func (dao *Dao) FindUserByEmail(email string) (*models.User, error) {
|
||||
model := &models.User{}
|
||||
|
||||
err := dao.UserQuery().
|
||||
AndWhere(dbx.HashExp{"email": email}).
|
||||
Limit(1).
|
||||
One(model)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// try to load the user profile (if exist)
|
||||
if err := dao.LoadProfile(model); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return model, nil
|
||||
}
|
||||
|
||||
// FindUserByToken finds the user associated with the provided JWT token.
|
||||
// Returns an error if the JWT token is invalid or expired.
|
||||
//
|
||||
// This method also auto loads the related user profile record
|
||||
// into the found model.
|
||||
func (dao *Dao) FindUserByToken(token string, baseTokenKey string) (*models.User, error) {
|
||||
unverifiedClaims, err := security.ParseUnverifiedJWT(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check required claims
|
||||
id, _ := unverifiedClaims["id"].(string)
|
||||
if id == "" {
|
||||
return nil, errors.New("Missing or invalid token claims.")
|
||||
}
|
||||
|
||||
user, err := dao.FindUserById(id)
|
||||
if err != nil || user == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
verificationKey := user.TokenKey + baseTokenKey
|
||||
|
||||
// verify token signature
|
||||
if _, err := security.ParseJWT(token, verificationKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// IsUserEmailUnique checks if the provided email address is not
|
||||
// already in use by other users.
|
||||
func (dao *Dao) IsUserEmailUnique(email string, excludeId string) bool {
|
||||
if email == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
var exists bool
|
||||
err := dao.UserQuery().
|
||||
Select("count(*)").
|
||||
AndWhere(dbx.Not(dbx.HashExp{"id": excludeId})).
|
||||
AndWhere(dbx.HashExp{"email": email}).
|
||||
Limit(1).
|
||||
Row(&exists)
|
||||
|
||||
return err == nil && !exists
|
||||
}
|
||||
|
||||
// DeleteUser deletes the provided User model.
|
||||
//
|
||||
// This method will also cascade the delete operation to all
|
||||
// Record models that references the provided User model
|
||||
// (delete or set to NULL, depending on the related user shema field settings).
|
||||
//
|
||||
// The delete operation may fail if the user is part of a required
|
||||
// reference in another Record model (aka. cannot be deleted or set to NULL).
|
||||
func (dao *Dao) DeleteUser(user *models.User) error {
|
||||
// fetch related records
|
||||
// note: the select is outside of the transaction to prevent SQLITE_LOCKED error when mixing read&write in a single transaction
|
||||
relatedRecords, err := dao.FindUserRelatedRecords(user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dao.RunInTransaction(func(txDao *Dao) error {
|
||||
// check if related records has to be deleted (if `CascadeDelete` is set)
|
||||
// OR
|
||||
// just unset the user related fields (if they are not required)
|
||||
// -----------------------------------------------------------
|
||||
recordsLoop:
|
||||
for _, record := range relatedRecords {
|
||||
var needSave bool
|
||||
|
||||
for _, field := range record.Collection().Schema.Fields() {
|
||||
if field.Type != schema.FieldTypeUser {
|
||||
continue // not a user field
|
||||
}
|
||||
|
||||
ids := record.GetStringSliceDataValue(field.Name)
|
||||
|
||||
// unset the user id
|
||||
for i := len(ids) - 1; i >= 0; i-- {
|
||||
if ids[i] == user.Id {
|
||||
ids = append(ids[:i], ids[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
options, _ := field.Options.(*schema.UserOptions)
|
||||
|
||||
// cascade delete
|
||||
// (only if there are no other user references in case of multiple select)
|
||||
if options.CascadeDelete && len(ids) == 0 {
|
||||
if err := txDao.DeleteRecord(record); err != nil {
|
||||
return err
|
||||
}
|
||||
// no need to further iterate the user fields (the record is deleted)
|
||||
continue recordsLoop
|
||||
}
|
||||
|
||||
if field.Required && len(ids) == 0 {
|
||||
return fmt.Errorf("Failed delete the user because a record exist with required user reference to the current model (%q, %q).", record.Id, record.Collection().Name)
|
||||
}
|
||||
|
||||
// apply the reference changes
|
||||
record.SetDataValue(field.Name, field.PrepareValue(ids))
|
||||
needSave = true
|
||||
}
|
||||
|
||||
if needSave {
|
||||
if err := txDao.SaveRecord(record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// -----------------------------------------------------------
|
||||
|
||||
return txDao.Delete(user)
|
||||
})
|
||||
}
|
||||
|
||||
// SaveUser upserts the provided User model.
|
||||
//
|
||||
// An empty profile record will be created if the user
|
||||
// doesn't have a profile record set yet.
|
||||
func (dao *Dao) SaveUser(user *models.User) error {
|
||||
profileCollection, err := dao.FindCollectionByNameOrId(models.ProfileCollectionName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// fetch the related user profile record (if exist)
|
||||
var userProfile *models.Record
|
||||
if user.HasId() {
|
||||
userProfile, _ = dao.FindFirstRecordByData(
|
||||
profileCollection,
|
||||
models.ProfileCollectionUserFieldName,
|
||||
user.Id,
|
||||
)
|
||||
}
|
||||
|
||||
return dao.RunInTransaction(func(txDao *Dao) error {
|
||||
if err := txDao.Save(user); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create default/empty profile record if doesn't exist
|
||||
if userProfile == nil {
|
||||
userProfile = models.NewRecord(profileCollection)
|
||||
userProfile.SetDataValue(models.ProfileCollectionUserFieldName, user.Id)
|
||||
if err := txDao.Save(userProfile); err != nil {
|
||||
return err
|
||||
}
|
||||
user.Profile = userProfile
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user