Compare commits
219 Commits
b74f701448
...
b61caa9ee7
| Author | SHA1 | Date | |
|---|---|---|---|
| b61caa9ee7 | |||
| 6df8148f40 | |||
|
|
7dfed25d8e | ||
|
|
9ed615110d | ||
|
|
4f0515f8f4 | ||
| 361a2ab5c3 | |||
| 0db199e74f | |||
| a6fc75f492 | |||
| af9e06cd98 | |||
| 7dc80ec006 | |||
| 4abeb8cf5a | |||
| b88dbea6c6 | |||
| 138eb963d0 | |||
| ae08ebeae5 | |||
| 49988c9814 | |||
| 915ad7bdb5 | |||
| aad79953f5 | |||
| 404c16946b | |||
| ac837ef241 | |||
| 3f74646437 | |||
| 6e2b0eaec0 | |||
| 1e0e277bbf | |||
| 9a19440622 | |||
| 85adf50418 | |||
| 8ca07410b0 | |||
| 84767d41b8 | |||
| ec63cde59b | |||
| 166d556b6c | |||
| 17398dafa6 | |||
| 45cdbd91c9 | |||
|
|
ddc6f9e393 | ||
| d294bcef44 | |||
| 9ceba92447 | |||
| 69d27c7471 | |||
| 30cd9d3a57 | |||
| 0427b41961 | |||
| e407c96bda | |||
| 8f1c1489e5 | |||
| 5464ee6200 | |||
|
|
66c167f027 | ||
| aaacd6be91 | |||
| f93caa7994 | |||
| 7fd3c7b0b3 | |||
| 928886ac72 | |||
| eb096685d5 | |||
| d62725cb38 | |||
| 64aaf325bd | |||
| 60acd104c1 | |||
| b3ceecaf9e | |||
| 1e70f2bffc | |||
| eb897af87f | |||
| 93997c16a2 | |||
| 88233d9435 | |||
| 3b09a5f625 | |||
| de8ef72cae | |||
| 9921683fa6 | |||
|
|
a5c472b52e | ||
| 54c66daa18 | |||
| 7c1171fbb0 | |||
| a0bf548e64 | |||
|
|
94305f34ed | ||
| 25c91a64ad | |||
| a7f41af44f | |||
| c9073652c8 | |||
| 2650bdb17a | |||
| f7543a37a1 | |||
| 42b5478b52 | |||
| eac2c31ea4 | |||
| a00ef4313a | |||
| 20b1f45915 | |||
| 3c14df4d53 | |||
| 5f2626db88 | |||
| 9da45240c3 | |||
| 81d3e016c2 | |||
| bea8793fba | |||
| 9c25aa1aab | |||
| 9e813c1e51 | |||
|
|
6e0cdbcfe3 | ||
| 76671bc5dc | |||
| 4f250ab46e | |||
| bab5243108 | |||
| 47d7cbba4f | |||
| 3d9e22c308 | |||
| f2699898ed | |||
|
|
9d67a25488 | ||
| 58b0a3fbaa | |||
| b5ce7eb73f | |||
| a23c8c3ff0 | |||
| 52e9cb7de9 | |||
| 8da02932e4 | |||
| ccef0ba192 | |||
| 621b96a805 | |||
| f0bf5dca83 | |||
| d60608e544 | |||
| a48d4d308f | |||
| 197b4aea8d | |||
| 453a64fea0 | |||
| a755e77397 | |||
| 049189024a | |||
| c1cc8d5d34 | |||
| f7d90b85e8 | |||
| f621dbf27c | |||
| 4650e0cbbb | |||
| fb2648ba17 | |||
| 54ea82b984 | |||
| 3128372a78 | |||
| bad2386fa4 | |||
| 6a1fa9a4f8 | |||
| 605fd9c78d | |||
| 5fde9c5e53 | |||
| 7d704b138b | |||
| 3841dca11e | |||
| 5e184c770d | |||
| 0795eda5cc | |||
| e175e58450 | |||
| 80da3199b9 | |||
|
|
eea2b7a8b3 | ||
| 7dca88153f | |||
| 220b87d5c3 | |||
| ed9a2467e9 | |||
| ff13507f8f | |||
| 0fb3690133 | |||
| 7a2ce067ab | |||
| 115e38b58f | |||
| 578b2e0d67 | |||
|
|
eb3bd425f6 | ||
| 81f91c3b6d | |||
| 9e124d0ae5 | |||
| d3a800fbf9 | |||
| c51da8d1b4 | |||
| 10322fa432 | |||
| 2a0adc75c4 | |||
| 38463ccf07 | |||
| 6dbd968676 | |||
| 707b948e15 | |||
| 2456a9a039 | |||
| d1995b820c | |||
|
|
8c915d7a1d | ||
| b15022774f | |||
| 3633d2ce4b | |||
| 9922255514 | |||
| ad659c4fc9 | |||
| d136e8b95e | |||
| 9fd01c7903 | |||
| 872bcdd236 | |||
| 8209f06410 | |||
| a4aae7ebbe | |||
| 9a5be28196 | |||
| 1d6f8825c6 | |||
| 6f902178a4 | |||
| 282a21adc2 | |||
| f994ee14a2 | |||
| 4eeeec5dbe | |||
| 56e444f87f | |||
| 7016e38bc4 | |||
| 0917b6665f | |||
| 1f49057cb6 | |||
|
|
5a9c08fd6c | ||
| cc0ac31746 | |||
| 5d71f3f2db | |||
| 2985cba79b | |||
| 81992f6abd | |||
|
|
1f2cc2b2e6 | ||
| 542701f342 | |||
| 33e765558c | |||
| ec1325dc03 | |||
| fa3a7ab7f2 | |||
| 10625101e1 | |||
| dc36cb57f4 | |||
| f820bedba3 | |||
| 15c665236b | |||
| 20eaccef04 | |||
| 3376812538 | |||
| e427781ad9 | |||
| fe0d5e8458 | |||
| e89036359d | |||
| 999f9168c2 | |||
| 0a59f87b84 | |||
| 49bbd87a3d | |||
| d52956abd9 | |||
| e049b6c996 | |||
| 532e0ff16d | |||
|
|
967488e9c1 | ||
| ecc8c6d801 | |||
| 3043a3f7ed | |||
| bb0c2acc87 | |||
| 7dffdc4c25 | |||
| d5e150d768 | |||
| f26d5d0021 | |||
|
|
278a55ebe3 | ||
| 38babea9d5 | |||
| 73df47e540 | |||
| 5932a21fcc | |||
| 58c73383b2 | |||
| 004bb94d99 | |||
| 2c20d49609 | |||
| 2002a79360 | |||
| e760d51987 | |||
| d235bc8211 | |||
| 9e9bb6fecd | |||
| 9f37c37e18 | |||
| 775c17531b | |||
| 2fc44ec499 | |||
| 0a8c5cf587 | |||
| baa168ff8f | |||
| 47ad6231dd | |||
| 14b0d1bfc7 | |||
| 5eb100c1f6 | |||
| 863a154ec6 | |||
| 34577c41f7 | |||
| 1cb7a9fea8 | |||
| 7ef2c720cb | |||
| a5cf2025a1 | |||
|
|
5b08b617cf | ||
| 5f9784faa8 | |||
| 18acfef5a0 | |||
| 92fd335eaf | |||
| 0467a825ad | |||
| 357d615d1b |
@ -78,12 +78,14 @@ namespace Marco.Pms.DataAccess.Data
|
||||
public DbSet<ContactTagMapping> ContactTagMappings { get; set; }
|
||||
public DbSet<EmployeeBucketMapping> EmployeeBucketMappings { get; set; }
|
||||
public DbSet<ContactBucketMapping> ContactBucketMappings { get; set; }
|
||||
public DbSet<DirectoryUpdateLog> DirectoryUpdateLogs { get; set; }
|
||||
public DbSet<ContactProjectMapping> ContactProjectMappings { get; set; }
|
||||
public DbSet<DirectoryUpdateLog> DirectoryUpdateLogs { get; set; }
|
||||
|
||||
public DbSet<MailingList> MailingList { get; set; }
|
||||
public DbSet<MailDetails> MailDetails { get; set; }
|
||||
public DbSet<MailLog> MailLogs { get; set; }
|
||||
public DbSet<OTPDetails> OTPDetails { get; set; }
|
||||
public DbSet<MPINDetails> MPINDetails { get; set; }
|
||||
|
||||
|
||||
|
||||
@ -509,8 +511,9 @@ namespace Marco.Pms.DataAccess.Data
|
||||
new FeaturePermission { Id = new Guid("fbd213e0-0250-46f1-9f5f-4b2a1e6e76a3"), FeatureId = new Guid("81ab8a87-8ccd-4015-a917-0627cee6a100"), IsEnabled = true, Name = "Assign Roles", Description = "Grants a user the authority to manage employee application roles, enabling them to assign or revoke access privileges within the system." },
|
||||
|
||||
|
||||
new FeaturePermission { Id = new Guid("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"), FeatureId = new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), IsEnabled = true, Name = "Perform Attendance ", Description = "Grants a user the ability to record their own work hours or presence within the system. This typically involves checking in and checking out, logging break times, and potentially viewing their own attendance history." },
|
||||
new FeaturePermission { Id = new Guid("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"), FeatureId = new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), IsEnabled = true, Name = "Team Attendance ", Description = "Team Attendance refers to tracking and managing the attendance of all team members collectively, often monitored by a team lead or manager." },
|
||||
new FeaturePermission { Id = new Guid("57802c4a-00aa-4a1f-a048-fd2f70dd44b6"), FeatureId = new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), IsEnabled = true, Name = "Regularize Attendance", Description = "Grants a user the authority to approve requests from employees to adjust or correct their recorded attendance. This typically involves reviewing the reason for the regularization, verifying any supporting documentation, and then officially accepting the changes to the employee's attendance records" },
|
||||
new FeaturePermission { Id = new Guid("ccb0589f-712b-43de-92ed-5b6088e7dc4e"), FeatureId = new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), IsEnabled = true, Name = "Self Attendance", Description = "Team Attendance refers to tracking and managing the attendance of all team members collectively, often monitored by a team lead or manager." },
|
||||
|
||||
new FeaturePermission { Id = new Guid("5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d"), FeatureId = new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be"), IsEnabled = true, Name = "View Masters", Description = "Grants a user read-only access to foundational or reference data within the system. \"Masters\" typically refer to predefined lists, categories, or templates that are used throughout the application to standardize information and maintain consistency" },
|
||||
new FeaturePermission { Id = new Guid("588a8824-f924-4955-82d8-fc51956cf323"), FeatureId = new Guid("be3b3afc-6ccf-4566-b9b6-aafcb65546be"), IsEnabled = true, Name = "Manage Masters", Description = "Grants a user the authority to create, modify, and delete foundational or reference data within the system. These \"masters\" are typically the core lists, categories, and configurations that other data and functionalities rely upon, such as departments, job titles, product categories" },
|
||||
|
||||
2589
Marco.Pms.DataAccess/Migrations/20250604094759_Added_Self_Attendance_Feature_Permission.Designer.cs
generated
Normal file
2589
Marco.Pms.DataAccess/Migrations/20250604094759_Added_Self_Attendance_Feature_Permission.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Added_Self_Attendance_Feature_Permission : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.UpdateData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"),
|
||||
columns: new[] { "Description", "Name" },
|
||||
values: new object[] { "Team Attendance refers to tracking and managing the attendance of all team members collectively, often monitored by a team lead or manager.", "Team Attendance " });
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "FeaturePermissions",
|
||||
columns: new[] { "Id", "Description", "FeatureId", "IsEnabled", "Name" },
|
||||
values: new object[] { new Guid("ccb0589f-712b-43de-92ed-5b6088e7dc4e"), "Team Attendance refers to tracking and managing the attendance of all team members collectively, often monitored by a team lead or manager.", new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"), true, "Self Attendance" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DeleteData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("ccb0589f-712b-43de-92ed-5b6088e7dc4e"));
|
||||
|
||||
migrationBuilder.UpdateData(
|
||||
table: "FeaturePermissions",
|
||||
keyColumn: "Id",
|
||||
keyValue: new Guid("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"),
|
||||
columns: new[] { "Description", "Name" },
|
||||
values: new object[] { "Grants a user the ability to record their own work hours or presence within the system. This typically involves checking in and checking out, logging break times, and potentially viewing their own attendance history.", "Perform Attendance " });
|
||||
}
|
||||
}
|
||||
}
|
||||
2670
Marco.Pms.DataAccess/Migrations/20250605102139_Added_OTP_And_MPIN_Table.Designer.cs
generated
Normal file
2670
Marco.Pms.DataAccess/Migrations/20250605102139_Added_OTP_And_MPIN_Table.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Added_OTP_And_MPIN_Table : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "MPINDetails",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
MPIN = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
MPINToken = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
TimeStamp = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_MPINDetails", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_MPINDetails_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OTPDetails",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
UserId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci"),
|
||||
OTP = table.Column<string>(type: "longtext", nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||
ExpriesInSec = table.Column<int>(type: "int", nullable: false),
|
||||
TimeStamp = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||
TenantId = table.Column<Guid>(type: "char(36)", nullable: false, collation: "ascii_general_ci")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_OTPDetails", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_OTPDetails_Tenants_TenantId",
|
||||
column: x => x.TenantId,
|
||||
principalTable: "Tenants",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
})
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_MPINDetails_TenantId",
|
||||
table: "MPINDetails",
|
||||
column: "TenantId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_OTPDetails_TenantId",
|
||||
table: "OTPDetails",
|
||||
column: "TenantId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "MPINDetails");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "OTPDetails");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,40 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Added_Subject_In_MailingList_And_Removed_From_MailDetails : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Subject",
|
||||
table: "MailDetails");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Subject",
|
||||
table: "MailingList",
|
||||
type: "longtext",
|
||||
nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Subject",
|
||||
table: "MailingList");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Subject",
|
||||
table: "MailDetails",
|
||||
type: "longtext",
|
||||
nullable: false)
|
||||
.Annotation("MySql:CharSet", "utf8mb4");
|
||||
}
|
||||
}
|
||||
}
|
||||
2673
Marco.Pms.DataAccess/Migrations/20250607061133_Added_IsUsed_FLag_In_OTPDetails_Table.Designer.cs
generated
Normal file
2673
Marco.Pms.DataAccess/Migrations/20250607061133_Added_IsUsed_FLag_In_OTPDetails_Table.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Marco.Pms.DataAccess.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Added_IsUsed_FLag_In_OTPDetails_Table : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "IsUsed",
|
||||
table: "OTPDetails",
|
||||
type: "tinyint(1)",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "IsUsed",
|
||||
table: "OTPDetails");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -229,6 +229,68 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.ToTable("AttendanceLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.MPINDetails", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<string>("MPIN")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("MPINToken")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("TimeStamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("MPINDetails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.OTPDetails", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<int>("ExpriesInSec")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<bool>("IsUsed")
|
||||
.HasColumnType("tinyint(1)");
|
||||
|
||||
b.Property<string>("OTP")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.Property<DateTime>("TimeStamp")
|
||||
.HasColumnType("datetime(6)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("TenantId");
|
||||
|
||||
b.ToTable("OTPDetails");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.RefreshToken", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
@ -941,10 +1003,10 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
new
|
||||
{
|
||||
Id = new Guid("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"),
|
||||
Description = "Grants a user the ability to record their own work hours or presence within the system. This typically involves checking in and checking out, logging break times, and potentially viewing their own attendance history.",
|
||||
Description = "Team Attendance refers to tracking and managing the attendance of all team members collectively, often monitored by a team lead or manager.",
|
||||
FeatureId = new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"),
|
||||
IsEnabled = true,
|
||||
Name = "Perform Attendance "
|
||||
Name = "Team Attendance "
|
||||
},
|
||||
new
|
||||
{
|
||||
@ -955,6 +1017,14 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
Name = "Regularize Attendance"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("ccb0589f-712b-43de-92ed-5b6088e7dc4e"),
|
||||
Description = "Team Attendance refers to tracking and managing the attendance of all team members collectively, often monitored by a team lead or manager.",
|
||||
FeatureId = new Guid("52c9cf54-1eb2-44d2-81bb-524cf29c0a94"),
|
||||
IsEnabled = true,
|
||||
Name = "Self Attendance"
|
||||
},
|
||||
new
|
||||
{
|
||||
Id = new Guid("5ffbafe0-7ab0-48b1-bb50-c1bf76b65f9d"),
|
||||
Description = "Grants a user read-only access to foundational or reference data within the system. \"Masters\" typically refer to predefined lists, categories, or templates that are used throughout the application to standardize information and maintain consistency",
|
||||
@ -1260,10 +1330,6 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
@ -1319,6 +1385,10 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<string>("Subject")
|
||||
.IsRequired()
|
||||
.HasColumnType("longtext");
|
||||
|
||||
b.Property<Guid>("TenantId")
|
||||
.HasColumnType("char(36)");
|
||||
|
||||
@ -2476,6 +2546,28 @@ namespace Marco.Pms.DataAccess.Migrations
|
||||
b.Navigation("UpdatedByEmployee");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.MPINDetails", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Entitlements.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.OTPDetails", b =>
|
||||
{
|
||||
b.HasOne("Marco.Pms.Model.Entitlements.Tenant", "Tenant")
|
||||
.WithMany()
|
||||
.HasForeignKey("TenantId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Tenant");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Marco.Pms.Model.Authentication.RefreshToken", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
||||
|
||||
13
Marco.Pms.Model/Authentication/MPINDetails.cs
Normal file
13
Marco.Pms.Model/Authentication/MPINDetails.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Marco.Pms.Model.Utilities;
|
||||
|
||||
namespace Marco.Pms.Model.Authentication
|
||||
{
|
||||
public class MPINDetails : TenantRelation
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public string MPIN { get; set; } = string.Empty;
|
||||
public string MPINToken { get; set; } = string.Empty;
|
||||
public DateTime TimeStamp { get; set; }
|
||||
}
|
||||
}
|
||||
14
Marco.Pms.Model/Authentication/OTPDetails.cs
Normal file
14
Marco.Pms.Model/Authentication/OTPDetails.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using Marco.Pms.Model.Utilities;
|
||||
|
||||
namespace Marco.Pms.Model.Authentication
|
||||
{
|
||||
public class OTPDetails : TenantRelation
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public Guid UserId { get; set; }
|
||||
public string OTP { get; set; } = string.Empty;
|
||||
public int ExpriesInSec { get; set; }
|
||||
public bool IsUsed { get; set; } = false;
|
||||
public DateTime TimeStamp { get; set; }
|
||||
}
|
||||
}
|
||||
@ -17,4 +17,4 @@ namespace Marco.Pms.Model.Directory
|
||||
[ForeignKey("ContactId")]
|
||||
public Contact? Contact { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
17
Marco.Pms.Model/Dtos/Authentication/ChangePasswordDto.cs
Normal file
17
Marco.Pms.Model/Dtos/Authentication/ChangePasswordDto.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class ChangePasswordDto
|
||||
{
|
||||
public string? Email { get; set; }
|
||||
|
||||
public string? OldPassword { get; set; }
|
||||
|
||||
public string? NewPassword { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Marco.Pms.Model.Dtos
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class ForgotPasswordDto
|
||||
{
|
||||
8
Marco.Pms.Model/Dtos/Authentication/GenerateMPINDto.cs
Normal file
8
Marco.Pms.Model/Dtos/Authentication/GenerateMPINDto.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class GenerateMPINDto
|
||||
{
|
||||
public Guid EmployeeId { get; set; }
|
||||
public string? MPIN { get; set; }
|
||||
}
|
||||
}
|
||||
7
Marco.Pms.Model/Dtos/Authentication/GenerateOTPDto.cs
Normal file
7
Marco.Pms.Model/Dtos/Authentication/GenerateOTPDto.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class GenerateOTPDto
|
||||
{
|
||||
public string? Email { get; set; }
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Marco.Pms.Model.Dtos
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class LoginDto
|
||||
{
|
||||
@ -1,5 +1,4 @@
|
||||
|
||||
namespace Marco.Pms.Model.Dtos
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class LogoutDto
|
||||
{ public string? RefreshToken { get; set; }
|
||||
@ -1,4 +1,4 @@
|
||||
namespace Marco.Pms.Model.Dtos
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class RefreshTokenDto
|
||||
{
|
||||
@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Marco.Pms.Model.Dtos
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class RegisterDto
|
||||
{
|
||||
@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Marco.Pms.Model.Dtos
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class ResetPasswordDto
|
||||
{
|
||||
9
Marco.Pms.Model/Dtos/Authentication/VerifyMPINDto.cs
Normal file
9
Marco.Pms.Model/Dtos/Authentication/VerifyMPINDto.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class VerifyMPINDto
|
||||
{
|
||||
public Guid EmployeeId { get; set; }
|
||||
public string? MPIN { get; set; }
|
||||
public string? MPINToken { get; set; }
|
||||
}
|
||||
}
|
||||
8
Marco.Pms.Model/Dtos/Authentication/VerifyOTPDto.cs
Normal file
8
Marco.Pms.Model/Dtos/Authentication/VerifyOTPDto.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Marco.Pms.Model.Dtos.Authentication
|
||||
{
|
||||
public class VerifyOTPDto
|
||||
{
|
||||
public string? Email { get; set; }
|
||||
public string? OTP { get; set; }
|
||||
}
|
||||
}
|
||||
@ -4,7 +4,6 @@
|
||||
{
|
||||
public Guid ProjectId { get; set; }
|
||||
public string Recipient { get; set; } = string.Empty; // Eamil Address of recipient
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public string Schedule { get; set; } = string.Empty; // json object which includes when to send mail and at what interval
|
||||
public Guid MailListId { get; set; }
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
{
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Body { get; set; } = string.Empty;
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public string Keywords { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ namespace Marco.Pms.Model.Mail
|
||||
public Guid Id { get; set; }
|
||||
public Guid ProjectId { get; set; }
|
||||
public string Recipient { get; set; } = string.Empty; // Eamil Address of recipient
|
||||
public string Subject { get; set; } = string.Empty; // Eamil Address of recipient
|
||||
public string Schedule { get; set; } = string.Empty; // json object which includes when to send mail and at what interval
|
||||
public Guid MailListId { get; set; }
|
||||
[ValidateNever]
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
public Guid Id { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Body { get; set; } = string.Empty;
|
||||
public string Subject { get; set; } = string.Empty; // Eamil Subject of recipient
|
||||
public string Keywords { get; set; } = string.Empty; // Comma seprated list of variables in mail body
|
||||
public Guid TenantId { get; set; }
|
||||
}
|
||||
|
||||
@ -28,11 +28,12 @@ namespace MarcoBMS.Services.Controllers
|
||||
private readonly ProjectsHelper _projectsHelper;
|
||||
private readonly UserHelper _userHelper;
|
||||
private readonly S3UploadService _s3Service;
|
||||
private readonly PermissionServices _permission;
|
||||
private readonly ILoggingService _logger;
|
||||
|
||||
|
||||
public AttendanceController(
|
||||
ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger)
|
||||
ApplicationDbContext context, EmployeeHelper employeeHelper, ProjectsHelper projectsHelper, UserHelper userHelper, S3UploadService s3Service, ILoggingService logger, PermissionServices permission)
|
||||
{
|
||||
_context = context;
|
||||
_employeeHelper = employeeHelper;
|
||||
@ -40,6 +41,7 @@ namespace MarcoBMS.Services.Controllers
|
||||
_userHelper = userHelper;
|
||||
_s3Service = s3Service;
|
||||
_logger = logger;
|
||||
_permission = permission;
|
||||
}
|
||||
|
||||
private Guid GetTenantId()
|
||||
@ -132,6 +134,17 @@ namespace MarcoBMS.Services.Controllers
|
||||
public async Task<IActionResult> EmployeeAttendanceByDateRange([FromQuery] Guid projectId, [FromQuery] string? dateFrom = null, [FromQuery] string? dateTo = null)
|
||||
{
|
||||
Guid TenantId = GetTenantId();
|
||||
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var hasTeamAttendancePermission = await _permission.HasPermission(new Guid("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"), LoggedInEmployee.Id);
|
||||
var hasSelfAttendancePermission = await _permission.HasPermission(new Guid("ccb0589f-712b-43de-92ed-5b6088e7dc4e"), LoggedInEmployee.Id);
|
||||
var hasProjectPermission = await _permission.HasProjectPermission(LoggedInEmployee, projectId.ToString());
|
||||
|
||||
if (!hasProjectPermission)
|
||||
{
|
||||
_logger.LogWarning("Employee {EmployeeId} tries to access attendance of project {ProjectId}, but don't have access", LoggedInEmployee.Id, projectId);
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", "Unauthorized access", 404));
|
||||
}
|
||||
|
||||
DateTime fromDate = new DateTime();
|
||||
DateTime toDate = new DateTime();
|
||||
|
||||
@ -159,42 +172,68 @@ namespace MarcoBMS.Services.Controllers
|
||||
if (dateFrom == null) fromDate = DateTime.UtcNow.Date;
|
||||
if (dateTo == null && dateFrom != null) toDate = fromDate.AddDays(-1);
|
||||
|
||||
|
||||
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date >= fromDate && c.AttendanceDate.Date <= toDate && c.TenantId == TenantId).ToListAsync();
|
||||
|
||||
|
||||
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, true);
|
||||
var jobRole = await _context.JobRoles.ToListAsync();
|
||||
foreach (Attendance? attendance in lstAttendance)
|
||||
if (hasTeamAttendancePermission)
|
||||
{
|
||||
var result1 = new EmployeeAttendanceVM()
|
||||
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == TenantId).ToListAsync();
|
||||
|
||||
|
||||
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, true);
|
||||
var jobRole = await _context.JobRoles.ToListAsync();
|
||||
foreach (Attendance? attendance in lstAttendance)
|
||||
{
|
||||
Id = attendance.Id,
|
||||
CheckInTime = attendance.InTime,
|
||||
CheckOutTime = attendance.OutTime,
|
||||
Activity = attendance.Activity
|
||||
};
|
||||
teamMember = projectteam.Find(x => x.EmployeeId == attendance.EmployeeID);
|
||||
if (teamMember != null)
|
||||
{
|
||||
result1.EmployeeAvatar = null;
|
||||
result1.EmployeeId = teamMember.EmployeeId;
|
||||
if (teamMember.Employee != null)
|
||||
var result1 = new EmployeeAttendanceVM()
|
||||
{
|
||||
result1.FirstName = teamMember.Employee.FirstName;
|
||||
result1.LastName = teamMember.Employee.LastName;
|
||||
result1.JobRoleName = teamMember.Employee.JobRole != null ? teamMember.Employee.JobRole.Name : null;
|
||||
}
|
||||
else
|
||||
Id = attendance.Id,
|
||||
CheckInTime = attendance.InTime,
|
||||
CheckOutTime = attendance.OutTime,
|
||||
Activity = attendance.Activity
|
||||
};
|
||||
teamMember = projectteam.Find(x => x.EmployeeId == attendance.EmployeeID);
|
||||
if (teamMember != null)
|
||||
{
|
||||
result1.FirstName = null;
|
||||
result1.LastName = null;
|
||||
result1.JobRoleName = null;
|
||||
result1.EmployeeAvatar = null;
|
||||
result1.EmployeeId = teamMember.EmployeeId;
|
||||
if (teamMember.Employee != null)
|
||||
{
|
||||
result1.FirstName = teamMember.Employee.FirstName;
|
||||
result1.LastName = teamMember.Employee.LastName;
|
||||
result1.JobRoleName = teamMember.Employee.JobRole != null ? teamMember.Employee.JobRole.Name : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
result1.FirstName = null;
|
||||
result1.LastName = null;
|
||||
result1.JobRoleName = null;
|
||||
}
|
||||
|
||||
result.Add(result1);
|
||||
}
|
||||
|
||||
result.Add(result1);
|
||||
}
|
||||
|
||||
}
|
||||
else if (hasSelfAttendancePermission)
|
||||
{
|
||||
List<Attendance> lstAttendances = await _context.Attendes.Where(c => c.ProjectID == projectId && c.EmployeeID == LoggedInEmployee.Id && c.AttendanceDate.Date >= fromDate.Date && c.AttendanceDate.Date <= toDate.Date && c.TenantId == TenantId).ToListAsync();
|
||||
ProjectAllocation? projectAllocation = await _context.ProjectAllocations.Include(pa => pa.Employee).FirstOrDefaultAsync(pa => pa.ProjectId == projectId && pa.EmployeeId == LoggedInEmployee.Id && pa.TenantId == TenantId && pa.IsActive);
|
||||
foreach (var attendance in lstAttendances)
|
||||
{
|
||||
if (projectAllocation != null)
|
||||
{
|
||||
EmployeeAttendanceVM result1 = new EmployeeAttendanceVM
|
||||
{
|
||||
Id = attendance.Id,
|
||||
EmployeeAvatar = null,
|
||||
EmployeeId = projectAllocation.EmployeeId,
|
||||
FirstName = projectAllocation.Employee?.FirstName,
|
||||
LastName = projectAllocation.Employee?.LastName,
|
||||
JobRoleName = projectAllocation.Employee?.JobRole?.Name,
|
||||
CheckInTime = attendance.InTime,
|
||||
CheckOutTime = attendance.OutTime,
|
||||
Activity = attendance.Activity
|
||||
};
|
||||
result.Add(result1);
|
||||
}
|
||||
}
|
||||
}
|
||||
_logger.LogInfo("{count} Attendance records fetched successfully", result.Count);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(result, System.String.Format("{0} Attendance records fetched successfully", result.Count), 200));
|
||||
@ -211,6 +250,17 @@ namespace MarcoBMS.Services.Controllers
|
||||
public async Task<IActionResult> EmployeeAttendanceByProject([FromQuery] Guid projectId, [FromQuery] bool IncludeInActive, [FromQuery] string? date = null)
|
||||
{
|
||||
Guid TenantId = GetTenantId();
|
||||
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var hasTeamAttendancePermission = await _permission.HasPermission(new Guid("915e6bff-65f6-4e3f-aea8-3fd217d3ea9e"), LoggedInEmployee.Id);
|
||||
var hasSelfAttendancePermission = await _permission.HasPermission(new Guid("ccb0589f-712b-43de-92ed-5b6088e7dc4e"), LoggedInEmployee.Id);
|
||||
var hasProjectPermission = await _permission.HasProjectPermission(LoggedInEmployee, projectId.ToString());
|
||||
|
||||
if (!hasProjectPermission)
|
||||
{
|
||||
_logger.LogWarning("Employee {EmployeeId} tries to access attendance of project {ProjectId}, but don't have access", LoggedInEmployee.Id, projectId);
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", "Unauthorized access", 404));
|
||||
}
|
||||
|
||||
DateTime forDate = new DateTime();
|
||||
|
||||
if (date != null && DateTime.TryParse(date, out forDate) == false)
|
||||
@ -229,49 +279,72 @@ namespace MarcoBMS.Services.Controllers
|
||||
Attendance? attendance = null;
|
||||
|
||||
if (date == null) forDate = DateTime.UtcNow.Date;
|
||||
|
||||
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date == forDate && c.TenantId == TenantId).ToListAsync();
|
||||
|
||||
|
||||
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, IncludeInActive);
|
||||
var idList = projectteam.Select(p => p.EmployeeId).ToList();
|
||||
//var emp = await _context.Employees.Where(e => idList.Contains(e.Id)).Include(e => e.JobRole).ToListAsync();
|
||||
var jobRole = await _context.JobRoles.ToListAsync();
|
||||
|
||||
foreach (ProjectAllocation teamMember in projectteam)
|
||||
if (hasTeamAttendancePermission)
|
||||
{
|
||||
if (teamMember.Employee != null && teamMember.Employee.JobRole != null)
|
||||
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.AttendanceDate.Date == forDate && c.TenantId == TenantId).ToListAsync();
|
||||
|
||||
|
||||
List<ProjectAllocation> projectteam = await _projectsHelper.GetTeamByProject(TenantId, projectId, IncludeInActive);
|
||||
var idList = projectteam.Select(p => p.EmployeeId).ToList();
|
||||
//var emp = await _context.Employees.Where(e => idList.Contains(e.Id)).Include(e => e.JobRole).ToListAsync();
|
||||
var jobRole = await _context.JobRoles.ToListAsync();
|
||||
|
||||
foreach (ProjectAllocation teamMember in projectteam)
|
||||
{
|
||||
var result1 = new EmployeeAttendanceVM()
|
||||
if (teamMember.Employee != null && teamMember.Employee.JobRole != null)
|
||||
{
|
||||
EmployeeAvatar = null,
|
||||
EmployeeId = teamMember.EmployeeId,
|
||||
FirstName = teamMember.Employee.FirstName,
|
||||
LastName = teamMember.Employee.LastName,
|
||||
JobRoleName = teamMember.Employee.JobRole.Name,
|
||||
};
|
||||
var result1 = new EmployeeAttendanceVM()
|
||||
{
|
||||
EmployeeAvatar = null,
|
||||
EmployeeId = teamMember.EmployeeId,
|
||||
FirstName = teamMember.Employee.FirstName,
|
||||
LastName = teamMember.Employee.LastName,
|
||||
JobRoleName = teamMember.Employee.JobRole.Name,
|
||||
};
|
||||
|
||||
//var member = emp.Where(e => e.Id == teamMember.EmployeeId);
|
||||
//var member = emp.Where(e => e.Id == teamMember.EmployeeId);
|
||||
|
||||
|
||||
attendance = lstAttendance.Find(x => x.EmployeeID == teamMember.EmployeeId) ?? new Attendance();
|
||||
if (attendance != null)
|
||||
{
|
||||
result1.Id = attendance.Id;
|
||||
result1.CheckInTime = attendance.InTime;
|
||||
result1.CheckOutTime = attendance.OutTime;
|
||||
result1.Activity = attendance.Activity;
|
||||
attendance = lstAttendance.Find(x => x.EmployeeID == teamMember.EmployeeId) ?? new Attendance();
|
||||
if (attendance != null)
|
||||
{
|
||||
result1.Id = attendance.Id;
|
||||
result1.CheckInTime = attendance.InTime;
|
||||
result1.CheckOutTime = attendance.OutTime;
|
||||
result1.Activity = attendance.Activity;
|
||||
}
|
||||
|
||||
result.Add(result1);
|
||||
}
|
||||
}
|
||||
|
||||
result.Sort(delegate (EmployeeAttendanceVM x, EmployeeAttendanceVM y)
|
||||
{
|
||||
//return x.FirstName.CompareTo(y.FirstName);
|
||||
return string.Compare(x.FirstName, y.FirstName, StringComparison.Ordinal);
|
||||
});
|
||||
}
|
||||
else if (hasSelfAttendancePermission)
|
||||
{
|
||||
Attendance lstAttendance = await _context.Attendes.FirstOrDefaultAsync(c => c.ProjectID == projectId && c.EmployeeID == LoggedInEmployee.Id && c.AttendanceDate.Date == forDate && c.TenantId == TenantId) ?? new Attendance();
|
||||
ProjectAllocation? projectAllocation = await _context.ProjectAllocations.Include(pa => pa.Employee).FirstOrDefaultAsync(pa => pa.ProjectId == projectId && pa.EmployeeId == LoggedInEmployee.Id && pa.TenantId == TenantId && pa.IsActive);
|
||||
if (projectAllocation != null)
|
||||
{
|
||||
EmployeeAttendanceVM result1 = new EmployeeAttendanceVM
|
||||
{
|
||||
Id = lstAttendance.Id,
|
||||
EmployeeAvatar = null,
|
||||
EmployeeId = projectAllocation.EmployeeId,
|
||||
FirstName = projectAllocation.Employee?.FirstName,
|
||||
LastName = projectAllocation.Employee?.LastName,
|
||||
JobRoleName = projectAllocation.Employee?.JobRole?.Name,
|
||||
CheckInTime = lstAttendance.InTime,
|
||||
CheckOutTime = lstAttendance.OutTime,
|
||||
Activity = lstAttendance.Activity
|
||||
};
|
||||
result.Add(result1);
|
||||
}
|
||||
}
|
||||
|
||||
result.Sort(delegate (EmployeeAttendanceVM x, EmployeeAttendanceVM y)
|
||||
{
|
||||
//return x.FirstName.CompareTo(y.FirstName);
|
||||
return string.Compare(x.FirstName, y.FirstName, StringComparison.Ordinal);
|
||||
});
|
||||
_logger.LogInfo("{count} Attendance records fetched successfully", result.Count);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(result, System.String.Format("{0} Attendance records fetched successfully", result.Count), 200));
|
||||
@ -282,7 +355,15 @@ namespace MarcoBMS.Services.Controllers
|
||||
public async Task<IActionResult> GetRequestRegularizeAttendance([FromQuery] Guid projectId, [FromQuery] bool IncludeInActive)
|
||||
{
|
||||
Guid TenantId = GetTenantId();
|
||||
Employee LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
var result = new List<EmployeeAttendanceVM>();
|
||||
var hasProjectPermission = await _permission.HasProjectPermission(LoggedInEmployee, projectId.ToString());
|
||||
|
||||
if (!hasProjectPermission)
|
||||
{
|
||||
_logger.LogWarning("Employee {EmployeeId} tries to access attendance of project {ProjectId}, but don't have access", LoggedInEmployee.Id, projectId);
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized access", "Unauthorized access", 404));
|
||||
}
|
||||
|
||||
List<Attendance> lstAttendance = await _context.Attendes.Where(c => c.ProjectID == projectId && c.Activity == ATTENDANCE_MARK_TYPE.REQUEST_REGULARIZE && c.TenantId == TenantId).ToListAsync();
|
||||
|
||||
|
||||
@ -1,13 +1,17 @@
|
||||
using System.Net;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.Authentication;
|
||||
using Marco.Pms.Model.Dtos;
|
||||
using Marco.Pms.Model.Dtos.Authentication;
|
||||
using Marco.Pms.Model.Dtos.Util;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Entitlements;
|
||||
using Marco.Pms.Model.Utilities;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using MarcoBMS.Services.Service;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -19,15 +23,17 @@ namespace MarcoBMS.Services.Controllers
|
||||
public class AuthController : ControllerBase
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly UserHelper _userHelper;
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly JwtSettings _jwtSettings;
|
||||
private readonly RefreshTokenService _refreshTokenService;
|
||||
private readonly IEmailSender _emailSender;
|
||||
private readonly IConfiguration _configuration;
|
||||
private readonly EmployeeHelper _employeeHelper;
|
||||
private readonly ILoggingService _logger;
|
||||
//string tenentId = "1";
|
||||
public AuthController(UserManager<ApplicationUser> userManager, ApplicationDbContext context, JwtSettings jwtSettings, RefreshTokenService refreshTokenService,
|
||||
IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper)
|
||||
IEmailSender emailSender, IConfiguration configuration, EmployeeHelper employeeHelper, UserHelper userHelper, ILoggingService logger)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_jwtSettings = jwtSettings;
|
||||
@ -36,146 +42,417 @@ namespace MarcoBMS.Services.Controllers
|
||||
_configuration = configuration;
|
||||
_employeeHelper = employeeHelper;
|
||||
_context = context;
|
||||
_userHelper = userHelper;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPost("login")]
|
||||
public async Task<IActionResult> Login([FromBody] LoginDto loginDto)
|
||||
{
|
||||
var user = await _context.ApplicationUsers.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
||||
|
||||
if (user != null)
|
||||
try
|
||||
{
|
||||
// Find user by email or phone number
|
||||
var user = await _context.ApplicationUsers
|
||||
.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("Login failed: User not found for input {Username}", loginDto.Username ?? string.Empty);
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
||||
}
|
||||
|
||||
// Check if the user is active
|
||||
if (!user.IsActive)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("User is In Active", "User is In Active", 400));
|
||||
_logger.LogWarning("Login failed: Inactive user attempted login - UserId: {UserId}", user.Id);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
|
||||
}
|
||||
|
||||
// Ensure the user's email is confirmed
|
||||
if (!user.EmailConfirmed)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Your email is not verified, Please verify your email", "Your email is not verified, Please verify your email", 400));
|
||||
_logger.LogWarning("Login failed: Email not confirmed for UserId: {UserId}", user.Id);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Email not verified", "Your email is not verified, please verify your email", 400));
|
||||
}
|
||||
if (await _userManager.CheckPasswordAsync(user, loginDto.Password ?? string.Empty))
|
||||
|
||||
// Validate the password
|
||||
if (!await _userManager.CheckPasswordAsync(user, loginDto.Password ?? string.Empty))
|
||||
{
|
||||
Employee emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
||||
//var refreshToken = GenerateRefreshToken();
|
||||
if (user.UserName == null) return NotFound(ApiResponse<object>.ErrorResponse("UserName Not found", "UserName Not found", 404)); ;
|
||||
|
||||
var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings);
|
||||
|
||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(new { token = token, refreshToken = refreshToken }, "User logged in successfully.", 200));
|
||||
_logger.LogWarning("Login failed: Incorrect password for UserId: {UserId}", user.Id);
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
||||
}
|
||||
|
||||
// Retrieve employee details
|
||||
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
||||
if (emp == null)
|
||||
{
|
||||
_logger.LogWarning("Login failed: No employee record found for UserId: {UserId}", user.Id);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Employee record not found", "Employee not found", 404));
|
||||
}
|
||||
|
||||
// Ensure UserName exists for JWT
|
||||
if (string.IsNullOrWhiteSpace(user.UserName))
|
||||
{
|
||||
_logger.LogWarning("Login failed: Username not found for UserId: {UserId}", user.Id);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Username not found", "Username not found", 404));
|
||||
}
|
||||
|
||||
// Generate tokens
|
||||
var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings);
|
||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
||||
|
||||
_logger.LogInfo("User login successful - UserId: {UserId}", user.Id);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(new
|
||||
{
|
||||
token,
|
||||
refreshToken
|
||||
}, "User logged in successfully.", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Unexpected error during login : {Error}", ex.Message);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("login-mobile")]
|
||||
public async Task<IActionResult> LoginMobile([FromBody] LoginDto loginDto)
|
||||
{
|
||||
// Validate input DTO
|
||||
if (loginDto == null || string.IsNullOrWhiteSpace(loginDto.Username) || string.IsNullOrWhiteSpace(loginDto.Password))
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Username or password is missing.", "Invalid request", 400));
|
||||
}
|
||||
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
||||
// Find user by email or phone number
|
||||
var user = await _context.ApplicationUsers
|
||||
.FirstOrDefaultAsync(u => u.Email == loginDto.Username || u.PhoneNumber == loginDto.Username);
|
||||
|
||||
// If user not found, return unauthorized
|
||||
if (user == null)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid username or password.", 401));
|
||||
}
|
||||
|
||||
// Check if user is inactive
|
||||
if (!user.IsActive)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("User is inactive", "User is inactive", 400));
|
||||
}
|
||||
|
||||
// Check if user email is not confirmed
|
||||
if (!user.EmailConfirmed)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Your email is not verified. Please verify your email.", "Email not verified", 400));
|
||||
}
|
||||
|
||||
// Validate password using ASP.NET Identity
|
||||
var isPasswordValid = await _userManager.CheckPasswordAsync(user, loginDto.Password);
|
||||
if (!isPasswordValid)
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid username or password.", "Invalid credentials", 401));
|
||||
}
|
||||
|
||||
// Check if username is missing
|
||||
if (string.IsNullOrWhiteSpace(user.UserName))
|
||||
{
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("UserName not found", "Username is missing", 404));
|
||||
}
|
||||
|
||||
// Get employee information for tenant context
|
||||
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
||||
if (emp == null)
|
||||
{
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Employee not found", "Employee details missing", 404));
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
var token = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings);
|
||||
|
||||
// Generate Refresh Token and store in DB
|
||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
||||
|
||||
// Fetch MPIN Token
|
||||
var mpinToken = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(user.Id) && p.TenantId == emp.TenantId);
|
||||
|
||||
// Combine all tokens in response
|
||||
var responseData = new
|
||||
{
|
||||
token,
|
||||
refreshToken,
|
||||
mpinToken = mpinToken?.MPINToken
|
||||
};
|
||||
|
||||
// Return success response
|
||||
return Ok(ApiResponse<object>.SuccessResponse(responseData, "User logged in successfully.", 200));
|
||||
}
|
||||
|
||||
[HttpPost("login-mpin")]
|
||||
public async Task<IActionResult> VerifyMPIN([FromBody] VerifyMPINDto verifyMPIN)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate the MPIN token and extract claims
|
||||
var claimsPrincipal = _refreshTokenService.ValidateToken(verifyMPIN.MPINToken, _jwtSettings);
|
||||
if (claimsPrincipal?.Identity == null || !claimsPrincipal.Identity.IsAuthenticated)
|
||||
{
|
||||
_logger.LogWarning("Invalid or unauthenticated MPIN token");
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid MPIN token", "Unauthorized", 401));
|
||||
}
|
||||
|
||||
string? tokenType = claimsPrincipal.FindFirst("token_type")?.Value;
|
||||
string? tokenTenantId = claimsPrincipal.FindFirst("TenantId")?.Value;
|
||||
string? tokenUserId = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
// Validate essential claims
|
||||
if (string.IsNullOrWhiteSpace(tokenType) || string.IsNullOrWhiteSpace(tokenTenantId) || string.IsNullOrWhiteSpace(tokenUserId))
|
||||
{
|
||||
_logger.LogWarning("MPIN token claims are incomplete");
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid token claims", "MPIN token does not match your identity", 401));
|
||||
}
|
||||
|
||||
Guid tenantId = Guid.Parse(tokenTenantId);
|
||||
|
||||
// Fetch employee by ID and tenant
|
||||
var requestEmployee = await _context.Employees
|
||||
.Include(e => e.ApplicationUser)
|
||||
.FirstOrDefaultAsync(e => e.Id == verifyMPIN.EmployeeId && e.TenantId == tenantId && e.ApplicationUserId == tokenUserId && e.IsActive);
|
||||
|
||||
if (requestEmployee == null || string.IsNullOrWhiteSpace(requestEmployee.ApplicationUserId))
|
||||
{
|
||||
_logger.LogWarning("Employee not found or invalid for verification - EmployeeId: {EmployeeId}", verifyMPIN.EmployeeId);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request", "Provided invalid employee information", 400));
|
||||
}
|
||||
|
||||
// Validate that the token belongs to the same employee making the request
|
||||
if (requestEmployee.ApplicationUserId != tokenUserId || tokenType != "mpin")
|
||||
{
|
||||
_logger.LogWarning("Token identity does not match employee info - EmployeeId: {EmployeeId}", requestEmployee.Id);
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Unauthorized", "MPIN token does not match your identity", 401));
|
||||
}
|
||||
|
||||
// Ensure MPIN input is valid
|
||||
if (string.IsNullOrWhiteSpace(verifyMPIN.MPIN))
|
||||
{
|
||||
_logger.LogWarning("MPIN not provided for EmployeeId: {EmployeeId}", requestEmployee.Id);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request", "MPIN not provided", 400));
|
||||
}
|
||||
|
||||
// Retrieve MPIN details
|
||||
var mpinDetails = await _context.MPINDetails
|
||||
.FirstOrDefaultAsync(p => p.UserId == Guid.Parse(requestEmployee.ApplicationUserId) && p.TenantId == tenantId);
|
||||
|
||||
if (mpinDetails == null)
|
||||
{
|
||||
_logger.LogWarning("MPIN not set for EmployeeId: {EmployeeId}", requestEmployee.Id);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("MPIN not set", "You have not set an MPIN", 400));
|
||||
}
|
||||
|
||||
// Compare hashed MPIN
|
||||
var providedMPINHash = ComputeSha256Hash(verifyMPIN.MPIN);
|
||||
if (providedMPINHash != mpinDetails.MPIN)
|
||||
{
|
||||
_logger.LogWarning("MPIN mismatch for EmployeeId: {EmployeeId}", requestEmployee.Id);
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("MPIN mismatch", "MPIN did not match", 401));
|
||||
}
|
||||
|
||||
// Generate new tokens
|
||||
var jwtToken = _refreshTokenService.GenerateJwtToken(requestEmployee.Email, tenantId, _jwtSettings);
|
||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(requestEmployee.ApplicationUserId, tenantId.ToString(), _jwtSettings);
|
||||
|
||||
_logger.LogInfo("MPIN verification successful - EmployeeId: {EmployeeId}", requestEmployee.Id);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(new
|
||||
{
|
||||
token = jwtToken,
|
||||
refreshToken
|
||||
}, "User logged in successfully.", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Unexpected error occurred while verifying MPIN : {Error}", ex.Message);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("logout")]
|
||||
public async Task<IActionResult> Logout([FromBody] LogoutDto logoutDto)
|
||||
{
|
||||
if (string.IsNullOrEmpty(logoutDto.RefreshToken))
|
||||
if (string.IsNullOrWhiteSpace(logoutDto.RefreshToken))
|
||||
{
|
||||
_logger.LogWarning("Logout failed: Refresh token is missing");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Refresh token is required", "Refresh token is required", 400));
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Revoke the refresh token
|
||||
bool isRevoked = await _refreshTokenService.RevokeRefreshTokenAsync(logoutDto.RefreshToken);
|
||||
|
||||
if (!isRevoked)
|
||||
{
|
||||
_logger.LogWarning("Logout failed: Invalid or expired refresh token");
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid or expired refresh token", "Invalid or expired refresh token", 401));
|
||||
}
|
||||
|
||||
|
||||
// Optional: Blacklist the access token (JWT)
|
||||
// Optional: Blacklist the JWT access token
|
||||
string jwtToken = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
|
||||
if (!string.IsNullOrEmpty(jwtToken))
|
||||
if (!string.IsNullOrWhiteSpace(jwtToken))
|
||||
{
|
||||
await _refreshTokenService.BlacklistJwtTokenAsync(jwtToken);
|
||||
_logger.LogInfo("JWT access token blacklisted successfully");
|
||||
}
|
||||
|
||||
_logger.LogInfo("User logged out successfully");
|
||||
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Logged out successfully", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// _logger.LogError(ex, "Error during logout");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Internal server error", ex.Message, 500));
|
||||
_logger.LogError("Unexpected error during logout : {Error}", ex.Message);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error occurred", ex.Message, 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("refresh-token")]
|
||||
public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenDto refreshTokenDto)
|
||||
{
|
||||
var refreshToken = await _refreshTokenService.GetRefreshToken(refreshTokenDto.RefreshToken);
|
||||
if (refreshToken == null || refreshToken.ExpiryDate < DateTime.UtcNow)
|
||||
if (string.IsNullOrWhiteSpace(refreshTokenDto.RefreshToken))
|
||||
{
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid or expired refresh token.", "Invalid or expired refresh token.", 401));
|
||||
_logger.LogWarning("Refresh token is missing from the request body.");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Refresh token is required.", "Missing refresh token.", 400));
|
||||
}
|
||||
|
||||
// Mark token as used
|
||||
await _refreshTokenService.MarkRefreshTokenAsUsed(refreshToken);
|
||||
try
|
||||
{
|
||||
// Step 1: Fetch and validate the refresh token
|
||||
var refreshToken = await _refreshTokenService.GetRefreshToken(refreshTokenDto.RefreshToken);
|
||||
if (refreshToken == null)
|
||||
{
|
||||
_logger.LogWarning("Refresh token not found in the database");
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid or expired refresh token.", "Token not found.", 401));
|
||||
}
|
||||
|
||||
// Generate new JWT token and refresh token
|
||||
var user = await _userManager.FindByIdAsync(refreshToken.UserId ?? string.Empty);
|
||||
if (user == null)
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "Invalid request.", 400));
|
||||
if (refreshToken.ExpiryDate < DateTime.UtcNow)
|
||||
{
|
||||
_logger.LogWarning("Refresh token expired");
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Refresh token expired.", "Token expired.", 401));
|
||||
}
|
||||
|
||||
Employee emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
||||
// Step 2: Mark the token as used
|
||||
await _refreshTokenService.MarkRefreshTokenAsUsed(refreshToken);
|
||||
_logger.LogInfo("Refresh token marked as used");
|
||||
|
||||
if (user.UserName == null) return NotFound(ApiResponse<object>.ErrorResponse("UserName Not found", "UserName Not found", 404));
|
||||
// Step 3: Validate and retrieve user
|
||||
var user = await _userManager.FindByIdAsync(refreshToken.UserId ?? string.Empty);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found for RefreshToken: {Token}", refreshTokenDto.RefreshToken);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "User not found.", 400));
|
||||
}
|
||||
|
||||
var newJwtToken = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings);
|
||||
var newRefreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
||||
if (string.IsNullOrWhiteSpace(user.UserName))
|
||||
{
|
||||
_logger.LogError("Username missing for user ID: {UserId}", user.Id);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("Username not found.", "Username not found.", 404));
|
||||
}
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(new { token = newJwtToken, refreshToken = newRefreshToken }, "User refresh token generated successfully.", 200));
|
||||
// Step 4: Fetch employee and generate new tokens
|
||||
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
||||
|
||||
var newJwtToken = _refreshTokenService.GenerateJwtToken(user.UserName, emp.TenantId, _jwtSettings);
|
||||
var newRefreshToken = await _refreshTokenService.CreateRefreshToken(user.Id, emp.TenantId.ToString(), _jwtSettings);
|
||||
|
||||
_logger.LogInfo("New access and refresh token issued for user: {UserId}", user.Id);
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(
|
||||
new { token = newJwtToken, refreshToken = newRefreshToken },
|
||||
"User refresh token generated successfully.",
|
||||
200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("An unexpected error occurred during token refresh. : {Error}", ex.Message);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error occurred.", ex.Message, 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("forgot-password")]
|
||||
public async Task<IActionResult> ForgotPassword([FromBody] ForgotPasswordDto forgotPasswordDto)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(forgotPasswordDto.Email))
|
||||
{
|
||||
_logger.LogWarning("ForgotPassword request received without email.");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Email is required.", "Email is required.", 400));
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByEmailAsync(forgotPasswordDto.Email);
|
||||
if (user == null)
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("User not found.", "User not found.", 404));
|
||||
if (user == null || user.Email == null)
|
||||
{
|
||||
_logger.LogWarning("ForgotPassword requested for non-existent or null-email user: {Email}", forgotPasswordDto.Email);
|
||||
// Do not disclose whether the email exists (security best practice)
|
||||
return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset link sent if the account exists.", 200));
|
||||
}
|
||||
|
||||
/* SEND USER REGISTRATION MAIL*/
|
||||
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
|
||||
try
|
||||
{
|
||||
// Generate token and build reset link
|
||||
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
|
||||
var resetLink = $"{_configuration["AppSettings:WebFrontendUrl"]}/reset-password?token={WebUtility.UrlEncode(token)}";
|
||||
|
||||
if (user.Email == null) return NotFound(ApiResponse<object>.ErrorResponse("Email Not found", "Email Not found", 404));
|
||||
// Send reset email
|
||||
await _emailSender.SendResetPasswordEmail(user.Email, user.UserName ?? "User", resetLink);
|
||||
|
||||
await _emailSender.SendResetPasswordEmail(user.Email, "", resetLink);
|
||||
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset link sent.", 200));
|
||||
_logger.LogInfo("Password reset link sent to user: {Email}", user.Email);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset link sent if the account exists.", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error while sending password reset email to: {Error}", ex.Message);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Error sending password reset email.", ex.Message, 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("reset-password")]
|
||||
public async Task<IActionResult> ResetPassword([FromBody] ResetPasswordDto model)
|
||||
{
|
||||
var user = await _userManager.FindByEmailAsync(model.Email ?? string.Empty);
|
||||
if (user == null)
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "Invalid request.", 400));
|
||||
_logger.LogInfo("Password reset request received for email: {Email}", model.Email ?? string.Empty);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(model.Email) || string.IsNullOrWhiteSpace(model.Token) || string.IsNullOrWhiteSpace(model.NewPassword))
|
||||
{
|
||||
_logger.LogWarning("Reset password failed due to missing input fields for email: {Email}", model.Email ?? string.Empty);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("All fields are required.", "Invalid input.", 400));
|
||||
}
|
||||
|
||||
var user = await _userManager.FindByEmailAsync(model.Email);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("Reset password failed - user not found for email: {Email}", model.Email);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "Invalid user.", 400));
|
||||
}
|
||||
|
||||
// var isTokenValid = await _userManager.VerifyUserTokenAsync(user,UserManager<ApplicationUser>.ResetPasswordTokenPurpose, model.ResetCode);
|
||||
var isTokenValid = await _userManager.VerifyUserTokenAsync(
|
||||
user,
|
||||
TokenOptions.DefaultProvider, // This is the token provider
|
||||
UserManager<ApplicationUser>.ResetPasswordTokenPurpose,
|
||||
WebUtility.UrlDecode(model.Token)
|
||||
);
|
||||
user,
|
||||
TokenOptions.DefaultProvider, // This is the token provider
|
||||
UserManager<ApplicationUser>.ResetPasswordTokenPurpose,
|
||||
WebUtility.UrlDecode(model.Token)
|
||||
);
|
||||
string token = "";
|
||||
|
||||
if (!isTokenValid)
|
||||
{
|
||||
_logger.LogWarning("Decoded token failed, retrying with raw token for email: {Email}", model.Email);
|
||||
|
||||
var isDecodedTokenValid = await _userManager.VerifyUserTokenAsync(
|
||||
user,
|
||||
TokenOptions.DefaultProvider, // This is the token provider
|
||||
TokenOptions.DefaultProvider,
|
||||
UserManager<ApplicationUser>.ResetPasswordTokenPurpose,
|
||||
model.Token
|
||||
model.Token
|
||||
);
|
||||
|
||||
if (!isDecodedTokenValid)
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "Invalid request.", 400));
|
||||
{
|
||||
_logger.LogWarning("Both decoded and raw token failed for email: {Email}", model.Email);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid request.", "Invalid or expired reset token.", 400));
|
||||
}
|
||||
|
||||
token = model.Token;
|
||||
}
|
||||
@ -184,29 +461,187 @@ namespace MarcoBMS.Services.Controllers
|
||||
token = WebUtility.UrlDecode(model.Token);
|
||||
}
|
||||
|
||||
|
||||
var result = await _userManager.ResetPasswordAsync(user, token, model.NewPassword ?? string.Empty);
|
||||
var result = await _userManager.ResetPasswordAsync(user, token, model.NewPassword);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
var errors = result.Errors.Select(e => e.Description).ToList();
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Failed to Change password", errors, 400));
|
||||
_logger.LogWarning("Reset password failed for user: {Email}. Errors: {Errors}", model.Email, string.Join(", ", errors));
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Failed to reset password.", errors, 400));
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Employee emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
||||
await _emailSender.SendResetPasswordSuccessEmail(user.Email ?? string.Empty, emp.FirstName + " " + emp.LastName);
|
||||
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(user.Id);
|
||||
string fullName = $"{emp.FirstName} {emp.LastName}".Trim();
|
||||
|
||||
await _emailSender.SendResetPasswordSuccessEmail(user.Email!, fullName);
|
||||
|
||||
_logger.LogInfo("Reset password success email sent to user: {Email}", model.Email);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse(ex.Message, ex.Message, 400));
|
||||
_logger.LogError("Error while sending reset password success email to user: {Error}", ex.Message);
|
||||
// Continue, do not fail because of email issue
|
||||
}
|
||||
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(result.Succeeded, "Password reset successfully.", 200));
|
||||
_logger.LogInfo("Password reset successful for user: {Email}", model.Email);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(true, "Password reset successfully.", 200));
|
||||
}
|
||||
|
||||
[HttpPost("send-otp")]
|
||||
public async Task<IActionResult> SendOtpEmail([FromBody] GenerateOTPDto generateOTP)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate input email
|
||||
if (string.IsNullOrWhiteSpace(generateOTP.Email))
|
||||
{
|
||||
_logger.LogWarning("Send OTP failed - Email is missing");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Email is required", "Invalid email", 400));
|
||||
}
|
||||
|
||||
// Fetch user by email
|
||||
var requestedUser = await _userManager.FindByEmailAsync(generateOTP.Email);
|
||||
string title = "Send OTP";
|
||||
|
||||
if (requestedUser != null && requestedUser.IsActive)
|
||||
{
|
||||
// Fetch employee details
|
||||
var requestedEmployee = await _context.Employees
|
||||
.FirstOrDefaultAsync(e => e.ApplicationUserId == requestedUser.Id);
|
||||
|
||||
// Generate a random 4-digit OTP
|
||||
string otp = new Random().Next(1000, 9999).ToString();
|
||||
|
||||
// Store OTP in database
|
||||
var otpDetails = new OTPDetails
|
||||
{
|
||||
UserId = Guid.Parse(requestedUser.Id),
|
||||
OTP = otp,
|
||||
ExpriesInSec = 600, // 10 minutes
|
||||
TimeStamp = DateTime.UtcNow,
|
||||
TenantId = requestedUser.TenantId
|
||||
};
|
||||
|
||||
_context.OTPDetails.Add(otpDetails);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Prepare email
|
||||
List<string> toEmails = [generateOTP.Email];
|
||||
string name = $"{requestedEmployee?.FirstName} {requestedEmployee?.LastName}".Trim();
|
||||
var mailTemplate = await _context.MailingList
|
||||
.FirstOrDefaultAsync(t => t.Title.ToLower() == title.ToLower());
|
||||
|
||||
string subject = mailTemplate?.Subject ?? string.Empty;
|
||||
string emailBody = mailTemplate?.Body ?? string.Empty;
|
||||
|
||||
// Send OTP via email
|
||||
await _emailSender.SendOTP(toEmails, emailBody, name, otp, subject);
|
||||
|
||||
_logger.LogInfo("OTP sent successfully to {Email}", generateOTP.Email);
|
||||
return Ok(ApiResponse<object>.SuccessResponse("Success", "OTP generated successfully", 200));
|
||||
}
|
||||
|
||||
_logger.LogWarning("Send OTP failed - Invalid or inactive user: {Email}", generateOTP.Email);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Provided invalid information", "User not found or inactive", 400));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("An unexpected error occurred while sending OTP to {Email} : {Error}", generateOTP.Email ?? "", ex.Message);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("An unexpected error occurred.", ex.Message, 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("login-otp")]
|
||||
public async Task<IActionResult> LoginWithOTP([FromBody] VerifyOTPDto verifyOTP)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Validate input
|
||||
if (string.IsNullOrWhiteSpace(verifyOTP.Email) ||
|
||||
string.IsNullOrWhiteSpace(verifyOTP.OTP) ||
|
||||
verifyOTP.OTP.Length != 4 ||
|
||||
!verifyOTP.OTP.All(char.IsDigit))
|
||||
{
|
||||
_logger.LogWarning("OTP login failed - invalid input provided");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid input", "Please provide a valid 4-digit OTP and Email", 400));
|
||||
}
|
||||
|
||||
// Fetch employee by email
|
||||
var requestEmployee = await _context.Employees
|
||||
.Include(e => e.ApplicationUser)
|
||||
.FirstOrDefaultAsync(e => e.Email == verifyOTP.Email && e.IsActive);
|
||||
|
||||
if (requestEmployee == null || string.IsNullOrWhiteSpace(requestEmployee.ApplicationUserId))
|
||||
{
|
||||
_logger.LogWarning("OTP login failed - user not found for email {Email}", verifyOTP.Email);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("User not found", "User not found", 404));
|
||||
}
|
||||
|
||||
Guid userId = Guid.Parse(requestEmployee.ApplicationUserId);
|
||||
|
||||
// Fetch most recent OTP
|
||||
var otpDetails = await _context.OTPDetails
|
||||
.Where(o => o.UserId == userId && o.TenantId == requestEmployee.TenantId)
|
||||
.OrderByDescending(o => o.TimeStamp)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
if (otpDetails == null)
|
||||
{
|
||||
_logger.LogWarning("OTP login failed - no OTP found for user {UserId}", userId);
|
||||
return NotFound(ApiResponse<object>.ErrorResponse("OTP not found", "No OTP was generated for this user", 404));
|
||||
}
|
||||
|
||||
// Validate OTP expiration
|
||||
var validUntil = otpDetails.TimeStamp.AddSeconds(otpDetails.ExpriesInSec);
|
||||
if (DateTime.UtcNow > validUntil || otpDetails.IsUsed)
|
||||
{
|
||||
_logger.LogWarning("OTP login failed - OTP expired for user {UserId}", userId);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("OTP expired", "The OTP has expired, please request a new one", 400));
|
||||
}
|
||||
|
||||
// Match OTP
|
||||
if (otpDetails.OTP != verifyOTP.OTP)
|
||||
{
|
||||
_logger.LogWarning("OTP login failed - incorrect OTP entered for user {UserId}", userId);
|
||||
return Unauthorized(ApiResponse<object>.ErrorResponse("Invalid OTP", "OTP did not match", 401));
|
||||
}
|
||||
|
||||
// Generate access and refresh tokens
|
||||
var accessToken = _refreshTokenService.GenerateJwtToken(
|
||||
requestEmployee.ApplicationUser?.UserName,
|
||||
requestEmployee.TenantId,
|
||||
_jwtSettings
|
||||
);
|
||||
|
||||
var refreshToken = await _refreshTokenService.CreateRefreshToken(
|
||||
requestEmployee.ApplicationUserId,
|
||||
requestEmployee.TenantId.ToString(),
|
||||
_jwtSettings
|
||||
);
|
||||
|
||||
// Fetch MPIN token if exists
|
||||
var mpinDetails = await _context.MPINDetails
|
||||
.FirstOrDefaultAsync(p => p.UserId == userId && p.TenantId == requestEmployee.TenantId);
|
||||
|
||||
// Build and return response
|
||||
var response = new
|
||||
{
|
||||
token = accessToken,
|
||||
refreshToken,
|
||||
mpinToken = mpinDetails?.MPINToken
|
||||
};
|
||||
otpDetails.IsUsed = true;
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInfo("OTP login successful for employee {EmployeeId}", requestEmployee.Id);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(response, "User logged in successfully.", 200));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("An unexpected error occurred during OTP login for email {Email} : {Error}", verifyOTP.Email ?? string.Empty, ex.Message);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("Unexpected error", ex.Message, 500));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("sendmail")]
|
||||
public async Task<IActionResult> SendEmail([FromBody] EmailDot emailDot)
|
||||
@ -242,5 +677,150 @@ namespace MarcoBMS.Services.Controllers
|
||||
|
||||
return Ok(ApiResponse<object>.SuccessResponse(new { }, "Password reset link sent.", 200));
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("change-password")]
|
||||
public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordDto changePassword)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get the currently logged-in user
|
||||
var loggedUser = await _userHelper.GetCurrentUserAsync();
|
||||
|
||||
// Validate email
|
||||
if (string.IsNullOrWhiteSpace(changePassword.Email))
|
||||
{
|
||||
_logger.LogWarning("Change password attempt failed - Email is missing");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Email is missing", "Email is missing", 400));
|
||||
}
|
||||
|
||||
// Find the user by email
|
||||
var requestedUser = await _userManager.FindByEmailAsync(changePassword.Email);
|
||||
if (requestedUser == null)
|
||||
{
|
||||
_logger.LogWarning("Change password attempt failed - Email not found: {Email}", changePassword.Email);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Invalid email", "User not found.", 400));
|
||||
}
|
||||
|
||||
// Validate the old password
|
||||
bool isOldPasswordCorrect = await _userManager.CheckPasswordAsync(requestedUser, changePassword.OldPassword ?? string.Empty);
|
||||
|
||||
// Ensure user identity and old password match
|
||||
if (loggedUser?.Email != requestedUser.Email || !isOldPasswordCorrect)
|
||||
{
|
||||
_logger.LogWarning("Change password denied - User {Email} provided incorrect credentials", changePassword.Email);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Incorrect credentials", "Invalid request.", 400));
|
||||
}
|
||||
|
||||
// Generate reset token and change password
|
||||
var resetToken = await _userManager.GeneratePasswordResetTokenAsync(requestedUser);
|
||||
var result = await _userManager.ResetPasswordAsync(requestedUser, resetToken, changePassword.NewPassword ?? string.Empty);
|
||||
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
var errors = result.Errors.Select(e => e.Description).ToList();
|
||||
_logger.LogError("Password reset failed for user {Email}. Errors: {Errors}", changePassword.Email, string.Join("; ", errors));
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Failed to change password", errors, 400));
|
||||
}
|
||||
|
||||
// Send confirmation email
|
||||
var emp = await _employeeHelper.GetEmployeeByApplicationUserID(requestedUser.Id);
|
||||
await _emailSender.SendResetPasswordSuccessEmail(requestedUser.Email ?? string.Empty, $"{emp.FirstName} {emp.LastName}");
|
||||
|
||||
_logger.LogInfo("Password changed successfully for user {Email}", requestedUser.Email ?? string.Empty);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(true, "Password changed successfully.", 200));
|
||||
}
|
||||
catch (Exception exp)
|
||||
{
|
||||
_logger.LogError("An unexpected error occurred while changing password : {Error}", exp.Message);
|
||||
return StatusCode(500, ApiResponse<object>.ErrorResponse("An unexpected error occurred.", exp.Message, 500));
|
||||
}
|
||||
}
|
||||
|
||||
[Authorize]
|
||||
[HttpPost("generate-mpin")]
|
||||
public async Task<IActionResult> GenerateMPIN([FromBody] GenerateMPINDto generateMPINDto)
|
||||
{
|
||||
Guid tenantId = _userHelper.GetTenantId();
|
||||
Employee loggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
|
||||
// Get the employee for whom MPIN is being generated
|
||||
var requestEmployee = await _context.Employees
|
||||
.Include(e => e.ApplicationUser)
|
||||
.FirstOrDefaultAsync(e => e.Id == generateMPINDto.EmployeeId && e.TenantId == tenantId);
|
||||
|
||||
// Validate employee and MPIN input
|
||||
if (requestEmployee == null || string.IsNullOrWhiteSpace(generateMPINDto.MPIN) || generateMPINDto.MPIN.Length != 6 || !generateMPINDto.MPIN.All(char.IsDigit))
|
||||
{
|
||||
_logger.LogError("Employee {EmployeeId} provided invalid information to generate MPIN", loggedInEmployee.Id);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Provided invalid information", "Provided invalid information", 400));
|
||||
}
|
||||
|
||||
// Ensure the logged-in user is only generating their own MPIN
|
||||
if (requestEmployee.Id != loggedInEmployee.Id)
|
||||
{
|
||||
_logger.LogWarning("Employee {EmployeeId} tried to set MPIN for a different employee", loggedInEmployee.Id);
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("You can't create MPIN for another employee", "Unauthorized MPIN creation", 400));
|
||||
}
|
||||
|
||||
// Generate hash and token
|
||||
string mpinHash = ComputeSha256Hash(generateMPINDto.MPIN);
|
||||
string mpinToken = _refreshTokenService.CreateMPINToken(
|
||||
requestEmployee.ApplicationUserId,
|
||||
requestEmployee.TenantId.ToString(),
|
||||
_jwtSettings
|
||||
);
|
||||
|
||||
// Prepare MPIN entity
|
||||
Guid userId = Guid.Parse(requestEmployee.ApplicationUserId ?? string.Empty);
|
||||
var existingMPIN = await _context.MPINDetails.FirstOrDefaultAsync(p => p.UserId == userId && p.TenantId == tenantId);
|
||||
|
||||
if (existingMPIN == null)
|
||||
{
|
||||
// Add new MPIN record
|
||||
var mPINDetails = new MPINDetails
|
||||
{
|
||||
UserId = userId,
|
||||
MPIN = mpinHash,
|
||||
MPINToken = mpinToken,
|
||||
TimeStamp = DateTime.UtcNow,
|
||||
TenantId = tenantId
|
||||
};
|
||||
|
||||
_context.MPINDetails.Add(mPINDetails);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInfo("MPIN generated successfully for employee {EmployeeId}", requestEmployee.Id);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(mpinToken, "MPIN generated successfully", 200));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update existing MPIN record
|
||||
existingMPIN.MPIN = mpinHash;
|
||||
existingMPIN.MPINToken = mpinToken;
|
||||
existingMPIN.TimeStamp = DateTime.UtcNow;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInfo("MPIN updated successfully for employee {EmployeeId}", requestEmployee.Id);
|
||||
return Ok(ApiResponse<object>.SuccessResponse(mpinToken, "MPIN updated successfully", 200));
|
||||
}
|
||||
}
|
||||
private static string ComputeSha256Hash(string rawData)
|
||||
{
|
||||
using (SHA256 sha256 = SHA256.Create())
|
||||
{
|
||||
// Convert the input string to bytes and compute the hash
|
||||
byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(rawData));
|
||||
|
||||
// Convert byte array to a readable hex string
|
||||
StringBuilder builder = new StringBuilder();
|
||||
foreach (var b in bytes)
|
||||
builder.Append(b.ToString("x2"));
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,13 +25,17 @@ namespace MarcoBMS.Services.Controllers
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly UserHelper _userHelper;
|
||||
private readonly ILoggingService _logger;
|
||||
private readonly RolesHelper _rolesHelper;
|
||||
private readonly ProjectsHelper _projectsHelper;
|
||||
|
||||
|
||||
public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger)
|
||||
public ProjectController(ApplicationDbContext context, UserHelper userHelper, ILoggingService logger, RolesHelper rolesHelper, ProjectsHelper projectHelper)
|
||||
{
|
||||
_context = context;
|
||||
_userHelper = userHelper;
|
||||
_logger = logger;
|
||||
_rolesHelper = rolesHelper;
|
||||
_projectsHelper = projectHelper;
|
||||
}
|
||||
[HttpGet("list")]
|
||||
public async Task<IActionResult> GetAll()
|
||||
@ -46,7 +50,22 @@ namespace MarcoBMS.Services.Controllers
|
||||
|
||||
}
|
||||
Guid tenantId = _userHelper.GetTenantId();
|
||||
List<Project> projects = await _context.Projects.Where(c => c.TenantId == tenantId).ToListAsync();
|
||||
var LoggedInEmployee = await _userHelper.GetCurrentEmployeeAsync();
|
||||
List<FeaturePermission> featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeID(LoggedInEmployee.Id);
|
||||
string[] projectsId = [];
|
||||
List<Project> projects = new List<Project>();
|
||||
|
||||
/* User with permission manage project can see all projects */
|
||||
if (featurePermission != null && featurePermission.Exists(c => c.Id.ToString() == "172fc9b6-755b-4f62-ab26-55c34a330614"))
|
||||
{
|
||||
projects = await _projectsHelper.GetAllProjectByTanentID(LoggedInEmployee.TenantId);
|
||||
}
|
||||
else
|
||||
{
|
||||
List<ProjectAllocation> allocation = await _projectsHelper.GetProjectByEmployeeID(LoggedInEmployee.Id);
|
||||
projectsId = allocation.Select(c => c.ProjectId.ToString()).ToArray();
|
||||
projects = await _context.Projects.Where(c => projectsId.Contains(c.Id.ToString()) && c.TenantId == tenantId).ToListAsync();
|
||||
}
|
||||
|
||||
|
||||
List<ProjectListVM> response = new List<ProjectListVM>();
|
||||
|
||||
@ -45,7 +45,6 @@ namespace Marco.Pms.Services.Controllers
|
||||
Recipient = mailDetailsDto.Recipient,
|
||||
Schedule = mailDetailsDto.Schedule,
|
||||
MailListId = mailDetailsDto.MailListId,
|
||||
Subject = mailDetailsDto.Subject,
|
||||
TenantId = tenantId
|
||||
};
|
||||
_context.MailDetails.Add(mailDetails);
|
||||
@ -57,10 +56,22 @@ namespace Marco.Pms.Services.Controllers
|
||||
public async Task<IActionResult> AddMailTemplate([FromBody] MailTemeplateDto mailTemeplateDto)
|
||||
{
|
||||
Guid tenantId = _userHelper.GetTenantId();
|
||||
if (string.IsNullOrWhiteSpace(mailTemeplateDto.Body) && string.IsNullOrWhiteSpace(mailTemeplateDto.Title))
|
||||
{
|
||||
_logger.LogWarning("User tries to set email template but send invalid data");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Provided Invalid data", "Provided Invalid data", 400));
|
||||
}
|
||||
var existngTemalate = await _context.MailingList.FirstOrDefaultAsync(t => t.Title.ToLower() == mailTemeplateDto.Title.ToLower());
|
||||
if (existngTemalate != null)
|
||||
{
|
||||
_logger.LogWarning("User tries to set email template, but title already existed in database");
|
||||
return BadRequest(ApiResponse<object>.ErrorResponse("Email title is already existed", "Email title is already existed", 400));
|
||||
}
|
||||
MailingList mailingList = new MailingList
|
||||
{
|
||||
Title = mailTemeplateDto.Title,
|
||||
Body = mailTemeplateDto.Body,
|
||||
Subject = mailTemeplateDto.Subject,
|
||||
Keywords = mailTemeplateDto.Keywords,
|
||||
TenantId = tenantId
|
||||
};
|
||||
@ -92,7 +103,8 @@ namespace Marco.Pms.Services.Controllers
|
||||
ProjectId = g.Key.ProjectId,
|
||||
MailListId = g.Key.MailListId,
|
||||
Recipients = g.Select(m => m.Recipient).Distinct().ToList(),
|
||||
MailBody = g.FirstOrDefault()?.MailBody?.Body ?? ""
|
||||
MailBody = g.FirstOrDefault()?.MailBody?.Body ?? "",
|
||||
Subject = g.FirstOrDefault()?.MailBody?.Subject ?? string.Empty,
|
||||
})
|
||||
.ToList();
|
||||
|
||||
@ -104,7 +116,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
await semaphore.WaitAsync();
|
||||
try
|
||||
{
|
||||
var response = await GetProjectStatistics(mailDetail.ProjectId, mailDetail.Recipients, mailDetail.MailBody, tenantId);
|
||||
var response = await GetProjectStatistics(mailDetail.ProjectId, mailDetail.Recipients, mailDetail.MailBody, mailDetail.Subject, tenantId);
|
||||
if (response.StatusCode == 200)
|
||||
Interlocked.Increment(ref successCount);
|
||||
else if (response.StatusCode == 404)
|
||||
@ -137,7 +149,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
/// <param name="projectId">The ID of the project.</param>
|
||||
/// <param name="recipientEmail">The email address of the recipient.</param>
|
||||
/// <returns>An ApiResponse indicating the success or failure of retrieving statistics and sending the email.</returns>
|
||||
private async Task<ApiResponse<object>> GetProjectStatistics(Guid projectId, List<string> recipientEmails, string body, Guid tenantId)
|
||||
private async Task<ApiResponse<object>> GetProjectStatistics(Guid projectId, List<string> recipientEmails, string body, string subject, Guid tenantId)
|
||||
{
|
||||
DateTime reportDate = DateTime.UtcNow.AddDays(-1).Date;
|
||||
|
||||
@ -303,7 +315,7 @@ namespace Marco.Pms.Services.Controllers
|
||||
statisticReport.PerformedAttendance = performedAttendance;
|
||||
|
||||
// Send Email
|
||||
var emailBody = await _emailSender.SendProjectStatisticsEmail(recipientEmails, body, statisticReport);
|
||||
var emailBody = await _emailSender.SendProjectStatisticsEmail(recipientEmails, body, subject, statisticReport);
|
||||
var employee = await _context.Employees.FirstOrDefaultAsync(e => e.Email != null && recipientEmails.Contains(e.Email)) ?? new Employee();
|
||||
|
||||
List<MailLog> mailLogs = new List<MailLog>();
|
||||
|
||||
@ -173,7 +173,7 @@
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color: #f9f9f9;"><![endif]-->
|
||||
<!--<div>Top Spacing</div>-->
|
||||
|
||||
<div class="u-row-container" style="padding: 0px;background-color: #f9f9f9">
|
||||
<div class="u-row-container" style="padding: 0px; background-color: #f9f9f9; page-break-before: always;">
|
||||
<div class="u-row"
|
||||
style="margin: 0 auto;min-width: 320px;max-width: 95%;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #f9f9f9;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
@ -338,7 +338,6 @@
|
||||
<!--<p style="font-size: 14px; line-height: 140%; text-align: center;"><span style="font-size: 28px; line-height: 39.2px; color: #ffffff; font-family: Lato, sans-serif;">{{PROJECT_NAME}} </span></p>.-->
|
||||
<p style="font-size: 14px; line-height: 140%; text-align: center;">
|
||||
<span style="font-size: 28px; line-height: 39.2px; color: #ffffff; font-family: Lato, sans-serif;">
|
||||
Raja
|
||||
{{PROJECT_NAME}}
|
||||
</span>
|
||||
</p>
|
||||
@ -363,7 +362,7 @@
|
||||
<!--<div>Mail Body</div>-->
|
||||
|
||||
|
||||
<div class="u-row-container" style="padding: 0px;background-color: transparent">
|
||||
<div class="u-row-container" style="padding: 0px; background-color: transparent; page-break-before: always; page-break-after: always;">
|
||||
<div class="u-row"
|
||||
style="margin: 0 auto;min-width: 320px;max-width: 95%;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #ffffff;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
@ -384,9 +383,9 @@
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:40px 10px 30px;font-family:'Lato',sans-serif;"
|
||||
align="left">
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: center; word-wrap: break-word;">
|
||||
<span style="font-size: 18px; line-height: 25.2px; color: #666666;">
|
||||
Project Status Reported - Generated at {{TIMESTAMP}}
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: start; word-wrap: break-word;">
|
||||
<span style="font-size: 10px; line-height: 25.2px; color: #666666;">
|
||||
* Project Status Reported - Generated at {{TIMESTAMP}} UTC
|
||||
</span>
|
||||
<table cellpadding="1" cellspacing="0" width="100%"
|
||||
border="0" style="margin-top: 15px">
|
||||
@ -394,7 +393,7 @@
|
||||
<td class="column" style="text-align:center">
|
||||
<div style="border: 1px solid #d5d5d5; border-radius: 10px; margin: 10px 10px; padding: 10px; height: 135px !important">
|
||||
<div style="font-size: 18px; color: #525b75 ">
|
||||
Todays Attendane
|
||||
Todays Attendance
|
||||
</div>
|
||||
<div style="font-size: 25px; color: #bc3803;margin:20px 20px 0px!important; font-weight:bold; ">
|
||||
{{TODAYS_ATTENDANCES}} /
|
||||
@ -411,7 +410,7 @@
|
||||
Daily Tasks Completed
|
||||
</div>
|
||||
<div style="font-size: 25px; color: #bc3803; margin: 20px 20px 0px !important; font-weight: bold;">
|
||||
{{TODAYS_COMPLETED}} /
|
||||
{{TODAYS_COMPLETED}} /
|
||||
{{TODAYS_PLANNED}}
|
||||
</div>
|
||||
<span style="font-size: 10px; color: #0984e3; font-weight: bold;">
|
||||
@ -478,13 +477,47 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="u-row-container" style="padding: 0px; background-color: transparent; page-break-before: always; page-break-after: always;">
|
||||
<div class="u-row"
|
||||
style="margin: 0 auto;min-width: 320px;max-width: 95%;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #ffffff;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: transparent;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:600px;"><tr style="background-color: #ffffff;"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="600" style="width: 600px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-100"
|
||||
style="max-width: 320px;min-width: 600px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div style="box-sizing: border-box; height: 100%; padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation"
|
||||
cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr style="page-break-before: always;">
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:0px 1px 30px;font-family:'Lato',sans-serif;"
|
||||
align="left">
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: center; word-wrap: break-word;">
|
||||
<span style="font-size: 18px; line-height: 25.2px; color: #666666;">
|
||||
Team
|
||||
Available On Site Today
|
||||
Available On Site {{DATE}}
|
||||
</span>
|
||||
<table cellpadding="1" cellspacing="0" width="100%"
|
||||
border="0">
|
||||
@ -493,63 +526,137 @@
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="u-row-container" style="padding: 0px; background-color: transparent; page-break-before: always; page-break-after: always;">
|
||||
<div class="u-row"
|
||||
style="margin: 0 auto;min-width: 320px;max-width: 95%;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #ffffff;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: transparent;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:600px;"><tr style="background-color: #ffffff;"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="600" style="width: 600px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-100"
|
||||
style="max-width: 320px;min-width: 600px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div style="box-sizing: border-box; height: 100%; padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation"
|
||||
cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr style="page-break-before: always;">
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:0px 10px 30px;font-family:'Lato',sans-serif;"
|
||||
align="left">
|
||||
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: center; word-wrap: break-word;">
|
||||
<span style="font-size: 18px; line-height: 25.2px; color: #666666;margin:10px">
|
||||
Activities
|
||||
(Tasks) Performed Today
|
||||
(Tasks) Performed {{DATE}}
|
||||
</span> <br />
|
||||
|
||||
<table cellpadding="1" cellspacing="0" width="100%"
|
||||
border="1">
|
||||
<tr style="vertical-align:middle">
|
||||
<th style="text-align:center">
|
||||
Activity/ <br />
|
||||
Location
|
||||
</th>
|
||||
<th style="text-align:center">
|
||||
Assigned
|
||||
Today/<br /> Pending
|
||||
</th>
|
||||
<th style="text-align:center">
|
||||
Completed Today
|
||||
</th>
|
||||
<th style="text-align:center">Date </th>
|
||||
<th style="text-align:center">Team Members</th>
|
||||
<th style="text-align:center">Comment</th>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
{{PERFORMED_TASK}}
|
||||
</table>
|
||||
<table cellpadding="1" cellspacing="0" width="100%"
|
||||
border="1">
|
||||
<thead>
|
||||
<tr style="vertical-align:middle">
|
||||
<th style="text-align:center">
|
||||
Activity/ <br />
|
||||
Location
|
||||
</th>
|
||||
<th style="text-align:center">
|
||||
Assigned
|
||||
Today/<br /> Pending
|
||||
</th>
|
||||
<th style="text-align:center">
|
||||
Completed Today
|
||||
</th>
|
||||
<th style="text-align:center">Date </th>
|
||||
<th style="text-align:center">Team Members</th>
|
||||
<th style="text-align:center">Comment</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr></tr>
|
||||
<tbody>
|
||||
{{PERFORMED_TASK}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<tr>
|
||||
|
||||
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="u-row-container" style="padding: 0px; background-color: transparent; page-break-before: always; page-break-after: always;">
|
||||
<div class="u-row"
|
||||
style="margin: 0 auto;min-width: 320px;max-width: 95%;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #ffffff;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: transparent;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:600px;"><tr style="background-color: #ffffff;"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="600" style="width: 600px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-100"
|
||||
style="max-width: 320px;min-width: 600px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
<div style="box-sizing: border-box; height: 100%; padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation"
|
||||
cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
|
||||
<tr style="page-break-before: always;">
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:0px 10px 30px;font-family:'Lato',sans-serif;"
|
||||
align="left">
|
||||
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: center; word-wrap: break-word;">
|
||||
<span style="font-size: 18px; line-height: 25.2px; color: #666666;margin:10px">
|
||||
Attendance
|
||||
Performed Today
|
||||
Performed {{DATE}}
|
||||
</span> <br />
|
||||
|
||||
<table cellpadding="1" cellspacing="0" width="100%"
|
||||
border="1">
|
||||
<tr style="vertical-align:middle">
|
||||
<th style="text-align:center">Name</th>
|
||||
<th style="text-align:center">Job Role</th>
|
||||
<th style="text-align:center">Check In</th>
|
||||
<th style="text-align:center">Check Out </th>
|
||||
<th style="text-align:center">Comment</th>
|
||||
</tr>
|
||||
<tr></tr>
|
||||
{{PERFORMED_ATTENDANCE}}
|
||||
</table>
|
||||
<table cellpadding="1" cellspacing="0" width="100%"
|
||||
border="1">
|
||||
<thead>
|
||||
<tr style="vertical-align:middle">
|
||||
<th style="text-align:center">Name</th>
|
||||
<th style="text-align:center">Job Role</th>
|
||||
<th style="text-align:center">Check In</th>
|
||||
<th style="text-align:center">Check Out </th>
|
||||
<th style="text-align:center">Comment</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr></tr>
|
||||
<tbody>
|
||||
{{PERFORMED_ATTENDANCE}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@ -572,7 +679,7 @@
|
||||
<!--<div>Contact</div>-->
|
||||
|
||||
|
||||
<div class="u-row-container" style="padding: 0px;background-color: transparent">
|
||||
<div class="u-row-container" style="padding: 0px; background-color: transparent; page-break-before: always;">
|
||||
<div class="u-row"
|
||||
style="margin: 0 auto;min-width: 320px;max-width: 95%;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #e93f32;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
@ -725,18 +832,18 @@
|
||||
</table>
|
||||
|
||||
<!--<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:5px 10px 10px;font-family:'Lato',sans-serif;" align="left">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:5px 10px 10px;font-family:'Lato',sans-serif;" align="left">
|
||||
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: left; word-wrap: break-word;">
|
||||
<p style="line-height: 140%; font-size: 14px;"><span style="font-size: 14px; line-height: 19.6px;"><span style="color: #ecf0f1; font-size: 14px; line-height: 19.6px;"><span style="line-height: 19.6px; font-size: 14px;">Marco AIoT Technologies Pvt. Ltd. © All Rights Reserved</span></span></span></p>
|
||||
</div>
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: left; word-wrap: break-word;">
|
||||
<p style="line-height: 140%; font-size: 14px;"><span style="font-size: 14px; line-height: 19.6px;"><span style="color: #ecf0f1; font-size: 14px; line-height: 19.6px;"><span style="line-height: 19.6px; font-size: 14px;">Marco AIoT Technologies Pvt. Ltd. © All Rights Reserved</span></span></span></p>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>-->
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
@ -785,14 +892,14 @@
|
||||
</p>
|
||||
|
||||
<!--<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;border-top: 1px solid #e93f32;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top">
|
||||
<td style="word-break: break-word;border-collapse: collapse !important;vertical-align: top;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top">
|
||||
<td style="word-break: break-word;border-collapse: collapse !important;vertical-align: top;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>-->
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
538
Marco.Pms.Services/EmailTemplates/send-otp.html
Normal file
538
Marco.Pms.Services/EmailTemplates/send-otp.html
Normal file
@ -0,0 +1,538 @@
|
||||
|
||||
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
|
||||
<head>
|
||||
<!--[if gte mso 9]>
|
||||
<xml>
|
||||
<o:OfficeDocumentSettings>
|
||||
<o:AllowPNG/>
|
||||
<o:PixelsPerInch>96</o:PixelsPerInch>
|
||||
</o:OfficeDocumentSettings>
|
||||
</xml>
|
||||
<![endif]-->
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="x-apple-disable-message-reformatting">
|
||||
<!--[if !mso]><!-->
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]-->
|
||||
<title></title>
|
||||
|
||||
<style type="text/css">
|
||||
|
||||
@media only screen and (min-width: 620px) {
|
||||
.u-row {
|
||||
width: 600px !important;
|
||||
}
|
||||
|
||||
.u-row .u-col {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
|
||||
.u-row .u-col-50 {
|
||||
width: 300px !important;
|
||||
}
|
||||
|
||||
|
||||
.u-row .u-col-100 {
|
||||
width: 600px !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 620px) {
|
||||
.u-row-container {
|
||||
max-width: 100% !important;
|
||||
padding-left: 0px !important;
|
||||
padding-right: 0px !important;
|
||||
}
|
||||
|
||||
.u-row {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.u-row .u-col {
|
||||
display: block !important;
|
||||
width: 100% !important;
|
||||
min-width: 320px !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.u-row .u-col > div {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
|
||||
.u-row .u-col img {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
table, td, tr {
|
||||
border-collapse: collapse;
|
||||
vertical-align: top
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.ie-container table, .mso-container table {
|
||||
table-layout: fixed
|
||||
}
|
||||
|
||||
* {
|
||||
line-height: inherit
|
||||
}
|
||||
|
||||
a[x-apple-data-detectors=true] {
|
||||
color: inherit !important;
|
||||
text-decoration: none !important
|
||||
}
|
||||
|
||||
|
||||
table, td {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
#u_body a {
|
||||
color: #e93f32;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
||||
<!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato:400,700" rel="stylesheet" type="text/css">
|
||||
<link href="https://fonts.googleapis.com/css?family=Lato:400,700" rel="stylesheet" type="text/css"><!--<![endif]-->
|
||||
|
||||
</head>
|
||||
|
||||
<body class="clean-body u_body" style="margin: 0;padding: 0;-webkit-text-size-adjust: 100%;background-color: #f9f9f9;color: #000000">
|
||||
<!--[if IE]><div class="ie-container"><![endif]-->
|
||||
<!--[if mso]><div class="mso-container"><![endif]-->
|
||||
<table id="u_body" style="border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;min-width: 320px;Margin: 0 auto;background-color: #f9f9f9;width:100%" cellpadding="0" cellspacing="0">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top">
|
||||
<td style="word-break: break-word;border-collapse: collapse !important;vertical-align: top">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color: #f9f9f9;"><![endif]-->
|
||||
<!--<div>Top Spacing</div>-->
|
||||
|
||||
<div class="u-row-container" style="padding: 0px;background-color: #f9f9f9">
|
||||
<div class="u-row" style="margin: 0 auto;min-width: 320px;max-width: 600px;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #f9f9f9;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: #f9f9f9;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:600px;"><tr style="background-color: #f9f9f9;"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="600" style="width: 600px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-100" style="max-width: 320px;min-width: 600px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!--><div style="box-sizing: border-box; height: 100%; padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:15px;font-family:'Lato',sans-serif;" align="left">
|
||||
|
||||
<table height="0px" align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;border-top: 1px solid #f9f9f9;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top">
|
||||
<td style="word-break: break-word;border-collapse: collapse !important;vertical-align: top;font-size: 0px;line-height: 0px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%">
|
||||
Sita
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!--<div>Logo Block</div>-->
|
||||
|
||||
<div class="u-row-container" style="padding: 0px;background-color: transparent">
|
||||
<div class="u-row" style="margin: 0 auto;min-width: 320px;max-width: 600px;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #ffffff;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: transparent;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:600px;"><tr style="background-color: #ffffff;"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="600" style="width: 600px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-100" style="max-width: 320px;min-width: 600px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!--><div style="box-sizing: border-box; height: 100%; padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:25px 10px;font-family:'Lato',sans-serif;" align="left">
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="padding-right: 0px;padding-left: 0px;" align="center">
|
||||
|
||||
<img border="0" src="https://stageapi.marcoaiot.com/logos/marco-aiot-tech-logo.jpg" alt="Image" title="Image" style="outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;clear: both;display: inline-block !important;border: none;height: auto;float: none;width: 29%;max-width: 168.2px;" width="168" />
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!--<div>Title Block</div>-->
|
||||
|
||||
|
||||
<div class="u-row-container" style="padding: 0px;background-color: transparent">
|
||||
<div class="u-row" style="margin: 0 auto; min-width: 320px; max-width: 600px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: #f46b61;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: transparent;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:600px;"><tr style="background-color: #e93f32;"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="600" style="width: 600px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-100" style="max-width: 320px;min-width: 600px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!--><div style="box-sizing: border-box; height: 100%; padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:35px 10px 10px;font-family:'Lato',sans-serif;" align="left">
|
||||
|
||||
<table width="100%" cellpadding="0" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<td style="padding-right: 0px;padding-left: 0px;" align="center">
|
||||
|
||||
<img border="0" src="https://cdn.templates.unlayer.com/assets/1593141680866-reset.png" alt="Image" title="Image" style="outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;clear: both;display: inline-block !important;border: none;height: auto;float: none;width: 10%;max-width: 58px;" width="58" />
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:0px 10px 30px;font-family:'Lato',sans-serif;" align="left">
|
||||
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: left; word-wrap: break-word;">
|
||||
<p style="font-size: 14px; line-height: 140%; text-align: center;"><span style="font-size: 28px; line-height: 39.2px; color: #ffffff; font-family: Lato, sans-serif;">Your OTP Code for Verification</span></p>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!--<div>Mail Body</div>-->
|
||||
|
||||
|
||||
<div class="u-row-container" style="padding: 0px;background-color: transparent">
|
||||
<div class="u-row" style="margin: 0 auto;min-width: 320px;max-width: 600px;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #ffffff;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: transparent;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:600px;"><tr style="background-color: #ffffff;"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="600" style="width: 600px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-100" style="max-width: 320px;min-width: 600px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!--><div style="box-sizing: border-box; height: 100%; padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:40px 40px 30px;font-family:'Lato',sans-serif;" align="left">
|
||||
|
||||
<p>Dear {{NAME}},</p>
|
||||
<br/>
|
||||
<p>Your One-Time Password (OTP) for verification is: <strong style="color:#2e6c80;">{{OTP}}</strong></p>
|
||||
<br/>
|
||||
<p>This OTP is valid for the next <strong>10 minutes</strong>. Please do not share this code with anyone.</p>
|
||||
<br/>
|
||||
<p>If you did not request this code, please ignore this email or contact our support team immediately.</p>
|
||||
|
||||
<br>
|
||||
<p>
|
||||
Thank you,<br>
|
||||
Marco AIoT Team
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!--<div>Contact</div>-->
|
||||
|
||||
|
||||
<div class="u-row-container" style="padding: 0px;background-color: transparent">
|
||||
<div class="u-row" style="margin: 0 auto;min-width: 320px;max-width: 600px;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #e93f32;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: transparent;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:600px;"><tr style="background-color: #e93f32;"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="300" style="width: 300px;padding: 20px 20px 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-50" style="max-width: 320px;min-width: 300px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!--><div style="box-sizing: border-box; height: 100%; padding: 20px 0px 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:10px;font-family:'Lato',sans-serif;" align="left">
|
||||
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: left; word-wrap: break-word;">
|
||||
<!--<p style="font-size: 14px; line-height: 140%;"><span style="font-size: 16px; line-height: 22.4px; color: #ecf0f1;">Contact</span></p>-->
|
||||
<!--<p style="font-size: 14px; line-height: 140%;"><span style="font-size: 14px; line-height: 19.6px; color: #ecf0f1;">2nd Floor, Fullora Building, Tejas CHS, Dahanukar Colony, Kothrud, Pune (INDIA) - 411038</span></p>-->
|
||||
<p style="font-size: 14px; line-height: 140%;"><span style="font-size: 14px; line-height: 19.6px; color: #ecf0f1;">Contact Us: <a href="mailto:info@marcoaiot.com" style="color:#ffff" target="_blank">info@marcoaiot.com</a></span></p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="300" style="width: 300px;padding: 0px 0px 0px 20px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-50" style="max-width: 320px;min-width: 300px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!--><div style="box-sizing: border-box; height: 100%; padding: 0px 0px 0px 20px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:25px 10px 10px;font-family:'Lato',sans-serif;" align="left">
|
||||
|
||||
<div align="right" style="direction: ltr;">
|
||||
<div style="display: table; max-width:187px;">
|
||||
<!--[if (mso)|(IE)]><table width="187" cellpadding="0" cellspacing="0" border="0"><tr><td style="border-collapse:collapse;" align="left"><table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-collapse:collapse; mso-table-lspace: 0pt;mso-table-rspace: 0pt; width:187px;"><tr><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td width="32" style="width:32px; padding-right: 15px;" valign="top"><![endif]-->
|
||||
<table border="0" cellspacing="0" cellpadding="0" width="32" height="32" style="width: 32px !important;height: 32px !important;display: inline-block;border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;margin-right: 15px">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top">
|
||||
<td valign="middle" style="word-break: break-word;border-collapse: collapse !important;vertical-align: top">
|
||||
<a href=" " title="Facebook" target="_blank">
|
||||
<img src="https://cdn.tools.unlayer.com/social/icons/circle-white/facebook.png" alt="Facebook" title="Facebook" width="32" style="outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;clear: both;display: block !important;border: none;height: auto;float: none;max-width: 32px !important">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td width="32" style="width:32px; padding-right: 15px;" valign="top"><![endif]-->
|
||||
<table border="0" cellspacing="0" cellpadding="0" width="32" height="32" style="width: 32px !important;height: 32px !important;display: inline-block;border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;margin-right: 15px">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top">
|
||||
<td valign="middle" style="word-break: break-word;border-collapse: collapse !important;vertical-align: top">
|
||||
<a href="https://x.com/marcoaiot" title="X" target="_blank">
|
||||
<img src="https://cdn.tools.unlayer.com/social/icons/circle-white/x.png" alt="Twitter" title="Twitter" width="32" style="color:#000;outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;clear: both;display: block !important;border: none;height: auto;float: none;max-width: 32px !important">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td width="32" style="width:32px; padding-right: 15px;" valign="top"><![endif]-->
|
||||
<table border="0" cellspacing="0" cellpadding="0" width="32" height="32" style="width: 32px !important;height: 32px !important;display: inline-block;border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;margin-right: 15px">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top">
|
||||
<td valign="middle" style="word-break: break-word;border-collapse: collapse !important;vertical-align: top">
|
||||
<a href=" " title="Instagram" target="_blank">
|
||||
<img src="https://cdn.tools.unlayer.com/social/icons/circle-white/instagram.png" alt="Instagram" title="Instagram" width="32" style="outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;clear: both;display: block !important;border: none;height: auto;float: none;max-width: 32px !important">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td width="32" style="width:32px; padding-right: 0px;" valign="top"><![endif]-->
|
||||
<table border="0" cellspacing="0" cellpadding="0" width="32" height="32" style="width: 32px !important;height: 32px !important;display: inline-block;border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;margin-right: 0px">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top">
|
||||
<td valign="middle" style="word-break: break-word;border-collapse: collapse !important;vertical-align: top">
|
||||
<a href=" " title="LinkedIn" target="_blank">
|
||||
<img src="https://cdn.tools.unlayer.com/social/icons/circle-white/linkedin.png" alt="LinkedIn" title="LinkedIn" width="32" style="outline: none;text-decoration: none;-ms-interpolation-mode: bicubic;clear: both;display: block !important;border: none;height: auto;float: none;max-width: 32px !important">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!--<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:5px 10px 10px;font-family:'Lato',sans-serif;" align="left">
|
||||
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: left; word-wrap: break-word;">
|
||||
<p style="line-height: 140%; font-size: 14px;"><span style="font-size: 14px; line-height: 19.6px;"><span style="color: #ecf0f1; font-size: 14px; line-height: 19.6px;"><span style="line-height: 19.6px; font-size: 14px;">Marco AIoT Technologies Pvt. Ltd. © All Rights Reserved</span></span></span></p>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>-->
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!--<div>Bottom Spacing - light red</div>-->
|
||||
|
||||
<div class="u-row-container" style="padding: 0px;background-color: #f9f9f9">
|
||||
<div class="u-row" style="margin: 0 auto; min-width: 320px; max-width: 600px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: #f46b61;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: #f9f9f9;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:600px;"><tr style="background-color: #e93f32;"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="600" style="width: 600px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-100" style="max-width: 320px;min-width: 600px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!--><div style="box-sizing: border-box; height: 100%; padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:15px;font-family:'Lato',sans-serif;" align="center">
|
||||
<p style="line-height: 140%; font-size: 14px;"><span style="font-size: 14px; line-height: 19.6px;"><span style="color: #ecf0f1; font-size: 14px; line-height: 19.6px;"><span style="line-height: 19.6px; font-size: 14px;">Marco AIoT Technologies Pvt. Ltd. © All Rights Reserved</span></span></span></p>
|
||||
|
||||
<!--<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;border-top: 1px solid #e93f32;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%">
|
||||
<tbody>
|
||||
<tr style="vertical-align: top">
|
||||
<td style="word-break: break-word;border-collapse: collapse !important;vertical-align: top;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%">
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>-->
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="u-row-container" style="padding: 0px;background-color: transparent">
|
||||
<div class="u-row" style="margin: 0 auto;min-width: 320px;max-width: 600px;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #f9f9f9;">
|
||||
<div style="border-collapse: collapse;display: table;width: 100%;height: 100%;background-color: transparent;">
|
||||
<!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px;background-color: transparent;" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:600px;"><tr style="background-color: #f9f9f9;"><![endif]-->
|
||||
<!--[if (mso)|(IE)]><td align="center" width="600" style="width: 600px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
|
||||
<div class="u-col u-col-100" style="max-width: 320px;min-width: 600px;display: table-cell;vertical-align: top;">
|
||||
<div style="height: 100%;width: 100% !important;">
|
||||
<!--[if (!mso)&(!IE)]><!--><div style="box-sizing: border-box; height: 100%; padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;">
|
||||
<!--<![endif]-->
|
||||
|
||||
<table style="font-family:'Lato',sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="overflow-wrap:break-word;word-break:break-word;padding:20px 40px 30px 20px;font-family:'Lato',sans-serif;" align="left">
|
||||
|
||||
<div style="font-size: 14px; line-height: 140%; text-align: left; word-wrap: break-word;">
|
||||
<small style="color: #a5a3a3;"> You're receiving this email because you have a MarcoPMS account. This email is not a marketing or promotional email. That is why this email does not contain an unsubscribe link. You will receive this email even if you have unsubscribed from MarcoPMS's marketing emails</small>
|
||||
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!--[if (!mso)&(!IE)]><!-->
|
||||
</div><!--<![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td><![endif]-->
|
||||
<!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--[if (mso)|(IE)]></td></tr></table><![endif]-->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!--[if mso]></div><![endif]-->
|
||||
<!--[if IE]></div><![endif]-->
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -126,6 +126,7 @@ builder.Services.AddMemoryCache();
|
||||
//builder.Services.AddScoped<IProjectAllocationRepository, ProjectAllocationRepository>();
|
||||
|
||||
builder.Services.AddScoped<RefreshTokenService>();
|
||||
builder.Services.AddScoped<PermissionServices>();
|
||||
|
||||
builder.Services.AddScoped<UserHelper>();
|
||||
builder.Services.AddScoped<RolesHelper>();
|
||||
|
||||
@ -38,6 +38,17 @@ namespace MarcoBMS.Services.Service
|
||||
|
||||
return content;
|
||||
}
|
||||
//public async Task<string> GetEmailTemplate1()
|
||||
//{
|
||||
// string path = Path.Combine(_env.ContentRootPath, "EmailTemplates", $"send-otp.html");
|
||||
|
||||
// if (!File.Exists(path))
|
||||
// throw new FileNotFoundException("Template file not found");
|
||||
|
||||
// string content = await File.ReadAllTextAsync(path);
|
||||
|
||||
// return content;
|
||||
//}
|
||||
|
||||
public async Task SendResetPasswordEmailOnRegister(string toEmail, string toName, string resetLink)
|
||||
{
|
||||
@ -74,7 +85,6 @@ namespace MarcoBMS.Services.Service
|
||||
await SendEmailAsync(toEmails, "Reset Your Password", emailBody);
|
||||
|
||||
}
|
||||
|
||||
public async Task SendResetPasswordSuccessEmail(string toEmail, string toName)
|
||||
{
|
||||
var replacements = new Dictionary<string, string>
|
||||
@ -111,8 +121,7 @@ namespace MarcoBMS.Services.Service
|
||||
await SendEmailAsync(toEmails, "User Requested a Demo", emailBody);
|
||||
|
||||
}
|
||||
|
||||
public async Task<string> SendProjectStatisticsEmail(List<string> toEmails, string emailBody, ProjectStatisticReport report)
|
||||
public async Task<string> SendProjectStatisticsEmail(List<string> toEmails, string emailBody, string subject, ProjectStatisticReport report)
|
||||
{
|
||||
var date = report.Date.ToString("dd-MMM-yyyy", CultureInfo.InvariantCulture);
|
||||
var replacements = new Dictionary<string, string>
|
||||
@ -141,11 +150,68 @@ namespace MarcoBMS.Services.Service
|
||||
emailBody = emailBody.Replace("{{TEAM_ON_SITE}}", BuildTeamOnSiteHtml(report.TeamOnSite));
|
||||
emailBody = emailBody.Replace("{{PERFORMED_TASK}}", BuildPerformedTaskHtml(report.PerformedTasks, report.Date));
|
||||
emailBody = emailBody.Replace("{{PERFORMED_ATTENDANCE}}", BuildPerformedAttendanceHtml(report.PerformedAttendance));
|
||||
|
||||
string subject = $"DPR - {date} - {report.ProjectName}";
|
||||
var subjectReplacements = new Dictionary<string, string>
|
||||
{
|
||||
{"DATE", date },
|
||||
{"PROJECT_NAME", report.ProjectName}
|
||||
};
|
||||
foreach (var item in subjectReplacements)
|
||||
{
|
||||
subject = subject.Replace($"{{{{{item.Key}}}}}", item.Value);
|
||||
}
|
||||
string env = _configuration["environment:Title"] ?? string.Empty;
|
||||
subject = CheckSubject(subject);
|
||||
await SendEmailAsync(toEmails, subject, emailBody);
|
||||
return emailBody;
|
||||
}
|
||||
public async Task SendOTP(List<string> toEmails, string emailBody, string name, string otp, string subject)
|
||||
{
|
||||
var replacements = new Dictionary<string, string>
|
||||
{
|
||||
{ "NAME", name },
|
||||
{ "OTP", otp }
|
||||
};
|
||||
|
||||
foreach (var item in replacements)
|
||||
{
|
||||
emailBody = emailBody.Replace($"{{{{{item.Key}}}}}", item.Value);
|
||||
}
|
||||
|
||||
subject = CheckSubject(subject);
|
||||
await SendEmailAsync(toEmails, subject, emailBody);
|
||||
}
|
||||
public async Task SendEmailAsync(List<string> toEmails, string subject, string body)
|
||||
{
|
||||
var email = new MimeMessage();
|
||||
email.From.Add(new MailboxAddress(_smtpSettings.SenderName, _smtpSettings.SenderEmail));
|
||||
foreach (var toEmail in toEmails)
|
||||
{
|
||||
email.To.Add(MailboxAddress.Parse(toEmail));
|
||||
}
|
||||
email.Subject = subject;
|
||||
|
||||
var bodyBuilder = new BodyBuilder { HtmlBody = body };
|
||||
email.Body = bodyBuilder.ToMessageBody();
|
||||
|
||||
using var smtp = new SmtpClient();
|
||||
await smtp.ConnectAsync(_smtpSettings.SmtpServer, _smtpSettings.Port, MailKit.Security.SecureSocketOptions.StartTls);
|
||||
await smtp.AuthenticateAsync(_smtpSettings.SenderEmail, _smtpSettings.Password);
|
||||
await smtp.SendAsync(email);
|
||||
await smtp.DisconnectAsync(true);
|
||||
}
|
||||
|
||||
private string CheckSubject(string subject)
|
||||
{
|
||||
string env = _configuration["Environment:Title"] ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(env))
|
||||
{
|
||||
return subject = $"{subject}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return subject = $"({env}) {subject}";
|
||||
}
|
||||
}
|
||||
private string BuildTeamOnSiteHtml(List<TeamOnSite> team)
|
||||
{
|
||||
if (team == null || !team.Any()) return "";
|
||||
@ -234,26 +300,6 @@ namespace MarcoBMS.Services.Service
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public async Task SendEmailAsync(List<string> toEmails, string subject, string body)
|
||||
{
|
||||
var email = new MimeMessage();
|
||||
email.From.Add(new MailboxAddress(_smtpSettings.SenderName, _smtpSettings.SenderEmail));
|
||||
foreach (var toEmail in toEmails)
|
||||
{
|
||||
email.To.Add(MailboxAddress.Parse(toEmail));
|
||||
}
|
||||
email.Subject = subject;
|
||||
|
||||
var bodyBuilder = new BodyBuilder { HtmlBody = body };
|
||||
email.Body = bodyBuilder.ToMessageBody();
|
||||
|
||||
using var smtp = new SmtpClient();
|
||||
await smtp.ConnectAsync(_smtpSettings.SmtpServer, _smtpSettings.Port, MailKit.Security.SecureSocketOptions.StartTls);
|
||||
await smtp.AuthenticateAsync(_smtpSettings.SenderEmail, _smtpSettings.Password);
|
||||
await smtp.SendAsync(email);
|
||||
await smtp.DisconnectAsync(true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -5,11 +5,13 @@ namespace MarcoBMS.Services.Service
|
||||
{
|
||||
public interface IEmailSender
|
||||
{
|
||||
//Task<string> GetEmailTemplate1();
|
||||
Task SendResetPasswordEmail(string toEmail, string userName, string resetLink);
|
||||
Task SendResetPasswordEmailOnRegister(string toEmail, string toName, string resetLink);
|
||||
Task SendResetPasswordSuccessEmail(string toEmail, string userName);
|
||||
Task SendRequestDemoEmail(List<string> toEmails, InquiryEmailObject demoEmailObject);
|
||||
Task SendEmailAsync(List<string> toEmails, string subject, string body);
|
||||
Task<string> SendProjectStatisticsEmail(List<string> toEmails, string emailBody, ProjectStatisticReport report);
|
||||
Task SendOTP(List<string> toEmails, string emailBody, string name, string otp, string subject);
|
||||
Task<string> SendProjectStatisticsEmail(List<string> toEmails, string emailBody, string subject, ProjectStatisticReport report);
|
||||
}
|
||||
}
|
||||
|
||||
52
Marco.Pms.Services/Service/PermissionServices.cs
Normal file
52
Marco.Pms.Services/Service/PermissionServices.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using Marco.Pms.DataAccess.Data;
|
||||
using Marco.Pms.Model.Employees;
|
||||
using Marco.Pms.Model.Entitlements;
|
||||
using Marco.Pms.Model.Projects;
|
||||
using MarcoBMS.Services.Helpers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Marco.Pms.Services.Service
|
||||
{
|
||||
public class PermissionServices
|
||||
{
|
||||
private readonly ApplicationDbContext _context;
|
||||
private readonly RolesHelper _rolesHelper;
|
||||
private readonly ProjectsHelper _projectsHelper;
|
||||
public PermissionServices(ApplicationDbContext context, RolesHelper rolesHelper, ProjectsHelper projectsHelper)
|
||||
{
|
||||
_context = context;
|
||||
_rolesHelper = rolesHelper;
|
||||
_projectsHelper = projectsHelper;
|
||||
}
|
||||
|
||||
public async Task<bool> HasPermission(Guid featurePermissionId, Guid employeeId)
|
||||
{
|
||||
var hasPermission = await _context.EmployeeRoleMappings
|
||||
.Where(er => er.EmployeeId == employeeId)
|
||||
.Select(er => er.RoleId)
|
||||
.Distinct()
|
||||
.AnyAsync(roleId => _context.RolePermissionMappings
|
||||
.Any(rp => rp.FeaturePermissionId == featurePermissionId && rp.ApplicationRoleId == roleId));
|
||||
return hasPermission;
|
||||
}
|
||||
public async Task<bool> HasProjectPermission(Employee emp, string projectId)
|
||||
{
|
||||
List<FeaturePermission> featurePermission = await _rolesHelper.GetFeaturePermissionByEmployeeID(emp.Id);
|
||||
string[] projectsId = [];
|
||||
|
||||
/* User with permission manage project can see all projects */
|
||||
if (featurePermission != null && featurePermission.Exists(c => c.Id.ToString() == "172fc9b6-755b-4f62-ab26-55c34a330614"))
|
||||
{
|
||||
List<Project> projects = await _projectsHelper.GetAllProjectByTanentID(emp.TenantId);
|
||||
projectsId = projects.Select(c => c.Id.ToString()).ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
List<ProjectAllocation> allocation = await _projectsHelper.GetProjectByEmployeeID(emp.Id);
|
||||
projectsId = allocation.Select(c => c.ProjectId.ToString()).ToArray();
|
||||
}
|
||||
bool response = projectsId.Contains(projectId);
|
||||
return response;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -48,36 +48,37 @@ namespace MarcoBMS.Services.Service
|
||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||
}
|
||||
|
||||
public async Task<string> CreateRefreshToken(string userId, string tenantId, JwtSettings _jwtSettings)
|
||||
public async Task<string> CreateRefreshToken(string userId, string tenantId, JwtSettings jwtSettings)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.UTF8.GetBytes(_jwtSettings.Key);
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, userId),
|
||||
new Claim("TenantId", tenantId),
|
||||
new Claim("token_type", "refresh")
|
||||
};
|
||||
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key));
|
||||
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, userId),
|
||||
new Claim("TenantId", tenantId), // Add TenantId claim
|
||||
|
||||
new Claim("token_type", "refresh") // Custom claim to differentiate refresh tokens
|
||||
}),
|
||||
Expires = DateTime.UtcNow.AddDays(7), // Refresh token valid for 7 days
|
||||
Issuer = _jwtSettings.Issuer,
|
||||
Audience = _jwtSettings.Audience,
|
||||
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Expires = DateTime.UtcNow.AddDays(jwtSettings.RefreshTokenExpiresInDays),
|
||||
Issuer = jwtSettings.Issuer,
|
||||
Audience = jwtSettings.Audience,
|
||||
SigningCredentials = credentials
|
||||
};
|
||||
|
||||
var token = tokenHandler.CreateToken(tokenDescriptor);
|
||||
string strToken = tokenHandler.WriteToken(token);
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var refreshTokenString = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
||||
|
||||
var refreshToken = new RefreshToken
|
||||
{
|
||||
Token = strToken,
|
||||
Token = refreshTokenString,
|
||||
UserId = userId,
|
||||
ExpiryDate = DateTime.UtcNow.AddDays(_jwtSettings.RefreshTokenExpiresInDays),
|
||||
ExpiryDate = DateTime.UtcNow.AddDays(jwtSettings.RefreshTokenExpiresInDays),
|
||||
IsRevoked = false
|
||||
};
|
||||
|
||||
@ -89,7 +90,7 @@ namespace MarcoBMS.Services.Service
|
||||
_context.RefreshTokens.Add(refreshToken);
|
||||
}
|
||||
await _context.SaveChangesAsync();
|
||||
return strToken;
|
||||
return refreshTokenString;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -97,6 +98,44 @@ namespace MarcoBMS.Services.Service
|
||||
throw;
|
||||
}
|
||||
}
|
||||
public string CreateMPINToken(string userId, string tenantId, JwtSettings jwtSettings)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var claims = new[]
|
||||
{
|
||||
new Claim(ClaimTypes.NameIdentifier, userId),
|
||||
new Claim("TenantId", tenantId),
|
||||
new Claim("token_type", "mpin")
|
||||
};
|
||||
|
||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings.Key));
|
||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
|
||||
|
||||
var tokenDescriptor = new SecurityTokenDescriptor
|
||||
{
|
||||
Subject = new ClaimsIdentity(claims),
|
||||
Issuer = jwtSettings.Issuer,
|
||||
Audience = jwtSettings.Audience,
|
||||
SigningCredentials = creds
|
||||
// No 'Expires' means the token won't expire
|
||||
};
|
||||
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var MPINToken = tokenHandler.WriteToken(tokenHandler.CreateToken(tokenDescriptor));
|
||||
return MPINToken;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError("Error creating MPIN token for userId: {UserId}, tenantId: {TenantId}, error : {Error}", userId, tenantId, ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RefreshToken> GetRefreshToken(string token)
|
||||
{
|
||||
@ -154,5 +193,35 @@ namespace MarcoBMS.Services.Service
|
||||
return jwtToken?.ValidTo;
|
||||
}
|
||||
|
||||
public ClaimsPrincipal ValidateToken(string token, JwtSettings jwtSettings)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = System.Text.Encoding.ASCII.GetBytes(jwtSettings.Key);
|
||||
|
||||
var validationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(key),
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = jwtSettings.Issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = jwtSettings.Audience,
|
||||
ValidateLifetime = false, // Disable lifetime validation (ignores expiration)
|
||||
ClockSkew = TimeSpan.Zero // Optional: Remove time skew buffer
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
|
||||
return principal;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Token is invalid
|
||||
Console.WriteLine($"Token validation failed: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,10 @@
|
||||
"AllowedMethods": "*",
|
||||
"AllowedHeaders": "*"
|
||||
},
|
||||
|
||||
"Environment": {
|
||||
"Name": "Development",
|
||||
"Title": "Dev"
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMSGuid"
|
||||
},
|
||||
@ -34,7 +37,7 @@
|
||||
"RefreshTokenExpiresInDays": 7
|
||||
},
|
||||
"MailingList": {
|
||||
"RequestDemoReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com",
|
||||
"RequestDemoReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com"
|
||||
//"ProjectStatisticsReceivers": "ashutosh.nehete@marcoaiot.com;vikas@marcoaiot.com;umesh@marcoait.com"
|
||||
},
|
||||
"AWS": {
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
"AllowedMethods": "*",
|
||||
"AllowedHeaders": "*"
|
||||
},
|
||||
"Environment": {
|
||||
"Name": "Production",
|
||||
"Title": ""
|
||||
},
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnectionString": "Server=147.93.98.152;User ID=devuser;Password=AppUser@123$;Database=MarcoBMS1"
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user