This commit is contained in:
2021-06-22 10:05:59 +01:00
parent 558be9063f
commit e40ff8a7f2
50 changed files with 899 additions and 184 deletions
@@ -148,7 +148,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
if (model.AccountContactImage.Count != 1)
{
SaLogger.Warn(fn + " Invalid number of image files uploaded: " + model.AccountContactImage.Count);
return Json(responseData.SetControlError("AccountContactImage", "Only one image can be uploaded."));
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.upload.image.single"]));
}
IFormFile file = model.AccountContactImage[0];
@@ -163,7 +163,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
// Validate the image.
ImageUtils.ImageConfig imageConfig = ImageUtils.VerifyImageData(imageData);
if (!imageConfig.ValidImage)
return Json(responseData.SetControlError("AccountContactImage", "Invalid image file uploaded."));
return Json(responseData.SetControlError("AccountContactImage", Localiser["msg.upload.image.invalid"]));
// Image belongs to the user's account.
ItemId userItemId = GetUserItemId(SessionData.User.UserAccountId);
@@ -47,7 +47,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
/// <summary>Article activity info. </summary>
public class ActivityResponseData_Article
{
public ActivityResponseData_Article(SaxModel_Online model, SaxJournalistArticle article)
public ActivityResponseData_Article(SaxController_Online controller, SaxModel_Online model, SaxJournalistArticle article)
{
Headline = article.Headline;
Summary = article.Summary;
@@ -56,7 +56,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
PublishedOnFmt = model.Localiser.FormatDate(article.PublishedAt);
SaxJournalistArticle.ContentData content = SaxJournalistArticle.ExtractContent(article.BodyData);
foreach (SaxJournalistArticle.ContentData.SegmentData segment in content.Segments)
Content.Segments.Add(new ContentData.SegmentData(segment));
Content.Segments.Add(new ContentData.SegmentData(controller, segment));
}
public String Headline { get; set; }
public String Summary { get; set; }
@@ -67,15 +67,29 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
{
public class SegmentData
{
public SegmentData(SaxJournalistArticle.ContentData.SegmentData segment)
public SegmentData(SaxController_Online controller, SaxJournalistArticle.ContentData.SegmentData segment)
{
Body.Html = segment.Body.Html;
if (segment.Body.IsValid())
{
Body = new BodyData();
Body.Html = segment.Body.Html;
}
else if (segment.Image.IsValid())
{
Image = new ImageData();
Image.Link = JournalistController.GetArticleImageLink(controller, segment.Image.ItemImageId);
}
}
public class BodyData
{
public String Html { get; set; }
}
public BodyData Body { get; } = new BodyData();
public BodyData Body { get; }
public class ImageData
{
public String Link { get; set; }
}
public ImageData Image { get; }
}
public List<SegmentData> Segments { get; } = new List<SegmentData>();
}
@@ -214,7 +228,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
SaLogger.Warn(fn + " Article not found for Activity Id: " + itemId.ToString() + "." + getLogSuffix());
return Json(responseData);
}
responseData.Article = new ActivityResponseData_Article(model, article);
responseData.Article = new ActivityResponseData_Article(this, model, article);
}
else if (itemType == Sax.DataItemType.OrganisationOpportunity)
{
@@ -19,6 +19,7 @@ using System.IO;
using System.Drawing;
using Sarand.Net.Standard.Base.Core;
using Microsoft.Net.Http.Headers;
using System.Collections.Specialized;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
@@ -118,6 +119,42 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
return File(itemImage.ImageData, itemImage.ContentType);
}
//-------------------------------------------------------------------
// Journalist Article image
//-------------------------------------------------------------------
public static String ParamName_JournalistArticleImageEncrypted { get; } = "jaie";
public static String ParamName_ItemImageId { get; } = "iii";
/// <summary>
/// Get the specified Journalist Article Image.
/// <para>Encrypted parameters:</para>
/// <para>- iii: item Image Id.</para>
/// </summary>
[HttpGet]
[Route("Image/Journalist/Article")]
public IActionResult GetJournalistArticleImage(String jaie)
{
String fn = GetType().Name + ".GetJournalistArticleImage";
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return Forbid();
ItemId userItemId = new ItemId((short)Sax.DataItemType.UserAccount, SessionData.User.UserAccountId);
// EXtract encrypted parameters.
NameValueCollection parameters = ExtractOnlineParams(jaie);
if (parameters.Count<1) return Forbid();
// Item Image.
SysId itemImageId = parameters.Get(ParamName_ItemImageId).ToSysId();
if (!SysId.IsValid(itemImageId)) return Forbid();
SaxItemImage itemImage = ItemDataMgr.GetItemImageById(itemImageId);
if (itemImage == null)
return NotFound();
// Construct response.
return File(itemImage.ImageData, itemImage.ContentType);
}
//-------------------------------------------------------------------
}
//=======================================================================
@@ -1,15 +1,20 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Base.Helpers;
using Sarand.Net.Standard.Base.Utils;
using Saxonite.Net.Core.Web.Online.App;
using Saxonite.Net.Core.Web.Online.Models;
using Saxonite.Net.Standard.Base.App;
using Saxonite.Net.Standard.Base.Bus;
using Saxonite.Net.Standard.Base.Consoles;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
@@ -28,12 +33,19 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
}
public class ArticleContentSegmentData
{
public ArticleContentSegmentBodyData Body { get; } = new ArticleContentSegmentBodyData();
public ArticleContentSegmentBodyData Body { get; set; }
public ArticleContentSegmentImageData Image { get; set; }
}
public class ArticleContentSegmentBodyData
{
public String Html { get; set; }
}
public class ArticleContentSegmentImageData
{
public int ItemImageId { get; set; }
public int ImageIdx { get; set; }
public String ImageLink { get; set; }
}
//-------------------------------------------------------------------
// Journalist Article data.
@@ -80,7 +92,17 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
foreach (var contentSegment in articleContent.Segments)
{
var segmentData = new ArticleContentSegmentData();
segmentData.Body.Html = contentSegment.Body.Html;
if (contentSegment.Body.IsValid())
{
segmentData.Body = new ArticleContentSegmentBodyData();
segmentData.Body.Html = contentSegment.Body.Html;
}
else if (contentSegment.Image.IsValid())
{
segmentData.Image = new ArticleContentSegmentImageData();
segmentData.Image.ItemImageId = SysId.ToInt(contentSegment.Image.ItemImageId);
segmentData.Image.ImageLink = GetArticleImageLink(contentSegment.Image.ItemImageId);
}
data.Content.Segments.Add(segmentData);
}
}
@@ -102,16 +124,14 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
AjaxResponse_ArticleList responseData = new AjaxResponse_ArticleList();
// Validate session and referrer.
if (!ValidReferer)
return Forbid();
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
if (!UserIsJournalist)
return GetAjaxResult_GoHome(responseData);
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
if (!UserIsJournalist) return GetAjaxResult_GoHome(responseData);
SaxSessionData_Online sessionData = SessionData_Online;
// Item filter.
SaxJournalistArticle.Params prmArticle = new SaxJournalistArticle.Params();
prmArticle.UserAccountIdsJournalistIncluded.Add(SessionData.User.UserAccountId);
prmArticle.UserAccountIdsJournalistIncluded.Add(sessionData.User.UserAccountId);
prmArticle.ArticleFilter = model.Filter.Article;
prmArticle.StatusCat = SaxJournalistArticle.StatusCat.NotDeleted;
model.DataItemTotal = (ulong)ActivityMgr.GetJournalistArticleCount(prmArticle);
@@ -204,12 +224,144 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.id.not.found"]));
}
// Delete upload images.
SaxItemImage.Params prmItemImage = new SaxItemImage.Params();
prmItemImage.ItemIdsIncluded.Add(new ItemId((short)Sax.DataItemType.JournalistArticle, articleId));
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.ArticleUpload);
prmItemImage.StatusCat = SaxItemImage.StatusCat.NotDeleted;
foreach (SaxItemImage itemImage in ItemDataMgr.GetItemImageList(prmItemImage))
{
itemImage.SessionUid = 0;
itemImage.ImageUid = 0;
itemImage.Status = SaStatus.Deleted;
ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo);
}
// Construct response.
PopulateArticleResponseData(model, article, responseData.Article);
responseData.Success = true;
return Json(responseData);
}
//-------------------------------------------------------------------
// Journalist - Upload Article Image.
//-------------------------------------------------------------------
public class AjaxResponse_UploadArticleImage : AjaxResponse_Data
{
public ArticleContentSegmentData Segment { get; set; }
public String UploadMsg { get; set; }
}
// Ajax request.
[HttpPost]
[Route("Journalist/Ajax/UploadArticleImage")]
public IActionResult UploadArticleImage(Model_Journalist model)
{
String fn = GetType().Name + ".UploadArticleImage";
AjaxResponse_UploadArticleImage responseData = new AjaxResponse_UploadArticleImage();
String messageTitle = Localiser["journalist.caption.article"];
// Validate session and referrer.
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
SaxSessionData_Online sessionData = SessionData_Online;
// Validate number of images.
if (model.ArticleImage.IsEmpty())
{
SaLogger.Warn(fn + " No image file supplied for upload");
return Json(responseData);
}
if (model.ArticleImage.Count != 1)
{
SaLogger.Warn(fn + " Invalid number of image files uploaded: " + model.ArticleImage.Count);
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.single"]));
}
IFormFile file = model.ArticleImage[0];
// Image properties.
byte[] imageData = null;
using (MemoryStream fileStream = new MemoryStream())
{
file.CopyTo(fileStream);
imageData = fileStream.ToArray();
}
// Validate the image.
ImageUtils.ImageConfig imageConfig = ImageUtils.VerifyImageData(imageData);
if (!imageConfig.ValidImage)
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.upload.image.invalid"]));
// Image belongs to the article.
// If the article doesn't yet exist, it will need to be uploaded to the user first and later transferred.
SysId articleId = model.ArticleId.ToSysId();
ItemId articleItemId = new ItemId((short)Sax.DataItemType.JournalistArticle, articleId);
ItemId userItemId = new ItemId((short)Sax.DataItemType.UserAccount, sessionData.User.UserAccountId);
ItemId imageItemId = SysId.IsValid(articleId) ? articleItemId : userItemId;
// Store uploaded image as 'Upload' - awaiting confirmation that the article details are to be saved.
// NB Reuse any existing deleted article upload image to avoid creating new images unless necessary.
SaxItemImage.Params prmItemImage = new SaxItemImage.Params();
prmItemImage.ItemIdsIncluded.Add(imageItemId);
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Article);
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.ArticleUpload);
prmItemImage.StatusCat = SaxItemImage.StatusCat.Deleted;
prmItemImage.ResultsPaging.PageLength = 1;
prmItemImage.ResultsPaging.PageNo = 1;
List<SaxItemImage> itemImages = ItemDataMgr.GetItemImageList(prmItemImage);
SaxItemImage itemImage = !itemImages.IsEmpty() ? itemImages[0] : null;
if (itemImage == null)
itemImage = ItemDataMgr.CreateItemImage(imageItemId, Sax.ImageType.ArticleUpload);
itemImage.ImageType = Sax.ImageType.ArticleUpload;
itemImage.Filename = file.FileName;
itemImage.Mime = SacsContentTypeHelper.GetMimeTypeFromContentType(file.ContentType).ToString().ToLower();
itemImage.ContentType = file.ContentType;
itemImage.Width = imageConfig.Width;
itemImage.Height = imageConfig.Height;
itemImage.ImageData = imageData;
itemImage.SessionUid = sessionData.SessionIdx;
itemImage.ImageUid = sessionData.NextItemIdx;
itemImage.Status = SaxItemImage.DefaultStatus;
if (!SysId.IsValid(itemImage.Id))
{
if (!ItemDataMgr.InsertItemImage(itemImage, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to insert uploaded image - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.error.item.insert.failed"]));
}
}
else
{
if (!ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update uploaded image - " + ItemDataMgr.ErrorStateMessage);
return Json(responseData.SetControlError("ArticleImage", Localiser["msg.error.item.insert.failed"]));
}
}
// Segment image info.
responseData.Segment = new ArticleContentSegmentData();
responseData.Segment.Image = new ArticleContentSegmentImageData();
responseData.Segment.Image.ImageLink = GetArticleImageLink(itemImage.Id);
responseData.Segment.Image.ImageIdx = itemImage.ImageUid;
// Complete response.
//responseData.SetNotification(messageTitle, "Image uploaded.");
responseData.Success = true;
return Json(responseData);
}
private String GetArticleImageLink(SysId itemImageId)
{
return GetArticleImageLink(this, itemImageId);
}
public static String GetArticleImageLink(SaxController_Online controller, SysId itemImageId)
{
String imageLink = "Image/Journalist/Article?";
NameValueCollection parameters = new NameValueCollection();
parameters.Add(ImageController.ParamName_ItemImageId, SysId.ToString(itemImageId));
return imageLink + ImageController.ParamName_JournalistArticleImageEncrypted + "=" + SaxController_Online.EncryptOnlineParams(controller, parameters);
}
//-------------------------------------------------------------------
// Store Article ajax request.
//-------------------------------------------------------------------
@@ -222,12 +374,10 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
AjaxResponse_Article responseData = new AjaxResponse_Article();
// Validate session and referrer.
if (!ValidReferer)
return Forbid();
if (!ValidUserSession)
return GetAjaxResult_InvalidSession(responseData);
if (!UserIsJournalist)
return GetAjaxResult_GoHome(responseData);
if (!ValidReferer) return Forbid();
if (!ValidUserSession) return GetAjaxResult_InvalidSession(responseData);
if (!UserIsJournalist) return GetAjaxResult_GoHome(responseData);
SaxSessionData_Online sessionData = SessionData_Online;
// Validate item properties.
model.ValidateArticleData(responseData);
@@ -258,7 +408,7 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
// Create new item if required.
if (article == null)
article = ActivityMgr.CreateJournalistArticle(SessionData.User.UserAccountId);
article = ActivityMgr.CreateJournalistArticle(sessionData.User.UserAccountId);
// Populate item properties.
if (model.ArticleRegionId != null)
@@ -272,20 +422,18 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
if (model.ArticleStatusCode != null)
article.Status = model.ArticleStatus;
// Content.
if (model.ArticleContentSegments != null)
{
SaxJournalistArticle.ContentData articleContent = new SaxJournalistArticle.ContentData();
foreach (String contentSegment in model.ArticleContentSegments)
{
if (StringUtil.IsEmpty(contentSegment)) continue;
var segment = articleContent.AddSegment();
segment.Body.Html = contentSegment;
}
article.BodyData = SaxJournalistArticle.GetContentData(articleContent);
}
// Retrieve all article images - before new article is created as uploaded images are assigned to user first.
SaxItemImage.Params prmItemImage = new SaxItemImage.Params();
if (SysId.IsValid(article.Id))
prmItemImage.ItemIdsIncluded.Add(article.GetItemId());
else
prmItemImage.ItemIdsIncluded.Add(new ItemId((short)Sax.DataItemType.UserAccount, sessionData.User.UserAccountId));
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.Article);
prmItemImage.ImageTypesIncluded.Add(Sax.ImageType.ArticleUpload);
prmItemImage.StatusCat = SaxItemImage.StatusCat.NotDeleted;
List<SaxItemImage> itemImages = ItemDataMgr.GetItemImageList(prmItemImage);
// Store item.
// If Article doesn't yet exist, create it now so that images can be assigned.
if (!SysId.IsValid(article.Id))
{
if (!ActivityMgr.InsertJournalistArticle(article, DbUpdateInfo))
@@ -294,18 +442,76 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.insert.failed"] + "\n\n" + ActivityMgr.ErrorStateMessage));
}
}
else
// Content.
if (model.ArticleContentSegmentBodies != null)
{
if (!ActivityMgr.UpdateJournalistArticle(article, DbUpdateInfo))
SaxJournalistArticle.ContentData articleContent = new SaxJournalistArticle.ContentData();
for (int segmentIdx = 0; segmentIdx < model.ArticleContentSegmentBodies.Count; segmentIdx++)
{
SaLogger.Warn(fn + " Failed to update article [Id:" + SysId.ToString(article.Id) + "]: " + ActivityMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ActivityMgr.ErrorStateMessage));
String segmentBody = model.ArticleContentSegmentBodies[segmentIdx];
String segmentItemImageId = model.ArticleContentSegmentItemImageIds.Count > segmentIdx ? model.ArticleContentSegmentItemImageIds[segmentIdx] : "";
String segmentImageIdx = model.ArticleContentSegmentImageIdxs.Count > segmentIdx ? model.ArticleContentSegmentImageIdxs[segmentIdx] : "";
if (!StringUtil.IsEmpty(segmentBody))
{
// HTML Body segment.
var segment = articleContent.AddSegment();
segment.Body.Html = segmentBody;
}
else if (!StringUtil.IsEmpty(segmentItemImageId) || !StringUtil.IsEmpty(segmentImageIdx))
{
SysId itemImageId = segmentItemImageId.ToSysId();
int imageIdx = StringUtil.ToInt(segmentImageIdx);
// Check for new image first.
SaxItemImage itemImage = itemImages.GetItemImageBySessionImage(sessionData.SessionIdx, imageIdx);
if (itemImage == null)
itemImage = itemImages.GetItemImageById(itemImageId);
if (itemImage != null)
{
// Ensure that the image is correctly identified for the Article.
itemImage.ItemId = article.GetItemId();
itemImage.ImageType = Sax.ImageType.Article;
itemImage.SessionUid = 0;
itemImage.ImageUid = 0;
itemImage.Status = SaxItemImage.DefaultStatus;
if (ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo))
{
// Assign the image segment to the article.
var segment = articleContent.AddSegment();
segment.Image.ItemImageId = itemImage.Id;
// Remove the image from the list as it's now been handled.
itemImages.Remove(itemImage);
}
else
SaLogger.Warn(fn + " Failed to update article image: " + ItemDataMgr.ErrorStateMessage);
}
else
SaLogger.Warn(fn + " Failed to identify article image. ItemImageId: " + itemImageId.ToInt() + ". SessionIdx: " + sessionData.SessionIdx + ". ImageIdx: " + imageIdx + ".");
}
}
article.BodyData = SaxJournalistArticle.GetContentData(articleContent);
}
// Update the Article.
if (!ActivityMgr.UpdateJournalistArticle(article, DbUpdateInfo))
{
SaLogger.Warn(fn + " Failed to update article [Id:" + SysId.ToString(article.Id) + "]: " + ActivityMgr.ErrorStateMessage);
return Json(responseData.SetMessage(messageTitle, Localiser["msg.error.item.update.failed"] + "\n\n" + ActivityMgr.ErrorStateMessage));
}
// Store Article Activity Index data.
ActivityConsole.StoreActivityIndex(article, DbUpdateInfo);
// Delete remaining images as they are no longer required.
foreach (SaxItemImage itemImage in itemImages)
{
itemImage.SessionUid = 0;
itemImage.ImageUid = 0;
itemImage.Status = SaStatus.Deleted;
ItemDataMgr.UpdateItemImage(itemImage, DbUpdateInfo);
}
// Construct response.
PopulateArticleResponseData(model, article, responseData.Article);
responseData.Success = true;
@@ -15,6 +15,7 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace Saxonite.Net.Core.Web.Online.Controllers
{
@@ -236,8 +237,27 @@ namespace Saxonite.Net.Core.Web.Online.Controllers
return keepLoggedInParams;
}
//-------------------------------------------------------------------
// Parameter encryption, eg. for images.
//-------------------------------------------------------------------
// Encryption key is the same for all users but a different IV value is generated for each session so that the links/parameters cannot be shared.
public static String Base64Key_OnlineApp { get; } = SacsEncryptionHelper.EncodePlainKey("<Saxonite#Online+App@Encryption>", SacsEncryptionHelper.KeyLength);
protected String EncryptOnlineParams(NameValueCollection parameters)
{
return HttpUtility.UrlEncode(EncryptParameters(parameters, SessionData_Online.Base64IV_OnlineApp, Base64Key_OnlineApp));
}
public static String EncryptOnlineParams(SaxController_Online controller, NameValueCollection parameters)
{
return controller.EncryptOnlineParams(parameters);
}
protected NameValueCollection ExtractOnlineParams(String encryptedParams)
{
return DecryptParameters(encryptedParams, SessionData_Online.Base64IV_OnlineApp, Base64Key_OnlineApp);
}
//-------------------------------------------------------------------
// Logging.
//-------------------------------------------------------------------
protected String getLogSuffix()
{
List<String> logSuffix = new List<string>();