Add notification settings, account deletion, photo reporting, and pending-user photos
Addresses user bug reports: settings pages needed real notification preferences and account deletion, admin needed a way to flag a specific photo independent of deleting it, and the pending-approval users list needed to surface applicant photos for review. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
module dating-app
|
module dating-app
|
||||||
|
|
||||||
go 1.22.2
|
go 1.25
|
||||||
|
|
||||||
replace golang.org/x/net => github.com/golang/net v0.25.0
|
replace golang.org/x/net => github.com/golang/net v0.25.0
|
||||||
|
|
||||||
@@ -26,44 +26,49 @@ replace google.golang.org/protobuf => github.com/protocolbuffers/protobuf-go v1.
|
|||||||
|
|
||||||
replace golang.org/x/xerrors => github.com/golang/xerrors v0.0.0-20191204190536-9bdfabe68543
|
replace golang.org/x/xerrors => github.com/golang/xerrors v0.0.0-20191204190536-9bdfabe68543
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
gorm.io/driver/postgres v1.5.9
|
||||||
|
gorm.io/gorm v1.25.10
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/sonic v1.9.1 // indirect
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/joho/godotenv v1.5.1 // indirect
|
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.4 // indirect
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.16.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
golang.org/x/arch v0.3.0 // indirect
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
golang.org/x/crypto v0.23.0 // indirect
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
golang.org/x/net v0.21.0 // indirect
|
golang.org/x/net v0.21.0 // indirect
|
||||||
golang.org/x/sync v0.1.0 // indirect
|
golang.org/x/sync v0.1.0 // indirect
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/text v0.15.0 // indirect
|
golang.org/x/text v0.15.0 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
gorm.io/driver/postgres v1.5.9 // indirect
|
|
||||||
gorm.io/gorm v1.25.10 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
||||||
replace gorm.io/gorm => github.com/go-gorm/gorm v1.25.10
|
replace gorm.io/gorm => github.com/go-gorm/gorm v1.25.10
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZX
|
|||||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
@@ -12,11 +14,14 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
|
|||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/go-check/check v0.0.0-20180628173108-788fd7840127 h1:0gkP6mzaMqkmpcJYCFOLkIBwI7xFExG03bbkOkCvUPI=
|
||||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||||
github.com/go-gorm/gorm v1.25.10 h1:s8FP+QiJt4yTWhaqqaTEXmJezVd4pLuYihWFa5ka6oc=
|
github.com/go-gorm/gorm v1.25.10 h1:s8FP+QiJt4yTWhaqqaTEXmJezVd4pLuYihWFa5ka6oc=
|
||||||
github.com/go-gorm/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
github.com/go-gorm/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
github.com/go-gorm/postgres v1.5.9 h1:R8GFj+hNnViUX6OKlL8jp7b5fVUVlDE3ULqQ+ae7aPk=
|
github.com/go-gorm/postgres v1.5.9 h1:R8GFj+hNnViUX6OKlL8jp7b5fVUVlDE3ULqQ+ae7aPk=
|
||||||
github.com/go-gorm/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
github.com/go-gorm/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
@@ -42,7 +47,9 @@ github.com/golang/sys v0.20.0 h1:s6f6MpCY0CE+A62kAS9FiDNmD3rpVbd+OISylPY45NQ=
|
|||||||
github.com/golang/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
github.com/golang/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
github.com/golang/text v0.15.0 h1:Hr6Y5cbI9MiTMnLQhMfqQzC3smwk0O9n6gYp78JNYJ4=
|
github.com/golang/text v0.15.0 h1:Hr6Y5cbI9MiTMnLQhMfqQzC3smwk0O9n6gYp78JNYJ4=
|
||||||
github.com/golang/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
github.com/golang/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
github.com/golang/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:WKKabR3QofYgj3cabwq+Blt8MSvmXR4GKLoohSdeY1A=
|
||||||
github.com/golang/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
github.com/golang/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -66,6 +73,10 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
|
|||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
@@ -77,9 +88,12 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
|
|||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/protocolbuffers/protobuf-go v1.30.0 h1:8l0IAhOWx+qT8fxNzYIj+TS9SEU6xamBNS0ZmRIy3eY=
|
github.com/protocolbuffers/protobuf-go v1.30.0 h1:8l0IAhOWx+qT8fxNzYIj+TS9SEU6xamBNS0ZmRIy3eY=
|
||||||
github.com/protocolbuffers/protobuf-go v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
github.com/protocolbuffers/protobuf-go v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
github.com/rogpeppe/go-internal v1.16.0 h1:O9DK+vNMDVGLr2BeZqmpLeMjiMNkuXfcqntWbZV6S5g=
|
||||||
|
github.com/rogpeppe/go-internal v1.16.0/go.mod h1:DrUVZyrJU+txYW5/1kwtXQSMFio52ZOxX7yM1VHvnxs=
|
||||||
github.com/rsc/pdf v0.1.1/go.mod h1:+1cgsOaCtY/6pdg9OEno8rLCRIQj5LeL9ckNDtNZsFs=
|
github.com/rsc/pdf v0.1.1/go.mod h1:+1cgsOaCtY/6pdg9OEno8rLCRIQj5LeL9ckNDtNZsFs=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
@@ -90,6 +104,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
|
||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
|||||||
@@ -51,4 +51,14 @@ type UserProfile struct {
|
|||||||
BlockedAt *time.Time `json:"blocked_at,omitempty"`
|
BlockedAt *time.Time `json:"blocked_at,omitempty"`
|
||||||
Bio *string `json:"bio,omitempty"`
|
Bio *string `json:"bio,omitempty"`
|
||||||
StreakCount int `json:"streak_count"`
|
StreakCount int `json:"streak_count"`
|
||||||
|
NotifyMatches bool `json:"notify_matches"`
|
||||||
|
NotifyMessages bool `json:"notify_messages"`
|
||||||
|
NotifyLikes bool `json:"notify_likes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserProfileWithPhotos برای لیستهای ادمینی که علاوه بر خلاصهی پروفایل، عکس
|
||||||
|
// کاربر رو هم نشون میدن (مثلاً کاربران در انتظار تایید).
|
||||||
|
type UserProfileWithPhotos struct {
|
||||||
|
UserProfile
|
||||||
|
Photos []string `json:"photos"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,3 +16,10 @@ type CheckInResult struct {
|
|||||||
StreakCount int `json:"streak_count"`
|
StreakCount int `json:"streak_count"`
|
||||||
IsNewToday bool `json:"is_new_today"`
|
IsNewToday bool `json:"is_new_today"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateNotificationSettingsRequest ترجیحات اعلان کاربر
|
||||||
|
type UpdateNotificationSettingsRequest struct {
|
||||||
|
NotifyMatches bool `json:"notify_matches"`
|
||||||
|
NotifyMessages bool `json:"notify_messages"`
|
||||||
|
NotifyLikes bool `json:"notify_likes"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -165,14 +165,21 @@ func (h *AdminHandler) DeleteOption(c *gin.Context) {
|
|||||||
|
|
||||||
// ListPendingUsers GET /admin/users/pending
|
// ListPendingUsers GET /admin/users/pending
|
||||||
func (h *AdminHandler) ListPendingUsers(c *gin.Context) {
|
func (h *AdminHandler) ListPendingUsers(c *gin.Context) {
|
||||||
users, err := h.adminService.ListPendingUsers()
|
users, photosByUser, err := h.adminService.ListPendingUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fail(c, http.StatusInternalServerError, "خطا در دریافت لیست کاربران", nil)
|
utils.Fail(c, http.StatusInternalServerError, "خطا در دریافت لیست کاربران", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result := make([]dto.UserProfile, 0, len(users))
|
result := make([]dto.UserProfileWithPhotos, 0, len(users))
|
||||||
for i := range users {
|
for i := range users {
|
||||||
result = append(result, toUserProfile(&users[i]))
|
photos := photosByUser[users[i].ID]
|
||||||
|
if photos == nil {
|
||||||
|
photos = []string{}
|
||||||
|
}
|
||||||
|
result = append(result, dto.UserProfileWithPhotos{
|
||||||
|
UserProfile: toUserProfile(&users[i]),
|
||||||
|
Photos: photos,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
utils.Success(c, http.StatusOK, "", result)
|
utils.Success(c, http.StatusOK, "", result)
|
||||||
}
|
}
|
||||||
@@ -382,6 +389,30 @@ func (h *AdminHandler) DeletePhoto(c *gin.Context) {
|
|||||||
utils.Success(c, http.StatusOK, "عکس حذف شد", nil)
|
utils.Success(c, http.StatusOK, "عکس حذف شد", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReportPhoto POST /admin/photos/:id/report body: {reason}
|
||||||
|
func (h *AdminHandler) ReportPhoto(c *gin.Context) {
|
||||||
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req dto.ReportUserRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است (ذکر دلیل گزارش الزامی است)", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
report, err := h.adminService.ReportPhoto(uint(id), req.Reason)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
utils.Fail(c, http.StatusNotFound, "عکس یافت نشد", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.Fail(c, http.StatusBadRequest, err.Error(), nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.Success(c, http.StatusCreated, "گزارش ثبت شد", report)
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------- سوالات پروفایل ----------------
|
// ---------------- سوالات پروفایل ----------------
|
||||||
|
|
||||||
// ListQuestions GET /admin/profile-questions
|
// ListQuestions GET /admin/profile-questions
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ func toUserProfile(u *models.User) dto.UserProfile {
|
|||||||
BlockedAt: u.BlockedAt,
|
BlockedAt: u.BlockedAt,
|
||||||
Bio: u.Bio,
|
Bio: u.Bio,
|
||||||
StreakCount: u.StreakCount,
|
StreakCount: u.StreakCount,
|
||||||
|
NotifyMatches: u.NotifyMatches,
|
||||||
|
NotifyMessages: u.NotifyMessages,
|
||||||
|
NotifyLikes: u.NotifyLikes,
|
||||||
}
|
}
|
||||||
if u.Gender != nil {
|
if u.Gender != nil {
|
||||||
g := string(*u.Gender)
|
g := string(*u.Gender)
|
||||||
|
|||||||
@@ -170,3 +170,34 @@ func (h *ProfileHandler) CheckIn(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
utils.Success(c, http.StatusOK, "", result)
|
utils.Success(c, http.StatusOK, "", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateNotificationSettings PATCH /profile/notifications
|
||||||
|
func (h *ProfileHandler) UpdateNotificationSettings(c *gin.Context) {
|
||||||
|
user, ok := currentUser(c)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var req dto.UpdateNotificationSettingsRequest
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.profileService.UpdateNotificationSettings(user, req); err != nil {
|
||||||
|
utils.Fail(c, http.StatusInternalServerError, "خطا در ذخیرهی تنظیمات اعلان", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.Success(c, http.StatusOK, "تنظیمات اعلان ذخیره شد", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccount DELETE /profile/me — غیرفعال کردن (soft delete) حساب کاربر
|
||||||
|
func (h *ProfileHandler) DeleteAccount(c *gin.Context) {
|
||||||
|
user, ok := currentUser(c)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.profileService.DeleteAccount(user); err != nil {
|
||||||
|
utils.Fail(c, http.StatusInternalServerError, "خطا در حذف حساب", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
utils.Success(c, http.StatusOK, "حساب کاربری غیرفعال شد", nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ package models
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// Report یک یادداشت/پرچم نظارتی که ادمین روی یک کاربر ثبت میکنه (مثلاً بر
|
// Report یک یادداشت/پرچم نظارتی که ادمین روی یک کاربر (یا یک عکس مشخص از
|
||||||
// اساس شکایتی که از کانال دیگهای دریافت شده)؛ برای پیگیری سابقهی نظارتی
|
// اون کاربر) ثبت میکنه؛ برای پیگیری سابقهی نظارتی، مستقل از بلاک/حذف کردن.
|
||||||
// یک کاربر، مستقل از بلاکشدن یا نشدنش.
|
|
||||||
type Report struct {
|
type Report struct {
|
||||||
ID uint `gorm:"primaryKey" json:"id"`
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
UserID uint `gorm:"not null;index" json:"user_id"`
|
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||||
|
PhotoID *uint `json:"photo_id,omitempty"`
|
||||||
Reason string `gorm:"type:text;not null" json:"reason"`
|
Reason string `gorm:"type:text;not null" json:"reason"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,6 +97,13 @@ type User struct {
|
|||||||
StreakCount int `gorm:"default:0;not null" json:"streak_count"`
|
StreakCount int `gorm:"default:0;not null" json:"streak_count"`
|
||||||
LastActiveDate *time.Time `json:"last_active_date,omitempty"`
|
LastActiveDate *time.Time `json:"last_active_date,omitempty"`
|
||||||
|
|
||||||
|
// -------- ترجیحات اعلان --------
|
||||||
|
// فعلاً فقط ذخیره میشن (زیرساخت ارسال واقعی اعلان هنوز پیاده نشده)؛ وقتی
|
||||||
|
// اون سیستم اضافه شد، همین مقادیر رو برای تصمیم به ارسال یا نه میخونه.
|
||||||
|
NotifyMatches bool `gorm:"default:true;not null" json:"notify_matches"`
|
||||||
|
NotifyMessages bool `gorm:"default:true;not null" json:"notify_messages"`
|
||||||
|
NotifyLikes bool `gorm:"default:true;not null" json:"notify_likes"`
|
||||||
|
|
||||||
// -------- موقعیت مکانی (برای محاسبهی فاصلهی تقریبی در دوستیابی) --------
|
// -------- موقعیت مکانی (برای محاسبهی فاصلهی تقریبی در دوستیابی) --------
|
||||||
Latitude *float64 `json:"-"`
|
Latitude *float64 `json:"-"`
|
||||||
Longitude *float64 `json:"-"`
|
Longitude *float64 `json:"-"`
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ func Setup(
|
|||||||
profile.PATCH("/bio", h.Profile.UpdateBio)
|
profile.PATCH("/bio", h.Profile.UpdateBio)
|
||||||
// استریک روزانه (گیمیفیکیشن): هر بار اپ باز میشه یه بار صدا زده میشه
|
// استریک روزانه (گیمیفیکیشن): هر بار اپ باز میشه یه بار صدا زده میشه
|
||||||
profile.POST("/checkin", h.Profile.CheckIn)
|
profile.POST("/checkin", h.Profile.CheckIn)
|
||||||
|
profile.PATCH("/notifications", h.Profile.UpdateNotificationSettings)
|
||||||
|
profile.DELETE("/me", h.Profile.DeleteAccount)
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------- آنبوردینگ (نیاز به ورود) --------
|
// -------- آنبوردینگ (نیاز به ورود) --------
|
||||||
@@ -115,6 +117,7 @@ func Setup(
|
|||||||
// نظارت بر عکسهای آپلودشده
|
// نظارت بر عکسهای آپلودشده
|
||||||
admin.GET("/photos", h.Admin.ListAllPhotos)
|
admin.GET("/photos", h.Admin.ListAllPhotos)
|
||||||
admin.DELETE("/photos/:id", h.Admin.DeletePhoto)
|
admin.DELETE("/photos/:id", h.Admin.DeletePhoto)
|
||||||
|
admin.POST("/photos/:id/report", h.Admin.ReportPhoto)
|
||||||
|
|
||||||
admin.GET("/categories/:key", h.Admin.GetCategory)
|
admin.GET("/categories/:key", h.Admin.GetCategory)
|
||||||
admin.PATCH("/categories/:key", h.Admin.UpdateCategory)
|
admin.PATCH("/categories/:key", h.Admin.UpdateCategory)
|
||||||
|
|||||||
@@ -52,9 +52,29 @@ func (s *AdminService) GetUser(id uint) (*models.User, error) {
|
|||||||
return s.userRepo.FindByID(id)
|
return s.userRepo.FindByID(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListPendingUsers کاربرانی که آنبوردینگ رو تکمیل کردن و منتظر بررسیاند
|
// ListPendingUsers کاربرانی که آنبوردینگ رو تکمیل کردن و منتظر بررسیاند، به
|
||||||
func (s *AdminService) ListPendingUsers() ([]models.User, error) {
|
// همراه عکسهاشون (batch گرفته میشه، نه یک کوئری جدا بهازای هر کاربر) تا
|
||||||
return s.userRepo.ListPendingApproval()
|
// ادمین بدون کلیک روی هرکدوم بتونه یه نگاه سریع به عکسها بندازه.
|
||||||
|
func (s *AdminService) ListPendingUsers() ([]models.User, map[uint][]string, error) {
|
||||||
|
users, err := s.userRepo.ListPendingApproval()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ids := make([]uint, len(users))
|
||||||
|
for i, u := range users {
|
||||||
|
ids[i] = u.ID
|
||||||
|
}
|
||||||
|
photos, err := s.photoRepo.ListByUserIDs(ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
photosByUser := make(map[uint][]string, len(ids))
|
||||||
|
for _, p := range photos {
|
||||||
|
photosByUser[p.UserID] = append(photosByUser[p.UserID], p.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
return users, photosByUser, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApproveUser کاربر رو تایید میکنه تا بتونه وارد صفحهی دوستیابی بشه
|
// ApproveUser کاربر رو تایید میکنه تا بتونه وارد صفحهی دوستیابی بشه
|
||||||
@@ -205,6 +225,23 @@ func (s *AdminService) ReportUser(id uint, reason string) (*models.Report, error
|
|||||||
return report, nil
|
return report, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReportPhoto یک عکس مشخص رو گزارش میکنه (روی صاحب عکس هم توی سابقهی
|
||||||
|
// گزارشها ثبت میشه، با ارجاع به همون عکس).
|
||||||
|
func (s *AdminService) ReportPhoto(photoID uint, reason string) (*models.Report, error) {
|
||||||
|
if reason == "" {
|
||||||
|
return nil, ErrReportReasonEmpty
|
||||||
|
}
|
||||||
|
photo, err := s.photoRepo.FindByID(photoID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
report := &models.Report{UserID: photo.UserID, PhotoID: &photo.ID, Reason: reason}
|
||||||
|
if err := s.reportRepo.Create(report); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return report, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ListReports سابقهی گزارشهای ثبتشده روی یک کاربر رو برمیگردونه.
|
// ListReports سابقهی گزارشهای ثبتشده روی یک کاربر رو برمیگردونه.
|
||||||
func (s *AdminService) ListReports(userID uint) ([]models.Report, error) {
|
func (s *AdminService) ListReports(userID uint) ([]models.Report, error) {
|
||||||
return s.reportRepo.ListByUser(userID)
|
return s.reportRepo.ListByUser(userID)
|
||||||
|
|||||||
@@ -141,3 +141,25 @@ func (s *ProfileService) CheckIn(user *models.User) (*dto.CheckInResult, error)
|
|||||||
|
|
||||||
return &dto.CheckInResult{StreakCount: newStreak, IsNewToday: true}, nil
|
return &dto.CheckInResult{StreakCount: newStreak, IsNewToday: true}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateNotificationSettings ترجیحات اعلان کاربر رو ذخیره میکنه.
|
||||||
|
func (s *ProfileService) UpdateNotificationSettings(user *models.User, req dto.UpdateNotificationSettingsRequest) error {
|
||||||
|
if err := s.userRepo.UpdateFields(user, map[string]interface{}{
|
||||||
|
"notify_matches": req.NotifyMatches,
|
||||||
|
"notify_messages": req.NotifyMessages,
|
||||||
|
"notify_likes": req.NotifyLikes,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
user.NotifyMatches = req.NotifyMatches
|
||||||
|
user.NotifyMessages = req.NotifyMessages
|
||||||
|
user.NotifyLikes = req.NotifyLikes
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAccount حساب کاربر رو غیرفعال میکنه (soft) نه پاکسازی کامل رکورد؛
|
||||||
|
// چون مچها/پیامهای طرف مقابل نباید یهو پاره بشن. کاربر غیرفعال دیگه اصلاً
|
||||||
|
// نمیتونه لاگین کنه (چک IsActive توی AuthRequired و AuthService).
|
||||||
|
func (s *ProfileService) DeleteAccount(user *models.User) error {
|
||||||
|
return s.userRepo.UpdateFields(user, map[string]interface{}{"is_active": false})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user