Skip to content

Commit

Permalink
created basic recomentation algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
bilimig committed Jun 22, 2024
1 parent 1cd49c1 commit 553f2d6
Show file tree
Hide file tree
Showing 9 changed files with 440 additions and 33 deletions.
12 changes: 12 additions & 0 deletions Database/init-db.sql
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,18 @@ CREATE TABLE IF NOT EXISTS users.interest (
"name" text NOT NULL CONSTRAINT users_interest_name_maxlength CHECK (LENGTH("name") <= 32) UNIQUE
);

CREATE TABLE IF NOT EXISTS common.related (
"tag_name" text,
"interest_name" text,
"value" decimal,
UNIQUE ("tag_name", "interest_name")
);

CREATE TABLE IF NOT EXISTS common.translated (
"name_pl" text,
"name_ang" text
);

ALTER TABLE events.event ADD FOREIGN KEY ("address_id") REFERENCES common.address ("id");

ALTER TABLE events.comment ADD FOREIGN KEY ("event_id") REFERENCES events.event ("id");
Expand Down
36 changes: 35 additions & 1 deletion Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,49 @@
using Microsoft.AspNetCore.Mvc;
using ReasnAPI.Services;
using System.Net.Http;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

Check warning on line 8 in Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs

View workflow job for this annotation

GitHub Actions / dotnet-tests (ubuntu-latest)

The using directive for 'Microsoft.AspNetCore.Mvc' appeared previously in this namespace

Check warning on line 8 in Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs

View workflow job for this annotation

GitHub Actions / dotnet-tests (macos-latest)

The using directive for 'Microsoft.AspNetCore.Mvc' appeared previously in this namespace
using ReasnAPI.Models.Database;


namespace ReasnAPI.Controllers;

