5d711bd13e
Implements the facility-classes-booking-dashboard plan (backend): - Facility.BookingMode (single Scheduled/AdHoc choice) replaced with independent SupportsClasses/SupportsBooking flags, so one facility can run a class and accept walk-in bookings; a running class now blocks walk-in booking for its window on both create and reschedule. - CustomerPlan.GrantsGymAccess + CustomerPlanClass join table let a customer hold one gym-access plan plus any number of class-only plans. Replaced the old single-subscription resolution (GetActiveSubscriptionByCustomerId/HasAnyActiveSubscriptionAsync) with gym-access-aware and class-aware resolvers across booking creation/cancellation, plan assignment, and customer self-service. Fixed ChangeMembershipPlan/renewal plan-changes, which resolved "the subscription to replace" by most-recent-created and would have deactivated a member's class subscription instead of their gym one. - New FacilityClass model: recurring class definitions (Daily/Weekly/ Monthly/Custom) with lazy occurrence generation (8-week horizon, race-safe via a unique index) and proper unlimited-capacity handling. "Mark not held" replaces the old bare-cancel: cascades to cancel and refund every confirmed booking on the occurrence through the same core CancelBooking uses, and frees the slot for walk-in booking. Scheduling conflicts (a walk-in booking now overlapping a class) are surfaced for staff to resolve via the existing Reschedule flow. - Renamed the pre-existing "AdHoc" booking-mode terminology to "WalkIn" throughout (routes, methods, DTOs, the stored BookingMode value) for a clearer, simpler name. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
303 lines
14 KiB
C#
303 lines
14 KiB
C#
using Fitness_core_BE;
|
|
using Fitness_core_BE.AI;
|
|
using Fitness_core_BE.Auth;
|
|
using Fitness_core_BE.Helpers;
|
|
using Fitness_core_BE.Hubs;
|
|
using Fitness_core_BE.Infra.UoW;
|
|
using Fitness_core_BE.Interfaces;
|
|
using Fitness_core_BE.Interfaces.Services;
|
|
using Fitness_core_BE.Repos;
|
|
using Fitness_core_BE.Services;
|
|
using Fitness_core_BE.Services.ActivityLog;
|
|
using Fitness_core_BE.Services.Auth;
|
|
using Fitness_core_BE.Services.Chat;
|
|
using Fitness_core_BE.Services.ClientInvoice;
|
|
using Fitness_core_BE.Services.CustomerService;
|
|
using Fitness_core_BE.Services.CustomerInvoice;
|
|
using Fitness_core_BE.Services.Payment;
|
|
using Fitness_core_BE.Services.CustomerPlanService;
|
|
using Fitness_core_BE.Services.CustomerSubscription;
|
|
using Fitness_core_BE.Services.Document;
|
|
using Fitness_core_BE.Services.Feed;
|
|
using Fitness_core_BE.Services.FunctionHandler;
|
|
using Fitness_core_BE.Services.Meal;
|
|
using Fitness_core_BE.Services.Staff;
|
|
using Fitness_core_BE.Services.Ticket;
|
|
using Fitness_core_BE.Services.Workout;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
|
|
QuestPDF.Settings.License = QuestPDF.Infrastructure.LicenseType.Community;
|
|
|
|
// Add services to the container.
|
|
|
|
builder.Services.AddControllers()
|
|
.AddJsonOptions(options =>
|
|
{
|
|
options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
|
|
});
|
|
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
|
builder.Services.AddEndpointsApiExplorer();
|
|
builder.Services.AddSwaggerGen(options =>
|
|
{
|
|
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
|
|
{
|
|
Title = "Fitness Core Backend API",
|
|
Version = "v1",
|
|
Description = "API for managing fitness-related operations including ticket management system",
|
|
Contact = new Microsoft.OpenApi.Models.OpenApiContact
|
|
{
|
|
Name = "Development Team",
|
|
Email = "dev@fitness-core.com"
|
|
}
|
|
});
|
|
|
|
// Enable XML comments if available
|
|
var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
|
|
var xmlPath = System.IO.Path.Combine(AppContext.BaseDirectory, xmlFile);
|
|
if (System.IO.File.Exists(xmlPath))
|
|
{
|
|
options.IncludeXmlComments(xmlPath);
|
|
}
|
|
});
|
|
|
|
|
|
|
|
// AppDBContext is kept as inert legacy code (not deleted, per the non-destructive migration
|
|
// principle) but is intentionally left unregistered -- CentralDbContext/TenantDbContext below
|
|
// are what every repo/service now actually uses.
|
|
|
|
builder.Services.AddHttpContextAccessor();
|
|
|
|
// Single club, single database -- both contexts share one connection string. (GYM's split
|
|
// used CentralDbContext/TenantDbContext to route each request to one of many per-client MySQL
|
|
// schemas; Vijaya has exactly one club, so that routing layer is gone and both contexts just
|
|
// point at the same DB. The two C# context classes stay separate to avoid a much larger,
|
|
// riskier refactor across every repo that injects one or the other.)
|
|
builder.Services.AddDbContext<CentralDbContext>(options =>
|
|
{
|
|
options.UseMySql(
|
|
builder.Configuration.GetConnectionString("DefaultConnection"),
|
|
// Keep startup and EF tooling independent of database network availability.
|
|
// This is the minimum supported MySQL baseline; Pomelo adjusts generated SQL.
|
|
new MySqlServerVersion(new Version(8, 0, 0))
|
|
);
|
|
});
|
|
|
|
builder.Services.AddDbContext<TenantDbContext>(options =>
|
|
{
|
|
options.UseMySql(
|
|
builder.Configuration.GetConnectionString("DefaultConnection"),
|
|
new MySqlServerVersion(new Version(8, 0, 0))
|
|
);
|
|
});
|
|
|
|
builder.Services.AddCors(o =>
|
|
o.AddPolicy("AllowAll", p =>
|
|
p.SetIsOriginAllowed(origin => true)
|
|
.AllowAnyMethod()
|
|
.AllowAnyHeader()
|
|
.AllowCredentials()
|
|
)
|
|
);
|
|
|
|
|
|
builder.Services.AddHttpClient();
|
|
|
|
builder.Services.AddScoped<IUnitOfWork, EFUnitOfWork>();
|
|
|
|
builder.Services.AddSignalR();
|
|
|
|
builder.Services.AddHttpContextAccessor();
|
|
|
|
// Register Repositories
|
|
builder.Services.AddScoped<ITicketRepository, TicketRepository>();
|
|
builder.Services.AddScoped<ITermsRepository, TermsRepository>();
|
|
builder.Services.AddScoped<IActivityLogRepository, ActivityLogRepository>();
|
|
builder.Services.AddScoped<IClientRepository, ClientRepository>();
|
|
builder.Services.AddScoped<IStaffRepository, StaffRepository>();
|
|
builder.Services.AddScoped<IClientInvoiceRepository, ClientInvoiceRepository>();
|
|
builder.Services.AddScoped<ICustomerRepository, CustomerRepository>();
|
|
builder.Services.AddScoped<ICustomerInvoiceRepository, CustomerInvoiceRepository>();
|
|
builder.Services.AddScoped<ICustomerSubscriptionRepository, CustomerSubscriptionRepository>();
|
|
builder.Services.AddScoped<ICustomerPlanRepository, CustomerPlanRepository>();
|
|
builder.Services.AddScoped<IMealRepository, MealRepository>();
|
|
builder.Services.AddScoped<IWorkoutRepository, WorkoutRepository>();
|
|
builder.Services.AddScoped<IChatRepo, ChatRepository>();
|
|
builder.Services.AddScoped<IDocumentRepository, DocumentRepository>();
|
|
builder.Services.AddScoped<IFeedRepository, FeedRepository>();
|
|
builder.Services.AddScoped<IAttendanceRepository, AttendanceRepository>();
|
|
builder.Services.AddScoped<IClientStatRepository, ClientStatRepository>();
|
|
builder.Services.AddScoped<IPaymentRepository, PaymentRepository>();
|
|
builder.Services.AddScoped<IPaymentsEnablementRepository, PaymentsEnablementRepository>();
|
|
builder.Services.AddScoped<IAdminClientDirectoryRepository, AdminClientDirectoryRepository>();
|
|
builder.Services.AddScoped<IAdminSuggestionRepository, AdminSuggestionRepository>();
|
|
builder.Services.AddScoped<IGymRoleRepository, GymRoleRepository>();
|
|
builder.Services.AddScoped<INavItemRepository, NavItemRepository>();
|
|
builder.Services.AddScoped<IProfitLossRepository, ProfitLossRepository>();
|
|
builder.Services.AddScoped<IOverdueLockRepository, OverdueLockRepository>();
|
|
builder.Services.AddScoped<IPlatformBillingRepository, PlatformBillingRepository>();
|
|
builder.Services.AddScoped<ICustomerNotificationRepository, CustomerNotificationRepository>();
|
|
builder.Services.AddScoped<IFacilityRepository, FacilityRepository>();
|
|
builder.Services.AddScoped<IFacilityClassRepository, FacilityClassRepository>();
|
|
builder.Services.AddScoped<IBookingRepository, BookingRepository>();
|
|
builder.Services.AddScoped<IInventoryRepository, InventoryRepository>();
|
|
builder.Services.AddScoped<ITimeBankCardRepository, TimeBankCardRepository>();
|
|
builder.Services.AddScoped<IRfidTagRepository, RfidTagRepository>();
|
|
|
|
// Register Helpers
|
|
builder.Services.AddScoped<Fitness_core_BE.Infra.Tenancy.ITenantContext, Fitness_core_BE.Infra.Tenancy.TenantContext>();
|
|
builder.Services.AddScoped<UserRoleHelper>();
|
|
builder.Services.AddScoped<RequestAccessTokenHelper>();
|
|
builder.Services.AddScoped<InvoicePaymentTokenHelper>();
|
|
builder.Services.AddScoped<PlatformInvoicePaymentTokenHelper>();
|
|
|
|
// Register Services
|
|
builder.Services.AddScoped<ITicketService, TicketService>();
|
|
builder.Services.AddScoped<IActivityLogService, ActivityLogService>();
|
|
builder.Services.AddScoped<IClientService, ClientService>();
|
|
builder.Services.AddScoped<IStaffService, StaffService>();
|
|
builder.Services.AddScoped<IClientInvoiceService, ClientInvoiceService>();
|
|
builder.Services.AddScoped<ICustomerService, CustomerService>();
|
|
builder.Services.AddScoped<ICustomerInvoiceService, CustomerInvoiceService>();
|
|
builder.Services.AddScoped<ICustomerSubscriptionService, CustomerSubscriptionService>();
|
|
builder.Services.AddScoped<IFacilityService, FacilityService>();
|
|
builder.Services.AddScoped<IBookingService, BookingService>();
|
|
builder.Services.AddScoped<IClassService, ClassService>();
|
|
builder.Services.AddScoped<IInventoryService, InventoryService>();
|
|
builder.Services.AddScoped<IRfidTagService, RfidTagService>();
|
|
builder.Services.AddScoped<ITimeBankCardService, TimeBankCardService>();
|
|
builder.Services.AddScoped<IBootstrapService, BootstrapService>();
|
|
builder.Services.AddScoped<ISubscriptionRenewalApprovalService, SubscriptionRenewalApprovalService>();
|
|
builder.Services.AddScoped<ICustomerPlanService, CustomerPlanService>();
|
|
builder.Services.AddScoped<IMealService, MealService>();
|
|
builder.Services.AddScoped<IWorkoutService, WorkoutService>();
|
|
builder.Services.AddScoped<IAuthService, AuthService>();
|
|
builder.Services.AddScoped<IDocumentService, DocumentService>();
|
|
builder.Services.AddScoped<IFeedService, FeedService>();
|
|
builder.Services.AddScoped<IChatService, ChatService>();
|
|
builder.Services.AddScoped<IClientStatService, ClientStatService>();
|
|
builder.Services.AddScoped<IPaymentService, PaymentService>();
|
|
builder.Services.AddScoped<IPaymentsEnablementService, PaymentsEnablementService>();
|
|
builder.Services.AddScoped<IThirdPartyService, ThirdPartyService>();
|
|
builder.Services.AddScoped<IReceiptService, ReceiptService>();
|
|
builder.Services.AddScoped<IPayoutKycReviewService, PayoutKycReviewService>();
|
|
builder.Services.AddScoped<IAdminClientDirectoryService, AdminClientDirectoryService>();
|
|
builder.Services.AddScoped<IAdminSuggestionService, AdminSuggestionService>();
|
|
builder.Services.AddScoped<IPlatformBillingService, PlatformBillingService>();
|
|
builder.Services.AddScoped<IClientPaymentMethodService, ClientPaymentMethodService>();
|
|
builder.Services.AddScoped<IGymRoleService, GymRoleService>();
|
|
builder.Services.AddScoped<IPermissionService, PermissionService>();
|
|
builder.Services.AddScoped<INavigationService, NavigationService>();
|
|
builder.Services.AddScoped<IPushNotificationService, PushNotificationService>();
|
|
// Register Function Handlers
|
|
builder.Services.AddScoped<TicketFuncHandler>();
|
|
builder.Services.AddScoped<ActivityLogFuncHandler>();
|
|
builder.Services.AddScoped<ClientFuncHandler>();
|
|
builder.Services.AddScoped<StaffFunctionHandler>();
|
|
builder.Services.AddScoped<ClientInvoiceFunctionHandler>();
|
|
builder.Services.AddScoped<CustomerFunctionHandler>();
|
|
builder.Services.AddScoped<CustomerInvoiceFunctionHandler>();
|
|
builder.Services.AddScoped<CustomerPlanFunctionHandler>();
|
|
builder.Services.AddScoped<CustomerSubscriptionFunctionHandler>();
|
|
builder.Services.AddScoped<MealFunctionHandler>();
|
|
builder.Services.AddScoped<WorkoutFunctionHandler>();
|
|
builder.Services.AddScoped<AuthFuncHandler>();
|
|
builder.Services.AddScoped<DocumentFunctionHandler>();
|
|
builder.Services.AddScoped<FeedFuncHandler>();
|
|
builder.Services.AddScoped<AccessDeviceFunctionHandler>();
|
|
builder.Services.AddScoped<ChatFunctionHandler>();
|
|
builder.Services.AddScoped<UserRegisterHandler>();
|
|
builder.Services.AddScoped<ClientStatFuncHandler>();
|
|
builder.Services.AddScoped<PaymentFunctionHandler>();
|
|
|
|
// Register AI Services
|
|
builder.Services.AddScoped<ModificationGuard>();
|
|
builder.Services.AddScoped<MealPlanOrchestrator>();
|
|
builder.Services.AddScoped<WorkoutPlanOrchestrator>();
|
|
builder.Services.AddHttpClient<IAiClient, GeminiClient>();
|
|
|
|
// Register Access Device Service
|
|
builder.Services.AddScoped<IAccessDeviceService, AccessDeviceService>();
|
|
builder.Services.AddScoped<IAccessDeviceRepo, AccessDeviceRepo>();
|
|
|
|
// Register Payment-Service clients: catalog/provisioning admin proxy (X-Internal-Key),
|
|
// and the per-platform HMAC-signed client for real card/payment/refund calls.
|
|
builder.Services.AddScoped<IPaymentServiceClient, PaymentServiceClient>();
|
|
builder.Services.AddScoped<IPaymentGatewayClient, PaymentGatewayClient>();
|
|
|
|
// Shared by background jobs that have no HttpContext to resolve a tenant from -- lets them
|
|
// loop over every real, provisioned tenant schema directly instead of only ever seeing
|
|
// whichever schema TenantConnectionResolver's request-based fallback resolves to.
|
|
builder.Services.AddSingleton<Fitness_core_BE.Infra.Tenancy.TenantSchemaIterator>();
|
|
builder.Services.AddSingleton<SubscriptionRenewalRunner>();
|
|
builder.Services.AddSingleton<OverdueLockRunner>();
|
|
builder.Services.AddSingleton<IJobRunHistory, DbJobRunHistory>();
|
|
|
|
// Auto-lock members after configurable days overdue (req 7) -- disabled at the user's request.
|
|
// OverdueLockRunner (above) stays registered, so the manual "trigger now" endpoint
|
|
// (POST api/admin/background-jobs/overdue-lock/trigger) still works on demand; only the
|
|
// automatic startup + 6-hourly run is turned off. Uncomment to re-enable.
|
|
// builder.Services.AddHostedService<OverdueLockJob>();
|
|
|
|
// Auto-renew expired subscriptions (new plan price + carried-over overdue balance)
|
|
builder.Services.AddHostedService<SubscriptionRenewalJob>();
|
|
|
|
// Basic P&L / ledger for the client portal
|
|
builder.Services.AddScoped<IProfitLossService, ProfitLossService>();
|
|
|
|
// Register On-prem Agent platform
|
|
builder.Services.AddScoped<IAgentRepo, AgentRepo>();
|
|
builder.Services.AddScoped<IPairingCodeRepo, PairingCodeRepo>();
|
|
builder.Services.AddScoped<IDeviceCommandRepo, DeviceCommandRepo>();
|
|
builder.Services.AddScoped<IAgentService, AgentService>();
|
|
|
|
// Agent API-key authentication scheme + policy (does not affect existing admin/user auth).
|
|
builder.Services
|
|
.AddAuthentication()
|
|
.AddScheme<AgentApiKeyAuthenticationOptions, AgentApiKeyAuthenticationHandler>(
|
|
AgentApiKeyDefaults.AuthenticationScheme, _ => { })
|
|
.AddScheme<UserJwtAuthenticationOptions, UserJwtAuthenticationHandler>(
|
|
UserJwtDefaults.AuthenticationScheme, _ => { });
|
|
|
|
builder.Services.AddAuthorization(options =>
|
|
{
|
|
options.AddPolicy(AgentApiKeyDefaults.PolicyName, policy =>
|
|
{
|
|
policy.AddAuthenticationSchemes(AgentApiKeyDefaults.AuthenticationScheme);
|
|
policy.RequireAuthenticatedUser();
|
|
});
|
|
});
|
|
|
|
// Register Attendance Service
|
|
builder.Services.AddScoped<IAttendanceService, AttendanceService>();
|
|
builder.Services.AddScoped<AttendanceFunctionHandler>();
|
|
|
|
var app = builder.Build();
|
|
// Configure the HTTP request pipeline.
|
|
if (app.Environment.IsDevelopment())
|
|
{
|
|
app.UseSwagger();
|
|
app.UseSwaggerUI();
|
|
}
|
|
|
|
// First in the pipeline so it catches anything thrown by everything after it.
|
|
app.UseMiddleware<Fitness_core_BE.Helpers.GlobalExceptionMiddleware>();
|
|
|
|
app.UseRouting();
|
|
|
|
app.UseCors("AllowAll");
|
|
|
|
app.UseAuthentication();
|
|
|
|
app.UseAuthorization();
|
|
|
|
app.MapControllers();
|
|
|
|
app.MapHub<ChatHub>("/hubs/chat");
|
|
app.MapHub<AccessHub>("/hubs/access");
|
|
|
|
app.Run();
|