Implement Store Backend
MODEL CHANGE COMMANDS: `ALTER TABLE Users ADD ActiveProfileCosmetic int(11) NOT NULL;` `CREATE TABLE OwnedStoreItems (Id int(11) NOT NULL, UserId varchar(255) NOT NULL, StoreItemId int(11) NOT NULL, FOREIGN KEY (UserId) REFERENCES Users(Id));`
This commit is contained in:
parent
0000f3bfe4
commit
34a6ac92d3
54
qtc-net-server/Controllers/StoreController.cs
Normal file
54
qtc-net-server/Controllers/StoreController.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using qtc_api.Services.StoreService;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace qtc_api.Controllers
|
||||||
|
{
|
||||||
|
[Route("api/store")]
|
||||||
|
[ApiController]
|
||||||
|
public class StoreController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly StoreService _storeService;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
public StoreController(StoreService storeService, IUserService userService)
|
||||||
|
{
|
||||||
|
_storeService = storeService;
|
||||||
|
_userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("all-items")]
|
||||||
|
public ActionResult<ServiceResponse<List<StoreItem>>> GetAllItems()
|
||||||
|
{
|
||||||
|
return Ok(_storeService.GetStoreItems());
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("buy-item")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<ServiceResponse<OwnedStoreItem>>> BuyStoreItem(int id)
|
||||||
|
{
|
||||||
|
var identity = HttpContext.User.Identity as ClaimsIdentity;
|
||||||
|
|
||||||
|
if (identity != null)
|
||||||
|
{
|
||||||
|
IEnumerable<Claim> claims = identity.Claims;
|
||||||
|
var userId = claims.First().Value;
|
||||||
|
|
||||||
|
if (userId != null)
|
||||||
|
{
|
||||||
|
var user = await _userService.GetUserById(userId);
|
||||||
|
if(user != null && user.Success && user.Data != null)
|
||||||
|
{
|
||||||
|
var result = await _storeService.BuyStoreItem(user.Data.Id, id);
|
||||||
|
return Ok(result);
|
||||||
|
}
|
||||||
|
else return Ok(new ServiceResponse<OwnedStoreItem> { Success = false, Message = "User Not Found In Auth Header" });
|
||||||
|
}
|
||||||
|
else return Ok(new ServiceResponse<OwnedStoreItem> { Success = false, Message = "No UserId In Auth Header" });
|
||||||
|
}
|
||||||
|
else return Ok(new ServiceResponse<OwnedStoreItem> { Success = false, Message = "No Auth Header" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Diagnostics;
|
using Microsoft.EntityFrameworkCore.Diagnostics;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using Org.BouncyCastle.Asn1.Nist;
|
||||||
|
|
||||||
namespace qtc_api.Data
|
namespace qtc_api.Data
|
||||||
{
|
{
|
||||||
@ -13,6 +14,7 @@ namespace qtc_api.Data
|
|||||||
public DbSet<Room> Rooms { get; set; }
|
public DbSet<Room> Rooms { get; set; }
|
||||||
public DbSet<RefreshToken> ValidRefreshTokens { get; set; }
|
public DbSet<RefreshToken> ValidRefreshTokens { get; set; }
|
||||||
public DbSet<Contact> Contacts { get; set; }
|
public DbSet<Contact> Contacts { get; set; }
|
||||||
|
public DbSet<OwnedStoreItem> OwnedStoreItems { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||||
{
|
{
|
||||||
@ -40,6 +42,7 @@ namespace qtc_api.Data
|
|||||||
|
|
||||||
builder.Entity<User>().HasMany(e => e.ContactsList);
|
builder.Entity<User>().HasMany(e => e.ContactsList);
|
||||||
builder.Entity<User>().HasMany(e => e.ContactsMade);
|
builder.Entity<User>().HasMany(e => e.ContactsMade);
|
||||||
|
builder.Entity<User>().HasMany(e => e.OwnedStoreItems);
|
||||||
|
|
||||||
// Rooms (no relations)
|
// Rooms (no relations)
|
||||||
|
|
||||||
@ -60,6 +63,12 @@ namespace qtc_api.Data
|
|||||||
builder.Entity<Contact>().HasOne(e => e.User)
|
builder.Entity<Contact>().HasOne(e => e.User)
|
||||||
.WithMany(e => e.ContactsList)
|
.WithMany(e => e.ContactsList)
|
||||||
.HasForeignKey(e => e.UserId);
|
.HasForeignKey(e => e.UserId);
|
||||||
|
|
||||||
|
// Purchased Store Items
|
||||||
|
|
||||||
|
builder.Entity<OwnedStoreItem>().HasOne(e => e.User)
|
||||||
|
.WithMany(e => e.OwnedStoreItems)
|
||||||
|
.HasForeignKey(e => e.UserId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,5 +11,6 @@
|
|||||||
public DateTime CreatedAt { get; set; } = new DateTime();
|
public DateTime CreatedAt { get; set; } = new DateTime();
|
||||||
public int Status { get; set; } = 0;
|
public int Status { get; set; } = 0;
|
||||||
public int CurrencyAmount { get; set; } = 0;
|
public int CurrencyAmount { get; set; } = 0;
|
||||||
|
public int ProfileCosmeticId { get; set; } = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,5 +6,6 @@
|
|||||||
public string Username { get; set; } = string.Empty;
|
public string Username { get; set; } = string.Empty;
|
||||||
public string Bio { get; set; } = string.Empty;
|
public string Bio { get; set; } = string.Empty;
|
||||||
public DateTime DateOfBirth { get; set; } = new DateTime();
|
public DateTime DateOfBirth { get; set; } = new DateTime();
|
||||||
|
public int ProfileCosmeticId { get; set; } = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
8
qtc-net-server/Enums/StoreItemType.cs
Normal file
8
qtc-net-server/Enums/StoreItemType.cs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
namespace qtc_api.Enums
|
||||||
|
{
|
||||||
|
public enum StoreItemType
|
||||||
|
{
|
||||||
|
ProfileCosmetic = 1,
|
||||||
|
ClientCosmetic = 2
|
||||||
|
}
|
||||||
|
}
|
14
qtc-net-server/Models/OwnedStoreItem.cs
Normal file
14
qtc-net-server/Models/OwnedStoreItem.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace qtc_api.Models
|
||||||
|
{
|
||||||
|
public class OwnedStoreItem
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string UserId { get; set; } = string.Empty;
|
||||||
|
public int StoreItemId { get; set; }
|
||||||
|
|
||||||
|
public virtual User? User { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -15,9 +15,11 @@
|
|||||||
public int CurrencyAmount { get; set; } = 0;
|
public int CurrencyAmount { get; set; } = 0;
|
||||||
public int StockAmount { get; set; } = 0;
|
public int StockAmount { get; set; } = 0;
|
||||||
public DateTime LastCurrencySpin { get; set; }
|
public DateTime LastCurrencySpin { get; set; }
|
||||||
|
public int ActiveProfileCosmetic { get; set; } = 0;
|
||||||
|
|
||||||
public virtual IEnumerable<RefreshToken>? RefreshTokens { get; }
|
public virtual IEnumerable<RefreshToken>? RefreshTokens { get; }
|
||||||
public virtual IEnumerable<Contact>? ContactsMade { get; }
|
public virtual IEnumerable<Contact>? ContactsMade { get; }
|
||||||
public virtual IEnumerable<Contact>? ContactsList { get; }
|
public virtual IEnumerable<Contact>? ContactsList { get; }
|
||||||
|
public virtual IEnumerable<OwnedStoreItem>? OwnedStoreItems { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ global using Microsoft.EntityFrameworkCore;
|
|||||||
global using Microsoft.AspNetCore.SignalR;
|
global using Microsoft.AspNetCore.SignalR;
|
||||||
global using Microsoft.AspNetCore.Authorization;
|
global using Microsoft.AspNetCore.Authorization;
|
||||||
global using qtc_api.Models;
|
global using qtc_api.Models;
|
||||||
|
global using qtc_api.Schema;
|
||||||
global using qtc_api.Data;
|
global using qtc_api.Data;
|
||||||
global using qtc_api.Dtos.User;
|
global using qtc_api.Dtos.User;
|
||||||
global using qtc_api.Dtos.Room;
|
global using qtc_api.Dtos.Room;
|
||||||
@ -15,6 +16,7 @@ using qtc_api.Services.RoomService;
|
|||||||
using qtc_api.Services.ContactService;
|
using qtc_api.Services.ContactService;
|
||||||
using qtc_api.Services.CurrencyGamesService;
|
using qtc_api.Services.CurrencyGamesService;
|
||||||
using qtc_api.Services.GameRoomService;
|
using qtc_api.Services.GameRoomService;
|
||||||
|
using qtc_api.Services.StoreService;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@ -57,6 +59,7 @@ builder.Services.AddScoped<IUserService, UserService>();
|
|||||||
builder.Services.AddScoped<ITokenService, TokenService>();
|
builder.Services.AddScoped<ITokenService, TokenService>();
|
||||||
builder.Services.AddScoped<IRoomService, RoomService>();
|
builder.Services.AddScoped<IRoomService, RoomService>();
|
||||||
builder.Services.AddScoped<IContactService, ContactService>();
|
builder.Services.AddScoped<IContactService, ContactService>();
|
||||||
|
builder.Services.AddScoped<StoreService>();
|
||||||
|
|
||||||
builder.Services.AddSingleton<CurrencyGamesService>();
|
builder.Services.AddSingleton<CurrencyGamesService>();
|
||||||
builder.Services.AddSingleton<GameRoomService>();
|
builder.Services.AddSingleton<GameRoomService>();
|
||||||
|
11
qtc-net-server/Resources/store.json
Normal file
11
qtc-net-server/Resources/store.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"Id": 1,
|
||||||
|
"Type": 1,
|
||||||
|
"Price": 100,
|
||||||
|
"Name": "Exmaple",
|
||||||
|
"Description": "Change Me!",
|
||||||
|
"AssetUrl": "https://cdn.alanmoon.net/qtc/cosmetics/test/test.gif",
|
||||||
|
"ThumbnailUrl": "https://cdn.alanmoon.net/qtc/cosmetics/test/thumbnail.jpg"
|
||||||
|
}
|
||||||
|
]
|
23
qtc-net-server/Schema/StoreItem.cs
Normal file
23
qtc-net-server/Schema/StoreItem.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using qtc_api.Enums;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace qtc_api.Schema
|
||||||
|
{
|
||||||
|
public class StoreItem
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Id")]
|
||||||
|
public int Id { get; set; }
|
||||||
|
[JsonPropertyName("Type")]
|
||||||
|
public StoreItemType Type { get; set; }
|
||||||
|
[JsonPropertyName("Price")]
|
||||||
|
public int Price { get; set; }
|
||||||
|
[JsonPropertyName("Name")]
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("Description")]
|
||||||
|
public string Description { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("AssetUrl")]
|
||||||
|
public string AssetUrl { get; set; } = string.Empty;
|
||||||
|
[JsonPropertyName("ThumbnailUrl")]
|
||||||
|
public string ThumbnailUrl { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
82
qtc-net-server/Services/StoreService/StoreService.cs
Normal file
82
qtc-net-server/Services/StoreService/StoreService.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace qtc_api.Services.StoreService
|
||||||
|
{
|
||||||
|
public class StoreService
|
||||||
|
{
|
||||||
|
public static List<StoreItem> StoreItems { get; set; } = [];
|
||||||
|
|
||||||
|
private readonly DataContext _ctx;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
public StoreService(DataContext ctx, IUserService userService)
|
||||||
|
{
|
||||||
|
_ctx = ctx;
|
||||||
|
_userService = userService;
|
||||||
|
|
||||||
|
StoreItem[]? storeItems = JsonSerializer.Deserialize<StoreItem[]>(File.ReadAllText("./Resources/store.json"));
|
||||||
|
if (storeItems != null && storeItems.Length > 0)
|
||||||
|
{
|
||||||
|
StoreItems = storeItems.ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceResponse<List<StoreItem>> GetStoreItems()
|
||||||
|
{
|
||||||
|
return new ServiceResponse<List<StoreItem>> { Success = true, Data = StoreItems };
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceResponse<OwnedStoreItem> GetBoughtStoreItemFromUser(string userId, int itemId)
|
||||||
|
{
|
||||||
|
// find item owned by user
|
||||||
|
var item = _ctx.OwnedStoreItems.FirstOrDefault(e => e.UserId == userId && e.StoreItemId == itemId);
|
||||||
|
if (item != null)
|
||||||
|
return new ServiceResponse<OwnedStoreItem> { Success = true, Data = item };
|
||||||
|
else return new ServiceResponse<OwnedStoreItem> { Success = false, Message = "Item Not Yet Purchased" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServiceResponse<List<OwnedStoreItem>> GetBoughtStoreItemsFromUser(string userId)
|
||||||
|
{
|
||||||
|
// find items owned by user
|
||||||
|
var items = _ctx.OwnedStoreItems.Where(e => e.UserId == userId).ToList();
|
||||||
|
if (items != null && items.Count > 0)
|
||||||
|
return new ServiceResponse<List<OwnedStoreItem>> { Success = true, Data = items };
|
||||||
|
else return new ServiceResponse<List<OwnedStoreItem>> { Success = false, Message = "User Owns No Items" };
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ServiceResponse<OwnedStoreItem>> BuyStoreItem(string userId, int id)
|
||||||
|
{
|
||||||
|
// find item in store
|
||||||
|
var item = StoreItems.FirstOrDefault(e => e.Id == id);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
// deduct currency from user
|
||||||
|
var user = await _userService.GetUserById(userId);
|
||||||
|
if (user != null && user.Success && user.Data != null)
|
||||||
|
{
|
||||||
|
if (user.Data.CurrencyAmount >= item.Price)
|
||||||
|
{
|
||||||
|
// remove currency from user
|
||||||
|
await _userService.RemoveCurrencyFromUser(userId, item.Price);
|
||||||
|
|
||||||
|
// create owned item
|
||||||
|
OwnedStoreItem ownedStoreItem = new OwnedStoreItem
|
||||||
|
{
|
||||||
|
StoreItemId = item.Id,
|
||||||
|
UserId = userId
|
||||||
|
};
|
||||||
|
|
||||||
|
// add to table
|
||||||
|
_ctx.OwnedStoreItems.Add(ownedStoreItem);
|
||||||
|
await _ctx.SaveChangesAsync();
|
||||||
|
|
||||||
|
// return successful service response
|
||||||
|
return new ServiceResponse<OwnedStoreItem> { Success = true, Data = ownedStoreItem };
|
||||||
|
}
|
||||||
|
else return new ServiceResponse<OwnedStoreItem> { Success = false, Message = "Insufficient Currency" };
|
||||||
|
}
|
||||||
|
else return new ServiceResponse<OwnedStoreItem> { Success = false, Message = "User Not Found" };
|
||||||
|
}
|
||||||
|
else return new ServiceResponse<OwnedStoreItem> { Success = false, Message = "Item Not Found" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -174,6 +174,7 @@
|
|||||||
dto.CreatedAt = user.CreatedAt;
|
dto.CreatedAt = user.CreatedAt;
|
||||||
dto.Status = user.Status;
|
dto.Status = user.Status;
|
||||||
dto.CurrencyAmount = user.CurrencyAmount;
|
dto.CurrencyAmount = user.CurrencyAmount;
|
||||||
|
dto.ProfileCosmeticId = user.ActiveProfileCosmetic;
|
||||||
|
|
||||||
serviceResponse.Success = true;
|
serviceResponse.Success = true;
|
||||||
serviceResponse.Data = dto;
|
serviceResponse.Data = dto;
|
||||||
@ -219,6 +220,7 @@
|
|||||||
infoDto.CreatedAt = dbUser.CreatedAt;
|
infoDto.CreatedAt = dbUser.CreatedAt;
|
||||||
infoDto.Status = dbUser.Status;
|
infoDto.Status = dbUser.Status;
|
||||||
infoDto.CurrencyAmount = dbUser.CurrencyAmount;
|
infoDto.CurrencyAmount = dbUser.CurrencyAmount;
|
||||||
|
infoDto.ProfileCosmeticId = request.ProfileCosmeticId;
|
||||||
|
|
||||||
serviceResponse.Success = true;
|
serviceResponse.Success = true;
|
||||||
serviceResponse.Data = infoDto;
|
serviceResponse.Data = infoDto;
|
||||||
|
@ -31,6 +31,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Content Update="Resources\store.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Update="ServerConfig.json">
|
<Content Update="ServerConfig.json">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user