[ApiController]
[Route("[controller]")]
public class UsersController : ControllerBase
{
private readonly RecomendationService _recomendationService;
private readonly UserService _userService;
private readonly HttpClient _httpClient;

Check warning on line 20 in Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs

View workflow job for this annotation

GitHub Actions / dotnet-tests (ubuntu-latest)

Remove this unread private field '_httpClient' or refactor the code to use its value. (https://rules.sonarsource.com/csharp/RSPEC-4487)

Check warning on line 20 in Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs

View workflow job for this annotation

GitHub Actions / dotnet-tests (macos-latest)

Remove this unread private field '_httpClient' or refactor the code to use its value. (https://rules.sonarsource.com/csharp/RSPEC-4487)

public UsersController(RecomendationService recomendationService, UserService userService, HttpClient httpClient)
{
_recomendationService = recomendationService;
_userService = userService;
_httpClient = httpClient;
}

[HttpGet]
[Route("{username}")]
public IActionResult GetUserByUsername(string username)

Check warning on line 31 in Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs

View workflow job for this annotation

GitHub Actions / dotnet-tests (ubuntu-latest)

Annotate this method with ProducesResponseType containing the return type for successful responses. (https://rules.sonarsource.com/csharp/RSPEC-6968)

Check warning on line 31 in Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs

View workflow job for this annotation

GitHub Actions / dotnet-tests (macos-latest)

Annotate this method with ProducesResponseType containing the return type for successful responses. (https://rules.sonarsource.com/csharp/RSPEC-6968)
{
throw new NotImplementedException();
var user =_userService.GetUserByUsername(username);
return Ok(user);
}

[HttpGet]
[Route("{username}/recomendetevents")]
public async Task<IActionResult> GetRecomendetEvents(string username)
{
var currentUser = _userService.GetUserByUsername(username);
var interests = currentUser.Interests;

// Poprawka: Dodanie await i poprawienie b³êdu w nazwie metody
var events = await _recomendationService.GetEventsByInterest(interests);

Check warning on line 45 in Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs

View workflow job for this annotation

GitHub Actions / dotnet-tests (ubuntu-latest)

Possible null reference argument for parameter 'interestsDto' in 'Task<List<EventDto>> RecomendationService.GetEventsByInterest(List<UserInterestDto> interestsDto)'.

Check warning on line 45 in Server/ReasnAPI/ReasnAPI/Controllers/UsersController.cs

View workflow job for this annotation

GitHub Actions / dotnet-tests (macos-latest)

Possible null reference argument for parameter 'interestsDto' in 'Task<List<EventDto>> RecomendationService.GetEventsByInterest(List<UserInterestDto> interestsDto)'.

return Ok(events);
}
}
5 changes: 3 additions & 2 deletions Server/ReasnAPI/ReasnAPI/Mappers/UserInterestMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ public static List<UserInterestDto> ToDtoList(this IEnumerable<UserInterest> use
return userInterests.Select(ToDto).ToList();
}

public static UserInterest ToEntity(this UserInterestDto userInterestDto)
public static UserInterest ToEntity(this UserInterestDto userInterestDto, int userId, int interestId)
{
return new UserInterest
{
Interest = userInterestDto.Interest.ToEntity(),
UserId = userId,
InterestId = interestId,
Level = userInterestDto.Level
};
}
Expand Down
1 change: 0 additions & 1 deletion Server/ReasnAPI/ReasnAPI/Services/EventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
namespace ReasnAPI.Services;
public class EventService(ReasnContext context, ParameterService parameterService, TagService tagService, CommentService commentService)
{

public EventDto CreateEvent(EventDto eventDto)
{
using (var scope = new TransactionScope())
Expand Down
62 changes: 62 additions & 0 deletions Server/ReasnAPI/ReasnAPI/Services/RecomendationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using ReasnAPI.Models.Database;
using ReasnAPI.Models.DTOs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using ReasnAPI.Mappers;

namespace ReasnAPI.Services
{
public class RecomendationService
{
private readonly HttpClient httpClient;
private readonly ReasnContext context;
private readonly string flaskApiUrl;

public RecomendationService(HttpClient httpClient, ReasnContext context, IConfiguration configuration)
{
this.httpClient = httpClient;
this.context = context;
this.flaskApiUrl = $"{configuration.GetValue<string>("FlaskApi:BaseUrl")}/similar-tags";
}

public async Task<List<EventDto>> GetEventsByInterest(List<UserInterestDto> interestsDto)
{
var interests = interestsDto.Select(i => i.Interest.Name).ToList();

try
{
var response = await httpClient.PostAsJsonAsync(flaskApiUrl, interests);

if (!response.IsSuccessStatusCode)
{
throw new HttpRequestException(
$"Error fetching tags from Flask API. Status code: {response.StatusCode}");
}

var tagNames = await response.Content.ReadFromJsonAsync<List<string>>();

if (tagNames == null || tagNames.Count == 0)
{
return new List<EventDto>();
}

var events = await context.Events
.Include(e => e.Tags).Include(e => e.Parameters)
.Where(e => e.Tags.Any(t => tagNames.Contains(t.Name)))
.ToListAsync();

return events.ToDtoList();
}
catch (Exception ex)
{
Console.WriteLine($"Exception occurred while fetching events: {ex.Message}");
throw;
}
}
}
}
108 changes: 79 additions & 29 deletions Server/ReasnAPI/ReasnAPI/Services/UserService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,81 +3,115 @@
using ReasnAPI.Mappers;
using ReasnAPI.Models.Database;
using ReasnAPI.Models.DTOs;
using Serilog;
using System.Linq.Expressions;
using System.Security.Claims;
using System.Transactions;

namespace ReasnAPI.Services;

public class UserService(ReasnContext context)
public class UserService
{
public UserDto UpdateUser(int userId, UserDto userDto)
private readonly ReasnContext _context;
private readonly IHttpContextAccessor _httpContextAccessor;

public UserService(ReasnContext context)

Check warning on line 18 in Server/ReasnAPI/ReasnAPI/Services/UserService.cs

View workflow job for this annotation

GitHub Actions / dotnet-tests (ubuntu-latest)

Non-nullable field '_httpContextAccessor' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.

Check warning on line 18 in Server/ReasnAPI/ReasnAPI/Services/UserService.cs

View workflow job for this annotation

GitHub Actions / dotnet-tests (macos-latest)

Non-nullable field '_httpContextAccessor' must contain a non-null value when exiting constructor. Consider declaring the field as nullable.
{
_context = context;
}

public UserService(ReasnContext context, IHttpContextAccessor httpContextAccessor)
{
_context = context;
_httpContextAccessor = httpContextAccessor;
}

public User GetCurrentUser()
{
var httpContext = _httpContextAccessor.HttpContext;

if (httpContext is null)
{
throw new InvalidOperationException("No HTTP context available");
}

var email = httpContext.User.FindFirstValue(ClaimTypes.Email);
if (string.IsNullOrEmpty(email))
{
throw new UnauthorizedAccessException("No email claim found in token");
}

var user = _context.Users.FirstOrDefault(u => u.Email == email);

if (user is null)
{
throw new NotFoundException("User associated with email not found");
}

return user;
}

public UserDto UpdateUser(string username, UserDto userDto)
{
using (var scope = new TransactionScope())
{
ArgumentNullException.ThrowIfNull(userDto);

var user = context.Users
var user = _context.Users
.Include(u => u.UserInterests)
.ThenInclude(ui => ui.Interest)
.FirstOrDefault(r => r.Id == userId);
.FirstOrDefault(r => r.Username == username);

if (user is null)
{
throw new NotFoundException("User not found");
}

var usernameExists = context.Users.Any(r => r.Username == userDto.Username && r.Id != userId);
var usernameExists = _context.Users.Any(r => r.Username == userDto.Username && r.Id != user.Id);

if (usernameExists)
{
throw new BadRequestException("User with given username already exists");
}

var emailExists = context.Users.Any(r => r.Email == userDto.Email && r.Id != userId);
var emailExists = _context.Users.Any(r => r.Email == userDto.Email && r.Id != user.Id);

if (emailExists)
{
throw new BadRequestException("User with given email already exists");
}

var phoneExists = context.Users.Any(r => r.Phone == userDto.Phone && r.Id != userId);
var phoneExists = _context.Users.Any(r => r.Phone == userDto.Phone && r.Id != user.Id);

if (phoneExists)
{
throw new BadRequestException("User with given phone number already exists");
}

user.Username = userDto.Username;
user.Name = userDto.Name;
user.Surname = userDto.Surname;
user.Username = userDto.Username;
user.Email = userDto.Email;
user.Phone = userDto.Phone;
user.Role = userDto.Role;
user.AddressId = userDto.AddressId;
user.UpdatedAt = DateTime.UtcNow;

context.Users.Update(user);
_context.Users.Update(user);

// Get list of interests to remove
var interestsToRemove = user.UserInterests
.Where(ui => !userDto.Interests!.Exists(uid => uid.Interest.Name == ui.Interest.Name));

_context.UserInterests.RemoveRange(interestsToRemove);

if (userDto.Interests is null || userDto.Interests.Count == 0)
{
context.SaveChanges();
_context.SaveChanges();
scope.Complete();
return userDto;
}

var interestsToRemove = user.UserInterests
.Where(ui => !userDto.Interests.Exists(uid => uid.Interest.Name == ui.Interest.Name));

context.UserInterests.RemoveRange(interestsToRemove);

var interestsToAdd = userDto.Interests
.Where(uid => !user.UserInterests.Any(ui => ui.Interest.Name == uid.Interest.Name))
.Select(uid => uid.ToEntity())
.ToList();

context.UserInterests.AddRange(interestsToAdd);

// Get list of interests to update
var interestsToUpdate = user.UserInterests
.Where(ui => userDto.Interests.Exists(uid => uid.Interest.Name == ui.Interest.Name))
.ToList();
Expand All @@ -92,10 +126,24 @@ public UserDto UpdateUser(int userId, UserDto userDto)
}

interest.Level = updatedInterest.Level;
context.UserInterests.Update(interest);
_context.UserInterests.Update(interest);
}

context.SaveChanges();
// Get list of existing interests in the database
var existingInterests = _context.Interests.ToList();

// Get list of interests to add
// Look for interests that are not already in the user's interests
var interestsToAdd = userDto.Interests
.Where(uid => !user.UserInterests.Any(ui => ui.Interest.Name == uid.Interest.Name))
.Select(uid => uid.ToEntity(user.Id, existingInterests.Find(ei => ei.Name == uid.Interest.Name)!.Id))
.ToList();

// Update interests for
interestsToAdd.ForEach(user.UserInterests.Add);
_context.Users.Update(user);

_context.SaveChanges();
scope.Complete();
}

Expand All @@ -104,8 +152,9 @@ public UserDto UpdateUser(int userId, UserDto userDto)

public UserDto GetUserById(int userId)
{
var user = context.Users
var user = _context.Users
.Include(u => u.UserInterests)
.ThenInclude(ui => ui.Interest)
.FirstOrDefault(u => u.Id == userId);

if (user is null)
Expand All @@ -118,8 +167,9 @@ public UserDto GetUserById(int userId)

public UserDto GetUserByUsername(string username)
{
var user = context.Users
var user = _context.Users
.Include(u => u.UserInterests)
.ThenInclude(ui => ui.Interest)
.FirstOrDefault(u => u.Username == username);

if (user is null)
Expand All @@ -132,7 +182,7 @@ public UserDto GetUserByUsername(string username)

public IEnumerable<UserDto> GetUsersByFilter(Expression<Func<User, bool>> filter)
{
return context.Users
return _context.Users
.Include(u => u.UserInterests)
.ThenInclude(ui => ui.Interest)
.Where(filter)
Expand All @@ -142,7 +192,7 @@ public IEnumerable<UserDto> GetUsersByFilter(Expression<Func<User, bool>> filter

public IEnumerable<UserDto> GetAllUsers()
{
var users = context.Users
var users = _context.Users
.Include(u => u.UserInterests)
.ThenInclude(ui => ui.Interest)
.ToList();
Expand Down
Loading

0 comments on commit 553f2d6

Please sign in to comment.