deploy commited on
Commit
fc06b79
·
0 Parent(s):

Deploy restore contacts feature

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +17 -0
  2. ContactManagementAPI/ContactManagementAPI.csproj +22 -0
  3. ContactManagementAPI/Controllers/AccountController.cs +66 -0
  4. ContactManagementAPI/Controllers/AdminController.cs +1171 -0
  5. ContactManagementAPI/Controllers/DocumentController.cs +100 -0
  6. ContactManagementAPI/Controllers/HomeController.cs +1045 -0
  7. ContactManagementAPI/Controllers/PhotoController.cs +176 -0
  8. ContactManagementAPI/Data/ApplicationDbContext.cs +104 -0
  9. ContactManagementAPI/Migrations/20260206165849_InitialCreate.Designer.cs +305 -0
  10. ContactManagementAPI/Migrations/20260206165849_InitialCreate.cs +162 -0
  11. ContactManagementAPI/Migrations/20260208162549_MakeContactFieldsNullable.Designer.cs +291 -0
  12. ContactManagementAPI/Migrations/20260208162549_MakeContactFieldsNullable.cs +355 -0
  13. ContactManagementAPI/Migrations/20260209052719_AddSampleData.Designer.cs +291 -0
  14. ContactManagementAPI/Migrations/20260209052719_AddSampleData.cs +197 -0
  15. ContactManagementAPI/Migrations/20260209090000_AddUserSecurity.Designer.cs +463 -0
  16. ContactManagementAPI/Migrations/20260209090000_AddUserSecurity.cs +143 -0
  17. ContactManagementAPI/Migrations/20260216070933_AddNewContactGroups.Designer.cs +478 -0
  18. ContactManagementAPI/Migrations/20260216070933_AddNewContactGroups.cs +142 -0
  19. ContactManagementAPI/Migrations/20260220171502_AddContactIdentityBankGenderDobFields.Designer.cs +508 -0
  20. ContactManagementAPI/Migrations/20260220171502_AddContactIdentityBankGenderDobFields.cs +231 -0
  21. ContactManagementAPI/Migrations/20260220171919_AddContactBankAccountsTable.Designer.cs +557 -0
  22. ContactManagementAPI/Migrations/20260220171919_AddContactBankAccountsTable.cs +164 -0
  23. ContactManagementAPI/Migrations/20260221045734_AddPanNumberToContact.Designer.cs +560 -0
  24. ContactManagementAPI/Migrations/20260221045734_AddPanNumberToContact.cs +141 -0
  25. ContactManagementAPI/Migrations/ApplicationDbContextModelSnapshot.cs +557 -0
  26. ContactManagementAPI/Models/AdminHistoryEntry.cs +12 -0
  27. ContactManagementAPI/Models/AppUser.cs +33 -0
  28. ContactManagementAPI/Models/Contact.cs +46 -0
  29. ContactManagementAPI/Models/ContactBankAccount.cs +15 -0
  30. ContactManagementAPI/Models/ContactDocument.cs +17 -0
  31. ContactManagementAPI/Models/ContactGroup.cs +15 -0
  32. ContactManagementAPI/Models/ContactPhoto.cs +17 -0
  33. ContactManagementAPI/Models/GroupRight.cs +18 -0
  34. ContactManagementAPI/Models/RightsCatalog.cs +44 -0
  35. ContactManagementAPI/Models/UserGroup.cs +23 -0
  36. ContactManagementAPI/Models/UserRight.cs +18 -0
  37. ContactManagementAPI/Program.cs +179 -0
  38. ContactManagementAPI/Properties/launchSettings.json +37 -0
  39. ContactManagementAPI/Security/RequireRightAttribute.cs +49 -0
  40. ContactManagementAPI/Services/AdminHistoryService.cs +73 -0
  41. ContactManagementAPI/Services/AuthorizationService.cs +52 -0
  42. ContactManagementAPI/Services/ContactStatisticsService.cs +173 -0
  43. ContactManagementAPI/Services/FileUploadService.cs +142 -0
  44. ContactManagementAPI/Services/ImportExportService.cs +573 -0
  45. ContactManagementAPI/Services/SeedData.cs +333 -0
  46. ContactManagementAPI/Services/SessionKeys.cs +7 -0
  47. ContactManagementAPI/Services/UserContextService.cs +49 -0
  48. ContactManagementAPI/ViewModels/AdminViewModels.cs +111 -0
  49. ContactManagementAPI/Views/Account/AccessDenied.cshtml +11 -0
  50. ContactManagementAPI/Views/Account/Login.cshtml +42 -0
.dockerignore ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ **/bin/
2
+ **/obj/
3
+ **/.vs/
4
+ **/.vscode/
5
+ **/.git/
6
+ **/.venv/
7
+
8
+ Deployment/
9
+ Installers/
10
+ Published/
11
+
12
+ screenshots/
13
+ *.rar
14
+ *.zip
15
+ *.7z
16
+ *.pdf
17
+ *.docx
ContactManagementAPI/ContactManagementAPI.csproj ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <Project Sdk="Microsoft.NET.Sdk.Web">
2
+
3
+ <PropertyGroup>
4
+ <TargetFramework>net8.0</TargetFramework>
5
+ <Nullable>enable</Nullable>
6
+ <ImplicitUsings>enable</ImplicitUsings>
7
+ </PropertyGroup>
8
+
9
+ <ItemGroup>
10
+ <PackageReference Include="CsvHelper" Version="30.0.1" />
11
+ <PackageReference Include="EPPlus" Version="7.0.0" />
12
+ <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
13
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
14
+ <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.0" />
15
+ <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.0">
16
+ <PrivateAssets>all</PrivateAssets>
17
+ <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
18
+ </PackageReference>
19
+ <PackageReference Include="QuestPDF" Version="2024.12.3" />
20
+ </ItemGroup>
21
+
22
+ </Project>
ContactManagementAPI/Controllers/AccountController.cs ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using ContactManagementAPI.Data;
2
+ using ContactManagementAPI.Models;
3
+ using ContactManagementAPI.Services;
4
+ using ContactManagementAPI.ViewModels;
5
+ using Microsoft.AspNetCore.Identity;
6
+ using Microsoft.AspNetCore.Mvc;
7
+ using Microsoft.EntityFrameworkCore;
8
+
9
+ namespace ContactManagementAPI.Controllers
10
+ {
11
+ public class AccountController : Controller
12
+ {
13
+ private readonly ApplicationDbContext _context;
14
+ private readonly PasswordHasher<AppUser> _passwordHasher = new();
15
+
16
+ public AccountController(ApplicationDbContext context)
17
+ {
18
+ _context = context;
19
+ }
20
+
21
+ [HttpGet]
22
+ public IActionResult Login(string? returnUrl = null)
23
+ {
24
+ return View(new LoginViewModel { ReturnUrl = returnUrl });
25
+ }
26
+
27
+ [HttpPost]
28
+ [ValidateAntiForgeryToken]
29
+ public IActionResult Login(LoginViewModel model)
30
+ {
31
+ if (!ModelState.IsValid)
32
+ return View(model);
33
+
34
+ var user = _context.AppUsers
35
+ .Include(u => u.Group)
36
+ .FirstOrDefault(u => u.UserName == model.UserName);
37
+
38
+ if (user == null || !user.IsActive)
39
+ {
40
+ ModelState.AddModelError(string.Empty, "Invalid username or password.");
41
+ return View(model);
42
+ }
43
+
44
+ var result = _passwordHasher.VerifyHashedPassword(user, user.PasswordHash, model.Password);
45
+ if (result == PasswordVerificationResult.Failed)
46
+ {
47
+ ModelState.AddModelError(string.Empty, "Invalid username or password.");
48
+ return View(model);
49
+ }
50
+
51
+ HttpContext.Session.SetInt32(SessionKeys.UserId, user.Id);
52
+ return Redirect(string.IsNullOrWhiteSpace(model.ReturnUrl) ? "/" : model.ReturnUrl);
53
+ }
54
+
55
+ public IActionResult Logout()
56
+ {
57
+ HttpContext.Session.Clear();
58
+ return RedirectToAction("Login");
59
+ }
60
+
61
+ public IActionResult AccessDenied()
62
+ {
63
+ return View();
64
+ }
65
+ }
66
+ }
ContactManagementAPI/Controllers/AdminController.cs ADDED
@@ -0,0 +1,1171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using System.Linq;
3
+ using System.IO.Compression;
4
+ using Microsoft.Data.Sqlite;
5
+ using ContactManagementAPI.Data;
6
+ using ContactManagementAPI.Models;
7
+ using ContactManagementAPI.Security;
8
+ using ContactManagementAPI.Services;
9
+ using ContactManagementAPI.ViewModels;
10
+ using Microsoft.AspNetCore.Identity;
11
+ using Microsoft.AspNetCore.Mvc;
12
+ using Microsoft.EntityFrameworkCore;
13
+
14
+ namespace ContactManagementAPI.Controllers
15
+ {
16
+ public class AdminController : Controller
17
+ {
18
+ private readonly ApplicationDbContext _context;
19
+ private readonly UserContextService _userContextService;
20
+ private readonly AdminHistoryService _adminHistoryService;
21
+ private readonly IWebHostEnvironment _environment;
22
+ private readonly PasswordHasher<AppUser> _passwordHasher = new();
23
+
24
+ public AdminController(ApplicationDbContext context, UserContextService userContextService, AdminHistoryService adminHistoryService, IWebHostEnvironment environment)
25
+ {
26
+ _context = context;
27
+ _userContextService = userContextService;
28
+ _adminHistoryService = adminHistoryService;
29
+ _environment = environment;
30
+ }
31
+
32
+ private static bool IsSuperAdminUser(AppUser? user)
33
+ {
34
+ return user != null && string.Equals(user.UserName, SeedData.SuperAdminUserName, StringComparison.OrdinalIgnoreCase);
35
+ }
36
+
37
+ private static bool IsProtectedSystemUser(AppUser? user)
38
+ {
39
+ return user != null &&
40
+ (IsSuperAdminUser(user) || string.Equals(user.UserName, "admin", StringComparison.OrdinalIgnoreCase));
41
+ }
42
+
43
+ private ContactGroup? ResolveContactGroupForUserGroup(UserGroup? userGroup)
44
+ {
45
+ if (userGroup == null)
46
+ {
47
+ return null;
48
+ }
49
+
50
+ if (!string.IsNullOrWhiteSpace(userGroup.Name) && userGroup.Name.StartsWith("ContactGroup - ", StringComparison.OrdinalIgnoreCase))
51
+ {
52
+ var contactGroupName = userGroup.Name.Substring("ContactGroup - ".Length).Trim();
53
+ if (!string.IsNullOrWhiteSpace(contactGroupName))
54
+ {
55
+ return _context.ContactGroups.FirstOrDefault(cg => cg.Name == contactGroupName);
56
+ }
57
+ }
58
+
59
+ return _context.ContactGroups.FirstOrDefault(cg => cg.Id == userGroup.Id);
60
+ }
61
+
62
+ private string GetUploadsRoot()
63
+ {
64
+ var uploadsRoot = Environment.GetEnvironmentVariable("UPLOADS_ROOT");
65
+ if (!string.IsNullOrWhiteSpace(uploadsRoot))
66
+ {
67
+ Directory.CreateDirectory(uploadsRoot);
68
+ Directory.CreateDirectory(Path.Combine(uploadsRoot, "photos"));
69
+ Directory.CreateDirectory(Path.Combine(uploadsRoot, "documents"));
70
+ return uploadsRoot;
71
+ }
72
+
73
+ var fallback = Path.Combine(_environment.WebRootPath, "uploads");
74
+ Directory.CreateDirectory(fallback);
75
+ Directory.CreateDirectory(Path.Combine(fallback, "photos"));
76
+ Directory.CreateDirectory(Path.Combine(fallback, "documents"));
77
+ return fallback;
78
+ }
79
+
80
+ private static string NormalizeText(string? value)
81
+ {
82
+ return (value ?? string.Empty).Trim().ToUpperInvariant();
83
+ }
84
+
85
+ [RequireRight(RightsCatalog.AdminManageUsers)]
86
+ public IActionResult Users()
87
+ {
88
+ var currentUser = _userContextService.CurrentUser;
89
+ var isSuperAdmin = IsSuperAdminUser(currentUser);
90
+
91
+ var usersQuery = _context.AppUsers
92
+ .Include(u => u.Group)
93
+ .AsQueryable();
94
+
95
+ if (!isSuperAdmin)
96
+ {
97
+ usersQuery = usersQuery.Where(u => !string.Equals(u.UserName, SeedData.SuperAdminUserName, StringComparison.OrdinalIgnoreCase));
98
+ }
99
+
100
+ var users = usersQuery
101
+ .OrderBy(u => u.UserName)
102
+ .ToList();
103
+
104
+ ViewBag.IsSuperAdmin = isSuperAdmin;
105
+
106
+ return View(users);
107
+ }
108
+
109
+ [RequireRight(RightsCatalog.AdminManageUsers)]
110
+ public IActionResult CreateUser()
111
+ {
112
+ ViewData["Groups"] = _context.UserGroups.OrderBy(g => g.Name).ToList();
113
+ return View(new UserCreateViewModel());
114
+ }
115
+
116
+ [HttpPost]
117
+ [ValidateAntiForgeryToken]
118
+ [RequireRight(RightsCatalog.AdminManageUsers)]
119
+ public IActionResult CreateUser(UserCreateViewModel model)
120
+ {
121
+ var currentUser = _userContextService.CurrentUser;
122
+ var isSuperAdmin = IsSuperAdminUser(currentUser);
123
+
124
+ if (string.Equals(model.UserName, SeedData.SuperAdminUserName, StringComparison.OrdinalIgnoreCase) && !isSuperAdmin)
125
+ {
126
+ ModelState.AddModelError(nameof(UserCreateViewModel.UserName), "Super Admin user name is reserved.");
127
+ }
128
+
129
+ if (_context.AppUsers.Any(u => u.UserName == model.UserName))
130
+ {
131
+ ModelState.AddModelError(nameof(UserCreateViewModel.UserName), "User name already exists.");
132
+ }
133
+
134
+ if (!ModelState.IsValid)
135
+ {
136
+ ViewData["Groups"] = _context.UserGroups.OrderBy(g => g.Name).ToList();
137
+ return View(model);
138
+ }
139
+
140
+ var user = new AppUser
141
+ {
142
+ UserName = model.UserName,
143
+ FullName = model.FullName,
144
+ GroupId = model.GroupId,
145
+ IsAdmin = model.IsAdmin,
146
+ IsActive = model.IsActive,
147
+ CreatedAt = DateTime.Now,
148
+ UpdatedAt = DateTime.Now
149
+ };
150
+
151
+ user.PasswordHash = _passwordHasher.HashPassword(user, model.Password);
152
+ _context.AppUsers.Add(user);
153
+ _context.SaveChanges();
154
+
155
+ _adminHistoryService.Log(
156
+ actionType: "Create",
157
+ entityType: "User",
158
+ entityId: user.Id,
159
+ performedBy: _userContextService.CurrentUser?.UserName ?? "Unknown",
160
+ details: $"Created user '{user.UserName}' in group id '{user.GroupId}'.");
161
+
162
+ TempData["SuccessMessage"] = "User created successfully.";
163
+ return RedirectToAction("Users");
164
+ }
165
+
166
+ [RequireRight(RightsCatalog.AdminManageUsers)]
167
+ public IActionResult EditUser(int id)
168
+ {
169
+ var currentUser = _userContextService.CurrentUser;
170
+ var isSuperAdmin = IsSuperAdminUser(currentUser);
171
+
172
+ var user = _context.AppUsers.FirstOrDefault(u => u.Id == id);
173
+ if (user == null)
174
+ return NotFound();
175
+
176
+ if (IsSuperAdminUser(user) && !isSuperAdmin)
177
+ {
178
+ return NotFound();
179
+ }
180
+
181
+ var model = new UserEditViewModel
182
+ {
183
+ Id = user.Id,
184
+ UserName = user.UserName,
185
+ FullName = user.FullName,
186
+ GroupId = user.GroupId,
187
+ IsAdmin = user.IsAdmin,
188
+ IsActive = user.IsActive
189
+ };
190
+
191
+ ViewData["Groups"] = _context.UserGroups.OrderBy(g => g.Name).ToList();
192
+ return View(model);
193
+ }
194
+
195
+ [HttpPost]
196
+ [ValidateAntiForgeryToken]
197
+ [RequireRight(RightsCatalog.AdminManageUsers)]
198
+ public IActionResult EditUser(UserEditViewModel model)
199
+ {
200
+ var currentUser = _userContextService.CurrentUser;
201
+ var isSuperAdmin = IsSuperAdminUser(currentUser);
202
+
203
+ var user = _context.AppUsers.FirstOrDefault(u => u.Id == model.Id);
204
+ if (user == null)
205
+ return NotFound();
206
+
207
+ if (IsSuperAdminUser(user) && !isSuperAdmin)
208
+ {
209
+ return NotFound();
210
+ }
211
+
212
+ if (_context.AppUsers.Any(u => u.UserName == model.UserName && u.Id != model.Id))
213
+ {
214
+ ModelState.AddModelError(nameof(UserEditViewModel.UserName), "User name already exists.");
215
+ }
216
+
217
+ if (string.Equals(model.UserName, SeedData.SuperAdminUserName, StringComparison.OrdinalIgnoreCase) && !IsSuperAdminUser(user))
218
+ {
219
+ ModelState.AddModelError(nameof(UserEditViewModel.UserName), "Super Admin user name is reserved.");
220
+ }
221
+
222
+ if (!ModelState.IsValid)
223
+ {
224
+ ViewData["Groups"] = _context.UserGroups.OrderBy(g => g.Name).ToList();
225
+ return View(model);
226
+ }
227
+
228
+ var wasAdminUser = string.Equals(user.UserName, "admin", StringComparison.OrdinalIgnoreCase);
229
+ var wasSuperAdminUser = IsSuperAdminUser(user);
230
+
231
+ if (wasSuperAdminUser)
232
+ {
233
+ // Super Admin is immutable (prevents lockout and prevents admin-level changes even by mistake)
234
+ ModelState.AddModelError(string.Empty, "Super Admin account cannot be edited.");
235
+ ViewData["Groups"] = _context.UserGroups.OrderBy(g => g.Name).ToList();
236
+ return View(model);
237
+ }
238
+
239
+ user.UserName = model.UserName;
240
+ user.FullName = model.FullName;
241
+ user.GroupId = model.GroupId;
242
+ user.IsAdmin = model.IsAdmin;
243
+ user.IsActive = model.IsActive;
244
+ user.UpdatedAt = DateTime.Now;
245
+
246
+ if (wasAdminUser)
247
+ {
248
+ var administratorsGroupId = _context.UserGroups
249
+ .Where(g => g.Name == "Administrators")
250
+ .Select(g => g.Id)
251
+ .FirstOrDefault();
252
+
253
+ user.IsAdmin = true;
254
+ user.IsActive = true;
255
+ if (administratorsGroupId > 0)
256
+ {
257
+ user.GroupId = administratorsGroupId;
258
+ }
259
+ }
260
+
261
+ if (!string.IsNullOrWhiteSpace(model.NewPassword))
262
+ {
263
+ user.PasswordHash = _passwordHasher.HashPassword(user, model.NewPassword);
264
+ }
265
+
266
+ _context.SaveChanges();
267
+
268
+ _adminHistoryService.Log(
269
+ actionType: "Edit",
270
+ entityType: "User",
271
+ entityId: user.Id,
272
+ performedBy: _userContextService.CurrentUser?.UserName ?? "Unknown",
273
+ details: $"Edited user '{user.UserName}' (Active: {user.IsActive}, Admin: {user.IsAdmin}).");
274
+
275
+ TempData["SuccessMessage"] = "User updated successfully.";
276
+ return RedirectToAction("Users");
277
+ }
278
+
279
+ [RequireRight(RightsCatalog.AdminManageRights)]
280
+ public IActionResult UserRights(int id)
281
+ {
282
+ var currentUser = _userContextService.CurrentUser;
283
+ var isSuperAdmin = IsSuperAdminUser(currentUser);
284
+
285
+ var user = _context.AppUsers
286
+ .Include(u => u.Group)
287
+ .FirstOrDefault(u => u.Id == id);
288
+
289
+ if (user == null)
290
+ return NotFound();
291
+
292
+ if (IsSuperAdminUser(user) && !isSuperAdmin)
293
+ {
294
+ return NotFound();
295
+ }
296
+
297
+ var userRights = _context.UserRights
298
+ .Where(r => r.AppUserId == id)
299
+ .ToList();
300
+
301
+ var groupRights = _context.GroupRights
302
+ .Where(r => r.UserGroupId == user.GroupId)
303
+ .ToList();
304
+
305
+ var rights = RightsCatalog.All.Select(r =>
306
+ {
307
+ var userRight = userRights.FirstOrDefault(ur => ur.RightKey == r.Key);
308
+ var groupRight = groupRights.FirstOrDefault(gr => gr.RightKey == r.Key);
309
+ var selection = userRight == null ? "Inherit" : (userRight.IsGranted ? "Grant" : "Deny");
310
+ var effectiveGranted = user.IsAdmin || (userRight?.IsGranted ?? (groupRight?.IsGranted ?? false));
311
+ var effectiveSource = user.IsAdmin ? "Admin" : userRight != null ? "User" : groupRight != null ? "Group" : "None";
312
+
313
+ return new RightAssignmentViewModel
314
+ {
315
+ Key = r.Key,
316
+ Label = r.Label,
317
+ Category = r.Category,
318
+ Selection = selection,
319
+ EffectiveGranted = effectiveGranted,
320
+ EffectiveSource = effectiveSource
321
+ };
322
+ }).ToList();
323
+
324
+ var model = new UserRightsViewModel
325
+ {
326
+ UserId = user.Id,
327
+ UserName = user.UserName,
328
+ GroupName = user.Group?.Name,
329
+ Rights = rights
330
+ };
331
+
332
+ return View(model);
333
+ }
334
+
335
+ [HttpPost]
336
+ [ValidateAntiForgeryToken]
337
+ [RequireRight(RightsCatalog.AdminManageRights)]
338
+ public IActionResult UserRights(UserRightsViewModel model)
339
+ {
340
+ var currentUser = _userContextService.CurrentUser;
341
+ var isSuperAdmin = IsSuperAdminUser(currentUser);
342
+
343
+ var user = _context.AppUsers.FirstOrDefault(u => u.Id == model.UserId);
344
+ if (user == null)
345
+ return NotFound();
346
+
347
+ if (IsSuperAdminUser(user) && !isSuperAdmin)
348
+ {
349
+ return NotFound();
350
+ }
351
+
352
+ foreach (var right in model.Rights)
353
+ {
354
+ var existing = _context.UserRights.FirstOrDefault(r => r.AppUserId == model.UserId && r.RightKey == right.Key);
355
+ if (right.Selection == "Inherit")
356
+ {
357
+ if (existing != null)
358
+ _context.UserRights.Remove(existing);
359
+ continue;
360
+ }
361
+
362
+ var isGranted = right.Selection == "Grant";
363
+ if (existing == null)
364
+ {
365
+ _context.UserRights.Add(new UserRight
366
+ {
367
+ AppUserId = model.UserId,
368
+ RightKey = right.Key,
369
+ IsGranted = isGranted
370
+ });
371
+ }
372
+ else
373
+ {
374
+ existing.IsGranted = isGranted;
375
+ }
376
+ }
377
+
378
+ _context.SaveChanges();
379
+
380
+ _adminHistoryService.Log(
381
+ actionType: "Edit",
382
+ entityType: "UserRights",
383
+ entityId: model.UserId,
384
+ performedBy: _userContextService.CurrentUser?.UserName ?? "Unknown",
385
+ details: $"Updated rights for user '{user.UserName}'.");
386
+
387
+ TempData["SuccessMessage"] = "User rights updated successfully.";
388
+ return RedirectToAction("UserRights", new { id = model.UserId });
389
+ }
390
+
391
+ [RequireRight(RightsCatalog.AdminManageGroups)]
392
+ public IActionResult Groups()
393
+ {
394
+ var groups = _context.UserGroups
395
+ .OrderBy(g => g.Name)
396
+ .ToList();
397
+
398
+ ViewBag.IsSuperAdmin = IsSuperAdminUser(_userContextService.CurrentUser);
399
+
400
+ return View(groups);
401
+ }
402
+
403
+ [RequireRight(RightsCatalog.AdminManageGroups)]
404
+ public IActionResult CreateGroup()
405
+ {
406
+ return View(new GroupEditViewModel());
407
+ }
408
+
409
+ [HttpPost]
410
+ [ValidateAntiForgeryToken]
411
+ [RequireRight(RightsCatalog.AdminManageGroups)]
412
+ public IActionResult CreateGroup(GroupEditViewModel model)
413
+ {
414
+ if (_context.UserGroups.Any(g => g.Name == model.Name))
415
+ {
416
+ ModelState.AddModelError(nameof(GroupEditViewModel.Name), "Group name already exists.");
417
+ }
418
+
419
+ if (!ModelState.IsValid)
420
+ return View(model);
421
+
422
+ var group = new UserGroup
423
+ {
424
+ Name = model.Name,
425
+ Description = model.Description,
426
+ CreatedAt = DateTime.Now
427
+ };
428
+
429
+ _context.UserGroups.Add(group);
430
+ _context.SaveChanges();
431
+
432
+ var rights = RightsCatalog.All.Select(r => new GroupRight
433
+ {
434
+ UserGroupId = group.Id,
435
+ RightKey = r.Key,
436
+ IsGranted = false
437
+ }).ToList();
438
+
439
+ _context.GroupRights.AddRange(rights);
440
+ _context.SaveChanges();
441
+
442
+ _adminHistoryService.Log(
443
+ actionType: "Create",
444
+ entityType: "Group",
445
+ entityId: group.Id,
446
+ performedBy: _userContextService.CurrentUser?.UserName ?? "Unknown",
447
+ details: $"Created group '{group.Name}'.");
448
+
449
+ TempData["SuccessMessage"] = "Group created successfully.";
450
+ return RedirectToAction("Groups");
451
+ }
452
+
453
+ [RequireRight(RightsCatalog.AdminManageGroups)]
454
+ public IActionResult EditGroup(int id)
455
+ {
456
+ var group = _context.UserGroups.FirstOrDefault(g => g.Id == id);
457
+ if (group == null)
458
+ return NotFound();
459
+
460
+ var model = new GroupEditViewModel
461
+ {
462
+ Id = group.Id,
463
+ Name = group.Name,
464
+ Description = group.Description
465
+ };
466
+
467
+ return View(model);
468
+ }
469
+
470
+ [HttpPost]
471
+ [ValidateAntiForgeryToken]
472
+ [RequireRight(RightsCatalog.AdminManageUsers)]
473
+ public IActionResult DeleteSelectedUsers(List<int> userIds)
474
+ {
475
+ var currentUser = _userContextService.CurrentUser;
476
+ if (!IsSuperAdminUser(currentUser))
477
+ {
478
+ return Forbid();
479
+ }
480
+
481
+ if (userIds == null || userIds.Count == 0)
482
+ {
483
+ TempData["ErrorMessage"] = "No users selected.";
484
+ return RedirectToAction(nameof(Users));
485
+ }
486
+
487
+ var messages = new System.Collections.Generic.List<string>();
488
+ var deleted = 0;
489
+
490
+ foreach (var userId in userIds.Distinct())
491
+ {
492
+ var user = _context.AppUsers
493
+ .Include(u => u.Group)
494
+ .FirstOrDefault(u => u.Id == userId);
495
+
496
+ if (user == null)
497
+ {
498
+ continue;
499
+ }
500
+
501
+ if (IsProtectedSystemUser(user))
502
+ {
503
+ messages.Add($"User '{user.UserName}' cannot be deleted.");
504
+ continue;
505
+ }
506
+
507
+ var mappedContactGroup = ResolveContactGroupForUserGroup(user.Group);
508
+ if (mappedContactGroup != null)
509
+ {
510
+ var hasContacts = _context.Contacts.Any(c => c.GroupId == mappedContactGroup.Id);
511
+ if (hasContacts)
512
+ {
513
+ messages.Add($"Delete contacts in '{mappedContactGroup.Name}' first, then delete user '{user.UserName}'.");
514
+ continue;
515
+ }
516
+ }
517
+
518
+ _context.AppUsers.Remove(user);
519
+ deleted++;
520
+
521
+ _adminHistoryService.Log(
522
+ actionType: "Delete",
523
+ entityType: "User",
524
+ entityId: user.Id,
525
+ performedBy: currentUser?.UserName ?? "Unknown",
526
+ details: $"Deleted user '{user.UserName}'.");
527
+ }
528
+
529
+ if (deleted > 0)
530
+ {
531
+ _context.SaveChanges();
532
+ }
533
+
534
+ if (messages.Count > 0)
535
+ {
536
+ TempData["ErrorMessage"] = string.Join(" ", messages);
537
+ }
538
+
539
+ if (deleted > 0)
540
+ {
541
+ TempData["SuccessMessage"] = $"Deleted {deleted} user(s).";
542
+ }
543
+
544
+ return RedirectToAction(nameof(Users));
545
+ }
546
+
547
+ [HttpPost]
548
+ [ValidateAntiForgeryToken]
549
+ [RequireRight(RightsCatalog.AdminManageGroups)]
550
+ public IActionResult DeleteSelectedGroups(List<int> groupIds)
551
+ {
552
+ var currentUser = _userContextService.CurrentUser;
553
+ if (!IsSuperAdminUser(currentUser))
554
+ {
555
+ return Forbid();
556
+ }
557
+
558
+ if (groupIds == null || groupIds.Count == 0)
559
+ {
560
+ TempData["ErrorMessage"] = "No groups selected.";
561
+ return RedirectToAction(nameof(Groups));
562
+ }
563
+
564
+ var messages = new System.Collections.Generic.List<string>();
565
+ var deleted = 0;
566
+
567
+ foreach (var groupId in groupIds.Distinct())
568
+ {
569
+ var group = _context.UserGroups
570
+ .Include(g => g.Users)
571
+ .FirstOrDefault(g => g.Id == groupId);
572
+
573
+ if (group == null)
574
+ {
575
+ continue;
576
+ }
577
+
578
+ if (string.Equals(group.Name, "Administrators", StringComparison.OrdinalIgnoreCase))
579
+ {
580
+ messages.Add("Administrators group cannot be deleted.");
581
+ continue;
582
+ }
583
+
584
+ var mappedContactGroup = ResolveContactGroupForUserGroup(group);
585
+ if (mappedContactGroup != null)
586
+ {
587
+ var hasContacts = _context.Contacts.Any(c => c.GroupId == mappedContactGroup.Id);
588
+ if (hasContacts)
589
+ {
590
+ messages.Add($"Delete contacts in '{mappedContactGroup.Name}' first, then delete users, then delete group '{group.Name}'.");
591
+ continue;
592
+ }
593
+ }
594
+
595
+ if (group.Users.Any())
596
+ {
597
+ messages.Add($"Delete users in group '{group.Name}' first, then delete the group.");
598
+ continue;
599
+ }
600
+
601
+ _context.UserGroups.Remove(group);
602
+ deleted++;
603
+
604
+ _adminHistoryService.Log(
605
+ actionType: "Delete",
606
+ entityType: "Group",
607
+ entityId: group.Id,
608
+ performedBy: currentUser?.UserName ?? "Unknown",
609
+ details: $"Deleted group '{group.Name}'.");
610
+ }
611
+
612
+ if (deleted > 0)
613
+ {
614
+ _context.SaveChanges();
615
+ }
616
+
617
+ if (messages.Count > 0)
618
+ {
619
+ TempData["ErrorMessage"] = string.Join(" ", messages);
620
+ }
621
+
622
+ if (deleted > 0)
623
+ {
624
+ TempData["SuccessMessage"] = $"Deleted {deleted} group(s).";
625
+ }
626
+
627
+ return RedirectToAction(nameof(Groups));
628
+ }
629
+
630
+ [HttpPost]
631
+ [ValidateAntiForgeryToken]
632
+ [RequireRight(RightsCatalog.AdminManageGroups)]
633
+ public IActionResult EditGroup(GroupEditViewModel model)
634
+ {
635
+ var group = _context.UserGroups.FirstOrDefault(g => g.Id == model.Id);
636
+ if (group == null)
637
+ return NotFound();
638
+
639
+ if (_context.UserGroups.Any(g => g.Name == model.Name && g.Id != model.Id))
640
+ {
641
+ ModelState.AddModelError(nameof(GroupEditViewModel.Name), "Group name already exists.");
642
+ }
643
+
644
+ if (!ModelState.IsValid)
645
+ return View(model);
646
+
647
+ if (string.Equals(group.Name, "Administrators", StringComparison.OrdinalIgnoreCase) &&
648
+ !string.Equals(model.Name, "Administrators", StringComparison.OrdinalIgnoreCase))
649
+ {
650
+ ModelState.AddModelError(nameof(GroupEditViewModel.Name), "Administrators group name cannot be changed.");
651
+ return View(model);
652
+ }
653
+
654
+ group.Name = model.Name;
655
+ group.Description = model.Description;
656
+ _context.SaveChanges();
657
+
658
+ _adminHistoryService.Log(
659
+ actionType: "Edit",
660
+ entityType: "Group",
661
+ entityId: group.Id,
662
+ performedBy: _userContextService.CurrentUser?.UserName ?? "Unknown",
663
+ details: $"Edited group '{group.Name}'.");
664
+
665
+ TempData["SuccessMessage"] = "Group updated successfully.";
666
+ return RedirectToAction("Groups");
667
+ }
668
+
669
+ [RequireRight(RightsCatalog.AdminManageRights)]
670
+ public IActionResult GroupRights(int id)
671
+ {
672
+ var group = _context.UserGroups.FirstOrDefault(g => g.Id == id);
673
+ if (group == null)
674
+ return NotFound();
675
+
676
+ var groupRights = _context.GroupRights
677
+ .Where(r => r.UserGroupId == id)
678
+ .ToList();
679
+
680
+ var rights = RightsCatalog.All.Select(r =>
681
+ {
682
+ var existing = groupRights.FirstOrDefault(gr => gr.RightKey == r.Key);
683
+ return new RightAssignmentViewModel
684
+ {
685
+ Key = r.Key,
686
+ Label = r.Label,
687
+ Category = r.Category,
688
+ IsGranted = existing?.IsGranted ?? false
689
+ };
690
+ }).ToList();
691
+
692
+ var model = new GroupRightsViewModel
693
+ {
694
+ GroupId = group.Id,
695
+ GroupName = group.Name,
696
+ Rights = rights
697
+ };
698
+
699
+ return View(model);
700
+ }
701
+
702
+ [HttpPost]
703
+ [ValidateAntiForgeryToken]
704
+ [RequireRight(RightsCatalog.AdminManageRights)]
705
+ public IActionResult GroupRights(GroupRightsViewModel model)
706
+ {
707
+ foreach (var right in model.Rights)
708
+ {
709
+ var existing = _context.GroupRights.FirstOrDefault(r => r.UserGroupId == model.GroupId && r.RightKey == right.Key);
710
+ if (existing == null)
711
+ {
712
+ _context.GroupRights.Add(new GroupRight
713
+ {
714
+ UserGroupId = model.GroupId,
715
+ RightKey = right.Key,
716
+ IsGranted = right.IsGranted
717
+ });
718
+ }
719
+ else
720
+ {
721
+ existing.IsGranted = right.IsGranted;
722
+ }
723
+ }
724
+
725
+ _context.SaveChanges();
726
+
727
+ _adminHistoryService.Log(
728
+ actionType: "Edit",
729
+ entityType: "GroupRights",
730
+ entityId: model.GroupId,
731
+ performedBy: _userContextService.CurrentUser?.UserName ?? "Unknown",
732
+ details: $"Updated rights for group '{model.GroupName}'.");
733
+
734
+ TempData["SuccessMessage"] = "Group rights updated successfully.";
735
+ return RedirectToAction("GroupRights", new { id = model.GroupId });
736
+ }
737
+
738
+ [RequireRight(RightsCatalog.AdminManageUsers)]
739
+ public IActionResult History(int take = 200)
740
+ {
741
+ var entries = _adminHistoryService
742
+ .GetLatest(take)
743
+ .Select(e => new AdminHistoryEntryViewModel
744
+ {
745
+ ActionType = e.ActionType,
746
+ EntityType = e.EntityType,
747
+ EntityId = e.EntityId,
748
+ Details = e.Details,
749
+ PerformedBy = e.PerformedBy,
750
+ PerformedAt = e.PerformedAt
751
+ })
752
+ .ToList();
753
+
754
+ var model = new AdminHistoryListViewModel
755
+ {
756
+ Entries = entries
757
+ };
758
+
759
+ return View(model);
760
+ }
761
+
762
+ [RequireRight(RightsCatalog.AdminManageUsers)]
763
+ public IActionResult RestoreContacts()
764
+ {
765
+ var currentUser = _userContextService.CurrentUser;
766
+ if (!IsSuperAdminUser(currentUser))
767
+ {
768
+ return Forbid();
769
+ }
770
+
771
+ return View();
772
+ }
773
+
774
+ [HttpPost]
775
+ [ValidateAntiForgeryToken]
776
+ [RequireRight(RightsCatalog.AdminManageUsers)]
777
+ public async Task<IActionResult> RestoreContacts(IFormFile backupZip)
778
+ {
779
+ var currentUser = _userContextService.CurrentUser;
780
+ if (!IsSuperAdminUser(currentUser))
781
+ {
782
+ return Forbid();
783
+ }
784
+
785
+ if (backupZip == null || backupZip.Length == 0)
786
+ {
787
+ TempData["ErrorMessage"] = "Please select a backup ZIP file.";
788
+ return RedirectToAction(nameof(RestoreContacts));
789
+ }
790
+
791
+ var tempRoot = Path.Combine(Path.GetTempPath(), "cms-restore-" + Guid.NewGuid().ToString("N"));
792
+ Directory.CreateDirectory(tempRoot);
793
+
794
+ try
795
+ {
796
+ var zipPath = Path.Combine(tempRoot, "backup.zip");
797
+ await using (var fs = System.IO.File.Create(zipPath))
798
+ {
799
+ await backupZip.CopyToAsync(fs);
800
+ }
801
+
802
+ ZipFile.ExtractToDirectory(zipPath, tempRoot);
803
+
804
+ // Expect: ContactManagement.db + uploads/photos + uploads/documents
805
+ var backupDbPath = Directory.GetFiles(tempRoot, "ContactManagement.db", SearchOption.AllDirectories)
806
+ .FirstOrDefault();
807
+
808
+ if (string.IsNullOrWhiteSpace(backupDbPath) || !System.IO.File.Exists(backupDbPath))
809
+ {
810
+ TempData["ErrorMessage"] = "Backup ZIP must contain ContactManagement.db";
811
+ return RedirectToAction(nameof(RestoreContacts));
812
+ }
813
+
814
+ var backupUploadsRoot = Directory.GetDirectories(tempRoot, "uploads", SearchOption.AllDirectories)
815
+ .FirstOrDefault();
816
+
817
+ if (string.IsNullOrWhiteSpace(backupUploadsRoot) || !Directory.Exists(backupUploadsRoot))
818
+ {
819
+ TempData["ErrorMessage"] = "Backup ZIP must contain uploads/photos and uploads/documents.";
820
+ return RedirectToAction(nameof(RestoreContacts));
821
+ }
822
+
823
+ var targetUploadsRoot = GetUploadsRoot();
824
+
825
+ var backupConnString = new SqliteConnectionStringBuilder { DataSource = backupDbPath }.ToString();
826
+ await using var backupConn = new SqliteConnection(backupConnString);
827
+ await backupConn.OpenAsync();
828
+
829
+ var wantedNames = new[] { "ABRAHAM", "PREMA", "PONNURAJ" };
830
+ var contactsToRestore = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object?>>();
831
+
832
+ var contactCmd = backupConn.CreateCommand();
833
+ contactCmd.CommandText = @"
834
+ SELECT Id, FirstName, LastName, NickName, Gender, DateOfBirth, Email,
835
+ Mobile1, Mobile2, Mobile3, WhatsAppNumber,
836
+ PassportNumber, PanNumber, AadharNumber, DrivingLicenseNumber, VotersId,
837
+ BankAccountNumber, BankName, BranchName, IfscCode,
838
+ Address, City, State, PostalCode, Country,
839
+ PhotoPath, GroupId, OtherDetails, CreatedAt, UpdatedAt
840
+ FROM Contacts
841
+ WHERE lower(FirstName) IN ('abraham','prema','ponnuraj');";
842
+
843
+ await using (var reader = await contactCmd.ExecuteReaderAsync())
844
+ {
845
+ while (await reader.ReadAsync())
846
+ {
847
+ var dict = new System.Collections.Generic.Dictionary<string, object?>();
848
+ for (var i = 0; i < reader.FieldCount; i++)
849
+ {
850
+ dict[reader.GetName(i)] = await reader.IsDBNullAsync(i) ? null : reader.GetValue(i);
851
+ }
852
+ contactsToRestore.Add(dict);
853
+ }
854
+ }
855
+
856
+ if (contactsToRestore.Count == 0)
857
+ {
858
+ TempData["ErrorMessage"] = "No Abraham/Prema/Ponnuraj contacts were found in the backup DB.";
859
+ return RedirectToAction(nameof(RestoreContacts));
860
+ }
861
+
862
+ // Pull related rows from backup
863
+ var backupContactIds = contactsToRestore.Select(c => Convert.ToInt32(c["Id"])).ToArray();
864
+ var idList = string.Join(",", backupContactIds);
865
+
866
+ var backupPhotos = new System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object?>>>();
867
+ var photoCmd = backupConn.CreateCommand();
868
+ photoCmd.CommandText = $@"
869
+ SELECT ContactId, PhotoPath, FileName, FileSize, ContentType, IsProfilePhoto, UploadedAt
870
+ FROM ContactPhotos
871
+ WHERE ContactId IN ({idList});";
872
+ await using (var reader = await photoCmd.ExecuteReaderAsync())
873
+ {
874
+ while (await reader.ReadAsync())
875
+ {
876
+ var contactId = reader.GetInt32(0);
877
+ if (!backupPhotos.TryGetValue(contactId, out var list))
878
+ {
879
+ list = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object?>>();
880
+ backupPhotos[contactId] = list;
881
+ }
882
+
883
+ list.Add(new System.Collections.Generic.Dictionary<string, object?>
884
+ {
885
+ ["PhotoPath"] = reader.IsDBNull(1) ? null : reader.GetString(1),
886
+ ["FileName"] = reader.IsDBNull(2) ? null : reader.GetString(2),
887
+ ["FileSize"] = reader.IsDBNull(3) ? 0L : reader.GetInt64(3),
888
+ ["ContentType"] = reader.IsDBNull(4) ? null : reader.GetString(4),
889
+ ["IsProfilePhoto"] = !reader.IsDBNull(5) && reader.GetBoolean(5),
890
+ ["UploadedAt"] = reader.IsDBNull(6) ? (DateTime?)null : reader.GetDateTime(6)
891
+ });
892
+ }
893
+ }
894
+
895
+ var backupDocs = new System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object?>>>();
896
+ var docCmd = backupConn.CreateCommand();
897
+ docCmd.CommandText = $@"
898
+ SELECT ContactId, DocumentPath, FileName, FileSize, ContentType, DocumentType, UploadedAt
899
+ FROM ContactDocuments
900
+ WHERE ContactId IN ({idList});";
901
+ await using (var reader = await docCmd.ExecuteReaderAsync())
902
+ {
903
+ while (await reader.ReadAsync())
904
+ {
905
+ var contactId = reader.GetInt32(0);
906
+ if (!backupDocs.TryGetValue(contactId, out var list))
907
+ {
908
+ list = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object?>>();
909
+ backupDocs[contactId] = list;
910
+ }
911
+
912
+ list.Add(new System.Collections.Generic.Dictionary<string, object?>
913
+ {
914
+ ["DocumentPath"] = reader.IsDBNull(1) ? null : reader.GetString(1),
915
+ ["FileName"] = reader.IsDBNull(2) ? null : reader.GetString(2),
916
+ ["FileSize"] = reader.IsDBNull(3) ? 0L : reader.GetInt64(3),
917
+ ["ContentType"] = reader.IsDBNull(4) ? null : reader.GetString(4),
918
+ ["DocumentType"] = reader.IsDBNull(5) ? null : reader.GetString(5),
919
+ ["UploadedAt"] = reader.IsDBNull(6) ? (DateTime?)null : reader.GetDateTime(6)
920
+ });
921
+ }
922
+ }
923
+
924
+ var backupBanks = new System.Collections.Generic.Dictionary<int, System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object?>>>();
925
+ var bankCmd = backupConn.CreateCommand();
926
+ bankCmd.CommandText = $@"
927
+ SELECT ContactId, AccountNumber, BankName, BranchName, IfscCode, CreatedAt, UpdatedAt
928
+ FROM ContactBankAccounts
929
+ WHERE ContactId IN ({idList});";
930
+ await using (var reader = await bankCmd.ExecuteReaderAsync())
931
+ {
932
+ while (await reader.ReadAsync())
933
+ {
934
+ var contactId = reader.GetInt32(0);
935
+ if (!backupBanks.TryGetValue(contactId, out var list))
936
+ {
937
+ list = new System.Collections.Generic.List<System.Collections.Generic.Dictionary<string, object?>>();
938
+ backupBanks[contactId] = list;
939
+ }
940
+
941
+ list.Add(new System.Collections.Generic.Dictionary<string, object?>
942
+ {
943
+ ["AccountNumber"] = reader.IsDBNull(1) ? null : reader.GetString(1),
944
+ ["BankName"] = reader.IsDBNull(2) ? null : reader.GetString(2),
945
+ ["BranchName"] = reader.IsDBNull(3) ? null : reader.GetString(3),
946
+ ["IfscCode"] = reader.IsDBNull(4) ? null : reader.GetString(4),
947
+ ["CreatedAt"] = reader.IsDBNull(5) ? (DateTime?)null : reader.GetDateTime(5),
948
+ ["UpdatedAt"] = reader.IsDBNull(6) ? (DateTime?)null : reader.GetDateTime(6)
949
+ });
950
+ }
951
+ }
952
+
953
+ // Merge into current DB
954
+ _context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;
955
+
956
+ var restoredCount = 0;
957
+ var copiedFiles = 0;
958
+
959
+ foreach (var backupContact in contactsToRestore)
960
+ {
961
+ var firstName = backupContact["FirstName"]?.ToString() ?? string.Empty;
962
+ var lastName = backupContact["LastName"]?.ToString();
963
+ var nickName = backupContact["NickName"]?.ToString();
964
+
965
+ var normalizedFirstName = NormalizeText(firstName);
966
+ if (!wantedNames.Contains(normalizedFirstName))
967
+ {
968
+ continue;
969
+ }
970
+
971
+ var normalizedLastName = NormalizeText(lastName);
972
+ var normalizedNickName = NormalizeText(nickName);
973
+
974
+ var existing = _context.Contacts
975
+ .Include(c => c.Photos)
976
+ .Include(c => c.Documents)
977
+ .Include(c => c.BankAccounts)
978
+ .FirstOrDefault(c =>
979
+ NormalizeText(c.FirstName) == normalizedFirstName &&
980
+ NormalizeText(c.LastName) == normalizedLastName &&
981
+ NormalizeText(c.NickName) == normalizedNickName);
982
+
983
+ if (existing == null)
984
+ {
985
+ existing = new Contact();
986
+ _context.Contacts.Add(existing);
987
+ }
988
+
989
+ // Copy scalar fields
990
+ existing.FirstName = firstName;
991
+ existing.LastName = lastName;
992
+ existing.NickName = nickName;
993
+ existing.Gender = backupContact["Gender"]?.ToString();
994
+ existing.DateOfBirth = backupContact["DateOfBirth"] as DateTime?;
995
+ existing.Email = backupContact["Email"]?.ToString();
996
+ existing.Mobile1 = backupContact["Mobile1"]?.ToString();
997
+ existing.Mobile2 = backupContact["Mobile2"]?.ToString();
998
+ existing.Mobile3 = backupContact["Mobile3"]?.ToString();
999
+ existing.WhatsAppNumber = backupContact["WhatsAppNumber"]?.ToString();
1000
+ existing.PassportNumber = backupContact["PassportNumber"]?.ToString();
1001
+ existing.PanNumber = backupContact["PanNumber"]?.ToString();
1002
+ existing.AadharNumber = backupContact["AadharNumber"]?.ToString();
1003
+ existing.DrivingLicenseNumber = backupContact["DrivingLicenseNumber"]?.ToString();
1004
+ existing.VotersId = backupContact["VotersId"]?.ToString();
1005
+ existing.BankAccountNumber = backupContact["BankAccountNumber"]?.ToString();
1006
+ existing.BankName = backupContact["BankName"]?.ToString();
1007
+ existing.BranchName = backupContact["BranchName"]?.ToString();
1008
+ existing.IfscCode = backupContact["IfscCode"]?.ToString();
1009
+ existing.Address = backupContact["Address"]?.ToString();
1010
+ existing.City = backupContact["City"]?.ToString();
1011
+ existing.State = backupContact["State"]?.ToString();
1012
+ existing.PostalCode = backupContact["PostalCode"]?.ToString();
1013
+ existing.Country = backupContact["Country"]?.ToString();
1014
+ existing.PhotoPath = backupContact["PhotoPath"]?.ToString();
1015
+ existing.GroupId = backupContact["GroupId"] as int?;
1016
+ existing.OtherDetails = backupContact["OtherDetails"]?.ToString();
1017
+
1018
+ existing.CreatedAt = backupContact["CreatedAt"] as DateTime? ?? existing.CreatedAt;
1019
+ existing.UpdatedAt = DateTime.Now;
1020
+
1021
+ await _context.SaveChangesAsync();
1022
+
1023
+ // Replace related data
1024
+ if (existing.Photos.Any())
1025
+ {
1026
+ _context.ContactPhotos.RemoveRange(existing.Photos);
1027
+ }
1028
+ if (existing.Documents.Any())
1029
+ {
1030
+ _context.ContactDocuments.RemoveRange(existing.Documents);
1031
+ }
1032
+ if (existing.BankAccounts.Any())
1033
+ {
1034
+ _context.ContactBankAccounts.RemoveRange(existing.BankAccounts);
1035
+ }
1036
+ await _context.SaveChangesAsync();
1037
+
1038
+ var sourceId = Convert.ToInt32(backupContact["Id"]);
1039
+
1040
+ if (backupPhotos.TryGetValue(sourceId, out var photos))
1041
+ {
1042
+ foreach (var row in photos)
1043
+ {
1044
+ var photoPath = row["PhotoPath"]?.ToString();
1045
+ if (!string.IsNullOrWhiteSpace(photoPath))
1046
+ {
1047
+ CopyBackupFile(backupUploadsRoot, targetUploadsRoot, photoPath, ref copiedFiles);
1048
+ }
1049
+
1050
+ _context.ContactPhotos.Add(new ContactPhoto
1051
+ {
1052
+ ContactId = existing.Id,
1053
+ PhotoPath = photoPath ?? string.Empty,
1054
+ FileName = row["FileName"]?.ToString() ?? string.Empty,
1055
+ FileSize = row["FileSize"] is long l ? l : 0L,
1056
+ ContentType = row["ContentType"]?.ToString() ?? "application/octet-stream",
1057
+ IsProfilePhoto = row["IsProfilePhoto"] is bool b && b,
1058
+ UploadedAt = row["UploadedAt"] as DateTime? ?? DateTime.Now
1059
+ });
1060
+ }
1061
+ }
1062
+
1063
+ if (backupDocs.TryGetValue(sourceId, out var docs))
1064
+ {
1065
+ foreach (var row in docs)
1066
+ {
1067
+ var docPath = row["DocumentPath"]?.ToString();
1068
+ if (!string.IsNullOrWhiteSpace(docPath))
1069
+ {
1070
+ CopyBackupFile(backupUploadsRoot, targetUploadsRoot, docPath, ref copiedFiles);
1071
+ }
1072
+
1073
+ _context.ContactDocuments.Add(new ContactDocument
1074
+ {
1075
+ ContactId = existing.Id,
1076
+ DocumentPath = docPath ?? string.Empty,
1077
+ FileName = row["FileName"]?.ToString() ?? string.Empty,
1078
+ FileSize = row["FileSize"] is long l ? l : 0L,
1079
+ ContentType = row["ContentType"]?.ToString() ?? "application/octet-stream",
1080
+ DocumentType = row["DocumentType"]?.ToString() ?? "Other",
1081
+ UploadedAt = row["UploadedAt"] as DateTime? ?? DateTime.Now
1082
+ });
1083
+ }
1084
+ }
1085
+
1086
+ if (backupBanks.TryGetValue(sourceId, out var banks))
1087
+ {
1088
+ foreach (var row in banks)
1089
+ {
1090
+ _context.ContactBankAccounts.Add(new ContactBankAccount
1091
+ {
1092
+ ContactId = existing.Id,
1093
+ AccountNumber = row["AccountNumber"]?.ToString(),
1094
+ BankName = row["BankName"]?.ToString(),
1095
+ BranchName = row["BranchName"]?.ToString(),
1096
+ IfscCode = row["IfscCode"]?.ToString(),
1097
+ CreatedAt = row["CreatedAt"] as DateTime? ?? DateTime.Now,
1098
+ UpdatedAt = row["UpdatedAt"] as DateTime? ?? DateTime.Now
1099
+ });
1100
+ }
1101
+ }
1102
+
1103
+ await _context.SaveChangesAsync();
1104
+ restoredCount++;
1105
+ }
1106
+
1107
+ _context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
1108
+
1109
+ _adminHistoryService.Log(
1110
+ actionType: "Restore",
1111
+ entityType: "Contacts",
1112
+ entityId: 0,
1113
+ performedBy: currentUser?.UserName ?? "Unknown",
1114
+ details: $"Restored {restoredCount} key contacts from backup (copied {copiedFiles} files)."
1115
+ );
1116
+
1117
+ TempData["SuccessMessage"] = $"Restored {restoredCount} contact(s) and copied {copiedFiles} file(s).";
1118
+ return RedirectToAction("Index", "Home");
1119
+ }
1120
+ catch (Exception ex)
1121
+ {
1122
+ TempData["ErrorMessage"] = $"Restore failed: {ex.Message}";
1123
+ return RedirectToAction(nameof(RestoreContacts));
1124
+ }
1125
+ finally
1126
+ {
1127
+ try
1128
+ {
1129
+ if (Directory.Exists(tempRoot))
1130
+ {
1131
+ Directory.Delete(tempRoot, recursive: true);
1132
+ }
1133
+ }
1134
+ catch
1135
+ {
1136
+ // ignore cleanup issues
1137
+ }
1138
+ }
1139
+
1140
+ static void CopyBackupFile(string backupUploadsRoot, string targetUploadsRoot, string webPath, ref int copiedFiles)
1141
+ {
1142
+ var normalized = webPath.Trim();
1143
+ if (normalized.StartsWith("/") == false)
1144
+ {
1145
+ normalized = "/" + normalized;
1146
+ }
1147
+
1148
+ if (!normalized.StartsWith("/uploads/", StringComparison.OrdinalIgnoreCase))
1149
+ {
1150
+ return;
1151
+ }
1152
+
1153
+ var relative = normalized.Substring("/uploads/".Length).Replace('/', Path.DirectorySeparatorChar);
1154
+ var source = Path.Combine(backupUploadsRoot, relative);
1155
+ var dest = Path.Combine(targetUploadsRoot, relative);
1156
+
1157
+ var destDir = Path.GetDirectoryName(dest);
1158
+ if (!string.IsNullOrWhiteSpace(destDir))
1159
+ {
1160
+ Directory.CreateDirectory(destDir);
1161
+ }
1162
+
1163
+ if (System.IO.File.Exists(source))
1164
+ {
1165
+ System.IO.File.Copy(source, dest, overwrite: true);
1166
+ copiedFiles++;
1167
+ }
1168
+ }
1169
+ }
1170
+ }
1171
+ }
ContactManagementAPI/Controllers/DocumentController.cs ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.AspNetCore.Mvc;
2
+ using Microsoft.EntityFrameworkCore;
3
+ using ContactManagementAPI.Data;
4
+ using ContactManagementAPI.Models;
5
+ using ContactManagementAPI.Services;
6
+ using ContactManagementAPI.Security;
7
+
8
+ namespace ContactManagementAPI.Controllers
9
+ {
10
+ public class DocumentController : Controller
11
+ {
12
+ private readonly ApplicationDbContext _context;
13
+ private readonly FileUploadService _fileUploadService;
14
+
15
+ public DocumentController(ApplicationDbContext context, FileUploadService fileUploadService)
16
+ {
17
+ _context = context;
18
+ _fileUploadService = fileUploadService;
19
+ }
20
+
21
+ // GET: Document/List/5
22
+ [RequireRight(RightsCatalog.ContactsView)]
23
+ public async Task<IActionResult> List(int? id)
24
+ {
25
+ if (id == null)
26
+ return NotFound();
27
+
28
+ var contact = await _context.Contacts
29
+ .Include(c => c.Documents)
30
+ .FirstOrDefaultAsync(c => c.Id == id);
31
+
32
+ if (contact == null)
33
+ return NotFound();
34
+
35
+ return View(contact);
36
+ }
37
+
38
+ // POST: Document/Upload
39
+ [HttpPost]
40
+ [RequireRight(RightsCatalog.DocumentsManage)]
41
+ public async Task<IActionResult> Upload(int contactId, IFormFile documentFile, string documentType = "Other")
42
+ {
43
+ var contact = await _context.Contacts.FindAsync(contactId);
44
+ if (contact == null)
45
+ return Json(new { success = false, message = "Contact not found" });
46
+
47
+ var (success, filePath, errorMessage) = await _fileUploadService.UploadDocumentAsync(documentFile, contactId);
48
+ if (!success)
49
+ return Json(new { success = false, message = errorMessage });
50
+
51
+ var document = new ContactDocument
52
+ {
53
+ ContactId = contactId,
54
+ DocumentPath = filePath,
55
+ FileName = documentFile.FileName,
56
+ FileSize = documentFile.Length,
57
+ ContentType = documentFile.ContentType,
58
+ DocumentType = documentType,
59
+ UploadedAt = DateTime.Now
60
+ };
61
+
62
+ _context.ContactDocuments.Add(document);
63
+ await _context.SaveChangesAsync();
64
+
65
+ return Json(new { success = true, documentId = document.Id, documentPath = filePath });
66
+ }
67
+
68
+ // POST: Document/Delete
69
+ [HttpPost]
70
+ [RequireRight(RightsCatalog.DocumentsManage)]
71
+ public async Task<IActionResult> Delete(int id, int contactId)
72
+ {
73
+ var document = await _context.ContactDocuments.FirstOrDefaultAsync(d => d.Id == id && d.ContactId == contactId);
74
+ if (document == null)
75
+ return Json(new { success = false, message = "Document not found" });
76
+
77
+ _fileUploadService.DeleteFile(document.DocumentPath);
78
+ _context.ContactDocuments.Remove(document);
79
+ await _context.SaveChangesAsync();
80
+
81
+ return Json(new { success = true, message = "Document deleted" });
82
+ }
83
+
84
+ // GET: Document/Download
85
+ [RequireRight(RightsCatalog.ContactsView)]
86
+ public async Task<IActionResult> Download(int id, int contactId)
87
+ {
88
+ var document = await _context.ContactDocuments.FirstOrDefaultAsync(d => d.Id == id && d.ContactId == contactId);
89
+ if (document == null)
90
+ return NotFound();
91
+
92
+ var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", document.DocumentPath.TrimStart('/'));
93
+ if (!System.IO.File.Exists(filePath))
94
+ return NotFound();
95
+
96
+ var fileBytes = System.IO.File.ReadAllBytes(filePath);
97
+ return File(fileBytes, document.ContentType, document.FileName);
98
+ }
99
+ }
100
+ }
ContactManagementAPI/Controllers/HomeController.cs ADDED
@@ -0,0 +1,1045 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.AspNetCore.Mvc;
2
+ using Microsoft.EntityFrameworkCore;
3
+ using ContactManagementAPI.Data;
4
+ using ContactManagementAPI.Models;
5
+ using ContactManagementAPI.Services;
6
+ using ContactManagementAPI.Security;
7
+ using System.Globalization;
8
+
9
+ namespace ContactManagementAPI.Controllers
10
+ {
11
+ public class HomeController : Controller
12
+ {
13
+ private readonly ApplicationDbContext _context;
14
+ private readonly FileUploadService _fileUploadService;
15
+ private readonly ImportExportService _importExportService;
16
+ private readonly ContactStatisticsService _statisticsService;
17
+ private readonly UserContextService _userContextService;
18
+ private readonly AdminHistoryService _adminHistoryService;
19
+
20
+ public HomeController(
21
+ ApplicationDbContext context,
22
+ FileUploadService fileUploadService,
23
+ ImportExportService importExportService,
24
+ ContactStatisticsService statisticsService,
25
+ UserContextService userContextService,
26
+ AdminHistoryService adminHistoryService)
27
+ {
28
+ _context = context;
29
+ _fileUploadService = fileUploadService;
30
+ _importExportService = importExportService;
31
+ _statisticsService = statisticsService;
32
+ _userContextService = userContextService;
33
+ _adminHistoryService = adminHistoryService;
34
+ }
35
+
36
+ // GET: Home/Index - Display all contacts with search functionality
37
+ [RequireRight(RightsCatalog.ContactsView)]
38
+ public async Task<IActionResult> Index(string searchTerm = "")
39
+ {
40
+ var currentUser = _userContextService.CurrentUser;
41
+ if (currentUser == null)
42
+ {
43
+ return RedirectToAction("Login", "Account");
44
+ }
45
+
46
+ var contacts = ApplyContactScope(
47
+ _context.Contacts
48
+ .Include(c => c.Group)
49
+ .AsQueryable(),
50
+ currentUser)
51
+ .AsQueryable();
52
+
53
+ if (!string.IsNullOrEmpty(searchTerm))
54
+ {
55
+ contacts = contacts.Where(c =>
56
+ c.FirstName.Contains(searchTerm) ||
57
+ (c.LastName != null && c.LastName.Contains(searchTerm)) ||
58
+ (c.Email != null && c.Email.Contains(searchTerm)) ||
59
+ (c.Mobile1 != null && c.Mobile1.Contains(searchTerm)) ||
60
+ (c.Mobile2 != null && c.Mobile2.Contains(searchTerm)) ||
61
+ (c.Mobile3 != null && c.Mobile3.Contains(searchTerm)));
62
+ }
63
+
64
+ ViewBag.SearchTerm = searchTerm;
65
+ return View(await contacts.OrderByDescending(c => c.UpdatedAt).ToListAsync());
66
+ }
67
+
68
+ // GET: Home/Details/5
69
+ [RequireRight(RightsCatalog.ContactsView)]
70
+ public async Task<IActionResult> Details(int? id)
71
+ {
72
+ if (id == null)
73
+ return NotFound();
74
+
75
+ var currentUser = _userContextService.CurrentUser;
76
+ if (currentUser == null)
77
+ {
78
+ return RedirectToAction("Login", "Account");
79
+ }
80
+
81
+ var contact = await _context.Contacts
82
+ .Include(c => c.Group)
83
+ .Include(c => c.Photos)
84
+ .Include(c => c.Documents)
85
+ .Include(c => c.BankAccounts)
86
+ .FirstOrDefaultAsync(c => c.Id == id);
87
+
88
+ if (contact == null)
89
+ return NotFound();
90
+
91
+ if (!CanAccessContact(currentUser, contact))
92
+ {
93
+ TempData["ErrorMessage"] = "You can view only contacts from your group.";
94
+ return RedirectToAction(nameof(Index));
95
+ }
96
+
97
+ return View(contact);
98
+ }
99
+
100
+ // GET: Home/Create
101
+ [RequireRight(RightsCatalog.ContactsCreate)]
102
+ public IActionResult Create()
103
+ {
104
+ var currentUser = _userContextService.CurrentUser;
105
+ if (currentUser == null)
106
+ {
107
+ return RedirectToAction("Login", "Account");
108
+ }
109
+
110
+ PopulateFormData();
111
+
112
+ if (!currentUser.IsAdmin)
113
+ {
114
+ var scopedContactGroupId = ResolveContactGroupIdForUser(currentUser);
115
+ if (scopedContactGroupId.HasValue)
116
+ {
117
+ ViewData["ForcedGroupId"] = scopedContactGroupId.Value;
118
+ }
119
+ }
120
+
121
+ return View();
122
+ }
123
+
124
+ // POST: Home/Create
125
+ [HttpPost]
126
+ [ValidateAntiForgeryToken]
127
+ [RequireRight(RightsCatalog.ContactsCreate)]
128
+ public async Task<IActionResult> Create([Bind("FirstName,LastName,NickName,Gender,DateOfBirth,Email,Mobile1,Mobile2,Mobile3,WhatsAppNumber,PassportNumber,PanNumber,AadharNumber,DrivingLicenseNumber,VotersId,Address,City,State,PostalCode,Country,GroupId,OtherDetails")] Contact contact, List<ContactBankAccount>? bankAccounts, IFormFile? profilePhoto)
129
+ {
130
+ var currentUser = _userContextService.CurrentUser;
131
+ if (currentUser == null)
132
+ {
133
+ return RedirectToAction("Login", "Account");
134
+ }
135
+
136
+ if (!currentUser.IsAdmin)
137
+ {
138
+ var scopedContactGroupId = ResolveContactGroupIdForUser(currentUser);
139
+ if (!scopedContactGroupId.HasValue)
140
+ {
141
+ ModelState.AddModelError(nameof(Contact.GroupId), "Your account is not assigned to a contact group.");
142
+ }
143
+ else
144
+ {
145
+ contact.GroupId = scopedContactGroupId.Value;
146
+ }
147
+ }
148
+
149
+ NormalizeOptionalBankAccountModelState();
150
+ ValidateDuplicateContact(contact);
151
+
152
+ if (ModelState.IsValid)
153
+ {
154
+ contact.CreatedAt = DateTime.Now;
155
+ contact.UpdatedAt = DateTime.Now;
156
+
157
+ var preparedBankAccounts = PrepareBankAccounts(bankAccounts);
158
+ SyncLegacyBankFields(contact, preparedBankAccounts.FirstOrDefault());
159
+
160
+ // Save contact first to get the ID
161
+ _context.Add(contact);
162
+ await _context.SaveChangesAsync();
163
+
164
+ if (preparedBankAccounts.Any())
165
+ {
166
+ foreach (var bankAccount in preparedBankAccounts)
167
+ {
168
+ bankAccount.ContactId = contact.Id;
169
+ }
170
+
171
+ _context.ContactBankAccounts.AddRange(preparedBankAccounts);
172
+ await _context.SaveChangesAsync();
173
+ }
174
+
175
+ // Handle profile photo upload after we have the contact ID
176
+ if (profilePhoto != null)
177
+ {
178
+ var result = await _fileUploadService.UploadPhotoAsync(profilePhoto, contact.Id);
179
+ if (result.Success)
180
+ {
181
+ contact.PhotoPath = result.FilePath;
182
+ _context.Update(contact);
183
+ await _context.SaveChangesAsync();
184
+ }
185
+ }
186
+
187
+ _adminHistoryService.Log(
188
+ actionType: "Create",
189
+ entityType: "Contact",
190
+ entityId: contact.Id,
191
+ performedBy: _userContextService.CurrentUser?.UserName ?? "Unknown",
192
+ details: $"Created contact '{contact.FirstName} {contact.LastName}'.");
193
+
194
+ TempData["SuccessMessage"] = "Contact created successfully!";
195
+ return RedirectToAction(nameof(Details), new { id = contact.Id });
196
+ }
197
+ PopulateFormData(contact, PrepareBankAccounts(bankAccounts));
198
+ return View(contact);
199
+ }
200
+
201
+ // GET: Home/Edit/5
202
+ [RequireRight(RightsCatalog.ContactsEdit)]
203
+ public async Task<IActionResult> Edit(int? id)
204
+ {
205
+ if (id == null)
206
+ return NotFound();
207
+
208
+ var currentUser = _userContextService.CurrentUser;
209
+ if (currentUser == null)
210
+ {
211
+ return RedirectToAction("Login", "Account");
212
+ }
213
+
214
+ if (!currentUser.IsAdmin)
215
+ {
216
+ TempData["ErrorMessage"] = "Only admin can edit existing contacts.";
217
+ return RedirectToAction(nameof(Index));
218
+ }
219
+
220
+ var contact = await _context.Contacts
221
+ .Include(c => c.BankAccounts)
222
+ .FirstOrDefaultAsync(c => c.Id == id);
223
+ if (contact == null)
224
+ return NotFound();
225
+
226
+ PopulateFormData(contact);
227
+ return View(contact);
228
+ }
229
+
230
+ // POST: Home/Edit/5
231
+ [HttpPost]
232
+ [ValidateAntiForgeryToken]
233
+ [RequireRight(RightsCatalog.ContactsEdit)]
234
+ public async Task<IActionResult> Edit(int id, List<ContactBankAccount>? bankAccounts, IFormFile? profilePhoto)
235
+ {
236
+ var currentUser = _userContextService.CurrentUser;
237
+ if (currentUser == null)
238
+ {
239
+ return RedirectToAction("Login", "Account");
240
+ }
241
+
242
+ if (!currentUser.IsAdmin)
243
+ {
244
+ TempData["ErrorMessage"] = "Only admin can edit existing contacts.";
245
+ return RedirectToAction(nameof(Index));
246
+ }
247
+
248
+ NormalizeOptionalBankAccountModelState();
249
+
250
+ var existingContact = await _context.Contacts
251
+ .AsTracking()
252
+ .Include(c => c.BankAccounts)
253
+ .FirstOrDefaultAsync(c => c.Id == id);
254
+
255
+ if (existingContact == null)
256
+ return NotFound();
257
+
258
+ var updateSucceeded = await TryUpdateModelAsync(
259
+ existingContact,
260
+ "",
261
+ c => c.FirstName,
262
+ c => c.LastName,
263
+ c => c.NickName,
264
+ c => c.Gender,
265
+ c => c.DateOfBirth,
266
+ c => c.Email,
267
+ c => c.Mobile1,
268
+ c => c.Mobile2,
269
+ c => c.Mobile3,
270
+ c => c.WhatsAppNumber,
271
+ c => c.PassportNumber,
272
+ c => c.PanNumber,
273
+ c => c.AadharNumber,
274
+ c => c.DrivingLicenseNumber,
275
+ c => c.VotersId,
276
+ c => c.Address,
277
+ c => c.City,
278
+ c => c.State,
279
+ c => c.PostalCode,
280
+ c => c.Country,
281
+ c => c.GroupId,
282
+ c => c.OtherDetails);
283
+
284
+ if (!updateSucceeded)
285
+ {
286
+ PopulateFormData(existingContact, PrepareBankAccounts(bankAccounts));
287
+ return View(existingContact);
288
+ }
289
+
290
+ ValidateDuplicateContact(existingContact, id);
291
+
292
+ if (ModelState.IsValid)
293
+ {
294
+ try
295
+ {
296
+ var postedGender = Request.Form["Gender"].ToString();
297
+ existingContact.Gender = string.IsNullOrWhiteSpace(postedGender) ? null : postedGender;
298
+
299
+ var postedDateOfBirth = Request.Form["DateOfBirth"].ToString();
300
+ if (string.IsNullOrWhiteSpace(postedDateOfBirth))
301
+ {
302
+ existingContact.DateOfBirth = null;
303
+ }
304
+ else if (DateTime.TryParseExact(postedDateOfBirth, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsedDateOfBirth) ||
305
+ DateTime.TryParse(postedDateOfBirth, out parsedDateOfBirth))
306
+ {
307
+ existingContact.DateOfBirth = parsedDateOfBirth;
308
+ }
309
+
310
+ existingContact.UpdatedAt = DateTime.Now;
311
+
312
+ var preparedBankAccounts = PrepareBankAccounts(bankAccounts);
313
+ if (existingContact.BankAccounts.Any())
314
+ {
315
+ _context.ContactBankAccounts.RemoveRange(existingContact.BankAccounts);
316
+ }
317
+
318
+ if (preparedBankAccounts.Any())
319
+ {
320
+ foreach (var bankAccount in preparedBankAccounts)
321
+ {
322
+ bankAccount.ContactId = existingContact.Id;
323
+ }
324
+
325
+ _context.ContactBankAccounts.AddRange(preparedBankAccounts);
326
+ }
327
+
328
+ SyncLegacyBankFields(existingContact, preparedBankAccounts.FirstOrDefault());
329
+
330
+ // Handle profile photo upload
331
+ if (profilePhoto != null)
332
+ {
333
+ var result = await _fileUploadService.UploadPhotoAsync(profilePhoto, existingContact.Id);
334
+ if (result.Success)
335
+ {
336
+ // Delete old photo if exists
337
+ if (!string.IsNullOrEmpty(existingContact.PhotoPath))
338
+ {
339
+ _fileUploadService.DeleteFile(existingContact.PhotoPath);
340
+ }
341
+ existingContact.PhotoPath = result.FilePath;
342
+ }
343
+ }
344
+
345
+ await _context.SaveChangesAsync();
346
+
347
+ _adminHistoryService.Log(
348
+ actionType: "Edit",
349
+ entityType: "Contact",
350
+ entityId: existingContact.Id,
351
+ performedBy: _userContextService.CurrentUser?.UserName ?? "Unknown",
352
+ details: $"Edited contact '{existingContact.FirstName} {existingContact.LastName}'.");
353
+
354
+ TempData["SuccessMessage"] = "Contact updated successfully!";
355
+ return RedirectToAction(nameof(Details), new { id = existingContact.Id });
356
+ }
357
+ catch (DbUpdateConcurrencyException)
358
+ {
359
+ if (!ContactExists(id))
360
+ return NotFound();
361
+ throw;
362
+ }
363
+ }
364
+ PopulateFormData(existingContact, PrepareBankAccounts(bankAccounts));
365
+ return View(existingContact);
366
+ }
367
+
368
+ // GET: Home/Delete/5
369
+ [RequireRight(RightsCatalog.ContactsDelete)]
370
+ public async Task<IActionResult> Delete(int? id)
371
+ {
372
+ if (id == null)
373
+ return NotFound();
374
+
375
+ var currentUser = _userContextService.CurrentUser;
376
+ if (currentUser == null)
377
+ {
378
+ return RedirectToAction("Login", "Account");
379
+ }
380
+
381
+ if (!currentUser.IsAdmin)
382
+ {
383
+ TempData["ErrorMessage"] = "Only admin can delete contacts.";
384
+ return RedirectToAction(nameof(Index));
385
+ }
386
+
387
+ var contact = await _context.Contacts
388
+ .Include(c => c.Group)
389
+ .FirstOrDefaultAsync(c => c.Id == id);
390
+
391
+ if (contact == null)
392
+ return NotFound();
393
+
394
+ return View(contact);
395
+ }
396
+
397
+ // POST: Home/Delete/5
398
+ [HttpPost, ActionName("Delete")]
399
+ [ValidateAntiForgeryToken]
400
+ [RequireRight(RightsCatalog.ContactsDelete)]
401
+ public async Task<IActionResult> DeleteConfirmed(int id)
402
+ {
403
+ var currentUser = _userContextService.CurrentUser;
404
+ if (currentUser == null)
405
+ {
406
+ return RedirectToAction("Login", "Account");
407
+ }
408
+
409
+ if (!currentUser.IsAdmin)
410
+ {
411
+ TempData["ErrorMessage"] = "Only admin can delete contacts.";
412
+ return RedirectToAction(nameof(Index));
413
+ }
414
+
415
+ var contact = await _context.Contacts.FindAsync(id);
416
+ if (contact != null)
417
+ {
418
+ _context.Contacts.Remove(contact);
419
+ await _context.SaveChangesAsync();
420
+ TempData["SuccessMessage"] = "Contact deleted successfully!";
421
+ }
422
+ return RedirectToAction(nameof(Index));
423
+ }
424
+
425
+ // POST: Home/DeleteMultiple - Bulk delete contacts
426
+ [HttpPost]
427
+ [ValidateAntiForgeryToken]
428
+ [RequireRight(RightsCatalog.ContactsDelete)]
429
+ public async Task<IActionResult> DeleteMultiple(List<int> contactIds)
430
+ {
431
+ var currentUser = _userContextService.CurrentUser;
432
+ if (currentUser == null)
433
+ {
434
+ return RedirectToAction("Login", "Account");
435
+ }
436
+
437
+ if (!currentUser.IsAdmin)
438
+ {
439
+ TempData["ErrorMessage"] = "Only admin can delete contacts.";
440
+ return RedirectToAction(nameof(Index));
441
+ }
442
+
443
+ if (contactIds == null || !contactIds.Any())
444
+ {
445
+ TempData["ErrorMessage"] = "No contacts selected for deletion.";
446
+ return RedirectToAction(nameof(Index));
447
+ }
448
+
449
+ try
450
+ {
451
+ var contactsToDelete = await _context.Contacts
452
+ .Where(c => contactIds.Contains(c.Id))
453
+ .ToListAsync();
454
+
455
+ if (contactsToDelete.Any())
456
+ {
457
+ _context.Contacts.RemoveRange(contactsToDelete);
458
+ await _context.SaveChangesAsync();
459
+
460
+ TempData["SuccessMessage"] = $"Successfully deleted {contactsToDelete.Count} contact(s)!";
461
+ }
462
+ else
463
+ {
464
+ TempData["ErrorMessage"] = "No matching contacts found to delete.";
465
+ }
466
+ }
467
+ catch (Exception ex)
468
+ {
469
+ TempData["ErrorMessage"] = $"Error deleting contacts: {ex.Message}";
470
+ }
471
+
472
+ return RedirectToAction(nameof(Index));
473
+ }
474
+
475
+ private bool ContactExists(int id)
476
+ {
477
+ return _context.Contacts.Any(e => e.Id == id);
478
+ }
479
+
480
+ private void PopulateFormData(Contact? contact = null, List<ContactBankAccount>? bankAccounts = null)
481
+ {
482
+ ViewData["Groups"] = _context.ContactGroups.OrderBy(g => g.Name).ToList();
483
+
484
+ var bankNames = _context.ContactBankAccounts
485
+ .Where(b => !string.IsNullOrWhiteSpace(b.BankName))
486
+ .Select(b => b.BankName!)
487
+ .Distinct()
488
+ .OrderBy(name => name)
489
+ .ToList();
490
+
491
+ if (!string.IsNullOrWhiteSpace(contact?.BankName) && !bankNames.Contains(contact.BankName))
492
+ {
493
+ bankNames.Add(contact.BankName);
494
+ bankNames = bankNames.OrderBy(name => name).ToList();
495
+ }
496
+
497
+ ViewData["BankNames"] = bankNames;
498
+
499
+ if (bankAccounts != null && bankAccounts.Any())
500
+ {
501
+ ViewData["BankAccounts"] = bankAccounts;
502
+ return;
503
+ }
504
+
505
+ if (contact?.BankAccounts != null && contact.BankAccounts.Any())
506
+ {
507
+ ViewData["BankAccounts"] = contact.BankAccounts.OrderBy(b => b.Id).ToList();
508
+ return;
509
+ }
510
+
511
+ if (contact != null && (!string.IsNullOrWhiteSpace(contact.BankAccountNumber) || !string.IsNullOrWhiteSpace(contact.BankName) || !string.IsNullOrWhiteSpace(contact.BranchName) || !string.IsNullOrWhiteSpace(contact.IfscCode)))
512
+ {
513
+ ViewData["BankAccounts"] = new List<ContactBankAccount>
514
+ {
515
+ new ContactBankAccount
516
+ {
517
+ AccountNumber = contact.BankAccountNumber,
518
+ BankName = contact.BankName,
519
+ BranchName = contact.BranchName,
520
+ IfscCode = contact.IfscCode
521
+ }
522
+ };
523
+ return;
524
+ }
525
+
526
+ ViewData["BankAccounts"] = new List<ContactBankAccount> { new ContactBankAccount() };
527
+ }
528
+
529
+ private static List<ContactBankAccount> PrepareBankAccounts(List<ContactBankAccount>? bankAccounts)
530
+ {
531
+ return (bankAccounts ?? new List<ContactBankAccount>())
532
+ .Where(b => !string.IsNullOrWhiteSpace(b.AccountNumber) || !string.IsNullOrWhiteSpace(b.BankName) || !string.IsNullOrWhiteSpace(b.BranchName) || !string.IsNullOrWhiteSpace(b.IfscCode))
533
+ .Select(b => new ContactBankAccount
534
+ {
535
+ AccountNumber = b.AccountNumber,
536
+ BankName = b.BankName,
537
+ BranchName = b.BranchName,
538
+ IfscCode = b.IfscCode,
539
+ CreatedAt = DateTime.Now,
540
+ UpdatedAt = DateTime.Now
541
+ })
542
+ .ToList();
543
+ }
544
+
545
+ private static void SyncLegacyBankFields(Contact contact, ContactBankAccount? primaryBankAccount)
546
+ {
547
+ if (primaryBankAccount == null)
548
+ {
549
+ contact.BankAccountNumber = null;
550
+ contact.BankName = null;
551
+ contact.BranchName = null;
552
+ contact.IfscCode = null;
553
+ return;
554
+ }
555
+
556
+ contact.BankAccountNumber = primaryBankAccount.AccountNumber;
557
+ contact.BankName = primaryBankAccount.BankName;
558
+ contact.BranchName = primaryBankAccount.BranchName;
559
+ contact.IfscCode = primaryBankAccount.IfscCode;
560
+ }
561
+
562
+ private void NormalizeOptionalBankAccountModelState()
563
+ {
564
+ var bankAccountKeys = ModelState.Keys
565
+ .Where(key => key.StartsWith("bankAccounts[", StringComparison.OrdinalIgnoreCase))
566
+ .ToList();
567
+
568
+ foreach (var key in bankAccountKeys)
569
+ {
570
+ ModelState.Remove(key);
571
+ }
572
+ }
573
+
574
+ private void ValidateDuplicateContact(Contact contact, int? excludeContactId = null)
575
+ {
576
+ var normalizedMobile = NormalizeDigits(contact.Mobile1);
577
+ var normalizedAadhar = NormalizeDigits(contact.AadharNumber);
578
+
579
+ if (!string.IsNullOrWhiteSpace(normalizedMobile))
580
+ {
581
+ var mobileConflict = _context.Contacts
582
+ .AsNoTracking()
583
+ .Where(c => c.Id != (excludeContactId ?? 0) && !string.IsNullOrWhiteSpace(c.Mobile1))
584
+ .Select(c => new { c.FirstName, c.LastName, c.Mobile1 })
585
+ .ToList()
586
+ .FirstOrDefault(c => NormalizeDigits(c.Mobile1) == normalizedMobile);
587
+
588
+ if (mobileConflict != null)
589
+ {
590
+ ModelState.AddModelError(nameof(Contact.Mobile1),
591
+ $"Mobile number already exists for contact '{mobileConflict.FirstName} {mobileConflict.LastName}'.");
592
+ }
593
+ }
594
+
595
+ if (!string.IsNullOrWhiteSpace(normalizedAadhar))
596
+ {
597
+ var aadharConflict = _context.Contacts
598
+ .AsNoTracking()
599
+ .Where(c => c.Id != (excludeContactId ?? 0) && !string.IsNullOrWhiteSpace(c.AadharNumber))
600
+ .Select(c => new { c.FirstName, c.LastName, c.AadharNumber })
601
+ .ToList()
602
+ .FirstOrDefault(c => NormalizeDigits(c.AadharNumber) == normalizedAadhar);
603
+
604
+ if (aadharConflict != null)
605
+ {
606
+ ModelState.AddModelError(nameof(Contact.AadharNumber),
607
+ $"Aadhar number already exists for contact '{aadharConflict.FirstName} {aadharConflict.LastName}'.");
608
+ }
609
+ }
610
+
611
+ if (string.IsNullOrWhiteSpace(normalizedMobile) && string.IsNullOrWhiteSpace(normalizedAadhar))
612
+ {
613
+ var firstName = NormalizeText(contact.FirstName);
614
+ var lastName = NormalizeText(contact.LastName);
615
+ var nickName = NormalizeText(contact.NickName);
616
+
617
+ if (!string.IsNullOrWhiteSpace(firstName))
618
+ {
619
+ var nameConflict = _context.Contacts
620
+ .AsNoTracking()
621
+ .Where(c => c.Id != (excludeContactId ?? 0) && !string.IsNullOrWhiteSpace(c.FirstName))
622
+ .Select(c => new { c.FirstName, c.LastName, c.NickName })
623
+ .ToList()
624
+ .FirstOrDefault(c =>
625
+ NormalizeText(c.FirstName) == firstName &&
626
+ NormalizeText(c.LastName) == lastName &&
627
+ NormalizeText(c.NickName) == nickName);
628
+
629
+ if (nameConflict != null)
630
+ {
631
+ ModelState.AddModelError(nameof(Contact.FirstName),
632
+ "A contact with the same First Name, Last Name, and Nick Name already exists. Provide Mobile1 or Aadhar if this is a different person.");
633
+ }
634
+ }
635
+ }
636
+ }
637
+
638
+ private List<Contact> FilterDuplicateImportedContacts(List<Contact> contacts, List<string> errors)
639
+ {
640
+ var validContacts = new List<Contact>();
641
+
642
+ var existingContacts = _context.Contacts
643
+ .AsNoTracking()
644
+ .Select(c => new { c.FirstName, c.LastName, c.NickName, c.Mobile1, c.AadharNumber })
645
+ .ToList();
646
+
647
+ var seenMobiles = new HashSet<string>();
648
+ var seenAadhars = new HashSet<string>();
649
+ var seenNames = new HashSet<string>();
650
+
651
+ for (var i = 0; i < contacts.Count; i++)
652
+ {
653
+ var contact = contacts[i];
654
+ var rowLabel = $"Row {i + 1} ({contact.FirstName} {contact.LastName})";
655
+
656
+ var normalizedMobile = NormalizeDigits(contact.Mobile1);
657
+ var normalizedAadhar = NormalizeDigits(contact.AadharNumber);
658
+ var firstName = NormalizeText(contact.FirstName);
659
+ var lastName = NormalizeText(contact.LastName);
660
+ var nickName = NormalizeText(contact.NickName);
661
+ var nameKey = $"{firstName}|{lastName}|{nickName}";
662
+
663
+ var hasError = false;
664
+
665
+ if (!string.IsNullOrWhiteSpace(normalizedMobile))
666
+ {
667
+ var existsInDb = existingContacts.Any(c => NormalizeDigits(c.Mobile1) == normalizedMobile);
668
+ var existsInImport = seenMobiles.Contains(normalizedMobile);
669
+ if (existsInDb || existsInImport)
670
+ {
671
+ errors.Add($"{rowLabel}: Mobile1 already exists.");
672
+ hasError = true;
673
+ }
674
+ }
675
+
676
+ if (!string.IsNullOrWhiteSpace(normalizedAadhar))
677
+ {
678
+ var existsInDb = existingContacts.Any(c => NormalizeDigits(c.AadharNumber) == normalizedAadhar);
679
+ var existsInImport = seenAadhars.Contains(normalizedAadhar);
680
+ if (existsInDb || existsInImport)
681
+ {
682
+ errors.Add($"{rowLabel}: AadharNumber already exists.");
683
+ hasError = true;
684
+ }
685
+ }
686
+
687
+ if (string.IsNullOrWhiteSpace(normalizedMobile) && string.IsNullOrWhiteSpace(normalizedAadhar))
688
+ {
689
+ var existsInDb = existingContacts.Any(c =>
690
+ NormalizeText(c.FirstName) == firstName &&
691
+ NormalizeText(c.LastName) == lastName &&
692
+ NormalizeText(c.NickName) == nickName);
693
+ var existsInImport = seenNames.Contains(nameKey);
694
+
695
+ if (existsInDb || existsInImport)
696
+ {
697
+ errors.Add($"{rowLabel}: Same First Name + Last Name + Nick Name already exists.");
698
+ hasError = true;
699
+ }
700
+ }
701
+
702
+ if (hasError)
703
+ {
704
+ continue;
705
+ }
706
+
707
+ if (!string.IsNullOrWhiteSpace(normalizedMobile))
708
+ {
709
+ seenMobiles.Add(normalizedMobile);
710
+ }
711
+
712
+ if (!string.IsNullOrWhiteSpace(normalizedAadhar))
713
+ {
714
+ seenAadhars.Add(normalizedAadhar);
715
+ }
716
+
717
+ if (string.IsNullOrWhiteSpace(normalizedMobile) && string.IsNullOrWhiteSpace(normalizedAadhar))
718
+ {
719
+ seenNames.Add(nameKey);
720
+ }
721
+
722
+ validContacts.Add(contact);
723
+ }
724
+
725
+ return validContacts;
726
+ }
727
+
728
+ private static string NormalizeDigits(string? value)
729
+ {
730
+ if (string.IsNullOrWhiteSpace(value))
731
+ {
732
+ return string.Empty;
733
+ }
734
+
735
+ return new string(value.Where(char.IsDigit).ToArray());
736
+ }
737
+
738
+ private static string NormalizeText(string? value)
739
+ {
740
+ return (value ?? string.Empty).Trim().ToUpperInvariant();
741
+ }
742
+
743
+ #region Import/Export Actions
744
+
745
+ // GET: Home/Dashboard - Display contact statistics
746
+ [RequireRight(RightsCatalog.ContactsView)]
747
+ public async Task<IActionResult> Dashboard()
748
+ {
749
+ var currentUser = _userContextService.CurrentUser;
750
+ if (currentUser == null)
751
+ {
752
+ return RedirectToAction("Login", "Account");
753
+ }
754
+
755
+ if (!currentUser.IsAdmin)
756
+ {
757
+ TempData["ErrorMessage"] = "Dashboard is available only for admin.";
758
+ return RedirectToAction(nameof(Index));
759
+ }
760
+
761
+ var statistics = await _statisticsService.GetStatisticsAsync();
762
+ return View(statistics);
763
+ }
764
+
765
+ // GET: Home/FindDuplicates - Display potential duplicate contacts
766
+ [RequireRight(RightsCatalog.ContactsView)]
767
+ public async Task<IActionResult> FindDuplicates()
768
+ {
769
+ var currentUser = _userContextService.CurrentUser;
770
+ if (currentUser == null)
771
+ {
772
+ return RedirectToAction("Login", "Account");
773
+ }
774
+
775
+ if (!currentUser.IsAdmin)
776
+ {
777
+ TempData["ErrorMessage"] = "Find Duplicates is available only for admin.";
778
+ return RedirectToAction(nameof(Index));
779
+ }
780
+
781
+ var duplicates = await _statisticsService.FindDuplicatesAsync();
782
+ return View(duplicates);
783
+ }
784
+
785
+ // GET: Home/Import - Display import page
786
+ [RequireRight(RightsCatalog.ContactsCreate)]
787
+ public IActionResult Import()
788
+ {
789
+ return View();
790
+ }
791
+
792
+ // POST: Home/ImportFile - Handle file import
793
+ [HttpPost]
794
+ [ValidateAntiForgeryToken]
795
+ [RequireRight(RightsCatalog.ContactsCreate)]
796
+ public async Task<IActionResult> ImportFile(IFormFile file, string fileType)
797
+ {
798
+ var currentUser = _userContextService.CurrentUser;
799
+ if (currentUser == null)
800
+ {
801
+ return RedirectToAction("Login", "Account");
802
+ }
803
+
804
+ if (file == null || file.Length == 0)
805
+ {
806
+ TempData["ErrorMessage"] = "Please select a file to import.";
807
+ return RedirectToAction(nameof(Import));
808
+ }
809
+
810
+ List<Contact> contacts;
811
+ List<string> errors;
812
+
813
+ try
814
+ {
815
+ using var stream = file.OpenReadStream();
816
+
817
+ if (fileType == "excel")
818
+ {
819
+ (contacts, errors) = await _importExportService.ImportFromExcel(stream);
820
+ }
821
+ else if (fileType == "csv")
822
+ {
823
+ (contacts, errors) = await _importExportService.ImportFromCsv(stream);
824
+ }
825
+ else
826
+ {
827
+ TempData["ErrorMessage"] = "Invalid file type selected.";
828
+ return RedirectToAction(nameof(Import));
829
+ }
830
+
831
+ if (errors.Any())
832
+ {
833
+ TempData["ErrorMessage"] = $"Import completed with errors:<br/>{string.Join("<br/>", errors)}";
834
+ }
835
+
836
+ if (contacts.Any())
837
+ {
838
+ if (!currentUser.IsAdmin)
839
+ {
840
+ var scopedContactGroupId = ResolveContactGroupIdForUser(currentUser);
841
+ if (!scopedContactGroupId.HasValue)
842
+ {
843
+ TempData["ErrorMessage"] = "Your account is not assigned to a contact group.";
844
+ return RedirectToAction(nameof(Import));
845
+ }
846
+
847
+ foreach (var importedContact in contacts)
848
+ {
849
+ importedContact.GroupId = scopedContactGroupId.Value;
850
+ }
851
+ }
852
+
853
+ contacts = FilterDuplicateImportedContacts(contacts, errors);
854
+
855
+ if (errors.Any())
856
+ {
857
+ TempData["ErrorMessage"] = $"Import completed with errors:<br/>{string.Join("<br/>", errors)}";
858
+ }
859
+
860
+ if (!contacts.Any())
861
+ {
862
+ TempData["ErrorMessage"] = TempData["ErrorMessage"] ?? "No valid contacts found in the file.";
863
+ return RedirectToAction(nameof(Import));
864
+ }
865
+
866
+ // Set change tracking to true for saving
867
+ _context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;
868
+
869
+ await _context.Contacts.AddRangeAsync(contacts);
870
+ await _context.SaveChangesAsync();
871
+
872
+ // Reset tracking behavior
873
+ _context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
874
+
875
+ TempData["SuccessMessage"] = $"Successfully imported {contacts.Count} contact(s)!";
876
+ }
877
+ else
878
+ {
879
+ TempData["ErrorMessage"] = "No valid contacts found in the file.";
880
+ }
881
+ }
882
+ catch (Exception ex)
883
+ {
884
+ TempData["ErrorMessage"] = $"Error importing file: {ex.Message}";
885
+ }
886
+
887
+ return RedirectToAction(nameof(Index));
888
+ }
889
+
890
+ // GET: Home/ExportExcel - Export to Excel
891
+ [RequireRight(RightsCatalog.ContactsView)]
892
+ public async Task<IActionResult> ExportExcel()
893
+ {
894
+ var currentUser = _userContextService.CurrentUser;
895
+ if (currentUser == null)
896
+ {
897
+ return RedirectToAction("Login", "Account");
898
+ }
899
+
900
+ var contacts = await ApplyContactScope(
901
+ _context.Contacts
902
+ .Include(c => c.Group)
903
+ .OrderBy(c => c.FirstName),
904
+ currentUser)
905
+ .ToListAsync();
906
+
907
+ var fileBytes = await _importExportService.ExportToExcel(contacts);
908
+ var fileName = $"Contacts_{DateTime.Now:yyyyMMdd_HHmmss}.xlsx";
909
+
910
+ return File(fileBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", fileName);
911
+ }
912
+
913
+ // GET: Home/ExportCsv - Export to CSV
914
+ [RequireRight(RightsCatalog.ContactsView)]
915
+ public async Task<IActionResult> ExportCsv()
916
+ {
917
+ var currentUser = _userContextService.CurrentUser;
918
+ if (currentUser == null)
919
+ {
920
+ return RedirectToAction("Login", "Account");
921
+ }
922
+
923
+ var contacts = await ApplyContactScope(
924
+ _context.Contacts
925
+ .Include(c => c.Group)
926
+ .OrderBy(c => c.FirstName),
927
+ currentUser)
928
+ .ToListAsync();
929
+
930
+ var fileBytes = await _importExportService.ExportToCsv(contacts);
931
+ var fileName = $"Contacts_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
932
+
933
+ return File(fileBytes, "text/csv", fileName);
934
+ }
935
+
936
+ // GET: Home/ExportPdf - Export to PDF
937
+ [RequireRight(RightsCatalog.ContactsView)]
938
+ public async Task<IActionResult> ExportPdf()
939
+ {
940
+ var currentUser = _userContextService.CurrentUser;
941
+ if (currentUser == null)
942
+ {
943
+ return RedirectToAction("Login", "Account");
944
+ }
945
+
946
+ var contacts = await ApplyContactScope(
947
+ _context.Contacts
948
+ .Include(c => c.Group)
949
+ .OrderBy(c => c.FirstName),
950
+ currentUser)
951
+ .ToListAsync();
952
+
953
+ var fileBytes = await _importExportService.ExportToPdf(contacts);
954
+ var fileName = $"Contacts_{DateTime.Now:yyyyMMdd_HHmmss}.pdf";
955
+
956
+ return File(fileBytes, "application/pdf", fileName);
957
+ }
958
+
959
+ // GET: Home/DownloadTemplate - Download import template
960
+ public async Task<IActionResult> DownloadTemplate(string type)
961
+ {
962
+ if (type == "excel")
963
+ {
964
+ var fileBytes = await _importExportService.GenerateExcelTemplate();
965
+ return File(fileBytes, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "Contact_Import_Template.xlsx");
966
+ }
967
+ else if (type == "csv")
968
+ {
969
+ var fileBytes = await _importExportService.GenerateCsvTemplate();
970
+ return File(fileBytes, "text/csv", "Contact_Import_Template.csv");
971
+ }
972
+
973
+ return NotFound();
974
+ }
975
+
976
+ private IQueryable<Contact> ApplyContactScope(IQueryable<Contact> query, AppUser currentUser)
977
+ {
978
+ if (currentUser.IsAdmin)
979
+ {
980
+ return query;
981
+ }
982
+
983
+ var scopedContactGroupId = ResolveContactGroupIdForUser(currentUser);
984
+ if (!scopedContactGroupId.HasValue)
985
+ {
986
+ return query.Where(c => false);
987
+ }
988
+
989
+ var groupId = scopedContactGroupId.Value;
990
+ return query.Where(c => c.GroupId == groupId);
991
+ }
992
+
993
+ private bool CanAccessContact(AppUser currentUser, Contact contact)
994
+ {
995
+ if (currentUser.IsAdmin)
996
+ {
997
+ return true;
998
+ }
999
+
1000
+ var scopedContactGroupId = ResolveContactGroupIdForUser(currentUser);
1001
+ return scopedContactGroupId.HasValue && contact.GroupId == scopedContactGroupId.Value;
1002
+ }
1003
+
1004
+ private int? ResolveContactGroupIdForUser(AppUser user)
1005
+ {
1006
+ if (user.IsAdmin)
1007
+ {
1008
+ return null;
1009
+ }
1010
+
1011
+ if (user.GroupId <= 0)
1012
+ {
1013
+ return null;
1014
+ }
1015
+
1016
+ var userGroupName = _context.UserGroups
1017
+ .Where(g => g.Id == user.GroupId)
1018
+ .Select(g => g.Name)
1019
+ .FirstOrDefault();
1020
+
1021
+ if (!string.IsNullOrWhiteSpace(userGroupName) && userGroupName.StartsWith("ContactGroup - ", StringComparison.OrdinalIgnoreCase))
1022
+ {
1023
+ var contactGroupName = userGroupName.Substring("ContactGroup - ".Length).Trim();
1024
+ var mappedContactGroupId = _context.ContactGroups
1025
+ .Where(cg => cg.Name == contactGroupName)
1026
+ .Select(cg => (int?)cg.Id)
1027
+ .FirstOrDefault();
1028
+
1029
+ if (mappedContactGroupId.HasValue)
1030
+ {
1031
+ return mappedContactGroupId.Value;
1032
+ }
1033
+ }
1034
+
1035
+ var directMatch = _context.ContactGroups
1036
+ .Where(cg => cg.Id == user.GroupId)
1037
+ .Select(cg => (int?)cg.Id)
1038
+ .FirstOrDefault();
1039
+
1040
+ return directMatch;
1041
+ }
1042
+
1043
+ #endregion
1044
+ }
1045
+ }
ContactManagementAPI/Controllers/PhotoController.cs ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.AspNetCore.Mvc;
2
+ using Microsoft.EntityFrameworkCore;
3
+ using ContactManagementAPI.Data;
4
+ using ContactManagementAPI.Models;
5
+ using ContactManagementAPI.Services;
6
+ using ContactManagementAPI.Security;
7
+ using System.Text.Json;
8
+
9
+ namespace ContactManagementAPI.Controllers
10
+ {
11
+ public class PhotoController : Controller
12
+ {
13
+ private readonly ApplicationDbContext _context;
14
+ private readonly FileUploadService _fileUploadService;
15
+
16
+ public PhotoController(ApplicationDbContext context, FileUploadService fileUploadService)
17
+ {
18
+ _context = context;
19
+ _fileUploadService = fileUploadService;
20
+ }
21
+
22
+ // GET: Photo/Gallery/5
23
+ [RequireRight(RightsCatalog.PhotosManage)]
24
+ public async Task<IActionResult> Gallery(int? id)
25
+ {
26
+ if (id == null)
27
+ return NotFound();
28
+
29
+ var contact = await _context.Contacts
30
+ .Include(c => c.Photos)
31
+ .FirstOrDefaultAsync(c => c.Id == id);
32
+
33
+ if (contact == null)
34
+ return NotFound();
35
+
36
+ return View(contact);
37
+ }
38
+
39
+ // POST: Photo/Upload
40
+ [HttpPost]
41
+ [RequireRight(RightsCatalog.PhotosManage)]
42
+ public async Task<IActionResult> Upload(int contactId, IFormFile photoFile)
43
+ {
44
+ var contact = await _context.Contacts.FindAsync(contactId);
45
+ if (contact == null)
46
+ return Json(new { success = false, message = "Contact not found" });
47
+
48
+ var (success, filePath, errorMessage) = await _fileUploadService.UploadPhotoAsync(photoFile, contactId);
49
+ if (!success)
50
+ return Json(new { success = false, message = errorMessage });
51
+
52
+ var photo = new ContactPhoto
53
+ {
54
+ ContactId = contactId,
55
+ PhotoPath = filePath,
56
+ FileName = photoFile.FileName,
57
+ FileSize = photoFile.Length,
58
+ ContentType = photoFile.ContentType,
59
+ IsProfilePhoto = false,
60
+ UploadedAt = DateTime.Now
61
+ };
62
+
63
+ _context.ContactPhotos.Add(photo);
64
+ await _context.SaveChangesAsync();
65
+
66
+ return Json(new { success = true, photoId = photo.Id, photoPath = filePath });
67
+ }
68
+
69
+ // POST: Photo/SetProfilePhoto
70
+ [HttpPost]
71
+ [RequireRight(RightsCatalog.PhotosManage)]
72
+ public async Task<IActionResult> SetProfilePhoto(int contactId, int photoId)
73
+ {
74
+ (contactId, photoId) = await ResolveContactAndPhotoIdsAsync(contactId, photoId);
75
+
76
+ var contact = await _context.Contacts
77
+ .Include(c => c.Photos)
78
+ .FirstOrDefaultAsync(c => c.Id == contactId);
79
+
80
+ if (contact == null)
81
+ return Json(new { success = false, message = "Contact not found" });
82
+
83
+ var photo = await _context.ContactPhotos.FirstOrDefaultAsync(p => p.Id == photoId && p.ContactId == contactId);
84
+ if (photo == null)
85
+ return Json(new { success = false, message = "Photo not found" });
86
+
87
+ // Unset all other profile photos
88
+ foreach (var p in contact.Photos.Where(p => p.IsProfilePhoto))
89
+ {
90
+ p.IsProfilePhoto = false;
91
+ }
92
+
93
+ photo.IsProfilePhoto = true;
94
+ contact.PhotoPath = photo.PhotoPath;
95
+ _context.Update(contact);
96
+ await _context.SaveChangesAsync();
97
+
98
+ return Json(new { success = true, message = "Profile photo updated" });
99
+ }
100
+
101
+ // POST: Photo/Delete
102
+ [HttpPost]
103
+ [RequireRight(RightsCatalog.PhotosManage)]
104
+ public async Task<IActionResult> Delete(int id, int contactId)
105
+ {
106
+ (contactId, id) = await ResolveContactAndPhotoIdsAsync(contactId, id);
107
+
108
+ var photo = await _context.ContactPhotos.FirstOrDefaultAsync(p => p.Id == id && p.ContactId == contactId);
109
+ if (photo == null)
110
+ {
111
+ photo = await _context.ContactPhotos.FirstOrDefaultAsync(p => p.Id == id);
112
+ }
113
+
114
+ if (photo == null)
115
+ return Json(new { success = false, message = "Photo not found" });
116
+
117
+ _fileUploadService.DeleteFile(photo.PhotoPath);
118
+ _context.ContactPhotos.Remove(photo);
119
+ await _context.SaveChangesAsync();
120
+
121
+ return Json(new { success = true, message = "Photo deleted" });
122
+ }
123
+
124
+ private async Task<(int contactId, int photoId)> ResolveContactAndPhotoIdsAsync(int contactId, int photoId)
125
+ {
126
+ if (Request.HasFormContentType)
127
+ {
128
+ if (contactId <= 0 && int.TryParse(Request.Form["contactId"], out var formContactId))
129
+ contactId = formContactId;
130
+
131
+ if (photoId <= 0)
132
+ {
133
+ if (int.TryParse(Request.Form["photoId"], out var formPhotoId))
134
+ photoId = formPhotoId;
135
+ else if (int.TryParse(Request.Form["id"], out var formId))
136
+ photoId = formId;
137
+ }
138
+ }
139
+
140
+ if (contactId <= 0 && int.TryParse(Request.Query["contactId"], out var queryContactId))
141
+ contactId = queryContactId;
142
+
143
+ if (photoId <= 0)
144
+ {
145
+ if (int.TryParse(Request.Query["photoId"], out var queryPhotoId))
146
+ photoId = queryPhotoId;
147
+ else if (int.TryParse(Request.Query["id"], out var queryId))
148
+ photoId = queryId;
149
+ }
150
+
151
+ if ((contactId <= 0 || photoId <= 0) && Request.ContentType?.Contains("application/json", StringComparison.OrdinalIgnoreCase) == true)
152
+ {
153
+ Request.EnableBuffering();
154
+ Request.Body.Position = 0;
155
+
156
+ using var document = await JsonDocument.ParseAsync(Request.Body);
157
+ var root = document.RootElement;
158
+
159
+ if (contactId <= 0 && root.TryGetProperty("contactId", out var jsonContactId) && jsonContactId.TryGetInt32(out var parsedContactId))
160
+ contactId = parsedContactId;
161
+
162
+ if (photoId <= 0)
163
+ {
164
+ if (root.TryGetProperty("photoId", out var jsonPhotoId) && jsonPhotoId.TryGetInt32(out var parsedPhotoId))
165
+ photoId = parsedPhotoId;
166
+ else if (root.TryGetProperty("id", out var jsonId) && jsonId.TryGetInt32(out var parsedId))
167
+ photoId = parsedId;
168
+ }
169
+
170
+ Request.Body.Position = 0;
171
+ }
172
+
173
+ return (contactId, photoId);
174
+ }
175
+ }
176
+ }
ContactManagementAPI/Data/ApplicationDbContext.cs ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.EntityFrameworkCore;
2
+ using ContactManagementAPI.Models;
3
+
4
+ namespace ContactManagementAPI.Data
5
+ {
6
+ public class ApplicationDbContext : DbContext
7
+ {
8
+ public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
9
+ : base(options)
10
+ {
11
+ }
12
+
13
+ public DbSet<Contact> Contacts { get; set; }
14
+ public DbSet<ContactGroup> ContactGroups { get; set; }
15
+ public DbSet<ContactPhoto> ContactPhotos { get; set; }
16
+ public DbSet<ContactDocument> ContactDocuments { get; set; }
17
+ public DbSet<ContactBankAccount> ContactBankAccounts { get; set; }
18
+ public DbSet<AppUser> AppUsers { get; set; }
19
+ public DbSet<UserGroup> UserGroups { get; set; }
20
+ public DbSet<GroupRight> GroupRights { get; set; }
21
+ public DbSet<UserRight> UserRights { get; set; }
22
+
23
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
24
+ {
25
+ base.OnModelCreating(modelBuilder);
26
+
27
+ // Contact - ContactGroup relationship
28
+ modelBuilder.Entity<Contact>()
29
+ .HasOne(c => c.Group)
30
+ .WithMany(g => g.Contacts)
31
+ .HasForeignKey(c => c.GroupId)
32
+ .OnDelete(DeleteBehavior.SetNull);
33
+
34
+ // Contact - ContactPhoto relationship
35
+ modelBuilder.Entity<ContactPhoto>()
36
+ .HasOne(cp => cp.Contact)
37
+ .WithMany(c => c.Photos)
38
+ .HasForeignKey(cp => cp.ContactId)
39
+ .OnDelete(DeleteBehavior.Cascade);
40
+
41
+ // Contact - ContactDocument relationship
42
+ modelBuilder.Entity<ContactDocument>()
43
+ .HasOne(cd => cd.Contact)
44
+ .WithMany(c => c.Documents)
45
+ .HasForeignKey(cd => cd.ContactId)
46
+ .OnDelete(DeleteBehavior.Cascade);
47
+
48
+ modelBuilder.Entity<ContactBankAccount>()
49
+ .HasOne(cb => cb.Contact)
50
+ .WithMany(c => c.BankAccounts)
51
+ .HasForeignKey(cb => cb.ContactId)
52
+ .OnDelete(DeleteBehavior.Cascade);
53
+
54
+ // AppUser - UserGroup relationship
55
+ modelBuilder.Entity<AppUser>()
56
+ .HasOne(u => u.Group)
57
+ .WithMany(g => g.Users)
58
+ .HasForeignKey(u => u.GroupId)
59
+ .OnDelete(DeleteBehavior.SetNull);
60
+
61
+ // GroupRight - UserGroup relationship
62
+ modelBuilder.Entity<GroupRight>()
63
+ .HasOne(gr => gr.UserGroup)
64
+ .WithMany(g => g.GroupRights)
65
+ .HasForeignKey(gr => gr.UserGroupId)
66
+ .OnDelete(DeleteBehavior.Cascade);
67
+
68
+ // UserRight - AppUser relationship
69
+ modelBuilder.Entity<UserRight>()
70
+ .HasOne(ur => ur.AppUser)
71
+ .WithMany(u => u.UserRights)
72
+ .HasForeignKey(ur => ur.AppUserId)
73
+ .OnDelete(DeleteBehavior.Cascade);
74
+
75
+ modelBuilder.Entity<AppUser>()
76
+ .HasIndex(u => u.UserName)
77
+ .IsUnique();
78
+
79
+ modelBuilder.Entity<UserGroup>()
80
+ .HasIndex(g => g.Name)
81
+ .IsUnique();
82
+
83
+ modelBuilder.Entity<GroupRight>()
84
+ .HasIndex(gr => new { gr.UserGroupId, gr.RightKey })
85
+ .IsUnique();
86
+
87
+ modelBuilder.Entity<UserRight>()
88
+ .HasIndex(ur => new { ur.AppUserId, ur.RightKey })
89
+ .IsUnique();
90
+
91
+ // Seed default contact groups
92
+ modelBuilder.Entity<ContactGroup>().HasData(
93
+ new ContactGroup { Id = 1, Name = "Family", Description = "Family members", CreatedAt = DateTime.Now },
94
+ new ContactGroup { Id = 2, Name = "Friends", Description = "Friends", CreatedAt = DateTime.Now },
95
+ new ContactGroup { Id = 3, Name = "Business", Description = "Business contacts", CreatedAt = DateTime.Now },
96
+ new ContactGroup { Id = 4, Name = "School", Description = "School contacts", CreatedAt = DateTime.Now },
97
+ new ContactGroup { Id = 5, Name = "Church", Description = "Church members", CreatedAt = DateTime.Now },
98
+ new ContactGroup { Id = 6, Name = "Others", Description = "Other contacts", CreatedAt = DateTime.Now },
99
+ new ContactGroup { Id = 7, Name = "College", Description = "College contacts", CreatedAt = DateTime.Now },
100
+ new ContactGroup { Id = 8, Name = "AA", Description = "Alcoholics Anonymous", CreatedAt = DateTime.Now }
101
+ );
102
+ }
103
+ }
104
+ }
ContactManagementAPI/Migrations/20260206165849_InitialCreate.Designer.cs ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // <auto-generated />
2
+ using System;
3
+ using ContactManagementAPI.Data;
4
+ using Microsoft.EntityFrameworkCore;
5
+ using Microsoft.EntityFrameworkCore.Infrastructure;
6
+ using Microsoft.EntityFrameworkCore.Metadata;
7
+ using Microsoft.EntityFrameworkCore.Migrations;
8
+ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9
+
10
+ #nullable disable
11
+
12
+ namespace ContactManagementAPI.Migrations
13
+ {
14
+ [DbContext(typeof(ApplicationDbContext))]
15
+ [Migration("20260206165849_InitialCreate")]
16
+ partial class InitialCreate
17
+ {
18
+ /// <inheritdoc />
19
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
20
+ {
21
+ #pragma warning disable 612, 618
22
+ modelBuilder
23
+ .HasAnnotation("ProductVersion", "8.0.0")
24
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
25
+
26
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
27
+
28
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
29
+ {
30
+ b.Property<int>("Id")
31
+ .ValueGeneratedOnAdd()
32
+ .HasColumnType("int");
33
+
34
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
35
+
36
+ b.Property<string>("Address")
37
+ .IsRequired()
38
+ .HasColumnType("nvarchar(max)");
39
+
40
+ b.Property<string>("City")
41
+ .IsRequired()
42
+ .HasColumnType("nvarchar(max)");
43
+
44
+ b.Property<string>("Country")
45
+ .IsRequired()
46
+ .HasColumnType("nvarchar(max)");
47
+
48
+ b.Property<DateTime>("CreatedAt")
49
+ .HasColumnType("datetime2");
50
+
51
+ b.Property<string>("Email")
52
+ .IsRequired()
53
+ .HasColumnType("nvarchar(max)");
54
+
55
+ b.Property<string>("FirstName")
56
+ .IsRequired()
57
+ .HasColumnType("nvarchar(max)");
58
+
59
+ b.Property<int?>("GroupId")
60
+ .HasColumnType("int");
61
+
62
+ b.Property<string>("LastName")
63
+ .IsRequired()
64
+ .HasColumnType("nvarchar(max)");
65
+
66
+ b.Property<string>("Mobile1")
67
+ .IsRequired()
68
+ .HasColumnType("nvarchar(max)");
69
+
70
+ b.Property<string>("Mobile2")
71
+ .IsRequired()
72
+ .HasColumnType("nvarchar(max)");
73
+
74
+ b.Property<string>("Mobile3")
75
+ .IsRequired()
76
+ .HasColumnType("nvarchar(max)");
77
+
78
+ b.Property<string>("NickName")
79
+ .IsRequired()
80
+ .HasColumnType("nvarchar(max)");
81
+
82
+ b.Property<string>("OtherDetails")
83
+ .IsRequired()
84
+ .HasColumnType("nvarchar(max)");
85
+
86
+ b.Property<string>("PhotoPath")
87
+ .IsRequired()
88
+ .HasColumnType("nvarchar(max)");
89
+
90
+ b.Property<string>("PostalCode")
91
+ .IsRequired()
92
+ .HasColumnType("nvarchar(max)");
93
+
94
+ b.Property<string>("State")
95
+ .IsRequired()
96
+ .HasColumnType("nvarchar(max)");
97
+
98
+ b.Property<DateTime>("UpdatedAt")
99
+ .HasColumnType("datetime2");
100
+
101
+ b.Property<string>("WhatsAppNumber")
102
+ .IsRequired()
103
+ .HasColumnType("nvarchar(max)");
104
+
105
+ b.HasKey("Id");
106
+
107
+ b.HasIndex("GroupId");
108
+
109
+ b.ToTable("Contacts");
110
+ });
111
+
112
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
113
+ {
114
+ b.Property<int>("Id")
115
+ .ValueGeneratedOnAdd()
116
+ .HasColumnType("int");
117
+
118
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
119
+
120
+ b.Property<int>("ContactId")
121
+ .HasColumnType("int");
122
+
123
+ b.Property<string>("ContentType")
124
+ .IsRequired()
125
+ .HasColumnType("nvarchar(max)");
126
+
127
+ b.Property<string>("DocumentPath")
128
+ .IsRequired()
129
+ .HasColumnType("nvarchar(max)");
130
+
131
+ b.Property<string>("DocumentType")
132
+ .IsRequired()
133
+ .HasColumnType("nvarchar(max)");
134
+
135
+ b.Property<string>("FileName")
136
+ .IsRequired()
137
+ .HasColumnType("nvarchar(max)");
138
+
139
+ b.Property<long>("FileSize")
140
+ .HasColumnType("bigint");
141
+
142
+ b.Property<DateTime>("UploadedAt")
143
+ .HasColumnType("datetime2");
144
+
145
+ b.HasKey("Id");
146
+
147
+ b.HasIndex("ContactId");
148
+
149
+ b.ToTable("ContactDocuments");
150
+ });
151
+
152
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
153
+ {
154
+ b.Property<int>("Id")
155
+ .ValueGeneratedOnAdd()
156
+ .HasColumnType("int");
157
+
158
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
159
+
160
+ b.Property<DateTime>("CreatedAt")
161
+ .HasColumnType("datetime2");
162
+
163
+ b.Property<string>("Description")
164
+ .IsRequired()
165
+ .HasColumnType("nvarchar(max)");
166
+
167
+ b.Property<string>("Name")
168
+ .IsRequired()
169
+ .HasColumnType("nvarchar(max)");
170
+
171
+ b.HasKey("Id");
172
+
173
+ b.ToTable("ContactGroups");
174
+
175
+ b.HasData(
176
+ new
177
+ {
178
+ Id = 1,
179
+ CreatedAt = new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2243),
180
+ Description = "Family members",
181
+ Name = "Family"
182
+ },
183
+ new
184
+ {
185
+ Id = 2,
186
+ CreatedAt = new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2260),
187
+ Description = "Friends",
188
+ Name = "Friends"
189
+ },
190
+ new
191
+ {
192
+ Id = 3,
193
+ CreatedAt = new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2263),
194
+ Description = "Business contacts",
195
+ Name = "Business"
196
+ },
197
+ new
198
+ {
199
+ Id = 4,
200
+ CreatedAt = new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2266),
201
+ Description = "School contacts",
202
+ Name = "School"
203
+ },
204
+ new
205
+ {
206
+ Id = 5,
207
+ CreatedAt = new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2270),
208
+ Description = "Church members",
209
+ Name = "Church"
210
+ },
211
+ new
212
+ {
213
+ Id = 6,
214
+ CreatedAt = new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2273),
215
+ Description = "Other contacts",
216
+ Name = "Others"
217
+ });
218
+ });
219
+
220
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
221
+ {
222
+ b.Property<int>("Id")
223
+ .ValueGeneratedOnAdd()
224
+ .HasColumnType("int");
225
+
226
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
227
+
228
+ b.Property<int>("ContactId")
229
+ .HasColumnType("int");
230
+
231
+ b.Property<string>("ContentType")
232
+ .IsRequired()
233
+ .HasColumnType("nvarchar(max)");
234
+
235
+ b.Property<string>("FileName")
236
+ .IsRequired()
237
+ .HasColumnType("nvarchar(max)");
238
+
239
+ b.Property<long>("FileSize")
240
+ .HasColumnType("bigint");
241
+
242
+ b.Property<bool>("IsProfilePhoto")
243
+ .HasColumnType("bit");
244
+
245
+ b.Property<string>("PhotoPath")
246
+ .IsRequired()
247
+ .HasColumnType("nvarchar(max)");
248
+
249
+ b.Property<DateTime>("UploadedAt")
250
+ .HasColumnType("datetime2");
251
+
252
+ b.HasKey("Id");
253
+
254
+ b.HasIndex("ContactId");
255
+
256
+ b.ToTable("ContactPhotos");
257
+ });
258
+
259
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
260
+ {
261
+ b.HasOne("ContactManagementAPI.Models.ContactGroup", "Group")
262
+ .WithMany("Contacts")
263
+ .HasForeignKey("GroupId")
264
+ .OnDelete(DeleteBehavior.SetNull);
265
+
266
+ b.Navigation("Group");
267
+ });
268
+
269
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
270
+ {
271
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
272
+ .WithMany("Documents")
273
+ .HasForeignKey("ContactId")
274
+ .OnDelete(DeleteBehavior.Cascade)
275
+ .IsRequired();
276
+
277
+ b.Navigation("Contact");
278
+ });
279
+
280
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
281
+ {
282
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
283
+ .WithMany("Photos")
284
+ .HasForeignKey("ContactId")
285
+ .OnDelete(DeleteBehavior.Cascade)
286
+ .IsRequired();
287
+
288
+ b.Navigation("Contact");
289
+ });
290
+
291
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
292
+ {
293
+ b.Navigation("Documents");
294
+
295
+ b.Navigation("Photos");
296
+ });
297
+
298
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
299
+ {
300
+ b.Navigation("Contacts");
301
+ });
302
+ #pragma warning restore 612, 618
303
+ }
304
+ }
305
+ }
ContactManagementAPI/Migrations/20260206165849_InitialCreate.cs ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using Microsoft.EntityFrameworkCore.Migrations;
3
+
4
+ #nullable disable
5
+
6
+ #pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
7
+
8
+ namespace ContactManagementAPI.Migrations
9
+ {
10
+ /// <inheritdoc />
11
+ public partial class InitialCreate : Migration
12
+ {
13
+ /// <inheritdoc />
14
+ protected override void Up(MigrationBuilder migrationBuilder)
15
+ {
16
+ migrationBuilder.CreateTable(
17
+ name: "ContactGroups",
18
+ columns: table => new
19
+ {
20
+ Id = table.Column<int>(type: "int", nullable: false)
21
+ .Annotation("SqlServer:Identity", "1, 1"),
22
+ Name = table.Column<string>(type: "nvarchar(max)", nullable: false),
23
+ Description = table.Column<string>(type: "nvarchar(max)", nullable: false),
24
+ CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
25
+ },
26
+ constraints: table =>
27
+ {
28
+ table.PrimaryKey("PK_ContactGroups", x => x.Id);
29
+ });
30
+
31
+ migrationBuilder.CreateTable(
32
+ name: "Contacts",
33
+ columns: table => new
34
+ {
35
+ Id = table.Column<int>(type: "int", nullable: false)
36
+ .Annotation("SqlServer:Identity", "1, 1"),
37
+ FirstName = table.Column<string>(type: "nvarchar(max)", nullable: false),
38
+ LastName = table.Column<string>(type: "nvarchar(max)", nullable: false),
39
+ NickName = table.Column<string>(type: "nvarchar(max)", nullable: false),
40
+ Email = table.Column<string>(type: "nvarchar(max)", nullable: false),
41
+ Mobile1 = table.Column<string>(type: "nvarchar(max)", nullable: false),
42
+ Mobile2 = table.Column<string>(type: "nvarchar(max)", nullable: false),
43
+ Mobile3 = table.Column<string>(type: "nvarchar(max)", nullable: false),
44
+ WhatsAppNumber = table.Column<string>(type: "nvarchar(max)", nullable: false),
45
+ Address = table.Column<string>(type: "nvarchar(max)", nullable: false),
46
+ City = table.Column<string>(type: "nvarchar(max)", nullable: false),
47
+ State = table.Column<string>(type: "nvarchar(max)", nullable: false),
48
+ PostalCode = table.Column<string>(type: "nvarchar(max)", nullable: false),
49
+ Country = table.Column<string>(type: "nvarchar(max)", nullable: false),
50
+ PhotoPath = table.Column<string>(type: "nvarchar(max)", nullable: false),
51
+ GroupId = table.Column<int>(type: "int", nullable: true),
52
+ OtherDetails = table.Column<string>(type: "nvarchar(max)", nullable: false),
53
+ CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
54
+ UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
55
+ },
56
+ constraints: table =>
57
+ {
58
+ table.PrimaryKey("PK_Contacts", x => x.Id);
59
+ table.ForeignKey(
60
+ name: "FK_Contacts_ContactGroups_GroupId",
61
+ column: x => x.GroupId,
62
+ principalTable: "ContactGroups",
63
+ principalColumn: "Id",
64
+ onDelete: ReferentialAction.SetNull);
65
+ });
66
+
67
+ migrationBuilder.CreateTable(
68
+ name: "ContactDocuments",
69
+ columns: table => new
70
+ {
71
+ Id = table.Column<int>(type: "int", nullable: false)
72
+ .Annotation("SqlServer:Identity", "1, 1"),
73
+ ContactId = table.Column<int>(type: "int", nullable: false),
74
+ DocumentPath = table.Column<string>(type: "nvarchar(max)", nullable: false),
75
+ FileName = table.Column<string>(type: "nvarchar(max)", nullable: false),
76
+ FileSize = table.Column<long>(type: "bigint", nullable: false),
77
+ ContentType = table.Column<string>(type: "nvarchar(max)", nullable: false),
78
+ DocumentType = table.Column<string>(type: "nvarchar(max)", nullable: false),
79
+ UploadedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
80
+ },
81
+ constraints: table =>
82
+ {
83
+ table.PrimaryKey("PK_ContactDocuments", x => x.Id);
84
+ table.ForeignKey(
85
+ name: "FK_ContactDocuments_Contacts_ContactId",
86
+ column: x => x.ContactId,
87
+ principalTable: "Contacts",
88
+ principalColumn: "Id",
89
+ onDelete: ReferentialAction.Cascade);
90
+ });
91
+
92
+ migrationBuilder.CreateTable(
93
+ name: "ContactPhotos",
94
+ columns: table => new
95
+ {
96
+ Id = table.Column<int>(type: "int", nullable: false)
97
+ .Annotation("SqlServer:Identity", "1, 1"),
98
+ ContactId = table.Column<int>(type: "int", nullable: false),
99
+ PhotoPath = table.Column<string>(type: "nvarchar(max)", nullable: false),
100
+ FileName = table.Column<string>(type: "nvarchar(max)", nullable: false),
101
+ FileSize = table.Column<long>(type: "bigint", nullable: false),
102
+ ContentType = table.Column<string>(type: "nvarchar(max)", nullable: false),
103
+ IsProfilePhoto = table.Column<bool>(type: "bit", nullable: false),
104
+ UploadedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
105
+ },
106
+ constraints: table =>
107
+ {
108
+ table.PrimaryKey("PK_ContactPhotos", x => x.Id);
109
+ table.ForeignKey(
110
+ name: "FK_ContactPhotos_Contacts_ContactId",
111
+ column: x => x.ContactId,
112
+ principalTable: "Contacts",
113
+ principalColumn: "Id",
114
+ onDelete: ReferentialAction.Cascade);
115
+ });
116
+
117
+ migrationBuilder.InsertData(
118
+ table: "ContactGroups",
119
+ columns: new[] { "Id", "CreatedAt", "Description", "Name" },
120
+ values: new object[,]
121
+ {
122
+ { 1, new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2243), "Family members", "Family" },
123
+ { 2, new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2260), "Friends", "Friends" },
124
+ { 3, new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2263), "Business contacts", "Business" },
125
+ { 4, new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2266), "School contacts", "School" },
126
+ { 5, new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2270), "Church members", "Church" },
127
+ { 6, new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2273), "Other contacts", "Others" }
128
+ });
129
+
130
+ migrationBuilder.CreateIndex(
131
+ name: "IX_ContactDocuments_ContactId",
132
+ table: "ContactDocuments",
133
+ column: "ContactId");
134
+
135
+ migrationBuilder.CreateIndex(
136
+ name: "IX_ContactPhotos_ContactId",
137
+ table: "ContactPhotos",
138
+ column: "ContactId");
139
+
140
+ migrationBuilder.CreateIndex(
141
+ name: "IX_Contacts_GroupId",
142
+ table: "Contacts",
143
+ column: "GroupId");
144
+ }
145
+
146
+ /// <inheritdoc />
147
+ protected override void Down(MigrationBuilder migrationBuilder)
148
+ {
149
+ migrationBuilder.DropTable(
150
+ name: "ContactDocuments");
151
+
152
+ migrationBuilder.DropTable(
153
+ name: "ContactPhotos");
154
+
155
+ migrationBuilder.DropTable(
156
+ name: "Contacts");
157
+
158
+ migrationBuilder.DropTable(
159
+ name: "ContactGroups");
160
+ }
161
+ }
162
+ }
ContactManagementAPI/Migrations/20260208162549_MakeContactFieldsNullable.Designer.cs ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // <auto-generated />
2
+ using System;
3
+ using ContactManagementAPI.Data;
4
+ using Microsoft.EntityFrameworkCore;
5
+ using Microsoft.EntityFrameworkCore.Infrastructure;
6
+ using Microsoft.EntityFrameworkCore.Metadata;
7
+ using Microsoft.EntityFrameworkCore.Migrations;
8
+ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9
+
10
+ #nullable disable
11
+
12
+ namespace ContactManagementAPI.Migrations
13
+ {
14
+ [DbContext(typeof(ApplicationDbContext))]
15
+ [Migration("20260208162549_MakeContactFieldsNullable")]
16
+ partial class MakeContactFieldsNullable
17
+ {
18
+ /// <inheritdoc />
19
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
20
+ {
21
+ #pragma warning disable 612, 618
22
+ modelBuilder
23
+ .HasAnnotation("ProductVersion", "8.0.0")
24
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
25
+
26
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
27
+
28
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
29
+ {
30
+ b.Property<int>("Id")
31
+ .ValueGeneratedOnAdd()
32
+ .HasColumnType("int");
33
+
34
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
35
+
36
+ b.Property<string>("Address")
37
+ .HasColumnType("nvarchar(max)");
38
+
39
+ b.Property<string>("City")
40
+ .HasColumnType("nvarchar(max)");
41
+
42
+ b.Property<string>("Country")
43
+ .HasColumnType("nvarchar(max)");
44
+
45
+ b.Property<DateTime>("CreatedAt")
46
+ .HasColumnType("datetime2");
47
+
48
+ b.Property<string>("Email")
49
+ .HasColumnType("nvarchar(max)");
50
+
51
+ b.Property<string>("FirstName")
52
+ .IsRequired()
53
+ .HasColumnType("nvarchar(max)");
54
+
55
+ b.Property<int?>("GroupId")
56
+ .HasColumnType("int");
57
+
58
+ b.Property<string>("LastName")
59
+ .HasColumnType("nvarchar(max)");
60
+
61
+ b.Property<string>("Mobile1")
62
+ .HasColumnType("nvarchar(max)");
63
+
64
+ b.Property<string>("Mobile2")
65
+ .HasColumnType("nvarchar(max)");
66
+
67
+ b.Property<string>("Mobile3")
68
+ .HasColumnType("nvarchar(max)");
69
+
70
+ b.Property<string>("NickName")
71
+ .HasColumnType("nvarchar(max)");
72
+
73
+ b.Property<string>("OtherDetails")
74
+ .HasColumnType("nvarchar(max)");
75
+
76
+ b.Property<string>("PhotoPath")
77
+ .HasColumnType("nvarchar(max)");
78
+
79
+ b.Property<string>("PostalCode")
80
+ .HasColumnType("nvarchar(max)");
81
+
82
+ b.Property<string>("State")
83
+ .HasColumnType("nvarchar(max)");
84
+
85
+ b.Property<DateTime>("UpdatedAt")
86
+ .HasColumnType("datetime2");
87
+
88
+ b.Property<string>("WhatsAppNumber")
89
+ .HasColumnType("nvarchar(max)");
90
+
91
+ b.HasKey("Id");
92
+
93
+ b.HasIndex("GroupId");
94
+
95
+ b.ToTable("Contacts");
96
+ });
97
+
98
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
99
+ {
100
+ b.Property<int>("Id")
101
+ .ValueGeneratedOnAdd()
102
+ .HasColumnType("int");
103
+
104
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
105
+
106
+ b.Property<int>("ContactId")
107
+ .HasColumnType("int");
108
+
109
+ b.Property<string>("ContentType")
110
+ .IsRequired()
111
+ .HasColumnType("nvarchar(max)");
112
+
113
+ b.Property<string>("DocumentPath")
114
+ .IsRequired()
115
+ .HasColumnType("nvarchar(max)");
116
+
117
+ b.Property<string>("DocumentType")
118
+ .IsRequired()
119
+ .HasColumnType("nvarchar(max)");
120
+
121
+ b.Property<string>("FileName")
122
+ .IsRequired()
123
+ .HasColumnType("nvarchar(max)");
124
+
125
+ b.Property<long>("FileSize")
126
+ .HasColumnType("bigint");
127
+
128
+ b.Property<DateTime>("UploadedAt")
129
+ .HasColumnType("datetime2");
130
+
131
+ b.HasKey("Id");
132
+
133
+ b.HasIndex("ContactId");
134
+
135
+ b.ToTable("ContactDocuments");
136
+ });
137
+
138
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
139
+ {
140
+ b.Property<int>("Id")
141
+ .ValueGeneratedOnAdd()
142
+ .HasColumnType("int");
143
+
144
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
145
+
146
+ b.Property<DateTime>("CreatedAt")
147
+ .HasColumnType("datetime2");
148
+
149
+ b.Property<string>("Description")
150
+ .IsRequired()
151
+ .HasColumnType("nvarchar(max)");
152
+
153
+ b.Property<string>("Name")
154
+ .IsRequired()
155
+ .HasColumnType("nvarchar(max)");
156
+
157
+ b.HasKey("Id");
158
+
159
+ b.ToTable("ContactGroups");
160
+
161
+ b.HasData(
162
+ new
163
+ {
164
+ Id = 1,
165
+ CreatedAt = new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5320),
166
+ Description = "Family members",
167
+ Name = "Family"
168
+ },
169
+ new
170
+ {
171
+ Id = 2,
172
+ CreatedAt = new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5332),
173
+ Description = "Friends",
174
+ Name = "Friends"
175
+ },
176
+ new
177
+ {
178
+ Id = 3,
179
+ CreatedAt = new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5333),
180
+ Description = "Business contacts",
181
+ Name = "Business"
182
+ },
183
+ new
184
+ {
185
+ Id = 4,
186
+ CreatedAt = new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5335),
187
+ Description = "School contacts",
188
+ Name = "School"
189
+ },
190
+ new
191
+ {
192
+ Id = 5,
193
+ CreatedAt = new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5339),
194
+ Description = "Church members",
195
+ Name = "Church"
196
+ },
197
+ new
198
+ {
199
+ Id = 6,
200
+ CreatedAt = new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5340),
201
+ Description = "Other contacts",
202
+ Name = "Others"
203
+ });
204
+ });
205
+
206
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
207
+ {
208
+ b.Property<int>("Id")
209
+ .ValueGeneratedOnAdd()
210
+ .HasColumnType("int");
211
+
212
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
213
+
214
+ b.Property<int>("ContactId")
215
+ .HasColumnType("int");
216
+
217
+ b.Property<string>("ContentType")
218
+ .IsRequired()
219
+ .HasColumnType("nvarchar(max)");
220
+
221
+ b.Property<string>("FileName")
222
+ .IsRequired()
223
+ .HasColumnType("nvarchar(max)");
224
+
225
+ b.Property<long>("FileSize")
226
+ .HasColumnType("bigint");
227
+
228
+ b.Property<bool>("IsProfilePhoto")
229
+ .HasColumnType("bit");
230
+
231
+ b.Property<string>("PhotoPath")
232
+ .IsRequired()
233
+ .HasColumnType("nvarchar(max)");
234
+
235
+ b.Property<DateTime>("UploadedAt")
236
+ .HasColumnType("datetime2");
237
+
238
+ b.HasKey("Id");
239
+
240
+ b.HasIndex("ContactId");
241
+
242
+ b.ToTable("ContactPhotos");
243
+ });
244
+
245
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
246
+ {
247
+ b.HasOne("ContactManagementAPI.Models.ContactGroup", "Group")
248
+ .WithMany("Contacts")
249
+ .HasForeignKey("GroupId")
250
+ .OnDelete(DeleteBehavior.SetNull);
251
+
252
+ b.Navigation("Group");
253
+ });
254
+
255
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
256
+ {
257
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
258
+ .WithMany("Documents")
259
+ .HasForeignKey("ContactId")
260
+ .OnDelete(DeleteBehavior.Cascade)
261
+ .IsRequired();
262
+
263
+ b.Navigation("Contact");
264
+ });
265
+
266
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
267
+ {
268
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
269
+ .WithMany("Photos")
270
+ .HasForeignKey("ContactId")
271
+ .OnDelete(DeleteBehavior.Cascade)
272
+ .IsRequired();
273
+
274
+ b.Navigation("Contact");
275
+ });
276
+
277
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
278
+ {
279
+ b.Navigation("Documents");
280
+
281
+ b.Navigation("Photos");
282
+ });
283
+
284
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
285
+ {
286
+ b.Navigation("Contacts");
287
+ });
288
+ #pragma warning restore 612, 618
289
+ }
290
+ }
291
+ }
ContactManagementAPI/Migrations/20260208162549_MakeContactFieldsNullable.cs ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using Microsoft.EntityFrameworkCore.Migrations;
3
+
4
+ #nullable disable
5
+
6
+ namespace ContactManagementAPI.Migrations
7
+ {
8
+ /// <inheritdoc />
9
+ public partial class MakeContactFieldsNullable : Migration
10
+ {
11
+ /// <inheritdoc />
12
+ protected override void Up(MigrationBuilder migrationBuilder)
13
+ {
14
+ migrationBuilder.AlterColumn<string>(
15
+ name: "WhatsAppNumber",
16
+ table: "Contacts",
17
+ type: "nvarchar(max)",
18
+ nullable: true,
19
+ oldClrType: typeof(string),
20
+ oldType: "nvarchar(max)");
21
+
22
+ migrationBuilder.AlterColumn<string>(
23
+ name: "State",
24
+ table: "Contacts",
25
+ type: "nvarchar(max)",
26
+ nullable: true,
27
+ oldClrType: typeof(string),
28
+ oldType: "nvarchar(max)");
29
+
30
+ migrationBuilder.AlterColumn<string>(
31
+ name: "PostalCode",
32
+ table: "Contacts",
33
+ type: "nvarchar(max)",
34
+ nullable: true,
35
+ oldClrType: typeof(string),
36
+ oldType: "nvarchar(max)");
37
+
38
+ migrationBuilder.AlterColumn<string>(
39
+ name: "PhotoPath",
40
+ table: "Contacts",
41
+ type: "nvarchar(max)",
42
+ nullable: true,
43
+ oldClrType: typeof(string),
44
+ oldType: "nvarchar(max)");
45
+
46
+ migrationBuilder.AlterColumn<string>(
47
+ name: "OtherDetails",
48
+ table: "Contacts",
49
+ type: "nvarchar(max)",
50
+ nullable: true,
51
+ oldClrType: typeof(string),
52
+ oldType: "nvarchar(max)");
53
+
54
+ migrationBuilder.AlterColumn<string>(
55
+ name: "NickName",
56
+ table: "Contacts",
57
+ type: "nvarchar(max)",
58
+ nullable: true,
59
+ oldClrType: typeof(string),
60
+ oldType: "nvarchar(max)");
61
+
62
+ migrationBuilder.AlterColumn<string>(
63
+ name: "Mobile3",
64
+ table: "Contacts",
65
+ type: "nvarchar(max)",
66
+ nullable: true,
67
+ oldClrType: typeof(string),
68
+ oldType: "nvarchar(max)");
69
+
70
+ migrationBuilder.AlterColumn<string>(
71
+ name: "Mobile2",
72
+ table: "Contacts",
73
+ type: "nvarchar(max)",
74
+ nullable: true,
75
+ oldClrType: typeof(string),
76
+ oldType: "nvarchar(max)");
77
+
78
+ migrationBuilder.AlterColumn<string>(
79
+ name: "Mobile1",
80
+ table: "Contacts",
81
+ type: "nvarchar(max)",
82
+ nullable: true,
83
+ oldClrType: typeof(string),
84
+ oldType: "nvarchar(max)");
85
+
86
+ migrationBuilder.AlterColumn<string>(
87
+ name: "LastName",
88
+ table: "Contacts",
89
+ type: "nvarchar(max)",
90
+ nullable: true,
91
+ oldClrType: typeof(string),
92
+ oldType: "nvarchar(max)");
93
+
94
+ migrationBuilder.AlterColumn<string>(
95
+ name: "Email",
96
+ table: "Contacts",
97
+ type: "nvarchar(max)",
98
+ nullable: true,
99
+ oldClrType: typeof(string),
100
+ oldType: "nvarchar(max)");
101
+
102
+ migrationBuilder.AlterColumn<string>(
103
+ name: "Country",
104
+ table: "Contacts",
105
+ type: "nvarchar(max)",
106
+ nullable: true,
107
+ oldClrType: typeof(string),
108
+ oldType: "nvarchar(max)");
109
+
110
+ migrationBuilder.AlterColumn<string>(
111
+ name: "City",
112
+ table: "Contacts",
113
+ type: "nvarchar(max)",
114
+ nullable: true,
115
+ oldClrType: typeof(string),
116
+ oldType: "nvarchar(max)");
117
+
118
+ migrationBuilder.AlterColumn<string>(
119
+ name: "Address",
120
+ table: "Contacts",
121
+ type: "nvarchar(max)",
122
+ nullable: true,
123
+ oldClrType: typeof(string),
124
+ oldType: "nvarchar(max)");
125
+
126
+ migrationBuilder.UpdateData(
127
+ table: "ContactGroups",
128
+ keyColumn: "Id",
129
+ keyValue: 1,
130
+ column: "CreatedAt",
131
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5320));
132
+
133
+ migrationBuilder.UpdateData(
134
+ table: "ContactGroups",
135
+ keyColumn: "Id",
136
+ keyValue: 2,
137
+ column: "CreatedAt",
138
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5332));
139
+
140
+ migrationBuilder.UpdateData(
141
+ table: "ContactGroups",
142
+ keyColumn: "Id",
143
+ keyValue: 3,
144
+ column: "CreatedAt",
145
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5333));
146
+
147
+ migrationBuilder.UpdateData(
148
+ table: "ContactGroups",
149
+ keyColumn: "Id",
150
+ keyValue: 4,
151
+ column: "CreatedAt",
152
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5335));
153
+
154
+ migrationBuilder.UpdateData(
155
+ table: "ContactGroups",
156
+ keyColumn: "Id",
157
+ keyValue: 5,
158
+ column: "CreatedAt",
159
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5339));
160
+
161
+ migrationBuilder.UpdateData(
162
+ table: "ContactGroups",
163
+ keyColumn: "Id",
164
+ keyValue: 6,
165
+ column: "CreatedAt",
166
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5340));
167
+ }
168
+
169
+ /// <inheritdoc />
170
+ protected override void Down(MigrationBuilder migrationBuilder)
171
+ {
172
+ migrationBuilder.AlterColumn<string>(
173
+ name: "WhatsAppNumber",
174
+ table: "Contacts",
175
+ type: "nvarchar(max)",
176
+ nullable: false,
177
+ defaultValue: "",
178
+ oldClrType: typeof(string),
179
+ oldType: "nvarchar(max)",
180
+ oldNullable: true);
181
+
182
+ migrationBuilder.AlterColumn<string>(
183
+ name: "State",
184
+ table: "Contacts",
185
+ type: "nvarchar(max)",
186
+ nullable: false,
187
+ defaultValue: "",
188
+ oldClrType: typeof(string),
189
+ oldType: "nvarchar(max)",
190
+ oldNullable: true);
191
+
192
+ migrationBuilder.AlterColumn<string>(
193
+ name: "PostalCode",
194
+ table: "Contacts",
195
+ type: "nvarchar(max)",
196
+ nullable: false,
197
+ defaultValue: "",
198
+ oldClrType: typeof(string),
199
+ oldType: "nvarchar(max)",
200
+ oldNullable: true);
201
+
202
+ migrationBuilder.AlterColumn<string>(
203
+ name: "PhotoPath",
204
+ table: "Contacts",
205
+ type: "nvarchar(max)",
206
+ nullable: false,
207
+ defaultValue: "",
208
+ oldClrType: typeof(string),
209
+ oldType: "nvarchar(max)",
210
+ oldNullable: true);
211
+
212
+ migrationBuilder.AlterColumn<string>(
213
+ name: "OtherDetails",
214
+ table: "Contacts",
215
+ type: "nvarchar(max)",
216
+ nullable: false,
217
+ defaultValue: "",
218
+ oldClrType: typeof(string),
219
+ oldType: "nvarchar(max)",
220
+ oldNullable: true);
221
+
222
+ migrationBuilder.AlterColumn<string>(
223
+ name: "NickName",
224
+ table: "Contacts",
225
+ type: "nvarchar(max)",
226
+ nullable: false,
227
+ defaultValue: "",
228
+ oldClrType: typeof(string),
229
+ oldType: "nvarchar(max)",
230
+ oldNullable: true);
231
+
232
+ migrationBuilder.AlterColumn<string>(
233
+ name: "Mobile3",
234
+ table: "Contacts",
235
+ type: "nvarchar(max)",
236
+ nullable: false,
237
+ defaultValue: "",
238
+ oldClrType: typeof(string),
239
+ oldType: "nvarchar(max)",
240
+ oldNullable: true);
241
+
242
+ migrationBuilder.AlterColumn<string>(
243
+ name: "Mobile2",
244
+ table: "Contacts",
245
+ type: "nvarchar(max)",
246
+ nullable: false,
247
+ defaultValue: "",
248
+ oldClrType: typeof(string),
249
+ oldType: "nvarchar(max)",
250
+ oldNullable: true);
251
+
252
+ migrationBuilder.AlterColumn<string>(
253
+ name: "Mobile1",
254
+ table: "Contacts",
255
+ type: "nvarchar(max)",
256
+ nullable: false,
257
+ defaultValue: "",
258
+ oldClrType: typeof(string),
259
+ oldType: "nvarchar(max)",
260
+ oldNullable: true);
261
+
262
+ migrationBuilder.AlterColumn<string>(
263
+ name: "LastName",
264
+ table: "Contacts",
265
+ type: "nvarchar(max)",
266
+ nullable: false,
267
+ defaultValue: "",
268
+ oldClrType: typeof(string),
269
+ oldType: "nvarchar(max)",
270
+ oldNullable: true);
271
+
272
+ migrationBuilder.AlterColumn<string>(
273
+ name: "Email",
274
+ table: "Contacts",
275
+ type: "nvarchar(max)",
276
+ nullable: false,
277
+ defaultValue: "",
278
+ oldClrType: typeof(string),
279
+ oldType: "nvarchar(max)",
280
+ oldNullable: true);
281
+
282
+ migrationBuilder.AlterColumn<string>(
283
+ name: "Country",
284
+ table: "Contacts",
285
+ type: "nvarchar(max)",
286
+ nullable: false,
287
+ defaultValue: "",
288
+ oldClrType: typeof(string),
289
+ oldType: "nvarchar(max)",
290
+ oldNullable: true);
291
+
292
+ migrationBuilder.AlterColumn<string>(
293
+ name: "City",
294
+ table: "Contacts",
295
+ type: "nvarchar(max)",
296
+ nullable: false,
297
+ defaultValue: "",
298
+ oldClrType: typeof(string),
299
+ oldType: "nvarchar(max)",
300
+ oldNullable: true);
301
+
302
+ migrationBuilder.AlterColumn<string>(
303
+ name: "Address",
304
+ table: "Contacts",
305
+ type: "nvarchar(max)",
306
+ nullable: false,
307
+ defaultValue: "",
308
+ oldClrType: typeof(string),
309
+ oldType: "nvarchar(max)",
310
+ oldNullable: true);
311
+
312
+ migrationBuilder.UpdateData(
313
+ table: "ContactGroups",
314
+ keyColumn: "Id",
315
+ keyValue: 1,
316
+ column: "CreatedAt",
317
+ value: new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2243));
318
+
319
+ migrationBuilder.UpdateData(
320
+ table: "ContactGroups",
321
+ keyColumn: "Id",
322
+ keyValue: 2,
323
+ column: "CreatedAt",
324
+ value: new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2260));
325
+
326
+ migrationBuilder.UpdateData(
327
+ table: "ContactGroups",
328
+ keyColumn: "Id",
329
+ keyValue: 3,
330
+ column: "CreatedAt",
331
+ value: new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2263));
332
+
333
+ migrationBuilder.UpdateData(
334
+ table: "ContactGroups",
335
+ keyColumn: "Id",
336
+ keyValue: 4,
337
+ column: "CreatedAt",
338
+ value: new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2266));
339
+
340
+ migrationBuilder.UpdateData(
341
+ table: "ContactGroups",
342
+ keyColumn: "Id",
343
+ keyValue: 5,
344
+ column: "CreatedAt",
345
+ value: new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2270));
346
+
347
+ migrationBuilder.UpdateData(
348
+ table: "ContactGroups",
349
+ keyColumn: "Id",
350
+ keyValue: 6,
351
+ column: "CreatedAt",
352
+ value: new DateTime(2026, 2, 6, 22, 28, 46, 432, DateTimeKind.Local).AddTicks(2273));
353
+ }
354
+ }
355
+ }
ContactManagementAPI/Migrations/20260209052719_AddSampleData.Designer.cs ADDED
@@ -0,0 +1,291 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // <auto-generated />
2
+ using System;
3
+ using ContactManagementAPI.Data;
4
+ using Microsoft.EntityFrameworkCore;
5
+ using Microsoft.EntityFrameworkCore.Infrastructure;
6
+ using Microsoft.EntityFrameworkCore.Metadata;
7
+ using Microsoft.EntityFrameworkCore.Migrations;
8
+ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9
+
10
+ #nullable disable
11
+
12
+ namespace ContactManagementAPI.Migrations
13
+ {
14
+ [DbContext(typeof(ApplicationDbContext))]
15
+ [Migration("20260209052719_AddSampleData")]
16
+ partial class AddSampleData
17
+ {
18
+ /// <inheritdoc />
19
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
20
+ {
21
+ #pragma warning disable 612, 618
22
+ modelBuilder
23
+ .HasAnnotation("ProductVersion", "8.0.0")
24
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
25
+
26
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
27
+
28
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
29
+ {
30
+ b.Property<int>("Id")
31
+ .ValueGeneratedOnAdd()
32
+ .HasColumnType("int");
33
+
34
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
35
+
36
+ b.Property<string>("Address")
37
+ .HasColumnType("nvarchar(max)");
38
+
39
+ b.Property<string>("City")
40
+ .HasColumnType("nvarchar(max)");
41
+
42
+ b.Property<string>("Country")
43
+ .HasColumnType("nvarchar(max)");
44
+
45
+ b.Property<DateTime>("CreatedAt")
46
+ .HasColumnType("datetime2");
47
+
48
+ b.Property<string>("Email")
49
+ .HasColumnType("nvarchar(max)");
50
+
51
+ b.Property<string>("FirstName")
52
+ .IsRequired()
53
+ .HasColumnType("nvarchar(max)");
54
+
55
+ b.Property<int?>("GroupId")
56
+ .HasColumnType("int");
57
+
58
+ b.Property<string>("LastName")
59
+ .HasColumnType("nvarchar(max)");
60
+
61
+ b.Property<string>("Mobile1")
62
+ .HasColumnType("nvarchar(max)");
63
+
64
+ b.Property<string>("Mobile2")
65
+ .HasColumnType("nvarchar(max)");
66
+
67
+ b.Property<string>("Mobile3")
68
+ .HasColumnType("nvarchar(max)");
69
+
70
+ b.Property<string>("NickName")
71
+ .HasColumnType("nvarchar(max)");
72
+
73
+ b.Property<string>("OtherDetails")
74
+ .HasColumnType("nvarchar(max)");
75
+
76
+ b.Property<string>("PhotoPath")
77
+ .HasColumnType("nvarchar(max)");
78
+
79
+ b.Property<string>("PostalCode")
80
+ .HasColumnType("nvarchar(max)");
81
+
82
+ b.Property<string>("State")
83
+ .HasColumnType("nvarchar(max)");
84
+
85
+ b.Property<DateTime>("UpdatedAt")
86
+ .HasColumnType("datetime2");
87
+
88
+ b.Property<string>("WhatsAppNumber")
89
+ .HasColumnType("nvarchar(max)");
90
+
91
+ b.HasKey("Id");
92
+
93
+ b.HasIndex("GroupId");
94
+
95
+ b.ToTable("Contacts");
96
+ });
97
+
98
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
99
+ {
100
+ b.Property<int>("Id")
101
+ .ValueGeneratedOnAdd()
102
+ .HasColumnType("int");
103
+
104
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
105
+
106
+ b.Property<int>("ContactId")
107
+ .HasColumnType("int");
108
+
109
+ b.Property<string>("ContentType")
110
+ .IsRequired()
111
+ .HasColumnType("nvarchar(max)");
112
+
113
+ b.Property<string>("DocumentPath")
114
+ .IsRequired()
115
+ .HasColumnType("nvarchar(max)");
116
+
117
+ b.Property<string>("DocumentType")
118
+ .IsRequired()
119
+ .HasColumnType("nvarchar(max)");
120
+
121
+ b.Property<string>("FileName")
122
+ .IsRequired()
123
+ .HasColumnType("nvarchar(max)");
124
+
125
+ b.Property<long>("FileSize")
126
+ .HasColumnType("bigint");
127
+
128
+ b.Property<DateTime>("UploadedAt")
129
+ .HasColumnType("datetime2");
130
+
131
+ b.HasKey("Id");
132
+
133
+ b.HasIndex("ContactId");
134
+
135
+ b.ToTable("ContactDocuments");
136
+ });
137
+
138
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
139
+ {
140
+ b.Property<int>("Id")
141
+ .ValueGeneratedOnAdd()
142
+ .HasColumnType("int");
143
+
144
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
145
+
146
+ b.Property<DateTime>("CreatedAt")
147
+ .HasColumnType("datetime2");
148
+
149
+ b.Property<string>("Description")
150
+ .IsRequired()
151
+ .HasColumnType("nvarchar(max)");
152
+
153
+ b.Property<string>("Name")
154
+ .IsRequired()
155
+ .HasColumnType("nvarchar(max)");
156
+
157
+ b.HasKey("Id");
158
+
159
+ b.ToTable("ContactGroups");
160
+
161
+ b.HasData(
162
+ new
163
+ {
164
+ Id = 1,
165
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8518),
166
+ Description = "Family members",
167
+ Name = "Family"
168
+ },
169
+ new
170
+ {
171
+ Id = 2,
172
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8531),
173
+ Description = "Friends",
174
+ Name = "Friends"
175
+ },
176
+ new
177
+ {
178
+ Id = 3,
179
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8533),
180
+ Description = "Business contacts",
181
+ Name = "Business"
182
+ },
183
+ new
184
+ {
185
+ Id = 4,
186
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8535),
187
+ Description = "School contacts",
188
+ Name = "School"
189
+ },
190
+ new
191
+ {
192
+ Id = 5,
193
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8537),
194
+ Description = "Church members",
195
+ Name = "Church"
196
+ },
197
+ new
198
+ {
199
+ Id = 6,
200
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8538),
201
+ Description = "Other contacts",
202
+ Name = "Others"
203
+ });
204
+ });
205
+
206
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
207
+ {
208
+ b.Property<int>("Id")
209
+ .ValueGeneratedOnAdd()
210
+ .HasColumnType("int");
211
+
212
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
213
+
214
+ b.Property<int>("ContactId")
215
+ .HasColumnType("int");
216
+
217
+ b.Property<string>("ContentType")
218
+ .IsRequired()
219
+ .HasColumnType("nvarchar(max)");
220
+
221
+ b.Property<string>("FileName")
222
+ .IsRequired()
223
+ .HasColumnType("nvarchar(max)");
224
+
225
+ b.Property<long>("FileSize")
226
+ .HasColumnType("bigint");
227
+
228
+ b.Property<bool>("IsProfilePhoto")
229
+ .HasColumnType("bit");
230
+
231
+ b.Property<string>("PhotoPath")
232
+ .IsRequired()
233
+ .HasColumnType("nvarchar(max)");
234
+
235
+ b.Property<DateTime>("UploadedAt")
236
+ .HasColumnType("datetime2");
237
+
238
+ b.HasKey("Id");
239
+
240
+ b.HasIndex("ContactId");
241
+
242
+ b.ToTable("ContactPhotos");
243
+ });
244
+
245
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
246
+ {
247
+ b.HasOne("ContactManagementAPI.Models.ContactGroup", "Group")
248
+ .WithMany("Contacts")
249
+ .HasForeignKey("GroupId")
250
+ .OnDelete(DeleteBehavior.SetNull);
251
+
252
+ b.Navigation("Group");
253
+ });
254
+
255
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
256
+ {
257
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
258
+ .WithMany("Documents")
259
+ .HasForeignKey("ContactId")
260
+ .OnDelete(DeleteBehavior.Cascade)
261
+ .IsRequired();
262
+
263
+ b.Navigation("Contact");
264
+ });
265
+
266
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
267
+ {
268
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
269
+ .WithMany("Photos")
270
+ .HasForeignKey("ContactId")
271
+ .OnDelete(DeleteBehavior.Cascade)
272
+ .IsRequired();
273
+
274
+ b.Navigation("Contact");
275
+ });
276
+
277
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
278
+ {
279
+ b.Navigation("Documents");
280
+
281
+ b.Navigation("Photos");
282
+ });
283
+
284
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
285
+ {
286
+ b.Navigation("Contacts");
287
+ });
288
+ #pragma warning restore 612, 618
289
+ }
290
+ }
291
+ }
ContactManagementAPI/Migrations/20260209052719_AddSampleData.cs ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using Microsoft.EntityFrameworkCore.Migrations;
3
+
4
+ #nullable disable
5
+
6
+ namespace ContactManagementAPI.Migrations
7
+ {
8
+ /// <inheritdoc />
9
+ public partial class AddSampleData : Migration
10
+ {
11
+ /// <inheritdoc />
12
+ protected override void Up(MigrationBuilder migrationBuilder)
13
+ {
14
+ migrationBuilder.UpdateData(
15
+ table: "ContactGroups",
16
+ keyColumn: "Id",
17
+ keyValue: 1,
18
+ column: "CreatedAt",
19
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8518));
20
+
21
+ migrationBuilder.UpdateData(
22
+ table: "ContactGroups",
23
+ keyColumn: "Id",
24
+ keyValue: 2,
25
+ column: "CreatedAt",
26
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8531));
27
+
28
+ migrationBuilder.UpdateData(
29
+ table: "ContactGroups",
30
+ keyColumn: "Id",
31
+ keyValue: 3,
32
+ column: "CreatedAt",
33
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8533));
34
+
35
+ migrationBuilder.UpdateData(
36
+ table: "ContactGroups",
37
+ keyColumn: "Id",
38
+ keyValue: 4,
39
+ column: "CreatedAt",
40
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8535));
41
+
42
+ migrationBuilder.UpdateData(
43
+ table: "ContactGroups",
44
+ keyColumn: "Id",
45
+ keyValue: 5,
46
+ column: "CreatedAt",
47
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8537));
48
+
49
+ migrationBuilder.UpdateData(
50
+ table: "ContactGroups",
51
+ keyColumn: "Id",
52
+ keyValue: 6,
53
+ column: "CreatedAt",
54
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8538));
55
+
56
+ // Insert sample contacts
57
+ migrationBuilder.InsertData(
58
+ table: "Contacts",
59
+ columns: new[] { "Id", "FirstName", "LastName", "NickName", "Email", "Mobile1", "Mobile2", "Mobile3", "WhatsAppNumber", "Address", "City", "State", "PostalCode", "Country", "PhotoPath", "GroupId", "OtherDetails", "CreatedAt", "UpdatedAt" },
60
+ values: new object[,]
61
+ {
62
+ { 1, "John", "Doe", "Johnny", "john.doe@email.com", "+1-555-0101", "+1-555-0102", null, "+1-555-0101", "123 Main Street", "New York", "NY", "10001", "USA", "/uploads/photos/1_sample.jpg", 1, "Close friend from college", new DateTime(2026, 2, 9, 10, 30, 0), new DateTime(2026, 2, 9, 10, 30, 0) },
63
+ { 2, "Sarah", "Smith", "Sara", "sarah.smith@email.com", "+1-555-0201", "+1-555-0202", "+1-555-0203", "+1-555-0201", "456 Oak Avenue", "Los Angeles", "CA", "90001", "USA", "/uploads/photos/2_sample.jpg", 2, "Works at Tech Corp", new DateTime(2026, 2, 9, 10, 32, 0), new DateTime(2026, 2, 9, 10, 32, 0) },
64
+ { 3, "Michael", "Johnson", "Mike", "michael.j@email.com", "+1-555-0301", null, null, "+1-555-0301", "789 Pine Road", "Chicago", "IL", "60601", "USA", "/uploads/photos/3_sample.jpg", 3, "Project manager", new DateTime(2026, 2, 9, 10, 34, 0), new DateTime(2026, 2, 9, 10, 34, 0) },
65
+ { 4, "Emily", "Brown", "Em", "emily.brown@email.com", "+1-555-0401", "+1-555-0402", null, "+1-555-0401", "321 Elm Street", "Houston", "TX", "77001", "USA", "/uploads/photos/4_sample.jpg", 1, "Sister, lives in Houston", new DateTime(2026, 2, 9, 10, 36, 0), new DateTime(2026, 2, 9, 10, 36, 0) },
66
+ { 5, "David", "Wilson", "Dave", "david.w@email.com", "+1-555-0501", "+1-555-0502", "+1-555-0503", "+1-555-0501", "654 Cedar Lane", "Phoenix", "AZ", "85001", "USA", "/uploads/photos/5_sample.jpg", 4, "School principal", new DateTime(2026, 2, 9, 10, 38, 0), new DateTime(2026, 2, 9, 10, 38, 0) }
67
+ });
68
+
69
+ // Insert sample documents
70
+ migrationBuilder.InsertData(
71
+ table: "ContactDocuments",
72
+ columns: new[] { "ContactId", "DocumentPath", "FileName", "FileSize", "ContentType", "DocumentType", "UploadedAt" },
73
+ values: new object[,]
74
+ {
75
+ { 1, "/uploads/documents/1_ID_Proof.pdf", "john_doe_id.pdf", 145000L, "application/pdf", "ID", new DateTime(2026, 2, 9, 10, 30, 0) },
76
+ { 1, "/uploads/documents/1_Resume.pdf", "john_doe_resume.pdf", 235000L, "application/pdf", "Resume", new DateTime(2026, 2, 9, 10, 31, 0) },
77
+ { 2, "/uploads/documents/2_Business_Card.pdf", "sarah_smith_business.pdf", 89000L, "application/pdf", "Business", new DateTime(2026, 2, 9, 10, 32, 0) },
78
+ { 2, "/uploads/documents/2_Address_Proof.pdf", "sarah_address.pdf", 156000L, "application/pdf", "Address", new DateTime(2026, 2, 9, 10, 33, 0) },
79
+ { 3, "/uploads/documents/3_Contract.pdf", "michael_contract.pdf", 289000L, "application/pdf", "Contract", new DateTime(2026, 2, 9, 10, 34, 0) },
80
+ { 4, "/uploads/documents/4_ID_Proof.pdf", "emily_id.pdf", 167000L, "application/pdf", "ID", new DateTime(2026, 2, 9, 10, 36, 0) },
81
+ { 5, "/uploads/documents/5_Certification.pdf", "david_certification.pdf", 198000L, "application/pdf", "Certification", new DateTime(2026, 2, 9, 10, 38, 0) },
82
+ { 5, "/uploads/documents/5_License.pdf", "david_license.pdf", 142000L, "application/pdf", "License", new DateTime(2026, 2, 9, 10, 39, 0) }
83
+ });
84
+ }
85
+
86
+ /// <inheritdoc />
87
+ protected override void Down(MigrationBuilder migrationBuilder)
88
+ {
89
+ migrationBuilder.DeleteData(
90
+ table: "ContactDocuments",
91
+ keyColumn: "Id",
92
+ keyValue: 1);
93
+
94
+ migrationBuilder.DeleteData(
95
+ table: "ContactDocuments",
96
+ keyColumn: "Id",
97
+ keyValue: 2);
98
+
99
+ migrationBuilder.DeleteData(
100
+ table: "ContactDocuments",
101
+ keyColumn: "Id",
102
+ keyValue: 3);
103
+
104
+ migrationBuilder.DeleteData(
105
+ table: "ContactDocuments",
106
+ keyColumn: "Id",
107
+ keyValue: 4);
108
+
109
+ migrationBuilder.DeleteData(
110
+ table: "ContactDocuments",
111
+ keyColumn: "Id",
112
+ keyValue: 5);
113
+
114
+ migrationBuilder.DeleteData(
115
+ table: "ContactDocuments",
116
+ keyColumn: "Id",
117
+ keyValue: 6);
118
+
119
+ migrationBuilder.DeleteData(
120
+ table: "ContactDocuments",
121
+ keyColumn: "Id",
122
+ keyValue: 7);
123
+
124
+ migrationBuilder.DeleteData(
125
+ table: "ContactDocuments",
126
+ keyColumn: "Id",
127
+ keyValue: 8);
128
+
129
+ migrationBuilder.DeleteData(
130
+ table: "Contacts",
131
+ keyColumn: "Id",
132
+ keyValue: 1);
133
+
134
+ migrationBuilder.DeleteData(
135
+ table: "Contacts",
136
+ keyColumn: "Id",
137
+ keyValue: 2);
138
+
139
+ migrationBuilder.DeleteData(
140
+ table: "Contacts",
141
+ keyColumn: "Id",
142
+ keyValue: 3);
143
+
144
+ migrationBuilder.DeleteData(
145
+ table: "Contacts",
146
+ keyColumn: "Id",
147
+ keyValue: 4);
148
+
149
+ migrationBuilder.DeleteData(
150
+ table: "Contacts",
151
+ keyColumn: "Id",
152
+ keyValue: 5);
153
+
154
+ migrationBuilder.UpdateData(
155
+ table: "ContactGroups",
156
+ keyColumn: "Id",
157
+ keyValue: 1,
158
+ column: "CreatedAt",
159
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5320));
160
+
161
+ migrationBuilder.UpdateData(
162
+ table: "ContactGroups",
163
+ keyColumn: "Id",
164
+ keyValue: 2,
165
+ column: "CreatedAt",
166
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5332));
167
+
168
+ migrationBuilder.UpdateData(
169
+ table: "ContactGroups",
170
+ keyColumn: "Id",
171
+ keyValue: 3,
172
+ column: "CreatedAt",
173
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5333));
174
+
175
+ migrationBuilder.UpdateData(
176
+ table: "ContactGroups",
177
+ keyColumn: "Id",
178
+ keyValue: 4,
179
+ column: "CreatedAt",
180
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5335));
181
+
182
+ migrationBuilder.UpdateData(
183
+ table: "ContactGroups",
184
+ keyColumn: "Id",
185
+ keyValue: 5,
186
+ column: "CreatedAt",
187
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5339));
188
+
189
+ migrationBuilder.UpdateData(
190
+ table: "ContactGroups",
191
+ keyColumn: "Id",
192
+ keyValue: 6,
193
+ column: "CreatedAt",
194
+ value: new DateTime(2026, 2, 8, 21, 55, 46, 992, DateTimeKind.Local).AddTicks(5340));
195
+ }
196
+ }
197
+ }
ContactManagementAPI/Migrations/20260209090000_AddUserSecurity.Designer.cs ADDED
@@ -0,0 +1,463 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // <auto-generated />
2
+ using System;
3
+ using ContactManagementAPI.Data;
4
+ using Microsoft.EntityFrameworkCore;
5
+ using Microsoft.EntityFrameworkCore.Infrastructure;
6
+ using Microsoft.EntityFrameworkCore.Metadata;
7
+ using Microsoft.EntityFrameworkCore.Migrations;
8
+ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9
+
10
+ #nullable disable
11
+
12
+ namespace ContactManagementAPI.Migrations
13
+ {
14
+ [DbContext(typeof(ApplicationDbContext))]
15
+ [Migration("20260209090000_AddUserSecurity")]
16
+ partial class AddUserSecurity
17
+ {
18
+ /// <inheritdoc />
19
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
20
+ {
21
+ #pragma warning disable 612, 618
22
+ modelBuilder
23
+ .HasAnnotation("ProductVersion", "8.0.0")
24
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
25
+
26
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
27
+
28
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
29
+ {
30
+ b.Property<int>("Id")
31
+ .ValueGeneratedOnAdd()
32
+ .HasColumnType("int");
33
+
34
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
35
+
36
+ b.Property<string>("Address")
37
+ .HasColumnType("nvarchar(max)");
38
+
39
+ b.Property<string>("City")
40
+ .HasColumnType("nvarchar(max)");
41
+
42
+ b.Property<string>("Country")
43
+ .HasColumnType("nvarchar(max)");
44
+
45
+ b.Property<DateTime>("CreatedAt")
46
+ .HasColumnType("datetime2");
47
+
48
+ b.Property<string>("Email")
49
+ .HasColumnType("nvarchar(max)");
50
+
51
+ b.Property<string>("FirstName")
52
+ .IsRequired()
53
+ .HasColumnType("nvarchar(max)");
54
+
55
+ b.Property<int?>("GroupId")
56
+ .HasColumnType("int");
57
+
58
+ b.Property<string>("LastName")
59
+ .HasColumnType("nvarchar(max)");
60
+
61
+ b.Property<string>("Mobile1")
62
+ .HasColumnType("nvarchar(max)");
63
+
64
+ b.Property<string>("Mobile2")
65
+ .HasColumnType("nvarchar(max)");
66
+
67
+ b.Property<string>("Mobile3")
68
+ .HasColumnType("nvarchar(max)");
69
+
70
+ b.Property<string>("NickName")
71
+ .HasColumnType("nvarchar(max)");
72
+
73
+ b.Property<string>("OtherDetails")
74
+ .HasColumnType("nvarchar(max)");
75
+
76
+ b.Property<string>("PhotoPath")
77
+ .HasColumnType("nvarchar(max)");
78
+
79
+ b.Property<string>("PostalCode")
80
+ .HasColumnType("nvarchar(max)");
81
+
82
+ b.Property<string>("State")
83
+ .HasColumnType("nvarchar(max)");
84
+
85
+ b.Property<DateTime>("UpdatedAt")
86
+ .HasColumnType("datetime2");
87
+
88
+ b.Property<string>("WhatsAppNumber")
89
+ .HasColumnType("nvarchar(max)");
90
+
91
+ b.HasKey("Id");
92
+
93
+ b.HasIndex("GroupId");
94
+
95
+ b.ToTable("Contacts");
96
+ });
97
+
98
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
99
+ {
100
+ b.Property<int>("Id")
101
+ .ValueGeneratedOnAdd()
102
+ .HasColumnType("int");
103
+
104
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
105
+
106
+ b.Property<int>("ContactId")
107
+ .HasColumnType("int");
108
+
109
+ b.Property<string>("ContentType")
110
+ .IsRequired()
111
+ .HasColumnType("nvarchar(max)");
112
+
113
+ b.Property<string>("DocumentPath")
114
+ .IsRequired()
115
+ .HasColumnType("nvarchar(max)");
116
+
117
+ b.Property<string>("DocumentType")
118
+ .IsRequired()
119
+ .HasColumnType("nvarchar(max)");
120
+
121
+ b.Property<string>("FileName")
122
+ .IsRequired()
123
+ .HasColumnType("nvarchar(max)");
124
+
125
+ b.Property<long>("FileSize")
126
+ .HasColumnType("bigint");
127
+
128
+ b.Property<DateTime>("UploadedAt")
129
+ .HasColumnType("datetime2");
130
+
131
+ b.HasKey("Id");
132
+
133
+ b.HasIndex("ContactId");
134
+
135
+ b.ToTable("ContactDocuments");
136
+ });
137
+
138
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
139
+ {
140
+ b.Property<int>("Id")
141
+ .ValueGeneratedOnAdd()
142
+ .HasColumnType("int");
143
+
144
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
145
+
146
+ b.Property<DateTime>("CreatedAt")
147
+ .HasColumnType("datetime2");
148
+
149
+ b.Property<string>("Description")
150
+ .IsRequired()
151
+ .HasColumnType("nvarchar(max)");
152
+
153
+ b.Property<string>("Name")
154
+ .IsRequired()
155
+ .HasColumnType("nvarchar(max)");
156
+
157
+ b.HasKey("Id");
158
+
159
+ b.ToTable("ContactGroups");
160
+
161
+ b.HasData(
162
+ new
163
+ {
164
+ Id = 1,
165
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8518),
166
+ Description = "Family members",
167
+ Name = "Family"
168
+ },
169
+ new
170
+ {
171
+ Id = 2,
172
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8531),
173
+ Description = "Friends",
174
+ Name = "Friends"
175
+ },
176
+ new
177
+ {
178
+ Id = 3,
179
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8533),
180
+ Description = "Business contacts",
181
+ Name = "Business"
182
+ },
183
+ new
184
+ {
185
+ Id = 4,
186
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8535),
187
+ Description = "School contacts",
188
+ Name = "School"
189
+ },
190
+ new
191
+ {
192
+ Id = 5,
193
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8537),
194
+ Description = "Church members",
195
+ Name = "Church"
196
+ },
197
+ new
198
+ {
199
+ Id = 6,
200
+ CreatedAt = new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8538),
201
+ Description = "Other contacts",
202
+ Name = "Others"
203
+ });
204
+ });
205
+
206
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
207
+ {
208
+ b.Property<int>("Id")
209
+ .ValueGeneratedOnAdd()
210
+ .HasColumnType("int");
211
+
212
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
213
+
214
+ b.Property<int>("ContactId")
215
+ .HasColumnType("int");
216
+
217
+ b.Property<string>("ContentType")
218
+ .IsRequired()
219
+ .HasColumnType("nvarchar(max)");
220
+
221
+ b.Property<string>("FileName")
222
+ .IsRequired()
223
+ .HasColumnType("nvarchar(max)");
224
+
225
+ b.Property<long>("FileSize")
226
+ .HasColumnType("bigint");
227
+
228
+ b.Property<bool>("IsProfilePhoto")
229
+ .HasColumnType("bit");
230
+
231
+ b.Property<string>("PhotoPath")
232
+ .IsRequired()
233
+ .HasColumnType("nvarchar(max)");
234
+
235
+ b.Property<DateTime>("UploadedAt")
236
+ .HasColumnType("datetime2");
237
+
238
+ b.HasKey("Id");
239
+
240
+ b.HasIndex("ContactId");
241
+
242
+ b.ToTable("ContactPhotos");
243
+ });
244
+
245
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
246
+ {
247
+ b.Property<int>("Id")
248
+ .ValueGeneratedOnAdd()
249
+ .HasColumnType("int");
250
+
251
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
252
+
253
+ b.Property<DateTime>("CreatedAt")
254
+ .HasColumnType("datetime2");
255
+
256
+ b.Property<string>("FullName")
257
+ .HasMaxLength(200)
258
+ .HasColumnType("nvarchar(200)");
259
+
260
+ b.Property<int?>("GroupId")
261
+ .HasColumnType("int");
262
+
263
+ b.Property<bool>("IsActive")
264
+ .HasColumnType("bit");
265
+
266
+ b.Property<bool>("IsAdmin")
267
+ .HasColumnType("bit");
268
+
269
+ b.Property<string>("PasswordHash")
270
+ .IsRequired()
271
+ .HasColumnType("nvarchar(max)");
272
+
273
+ b.Property<DateTime>("UpdatedAt")
274
+ .HasColumnType("datetime2");
275
+
276
+ b.Property<string>("UserName")
277
+ .IsRequired()
278
+ .HasMaxLength(100)
279
+ .HasColumnType("nvarchar(100)");
280
+
281
+ b.HasKey("Id");
282
+
283
+ b.HasIndex("GroupId");
284
+
285
+ b.HasIndex("UserName")
286
+ .IsUnique();
287
+
288
+ b.ToTable("AppUsers");
289
+ });
290
+
291
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
292
+ {
293
+ b.Property<int>("Id")
294
+ .ValueGeneratedOnAdd()
295
+ .HasColumnType("int");
296
+
297
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
298
+
299
+ b.Property<bool>("IsGranted")
300
+ .HasColumnType("bit");
301
+
302
+ b.Property<string>("RightKey")
303
+ .IsRequired()
304
+ .HasMaxLength(100)
305
+ .HasColumnType("nvarchar(100)");
306
+
307
+ b.Property<int>("UserGroupId")
308
+ .HasColumnType("int");
309
+
310
+ b.HasKey("Id");
311
+
312
+ b.HasIndex("UserGroupId", "RightKey")
313
+ .IsUnique();
314
+
315
+ b.ToTable("GroupRights");
316
+ });
317
+
318
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
319
+ {
320
+ b.Property<int>("Id")
321
+ .ValueGeneratedOnAdd()
322
+ .HasColumnType("int");
323
+
324
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
325
+
326
+ b.Property<DateTime>("CreatedAt")
327
+ .HasColumnType("datetime2");
328
+
329
+ b.Property<string>("Description")
330
+ .HasMaxLength(500)
331
+ .HasColumnType("nvarchar(500)");
332
+
333
+ b.Property<string>("Name")
334
+ .IsRequired()
335
+ .HasMaxLength(150)
336
+ .HasColumnType("nvarchar(150)");
337
+
338
+ b.HasKey("Id");
339
+
340
+ b.HasIndex("Name")
341
+ .IsUnique();
342
+
343
+ b.ToTable("UserGroups");
344
+ });
345
+
346
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
347
+ {
348
+ b.Property<int>("Id")
349
+ .ValueGeneratedOnAdd()
350
+ .HasColumnType("int");
351
+
352
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
353
+
354
+ b.Property<int>("AppUserId")
355
+ .HasColumnType("int");
356
+
357
+ b.Property<bool>("IsGranted")
358
+ .HasColumnType("bit");
359
+
360
+ b.Property<string>("RightKey")
361
+ .IsRequired()
362
+ .HasMaxLength(100)
363
+ .HasColumnType("nvarchar(100)");
364
+
365
+ b.HasKey("Id");
366
+
367
+ b.HasIndex("AppUserId", "RightKey")
368
+ .IsUnique();
369
+
370
+ b.ToTable("UserRights");
371
+ });
372
+
373
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
374
+ {
375
+ b.HasOne("ContactManagementAPI.Models.ContactGroup", "Group")
376
+ .WithMany("Contacts")
377
+ .HasForeignKey("GroupId")
378
+ .OnDelete(DeleteBehavior.SetNull);
379
+
380
+ b.Navigation("Group");
381
+ });
382
+
383
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
384
+ {
385
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
386
+ .WithMany("Documents")
387
+ .HasForeignKey("ContactId")
388
+ .OnDelete(DeleteBehavior.Cascade)
389
+ .IsRequired();
390
+
391
+ b.Navigation("Contact");
392
+ });
393
+
394
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
395
+ {
396
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
397
+ .WithMany("Photos")
398
+ .HasForeignKey("ContactId")
399
+ .OnDelete(DeleteBehavior.Cascade)
400
+ .IsRequired();
401
+
402
+ b.Navigation("Contact");
403
+ });
404
+
405
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
406
+ {
407
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "Group")
408
+ .WithMany("Users")
409
+ .HasForeignKey("GroupId")
410
+ .OnDelete(DeleteBehavior.SetNull);
411
+
412
+ b.Navigation("Group");
413
+ });
414
+
415
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
416
+ {
417
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "UserGroup")
418
+ .WithMany("GroupRights")
419
+ .HasForeignKey("UserGroupId")
420
+ .OnDelete(DeleteBehavior.Cascade)
421
+ .IsRequired();
422
+
423
+ b.Navigation("UserGroup");
424
+ });
425
+
426
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
427
+ {
428
+ b.HasOne("ContactManagementAPI.Models.AppUser", "AppUser")
429
+ .WithMany("UserRights")
430
+ .HasForeignKey("AppUserId")
431
+ .OnDelete(DeleteBehavior.Cascade)
432
+ .IsRequired();
433
+
434
+ b.Navigation("AppUser");
435
+ });
436
+
437
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
438
+ {
439
+ b.Navigation("Documents");
440
+
441
+ b.Navigation("Photos");
442
+ });
443
+
444
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
445
+ {
446
+ b.Navigation("Contacts");
447
+ });
448
+
449
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
450
+ {
451
+ b.Navigation("UserRights");
452
+ });
453
+
454
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
455
+ {
456
+ b.Navigation("GroupRights");
457
+
458
+ b.Navigation("Users");
459
+ });
460
+ #pragma warning restore 612, 618
461
+ }
462
+ }
463
+ }
ContactManagementAPI/Migrations/20260209090000_AddUserSecurity.cs ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using Microsoft.EntityFrameworkCore.Migrations;
3
+
4
+ #nullable disable
5
+
6
+ namespace ContactManagementAPI.Migrations
7
+ {
8
+ /// <inheritdoc />
9
+ public partial class AddUserSecurity : Migration
10
+ {
11
+ /// <inheritdoc />
12
+ protected override void Up(MigrationBuilder migrationBuilder)
13
+ {
14
+ migrationBuilder.CreateTable(
15
+ name: "UserGroups",
16
+ columns: table => new
17
+ {
18
+ Id = table.Column<int>(type: "int", nullable: false)
19
+ .Annotation("SqlServer:Identity", "1, 1"),
20
+ Name = table.Column<string>(type: "nvarchar(150)", maxLength: 150, nullable: false),
21
+ Description = table.Column<string>(type: "nvarchar(500)", maxLength: 500, nullable: true),
22
+ CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
23
+ },
24
+ constraints: table =>
25
+ {
26
+ table.PrimaryKey("PK_UserGroups", x => x.Id);
27
+ });
28
+
29
+ migrationBuilder.CreateTable(
30
+ name: "AppUsers",
31
+ columns: table => new
32
+ {
33
+ Id = table.Column<int>(type: "int", nullable: false)
34
+ .Annotation("SqlServer:Identity", "1, 1"),
35
+ UserName = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
36
+ PasswordHash = table.Column<string>(type: "nvarchar(max)", nullable: false),
37
+ FullName = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: true),
38
+ IsAdmin = table.Column<bool>(type: "bit", nullable: false),
39
+ IsActive = table.Column<bool>(type: "bit", nullable: false),
40
+ GroupId = table.Column<int>(type: "int", nullable: true),
41
+ CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
42
+ UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
43
+ },
44
+ constraints: table =>
45
+ {
46
+ table.PrimaryKey("PK_AppUsers", x => x.Id);
47
+ table.ForeignKey(
48
+ name: "FK_AppUsers_UserGroups_GroupId",
49
+ column: x => x.GroupId,
50
+ principalTable: "UserGroups",
51
+ principalColumn: "Id",
52
+ onDelete: ReferentialAction.SetNull);
53
+ });
54
+
55
+ migrationBuilder.CreateTable(
56
+ name: "GroupRights",
57
+ columns: table => new
58
+ {
59
+ Id = table.Column<int>(type: "int", nullable: false)
60
+ .Annotation("SqlServer:Identity", "1, 1"),
61
+ UserGroupId = table.Column<int>(type: "int", nullable: false),
62
+ RightKey = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
63
+ IsGranted = table.Column<bool>(type: "bit", nullable: false)
64
+ },
65
+ constraints: table =>
66
+ {
67
+ table.PrimaryKey("PK_GroupRights", x => x.Id);
68
+ table.ForeignKey(
69
+ name: "FK_GroupRights_UserGroups_UserGroupId",
70
+ column: x => x.UserGroupId,
71
+ principalTable: "UserGroups",
72
+ principalColumn: "Id",
73
+ onDelete: ReferentialAction.Cascade);
74
+ });
75
+
76
+ migrationBuilder.CreateTable(
77
+ name: "UserRights",
78
+ columns: table => new
79
+ {
80
+ Id = table.Column<int>(type: "int", nullable: false)
81
+ .Annotation("SqlServer:Identity", "1, 1"),
82
+ AppUserId = table.Column<int>(type: "int", nullable: false),
83
+ RightKey = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false),
84
+ IsGranted = table.Column<bool>(type: "bit", nullable: false)
85
+ },
86
+ constraints: table =>
87
+ {
88
+ table.PrimaryKey("PK_UserRights", x => x.Id);
89
+ table.ForeignKey(
90
+ name: "FK_UserRights_AppUsers_AppUserId",
91
+ column: x => x.AppUserId,
92
+ principalTable: "AppUsers",
93
+ principalColumn: "Id",
94
+ onDelete: ReferentialAction.Cascade);
95
+ });
96
+
97
+ migrationBuilder.CreateIndex(
98
+ name: "IX_AppUsers_GroupId",
99
+ table: "AppUsers",
100
+ column: "GroupId");
101
+
102
+ migrationBuilder.CreateIndex(
103
+ name: "IX_AppUsers_UserName",
104
+ table: "AppUsers",
105
+ column: "UserName",
106
+ unique: true);
107
+
108
+ migrationBuilder.CreateIndex(
109
+ name: "IX_GroupRights_UserGroupId_RightKey",
110
+ table: "GroupRights",
111
+ columns: new[] { "UserGroupId", "RightKey" },
112
+ unique: true);
113
+
114
+ migrationBuilder.CreateIndex(
115
+ name: "IX_UserGroups_Name",
116
+ table: "UserGroups",
117
+ column: "Name",
118
+ unique: true);
119
+
120
+ migrationBuilder.CreateIndex(
121
+ name: "IX_UserRights_AppUserId_RightKey",
122
+ table: "UserRights",
123
+ columns: new[] { "AppUserId", "RightKey" },
124
+ unique: true);
125
+ }
126
+
127
+ /// <inheritdoc />
128
+ protected override void Down(MigrationBuilder migrationBuilder)
129
+ {
130
+ migrationBuilder.DropTable(
131
+ name: "UserRights");
132
+
133
+ migrationBuilder.DropTable(
134
+ name: "GroupRights");
135
+
136
+ migrationBuilder.DropTable(
137
+ name: "AppUsers");
138
+
139
+ migrationBuilder.DropTable(
140
+ name: "UserGroups");
141
+ }
142
+ }
143
+ }
ContactManagementAPI/Migrations/20260216070933_AddNewContactGroups.Designer.cs ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // <auto-generated />
2
+ using System;
3
+ using ContactManagementAPI.Data;
4
+ using Microsoft.EntityFrameworkCore;
5
+ using Microsoft.EntityFrameworkCore.Infrastructure;
6
+ using Microsoft.EntityFrameworkCore.Metadata;
7
+ using Microsoft.EntityFrameworkCore.Migrations;
8
+ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9
+
10
+ #nullable disable
11
+
12
+ namespace ContactManagementAPI.Migrations
13
+ {
14
+ [DbContext(typeof(ApplicationDbContext))]
15
+ [Migration("20260216070933_AddNewContactGroups")]
16
+ partial class AddNewContactGroups
17
+ {
18
+ /// <inheritdoc />
19
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
20
+ {
21
+ #pragma warning disable 612, 618
22
+ modelBuilder
23
+ .HasAnnotation("ProductVersion", "8.0.0")
24
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
25
+
26
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
27
+
28
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
29
+ {
30
+ b.Property<int>("Id")
31
+ .ValueGeneratedOnAdd()
32
+ .HasColumnType("int");
33
+
34
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
35
+
36
+ b.Property<DateTime>("CreatedAt")
37
+ .HasColumnType("datetime2");
38
+
39
+ b.Property<string>("FullName")
40
+ .HasMaxLength(200)
41
+ .HasColumnType("nvarchar(200)");
42
+
43
+ b.Property<int>("GroupId")
44
+ .HasColumnType("int");
45
+
46
+ b.Property<bool>("IsActive")
47
+ .HasColumnType("bit");
48
+
49
+ b.Property<bool>("IsAdmin")
50
+ .HasColumnType("bit");
51
+
52
+ b.Property<string>("PasswordHash")
53
+ .IsRequired()
54
+ .HasColumnType("nvarchar(max)");
55
+
56
+ b.Property<DateTime>("UpdatedAt")
57
+ .HasColumnType("datetime2");
58
+
59
+ b.Property<string>("UserName")
60
+ .IsRequired()
61
+ .HasMaxLength(100)
62
+ .HasColumnType("nvarchar(100)");
63
+
64
+ b.HasKey("Id");
65
+
66
+ b.HasIndex("GroupId");
67
+
68
+ b.HasIndex("UserName")
69
+ .IsUnique();
70
+
71
+ b.ToTable("AppUsers");
72
+ });
73
+
74
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
75
+ {
76
+ b.Property<int>("Id")
77
+ .ValueGeneratedOnAdd()
78
+ .HasColumnType("int");
79
+
80
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
81
+
82
+ b.Property<string>("Address")
83
+ .HasColumnType("nvarchar(max)");
84
+
85
+ b.Property<string>("City")
86
+ .HasColumnType("nvarchar(max)");
87
+
88
+ b.Property<string>("Country")
89
+ .HasColumnType("nvarchar(max)");
90
+
91
+ b.Property<DateTime>("CreatedAt")
92
+ .HasColumnType("datetime2");
93
+
94
+ b.Property<string>("Email")
95
+ .HasColumnType("nvarchar(max)");
96
+
97
+ b.Property<string>("FirstName")
98
+ .IsRequired()
99
+ .HasColumnType("nvarchar(max)");
100
+
101
+ b.Property<int?>("GroupId")
102
+ .HasColumnType("int");
103
+
104
+ b.Property<string>("LastName")
105
+ .HasColumnType("nvarchar(max)");
106
+
107
+ b.Property<string>("Mobile1")
108
+ .HasColumnType("nvarchar(max)");
109
+
110
+ b.Property<string>("Mobile2")
111
+ .HasColumnType("nvarchar(max)");
112
+
113
+ b.Property<string>("Mobile3")
114
+ .HasColumnType("nvarchar(max)");
115
+
116
+ b.Property<string>("NickName")
117
+ .HasColumnType("nvarchar(max)");
118
+
119
+ b.Property<string>("OtherDetails")
120
+ .HasColumnType("nvarchar(max)");
121
+
122
+ b.Property<string>("PhotoPath")
123
+ .HasColumnType("nvarchar(max)");
124
+
125
+ b.Property<string>("PostalCode")
126
+ .HasColumnType("nvarchar(max)");
127
+
128
+ b.Property<string>("State")
129
+ .HasColumnType("nvarchar(max)");
130
+
131
+ b.Property<DateTime>("UpdatedAt")
132
+ .HasColumnType("datetime2");
133
+
134
+ b.Property<string>("WhatsAppNumber")
135
+ .HasColumnType("nvarchar(max)");
136
+
137
+ b.HasKey("Id");
138
+
139
+ b.HasIndex("GroupId");
140
+
141
+ b.ToTable("Contacts");
142
+ });
143
+
144
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
145
+ {
146
+ b.Property<int>("Id")
147
+ .ValueGeneratedOnAdd()
148
+ .HasColumnType("int");
149
+
150
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
151
+
152
+ b.Property<int>("ContactId")
153
+ .HasColumnType("int");
154
+
155
+ b.Property<string>("ContentType")
156
+ .IsRequired()
157
+ .HasColumnType("nvarchar(max)");
158
+
159
+ b.Property<string>("DocumentPath")
160
+ .IsRequired()
161
+ .HasColumnType("nvarchar(max)");
162
+
163
+ b.Property<string>("DocumentType")
164
+ .IsRequired()
165
+ .HasColumnType("nvarchar(max)");
166
+
167
+ b.Property<string>("FileName")
168
+ .IsRequired()
169
+ .HasColumnType("nvarchar(max)");
170
+
171
+ b.Property<long>("FileSize")
172
+ .HasColumnType("bigint");
173
+
174
+ b.Property<DateTime>("UploadedAt")
175
+ .HasColumnType("datetime2");
176
+
177
+ b.HasKey("Id");
178
+
179
+ b.HasIndex("ContactId");
180
+
181
+ b.ToTable("ContactDocuments");
182
+ });
183
+
184
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
185
+ {
186
+ b.Property<int>("Id")
187
+ .ValueGeneratedOnAdd()
188
+ .HasColumnType("int");
189
+
190
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
191
+
192
+ b.Property<DateTime>("CreatedAt")
193
+ .HasColumnType("datetime2");
194
+
195
+ b.Property<string>("Description")
196
+ .IsRequired()
197
+ .HasColumnType("nvarchar(max)");
198
+
199
+ b.Property<string>("Name")
200
+ .IsRequired()
201
+ .HasColumnType("nvarchar(max)");
202
+
203
+ b.HasKey("Id");
204
+
205
+ b.ToTable("ContactGroups");
206
+
207
+ b.HasData(
208
+ new
209
+ {
210
+ Id = 1,
211
+ CreatedAt = new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7842),
212
+ Description = "Family members",
213
+ Name = "Family"
214
+ },
215
+ new
216
+ {
217
+ Id = 2,
218
+ CreatedAt = new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7855),
219
+ Description = "Friends",
220
+ Name = "Friends"
221
+ },
222
+ new
223
+ {
224
+ Id = 3,
225
+ CreatedAt = new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7857),
226
+ Description = "Business contacts",
227
+ Name = "Business"
228
+ },
229
+ new
230
+ {
231
+ Id = 4,
232
+ CreatedAt = new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7858),
233
+ Description = "School contacts",
234
+ Name = "School"
235
+ },
236
+ new
237
+ {
238
+ Id = 5,
239
+ CreatedAt = new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7860),
240
+ Description = "Church members",
241
+ Name = "Church"
242
+ },
243
+ new
244
+ {
245
+ Id = 6,
246
+ CreatedAt = new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7862),
247
+ Description = "Other contacts",
248
+ Name = "Others"
249
+ },
250
+ new
251
+ {
252
+ Id = 7,
253
+ CreatedAt = new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7863),
254
+ Description = "College contacts",
255
+ Name = "College"
256
+ },
257
+ new
258
+ {
259
+ Id = 8,
260
+ CreatedAt = new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7864),
261
+ Description = "Alcoholics Anonymous",
262
+ Name = "AA"
263
+ });
264
+ });
265
+
266
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
267
+ {
268
+ b.Property<int>("Id")
269
+ .ValueGeneratedOnAdd()
270
+ .HasColumnType("int");
271
+
272
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
273
+
274
+ b.Property<int>("ContactId")
275
+ .HasColumnType("int");
276
+
277
+ b.Property<string>("ContentType")
278
+ .IsRequired()
279
+ .HasColumnType("nvarchar(max)");
280
+
281
+ b.Property<string>("FileName")
282
+ .IsRequired()
283
+ .HasColumnType("nvarchar(max)");
284
+
285
+ b.Property<long>("FileSize")
286
+ .HasColumnType("bigint");
287
+
288
+ b.Property<bool>("IsProfilePhoto")
289
+ .HasColumnType("bit");
290
+
291
+ b.Property<string>("PhotoPath")
292
+ .IsRequired()
293
+ .HasColumnType("nvarchar(max)");
294
+
295
+ b.Property<DateTime>("UploadedAt")
296
+ .HasColumnType("datetime2");
297
+
298
+ b.HasKey("Id");
299
+
300
+ b.HasIndex("ContactId");
301
+
302
+ b.ToTable("ContactPhotos");
303
+ });
304
+
305
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
306
+ {
307
+ b.Property<int>("Id")
308
+ .ValueGeneratedOnAdd()
309
+ .HasColumnType("int");
310
+
311
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
312
+
313
+ b.Property<bool>("IsGranted")
314
+ .HasColumnType("bit");
315
+
316
+ b.Property<string>("RightKey")
317
+ .IsRequired()
318
+ .HasMaxLength(100)
319
+ .HasColumnType("nvarchar(100)");
320
+
321
+ b.Property<int>("UserGroupId")
322
+ .HasColumnType("int");
323
+
324
+ b.HasKey("Id");
325
+
326
+ b.HasIndex("UserGroupId", "RightKey")
327
+ .IsUnique();
328
+
329
+ b.ToTable("GroupRights");
330
+ });
331
+
332
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
333
+ {
334
+ b.Property<int>("Id")
335
+ .ValueGeneratedOnAdd()
336
+ .HasColumnType("int");
337
+
338
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
339
+
340
+ b.Property<DateTime>("CreatedAt")
341
+ .HasColumnType("datetime2");
342
+
343
+ b.Property<string>("Description")
344
+ .HasMaxLength(500)
345
+ .HasColumnType("nvarchar(500)");
346
+
347
+ b.Property<string>("Name")
348
+ .IsRequired()
349
+ .HasMaxLength(150)
350
+ .HasColumnType("nvarchar(150)");
351
+
352
+ b.HasKey("Id");
353
+
354
+ b.HasIndex("Name")
355
+ .IsUnique();
356
+
357
+ b.ToTable("UserGroups");
358
+ });
359
+
360
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
361
+ {
362
+ b.Property<int>("Id")
363
+ .ValueGeneratedOnAdd()
364
+ .HasColumnType("int");
365
+
366
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
367
+
368
+ b.Property<int>("AppUserId")
369
+ .HasColumnType("int");
370
+
371
+ b.Property<bool>("IsGranted")
372
+ .HasColumnType("bit");
373
+
374
+ b.Property<string>("RightKey")
375
+ .IsRequired()
376
+ .HasMaxLength(100)
377
+ .HasColumnType("nvarchar(100)");
378
+
379
+ b.HasKey("Id");
380
+
381
+ b.HasIndex("AppUserId", "RightKey")
382
+ .IsUnique();
383
+
384
+ b.ToTable("UserRights");
385
+ });
386
+
387
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
388
+ {
389
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "Group")
390
+ .WithMany("Users")
391
+ .HasForeignKey("GroupId")
392
+ .OnDelete(DeleteBehavior.SetNull)
393
+ .IsRequired();
394
+
395
+ b.Navigation("Group");
396
+ });
397
+
398
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
399
+ {
400
+ b.HasOne("ContactManagementAPI.Models.ContactGroup", "Group")
401
+ .WithMany("Contacts")
402
+ .HasForeignKey("GroupId")
403
+ .OnDelete(DeleteBehavior.SetNull);
404
+
405
+ b.Navigation("Group");
406
+ });
407
+
408
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
409
+ {
410
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
411
+ .WithMany("Documents")
412
+ .HasForeignKey("ContactId")
413
+ .OnDelete(DeleteBehavior.Cascade)
414
+ .IsRequired();
415
+
416
+ b.Navigation("Contact");
417
+ });
418
+
419
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
420
+ {
421
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
422
+ .WithMany("Photos")
423
+ .HasForeignKey("ContactId")
424
+ .OnDelete(DeleteBehavior.Cascade)
425
+ .IsRequired();
426
+
427
+ b.Navigation("Contact");
428
+ });
429
+
430
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
431
+ {
432
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "UserGroup")
433
+ .WithMany("GroupRights")
434
+ .HasForeignKey("UserGroupId")
435
+ .OnDelete(DeleteBehavior.Cascade)
436
+ .IsRequired();
437
+
438
+ b.Navigation("UserGroup");
439
+ });
440
+
441
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
442
+ {
443
+ b.HasOne("ContactManagementAPI.Models.AppUser", "AppUser")
444
+ .WithMany("UserRights")
445
+ .HasForeignKey("AppUserId")
446
+ .OnDelete(DeleteBehavior.Cascade)
447
+ .IsRequired();
448
+
449
+ b.Navigation("AppUser");
450
+ });
451
+
452
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
453
+ {
454
+ b.Navigation("UserRights");
455
+ });
456
+
457
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
458
+ {
459
+ b.Navigation("Documents");
460
+
461
+ b.Navigation("Photos");
462
+ });
463
+
464
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
465
+ {
466
+ b.Navigation("Contacts");
467
+ });
468
+
469
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
470
+ {
471
+ b.Navigation("GroupRights");
472
+
473
+ b.Navigation("Users");
474
+ });
475
+ #pragma warning restore 612, 618
476
+ }
477
+ }
478
+ }
ContactManagementAPI/Migrations/20260216070933_AddNewContactGroups.cs ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using Microsoft.EntityFrameworkCore.Migrations;
3
+
4
+ #nullable disable
5
+
6
+ #pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
7
+
8
+ namespace ContactManagementAPI.Migrations
9
+ {
10
+ /// <inheritdoc />
11
+ public partial class AddNewContactGroups : Migration
12
+ {
13
+ /// <inheritdoc />
14
+ protected override void Up(MigrationBuilder migrationBuilder)
15
+ {
16
+ migrationBuilder.AlterColumn<int>(
17
+ name: "GroupId",
18
+ table: "AppUsers",
19
+ type: "int",
20
+ nullable: false,
21
+ defaultValue: 0,
22
+ oldClrType: typeof(int),
23
+ oldType: "int",
24
+ oldNullable: true);
25
+
26
+ migrationBuilder.UpdateData(
27
+ table: "ContactGroups",
28
+ keyColumn: "Id",
29
+ keyValue: 1,
30
+ column: "CreatedAt",
31
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7842));
32
+
33
+ migrationBuilder.UpdateData(
34
+ table: "ContactGroups",
35
+ keyColumn: "Id",
36
+ keyValue: 2,
37
+ column: "CreatedAt",
38
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7855));
39
+
40
+ migrationBuilder.UpdateData(
41
+ table: "ContactGroups",
42
+ keyColumn: "Id",
43
+ keyValue: 3,
44
+ column: "CreatedAt",
45
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7857));
46
+
47
+ migrationBuilder.UpdateData(
48
+ table: "ContactGroups",
49
+ keyColumn: "Id",
50
+ keyValue: 4,
51
+ column: "CreatedAt",
52
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7858));
53
+
54
+ migrationBuilder.UpdateData(
55
+ table: "ContactGroups",
56
+ keyColumn: "Id",
57
+ keyValue: 5,
58
+ column: "CreatedAt",
59
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7860));
60
+
61
+ migrationBuilder.UpdateData(
62
+ table: "ContactGroups",
63
+ keyColumn: "Id",
64
+ keyValue: 6,
65
+ column: "CreatedAt",
66
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7862));
67
+
68
+ migrationBuilder.InsertData(
69
+ table: "ContactGroups",
70
+ columns: new[] { "Id", "CreatedAt", "Description", "Name" },
71
+ values: new object[,]
72
+ {
73
+ { 7, new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7863), "College contacts", "College" },
74
+ { 8, new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7864), "Alcoholics Anonymous", "AA" }
75
+ });
76
+ }
77
+
78
+ /// <inheritdoc />
79
+ protected override void Down(MigrationBuilder migrationBuilder)
80
+ {
81
+ migrationBuilder.DeleteData(
82
+ table: "ContactGroups",
83
+ keyColumn: "Id",
84
+ keyValue: 7);
85
+
86
+ migrationBuilder.DeleteData(
87
+ table: "ContactGroups",
88
+ keyColumn: "Id",
89
+ keyValue: 8);
90
+
91
+ migrationBuilder.AlterColumn<int>(
92
+ name: "GroupId",
93
+ table: "AppUsers",
94
+ type: "int",
95
+ nullable: true,
96
+ oldClrType: typeof(int),
97
+ oldType: "int");
98
+
99
+ migrationBuilder.UpdateData(
100
+ table: "ContactGroups",
101
+ keyColumn: "Id",
102
+ keyValue: 1,
103
+ column: "CreatedAt",
104
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8518));
105
+
106
+ migrationBuilder.UpdateData(
107
+ table: "ContactGroups",
108
+ keyColumn: "Id",
109
+ keyValue: 2,
110
+ column: "CreatedAt",
111
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8531));
112
+
113
+ migrationBuilder.UpdateData(
114
+ table: "ContactGroups",
115
+ keyColumn: "Id",
116
+ keyValue: 3,
117
+ column: "CreatedAt",
118
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8533));
119
+
120
+ migrationBuilder.UpdateData(
121
+ table: "ContactGroups",
122
+ keyColumn: "Id",
123
+ keyValue: 4,
124
+ column: "CreatedAt",
125
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8535));
126
+
127
+ migrationBuilder.UpdateData(
128
+ table: "ContactGroups",
129
+ keyColumn: "Id",
130
+ keyValue: 5,
131
+ column: "CreatedAt",
132
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8537));
133
+
134
+ migrationBuilder.UpdateData(
135
+ table: "ContactGroups",
136
+ keyColumn: "Id",
137
+ keyValue: 6,
138
+ column: "CreatedAt",
139
+ value: new DateTime(2026, 2, 9, 10, 57, 18, 734, DateTimeKind.Local).AddTicks(8538));
140
+ }
141
+ }
142
+ }
ContactManagementAPI/Migrations/20260220171502_AddContactIdentityBankGenderDobFields.Designer.cs ADDED
@@ -0,0 +1,508 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // <auto-generated />
2
+ using System;
3
+ using ContactManagementAPI.Data;
4
+ using Microsoft.EntityFrameworkCore;
5
+ using Microsoft.EntityFrameworkCore.Infrastructure;
6
+ using Microsoft.EntityFrameworkCore.Metadata;
7
+ using Microsoft.EntityFrameworkCore.Migrations;
8
+ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9
+
10
+ #nullable disable
11
+
12
+ namespace ContactManagementAPI.Migrations
13
+ {
14
+ [DbContext(typeof(ApplicationDbContext))]
15
+ [Migration("20260220171502_AddContactIdentityBankGenderDobFields")]
16
+ partial class AddContactIdentityBankGenderDobFields
17
+ {
18
+ /// <inheritdoc />
19
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
20
+ {
21
+ #pragma warning disable 612, 618
22
+ modelBuilder
23
+ .HasAnnotation("ProductVersion", "8.0.0")
24
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
25
+
26
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
27
+
28
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
29
+ {
30
+ b.Property<int>("Id")
31
+ .ValueGeneratedOnAdd()
32
+ .HasColumnType("int");
33
+
34
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
35
+
36
+ b.Property<DateTime>("CreatedAt")
37
+ .HasColumnType("datetime2");
38
+
39
+ b.Property<string>("FullName")
40
+ .HasMaxLength(200)
41
+ .HasColumnType("nvarchar(200)");
42
+
43
+ b.Property<int>("GroupId")
44
+ .HasColumnType("int");
45
+
46
+ b.Property<bool>("IsActive")
47
+ .HasColumnType("bit");
48
+
49
+ b.Property<bool>("IsAdmin")
50
+ .HasColumnType("bit");
51
+
52
+ b.Property<string>("PasswordHash")
53
+ .IsRequired()
54
+ .HasColumnType("nvarchar(max)");
55
+
56
+ b.Property<DateTime>("UpdatedAt")
57
+ .HasColumnType("datetime2");
58
+
59
+ b.Property<string>("UserName")
60
+ .IsRequired()
61
+ .HasMaxLength(100)
62
+ .HasColumnType("nvarchar(100)");
63
+
64
+ b.HasKey("Id");
65
+
66
+ b.HasIndex("GroupId");
67
+
68
+ b.HasIndex("UserName")
69
+ .IsUnique();
70
+
71
+ b.ToTable("AppUsers");
72
+ });
73
+
74
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
75
+ {
76
+ b.Property<int>("Id")
77
+ .ValueGeneratedOnAdd()
78
+ .HasColumnType("int");
79
+
80
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
81
+
82
+ b.Property<string>("AadharNumber")
83
+ .HasColumnType("nvarchar(max)");
84
+
85
+ b.Property<string>("Address")
86
+ .HasColumnType("nvarchar(max)");
87
+
88
+ b.Property<string>("BankAccountNumber")
89
+ .HasColumnType("nvarchar(max)");
90
+
91
+ b.Property<string>("BankName")
92
+ .HasColumnType("nvarchar(max)");
93
+
94
+ b.Property<string>("BranchName")
95
+ .HasColumnType("nvarchar(max)");
96
+
97
+ b.Property<string>("City")
98
+ .HasColumnType("nvarchar(max)");
99
+
100
+ b.Property<string>("Country")
101
+ .HasColumnType("nvarchar(max)");
102
+
103
+ b.Property<DateTime>("CreatedAt")
104
+ .HasColumnType("datetime2");
105
+
106
+ b.Property<DateTime?>("DateOfBirth")
107
+ .HasColumnType("datetime2");
108
+
109
+ b.Property<string>("DrivingLicenseNumber")
110
+ .HasColumnType("nvarchar(max)");
111
+
112
+ b.Property<string>("Email")
113
+ .HasColumnType("nvarchar(max)");
114
+
115
+ b.Property<string>("FirstName")
116
+ .IsRequired()
117
+ .HasColumnType("nvarchar(max)");
118
+
119
+ b.Property<string>("Gender")
120
+ .HasColumnType("nvarchar(max)");
121
+
122
+ b.Property<int?>("GroupId")
123
+ .HasColumnType("int");
124
+
125
+ b.Property<string>("IfscCode")
126
+ .HasColumnType("nvarchar(max)");
127
+
128
+ b.Property<string>("LastName")
129
+ .HasColumnType("nvarchar(max)");
130
+
131
+ b.Property<string>("Mobile1")
132
+ .HasColumnType("nvarchar(max)");
133
+
134
+ b.Property<string>("Mobile2")
135
+ .HasColumnType("nvarchar(max)");
136
+
137
+ b.Property<string>("Mobile3")
138
+ .HasColumnType("nvarchar(max)");
139
+
140
+ b.Property<string>("NickName")
141
+ .HasColumnType("nvarchar(max)");
142
+
143
+ b.Property<string>("OtherDetails")
144
+ .HasColumnType("nvarchar(max)");
145
+
146
+ b.Property<string>("PassportNumber")
147
+ .HasColumnType("nvarchar(max)");
148
+
149
+ b.Property<string>("PhotoPath")
150
+ .HasColumnType("nvarchar(max)");
151
+
152
+ b.Property<string>("PostalCode")
153
+ .HasColumnType("nvarchar(max)");
154
+
155
+ b.Property<string>("State")
156
+ .HasColumnType("nvarchar(max)");
157
+
158
+ b.Property<DateTime>("UpdatedAt")
159
+ .HasColumnType("datetime2");
160
+
161
+ b.Property<string>("VotersId")
162
+ .HasColumnType("nvarchar(max)");
163
+
164
+ b.Property<string>("WhatsAppNumber")
165
+ .HasColumnType("nvarchar(max)");
166
+
167
+ b.HasKey("Id");
168
+
169
+ b.HasIndex("GroupId");
170
+
171
+ b.ToTable("Contacts");
172
+ });
173
+
174
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
175
+ {
176
+ b.Property<int>("Id")
177
+ .ValueGeneratedOnAdd()
178
+ .HasColumnType("int");
179
+
180
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
181
+
182
+ b.Property<int>("ContactId")
183
+ .HasColumnType("int");
184
+
185
+ b.Property<string>("ContentType")
186
+ .IsRequired()
187
+ .HasColumnType("nvarchar(max)");
188
+
189
+ b.Property<string>("DocumentPath")
190
+ .IsRequired()
191
+ .HasColumnType("nvarchar(max)");
192
+
193
+ b.Property<string>("DocumentType")
194
+ .IsRequired()
195
+ .HasColumnType("nvarchar(max)");
196
+
197
+ b.Property<string>("FileName")
198
+ .IsRequired()
199
+ .HasColumnType("nvarchar(max)");
200
+
201
+ b.Property<long>("FileSize")
202
+ .HasColumnType("bigint");
203
+
204
+ b.Property<DateTime>("UploadedAt")
205
+ .HasColumnType("datetime2");
206
+
207
+ b.HasKey("Id");
208
+
209
+ b.HasIndex("ContactId");
210
+
211
+ b.ToTable("ContactDocuments");
212
+ });
213
+
214
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
215
+ {
216
+ b.Property<int>("Id")
217
+ .ValueGeneratedOnAdd()
218
+ .HasColumnType("int");
219
+
220
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
221
+
222
+ b.Property<DateTime>("CreatedAt")
223
+ .HasColumnType("datetime2");
224
+
225
+ b.Property<string>("Description")
226
+ .IsRequired()
227
+ .HasColumnType("nvarchar(max)");
228
+
229
+ b.Property<string>("Name")
230
+ .IsRequired()
231
+ .HasColumnType("nvarchar(max)");
232
+
233
+ b.HasKey("Id");
234
+
235
+ b.ToTable("ContactGroups");
236
+
237
+ b.HasData(
238
+ new
239
+ {
240
+ Id = 1,
241
+ CreatedAt = new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5689),
242
+ Description = "Family members",
243
+ Name = "Family"
244
+ },
245
+ new
246
+ {
247
+ Id = 2,
248
+ CreatedAt = new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5716),
249
+ Description = "Friends",
250
+ Name = "Friends"
251
+ },
252
+ new
253
+ {
254
+ Id = 3,
255
+ CreatedAt = new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5720),
256
+ Description = "Business contacts",
257
+ Name = "Business"
258
+ },
259
+ new
260
+ {
261
+ Id = 4,
262
+ CreatedAt = new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5722),
263
+ Description = "School contacts",
264
+ Name = "School"
265
+ },
266
+ new
267
+ {
268
+ Id = 5,
269
+ CreatedAt = new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5723),
270
+ Description = "Church members",
271
+ Name = "Church"
272
+ },
273
+ new
274
+ {
275
+ Id = 6,
276
+ CreatedAt = new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5725),
277
+ Description = "Other contacts",
278
+ Name = "Others"
279
+ },
280
+ new
281
+ {
282
+ Id = 7,
283
+ CreatedAt = new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5727),
284
+ Description = "College contacts",
285
+ Name = "College"
286
+ },
287
+ new
288
+ {
289
+ Id = 8,
290
+ CreatedAt = new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5729),
291
+ Description = "Alcoholics Anonymous",
292
+ Name = "AA"
293
+ });
294
+ });
295
+
296
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
297
+ {
298
+ b.Property<int>("Id")
299
+ .ValueGeneratedOnAdd()
300
+ .HasColumnType("int");
301
+
302
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
303
+
304
+ b.Property<int>("ContactId")
305
+ .HasColumnType("int");
306
+
307
+ b.Property<string>("ContentType")
308
+ .IsRequired()
309
+ .HasColumnType("nvarchar(max)");
310
+
311
+ b.Property<string>("FileName")
312
+ .IsRequired()
313
+ .HasColumnType("nvarchar(max)");
314
+
315
+ b.Property<long>("FileSize")
316
+ .HasColumnType("bigint");
317
+
318
+ b.Property<bool>("IsProfilePhoto")
319
+ .HasColumnType("bit");
320
+
321
+ b.Property<string>("PhotoPath")
322
+ .IsRequired()
323
+ .HasColumnType("nvarchar(max)");
324
+
325
+ b.Property<DateTime>("UploadedAt")
326
+ .HasColumnType("datetime2");
327
+
328
+ b.HasKey("Id");
329
+
330
+ b.HasIndex("ContactId");
331
+
332
+ b.ToTable("ContactPhotos");
333
+ });
334
+
335
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
336
+ {
337
+ b.Property<int>("Id")
338
+ .ValueGeneratedOnAdd()
339
+ .HasColumnType("int");
340
+
341
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
342
+
343
+ b.Property<bool>("IsGranted")
344
+ .HasColumnType("bit");
345
+
346
+ b.Property<string>("RightKey")
347
+ .IsRequired()
348
+ .HasMaxLength(100)
349
+ .HasColumnType("nvarchar(100)");
350
+
351
+ b.Property<int>("UserGroupId")
352
+ .HasColumnType("int");
353
+
354
+ b.HasKey("Id");
355
+
356
+ b.HasIndex("UserGroupId", "RightKey")
357
+ .IsUnique();
358
+
359
+ b.ToTable("GroupRights");
360
+ });
361
+
362
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
363
+ {
364
+ b.Property<int>("Id")
365
+ .ValueGeneratedOnAdd()
366
+ .HasColumnType("int");
367
+
368
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
369
+
370
+ b.Property<DateTime>("CreatedAt")
371
+ .HasColumnType("datetime2");
372
+
373
+ b.Property<string>("Description")
374
+ .HasMaxLength(500)
375
+ .HasColumnType("nvarchar(500)");
376
+
377
+ b.Property<string>("Name")
378
+ .IsRequired()
379
+ .HasMaxLength(150)
380
+ .HasColumnType("nvarchar(150)");
381
+
382
+ b.HasKey("Id");
383
+
384
+ b.HasIndex("Name")
385
+ .IsUnique();
386
+
387
+ b.ToTable("UserGroups");
388
+ });
389
+
390
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
391
+ {
392
+ b.Property<int>("Id")
393
+ .ValueGeneratedOnAdd()
394
+ .HasColumnType("int");
395
+
396
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
397
+
398
+ b.Property<int>("AppUserId")
399
+ .HasColumnType("int");
400
+
401
+ b.Property<bool>("IsGranted")
402
+ .HasColumnType("bit");
403
+
404
+ b.Property<string>("RightKey")
405
+ .IsRequired()
406
+ .HasMaxLength(100)
407
+ .HasColumnType("nvarchar(100)");
408
+
409
+ b.HasKey("Id");
410
+
411
+ b.HasIndex("AppUserId", "RightKey")
412
+ .IsUnique();
413
+
414
+ b.ToTable("UserRights");
415
+ });
416
+
417
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
418
+ {
419
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "Group")
420
+ .WithMany("Users")
421
+ .HasForeignKey("GroupId")
422
+ .OnDelete(DeleteBehavior.SetNull)
423
+ .IsRequired();
424
+
425
+ b.Navigation("Group");
426
+ });
427
+
428
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
429
+ {
430
+ b.HasOne("ContactManagementAPI.Models.ContactGroup", "Group")
431
+ .WithMany("Contacts")
432
+ .HasForeignKey("GroupId")
433
+ .OnDelete(DeleteBehavior.SetNull);
434
+
435
+ b.Navigation("Group");
436
+ });
437
+
438
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
439
+ {
440
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
441
+ .WithMany("Documents")
442
+ .HasForeignKey("ContactId")
443
+ .OnDelete(DeleteBehavior.Cascade)
444
+ .IsRequired();
445
+
446
+ b.Navigation("Contact");
447
+ });
448
+
449
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
450
+ {
451
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
452
+ .WithMany("Photos")
453
+ .HasForeignKey("ContactId")
454
+ .OnDelete(DeleteBehavior.Cascade)
455
+ .IsRequired();
456
+
457
+ b.Navigation("Contact");
458
+ });
459
+
460
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
461
+ {
462
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "UserGroup")
463
+ .WithMany("GroupRights")
464
+ .HasForeignKey("UserGroupId")
465
+ .OnDelete(DeleteBehavior.Cascade)
466
+ .IsRequired();
467
+
468
+ b.Navigation("UserGroup");
469
+ });
470
+
471
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
472
+ {
473
+ b.HasOne("ContactManagementAPI.Models.AppUser", "AppUser")
474
+ .WithMany("UserRights")
475
+ .HasForeignKey("AppUserId")
476
+ .OnDelete(DeleteBehavior.Cascade)
477
+ .IsRequired();
478
+
479
+ b.Navigation("AppUser");
480
+ });
481
+
482
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
483
+ {
484
+ b.Navigation("UserRights");
485
+ });
486
+
487
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
488
+ {
489
+ b.Navigation("Documents");
490
+
491
+ b.Navigation("Photos");
492
+ });
493
+
494
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
495
+ {
496
+ b.Navigation("Contacts");
497
+ });
498
+
499
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
500
+ {
501
+ b.Navigation("GroupRights");
502
+
503
+ b.Navigation("Users");
504
+ });
505
+ #pragma warning restore 612, 618
506
+ }
507
+ }
508
+ }
ContactManagementAPI/Migrations/20260220171502_AddContactIdentityBankGenderDobFields.cs ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using Microsoft.EntityFrameworkCore.Migrations;
3
+
4
+ #nullable disable
5
+
6
+ namespace ContactManagementAPI.Migrations
7
+ {
8
+ /// <inheritdoc />
9
+ public partial class AddContactIdentityBankGenderDobFields : Migration
10
+ {
11
+ /// <inheritdoc />
12
+ protected override void Up(MigrationBuilder migrationBuilder)
13
+ {
14
+ migrationBuilder.AddColumn<string>(
15
+ name: "AadharNumber",
16
+ table: "Contacts",
17
+ type: "nvarchar(max)",
18
+ nullable: true);
19
+
20
+ migrationBuilder.AddColumn<string>(
21
+ name: "BankAccountNumber",
22
+ table: "Contacts",
23
+ type: "nvarchar(max)",
24
+ nullable: true);
25
+
26
+ migrationBuilder.AddColumn<string>(
27
+ name: "BankName",
28
+ table: "Contacts",
29
+ type: "nvarchar(max)",
30
+ nullable: true);
31
+
32
+ migrationBuilder.AddColumn<string>(
33
+ name: "BranchName",
34
+ table: "Contacts",
35
+ type: "nvarchar(max)",
36
+ nullable: true);
37
+
38
+ migrationBuilder.AddColumn<DateTime>(
39
+ name: "DateOfBirth",
40
+ table: "Contacts",
41
+ type: "datetime2",
42
+ nullable: true);
43
+
44
+ migrationBuilder.AddColumn<string>(
45
+ name: "DrivingLicenseNumber",
46
+ table: "Contacts",
47
+ type: "nvarchar(max)",
48
+ nullable: true);
49
+
50
+ migrationBuilder.AddColumn<string>(
51
+ name: "Gender",
52
+ table: "Contacts",
53
+ type: "nvarchar(max)",
54
+ nullable: true);
55
+
56
+ migrationBuilder.AddColumn<string>(
57
+ name: "IfscCode",
58
+ table: "Contacts",
59
+ type: "nvarchar(max)",
60
+ nullable: true);
61
+
62
+ migrationBuilder.AddColumn<string>(
63
+ name: "PassportNumber",
64
+ table: "Contacts",
65
+ type: "nvarchar(max)",
66
+ nullable: true);
67
+
68
+ migrationBuilder.AddColumn<string>(
69
+ name: "VotersId",
70
+ table: "Contacts",
71
+ type: "nvarchar(max)",
72
+ nullable: true);
73
+
74
+ migrationBuilder.UpdateData(
75
+ table: "ContactGroups",
76
+ keyColumn: "Id",
77
+ keyValue: 1,
78
+ column: "CreatedAt",
79
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5689));
80
+
81
+ migrationBuilder.UpdateData(
82
+ table: "ContactGroups",
83
+ keyColumn: "Id",
84
+ keyValue: 2,
85
+ column: "CreatedAt",
86
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5716));
87
+
88
+ migrationBuilder.UpdateData(
89
+ table: "ContactGroups",
90
+ keyColumn: "Id",
91
+ keyValue: 3,
92
+ column: "CreatedAt",
93
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5720));
94
+
95
+ migrationBuilder.UpdateData(
96
+ table: "ContactGroups",
97
+ keyColumn: "Id",
98
+ keyValue: 4,
99
+ column: "CreatedAt",
100
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5722));
101
+
102
+ migrationBuilder.UpdateData(
103
+ table: "ContactGroups",
104
+ keyColumn: "Id",
105
+ keyValue: 5,
106
+ column: "CreatedAt",
107
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5723));
108
+
109
+ migrationBuilder.UpdateData(
110
+ table: "ContactGroups",
111
+ keyColumn: "Id",
112
+ keyValue: 6,
113
+ column: "CreatedAt",
114
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5725));
115
+
116
+ migrationBuilder.UpdateData(
117
+ table: "ContactGroups",
118
+ keyColumn: "Id",
119
+ keyValue: 7,
120
+ column: "CreatedAt",
121
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5727));
122
+
123
+ migrationBuilder.UpdateData(
124
+ table: "ContactGroups",
125
+ keyColumn: "Id",
126
+ keyValue: 8,
127
+ column: "CreatedAt",
128
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5729));
129
+ }
130
+
131
+ /// <inheritdoc />
132
+ protected override void Down(MigrationBuilder migrationBuilder)
133
+ {
134
+ migrationBuilder.DropColumn(
135
+ name: "AadharNumber",
136
+ table: "Contacts");
137
+
138
+ migrationBuilder.DropColumn(
139
+ name: "BankAccountNumber",
140
+ table: "Contacts");
141
+
142
+ migrationBuilder.DropColumn(
143
+ name: "BankName",
144
+ table: "Contacts");
145
+
146
+ migrationBuilder.DropColumn(
147
+ name: "BranchName",
148
+ table: "Contacts");
149
+
150
+ migrationBuilder.DropColumn(
151
+ name: "DateOfBirth",
152
+ table: "Contacts");
153
+
154
+ migrationBuilder.DropColumn(
155
+ name: "DrivingLicenseNumber",
156
+ table: "Contacts");
157
+
158
+ migrationBuilder.DropColumn(
159
+ name: "Gender",
160
+ table: "Contacts");
161
+
162
+ migrationBuilder.DropColumn(
163
+ name: "IfscCode",
164
+ table: "Contacts");
165
+
166
+ migrationBuilder.DropColumn(
167
+ name: "PassportNumber",
168
+ table: "Contacts");
169
+
170
+ migrationBuilder.DropColumn(
171
+ name: "VotersId",
172
+ table: "Contacts");
173
+
174
+ migrationBuilder.UpdateData(
175
+ table: "ContactGroups",
176
+ keyColumn: "Id",
177
+ keyValue: 1,
178
+ column: "CreatedAt",
179
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7842));
180
+
181
+ migrationBuilder.UpdateData(
182
+ table: "ContactGroups",
183
+ keyColumn: "Id",
184
+ keyValue: 2,
185
+ column: "CreatedAt",
186
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7855));
187
+
188
+ migrationBuilder.UpdateData(
189
+ table: "ContactGroups",
190
+ keyColumn: "Id",
191
+ keyValue: 3,
192
+ column: "CreatedAt",
193
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7857));
194
+
195
+ migrationBuilder.UpdateData(
196
+ table: "ContactGroups",
197
+ keyColumn: "Id",
198
+ keyValue: 4,
199
+ column: "CreatedAt",
200
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7858));
201
+
202
+ migrationBuilder.UpdateData(
203
+ table: "ContactGroups",
204
+ keyColumn: "Id",
205
+ keyValue: 5,
206
+ column: "CreatedAt",
207
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7860));
208
+
209
+ migrationBuilder.UpdateData(
210
+ table: "ContactGroups",
211
+ keyColumn: "Id",
212
+ keyValue: 6,
213
+ column: "CreatedAt",
214
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7862));
215
+
216
+ migrationBuilder.UpdateData(
217
+ table: "ContactGroups",
218
+ keyColumn: "Id",
219
+ keyValue: 7,
220
+ column: "CreatedAt",
221
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7863));
222
+
223
+ migrationBuilder.UpdateData(
224
+ table: "ContactGroups",
225
+ keyColumn: "Id",
226
+ keyValue: 8,
227
+ column: "CreatedAt",
228
+ value: new DateTime(2026, 2, 16, 12, 39, 30, 575, DateTimeKind.Local).AddTicks(7864));
229
+ }
230
+ }
231
+ }
ContactManagementAPI/Migrations/20260220171919_AddContactBankAccountsTable.Designer.cs ADDED
@@ -0,0 +1,557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // <auto-generated />
2
+ using System;
3
+ using ContactManagementAPI.Data;
4
+ using Microsoft.EntityFrameworkCore;
5
+ using Microsoft.EntityFrameworkCore.Infrastructure;
6
+ using Microsoft.EntityFrameworkCore.Metadata;
7
+ using Microsoft.EntityFrameworkCore.Migrations;
8
+ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9
+
10
+ #nullable disable
11
+
12
+ namespace ContactManagementAPI.Migrations
13
+ {
14
+ [DbContext(typeof(ApplicationDbContext))]
15
+ [Migration("20260220171919_AddContactBankAccountsTable")]
16
+ partial class AddContactBankAccountsTable
17
+ {
18
+ /// <inheritdoc />
19
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
20
+ {
21
+ #pragma warning disable 612, 618
22
+ modelBuilder
23
+ .HasAnnotation("ProductVersion", "8.0.0")
24
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
25
+
26
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
27
+
28
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
29
+ {
30
+ b.Property<int>("Id")
31
+ .ValueGeneratedOnAdd()
32
+ .HasColumnType("int");
33
+
34
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
35
+
36
+ b.Property<DateTime>("CreatedAt")
37
+ .HasColumnType("datetime2");
38
+
39
+ b.Property<string>("FullName")
40
+ .HasMaxLength(200)
41
+ .HasColumnType("nvarchar(200)");
42
+
43
+ b.Property<int>("GroupId")
44
+ .HasColumnType("int");
45
+
46
+ b.Property<bool>("IsActive")
47
+ .HasColumnType("bit");
48
+
49
+ b.Property<bool>("IsAdmin")
50
+ .HasColumnType("bit");
51
+
52
+ b.Property<string>("PasswordHash")
53
+ .IsRequired()
54
+ .HasColumnType("nvarchar(max)");
55
+
56
+ b.Property<DateTime>("UpdatedAt")
57
+ .HasColumnType("datetime2");
58
+
59
+ b.Property<string>("UserName")
60
+ .IsRequired()
61
+ .HasMaxLength(100)
62
+ .HasColumnType("nvarchar(100)");
63
+
64
+ b.HasKey("Id");
65
+
66
+ b.HasIndex("GroupId");
67
+
68
+ b.HasIndex("UserName")
69
+ .IsUnique();
70
+
71
+ b.ToTable("AppUsers");
72
+ });
73
+
74
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
75
+ {
76
+ b.Property<int>("Id")
77
+ .ValueGeneratedOnAdd()
78
+ .HasColumnType("int");
79
+
80
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
81
+
82
+ b.Property<string>("AadharNumber")
83
+ .HasColumnType("nvarchar(max)");
84
+
85
+ b.Property<string>("Address")
86
+ .HasColumnType("nvarchar(max)");
87
+
88
+ b.Property<string>("BankAccountNumber")
89
+ .HasColumnType("nvarchar(max)");
90
+
91
+ b.Property<string>("BankName")
92
+ .HasColumnType("nvarchar(max)");
93
+
94
+ b.Property<string>("BranchName")
95
+ .HasColumnType("nvarchar(max)");
96
+
97
+ b.Property<string>("City")
98
+ .HasColumnType("nvarchar(max)");
99
+
100
+ b.Property<string>("Country")
101
+ .HasColumnType("nvarchar(max)");
102
+
103
+ b.Property<DateTime>("CreatedAt")
104
+ .HasColumnType("datetime2");
105
+
106
+ b.Property<DateTime?>("DateOfBirth")
107
+ .HasColumnType("datetime2");
108
+
109
+ b.Property<string>("DrivingLicenseNumber")
110
+ .HasColumnType("nvarchar(max)");
111
+
112
+ b.Property<string>("Email")
113
+ .HasColumnType("nvarchar(max)");
114
+
115
+ b.Property<string>("FirstName")
116
+ .IsRequired()
117
+ .HasColumnType("nvarchar(max)");
118
+
119
+ b.Property<string>("Gender")
120
+ .HasColumnType("nvarchar(max)");
121
+
122
+ b.Property<int?>("GroupId")
123
+ .HasColumnType("int");
124
+
125
+ b.Property<string>("IfscCode")
126
+ .HasColumnType("nvarchar(max)");
127
+
128
+ b.Property<string>("LastName")
129
+ .HasColumnType("nvarchar(max)");
130
+
131
+ b.Property<string>("Mobile1")
132
+ .HasColumnType("nvarchar(max)");
133
+
134
+ b.Property<string>("Mobile2")
135
+ .HasColumnType("nvarchar(max)");
136
+
137
+ b.Property<string>("Mobile3")
138
+ .HasColumnType("nvarchar(max)");
139
+
140
+ b.Property<string>("NickName")
141
+ .HasColumnType("nvarchar(max)");
142
+
143
+ b.Property<string>("OtherDetails")
144
+ .HasColumnType("nvarchar(max)");
145
+
146
+ b.Property<string>("PassportNumber")
147
+ .HasColumnType("nvarchar(max)");
148
+
149
+ b.Property<string>("PhotoPath")
150
+ .HasColumnType("nvarchar(max)");
151
+
152
+ b.Property<string>("PostalCode")
153
+ .HasColumnType("nvarchar(max)");
154
+
155
+ b.Property<string>("State")
156
+ .HasColumnType("nvarchar(max)");
157
+
158
+ b.Property<DateTime>("UpdatedAt")
159
+ .HasColumnType("datetime2");
160
+
161
+ b.Property<string>("VotersId")
162
+ .HasColumnType("nvarchar(max)");
163
+
164
+ b.Property<string>("WhatsAppNumber")
165
+ .HasColumnType("nvarchar(max)");
166
+
167
+ b.HasKey("Id");
168
+
169
+ b.HasIndex("GroupId");
170
+
171
+ b.ToTable("Contacts");
172
+ });
173
+
174
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactBankAccount", b =>
175
+ {
176
+ b.Property<int>("Id")
177
+ .ValueGeneratedOnAdd()
178
+ .HasColumnType("int");
179
+
180
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
181
+
182
+ b.Property<string>("AccountNumber")
183
+ .HasColumnType("nvarchar(max)");
184
+
185
+ b.Property<string>("BankName")
186
+ .HasColumnType("nvarchar(max)");
187
+
188
+ b.Property<string>("BranchName")
189
+ .HasColumnType("nvarchar(max)");
190
+
191
+ b.Property<int>("ContactId")
192
+ .HasColumnType("int");
193
+
194
+ b.Property<DateTime>("CreatedAt")
195
+ .HasColumnType("datetime2");
196
+
197
+ b.Property<string>("IfscCode")
198
+ .HasColumnType("nvarchar(max)");
199
+
200
+ b.Property<DateTime>("UpdatedAt")
201
+ .HasColumnType("datetime2");
202
+
203
+ b.HasKey("Id");
204
+
205
+ b.HasIndex("ContactId");
206
+
207
+ b.ToTable("ContactBankAccounts");
208
+ });
209
+
210
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
211
+ {
212
+ b.Property<int>("Id")
213
+ .ValueGeneratedOnAdd()
214
+ .HasColumnType("int");
215
+
216
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
217
+
218
+ b.Property<int>("ContactId")
219
+ .HasColumnType("int");
220
+
221
+ b.Property<string>("ContentType")
222
+ .IsRequired()
223
+ .HasColumnType("nvarchar(max)");
224
+
225
+ b.Property<string>("DocumentPath")
226
+ .IsRequired()
227
+ .HasColumnType("nvarchar(max)");
228
+
229
+ b.Property<string>("DocumentType")
230
+ .IsRequired()
231
+ .HasColumnType("nvarchar(max)");
232
+
233
+ b.Property<string>("FileName")
234
+ .IsRequired()
235
+ .HasColumnType("nvarchar(max)");
236
+
237
+ b.Property<long>("FileSize")
238
+ .HasColumnType("bigint");
239
+
240
+ b.Property<DateTime>("UploadedAt")
241
+ .HasColumnType("datetime2");
242
+
243
+ b.HasKey("Id");
244
+
245
+ b.HasIndex("ContactId");
246
+
247
+ b.ToTable("ContactDocuments");
248
+ });
249
+
250
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
251
+ {
252
+ b.Property<int>("Id")
253
+ .ValueGeneratedOnAdd()
254
+ .HasColumnType("int");
255
+
256
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
257
+
258
+ b.Property<DateTime>("CreatedAt")
259
+ .HasColumnType("datetime2");
260
+
261
+ b.Property<string>("Description")
262
+ .IsRequired()
263
+ .HasColumnType("nvarchar(max)");
264
+
265
+ b.Property<string>("Name")
266
+ .IsRequired()
267
+ .HasColumnType("nvarchar(max)");
268
+
269
+ b.HasKey("Id");
270
+
271
+ b.ToTable("ContactGroups");
272
+
273
+ b.HasData(
274
+ new
275
+ {
276
+ Id = 1,
277
+ CreatedAt = new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4198),
278
+ Description = "Family members",
279
+ Name = "Family"
280
+ },
281
+ new
282
+ {
283
+ Id = 2,
284
+ CreatedAt = new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4211),
285
+ Description = "Friends",
286
+ Name = "Friends"
287
+ },
288
+ new
289
+ {
290
+ Id = 3,
291
+ CreatedAt = new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4213),
292
+ Description = "Business contacts",
293
+ Name = "Business"
294
+ },
295
+ new
296
+ {
297
+ Id = 4,
298
+ CreatedAt = new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4214),
299
+ Description = "School contacts",
300
+ Name = "School"
301
+ },
302
+ new
303
+ {
304
+ Id = 5,
305
+ CreatedAt = new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4216),
306
+ Description = "Church members",
307
+ Name = "Church"
308
+ },
309
+ new
310
+ {
311
+ Id = 6,
312
+ CreatedAt = new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4217),
313
+ Description = "Other contacts",
314
+ Name = "Others"
315
+ },
316
+ new
317
+ {
318
+ Id = 7,
319
+ CreatedAt = new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4218),
320
+ Description = "College contacts",
321
+ Name = "College"
322
+ },
323
+ new
324
+ {
325
+ Id = 8,
326
+ CreatedAt = new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4220),
327
+ Description = "Alcoholics Anonymous",
328
+ Name = "AA"
329
+ });
330
+ });
331
+
332
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
333
+ {
334
+ b.Property<int>("Id")
335
+ .ValueGeneratedOnAdd()
336
+ .HasColumnType("int");
337
+
338
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
339
+
340
+ b.Property<int>("ContactId")
341
+ .HasColumnType("int");
342
+
343
+ b.Property<string>("ContentType")
344
+ .IsRequired()
345
+ .HasColumnType("nvarchar(max)");
346
+
347
+ b.Property<string>("FileName")
348
+ .IsRequired()
349
+ .HasColumnType("nvarchar(max)");
350
+
351
+ b.Property<long>("FileSize")
352
+ .HasColumnType("bigint");
353
+
354
+ b.Property<bool>("IsProfilePhoto")
355
+ .HasColumnType("bit");
356
+
357
+ b.Property<string>("PhotoPath")
358
+ .IsRequired()
359
+ .HasColumnType("nvarchar(max)");
360
+
361
+ b.Property<DateTime>("UploadedAt")
362
+ .HasColumnType("datetime2");
363
+
364
+ b.HasKey("Id");
365
+
366
+ b.HasIndex("ContactId");
367
+
368
+ b.ToTable("ContactPhotos");
369
+ });
370
+
371
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
372
+ {
373
+ b.Property<int>("Id")
374
+ .ValueGeneratedOnAdd()
375
+ .HasColumnType("int");
376
+
377
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
378
+
379
+ b.Property<bool>("IsGranted")
380
+ .HasColumnType("bit");
381
+
382
+ b.Property<string>("RightKey")
383
+ .IsRequired()
384
+ .HasMaxLength(100)
385
+ .HasColumnType("nvarchar(100)");
386
+
387
+ b.Property<int>("UserGroupId")
388
+ .HasColumnType("int");
389
+
390
+ b.HasKey("Id");
391
+
392
+ b.HasIndex("UserGroupId", "RightKey")
393
+ .IsUnique();
394
+
395
+ b.ToTable("GroupRights");
396
+ });
397
+
398
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
399
+ {
400
+ b.Property<int>("Id")
401
+ .ValueGeneratedOnAdd()
402
+ .HasColumnType("int");
403
+
404
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
405
+
406
+ b.Property<DateTime>("CreatedAt")
407
+ .HasColumnType("datetime2");
408
+
409
+ b.Property<string>("Description")
410
+ .HasMaxLength(500)
411
+ .HasColumnType("nvarchar(500)");
412
+
413
+ b.Property<string>("Name")
414
+ .IsRequired()
415
+ .HasMaxLength(150)
416
+ .HasColumnType("nvarchar(150)");
417
+
418
+ b.HasKey("Id");
419
+
420
+ b.HasIndex("Name")
421
+ .IsUnique();
422
+
423
+ b.ToTable("UserGroups");
424
+ });
425
+
426
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
427
+ {
428
+ b.Property<int>("Id")
429
+ .ValueGeneratedOnAdd()
430
+ .HasColumnType("int");
431
+
432
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
433
+
434
+ b.Property<int>("AppUserId")
435
+ .HasColumnType("int");
436
+
437
+ b.Property<bool>("IsGranted")
438
+ .HasColumnType("bit");
439
+
440
+ b.Property<string>("RightKey")
441
+ .IsRequired()
442
+ .HasMaxLength(100)
443
+ .HasColumnType("nvarchar(100)");
444
+
445
+ b.HasKey("Id");
446
+
447
+ b.HasIndex("AppUserId", "RightKey")
448
+ .IsUnique();
449
+
450
+ b.ToTable("UserRights");
451
+ });
452
+
453
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
454
+ {
455
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "Group")
456
+ .WithMany("Users")
457
+ .HasForeignKey("GroupId")
458
+ .OnDelete(DeleteBehavior.SetNull)
459
+ .IsRequired();
460
+
461
+ b.Navigation("Group");
462
+ });
463
+
464
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
465
+ {
466
+ b.HasOne("ContactManagementAPI.Models.ContactGroup", "Group")
467
+ .WithMany("Contacts")
468
+ .HasForeignKey("GroupId")
469
+ .OnDelete(DeleteBehavior.SetNull);
470
+
471
+ b.Navigation("Group");
472
+ });
473
+
474
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactBankAccount", b =>
475
+ {
476
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
477
+ .WithMany("BankAccounts")
478
+ .HasForeignKey("ContactId")
479
+ .OnDelete(DeleteBehavior.Cascade)
480
+ .IsRequired();
481
+
482
+ b.Navigation("Contact");
483
+ });
484
+
485
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
486
+ {
487
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
488
+ .WithMany("Documents")
489
+ .HasForeignKey("ContactId")
490
+ .OnDelete(DeleteBehavior.Cascade)
491
+ .IsRequired();
492
+
493
+ b.Navigation("Contact");
494
+ });
495
+
496
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
497
+ {
498
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
499
+ .WithMany("Photos")
500
+ .HasForeignKey("ContactId")
501
+ .OnDelete(DeleteBehavior.Cascade)
502
+ .IsRequired();
503
+
504
+ b.Navigation("Contact");
505
+ });
506
+
507
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
508
+ {
509
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "UserGroup")
510
+ .WithMany("GroupRights")
511
+ .HasForeignKey("UserGroupId")
512
+ .OnDelete(DeleteBehavior.Cascade)
513
+ .IsRequired();
514
+
515
+ b.Navigation("UserGroup");
516
+ });
517
+
518
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
519
+ {
520
+ b.HasOne("ContactManagementAPI.Models.AppUser", "AppUser")
521
+ .WithMany("UserRights")
522
+ .HasForeignKey("AppUserId")
523
+ .OnDelete(DeleteBehavior.Cascade)
524
+ .IsRequired();
525
+
526
+ b.Navigation("AppUser");
527
+ });
528
+
529
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
530
+ {
531
+ b.Navigation("UserRights");
532
+ });
533
+
534
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
535
+ {
536
+ b.Navigation("BankAccounts");
537
+
538
+ b.Navigation("Documents");
539
+
540
+ b.Navigation("Photos");
541
+ });
542
+
543
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
544
+ {
545
+ b.Navigation("Contacts");
546
+ });
547
+
548
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
549
+ {
550
+ b.Navigation("GroupRights");
551
+
552
+ b.Navigation("Users");
553
+ });
554
+ #pragma warning restore 612, 618
555
+ }
556
+ }
557
+ }
ContactManagementAPI/Migrations/20260220171919_AddContactBankAccountsTable.cs ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using Microsoft.EntityFrameworkCore.Migrations;
3
+
4
+ #nullable disable
5
+
6
+ namespace ContactManagementAPI.Migrations
7
+ {
8
+ /// <inheritdoc />
9
+ public partial class AddContactBankAccountsTable : Migration
10
+ {
11
+ /// <inheritdoc />
12
+ protected override void Up(MigrationBuilder migrationBuilder)
13
+ {
14
+ migrationBuilder.CreateTable(
15
+ name: "ContactBankAccounts",
16
+ columns: table => new
17
+ {
18
+ Id = table.Column<int>(type: "int", nullable: false)
19
+ .Annotation("SqlServer:Identity", "1, 1"),
20
+ ContactId = table.Column<int>(type: "int", nullable: false),
21
+ AccountNumber = table.Column<string>(type: "nvarchar(max)", nullable: true),
22
+ BankName = table.Column<string>(type: "nvarchar(max)", nullable: true),
23
+ BranchName = table.Column<string>(type: "nvarchar(max)", nullable: true),
24
+ IfscCode = table.Column<string>(type: "nvarchar(max)", nullable: true),
25
+ CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false),
26
+ UpdatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
27
+ },
28
+ constraints: table =>
29
+ {
30
+ table.PrimaryKey("PK_ContactBankAccounts", x => x.Id);
31
+ table.ForeignKey(
32
+ name: "FK_ContactBankAccounts_Contacts_ContactId",
33
+ column: x => x.ContactId,
34
+ principalTable: "Contacts",
35
+ principalColumn: "Id",
36
+ onDelete: ReferentialAction.Cascade);
37
+ });
38
+
39
+ migrationBuilder.UpdateData(
40
+ table: "ContactGroups",
41
+ keyColumn: "Id",
42
+ keyValue: 1,
43
+ column: "CreatedAt",
44
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4198));
45
+
46
+ migrationBuilder.UpdateData(
47
+ table: "ContactGroups",
48
+ keyColumn: "Id",
49
+ keyValue: 2,
50
+ column: "CreatedAt",
51
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4211));
52
+
53
+ migrationBuilder.UpdateData(
54
+ table: "ContactGroups",
55
+ keyColumn: "Id",
56
+ keyValue: 3,
57
+ column: "CreatedAt",
58
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4213));
59
+
60
+ migrationBuilder.UpdateData(
61
+ table: "ContactGroups",
62
+ keyColumn: "Id",
63
+ keyValue: 4,
64
+ column: "CreatedAt",
65
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4214));
66
+
67
+ migrationBuilder.UpdateData(
68
+ table: "ContactGroups",
69
+ keyColumn: "Id",
70
+ keyValue: 5,
71
+ column: "CreatedAt",
72
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4216));
73
+
74
+ migrationBuilder.UpdateData(
75
+ table: "ContactGroups",
76
+ keyColumn: "Id",
77
+ keyValue: 6,
78
+ column: "CreatedAt",
79
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4217));
80
+
81
+ migrationBuilder.UpdateData(
82
+ table: "ContactGroups",
83
+ keyColumn: "Id",
84
+ keyValue: 7,
85
+ column: "CreatedAt",
86
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4218));
87
+
88
+ migrationBuilder.UpdateData(
89
+ table: "ContactGroups",
90
+ keyColumn: "Id",
91
+ keyValue: 8,
92
+ column: "CreatedAt",
93
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4220));
94
+
95
+ migrationBuilder.CreateIndex(
96
+ name: "IX_ContactBankAccounts_ContactId",
97
+ table: "ContactBankAccounts",
98
+ column: "ContactId");
99
+ }
100
+
101
+ /// <inheritdoc />
102
+ protected override void Down(MigrationBuilder migrationBuilder)
103
+ {
104
+ migrationBuilder.DropTable(
105
+ name: "ContactBankAccounts");
106
+
107
+ migrationBuilder.UpdateData(
108
+ table: "ContactGroups",
109
+ keyColumn: "Id",
110
+ keyValue: 1,
111
+ column: "CreatedAt",
112
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5689));
113
+
114
+ migrationBuilder.UpdateData(
115
+ table: "ContactGroups",
116
+ keyColumn: "Id",
117
+ keyValue: 2,
118
+ column: "CreatedAt",
119
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5716));
120
+
121
+ migrationBuilder.UpdateData(
122
+ table: "ContactGroups",
123
+ keyColumn: "Id",
124
+ keyValue: 3,
125
+ column: "CreatedAt",
126
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5720));
127
+
128
+ migrationBuilder.UpdateData(
129
+ table: "ContactGroups",
130
+ keyColumn: "Id",
131
+ keyValue: 4,
132
+ column: "CreatedAt",
133
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5722));
134
+
135
+ migrationBuilder.UpdateData(
136
+ table: "ContactGroups",
137
+ keyColumn: "Id",
138
+ keyValue: 5,
139
+ column: "CreatedAt",
140
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5723));
141
+
142
+ migrationBuilder.UpdateData(
143
+ table: "ContactGroups",
144
+ keyColumn: "Id",
145
+ keyValue: 6,
146
+ column: "CreatedAt",
147
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5725));
148
+
149
+ migrationBuilder.UpdateData(
150
+ table: "ContactGroups",
151
+ keyColumn: "Id",
152
+ keyValue: 7,
153
+ column: "CreatedAt",
154
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5727));
155
+
156
+ migrationBuilder.UpdateData(
157
+ table: "ContactGroups",
158
+ keyColumn: "Id",
159
+ keyValue: 8,
160
+ column: "CreatedAt",
161
+ value: new DateTime(2026, 2, 20, 22, 44, 58, 435, DateTimeKind.Local).AddTicks(5729));
162
+ }
163
+ }
164
+ }
ContactManagementAPI/Migrations/20260221045734_AddPanNumberToContact.Designer.cs ADDED
@@ -0,0 +1,560 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // <auto-generated />
2
+ using System;
3
+ using ContactManagementAPI.Data;
4
+ using Microsoft.EntityFrameworkCore;
5
+ using Microsoft.EntityFrameworkCore.Infrastructure;
6
+ using Microsoft.EntityFrameworkCore.Metadata;
7
+ using Microsoft.EntityFrameworkCore.Migrations;
8
+ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
9
+
10
+ #nullable disable
11
+
12
+ namespace ContactManagementAPI.Migrations
13
+ {
14
+ [DbContext(typeof(ApplicationDbContext))]
15
+ [Migration("20260221045734_AddPanNumberToContact")]
16
+ partial class AddPanNumberToContact
17
+ {
18
+ /// <inheritdoc />
19
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
20
+ {
21
+ #pragma warning disable 612, 618
22
+ modelBuilder
23
+ .HasAnnotation("ProductVersion", "8.0.0")
24
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
25
+
26
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
27
+
28
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
29
+ {
30
+ b.Property<int>("Id")
31
+ .ValueGeneratedOnAdd()
32
+ .HasColumnType("int");
33
+
34
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
35
+
36
+ b.Property<DateTime>("CreatedAt")
37
+ .HasColumnType("datetime2");
38
+
39
+ b.Property<string>("FullName")
40
+ .HasMaxLength(200)
41
+ .HasColumnType("nvarchar(200)");
42
+
43
+ b.Property<int>("GroupId")
44
+ .HasColumnType("int");
45
+
46
+ b.Property<bool>("IsActive")
47
+ .HasColumnType("bit");
48
+
49
+ b.Property<bool>("IsAdmin")
50
+ .HasColumnType("bit");
51
+
52
+ b.Property<string>("PasswordHash")
53
+ .IsRequired()
54
+ .HasColumnType("nvarchar(max)");
55
+
56
+ b.Property<DateTime>("UpdatedAt")
57
+ .HasColumnType("datetime2");
58
+
59
+ b.Property<string>("UserName")
60
+ .IsRequired()
61
+ .HasMaxLength(100)
62
+ .HasColumnType("nvarchar(100)");
63
+
64
+ b.HasKey("Id");
65
+
66
+ b.HasIndex("GroupId");
67
+
68
+ b.HasIndex("UserName")
69
+ .IsUnique();
70
+
71
+ b.ToTable("AppUsers");
72
+ });
73
+
74
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
75
+ {
76
+ b.Property<int>("Id")
77
+ .ValueGeneratedOnAdd()
78
+ .HasColumnType("int");
79
+
80
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
81
+
82
+ b.Property<string>("AadharNumber")
83
+ .HasColumnType("nvarchar(max)");
84
+
85
+ b.Property<string>("Address")
86
+ .HasColumnType("nvarchar(max)");
87
+
88
+ b.Property<string>("BankAccountNumber")
89
+ .HasColumnType("nvarchar(max)");
90
+
91
+ b.Property<string>("BankName")
92
+ .HasColumnType("nvarchar(max)");
93
+
94
+ b.Property<string>("BranchName")
95
+ .HasColumnType("nvarchar(max)");
96
+
97
+ b.Property<string>("City")
98
+ .HasColumnType("nvarchar(max)");
99
+
100
+ b.Property<string>("Country")
101
+ .HasColumnType("nvarchar(max)");
102
+
103
+ b.Property<DateTime>("CreatedAt")
104
+ .HasColumnType("datetime2");
105
+
106
+ b.Property<DateTime?>("DateOfBirth")
107
+ .HasColumnType("datetime2");
108
+
109
+ b.Property<string>("DrivingLicenseNumber")
110
+ .HasColumnType("nvarchar(max)");
111
+
112
+ b.Property<string>("Email")
113
+ .HasColumnType("nvarchar(max)");
114
+
115
+ b.Property<string>("FirstName")
116
+ .IsRequired()
117
+ .HasColumnType("nvarchar(max)");
118
+
119
+ b.Property<string>("Gender")
120
+ .HasColumnType("nvarchar(max)");
121
+
122
+ b.Property<int?>("GroupId")
123
+ .HasColumnType("int");
124
+
125
+ b.Property<string>("IfscCode")
126
+ .HasColumnType("nvarchar(max)");
127
+
128
+ b.Property<string>("LastName")
129
+ .HasColumnType("nvarchar(max)");
130
+
131
+ b.Property<string>("Mobile1")
132
+ .HasColumnType("nvarchar(max)");
133
+
134
+ b.Property<string>("Mobile2")
135
+ .HasColumnType("nvarchar(max)");
136
+
137
+ b.Property<string>("Mobile3")
138
+ .HasColumnType("nvarchar(max)");
139
+
140
+ b.Property<string>("NickName")
141
+ .HasColumnType("nvarchar(max)");
142
+
143
+ b.Property<string>("OtherDetails")
144
+ .HasColumnType("nvarchar(max)");
145
+
146
+ b.Property<string>("PanNumber")
147
+ .HasColumnType("nvarchar(max)");
148
+
149
+ b.Property<string>("PassportNumber")
150
+ .HasColumnType("nvarchar(max)");
151
+
152
+ b.Property<string>("PhotoPath")
153
+ .HasColumnType("nvarchar(max)");
154
+
155
+ b.Property<string>("PostalCode")
156
+ .HasColumnType("nvarchar(max)");
157
+
158
+ b.Property<string>("State")
159
+ .HasColumnType("nvarchar(max)");
160
+
161
+ b.Property<DateTime>("UpdatedAt")
162
+ .HasColumnType("datetime2");
163
+
164
+ b.Property<string>("VotersId")
165
+ .HasColumnType("nvarchar(max)");
166
+
167
+ b.Property<string>("WhatsAppNumber")
168
+ .HasColumnType("nvarchar(max)");
169
+
170
+ b.HasKey("Id");
171
+
172
+ b.HasIndex("GroupId");
173
+
174
+ b.ToTable("Contacts");
175
+ });
176
+
177
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactBankAccount", b =>
178
+ {
179
+ b.Property<int>("Id")
180
+ .ValueGeneratedOnAdd()
181
+ .HasColumnType("int");
182
+
183
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
184
+
185
+ b.Property<string>("AccountNumber")
186
+ .HasColumnType("nvarchar(max)");
187
+
188
+ b.Property<string>("BankName")
189
+ .HasColumnType("nvarchar(max)");
190
+
191
+ b.Property<string>("BranchName")
192
+ .HasColumnType("nvarchar(max)");
193
+
194
+ b.Property<int>("ContactId")
195
+ .HasColumnType("int");
196
+
197
+ b.Property<DateTime>("CreatedAt")
198
+ .HasColumnType("datetime2");
199
+
200
+ b.Property<string>("IfscCode")
201
+ .HasColumnType("nvarchar(max)");
202
+
203
+ b.Property<DateTime>("UpdatedAt")
204
+ .HasColumnType("datetime2");
205
+
206
+ b.HasKey("Id");
207
+
208
+ b.HasIndex("ContactId");
209
+
210
+ b.ToTable("ContactBankAccounts");
211
+ });
212
+
213
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
214
+ {
215
+ b.Property<int>("Id")
216
+ .ValueGeneratedOnAdd()
217
+ .HasColumnType("int");
218
+
219
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
220
+
221
+ b.Property<int>("ContactId")
222
+ .HasColumnType("int");
223
+
224
+ b.Property<string>("ContentType")
225
+ .IsRequired()
226
+ .HasColumnType("nvarchar(max)");
227
+
228
+ b.Property<string>("DocumentPath")
229
+ .IsRequired()
230
+ .HasColumnType("nvarchar(max)");
231
+
232
+ b.Property<string>("DocumentType")
233
+ .IsRequired()
234
+ .HasColumnType("nvarchar(max)");
235
+
236
+ b.Property<string>("FileName")
237
+ .IsRequired()
238
+ .HasColumnType("nvarchar(max)");
239
+
240
+ b.Property<long>("FileSize")
241
+ .HasColumnType("bigint");
242
+
243
+ b.Property<DateTime>("UploadedAt")
244
+ .HasColumnType("datetime2");
245
+
246
+ b.HasKey("Id");
247
+
248
+ b.HasIndex("ContactId");
249
+
250
+ b.ToTable("ContactDocuments");
251
+ });
252
+
253
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
254
+ {
255
+ b.Property<int>("Id")
256
+ .ValueGeneratedOnAdd()
257
+ .HasColumnType("int");
258
+
259
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
260
+
261
+ b.Property<DateTime>("CreatedAt")
262
+ .HasColumnType("datetime2");
263
+
264
+ b.Property<string>("Description")
265
+ .IsRequired()
266
+ .HasColumnType("nvarchar(max)");
267
+
268
+ b.Property<string>("Name")
269
+ .IsRequired()
270
+ .HasColumnType("nvarchar(max)");
271
+
272
+ b.HasKey("Id");
273
+
274
+ b.ToTable("ContactGroups");
275
+
276
+ b.HasData(
277
+ new
278
+ {
279
+ Id = 1,
280
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7176),
281
+ Description = "Family members",
282
+ Name = "Family"
283
+ },
284
+ new
285
+ {
286
+ Id = 2,
287
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7190),
288
+ Description = "Friends",
289
+ Name = "Friends"
290
+ },
291
+ new
292
+ {
293
+ Id = 3,
294
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7192),
295
+ Description = "Business contacts",
296
+ Name = "Business"
297
+ },
298
+ new
299
+ {
300
+ Id = 4,
301
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7196),
302
+ Description = "School contacts",
303
+ Name = "School"
304
+ },
305
+ new
306
+ {
307
+ Id = 5,
308
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7198),
309
+ Description = "Church members",
310
+ Name = "Church"
311
+ },
312
+ new
313
+ {
314
+ Id = 6,
315
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7199),
316
+ Description = "Other contacts",
317
+ Name = "Others"
318
+ },
319
+ new
320
+ {
321
+ Id = 7,
322
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7200),
323
+ Description = "College contacts",
324
+ Name = "College"
325
+ },
326
+ new
327
+ {
328
+ Id = 8,
329
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7202),
330
+ Description = "Alcoholics Anonymous",
331
+ Name = "AA"
332
+ });
333
+ });
334
+
335
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
336
+ {
337
+ b.Property<int>("Id")
338
+ .ValueGeneratedOnAdd()
339
+ .HasColumnType("int");
340
+
341
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
342
+
343
+ b.Property<int>("ContactId")
344
+ .HasColumnType("int");
345
+
346
+ b.Property<string>("ContentType")
347
+ .IsRequired()
348
+ .HasColumnType("nvarchar(max)");
349
+
350
+ b.Property<string>("FileName")
351
+ .IsRequired()
352
+ .HasColumnType("nvarchar(max)");
353
+
354
+ b.Property<long>("FileSize")
355
+ .HasColumnType("bigint");
356
+
357
+ b.Property<bool>("IsProfilePhoto")
358
+ .HasColumnType("bit");
359
+
360
+ b.Property<string>("PhotoPath")
361
+ .IsRequired()
362
+ .HasColumnType("nvarchar(max)");
363
+
364
+ b.Property<DateTime>("UploadedAt")
365
+ .HasColumnType("datetime2");
366
+
367
+ b.HasKey("Id");
368
+
369
+ b.HasIndex("ContactId");
370
+
371
+ b.ToTable("ContactPhotos");
372
+ });
373
+
374
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
375
+ {
376
+ b.Property<int>("Id")
377
+ .ValueGeneratedOnAdd()
378
+ .HasColumnType("int");
379
+
380
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
381
+
382
+ b.Property<bool>("IsGranted")
383
+ .HasColumnType("bit");
384
+
385
+ b.Property<string>("RightKey")
386
+ .IsRequired()
387
+ .HasMaxLength(100)
388
+ .HasColumnType("nvarchar(100)");
389
+
390
+ b.Property<int>("UserGroupId")
391
+ .HasColumnType("int");
392
+
393
+ b.HasKey("Id");
394
+
395
+ b.HasIndex("UserGroupId", "RightKey")
396
+ .IsUnique();
397
+
398
+ b.ToTable("GroupRights");
399
+ });
400
+
401
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
402
+ {
403
+ b.Property<int>("Id")
404
+ .ValueGeneratedOnAdd()
405
+ .HasColumnType("int");
406
+
407
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
408
+
409
+ b.Property<DateTime>("CreatedAt")
410
+ .HasColumnType("datetime2");
411
+
412
+ b.Property<string>("Description")
413
+ .HasMaxLength(500)
414
+ .HasColumnType("nvarchar(500)");
415
+
416
+ b.Property<string>("Name")
417
+ .IsRequired()
418
+ .HasMaxLength(150)
419
+ .HasColumnType("nvarchar(150)");
420
+
421
+ b.HasKey("Id");
422
+
423
+ b.HasIndex("Name")
424
+ .IsUnique();
425
+
426
+ b.ToTable("UserGroups");
427
+ });
428
+
429
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
430
+ {
431
+ b.Property<int>("Id")
432
+ .ValueGeneratedOnAdd()
433
+ .HasColumnType("int");
434
+
435
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
436
+
437
+ b.Property<int>("AppUserId")
438
+ .HasColumnType("int");
439
+
440
+ b.Property<bool>("IsGranted")
441
+ .HasColumnType("bit");
442
+
443
+ b.Property<string>("RightKey")
444
+ .IsRequired()
445
+ .HasMaxLength(100)
446
+ .HasColumnType("nvarchar(100)");
447
+
448
+ b.HasKey("Id");
449
+
450
+ b.HasIndex("AppUserId", "RightKey")
451
+ .IsUnique();
452
+
453
+ b.ToTable("UserRights");
454
+ });
455
+
456
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
457
+ {
458
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "Group")
459
+ .WithMany("Users")
460
+ .HasForeignKey("GroupId")
461
+ .OnDelete(DeleteBehavior.SetNull)
462
+ .IsRequired();
463
+
464
+ b.Navigation("Group");
465
+ });
466
+
467
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
468
+ {
469
+ b.HasOne("ContactManagementAPI.Models.ContactGroup", "Group")
470
+ .WithMany("Contacts")
471
+ .HasForeignKey("GroupId")
472
+ .OnDelete(DeleteBehavior.SetNull);
473
+
474
+ b.Navigation("Group");
475
+ });
476
+
477
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactBankAccount", b =>
478
+ {
479
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
480
+ .WithMany("BankAccounts")
481
+ .HasForeignKey("ContactId")
482
+ .OnDelete(DeleteBehavior.Cascade)
483
+ .IsRequired();
484
+
485
+ b.Navigation("Contact");
486
+ });
487
+
488
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
489
+ {
490
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
491
+ .WithMany("Documents")
492
+ .HasForeignKey("ContactId")
493
+ .OnDelete(DeleteBehavior.Cascade)
494
+ .IsRequired();
495
+
496
+ b.Navigation("Contact");
497
+ });
498
+
499
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
500
+ {
501
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
502
+ .WithMany("Photos")
503
+ .HasForeignKey("ContactId")
504
+ .OnDelete(DeleteBehavior.Cascade)
505
+ .IsRequired();
506
+
507
+ b.Navigation("Contact");
508
+ });
509
+
510
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
511
+ {
512
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "UserGroup")
513
+ .WithMany("GroupRights")
514
+ .HasForeignKey("UserGroupId")
515
+ .OnDelete(DeleteBehavior.Cascade)
516
+ .IsRequired();
517
+
518
+ b.Navigation("UserGroup");
519
+ });
520
+
521
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
522
+ {
523
+ b.HasOne("ContactManagementAPI.Models.AppUser", "AppUser")
524
+ .WithMany("UserRights")
525
+ .HasForeignKey("AppUserId")
526
+ .OnDelete(DeleteBehavior.Cascade)
527
+ .IsRequired();
528
+
529
+ b.Navigation("AppUser");
530
+ });
531
+
532
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
533
+ {
534
+ b.Navigation("UserRights");
535
+ });
536
+
537
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
538
+ {
539
+ b.Navigation("BankAccounts");
540
+
541
+ b.Navigation("Documents");
542
+
543
+ b.Navigation("Photos");
544
+ });
545
+
546
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
547
+ {
548
+ b.Navigation("Contacts");
549
+ });
550
+
551
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
552
+ {
553
+ b.Navigation("GroupRights");
554
+
555
+ b.Navigation("Users");
556
+ });
557
+ #pragma warning restore 612, 618
558
+ }
559
+ }
560
+ }
ContactManagementAPI/Migrations/20260221045734_AddPanNumberToContact.cs ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using Microsoft.EntityFrameworkCore.Migrations;
3
+
4
+ #nullable disable
5
+
6
+ namespace ContactManagementAPI.Migrations
7
+ {
8
+ /// <inheritdoc />
9
+ public partial class AddPanNumberToContact : Migration
10
+ {
11
+ /// <inheritdoc />
12
+ protected override void Up(MigrationBuilder migrationBuilder)
13
+ {
14
+ migrationBuilder.AddColumn<string>(
15
+ name: "PanNumber",
16
+ table: "Contacts",
17
+ type: "nvarchar(max)",
18
+ nullable: true);
19
+
20
+ migrationBuilder.UpdateData(
21
+ table: "ContactGroups",
22
+ keyColumn: "Id",
23
+ keyValue: 1,
24
+ column: "CreatedAt",
25
+ value: new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7176));
26
+
27
+ migrationBuilder.UpdateData(
28
+ table: "ContactGroups",
29
+ keyColumn: "Id",
30
+ keyValue: 2,
31
+ column: "CreatedAt",
32
+ value: new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7190));
33
+
34
+ migrationBuilder.UpdateData(
35
+ table: "ContactGroups",
36
+ keyColumn: "Id",
37
+ keyValue: 3,
38
+ column: "CreatedAt",
39
+ value: new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7192));
40
+
41
+ migrationBuilder.UpdateData(
42
+ table: "ContactGroups",
43
+ keyColumn: "Id",
44
+ keyValue: 4,
45
+ column: "CreatedAt",
46
+ value: new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7196));
47
+
48
+ migrationBuilder.UpdateData(
49
+ table: "ContactGroups",
50
+ keyColumn: "Id",
51
+ keyValue: 5,
52
+ column: "CreatedAt",
53
+ value: new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7198));
54
+
55
+ migrationBuilder.UpdateData(
56
+ table: "ContactGroups",
57
+ keyColumn: "Id",
58
+ keyValue: 6,
59
+ column: "CreatedAt",
60
+ value: new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7199));
61
+
62
+ migrationBuilder.UpdateData(
63
+ table: "ContactGroups",
64
+ keyColumn: "Id",
65
+ keyValue: 7,
66
+ column: "CreatedAt",
67
+ value: new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7200));
68
+
69
+ migrationBuilder.UpdateData(
70
+ table: "ContactGroups",
71
+ keyColumn: "Id",
72
+ keyValue: 8,
73
+ column: "CreatedAt",
74
+ value: new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7202));
75
+ }
76
+
77
+ /// <inheritdoc />
78
+ protected override void Down(MigrationBuilder migrationBuilder)
79
+ {
80
+ migrationBuilder.DropColumn(
81
+ name: "PanNumber",
82
+ table: "Contacts");
83
+
84
+ migrationBuilder.UpdateData(
85
+ table: "ContactGroups",
86
+ keyColumn: "Id",
87
+ keyValue: 1,
88
+ column: "CreatedAt",
89
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4198));
90
+
91
+ migrationBuilder.UpdateData(
92
+ table: "ContactGroups",
93
+ keyColumn: "Id",
94
+ keyValue: 2,
95
+ column: "CreatedAt",
96
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4211));
97
+
98
+ migrationBuilder.UpdateData(
99
+ table: "ContactGroups",
100
+ keyColumn: "Id",
101
+ keyValue: 3,
102
+ column: "CreatedAt",
103
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4213));
104
+
105
+ migrationBuilder.UpdateData(
106
+ table: "ContactGroups",
107
+ keyColumn: "Id",
108
+ keyValue: 4,
109
+ column: "CreatedAt",
110
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4214));
111
+
112
+ migrationBuilder.UpdateData(
113
+ table: "ContactGroups",
114
+ keyColumn: "Id",
115
+ keyValue: 5,
116
+ column: "CreatedAt",
117
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4216));
118
+
119
+ migrationBuilder.UpdateData(
120
+ table: "ContactGroups",
121
+ keyColumn: "Id",
122
+ keyValue: 6,
123
+ column: "CreatedAt",
124
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4217));
125
+
126
+ migrationBuilder.UpdateData(
127
+ table: "ContactGroups",
128
+ keyColumn: "Id",
129
+ keyValue: 7,
130
+ column: "CreatedAt",
131
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4218));
132
+
133
+ migrationBuilder.UpdateData(
134
+ table: "ContactGroups",
135
+ keyColumn: "Id",
136
+ keyValue: 8,
137
+ column: "CreatedAt",
138
+ value: new DateTime(2026, 2, 20, 22, 49, 18, 470, DateTimeKind.Local).AddTicks(4220));
139
+ }
140
+ }
141
+ }
ContactManagementAPI/Migrations/ApplicationDbContextModelSnapshot.cs ADDED
@@ -0,0 +1,557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // <auto-generated />
2
+ using System;
3
+ using ContactManagementAPI.Data;
4
+ using Microsoft.EntityFrameworkCore;
5
+ using Microsoft.EntityFrameworkCore.Infrastructure;
6
+ using Microsoft.EntityFrameworkCore.Metadata;
7
+ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
8
+
9
+ #nullable disable
10
+
11
+ namespace ContactManagementAPI.Migrations
12
+ {
13
+ [DbContext(typeof(ApplicationDbContext))]
14
+ partial class ApplicationDbContextModelSnapshot : ModelSnapshot
15
+ {
16
+ protected override void BuildModel(ModelBuilder modelBuilder)
17
+ {
18
+ #pragma warning disable 612, 618
19
+ modelBuilder
20
+ .HasAnnotation("ProductVersion", "8.0.0")
21
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
22
+
23
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
24
+
25
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
26
+ {
27
+ b.Property<int>("Id")
28
+ .ValueGeneratedOnAdd()
29
+ .HasColumnType("int");
30
+
31
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
32
+
33
+ b.Property<DateTime>("CreatedAt")
34
+ .HasColumnType("datetime2");
35
+
36
+ b.Property<string>("FullName")
37
+ .HasMaxLength(200)
38
+ .HasColumnType("nvarchar(200)");
39
+
40
+ b.Property<int>("GroupId")
41
+ .HasColumnType("int");
42
+
43
+ b.Property<bool>("IsActive")
44
+ .HasColumnType("bit");
45
+
46
+ b.Property<bool>("IsAdmin")
47
+ .HasColumnType("bit");
48
+
49
+ b.Property<string>("PasswordHash")
50
+ .IsRequired()
51
+ .HasColumnType("nvarchar(max)");
52
+
53
+ b.Property<DateTime>("UpdatedAt")
54
+ .HasColumnType("datetime2");
55
+
56
+ b.Property<string>("UserName")
57
+ .IsRequired()
58
+ .HasMaxLength(100)
59
+ .HasColumnType("nvarchar(100)");
60
+
61
+ b.HasKey("Id");
62
+
63
+ b.HasIndex("GroupId");
64
+
65
+ b.HasIndex("UserName")
66
+ .IsUnique();
67
+
68
+ b.ToTable("AppUsers");
69
+ });
70
+
71
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
72
+ {
73
+ b.Property<int>("Id")
74
+ .ValueGeneratedOnAdd()
75
+ .HasColumnType("int");
76
+
77
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
78
+
79
+ b.Property<string>("AadharNumber")
80
+ .HasColumnType("nvarchar(max)");
81
+
82
+ b.Property<string>("Address")
83
+ .HasColumnType("nvarchar(max)");
84
+
85
+ b.Property<string>("BankAccountNumber")
86
+ .HasColumnType("nvarchar(max)");
87
+
88
+ b.Property<string>("BankName")
89
+ .HasColumnType("nvarchar(max)");
90
+
91
+ b.Property<string>("BranchName")
92
+ .HasColumnType("nvarchar(max)");
93
+
94
+ b.Property<string>("City")
95
+ .HasColumnType("nvarchar(max)");
96
+
97
+ b.Property<string>("Country")
98
+ .HasColumnType("nvarchar(max)");
99
+
100
+ b.Property<DateTime>("CreatedAt")
101
+ .HasColumnType("datetime2");
102
+
103
+ b.Property<DateTime?>("DateOfBirth")
104
+ .HasColumnType("datetime2");
105
+
106
+ b.Property<string>("DrivingLicenseNumber")
107
+ .HasColumnType("nvarchar(max)");
108
+
109
+ b.Property<string>("Email")
110
+ .HasColumnType("nvarchar(max)");
111
+
112
+ b.Property<string>("FirstName")
113
+ .IsRequired()
114
+ .HasColumnType("nvarchar(max)");
115
+
116
+ b.Property<string>("Gender")
117
+ .HasColumnType("nvarchar(max)");
118
+
119
+ b.Property<int?>("GroupId")
120
+ .HasColumnType("int");
121
+
122
+ b.Property<string>("IfscCode")
123
+ .HasColumnType("nvarchar(max)");
124
+
125
+ b.Property<string>("LastName")
126
+ .HasColumnType("nvarchar(max)");
127
+
128
+ b.Property<string>("Mobile1")
129
+ .HasColumnType("nvarchar(max)");
130
+
131
+ b.Property<string>("Mobile2")
132
+ .HasColumnType("nvarchar(max)");
133
+
134
+ b.Property<string>("Mobile3")
135
+ .HasColumnType("nvarchar(max)");
136
+
137
+ b.Property<string>("NickName")
138
+ .HasColumnType("nvarchar(max)");
139
+
140
+ b.Property<string>("OtherDetails")
141
+ .HasColumnType("nvarchar(max)");
142
+
143
+ b.Property<string>("PanNumber")
144
+ .HasColumnType("nvarchar(max)");
145
+
146
+ b.Property<string>("PassportNumber")
147
+ .HasColumnType("nvarchar(max)");
148
+
149
+ b.Property<string>("PhotoPath")
150
+ .HasColumnType("nvarchar(max)");
151
+
152
+ b.Property<string>("PostalCode")
153
+ .HasColumnType("nvarchar(max)");
154
+
155
+ b.Property<string>("State")
156
+ .HasColumnType("nvarchar(max)");
157
+
158
+ b.Property<DateTime>("UpdatedAt")
159
+ .HasColumnType("datetime2");
160
+
161
+ b.Property<string>("VotersId")
162
+ .HasColumnType("nvarchar(max)");
163
+
164
+ b.Property<string>("WhatsAppNumber")
165
+ .HasColumnType("nvarchar(max)");
166
+
167
+ b.HasKey("Id");
168
+
169
+ b.HasIndex("GroupId");
170
+
171
+ b.ToTable("Contacts");
172
+ });
173
+
174
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactBankAccount", b =>
175
+ {
176
+ b.Property<int>("Id")
177
+ .ValueGeneratedOnAdd()
178
+ .HasColumnType("int");
179
+
180
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
181
+
182
+ b.Property<string>("AccountNumber")
183
+ .HasColumnType("nvarchar(max)");
184
+
185
+ b.Property<string>("BankName")
186
+ .HasColumnType("nvarchar(max)");
187
+
188
+ b.Property<string>("BranchName")
189
+ .HasColumnType("nvarchar(max)");
190
+
191
+ b.Property<int>("ContactId")
192
+ .HasColumnType("int");
193
+
194
+ b.Property<DateTime>("CreatedAt")
195
+ .HasColumnType("datetime2");
196
+
197
+ b.Property<string>("IfscCode")
198
+ .HasColumnType("nvarchar(max)");
199
+
200
+ b.Property<DateTime>("UpdatedAt")
201
+ .HasColumnType("datetime2");
202
+
203
+ b.HasKey("Id");
204
+
205
+ b.HasIndex("ContactId");
206
+
207
+ b.ToTable("ContactBankAccounts");
208
+ });
209
+
210
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
211
+ {
212
+ b.Property<int>("Id")
213
+ .ValueGeneratedOnAdd()
214
+ .HasColumnType("int");
215
+
216
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
217
+
218
+ b.Property<int>("ContactId")
219
+ .HasColumnType("int");
220
+
221
+ b.Property<string>("ContentType")
222
+ .IsRequired()
223
+ .HasColumnType("nvarchar(max)");
224
+
225
+ b.Property<string>("DocumentPath")
226
+ .IsRequired()
227
+ .HasColumnType("nvarchar(max)");
228
+
229
+ b.Property<string>("DocumentType")
230
+ .IsRequired()
231
+ .HasColumnType("nvarchar(max)");
232
+
233
+ b.Property<string>("FileName")
234
+ .IsRequired()
235
+ .HasColumnType("nvarchar(max)");
236
+
237
+ b.Property<long>("FileSize")
238
+ .HasColumnType("bigint");
239
+
240
+ b.Property<DateTime>("UploadedAt")
241
+ .HasColumnType("datetime2");
242
+
243
+ b.HasKey("Id");
244
+
245
+ b.HasIndex("ContactId");
246
+
247
+ b.ToTable("ContactDocuments");
248
+ });
249
+
250
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
251
+ {
252
+ b.Property<int>("Id")
253
+ .ValueGeneratedOnAdd()
254
+ .HasColumnType("int");
255
+
256
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
257
+
258
+ b.Property<DateTime>("CreatedAt")
259
+ .HasColumnType("datetime2");
260
+
261
+ b.Property<string>("Description")
262
+ .IsRequired()
263
+ .HasColumnType("nvarchar(max)");
264
+
265
+ b.Property<string>("Name")
266
+ .IsRequired()
267
+ .HasColumnType("nvarchar(max)");
268
+
269
+ b.HasKey("Id");
270
+
271
+ b.ToTable("ContactGroups");
272
+
273
+ b.HasData(
274
+ new
275
+ {
276
+ Id = 1,
277
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7176),
278
+ Description = "Family members",
279
+ Name = "Family"
280
+ },
281
+ new
282
+ {
283
+ Id = 2,
284
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7190),
285
+ Description = "Friends",
286
+ Name = "Friends"
287
+ },
288
+ new
289
+ {
290
+ Id = 3,
291
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7192),
292
+ Description = "Business contacts",
293
+ Name = "Business"
294
+ },
295
+ new
296
+ {
297
+ Id = 4,
298
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7196),
299
+ Description = "School contacts",
300
+ Name = "School"
301
+ },
302
+ new
303
+ {
304
+ Id = 5,
305
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7198),
306
+ Description = "Church members",
307
+ Name = "Church"
308
+ },
309
+ new
310
+ {
311
+ Id = 6,
312
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7199),
313
+ Description = "Other contacts",
314
+ Name = "Others"
315
+ },
316
+ new
317
+ {
318
+ Id = 7,
319
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7200),
320
+ Description = "College contacts",
321
+ Name = "College"
322
+ },
323
+ new
324
+ {
325
+ Id = 8,
326
+ CreatedAt = new DateTime(2026, 2, 21, 10, 27, 31, 12, DateTimeKind.Local).AddTicks(7202),
327
+ Description = "Alcoholics Anonymous",
328
+ Name = "AA"
329
+ });
330
+ });
331
+
332
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
333
+ {
334
+ b.Property<int>("Id")
335
+ .ValueGeneratedOnAdd()
336
+ .HasColumnType("int");
337
+
338
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
339
+
340
+ b.Property<int>("ContactId")
341
+ .HasColumnType("int");
342
+
343
+ b.Property<string>("ContentType")
344
+ .IsRequired()
345
+ .HasColumnType("nvarchar(max)");
346
+
347
+ b.Property<string>("FileName")
348
+ .IsRequired()
349
+ .HasColumnType("nvarchar(max)");
350
+
351
+ b.Property<long>("FileSize")
352
+ .HasColumnType("bigint");
353
+
354
+ b.Property<bool>("IsProfilePhoto")
355
+ .HasColumnType("bit");
356
+
357
+ b.Property<string>("PhotoPath")
358
+ .IsRequired()
359
+ .HasColumnType("nvarchar(max)");
360
+
361
+ b.Property<DateTime>("UploadedAt")
362
+ .HasColumnType("datetime2");
363
+
364
+ b.HasKey("Id");
365
+
366
+ b.HasIndex("ContactId");
367
+
368
+ b.ToTable("ContactPhotos");
369
+ });
370
+
371
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
372
+ {
373
+ b.Property<int>("Id")
374
+ .ValueGeneratedOnAdd()
375
+ .HasColumnType("int");
376
+
377
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
378
+
379
+ b.Property<bool>("IsGranted")
380
+ .HasColumnType("bit");
381
+
382
+ b.Property<string>("RightKey")
383
+ .IsRequired()
384
+ .HasMaxLength(100)
385
+ .HasColumnType("nvarchar(100)");
386
+
387
+ b.Property<int>("UserGroupId")
388
+ .HasColumnType("int");
389
+
390
+ b.HasKey("Id");
391
+
392
+ b.HasIndex("UserGroupId", "RightKey")
393
+ .IsUnique();
394
+
395
+ b.ToTable("GroupRights");
396
+ });
397
+
398
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
399
+ {
400
+ b.Property<int>("Id")
401
+ .ValueGeneratedOnAdd()
402
+ .HasColumnType("int");
403
+
404
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
405
+
406
+ b.Property<DateTime>("CreatedAt")
407
+ .HasColumnType("datetime2");
408
+
409
+ b.Property<string>("Description")
410
+ .HasMaxLength(500)
411
+ .HasColumnType("nvarchar(500)");
412
+
413
+ b.Property<string>("Name")
414
+ .IsRequired()
415
+ .HasMaxLength(150)
416
+ .HasColumnType("nvarchar(150)");
417
+
418
+ b.HasKey("Id");
419
+
420
+ b.HasIndex("Name")
421
+ .IsUnique();
422
+
423
+ b.ToTable("UserGroups");
424
+ });
425
+
426
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
427
+ {
428
+ b.Property<int>("Id")
429
+ .ValueGeneratedOnAdd()
430
+ .HasColumnType("int");
431
+
432
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
433
+
434
+ b.Property<int>("AppUserId")
435
+ .HasColumnType("int");
436
+
437
+ b.Property<bool>("IsGranted")
438
+ .HasColumnType("bit");
439
+
440
+ b.Property<string>("RightKey")
441
+ .IsRequired()
442
+ .HasMaxLength(100)
443
+ .HasColumnType("nvarchar(100)");
444
+
445
+ b.HasKey("Id");
446
+
447
+ b.HasIndex("AppUserId", "RightKey")
448
+ .IsUnique();
449
+
450
+ b.ToTable("UserRights");
451
+ });
452
+
453
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
454
+ {
455
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "Group")
456
+ .WithMany("Users")
457
+ .HasForeignKey("GroupId")
458
+ .OnDelete(DeleteBehavior.SetNull)
459
+ .IsRequired();
460
+
461
+ b.Navigation("Group");
462
+ });
463
+
464
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
465
+ {
466
+ b.HasOne("ContactManagementAPI.Models.ContactGroup", "Group")
467
+ .WithMany("Contacts")
468
+ .HasForeignKey("GroupId")
469
+ .OnDelete(DeleteBehavior.SetNull);
470
+
471
+ b.Navigation("Group");
472
+ });
473
+
474
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactBankAccount", b =>
475
+ {
476
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
477
+ .WithMany("BankAccounts")
478
+ .HasForeignKey("ContactId")
479
+ .OnDelete(DeleteBehavior.Cascade)
480
+ .IsRequired();
481
+
482
+ b.Navigation("Contact");
483
+ });
484
+
485
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactDocument", b =>
486
+ {
487
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
488
+ .WithMany("Documents")
489
+ .HasForeignKey("ContactId")
490
+ .OnDelete(DeleteBehavior.Cascade)
491
+ .IsRequired();
492
+
493
+ b.Navigation("Contact");
494
+ });
495
+
496
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactPhoto", b =>
497
+ {
498
+ b.HasOne("ContactManagementAPI.Models.Contact", "Contact")
499
+ .WithMany("Photos")
500
+ .HasForeignKey("ContactId")
501
+ .OnDelete(DeleteBehavior.Cascade)
502
+ .IsRequired();
503
+
504
+ b.Navigation("Contact");
505
+ });
506
+
507
+ modelBuilder.Entity("ContactManagementAPI.Models.GroupRight", b =>
508
+ {
509
+ b.HasOne("ContactManagementAPI.Models.UserGroup", "UserGroup")
510
+ .WithMany("GroupRights")
511
+ .HasForeignKey("UserGroupId")
512
+ .OnDelete(DeleteBehavior.Cascade)
513
+ .IsRequired();
514
+
515
+ b.Navigation("UserGroup");
516
+ });
517
+
518
+ modelBuilder.Entity("ContactManagementAPI.Models.UserRight", b =>
519
+ {
520
+ b.HasOne("ContactManagementAPI.Models.AppUser", "AppUser")
521
+ .WithMany("UserRights")
522
+ .HasForeignKey("AppUserId")
523
+ .OnDelete(DeleteBehavior.Cascade)
524
+ .IsRequired();
525
+
526
+ b.Navigation("AppUser");
527
+ });
528
+
529
+ modelBuilder.Entity("ContactManagementAPI.Models.AppUser", b =>
530
+ {
531
+ b.Navigation("UserRights");
532
+ });
533
+
534
+ modelBuilder.Entity("ContactManagementAPI.Models.Contact", b =>
535
+ {
536
+ b.Navigation("BankAccounts");
537
+
538
+ b.Navigation("Documents");
539
+
540
+ b.Navigation("Photos");
541
+ });
542
+
543
+ modelBuilder.Entity("ContactManagementAPI.Models.ContactGroup", b =>
544
+ {
545
+ b.Navigation("Contacts");
546
+ });
547
+
548
+ modelBuilder.Entity("ContactManagementAPI.Models.UserGroup", b =>
549
+ {
550
+ b.Navigation("GroupRights");
551
+
552
+ b.Navigation("Users");
553
+ });
554
+ #pragma warning restore 612, 618
555
+ }
556
+ }
557
+ }
ContactManagementAPI/Models/AdminHistoryEntry.cs ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ namespace ContactManagementAPI.Models
2
+ {
3
+ public class AdminHistoryEntry
4
+ {
5
+ public string ActionType { get; set; } = string.Empty;
6
+ public string EntityType { get; set; } = string.Empty;
7
+ public int? EntityId { get; set; }
8
+ public string Details { get; set; } = string.Empty;
9
+ public string PerformedBy { get; set; } = string.Empty;
10
+ public DateTime PerformedAt { get; set; }
11
+ }
12
+ }
ContactManagementAPI/Models/AppUser.cs ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.ComponentModel.DataAnnotations;
4
+
5
+ namespace ContactManagementAPI.Models
6
+ {
7
+ public class AppUser
8
+ {
9
+ public int Id { get; set; }
10
+
11
+ [Required]
12
+ [MaxLength(100)]
13
+ public string UserName { get; set; } = string.Empty;
14
+
15
+ [Required]
16
+ public string PasswordHash { get; set; } = string.Empty;
17
+
18
+ [MaxLength(200)]
19
+ public string? FullName { get; set; }
20
+
21
+ public bool IsAdmin { get; set; }
22
+ public bool IsActive { get; set; } = true;
23
+
24
+ [Required]
25
+ public int GroupId { get; set; }
26
+ public UserGroup? Group { get; set; }
27
+
28
+ public DateTime CreatedAt { get; set; }
29
+ public DateTime UpdatedAt { get; set; }
30
+
31
+ public ICollection<UserRight> UserRights { get; set; } = new List<UserRight>();
32
+ }
33
+ }
ContactManagementAPI/Models/Contact.cs ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.ComponentModel.DataAnnotations;
4
+
5
+ namespace ContactManagementAPI.Models
6
+ {
7
+ public class Contact
8
+ {
9
+ public int Id { get; set; }
10
+ [Required]
11
+ public string FirstName { get; set; } = string.Empty;
12
+ public string? LastName { get; set; }
13
+ public string? NickName { get; set; }
14
+ public string? Gender { get; set; }
15
+ public DateTime? DateOfBirth { get; set; }
16
+ public string? Email { get; set; }
17
+ public string? Mobile1 { get; set; }
18
+ public string? Mobile2 { get; set; }
19
+ public string? Mobile3 { get; set; }
20
+ public string? WhatsAppNumber { get; set; }
21
+ public string? PassportNumber { get; set; }
22
+ public string? PanNumber { get; set; }
23
+ public string? AadharNumber { get; set; }
24
+ public string? DrivingLicenseNumber { get; set; }
25
+ public string? VotersId { get; set; }
26
+ public string? BankAccountNumber { get; set; }
27
+ public string? BankName { get; set; }
28
+ public string? BranchName { get; set; }
29
+ public string? IfscCode { get; set; }
30
+ public string? Address { get; set; }
31
+ public string? City { get; set; }
32
+ public string? State { get; set; }
33
+ public string? PostalCode { get; set; }
34
+ public string? Country { get; set; }
35
+ public string? PhotoPath { get; set; }
36
+ public int? GroupId { get; set; }
37
+ public ContactGroup? Group { get; set; }
38
+ public string? OtherDetails { get; set; }
39
+ public DateTime CreatedAt { get; set; }
40
+ public DateTime UpdatedAt { get; set; }
41
+
42
+ public ICollection<ContactPhoto> Photos { get; set; } = new List<ContactPhoto>();
43
+ public ICollection<ContactDocument> Documents { get; set; } = new List<ContactDocument>();
44
+ public ICollection<ContactBankAccount> BankAccounts { get; set; } = new List<ContactBankAccount>();
45
+ }
46
+ }
ContactManagementAPI/Models/ContactBankAccount.cs ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ namespace ContactManagementAPI.Models
2
+ {
3
+ public class ContactBankAccount
4
+ {
5
+ public int Id { get; set; }
6
+ public int ContactId { get; set; }
7
+ public Contact? Contact { get; set; }
8
+ public string? AccountNumber { get; set; }
9
+ public string? BankName { get; set; }
10
+ public string? BranchName { get; set; }
11
+ public string? IfscCode { get; set; }
12
+ public DateTime CreatedAt { get; set; }
13
+ public DateTime UpdatedAt { get; set; }
14
+ }
15
+ }
ContactManagementAPI/Models/ContactDocument.cs ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+
3
+ namespace ContactManagementAPI.Models
4
+ {
5
+ public class ContactDocument
6
+ {
7
+ public int Id { get; set; }
8
+ public int ContactId { get; set; }
9
+ public Contact Contact { get; set; }
10
+ public string DocumentPath { get; set; }
11
+ public string FileName { get; set; }
12
+ public long FileSize { get; set; }
13
+ public string ContentType { get; set; }
14
+ public string DocumentType { get; set; } // e.g., "ID", "Address", "Contract", etc.
15
+ public DateTime UploadedAt { get; set; }
16
+ }
17
+ }
ContactManagementAPI/Models/ContactGroup.cs ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using System.Collections.Generic;
3
+
4
+ namespace ContactManagementAPI.Models
5
+ {
6
+ public class ContactGroup
7
+ {
8
+ public int Id { get; set; }
9
+ public string Name { get; set; }
10
+ public string Description { get; set; }
11
+ public DateTime CreatedAt { get; set; }
12
+
13
+ public ICollection<Contact> Contacts { get; set; } = new List<Contact>();
14
+ }
15
+ }
ContactManagementAPI/Models/ContactPhoto.cs ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+
3
+ namespace ContactManagementAPI.Models
4
+ {
5
+ public class ContactPhoto
6
+ {
7
+ public int Id { get; set; }
8
+ public int ContactId { get; set; }
9
+ public Contact Contact { get; set; }
10
+ public string PhotoPath { get; set; }
11
+ public string FileName { get; set; }
12
+ public long FileSize { get; set; }
13
+ public string ContentType { get; set; }
14
+ public bool IsProfilePhoto { get; set; }
15
+ public DateTime UploadedAt { get; set; }
16
+ }
17
+ }
ContactManagementAPI/Models/GroupRight.cs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+ namespace ContactManagementAPI.Models
4
+ {
5
+ public class GroupRight
6
+ {
7
+ public int Id { get; set; }
8
+
9
+ public int UserGroupId { get; set; }
10
+ public UserGroup? UserGroup { get; set; }
11
+
12
+ [Required]
13
+ [MaxLength(100)]
14
+ public string RightKey { get; set; } = string.Empty;
15
+
16
+ public bool IsGranted { get; set; }
17
+ }
18
+ }
ContactManagementAPI/Models/RightsCatalog.cs ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Collections.Generic;
2
+
3
+ namespace ContactManagementAPI.Models
4
+ {
5
+ public static class RightsCatalog
6
+ {
7
+ public const string ContactsView = "Contacts.View";
8
+ public const string ContactsCreate = "Contacts.Create";
9
+ public const string ContactsEdit = "Contacts.Edit";
10
+ public const string ContactsDelete = "Contacts.Delete";
11
+ public const string DocumentsManage = "Documents.Manage";
12
+ public const string PhotosManage = "Photos.Manage";
13
+ public const string AdminManageUsers = "Admin.ManageUsers";
14
+ public const string AdminManageGroups = "Admin.ManageGroups";
15
+ public const string AdminManageRights = "Admin.ManageRights";
16
+
17
+ public static readonly IReadOnlyList<RightDefinition> All = new List<RightDefinition>
18
+ {
19
+ new RightDefinition(ContactsView, "Contacts", "View contacts"),
20
+ new RightDefinition(ContactsCreate, "Contacts", "Create contacts"),
21
+ new RightDefinition(ContactsEdit, "Contacts", "Edit contacts"),
22
+ new RightDefinition(ContactsDelete, "Contacts", "Delete contacts"),
23
+ new RightDefinition(PhotosManage, "Photos", "Manage photo gallery"),
24
+ new RightDefinition(DocumentsManage, "Documents", "Manage contact documents"),
25
+ new RightDefinition(AdminManageUsers, "Administration", "Manage users"),
26
+ new RightDefinition(AdminManageGroups, "Administration", "Manage user groups"),
27
+ new RightDefinition(AdminManageRights, "Administration", "Manage rights")
28
+ };
29
+ }
30
+
31
+ public class RightDefinition
32
+ {
33
+ public RightDefinition(string key, string category, string label)
34
+ {
35
+ Key = key;
36
+ Category = category;
37
+ Label = label;
38
+ }
39
+
40
+ public string Key { get; }
41
+ public string Category { get; }
42
+ public string Label { get; }
43
+ }
44
+ }
ContactManagementAPI/Models/UserGroup.cs ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.ComponentModel.DataAnnotations;
4
+
5
+ namespace ContactManagementAPI.Models
6
+ {
7
+ public class UserGroup
8
+ {
9
+ public int Id { get; set; }
10
+
11
+ [Required]
12
+ [MaxLength(150)]
13
+ public string Name { get; set; } = string.Empty;
14
+
15
+ [MaxLength(500)]
16
+ public string? Description { get; set; }
17
+
18
+ public DateTime CreatedAt { get; set; }
19
+
20
+ public ICollection<AppUser> Users { get; set; } = new List<AppUser>();
21
+ public ICollection<GroupRight> GroupRights { get; set; } = new List<GroupRight>();
22
+ }
23
+ }
ContactManagementAPI/Models/UserRight.cs ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.ComponentModel.DataAnnotations;
2
+
3
+ namespace ContactManagementAPI.Models
4
+ {
5
+ public class UserRight
6
+ {
7
+ public int Id { get; set; }
8
+
9
+ public int AppUserId { get; set; }
10
+ public AppUser? AppUser { get; set; }
11
+
12
+ [Required]
13
+ [MaxLength(100)]
14
+ public string RightKey { get; set; } = string.Empty;
15
+
16
+ public bool IsGranted { get; set; }
17
+ }
18
+ }
ContactManagementAPI/Program.cs ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.EntityFrameworkCore;
2
+ using ContactManagementAPI.Data;
3
+ using ContactManagementAPI.Services;
4
+ using Microsoft.Data.Sqlite;
5
+ using Microsoft.AspNetCore.HttpOverrides;
6
+ using Microsoft.Extensions.FileProviders;
7
+
8
+ var builder = WebApplication.CreateBuilder(args);
9
+
10
+ // Add services to the container
11
+ builder.Services.AddControllersWithViews()
12
+ .AddJsonOptions(options =>
13
+ {
14
+ // Prevent circular references
15
+ options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
16
+ });
17
+ builder.Services.AddRazorPages();
18
+ builder.Services.AddHttpContextAccessor();
19
+ builder.Services.AddSession(options =>
20
+ {
21
+ options.Cookie.HttpOnly = true;
22
+ options.Cookie.IsEssential = true;
23
+ options.IdleTimeout = TimeSpan.FromHours(2); // Reduced from 8 to 2 hours to prevent memory buildup
24
+ });
25
+
26
+ // Add memory cache with size limit
27
+ builder.Services.AddMemoryCache(options =>
28
+ {
29
+ options.SizeLimit = 1024; // Limit cache size
30
+ });
31
+
32
+ // Configure Antiforgery with SameSite settings
33
+ builder.Services.AddAntiforgery(options =>
34
+ {
35
+ options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest;
36
+ options.Cookie.SameSite = SameSiteMode.Lax;
37
+ });
38
+
39
+ // Configure database connection (SQLite preferred for portable installs)
40
+ var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
41
+ ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
42
+
43
+ var useSqlite = connectionString.TrimStart().StartsWith("Data Source=", StringComparison.OrdinalIgnoreCase);
44
+
45
+ if (useSqlite)
46
+ {
47
+ var sqliteBuilder = new SqliteConnectionStringBuilder(connectionString);
48
+ var sqliteDbPathOverride = Environment.GetEnvironmentVariable("SQLITE_DB_PATH");
49
+ var dataSource = sqliteBuilder.DataSource;
50
+
51
+ if (!string.IsNullOrWhiteSpace(sqliteDbPathOverride))
52
+ {
53
+ dataSource = sqliteDbPathOverride;
54
+ }
55
+
56
+ if (string.IsNullOrWhiteSpace(dataSource))
57
+ {
58
+ dataSource = "ContactManagement.db";
59
+ }
60
+
61
+ if (!Path.IsPathRooted(dataSource))
62
+ {
63
+ var appDataFolder = Path.Combine(
64
+ Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
65
+ "ContactManagementSystem");
66
+
67
+ Directory.CreateDirectory(appDataFolder);
68
+ dataSource = Path.Combine(appDataFolder, dataSource);
69
+ }
70
+
71
+ sqliteBuilder.DataSource = dataSource;
72
+ connectionString = sqliteBuilder.ToString();
73
+ }
74
+
75
+ builder.Services.AddDbContext<ApplicationDbContext>(options =>
76
+ {
77
+ if (useSqlite)
78
+ {
79
+ options.UseSqlite(connectionString);
80
+ }
81
+ else
82
+ {
83
+ options.UseSqlServer(connectionString);
84
+ }
85
+
86
+ // Optimize EF Core for better memory management
87
+ options.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking);
88
+ options.EnableSensitiveDataLogging(false);
89
+ options.EnableDetailedErrors(false);
90
+ });
91
+
92
+ // Add custom services
93
+ builder.Services.AddScoped<FileUploadService>();
94
+ builder.Services.AddScoped<AuthorizationService>();
95
+ builder.Services.AddScoped<UserContextService>();
96
+ builder.Services.AddScoped<ImportExportService>();
97
+ builder.Services.AddScoped<ContactStatisticsService>();
98
+ builder.Services.AddScoped<AdminHistoryService>();
99
+
100
+ // Configure CORS if needed for future API consumption
101
+ builder.Services.AddCors(options =>
102
+ {
103
+ options.AddPolicy("AllowAll", policy =>
104
+ {
105
+ policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
106
+ .AllowAnyMethod()
107
+ .AllowAnyHeader()
108
+ .AllowCredentials();
109
+ });
110
+ });
111
+
112
+ var app = builder.Build();
113
+
114
+ // Support reverse proxies/load balancers in cloud hosting
115
+ app.UseForwardedHeaders(new ForwardedHeadersOptions
116
+ {
117
+ ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
118
+ });
119
+
120
+ var disableHttpsRedirection =
121
+ app.Configuration.GetValue<bool>("DisableHttpsRedirection") ||
122
+ string.Equals(Environment.GetEnvironmentVariable("DISABLE_HTTPS_REDIRECTION"), "true", StringComparison.OrdinalIgnoreCase);
123
+
124
+ // Ensure database exists and seed defaults
125
+ using (var scope = app.Services.CreateScope())
126
+ {
127
+ var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
128
+
129
+ if (useSqlite)
130
+ {
131
+ dbContext.Database.EnsureCreated();
132
+ }
133
+ else
134
+ {
135
+ dbContext.Database.Migrate();
136
+ }
137
+
138
+ SeedData.Initialize(dbContext);
139
+ }
140
+
141
+ // Configure the HTTP request pipeline
142
+ if (!app.Environment.IsDevelopment())
143
+ {
144
+ app.UseExceptionHandler("/Home/Error");
145
+ app.UseHsts();
146
+ }
147
+
148
+ if (!disableHttpsRedirection)
149
+ {
150
+ app.UseHttpsRedirection();
151
+ }
152
+
153
+ app.UseStaticFiles();
154
+
155
+ var uploadsRoot = Environment.GetEnvironmentVariable("UPLOADS_ROOT");
156
+ if (!string.IsNullOrWhiteSpace(uploadsRoot) && Directory.Exists(uploadsRoot))
157
+ {
158
+ Directory.CreateDirectory(Path.Combine(uploadsRoot, "photos"));
159
+ Directory.CreateDirectory(Path.Combine(uploadsRoot, "documents"));
160
+
161
+ app.UseStaticFiles(new StaticFileOptions
162
+ {
163
+ FileProvider = new PhysicalFileProvider(uploadsRoot),
164
+ RequestPath = "/uploads"
165
+ });
166
+ }
167
+
168
+ app.UseRouting();
169
+ app.UseSession();
170
+ app.UseCors("AllowAll");
171
+ app.UseAuthorization();
172
+
173
+ app.MapControllerRoute(
174
+ name: "default",
175
+ pattern: "{controller=Home}/{action=Index}/{id?}");
176
+
177
+ app.MapRazorPages();
178
+
179
+ app.Run();
ContactManagementAPI/Properties/launchSettings.json ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "iisSettings": {
3
+ "windowsAuthentication": false,
4
+ "anonymousAuthentication": true,
5
+ "iisExpress": {
6
+ "applicationUrl": "http://localhost:7089",
7
+ "sslPort": 44318
8
+ }
9
+ },
10
+ "profiles": {
11
+ "http": {
12
+ "commandName": "Project",
13
+ "dotnetRunMessages": true,
14
+ "launchBrowser": true,
15
+ "applicationUrl": "http://localhost:5000",
16
+ "environmentVariables": {
17
+ "ASPNETCORE_ENVIRONMENT": "Development"
18
+ }
19
+ },
20
+ "https": {
21
+ "commandName": "Project",
22
+ "dotnetRunMessages": true,
23
+ "launchBrowser": true,
24
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
25
+ "environmentVariables": {
26
+ "ASPNETCORE_ENVIRONMENT": "Development"
27
+ }
28
+ },
29
+ "IIS Express": {
30
+ "commandName": "IISExpress",
31
+ "launchBrowser": true,
32
+ "environmentVariables": {
33
+ "ASPNETCORE_ENVIRONMENT": "Development"
34
+ }
35
+ }
36
+ }
37
+ }
ContactManagementAPI/Security/RequireRightAttribute.cs ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Threading.Tasks;
2
+ using ContactManagementAPI.Models;
3
+ using ContactManagementAPI.Services;
4
+ using Microsoft.AspNetCore.Mvc;
5
+ using Microsoft.AspNetCore.Mvc.Filters;
6
+
7
+ namespace ContactManagementAPI.Security
8
+ {
9
+ public class RequireRightAttribute : TypeFilterAttribute
10
+ {
11
+ public RequireRightAttribute(string rightKey) : base(typeof(RequireRightFilter))
12
+ {
13
+ Arguments = new object[] { rightKey };
14
+ }
15
+ }
16
+
17
+ public class RequireRightFilter : IAsyncActionFilter
18
+ {
19
+ private readonly string _rightKey;
20
+ private readonly UserContextService _userContext;
21
+ private readonly AuthorizationService _authorizationService;
22
+
23
+ public RequireRightFilter(string rightKey, UserContextService userContext, AuthorizationService authorizationService)
24
+ {
25
+ _rightKey = rightKey;
26
+ _userContext = userContext;
27
+ _authorizationService = authorizationService;
28
+ }
29
+
30
+ public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
31
+ {
32
+ if (!_userContext.UserId.HasValue)
33
+ {
34
+ var returnUrl = context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
35
+ context.Result = new RedirectToActionResult("Login", "Account", new { returnUrl });
36
+ return;
37
+ }
38
+
39
+ var hasRight = _authorizationService.HasRight(_userContext.UserId.Value, _rightKey);
40
+ if (!hasRight)
41
+ {
42
+ context.Result = new RedirectToActionResult("AccessDenied", "Account", null);
43
+ return;
44
+ }
45
+
46
+ await next();
47
+ }
48
+ }
49
+ }
ContactManagementAPI/Services/AdminHistoryService.cs ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Text.Json;
2
+ using ContactManagementAPI.Models;
3
+
4
+ namespace ContactManagementAPI.Services
5
+ {
6
+ public class AdminHistoryService
7
+ {
8
+ private readonly string _historyFilePath;
9
+ private static readonly JsonSerializerOptions JsonOptions = new()
10
+ {
11
+ WriteIndented = false
12
+ };
13
+
14
+ public AdminHistoryService(IWebHostEnvironment environment)
15
+ {
16
+ var historyDirectory = Path.Combine(environment.ContentRootPath, "App_Data");
17
+ Directory.CreateDirectory(historyDirectory);
18
+ _historyFilePath = Path.Combine(historyDirectory, "admin-history.jsonl");
19
+ }
20
+
21
+ public void Log(string actionType, string entityType, int? entityId, string performedBy, string details)
22
+ {
23
+ var entry = new AdminHistoryEntry
24
+ {
25
+ ActionType = actionType,
26
+ EntityType = entityType,
27
+ EntityId = entityId,
28
+ PerformedBy = string.IsNullOrWhiteSpace(performedBy) ? "Unknown" : performedBy,
29
+ Details = details,
30
+ PerformedAt = DateTime.Now
31
+ };
32
+
33
+ var line = JsonSerializer.Serialize(entry, JsonOptions);
34
+ File.AppendAllText(_historyFilePath, line + Environment.NewLine);
35
+ }
36
+
37
+ public IReadOnlyList<AdminHistoryEntry> GetLatest(int take = 200)
38
+ {
39
+ if (!File.Exists(_historyFilePath))
40
+ {
41
+ return Array.Empty<AdminHistoryEntry>();
42
+ }
43
+
44
+ var entries = new List<AdminHistoryEntry>();
45
+ var lines = File.ReadLines(_historyFilePath);
46
+ foreach (var line in lines)
47
+ {
48
+ if (string.IsNullOrWhiteSpace(line))
49
+ {
50
+ continue;
51
+ }
52
+
53
+ try
54
+ {
55
+ var entry = JsonSerializer.Deserialize<AdminHistoryEntry>(line);
56
+ if (entry != null)
57
+ {
58
+ entries.Add(entry);
59
+ }
60
+ }
61
+ catch
62
+ {
63
+ // Ignore malformed history lines and continue
64
+ }
65
+ }
66
+
67
+ return entries
68
+ .OrderByDescending(e => e.PerformedAt)
69
+ .Take(take)
70
+ .ToList();
71
+ }
72
+ }
73
+ }
ContactManagementAPI/Services/AuthorizationService.cs ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System.Linq;
2
+ using ContactManagementAPI.Data;
3
+ using ContactManagementAPI.Models;
4
+ using Microsoft.EntityFrameworkCore;
5
+
6
+ namespace ContactManagementAPI.Services
7
+ {
8
+ public class AuthorizationService
9
+ {
10
+ private readonly ApplicationDbContext _context;
11
+
12
+ public AuthorizationService(ApplicationDbContext context)
13
+ {
14
+ _context = context;
15
+ }
16
+
17
+ public bool HasRight(int userId, string rightKey)
18
+ {
19
+ var user = _context.AppUsers
20
+ .AsNoTracking()
21
+ .FirstOrDefault(u => u.Id == userId);
22
+
23
+ if (user == null || !user.IsActive)
24
+ return false;
25
+
26
+ if (user.IsAdmin)
27
+ return true;
28
+
29
+ var userRight = _context.UserRights
30
+ .AsNoTracking()
31
+ .FirstOrDefault(r => r.AppUserId == userId && r.RightKey == rightKey);
32
+
33
+ if (userRight != null)
34
+ return userRight.IsGranted;
35
+
36
+ var groupRight = _context.GroupRights
37
+ .AsNoTracking()
38
+ .FirstOrDefault(r => r.UserGroupId == user.GroupId && r.RightKey == rightKey);
39
+
40
+ return groupRight?.IsGranted ?? false;
41
+ }
42
+
43
+ public bool IsAdmin(int userId)
44
+ {
45
+ var user = _context.AppUsers
46
+ .AsNoTracking()
47
+ .FirstOrDefault(u => u.Id == userId);
48
+
49
+ return user?.IsAdmin == true && user.IsActive;
50
+ }
51
+ }
52
+ }
ContactManagementAPI/Services/ContactStatisticsService.cs ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using ContactManagementAPI.Models;
2
+ using ContactManagementAPI.Data;
3
+ using Microsoft.EntityFrameworkCore;
4
+
5
+ namespace ContactManagementAPI.Services
6
+ {
7
+ public class ContactStatisticsService
8
+ {
9
+ private readonly ApplicationDbContext _context;
10
+
11
+ public ContactStatisticsService(ApplicationDbContext context)
12
+ {
13
+ _context = context;
14
+ }
15
+
16
+ public async Task<ContactStatistics> GetStatisticsAsync()
17
+ {
18
+ var totalContacts = await _context.Contacts.CountAsync();
19
+ var contactsWithEmail = await _context.Contacts.Where(c => !string.IsNullOrEmpty(c.Email)).CountAsync();
20
+ var contactsWithPhone = await _context.Contacts.Where(c =>
21
+ !string.IsNullOrEmpty(c.Mobile1) || !string.IsNullOrEmpty(c.Mobile2) || !string.IsNullOrEmpty(c.Mobile3)).CountAsync();
22
+
23
+ var topCities = await _context.Contacts
24
+ .Where(c => !string.IsNullOrEmpty(c.City))
25
+ .GroupBy(c => c.City)
26
+ .Select(g => new CityStatistic { City = g.Key, Count = g.Count() })
27
+ .OrderByDescending(x => x.Count)
28
+ .Take(5)
29
+ .ToListAsync();
30
+
31
+ var contactsByGroup = await _context.Contacts
32
+ .Include(c => c.Group)
33
+ .GroupBy(c => c.Group == null ? "Unassigned" : c.Group.Name)
34
+ .Select(g => new GroupStatistic { GroupName = g.Key, Count = g.Count() })
35
+ .OrderByDescending(x => x.Count)
36
+ .ToListAsync();
37
+
38
+ return new ContactStatistics
39
+ {
40
+ TotalContacts = totalContacts,
41
+ ContactsWithEmail = contactsWithEmail,
42
+ ContactsWithPhone = contactsWithPhone,
43
+ TopCities = topCities,
44
+ ContactsByGroup = contactsByGroup,
45
+ LastUpdated = DateTime.Now
46
+ };
47
+ }
48
+
49
+ // Detect potential duplicates
50
+ public async Task<List<ContactDuplicate>> FindDuplicatesAsync()
51
+ {
52
+ var duplicates = new List<ContactDuplicate>();
53
+ var contacts = await _context.Contacts.ToListAsync();
54
+
55
+ for (int i = 0; i < contacts.Count; i++)
56
+ {
57
+ for (int j = i + 1; j < contacts.Count; j++)
58
+ {
59
+ var similarity = CalculateSimilarity(contacts[i], contacts[j]);
60
+ if (similarity >= 0.7) // 70% similarity threshold
61
+ {
62
+ duplicates.Add(new ContactDuplicate
63
+ {
64
+ Contact1Id = contacts[i].Id,
65
+ Contact1Name = $"{contacts[i].FirstName} {contacts[i].LastName}",
66
+ Contact2Id = contacts[j].Id,
67
+ Contact2Name = $"{contacts[j].FirstName} {contacts[j].LastName}",
68
+ SimilarityScore = similarity
69
+ });
70
+ }
71
+ }
72
+ }
73
+
74
+ return duplicates.OrderByDescending(x => x.SimilarityScore).ToList();
75
+ }
76
+
77
+ private double CalculateSimilarity(Contact c1, Contact c2)
78
+ {
79
+ double score = 0;
80
+ int factors = 0;
81
+
82
+ // Compare names
83
+ if (!string.IsNullOrEmpty(c1.FirstName) && !string.IsNullOrEmpty(c2.FirstName))
84
+ {
85
+ if (c1.FirstName.Equals(c2.FirstName, StringComparison.OrdinalIgnoreCase))
86
+ score += 1;
87
+ else if (LevenshteinDistance(c1.FirstName, c2.FirstName) <= 2)
88
+ score += 0.8;
89
+ factors++;
90
+ }
91
+
92
+ if (!string.IsNullOrEmpty(c1.LastName) && !string.IsNullOrEmpty(c2.LastName))
93
+ {
94
+ if (c1.LastName.Equals(c2.LastName, StringComparison.OrdinalIgnoreCase))
95
+ score += 1;
96
+ else if (LevenshteinDistance(c1.LastName, c2.LastName) <= 2)
97
+ score += 0.8;
98
+ factors++;
99
+ }
100
+
101
+ // Compare emails
102
+ if (!string.IsNullOrEmpty(c1.Email) && !string.IsNullOrEmpty(c2.Email))
103
+ {
104
+ if (c1.Email.Equals(c2.Email, StringComparison.OrdinalIgnoreCase))
105
+ score += 2; // High weight for email match
106
+ factors++;
107
+ }
108
+
109
+ // Compare phones
110
+ if (!string.IsNullOrEmpty(c1.Mobile1) && !string.IsNullOrEmpty(c2.Mobile1))
111
+ {
112
+ if (c1.Mobile1.Equals(c2.Mobile1))
113
+ score += 2; // High weight for phone match
114
+ factors++;
115
+ }
116
+
117
+ return factors > 0 ? score / (factors * 1.5) : 0;
118
+ }
119
+
120
+ private int LevenshteinDistance(string s1, string s2)
121
+ {
122
+ var length1 = s1.Length;
123
+ var length2 = s2.Length;
124
+ var d = new int[length1 + 1, length2 + 1];
125
+
126
+ for (int i = 0; i <= length1; i++)
127
+ d[i, 0] = i;
128
+
129
+ for (int j = 0; j <= length2; j++)
130
+ d[0, j] = j;
131
+
132
+ for (int i = 1; i <= length1; i++)
133
+ for (int j = 1; j <= length2; j++)
134
+ {
135
+ int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
136
+ d[i, j] = Math.Min(Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1), d[i - 1, j - 1] + cost);
137
+ }
138
+
139
+ return d[length1, length2];
140
+ }
141
+ }
142
+
143
+ public class ContactStatistics
144
+ {
145
+ public int TotalContacts { get; set; }
146
+ public int ContactsWithEmail { get; set; }
147
+ public int ContactsWithPhone { get; set; }
148
+ public List<CityStatistic> TopCities { get; set; } = new();
149
+ public List<GroupStatistic> ContactsByGroup { get; set; } = new();
150
+ public DateTime LastUpdated { get; set; }
151
+ }
152
+
153
+ public class CityStatistic
154
+ {
155
+ public string City { get; set; }
156
+ public int Count { get; set; }
157
+ }
158
+
159
+ public class GroupStatistic
160
+ {
161
+ public string GroupName { get; set; }
162
+ public int Count { get; set; }
163
+ }
164
+
165
+ public class ContactDuplicate
166
+ {
167
+ public int Contact1Id { get; set; }
168
+ public string Contact1Name { get; set; }
169
+ public int Contact2Id { get; set; }
170
+ public string Contact2Name { get; set; }
171
+ public double SimilarityScore { get; set; }
172
+ }
173
+ }
ContactManagementAPI/Services/FileUploadService.cs ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using Microsoft.AspNetCore.Http;
2
+ using System;
3
+ using System.IO;
4
+ using System.Threading.Tasks;
5
+
6
+ namespace ContactManagementAPI.Services
7
+ {
8
+ public class FileUploadService
9
+ {
10
+ private readonly IWebHostEnvironment _environment;
11
+ private readonly IConfiguration _configuration;
12
+ private const long MAX_PHOTO_SIZE = 5242880; // 5MB
13
+ private const long MAX_DOCUMENT_SIZE = 10485760; // 10MB
14
+
15
+ public FileUploadService(IWebHostEnvironment environment, IConfiguration configuration)
16
+ {
17
+ _environment = environment;
18
+ _configuration = configuration;
19
+ }
20
+
21
+ private string GetUploadsRoot()
22
+ {
23
+ var uploadsRoot = Environment.GetEnvironmentVariable("UPLOADS_ROOT");
24
+ if (!string.IsNullOrWhiteSpace(uploadsRoot))
25
+ {
26
+ Directory.CreateDirectory(uploadsRoot);
27
+ return uploadsRoot;
28
+ }
29
+
30
+ return Path.Combine(_environment.WebRootPath, "uploads");
31
+ }
32
+
33
+ public async Task<(bool Success, string FilePath, string ErrorMessage)> UploadPhotoAsync(IFormFile file, int contactId)
34
+ {
35
+ try
36
+ {
37
+ if (file == null || file.Length == 0)
38
+ return (false, "", "No file selected");
39
+
40
+ if (file.Length > MAX_PHOTO_SIZE)
41
+ return (false, "", "Photo size exceeds maximum limit of 5MB");
42
+
43
+ var allowedExtensions = _configuration.GetSection("FileUpload:AllowedPhotoExtensions").Get<string[]>();
44
+ var fileExtension = Path.GetExtension(file.FileName).ToLower();
45
+
46
+ if (!allowedExtensions.Contains(fileExtension))
47
+ return (false, "", "Invalid file format. Allowed formats: JPG, PNG, GIF, BMP");
48
+
49
+ var uploadPath = Path.Combine(GetUploadsRoot(), "photos");
50
+ if (!Directory.Exists(uploadPath))
51
+ Directory.CreateDirectory(uploadPath);
52
+
53
+ var fileName = $"{contactId}_{DateTime.Now.Ticks}{fileExtension}";
54
+ var filePath = Path.Combine(uploadPath, fileName);
55
+
56
+ using (var stream = new FileStream(filePath, FileMode.Create))
57
+ {
58
+ await file.CopyToAsync(stream);
59
+ }
60
+
61
+ return (true, $"/uploads/photos/{fileName}", "");
62
+ }
63
+ catch (Exception ex)
64
+ {
65
+ return (false, "", $"Error uploading file: {ex.Message}");
66
+ }
67
+ }
68
+
69
+ public async Task<(bool Success, string FilePath, string ErrorMessage)> UploadDocumentAsync(IFormFile file, int contactId)
70
+ {
71
+ try
72
+ {
73
+ if (file == null || file.Length == 0)
74
+ return (false, "", "No file selected");
75
+
76
+ if (file.Length > MAX_DOCUMENT_SIZE)
77
+ return (false, "", "Document size exceeds maximum limit of 10MB");
78
+
79
+ var allowedExtensions = _configuration.GetSection("FileUpload:AllowedDocumentExtensions").Get<string[]>();
80
+ var fileExtension = Path.GetExtension(file.FileName).ToLower();
81
+
82
+ if (!allowedExtensions.Contains(fileExtension))
83
+ return (false, "", "Invalid file format");
84
+
85
+ var uploadPath = Path.Combine(GetUploadsRoot(), "documents");
86
+ if (!Directory.Exists(uploadPath))
87
+ Directory.CreateDirectory(uploadPath);
88
+
89
+ var fileName = $"{contactId}_{DateTime.Now.Ticks}{fileExtension}";
90
+ var filePath = Path.Combine(uploadPath, fileName);
91
+
92
+ using (var stream = new FileStream(filePath, FileMode.Create))
93
+ {
94
+ await file.CopyToAsync(stream);
95
+ }
96
+
97
+ return (true, $"/uploads/documents/{fileName}", "");
98
+ }
99
+ catch (Exception ex)
100
+ {
101
+ return (false, "", $"Error uploading file: {ex.Message}");
102
+ }
103
+ }
104
+
105
+ public bool DeleteFile(string filePath)
106
+ {
107
+ try
108
+ {
109
+ var trimmed = (filePath ?? string.Empty).Trim();
110
+ if (string.IsNullOrWhiteSpace(trimmed))
111
+ {
112
+ return false;
113
+ }
114
+
115
+ var normalized = trimmed.StartsWith("/") ? trimmed : "/" + trimmed;
116
+ var uploadsRoot = GetUploadsRoot();
117
+ string fullPath;
118
+
119
+ if (normalized.StartsWith("/uploads/", StringComparison.OrdinalIgnoreCase))
120
+ {
121
+ var relative = normalized.Substring("/uploads/".Length).Replace('/', Path.DirectorySeparatorChar);
122
+ fullPath = Path.Combine(uploadsRoot, relative);
123
+ }
124
+ else
125
+ {
126
+ fullPath = Path.Combine(_environment.WebRootPath, normalized.TrimStart('/').Replace('/', Path.DirectorySeparatorChar));
127
+ }
128
+
129
+ if (File.Exists(fullPath))
130
+ {
131
+ File.Delete(fullPath);
132
+ return true;
133
+ }
134
+ return false;
135
+ }
136
+ catch
137
+ {
138
+ return false;
139
+ }
140
+ }
141
+ }
142
+ }
ContactManagementAPI/Services/ImportExportService.cs ADDED
@@ -0,0 +1,573 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using ContactManagementAPI.Models;
2
+ using CsvHelper;
3
+ using CsvHelper.Configuration;
4
+ using OfficeOpenXml;
5
+ using QuestPDF.Fluent;
6
+ using QuestPDF.Helpers;
7
+ using QuestPDF.Infrastructure;
8
+ using System.Globalization;
9
+ using System.Text;
10
+
11
+ namespace ContactManagementAPI.Services
12
+ {
13
+ public class ImportExportService
14
+ {
15
+ public ImportExportService()
16
+ {
17
+ // Set EPPlus license context
18
+ ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
19
+
20
+ // Set QuestPDF license
21
+ QuestPDF.Settings.License = LicenseType.Community;
22
+ }
23
+
24
+ #region Import Methods
25
+
26
+ public async Task<(List<Contact> contacts, List<string> errors)> ImportFromExcel(Stream stream)
27
+ {
28
+ var contacts = new List<Contact>();
29
+ var errors = new List<string>();
30
+
31
+ try
32
+ {
33
+ using var package = new ExcelPackage(stream);
34
+ var worksheet = package.Workbook.Worksheets.FirstOrDefault();
35
+
36
+ if (worksheet == null)
37
+ {
38
+ errors.Add("No worksheet found in the Excel file.");
39
+ return (contacts, errors);
40
+ }
41
+
42
+ var rowCount = worksheet.Dimension?.Rows ?? 0;
43
+
44
+ // Start from row 2 (skip header)
45
+ for (int row = 2; row <= rowCount; row++)
46
+ {
47
+ try
48
+ {
49
+ var contact = new Contact
50
+ {
51
+ FirstName = worksheet.Cells[row, 1].Value?.ToString() ?? "",
52
+ LastName = worksheet.Cells[row, 2].Value?.ToString() ?? "",
53
+ NickName = worksheet.Cells[row, 3].Value?.ToString(),
54
+ Gender = worksheet.Cells[row, 4].Value?.ToString(),
55
+ DateOfBirth = DateTime.TryParse(worksheet.Cells[row, 5].Value?.ToString(), out var dob) ? dob : null,
56
+ Email = worksheet.Cells[row, 6].Value?.ToString(),
57
+ Mobile1 = worksheet.Cells[row, 7].Value?.ToString(),
58
+ Mobile2 = worksheet.Cells[row, 8].Value?.ToString(),
59
+ Mobile3 = worksheet.Cells[row, 9].Value?.ToString(),
60
+ WhatsAppNumber = worksheet.Cells[row, 10].Value?.ToString(),
61
+ PassportNumber = worksheet.Cells[row, 11].Value?.ToString(),
62
+ PanNumber = worksheet.Cells[row, 12].Value?.ToString(),
63
+ AadharNumber = worksheet.Cells[row, 13].Value?.ToString(),
64
+ DrivingLicenseNumber = worksheet.Cells[row, 14].Value?.ToString(),
65
+ VotersId = worksheet.Cells[row, 15].Value?.ToString(),
66
+ BankAccountNumber = worksheet.Cells[row, 16].Value?.ToString(),
67
+ BankName = worksheet.Cells[row, 17].Value?.ToString(),
68
+ BranchName = worksheet.Cells[row, 18].Value?.ToString(),
69
+ IfscCode = worksheet.Cells[row, 19].Value?.ToString(),
70
+ Address = worksheet.Cells[row, 20].Value?.ToString(),
71
+ City = worksheet.Cells[row, 21].Value?.ToString(),
72
+ State = worksheet.Cells[row, 22].Value?.ToString(),
73
+ PostalCode = worksheet.Cells[row, 23].Value?.ToString(),
74
+ Country = worksheet.Cells[row, 24].Value?.ToString(),
75
+ OtherDetails = worksheet.Cells[row, 25].Value?.ToString(),
76
+ CreatedAt = DateTime.Now,
77
+ UpdatedAt = DateTime.Now
78
+ };
79
+
80
+ if (!string.IsNullOrWhiteSpace(contact.FirstName) || !string.IsNullOrWhiteSpace(contact.LastName))
81
+ {
82
+ contacts.Add(contact);
83
+ }
84
+ }
85
+ catch (Exception ex)
86
+ {
87
+ errors.Add($"Row {row}: {ex.Message}");
88
+ }
89
+ }
90
+ }
91
+ catch (Exception ex)
92
+ {
93
+ errors.Add($"Error reading Excel file: {ex.Message}");
94
+ }
95
+
96
+ return (contacts, errors);
97
+ }
98
+
99
+ public async Task<(List<Contact> contacts, List<string> errors)> ImportFromCsv(Stream stream)
100
+ {
101
+ var contacts = new List<Contact>();
102
+ var errors = new List<string>();
103
+
104
+ try
105
+ {
106
+ using var reader = new StreamReader(stream);
107
+ var config = new CsvConfiguration(CultureInfo.InvariantCulture)
108
+ {
109
+ HasHeaderRecord = true,
110
+ MissingFieldFound = null,
111
+ BadDataFound = null
112
+ };
113
+
114
+ using var csv = new CsvReader(reader, config);
115
+
116
+ csv.Read();
117
+ csv.ReadHeader();
118
+ int rowNumber = 1;
119
+
120
+ while (csv.Read())
121
+ {
122
+ rowNumber++;
123
+ try
124
+ {
125
+ string? ReadField(string name, int index)
126
+ {
127
+ if (csv.TryGetField<string>(name, out var namedValue))
128
+ return namedValue;
129
+
130
+ if (csv.TryGetField<string>(index, out var indexedValue))
131
+ return indexedValue;
132
+
133
+ return null;
134
+ }
135
+
136
+ var contact = new Contact
137
+ {
138
+ FirstName = ReadField("FirstName", 0) ?? "",
139
+ LastName = ReadField("LastName", 1) ?? "",
140
+ NickName = ReadField("NickName", 2),
141
+ Gender = ReadField("Gender", 3),
142
+ DateOfBirth = DateTime.TryParse(ReadField("DateOfBirth", 4), out var dob) ? dob : null,
143
+ Email = ReadField("Email", 5),
144
+ Mobile1 = ReadField("Mobile1", 6),
145
+ Mobile2 = ReadField("Mobile2", 7),
146
+ Mobile3 = ReadField("Mobile3", 8),
147
+ WhatsAppNumber = ReadField("WhatsAppNumber", 9),
148
+ PassportNumber = ReadField("PassportNumber", 10),
149
+ PanNumber = ReadField("PanNumber", 11),
150
+ AadharNumber = ReadField("AadharNumber", 12),
151
+ DrivingLicenseNumber = ReadField("DrivingLicenseNumber", 13),
152
+ VotersId = ReadField("VotersId", 14),
153
+ BankAccountNumber = ReadField("BankAccountNumber", 15),
154
+ BankName = ReadField("BankName", 16),
155
+ BranchName = ReadField("BranchName", 17),
156
+ IfscCode = ReadField("IfscCode", 18),
157
+ Address = ReadField("Address", 19),
158
+ City = ReadField("City", 20),
159
+ State = ReadField("State", 21),
160
+ PostalCode = ReadField("PostalCode", 22),
161
+ Country = ReadField("Country", 23),
162
+ OtherDetails = ReadField("OtherDetails", 24),
163
+ CreatedAt = DateTime.Now,
164
+ UpdatedAt = DateTime.Now
165
+ };
166
+
167
+ if (!string.IsNullOrWhiteSpace(contact.FirstName) || !string.IsNullOrWhiteSpace(contact.LastName))
168
+ {
169
+ contacts.Add(contact);
170
+ }
171
+ }
172
+ catch (Exception ex)
173
+ {
174
+ errors.Add($"Row {rowNumber}: {ex.Message}");
175
+ }
176
+ }
177
+ }
178
+ catch (Exception ex)
179
+ {
180
+ errors.Add($"Error reading CSV file: {ex.Message}");
181
+ }
182
+
183
+ return (contacts, errors);
184
+ }
185
+
186
+ #endregion
187
+
188
+ #region Export Methods
189
+
190
+ public async Task<byte[]> ExportToExcel(List<Contact> contacts)
191
+ {
192
+ using var package = new ExcelPackage();
193
+ var worksheet = package.Workbook.Worksheets.Add("Contacts");
194
+
195
+ // Add headers
196
+ worksheet.Cells[1, 1].Value = "First Name";
197
+ worksheet.Cells[1, 2].Value = "Last Name";
198
+ worksheet.Cells[1, 3].Value = "Nick Name";
199
+ worksheet.Cells[1, 4].Value = "Gender";
200
+ worksheet.Cells[1, 5].Value = "Date Of Birth";
201
+ worksheet.Cells[1, 6].Value = "Email";
202
+ worksheet.Cells[1, 7].Value = "Mobile 1";
203
+ worksheet.Cells[1, 8].Value = "Mobile 2";
204
+ worksheet.Cells[1, 9].Value = "Mobile 3";
205
+ worksheet.Cells[1, 10].Value = "WhatsApp";
206
+ worksheet.Cells[1, 11].Value = "Passport Number";
207
+ worksheet.Cells[1, 12].Value = "PAN Number";
208
+ worksheet.Cells[1, 13].Value = "Aadhar Number";
209
+ worksheet.Cells[1, 14].Value = "Driving License Number";
210
+ worksheet.Cells[1, 15].Value = "Voters ID";
211
+ worksheet.Cells[1, 16].Value = "Bank Account Number";
212
+ worksheet.Cells[1, 17].Value = "Bank Name";
213
+ worksheet.Cells[1, 18].Value = "Branch Name";
214
+ worksheet.Cells[1, 19].Value = "IFSC Code";
215
+ worksheet.Cells[1, 20].Value = "Address";
216
+ worksheet.Cells[1, 21].Value = "City";
217
+ worksheet.Cells[1, 22].Value = "State";
218
+ worksheet.Cells[1, 23].Value = "Postal Code";
219
+ worksheet.Cells[1, 24].Value = "Country";
220
+ worksheet.Cells[1, 25].Value = "Other Details";
221
+ worksheet.Cells[1, 26].Value = "Group";
222
+ worksheet.Cells[1, 27].Value = "Created At";
223
+
224
+ // Style headers
225
+ using (var range = worksheet.Cells[1, 1, 1, 27])
226
+ {
227
+ range.Style.Font.Bold = true;
228
+ range.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
229
+ range.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightBlue);
230
+ }
231
+
232
+ // Add data
233
+ for (int i = 0; i < contacts.Count; i++)
234
+ {
235
+ var contact = contacts[i];
236
+ var row = i + 2;
237
+
238
+ worksheet.Cells[row, 1].Value = contact.FirstName;
239
+ worksheet.Cells[row, 2].Value = contact.LastName;
240
+ worksheet.Cells[row, 3].Value = contact.NickName;
241
+ worksheet.Cells[row, 4].Value = contact.Gender;
242
+ worksheet.Cells[row, 5].Value = contact.DateOfBirth?.ToString("yyyy-MM-dd");
243
+ worksheet.Cells[row, 6].Value = contact.Email;
244
+ worksheet.Cells[row, 7].Value = contact.Mobile1;
245
+ worksheet.Cells[row, 8].Value = contact.Mobile2;
246
+ worksheet.Cells[row, 9].Value = contact.Mobile3;
247
+ worksheet.Cells[row, 10].Value = contact.WhatsAppNumber;
248
+ worksheet.Cells[row, 11].Value = contact.PassportNumber;
249
+ worksheet.Cells[row, 12].Value = contact.PanNumber;
250
+ worksheet.Cells[row, 13].Value = contact.AadharNumber;
251
+ worksheet.Cells[row, 14].Value = contact.DrivingLicenseNumber;
252
+ worksheet.Cells[row, 15].Value = contact.VotersId;
253
+ worksheet.Cells[row, 16].Value = contact.BankAccountNumber;
254
+ worksheet.Cells[row, 17].Value = contact.BankName;
255
+ worksheet.Cells[row, 18].Value = contact.BranchName;
256
+ worksheet.Cells[row, 19].Value = contact.IfscCode;
257
+ worksheet.Cells[row, 20].Value = contact.Address;
258
+ worksheet.Cells[row, 21].Value = contact.City;
259
+ worksheet.Cells[row, 22].Value = contact.State;
260
+ worksheet.Cells[row, 23].Value = contact.PostalCode;
261
+ worksheet.Cells[row, 24].Value = contact.Country;
262
+ worksheet.Cells[row, 25].Value = contact.OtherDetails;
263
+ worksheet.Cells[row, 26].Value = contact.Group?.Name;
264
+ worksheet.Cells[row, 27].Value = contact.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss");
265
+ }
266
+
267
+ // Auto-fit columns
268
+ worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns();
269
+
270
+ return await Task.FromResult(package.GetAsByteArray());
271
+ }
272
+
273
+ public async Task<byte[]> ExportToCsv(List<Contact> contacts)
274
+ {
275
+ using var memoryStream = new MemoryStream();
276
+ using var writer = new StreamWriter(memoryStream, Encoding.UTF8);
277
+ using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
278
+
279
+ // Write headers
280
+ csv.WriteField("FirstName");
281
+ csv.WriteField("LastName");
282
+ csv.WriteField("NickName");
283
+ csv.WriteField("Gender");
284
+ csv.WriteField("DateOfBirth");
285
+ csv.WriteField("Email");
286
+ csv.WriteField("Mobile1");
287
+ csv.WriteField("Mobile2");
288
+ csv.WriteField("Mobile3");
289
+ csv.WriteField("WhatsAppNumber");
290
+ csv.WriteField("PassportNumber");
291
+ csv.WriteField("PanNumber");
292
+ csv.WriteField("AadharNumber");
293
+ csv.WriteField("DrivingLicenseNumber");
294
+ csv.WriteField("VotersId");
295
+ csv.WriteField("BankAccountNumber");
296
+ csv.WriteField("BankName");
297
+ csv.WriteField("BranchName");
298
+ csv.WriteField("IfscCode");
299
+ csv.WriteField("Address");
300
+ csv.WriteField("City");
301
+ csv.WriteField("State");
302
+ csv.WriteField("PostalCode");
303
+ csv.WriteField("Country");
304
+ csv.WriteField("OtherDetails");
305
+ csv.WriteField("Group");
306
+ csv.WriteField("CreatedAt");
307
+ csv.NextRecord();
308
+
309
+ // Write data
310
+ foreach (var contact in contacts)
311
+ {
312
+ csv.WriteField(contact.FirstName);
313
+ csv.WriteField(contact.LastName);
314
+ csv.WriteField(contact.NickName);
315
+ csv.WriteField(contact.Gender);
316
+ csv.WriteField(contact.DateOfBirth?.ToString("yyyy-MM-dd"));
317
+ csv.WriteField(contact.Email);
318
+ csv.WriteField(contact.Mobile1);
319
+ csv.WriteField(contact.Mobile2);
320
+ csv.WriteField(contact.Mobile3);
321
+ csv.WriteField(contact.WhatsAppNumber);
322
+ csv.WriteField(contact.PassportNumber);
323
+ csv.WriteField(contact.PanNumber);
324
+ csv.WriteField(contact.AadharNumber);
325
+ csv.WriteField(contact.DrivingLicenseNumber);
326
+ csv.WriteField(contact.VotersId);
327
+ csv.WriteField(contact.BankAccountNumber);
328
+ csv.WriteField(contact.BankName);
329
+ csv.WriteField(contact.BranchName);
330
+ csv.WriteField(contact.IfscCode);
331
+ csv.WriteField(contact.Address);
332
+ csv.WriteField(contact.City);
333
+ csv.WriteField(contact.State);
334
+ csv.WriteField(contact.PostalCode);
335
+ csv.WriteField(contact.Country);
336
+ csv.WriteField(contact.OtherDetails);
337
+ csv.WriteField(contact.Group?.Name);
338
+ csv.WriteField(contact.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss"));
339
+ csv.NextRecord();
340
+ }
341
+
342
+ await writer.FlushAsync();
343
+ return memoryStream.ToArray();
344
+ }
345
+
346
+ public async Task<byte[]> ExportToPdf(List<Contact> contacts)
347
+ {
348
+ var document = Document.Create(container =>
349
+ {
350
+ container.Page(page =>
351
+ {
352
+ page.Size(PageSizes.A4.Landscape());
353
+ page.Margin(1, Unit.Centimetre);
354
+ page.PageColor(Colors.White);
355
+ page.DefaultTextStyle(x => x.FontSize(9));
356
+
357
+ page.Header()
358
+ .Text("Contact List")
359
+ .SemiBold().FontSize(20).FontColor(Colors.Blue.Medium);
360
+
361
+ page.Content()
362
+ .PaddingVertical(1, Unit.Centimetre)
363
+ .Table(table =>
364
+ {
365
+ // Define columns
366
+ table.ColumnsDefinition(columns =>
367
+ {
368
+ columns.RelativeColumn(2); // Name
369
+ columns.RelativeColumn(2); // Email
370
+ columns.RelativeColumn(1.5f); // Mobile1
371
+ columns.RelativeColumn(1.5f); // Mobile2
372
+ columns.RelativeColumn(2); // City
373
+ columns.RelativeColumn(1.5f); // State
374
+ });
375
+
376
+ // Header
377
+ table.Header(header =>
378
+ {
379
+ header.Cell().Element(CellStyle).Text("Name").Bold();
380
+ header.Cell().Element(CellStyle).Text("Email").Bold();
381
+ header.Cell().Element(CellStyle).Text("Mobile 1").Bold();
382
+ header.Cell().Element(CellStyle).Text("Mobile 2").Bold();
383
+ header.Cell().Element(CellStyle).Text("City").Bold();
384
+ header.Cell().Element(CellStyle).Text("State").Bold();
385
+
386
+ static IContainer CellStyle(IContainer container)
387
+ {
388
+ return container.DefaultTextStyle(x => x.SemiBold())
389
+ .PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black);
390
+ }
391
+ });
392
+
393
+ // Data rows
394
+ foreach (var contact in contacts)
395
+ {
396
+ table.Cell().Element(CellStyle).Text($"{contact.FirstName} {contact.LastName}");
397
+ table.Cell().Element(CellStyle).Text(contact.Email ?? "-");
398
+ table.Cell().Element(CellStyle).Text(contact.Mobile1 ?? "-");
399
+ table.Cell().Element(CellStyle).Text(contact.Mobile2 ?? "-");
400
+ table.Cell().Element(CellStyle).Text(contact.City ?? "-");
401
+ table.Cell().Element(CellStyle).Text(contact.State ?? "-");
402
+
403
+ static IContainer CellStyle(IContainer container)
404
+ {
405
+ return container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2)
406
+ .PaddingVertical(5);
407
+ }
408
+ }
409
+ });
410
+
411
+ page.Footer()
412
+ .AlignCenter()
413
+ .Text(x =>
414
+ {
415
+ x.Span("Page ");
416
+ x.CurrentPageNumber();
417
+ x.Span(" of ");
418
+ x.TotalPages();
419
+ x.Span(" | Generated on ");
420
+ x.Span(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
421
+ });
422
+ });
423
+ });
424
+
425
+ return await Task.FromResult(document.GeneratePdf());
426
+ }
427
+
428
+ #endregion
429
+
430
+ #region Template Methods
431
+
432
+ public async Task<byte[]> GenerateExcelTemplate()
433
+ {
434
+ using var package = new ExcelPackage();
435
+ var worksheet = package.Workbook.Worksheets.Add("Contacts Template");
436
+
437
+ // Add headers
438
+ worksheet.Cells[1, 1].Value = "FirstName";
439
+ worksheet.Cells[1, 2].Value = "LastName";
440
+ worksheet.Cells[1, 3].Value = "NickName";
441
+ worksheet.Cells[1, 4].Value = "Gender";
442
+ worksheet.Cells[1, 5].Value = "DateOfBirth";
443
+ worksheet.Cells[1, 6].Value = "Email";
444
+ worksheet.Cells[1, 7].Value = "Mobile1";
445
+ worksheet.Cells[1, 8].Value = "Mobile2";
446
+ worksheet.Cells[1, 9].Value = "Mobile3";
447
+ worksheet.Cells[1, 10].Value = "WhatsAppNumber";
448
+ worksheet.Cells[1, 11].Value = "PassportNumber";
449
+ worksheet.Cells[1, 12].Value = "PanNumber";
450
+ worksheet.Cells[1, 13].Value = "AadharNumber";
451
+ worksheet.Cells[1, 14].Value = "DrivingLicenseNumber";
452
+ worksheet.Cells[1, 15].Value = "VotersId";
453
+ worksheet.Cells[1, 16].Value = "BankAccountNumber";
454
+ worksheet.Cells[1, 17].Value = "BankName";
455
+ worksheet.Cells[1, 18].Value = "BranchName";
456
+ worksheet.Cells[1, 19].Value = "IfscCode";
457
+ worksheet.Cells[1, 20].Value = "Address";
458
+ worksheet.Cells[1, 21].Value = "City";
459
+ worksheet.Cells[1, 22].Value = "State";
460
+ worksheet.Cells[1, 23].Value = "PostalCode";
461
+ worksheet.Cells[1, 24].Value = "Country";
462
+ worksheet.Cells[1, 25].Value = "OtherDetails";
463
+
464
+ // Style headers
465
+ using (var range = worksheet.Cells[1, 1, 1, 25])
466
+ {
467
+ range.Style.Font.Bold = true;
468
+ range.Style.Fill.PatternType = OfficeOpenXml.Style.ExcelFillStyle.Solid;
469
+ range.Style.Fill.BackgroundColor.SetColor(System.Drawing.Color.LightGreen);
470
+ }
471
+
472
+ // Add sample row
473
+ worksheet.Cells[2, 1].Value = "John";
474
+ worksheet.Cells[2, 2].Value = "Doe";
475
+ worksheet.Cells[2, 3].Value = "Johnny";
476
+ worksheet.Cells[2, 4].Value = "Male";
477
+ worksheet.Cells[2, 5].Value = "1990-01-01";
478
+ worksheet.Cells[2, 6].Value = "john.doe@example.com";
479
+ worksheet.Cells[2, 7].Value = "+1234567890";
480
+ worksheet.Cells[2, 8].Value = "+0987654321";
481
+ worksheet.Cells[2, 9].Value = "";
482
+ worksheet.Cells[2, 10].Value = "+1234567890";
483
+ worksheet.Cells[2, 11].Value = "P1234567";
484
+ worksheet.Cells[2, 12].Value = "ABCDE1234F";
485
+ worksheet.Cells[2, 13].Value = "1234-5678-9012";
486
+ worksheet.Cells[2, 14].Value = "DL-12345-2020";
487
+ worksheet.Cells[2, 15].Value = "VOTER12345";
488
+ worksheet.Cells[2, 16].Value = "123456789012";
489
+ worksheet.Cells[2, 17].Value = "State Bank";
490
+ worksheet.Cells[2, 18].Value = "Main Branch";
491
+ worksheet.Cells[2, 19].Value = "SBIN0001234";
492
+ worksheet.Cells[2, 20].Value = "123 Main St";
493
+ worksheet.Cells[2, 21].Value = "New York";
494
+ worksheet.Cells[2, 22].Value = "NY";
495
+ worksheet.Cells[2, 23].Value = "10001";
496
+ worksheet.Cells[2, 24].Value = "USA";
497
+ worksheet.Cells[2, 25].Value = "Sample contact";
498
+
499
+ // Auto-fit columns
500
+ worksheet.Cells[worksheet.Dimension.Address].AutoFitColumns();
501
+
502
+ return await Task.FromResult(package.GetAsByteArray());
503
+ }
504
+
505
+ public async Task<byte[]> GenerateCsvTemplate()
506
+ {
507
+ using var memoryStream = new MemoryStream();
508
+ using var writer = new StreamWriter(memoryStream, Encoding.UTF8);
509
+ using var csv = new CsvWriter(writer, CultureInfo.InvariantCulture);
510
+
511
+ // Write headers
512
+ csv.WriteField("FirstName");
513
+ csv.WriteField("LastName");
514
+ csv.WriteField("NickName");
515
+ csv.WriteField("Gender");
516
+ csv.WriteField("DateOfBirth");
517
+ csv.WriteField("Email");
518
+ csv.WriteField("Mobile1");
519
+ csv.WriteField("Mobile2");
520
+ csv.WriteField("Mobile3");
521
+ csv.WriteField("WhatsAppNumber");
522
+ csv.WriteField("PassportNumber");
523
+ csv.WriteField("PanNumber");
524
+ csv.WriteField("AadharNumber");
525
+ csv.WriteField("DrivingLicenseNumber");
526
+ csv.WriteField("VotersId");
527
+ csv.WriteField("BankAccountNumber");
528
+ csv.WriteField("BankName");
529
+ csv.WriteField("BranchName");
530
+ csv.WriteField("IfscCode");
531
+ csv.WriteField("Address");
532
+ csv.WriteField("City");
533
+ csv.WriteField("State");
534
+ csv.WriteField("PostalCode");
535
+ csv.WriteField("Country");
536
+ csv.WriteField("OtherDetails");
537
+ csv.NextRecord();
538
+
539
+ // Write sample row
540
+ csv.WriteField("John");
541
+ csv.WriteField("Doe");
542
+ csv.WriteField("Johnny");
543
+ csv.WriteField("Male");
544
+ csv.WriteField("1990-01-01");
545
+ csv.WriteField("john.doe@example.com");
546
+ csv.WriteField("+1234567890");
547
+ csv.WriteField("+0987654321");
548
+ csv.WriteField("");
549
+ csv.WriteField("+1234567890");
550
+ csv.WriteField("P1234567");
551
+ csv.WriteField("ABCDE1234F");
552
+ csv.WriteField("1234-5678-9012");
553
+ csv.WriteField("DL-12345-2020");
554
+ csv.WriteField("VOTER12345");
555
+ csv.WriteField("123456789012");
556
+ csv.WriteField("State Bank");
557
+ csv.WriteField("Main Branch");
558
+ csv.WriteField("SBIN0001234");
559
+ csv.WriteField("123 Main St");
560
+ csv.WriteField("New York");
561
+ csv.WriteField("NY");
562
+ csv.WriteField("10001");
563
+ csv.WriteField("USA");
564
+ csv.WriteField("Sample contact");
565
+ csv.NextRecord();
566
+
567
+ await writer.FlushAsync();
568
+ return memoryStream.ToArray();
569
+ }
570
+
571
+ #endregion
572
+ }
573
+ }
ContactManagementAPI/Services/SeedData.cs ADDED
@@ -0,0 +1,333 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using System.Linq;
3
+ using System.Text.RegularExpressions;
4
+ using ContactManagementAPI.Data;
5
+ using ContactManagementAPI.Models;
6
+ using Microsoft.AspNetCore.Identity;
7
+
8
+ namespace ContactManagementAPI.Services
9
+ {
10
+ public static class SeedData
11
+ {
12
+ public const string SuperAdminUserName = "abrahamcbe@gmail.com";
13
+ public const string SuperAdminDefaultPassword = "M@ld1ves";
14
+
15
+ private static string GetSuperAdminPassword()
16
+ {
17
+ var fromEnv = Environment.GetEnvironmentVariable("SUPERADMIN_PASSWORD");
18
+ return string.IsNullOrWhiteSpace(fromEnv) ? SuperAdminDefaultPassword : fromEnv;
19
+ }
20
+
21
+ public static void Initialize(ApplicationDbContext context)
22
+ {
23
+ var hasher = new PasswordHasher<AppUser>();
24
+
25
+ if (!context.UserGroups.Any())
26
+ {
27
+ var adminGroup = new UserGroup
28
+ {
29
+ Name = "Administrators",
30
+ Description = "System administrators with full access",
31
+ CreatedAt = DateTime.Now
32
+ };
33
+
34
+ context.UserGroups.Add(adminGroup);
35
+ context.SaveChanges();
36
+
37
+ var groupRights = RightsCatalog.All
38
+ .Select(r => new GroupRight
39
+ {
40
+ UserGroupId = adminGroup.Id,
41
+ RightKey = r.Key,
42
+ IsGranted = true
43
+ })
44
+ .ToList();
45
+
46
+ context.GroupRights.AddRange(groupRights);
47
+ context.SaveChanges();
48
+ }
49
+ else
50
+ {
51
+ var adminGroup = context.UserGroups.FirstOrDefault(g => g.Name == "Administrators");
52
+ if (adminGroup != null)
53
+ {
54
+ var existingAdminRights = context.GroupRights
55
+ .Where(r => r.UserGroupId == adminGroup.Id)
56
+ .ToList();
57
+
58
+ foreach (var right in RightsCatalog.All)
59
+ {
60
+ var existing = existingAdminRights.FirstOrDefault(r => r.RightKey == right.Key);
61
+ if (existing == null)
62
+ {
63
+ context.GroupRights.Add(new GroupRight
64
+ {
65
+ UserGroupId = adminGroup.Id,
66
+ RightKey = right.Key,
67
+ IsGranted = true
68
+ });
69
+ }
70
+ else if (!existing.IsGranted)
71
+ {
72
+ existing.IsGranted = true;
73
+ }
74
+ }
75
+
76
+ context.SaveChanges();
77
+ }
78
+ }
79
+
80
+ if (!context.AppUsers.Any())
81
+ {
82
+ var adminGroupId = context.UserGroups
83
+ .Where(g => g.Name == "Administrators")
84
+ .Select(g => g.Id)
85
+ .First();
86
+
87
+ var adminUser = new AppUser
88
+ {
89
+ UserName = "admin",
90
+ FullName = "System Administrator",
91
+ IsAdmin = true,
92
+ IsActive = true,
93
+ GroupId = adminGroupId,
94
+ CreatedAt = DateTime.Now,
95
+ UpdatedAt = DateTime.Now
96
+ };
97
+
98
+ adminUser.PasswordHash = hasher.HashPassword(adminUser, "Admin@123");
99
+
100
+ context.AppUsers.Add(adminUser);
101
+ context.SaveChanges();
102
+ }
103
+
104
+ EnsureSuperAdminUser(context, hasher);
105
+
106
+ EnsureContactGroupUsers(context, hasher);
107
+
108
+ EnsureInitialContacts(context);
109
+ }
110
+
111
+ private static void EnsureInitialContacts(ApplicationDbContext context)
112
+ {
113
+ var now = DateTime.Now;
114
+
115
+ var familyGroupId = context.ContactGroups
116
+ .Where(g => g.Name == "Family")
117
+ .Select(g => (int?)g.Id)
118
+ .FirstOrDefault();
119
+
120
+ void EnsureNamedContact(string firstName, string? lastName, string? nickName)
121
+ {
122
+ var exists = context.Contacts.Any(c => c.FirstName.ToLower() == firstName.ToLower());
123
+ if (exists)
124
+ {
125
+ return;
126
+ }
127
+
128
+ context.Contacts.Add(new Contact
129
+ {
130
+ FirstName = firstName,
131
+ LastName = lastName,
132
+ NickName = nickName,
133
+ Mobile1 = null,
134
+ GroupId = familyGroupId,
135
+ CreatedAt = now,
136
+ UpdatedAt = now,
137
+ OtherDetails = "Seeded default contact (restored if missing)."
138
+ });
139
+ }
140
+
141
+ EnsureNamedContact("Abraham", "CBE", "Abraham");
142
+ EnsureNamedContact("Prema", null, "Prema");
143
+ EnsureNamedContact("Ponnuraj", null, "Ponnuraj");
144
+
145
+ // Add one test contact per contact group (to help validate group scoping after a fresh DB)
146
+ var contactGroups = context.ContactGroups
147
+ .OrderBy(g => g.Id)
148
+ .ToList();
149
+
150
+ foreach (var group in contactGroups)
151
+ {
152
+ var exists = context.Contacts.Any(c => c.GroupId == group.Id && c.OtherDetails == "Seeded test contact for group validation.");
153
+ if (exists)
154
+ {
155
+ continue;
156
+ }
157
+
158
+ context.Contacts.Add(new Contact
159
+ {
160
+ FirstName = $"{group.Name} Test",
161
+ LastName = "Contact",
162
+ NickName = group.Name,
163
+ Mobile1 = null,
164
+ GroupId = group.Id,
165
+ CreatedAt = now,
166
+ UpdatedAt = now,
167
+ OtherDetails = "Seeded test contact for group validation."
168
+ });
169
+ }
170
+
171
+ context.SaveChanges();
172
+ }
173
+
174
+ private static void EnsureSuperAdminUser(ApplicationDbContext context, PasswordHasher<AppUser> hasher)
175
+ {
176
+ var adminGroupId = context.UserGroups
177
+ .Where(g => g.Name == "Administrators")
178
+ .Select(g => g.Id)
179
+ .FirstOrDefault();
180
+
181
+ if (adminGroupId <= 0)
182
+ {
183
+ return;
184
+ }
185
+
186
+ var existing = context.AppUsers.FirstOrDefault(u => u.UserName == SuperAdminUserName);
187
+ if (existing == null)
188
+ {
189
+ var password = GetSuperAdminPassword();
190
+ var user = new AppUser
191
+ {
192
+ UserName = SuperAdminUserName,
193
+ FullName = "Abraham",
194
+ IsAdmin = true,
195
+ IsActive = true,
196
+ GroupId = adminGroupId,
197
+ CreatedAt = DateTime.Now,
198
+ UpdatedAt = DateTime.Now
199
+ };
200
+
201
+ user.PasswordHash = hasher.HashPassword(user, password);
202
+ context.AppUsers.Add(user);
203
+ context.SaveChanges();
204
+ return;
205
+ }
206
+
207
+ var updated = false;
208
+ var enforcedPassword = GetSuperAdminPassword();
209
+
210
+ if (!existing.IsAdmin)
211
+ {
212
+ existing.IsAdmin = true;
213
+ updated = true;
214
+ }
215
+
216
+ if (!existing.IsActive)
217
+ {
218
+ existing.IsActive = true;
219
+ updated = true;
220
+ }
221
+
222
+ if (existing.GroupId != adminGroupId)
223
+ {
224
+ existing.GroupId = adminGroupId;
225
+ updated = true;
226
+ }
227
+
228
+ if (string.IsNullOrWhiteSpace(existing.FullName))
229
+ {
230
+ existing.FullName = "Abraham";
231
+ updated = true;
232
+ }
233
+
234
+ // Enforce Super Admin password so it matches the required credential.
235
+ existing.PasswordHash = hasher.HashPassword(existing, enforcedPassword);
236
+ updated = true;
237
+
238
+ if (updated)
239
+ {
240
+ existing.UpdatedAt = DateTime.Now;
241
+ context.SaveChanges();
242
+ }
243
+ }
244
+
245
+ private static void EnsureContactGroupUsers(ApplicationDbContext context, PasswordHasher<AppUser> hasher)
246
+ {
247
+ var contactGroups = context.ContactGroups
248
+ .OrderBy(g => g.Name)
249
+ .ToList();
250
+
251
+ foreach (var contactGroup in contactGroups)
252
+ {
253
+ var userGroupName = $"ContactGroup - {contactGroup.Name}";
254
+ var userGroup = context.UserGroups.FirstOrDefault(g => g.Name == userGroupName);
255
+ if (userGroup == null)
256
+ {
257
+ userGroup = new UserGroup
258
+ {
259
+ Name = userGroupName,
260
+ Description = $"Full access group for contact group '{contactGroup.Name}'",
261
+ CreatedAt = DateTime.Now
262
+ };
263
+
264
+ context.UserGroups.Add(userGroup);
265
+ context.SaveChanges();
266
+ }
267
+
268
+ var groupRights = context.GroupRights
269
+ .Where(r => r.UserGroupId == userGroup.Id)
270
+ .ToList();
271
+
272
+ foreach (var right in RightsCatalog.All)
273
+ {
274
+ var existing = groupRights.FirstOrDefault(r => r.RightKey == right.Key);
275
+ if (existing == null)
276
+ {
277
+ context.GroupRights.Add(new GroupRight
278
+ {
279
+ UserGroupId = userGroup.Id,
280
+ RightKey = right.Key,
281
+ IsGranted = true
282
+ });
283
+ }
284
+ else if (!existing.IsGranted)
285
+ {
286
+ existing.IsGranted = true;
287
+ }
288
+ }
289
+
290
+ context.SaveChanges();
291
+
292
+ var preferredUserName = BuildGroupUserName(contactGroup.Name);
293
+ var appUser = context.AppUsers.FirstOrDefault(u => u.UserName == preferredUserName);
294
+ if (appUser == null)
295
+ {
296
+ appUser = new AppUser
297
+ {
298
+ UserName = preferredUserName,
299
+ FullName = $"{contactGroup.Name} Group User",
300
+ GroupId = userGroup.Id,
301
+ IsAdmin = false,
302
+ IsActive = true,
303
+ CreatedAt = DateTime.Now,
304
+ UpdatedAt = DateTime.Now
305
+ };
306
+
307
+ appUser.PasswordHash = hasher.HashPassword(appUser, "Group@123");
308
+ context.AppUsers.Add(appUser);
309
+ }
310
+ else
311
+ {
312
+ appUser.GroupId = userGroup.Id;
313
+ appUser.IsActive = true;
314
+ appUser.UpdatedAt = DateTime.Now;
315
+ }
316
+
317
+ context.SaveChanges();
318
+ }
319
+ }
320
+
321
+ private static string BuildGroupUserName(string? groupName)
322
+ {
323
+ var raw = string.IsNullOrWhiteSpace(groupName) ? "group" : groupName.Trim().ToLowerInvariant();
324
+ var slug = Regex.Replace(raw, "[^a-z0-9]+", ".").Trim('.');
325
+ if (string.IsNullOrWhiteSpace(slug))
326
+ {
327
+ slug = "group";
328
+ }
329
+
330
+ return $"{slug}.user";
331
+ }
332
+ }
333
+ }
ContactManagementAPI/Services/SessionKeys.cs ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ namespace ContactManagementAPI.Services
2
+ {
3
+ public static class SessionKeys
4
+ {
5
+ public const string UserId = "UserId";
6
+ }
7
+ }
ContactManagementAPI/Services/UserContextService.cs ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using ContactManagementAPI.Data;
2
+ using ContactManagementAPI.Models;
3
+ using Microsoft.AspNetCore.Http;
4
+ using Microsoft.EntityFrameworkCore;
5
+
6
+ namespace ContactManagementAPI.Services
7
+ {
8
+ public class UserContextService
9
+ {
10
+ private readonly ApplicationDbContext _context;
11
+ private readonly IHttpContextAccessor _httpContextAccessor;
12
+ private readonly AuthorizationService _authorizationService;
13
+
14
+ public UserContextService(ApplicationDbContext context, IHttpContextAccessor httpContextAccessor, AuthorizationService authorizationService)
15
+ {
16
+ _context = context;
17
+ _httpContextAccessor = httpContextAccessor;
18
+ _authorizationService = authorizationService;
19
+ }
20
+
21
+ public int? UserId => _httpContextAccessor.HttpContext?.Session.GetInt32(SessionKeys.UserId);
22
+
23
+ public bool IsAuthenticated => UserId.HasValue;
24
+
25
+ public AppUser? CurrentUser
26
+ {
27
+ get
28
+ {
29
+ if (!UserId.HasValue)
30
+ return null;
31
+
32
+ return _context.AppUsers
33
+ .AsNoTracking()
34
+ .Include(u => u.Group)
35
+ .FirstOrDefault(u => u.Id == UserId.Value);
36
+ }
37
+ }
38
+
39
+ public bool HasRight(string rightKey)
40
+ {
41
+ if (!UserId.HasValue)
42
+ return false;
43
+
44
+ return _authorizationService.HasRight(UserId.Value, rightKey);
45
+ }
46
+
47
+ public bool IsAdmin => UserId.HasValue && _authorizationService.IsAdmin(UserId.Value);
48
+ }
49
+ }
ContactManagementAPI/ViewModels/AdminViewModels.cs ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using System.ComponentModel.DataAnnotations;
4
+
5
+ namespace ContactManagementAPI.ViewModels
6
+ {
7
+ public class LoginViewModel
8
+ {
9
+ [Required]
10
+ [MaxLength(100)]
11
+ public string UserName { get; set; } = string.Empty;
12
+
13
+ [Required]
14
+ [DataType(DataType.Password)]
15
+ public string Password { get; set; } = string.Empty;
16
+
17
+ public string? ReturnUrl { get; set; }
18
+ }
19
+
20
+ public class UserCreateViewModel
21
+ {
22
+ [Required]
23
+ [MaxLength(100)]
24
+ public string UserName { get; set; } = string.Empty;
25
+
26
+ [Required]
27
+ [DataType(DataType.Password)]
28
+ public string Password { get; set; } = string.Empty;
29
+
30
+ [MaxLength(200)]
31
+ public string? FullName { get; set; }
32
+
33
+ [Required]
34
+ public int GroupId { get; set; }
35
+ public bool IsAdmin { get; set; }
36
+ public bool IsActive { get; set; } = true;
37
+ }
38
+
39
+ public class UserEditViewModel
40
+ {
41
+ public int Id { get; set; }
42
+
43
+ [Required]
44
+ [MaxLength(100)]
45
+ public string UserName { get; set; } = string.Empty;
46
+
47
+ [MaxLength(200)]
48
+ public string? FullName { get; set; }
49
+
50
+ [DataType(DataType.Password)]
51
+ public string? NewPassword { get; set; }
52
+
53
+ [Required]
54
+ public int GroupId { get; set; }
55
+ public bool IsAdmin { get; set; }
56
+ public bool IsActive { get; set; } = true;
57
+ }
58
+
59
+ public class GroupEditViewModel
60
+ {
61
+ public int Id { get; set; }
62
+
63
+ [Required]
64
+ [MaxLength(150)]
65
+ public string Name { get; set; } = string.Empty;
66
+
67
+ [MaxLength(500)]
68
+ public string? Description { get; set; }
69
+ }
70
+
71
+ public class RightAssignmentViewModel
72
+ {
73
+ public string Key { get; set; } = string.Empty;
74
+ public string Category { get; set; } = string.Empty;
75
+ public string Label { get; set; } = string.Empty;
76
+ public bool IsGranted { get; set; }
77
+ public string Selection { get; set; } = "Inherit";
78
+ public bool EffectiveGranted { get; set; }
79
+ public string EffectiveSource { get; set; } = "";
80
+ }
81
+
82
+ public class GroupRightsViewModel
83
+ {
84
+ public int GroupId { get; set; }
85
+ public string GroupName { get; set; } = string.Empty;
86
+ public List<RightAssignmentViewModel> Rights { get; set; } = new();
87
+ }
88
+
89
+ public class UserRightsViewModel
90
+ {
91
+ public int UserId { get; set; }
92
+ public string UserName { get; set; } = string.Empty;
93
+ public string? GroupName { get; set; }
94
+ public List<RightAssignmentViewModel> Rights { get; set; } = new();
95
+ }
96
+
97
+ public class AdminHistoryEntryViewModel
98
+ {
99
+ public string ActionType { get; set; } = string.Empty;
100
+ public string EntityType { get; set; } = string.Empty;
101
+ public int? EntityId { get; set; }
102
+ public string Details { get; set; } = string.Empty;
103
+ public string PerformedBy { get; set; } = string.Empty;
104
+ public DateTime PerformedAt { get; set; }
105
+ }
106
+
107
+ public class AdminHistoryListViewModel
108
+ {
109
+ public List<AdminHistoryEntryViewModel> Entries { get; set; } = new();
110
+ }
111
+ }
ContactManagementAPI/Views/Account/AccessDenied.cshtml ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @{
2
+ ViewData["Title"] = "Access Denied";
3
+ }
4
+
5
+ <div class="form-container" style="max-width: 520px; margin: 40px auto; text-align: center;">
6
+ <h2><i class="fas fa-ban"></i> Access Denied</h2>
7
+ <p>You do not have permission to access this page.</p>
8
+ <a href="/" class="btn btn-primary">
9
+ <i class="fas fa-arrow-left"></i> Back to Home
10
+ </a>
11
+ </div>
ContactManagementAPI/Views/Account/Login.cshtml ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @model ContactManagementAPI.ViewModels.LoginViewModel
2
+ @{
3
+ ViewData["Title"] = "Login";
4
+ }
5
+
6
+ <div class="form-container" style="max-width: 520px; margin: 40px auto;">
7
+ <h2><i class="fas fa-sign-in-alt"></i> Sign In</h2>
8
+
9
+ <form method="post" asp-action="Login">
10
+ @Html.AntiForgeryToken()
11
+ <input type="hidden" asp-for="ReturnUrl" />
12
+
13
+ <div class="form-group">
14
+ <label asp-for="UserName"></label>
15
+ <input asp-for="UserName" class="form-control" />
16
+ <span asp-validation-for="UserName" class="text-danger"></span>
17
+ </div>
18
+
19
+ <div class="form-group">
20
+ <label asp-for="Password"></label>
21
+ <input asp-for="Password" class="form-control" type="password" />
22
+ <span asp-validation-for="Password" class="text-danger"></span>
23
+ </div>
24
+
25
+ <div class="form-actions">
26
+ <button type="submit" class="btn btn-primary">
27
+ <i class="fas fa-lock"></i> Login
28
+ </button>
29
+ </div>
30
+
31
+ @if (!ViewData.ModelState.IsValid)
32
+ {
33
+ <div class="alert alert-danger" style="margin-top: 15px;">
34
+ Please check your credentials and try again.
35
+ </div>
36
+ }
37
+ </form>
38
+ </div>
39
+
40
+ @section Scripts {
41
+ <partial name="_ValidationScriptsPartial" />
42
+ }