Merge pull request 'Backend: finishing the first milestone' (#6) from feat-profile_service into main
Reviewed-on: #6
This commit was merged in pull request #6.
This commit is contained in:
@@ -345,59 +345,63 @@ func (q *Queries) GetProfileByUsername(ctx context.Context, username string) (Pr
|
|||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getProfileByUsernameRestricted = `-- name: GetProfileByUsernameRestricted :one
|
const getProfileByUsernameWithPrivacy = `-- name: GetProfileByUsernameWithPrivacy :one
|
||||||
SELECT
|
SELECT
|
||||||
users.username,
|
u.username,
|
||||||
profiles.name,
|
p.name,
|
||||||
CASE
|
p.bio,
|
||||||
WHEN profile_settings.hide_birthday OR profile_settings.hide_profile_details THEN NULL
|
p.avatar_url,
|
||||||
ELSE profiles.birthday
|
CASE WHEN ps.hide_birthday THEN NULL ELSE p.birthday END AS birthday,
|
||||||
END AS birthday,
|
p.color,
|
||||||
CASE
|
p.color_grad,
|
||||||
WHEN profile_settings.hide_profile_details THEN NULL
|
NOT ($1::text = '' AND ps.hide_for_unauthenticated) AS access_allowed
|
||||||
ELSE profiles.bio
|
FROM
|
||||||
END AS bio,
|
users AS u
|
||||||
CASE
|
JOIN profiles AS p ON u.id = p.user_id
|
||||||
WHEN profile_settings.hide_profile_details THEN NULL
|
JOIN profile_settings AS ps ON p.id = ps.profile_id
|
||||||
ELSE profiles.avatar_url
|
WHERE
|
||||||
END AS avatar_url,
|
u.username = $2::text
|
||||||
profiles.color,
|
AND (
|
||||||
profiles.color_grad,
|
$2::text = $1::text
|
||||||
profile_settings.hide_profile_details
|
OR
|
||||||
FROM profiles
|
u.deleted IS FALSE
|
||||||
JOIN users ON users.id = profiles.user_id
|
AND u.verified IS TRUE
|
||||||
JOIN profile_settings ON profiles.id = profile_settings.profile_id
|
AND NOT EXISTS (
|
||||||
WHERE users.username = $1 AND ($2 IS FALSE OR profile_settings.hide_for_unauthenticated IS FALSE)
|
SELECT 1
|
||||||
|
FROM banned_users
|
||||||
|
WHERE user_id = u.id
|
||||||
|
)
|
||||||
|
)
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetProfileByUsernameRestrictedParams struct {
|
type GetProfileByUsernameWithPrivacyParams struct {
|
||||||
Username string
|
Requester string
|
||||||
Column2 *bool
|
SearchedUsername string
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetProfileByUsernameRestrictedRow struct {
|
type GetProfileByUsernameWithPrivacyRow struct {
|
||||||
Username string
|
Username string
|
||||||
Name string
|
Name string
|
||||||
|
Bio string
|
||||||
|
AvatarUrl string
|
||||||
Birthday pgtype.Timestamp
|
Birthday pgtype.Timestamp
|
||||||
Bio *string
|
|
||||||
AvatarUrl *string
|
|
||||||
Color string
|
Color string
|
||||||
ColorGrad string
|
ColorGrad string
|
||||||
HideProfileDetails bool
|
AccessAllowed *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetProfileByUsernameRestricted(ctx context.Context, arg GetProfileByUsernameRestrictedParams) (GetProfileByUsernameRestrictedRow, error) {
|
func (q *Queries) GetProfileByUsernameWithPrivacy(ctx context.Context, arg GetProfileByUsernameWithPrivacyParams) (GetProfileByUsernameWithPrivacyRow, error) {
|
||||||
row := q.db.QueryRow(ctx, getProfileByUsernameRestricted, arg.Username, arg.Column2)
|
row := q.db.QueryRow(ctx, getProfileByUsernameWithPrivacy, arg.Requester, arg.SearchedUsername)
|
||||||
var i GetProfileByUsernameRestrictedRow
|
var i GetProfileByUsernameWithPrivacyRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.Username,
|
&i.Username,
|
||||||
&i.Name,
|
&i.Name,
|
||||||
&i.Birthday,
|
|
||||||
&i.Bio,
|
&i.Bio,
|
||||||
&i.AvatarUrl,
|
&i.AvatarUrl,
|
||||||
|
&i.Birthday,
|
||||||
&i.Color,
|
&i.Color,
|
||||||
&i.ColorGrad,
|
&i.ColorGrad,
|
||||||
&i.HideProfileDetails,
|
&i.AccessAllowed,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ type NewProfileDto struct {
|
|||||||
Name string `json:"name" binding:"required" validate:"name"`
|
Name string `json:"name" binding:"required" validate:"name"`
|
||||||
Bio string `json:"bio" validate:"bio"`
|
Bio string `json:"bio" validate:"bio"`
|
||||||
AvatarUploadID string `json:"avatar_upload_id" validate:"omitempty,upload_id=avatar"`
|
AvatarUploadID string `json:"avatar_upload_id" validate:"omitempty,upload_id=avatar"`
|
||||||
Birthday int64 `json:"birthday"`
|
Birthday int64 `json:"birthday" validate:"birthday_unix_milli"`
|
||||||
Color string `json:"color" validate:"color_hex"`
|
Color string `json:"color" validate:"color_hex"`
|
||||||
ColorGrad string `json:"color_grad" validate:"color_hex"`
|
ColorGrad string `json:"color_grad" validate:"color_hex"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ type RegistrationCompleteRequest struct {
|
|||||||
Username string `json:"username" binding:"required" validate:"username"`
|
Username string `json:"username" binding:"required" validate:"username"`
|
||||||
VerificationCode string `json:"verification_code" binding:"required" validate:"verification_code=reg"`
|
VerificationCode string `json:"verification_code" binding:"required" validate:"verification_code=reg"`
|
||||||
Name string `json:"name" binding:"required" validate:"name"`
|
Name string `json:"name" binding:"required" validate:"name"`
|
||||||
Birthday *string `json:"birthday"`
|
Birthday int64 `json:"birthday" validate:"birthday_unix_milli"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegistrationCompleteResponse struct {
|
type RegistrationCompleteResponse struct {
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgerrcode"
|
"github.com/jackc/pgerrcode"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -437,9 +438,15 @@ func (a *authServiceImpl) RegistrationComplete(cinfo dto.ClientInfo, request mod
|
|||||||
return nil, errs.ErrServerError
|
return nil, errs.ErrServerError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
birthdayTimestamp := pgtype.Timestamp {
|
||||||
|
Time: time.UnixMilli(request.Birthday),
|
||||||
|
Valid: true,
|
||||||
|
}
|
||||||
|
|
||||||
profile, err = db.TXQueries.CreateProfile(db.CTX, database.CreateProfileParams{
|
profile, err = db.TXQueries.CreateProfile(db.CTX, database.CreateProfileParams{
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
Name: request.Name,
|
Name: request.Name,
|
||||||
|
Birthday: birthdayTimestamp,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ func (p *profileServiceImpl) GetMyProfile(cinfo dto.ClientInfo) (*dto.ProfileDto
|
|||||||
return profileDto, nil
|
return profileDto, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Profile privacy settings checks
|
|
||||||
func (p *profileServiceImpl) GetProfileByUsername(cinfo dto.ClientInfo, username string) (*dto.ProfileDto, error) {
|
func (p *profileServiceImpl) GetProfileByUsername(cinfo dto.ClientInfo, username string) (*dto.ProfileDto, error) {
|
||||||
helper, db, err := database.NewDbHelperTransaction(p.dbctx); if err != nil {
|
helper, db, err := database.NewDbHelperTransaction(p.dbctx); if err != nil {
|
||||||
p.log.Error(
|
p.log.Error(
|
||||||
@@ -80,7 +79,10 @@ func (p *profileServiceImpl) GetProfileByUsername(cinfo dto.ClientInfo, username
|
|||||||
}
|
}
|
||||||
defer helper.Rollback()
|
defer helper.Rollback()
|
||||||
|
|
||||||
profile, err := db.TXQueries.GetProfileByUsername(db.CTX, username); if err != nil {
|
profileRow, err := db.TXQueries.GetProfileByUsernameWithPrivacy(db.CTX, database.GetProfileByUsernameWithPrivacyParams{
|
||||||
|
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
|
||||||
}
|
}
|
||||||
@@ -92,8 +94,18 @@ func (p *profileServiceImpl) GetProfileByUsername(cinfo dto.ClientInfo, username
|
|||||||
return nil, errs.ErrServerError
|
return nil, errs.ErrServerError
|
||||||
}
|
}
|
||||||
|
|
||||||
profileDto := &dto.ProfileDto{}
|
if !*profileRow.AccessAllowed {
|
||||||
mapspecial.MapProfileDto(profile, profileDto)
|
return nil, errs.ErrForbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
profileDto := &dto.ProfileDto{
|
||||||
|
Name: profileRow.Name,
|
||||||
|
Bio: profileRow.Bio,
|
||||||
|
AvatarUrl: &profileRow.AvatarUrl,
|
||||||
|
Birthday: profileRow.Birthday.Time.UnixMilli(),
|
||||||
|
Color: profileRow.Color,
|
||||||
|
ColorGrad: profileRow.ColorGrad,
|
||||||
|
}
|
||||||
|
|
||||||
return profileDto, nil
|
return profileDto, nil
|
||||||
}
|
}
|
||||||
@@ -114,7 +126,6 @@ func (p *profileServiceImpl) GetProfileSettings(cinfo dto.ClientInfo) (*dto.Prof
|
|||||||
return profileSettingsDto, nil
|
return profileSettingsDto, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX: no validation for timestamps' allowed ranges
|
|
||||||
func (p *profileServiceImpl) UpdateProfile(cinfo dto.ClientInfo, newProfile dto.NewProfileDto) (bool, error) {
|
func (p *profileServiceImpl) UpdateProfile(cinfo dto.ClientInfo, newProfile dto.NewProfileDto) (bool, error) {
|
||||||
helper, db, err := database.NewDbHelperTransaction(p.dbctx); if err != nil {
|
helper, db, err := database.NewDbHelperTransaction(p.dbctx); if err != nil {
|
||||||
p.log.Error(
|
p.log.Error(
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"easywish/config"
|
"easywish/config"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
)
|
)
|
||||||
@@ -55,6 +56,22 @@ func GetCustomHandlers() []CustomValidatorHandler {
|
|||||||
return regexp.MustCompile(`^.{1,512}$`).MatchString(username)
|
return regexp.MustCompile(`^.{1,512}$`).MatchString(username)
|
||||||
}},
|
}},
|
||||||
|
|
||||||
|
{
|
||||||
|
FieldName: "birthday_unix_milli",
|
||||||
|
Function: func(fl validator.FieldLevel) bool {
|
||||||
|
|
||||||
|
timestamp := fl.Field().Int()
|
||||||
|
date := time.UnixMilli(timestamp)
|
||||||
|
currentDate := time.Now()
|
||||||
|
|
||||||
|
age := currentDate.Year() - date.Year()
|
||||||
|
if currentDate.YearDay() < date.YearDay() {
|
||||||
|
age--
|
||||||
|
}
|
||||||
|
|
||||||
|
return age >= 0 && age <= 122
|
||||||
|
}},
|
||||||
|
|
||||||
{
|
{
|
||||||
FieldName: "color_hex",
|
FieldName: "color_hex",
|
||||||
Function: func(fl validator.FieldLevel) bool {
|
Function: func(fl validator.FieldLevel) bool {
|
||||||
|
|||||||
@@ -296,29 +296,33 @@ SELECT profiles.* FROM profiles
|
|||||||
JOIN users ON users.id = profiles.user_id
|
JOIN users ON users.id = profiles.user_id
|
||||||
WHERE users.username = $1;
|
WHERE users.username = $1;
|
||||||
|
|
||||||
;-- name: GetProfileByUsernameRestricted :one
|
;-- name: GetProfileByUsernameWithPrivacy :one
|
||||||
SELECT
|
SELECT
|
||||||
users.username,
|
u.username,
|
||||||
profiles.name,
|
p.name,
|
||||||
CASE
|
p.bio,
|
||||||
WHEN profile_settings.hide_birthday OR profile_settings.hide_profile_details THEN NULL
|
p.avatar_url,
|
||||||
ELSE profiles.birthday
|
CASE WHEN ps.hide_birthday THEN NULL ELSE p.birthday END AS birthday,
|
||||||
END AS birthday,
|
p.color,
|
||||||
CASE
|
p.color_grad,
|
||||||
WHEN profile_settings.hide_profile_details THEN NULL
|
NOT (@requester::text = '' AND ps.hide_for_unauthenticated) AS access_allowed
|
||||||
ELSE profiles.bio
|
FROM
|
||||||
END AS bio,
|
users AS u
|
||||||
CASE
|
JOIN profiles AS p ON u.id = p.user_id
|
||||||
WHEN profile_settings.hide_profile_details THEN NULL
|
JOIN profile_settings AS ps ON p.id = ps.profile_id
|
||||||
ELSE profiles.avatar_url
|
WHERE
|
||||||
END AS avatar_url,
|
u.username = @searched_username::text
|
||||||
profiles.color,
|
AND (
|
||||||
profiles.color_grad,
|
@searched_username::text = @requester::text
|
||||||
profile_settings.hide_profile_details
|
OR
|
||||||
FROM profiles
|
u.deleted IS FALSE
|
||||||
JOIN users ON users.id = profiles.user_id
|
AND u.verified IS TRUE
|
||||||
JOIN profile_settings ON profiles.id = profile_settings.profile_id
|
AND NOT EXISTS (
|
||||||
WHERE users.username = $1 AND ($2 IS FALSE OR profile_settings.hide_for_unauthenticated IS FALSE);
|
SELECT 1
|
||||||
|
FROM banned_users
|
||||||
|
WHERE user_id = u.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
;-- name: GetProfilesRestricted :many
|
;-- name: GetProfilesRestricted :many
|
||||||
SELECT
|
SELECT
|
||||||
|
|||||||
Reference in New Issue
Block a user