feat: cleanup controllers

This commit is contained in:
Damien Ostler 2024-03-01 16:20:32 -05:00
parent cc2c4ff3c8
commit facb0f88ce
9 changed files with 911 additions and 511 deletions

View File

@ -34,6 +34,7 @@ public class ArtistController : Controller
_dbContext = dbContext;
}
[HttpGet]
[Authorize("read:artist")]
public async Task<IActionResult> GetArtist()
@ -51,103 +52,6 @@ public class ArtistController : Controller
return Ok(result);
}
[HttpGet]
[Authorize("read:artist")]
[Route("Reviews")]
public async Task<IActionResult> GetArtistReviews([FromQuery]int offset = 0, [FromQuery]int limit = 10)
{
var userId = User.GetUserId();
var Artist = await _dbContext.UserArtists.Include(x=>x.Requests).FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if(Artist==null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
var result = Artist.Requests.Where(x=>x.Reviewed).Skip(offset).Take(limit).Select(x=> new RequestReviewModel()
{
RequestId = x.Id,
Message = x.ReviewMessage,
Rating = x.Rating.Value,
ReviewDate = x.ReviewDate
}).ToList();
return Ok(result);
}
[HttpGet]
[Authorize("read:artist")]
[Route("Reviews/Count")]
public async Task<IActionResult> ReviewCount()
{
var userId = User.GetUserId();
var Artist = await _dbContext.UserArtists.Include(x=>x.Requests).FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if(Artist==null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
var result = Artist.Requests.Where(x=>x.Reviewed).Select(x=> new RequestReviewModel()
{
RequestId = x.Id,
Message = x.ReviewMessage,
Rating = x.Rating.Value,
ReviewDate = x.ReviewDate
}).ToList().Count;
return Ok(result);
}
[HttpGet]
[Authorize("read:artist")]
[Route("Stats")]
public async Task<IActionResult> GetArtistStats()
{
var userId = User.GetUserId();
var Artist = await _dbContext.UserArtists.Include(x=>x.Requests).FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if(Artist==null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
var result = Artist.ToStatsModel();
return Ok(result);
}
[HttpGet]
[Authorize("read:artist")]
[Route("Payout")]
public async Task<IActionResult> Payout()
{
var userId = User.GetUserId();
var Artist = await _dbContext.UserArtists.Include(x=>x.Requests).FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if(Artist==null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
var account = _paymentService.GetAccount(Artist.StripeAccountId);
var balance = _paymentService.GetBalance(Artist.StripeAccountId);
var pendingBalance = _paymentService.GetPendingBalance(Artist.StripeAccountId);
var result = new PayoutModel()
{
Enabled = account.PayoutsEnabled,
Balance = balance,
PendingBalance = pendingBalance,
PayoutUrl = _paymentService.CreateDashboardUrl(Artist.StripeAccountId)
};
return Ok(result);
}
[HttpPut]
[Authorize("write:artist")]
public async Task<IActionResult> UpdateArtist(ArtistModel model)
@ -184,6 +88,24 @@ public class ArtistController : Controller
return Ok(result);
}
[HttpGet]
[Authorize("read:artist")]
[Route("Stats")]
public async Task<IActionResult> GetArtistStats()
{
var userId = User.GetUserId();
var Artist = await _dbContext.UserArtists.Include(x=>x.Requests).FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if(Artist==null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
var result = Artist.ToStatsModel();
return Ok(result);
}
[HttpGet]
[Authorize("read:artist")]
[Route("Request")]
@ -197,41 +119,6 @@ public class ArtistController : Controller
return Ok(result);
}
[HttpGet]
[Authorize("read:artist")]
[Route("Page")]
public async Task<IActionResult> GetArtistPage()
{
var userId = User.GetUserId();
var Artist = await _dbContext.UserArtists.Include(x=>x.ArtistPageSettings).FirstOrDefaultAsync(artist=>artist.UserId==userId);
if(Artist==null)
return NotFound();
var result = Artist.ArtistPageSettings.ToModel();
return Ok(result);
}
[HttpPut]
[Authorize("write:artist")]
[Route("Page")]
public async Task<IActionResult> UpdateArtistPage([FromBody]ArtistPageSettingsModel model)
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists
.Include(x=>x.ArtistPageSettings).FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
var updatedArtist = model.ToModel(existingArtist.ArtistPageSettings);
updatedArtist = _dbContext.ArtistPageSettings.Update(updatedArtist).Entity;
await _dbContext.SaveChangesAsync();
var result = updatedArtist.ToModel();
return Ok(result);
}
[HttpPost]
[Authorize("write:artist")]
@ -262,148 +149,4 @@ public class ArtistController : Controller
return Ok();
}
[HttpGet]
[Authorize("read:artist")]
[Route("{sellerServiceId:int}/Portfolio/{portfolioId:int}")]
public async Task<IActionResult> GetPortfolio(int sellerServiceId, int portfolioId)
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
if(existingArtist.Suspended)
return BadRequest();
var portfolio = await _dbContext.ArtistPortfolioPieces
.FirstAsync(x => x.ArtistId == existingArtist.Id && x.Id==portfolioId);
var content = await _storageService.DownloadImageAsync(portfolio.FileReference);
return new FileStreamResult(content, "application/octet-stream");
}
[HttpGet]
[Route("Portfolio")]
[Authorize("read:artist")]
public async Task<IActionResult> GetPortfolio()
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
if(existingArtist.Suspended)
return BadRequest();
var portfolio = await _dbContext.ArtistPortfolioPieces.Where(x=>x.ArtistId==existingArtist.Id).ToListAsync();
var result = portfolio.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpPost]
[Route("Portfolio")]
[Authorize("write:artist")]
public async Task<IActionResult> AddPortfolio()
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
return BadRequest();
}
if(existingArtist.Suspended)
return BadRequest();
var url = await _storageService.UploadImageAsync(HttpContext.Request.Body, Guid.NewGuid().ToString());
var portfolio = new ArtistPortfolioPiece()
{
ArtistId = existingArtist.Id,
FileReference = url
};
portfolio.ArtistId = existingArtist.Id;
_dbContext.ArtistPortfolioPieces.Add(portfolio);
await _dbContext.SaveChangesAsync();
var result = portfolio.ToModel();
return Ok(result);
}
[HttpDelete]
[Authorize("write:artist")]
[Route("Portfolio/{portfolioId:int}")]
public async Task<IActionResult> DeletePortfolio(int portfolioId)
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
if(existingArtist.Suspended)
return BadRequest();
var portfolio = await _dbContext.ArtistPortfolioPieces.FirstOrDefaultAsync(x=>x.Id==portfolioId);
if(portfolio==null)
return NotFound();
if(portfolio.ArtistId!=existingArtist.Id)
return BadRequest();
_dbContext.ArtistPortfolioPieces.Remove(portfolio);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpGet]
[Authorize("write:artist")]
[Route("Onboard")]
public async Task<IActionResult> PaymentAccountStatus()
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return BadRequest();
}
if(existingArtist.Suspended)
return BadRequest();
var result = _paymentService.ArtistAccountIsOnboarded(existingArtist.StripeAccountId);
return Ok(new ArtistOnboardStatusModel(){ Onboarded= result });
}
[HttpGet]
[Authorize("write:artist")]
[Route("Onboard/Url")]
public async Task<IActionResult> GetPaymentAccount()
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
if(existingArtist.Suspended)
return BadRequest();
if(existingArtist.StripeAccountId==null)
return BadRequest();
var result = _paymentService.CreateArtistAccountOnboardingUrl(existingArtist.StripeAccountId);
return Ok(new ArtistOnboardUrlModel()
{
OnboardUrl = result
});
}
}

View File

@ -0,0 +1,63 @@
using comissions.app.api.Extensions;
using comissions.app.api.Models.Artist;
using comissions.app.api.Services.Payment;
using comissions.app.api.Services.Storage;
using comissions.app.database;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Novu;
namespace comissions.app.api.Controllers;
[Route("api/Artist")]
public class ArtistPageController: Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IStorageService _storageService;
private readonly IPaymentService _paymentService;
private readonly NovuClient _client;
public ArtistPageController(ApplicationDbContext dbContext, IPaymentService paymentService, IStorageService storageService, NovuClient client)
{
_client = client;
_paymentService = paymentService;
_storageService = storageService;
_dbContext = dbContext;
}
[HttpGet]
[Authorize("read:artist")]
[Route("Page")]
public async Task<IActionResult> GetArtistPage()
{
var userId = User.GetUserId();
var Artist = await _dbContext.UserArtists.Include(x=>x.ArtistPageSettings).FirstOrDefaultAsync(artist=>artist.UserId==userId);
if(Artist==null)
return NotFound();
var result = Artist.ArtistPageSettings.ToModel();
return Ok(result);
}
[HttpPut]
[Authorize("write:artist")]
[Route("Page")]
public async Task<IActionResult> UpdateArtistPage([FromBody]ArtistPageSettingsModel model)
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists
.Include(x=>x.ArtistPageSettings).FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
var updatedArtist = model.ToModel(existingArtist.ArtistPageSettings);
updatedArtist = _dbContext.ArtistPageSettings.Update(updatedArtist).Entity;
await _dbContext.SaveChangesAsync();
var result = updatedArtist.ToModel();
return Ok(result);
}
}

