diff --git a/.gitignore b/.gitignore index 80e7ec2..b346171 100644 --- a/.gitignore +++ b/.gitignore @@ -362,4 +362,6 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd /WardrobeManager.Api/database.dat +/WardrobeManager.Api/database.dat-shm +/WardrobeManager.Api/database.dat-wal .idea diff --git a/Session.vim b/Session.vim index 095aa77..000aa4d 100644 --- a/Session.vim +++ b/Session.vim @@ -14,19 +14,35 @@ if &shortmess =~ 'A' else set shortmess=aoO endif -badd +0 WardrobeManager.Presentation/Pages/Authenticated/Clothing.razor -badd +0 WardrobeManager.Presentation/Componenets/FormItems/DropDown.razor -badd +15 WardrobeManager.Presentation/Componenets/FormItems/SwitchToggle.razor -badd +12 WardrobeManager.Presentation/Componenets/FormItems/DatePicker.razor -badd +0 WardrobeManager.Presentation/Componenets/FormItems/Range.razor +badd +0 WardrobeManager.Api/Program.cs +badd +7 WardrobeManager.Api/Endpoints/ClothingEndpoints.cs +badd +44 WardrobeManager.Api/Database/Services/Implementation/UserService.cs +badd +11 WardrobeManager.Api/Database/Services/Interfaces/IUserService.cs +badd +24 WardrobeManager.Shared/Services/Implementation/SharedService.cs +badd +23 WardrobeManager.Api/Endpoints/ImageEndpoints.cs +badd +47 WardrobeManager.Api/Database/Services/Implementation/FileService.cs +badd +9 WardrobeManager.Api/Database/Services/Interfaces/IFileService.cs +badd +23 WardrobeManager.Api/Endpoints/MiscEndpoints.cs +badd +22 WardrobeManager.Shared/Models/ServerClothingItem.cs +badd +2 WardrobeManager.Shared/Models/NewOrEditedClothingItem.cs +badd +12 WardrobeManager.Shared/Models/NewClothingItemDTO.cs +badd +2 WardrobeManager.Shared/Models/ClientClothingItem.cs +badd +5 WardrobeManager.Shared/Models/User.cs +badd +4 WardrobeManager.Shared/Enums/WearLocation.cs +badd +9 WardrobeManager.Shared/Services/Interfaces/ISharedService.cs +badd +1 WardrobeManager.Api/Endpoints/ActionEndpoints.cs +badd +22 WardrobeManager.Api/Database/Services/Implementation/ClothingItemService.cs +badd +7 WardrobeManager.Api/Database/Services/Interfaces/IClothingItemService.cs +badd +0 WardrobeManager.Shared/Exceptions/Exceptions.cs argglobal %argdel set stal=2 tabnew +setlocal\ bufhidden=wipe tabnew +setlocal\ bufhidden=wipe tabnew +setlocal\ bufhidden=wipe +tabnew +setlocal\ bufhidden=wipe tabrewind -edit WardrobeManager.Presentation/Pages/Authenticated/Clothing.razor +edit WardrobeManager.Api/Program.cs argglobal setlocal fdm=manual setlocal fde=0 @@ -38,15 +54,16 @@ setlocal fdn=20 setlocal fen silent! normal! zE let &fdl = &fdl -let s:l = 57 - ((11 * winheight(0) + 13) / 26) +let s:l = 129 - ((23 * winheight(0) + 24) / 48) if s:l < 1 | let s:l = 1 | endif keepjumps exe s:l normal! zt -keepjumps 57 -normal! 047| +keepjumps 129 +normal! $ tabnext -edit WardrobeManager.Presentation/Componenets/FormItems/DropDown.razor +edit WardrobeManager.Api/Endpoints/ClothingEndpoints.cs argglobal +balt WardrobeManager.Shared/Models/ServerClothingItem.cs setlocal fdm=manual setlocal fde=0 setlocal fmr={{{,}}} @@ -57,16 +74,34 @@ setlocal fdn=20 setlocal fen silent! normal! zE let &fdl = &fdl -let s:l = 17 - ((16 * winheight(0) + 13) / 26) +let s:l = 14 - ((1 * winheight(0) + 25) / 51) if s:l < 1 | let s:l = 1 | endif keepjumps exe s:l normal! zt -keepjumps 17 -normal! 072| +keepjumps 14 +normal! 05| tabnext -edit WardrobeManager.Presentation/Componenets/FormItems/SwitchToggle.razor +edit WardrobeManager.Shared/Models/NewClothingItemDTO.cs +let s:save_splitbelow = &splitbelow +let s:save_splitright = &splitright +set splitbelow splitright +wincmd _ | wincmd | +vsplit +1wincmd h +wincmd w +let &splitbelow = s:save_splitbelow +let &splitright = s:save_splitright +wincmd t +let s:save_winminheight = &winminheight +let s:save_winminwidth = &winminwidth +set winminheight=0 +set winheight=1 +set winminwidth=0 +set winwidth=1 +exe 'vert 1resize ' . ((&columns * 114 + 118) / 236) +exe 'vert 2resize ' . ((&columns * 121 + 118) / 236) argglobal -balt WardrobeManager.Presentation/Componenets/FormItems/DatePicker.razor +balt WardrobeManager.Shared/Models/ServerClothingItem.cs setlocal fdm=manual setlocal fde=0 setlocal fmr={{{,}}} @@ -77,14 +112,190 @@ setlocal fdn=20 setlocal fen silent! normal! zE let &fdl = &fdl -let s:l = 15 - ((12 * winheight(0) + 13) / 26) +let s:l = 36 - ((35 * winheight(0) + 25) / 51) if s:l < 1 | let s:l = 1 | endif keepjumps exe s:l normal! zt -keepjumps 15 -normal! 0118| +keepjumps 36 +normal! 0 +wincmd w +argglobal +if bufexists(fnamemodify("WardrobeManager.Shared/Models/ServerClothingItem.cs", ":p")) | buffer WardrobeManager.Shared/Models/ServerClothingItem.cs | else | edit WardrobeManager.Shared/Models/ServerClothingItem.cs | endif +balt WardrobeManager.Shared/Models/NewClothingItemDTO.cs +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 55 - ((30 * winheight(0) + 25) / 51) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 55 +normal! 0 +wincmd w +exe 'vert 1resize ' . ((&columns * 114 + 118) / 236) +exe 'vert 2resize ' . ((&columns * 121 + 118) / 236) tabnext -edit WardrobeManager.Presentation/Componenets/FormItems/Range.razor +edit WardrobeManager.Api/Endpoints/ClothingEndpoints.cs +let s:save_splitbelow = &splitbelow +let s:save_splitright = &splitright +set splitbelow splitright +wincmd _ | wincmd | +vsplit +1wincmd h +wincmd _ | wincmd | +split +1wincmd k +wincmd w +wincmd w +wincmd _ | wincmd | +split +1wincmd k +wincmd w +let &splitbelow = s:save_splitbelow +let &splitright = s:save_splitright +wincmd t +let s:save_winminheight = &winminheight +let s:save_winminwidth = &winminwidth +set winminheight=0 +set winheight=1 +set winminwidth=0 +set winwidth=1 +exe '1resize ' . ((&lines * 25 + 27) / 54) +exe 'vert 1resize ' . ((&columns * 118 + 118) / 236) +exe '2resize ' . ((&lines * 25 + 27) / 54) +exe 'vert 2resize ' . ((&columns * 118 + 118) / 236) +exe '3resize ' . ((&lines * 40 + 27) / 54) +exe 'vert 3resize ' . ((&columns * 117 + 118) / 236) +exe '4resize ' . ((&lines * 10 + 27) / 54) +exe 'vert 4resize ' . ((&columns * 117 + 118) / 236) +argglobal +balt WardrobeManager.Api/Database/Services/Implementation/UserService.cs +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 7 - ((3 * winheight(0) + 12) / 25) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 7 +normal! 04| +wincmd w +argglobal +if bufexists(fnamemodify("WardrobeManager.Api/Database/Services/Implementation/UserService.cs", ":p")) | buffer WardrobeManager.Api/Database/Services/Implementation/UserService.cs | else | edit WardrobeManager.Api/Database/Services/Implementation/UserService.cs | endif +balt WardrobeManager.Api/Endpoints/ClothingEndpoints.cs +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 44 - ((12 * winheight(0) + 12) / 25) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 44 +normal! 09| +wincmd w +argglobal +if bufexists(fnamemodify("WardrobeManager.Api/Database/Services/Implementation/ClothingItemService.cs", ":p")) | buffer WardrobeManager.Api/Database/Services/Implementation/ClothingItemService.cs | else | edit WardrobeManager.Api/Database/Services/Implementation/ClothingItemService.cs | endif +balt WardrobeManager.Api/Database/Services/Interfaces/IClothingItemService.cs +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 22 - ((16 * winheight(0) + 20) / 40) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 22 +normal! 0 +wincmd w +argglobal +if bufexists(fnamemodify("WardrobeManager.Api/Database/Services/Interfaces/IClothingItemService.cs", ":p")) | buffer WardrobeManager.Api/Database/Services/Interfaces/IClothingItemService.cs | else | edit WardrobeManager.Api/Database/Services/Interfaces/IClothingItemService.cs | endif +balt WardrobeManager.Api/Database/Services/Implementation/ClothingItemService.cs +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal fen +silent! normal! zE +let &fdl = &fdl +let s:l = 7 - ((2 * winheight(0) + 5) / 10) +if s:l < 1 | let s:l = 1 | endif +keepjumps exe s:l +normal! zt +keepjumps 7 +normal! 05| +wincmd w +exe '1resize ' . ((&lines * 25 + 27) / 54) +exe 'vert 1resize ' . ((&columns * 118 + 118) / 236) +exe '2resize ' . ((&lines * 25 + 27) / 54) +exe 'vert 2resize ' . ((&columns * 118 + 118) / 236) +exe '3resize ' . ((&lines * 40 + 27) / 54) +exe 'vert 3resize ' . ((&columns * 117 + 118) / 236) +exe '4resize ' . ((&lines * 10 + 27) / 54) +exe 'vert 4resize ' . ((&columns * 117 + 118) / 236) +tabnext +edit WardrobeManager.Shared/Exceptions/Exceptions.cs +let s:save_splitbelow = &splitbelow +let s:save_splitright = &splitright +set splitbelow splitright +wincmd _ | wincmd | +vsplit +1wincmd h +wincmd w +let &splitbelow = s:save_splitbelow +let &splitright = s:save_splitright +wincmd t +let s:save_winminheight = &winminheight +let s:save_winminwidth = &winminwidth +set winminheight=0 +set winheight=1 +set winminwidth=0 +set winwidth=1 +exe '1resize ' . ((&lines * 48 + 27) / 54) +exe 'vert 1resize ' . ((&columns * 20 + 118) / 236) +exe '2resize ' . ((&lines * 48 + 27) / 54) +exe 'vert 2resize ' . ((&columns * 188 + 118) / 236) +argglobal +enew +file NERD_tree_tab_6 +setlocal fdm=manual +setlocal fde=0 +setlocal fmr={{{,}}} +setlocal fdi=# +setlocal fdl=0 +setlocal fml=1 +setlocal fdn=20 +setlocal nofen +wincmd w argglobal setlocal fdm=manual setlocal fde=0 @@ -96,13 +307,18 @@ setlocal fdn=20 setlocal fen silent! normal! zE let &fdl = &fdl -let s:l = 1 - ((0 * winheight(0) + 13) / 26) +let s:l = 10 - ((9 * winheight(0) + 24) / 48) if s:l < 1 | let s:l = 1 | endif keepjumps exe s:l normal! zt -keepjumps 1 +keepjumps 10 normal! 0 -tabnext 1 +wincmd w +exe '1resize ' . ((&lines * 48 + 27) / 54) +exe 'vert 1resize ' . ((&columns * 20 + 118) / 236) +exe '2resize ' . ((&lines * 48 + 27) / 54) +exe 'vert 2resize ' . ((&columns * 188 + 118) / 236) +tabnext 4 set stal=1 if exists('s:wipebuf') && len(win_findbuf(s:wipebuf)) == 0 silent exe 'bwipe ' . s:wipebuf @@ -110,11 +326,14 @@ endif unlet! s:wipebuf set winheight=1 winwidth=20 let &shortmess = s:shortmess_save +let &winminheight = s:save_winminheight +let &winminwidth = s:save_winminwidth let s:sx = expand(":p:r")."x.vim" if filereadable(s:sx) exe "source " . fnameescape(s:sx) endif let &g:so = s:so_save | let &g:siso = s:siso_save +nohlsearch doautoall SessionLoadPost unlet SessionLoad " vim: set ft=vim : diff --git a/WardrobeManager.Api/Database/Services/Implementation/ClothingItemService.cs b/WardrobeManager.Api/Database/Services/Implementation/ClothingItemService.cs index ab73856..152a3b5 100644 --- a/WardrobeManager.Api/Database/Services/Implementation/ClothingItemService.cs +++ b/WardrobeManager.Api/Database/Services/Implementation/ClothingItemService.cs @@ -1,64 +1,137 @@ using Microsoft.EntityFrameworkCore; using WardrobeManager.Shared.Models; +using WardrobeManager.Shared.Enums; +using WardrobeManager.Shared.Services.Interfaces; using WardrobeManager.Api.Database.Services.Interfaces; +using System.Diagnostics; namespace WardrobeManager.Api.Database.Services.Implementation; public class ClothingItemService : IClothingItemService { - private readonly DatabaseContext _databaseContext; + private readonly DatabaseContext _context; + private readonly IUserService _userService; + private readonly ISharedService _sharedService; + private readonly IFileService _fileService; - public ClothingItemService(DatabaseContext databaseContext) + public ClothingItemService(DatabaseContext databaseContext, IUserService userService, ISharedService sharedService, IFileService fileService) { - _databaseContext = databaseContext; + _context = databaseContext; + _userService = userService; + _sharedService = sharedService; + _fileService = fileService; } - // public async Task CreateUser(string userId) - // public async Task> GetClothes() - // { - // return await _databaseContext.ClothingItems.ToListAsync(); - // } + // ---- Methods for multiple clothing items --- + public async Task?> GetAllClothing(int userId) { + return _context.ClothingItems.Where(item => item.UserId == userId).ToList(); + } + + // ---- Methods for one clothing item --- + public async Task GetClothingItem(int userId, int itemId) { + return await _context.ClothingItems.Where(item => item.Id == itemId && item.UserId == userId).FirstOrDefaultAsync(); + } - public async Task Add(ServerClothingItem item) + public async Task AddClothingItem(int userId, NewOrEditedClothingItemDTO newItem) { - if (item == null) + if (newItem == null) { - throw new ArgumentNullException(nameof(item)); + throw new Exception("NewOrEditedClothingItemDTO is null"); } - await _databaseContext.ClothingItems.AddAsync(item); - await _databaseContext.SaveChangesAsync(); - } + Guid? newItemGuid = null; + if (_sharedService.IsValidBase64(newItem.ImageBase64)) { + newItemGuid = Guid.NewGuid(); + // decode and save file to place on disk with guid as name + await _fileService.SaveImage(newItemGuid, newItem.ImageBase64); + } - public async Task Delete(ServerClothingItem item) - { - _databaseContext.ClothingItems.Remove(item); - await _databaseContext.SaveChangesAsync(); + ServerClothingItem newClothingItem = new ServerClothingItem + ( + newItem.Name, + newItem.Category, + newItem.Season, + newItem.WearLocation, + newItem.Favourited, + newItem.DesiredTimesWornBeforeWash, + newItemGuid + ); + + newClothingItem.UserId = userId; + await _context.ClothingItems.AddAsync(newClothingItem); + await _context.SaveChangesAsync(); } - public async Task Delete(int Id) + + + public async Task UpdateClothingItem(int userId, int itemId, NewOrEditedClothingItemDTO editedItem) { - var item = await _databaseContext.ClothingItems.SingleOrDefaultAsync(s => s.Id == Id); - _databaseContext.ClothingItems.Remove(item); - await _databaseContext.SaveChangesAsync(); + var dbRecord = await GetClothingItem(userId, itemId); + + // There was no 'old' item to edit + if (dbRecord == null) { + throw new Exception("Cannot find old item"); + } + if (editedItem == null) { + throw new Exception("editedItem is null"); + } + + dbRecord.Name = editedItem.Name; + dbRecord.Category = editedItem.Category; + dbRecord.Season = editedItem.Season; + dbRecord.Favourited = editedItem.Favourited; + dbRecord.WearLocation = editedItem.WearLocation; + dbRecord.DesiredTimesWornBeforeWash = editedItem.DesiredTimesWornBeforeWash; + dbRecord.DateUpdated = DateTime.UtcNow; + + // If its null we assume they do not want to change the existing image + if (editedItem.ImageBase64 != null) { + Guid? editedItemGuid = null; + if (_sharedService.IsValidBase64(editedItem.ImageBase64)) { + editedItemGuid = Guid.NewGuid(); + // decode and save file to place on disk with guid as name + await _fileService.SaveImage(editedItemGuid, editedItem.ImageBase64); + dbRecord.ImageGuid = editedItemGuid; + } + } + + _context.ClothingItems.Update(dbRecord); + await _context.SaveChangesAsync(); } - public async Task Update(ServerClothingItem item) + public async Task CallMethodOnClothingItem(int userId, int itemId, ActionType type) { - // throws exception if it finds more than one (good cus we serial number should be unique) - var oldItem = await _databaseContext.ClothingItems.FindAsync(item.Id); - - if (oldItem != null) - { - await Delete(oldItem); + var dbRecord = await GetClothingItem(userId, itemId); - await _databaseContext.ClothingItems.AddAsync(item); + // There was no 'old' item to edit + if (dbRecord == null) { + throw new Exception("Cannot find old item"); + } - await _databaseContext.SaveChangesAsync(); + switch(type) { + case ActionType.Wear: + dbRecord.Wear(); + break; + case ActionType.Wash: + dbRecord.Wash(); + break; } - else - { - throw new Exception("Item does not exist"); + + _context.ClothingItems.Update(dbRecord); + await _context.SaveChangesAsync(); + } + + + public async Task DeleteClothingItem(int userId, int itemId) + { + var itemToDelete = await GetClothingItem(userId, itemId); + + if (itemToDelete == null) { + throw new Exception("Cannot find item to delete"); } + + _context.ClothingItems.Remove(itemToDelete); + await _context.SaveChangesAsync(); } + } diff --git a/WardrobeManager.Api/Database/Services/Implementation/FileService.cs b/WardrobeManager.Api/Database/Services/Implementation/FileService.cs index 3b81042..09481c6 100644 --- a/WardrobeManager.Api/Database/Services/Implementation/FileService.cs +++ b/WardrobeManager.Api/Database/Services/Implementation/FileService.cs @@ -16,7 +16,7 @@ public string GetDefaultUploadPath() string projectRootPath = AppDomain.CurrentDomain.BaseDirectory; // 2. Construct the Full File Path: - string uploadsFolderPath = Path.Combine(projectRootPath, "Uploads"); // "uploads" is the folder name + string uploadsFolderPath = Path.Combine(projectRootPath, "Uploads"); // "uploads" is the folder name // 3. Create the folder if it doesn't exist: Directory.CreateDirectory(uploadsFolderPath); // sneaking it in so i don't have to later @@ -26,7 +26,7 @@ public string GetDefaultUploadPath() public async Task SaveImage(Guid? guid, string ImageBase64) { - // we check on the client, but if the image is larger than our limit + // we check on the client, but if the image is larger than our limit // the base64 could be empty if (guid == null || ImageBase64 == string.Empty) { @@ -49,13 +49,13 @@ public async Task GetImage(string guid) if (File.Exists(path)) { - byte[] imageBytes = await File.ReadAllBytesAsync(path); // 6. Serve the file + byte[] imageBytes = await File.ReadAllBytesAsync(path); // 6. Serve the file return imageBytes; } else { - byte[] imageBytes = await File.ReadAllBytesAsync(notFound); // 6. Serve the file + byte[] imageBytes = await File.ReadAllBytesAsync(notFound); // 6. Serve the file return imageBytes; } } diff --git a/WardrobeManager.Api/Database/Services/Implementation/UserService.cs b/WardrobeManager.Api/Database/Services/Implementation/UserService.cs index 4417471..e765552 100644 --- a/WardrobeManager.Api/Database/Services/Implementation/UserService.cs +++ b/WardrobeManager.Api/Database/Services/Implementation/UserService.cs @@ -23,8 +23,8 @@ public async Task CreateUser(string Auth0Id) { var sampleClothingItems = new List { - new ServerClothingItem("Example T-Shirt", ClothingCategory.TShirt, Season.Fall, null), - new ServerClothingItem("Example Pants", ClothingCategory.Jeans, Season.Winter, null) + new ServerClothingItem("Example T-Shirt", ClothingCategory.TShirt, Season.Fall, WearLocation.HomeAndOutside, false, 5, null), + new ServerClothingItem("Example Pants", ClothingCategory.Jeans, Season.SummerAndFall, WearLocation.HomeAndOutside, false, 20, null), }; newUser.ServerClothingItems = sampleClothingItems; diff --git a/WardrobeManager.Api/Database/Services/Interfaces/IClothingItemService.cs b/WardrobeManager.Api/Database/Services/Interfaces/IClothingItemService.cs index a4e3fdf..28965ba 100644 --- a/WardrobeManager.Api/Database/Services/Interfaces/IClothingItemService.cs +++ b/WardrobeManager.Api/Database/Services/Interfaces/IClothingItemService.cs @@ -1,12 +1,16 @@ using WardrobeManager.Shared.Models; +using WardrobeManager.Shared.Enums; namespace WardrobeManager.Api.Database.Services.Interfaces; public interface IClothingItemService { - Task Add(ServerClothingItem item); - Task Delete(ServerClothingItem item); - // Task> GetClothes(); - Task Delete(int Id); - Task Update(ServerClothingItem item); + // ---- Methods for multiple clothing items --- + Task?> GetAllClothing(int userId); + // ---- Methods for one clothing item --- + Task GetClothingItem(int userId, int itemId); + Task AddClothingItem(int userId, NewOrEditedClothingItemDTO newItem); + Task UpdateClothingItem(int userId, int itemId, NewOrEditedClothingItemDTO editedItem); + Task DeleteClothingItem(int userId, int itemId); + Task CallMethodOnClothingItem(int userId, int itemId, ActionType type); } diff --git a/WardrobeManager.Api/Database/Services/Interfaces/IFileService.cs b/WardrobeManager.Api/Database/Services/Interfaces/IFileService.cs index 557a1dd..fe78dd2 100644 --- a/WardrobeManager.Api/Database/Services/Interfaces/IFileService.cs +++ b/WardrobeManager.Api/Database/Services/Interfaces/IFileService.cs @@ -3,7 +3,11 @@ public interface IFileService { string GetDefaultUploadPath(); + + /// Gets a specific image based on the guid + /// GUID of image + /// byte array of image Task GetImage(string guid); string ParseGuid(Guid guid); Task SaveImage(Guid? guid, string ImageBase64); -} \ No newline at end of file +} diff --git a/WardrobeManager.Api/Endpoints/ActionEndpoints.cs b/WardrobeManager.Api/Endpoints/ActionEndpoints.cs new file mode 100644 index 0000000..ef16c6c --- /dev/null +++ b/WardrobeManager.Api/Endpoints/ActionEndpoints.cs @@ -0,0 +1,43 @@ +using System; +using WardrobeManager.Api.Database; +using WardrobeManager.Api.Database.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using WardrobeManager.Shared.Models; +using WardrobeManager.Shared.Enums; +using WardrobeManager.Shared.Services.Interfaces; + + +namespace WardrobeManager.Api.Endpoints; + +public static class ActionEndpoints { + public static void MapActionEndpoints(this IEndpointRouteBuilder app) { + var group = app.MapGroup("/actions").RequireAuthorization(); + + group.MapGet("/wear/{id}", WearClothing); + group.MapGet("/wash/{id}", WashClothing); + } + + public static async Task WearClothing + ( + int itemId, HttpContext context, IClothingItemService clothingItemService + ){ + User? user = context.Items["user"] as User; + Debug.Assert(user != null, "Cannot get user"); + + await clothingItemService.CallMethodOnClothingItem(user.Id, itemId, ActionType.Wear); + + return Results.Ok("Item Worn"); + } + public static async Task WashClothing + ( + int itemId, HttpContext context, IClothingItemService clothingItemService + ){ + User? user = context.Items["user"] as User; + Debug.Assert(user != null, "Cannot get user"); + + await clothingItemService.CallMethodOnClothingItem(user.Id, itemId, ActionType.Wash); + return Results.Ok("Item Worn"); + } +} diff --git a/WardrobeManager.Api/Endpoints/ClothingEndpoints.cs b/WardrobeManager.Api/Endpoints/ClothingEndpoints.cs new file mode 100644 index 0000000..86d1669 --- /dev/null +++ b/WardrobeManager.Api/Endpoints/ClothingEndpoints.cs @@ -0,0 +1,83 @@ +using System; +using WardrobeManager.Api.Database; +using WardrobeManager.Api.Database.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using WardrobeManager.Shared.Models; +using WardrobeManager.Shared.Services.Interfaces; + + +namespace WardrobeManager.Api.Endpoints; + +public static class ClothingEndpoints { + public static void MapClothingEndpoints(this IEndpointRouteBuilder app) { + var group = app.MapGroup("/clothing").RequireAuthorization(); + + group.MapGet("", GetClothing); + group.MapPost("", AddClothingItem); + group.MapPut("/{id}", EditClothingItem); + group.MapDelete("/{id}", DeleteClothingItem); + } + + // --------------------- + // Get all clothing items + // --------------------- + public static async Task GetClothing( + HttpContext context, IClothingItemService clothingItemService, DatabaseContext _context + ){ + User? user = context.Items["user"] as User; + Debug.Assert(user != null, "Cannot get user"); + + var clothes = await clothingItemService.GetAllClothing(user.Id); + + return Results.Ok(clothes); + } + + // --------------------- + // Add a new clothing item (these comments are unecessar but i don't want no comments) + // --------------------- + public static async Task AddClothingItem( + [FromBody] NewOrEditedClothingItemDTO newItem, IClothingItemService clothingItemService, ISharedService sharedService, IFileService fileService, IUserService userService, HttpContext context, DatabaseContext _context + ){ + // if (!sharedService.IsValid(newItem.ClothingItem)) { + // return Results.BadRequest("Invalid clothing item"); + // } + User? user = context.Items["user"] as User; + Debug.Assert(user != null, "Cannot get user"); + + await clothingItemService.AddClothingItem(user.Id, newItem); + + return Results.Created(); + } + + // --------------------- + // Edit one clothing item + // --------------------- + public static async Task EditClothingItem( + int id, [FromBody] NewOrEditedClothingItemDTO editedItem, IClothingItemService clothingItemService, ISharedService sharedService, IFileService fileService, DatabaseContext _context, IUserService userService, HttpContext context + ){ + + User? user = context.Items["user"] as User; + Debug.Assert(user != null, "Cannot get user"); + + await clothingItemService.UpdateClothingItem(user.Id, id, editedItem); + + return Results.Ok(); + } + + // --------------------- + // Delete one clothing item + // --------------------- + public static async Task DeleteClothingItem( + int itemId, IClothingItemService clothingItemService, HttpContext context, IUserService userService, DatabaseContext _context + ){ + User? user = context.Items["user"] as User; + Debug.Assert(user != null, "Cannot get user"); + + await clothingItemService.DeleteClothingItem(user.Id,itemId); + + return Results.Ok("Deleted"); + } + +} diff --git a/WardrobeManager.Api/Endpoints/ImageEndpoints.cs b/WardrobeManager.Api/Endpoints/ImageEndpoints.cs new file mode 100644 index 0000000..593d10b --- /dev/null +++ b/WardrobeManager.Api/Endpoints/ImageEndpoints.cs @@ -0,0 +1,30 @@ +using System; +using WardrobeManager.Api.Database; +using WardrobeManager.Api.Database.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using WardrobeManager.Shared.Models; +using WardrobeManager.Shared.Services.Interfaces; + + +namespace WardrobeManager.Api.Endpoints; + +public static class ImageEndpoints { + public static void MapImageEndpoints(this IEndpointRouteBuilder app) { + var group = app.MapGroup("/images").RequireAuthorization(); + + group.MapGet("/{id}", GetImage); + } + + // No authorization for this for right now since doing does not include the JWT token + public static async Task GetImage( + string imageGuid, IFileService fileService + ){ + return Results.File(await fileService.GetImage(imageGuid)); + + // should implement - maybe not idk + //return Results.NotFound("Image not found"); + } +} + diff --git a/WardrobeManager.Api/Endpoints/MiscEndpoints.cs b/WardrobeManager.Api/Endpoints/MiscEndpoints.cs new file mode 100644 index 0000000..90751dd --- /dev/null +++ b/WardrobeManager.Api/Endpoints/MiscEndpoints.cs @@ -0,0 +1,26 @@ +using System; +using WardrobeManager.Api.Database; +using WardrobeManager.Api.Database.Services.Interfaces; +using Microsoft.AspNetCore.Authorization; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc; +using WardrobeManager.Shared.Models; +using WardrobeManager.Shared.Services.Interfaces; + + +namespace WardrobeManager.Api.Endpoints; + +public static class MiscEndpoints { + public static void MapMiscEndpoints(this IEndpointRouteBuilder app) { + // Since these are misc. authorization might not be necessary + app.MapGet("/ping", Ping); + } + + public static IResult Ping(HttpContext context) { + if (context.User.Identity?.IsAuthenticated == true) { + return Results.Ok("Authenticated: WardrobeManager API is active."); + } + return Results.Ok("Unauthenticated: WardrobeManager API is active."); + } +} + diff --git a/WardrobeManager.Api/GlobalExceptionHandler.cs b/WardrobeManager.Api/GlobalExceptionHandler.cs new file mode 100644 index 0000000..59d9d6d --- /dev/null +++ b/WardrobeManager.Api/GlobalExceptionHandler.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Diagnostics; + + +namespace WardrobeManager.Api; + +internal sealed class GlobalExceptionHandler : IExceptionHandler +{ + private readonly ILogger _logger; + + public GlobalExceptionHandler(ILogger logger) + { + _logger = logger; + } + + public async ValueTask TryHandleAsync( + HttpContext context, + Exception exception, + CancellationToken cancellationToken) + { + _logger.LogError(exception.Message); + + context.Response.StatusCode = 500; + await context.Response.WriteAsync(exception.Message); + + return true; + } +} + diff --git a/WardrobeManager.Api/Migrations/20240816021443_AddWearLocation.Designer.cs b/WardrobeManager.Api/Migrations/20240816021443_AddWearLocation.Designer.cs new file mode 100644 index 0000000..4cfc27d --- /dev/null +++ b/WardrobeManager.Api/Migrations/20240816021443_AddWearLocation.Designer.cs @@ -0,0 +1,109 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using WardrobeManager.Api.Database; + +#nullable disable + +namespace WardrobeManager.Api.Migrations +{ + [DbContext(typeof(DatabaseContext))] + [Migration("20240816021443_AddWearLocation")] + partial class AddWearLocation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.6"); + + modelBuilder.Entity("WardrobeManager.Shared.Models.ServerClothingItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Category") + .HasColumnType("INTEGER"); + + b.Property("DateAdded") + .HasColumnType("TEXT"); + + b.Property("DateUpdated") + .HasColumnType("TEXT"); + + b.Property("DesiredTimesWornBeforeWash") + .HasColumnType("INTEGER"); + + b.Property("Favourited") + .HasColumnType("INTEGER"); + + b.Property("ImageGuid") + .HasColumnType("TEXT"); + + b.Property("LastWorn") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Season") + .HasColumnType("INTEGER"); + + b.Property("TimesWornSinceWash") + .HasColumnType("INTEGER"); + + b.Property("TimesWornTotal") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.Property("WearLocation") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("ClothingItems"); + }); + + modelBuilder.Entity("WardrobeManager.Shared.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Auth0Id") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("WardrobeManager.Shared.Models.ServerClothingItem", b => + { + b.HasOne("WardrobeManager.Shared.Models.User", "User") + .WithMany("ServerClothingItems") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("WardrobeManager.Shared.Models.User", b => + { + b.Navigation("ServerClothingItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/WardrobeManager.Api/Migrations/20240816021443_AddWearLocation.cs b/WardrobeManager.Api/Migrations/20240816021443_AddWearLocation.cs new file mode 100644 index 0000000..26ed857 --- /dev/null +++ b/WardrobeManager.Api/Migrations/20240816021443_AddWearLocation.cs @@ -0,0 +1,47 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace WardrobeManager.Api.Migrations +{ + /// + public partial class AddWearLocation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Season", + table: "ClothingItems", + type: "INTEGER", + nullable: false, + defaultValue: 0, + oldClrType: typeof(int), + oldType: "INTEGER", + oldNullable: true); + + migrationBuilder.AddColumn( + name: "WearLocation", + table: "ClothingItems", + type: "INTEGER", + nullable: false, + defaultValue: 0); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "WearLocation", + table: "ClothingItems"); + + migrationBuilder.AlterColumn( + name: "Season", + table: "ClothingItems", + type: "INTEGER", + nullable: true, + oldClrType: typeof(int), + oldType: "INTEGER"); + } + } +} diff --git a/WardrobeManager.Api/Migrations/DatabaseContextModelSnapshot.cs b/WardrobeManager.Api/Migrations/DatabaseContextModelSnapshot.cs index a04bd02..46dceac 100644 --- a/WardrobeManager.Api/Migrations/DatabaseContextModelSnapshot.cs +++ b/WardrobeManager.Api/Migrations/DatabaseContextModelSnapshot.cs @@ -48,7 +48,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("TEXT"); - b.Property("Season") + b.Property("Season") .HasColumnType("INTEGER"); b.Property("TimesWornSinceWash") @@ -60,6 +60,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("UserId") .HasColumnType("INTEGER"); + b.Property("WearLocation") + .HasColumnType("INTEGER"); + b.HasKey("Id"); b.HasIndex("UserId"); diff --git a/WardrobeManager.Api/Program.cs b/WardrobeManager.Api/Program.cs index d99a981..cedd9e9 100644 --- a/WardrobeManager.Api/Program.cs +++ b/WardrobeManager.Api/Program.cs @@ -8,10 +8,13 @@ using WardrobeManager.Shared.Exceptions; using System.Diagnostics; using WardrobeManager.Shared.Services.Implementation; +using WardrobeManager.Api.Endpoints; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authorization; using Microsoft.OpenApi.Models; using Microsoft.AspNetCore.Http; +using WardrobeManager.Api; + var builder = WebApplication.CreateBuilder(args); @@ -23,14 +26,42 @@ // services added by me but created by microsft // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); -//builder.Services.AddSwaggerGen(); -builder.Services.AddSwaggerGen(); -// builder.Services.AddAuthentication(); -//builder.WebHost.UseUrls("http://localhost:9865"); +// Swagger can use JWT Bearer auth +builder.Services.AddSwaggerGen(opt => + { + opt.SwaggerDoc("v1", new OpenApiInfo { Title = "MyAPI", Version = "v1" }); + opt.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme + { + In = ParameterLocation.Header, + Description = "Please enter token", + Name = "Authorization", + Type = SecuritySchemeType.Http, + BearerFormat = "JWT", + Scheme = "bearer" + }); + + opt.AddSecurityRequirement(new OpenApiSecurityRequirement + { + { + new OpenApiSecurityScheme + { + Reference = new OpenApiReference + { + Type=ReferenceType.SecurityScheme, + Id="Bearer" + } + }, + new string[]{} + } + }); + }); // custom services +builder.Services.AddExceptionHandler(); +builder.Services.AddProblemDetails(); builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -48,13 +79,6 @@ ValidIssuer = domain, }; }); -//builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) -// .AddJwtBearer(options => -// { -// options.Authority = domain; -// options.Audience = builder.Configuration["Auth0:Audience"]; -// }); - builder.Services.AddAuthorization(o => { @@ -86,6 +110,14 @@ app.UseHttpsRedirection(); +app.UseExceptionHandler(); + + +// Mapping custom endpoints +app.MapClothingEndpoints(); +app.MapImageEndpoints(); +app.MapMiscEndpoints(); +app.MapActionEndpoints(); // https://learn.microsoft.com/en-us/aspnet/core/fundamentals/middleware/write?view=aspnetcore-8.0 @@ -102,12 +134,16 @@ { await userService.CreateUser(Auth0Id); Debug.Assert(await userService.DoesUserExist(Auth0Id) == true, "User finding/creating is broken"); + + var user = await userService.GetUser(Auth0Id); + Debug.Assert(user != null, "At this point in the pipeline user should exist"); + // pass along to controllers - context.Items["Auth0Id"] = Auth0Id; + context.Items["user"] = user; } await next(context); - }); +}); @@ -116,144 +152,4 @@ var context = scope.ServiceProvider.GetRequiredService(); DatabaseInitializer.Initialize(context); -// ---------------------------------- -// Miscellaneous -// ---------------------------------- -app.MapGet("/ping", async Task (HttpContext context) => - { - return Results.Ok("WardrobeManager API is active"); - }); - -// ---------------------------------- -// All clothing items -// ---------------------------------- -app.MapGet("/clothing", async Task (HttpContext context, IUserService userService, DatabaseContext con) => - { - // assuming user exists in db because of middleware - // assuming this exists because the user is logged in - string Auth0Id = context.Items["Auth0Id"].ToString() ?? string.Empty; - Debug.Assert(Auth0Id != string.Empty, "Cannot get Auth0Id"); - - var user = await userService.GetUserWithClothingItems(Auth0Id); - Debug.Assert(user != null, "User should not be null"); - - return Results.Ok(user.ServerClothingItems); - - }).RequireAuthorization(); - -// ---------------------------------- -// GET, POST, PUT for one clothing item -// ---------------------------------- -app.MapPost("/clothingitem", async Task ([FromBody] NewOrEditedClothingItem newItem, IClothingItemService clothingItemService, ISharedService sharedService, IFileService fileService, IUserService userService, HttpContext context, DatabaseContext _context) => - { - if (!sharedService.IsValid(newItem.ClothingItem)) { - return Results.BadRequest("Invalid clothing item"); - } - - string Auth0Id = context.Items["Auth0Id"].ToString() ?? string.Empty; - Debug.Assert(Auth0Id != string.Empty, "Cannot get Auth0Id"); - - var user = await userService.GetUser(Auth0Id); - Debug.Assert(user != null, "User should not be null"); - - - var newClothingItem = new ServerClothingItem - ( - name: newItem.ClothingItem.Name, - category: newItem.ClothingItem.Category, - season: newItem.ClothingItem.Season, - imageGuid: Guid.NewGuid() - ); - - // decode and save file to place on disk with guid as name - // make new obj of server clothing item and and use guid from before - // return "Created" - await fileService.SaveImage(newClothingItem.ImageGuid, newItem.ImageBase64); - - user.ServerClothingItems.Add(newClothingItem); - await _context.SaveChangesAsync(); - - - return Results.Created(); - - }).RequireAuthorization(); - -app.MapPut("/clothingitem", async Task ([FromBody] NewOrEditedClothingItem editedItem, IClothingItemService clothingItemService, ISharedService sharedService, IFileService fileService, DatabaseContext _context, IUserService userService, HttpContext context) => - { - if (!sharedService.IsValid(editedItem.ClothingItem)) - { - return Results.NotFound("Item cannot be updated. It can either not be found or the provided data is invalid."); - } - - string Auth0Id = context.Items["Auth0Id"].ToString() ?? string.Empty; - Debug.Assert(Auth0Id != string.Empty, "Cannot get Auth0Id"); - - var user = await userService.GetUser(Auth0Id); - Debug.Assert(user != null, "User should not be null"); - - var dbRecord = await _context.ClothingItems.FindAsync(editedItem.ClothingItem.Id); - - Debug.Assert(dbRecord != null, "If item is being edited old record must exist"); - Debug.Assert(editedItem.ClothingItem.Id != dbRecord.Id, "Edited and Old item IDs should always match"); - Debug.Assert(editedItem.ClothingItem.UserId != dbRecord.UserId, "Edited and Old item user IDs should always match"); - - - if (editedItem.ImageBase64 != string.Empty) - { - // generate new guid since its a new image - editedItem.ClothingItem.ImageGuid = Guid.NewGuid(); - } - - // decode and save file to place on disk with guid as name - // make new obj of server clothing item and and use guid from before - await fileService.SaveImage(editedItem.ClothingItem.ImageGuid, editedItem.ImageBase64); - await clothingItemService.Update(editedItem.ClothingItem); - - return Results.Ok(); - }).RequireAuthorization(); - -app.MapDelete("/clothingitem", async Task (int Id, IClothingItemService clothingItemService, HttpContext context, IUserService userService, DatabaseContext _context) => - { - if (Id == 0) - { - return Results.BadRequest("You must specify a clothing ID"); - } - - string Auth0Id = context.Items["Auth0Id"].ToString() ?? string.Empty; - Debug.Assert(Auth0Id != string.Empty, "Cannot get Auth0Id"); - - var user = await userService.GetUserWithClothingItems(Auth0Id); - Debug.Assert(user != null, "User should not be null"); - - var itemToDelete = user.ServerClothingItems.SingleOrDefault(x => x.Id == Id); - if (itemToDelete == null) { - return Results.NotFound("Could not find item to delete"); - } - - _context.ClothingItems.Remove(itemToDelete); - await _context.SaveChangesAsync(); - - // await clothingItemService.Delete(Id); - return Results.Ok("Deleted"); - - }).RequireAuthorization(); - - -// ---------------------------------- -// Images -// ---------------------------------- - -// No authorization for this for right now since doing does not include the JWT token -app.MapGet("/img/{*imagePath}", async Task (string imagePath, IFileService fileService) => - { - return Results.File(await fileService.GetImage(imagePath)); - - // should implement - //return Results.NotFound("Image not found"); - - }); - - - - app.Run(); diff --git a/WardrobeManager.Api/database.dat-shm b/WardrobeManager.Api/database.dat-shm deleted file mode 100644 index fe9ac28..0000000 Binary files a/WardrobeManager.Api/database.dat-shm and /dev/null differ diff --git a/WardrobeManager.Api/database.dat-wal b/WardrobeManager.Api/database.dat-wal deleted file mode 100644 index e69de29..0000000 diff --git a/WardrobeManager.Presentation/Pages/Authenticated/Clothing.razor b/WardrobeManager.Presentation/Pages/Authenticated/Clothing.razor index d544e81..f14864a 100644 --- a/WardrobeManager.Presentation/Pages/Authenticated/Clothing.razor +++ b/WardrobeManager.Presentation/Pages/Authenticated/Clothing.razor @@ -40,10 +40,10 @@
- - + k diff --git a/WardrobeManager.Shared/Enums/ActionType.cs b/WardrobeManager.Shared/Enums/ActionType.cs new file mode 100644 index 0000000..12ec6ba --- /dev/null +++ b/WardrobeManager.Shared/Enums/ActionType.cs @@ -0,0 +1,6 @@ +namespace WardrobeManager.Shared.Enums; + +public enum ActionType { + Wear, + Wash +} diff --git a/WardrobeManager.Shared/Enums/WearLocation.cs b/WardrobeManager.Shared/Enums/WearLocation.cs new file mode 100644 index 0000000..83b4531 --- /dev/null +++ b/WardrobeManager.Shared/Enums/WearLocation.cs @@ -0,0 +1,5 @@ +public enum WearLocation { + Home, + Outside, + HomeAndOutside +} diff --git a/WardrobeManager.Shared/Exceptions/Exceptions.cs b/WardrobeManager.Shared/Exceptions/Exceptions.cs new file mode 100644 index 0000000..409dbd2 --- /dev/null +++ b/WardrobeManager.Shared/Exceptions/Exceptions.cs @@ -0,0 +1,14 @@ +namespace WardrobeManager.Shared.Exceptions; + +[Serializable] +public class UserNotFoundException : Exception { + public UserNotFoundException() { } + public UserNotFoundException(string message): base(message) { } + public UserNotFoundException(string message, Exception inner): base(message, inner) { } +} +[Serializable] +public class ClothingItemNotFoundException : Exception { + public ClothingItemNotFoundException() { } + public ClothingItemNotFoundException(string message) : base(message) { } + public ClothingItemNotFoundException(string message, Exception inner): base(message, inner) { } +} diff --git a/WardrobeManager.Shared/Exceptions/UserNotFoundException.cs b/WardrobeManager.Shared/Exceptions/UserNotFoundException.cs deleted file mode 100644 index 69e1a9f..0000000 --- a/WardrobeManager.Shared/Exceptions/UserNotFoundException.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace WardrobeManager.Shared.Exceptions; - -[Serializable] -public class UserNotFoundException : Exception { - public UserNotFoundException() { } - - public UserNotFoundException(string message) - : base(message) { } - - public UserNotFoundException(string message, Exception inner) - : base(message, inner) { } -} diff --git a/WardrobeManager.Shared/Models/NewOrEditedClothingItem.cs b/WardrobeManager.Shared/Models/NewOrEditedClothingItem.cs deleted file mode 100644 index 03a2616..0000000 --- a/WardrobeManager.Shared/Models/NewOrEditedClothingItem.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace WardrobeManager.Shared.Models; -public class NewOrEditedClothingItem -{ - public NewOrEditedClothingItem() { } // ONLY FOR DESERIALIZER. THIS SHIT BETTER HAVE NO REFERENCES - - public NewOrEditedClothingItem(ServerClothingItem originalClothingItem, string ImageBase64 = "") - { - this.ClothingItem = originalClothingItem; - this.ImageBase64 = ImageBase64; - } - - - public ServerClothingItem ClothingItem { get; set; } - public string ImageBase64 { get; set; } // if this is empty the server will not update the image guid -} diff --git a/WardrobeManager.Shared/Models/NewOrEditedClothingItemDTO.cs b/WardrobeManager.Shared/Models/NewOrEditedClothingItemDTO.cs new file mode 100644 index 0000000..e110653 --- /dev/null +++ b/WardrobeManager.Shared/Models/NewOrEditedClothingItemDTO.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using WardrobeManager.Shared.Enums; + +namespace WardrobeManager.Shared.Models; + +/// DTO that client sends to create new clothing item +public class NewOrEditedClothingItemDTO +{ + public NewOrEditedClothingItemDTO() { } // ONLY FOR DESERIALIZER. THIS SHIT BETTER HAVE NO REFERENCES + + + public NewOrEditedClothingItemDTO + ( + string name, ClothingCategory category, Season season, bool favourited, + WearLocation wearLocation, int desiredTimesWornBeforeWash, string imageBase64 + ) { + this.Name = name; + this.Category = category; + this.Season = season; + this.Favourited = favourited; + this.WearLocation = wearLocation; + this.DesiredTimesWornBeforeWash = desiredTimesWornBeforeWash; + this.ImageBase64 = imageBase64; + } + + public string Name { get; set; } + public ClothingCategory Category { get; set; } + public Season Season { get; set; } + public bool Favourited { get; set; } + public WearLocation WearLocation { get; set; } + public int DesiredTimesWornBeforeWash { get; set; } + public string ImageBase64 { get; set; } +} diff --git a/WardrobeManager.Shared/Models/ServerClothingItem.cs b/WardrobeManager.Shared/Models/ServerClothingItem.cs index fb975ae..28f1f13 100644 --- a/WardrobeManager.Shared/Models/ServerClothingItem.cs +++ b/WardrobeManager.Shared/Models/ServerClothingItem.cs @@ -13,15 +13,22 @@ public ServerClothingItem() { } // ONLY FOR DESERIALIZER, DO NOT USE THIS. THIS // This constructor will be used to create default objects or initial objects // For now some of the other user editable fields will not be assigned here // In the future you can add this functionality to the frontend and then change this - public ServerClothingItem(string name, ClothingCategory category, Season? season, Guid? imageGuid) - { - this.Name = name; - this.Category = category; - this.Season = season; - this.ImageGuid = imageGuid; - } + public ServerClothingItem + ( + string name, ClothingCategory category, Season season, WearLocation wearLocation, + bool favourited, int desiredTimesWornBeforeWash, Guid? imageGuid + ) + { + this.Name = name; + this.Category = category; + this.Season = season; + this.WearLocation = wearLocation; + this.Favourited = favourited; + this.DesiredTimesWornBeforeWash = desiredTimesWornBeforeWash; + this.ImageGuid = imageGuid; + } - // EF Core modifies + // ---- EF Core modifies ----- public int Id { get; set; } // represents a mandatory one-to-many relationship with a User as @@ -31,11 +38,13 @@ public ServerClothingItem(string name, ClothingCategory category, Season? season public User User { get; set; } // navigation property public int UserId { get; set; } - // User modifies + // ---- User modifies ----- public string Name { get; set; } public ClothingCategory Category { get; set; } - public Season? Season { get; set; } // doesn't have to be set - public Guid? ImageGuid { get; set; } // modified in spirit by the user, they change the image. program assigns guid + public Season Season { get; set; } + public WearLocation WearLocation { get; set; } + // modified in spirit by the user, they change the image. program assigns guid + public Guid? ImageGuid { get; set; } public bool Favourited { get; set; } = false; // # of times the user wants to wear a piece of clothing before washing it @@ -44,12 +53,14 @@ public ServerClothingItem(string name, ClothingCategory category, Season? season public int DesiredTimesWornBeforeWash { get; set; } = 0; - // Only program modifies + // ---- Only program modifies ---- public int TimesWornSinceWash { get; set; } = 0; - public int TimesWornTotal { get; set; } = 0; // initialized at zero since the user can change this later + // initialized at zero since the user can change this later + public int TimesWornTotal { get; set; } = 0; public DateTime LastWorn { get; set; } public DateTime DateAdded { get; set; } = DateTime.UtcNow; - public DateTime DateUpdated { get; set; } = DateTime.UtcNow; // will be updated directly. at init this is fine for default + // will be updated directly. at init this is fine for default + public DateTime DateUpdated { get; set; } = DateTime.UtcNow; public void Wash() { diff --git a/WardrobeManager.Shared/Services/Implementation/SharedService.cs b/WardrobeManager.Shared/Services/Implementation/SharedService.cs index 6f20d8f..60f7922 100644 --- a/WardrobeManager.Shared/Services/Implementation/SharedService.cs +++ b/WardrobeManager.Shared/Services/Implementation/SharedService.cs @@ -21,7 +21,7 @@ public bool IsValid(ServerClothingItem item) return true; } - private bool IsValidBase64(string input) + public bool IsValidBase64(string input) { try { @@ -37,12 +37,7 @@ private bool IsValidBase64(string input) public ServerClothingItem CreateDefaultServerClothingItem() { - return new ServerClothingItem - ( - name: "Default Name", - ClothingCategory.TShirt, - Season.Fall, - null - ); + return new ServerClothingItem("Example T-Shirt", ClothingCategory.TShirt, Season.Fall, WearLocation.HomeAndOutside, false, 5, null); + } } diff --git a/WardrobeManager.Shared/Services/Interfaces/ISharedService.cs b/WardrobeManager.Shared/Services/Interfaces/ISharedService.cs index d627dfd..28d975f 100644 --- a/WardrobeManager.Shared/Services/Interfaces/ISharedService.cs +++ b/WardrobeManager.Shared/Services/Interfaces/ISharedService.cs @@ -5,5 +5,6 @@ public interface ISharedService { bool IsValid(ServerClothingItem item); ServerClothingItem CreateDefaultServerClothingItem(); + public bool IsValidBase64(string input); -} \ No newline at end of file +}