Changes between Initial Version and Version 1 of UseCaseImplementationsFinal


Ignore:
Timestamp:
12/13/25 19:08:33 (2 days ago)
Author:
231091
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • UseCaseImplementationsFinal

    v1 v1  
     1== ОAuth автентикација
     2Корисникот кликнува на "Sign in with Google" или "Sign in with Facebook" копчето на Login страната, што активира OnPost() метод со provider параметар (Google/Facebook).
     3
     4_signInManager.ConfigureExternalAuthenticationProperties() креира AuthenticationProperties објект со redirectUrl до "./ExternalLogin" Callback handler-от и враќа ChallengeResult(provider, properties).
     5
     6OnGetCallbackAsync() го повикува _signInManager.GetExternalLoginInfoAsync() за да ги добие ClaimTypes.Email и ClaimTypes.Name од OAuth provider-от преку FindFirstValue().
     7-       Ако корисникот веќе има поврзан external login, _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey) го најавува директно и прави LocalRedirect(returnUrl).
     8-       Ако корисникот постои со истиот email но без поврзан login, _userManager.AddLoginAsync(existingUser, info) го поврзува OAuth налогот со постоечкиот User запис.
     9-       Ако корисникот не постои, CreateUser() креира нов User instance, email.Split('@')[0] го генерира почетниот username, а while loop со _userManager.FindByNameAsync() проверува уникатност зголемувајќи counter додека не најде слободен username.
     10
     11
     12== Нотификации
     13Системот креира нотификација преку CreateNotificationAsync(userId, type, message, recipeId).
     14
     15_userManager.FindByIdAsync(userId) го наоѓа User објектот, а потоа проверува со type switch { "RecipeRated" => user.NotifyRecipeRated, "RecipeAccepted" => user.NotifyRecipeAccepted... } дали корисникот има овозможено нотификации од  тој тип.
     16
     17Ако shouldNotify е false, методот враќа false без да креира нотификација. Ако е true, се креира нов Notification објект со UserId, Type, Message, RecipeId, IsRead = false и CreatedAt = DateTime.UtcNow.
     18
     19_context.Notifications.Add(notification) го додава записот, а await _context.SaveChangesAsync() го зачувува во базата.
     20
     21/Notifications/Stream endpoint користи Response.Headers.Add("Content-Type", "text/event-stream") за Server-Sent Events (SSE) за real-time updates.
     22while (!HttpContext.RequestAborted.IsCancellationRequested) непрекинато врти, _context.Notifications.Count(n => n.UserId == userId && !n.IsRead) го пресметува unreadCount секои 3000ms преку await Task.Delay(3000).
     23
     24Кога unreadCount != lastUnreadCount, .OrderByDescending(n => n.CreatedAt).FirstOrDefault() ја зема најновата нотификација, System.Text.Json.JsonSerializer.Serialize(payload) ја конвертира во JSON.
     25await Response.WriteAsync($"data: {json}\n\n") го испраќа SSE форматираниот одговор, а await Response.Body.FlushAsync() осигурува дека податоците се веднаш испратени до клиентот.
     26
     27GetNotifications() ги враќа последните нотификации со .Take() и unreadCount преку Json(new { notifications, unreadCount }) response.
     28
     29MarkAsRead(notificationId) го сетира notification.IsRead = true и зачувува со _context.SaveChanges(), додека MarkAllAsRead() користи .Where(n => n.UserId == userId && !n.IsRead).ToList() и foreach loop за bulk update.
     30
     31Delete(notificationId) користи _context.Notifications.Remove(notification) за бришење, а DeleteAll() користи .RemoveRange(userNotifications) за да ги избриши сите нотификации на корисникот.
     32
     33NotificationPanel() враќа PartialView("_NotificationPanel", notifications) кој динамички генерира HTML со @foreach (var notification in Model) loop, каде GetTimeAgo(notification.CreatedAt) ја пресметува релативната временска разлика со timeSpan.TotalMinutes, timeSpan.TotalHours и timeSpan.TotalDays.
     34JavaScript код повикува fetch('/Notifications/GetNotifications') за да ги вчита нотификациите, додека EventSource('/Notifications/Stream') креира SSE connection за real-time updates со onmessage event handler.
     35
     36
     37== CRUD за Restaurant Meal
     38Администраторот кликнува на "Manage Restaurant Meals" копчето што го отвора modal-от со id="restaurantMealsModal", каде loadRestaurantMeals() прави GET барање до /Restaurants/Index.
     39
     40restaurantSelect.addEventListener("change") го детектира избраниот ресторан, а loadRestaurantMeals(restaurantId) прави fetch(/Restaurants/GetRestaurantMeals/${restaurantId}) за да ги добие сите оброци.
     41
     42showAddMealForm() го прикажува addMealFormSection div-от со style.display = "block", каде администраторот пополнува input полиња за mealItemName, mealItemDescription, calories, protein, carbs, fat .
     43addRestaurantMeal() валидира дека document.querySelectorAll('input[name="mealType"]:checked').length > 0, креира RestaurantMeal објект со selectedTypes = Array.from(checkedBoxes).map(cb => cb.value).
     44fetch('/Restaurants/AddRestaurantMeal', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(meal) }) го испраќа POST барањето.
     45
     46Контролерот AddRestaurantMeal([FromBody] RestaurantMeal meal) валидира IsNullOrWhiteSpace(meal.ItemName), проверува meal.RestaurantId != null && meal.RestaurantId != 0, и користи await _context.Restaurants.FindAsync(meal.RestaurantId) за да го најде ресторанот.
     47meal.RestaurantName = restaurant.Name го сетира името, _context.RestaurantMeals.Add(meal) го додава записот, а await _context.SaveChangesAsync() го зачувува.
     48
     49CreateMealNotifications(meal, restaurant) креира нотификации за сите followers на ресторанот преку _context.RestaurantFollowings.Include(f => f.User).Where(f => f.RestaurantId == meal.RestaurantId && f.User.NotifyRestaurantNewMeal).
     50
     51editRestaurantMeal() прикажува populated form со постоечки вредности, а EditRestaurantMeal([FromBody] RestaurantMeal meal) ги ажурира existing.ItemName, existing.Type, existing.Calories преку _context.RestaurantMeals.FindAsync(meal.Id).
     52
     53deleteRestaurantMeal(mealId) прикажува confirmation dialog, а DeleteRestaurantMeal(int id) користи await _mealPlanService.HandleDeletedRestaurantMealAsync(id) за да ги отстрани reference-ите од meal plans пред да направи _context.RestaurantMeals.Remove(meal).
     54
     55
     56== CRUD за Restaurant
     57Администраторот кликнува "Add New Restaurant" што го активира showAddRestaurantForm() кој сетира addRestaurantFormSection.style.display = "block" и restaurantFormTitle.textContent = "Add New Restaurant".
     58
     59previewImage() користи FileReader() API со reader.readAsDataURL(file) за да креира preview на избраната слика, а reader.onload = (e) => { previewImg.src = e.target.result } ја прикажува сликата во imagePreview div-от.
     60
     61addRestaurant() валидира дека restaurantName.value.trim() !== "" и restaurantImage.files.length > 0, креира FormData објект со formData.append('name', name), formData.append('description', description), formData.append('image', imageFile).
     62fetch('/Restaurants/AddRestaurant', { method: 'POST', body: formData }) го испраќа multipart/form-data барањето.
     63
     64AddRestaurant([FromForm] string name, [FromForm] string description, [FromForm] IFormFile image) ги bind-ува параметрите, Path.GetExtension(image.FileName).ToLowerInvariant() го проверува extension-от.
     65await _context.Users.Where(u => u.NotifyNewRestaurant).ToListAsync() ги добива сите корисници со овозможени нотификации за нови ресторани.
     66foreach (var user in users) loop креира Notification со Type = "NewRestaurant", Message = "New restaurant added: " + restaurant.Name, RecipeId = restaurant.Id.
     67
     68EditRestaurant([FromForm] int id, [FromForm] IFormFile image) проверува дали image != null && image.Length > 0, и ако е така, System.IO.File.Exists(oldImagePath) проверува за стара слика, System.IO.File.Delete(oldImagePath) ја брише старата слика пред да ја зачува новата.
     69
     70showEditRestaurantForm() ја популира формата со restaurant.Name, restaurant.Description и currentImage.src = restaurant.ImageUrl, а editRestaurantId.value = restaurant.Id го сетира hidden input-от за идентификација.
     71
     72DeleteRestaurant(int id) користи .Include(r => r.RestaurantMeals).Include(r => r.Followers) за eager loading, _context.RestaurantFollowings.RemoveRange(restaurant.Followers) ги брише followers.
     73
     74foreach (var meal in restaurant.RestaurantMeals) await _mealPlanService.HandleDeletedRestaurantMealAsync(meal.Id) ги отстранува meal references од сите meal plans.
     75
     76_context.RestaurantMeals.RemoveRange(restaurant.RestaurantMeals) ги брише сите оброци, System.IO.File.Delete(imagePath) ја брише сликата од wwwroot/images, а _context.Restaurants.Remove(restaurant) го брише ресторанот.
     77
     78
     79
     80
     81== CRUD за Meal Keyword
     82Администраторот го отвора "Manage Meal Tags" modal-от со id="mealTagsModal", каде loadMealKeywords() прави fetch('/Admin/GetMealKeywords') за да ги добие сите постоечки keywords.
     83
     84response.json() го парсира одговорот, а keywords.forEach(keyword => { ... }) динамички креира div.keyword-card елементи со innerHTML што содржи keyword.Name, keyword.Tag badge и delete копче.
     85
     86newKeywordName input field и newKeywordTag select dropdown овозможуваат внесување на нов keyword со тип (breakfast, main, snack).
     87
     88addMealKeyword() креира објект { Name: name, Tag: tag }, и прави fetch('/Admin/AddMealKeyword', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(keyword) }).
     89
     90Контролерот AddMealKeyword([FromBody] MealKeyword keyword) проверува дали keyword.Tag е валиден тип (breakfast, main, snack).
     91_context.MealKeywords.Add(keyword) го додава записот, await _context.SaveChangesAsync() го зачувува, а return Json(new { success = true, keyword }) го враќа креираниот keyword со неговиот ID.
     92
     93JavaScript then(response => response.json()) го добива одговорот, createKeywordCard(data.keyword) динамички креира нов card element, mealKeywordsList.insertBefore(card, mealKeywordsList.firstChild) го додава на врвот на листата.
     94newKeywordName.value = "" и newKeywordTag.selectedIndex = 0 ги ресетираат input полињата.
     95
     96deleteMealKeyword(keywordId) прикажува confirmation dialog со confirm('Are you sure you want to delete this keyword?'), а fetch(/Admin/DeleteMealKeyword/${keywordId}, { method: 'DELETE' }) го испраќа DELETE барањето.
     97
     98DeleteMealKeyword(int id) користи await _context.MealKeywords.FindAsync(id) за да го најде keyword-от, _context.MealKeywords.Remove(keyword) го брише записот.
     99
     100JavaScript then() handler користи document.querySelector([data-keyword-id="${keywordId}"]) за да го најде card-от, card.remove() го отстранува од DOM.
     101
     102loadMealKeywords() се повикува при отварање на modal-от преку $('#mealTagsModal').on('show.bs.modal', loadMealKeywords) jQuery event listener.
     103
     104