View File

@ -0,0 +1,106 @@
using comissions.app.api.Extensions;
using comissions.app.api.Models.Artist;
using comissions.app.api.Services.Payment;
using comissions.app.api.Services.Storage;
using comissions.app.database;
using comissions.app.database.Entities;
using comissions.app.database.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Novu;
namespace comissions.app.api.Controllers;
public class ArtistPaymentController:Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IStorageService _storageService;
private readonly IPaymentService _paymentService;
private readonly NovuClient _client;
public ArtistPaymentController(ApplicationDbContext dbContext, IPaymentService paymentService, IStorageService storageService, NovuClient client)
{
_client = client;
_paymentService = paymentService;
_storageService = storageService;
_dbContext = dbContext;
}
[HttpGet]
[Authorize("read:artist")]
[Route("Payout")]
public async Task<IActionResult> Payout()
{
var userId = User.GetUserId();
var Artist = await _dbContext.UserArtists.Include(x=>x.Requests).FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if(Artist==null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
var account = _paymentService.GetAccount(Artist.StripeAccountId);
var balance = _paymentService.GetBalance(Artist.StripeAccountId);
var pendingBalance = _paymentService.GetPendingBalance(Artist.StripeAccountId);
var result = new PayoutModel()
{
Enabled = account.PayoutsEnabled,
Balance = balance,
PendingBalance = pendingBalance,
PayoutUrl = _paymentService.CreateDashboardUrl(Artist.StripeAccountId)
};
return Ok(result);
}
[HttpGet]
[Authorize("write:artist")]
[Route("Onboard")]
public async Task<IActionResult> PaymentAccountStatus()
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return BadRequest();
}
if(existingArtist.Suspended)
return BadRequest();
var result = _paymentService.ArtistAccountIsOnboarded(existingArtist.StripeAccountId);
return Ok(new ArtistOnboardStatusModel(){ Onboarded= result });
}
[HttpGet]
[Authorize("write:artist")]
[Route("Onboard/Url")]
public async Task<IActionResult> GetPaymentAccount()
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
if(existingArtist.Suspended)
return BadRequest();
if(existingArtist.StripeAccountId==null)
return BadRequest();
var result = _paymentService.CreateArtistAccountOnboardingUrl(existingArtist.StripeAccountId);
return Ok(new ArtistOnboardUrlModel()
{
OnboardUrl = result
});
}
}

View File

@ -0,0 +1,128 @@
using comissions.app.api.Extensions;
using comissions.app.api.Models.PortfolioModel;
using comissions.app.api.Services.Payment;
using comissions.app.api.Services.Storage;
using comissions.app.database;
using comissions.app.database.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Novu;
namespace comissions.app.api.Controllers;
[Route("api/Artist")]
public class ArtistPortfolioController: Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IStorageService _storageService;
private readonly IPaymentService _paymentService;
private readonly NovuClient _client;
public ArtistPortfolioController(ApplicationDbContext dbContext, IPaymentService paymentService, IStorageService storageService, NovuClient client)
{
_client = client;
_paymentService = paymentService;
_storageService = storageService;
_dbContext = dbContext;
}
[HttpGet]
[Authorize("read:artist")]
[Route("{sellerServiceId:int}/Portfolio/{portfolioId:int}")]
public async Task<IActionResult> GetPortfolio(int sellerServiceId, int portfolioId)
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
if(existingArtist.Suspended)
return BadRequest();
var portfolio = await _dbContext.ArtistPortfolioPieces
.FirstAsync(x => x.ArtistId == existingArtist.Id && x.Id==portfolioId);
var content = await _storageService.DownloadImageAsync(portfolio.FileReference);
return new FileStreamResult(content, "application/octet-stream");
}
[HttpGet]
[Route("Portfolio")]
[Authorize("read:artist")]
public async Task<IActionResult> GetPortfolio()
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
if(existingArtist.Suspended)
return BadRequest();
var portfolio = await _dbContext.ArtistPortfolioPieces.Where(x=>x.ArtistId==existingArtist.Id).ToListAsync();
var result = portfolio.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpPost]
[Route("Portfolio")]
[Authorize("write:artist")]
public async Task<IActionResult> AddPortfolio()
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
return BadRequest();
}
if(existingArtist.Suspended)
return BadRequest();
var url = await _storageService.UploadImageAsync(HttpContext.Request.Body, Guid.NewGuid().ToString());
var portfolio = new ArtistPortfolioPiece()
{
ArtistId = existingArtist.Id,
FileReference = url
};
portfolio.ArtistId = existingArtist.Id;
_dbContext.ArtistPortfolioPieces.Add(portfolio);
await _dbContext.SaveChangesAsync();
var result = portfolio.ToModel();
return Ok(result);
}
[HttpDelete]
[Authorize("write:artist")]
[Route("Portfolio/{portfolioId:int}")]
public async Task<IActionResult> DeletePortfolio(int portfolioId)
{
var userId = User.GetUserId();
var existingArtist = await _dbContext.UserArtists.FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if (existingArtist == null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
if(existingArtist.Suspended)
return BadRequest();
var portfolio = await _dbContext.ArtistPortfolioPieces.FirstOrDefaultAsync(x=>x.Id==portfolioId);
if(portfolio==null)
return NotFound();
if(portfolio.ArtistId!=existingArtist.Id)
return BadRequest();
_dbContext.ArtistPortfolioPieces.Remove(portfolio);
await _dbContext.SaveChangesAsync();
return Ok();
}
}

