103 lines
3.2 KiB
C#
103 lines
3.2 KiB
C#
|
|
using JobsMedical.Web.Data;
|
||
|
|
using JobsMedical.Web.Models;
|
||
|
|
using Microsoft.EntityFrameworkCore;
|
||
|
|
|
||
|
|
namespace JobsMedical.Web.Services;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Persists visitor preferences and behavioral events. Creates the <see cref="Visitor"/> row
|
||
|
|
/// lazily on first write, so anonymous browsing doesn't hit the DB on every request.
|
||
|
|
/// </summary>
|
||
|
|
public class InterestService
|
||
|
|
{
|
||
|
|
private readonly AppDbContext _db;
|
||
|
|
private readonly VisitorContext _visitor;
|
||
|
|
|
||
|
|
public InterestService(AppDbContext db, VisitorContext visitor)
|
||
|
|
{
|
||
|
|
_db = db;
|
||
|
|
_visitor = visitor;
|
||
|
|
}
|
||
|
|
|
||
|
|
public string VisitorId => _visitor.VisitorId;
|
||
|
|
|
||
|
|
private async Task EnsureVisitorAsync()
|
||
|
|
{
|
||
|
|
var id = VisitorId;
|
||
|
|
if (string.IsNullOrEmpty(id)) return;
|
||
|
|
var exists = await _db.Visitors.AnyAsync(v => v.Id == id);
|
||
|
|
if (!exists)
|
||
|
|
{
|
||
|
|
_db.Visitors.Add(new Visitor { Id = id });
|
||
|
|
await _db.SaveChangesAsync();
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
await _db.Visitors.Where(v => v.Id == id)
|
||
|
|
.ExecuteUpdateAsync(s => s.SetProperty(v => v.LastSeenAt, DateTime.UtcNow));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task LogAsync(InterestEventType type, int shiftId)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrEmpty(VisitorId)) return;
|
||
|
|
await EnsureVisitorAsync();
|
||
|
|
_db.InterestEvents.Add(new InterestEvent
|
||
|
|
{
|
||
|
|
VisitorId = VisitorId,
|
||
|
|
ShiftId = shiftId,
|
||
|
|
EventType = type,
|
||
|
|
});
|
||
|
|
await _db.SaveChangesAsync();
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task LogJobAsync(InterestEventType type, int jobOpeningId)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrEmpty(VisitorId)) return;
|
||
|
|
await EnsureVisitorAsync();
|
||
|
|
_db.InterestEvents.Add(new InterestEvent
|
||
|
|
{
|
||
|
|
VisitorId = VisitorId,
|
||
|
|
JobOpeningId = jobOpeningId,
|
||
|
|
EventType = type,
|
||
|
|
});
|
||
|
|
await _db.SaveChangesAsync();
|
||
|
|
}
|
||
|
|
|
||
|
|
public Task<UserPreferences?> GetPreferencesAsync()
|
||
|
|
{
|
||
|
|
var id = VisitorId;
|
||
|
|
if (string.IsNullOrEmpty(id)) return Task.FromResult<UserPreferences?>(null);
|
||
|
|
return _db.UserPreferences.AsNoTracking().FirstOrDefaultAsync(p => p.VisitorId == id);
|
||
|
|
}
|
||
|
|
|
||
|
|
public async Task SavePreferencesAsync(int? roleId, int? cityId, ShiftType? shiftType, long? minPay)
|
||
|
|
{
|
||
|
|
await EnsureVisitorAsync();
|
||
|
|
var prefs = await _db.UserPreferences.FirstOrDefaultAsync(p => p.VisitorId == VisitorId);
|
||
|
|
if (prefs is null)
|
||
|
|
{
|
||
|
|
prefs = new UserPreferences { VisitorId = VisitorId };
|
||
|
|
_db.UserPreferences.Add(prefs);
|
||
|
|
}
|
||
|
|
prefs.RoleId = roleId;
|
||
|
|
prefs.CityId = cityId;
|
||
|
|
prefs.PreferredShiftType = shiftType;
|
||
|
|
prefs.MinPay = minPay;
|
||
|
|
prefs.UpdatedAt = DateTime.UtcNow;
|
||
|
|
await _db.SaveChangesAsync();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>Recent events for this visitor (newest first) — the behavioral signal.</summary>
|
||
|
|
public Task<List<InterestEvent>> RecentEventsAsync(int take = 100)
|
||
|
|
{
|
||
|
|
var id = VisitorId;
|
||
|
|
if (string.IsNullOrEmpty(id)) return Task.FromResult(new List<InterestEvent>());
|
||
|
|
return _db.InterestEvents.AsNoTracking()
|
||
|
|
.Where(e => e.VisitorId == id)
|
||
|
|
.OrderByDescending(e => e.CreatedAt)
|
||
|
|
.Take(take)
|
||
|
|
.ToListAsync();
|
||
|
|
}
|
||
|
|
}
|