seed/data Seeder Announcements
This commit is contained in:
parent
4bf6b4c257
commit
8be8fef972
6 changed files with 133 additions and 96 deletions
2
.github/instructions/lowcode.instructions.md
vendored
2
.github/instructions/lowcode.instructions.md
vendored
|
|
@ -3440,6 +3440,8 @@ Kurallar:
|
||||||
(tenant kapsamı dahil) eşleşmesine bakılarak belirlenir.
|
(tenant kapsamı dahil) eşleşmesine bakılarak belirlenir.
|
||||||
- **Dosyadaki değer varsayılanı ezer.** `InsertFieldsDefaultValueJson` yalnızca dosyada bulunmayan
|
- **Dosyadaki değer varsayılanı ezer.** `InsertFieldsDefaultValueJson` yalnızca dosyada bulunmayan
|
||||||
alanları doldurur (audit kolonları, kapsam, anahtar); ekrandan kayıtta ise varsayılan girilen değeri ezer.
|
alanları doldurur (audit kolonları, kapsam, anahtar); ekrandan kayıtta ise varsayılan girilen değeri ezer.
|
||||||
|
- **`@USER:kullanıcıAdı`** değeri seed sırasında o kullanıcının anahtarına çözülür; kullanıcı anahtarları
|
||||||
|
her kurulumda yeniden üretildiği için dosyaya GUID yazılmaz. Kullanıcı bulunamazsa alan boş kalır.
|
||||||
- **Uygulama yalnızca ekler:** anahtarı veritabanında bulunmayan satırlar `INSERT` edilir; var olan
|
- **Uygulama yalnızca ekler:** anahtarı veritabanında bulunmayan satırlar `INSERT` edilir; var olan
|
||||||
kayıt güncellenmez (her migrate'te ekrandaki güncel değerler dosyadaki eskisiyle ezilmesin diye)
|
kayıt güncellenmez (her migrate'te ekrandaki güncel değerler dosyadaki eskisiyle ezilmesin diye)
|
||||||
ve dosyada olmayan satır silinmez.
|
ve dosyada olmayan satır silinmez.
|
||||||
|
|
|
||||||
|
|
@ -2926,6 +2926,10 @@ public class ListFormSeeder_Administration : IDataSeedContributor, ITransientDep
|
||||||
Description = $"{listFormName}.Description",
|
Description = $"{listFormName}.Description",
|
||||||
SelectCommandType = SelectCommandTypeEnum.Table,
|
SelectCommandType = SelectCommandTypeEnum.Table,
|
||||||
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Announcement)),
|
SelectCommand = TableNameResolver.GetFullTableName(nameof(TableNameEnum.Announcement)),
|
||||||
|
SeedFilePath = SeedPathResolver.GetDataFilePath(listFormName),
|
||||||
|
SeedSyncInsert = false,
|
||||||
|
SeedSyncUpdate = false,
|
||||||
|
SeedSyncDelete = false,
|
||||||
KeyFieldName = "Id",
|
KeyFieldName = "Id",
|
||||||
KeyFieldDbSourceType = DbType.Guid,
|
KeyFieldDbSourceType = DbType.Guid,
|
||||||
DefaultFilter = DefaultFilterJson,
|
DefaultFilter = DefaultFilterJson,
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ using Sozsoft.Platform.ListForms;
|
||||||
using Sozsoft.Platform.Queries;
|
using Sozsoft.Platform.Queries;
|
||||||
using Volo.Abp.DependencyInjection;
|
using Volo.Abp.DependencyInjection;
|
||||||
using Volo.Abp.Domain.Repositories;
|
using Volo.Abp.Domain.Repositories;
|
||||||
|
using Volo.Abp.Identity;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Volo.Abp.MultiTenancy;
|
using Volo.Abp.MultiTenancy;
|
||||||
using Volo.Abp.Uow;
|
using Volo.Abp.Uow;
|
||||||
|
|
||||||
|
|
@ -48,8 +50,16 @@ public class ListFormSeedDataApplier(
|
||||||
IConfiguration configuration,
|
IConfiguration configuration,
|
||||||
ICurrentTenant currentTenant,
|
ICurrentTenant currentTenant,
|
||||||
IUnitOfWorkManager unitOfWorkManager,
|
IUnitOfWorkManager unitOfWorkManager,
|
||||||
|
IIdentityUserRepository identityUserRepository,
|
||||||
|
ILookupNormalizer lookupNormalizer,
|
||||||
ILogger<ListFormSeedDataApplier> logger) : IListFormSeedDataApplier, ITransientDependency
|
ILogger<ListFormSeedDataApplier> logger) : IListFormSeedDataApplier, ITransientDependency
|
||||||
{
|
{
|
||||||
|
/// <summary>Satir degerinde kullanicinin adiyla yazilan referans; anahtari seed sirasinda cozulur.</summary>
|
||||||
|
private const string UserTokenPrefix = "@USER:";
|
||||||
|
|
||||||
|
/// <summary>Cozulen kullanici anahtarlari; ayni kullanici her satirda yeniden sorgulanmaz.</summary>
|
||||||
|
private readonly Dictionary<string, Guid?> userIdCache = [];
|
||||||
|
|
||||||
public async Task ApplyAsync(Guid? tenantId)
|
public async Task ApplyAsync(Guid? tenantId)
|
||||||
{
|
{
|
||||||
var scopeFolderName = SeedPathResolver.GetScopeFolderName(tenantId);
|
var scopeFolderName = SeedPathResolver.GetScopeFolderName(tenantId);
|
||||||
|
|
@ -191,7 +201,7 @@ public class ListFormSeedDataApplier(
|
||||||
object[] keys = [keyValue];
|
object[] keys = [keyValue];
|
||||||
var hasKey = keyValue != null;
|
var hasKey = keyValue != null;
|
||||||
|
|
||||||
var insertParameters = BuildParameters(fields, row, keyFieldName, includeKey: hasKey);
|
var insertParameters = BuildParameters(fields, await ResolveTokensAsync(row), keyFieldName, includeKey: hasKey);
|
||||||
|
|
||||||
// Var olan kayda dokunulmaz: veritabanindaki guncel deger, dosyadaki eski degerle ezilmemeli.
|
// Var olan kayda dokunulmaz: veritabanindaki guncel deger, dosyadaki eski degerle ezilmemeli.
|
||||||
var match = hasKey
|
var match = hasKey
|
||||||
|
|
@ -271,6 +281,54 @@ public class ListFormSeedDataApplier(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Satirdaki <c>@USER:kullaniciAdi</c> degerlerini kullanicinin anahtariyla degistirir. Kullanici
|
||||||
|
/// anahtarlari her kurulumda yeniden uretildigi icin dosyaya yazilamaz; ad uzerinden cozulur.
|
||||||
|
/// </summary>
|
||||||
|
private async Task<Dictionary<string, object>> ResolveTokensAsync(Dictionary<string, object> row)
|
||||||
|
{
|
||||||
|
var resolved = new Dictionary<string, object>(row);
|
||||||
|
|
||||||
|
foreach (var item in row)
|
||||||
|
{
|
||||||
|
var userName = GetUserToken(item.Value);
|
||||||
|
if (userName == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userIdCache.TryGetValue(userName, out var userId))
|
||||||
|
{
|
||||||
|
var user = await identityUserRepository.FindByNormalizedUserNameAsync(lookupNormalizer.NormalizeName(userName));
|
||||||
|
userId = user?.Id;
|
||||||
|
userIdCache[userName] = userId;
|
||||||
|
|
||||||
|
if (userId == null)
|
||||||
|
{
|
||||||
|
logger.LogWarning("User '{UserName}' not found, '{Field}' is left empty.", userName, item.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolved[item.Key] = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetUserToken(object value)
|
||||||
|
{
|
||||||
|
var text = value switch
|
||||||
|
{
|
||||||
|
JsonElement { ValueKind: JsonValueKind.String } element => element.GetString(),
|
||||||
|
string s => s,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
return text?.StartsWith(UserTokenPrefix, StringComparison.OrdinalIgnoreCase) == true
|
||||||
|
? text[UserTokenPrefix.Length..]
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Dosyadaki satiri, ekranin alan tanimlarina gore tiplendirilmis parametrelere cevirir.</summary>
|
/// <summary>Dosyadaki satiri, ekranin alan tanimlarina gore tiplendirilmis parametrelere cevirir.</summary>
|
||||||
private static Dictionary<string, object> BuildParameters(
|
private static Dictionary<string, object> BuildParameters(
|
||||||
List<ListFormField> fields,
|
List<ListFormField> fields,
|
||||||
|
|
|
||||||
|
|
@ -391,60 +391,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"Announcements": [
|
|
||||||
{
|
|
||||||
"title": "🎉 Yeni Ofis Açılışı",
|
|
||||||
"content": "Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor! Tüm çalışanlarımızı açılış törenimize davet ediyoruz.",
|
|
||||||
"excerpt": "Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor!",
|
|
||||||
"category": "general",
|
|
||||||
"userName": "system@sozsoft.com",
|
|
||||||
"publishDate": "12-10-2024",
|
|
||||||
"isPinned": true,
|
|
||||||
"viewCount": 0,
|
|
||||||
"imageUrl": "https://images.unsplash.com/photo-1497366216548-37526070297c?w=800&q=80"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "📅 Performans Değerlendirme Dönemi",
|
|
||||||
"content": "Yıl sonu performans değerlendirmelerimiz 20 Ekim - 5 Kasım tarihleri arasında gerçekleştirilecektir. Lütfen formları zamanında doldurunuz.",
|
|
||||||
"excerpt": "Yıl sonu performans değerlendirmeleri başlıyor.",
|
|
||||||
"category": "event",
|
|
||||||
"userName": "system@sozsoft.com",
|
|
||||||
"publishDate": "08-10-2024",
|
|
||||||
"expiryDate": "05-11-2024",
|
|
||||||
"isPinned": true,
|
|
||||||
"viewCount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "💻 Sistem Bakımı Duyurusu",
|
|
||||||
"content": "Bu Cumartesi saat 02: 00 - 06: 00 arası sistemlerimizde bakım çalışması yapılacaktır. Bu süre içinde sistemlere erişim sağlanamayacaktır.",
|
|
||||||
"excerpt": "Cumartesi gecesi planlı bakım çalışması",
|
|
||||||
"category": "urgent",
|
|
||||||
"userName": "system@sozsoft.com",
|
|
||||||
"publishDate": "08-10-2024",
|
|
||||||
"isPinned": false,
|
|
||||||
"viewCount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "🎓 React İleri Seviye Eğitimi",
|
|
||||||
"content": "Yazılım Geliştirme ekibimiz için React İleri Seviye eğitimi 25-26 Ekim tarihlerinde düzenlenecektir. Katılım için IK birimine başvurunuz.",
|
|
||||||
"excerpt": "React İleri Seviye eğitimi kayıtları başladı",
|
|
||||||
"category": "event",
|
|
||||||
"userName": "system@sozsoft.com",
|
|
||||||
"publishDate": "09-10-2024",
|
|
||||||
"isPinned": false,
|
|
||||||
"viewCount": 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "⚠️ Güvenlik Politikası Güncellemesi",
|
|
||||||
"content": "Bilgi güvenliği politikamız güncellenmiştir. Tüm çalışanlarımızın yeni politikayı okuması ve onaylaması gerekmektedir.",
|
|
||||||
"excerpt": "Güvenlik politikası güncellendi - Onay gerekli",
|
|
||||||
"category": "urgent",
|
|
||||||
"userName": "system@sozsoft.com",
|
|
||||||
"publishDate": "04-10-2024",
|
|
||||||
"isPinned": true,
|
|
||||||
"viewCount": 0
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"Surveys": [
|
"Surveys": [
|
||||||
{
|
{
|
||||||
"Title": "Çalışan Memnuniyet Anketi 2024",
|
"Title": "Çalışan Memnuniyet Anketi 2024",
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ public class TenantSeederDto
|
||||||
public List<ForumCategorySeedDto> ForumCategories { get; set; }
|
public List<ForumCategorySeedDto> ForumCategories { get; set; }
|
||||||
|
|
||||||
//Intranet
|
//Intranet
|
||||||
public List<AnnouncementSeedDto> Announcements { get; set; }
|
|
||||||
public List<SurveySeedDto> Surveys { get; set; }
|
public List<SurveySeedDto> Surveys { get; set; }
|
||||||
public List<SurveyQuestionSeedDto> SurveyQuestions { get; set; }
|
public List<SurveyQuestionSeedDto> SurveyQuestions { get; set; }
|
||||||
public List<SurveyQuestionOptionSeedDto> SurveyQuestionOptions { get; set; }
|
public List<SurveyQuestionOptionSeedDto> SurveyQuestionOptions { get; set; }
|
||||||
|
|
@ -176,21 +175,6 @@ public class SurveyQuestionOptionSeedDto
|
||||||
public int Order { get; set; }
|
public int Order { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AnnouncementSeedDto
|
|
||||||
{
|
|
||||||
public string Title { get; set; }
|
|
||||||
public string Excerpt { get; set; }
|
|
||||||
public string Content { get; set; }
|
|
||||||
public string ImageUrl { get; set; }
|
|
||||||
public string Category { get; set; }
|
|
||||||
public string UserName { get; set; }
|
|
||||||
public DateTime PublishDate { get; set; }
|
|
||||||
public DateTime? ExpiryDate { get; set; }
|
|
||||||
public bool IsPinned { get; set; }
|
|
||||||
public int ViewCount { get; set; }
|
|
||||||
public string DepartmentNames { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ForumCategorySeedDto
|
public class ForumCategorySeedDto
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
@ -289,7 +273,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
private readonly IRepository<Contact, Guid> _contactRepository;
|
private readonly IRepository<Contact, Guid> _contactRepository;
|
||||||
private readonly IRepository<ForumCategory, Guid> _forumCategoryRepository;
|
private readonly IRepository<ForumCategory, Guid> _forumCategoryRepository;
|
||||||
private readonly IIdentityUserRepository _repositoryUser;
|
private readonly IIdentityUserRepository _repositoryUser;
|
||||||
private readonly IRepository<Announcement, Guid> _announcementRepository;
|
|
||||||
private readonly IRepository<Survey, Guid> _surveyRepository;
|
private readonly IRepository<Survey, Guid> _surveyRepository;
|
||||||
private readonly IRepository<SurveyQuestion, Guid> _surveyQuestionRepository;
|
private readonly IRepository<SurveyQuestion, Guid> _surveyQuestionRepository;
|
||||||
private readonly IRepository<SurveyQuestionOption, Guid> _surveyQuestionOptionRepository;
|
private readonly IRepository<SurveyQuestionOption, Guid> _surveyQuestionOptionRepository;
|
||||||
|
|
@ -315,7 +298,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
IRepository<Contact, Guid> contactRepository,
|
IRepository<Contact, Guid> contactRepository,
|
||||||
IRepository<ForumCategory, Guid> forumCategoryRepository,
|
IRepository<ForumCategory, Guid> forumCategoryRepository,
|
||||||
|
|
||||||
IRepository<Announcement, Guid> announcementRepository,
|
|
||||||
IRepository<Survey, Guid> surveyRepository,
|
IRepository<Survey, Guid> surveyRepository,
|
||||||
IRepository<SurveyQuestion, Guid> surveyQuestionRepository,
|
IRepository<SurveyQuestion, Guid> surveyQuestionRepository,
|
||||||
IRepository<SurveyQuestionOption, Guid> surveyQuestionOptionRepository,
|
IRepository<SurveyQuestionOption, Guid> surveyQuestionOptionRepository,
|
||||||
|
|
@ -338,7 +320,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
_aboutRepository = aboutRepository;
|
_aboutRepository = aboutRepository;
|
||||||
_contactRepository = contactRepository;
|
_contactRepository = contactRepository;
|
||||||
_forumCategoryRepository = forumCategoryRepository;
|
_forumCategoryRepository = forumCategoryRepository;
|
||||||
_announcementRepository = announcementRepository;
|
|
||||||
_surveyRepository = surveyRepository;
|
_surveyRepository = surveyRepository;
|
||||||
_surveyQuestionRepository = surveyQuestionRepository;
|
_surveyQuestionRepository = surveyQuestionRepository;
|
||||||
_surveyQuestionOptionRepository = surveyQuestionOptionRepository;
|
_surveyQuestionOptionRepository = surveyQuestionOptionRepository;
|
||||||
|
|
@ -460,28 +441,6 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var item in items.Announcements)
|
|
||||||
{
|
|
||||||
var exists = await _announcementRepository.AnyAsync(x => x.Title == item.Title);
|
|
||||||
if (exists)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var user = await _repositoryUser.FindByNormalizedUserNameAsync(_lookupNormalizer.NormalizeName(item.UserName));
|
|
||||||
await _announcementRepository.InsertAsync(new Announcement
|
|
||||||
{
|
|
||||||
Title = item.Title,
|
|
||||||
Excerpt = item.Excerpt,
|
|
||||||
Content = item.Content,
|
|
||||||
ImageUrl = item.ImageUrl,
|
|
||||||
Category = item.Category,
|
|
||||||
UserId = user != null ? user.Id : null,
|
|
||||||
PublishDate = item.PublishDate,
|
|
||||||
ExpiryDate = item.ExpiryDate,
|
|
||||||
IsPinned = item.IsPinned,
|
|
||||||
ViewCount = item.ViewCount
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var item in items.Surveys)
|
foreach (var item in items.Surveys)
|
||||||
{
|
{
|
||||||
var exists = await _surveyRepository.AnyAsync(x => x.Title == item.Title);
|
var exists = await _surveyRepository.AnyAsync(x => x.Title == item.Title);
|
||||||
|
|
|
||||||
68
configs/seeds/host/data/App.Intranet.Announcement.json
Normal file
68
configs/seeds/host/data/App.Intranet.Announcement.json
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
{
|
||||||
|
"ListFormCode": "App.Intranet.Announcement",
|
||||||
|
"KeyFieldName": "Id",
|
||||||
|
"GeneratedAt": "2026-09-04T00:00:00Z",
|
||||||
|
"Order": 0,
|
||||||
|
"Rows": [
|
||||||
|
{
|
||||||
|
"Id": "3da0e444-4085-5cbb-bcf2-5ccfb7566050",
|
||||||
|
"Title": "🎉 Yeni Ofis Açılışı",
|
||||||
|
"Excerpt": "Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor!",
|
||||||
|
"Content": "Ankara ofisimiz 1 Kasım tarihinde hizmete başlıyor! Tüm çalışanlarımızı açılış törenimize davet ediyoruz.",
|
||||||
|
"Category": "general",
|
||||||
|
"UserId": "@USER:system@sozsoft.com",
|
||||||
|
"PublishDate": "2024-10-12T00:00:00",
|
||||||
|
"ExpiryDate": null,
|
||||||
|
"IsPinned": true,
|
||||||
|
"ImageUrl": "https://images.unsplash.com/photo-1497366216548-37526070297c?w=800&q=80"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "169a5022-8c46-50bb-8113-aca8f348721d",
|
||||||
|
"Title": "📅 Performans Değerlendirme Dönemi",
|
||||||
|
"Excerpt": "Yıl sonu performans değerlendirmeleri başlıyor.",
|
||||||
|
"Content": "Yıl sonu performans değerlendirmelerimiz 20 Ekim - 5 Kasım tarihleri arasında gerçekleştirilecektir. Lütfen formları zamanında doldurunuz.",
|
||||||
|
"Category": "event",
|
||||||
|
"UserId": "@USER:system@sozsoft.com",
|
||||||
|
"PublishDate": "2024-10-08T00:00:00",
|
||||||
|
"ExpiryDate": "2024-11-05T00:00:00",
|
||||||
|
"IsPinned": true,
|
||||||
|
"ImageUrl": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "2d1377a3-49b5-51ce-80a3-5bbbbf6b3ad8",
|
||||||
|
"Title": "💻 Sistem Bakımı Duyurusu",
|
||||||
|
"Excerpt": "Cumartesi gecesi planlı bakım çalışması",
|
||||||
|
"Content": "Bu Cumartesi saat 02: 00 - 06: 00 arası sistemlerimizde bakım çalışması yapılacaktır. Bu süre içinde sistemlere erişim sağlanamayacaktır.",
|
||||||
|
"Category": "urgent",
|
||||||
|
"UserId": "@USER:system@sozsoft.com",
|
||||||
|
"PublishDate": "2024-10-08T00:00:00",
|
||||||
|
"ExpiryDate": null,
|
||||||
|
"IsPinned": false,
|
||||||
|
"ImageUrl": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "96a8a3dd-0f4a-5e1e-8b5e-bb44dd41a1f0",
|
||||||
|
"Title": "🎓 React İleri Seviye Eğitimi",
|
||||||
|
"Excerpt": "React İleri Seviye eğitimi kayıtları başladı",
|
||||||
|
"Content": "Yazılım Geliştirme ekibimiz için React İleri Seviye eğitimi 25-26 Ekim tarihlerinde düzenlenecektir. Katılım için IK birimine başvurunuz.",
|
||||||
|
"Category": "event",
|
||||||
|
"UserId": "@USER:system@sozsoft.com",
|
||||||
|
"PublishDate": "2024-10-09T00:00:00",
|
||||||
|
"ExpiryDate": null,
|
||||||
|
"IsPinned": false,
|
||||||
|
"ImageUrl": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Id": "a753ef12-be3e-56c9-b597-47538aa69fde",
|
||||||
|
"Title": "⚠️ Güvenlik Politikası Güncellemesi",
|
||||||
|
"Excerpt": "Güvenlik politikası güncellendi - Onay gerekli",
|
||||||
|
"Content": "Bilgi güvenliği politikamız güncellenmiştir. Tüm çalışanlarımızın yeni politikayı okuması ve onaylaması gerekmektedir.",
|
||||||
|
"Category": "urgent",
|
||||||
|
"UserId": "@USER:system@sozsoft.com",
|
||||||
|
"PublishDate": "2024-10-04T00:00:00",
|
||||||
|
"ExpiryDate": null,
|
||||||
|
"IsPinned": true,
|
||||||
|
"ImageUrl": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue