Mm 30807 granular data retention scaffold (#16891)
create the necessary tables, models and APIs for the granular data retention policy feature
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
03473f98ac
Коммит
3ea75332e7
@@ -41,6 +41,7 @@ type RetryLayer struct {
|
||||
ProductNoticesStore store.ProductNoticesStore
|
||||
ReactionStore store.ReactionStore
|
||||
RemoteClusterStore store.RemoteClusterStore
|
||||
RetentionPolicyStore store.RetentionPolicyStore
|
||||
RoleStore store.RoleStore
|
||||
SchemeStore store.SchemeStore
|
||||
SessionStore store.SessionStore
|
||||
@@ -142,6 +143,10 @@ func (s *RetryLayer) RemoteCluster() store.RemoteClusterStore {
|
||||
return s.RemoteClusterStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) RetentionPolicy() store.RetentionPolicyStore {
|
||||
return s.RetentionPolicyStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) Role() store.RoleStore {
|
||||
return s.RoleStore
|
||||
}
|
||||
@@ -307,6 +312,11 @@ type RetryLayerRemoteClusterStore struct {
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerRetentionPolicyStore struct {
|
||||
store.RetentionPolicyStore
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerRoleStore struct {
|
||||
store.RoleStore
|
||||
Root *RetryLayer
|
||||
@@ -6600,6 +6610,366 @@ func (s *RetryLayerRemoteClusterStore) UpdateTopics(remoteClusterId string, topi
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) AddChannels(policyId string, channelIds []string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.RetentionPolicyStore.AddChannels(policyId, channelIds)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) AddTeams(policyId string, teamIds []string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.RetentionPolicyStore.AddTeams(policyId, teamIds)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) Delete(id string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.RetentionPolicyStore.Delete(id)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) Get(id string) (*model.RetentionPolicyWithTeamAndChannelCounts, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.Get(id)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) GetAll(offset int, limit int) ([]*model.RetentionPolicyWithTeamAndChannelCounts, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.GetAll(offset, limit)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) GetChannelPoliciesCountForUser(userID string) (int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.GetChannelPoliciesCountForUser(userID)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) GetChannelPoliciesForUser(userID string, offset int, limit int) ([]*model.RetentionPolicyForChannel, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.GetChannelPoliciesForUser(userID, offset, limit)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) GetChannels(policyId string, offset int, limit int) (model.ChannelListWithTeamData, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.GetChannels(policyId, offset, limit)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) GetChannelsCount(policyId string) (int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.GetChannelsCount(policyId)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) GetCount() (int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.GetCount()
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) GetTeamPoliciesCountForUser(userID string) (int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.GetTeamPoliciesCountForUser(userID)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) GetTeamPoliciesForUser(userID string, offset int, limit int) ([]*model.RetentionPolicyForTeam, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.GetTeamPoliciesForUser(userID, offset, limit)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) GetTeams(policyId string, offset int, limit int) ([]*model.Team, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.GetTeams(policyId, offset, limit)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) GetTeamsCount(policyId string) (int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.GetTeamsCount(policyId)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) Patch(patch *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.Patch(patch)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) RemoveChannels(policyId string, channelIds []string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.RetentionPolicyStore.RemoveChannels(policyId, channelIds)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) RemoveTeams(policyId string, teamIds []string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.RetentionPolicyStore.RemoveTeams(policyId, teamIds)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRetentionPolicyStore) Save(policy *model.RetentionPolicyWithTeamAndChannelIDs) (*model.RetentionPolicyWithTeamAndChannelCounts, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.RetentionPolicyStore.Save(policy)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerRoleStore) AllChannelSchemeRoles() ([]*model.Role, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -8046,51 +8416,11 @@ func (s *RetryLayerTeamStore) AnalyticsGetTeamCountForScheme(schemeID string) (i
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) AnalyticsPrivateTeamCount() (int64, error) {
|
||||
func (s *RetryLayerTeamStore) AnalyticsTeamCount(opts *model.TeamSearch) (int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.TeamStore.AnalyticsPrivateTeamCount()
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) AnalyticsPublicTeamCount() (int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.TeamStore.AnalyticsPublicTeamCount()
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) AnalyticsTeamCount(includeDeleted bool) (int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.TeamStore.AnalyticsTeamCount(includeDeleted)
|
||||
result, err := s.TeamStore.AnalyticsTeamCount(opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
@@ -8212,11 +8542,11 @@ func (s *RetryLayerTeamStore) GetAllForExportAfter(limit int, afterID string) ([
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) GetAllPage(offset int, limit int) ([]*model.Team, error) {
|
||||
func (s *RetryLayerTeamStore) GetAllPage(offset int, limit int, opts *model.TeamSearch) ([]*model.Team, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.TeamStore.GetAllPage(offset, limit)
|
||||
result, err := s.TeamStore.GetAllPage(offset, limit, opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
@@ -8252,46 +8582,6 @@ func (s *RetryLayerTeamStore) GetAllPrivateTeamListing() ([]*model.Team, error)
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) GetAllPrivateTeamPageListing(offset int, limit int) ([]*model.Team, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.TeamStore.GetAllPrivateTeamPageListing(offset, limit)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) GetAllPublicTeamPageListing(offset int, limit int) ([]*model.Team, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.TeamStore.GetAllPublicTeamPageListing(offset, limit)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) GetAllTeamListing() ([]*model.Team, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -8312,26 +8602,6 @@ func (s *RetryLayerTeamStore) GetAllTeamListing() ([]*model.Team, error) {
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) GetAllTeamPageListing(offset int, limit int) ([]*model.Team, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.TeamStore.GetAllTeamPageListing(offset, limit)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if !isRepeatableError(err) {
|
||||
return result, err
|
||||
}
|
||||
tries++
|
||||
if tries >= 3 {
|
||||
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
|
||||
return result, err
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) GetByInviteId(inviteID string) (*model.Team, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -8858,11 +9128,11 @@ func (s *RetryLayerTeamStore) SaveMultipleMembers(members []*model.TeamMember, m
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) SearchAll(term string, opts *model.TeamSearch) ([]*model.Team, error) {
|
||||
func (s *RetryLayerTeamStore) SearchAll(opts *model.TeamSearch) ([]*model.Team, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.TeamStore.SearchAll(term, opts)
|
||||
result, err := s.TeamStore.SearchAll(opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
@@ -8878,11 +9148,11 @@ func (s *RetryLayerTeamStore) SearchAll(term string, opts *model.TeamSearch) ([]
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) SearchAllPaged(term string, opts *model.TeamSearch) ([]*model.Team, int64, error) {
|
||||
func (s *RetryLayerTeamStore) SearchAllPaged(opts *model.TeamSearch) ([]*model.Team, int64, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, resultVar1, err := s.TeamStore.SearchAllPaged(term, opts)
|
||||
result, resultVar1, err := s.TeamStore.SearchAllPaged(opts)
|
||||
if err == nil {
|
||||
return result, resultVar1, nil
|
||||
}
|
||||
@@ -8898,11 +9168,11 @@ func (s *RetryLayerTeamStore) SearchAllPaged(term string, opts *model.TeamSearch
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) SearchOpen(term string) ([]*model.Team, error) {
|
||||
func (s *RetryLayerTeamStore) SearchOpen(opts *model.TeamSearch) ([]*model.Team, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.TeamStore.SearchOpen(term)
|
||||
result, err := s.TeamStore.SearchOpen(opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
@@ -8918,11 +9188,11 @@ func (s *RetryLayerTeamStore) SearchOpen(term string) ([]*model.Team, error) {
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerTeamStore) SearchPrivate(term string) ([]*model.Team, error) {
|
||||
func (s *RetryLayerTeamStore) SearchPrivate(opts *model.TeamSearch) ([]*model.Team, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.TeamStore.SearchPrivate(term)
|
||||
result, err := s.TeamStore.SearchPrivate(opts)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
@@ -11804,6 +12074,7 @@ func New(childStore store.Store) *RetryLayer {
|
||||
newStore.ProductNoticesStore = &RetryLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
|
||||
newStore.ReactionStore = &RetryLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}
|
||||
newStore.RemoteClusterStore = &RetryLayerRemoteClusterStore{RemoteClusterStore: childStore.RemoteCluster(), Root: &newStore}
|
||||
newStore.RetentionPolicyStore = &RetryLayerRetentionPolicyStore{RetentionPolicyStore: childStore.RetentionPolicy(), Root: &newStore}
|
||||
newStore.RoleStore = &RetryLayerRoleStore{RoleStore: childStore.Role(), Root: &newStore}
|
||||
newStore.SchemeStore = &RetryLayerSchemeStore{SchemeStore: childStore.Scheme(), Root: &newStore}
|
||||
newStore.SessionStore = &RetryLayerSessionStore{SessionStore: childStore.Session(), Root: &newStore}
|
||||
|
||||
@@ -40,6 +40,7 @@ func genStore() *mocks.Store {
|
||||
mock.On("Preference").Return(&mocks.PreferenceStore{})
|
||||
mock.On("ProductNotices").Return(&mocks.ProductNoticesStore{})
|
||||
mock.On("Reaction").Return(&mocks.ReactionStore{})
|
||||
mock.On("RetentionPolicy").Return(&mocks.RetentionPolicyStore{})
|
||||
mock.On("Role").Return(&mocks.RoleStore{})
|
||||
mock.On("Scheme").Return(&mocks.SchemeStore{})
|
||||
mock.On("Session").Return(&mocks.SessionStore{})
|
||||
|
||||
Ссылка в новой задаче
Block a user