Merge branch 'Test_IRC_Net8' of https://gitea.frp.extimaging.com/XCKJ/irc-netcore-api into Test_IRC_Net8
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
commit
c69c139e00
|
@ -113,6 +113,11 @@ builder.Services.AddAutoMapper(automapper =>
|
||||||
//EF ORM QueryWithNoLock
|
//EF ORM QueryWithNoLock
|
||||||
builder.Services.AddEFSetup(_configuration);
|
builder.Services.AddEFSetup(_configuration);
|
||||||
|
|
||||||
|
builder.Services.AddMediator(cfg =>
|
||||||
|
{
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
//转发头设置 获取真实IP
|
//转发头设置 获取真实IP
|
||||||
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
builder.Services.Configure<ForwardedHeadersOptions>(options =>
|
||||||
|
|
|
@ -1,47 +1,34 @@
|
||||||
using Aliyun.OSS;
|
using AlibabaCloud.SDK.Sts20150401;
|
||||||
using IRaCIS.Core.Infrastructure;
|
using Aliyun.OSS;
|
||||||
using Microsoft.Extensions.Hosting;
|
|
||||||
using Microsoft.Extensions.Options;
|
|
||||||
using Minio.DataModel.Args;
|
|
||||||
using Minio;
|
|
||||||
using SharpCompress.Common;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.AccessControl;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using Minio.ApiEndpoints;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using IRaCIS.Core.Domain.Share;
|
|
||||||
using IRaCIS.Core.Infrastructure.NewtonsoftJson;
|
|
||||||
using Amazon.Runtime;
|
|
||||||
using Amazon.SecurityToken;
|
|
||||||
using Amazon.SecurityToken.Model;
|
|
||||||
using Amazon;
|
using Amazon;
|
||||||
|
using Amazon.Runtime;
|
||||||
using Amazon.S3;
|
using Amazon.S3;
|
||||||
using Amazon.S3.Model;
|
using Amazon.S3.Model;
|
||||||
|
using Amazon.SecurityToken;
|
||||||
|
using Amazon.SecurityToken.Model;
|
||||||
|
using IRaCIS.Core.Infrastructure;
|
||||||
|
using IRaCIS.Core.Infrastructure.NewtonsoftJson;
|
||||||
using MassTransit;
|
using MassTransit;
|
||||||
using AlibabaCloud.SDK.Sts20150401;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Minio;
|
||||||
|
using Minio.DataModel.Args;
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace IRaCIS.Core.SCP
|
namespace IRaCIS.Core.SCP;
|
||||||
|
|
||||||
|
#region 绑定和返回模型
|
||||||
|
|
||||||
|
[LowerCamelCaseJson]
|
||||||
|
public class MinIOOptions : AWSOptions
|
||||||
{
|
{
|
||||||
#region 绑定和返回模型
|
|
||||||
|
|
||||||
[LowerCamelCaseJson]
|
|
||||||
public class MinIOOptions : AWSOptions
|
|
||||||
{
|
|
||||||
public int Port { get; set; }
|
public int Port { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class AWSOptions
|
public class AWSOptions
|
||||||
{
|
{
|
||||||
public string EndPoint { get; set; }
|
public string EndPoint { get; set; }
|
||||||
public bool UseSSL { get; set; }
|
public bool UseSSL { get; set; }
|
||||||
public string AccessKeyId { get; set; }
|
public string AccessKeyId { get; set; }
|
||||||
|
@ -51,10 +38,10 @@ namespace IRaCIS.Core.SCP
|
||||||
public string ViewEndpoint { get; set; }
|
public string ViewEndpoint { get; set; }
|
||||||
public int DurationSeconds { get; set; }
|
public int DurationSeconds { get; set; }
|
||||||
public string Region { get; set; }
|
public string Region { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AliyunOSSOptions
|
public class AliyunOSSOptions
|
||||||
{
|
{
|
||||||
public string RegionId { get; set; }
|
public string RegionId { get; set; }
|
||||||
public string AccessKeyId { get; set; }
|
public string AccessKeyId { get; set; }
|
||||||
public string AccessKeySecret { get; set; }
|
public string AccessKeySecret { get; set; }
|
||||||
|
@ -74,10 +61,10 @@ namespace IRaCIS.Core.SCP
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ObjectStoreServiceOptions
|
public class ObjectStoreServiceOptions
|
||||||
{
|
{
|
||||||
public string ObjectStoreUse { get; set; }
|
public string ObjectStoreUse { get; set; }
|
||||||
|
|
||||||
public AliyunOSSOptions AliyunOSS { get; set; }
|
public AliyunOSSOptions AliyunOSS { get; set; }
|
||||||
|
@ -87,10 +74,10 @@ namespace IRaCIS.Core.SCP
|
||||||
|
|
||||||
public AWSOptions AWS { get; set; }
|
public AWSOptions AWS { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ObjectStoreDTO
|
public class ObjectStoreDTO
|
||||||
{
|
{
|
||||||
public string ObjectStoreUse { get; set; }
|
public string ObjectStoreUse { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,11 +87,11 @@ namespace IRaCIS.Core.SCP
|
||||||
|
|
||||||
public AWSTempToken AWS { get; set; }
|
public AWSTempToken AWS { get; set; }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[LowerCamelCaseJson]
|
[LowerCamelCaseJson]
|
||||||
public class AliyunOSSTempToken
|
public class AliyunOSSTempToken
|
||||||
{
|
{
|
||||||
public string AccessKeyId { get; set; }
|
public string AccessKeyId { get; set; }
|
||||||
public string AccessKeySecret { get; set; }
|
public string AccessKeySecret { get; set; }
|
||||||
|
|
||||||
|
@ -119,11 +106,11 @@ namespace IRaCIS.Core.SCP
|
||||||
public DateTime Expiration { get; set; }
|
public DateTime Expiration { get; set; }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[LowerCamelCaseJson]
|
[LowerCamelCaseJson]
|
||||||
public class AWSTempToken
|
public class AWSTempToken
|
||||||
{
|
{
|
||||||
public string Region { get; set; }
|
public string Region { get; set; }
|
||||||
public string SessionToken { get; set; }
|
public string SessionToken { get; set; }
|
||||||
public string EndPoint { get; set; }
|
public string EndPoint { get; set; }
|
||||||
|
@ -132,21 +119,21 @@ namespace IRaCIS.Core.SCP
|
||||||
public string BucketName { get; set; }
|
public string BucketName { get; set; }
|
||||||
public string ViewEndpoint { get; set; }
|
public string ViewEndpoint { get; set; }
|
||||||
public DateTime Expiration { get; set; }
|
public DateTime Expiration { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum ObjectStoreUse
|
public enum ObjectStoreUse
|
||||||
{
|
{
|
||||||
AliyunOSS = 0,
|
AliyunOSS = 0,
|
||||||
MinIO = 1,
|
MinIO = 1,
|
||||||
AWS = 2,
|
AWS = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
// aws 参考链接 https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/dotnetv3/S3/S3_Basics
|
// aws 参考链接 https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/dotnetv3/S3/S3_Basics
|
||||||
|
|
||||||
public interface IOSSService
|
public interface IOSSService
|
||||||
{
|
{
|
||||||
public Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true);
|
public Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true);
|
||||||
public Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true);
|
public Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true);
|
||||||
|
|
||||||
|
@ -158,12 +145,12 @@ namespace IRaCIS.Core.SCP
|
||||||
|
|
||||||
public Task DeleteFromPrefix(string prefix);
|
public Task DeleteFromPrefix(string prefix);
|
||||||
|
|
||||||
public Task<ObjectStoreDTO> GetObjectStoreTempToken();
|
public ObjectStoreDTO GetObjectStoreTempToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class OSSService : IOSSService
|
public class OSSService : IOSSService
|
||||||
{
|
{
|
||||||
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
|
public ObjectStoreServiceOptions ObjectStoreServiceOptions { get; set; }
|
||||||
|
|
||||||
private AliyunOSSTempToken AliyunOSSTempToken { get; set; }
|
private AliyunOSSTempToken AliyunOSSTempToken { get; set; }
|
||||||
|
@ -175,9 +162,10 @@ namespace IRaCIS.Core.SCP
|
||||||
{
|
{
|
||||||
ObjectStoreServiceOptions = options.CurrentValue;
|
ObjectStoreServiceOptions = options.CurrentValue;
|
||||||
|
|
||||||
GetObjectStoreTempToken().GetAwaiter().GetResult();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
|
/// oosFolderPath 不要 "/ "开头 应该: TempFolder/ChildFolder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -188,6 +176,8 @@ namespace IRaCIS.Core.SCP
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true)
|
public async Task<string> UploadToOSSAsync(Stream fileStream, string oosFolderPath, string fileRealName, bool isFileNameAddGuid = true)
|
||||||
{
|
{
|
||||||
|
GetObjectStoreTempToken();
|
||||||
|
|
||||||
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{fileRealName}" : $"{oosFolderPath}/{fileRealName}";
|
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{fileRealName}" : $"{oosFolderPath}/{fileRealName}";
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -287,6 +277,8 @@ namespace IRaCIS.Core.SCP
|
||||||
/// <exception cref="BusinessValidationFailedException"></exception>
|
/// <exception cref="BusinessValidationFailedException"></exception>
|
||||||
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true)
|
public async Task<string> UploadToOSSAsync(string localFilePath, string oosFolderPath, bool isFileNameAddGuid = true)
|
||||||
{
|
{
|
||||||
|
GetObjectStoreTempToken();
|
||||||
|
|
||||||
var localFileName = Path.GetFileName(localFilePath);
|
var localFileName = Path.GetFileName(localFilePath);
|
||||||
|
|
||||||
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{localFileName}" : $"{oosFolderPath}/{localFileName}";
|
var ossRelativePath = isFileNameAddGuid ? $"{oosFolderPath}/{Guid.NewGuid()}_{localFileName}" : $"{oosFolderPath}/{localFileName}";
|
||||||
|
@ -355,6 +347,7 @@ namespace IRaCIS.Core.SCP
|
||||||
|
|
||||||
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
|
public async Task DownLoadFromOSSAsync(string ossRelativePath, string localFilePath)
|
||||||
{
|
{
|
||||||
|
GetObjectStoreTempToken();
|
||||||
|
|
||||||
ossRelativePath = ossRelativePath.TrimStart('/');
|
ossRelativePath = ossRelativePath.TrimStart('/');
|
||||||
try
|
try
|
||||||
|
@ -440,6 +433,8 @@ namespace IRaCIS.Core.SCP
|
||||||
|
|
||||||
public async Task<string> GetSignedUrl(string ossRelativePath)
|
public async Task<string> GetSignedUrl(string ossRelativePath)
|
||||||
{
|
{
|
||||||
|
GetObjectStoreTempToken();
|
||||||
|
|
||||||
ossRelativePath = ossRelativePath.TrimStart('/');
|
ossRelativePath = ossRelativePath.TrimStart('/');
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -538,6 +533,8 @@ namespace IRaCIS.Core.SCP
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public async Task DeleteFromPrefix(string prefix)
|
public async Task DeleteFromPrefix(string prefix)
|
||||||
{
|
{
|
||||||
|
GetObjectStoreTempToken();
|
||||||
|
|
||||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||||
{
|
{
|
||||||
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
var aliConfig = ObjectStoreServiceOptions.AliyunOSS;
|
||||||
|
@ -674,10 +671,9 @@ namespace IRaCIS.Core.SCP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public ObjectStoreDTO GetObjectStoreTempToken()
|
||||||
|
|
||||||
public async Task<ObjectStoreDTO> GetObjectStoreTempToken()
|
|
||||||
{
|
{
|
||||||
|
|
||||||
var ossOptions = ObjectStoreServiceOptions.AliyunOSS;
|
var ossOptions = ObjectStoreServiceOptions.AliyunOSS;
|
||||||
|
|
||||||
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
if (ObjectStoreServiceOptions.ObjectStoreUse == "AliyunOSS")
|
||||||
|
@ -745,7 +741,7 @@ namespace IRaCIS.Core.SCP
|
||||||
DurationSeconds = awsOptions.DurationSeconds // 临时凭证有效期
|
DurationSeconds = awsOptions.DurationSeconds // 临时凭证有效期
|
||||||
};
|
};
|
||||||
|
|
||||||
var assumeRoleResponse = await stsClient.AssumeRoleAsync(assumeRoleRequest);
|
var assumeRoleResponse = stsClient.AssumeRoleAsync(assumeRoleRequest).Result;
|
||||||
|
|
||||||
var credentials = assumeRoleResponse.Credentials;
|
var credentials = assumeRoleResponse.Credentials;
|
||||||
|
|
||||||
|
@ -771,7 +767,4 @@ namespace IRaCIS.Core.SCP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -456,7 +456,9 @@ namespace IRaCIS.Core.Application.Services
|
||||||
if (taskInfo.TaskStudyCount > 0)
|
if (taskInfo.TaskStudyCount > 0)
|
||||||
{
|
{
|
||||||
|
|
||||||
var taskStudyList = await _taskStudyRepository.Where(t => t.TrialId == indto.TrialId && t.VisitTaskId == indto.VisitTaskId).ProjectTo<VisitStudyDTO>(_mapper.ConfigurationProvider).ToListAsync();
|
var taskStudyList = await _taskStudyRepository.Where(t => t.TrialId == indto.TrialId && t.VisitTaskId == indto.VisitTaskId)
|
||||||
|
.WhereIf(taskInfo.IsImageFilter == true, t => ("|" + taskInfo.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|"))
|
||||||
|
.ProjectTo<VisitStudyDTO>(_mapper.ConfigurationProvider).ToListAsync();
|
||||||
|
|
||||||
foreach (var study in taskStudyList)
|
foreach (var study in taskStudyList)
|
||||||
{
|
{
|
||||||
|
@ -484,7 +486,7 @@ namespace IRaCIS.Core.Application.Services
|
||||||
var isManualGenerate = await _trialReadingCriterionRepository.AnyAsync(t => t.Id == taskInfo.TrialReadingCriterionId && t.IsAutoCreate == false);
|
var isManualGenerate = await _trialReadingCriterionRepository.AnyAsync(t => t.Id == taskInfo.TrialReadingCriterionId && t.IsAutoCreate == false);
|
||||||
|
|
||||||
var dicomStudyList = await _dicomStudyRepository.Where(t => t.TrialId == indto.TrialId && t.SubjectVisitId == indto.SujectVisitId)
|
var dicomStudyList = await _dicomStudyRepository.Where(t => t.TrialId == indto.TrialId && t.SubjectVisitId == indto.SujectVisitId)
|
||||||
.WhereIf(taskInfo.IsImageFilter == true, t => taskInfo.CriterionModalitys.Contains(t.ModalityForEdit))
|
.WhereIf(taskInfo.IsImageFilter == true, t => ("|" + taskInfo.CriterionModalitys + "|").Contains("|" + t.ModalityForEdit + "|"))
|
||||||
.WhereIf(isManualGenerate, t => t.SubjectCriteriaEvaluationVisitStudyFilterList.Any(t => t.TrialReadingCriterionId == taskInfo.TrialReadingCriterionId && t.IsConfirmed && t.IsReading))
|
.WhereIf(isManualGenerate, t => t.SubjectCriteriaEvaluationVisitStudyFilterList.Any(t => t.TrialReadingCriterionId == taskInfo.TrialReadingCriterionId && t.IsConfirmed && t.IsReading))
|
||||||
.Select(k => new VisitStudyDTO()
|
.Select(k => new VisitStudyDTO()
|
||||||
{
|
{
|
||||||
|
|
|
@ -12,6 +12,9 @@ public class DicomInstance : BaseFullAuditEntity, IEntitySeqId
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
[ForeignKey("StudyId")]
|
[ForeignKey("StudyId")]
|
||||||
public DicomStudy DicomStudy { get; set; }
|
public DicomStudy DicomStudy { get; set; }
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public List<ReadingTableAnswerRowInfo> ReadingTableAnswerRowInfoList { get; set; }
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
public bool Anonymize { get; set; }
|
public bool Anonymize { get; set; }
|
||||||
|
|
|
@ -15,6 +15,7 @@ public class SubjectCriteriaEvaluationVisitStudyFilter : BaseFullAuditEntity
|
||||||
[ForeignKey("SeriesId")]
|
[ForeignKey("SeriesId")]
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public DicomSeries Series { get; set; }
|
public DicomSeries Series { get; set; }
|
||||||
|
|
||||||
[ForeignKey("StudyId")]
|
[ForeignKey("StudyId")]
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public DicomStudy Study { get; set; }
|
public DicomStudy Study { get; set; }
|
||||||
|
|
|
@ -13,9 +13,11 @@ public class DicomStudyConfigration : IEntityTypeConfiguration<DicomStudy>
|
||||||
{
|
{
|
||||||
builder.HasKey(e => e.SeqId);
|
builder.HasKey(e => e.SeqId);
|
||||||
|
|
||||||
builder.HasMany(s => s.SeriesList).WithOne(se => se.DicomStudy).HasForeignKey(se => se.StudyId).HasPrincipalKey(st=>st.Id);
|
builder.HasMany(s => s.SeriesList).WithOne(se => se.DicomStudy).HasForeignKey(se => se.StudyId).HasPrincipalKey(st => st.Id);
|
||||||
|
|
||||||
builder.HasMany(s => s.DicomStudyMonitorList).WithOne(sm => sm.DicomStudy).HasForeignKey(sm => sm.StudyId).HasPrincipalKey(se => se.Id);
|
builder.HasMany(s => s.DicomStudyMonitorList).WithOne(sm => sm.DicomStudy).HasForeignKey(sm => sm.StudyId).HasPrincipalKey(se => se.Id);
|
||||||
|
|
||||||
|
builder.HasMany(s => s.SubjectCriteriaEvaluationVisitStudyFilterList).WithOne(sm => sm.Study).HasForeignKey(sm => sm.StudyId).HasPrincipalKey(se => se.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +29,8 @@ public class DicomSeriesConfigration : IEntityTypeConfiguration<DicomSeries>
|
||||||
|
|
||||||
builder.HasMany(s => s.DicomInstanceList).WithOne(di => di.DicomSerie).HasForeignKey(t => t.SeriesId).HasPrincipalKey(se => se.Id);
|
builder.HasMany(s => s.DicomInstanceList).WithOne(di => di.DicomSerie).HasForeignKey(t => t.SeriesId).HasPrincipalKey(se => se.Id);
|
||||||
|
|
||||||
|
builder.HasMany(s => s.SubjectCriteriaEvaluationVisitStudyFilterList).WithOne(di => di.Series).HasForeignKey(t => t.SeriesId).HasPrincipalKey(se => se.Id);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +39,9 @@ public class DicomInstanceConfigration : IEntityTypeConfiguration<DicomInstance>
|
||||||
public void Configure(EntityTypeBuilder<DicomInstance> builder)
|
public void Configure(EntityTypeBuilder<DicomInstance> builder)
|
||||||
{
|
{
|
||||||
builder.HasKey(e => e.SeqId);
|
builder.HasKey(e => e.SeqId);
|
||||||
|
|
||||||
|
builder.HasMany(s => s.ReadingTableAnswerRowInfoList).WithOne(di => di.Instance).HasForeignKey(t => t.InstanceId).HasPrincipalKey(se => se.Id);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,3 +102,8 @@ public class SCPInstanceConfigration : IEntityTypeConfiguration<SCPInstance>
|
||||||
builder.HasKey(e => e.SeqId);
|
builder.HasKey(e => e.SeqId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue