This commit is contained in:
136
server/AyaNova/Controllers/IntegrationController.cs
Normal file
136
server/AyaNova/Controllers/IntegrationController.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using AyaNova.Models;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Biz;
|
||||
|
||||
|
||||
namespace AyaNova.Api.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("8.0")]
|
||||
[Route("api/v{version:apiVersion}/integration")]
|
||||
[Produces("application/json")]
|
||||
[Authorize]
|
||||
public class IntegrationController : ControllerBase
|
||||
{
|
||||
private readonly AyContext ct;
|
||||
private readonly ILogger<IntegrationController> log;
|
||||
private readonly ApiServerState serverState;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
/// <param name="dbcontext"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="apiServerState"></param>
|
||||
public IntegrationController(AyContext dbcontext, ILogger<IntegrationController> logger, ApiServerState apiServerState)
|
||||
{
|
||||
ct = dbcontext;
|
||||
log = logger;
|
||||
serverState = apiServerState;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create Integration
|
||||
/// </summary>
|
||||
/// <param name="newObject"></param>
|
||||
/// <param name="apiVersion">From route path</param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> PostIntegration([FromBody] Integration newObject, ApiVersion apiVersion)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasCreateRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
Integration o = await biz.CreateAsync(newObject);
|
||||
if (o == null)
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return CreatedAtAction(nameof(IntegrationController.GetIntegration), new { id = o.Id, version = apiVersion.ToString() }, new ApiCreatedResponse(o));
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Get Integration
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>Integration</returns>
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> GetIntegration([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasReadFullRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
var o = await biz.GetAsync(id, true, true);
|
||||
if (o == null) return NotFound(new ApiErrorResponse(ApiErrorCode.NOT_FOUND));
|
||||
return Ok(ApiOkResponse.Response(o));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update Integration
|
||||
/// </summary>
|
||||
/// <param name="updatedObject"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPut]
|
||||
public async Task<IActionResult> PutIntegration([FromBody] Integration updatedObject)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasModifyRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
var o = await biz.PutAsync(updatedObject);
|
||||
if (o == null)
|
||||
{
|
||||
if (biz.Errors.Exists(z => z.Code == ApiErrorCode.CONCURRENCY_CONFLICT))
|
||||
return StatusCode(409, new ApiErrorResponse(biz.Errors));
|
||||
else
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
}
|
||||
return Ok(ApiOkResponse.Response(new { Concurrency = o.Concurrency })); ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete Integration
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns>NoContent</returns>
|
||||
[HttpDelete("{id}")]
|
||||
public async Task<IActionResult> DeleteIntegration([FromRoute] long id)
|
||||
{
|
||||
if (!serverState.IsOpen)
|
||||
return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason));
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(new ApiErrorResponse(ModelState));
|
||||
IntegrationBiz biz = IntegrationBiz.GetBiz(ct, HttpContext);
|
||||
if (!Authorized.HasDeleteRole(HttpContext.Items, biz.BizType))
|
||||
return StatusCode(403, new ApiNotAuthorizedResponse());
|
||||
if (!await biz.DeleteAsync(id))
|
||||
return BadRequest(new ApiErrorResponse(biz.Errors));
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//------------
|
||||
|
||||
|
||||
}//eoc
|
||||
}//eons
|
||||
@@ -1043,14 +1043,65 @@ namespace AyaNova.Biz
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////
|
||||
//INTEGRATION - note this is for the management UI
|
||||
//for *all* integrations. Separately, each integration app
|
||||
//will have it's own authorization system
|
||||
//INTEGRATION
|
||||
// (everyone but outside users Customer and HO)
|
||||
//this right is for the integration data itself, NOT any other AyaNova data
|
||||
//so if someone is malicious the worst case scenario is they can mess up the integration data
|
||||
// but they would still need rights to access any AyaNova data under their account so there is no loophole here
|
||||
// technically an integration may be used by any role user
|
||||
// however not likely to be read only or limited rights rols
|
||||
// so will allow full access for any user and leave
|
||||
// finer tuning of authorization to integrating app itself
|
||||
// Also, integration is only used to store app data conveniently it in no way is required to
|
||||
// write api accessing apps so any limitations are not preventing 3rd parties from writing AyaNova api consuming apps of any kind
|
||||
//
|
||||
roles.Add(AyaType.Integration, new BizRoleSet()
|
||||
{
|
||||
Change = AuthorizationRoles.BizAdmin,
|
||||
Change = AuthorizationRoles.BizAdminRestricted
|
||||
| AuthorizationRoles.BizAdmin
|
||||
| AuthorizationRoles.ServiceRestricted
|
||||
| AuthorizationRoles.Service
|
||||
| AuthorizationRoles.InventoryRestricted
|
||||
| AuthorizationRoles.Inventory
|
||||
| AuthorizationRoles.Accounting
|
||||
| AuthorizationRoles.TechRestricted
|
||||
| AuthorizationRoles.Tech
|
||||
| AuthorizationRoles.SubContractorRestricted
|
||||
| AuthorizationRoles.SubContractor
|
||||
| AuthorizationRoles.Sales
|
||||
| AuthorizationRoles.SalesRestricted
|
||||
| AuthorizationRoles.OpsAdminRestricted
|
||||
| AuthorizationRoles.OpsAdmin,
|
||||
ReadFullRecord = AuthorizationRoles.BizAdminRestricted
|
||||
| AuthorizationRoles.BizAdmin
|
||||
| AuthorizationRoles.ServiceRestricted
|
||||
| AuthorizationRoles.Service
|
||||
| AuthorizationRoles.InventoryRestricted
|
||||
| AuthorizationRoles.Inventory
|
||||
| AuthorizationRoles.Accounting
|
||||
| AuthorizationRoles.TechRestricted
|
||||
| AuthorizationRoles.Tech
|
||||
| AuthorizationRoles.SubContractorRestricted
|
||||
| AuthorizationRoles.SubContractor
|
||||
| AuthorizationRoles.Sales
|
||||
| AuthorizationRoles.SalesRestricted
|
||||
| AuthorizationRoles.OpsAdminRestricted
|
||||
| AuthorizationRoles.OpsAdmin,
|
||||
Select = AuthorizationRoles.BizAdminRestricted
|
||||
| AuthorizationRoles.BizAdmin
|
||||
| AuthorizationRoles.ServiceRestricted
|
||||
| AuthorizationRoles.Service
|
||||
| AuthorizationRoles.InventoryRestricted
|
||||
| AuthorizationRoles.Inventory
|
||||
| AuthorizationRoles.Accounting
|
||||
| AuthorizationRoles.TechRestricted
|
||||
| AuthorizationRoles.Tech
|
||||
| AuthorizationRoles.SubContractorRestricted
|
||||
| AuthorizationRoles.SubContractor
|
||||
| AuthorizationRoles.Sales
|
||||
| AuthorizationRoles.SalesRestricted
|
||||
| AuthorizationRoles.OpsAdminRestricted
|
||||
| AuthorizationRoles.OpsAdmin,
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
193
server/AyaNova/biz/IntegrationBiz.cs
Normal file
193
server/AyaNova/biz/IntegrationBiz.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using AyaNova.Util;
|
||||
using AyaNova.Api.ControllerHelpers;
|
||||
using AyaNova.Models;
|
||||
|
||||
namespace AyaNova.Biz
|
||||
{
|
||||
internal class IntegrationBiz : BizObject
|
||||
{
|
||||
internal IntegrationBiz(AyContext dbcontext, long currentUserId, long userTranslationId, AuthorizationRoles UserRoles)
|
||||
{
|
||||
ct = dbcontext;
|
||||
UserId = currentUserId;
|
||||
UserTranslationId = userTranslationId;
|
||||
CurrentUserRoles = UserRoles;
|
||||
BizType = AyaType.Integration;
|
||||
}
|
||||
|
||||
internal static IntegrationBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null)
|
||||
{
|
||||
if (httpContext != null)
|
||||
return new IntegrationBiz(ct, UserIdFromContext.Id(httpContext.Items), UserTranslationIdFromContext.Id(httpContext.Items), UserRolesFromContext.Roles(httpContext.Items));
|
||||
else
|
||||
return new IntegrationBiz(ct, 1, ServerBootConfig.AYANOVA_DEFAULT_TRANSLATION_ID, AuthorizationRoles.BizAdmin);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//EXISTS
|
||||
internal async Task<bool> ExistsAsync(long id)
|
||||
{
|
||||
return await ct.Integration.AnyAsync(z => z.Id == id);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//CREATE
|
||||
//
|
||||
internal async Task<Integration> CreateAsync(Integration newObject)
|
||||
{
|
||||
await ValidateAsync(newObject, null);
|
||||
if (HasErrors)
|
||||
return null;
|
||||
else
|
||||
{
|
||||
|
||||
await ct.Integration.AddAsync(newObject);
|
||||
await ct.SaveChangesAsync();
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, newObject.Id, BizType, AyaEvent.Created), ct);
|
||||
return newObject;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//GET
|
||||
//
|
||||
internal async Task<Integration> GetAsync(long id, bool logTheGetEvent = true, bool populatePartNames = false)
|
||||
{
|
||||
var ret = await ct.Integration.AsNoTracking().Include(z => z.Items).SingleOrDefaultAsync(m => m.Id == id);
|
||||
if (logTheGetEvent && ret != null)
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, id, BizType, AyaEvent.Retrieved), ct);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//UPDATE
|
||||
//
|
||||
internal async Task<Integration> PutAsync(Integration putObject)
|
||||
{
|
||||
//Get the db object with no tracking as about to be replaced not updated
|
||||
Integration dbObject = await GetAsync(putObject.Id, false);
|
||||
if (dbObject == null)
|
||||
{
|
||||
AddError(ApiErrorCode.NOT_FOUND, "id");
|
||||
return null;
|
||||
}
|
||||
if (dbObject.Concurrency != putObject.Concurrency)
|
||||
{
|
||||
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
await ValidateAsync(putObject, dbObject);
|
||||
if (HasErrors) return null;
|
||||
ct.Replace(dbObject, putObject);
|
||||
try
|
||||
{
|
||||
await ct.SaveChangesAsync();
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
if (!await ExistsAsync(putObject.Id))
|
||||
AddError(ApiErrorCode.NOT_FOUND);
|
||||
else
|
||||
AddError(ApiErrorCode.CONCURRENCY_CONFLICT);
|
||||
return null;
|
||||
}
|
||||
await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserId, dbObject.Id, BizType, AyaEvent.Modified), ct);
|
||||
|
||||
|
||||
return putObject;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//DELETE
|
||||
//
|
||||
internal async Task<bool> DeleteAsync(long id)
|
||||
{
|
||||
using (var transaction = await ct.Database.BeginTransactionAsync())
|
||||
{
|
||||
Integration dbObject = await GetAsync(id, false);
|
||||
if (dbObject == null)
|
||||
{
|
||||
AddError(ApiErrorCode.NOT_FOUND);
|
||||
return false;
|
||||
}
|
||||
ValidateCanDelete(dbObject);
|
||||
if (HasErrors)
|
||||
return false;
|
||||
|
||||
ct.Integration.Remove(dbObject);
|
||||
await ct.SaveChangesAsync();
|
||||
|
||||
//Log event
|
||||
await EventLogProcessor.DeleteObjectLogAsync(UserId, BizType, dbObject.Id, dbObject.Name, ct);
|
||||
await transaction.CommitAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//VALIDATION
|
||||
//
|
||||
private async Task ValidateAsync(Integration proposedObj, Integration currentObj)
|
||||
{
|
||||
|
||||
bool isNew = currentObj == null;
|
||||
|
||||
//Name required
|
||||
if (string.IsNullOrWhiteSpace(proposedObj.Name))
|
||||
AddError(ApiErrorCode.VALIDATION_REQUIRED, "Name");
|
||||
|
||||
|
||||
//If name is otherwise OK, check that name is unique
|
||||
if (!PropertyHasErrors("Name"))
|
||||
{
|
||||
//Use Any command is efficient way to check existance, it doesn't return the record, just a true or false
|
||||
if (await ct.Integration.AnyAsync(m => m.Name == proposedObj.Name && m.Id != proposedObj.Id))
|
||||
{
|
||||
AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "Name");
|
||||
}
|
||||
}
|
||||
|
||||
//Name required
|
||||
if (proposedObj.IntegrationAppId == Guid.Empty)
|
||||
AddError(ApiErrorCode.VALIDATION_REQUIRED, "IntegrationAppId");
|
||||
|
||||
//If name is otherwise OK, check that name is unique
|
||||
if (!PropertyHasErrors("IntegrationAppId"))
|
||||
{
|
||||
//Use Any command is efficient way to check existance, it doesn't return the record, just a true or false
|
||||
if (await ct.Integration.AnyAsync(m => m.IntegrationAppId == proposedObj.IntegrationAppId && m.Id != proposedObj.Id))
|
||||
{
|
||||
AddError(ApiErrorCode.VALIDATION_NOT_UNIQUE, "IntegrationAppId");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ValidateCanDelete(Integration inObj)
|
||||
{
|
||||
//whatever needs to be check to delete this object
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
}//eoc
|
||||
|
||||
|
||||
}//eons
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace AyaNova.Models
|
||||
public long Id { get; set; }
|
||||
public uint Concurrency { get; set; }
|
||||
[Required]
|
||||
public Guid IntegrationAppId { get; set; }//Guid of integrating application. All data for it is stored under this guid key. This guid should be fixed and unchanged for all users of the integration app, i.e. it shouldn't be unique to each install but unique to itself once, a publish app id
|
||||
public Guid IntegrationAppId { get; set; } = Guid.Empty;//Guid of integrating application. All data for it is stored under this guid key. This guid should be fixed and unchanged for all users of the integration app, i.e. it shouldn't be unique to each install but unique to itself once, a publish app id
|
||||
[Required]
|
||||
public string Name { get; set; }
|
||||
public bool Active { get; set; }//integration apps should always check if this is true before working or not and give appropriate error if turned off
|
||||
|
||||
Reference in New Issue
Block a user