upd(system): add ctx to service ctors, tests
This commit is contained in:
parent
947548b4f5
commit
74d1afdeea
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
|
/coverage.txt
|
||||||
/*.iml
|
/*.iml
|
||||||
/.env*
|
/.env*
|
||||||
/.cover*
|
/.cover*
|
||||||
|
|||||||
@ -1,56 +0,0 @@
|
|||||||
package service
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
gomail "gopkg.in/mail.v2"
|
|
||||||
|
|
||||||
"github.com/crusttech/crust/internal/test"
|
|
||||||
"github.com/crusttech/crust/system/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUserRefExpanding(t *testing.T) {
|
|
||||||
mockCtrl := gomock.NewController(t)
|
|
||||||
defer mockCtrl.Finish()
|
|
||||||
|
|
||||||
ntf := ¬ification{}
|
|
||||||
|
|
||||||
// msg := &gomail.Message{}
|
|
||||||
usr := &types.User{ID: 72932592256548967, Email: "user@mock.ed", Name: "Mocked Owner"}
|
|
||||||
|
|
||||||
usrSvc := NewMocknotificationUserService(mockCtrl)
|
|
||||||
usrSvc.EXPECT().FindByID(usr.ID).Times(1).Return(usr, nil)
|
|
||||||
ntf.userSvc = usrSvc
|
|
||||||
|
|
||||||
input := []string{"sample@domain.tld", "sample@domain.tld Name", "72932592256548967"}
|
|
||||||
rcpts, err := ntf.expandUserRefs(usrSvc, input)
|
|
||||||
test.NoError(t, err, "expandUserRefs returned an error: %v")
|
|
||||||
test.Assert(t, len(rcpts) == len(input), "Expecting %d headers, got %d", len(input), len(rcpts))
|
|
||||||
test.Assert(t, rcpts[2] == usr.Email+" "+usr.Name, "Expecting %d headers, got %d", len(input), len(rcpts))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAttachEmailRecipients(t *testing.T) {
|
|
||||||
mockCtrl := gomock.NewController(t)
|
|
||||||
defer mockCtrl.Finish()
|
|
||||||
|
|
||||||
ntf := ¬ification{}
|
|
||||||
|
|
||||||
usrSvc := NewMocknotificationUserService(mockCtrl)
|
|
||||||
usrSvc.EXPECT().With(gomock.Any())
|
|
||||||
ntf.userSvc = usrSvc
|
|
||||||
|
|
||||||
msg := gomail.NewMessage()
|
|
||||||
|
|
||||||
input := []string{"sample@domain.tld", "sample2@domain.tld First Name"}
|
|
||||||
err := ntf.AttachEmailRecipients(msg, "To", input...)
|
|
||||||
to := msg.GetHeader("To")
|
|
||||||
|
|
||||||
test.NoError(t, err, "AttachEmailRecipients returned an error: %v")
|
|
||||||
|
|
||||||
test.Assert(t, len(input) == len(to), "Expecting %d headers, got %d", len(input), len(to))
|
|
||||||
|
|
||||||
test.Assert(t, to[0] == "sample@domain.tld", "Expecting address to match, got %v", to[0])
|
|
||||||
|
|
||||||
test.Assert(t, to[1] == "\"First Name\" <sample2@domain.tld>", "Expecting address to match, got %v", to[1])
|
|
||||||
}
|
|
||||||
@ -17,6 +17,10 @@ import (
|
|||||||
|
|
||||||
func TestPermissions(t *testing.T) {
|
func TestPermissions(t *testing.T) {
|
||||||
ctx := context.WithValue(context.Background(), "testing", true)
|
ctx := context.WithValue(context.Background(), "testing", true)
|
||||||
|
{
|
||||||
|
user := &systemTypes.User{ID: 1337}
|
||||||
|
ctx = auth.SetIdentityToContext(ctx, auth.NewIdentity(user.Identity()))
|
||||||
|
}
|
||||||
|
|
||||||
// Create user with role and add it to context.
|
// Create user with role and add it to context.
|
||||||
userSvc := systemService.TestUser(t, ctx)
|
userSvc := systemService.TestUser(t, ctx)
|
||||||
@ -26,12 +30,11 @@ func TestPermissions(t *testing.T) {
|
|||||||
SatosaID: "12345",
|
SatosaID: "12345",
|
||||||
}
|
}
|
||||||
err := user.GeneratePassword("johndoe")
|
err := user.GeneratePassword("johndoe")
|
||||||
NoError(t, err, "expected no error generating password, got %v", err)
|
NoError(t, err, "expected no error generating password, got %+v", err)
|
||||||
|
|
||||||
_, err = userSvc.Create(user, nil, "")
|
_, err = userSvc.Create(user)
|
||||||
NoError(t, err, "expected no error creating user, got %v", err)
|
NoError(t, err, "expected no error creating user, got %+v", err)
|
||||||
|
|
||||||
// Set Identity.
|
|
||||||
ctx = auth.SetIdentityToContext(ctx, user)
|
ctx = auth.SetIdentityToContext(ctx, user)
|
||||||
|
|
||||||
roleSvc := systemService.TestRole(t, ctx)
|
roleSvc := systemService.TestRole(t, ctx)
|
||||||
@ -39,10 +42,10 @@ func TestPermissions(t *testing.T) {
|
|||||||
Name: "Test role v1",
|
Name: "Test role v1",
|
||||||
}
|
}
|
||||||
role, err = roleSvc.Create(role)
|
role, err = roleSvc.Create(role)
|
||||||
NoError(t, err, "expected no error creating role, got %v", err)
|
NoError(t, err, "expected no error creating role, got %+v", err)
|
||||||
|
|
||||||
err = roleSvc.MemberAdd(role.ID, user.ID)
|
err = roleSvc.MemberAdd(role.ID, user.ID)
|
||||||
NoError(t, err, "expected no error adding user to role, got %v", err)
|
NoError(t, err, "expected no error adding user to role, got %+v", err)
|
||||||
|
|
||||||
// Insert `grant` permission for `compose`.
|
// Insert `grant` permission for `compose`.
|
||||||
{
|
{
|
||||||
@ -54,7 +57,7 @@ func TestPermissions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := resources.Grant(role.ID, list)
|
err := resources.Grant(role.ID, list)
|
||||||
NoError(t, err, "expected no error, got %v", err)
|
NoError(t, err, "expected no error, got %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate services.
|
// Generate services.
|
||||||
@ -64,16 +67,16 @@ func TestPermissions(t *testing.T) {
|
|||||||
|
|
||||||
// Test `access` to compose service.
|
// Test `access` to compose service.
|
||||||
ret := permissionsSvc.CanAccess()
|
ret := permissionsSvc.CanAccess()
|
||||||
Assert(t, ret == false, "expected CanAccess == false, got %v", ret)
|
Assert(t, ret == false, "expected CanAccess == false, got %+v", ret)
|
||||||
|
|
||||||
// Add `access` to compose service.
|
// Add `access` to compose service.
|
||||||
list := []rules.Rule{
|
list := []rules.Rule{
|
||||||
rules.Rule{Resource: types.PermissionResource, Operation: "access", Value: rules.Allow},
|
rules.Rule{Resource: types.PermissionResource, Operation: "access", Value: rules.Allow},
|
||||||
}
|
}
|
||||||
_, err = systemRulesSvc.Update(role.ID, list)
|
_, err = systemRulesSvc.Update(role.ID, list)
|
||||||
NoError(t, err, "expected no error, got %v", err)
|
NoError(t, err, "expected no error, got %+v", err)
|
||||||
|
|
||||||
// Test `access` to compose service.
|
// Test `access` to compose service.
|
||||||
ret = permissionsSvc.CanAccess()
|
ret = permissionsSvc.CanAccess()
|
||||||
Assert(t, ret == true, "expected CanAccess == true, got %v", ret)
|
Assert(t, ret == true, "expected CanAccess == true, got %+v", ret)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,9 @@ func TestRecord(t *testing.T) {
|
|||||||
ID: 1337,
|
ID: 1337,
|
||||||
Username: "TestUser",
|
Username: "TestUser",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx = auth.SetIdentityToContext(ctx, auth.NewIdentity(user.Identity()))
|
||||||
|
|
||||||
{
|
{
|
||||||
err := user.GeneratePassword("Mary had a little lamb, little lamb, little lamb")
|
err := user.GeneratePassword("Mary had a little lamb, little lamb, little lamb")
|
||||||
test.Assert(t, err == nil, "Error generating password: %+v", err)
|
test.Assert(t, err == nil, "Error generating password: %+v", err)
|
||||||
@ -27,12 +30,10 @@ func TestRecord(t *testing.T) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
userSvc := systemService.TestUser(t, ctx)
|
userSvc := systemService.TestUser(t, ctx)
|
||||||
_, err := userSvc.Create(user, nil, "")
|
_, err := userSvc.Create(user)
|
||||||
test.NoError(t, err, "expected no error creating user, got %v", err)
|
test.NoError(t, err, "expected no error creating user, got %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = auth.SetIdentityToContext(ctx, auth.NewIdentity(user.Identity()))
|
|
||||||
|
|
||||||
svc := Record().With(ctx)
|
svc := Record().With(ctx)
|
||||||
|
|
||||||
module := &types.Module{
|
module := &types.Module{
|
||||||
|
|||||||
@ -1,67 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/titpetric/factory"
|
|
||||||
|
|
||||||
"github.com/crusttech/crust/system/types"
|
|
||||||
|
|
||||||
. "github.com/crusttech/crust/internal/test"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestApplication(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping test in short mode.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
db := factory.Database.MustGet()
|
|
||||||
|
|
||||||
// Create application repository.
|
|
||||||
crepo := Application(context.Background(), db)
|
|
||||||
|
|
||||||
// Run tests in transaction to maintain DB state.
|
|
||||||
Error(t, db.Transaction(func() error {
|
|
||||||
db.Exec("DELETE FROM sys_application WHERE 1=1")
|
|
||||||
|
|
||||||
app := &types.Application{
|
|
||||||
Name: "created",
|
|
||||||
Enabled: true,
|
|
||||||
OwnerID: 1,
|
|
||||||
Unify: &types.ApplicationUnify{
|
|
||||||
Name: "created",
|
|
||||||
Listed: true,
|
|
||||||
Order: 1,
|
|
||||||
Icon: "...ico",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
app, err := crepo.Create(app)
|
|
||||||
NoError(t, err, "Application.Create error: %+v", err)
|
|
||||||
Assert(t, app.Valid(), "Expecting application to be valid after creation")
|
|
||||||
Assert(t, app.Name == "created", "Expecting application name to be set, got %q", app.Name)
|
|
||||||
Assert(t, app.Enabled, "Expecting application to be enabled")
|
|
||||||
Assert(t, app.Unify.Name == "created", "Expecting application name to be set in unify, got %q", app.Name)
|
|
||||||
Assert(t, app.Unify.Listed, "Expecting application to be listed in unify")
|
|
||||||
Assert(t, app.Unify.Order == 1, "Expecting application name to have order val 1")
|
|
||||||
|
|
||||||
app.Name = "updated"
|
|
||||||
app.Enabled = false
|
|
||||||
app.Unify.Name = "updated"
|
|
||||||
app.Unify.Listed = false
|
|
||||||
app, err = crepo.Update(app)
|
|
||||||
|
|
||||||
NoError(t, err, "Application.Create error: %+v", err)
|
|
||||||
Assert(t, err == nil, "Application.Create error: %+v", err)
|
|
||||||
Assert(t, app.Name == "updated", "Expecting application name to be updated")
|
|
||||||
Assert(t, !app.Enabled, "Expecting application to be disabled")
|
|
||||||
Assert(t, app.Unify.Name == "updated", "Expecting application name to be updated in unify")
|
|
||||||
Assert(t, !app.Unify.Listed, "Expecting application to be unlisted in unify")
|
|
||||||
|
|
||||||
return errors.New("Rollback")
|
|
||||||
}), "expected rollback error")
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -32,10 +32,8 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func Application() ApplicationService {
|
func Application(ctx context.Context) ApplicationService {
|
||||||
return (&application{
|
return (&application{}).With(ctx)
|
||||||
prm: DefaultPermissions,
|
|
||||||
}).With(context.Background())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *application) With(ctx context.Context) ApplicationService {
|
func (svc *application) With(ctx context.Context) ApplicationService {
|
||||||
@ -43,7 +41,7 @@ func (svc *application) With(ctx context.Context) ApplicationService {
|
|||||||
return &application{
|
return &application{
|
||||||
db: db,
|
db: db,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
prm: svc.prm.With(ctx),
|
prm: Permissions(ctx),
|
||||||
application: repository.Application(ctx, db),
|
application: repository.Application(ctx, db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,8 +32,8 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func Auth() AuthService {
|
func Auth(ctx context.Context) AuthService {
|
||||||
return (&auth{}).With(context.Background())
|
return (&auth{}).With(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *auth) With(ctx context.Context) AuthService {
|
func (svc *auth) With(ctx context.Context) AuthService {
|
||||||
|
|||||||
@ -32,8 +32,8 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func Organisation() OrganisationService {
|
func Organisation(ctx context.Context) OrganisationService {
|
||||||
return (&organisation{}).With(context.Background())
|
return (&organisation{}).With(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *organisation) With(ctx context.Context) OrganisationService {
|
func (svc *organisation) With(ctx context.Context) OrganisationService {
|
||||||
|
|||||||
@ -52,10 +52,8 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func Permissions() PermissionsService {
|
func Permissions(ctx context.Context) PermissionsService {
|
||||||
return (&permissions{
|
return (&permissions{}).With(ctx)
|
||||||
rules: DefaultRules,
|
|
||||||
}).With(context.Background())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *permissions) With(ctx context.Context) PermissionsService {
|
func (p *permissions) With(ctx context.Context) PermissionsService {
|
||||||
@ -64,7 +62,7 @@ func (p *permissions) With(ctx context.Context) PermissionsService {
|
|||||||
db: db,
|
db: db,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
|
||||||
rules: p.rules.With(ctx),
|
rules: Rules(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,10 +41,8 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func Role() RoleService {
|
func Role(ctx context.Context) RoleService {
|
||||||
return (&role{
|
return (&role{}).With(ctx)
|
||||||
prm: DefaultPermissions,
|
|
||||||
}).With(context.Background())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *role) With(ctx context.Context) RoleService {
|
func (svc *role) With(ctx context.Context) RoleService {
|
||||||
@ -53,8 +51,7 @@ func (svc *role) With(ctx context.Context) RoleService {
|
|||||||
db: db,
|
db: db,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
|
|
||||||
prm: svc.prm.With(ctx),
|
prm: Permissions(ctx),
|
||||||
|
|
||||||
role: repository.Role(ctx, db),
|
role: repository.Role(ctx, db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,8 +42,8 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func Rules() RulesService {
|
func Rules(ctx context.Context) RulesService {
|
||||||
return (&rules{}).With(context.Background())
|
return (&rules{}).With(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *rules) With(ctx context.Context) RulesService {
|
func (p *rules) With(ctx context.Context) RulesService {
|
||||||
|
|||||||
@ -30,7 +30,7 @@ func TestRules(t *testing.T) {
|
|||||||
ctx := internalAuth.SetIdentityToContext(context.Background(), user)
|
ctx := internalAuth.SetIdentityToContext(context.Background(), user)
|
||||||
|
|
||||||
// Create rules service.
|
// Create rules service.
|
||||||
rulesSvc := Rules().With(ctx)
|
rulesSvc := Rules(ctx)
|
||||||
rulesObj := rulesSvc.(*rules)
|
rulesObj := rulesSvc.(*rules)
|
||||||
|
|
||||||
// Connect do DB.
|
// Connect do DB.
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
package service
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
db interface {
|
db interface {
|
||||||
Transaction(callback func() error) error
|
Transaction(callback func() error) error
|
||||||
@ -17,12 +21,13 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Init() error {
|
func Init() error {
|
||||||
DefaultRules = Rules()
|
ctx := context.Background()
|
||||||
DefaultPermissions = Permissions()
|
DefaultRules = Rules(ctx)
|
||||||
DefaultAuth = Auth()
|
DefaultPermissions = Permissions(ctx)
|
||||||
DefaultUser = User()
|
DefaultAuth = Auth(ctx)
|
||||||
DefaultRole = Role()
|
DefaultUser = User(ctx)
|
||||||
DefaultOrganisation = Organisation()
|
DefaultRole = Role(ctx)
|
||||||
DefaultApplication = Application()
|
DefaultOrganisation = Organisation(ctx)
|
||||||
|
DefaultApplication = Application(ctx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,8 +50,8 @@ type (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func User() UserService {
|
func User(ctx context.Context) UserService {
|
||||||
return (&user{}).With(context.Background())
|
return (&user{}).With(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *user) With(ctx context.Context) UserService {
|
func (svc *user) With(ctx context.Context) UserService {
|
||||||
@ -60,7 +60,7 @@ func (svc *user) With(ctx context.Context) UserService {
|
|||||||
return &user{
|
return &user{
|
||||||
db: db,
|
db: db,
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
prm: DefaultPermissions,
|
prm: Permissions(ctx),
|
||||||
user: repository.User(ctx, db),
|
user: repository.User(ctx, db),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,14 +117,13 @@ func (svc *user) FindOrCreate(user *types.User) (out *types.User, err error) {
|
|||||||
|
|
||||||
func (svc *user) Create(input *types.User) (out *types.User, err error) {
|
func (svc *user) Create(input *types.User) (out *types.User, err error) {
|
||||||
return out, svc.db.Transaction(func() error {
|
return out, svc.db.Transaction(func() error {
|
||||||
if out, err = svc.user.Create(input); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !svc.prm.CanCreateUser() {
|
if !svc.prm.CanCreateUser() {
|
||||||
return errors.New("not allowed to create users")
|
return errors.New("not allowed to create users")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if out, err = svc.user.Create(input); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -88,7 +88,7 @@ func (ctrl *Auth) Handlers(jwtEncoder auth.TokenEncoder) *handlers.Auth {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userSvc := service.User().With(ctx)
|
userSvc := service.User(ctx)
|
||||||
|
|
||||||
// check email and username for login
|
// check email and username for login
|
||||||
user, err := userSvc.FindByEmail(params.Username)
|
user, err := userSvc.FindByEmail(params.Username)
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
func MountRoutes(oidcConfig *config.OIDC, socialConfig *config.Social, jwtEncoder auth.TokenEncoder) func(chi.Router) {
|
func MountRoutes(oidcConfig *config.OIDC, socialConfig *config.Social, jwtEncoder auth.TokenEncoder) func(chi.Router) {
|
||||||
var err error
|
var err error
|
||||||
var userSvc = service.User()
|
var userSvc = service.User(context.Background())
|
||||||
var ctx = context.Background()
|
var ctx = context.Background()
|
||||||
var oidc *openIdConnect
|
var oidc *openIdConnect
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,8 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestUsers(t *testing.T) {
|
func TestUsers(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// we need to set this due to using Init()
|
// we need to set this due to using Init()
|
||||||
os.Setenv("SYSTEM_DB_DSN", "crust:crust@tcp(crust-db:3306)/crust?collation=utf8mb4_general_ci")
|
os.Setenv("SYSTEM_DB_DSN", "crust:crust@tcp(crust-db:3306)/crust?collation=utf8mb4_general_ci")
|
||||||
|
|
||||||
@ -46,11 +48,9 @@ func TestUsers(t *testing.T) {
|
|||||||
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
log.SetFlags(log.LstdFlags | log.Lshortfile)
|
||||||
|
|
||||||
// Initialize routes and exit on failure.
|
// Initialize routes and exit on failure.
|
||||||
err := Init()
|
err := Init(ctx)
|
||||||
test.Assert(t, err == nil, "Error initializing: %+v", err)
|
test.Assert(t, err == nil, "Error initializing: %+v", err)
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
routes := Routes(ctx)
|
routes := Routes(ctx)
|
||||||
|
|
||||||
// Send check request with invalid JWT token.
|
// Send check request with invalid JWT token.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user