using JobsMedical.Web.Data;
using JobsMedical.Web.Models;
using Microsoft.EntityFrameworkCore;
namespace JobsMedical.Web.Services;
///
/// Persists visitor preferences and behavioral events. Creates the row
/// lazily on first write, so anonymous browsing doesn't hit the DB on every request.
///
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 GetPreferencesAsync()
{
var id = VisitorId;
if (string.IsNullOrEmpty(id)) return Task.FromResult(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();
}
/// Recent events for this visitor (newest first) — the behavioral signal.
public Task> RecentEventsAsync(int take = 100)
{
var id = VisitorId;
if (string.IsNullOrEmpty(id)) return Task.FromResult(new List());
return _db.InterestEvents.AsNoTracking()
.Where(e => e.VisitorId == id)
.OrderByDescending(e => e.CreatedAt)
.Take(take)
.ToListAsync();
}
}