User
deploy: update to latest release with Id case fix for notifications in seed_supabase.sql
22f4a07
Raw
History Blame Contribute Delete
16 kB
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using TaskTrackingSystem.Database.AppDbContextModels;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using System.Text;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddHttpClient();
builder.Services.AddSignalR();
// CORS policy for WebApp
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowWebApp", policy =>
{
policy.WithOrigins("http://localhost:5247", "https://localhost:7176")
.AllowCredentials()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
builder.Services.AddHttpContextAccessor();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Infrastructure.AuditLogService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.User.UserService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Auth.AuthService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Role.RoleService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Project.ProjectService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Task.TaskService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Issue.IssueService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Dashboard.DashboardService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Report.ReportService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Menu.MenuService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.UserDevice.UserDeviceService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Notification.FirebaseNotificationService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Notification.NotificationRealtimeService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Features.Notification.NotificationService>();
builder.Services.AddHostedService<TaskTrackingSystem.WebApi.Features.Notification.NotificationCleanupHostedService>();
builder.Services.AddScoped<TaskTrackingSystem.WebApi.Infrastructure.PermissionAuthorizationService>();
builder.Services.AddScoped<IPasswordHasher<TaskTrackingSystem.Database.AppDbContextModels.User>, PasswordHasher<TaskTrackingSystem.Database.AppDbContextModels.User>>();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddAuthorization();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "Paste the JWT token from the login response. You can include or omit the 'Bearer ' prefix."
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
var jwtSettings = builder.Configuration.GetSection("JwtSettings");
var key = jwtSettings["Key"] ?? throw new InvalidOperationException("JwtSettings:Key must be configured.");
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtSettings["Issuer"] ?? throw new InvalidOperationException("JwtSettings:Issuer must be configured."),
ValidAudience = jwtSettings["Audience"] ?? throw new InvalidOperationException("JwtSettings:Audience must be configured."),
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key))
};
});
builder.Services.AddDbContext<AppDbContext>(opt =>
{
opt.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"));
});
var app = builder.Build();
await EnsureSeedDataAsync(app);
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI(options =>
{
options.EnablePersistAuthorization();
});
}
app.UseCors("AllowWebApp");
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/health", () => Results.Ok(new { status = "ok" }));
app.MapControllers();
app.MapHub<TaskTrackingSystem.WebApi.Features.Notification.NotificationHub>("/hubs/notifications");
app.Run();
static async System.Threading.Tasks.Task EnsureSeedDataAsync(WebApplication app)
{
using var scope = app.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.EnsureCreatedAsync();
// In Supabase/Hugging Face, EnsureCreatedAsync() might skip table creation
// because other schemas (auth, storage, etc.) contain tables.
// We check if the 'Menus' table exists in the public schema, and force creation of tables if it doesn't.
var databaseCreator = db.Database.GetService<IDatabaseCreator>() as RelationalDatabaseCreator;
if (databaseCreator != null)
{
var connection = db.Database.GetDbConnection();
var connectionOpened = false;
if (connection.State != System.Data.ConnectionState.Open)
{
await db.Database.OpenConnectionAsync();
connectionOpened = true;
}
try
{
using var command = connection.CreateCommand();
command.CommandText = "SELECT EXISTS (SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = 'Menus');";
var tableExists = (bool)(await command.ExecuteScalarAsync() ?? false);
if (!tableExists)
{
await databaseCreator.CreateTablesAsync();
}
}
finally
{
if (connectionOpened)
{
await db.Database.CloseConnectionAsync();
}
}
}
await EnsureReportUpgradeSchemaAsync(db);
var dashboardMenu = await db.Menus.FirstOrDefaultAsync(m => m.MenuCode == "DASHBOARD");
if (dashboardMenu == null)
{
dashboardMenu = new Menu
{
MenuCode = "DASHBOARD",
MenuName = "Dashboard",
MenuUrl = null,
Icon = "layout-grid",
Visible = false,
OrderNo = 0,
IsDeleted = true,
CreatedAt = DateTime.UtcNow
};
db.Menus.Add(dashboardMenu);
await db.SaveChangesAsync();
}
else
{
dashboardMenu.MenuName = "Dashboard";
dashboardMenu.MenuUrl = null;
dashboardMenu.Icon = "layout-grid";
dashboardMenu.Visible = false;
dashboardMenu.OrderNo = 0;
dashboardMenu.IsDeleted = true;
dashboardMenu.UpdatedAt = DateTime.UtcNow;
}
var adminDashboardMenu = await db.Menus.FirstOrDefaultAsync(m => m.MenuCode == "DASHBOARD_ADMIN");
if (adminDashboardMenu == null)
{
adminDashboardMenu = new Menu
{
MenuCode = "DASHBOARD_ADMIN",
ParentMenuId = null,
MenuName = "Dashboard",
MenuUrl = "/dashboard",
Icon = "layout-dashboard",
Visible = true,
OrderNo = 0,
IsDeleted = false,
CreatedAt = DateTime.UtcNow
};
db.Menus.Add(adminDashboardMenu);
}
else
{
adminDashboardMenu.ParentMenuId = null;
adminDashboardMenu.MenuName = "Dashboard";
adminDashboardMenu.MenuUrl = "/dashboard";
adminDashboardMenu.Icon = "layout-dashboard";
adminDashboardMenu.Visible = true;
adminDashboardMenu.OrderNo = 0;
adminDashboardMenu.IsDeleted = false;
adminDashboardMenu.UpdatedAt = DateTime.UtcNow;
}
var managerDashboardMenu = await db.Menus.FirstOrDefaultAsync(m => m.MenuCode == "DASHBOARD_MANAGER");
if (managerDashboardMenu == null)
{
managerDashboardMenu = new Menu
{
MenuCode = "DASHBOARD_MANAGER",
ParentMenuId = null,
MenuName = "Dashboard",
MenuUrl = "/dashboard/manager",
Icon = "layout-dashboard",
Visible = true,
OrderNo = 0,
IsDeleted = false,
CreatedAt = DateTime.UtcNow
};
db.Menus.Add(managerDashboardMenu);
}
else
{
managerDashboardMenu.ParentMenuId = null;
managerDashboardMenu.MenuName = "Dashboard";
managerDashboardMenu.MenuUrl = "/dashboard/manager";
managerDashboardMenu.Icon = "layout-dashboard";
managerDashboardMenu.Visible = true;
managerDashboardMenu.OrderNo = 0;
managerDashboardMenu.IsDeleted = false;
managerDashboardMenu.UpdatedAt = DateTime.UtcNow;
}
var employeeDashboardMenu = await db.Menus.FirstOrDefaultAsync(m => m.MenuCode == "DASHBOARD_EMPLOYEE");
if (employeeDashboardMenu == null)
{
employeeDashboardMenu = new Menu
{
MenuCode = "DASHBOARD_EMPLOYEE",
ParentMenuId = null,
MenuName = "Dashboard",
MenuUrl = "/dashboard/employee",
Icon = "layout-dashboard",
Visible = true,
OrderNo = 0,
IsDeleted = false,
CreatedAt = DateTime.UtcNow
};
db.Menus.Add(employeeDashboardMenu);
}
else
{
employeeDashboardMenu.ParentMenuId = null;
employeeDashboardMenu.MenuName = "Dashboard";
employeeDashboardMenu.MenuUrl = "/dashboard/employee";
employeeDashboardMenu.Icon = "layout-dashboard";
employeeDashboardMenu.Visible = true;
employeeDashboardMenu.OrderNo = 0;
employeeDashboardMenu.IsDeleted = false;
employeeDashboardMenu.UpdatedAt = DateTime.UtcNow;
}
await db.SaveChangesAsync();
var adminRole = await db.Roles.FirstOrDefaultAsync(r => r.Name == "Admin" && !r.IsDeleted);
if (adminRole != null)
{
var adminDashboardAccess = await db.RoleMenus.FirstOrDefaultAsync(rm => rm.RoleId == adminRole.Id && rm.MenuId == adminDashboardMenu.MenuId);
if (adminDashboardAccess == null)
{
db.RoleMenus.Add(new RoleMenu
{
RoleId = adminRole.Id,
MenuId = adminDashboardMenu.MenuId,
IsDeleted = false,
CreatedAt = DateTime.UtcNow
});
}
else
{
adminDashboardAccess.IsDeleted = false;
adminDashboardAccess.UpdatedAt = DateTime.UtcNow;
}
}
var managerRole = await db.Roles.FirstOrDefaultAsync(r => r.Name == "Manager" && !r.IsDeleted);
if (managerRole != null)
{
var managerDashboardAccess = await db.RoleMenus.FirstOrDefaultAsync(rm => rm.RoleId == managerRole.Id && rm.MenuId == managerDashboardMenu.MenuId);
if (managerDashboardAccess == null)
{
db.RoleMenus.Add(new RoleMenu
{
RoleId = managerRole.Id,
MenuId = managerDashboardMenu.MenuId,
IsDeleted = false,
CreatedAt = DateTime.UtcNow
});
}
else
{
managerDashboardAccess.IsDeleted = false;
managerDashboardAccess.UpdatedAt = DateTime.UtcNow;
}
}
var employeeRole = await db.Roles.FirstOrDefaultAsync(r => r.Name == "Employee" && !r.IsDeleted);
if (employeeRole != null)
{
var employeeDashboardAccess = await db.RoleMenus.FirstOrDefaultAsync(rm => rm.RoleId == employeeRole.Id && rm.MenuId == employeeDashboardMenu.MenuId);
if (employeeDashboardAccess == null)
{
db.RoleMenus.Add(new RoleMenu
{
RoleId = employeeRole.Id,
MenuId = employeeDashboardMenu.MenuId,
IsDeleted = false,
CreatedAt = DateTime.UtcNow
});
}
else
{
employeeDashboardAccess.IsDeleted = false;
employeeDashboardAccess.UpdatedAt = DateTime.UtcNow;
}
}
await db.SaveChangesAsync();
await EnsureIssueReportMenuAsync(db);
// Soft-delete existing tasks that belong to deleted projects
var orphanedTasks = await db.Tasks
.Where(t => t.IsDeleted != true && t.Project.IsDeleted == true)
.ToListAsync();
foreach (var task in orphanedTasks)
{
task.IsDeleted = true;
task.UpdatedAt = DateTime.UtcNow;
}
if (orphanedTasks.Any())
{
db.Tasks.UpdateRange(orphanedTasks);
}
await db.SaveChangesAsync();
}
static async System.Threading.Tasks.Task EnsureReportUpgradeSchemaAsync(AppDbContext db)
{
await db.Database.ExecuteSqlRawAsync(@"
ALTER TABLE ""Issues"" ADD COLUMN IF NOT EXISTS ""DelayReason"" character varying(300);
ALTER TABLE ""Issues"" ADD COLUMN IF NOT EXISTS ""IsBlocked"" boolean NOT NULL DEFAULT FALSE;
ALTER TABLE ""Issues"" ADD COLUMN IF NOT EXISTS ""BlockedBy"" character varying(200);
ALTER TABLE ""Issues"" ADD COLUMN IF NOT EXISTS ""EscalationLevel"" integer NOT NULL DEFAULT 0;
");
}
static async System.Threading.Tasks.Task EnsureIssueReportMenuAsync(AppDbContext db)
{
var reportsMenu = await db.Menus.FirstOrDefaultAsync(m => m.MenuCode == "REPORTS" && !m.IsDeleted);
var issueReportMenu = await db.Menus.FirstOrDefaultAsync(m => m.MenuCode == "REPORTS_ISSUES");
if (issueReportMenu == null)
{
issueReportMenu = new Menu
{
MenuCode = "REPORTS_ISSUES",
ParentMenuId = reportsMenu?.MenuId,
MenuName = "Issue Report",
MenuUrl = "/reports/issues",
Icon = "file-pen-line",
Visible = true,
OrderNo = 15,
IsDeleted = false,
CreatedAt = DateTime.UtcNow
};
db.Menus.Add(issueReportMenu);
await db.SaveChangesAsync();
}
else
{
issueReportMenu.ParentMenuId = reportsMenu?.MenuId;
issueReportMenu.MenuName = "Issue Report";
issueReportMenu.MenuUrl = "/reports/issues";
issueReportMenu.Icon = "file-pen-line";
issueReportMenu.Visible = true;
issueReportMenu.OrderNo = 15;
issueReportMenu.IsDeleted = false;
issueReportMenu.UpdatedAt = DateTime.UtcNow;
await db.SaveChangesAsync();
}
var reportRoles = await db.Roles
.Where(role => !role.IsDeleted && (role.Name == "Admin" || role.Name == "Manager" || role.Name == "Employee"))
.ToListAsync();
foreach (var role in reportRoles)
{
var roleMenu = await db.RoleMenus.FirstOrDefaultAsync(rm => rm.RoleId == role.Id && rm.MenuId == issueReportMenu.MenuId);
if (roleMenu == null)
{
db.RoleMenus.Add(new RoleMenu
{
RoleId = role.Id,
MenuId = issueReportMenu.MenuId,
IsDeleted = false,
CreatedAt = DateTime.UtcNow
});
}
else
{
roleMenu.IsDeleted = false;
roleMenu.UpdatedAt = DateTime.UtcNow;
}
}
await db.SaveChangesAsync();
}