2022-06-29 19:46:41 -04:00
|
|
|
|
using System.Data;
|
2022-01-21 09:36:25 -05:00
|
|
|
|
using System.Text.Json;
|
2022-01-11 10:40:51 +01:00
|
|
|
|
using Bit.Core.Entities;
|
2017-05-11 12:22:14 -04:00
|
|
|
|
using Bit.Core.Models.Data;
|
2022-01-11 10:40:51 +01:00
|
|
|
|
using Bit.Core.Repositories;
|
2021-02-22 15:35:16 -06:00
|
|
|
|
using Bit.Core.Settings;
|
2021-12-16 15:35:09 +01:00
|
|
|
|
using Dapper;
|
2023-01-10 15:58:41 -05:00
|
|
|
|
using Microsoft.Data.SqlClient;
|
2017-03-07 23:06:14 -05:00
|
|
|
|
|
2022-01-11 10:40:51 +01:00
|
|
|
|
namespace Bit.Infrastructure.Dapper.Repositories;
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2022-01-11 10:40:51 +01:00
|
|
|
|
public class CollectionRepository : Repository<Collection, Guid>, ICollectionRepository
|
2017-03-07 23:06:14 -05:00
|
|
|
|
{
|
2017-04-27 09:19:30 -04:00
|
|
|
|
public CollectionRepository(GlobalSettings globalSettings)
|
2018-10-09 17:21:12 -04:00
|
|
|
|
: this(globalSettings.SqlServer.ConnectionString, globalSettings.SqlServer.ReadOnlyConnectionString)
|
2017-03-07 23:06:14 -05:00
|
|
|
|
{ }
|
|
|
|
|
|
|
2018-10-09 17:21:12 -04:00
|
|
|
|
public CollectionRepository(string connectionString, string readOnlyConnectionString)
|
|
|
|
|
|
: base(connectionString, readOnlyConnectionString)
|
2017-03-07 23:06:14 -05:00
|
|
|
|
{ }
|
|
|
|
|
|
|
2017-04-07 16:41:04 -04:00
|
|
|
|
public async Task<int> GetCountByOrganizationIdAsync(Guid organizationId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
2017-04-07 16:41:04 -04:00
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.ExecuteScalarAsync<int>(
|
2017-04-27 09:19:30 -04:00
|
|
|
|
"[dbo].[Collection_ReadCountByOrganizationId]",
|
2017-04-07 16:41:04 -04:00
|
|
|
|
new { OrganizationId = organizationId },
|
|
|
|
|
|
commandType: CommandType.StoredProcedure);
|
2017-05-09 12:41:36 -04:00
|
|
|
|
|
2018-10-17 14:58:45 -04:00
|
|
|
|
return results;
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2018-10-17 14:58:45 -04:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
public async Task<Tuple<Collection, CollectionAccessDetails>> GetByIdWithAccessAsync(Guid id)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-04-27 09:19:30 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
2017-03-09 23:58:43 -05:00
|
|
|
|
{
|
2017-04-27 09:19:30 -04:00
|
|
|
|
var results = await connection.QueryMultipleAsync(
|
2023-01-19 17:00:54 +01:00
|
|
|
|
$"[{Schema}].[Collection_ReadWithGroupsAndUsersById]",
|
2017-03-09 23:58:43 -05:00
|
|
|
|
new { Id = id },
|
|
|
|
|
|
commandType: CommandType.StoredProcedure);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-03-09 23:58:43 -05:00
|
|
|
|
var collection = await results.ReadFirstOrDefaultAsync<Collection>();
|
2023-01-19 17:00:54 +01:00
|
|
|
|
var groups = (await results.ReadAsync<CollectionAccessSelection>()).ToList();
|
|
|
|
|
|
var users = (await results.ReadAsync<CollectionAccessSelection>()).ToList();
|
|
|
|
|
|
var access = new CollectionAccessDetails { Groups = groups, Users = users };
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
return new Tuple<Collection, CollectionAccessDetails>(collection, access);
|
2017-03-09 23:58:43 -05:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-03-09 23:58:43 -05:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
public async Task<Tuple<CollectionDetails, CollectionAccessDetails>> GetByIdWithAccessAsync(
|
2018-10-17 14:58:45 -04:00
|
|
|
|
Guid id, Guid userId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2018-10-17 14:58:45 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
|
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.QueryMultipleAsync(
|
2023-01-19 17:00:54 +01:00
|
|
|
|
$"[{Schema}].[Collection_ReadWithGroupsAndUsersByIdUserId]",
|
2018-10-17 14:58:45 -04:00
|
|
|
|
new { Id = id, UserId = userId },
|
|
|
|
|
|
commandType: CommandType.StoredProcedure);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2018-10-17 14:58:45 -04:00
|
|
|
|
var collection = await results.ReadFirstOrDefaultAsync<CollectionDetails>();
|
2023-01-19 17:00:54 +01:00
|
|
|
|
var groups = (await results.ReadAsync<CollectionAccessSelection>()).ToList();
|
|
|
|
|
|
var users = (await results.ReadAsync<CollectionAccessSelection>()).ToList();
|
|
|
|
|
|
var access = new CollectionAccessDetails { Groups = groups, Users = users };
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
return new Tuple<CollectionDetails, CollectionAccessDetails>(collection, access);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ICollection<Collection>> GetManyByManyIdsAsync(IEnumerable<Guid> collectionIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
|
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.QueryAsync<Collection>(
|
|
|
|
|
|
$"[{Schema}].[Collection_ReadByIds]",
|
|
|
|
|
|
new { Ids = collectionIds.ToGuidIdArrayTVP() },
|
|
|
|
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
|
|
|
|
|
|
|
|
return results.ToList();
|
2018-10-17 14:58:45 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2018-10-17 14:58:45 -04:00
|
|
|
|
|
2018-06-11 14:25:53 -04:00
|
|
|
|
public async Task<ICollection<Collection>> GetManyByOrganizationIdAsync(Guid organizationId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2018-06-11 14:25:53 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
2017-03-07 23:06:14 -05:00
|
|
|
|
{
|
2018-06-11 14:25:53 -04:00
|
|
|
|
var results = await connection.QueryAsync<Collection>(
|
2017-05-11 10:32:25 -04:00
|
|
|
|
$"[{Schema}].[{Table}_ReadByOrganizationId]",
|
2018-06-11 14:25:53 -04:00
|
|
|
|
new { OrganizationId = organizationId },
|
2017-03-07 23:06:14 -05:00
|
|
|
|
commandType: CommandType.StoredProcedure);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2021-08-02 11:49:27 +10:00
|
|
|
|
return results.ToList();
|
2017-03-07 23:06:14 -05:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-05-09 12:41:36 -04:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
public async Task<ICollection<Tuple<Collection, CollectionAccessDetails>>> GetManyByOrganizationIdWithAccessAsync(Guid organizationId)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
|
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.QueryMultipleAsync(
|
|
|
|
|
|
$"[{Schema}].[Collection_ReadWithGroupsAndUsersByOrganizationId]",
|
|
|
|
|
|
new { OrganizationId = organizationId },
|
|
|
|
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
|
|
|
|
|
|
|
|
var collections = (await results.ReadAsync<Collection>());
|
|
|
|
|
|
var groups = (await results.ReadAsync<CollectionGroup>())
|
|
|
|
|
|
.GroupBy(g => g.CollectionId);
|
|
|
|
|
|
var users = (await results.ReadAsync<CollectionUser>())
|
|
|
|
|
|
.GroupBy(u => u.CollectionId);
|
|
|
|
|
|
|
|
|
|
|
|
return collections.Select(collection =>
|
|
|
|
|
|
new Tuple<Collection, CollectionAccessDetails>(
|
|
|
|
|
|
collection,
|
|
|
|
|
|
new CollectionAccessDetails
|
|
|
|
|
|
{
|
|
|
|
|
|
Groups = groups
|
|
|
|
|
|
.FirstOrDefault(g => g.Key == collection.Id)?
|
|
|
|
|
|
.Select(g => new CollectionAccessSelection
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = g.GroupId,
|
|
|
|
|
|
HidePasswords = g.HidePasswords,
|
|
|
|
|
|
ReadOnly = g.ReadOnly
|
|
|
|
|
|
}).ToList() ?? new List<CollectionAccessSelection>(),
|
|
|
|
|
|
Users = users
|
|
|
|
|
|
.FirstOrDefault(u => u.Key == collection.Id)?
|
|
|
|
|
|
.Select(c => new CollectionAccessSelection
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = c.OrganizationUserId,
|
|
|
|
|
|
HidePasswords = c.HidePasswords,
|
|
|
|
|
|
ReadOnly = c.ReadOnly
|
|
|
|
|
|
}).ToList() ?? new List<CollectionAccessSelection>()
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<ICollection<Tuple<Collection, CollectionAccessDetails>>> GetManyByUserIdWithAccessAsync(Guid userId, Guid organizationId)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
|
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.QueryMultipleAsync(
|
|
|
|
|
|
$"[{Schema}].[Collection_ReadWithGroupsAndUsersByUserId]",
|
|
|
|
|
|
new { UserId = userId },
|
|
|
|
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
|
|
|
|
|
|
|
|
var collections = (await results.ReadAsync<Collection>()).Where(c => c.OrganizationId == organizationId);
|
|
|
|
|
|
var groups = (await results.ReadAsync<CollectionGroup>())
|
|
|
|
|
|
.GroupBy(g => g.CollectionId);
|
|
|
|
|
|
var users = (await results.ReadAsync<CollectionUser>())
|
|
|
|
|
|
.GroupBy(u => u.CollectionId);
|
|
|
|
|
|
|
|
|
|
|
|
return collections.Select(collection =>
|
|
|
|
|
|
new Tuple<Collection, CollectionAccessDetails>(
|
|
|
|
|
|
collection,
|
|
|
|
|
|
new CollectionAccessDetails
|
|
|
|
|
|
{
|
|
|
|
|
|
Groups = groups
|
|
|
|
|
|
.FirstOrDefault(g => g.Key == collection.Id)?
|
|
|
|
|
|
.Select(g => new CollectionAccessSelection
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = g.GroupId,
|
|
|
|
|
|
HidePasswords = g.HidePasswords,
|
|
|
|
|
|
ReadOnly = g.ReadOnly
|
|
|
|
|
|
}).ToList() ?? new List<CollectionAccessSelection>(),
|
|
|
|
|
|
Users = users
|
|
|
|
|
|
.FirstOrDefault(u => u.Key == collection.Id)?
|
|
|
|
|
|
.Select(c => new CollectionAccessSelection
|
|
|
|
|
|
{
|
|
|
|
|
|
Id = c.OrganizationUserId,
|
|
|
|
|
|
HidePasswords = c.HidePasswords,
|
|
|
|
|
|
ReadOnly = c.ReadOnly
|
|
|
|
|
|
}).ToList() ?? new List<CollectionAccessSelection>()
|
|
|
|
|
|
}
|
|
|
|
|
|
)
|
|
|
|
|
|
).ToList();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-05-11 12:22:14 -04:00
|
|
|
|
public async Task<CollectionDetails> GetByIdAsync(Guid id, Guid userId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-05-11 12:22:14 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
2017-05-09 12:41:36 -04:00
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.QueryAsync<CollectionDetails>(
|
2018-10-25 07:46:58 -04:00
|
|
|
|
$"[{Schema}].[Collection_ReadByIdUserId]",
|
|
|
|
|
|
new { Id = id, UserId = userId },
|
2017-05-09 12:41:36 -04:00
|
|
|
|
commandType: CommandType.StoredProcedure);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2018-10-17 22:18:03 -04:00
|
|
|
|
return results.FirstOrDefault();
|
2017-05-09 12:41:36 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 14:53:16 -04:00
|
|
|
|
|
2017-05-11 12:22:14 -04:00
|
|
|
|
public async Task<ICollection<CollectionDetails>> GetManyByUserIdAsync(Guid userId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-05-11 12:22:14 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
2017-05-09 12:41:36 -04:00
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.QueryAsync<CollectionDetails>(
|
|
|
|
|
|
$"[{Schema}].[Collection_ReadByUserId]",
|
|
|
|
|
|
new { UserId = userId },
|
|
|
|
|
|
commandType: CommandType.StoredProcedure);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-05-11 12:22:14 -04:00
|
|
|
|
return results.ToList();
|
2017-05-09 12:41:36 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
public async Task CreateAsync(Collection obj, IEnumerable<CollectionAccessSelection> groups, IEnumerable<CollectionAccessSelection> users)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-05-09 12:41:36 -04:00
|
|
|
|
obj.SetNewId();
|
2023-01-19 17:00:54 +01:00
|
|
|
|
var objWithGroupsAndUsers = JsonSerializer.Deserialize<CollectionWithGroupsAndUsers>(JsonSerializer.Serialize(obj));
|
|
|
|
|
|
|
|
|
|
|
|
objWithGroupsAndUsers.Groups = groups != null ? groups.ToArrayTVP() : Enumerable.Empty<CollectionAccessSelection>().ToArrayTVP();
|
|
|
|
|
|
objWithGroupsAndUsers.Users = users != null ? users.ToArrayTVP() : Enumerable.Empty<CollectionAccessSelection>().ToArrayTVP();
|
2022-08-29 14:53:16 -04:00
|
|
|
|
|
2017-05-11 12:22:14 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
2018-10-17 14:58:45 -04:00
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.ExecuteAsync(
|
2023-01-19 17:00:54 +01:00
|
|
|
|
$"[{Schema}].[Collection_CreateWithGroupsAndUsers]",
|
|
|
|
|
|
objWithGroupsAndUsers,
|
2018-10-17 14:58:45 -04:00
|
|
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
public async Task ReplaceAsync(Collection obj, IEnumerable<CollectionAccessSelection> groups, IEnumerable<CollectionAccessSelection> users)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2023-01-19 17:00:54 +01:00
|
|
|
|
var objWithGroupsAndUsers = JsonSerializer.Deserialize<CollectionWithGroupsAndUsers>(JsonSerializer.Serialize(obj));
|
|
|
|
|
|
|
|
|
|
|
|
objWithGroupsAndUsers.Groups = groups != null ? groups.ToArrayTVP() : Enumerable.Empty<CollectionAccessSelection>().ToArrayTVP();
|
|
|
|
|
|
objWithGroupsAndUsers.Users = users != null ? users.ToArrayTVP() : Enumerable.Empty<CollectionAccessSelection>().ToArrayTVP();
|
2018-10-17 14:58:45 -04:00
|
|
|
|
|
2017-05-11 14:52:35 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
|
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.ExecuteAsync(
|
2023-01-19 17:00:54 +01:00
|
|
|
|
$"[{Schema}].[Collection_UpdateWithGroupsAndUsers]",
|
|
|
|
|
|
objWithGroupsAndUsers,
|
2017-05-11 14:52:35 -04:00
|
|
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-05-11 14:52:35 -04:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
public async Task DeleteManyAsync(IEnumerable<Guid> collectionIds)
|
|
|
|
|
|
{
|
|
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
|
|
|
|
|
{
|
|
|
|
|
|
await connection.ExecuteAsync("[dbo].[Collection_DeleteByIds]",
|
|
|
|
|
|
new { Ids = collectionIds.ToGuidIdArrayTVP() }, commandType: CommandType.StoredProcedure);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-10-17 14:58:45 -04:00
|
|
|
|
public async Task CreateUserAsync(Guid collectionId, Guid organizationUserId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2020-03-27 14:36:37 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
2018-10-17 14:58:45 -04:00
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.ExecuteAsync(
|
2018-10-17 22:18:03 -04:00
|
|
|
|
$"[{Schema}].[CollectionUser_Create]",
|
2018-10-18 08:38:22 -04:00
|
|
|
|
new { CollectionId = collectionId, OrganizationUserId = organizationUserId },
|
2018-10-17 14:58:45 -04:00
|
|
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2018-10-17 14:58:45 -04:00
|
|
|
|
|
2018-10-17 22:18:03 -04:00
|
|
|
|
public async Task DeleteUserAsync(Guid collectionId, Guid organizationUserId)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2018-10-17 22:18:03 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
|
|
|
|
|
{
|
|
|
|
|
|
var results = await connection.ExecuteAsync(
|
|
|
|
|
|
$"[{Schema}].[CollectionUser_Delete]",
|
|
|
|
|
|
new { CollectionId = collectionId, OrganizationUserId = organizationUserId },
|
|
|
|
|
|
commandType: CommandType.StoredProcedure);
|
|
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2018-10-17 22:18:03 -04:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
public async Task UpdateUsersAsync(Guid id, IEnumerable<CollectionAccessSelection> users)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-05-09 12:41:36 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
|
|
|
|
|
{
|
2017-05-11 12:22:14 -04:00
|
|
|
|
var results = await connection.ExecuteAsync(
|
2017-05-09 12:41:36 -04:00
|
|
|
|
$"[{Schema}].[CollectionUser_UpdateUsers]",
|
2017-05-11 12:22:14 -04:00
|
|
|
|
new { CollectionId = id, Users = users.ToArrayTVP() },
|
|
|
|
|
|
commandType: CommandType.StoredProcedure);
|
2017-05-09 12:41:36 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2022-08-29 14:53:16 -04:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
public async Task<ICollection<CollectionAccessSelection>> GetManyUsersByIdAsync(Guid id)
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-05-11 12:22:14 -04:00
|
|
|
|
using (var connection = new SqlConnection(ConnectionString))
|
2022-08-29 14:53:16 -04:00
|
|
|
|
{
|
2023-01-19 17:00:54 +01:00
|
|
|
|
var results = await connection.QueryAsync<CollectionAccessSelection>(
|
2018-10-17 14:58:45 -04:00
|
|
|
|
$"[{Schema}].[CollectionUser_ReadByCollectionId]",
|
2017-05-11 12:22:14 -04:00
|
|
|
|
new { CollectionId = id },
|
|
|
|
|
|
commandType: CommandType.StoredProcedure);
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2017-05-11 12:22:14 -04:00
|
|
|
|
return results.ToList();
|
2022-08-29 15:53:48 -04:00
|
|
|
|
}
|
2022-08-29 14:53:16 -04:00
|
|
|
|
}
|
2022-08-29 16:06:55 -04:00
|
|
|
|
|
2023-01-19 17:00:54 +01:00
|
|
|
|
public class CollectionWithGroupsAndUsers : Collection
|
2022-08-29 16:06:55 -04:00
|
|
|
|
{
|
2017-05-11 12:22:14 -04:00
|
|
|
|
public DataTable Groups { get; set; }
|
2023-01-19 17:00:54 +01:00
|
|
|
|
public DataTable Users { get; set; }
|
2022-08-29 16:06:55 -04:00
|
|
|
|
}
|
2017-03-07 23:06:14 -05:00
|
|
|
|
}
|