Compare commits
3 Commits
dd2960a742
...
f81e4eaa47
| Author | SHA1 | Date | |
|---|---|---|---|
| f81e4eaa47 | |||
| 14bad8e7ef | |||
| 3198612e16 |
@@ -748,7 +748,7 @@ const docTemplate = `{
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"birthday": {
|
"birthday": {
|
||||||
"type": "string"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
@@ -744,7 +744,7 @@
|
|||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"birthday": {
|
"birthday": {
|
||||||
"type": "string"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ definitions:
|
|||||||
models.RegistrationCompleteRequest:
|
models.RegistrationCompleteRequest:
|
||||||
properties:
|
properties:
|
||||||
birthday:
|
birthday:
|
||||||
type: string
|
type: integer
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
username:
|
username:
|
||||||
|
|||||||
@@ -120,8 +120,12 @@ func (ctrl *ProfileController) getProfileByUsername(c *gin.Context) {
|
|||||||
response, err := ctrl.ps.GetProfileByUsername(cinfo, username); if err != nil {
|
response, err := ctrl.ps.GetProfileByUsername(cinfo, username); if err != nil {
|
||||||
if errors.Is(err, errs.ErrNotFound) {
|
if errors.Is(err, errs.ErrNotFound) {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Profile not found"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "Profile not found"})
|
||||||
|
} else if errors.Is(err, errs.ErrUnauthorized) {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Profile avaiable to authrorized users only"})
|
||||||
} else if errors.Is(err, errs.ErrForbidden) {
|
} else if errors.Is(err, errs.ErrForbidden) {
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "Access restricted by profile's privacy settings"})
|
c.JSON(http.StatusForbidden, gin.H{"error": "Access restricted by profile's privacy settings"})
|
||||||
|
} else if errors.Is(err, errs.ErrGone) {
|
||||||
|
c.JSON(http.StatusGone, gin.H{"error": "Profile no longer available"})
|
||||||
} else {
|
} else {
|
||||||
c.Status(http.StatusInternalServerError)
|
c.Status(http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ type CheckProfileAccessRow struct {
|
|||||||
CaptchaRequired bool
|
CaptchaRequired bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: recheck, was tired
|
|
||||||
func (q *Queries) CheckProfileAccess(ctx context.Context, arg CheckProfileAccessParams) (CheckProfileAccessRow, error) {
|
func (q *Queries) CheckProfileAccess(ctx context.Context, arg CheckProfileAccessParams) (CheckProfileAccessRow, error) {
|
||||||
row := q.db.QueryRow(ctx, checkProfileAccess, arg.ID, arg.Requester)
|
row := q.db.QueryRow(ctx, checkProfileAccess, arg.ID, arg.Requester)
|
||||||
var i CheckProfileAccessRow
|
var i CheckProfileAccessRow
|
||||||
@@ -99,6 +98,46 @@ func (q *Queries) CheckUserRegistrationAvailability(ctx context.Context, arg Che
|
|||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkWishAccessByGuid = `-- name: CheckWishAccessByGuid :one
|
||||||
|
SELECT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM wishes w
|
||||||
|
JOIN wish_lists wl ON w.wish_list_id = wl.id
|
||||||
|
JOIN profiles p ON wl.profile_id = p.id
|
||||||
|
JOIN profile_settings ps ON ps.profile_id = p.id
|
||||||
|
JOIN users u ON p.user_id = u.id
|
||||||
|
LEFT JOIN banned_users bu ON u.id = bu.user_id
|
||||||
|
AND bu.pardoned = FALSE
|
||||||
|
AND (bu.expires_at IS NULL OR bu.expires_at > NOW())
|
||||||
|
WHERE w.guid = ($1::text)::uuid
|
||||||
|
AND ps.hide_profile_details = FALSE
|
||||||
|
AND (
|
||||||
|
$2::text != ''
|
||||||
|
OR ps.hide_for_unauthenticated IS FALSE
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
w.fulfilled = FALSE
|
||||||
|
OR ps.hide_fulfilled IS FALSE
|
||||||
|
)
|
||||||
|
AND w.deleted = FALSE
|
||||||
|
AND wl.deleted = FALSE
|
||||||
|
AND u.deleted = FALSE
|
||||||
|
AND bu.id IS NULL -- Ensures owner is not banned
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
type CheckWishAccessByGuidParams struct {
|
||||||
|
Guid string
|
||||||
|
Requester string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CheckWishAccessByGuid(ctx context.Context, arg CheckWishAccessByGuidParams) (bool, error) {
|
||||||
|
row := q.db.QueryRow(ctx, checkWishAccessByGuid, arg.Guid, arg.Requester)
|
||||||
|
var exists bool
|
||||||
|
err := row.Scan(&exists)
|
||||||
|
return exists, err
|
||||||
|
}
|
||||||
|
|
||||||
const createBannedUser = `-- name: CreateBannedUser :one
|
const createBannedUser = `-- name: CreateBannedUser :one
|
||||||
INSERT INTO banned_users(user_id, expires_at, reason, banned_by)
|
INSERT INTO banned_users(user_id, expires_at, reason, banned_by)
|
||||||
VALUES ( $1, $2, $3, $4) RETURNING id, user_id, date, reason, expires_at, banned_by, pardoned, pardoned_by
|
VALUES ( $1, $2, $3, $4) RETURNING id, user_id, date, reason, expires_at, banned_by, pardoned, pardoned_by
|
||||||
@@ -520,7 +559,6 @@ type GetProfileByUsernameWithPrivacyRow struct {
|
|||||||
ColorGrad string
|
ColorGrad string
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: tweak backend code to handle privacy correctly
|
|
||||||
func (q *Queries) GetProfileByUsernameWithPrivacy(ctx context.Context, searchedUsername string) (GetProfileByUsernameWithPrivacyRow, error) {
|
func (q *Queries) GetProfileByUsernameWithPrivacy(ctx context.Context, searchedUsername string) (GetProfileByUsernameWithPrivacyRow, error) {
|
||||||
row := q.db.QueryRow(ctx, getProfileByUsernameWithPrivacy, searchedUsername)
|
row := q.db.QueryRow(ctx, getProfileByUsernameWithPrivacy, searchedUsername)
|
||||||
var i GetProfileByUsernameWithPrivacyRow
|
var i GetProfileByUsernameWithPrivacyRow
|
||||||
@@ -1016,73 +1054,24 @@ func (q *Queries) GetWishByGuid(ctx context.Context, guid string) (Wish, error)
|
|||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getWishByGuidWithPrivacy = `-- name: GetWishByGuidWithPrivacy :one
|
const getWishListOwnerByGuid = `-- name: GetWishListOwnerByGuid :one
|
||||||
SELECT
|
SELECT u.id, u.username, u.verified, u.registration_date, u.role, u.deleted
|
||||||
w.id, w.guid, w.wish_list_id, w.wish_list_guid, w.name, w.description, w.picture_url, w.stars, w.creation_date, w.fulfilled, w.fulfilled_date, w.deleted,
|
FROM wish_lists wl
|
||||||
CASE
|
|
||||||
WHEN
|
|
||||||
(
|
|
||||||
$1::text = u.username OR
|
|
||||||
NOT ps.hide_profile_details AND
|
|
||||||
NOT
|
|
||||||
(
|
|
||||||
ps.hide_for_unauthenticated AND
|
|
||||||
$1::text = ''
|
|
||||||
) AND
|
|
||||||
NOT wl.hidden
|
|
||||||
)
|
|
||||||
THEN TRUE
|
|
||||||
ELSE FALSE
|
|
||||||
END AS access_allowed
|
|
||||||
FROM wishes w
|
|
||||||
JOIN wish_lists wl ON w.wish_list_id = wl.id
|
|
||||||
JOIN profiles p ON wl.profile_id = p.id
|
JOIN profiles p ON wl.profile_id = p.id
|
||||||
JOIN profile_settings ps ON ps.profile_id = p.id
|
|
||||||
JOIN users u ON p.user_id = u.id
|
JOIN users u ON p.user_id = u.id
|
||||||
WHERE
|
WHERE wl.guid = ($1::text)::uuid
|
||||||
w.guid = ($2::text)::uuid AND
|
|
||||||
w.deleted IS FALSE
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetWishByGuidWithPrivacyParams struct {
|
func (q *Queries) GetWishListOwnerByGuid(ctx context.Context, guid string) (User, error) {
|
||||||
Requester string
|
row := q.db.QueryRow(ctx, getWishListOwnerByGuid, guid)
|
||||||
Guid string
|
var i User
|
||||||
}
|
|
||||||
|
|
||||||
type GetWishByGuidWithPrivacyRow struct {
|
|
||||||
ID int64
|
|
||||||
Guid pgtype.UUID
|
|
||||||
WishListID int64
|
|
||||||
WishListGuid pgtype.UUID
|
|
||||||
Name string
|
|
||||||
Description string
|
|
||||||
PictureUrl string
|
|
||||||
Stars int16
|
|
||||||
CreationDate pgtype.Timestamp
|
|
||||||
Fulfilled bool
|
|
||||||
FulfilledDate pgtype.Timestamp
|
|
||||||
Deleted bool
|
|
||||||
AccessAllowed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX: Obsolete, use the according access check query instead
|
|
||||||
func (q *Queries) GetWishByGuidWithPrivacy(ctx context.Context, arg GetWishByGuidWithPrivacyParams) (GetWishByGuidWithPrivacyRow, error) {
|
|
||||||
row := q.db.QueryRow(ctx, getWishByGuidWithPrivacy, arg.Requester, arg.Guid)
|
|
||||||
var i GetWishByGuidWithPrivacyRow
|
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.Guid,
|
&i.Username,
|
||||||
&i.WishListID,
|
&i.Verified,
|
||||||
&i.WishListGuid,
|
&i.RegistrationDate,
|
||||||
&i.Name,
|
&i.Role,
|
||||||
&i.Description,
|
|
||||||
&i.PictureUrl,
|
|
||||||
&i.Stars,
|
|
||||||
&i.CreationDate,
|
|
||||||
&i.Fulfilled,
|
|
||||||
&i.FulfilledDate,
|
|
||||||
&i.Deleted,
|
&i.Deleted,
|
||||||
&i.AccessAllowed,
|
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
@@ -1240,7 +1229,6 @@ type GetWishlistsByUsernameWithPrivacyRow struct {
|
|||||||
AccessAllowed bool
|
AccessAllowed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: Obsolete, use the according access check query instead
|
|
||||||
func (q *Queries) GetWishlistsByUsernameWithPrivacy(ctx context.Context, arg GetWishlistsByUsernameWithPrivacyParams) ([]GetWishlistsByUsernameWithPrivacyRow, error) {
|
func (q *Queries) GetWishlistsByUsernameWithPrivacy(ctx context.Context, arg GetWishlistsByUsernameWithPrivacyParams) ([]GetWishlistsByUsernameWithPrivacyRow, error) {
|
||||||
rows, err := q.db.Query(ctx, getWishlistsByUsernameWithPrivacy, arg.Requester, arg.Username)
|
rows, err := q.db.Query(ctx, getWishlistsByUsernameWithPrivacy, arg.Requester, arg.Username)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -27,4 +27,5 @@ var (
|
|||||||
ErrForbidden = errors.New("Access is denied")
|
ErrForbidden = errors.New("Access is denied")
|
||||||
ErrTooManyRequests = errors.New("Too many requests")
|
ErrTooManyRequests = errors.New("Too many requests")
|
||||||
ErrNotFound = errors.New("Resource not found")
|
ErrNotFound = errors.New("Resource not found")
|
||||||
|
ErrGone = errors.New("Resource no longer available")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -79,10 +79,7 @@ func (p *profileServiceImpl) GetProfileByUsername(cinfo dto.ClientInfo, username
|
|||||||
}
|
}
|
||||||
defer helper.Rollback()
|
defer helper.Rollback()
|
||||||
|
|
||||||
profileRow, err := db.TXQueries.GetProfileByUsernameWithPrivacy(db.CTX, database.GetProfileByUsernameWithPrivacyParams{
|
profileRow, err := db.TXQueries.GetProfileByUsername(db.CTX, username); if err != nil {
|
||||||
Requester: cinfo.Username,
|
|
||||||
SearchedUsername: username,
|
|
||||||
}); if err != nil {
|
|
||||||
if errors.Is(err, pgx.ErrNoRows) {
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
return nil, errs.ErrNotFound
|
return nil, errs.ErrNotFound
|
||||||
}
|
}
|
||||||
@@ -94,9 +91,33 @@ func (p *profileServiceImpl) GetProfileByUsername(cinfo dto.ClientInfo, username
|
|||||||
return nil, errs.ErrServerError
|
return nil, errs.ErrServerError
|
||||||
}
|
}
|
||||||
|
|
||||||
if !*profileRow.AccessAllowed {
|
accessChecks, err := db.TXlessQueries.CheckProfileAccess(db.CTX, database.CheckProfileAccessParams{
|
||||||
|
Requester: cinfo.Username,
|
||||||
|
ID: profileRow.ID,
|
||||||
|
}); if err != nil {
|
||||||
|
p.log.Error(
|
||||||
|
"Failed to check access for given profile",
|
||||||
|
zap.String("profile_owner_username", username),
|
||||||
|
zap.String("requester", cinfo.Username),
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, errs.ErrServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
if accessChecks.AuthRequired {
|
||||||
|
return nil, errs.ErrUnauthorized
|
||||||
|
}
|
||||||
|
if accessChecks.Hidden {
|
||||||
return nil, errs.ErrForbidden
|
return nil, errs.ErrForbidden
|
||||||
}
|
}
|
||||||
|
if accessChecks.UserBanned {
|
||||||
|
return nil, errs.ErrGone
|
||||||
|
}
|
||||||
|
if accessChecks.UserUnavailable {
|
||||||
|
return nil, errs.ErrGone
|
||||||
|
}
|
||||||
|
if accessChecks.CaptchaRequired {
|
||||||
|
p.log.Warn("Captcha check is not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
profileDto := &dto.ProfileDto{
|
profileDto := &dto.ProfileDto{
|
||||||
Name: profileRow.Name,
|
Name: profileRow.Name,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ type s3ServiceImpl struct {
|
|||||||
imagePolicy minio.PostPolicy
|
imagePolicy minio.PostPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUploadService(_minio *minio.Client, _log *zap.Logger) S3Service {
|
func NewS3Service(_minio *minio.Client, _log *zap.Logger) S3Service {
|
||||||
service := s3ServiceImpl{
|
service := s3ServiceImpl{
|
||||||
minio: _minio,
|
minio: _minio,
|
||||||
log: _log,
|
log: _log,
|
||||||
|
|||||||
@@ -23,9 +23,10 @@ import (
|
|||||||
|
|
||||||
var Module = fx.Module("services",
|
var Module = fx.Module("services",
|
||||||
fx.Provide(
|
fx.Provide(
|
||||||
NewUploadService,
|
NewS3Service,
|
||||||
NewSmtpService,
|
NewSmtpService,
|
||||||
NewAuthService,
|
NewAuthService,
|
||||||
NewProfileService,
|
NewProfileService,
|
||||||
|
NewWishListService,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,10 +18,26 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"easywish/internal/database"
|
||||||
"easywish/internal/dto"
|
"easywish/internal/dto"
|
||||||
|
errs "easywish/internal/errors"
|
||||||
|
"easywish/internal/utils"
|
||||||
"easywish/internal/utils/enums"
|
"easywish/internal/utils/enums"
|
||||||
|
mapspecial "easywish/internal/utils/mapSpecial"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/go-redis/redis/v8"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type wishListServiceImpl struct {
|
||||||
|
log *zap.Logger
|
||||||
|
dbctx database.DbContext
|
||||||
|
redis *redis.Client
|
||||||
|
s3 S3Service
|
||||||
|
}
|
||||||
|
|
||||||
type WishListService interface {
|
type WishListService interface {
|
||||||
CreateWishList(cinfo dto.ClientInfo, object dto.NewWishListDto) (*dto.WishListDto, error)
|
CreateWishList(cinfo dto.ClientInfo, object dto.NewWishListDto) (*dto.WishListDto, error)
|
||||||
UpdateWishListByGuid(cinfo dto.ClientInfo, guid string, object dto.NewWishDto) (bool, error)
|
UpdateWishListByGuid(cinfo dto.ClientInfo, guid string, object dto.NewWishDto) (bool, error)
|
||||||
@@ -31,6 +47,148 @@ type WishListService interface {
|
|||||||
|
|
||||||
CreateWish(cinfo dto.ClientInfo, object dto.NewWishDto) (*dto.WishDto, error)
|
CreateWish(cinfo dto.ClientInfo, object dto.NewWishDto) (*dto.WishDto, error)
|
||||||
UpdateWish(cinfo dto.ClientInfo, guid string, object dto.NewWishDto) (bool, error)
|
UpdateWish(cinfo dto.ClientInfo, guid string, object dto.NewWishDto) (bool, error)
|
||||||
|
MoveWishToWishList(cinfo dto.ClientInfo, wishGuid string, wishListGuid string) (bool, error)
|
||||||
GetWishByGuid(cinfo dto.ClientInfo, guid string) (*dto.WishDto, error)
|
GetWishByGuid(cinfo dto.ClientInfo, guid string) (*dto.WishDto, error)
|
||||||
GetWishesByWishListGuidPaginated(cinfo dto.ClientInfo, guid string, amount int, page int, sorting enums.Sorting, sortOrder enums.SortOrder) (*[]dto.WishDto, error)
|
GetWishesByWishListGuidPaginated(cinfo dto.ClientInfo, guid string, amount int, page int, sorting enums.Sorting, sortOrder enums.SortOrder) (*[]dto.WishDto, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewWishListService(_log *zap.Logger, _dbctx database.DbContext, _redis *redis.Client, _s3 S3Service) WishListService {
|
||||||
|
return wishListServiceImpl{
|
||||||
|
log: _log,
|
||||||
|
dbctx: _dbctx,
|
||||||
|
redis: _redis,
|
||||||
|
s3: _s3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wishListServiceImpl) CreateWish(cinfo dto.ClientInfo, object dto.NewWishDto) (*dto.WishDto, error) {
|
||||||
|
helper, db, err := database.NewDbHelperTransaction(w.dbctx); if err != nil {
|
||||||
|
w.log.Error(
|
||||||
|
"Failed to open transaction",
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, errs.ErrServerError
|
||||||
|
}
|
||||||
|
defer helper.Rollback()
|
||||||
|
|
||||||
|
// Check if wish list exists
|
||||||
|
wishList, err := db.TXQueries.GetWishlistByGuid(db.CTX, object.WishListGuid); if err != nil {
|
||||||
|
if errors.Is(err, pgx.ErrNoRows) {
|
||||||
|
w.log.Warn(
|
||||||
|
"Attempted to create a wish for a wish list that does not exist",
|
||||||
|
zap.String("username", cinfo.Username),
|
||||||
|
zap.String("wish_list_guid", object.WishListGuid),
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, errs.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
w.log.Error(
|
||||||
|
"Failed to get wishlist for the new wish",
|
||||||
|
zap.String("username", cinfo.Username),
|
||||||
|
zap.String("wish_list_guid", object.WishListGuid),
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, errs.ErrServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
wishListOwnerUser, err := db.TXQueries.GetWishListOwnerByGuid(db.CTX, wishList.Guid.String()); if err != nil {
|
||||||
|
w.log.Error(
|
||||||
|
"Failed to get wish list owner",
|
||||||
|
zap.String("username", cinfo.Username),
|
||||||
|
zap.String("wish_list_guid", wishList.Guid.String()),
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, errs.ErrServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
if wishListOwnerUser.Username != cinfo.Username {
|
||||||
|
w.log.Warn(
|
||||||
|
"Attempt to create wish in a wish list the user does not own",
|
||||||
|
zap.String("owner_username", wishListOwnerUser.Username),
|
||||||
|
zap.String("username", cinfo.Username),
|
||||||
|
zap.String("wish_list_guid", object.WishListGuid))
|
||||||
|
|
||||||
|
// As usual, we will pretend that it does not exist
|
||||||
|
return nil, errs.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
var avatarUrl *string
|
||||||
|
if object.PictureUploadId != "" {
|
||||||
|
key, err := w.s3.SaveUpload(object.PictureUploadId, "images"); if err != nil {
|
||||||
|
if errors.Is(err, errs.ErrFileNotFound) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
w.log.Error(
|
||||||
|
"Failed to save image",
|
||||||
|
zap.String("upload_id", object.PictureUploadId),
|
||||||
|
zap.String("username", cinfo.Username),
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, errs.ErrServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
urlObj := w.s3.GetLocalizedFileUrl(*key, "images")
|
||||||
|
avatarUrl = utils.NewPointer(urlObj.String())
|
||||||
|
} else {
|
||||||
|
avatarUrl = utils.NewPointer("")
|
||||||
|
}
|
||||||
|
|
||||||
|
newWish, err := db.TXQueries.CreateWish(db.CTX, database.CreateWishParams{
|
||||||
|
WishListGuid: object.WishListGuid,
|
||||||
|
Name: object.Name,
|
||||||
|
Description: object.Description,
|
||||||
|
PictureUrl: *avatarUrl,
|
||||||
|
Stars: int16(object.Stars),
|
||||||
|
}); if err != nil {
|
||||||
|
w.log.Error(
|
||||||
|
"Failed to create a new wish",
|
||||||
|
zap.String("username", cinfo.Username),
|
||||||
|
zap.String("wish_list_guid", object.WishListGuid),
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, errs.ErrServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
err = helper.Commit(); if err != nil {
|
||||||
|
w.log.Error(
|
||||||
|
"Failed to commit transaction",
|
||||||
|
zap.Error(err))
|
||||||
|
return nil, errs.ErrServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
wishDto := &dto.WishDto{}
|
||||||
|
mapspecial.MapWishDto(newWish, wishDto)
|
||||||
|
return wishDto, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wishListServiceImpl) CreateWishList(cinfo dto.ClientInfo, object dto.NewWishListDto) (*dto.WishListDto, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wishListServiceImpl) DeleteWishListByGuid(cinfo dto.ClientInfo, guid string) (bool, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wishListServiceImpl) GetUserWishListsPaginated(cinfo dto.ClientInfo, amount int, page int, sorting enums.Sorting, sortOrder enums.SortOrder) (*[]dto.WishListDto, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wishListServiceImpl) GetWishByGuid(cinfo dto.ClientInfo, guid string) (*dto.WishDto, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wishListServiceImpl) GetWishListByGuid(cinfo dto.ClientInfo, guid string) (*dto.WishListDto, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wishListServiceImpl) GetWishesByWishListGuidPaginated(cinfo dto.ClientInfo, guid string, amount int, page int, sorting enums.Sorting, sortOrder enums.SortOrder) (*[]dto.WishDto, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wishListServiceImpl) MoveWishToWishList(cinfo dto.ClientInfo, wishGuid string, wishListGuid string) (bool, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wishListServiceImpl) UpdateWish(cinfo dto.ClientInfo, guid string, object dto.NewWishDto) (bool, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w wishListServiceImpl) UpdateWishListByGuid(cinfo dto.ClientInfo, guid string, object dto.NewWishDto) (bool, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|||||||
@@ -281,7 +281,6 @@ JOIN users ON users.id = profiles.user_id
|
|||||||
WHERE users.username = $1;
|
WHERE users.username = $1;
|
||||||
|
|
||||||
;-- name: CheckProfileAccess :one
|
;-- name: CheckProfileAccess :one
|
||||||
-- XXX: recheck, was tired
|
|
||||||
SELECT
|
SELECT
|
||||||
CASE WHEN u.deleted OR NOT u.verified THEN TRUE ELSE FALSE END AS user_unavailable,
|
CASE WHEN u.deleted OR NOT u.verified THEN TRUE ELSE FALSE END AS user_unavailable,
|
||||||
CASE WHEN EXISTS (
|
CASE WHEN EXISTS (
|
||||||
@@ -300,7 +299,6 @@ JOIN users u ON p.user_id = u.id
|
|||||||
WHERE p.id = $1;
|
WHERE p.id = $1;
|
||||||
|
|
||||||
;-- name: GetProfileByUsernameWithPrivacy :one
|
;-- name: GetProfileByUsernameWithPrivacy :one
|
||||||
-- FIXME: tweak backend code to handle privacy correctly
|
|
||||||
SELECT
|
SELECT
|
||||||
u.username,
|
u.username,
|
||||||
p.name,
|
p.name,
|
||||||
@@ -395,6 +393,13 @@ WHERE wl.guid = (@guid::text)::uuid;
|
|||||||
SELECT * FROM wish_lists wl
|
SELECT * FROM wish_lists wl
|
||||||
WHERE wl.guid = (@guid::text)::uuid;
|
WHERE wl.guid = (@guid::text)::uuid;
|
||||||
|
|
||||||
|
;-- name: GetWishListOwnerByGuid :one
|
||||||
|
SELECT u.*
|
||||||
|
FROM wish_lists wl
|
||||||
|
JOIN profiles p ON wl.profile_id = p.id
|
||||||
|
JOIN users u ON p.user_id = u.id
|
||||||
|
WHERE wl.guid = (@guid::text)::uuid;
|
||||||
|
|
||||||
;-- name: GetWishlistsByUsername :many
|
;-- name: GetWishlistsByUsername :many
|
||||||
SELECT * FROM wish_lists wl
|
SELECT * FROM wish_lists wl
|
||||||
JOIN profiles p ON p.id = wl.profile_id
|
JOIN profiles p ON p.id = wl.profile_id
|
||||||
@@ -402,7 +407,6 @@ JOIN users u ON u.id = p.user_id
|
|||||||
WHERE u.username = @username::text;
|
WHERE u.username = @username::text;
|
||||||
|
|
||||||
-- name: GetWishlistsByUsernameWithPrivacy :many
|
-- name: GetWishlistsByUsernameWithPrivacy :many
|
||||||
-- XXX: Obsolete, create according access check query instead
|
|
||||||
SELECT
|
SELECT
|
||||||
wl.*,
|
wl.*,
|
||||||
CASE
|
CASE
|
||||||
@@ -496,32 +500,31 @@ FROM updated;
|
|||||||
SELECT * FROM wishes w
|
SELECT * FROM wishes w
|
||||||
WHERE w.guid = (@guid::text)::uuid;
|
WHERE w.guid = (@guid::text)::uuid;
|
||||||
|
|
||||||
;-- name: GetWishByGuidWithPrivacy :one
|
;-- name: CheckWishAccessByGuid :one
|
||||||
-- XXX: Obsolete, create according access check query instead
|
SELECT EXISTS (
|
||||||
SELECT
|
SELECT 1
|
||||||
w.*,
|
FROM wishes w
|
||||||
CASE
|
JOIN wish_lists wl ON w.wish_list_id = wl.id
|
||||||
WHEN
|
JOIN profiles p ON wl.profile_id = p.id
|
||||||
(
|
JOIN profile_settings ps ON ps.profile_id = p.id
|
||||||
@requester::text = u.username OR
|
JOIN users u ON p.user_id = u.id
|
||||||
NOT ps.hide_profile_details AND
|
LEFT JOIN banned_users bu ON u.id = bu.user_id
|
||||||
NOT
|
AND bu.pardoned = FALSE
|
||||||
(
|
AND (bu.expires_at IS NULL OR bu.expires_at > NOW())
|
||||||
ps.hide_for_unauthenticated AND
|
WHERE w.guid = (@guid::text)::uuid
|
||||||
@requester::text = ''
|
AND ps.hide_profile_details = FALSE
|
||||||
) AND
|
AND (
|
||||||
NOT wl.hidden
|
@requester::text != ''
|
||||||
|
OR ps.hide_for_unauthenticated IS FALSE
|
||||||
)
|
)
|
||||||
THEN TRUE
|
AND (
|
||||||
ELSE FALSE
|
w.fulfilled = FALSE
|
||||||
END AS access_allowed
|
OR ps.hide_fulfilled IS FALSE
|
||||||
FROM wishes w
|
)
|
||||||
JOIN wish_lists wl ON w.wish_list_id = wl.id
|
AND w.deleted = FALSE
|
||||||
JOIN profiles p ON wl.profile_id = p.id
|
AND wl.deleted = FALSE
|
||||||
JOIN profile_settings ps ON ps.profile_id = p.id
|
AND u.deleted = FALSE
|
||||||
JOIN users u ON p.user_id = u.id
|
AND bu.id IS NULL -- Ensures owner is not banned
|
||||||
WHERE
|
);
|
||||||
w.guid = (@guid::text)::uuid AND
|
|
||||||
w.deleted IS FALSE;
|
|
||||||
|
|
||||||
--: }}}
|
--: }}}
|
||||||
|
|||||||
@@ -121,63 +121,3 @@ CREATE TABLE IF NOT EXISTS "wishes" (
|
|||||||
fulfilled_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
fulfilled_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
deleted BOOLEAN NOT NULL DEFAULT FALSE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION get_profile(requester_user_id BIGINT, target_profile_id BIGINT)
|
|
||||||
RETURNS JSONB AS $$
|
|
||||||
DECLARE
|
|
||||||
profile_record profiles%ROWTYPE;
|
|
||||||
settings_record profile_settings%ROWTYPE;
|
|
||||||
is_owner BOOLEAN;
|
|
||||||
is_banned BOOLEAN;
|
|
||||||
is_deleted BOOLEAN;
|
|
||||||
BEGIN
|
|
||||||
-- Check if target user exists and is not deleted/banned
|
|
||||||
SELECT p.*, u.deleted INTO profile_record
|
|
||||||
FROM profiles p
|
|
||||||
JOIN users u ON p.user_id = u.id
|
|
||||||
WHERE p.id = target_profile_id;
|
|
||||||
|
|
||||||
IF NOT FOUND THEN
|
|
||||||
RETURN NULL; -- Or raise an exception for "not found"
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
is_deleted := profile_record.deleted; -- From users table
|
|
||||||
IF is_deleted THEN
|
|
||||||
RETURN NULL;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Check if requester is banned (simplified; expand as needed)
|
|
||||||
SELECT EXISTS(SELECT 1 FROM banned_users WHERE user_id = requester_user_id AND pardoned = FALSE AND (expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP)) INTO is_banned;
|
|
||||||
IF is_banned THEN
|
|
||||||
RAISE EXCEPTION 'Requester is banned';
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Determine ownership
|
|
||||||
is_owner := (profile_record.user_id = requester_user_id);
|
|
||||||
|
|
||||||
-- Fetch settings
|
|
||||||
SELECT * INTO settings_record FROM profile_settings WHERE profile_id = target_profile_id;
|
|
||||||
|
|
||||||
-- Apply privacy: Hide for unauthenticated or based on settings
|
|
||||||
IF requester_user_id IS NULL AND settings_record.hide_for_unauthenticated THEN -- NULL requester means unauth
|
|
||||||
RETURN NULL;
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
IF NOT is_owner AND settings_record.hide_profile_details THEN
|
|
||||||
RETURN NULL; -- Or return minimal public data
|
|
||||||
END IF;
|
|
||||||
|
|
||||||
-- Sanitize fields based on settings
|
|
||||||
IF NOT is_owner AND settings_record.hide_birthday THEN
|
|
||||||
profile_record.birthday := NULL;
|
|
||||||
END IF;
|
|
||||||
-- Add more field-level masking here (e.g., bio, avatar_url)
|
|
||||||
|
|
||||||
-- Return as JSONB for easy app consumption
|
|
||||||
RETURN row_to_json(profile_record)::JSONB;
|
|
||||||
EXCEPTION
|
|
||||||
WHEN OTHERS THEN
|
|
||||||
RAISE NOTICE 'Access denied: %', SQLERRM;
|
|
||||||
RETURN NULL;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user