View File

@ -0,0 +1,393 @@
using comissions.app.api.Extensions;
using comissions.app.api.Services.Payment;
using comissions.app.api.Services.Storage;
using comissions.app.database;
using comissions.app.database.Entities;
using comissions.app.database.Models.Request;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Novu;
using Novu.DTO.Events;
namespace comissions.app.api.Controllers;
[Route("api/Requests")]
public class ArtistRequestsController: Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IStorageService _storageService;
private readonly IPaymentService _paymentService;
private readonly NovuClient _client;
private readonly string _webHookSecret;
public ArtistRequestsController(ApplicationDbContext dbContext, NovuClient client, IPaymentService paymentService, IStorageService storageService, IConfiguration configuration)
{
_client = client;
_webHookSecret = configuration.GetValue<string>("Stripe:WebHookSecret");
_paymentService = paymentService;
_storageService = storageService;
_dbContext = dbContext;
}
[HttpGet]
[Route("Artist/{requestId:int}/References")]
[Authorize("read:request")]
public async Task<IActionResult> GetArtistReferences(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var references = await _dbContext.RequestReferences
.Where(x=>x.RequestId==requestId)
.ToListAsync();
var result = references.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpGet]
[Route("Artist/{requestId:int}/References/Count")]
[Authorize("read:request")]
public async Task<IActionResult> GetArtistReferencesCount(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var references = await _dbContext.RequestReferences
.Where(x=>x.RequestId==requestId)
.ToListAsync();
var result = references.Select(x=>x.ToModel()).Count();
return Ok(result);
}
[HttpGet]
[Route("Artist/{requestId:int}/References/{referenceId:int}")]
[Authorize("read:request")]
public async Task<IActionResult> GetArtistReferenceImage(int requestId, int referenceId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var reference = await _dbContext.RequestReferences
.Where(x=>x.RequestId==requestId)
.FirstOrDefaultAsync(x=>x.Id==referenceId);
if(reference==null)
return NotFound();
var content = await _storageService.DownloadImageAsync(reference.FileReference);
return new FileStreamResult(content, "application/octet-stream");
}
[HttpGet]
[Route("Artist/{requestId:int}/Assets")]
[Authorize("read:request")]
public async Task<IActionResult> GetArtistAssets(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var references = await _dbContext.RequestAssets
.Where(x=>x.RequestId==requestId)
.ToListAsync();
var result = references.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpGet]
[Route("Artist/{requestId:int}/Assets/Count")]
[Authorize("read:request")]
public async Task<IActionResult> GetArtistAssetsCount(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var references = await _dbContext.RequestAssets
.Where(x=>x.RequestId==requestId)
.ToListAsync();
var result = references.Select(x=>x.ToModel()).Count();
return Ok(result);
}
[HttpGet]
[Route("Artist/{requestId:int}/Assets/{referenceId:int}")]
[Authorize("read:request")]
public async Task<IActionResult> GetArtistAssetImage(int requestId, int referenceId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var reference = await _dbContext.RequestAssets
.Where(x=>x.RequestId==requestId)
.FirstOrDefaultAsync(x=>x.Id==referenceId);
if(reference==null)
return NotFound();
var content = await _storageService.DownloadImageAsync(reference.FileReference);
return new FileStreamResult(content, "application/octet-stream");
}
[HttpPost]
[Route("Artist/{requestId:int}/References")]
[Authorize("write:request")]
public async Task<IActionResult> AddArtistAsset(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
if(request.Accepted==false)
return BadRequest("Request has not been accepted.");
if(request.Paid==false)
return BadRequest("Request has not been paid.");
if(request.Completed)
return BadRequest("Request has already been completed.");
var references = await _dbContext.RequestAssets
.Where(x=>x.RequestId==requestId)
.ToListAsync();
if(references.Count>=10)
return BadRequest("You can only add 10 assets to a request.");
var url = await _storageService.UploadImageAsync(HttpContext.Request.Body, Guid.NewGuid().ToString());
var requestReference = new RequestAsset()
{
RequestId = request.Id,
FileReference = url
};
_dbContext.RequestAssets.Add(requestReference);
await _dbContext.SaveChangesAsync();
var result = requestReference.ToModel();
return Ok(result);
}
[Authorize("read:request")]
[HttpGet]
[Route("Artist")]
public async Task<IActionResult> GetArtistRequests(string search="",int offset = 0, int pageSize = 10)
{
var userId = User.GetUserId();
var query = _dbContext.Requests.Include(x=>x.Artist)
.Where(x => x.Artist.UserId == userId);
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(x => x.Artist.Name.Contains(search) || x.Message.Contains(search));
}
var requests = await query
.Include(x => x.Artist)
.Skip(offset)
.Take(pageSize)
.ToListAsync();
var result = requests.Select(x => x.ToModel()).ToList();
return Ok(result);
}
[Authorize("read:request")]
[HttpGet]
[Route("Artist/Count")]
public async Task<IActionResult> GetArtistRequestCount(string search="")
{
var userId = User.GetUserId();
var query = _dbContext.Requests.Include(x=>x.Artist)
.Where(x => x.Artist.UserId == userId);
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(x => x.Artist.Name.Contains(search) || x.Message.Contains(search));
}
var result = query.Count();
return Ok(result);
}
[Authorize("read:request")]
[HttpGet]
[Route("Artist/{requestId:int}")]
public async Task<IActionResult> GetArtistRequest(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Include(x=>x.Artist)
.Where(x=>x.Artist.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var result = request.ToModel();
return Ok(result);
}
[Authorize("write:request")]
[HttpPut]
[Route("Artist/{requestId:int}/Complete")]
public async Task<IActionResult> CompleteRequest(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Include(x=>x.RequestAssets)
.Include(x=>x.RequestReferences)
.Include(x=>x.Artist)
.Where(x=>x.Artist.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request.RequestAssets.Count()==0)
return BadRequest("You must add at least one asset to complete the request.");
if(request.Accepted==false)
return BadRequest("Request has not been accepted.");
if (request.Declined)
return BadRequest("Request has already been declined.");
if(request==null)
return NotFound();
request.Completed = true;
request.CompletedDate = DateTime.UtcNow;
_dbContext.Entry(request).State = EntityState.Modified;
await _dbContext.SaveChangesAsync();
var result = request.ToModel();
var newTriggerModel = new EventCreateData()
{
EventName = "requestcompleted",
To =
{
SubscriberId = request.UserId
},
Payload = { }
};
await _client.Event.Trigger(newTriggerModel);
return Ok(result);
}
[Authorize("write:request")]
[HttpPut]
[Route("Artist/{requestId:int}/Accept")]
public async Task<IActionResult> AcceptRequest(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Include(x=>x.Artist)
.Where(x=>x.Artist.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request.Completed)
return BadRequest("Request has already been completed.");
if(request.Accepted)
return BadRequest("Request has already been accepted.");
if (request.Declined)
return BadRequest("Request has already been declined.");
if(request==null)
return NotFound();
var paymentUrl = _paymentService.Charge(request.Id,request.Artist.StripeAccountId,Convert.ToDouble(request.Amount));
request.Accepted = true;
request.AcceptedDate = DateTime.UtcNow;
request.Paid = false;
request.PaymentUrl = paymentUrl;
_dbContext.Entry(request).State = EntityState.Modified;
await _dbContext.SaveChangesAsync();
var newTriggerModel = new EventCreateData()
{
EventName = "requestacceptedbuyer",
To =
{
SubscriberId = request.UserId
},
Payload = { }
};
await _client.Event.Trigger(newTriggerModel);
var newTriggerArtistModel = new EventCreateData()
{
EventName = "requestacceptedartist",
To =
{
SubscriberId = request.Artist.UserId
},
Payload = { }
};
await _client.Event.Trigger(newTriggerModel);
var result = request.ToModel();
return Ok(result);
}
[Authorize("write:request")]
[HttpPut]
[Route("Artist/{requestId:int}/Deny")]
public async Task<IActionResult> DenyRequest(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Include(x=>x.Artist)
.Where(x=>x.Artist.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
if(request.Completed)
return BadRequest("Request has already been completed.");
if(request.Accepted)
return BadRequest("Request has already been accepted.");
if (request.Declined)
return BadRequest("Request has already been declined.");
request.Declined = true;
request.DeclinedDate = DateTime.UtcNow;
_dbContext.Entry(request).State = EntityState.Modified;
await _dbContext.SaveChangesAsync();
var result = request.ToModel();
var newTriggerModel = new EventCreateData()
{
EventName = "requestdenied",
To =
{
SubscriberId = request.UserId
},
Payload = { }
};
await _client.Event.Trigger(newTriggerModel);
return Ok(result);
}
}

View File

@ -0,0 +1,103 @@
using comissions.app.api.Extensions;
using comissions.app.api.Models.Artist;
using comissions.app.api.Services.Payment;
using comissions.app.api.Services.Storage;
using comissions.app.database;
using comissions.app.database.Models.Request;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Novu;
namespace comissions.app.api.Controllers;
[Route("api/Artist")]
public class ArtistReviewsController: Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IStorageService _storageService;
private readonly IPaymentService _paymentService;
private readonly NovuClient _client;
public ArtistReviewsController(ApplicationDbContext dbContext, IPaymentService paymentService, IStorageService storageService, NovuClient client)
{
_client = client;
_paymentService = paymentService;
_storageService = storageService;
_dbContext = dbContext;
}
[HttpGet]
[Authorize("read:artist")]
[Route("Reviews")]
public async Task<IActionResult> GetArtistReviews([FromQuery]int offset = 0, [FromQuery]int limit = 10)
{
var userId = User.GetUserId();
var Artist = await _dbContext.UserArtists.Include(x=>x.Requests).FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if(Artist==null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
var result = Artist.Requests.Where(x=>x.Reviewed).Skip(offset).Take(limit).Select(x=> new RequestReviewModel()
{
RequestId = x.Id,
Message = x.ReviewMessage,
Rating = x.Rating.Value,
ReviewDate = x.ReviewDate
}).ToList();
return Ok(result);
}
[HttpGet]
[Authorize("read:artist")]
[Route("Reviews/Count")]
public async Task<IActionResult> ReviewCount()
{
var userId = User.GetUserId();
var Artist = await _dbContext.UserArtists.Include(x=>x.Requests).FirstOrDefaultAsync(Artist=>Artist.UserId==userId);
if(Artist==null)
{
var ArtistRequest = await _dbContext.ArtistRequests.FirstOrDefaultAsync(request=>request.UserId==userId && request.Accepted==false);
if(ArtistRequest!=null)
return BadRequest();
return Unauthorized();
}
var result = Artist.Requests.Where(x=>x.Reviewed).Select(x=> new RequestReviewModel()
{
RequestId = x.Id,
Message = x.ReviewMessage,
Rating = x.Rating.Value,
ReviewDate = x.ReviewDate
}).ToList().Count;
return Ok(result);
}
}
// using comissions.app.api.Services.Payment;
// using comissions.app.api.Services.Storage;
// using comissions.app.database;
// using Novu;
//
// namespace comissions.app.api.Controllers;
//[Route("api/Artist")]
// public class ArtistReviews
// {
// private readonly ApplicationDbContext _dbContext;
// private readonly IStorageService _storageService;
// private readonly IPaymentService _paymentService;
// private readonly NovuClient _client;
//
// public ArtistReviews(ApplicationDbContext dbContext, IPaymentService paymentService, IStorageService storageService, NovuClient client)
// {
// _client = client;
// _paymentService = paymentService;
// _storageService = storageService;
// _dbContext = dbContext;
// }
// }

View File

@ -16,8 +16,8 @@ using Stripe.Checkout;
namespace comissions.app.api.Controllers;
[ApiController]
[Route("api/[controller]")]
public class RequestsController : Controller
[Route("api/Requests")]
public class CustomerRequestsController : Controller
{
private readonly ApplicationDbContext _dbContext;
private readonly IStorageService _storageService;
@ -25,7 +25,7 @@ public class RequestsController : Controller
private readonly NovuClient _client;
private readonly string _webHookSecret;
public RequestsController(ApplicationDbContext dbContext, NovuClient client, IPaymentService paymentService, IStorageService storageService, IConfiguration configuration)
public CustomerRequestsController(ApplicationDbContext dbContext, NovuClient client, IPaymentService paymentService, IStorageService storageService, IConfiguration configuration)
{
_client = client;
_webHookSecret = configuration.GetValue<string>("Stripe:WebHookSecret");
@ -663,6 +663,8 @@ public class RequestsController : Controller
return Ok();
}
#region Customer
[Authorize("read:request")]
[HttpGet]
[Route("Customer")]
@ -766,35 +768,10 @@ public class RequestsController : Controller
return Ok(result);
}
[Authorize("write:request")]
[HttpPost]
[Route("Artist/{requestId:int}/Asset")]
public async Task<IActionResult> AddAsset(int requestId, List<IFormFile> assetImages)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var references = new List<RequestAsset>();
foreach (var file in assetImages)
{
var reference = new RequestAsset()
{
RequestId = requestId,
FileReference = await _storageService.UploadImageAsync(file.OpenReadStream(), Guid.NewGuid().ToString())
};
references.Add(reference);
}
_dbContext.RequestAssets.AddRange(references);
await _dbContext.SaveChangesAsync();
return Ok();
}
[HttpGet]
[Route("Customer/{requestId:int}/Reference")]
[Route("Customer/{requestId:int}/References")]
[Authorize("read:request")]
public async Task<IActionResult> GetReferences(int requestId)
{
var userId = User.GetUserId();
@ -811,7 +788,79 @@ public class RequestsController : Controller
}
[HttpGet]
[Route("Customer/{requestId:int}/Asset")]
[Route("Customer/{requestId:int}/References/Count")]
[Authorize("read:request")]
public async Task<IActionResult> GetReferencesCount(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var references = await _dbContext.RequestReferences
.Where(x=>x.RequestId==requestId)
.ToListAsync();
var result = references.Select(x=>x.ToModel()).Count();
return Ok(result);
}
[HttpGet]
[Route("Customer/{requestId:int}/References/{referenceId:int}")]
[Authorize("read:request")]
public async Task<IActionResult> GetReferenceImage(int requestId, int referenceId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var reference = await _dbContext.RequestReferences
.Where(x=>x.RequestId==requestId)
.FirstOrDefaultAsync(x=>x.Id==referenceId);
if(reference==null)
return NotFound();
var content = await _storageService.DownloadImageAsync(reference.FileReference);
return new FileStreamResult(content, "application/octet-stream");
}
[HttpPost]
[Route("Customer/{requestId:int}/References")]
[Authorize("write:request")]
public async Task<IActionResult> AddReference(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
if (request.Accepted || request.Declined)
return BadRequest("Request has already been accepted or declined.");
var references = await _dbContext.RequestReferences
.Where(x=>x.RequestId==requestId)
.ToListAsync();
if(references.Count>=10)
return BadRequest("You can only add 10 references to a request.");
var url = await _storageService.UploadImageAsync(HttpContext.Request.Body, Guid.NewGuid().ToString());
var requestReference = new RequestReference()
{
RequestId = request.Id,
FileReference = url
};
_dbContext.RequestReferences.Add(requestReference);
await _dbContext.SaveChangesAsync();
var result = requestReference.ToModel();
return Ok(result);
}
[HttpGet]
[Route("Customer/{requestId:int}/Assets")]
[Authorize("read:request")]
public async Task<IActionResult> GetAssets(int requestId)
{
var userId = User.GetUserId();
@ -828,25 +877,9 @@ public class RequestsController : Controller
}
[HttpGet]
[Route("Artist/{requestId:int}/Reference")]
public async Task<IActionResult> GetArtistReferences(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var references = await _dbContext.RequestReferences
.Where(x=>x.RequestId==requestId)
.ToListAsync();
var result = references.Select(x=>x.ToModel()).ToList();
return Ok(result);
}
[HttpGet]
[Route("Artist/{requestId:int}/Asset")]
public async Task<IActionResult> GetArtistAssets(int requestId)
[Route("Customer/{requestId:int}/Assets/Count")]
[Authorize("read:request")]
public async Task<IActionResult> GetAssetsCount(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
@ -857,202 +890,33 @@ public class RequestsController : Controller
var references = await _dbContext.RequestAssets
.Where(x=>x.RequestId==requestId)
.ToListAsync();
var result = references.Select(x=>x.ToModel()).ToList();
var result = references.Select(x=>x.ToModel()).Count();
return Ok(result);
}
[Authorize("read:request")]
[HttpGet]
[Route("Artist")]
public async Task<IActionResult> GetArtistRequests(string search="",int offset = 0, int pageSize = 10)
{
var userId = User.GetUserId();
var query = _dbContext.Requests.Include(x=>x.Artist)
.Where(x => x.Artist.UserId == userId);
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(x => x.Artist.Name.Contains(search) || x.Message.Contains(search));
}
var requests = await query
.Include(x => x.Artist)
.Skip(offset)
.Take(pageSize)
.ToListAsync();
var result = requests.Select(x => x.ToModel()).ToList();
return Ok(result);
}
[Route("Customer/{requestId:int}/Assets/{referenceId:int}")]
[Authorize("read:request")]
[HttpGet]
[Route("Artist/Count")]
public async Task<IActionResult> GetArtistRequestCount(string search="")
{
var userId = User.GetUserId();
var query = _dbContext.Requests.Include(x=>x.Artist)
.Where(x => x.Artist.UserId == userId);
if (!string.IsNullOrWhiteSpace(search))
{
query = query.Where(x => x.Artist.Name.Contains(search) || x.Message.Contains(search));
}
var result = query.Count();
return Ok(result);
}
[Authorize("read:request")]
[HttpGet]
[Route("Artist/{requestId:int}")]
public async Task<IActionResult> GetArtistRequest(int requestId)
public async Task<IActionResult> GetAssetImage(int requestId, int referenceId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Include(x=>x.Artist)
.Where(x=>x.Artist.UserId==userId)
.Where(x=>x.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
var result = request.ToModel();
return Ok(result);
}
[Authorize("write:request")]
[HttpPut]
[Route("Artist/{requestId:int}/Complete")]
public async Task<IActionResult> CompleteRequest(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Include(x=>x.Artist)
.Where(x=>x.Artist.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request.Accepted==false)
return BadRequest("Request has not been accepted.");
if (request.Declined)
return BadRequest("Request has already been declined.");
if(request==null)
var reference = await _dbContext.RequestAssets
.Where(x=>x.RequestId==requestId)
.FirstOrDefaultAsync(x=>x.Id==referenceId);
if(reference==null)
return NotFound();
request.Completed = true;
request.CompletedDate = DateTime.UtcNow;
_dbContext.Entry(request).State = EntityState.Modified;
await _dbContext.SaveChangesAsync();
var result = request.ToModel();
var newTriggerModel = new EventCreateData()
{
EventName = "requestcompleted",
To =
{
SubscriberId = request.UserId
},
Payload = { }
};
await _client.Event.Trigger(newTriggerModel);
return Ok(result);
var content = await _storageService.DownloadImageAsync(reference.FileReference);
return new FileStreamResult(content, "application/octet-stream");
}
#endregion
[Authorize("write:request")]
[HttpPut]
[Route("Artist/{requestId:int}/Accept")]
public async Task<IActionResult> AcceptRequest(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Include(x=>x.Artist)
.Where(x=>x.Artist.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request.Completed)
return BadRequest("Request has already been completed.");
if(request.Accepted)
return BadRequest("Request has already been accepted.");
if (request.Declined)
return BadRequest("Request has already been declined.");
if(request==null)
return NotFound();
var paymentUrl = _paymentService.Charge(request.Id,request.Artist.StripeAccountId,Convert.ToDouble(request.Amount));
request.Accepted = true;
request.AcceptedDate = DateTime.UtcNow;
request.Paid = false;
request.PaymentUrl = paymentUrl;
_dbContext.Entry(request).State = EntityState.Modified;
await _dbContext.SaveChangesAsync();
var newTriggerModel = new EventCreateData()
{
EventName = "requestacceptedbuyer",
To =
{
SubscriberId = request.UserId
},
Payload = { }
};
await _client.Event.Trigger(newTriggerModel);
var newTriggerArtistModel = new EventCreateData()
{
EventName = "requestacceptedartist",
To =
{
SubscriberId = request.Artist.UserId
},
Payload = { }
};
await _client.Event.Trigger(newTriggerModel);
var result = request.ToModel();
return Ok(result);
}
[Authorize("write:request")]
[HttpPut]
[Route("Artist/{requestId:int}/Deny")]
public async Task<IActionResult> DenyRequest(int requestId)
{
var userId = User.GetUserId();
var request = await _dbContext.Requests
.Include(x=>x.Artist)
.Where(x=>x.Artist.UserId==userId)
.FirstOrDefaultAsync(x=>x.Id==requestId);
if(request==null)
return NotFound();
if(request.Completed)
return BadRequest("Request has already been completed.");
if(request.Accepted)
return BadRequest("Request has already been accepted.");
if (request.Declined)
return BadRequest("Request has already been declined.");
request.Declined = true;
request.DeclinedDate = DateTime.UtcNow;
_dbContext.Entry(request).State = EntityState.Modified;
await _dbContext.SaveChangesAsync();
var result = request.ToModel();
var newTriggerModel = new EventCreateData()
{
EventName = "requestdenied",
To =
{
SubscriberId = request.UserId
},
Payload = { }
};
await _client.Event.Trigger(newTriggerModel);
return Ok(result);
}
[Authorize("write:request")]
[HttpPost]

View File

@ -13,7 +13,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("comissions.app.database.migrator")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+c5fed0846312ca59c154d519173ea8bfac18d602")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+cc2c4ff3c89a7466e4e094e4da0675172ef50ce6")]
[assembly: System.Reflection.AssemblyProductAttribute("comissions.app.database.migrator")]
[assembly: System.Reflection.AssemblyTitleAttribute("comissions.app.database.migrator")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

View File

@ -1 +1 @@
423f68777d61ee74d51ce0198a0ec1b7acb5b22c296ca2f357c18da0a527af9c
c901efee444e48a2ac81df6448d2a1b2558f7eeb98830f81e82392ae9e03c84d