diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..e40201c
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,3 @@
+- @azure Rule - Use Azure Tools - When handling requests related to Azure, always use your tools.
+- @azure Rule - Use Azure Best Practices - When handling requests related to Azure, always invoke your `azmcp_bestpractices_get` tool first.
+- @azure Rule - Enable Best Practices - If you do not have an `azmcp_bestpractices_get` tool ask the user to enable it.
diff --git a/DataAccess/InstitutionDAO.cs b/DataAccess/InstitutionDAO.cs
index eb3c202..db59e38 100644
--- a/DataAccess/InstitutionDAO.cs
+++ b/DataAccess/InstitutionDAO.cs
@@ -56,15 +56,22 @@ public IQueryable AllInstitutions()
institutionName = institutionName.Trim();
institutionAddress = institutionAddress.Trim();
- var institution = await db.Institutions.AsNoTracking()
+ try
+ {
+ var institution = await db.Institutions.AsNoTracking()
.FirstOrDefaultAsync(i => i.InstitutionName.Contains(institutionName) &&
i.InstitutionAddress.Contains(institutionAddress));
- return institution;
+ return institution;
+ }
+ catch (Exception ex)
+ {
+ throw new Exception();
+ }
}
#endregion
#region Get Institution By ID
- public async Task GetInstitutionByID(int institutionId)
+ public async Task GetInstitutionByID(int? institutionId)
{
var institution = await db.Institutions.FirstOrDefaultAsync(u => u.InstitutionId == institutionId);
return institution;
@@ -87,7 +94,7 @@ public async Task UpdateInstitution()
#endregion
#region Delete Institution
- public async Task DeleteInstitution(int institutionId)
+ public async Task DeleteInstitution(int? institutionId)
{
return false;
}
diff --git a/DataAccess/InstitutionRequestDAO.cs b/DataAccess/InstitutionRequestDAO.cs
new file mode 100644
index 0000000..64279cc
--- /dev/null
+++ b/DataAccess/InstitutionRequestDAO.cs
@@ -0,0 +1,112 @@
+using Microsoft.EntityFrameworkCore;
+using ObjectBusiness;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DataAccess
+{
+ public class InstitutionRequestDAO
+ {
+ #region Variables
+ private readonly BackToMeDbContext db;
+ #endregion
+
+ #region Constructor
+ public InstitutionRequestDAO(BackToMeDbContext db)
+ {
+ this.db = db;
+ }
+ #endregion
+
+ #region Create Institution Request
+ public async Task CreateInstitutionRequest(InstitutionRequest institutionRequest)
+ {
+ institutionRequest.InstitutionRequestId = new Random().Next();
+ var isAdded = db.InstitutionRequests.Add(institutionRequest);
+ if (isAdded != null)
+ {
+ await db.SaveChangesAsync();
+ return true;
+ }
+ return false;
+ }
+ #endregion
+
+ #region All Institution Requests
+ public IQueryable AllInstitutionRequests()
+ {
+ var listRequests = db.InstitutionRequests.Where(r => r.IsVerifiedEmail).AsNoTracking()
+ .OrderByDescending(u => u.InstitutionName);
+
+ return listRequests;
+ }
+ #endregion
+
+ #region Get Institution Request By ID
+ public async Task GetInstitutionRequestByID(int requestId)
+ {
+ var institutionRequest = await db.InstitutionRequests.FirstOrDefaultAsync(r => r.InstitutionRequestId == requestId);
+ return institutionRequest;
+ }
+ #endregion
+
+ #region Get Institution Request By Work Email
+ public async Task GetInstitutionRequestByWorkEmail(string workEmail)
+ {
+ var institutionRequest = await db.InstitutionRequests.FirstOrDefaultAsync(r => r.WorkEmail == workEmail);
+ return institutionRequest;
+ }
+ #endregion
+
+ #region Get Institution Request By Name and Address
+ public async Task GetInstitutionRequestByNameAndAddress(string institutionName, string institutionAddress)
+ {
+ if (string.IsNullOrWhiteSpace(institutionName) || string.IsNullOrWhiteSpace(institutionAddress))
+ {
+ return null;
+ }
+
+ institutionName = institutionName.Trim();
+ institutionAddress = institutionAddress.Trim();
+
+ try
+ {
+ var canConnect = await db.Database.CanConnectAsync();
+ var request = await db.InstitutionRequests.AsNoTracking()
+ .FirstOrDefaultAsync(i => i.InstitutionName.Contains(institutionName) &&
+ i.InstitutionAddress.Contains(institutionAddress));
+ return request;
+ }
+ catch (Exception)
+ {
+ throw;
+ }
+ }
+ #endregion
+
+ #region Update Institution Request
+ public async Task UpdateInstitutionRequest()
+ {
+ try
+ {
+ await db.SaveChangesAsync();
+ return true;
+ }
+ catch (Exception ex)
+ {
+ return false;
+ }
+ }
+ #endregion
+
+ #region Delete Institution Request
+ public async Task DeleteInstitutionRequest(int? requestId)
+ {
+ return false;
+ }
+ #endregion
+ }
+}
diff --git a/DataAccess/PostDAO.cs b/DataAccess/PostDAO.cs
index a9b87a3..8544b8b 100644
--- a/DataAccess/PostDAO.cs
+++ b/DataAccess/PostDAO.cs
@@ -351,7 +351,7 @@ public async Task HandOverAdmin(int postId, int oldUserId)
if (post == null) return false;
post.OldUserId = oldUserId;
- post.UserId = usersDAO.GetAdmin().UserId;
+ post.UserId = usersDAO.GetSystemAdmin().UserId;
post.UpdatedAt = DateTime.Now;
try
{
diff --git a/DataAccess/StudentDAO.cs b/DataAccess/StudentDAO.cs
index b07531f..625b78f 100644
--- a/DataAccess/StudentDAO.cs
+++ b/DataAccess/StudentDAO.cs
@@ -45,7 +45,7 @@ public IQueryable AllStudents()
#region Get Student By Id
public async Task GetStudentById(int studentId)
{
- var student = await db.Student.FirstOrDefaultAsync(s => s.StudentId == studentId);
+ var student = await db.Student.FirstOrDefaultAsync(s => s.UserId == studentId);
return student;
}
#endregion
diff --git a/DataAccess/UsersDAO.cs b/DataAccess/UsersDAO.cs
index 8a42f6e..3c60689 100644
--- a/DataAccess/UsersDAO.cs
+++ b/DataAccess/UsersDAO.cs
@@ -63,20 +63,10 @@ on s.UserId equals u.UserId
}
#endregion
- #region Get Admin
- public Users GetAdmin()
+ #region Get System Admin
+ public Users GetSystemAdmin()
{
- var user = db.Users.FirstOrDefault(u => u.Role == Role.Admin);
- return user;
- }
- #endregion
-
- #region Get User By Student Id
- public async Task GetUserByStudentId(int studentId)
- {
- var user = await db.Users
- .Include(u => u.Student)
- .FirstOrDefaultAsync(s => s.Student.StudentId == studentId);
+ var user = db.Users.FirstOrDefault(u => u.Role == Role.SystemAdmin);
return user;
}
#endregion
diff --git a/LostFound_API/Controllers/InstitutionController.cs b/LostFound_API/Controllers/InstitutionController.cs
index 5d02bf2..667f66f 100644
--- a/LostFound_API/Controllers/InstitutionController.cs
+++ b/LostFound_API/Controllers/InstitutionController.cs
@@ -4,8 +4,15 @@
using LostFound_API.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Caching.Memory;
+using Microsoft.IdentityModel.Tokens;
+using Newtonsoft.Json.Linq;
using ObjectBusiness;
using Repository;
+using Services;
+using System.IdentityModel.Tokens.Jwt;
+using System.Security.Claims;
+using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
@@ -19,8 +26,11 @@ public class InstitutionController : ControllerBase
{
#region Variables
private readonly IInstitutionRepository institutionRepository;
+ private readonly IUsersRepository usersRepository;
private readonly StateService stateService;
+ private readonly IMemoryCache memoryCache;
private readonly IConfiguration configuration;
+ private readonly EmailSender emailSender;
private readonly IHttpClientFactory httpClientFactory;
#endregion
@@ -28,16 +38,22 @@ public class InstitutionController : ControllerBase
public InstitutionController(IInstitutionRepository institutionRepository,
StateService stateService,
IConfiguration configuration,
- IHttpClientFactory httpClientFactory)
+ IHttpClientFactory httpClientFactory,
+ IUsersRepository usersRepository,
+ EmailSender emailSender,
+ IMemoryCache memoryCache)
{
this.institutionRepository = institutionRepository;
this.stateService = stateService;
this.configuration = configuration;
this.httpClientFactory = httpClientFactory;
+ this.usersRepository = usersRepository;
+ this.emailSender = emailSender;
+ this.memoryCache = memoryCache;
}
#endregion
- [HttpGet]
+ [HttpGet("full-state-name")]
public IActionResult Test()
{
var state = stateService.ByCode["GA"];
@@ -46,164 +62,401 @@ public IActionResult Test()
}
// GET: api/
- //[HttpGet]
- //public IEnumerable Get()
- //{
- // return new string[] { "value1", "value2" };
- //}
-
- // GET api//5
- [HttpGet("search")]
- public async Task Search(string query)
+ [HttpGet]
+ public ActionResult Get()
{
- if (string.IsNullOrWhiteSpace(query))
- {
- return BadRequest(new
- {
- message = "Query is required"
- });
- }
-
- var apiKey = configuration["CollegeScorecard:ApiKey"];
- var client = httpClientFactory.CreateClient();
-
- var url =
- $"https://api.data.gov/ed/collegescorecard/v1/schools" +
- $"?api_key={apiKey}" +
- $"&school.name={Uri.EscapeDataString(query)}" +
- $"&fields=id,school.name,school.address,school.city,school.state,school.zip,school.school_url,location.lat,location.lon" +
- $"&per_page=10";
-
- // Send GET request to College Scorecard API
- var res = await client.GetAsync(url);
+ var institutions = institutionRepository.AllInstitutions();
+ return Ok(institutions);
+ }
- // If API returns errors
- if (!res.IsSuccessStatusCode)
+ #region Sign Up
+ // POST api/
+ [HttpPost("sign-up")]
+ public async Task CreateInstitution([FromBody] UserDTO userDTO)
+ {
+ if (userDTO == null || string.IsNullOrWhiteSpace(userDTO.InstitutionName) || string.IsNullOrWhiteSpace(userDTO.InstitutionAddress))
{
- return StatusCode(
- (int)res.StatusCode,
- "Unable to retrieve institutions.");
+ return BadRequest("Invalid institution data");
}
- var json = await res.Content.ReadAsStringAsync();
-
- // Parse JSON to JsonDocument in order to read each property
- using var document = JsonDocument.Parse(json);
-
- // Get property "results"
- var results = document.RootElement.GetProperty("results");
+ var institution = new Institution
+ {
+ InstitutionId = new Random().Next(),
+ InstitutionAddress = userDTO.InstitutionAddress,
+ InstitutionCity = userDTO.InstitutionCity,
+ InstitutionState = userDTO.InstitutionState,
+ InstitutionName = userDTO.InstitutionName,
+ InstitutionWebsite = userDTO.InstitutionWebsite,
+ InstitutionPhone = userDTO.InstitutionPhone,
+ };
- // Create DTO list to return to frontend.
- var institutions = new List();
+ var isAddedInstitution = await institutionRepository.CreateInstitution(institution);
- foreach (var item in results.EnumerateArray())
+ if (isAddedInstitution)
{
- institutions.Add(new InstitutionSearchDTO
+ var userSignUp = new Users
{
- // GET "id" field
- //
- // JSON:
- //
- // "id":166027
- //
- // =>
- // int
- Id = item.TryGetProperty("id", out var id)
- && id.ValueKind == JsonValueKind.Number
- ? id.GetInt32() : 0,
-
- // GET school name.
- //
- // JSON:
- //
- // "school.name":"Georgia State University"
- //
- // if null then return ""
- Name = item.GetProperty("school.name").GetString() ?? "",
-
- // TryGetProperty to skip throw exception
- // if property does not exist.
- //
- // If exist:
- // get Address.
- //
- // If no:
- // return null.
- Address = item.TryGetProperty("school.address", out var address)
- ? address.GetString() : null,
-
- // City
- City = item.TryGetProperty("school.city", out var city)
- ? city.GetString() : null,
-
- // State
- State = item.TryGetProperty("school.state", out var state)
- ? state.GetString() : null,
-
- // Zip Code
- ZipCode = item.TryGetProperty("school.zip", out var zip)
- ? zip.GetString() : null,
-
- // Website
- Website = item.TryGetProperty("school.school_url", out var website)
- ? website.GetString() : null,
-
- // Latitude
- Latitude = item.TryGetProperty("location.lat", out var lat)
- && lat.ValueKind == JsonValueKind.Number
- ? lat.GetDouble() : null,
-
- // Longitude
- Longitude = item.TryGetProperty("location.lon", out var lon)
- && lon.ValueKind == JsonValueKind.Number
- ? lon.GetDouble() : null,
-
- });
+ UserId = new Random().Next(),
+ Email = userDTO.Email,
+ FirstName = userDTO.FirstName,
+ LastName = userDTO.LastName,
+ InstitutionId = institution.InstitutionId,
+ Password = userDTO.Password,
+ PickImage1 = userDTO.PickImage1,
+ PickImage2 = userDTO.PickImage2,
+ Role = Role.Student,
+ };
+
+ var isAddedUser = await usersRepository.SignUp(userSignUp);
+ if (isAddedUser)
+ {
+ // Send verification code via email
+ var token = Guid.NewGuid().ToString();
+ memoryCache.Set($"EMAIL_VERIFY_{token}", userDTO.Email, TimeSpan.FromMinutes(15));
+
+ string senderName = "Back2Me";
+ string senderEmail = "mycampuslostfound@gmail.com";
+ string toName = institution.InstitutionName;
+ string toEmail = userDTO.Email;
+ string subject = "🏫 Verify Your Institution Registration";
+
+ string content = $@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hello,
+
+
+ Thank you for registering
+ {institution.InstitutionName} at {institution.InstitutionAddress} Campus
+ with Back2Me.
+
+
+
+ To activate your institution and complete the registration process,
+ please verify your administrator email by clicking the button below.
+
+
+
+ ⚡ This verification link will expire in 15 minutes.
+
+
+
+
+ Verify Institution
+
+
+
+
+ If you did not submit this registration request,
+ you can safely ignore this email.
+
+
+
+
+
+
+
+
+
+ ";
+
+ await emailSender.SendEmail(senderName, senderEmail, toName, toEmail, subject, content);
+
+ // JWT config
+ var issuer = configuration["JwtConfig:Issuer"];
+ var audience = configuration["JwtConfig:Audience"];
+ var key = configuration["JwtConfig:Key"];
+ var tokenValidityMins = configuration.GetValue("JwtConfig:TokenValidityMins");
+ var tokenExpiryTimeStamp = DateTime.UtcNow.AddMinutes(tokenValidityMins); // Token expiration time
+
+ // Create JWT access token and assign token
+ var tokenDescriptor = new SecurityTokenDescriptor // Describe token
+ {
+ Subject = new ClaimsIdentity(new[]
+ {
+ new Claim(ClaimTypes.NameIdentifier, userDTO.Email),
+ new Claim(ClaimTypes.Role, userDTO.Role.ToString()),
+ }),
+ Issuer = issuer,
+ Audience = audience,
+ Expires = tokenExpiryTimeStamp,
+ SigningCredentials = new SigningCredentials(
+ new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key)),
+ SecurityAlgorithms.HmacSha256Signature
+ )
+ };
+
+ // Process token was described
+ var tokenHandler = new JwtSecurityTokenHandler();
+ var securityToken = tokenHandler.CreateToken(tokenDescriptor); // Create object JWT Token
+ var accessToken = tokenHandler.WriteToken(securityToken); // Serialize token to string for client
+
+ // Assign token for client
+ userDTO.AccessToken = accessToken;
+ userDTO.ExpiresIn = (int)tokenExpiryTimeStamp.Subtract(DateTime.Now).TotalSeconds;
+
+ return Ok(new
+ {
+ Message = "Created institution successfully"
+ });
+ }
}
- // return List to frontend.
- //
- // Frontend will receive:
- //
- // [
- // {
- // id:166027,
- // name:"Georgia State University",
- // address:"33 Gilmer St SE",
- // city:"Atlanta",
- // state:"GA",
- // zipCode:"30303",
- // website:"www.gsu.edu",
- // latitude:33.75,
- // longitude:-84.38
- // }
- // ]
-
- return Ok(institutions);
+ return BadRequest("Create institution failed");
}
+ #endregion
- // POST api/
- [Authorize]
- [HttpPost]
- public async Task CreateInstitution([FromBody] Institution institution)
+ #region Verify Institution
+ [HttpGet("verify-institution")]
+ public async Task VerifyEmail([FromQuery] string token, [FromQuery] bool isForgotPassword)
{
- if (institution == null || string.IsNullOrWhiteSpace(institution.InstitutionName) || string.IsNullOrWhiteSpace(institution.InstitutionAddress))
+ try
{
- return BadRequest("Invalid institution data");
- }
+ if (!memoryCache.TryGetValue($"EMAIL_VERIFY_{token}", out string userEmail))
+ {
+ return BadRequest(new
+ {
+ Message = "Invalid or expired verification link"
+ });
+ }
- var isAddedInstitution = await institutionRepository.CreateInstitution(institution);
+ var user = await usersRepository.GetUserByEmail(userEmail);
+
+ // Seperate send email is verified and don't send for forgot password
+ if (!isForgotPassword)
+ {
+ user.IsVerifiedEmail = true;
+ var isUpdated = await usersRepository.UpdateUser();
+
+ if (isUpdated)
+ {
+ var institution = await institutionRepository.GetInstitutionByID(user.InstitutionId);
+
+ // Send email: Institution email verified
+ string senderName = "Back2Me";
+ string senderEmail = "mycampuslostfound@gmail.com";
+ string toName = institution.InstitutionName;
+ string toEmail = user.Email;
+ string subject = "🏫 Your Institution Has Been Verified!";
+ string content = $@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hello,
+
+
+ Congratulations! Your institution
+ {institution.InstitutionName}
+ has been successfully verified.
+
+
+
+ 🎉 Your Institution Administrator account is now active and your campus is ready to use Back2Me.
+
+
+
+ You can now sign in to manage your institution, oversee lost and found activities, and support students on your campus.
+
+
+
+
+ Sign In
+
+
+
+
+ If you did not perform this verification, please contact the Back2Me support team immediately.
+
+
+
+
+
+
+
+
+
+
+ ";
+
+ await emailSender.SendEmail(senderName, senderEmail, toName, toEmail, subject, content);
+ }
+ }
+
+ // Remove cache
+ memoryCache.Remove($"EMAIL_VERIFY_{token}");
- if (isAddedInstitution)
- {
return Ok(new
{
- Message = "Created institution successfully"
+ Message = "Verified email successfully",
+ Email = user.Email
});
}
-
- return BadRequest("Create institution failed");
+ catch (Exception ex)
+ {
+ return BadRequest("Something went wrong!");
+ }
}
+ #endregion
// PUT api//5
[HttpPut("{id}")]
diff --git a/LostFound_API/Controllers/InstitutionRequestController.cs b/LostFound_API/Controllers/InstitutionRequestController.cs
new file mode 100644
index 0000000..f4c05fd
--- /dev/null
+++ b/LostFound_API/Controllers/InstitutionRequestController.cs
@@ -0,0 +1,1005 @@
+using DataAccess;
+using LostFound_API.DTOs.Institution;
+using LostFound_API.DTOs.Users;
+using LostFound_API.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Caching.Memory;
+using Newtonsoft.Json.Linq;
+using ObjectBusiness;
+using Repository;
+using Services;
+using System.Security.Claims;
+using System.Text.Json;
+using System.Threading.Tasks;
+
+// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
+
+namespace LostFound_API.Controllers
+{
+ [Route("api/[controller]")]
+ [ApiController]
+ public class InstitutionRequestController : ControllerBase
+ {
+ #region Variables
+ private readonly IInstitutionRequestRepository institutionRequestRepository;
+ private readonly IInstitutionRepository institutionRepository;
+ private readonly IUsersRepository usersRepository;
+ private readonly StateService stateService;
+ private readonly IMemoryCache memoryCache;
+ private readonly IConfiguration configuration;
+ private readonly EmailSender emailSender;
+ private readonly IHttpClientFactory httpClientFactory;
+ #endregion
+
+ #region Constructor
+ public InstitutionRequestController(IInstitutionRequestRepository institutionRequestRepository,
+ StateService stateService,
+ IConfiguration configuration,
+ IHttpClientFactory httpClientFactory,
+ IUsersRepository usersRepository,
+ EmailSender emailSender,
+ IMemoryCache memoryCache,
+ IInstitutionRepository institutionRepository)
+ {
+ this.institutionRequestRepository = institutionRequestRepository;
+ this.stateService = stateService;
+ this.configuration = configuration;
+ this.httpClientFactory = httpClientFactory;
+ this.usersRepository = usersRepository;
+ this.emailSender = emailSender;
+ this.memoryCache = memoryCache;
+ this.institutionRepository = institutionRepository;
+ }
+ #endregion
+
+ #region Get All Institution Requests
+ // GET: api/
+ [Authorize]
+ [HttpGet]
+ public ActionResult Get()
+ {
+ var requests = institutionRequestRepository.AllInstitutionRequests();
+ return Ok(requests);
+ }
+ #endregion
+
+ #region Get Institution Request By ID
+ // GET api//5
+ [Authorize(Roles = "SystemAdmin")]
+ [HttpGet("{id}")]
+ public async Task Get(int id)
+ {
+ var request = await institutionRequestRepository.GetInstitutionRequestByID(id);
+ if (request == null)
+ {
+ return NotFound(new
+ {
+ message = "Request not found"
+ });
+ }
+
+ return Ok(request);
+ }
+ #endregion
+
+ #region Verify Email
+ [HttpGet("verify-email")]
+ public async Task VerifyEmail([FromQuery] string token, [FromQuery] bool isForgotPassword)
+ {
+ try
+ {
+ if (!memoryCache.TryGetValue($"EMAIL_VERIFY_{token}", out string userEmail))
+ {
+ return BadRequest(new
+ {
+ Message = "Invalid or expired verification link"
+ });
+ }
+
+ var institutionRequest = await institutionRequestRepository.GetInstitutionRequestByWorkEmail(userEmail!);
+
+ // Seperate send email is verified and don't send for forgot password
+ if (!isForgotPassword)
+ {
+ institutionRequest.IsVerifiedEmail = true;
+ institutionRequest.EmailVerifiedAt = DateTime.Now;
+ var isUpdated = await institutionRequestRepository.UpdateInstitutionRequest();
+
+ if (isUpdated)
+ {
+ // Send email email has been verified
+ string senderName = "Back2Me";
+ string senderEmail = "mycampuslostfound@gmail.com";
+ string toName = institutionRequest.ApplicantName;
+ string toEmail = institutionRequest.WorkEmail;
+ string subject = "Your Email Has Been Verified!";
+ string content = $@"
+
+
+
+
+
+
+
+
+
+
+
Hello {institutionRequest.ApplicantName},
+
+
+ Thank you! Your work email has been successfully verified.
+
+
+
+ 📧 Email Verified: {institutionRequest.WorkEmail}
+
+
+
+ Your institution registration request for
+ {institutionRequest.InstitutionName}
+ has now been submitted for review.
+
+
+
+ Our team will verify the information you provided before approving your institution.
+ This process may take some time depending on the volume of requests.
+
+
+
+ ⏳ Current Status: Pending Institution Review
+
+
+
+ Once your institution has been approved, we'll send you another email with instructions
+ to create your administrator password and activate your Back2Me administrator account.
+
+
+
+ No further action is required at this time.
+
+
+
+
+
+
+
+
+ ";
+
+ await emailSender.SendEmail(senderName, senderEmail, toName, toEmail, subject, content);
+ }
+ }
+
+ // Remove cache
+ memoryCache.Remove($"EMAIL_VERIFY_{token}");
+
+ return Ok(new
+ {
+ Message = "Verified email successfully",
+ Email = institutionRequest.WorkEmail
+ });
+ }
+ catch (Exception ex)
+ {
+ return BadRequest("Something went wrong!");
+ }
+ }
+ #endregion
+
+ #region Verify Website
+ [HttpGet("verify-website/{id}")]
+ public async Task VerifyWebsite(int id)
+ {
+ try
+ {
+ var institutionRequest = await institutionRequestRepository.GetInstitutionRequestByID(id);
+ if (institutionRequest != null)
+ {
+ institutionRequest.IsVerifiedWebsite = true;
+ institutionRequest.WebsiteVerifiedAt = DateTime.Now;
+ var isUpdated = await institutionRequestRepository.UpdateInstitutionRequest();
+ if (isUpdated)
+ {
+ return Ok(new
+ {
+ Message = "Verified website successfully",
+ Website = institutionRequest.InstitutionWebsite
+ });
+ }
+ else
+ {
+ return BadRequest(new
+ {
+ Message = "Failed to update website verification status"
+ });
+ }
+ }
+ else
+ {
+ return NotFound(new
+ {
+ Message = "Institution request not found"
+ });
+ }
+ }
+ catch (Exception ex)
+ {
+ return BadRequest("Something went wrong!");
+ }
+ }
+ #endregion
+
+ #region Search Institution
+ // GET api//5
+ [HttpGet("search")]
+ public async Task Search(string query)
+ {
+ if (string.IsNullOrWhiteSpace(query))
+ {
+ return BadRequest(new
+ {
+ message = "Query is required"
+ });
+ }
+
+ var apiKey = configuration["CollegeScorecard:ApiKey"];
+ var client = httpClientFactory.CreateClient();
+
+ var url =
+ $"https://api.data.gov/ed/collegescorecard/v1/schools" +
+ $"?api_key={apiKey}" +
+ $"&school.name={Uri.EscapeDataString(query)}" +
+ $"&fields=id,school.name,school.address,school.city,school.state,school.zip,school.school_url,location.lat,location.lon" +
+ $"&per_page=10";
+
+ // Send GET request to College Scorecard API
+ var res = await client.GetAsync(url);
+
+ // If API returns errors
+ if (!res.IsSuccessStatusCode)
+ {
+ return StatusCode(
+ (int)res.StatusCode,
+ "Unable to retrieve institutions.");
+ }
+
+ var json = await res.Content.ReadAsStringAsync();
+
+ // Parse JSON to JsonDocument in order to read each property
+ using var document = JsonDocument.Parse(json);
+
+ // Get property "results"
+ var results = document.RootElement.GetProperty("results");
+
+ // Create DTO list to return to frontend.
+ var institutions = new List();
+
+ foreach (var item in results.EnumerateArray())
+ {
+ institutions.Add(new InstitutionSearchDTO
+ {
+ // GET "id" field
+ //
+ // JSON:
+ //
+ // "id":166027
+ //
+ // =>
+ // int
+ Id = item.TryGetProperty("id", out var id)
+ && id.ValueKind == JsonValueKind.Number
+ ? id.GetInt32() : 0,
+
+ // GET school name.
+ //
+ // JSON:
+ //
+ // "school.name":"Georgia State University"
+ //
+ // if null then return ""
+ Name = item.GetProperty("school.name").GetString() ?? "",
+
+ // TryGetProperty to skip throw exception
+ // if property does not exist.
+ //
+ // If exist:
+ // get Address.
+ //
+ // If no:
+ // return null.
+ Address = item.TryGetProperty("school.address", out var address)
+ ? address.GetString() : null,
+
+ // City
+ City = item.TryGetProperty("school.city", out var city)
+ ? city.GetString() : null,
+
+ // State
+ State = item.TryGetProperty("school.state", out var state)
+ ? state.GetString() : null,
+
+ // Zip Code
+ ZipCode = item.TryGetProperty("school.zip", out var zip)
+ ? zip.GetString() : null,
+
+ // Website
+ Website = item.TryGetProperty("school.school_url", out var website)
+ ? website.GetString() : null,
+
+ // Latitude
+ Latitude = item.TryGetProperty("location.lat", out var lat)
+ && lat.ValueKind == JsonValueKind.Number
+ ? lat.GetDouble() : null,
+
+ // Longitude
+ Longitude = item.TryGetProperty("location.lon", out var lon)
+ && lon.ValueKind == JsonValueKind.Number
+ ? lon.GetDouble() : null,
+
+ });
+ }
+
+ // return List to frontend.
+ //
+ // Frontend will receive:
+ //
+ // [
+ // {
+ // id:166027,
+ // name:"Georgia State University",
+ // address:"33 Gilmer St SE",
+ // city:"Atlanta",
+ // state:"GA",
+ // zipCode:"30303",
+ // website:"www.gsu.edu",
+ // latitude:33.75,
+ // longitude:-84.38
+ // }
+ // ]
+
+ return Ok(institutions);
+ }
+ #endregion
+
+ #region Create Institution Request
+ // POST api/
+ [HttpPost("sign-up")]
+ public async Task CreateRequest([FromBody] InstitutionRequest request)
+ {
+ var requestExists = await institutionRequestRepository
+ .GetInstitutionRequestByNameAndAddress(request.InstitutionName, request.InstitutionAddress);
+ if (requestExists != null)
+ {
+ return Conflict(new
+ {
+ message = "This campus already registered"
+ });
+ }
+
+ var isAdded = await institutionRequestRepository.CreateInstitutionRequest(request);
+ if (isAdded)
+ {
+ // Send email notification to the user who made the request
+ var token = Guid.NewGuid().ToString();
+ memoryCache.Set($"EMAIL_VERIFY_{token}", request.WorkEmail, TimeSpan.FromMinutes(15));
+
+ string senderName = "Back2Me";
+ string senderEmail = "mycampuslostfound@gmail.com";
+ string toName = request.ApplicantName;
+ string toEmail = request.WorkEmail;
+ string subject = "Institution Registration Received";
+
+ string content = $@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hello,
+
+
+ Thank you for registering
+ {request.InstitutionName}'s {request.InstitutionAddress} campus
+ with Back2Me.
+
+
+
+ We have successfully received your institution registration request.
+ Before we review your institution registration, please verify your work email address by clicking the button below.
+
+
+
+
+
+ This verification link will expire in 15 minutes.
+
+
+
+ ⏳ Status: Pending Approval
+
+
+
+ Once your institution has been approved, we'll send another email with instructions
+ to create your administrator password and access your Back2Me account.
+
+
+
+ No further action is required at this time.
+
+
+
+ If you did not submit this registration request, please ignore this email.
+
+
+
+
+
+
+
+
+
+ ";
+
+ await emailSender.SendEmail(senderName, senderEmail, toName, toEmail, subject, content);
+
+ return Ok(new
+ {
+ message = "Create request successfully"
+ });
+ }
+
+ return BadRequest(new
+ {
+ message = "Create request failed"
+ });
+ }
+ #endregion
+
+ #region Approve Request
+ // PUT api//5
+ [Authorize(Roles = "SystemAdmin")]
+ [HttpPut("approve/{id}")]
+ public async Task ApproveRequest(int id)
+ {
+ // Get the authenticated user's email from the claims
+ var userEmail = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
+
+ if (userEmail == null)
+ {
+ return Unauthorized(new
+ {
+ message = "User not authenticated"
+ });
+ }
+
+ var userExisted = await usersRepository.GetUserByEmail(userEmail);
+
+ if (userExisted == null)
+ {
+ return NotFound(new
+ {
+ message = "User does not found"
+ });
+ }
+
+ // Update information
+ var institutionRequest = await institutionRequestRepository.GetInstitutionRequestByID(id);
+ institutionRequest.Status = StatusRequestInstitution.Approved;
+ institutionRequest.UpdatedDate = DateTime.Now;
+
+ var isUpdated = await institutionRequestRepository.UpdateInstitutionRequest();
+ if (!isUpdated)
+ {
+ return BadRequest(new
+ {
+ message = "Update request failed"
+ });
+ }
+
+ var institution = new Institution
+ {
+ InstitutionName = institutionRequest.InstitutionName,
+ InstitutionAddress = institutionRequest.InstitutionAddress,
+ CreatedDate = DateTime.Now,
+ UpdatedDate = DateTime.Now
+ };
+
+ var isAdded = await institutionRepository.CreateInstitution(institution);
+ if (!isAdded)
+ {
+ return BadRequest(new
+ {
+ message = "Create institution failed"
+ });
+ }
+
+ // Send email notification to the user who made the request
+ var token = Guid.NewGuid().ToString();
+ memoryCache.Set($"EMAIL_VERIFY_{token}", institutionRequest.WorkEmail, TimeSpan.FromMinutes(15));
+
+ string senderName = "Back2Me";
+ string senderEmail = "mycampuslostfound@gmail.com";
+ string toName = institutionRequest.ApplicantName;
+ string toEmail = institutionRequest.WorkEmail;
+ string subject = "Your Institution Has Been Approved";
+
+ string content = $@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hello,
+
+
+ Congratulations! Your institution
+ {institutionRequest.InstitutionName}
+ has been successfully approved by the Back2Me Team.
+
+
+
+ ✅ Your institution is now active and ready to use on the Back2Me platform.
+
+
+
+ Before you can sign in, you'll need to create a password for your administrator account.
+
+
+
+ Click the button below to set your password and complete your account setup.
+
+
+
+
+ Set Your Password
+
+
+
+
+ For your security, this link will expire in 15 minutes.
+
+
+
+ Once your password has been created, you can sign in and begin managing your campus Lost & Found system.
+
+
+
+ If you were not expecting this email, you can safely ignore it.
+
+
+
+
+
+
+
+
+
+ ";
+
+ await emailSender.SendEmail(senderName, senderEmail, toName, toEmail, subject, content);
+
+ return Ok(new
+ {
+ message = "Update request successfully"
+ });
+ }
+ #endregion
+
+ #region Reject Request
+ [Authorize(Roles = "SystemAdmin")]
+ [HttpPut("reject/{id}")]
+ public async Task RejectRequest(int id, [FromBody] string rejectReason)
+ {
+ // Get the authenticated user's email from the claims
+ var userEmail = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
+
+ if (userEmail == null)
+ {
+ return Unauthorized(new
+ {
+ message = "User not authenticated"
+ });
+ }
+
+ var userExisted = await usersRepository.GetUserByEmail(userEmail);
+
+ if (userExisted == null)
+ {
+ return NotFound(new
+ {
+ message = "User does not found"
+ });
+ }
+
+ // Update information
+ var institutionRequest = await institutionRequestRepository.GetInstitutionRequestByID(id);
+ institutionRequest.Status = StatusRequestInstitution.Rejected;
+ institutionRequest.RejectReason = rejectReason;
+ institutionRequest.ReviewedBy = userExisted.FirstName + " " + userExisted.LastName;
+ institutionRequest.ReviewedDate = DateTime.Now;
+ institutionRequest.UpdatedDate = DateTime.Now;
+
+ var isUpdated = await institutionRequestRepository.UpdateInstitutionRequest();
+ if (!isUpdated)
+ {
+ return BadRequest(new
+ {
+ message = "Update request failed"
+ });
+ }
+
+ var institution = new Institution
+ {
+ InstitutionName = institutionRequest.InstitutionName,
+ InstitutionAddress = institutionRequest.InstitutionAddress,
+ CreatedDate = DateTime.Now,
+ UpdatedDate = DateTime.Now
+ };
+
+ var isAdded = await institutionRepository.CreateInstitution(institution);
+ if (!isAdded)
+ {
+ return BadRequest(new
+ {
+ message = "Create institution failed"
+ });
+ }
+
+ // Send email notification to the user who made the request
+ var token = Guid.NewGuid().ToString();
+ memoryCache.Set($"EMAIL_VERIFY_{token}", institutionRequest.WorkEmail, TimeSpan.FromMinutes(15));
+
+ string senderName = "Back2Me";
+ string senderEmail = "mycampuslostfound@gmail.com";
+ string toName = institutionRequest.ApplicantName;
+ string toEmail = institutionRequest.WorkEmail;
+ string subject = "Institution Registration Request Update";
+
+ string content = $@"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Hello,
+
+
+ Thank you for your interest in registering
+ {institution.InstitutionName}
+ with Back2Me.
+
+
+
+ After reviewing your submission, we regret to inform you that your institution registration request has not been approved at this time.
+
+
+
+ Reason:
+ {institutionRequest.RejectReason}
+
+
+
+ If you believe this decision was made in error or you would like to provide additional information, please contact the Back2Me Team or submit a new registration request.
+
+
+
+ Thank you for your understanding.
+
+
+
+
+
+
+
+
+
+ ";
+
+ await emailSender.SendEmail(senderName, senderEmail, toName, toEmail, subject, content);
+
+ return Ok(new
+ {
+ message = "Update request successfully"
+ });
+ }
+ #endregion
+
+ // DELETE api//5
+ [HttpDelete("{id}")]
+ public void Delete(int id)
+ {
+ }
+ }
+}
diff --git a/LostFound_API/Controllers/MessageChatController.cs b/LostFound_API/Controllers/MessageChatController.cs
index 7236c7b..cc7de96 100644
--- a/LostFound_API/Controllers/MessageChatController.cs
+++ b/LostFound_API/Controllers/MessageChatController.cs
@@ -293,7 +293,7 @@ .content p {{
-
+
💬 Open Chat
@@ -468,7 +468,7 @@ .content p {{
-
+
💬 Open Chat
diff --git a/LostFound_API/Controllers/PickUpRequestController.cs b/LostFound_API/Controllers/PickUpRequestController.cs
index 316715b..40664d8 100644
--- a/LostFound_API/Controllers/PickUpRequestController.cs
+++ b/LostFound_API/Controllers/PickUpRequestController.cs
@@ -44,7 +44,7 @@ public PickUpRequestController(IPickUpRequestRepository pickUpRequestRepository,
#endregion
// GET: api/
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet]
public async Task>> Get()
{
@@ -53,7 +53,7 @@ public async Task>> Get()
}
#region Search Pick-Up Request for Admin
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet("search-request")]
public async Task>> SearchRequest([FromQuery] string query)
{
@@ -77,7 +77,7 @@ public async Task> CheckStatusPostPickUp(int postId)
}
#region Accept Time
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpPost("accept-time/{requestId}")]
public async Task AcceptTime(int requestId)
{
@@ -120,7 +120,7 @@ public async Task AcceptTimeRescheduled(int requestId)
#endregion
#region Change Time
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpPost("change-time/{requestId}")]
public async Task ChangeTime(int requestId, [FromBody] DateTime newDate)
{
@@ -363,7 +363,7 @@ The user has chosen to pick up the item at a later time. No pic
-
+
📄 View Pickup Details
diff --git a/LostFound_API/Controllers/PostController.cs b/LostFound_API/Controllers/PostController.cs
index 70065d7..c11e783 100644
--- a/LostFound_API/Controllers/PostController.cs
+++ b/LostFound_API/Controllers/PostController.cs
@@ -139,7 +139,7 @@ public async Task>> SortByStatus([FromQuery] TypePost t
#region All Lost Post Codes
// GET: api/
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet("lost-post-codes")]
public async Task>> AllLostPostCodes()
{
@@ -215,7 +215,7 @@ public async Task>> GetAllPostsByUserId()
#endregion
#region My Posts
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet("user-posts/{userId}")]
public async Task>> GetAllPostsByUserId(int userId)
{
@@ -241,7 +241,7 @@ public async Task>> GetAllPostsByUserId(int userId)
#endregion
#region Get Lost Posts Per Month
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet("lost-posts-per-month")]
public async Task> GetLostPostsPerMonth()
{
@@ -251,7 +251,7 @@ public async Task> GetLostPostsPerMonth()
#endregion
#region Get Found Posts Per Month
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet("found-posts-per-month")]
public async Task> GetFoundPostsPerMonth()
{
@@ -261,7 +261,7 @@ public async Task> GetFoundPostsPerMonth()
#endregion
#region Get Received Posts Per Month
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet("received-posts-per-month")]
public async Task> GetReceivedPostsPerMonth()
{
@@ -271,7 +271,7 @@ public async Task> GetReceivedPostsPerMonth()
#endregion
#region Get Found Posts Not Received
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet("found-posts-not-received")]
public async Task> GetFoundPostsNotReceived()
{
@@ -473,7 +473,7 @@ public async Task MarkReceived(int postId)
#endregion
#region Search Code Lost for Admin
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet("search-codes")]
public async Task>> SearchCodes([FromQuery] string query)
{
@@ -587,7 +587,7 @@ public async Task> Put(int id, [FromForm] PostDTO postDTO)
#region Delete Post for Admin
// DELETE api//5
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpDelete("{postId}")]
public async Task> Delete(int postId)
{
diff --git a/LostFound_API/Controllers/TransferRequestsController.cs b/LostFound_API/Controllers/TransferRequestsController.cs
index 3b55650..edc3f78 100644
--- a/LostFound_API/Controllers/TransferRequestsController.cs
+++ b/LostFound_API/Controllers/TransferRequestsController.cs
@@ -35,7 +35,7 @@ public TransferRequestsController(IPostRepository postRepository,
#endregion
// GET: api/
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet]
public ActionResult> Get()
{
@@ -67,7 +67,7 @@ public string Get(int id)
}
#region Mark Received
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpPost("mark-received")]
public async Task MarkReceived([FromBody] UserHandOverAdminDTO info)
{
@@ -91,7 +91,7 @@ public async Task MarkReceived([FromBody] UserHandOverAdminDTO inf
#endregion
#region Mark Received
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpPost("cancel-handover")]
public async Task CancelHandover([FromBody] UserHandOverAdminDTO info)
{
@@ -135,7 +135,7 @@ public async Task CreateRequet([FromBody] UserHandOverAdminDTO inf
#endregion
#region Search Transfer Request for Admin
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet("search-request")]
public async Task>> SearchFirstNameRequest([FromQuery] string query)
{
diff --git a/LostFound_API/Controllers/UsersController.cs b/LostFound_API/Controllers/UsersController.cs
index 377dad4..ae41a42 100644
--- a/LostFound_API/Controllers/UsersController.cs
+++ b/LostFound_API/Controllers/UsersController.cs
@@ -59,7 +59,7 @@ public UsersController(IUsersRepository usersRepository,
// GET: api/
#region Get All Users
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet]
public async Task>> Get()
{
@@ -85,7 +85,7 @@ public async Task>> Get()
#endregion
#region Search Email
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpGet("search-email")]
public async Task>> SearchEmail([FromQuery] string query)
{
@@ -169,7 +169,7 @@ public string Get(int id)
// POST api/
#region Sign Up
[HttpPost("sign-up")]
- public async Task SignUp([FromBody] Users user)
+ public async Task SignUp([FromBody] UserDTO user)
{
if (user == null)
{
@@ -179,22 +179,10 @@ public async Task SignUp([FromBody] Users user)
});
}
- var institutionIdExist = institutionRepository.GetInstitutionByID(user.InstitutionId);
+ var institutionIdExist = await institutionRepository.GetInstitutionByNameAndAddress(user.InstitutionName, user.InstitutionAddress);
if (institutionIdExist != null)
{
- // Check duplicate student ID
- var existingStudentByStudentId = studentRepository.AllStudents()
- .Any(s => s.StudentId == user.StudentId);
-
- if (existingStudentByStudentId)
- {
- return Conflict(new
- {
- message = "Your student ID is already in use"
- });
- }
-
// Check duplicate email
var existingUserByEmail = usersRepository.AllUsers()
.Any(u => u.Email.Equals(user.Email));
@@ -209,7 +197,20 @@ public async Task SignUp([FromBody] Users user)
try
{
- var isAddedUser = await usersRepository.SignUp(user);
+ var userSignUp = new Users
+ {
+ UserId = user.UserId,
+ Email = user.Email,
+ FirstName = user.FirstName,
+ LastName = user.LastName,
+ InstitutionId = institutionIdExist.InstitutionId,
+ Password = user.Password,
+ PickImage1 = user.PickImage1,
+ PickImage2 = user.PickImage2,
+ Role = Role.Student,
+ };
+
+ var isAddedUser = await usersRepository.SignUp(userSignUp);
if (isAddedUser)
{
var student = new Student
@@ -319,7 +320,7 @@ .content p {{
- ✅ Verify Email
+ ✅ Verify Email
If you did not perform this action, you can safely ignore this email.
@@ -401,7 +402,7 @@ .content p {{
{
return BadRequest(new
{
- message = "This campus is not available in the system, so the account cannot be created."
+ message = "Your account cannot be created because your campus has not joined the platform yet."
});
}
}
@@ -413,7 +414,7 @@ public async Task SignIn([FromBody] SignInRequestDTO signInRequest
{
try
{
- var user = await usersRepository.SignIn(signInRequestDTO.StudentId, signInRequestDTO.Password, signInRequestDTO.Email);
+ var user = await usersRepository.SignIn(signInRequestDTO.Password, signInRequestDTO.Email);
if (user != null && !user.IsActive)
{
@@ -425,9 +426,7 @@ public async Task SignIn([FromBody] SignInRequestDTO signInRequest
return Ok(); // Ok to change to select image
}
- return Unauthorized(signInRequestDTO.StudentId == 0 ?
- "Email or password is invalid" :
- "Student ID or password is invalid");
+ return Unauthorized("Email or password is invalid");
}
catch (Exception ex)
{
@@ -442,7 +441,7 @@ public async Task SelectImages([FromBody] SignInRequestDTO signIn
{
try
{
- var user = await usersRepository.SignIn(signInRequestDTO.StudentId, signInRequestDTO.Password, signInRequestDTO.Email);
+ var user = await usersRepository.SignIn(signInRequestDTO.Password, signInRequestDTO.Email);
if (user != null)
{
// Check Pick image
@@ -644,7 +643,7 @@ .content p {{
- Go to Profile
+ Go to Profile
If you did not perform this action, please contact our support team immediately.
@@ -819,7 +818,7 @@ .content p {{
- ✅ Verify Email
+ ✅ Verify Email
If you did not perform this action, you can safely ignore this email.
@@ -1022,7 +1021,7 @@ .content p {{
- Reset Password
+ Reset Password
If you did not request a password reset, please ignore this email. Your account will remain secure.
@@ -1320,7 +1319,7 @@ support team immediately to secure your account.
#endregion
#region Suspend User
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpPut("suspend-user/{userId}")]
public async Task SuspendUser(int userId)
{
@@ -1367,7 +1366,7 @@ await hubContext.Clients.Group("Admin")
#endregion
#region Unsuspend User
- [Authorize(Roles = "Admin")]
+ [Authorize(Roles = "SystemAdmin")]
[HttpPut("unsuspend-user/{userId}")]
public async Task UnsuspendUser(int userId)
{
diff --git a/LostFound_API/DTOs/Auth/SignInRequestDTO.cs b/LostFound_API/DTOs/Auth/SignInRequestDTO.cs
index 8dc1af0..5918fb9 100644
--- a/LostFound_API/DTOs/Auth/SignInRequestDTO.cs
+++ b/LostFound_API/DTOs/Auth/SignInRequestDTO.cs
@@ -2,9 +2,8 @@
{
public class SignInRequestDTO
{
- public int StudentId { get; set; }
public string Password { get; set; }
- public string? Email { get; set; }
+ public string Email { get; set; }
public List? PickedIndexes { get; set; }
}
}
diff --git a/LostFound_API/DTOs/Users/UserDTO.cs b/LostFound_API/DTOs/Users/UserDTO.cs
index 539af40..eeff2cf 100644
--- a/LostFound_API/DTOs/Users/UserDTO.cs
+++ b/LostFound_API/DTOs/Users/UserDTO.cs
@@ -1,11 +1,32 @@
-namespace LostFound_API.DTOs.Users
+using ObjectBusiness;
+using System.ComponentModel.DataAnnotations.Schema;
+
+namespace LostFound_API.DTOs.Users
{
public class UserDTO
{
+ public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IFormFile? AvatarUpload { get; set; }
public DateTime? DateOfBirth { get; set; }
public string? Avatar { get; set; }
+ public string? InstitutionName { get; set; }
+ public string? InstitutionAddress { get; set; }
+ public string? InstitutionCity { get; set; }
+ public string? InstitutionState { get; set; }
+ public string? InstitutionPhone { get; set; }
+ public string? InstitutionWebsite { get; set; }
+ public string Email { get; set; }
+ public int UserId { get; set; }
+ public Role Role { get; set; }
+ public string Password { get; set; }
+ public string PickImage1 { get; set; }
+ public string PickImage2 { get; set; }
+
+ [NotMapped] // This property is not mapped to the database
+ public string? AccessToken { get; set; }
+ [NotMapped]
+ public int ExpiresIn { get; set; }
}
}
diff --git a/LostFound_API/Program.cs b/LostFound_API/Program.cs
index 7e9d1f2..a0cc519 100644
--- a/LostFound_API/Program.cs
+++ b/LostFound_API/Program.cs
@@ -110,6 +110,7 @@
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
+builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
@@ -122,6 +123,7 @@
builder.Services.AddScoped();
builder.Services.AddScoped();
builder.Services.AddScoped();
+builder.Services.AddScoped();
// Register BackgroundService
builder.Services.AddHostedService();
@@ -129,7 +131,13 @@
// Connect to SQL Server
builder.Services.AddDbContext(options =>
{
- options.UseSqlServer(builder.Configuration.GetConnectionString("MyConnection"));
+ options.UseSqlServer(
+ builder.Configuration.GetConnectionString("MyConnection"),
+ sql => sql.EnableRetryOnFailure(
+ maxRetryCount: 5,
+ maxRetryDelay: TimeSpan.FromSeconds(10),
+ errorNumbersToAdd: null
+ ));
});
// Register for send email
diff --git a/ObjectBusiness/BackToMeDbContext.cs b/ObjectBusiness/BackToMeDbContext.cs
index 76f7d21..6820e86 100644
--- a/ObjectBusiness/BackToMeDbContext.cs
+++ b/ObjectBusiness/BackToMeDbContext.cs
@@ -29,10 +29,53 @@ public BackToMeDbContext(DbContextOptions options) : base(opt
public DbSet MessageChat { get; set; }
public DbSet Notifications { get; set; }
public DbSet Institutions { get; set; }
+ public DbSet InstitutionRequests { get; set; }
#endregion
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
+ modelBuilder.Entity()
+ .HasOne(m => m.Chat)
+ .WithMany(c => c.Messages)
+ .HasForeignKey(m => m.ChatId)
+ .OnDelete(DeleteBehavior.NoAction);
+
+ modelBuilder.Entity()
+ .HasOne(m => m.PostOriginal)
+ .WithMany()
+ .HasForeignKey(m => m.PostOriginalId)
+ .OnDelete(DeleteBehavior.NoAction); // Prevent multiple cascade paths issue in SQL Server
+
+ modelBuilder.Entity()
+ .HasOne(m => m.PostMatched)
+ .WithMany()
+ .HasForeignKey(m => m.PostMatchedId)
+ .OnDelete(DeleteBehavior.NoAction); // Prevent multiple cascade paths issue in SQL Server
+
+ modelBuilder.Entity()
+ .HasOne(m => m.LostPost)
+ .WithMany()
+ .HasForeignKey(m => m.LostPostId)
+ .OnDelete(DeleteBehavior.NoAction); // Prevent multiple cascade paths issue in SQL Server
+
+ modelBuilder.Entity()
+ .HasOne(m => m.FoundPost)
+ .WithMany()
+ .HasForeignKey(m => m.FoundPostId)
+ .OnDelete(DeleteBehavior.NoAction); // Prevent multiple cascade paths issue in SQL Server
+
+ modelBuilder.Entity()
+ .HasOne(c => c.UserA)
+ .WithMany()
+ .HasForeignKey(c => c.UserAId)
+ .OnDelete(DeleteBehavior.NoAction); // Prevent multiple cascade paths issue in SQL Server
+
+ modelBuilder.Entity()
+ .HasOne(c => c.UserB)
+ .WithMany()
+ .HasForeignKey(c => c.UserBId)
+ .OnDelete(DeleteBehavior.NoAction); // Prevent multiple cascade paths issue in SQL Server
+
// Allow converting enum to string
modelBuilder.Entity()
.Property(u => u.Role)
@@ -48,6 +91,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
.Property(u => u.Status)
.HasConversion();
+ // Allow converting enum to string
+ modelBuilder.Entity()
+ .Property(u => u.Status)
+ .HasConversion();
+
// Allow converting enum to string
modelBuilder.Entity()
.Property(u => u.Status)
@@ -62,16 +110,17 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity().HasData(new Users
{
UserId = new Random().Next(),
- FirstName = "Media",
- LastName = "Center",
+ FirstName = "System",
+ LastName = "Admin",
Password = "$2a$11$92uWViLQUKTIVIADFxhzqe39tDMoLWJX5e1FyXaeedfrq5CoMAGQ6",
CreatedAt = DateTime.Now,
IsActive = true,
+ IsVerifiedEmail = true,
IsAgreedToTerms = true,
- Email = "baoanwebapp@gmail.com",
+ Email = "mycampuslostfound@gmail.com",
PickImage1 = "1",
PickImage2 = "12",
- Role = Role.Admin,
+ Role = Role.SystemAdmin,
Avatar = "avatar_CV.jpg",
DateOfBirth = new DateTime(2007, 01, 30),
});
diff --git a/ObjectBusiness/Institution.cs b/ObjectBusiness/Institution.cs
index 18cf8de..3ec689f 100644
--- a/ObjectBusiness/Institution.cs
+++ b/ObjectBusiness/Institution.cs
@@ -14,18 +14,15 @@ public class Institution
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int InstitutionId { get; set; }
+ [Required(ErrorMessage = "Institution name cannot be blank")]
public string InstitutionName { get; set; }
+ [Required]
public string InstitutionAddress { get; set; }
+ [Required]
public string InstitutionCity { get; set; }
+ [Required]
public string InstitutionState { get; set; }
- public string Password { get; set; }
- public string PickImage1 { get; set; }
- public string PickImage2 { get; set; }
- public bool IsActive { get; set; } = true;
- public bool IsAgreedToTerms { get; set; } = false;
- public bool IsVerifiedEmail { get; set; } = false;
public string? InstitutionPhone { get; set; }
- public string? InstitutionEmail { get; set; }
public string? InstitutionWebsite { get; set; }
public string? InstitutionLogo { get; set; }
public string? InstitutionDescription { get; set; }
diff --git a/ObjectBusiness/InstitutionRequest.cs b/ObjectBusiness/InstitutionRequest.cs
new file mode 100644
index 0000000..026916d
--- /dev/null
+++ b/ObjectBusiness/InstitutionRequest.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ObjectBusiness
+{
+ public class InstitutionRequest
+ {
+ [Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
+ public int InstitutionRequestId { get; set; }
+ [Required(ErrorMessage = "Institution name cannot be blank")]
+ public string InstitutionName { get; set; }
+ [Required]
+ public string InstitutionAddress { get; set; }
+ [Required]
+ public string InstitutionCity { get; set; }
+ [Required]
+ public string InstitutionState { get; set; }
+ public string? InstitutionPhone { get; set; }
+ public string? InstitutionWebsite { get; set; }
+ public string? AdditionalInformation { get; set; }
+ public string? AdditionalNote { get; set; }
+ public StudentPopulationRange? EstimatedPopulation { get; set; }
+ [Required(ErrorMessage = "Applicant name cannot be blank")]
+ public string ApplicantName { get; set; }
+ public string? ApplicantPhoneNumber { get; set; }
+ [Required(ErrorMessage = "Work email cannot be blank")]
+ public string WorkEmail { get; set; }
+ [Required(ErrorMessage = "Job title cannot be blank")]
+ public string JobTitle { get; set; }
+ public bool IsVerifiedEmail { get; set; } = false;
+ public bool IsVerifiedWebsite { get; set; } = false;
+ public bool IsVerifiedInstitution { get; set; } = false;
+ public StatusRequestInstitution Status { get; set; } = StatusRequestInstitution.Pending;
+ public string? InstitutionDescription { get; set; }
+ public string? ReviewedBy { get; set; }
+ public DateTime? EmailVerifiedAt { get; set; }
+ public DateTime? WebsiteVerifiedAt { get; set; }
+ public DateTime? ReviewedDate { get; set; }
+ public string? RejectReason { get; set; }
+ public DateTime SubmittedDate { get; set; } = DateTime.Now;
+ public DateTime? UpdatedDate { get; set; }
+ }
+}
diff --git a/ObjectBusiness/Migrations/20260725184416_Initial.Designer.cs b/ObjectBusiness/Migrations/20260725184416_Initial.Designer.cs
new file mode 100644
index 0000000..43abb33
--- /dev/null
+++ b/ObjectBusiness/Migrations/20260725184416_Initial.Designer.cs
@@ -0,0 +1,787 @@
+//
+using System;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using ObjectBusiness;
+
+#nullable disable
+
+namespace ObjectBusiness.Migrations
+{
+ [DbContext(typeof(BackToMeDbContext))]
+ [Migration("20260725184416_Initial")]
+ partial class Initial
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "8.0.28")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("ObjectBusiness.CategoryPost", b =>
+ {
+ b.Property("CategoryPostId")
+ .HasColumnType("int");
+
+ b.Property("CategoryPostName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("CategoryPostId");
+
+ b.ToTable("CategoryPost");
+
+ b.HasData(
+ new
+ {
+ CategoryPostId = 781275554,
+ CategoryPostName = "IPhone",
+ CreatedAt = new DateTime(2026, 7, 25, 14, 44, 15, 852, DateTimeKind.Local).AddTicks(3499)
+ },
+ new
+ {
+ CategoryPostId = 171705011,
+ CategoryPostName = "IPad",
+ CreatedAt = new DateTime(2026, 7, 25, 14, 44, 15, 852, DateTimeKind.Local).AddTicks(3503)
+ },
+ new
+ {
+ CategoryPostId = 357308849,
+ CategoryPostName = "Chromebook",
+ CreatedAt = new DateTime(2026, 7, 25, 14, 44, 15, 852, DateTimeKind.Local).AddTicks(3532)
+ },
+ new
+ {
+ CategoryPostId = 663701472,
+ CategoryPostName = "Earbuds",
+ CreatedAt = new DateTime(2026, 7, 25, 14, 44, 15, 852, DateTimeKind.Local).AddTicks(3537)
+ },
+ new
+ {
+ CategoryPostId = 1270846585,
+ CategoryPostName = "Wallet",
+ CreatedAt = new DateTime(2026, 7, 25, 14, 44, 15, 852, DateTimeKind.Local).AddTicks(3541)
+ },
+ new
+ {
+ CategoryPostId = 2145456831,
+ CategoryPostName = "Charger",
+ CreatedAt = new DateTime(2026, 7, 25, 14, 44, 15, 852, DateTimeKind.Local).AddTicks(3545)
+ });
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Chat", b =>
+ {
+ b.Property("ChatId")
+ .HasColumnType("int");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("PostId")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UserAId")
+ .HasColumnType("int");
+
+ b.Property("UserALastReadMessageId")
+ .HasColumnType("int");
+
+ b.Property("UserBId")
+ .HasColumnType("int");
+
+ b.Property("UserBLastReadMessageId")
+ .HasColumnType("int");
+
+ b.HasKey("ChatId");
+
+ b.HasIndex("PostId");
+
+ b.HasIndex("UserAId");
+
+ b.HasIndex("UserBId");
+
+ b.ToTable("Chat");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Institution", b =>
+ {
+ b.Property("InstitutionId")
+ .HasColumnType("int");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("InstitutionAddress")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionCity")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionDescription")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionLogo")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionPhone")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionState")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionWebsite")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UpdatedDate")
+ .HasColumnType("datetime2");
+
+ b.HasKey("InstitutionId");
+
+ b.ToTable("Institutions");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.InstitutionRequest", b =>
+ {
+ b.Property("InstitutionRequestId")
+ .HasColumnType("int");
+
+ b.Property("AdditionalInformation")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("AdditionalNote")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ApplicantName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ApplicantPhoneNumber")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("EmailVerifiedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("EstimatedPopulation")
+ .HasColumnType("int");
+
+ b.Property("InstitutionAddress")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionCity")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionDescription")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionPhone")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionState")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionWebsite")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsVerifiedEmail")
+ .HasColumnType("bit");
+
+ b.Property("IsVerifiedInstitution")
+ .HasColumnType("bit");
+
+ b.Property("IsVerifiedWebsite")
+ .HasColumnType("bit");
+
+ b.Property("JobTitle")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("RejectReason")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ReviewedBy")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ReviewedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("SubmittedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("WebsiteVerifiedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("WorkEmail")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("InstitutionRequestId");
+
+ b.ToTable("InstitutionRequests");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Match", b =>
+ {
+ b.Property("MatchId")
+ .HasColumnType("int");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("FoundPostId")
+ .HasColumnType("int");
+
+ b.Property("LostPostId")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("MatchId");
+
+ b.HasIndex("FoundPostId");
+
+ b.HasIndex("LostPostId");
+
+ b.ToTable("Match");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.MessageChat", b =>
+ {
+ b.Property("MessageChatId")
+ .HasColumnType("int");
+
+ b.Property("ChatId")
+ .HasColumnType("int");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("MessageContent")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UserSenderId")
+ .HasColumnType("int");
+
+ b.HasKey("MessageChatId");
+
+ b.HasIndex("ChatId");
+
+ b.HasIndex("UserSenderId");
+
+ b.ToTable("MessageChat");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Notifications", b =>
+ {
+ b.Property("NotificationId")
+ .HasColumnType("int");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("IsRead")
+ .HasColumnType("bit");
+
+ b.Property("NotificationContent")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("NotificationType")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PostMatchedId")
+ .HasColumnType("int");
+
+ b.Property("PostOriginalId")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("NotificationId");
+
+ b.HasIndex("PostMatchedId");
+
+ b.HasIndex("PostOriginalId");
+
+ b.ToTable("Notifications");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.PickUpRequest", b =>
+ {
+ b.Property("RequestId")
+ .HasColumnType("int");
+
+ b.Property("CreatedDate")
+ .HasColumnType("datetime2");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PickUpDate")
+ .HasColumnType("datetime2");
+
+ b.Property("PostId")
+ .HasColumnType("int");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UpdatedDate")
+ .HasColumnType("datetime2");
+
+ b.HasKey("RequestId");
+
+ b.HasIndex("PostId");
+
+ b.ToTable("PickUpRequest");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Posts", b =>
+ {
+ b.Property("PostId")
+ .HasColumnType("int");
+
+ b.Property("CategoryPostId")
+ .HasColumnType("int");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Description")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Image")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("IsReceived")
+ .HasColumnType("bit");
+
+ b.Property("OldUserId")
+ .HasColumnType("int");
+
+ b.Property("Title")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("TypePost")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.Property("Vector")
+ .HasColumnType("nvarchar(max)");
+
+ b.HasKey("PostId");
+
+ b.HasIndex("CategoryPostId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Posts");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Student", b =>
+ {
+ b.Property("StudentId")
+ .HasColumnType("int");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.HasKey("StudentId");
+
+ b.HasIndex("UserId")
+ .IsUnique();
+
+ b.ToTable("Student");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.TransferRequests", b =>
+ {
+ b.Property("RequestId")
+ .HasColumnType("int");
+
+ b.Property("ConfirmedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("PostId")
+ .HasColumnType("int");
+
+ b.Property("Status")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("ToUserId")
+ .HasColumnType("int");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.HasKey("RequestId");
+
+ b.HasIndex("PostId");
+
+ b.ToTable("TransferRequests");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Users", b =>
+ {
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.Property("Avatar")
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DateOfBirth")
+ .HasColumnType("datetime2");
+
+ b.Property("Email")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("FirstName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("InstitutionId")
+ .HasColumnType("int");
+
+ b.Property("IsActive")
+ .HasColumnType("bit");
+
+ b.Property("IsAgreedToTerms")
+ .HasColumnType("bit");
+
+ b.Property("IsVerifiedEmail")
+ .HasColumnType("bit");
+
+ b.Property("LastName")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Password")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PickImage1")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("PickImage2")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("Role")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("UserId");
+
+ b.HasIndex("InstitutionId");
+
+ b.ToTable("Users");
+
+ b.HasData(
+ new
+ {
+ UserId = 2043193290,
+ Avatar = "avatar_CV.jpg",
+ CreatedAt = new DateTime(2026, 7, 25, 14, 44, 15, 852, DateTimeKind.Local).AddTicks(3191),
+ DateOfBirth = new DateTime(2007, 1, 30, 0, 0, 0, 0, DateTimeKind.Unspecified),
+ Email = "mycampuslostfound@gmail.com",
+ FirstName = "System",
+ IsActive = true,
+ IsAgreedToTerms = true,
+ IsVerifiedEmail = true,
+ LastName = "Admin",
+ Password = "$2a$11$92uWViLQUKTIVIADFxhzqe39tDMoLWJX5e1FyXaeedfrq5CoMAGQ6",
+ PickImage1 = "1",
+ PickImage2 = "12",
+ Role = "SystemAdmin"
+ });
+ });
+
+ modelBuilder.Entity("ObjectBusiness.VerificationCode", b =>
+ {
+ b.Property("VerificationCodeId")
+ .HasColumnType("int");
+
+ b.Property("Code")
+ .IsRequired()
+ .HasColumnType("nvarchar(max)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("IsUsed")
+ .HasColumnType("bit");
+
+ b.Property("MatchId")
+ .HasColumnType("int");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("VerificationCodeId");
+
+ b.HasIndex("MatchId")
+ .IsUnique();
+
+ b.ToTable("VerificationCode");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Chat", b =>
+ {
+ b.HasOne("ObjectBusiness.Posts", "Post")
+ .WithMany()
+ .HasForeignKey("PostId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("ObjectBusiness.Users", "UserA")
+ .WithMany()
+ .HasForeignKey("UserAId")
+ .OnDelete(DeleteBehavior.NoAction)
+ .IsRequired();
+
+ b.HasOne("ObjectBusiness.Users", "UserB")
+ .WithMany()
+ .HasForeignKey("UserBId")
+ .OnDelete(DeleteBehavior.NoAction)
+ .IsRequired();
+
+ b.Navigation("Post");
+
+ b.Navigation("UserA");
+
+ b.Navigation("UserB");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Match", b =>
+ {
+ b.HasOne("ObjectBusiness.Posts", "FoundPost")
+ .WithMany()
+ .HasForeignKey("FoundPostId")
+ .OnDelete(DeleteBehavior.NoAction)
+ .IsRequired();
+
+ b.HasOne("ObjectBusiness.Posts", "LostPost")
+ .WithMany()
+ .HasForeignKey("LostPostId")
+ .OnDelete(DeleteBehavior.NoAction)
+ .IsRequired();
+
+ b.Navigation("FoundPost");
+
+ b.Navigation("LostPost");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.MessageChat", b =>
+ {
+ b.HasOne("ObjectBusiness.Chat", "Chat")
+ .WithMany("Messages")
+ .HasForeignKey("ChatId")
+ .OnDelete(DeleteBehavior.NoAction)
+ .IsRequired();
+
+ b.HasOne("ObjectBusiness.Users", "UserSender")
+ .WithMany()
+ .HasForeignKey("UserSenderId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Chat");
+
+ b.Navigation("UserSender");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Notifications", b =>
+ {
+ b.HasOne("ObjectBusiness.Posts", "PostMatched")
+ .WithMany()
+ .HasForeignKey("PostMatchedId")
+ .OnDelete(DeleteBehavior.NoAction)
+ .IsRequired();
+
+ b.HasOne("ObjectBusiness.Posts", "PostOriginal")
+ .WithMany()
+ .HasForeignKey("PostOriginalId")
+ .OnDelete(DeleteBehavior.NoAction)
+ .IsRequired();
+
+ b.Navigation("PostMatched");
+
+ b.Navigation("PostOriginal");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.PickUpRequest", b =>
+ {
+ b.HasOne("ObjectBusiness.Posts", "Post")
+ .WithMany()
+ .HasForeignKey("PostId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Post");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Posts", b =>
+ {
+ b.HasOne("ObjectBusiness.CategoryPost", "CategoryPost")
+ .WithMany()
+ .HasForeignKey("CategoryPostId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("ObjectBusiness.Users", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("CategoryPost");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Student", b =>
+ {
+ b.HasOne("ObjectBusiness.Users", "User")
+ .WithOne("Student")
+ .HasForeignKey("ObjectBusiness.Student", "UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.TransferRequests", b =>
+ {
+ b.HasOne("ObjectBusiness.Posts", "Post")
+ .WithMany("TransferRequests")
+ .HasForeignKey("PostId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Post");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Users", b =>
+ {
+ b.HasOne("ObjectBusiness.Institution", "Institution")
+ .WithMany("Users")
+ .HasForeignKey("InstitutionId");
+
+ b.Navigation("Institution");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.VerificationCode", b =>
+ {
+ b.HasOne("ObjectBusiness.Match", "Match")
+ .WithOne("VerificationCode")
+ .HasForeignKey("ObjectBusiness.VerificationCode", "MatchId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Match");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Chat", b =>
+ {
+ b.Navigation("Messages");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Institution", b =>
+ {
+ b.Navigation("Users");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Match", b =>
+ {
+ b.Navigation("VerificationCode");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Posts", b =>
+ {
+ b.Navigation("TransferRequests");
+ });
+
+ modelBuilder.Entity("ObjectBusiness.Users", b =>
+ {
+ b.Navigation("Student");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/ObjectBusiness/Migrations/20260725184416_Initial.cs b/ObjectBusiness/Migrations/20260725184416_Initial.cs
new file mode 100644
index 0000000..f797e4d
--- /dev/null
+++ b/ObjectBusiness/Migrations/20260725184416_Initial.cs
@@ -0,0 +1,499 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
+
+namespace ObjectBusiness.Migrations
+{
+ ///
+ public partial class Initial : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "CategoryPost",
+ columns: table => new
+ {
+ CategoryPostId = table.Column(type: "int", nullable: false),
+ CategoryPostName = table.Column(type: "nvarchar(max)", nullable: false),
+ CreatedAt = table.Column(type: "datetime2", nullable: false),
+ UpdatedAt = table.Column(type: "datetime2", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_CategoryPost", x => x.CategoryPostId);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "InstitutionRequests",
+ columns: table => new
+ {
+ InstitutionRequestId = table.Column(type: "int", nullable: false),
+ InstitutionName = table.Column(type: "nvarchar(max)", nullable: false),
+ InstitutionAddress = table.Column(type: "nvarchar(max)", nullable: false),
+ InstitutionCity = table.Column(type: "nvarchar(max)", nullable: false),
+ InstitutionState = table.Column(type: "nvarchar(max)", nullable: false),
+ InstitutionPhone = table.Column(type: "nvarchar(max)", nullable: true),
+ InstitutionWebsite = table.Column(type: "nvarchar(max)", nullable: true),
+ AdditionalInformation = table.Column(type: "nvarchar(max)", nullable: true),
+ AdditionalNote = table.Column(type: "nvarchar(max)", nullable: true),
+ EstimatedPopulation = table.Column(type: "int", nullable: true),
+ ApplicantName = table.Column(type: "nvarchar(max)", nullable: false),
+ ApplicantPhoneNumber = table.Column(type: "nvarchar(max)", nullable: true),
+ WorkEmail = table.Column(type: "nvarchar(max)", nullable: false),
+ JobTitle = table.Column(type: "nvarchar(max)", nullable: false),
+ IsVerifiedEmail = table.Column(type: "bit", nullable: false),
+ IsVerifiedWebsite = table.Column(type: "bit", nullable: false),
+ IsVerifiedInstitution = table.Column(type: "bit", nullable: false),
+ Status = table.Column(type: "nvarchar(max)", nullable: false),
+ InstitutionDescription = table.Column(type: "nvarchar(max)", nullable: true),
+ ReviewedBy = table.Column(type: "nvarchar(max)", nullable: true),
+ EmailVerifiedAt = table.Column(type: "datetime2", nullable: true),
+ WebsiteVerifiedAt = table.Column(type: "datetime2", nullable: true),
+ ReviewedDate = table.Column(type: "datetime2", nullable: true),
+ RejectReason = table.Column(type: "nvarchar(max)", nullable: true),
+ SubmittedDate = table.Column(type: "datetime2", nullable: false),
+ UpdatedDate = table.Column(type: "datetime2", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_InstitutionRequests", x => x.InstitutionRequestId);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Institutions",
+ columns: table => new
+ {
+ InstitutionId = table.Column(type: "int", nullable: false),
+ InstitutionName = table.Column(type: "nvarchar(max)", nullable: false),
+ InstitutionAddress = table.Column(type: "nvarchar(max)", nullable: false),
+ InstitutionCity = table.Column(type: "nvarchar(max)", nullable: false),
+ InstitutionState = table.Column(type: "nvarchar(max)", nullable: false),
+ InstitutionPhone = table.Column(type: "nvarchar(max)", nullable: true),
+ InstitutionWebsite = table.Column(type: "nvarchar(max)", nullable: true),
+ InstitutionLogo = table.Column(type: "nvarchar(max)", nullable: true),
+ InstitutionDescription = table.Column(type: "nvarchar(max)", nullable: true),
+ CreatedDate = table.Column(type: "datetime2", nullable: false),
+ UpdatedDate = table.Column(type: "datetime2", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Institutions", x => x.InstitutionId);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Users",
+ columns: table => new
+ {
+ UserId = table.Column(type: "int", nullable: false),
+ InstitutionId = table.Column(type: "int", nullable: true),
+ FirstName = table.Column(type: "nvarchar(max)", nullable: false),
+ LastName = table.Column(type: "nvarchar(max)", nullable: false),
+ Email = table.Column(type: "nvarchar(max)", nullable: false),
+ DateOfBirth = table.Column(type: "datetime2", nullable: true),
+ Role = table.Column(type: "nvarchar(max)", nullable: false),
+ Password = table.Column(type: "nvarchar(max)", nullable: false),
+ Avatar = table.Column(type: "nvarchar(max)", nullable: true),
+ PickImage1 = table.Column(type: "nvarchar(max)", nullable: false),
+ PickImage2 = table.Column(type: "nvarchar(max)", nullable: false),
+ IsActive = table.Column(type: "bit", nullable: false),
+ IsAgreedToTerms = table.Column(type: "bit", nullable: false),
+ IsVerifiedEmail = table.Column(type: "bit", nullable: false),
+ CreatedAt = table.Column(type: "datetime2", nullable: false),
+ UpdatedAt = table.Column(type: "datetime2", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Users", x => x.UserId);
+ table.ForeignKey(
+ name: "FK_Users_Institutions_InstitutionId",
+ column: x => x.InstitutionId,
+ principalTable: "Institutions",
+ principalColumn: "InstitutionId");
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Posts",
+ columns: table => new
+ {
+ PostId = table.Column(type: "int", nullable: false),
+ UserId = table.Column(type: "int", nullable: false),
+ Title = table.Column(type: "nvarchar(max)", nullable: false),
+ Description = table.Column(type: "nvarchar(max)", nullable: false),
+ CategoryPostId = table.Column(type: "int", nullable: false),
+ TypePost = table.Column(type: "nvarchar(max)", nullable: false),
+ Vector = table.Column(type: "nvarchar(max)", nullable: true),
+ Image = table.Column(type: "nvarchar(max)", nullable: true),
+ Code = table.Column(type: "nvarchar(max)", nullable: false),
+ IsReceived = table.Column(type: "bit", nullable: true),
+ OldUserId = table.Column(type: "int", nullable: true),
+ CreatedAt = table.Column(type: "datetime2", nullable: false),
+ UpdatedAt = table.Column(type: "datetime2", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Posts", x => x.PostId);
+ table.ForeignKey(
+ name: "FK_Posts_CategoryPost_CategoryPostId",
+ column: x => x.CategoryPostId,
+ principalTable: "CategoryPost",
+ principalColumn: "CategoryPostId",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_Posts_Users_UserId",
+ column: x => x.UserId,
+ principalTable: "Users",
+ principalColumn: "UserId",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Student",
+ columns: table => new
+ {
+ StudentId = table.Column(type: "int", nullable: false),
+ UserId = table.Column(type: "int", nullable: false),
+ CreatedAt = table.Column(type: "datetime2", nullable: false),
+ UpdatedAt = table.Column(type: "datetime2", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Student", x => x.StudentId);
+ table.ForeignKey(
+ name: "FK_Student_Users_UserId",
+ column: x => x.UserId,
+ principalTable: "Users",
+ principalColumn: "UserId",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Chat",
+ columns: table => new
+ {
+ ChatId = table.Column(type: "int", nullable: false),
+ UserAId = table.Column(type: "int", nullable: false),
+ UserBId = table.Column(type: "int", nullable: false),
+ PostId = table.Column(type: "int", nullable: false),
+ UserALastReadMessageId = table.Column(type: "int", nullable: false),
+ UserBLastReadMessageId = table.Column(type: "int", nullable: false),
+ CreatedAt = table.Column(type: "datetime2", nullable: false),
+ UpdatedAt = table.Column(type: "datetime2", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Chat", x => x.ChatId);
+ table.ForeignKey(
+ name: "FK_Chat_Posts_PostId",
+ column: x => x.PostId,
+ principalTable: "Posts",
+ principalColumn: "PostId",
+ onDelete: ReferentialAction.Cascade);
+ table.ForeignKey(
+ name: "FK_Chat_Users_UserAId",
+ column: x => x.UserAId,
+ principalTable: "Users",
+ principalColumn: "UserId");
+ table.ForeignKey(
+ name: "FK_Chat_Users_UserBId",
+ column: x => x.UserBId,
+ principalTable: "Users",
+ principalColumn: "UserId");
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Match",
+ columns: table => new
+ {
+ MatchId = table.Column(type: "int", nullable: false),
+ LostPostId = table.Column(type: "int", nullable: false),
+ FoundPostId = table.Column(type: "int", nullable: false),
+ CreatedAt = table.Column(type: "datetime2", nullable: false),
+ UpdatedAt = table.Column(type: "datetime2", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Match", x => x.MatchId);
+ table.ForeignKey(
+ name: "FK_Match_Posts_FoundPostId",
+ column: x => x.FoundPostId,
+ principalTable: "Posts",
+ principalColumn: "PostId");
+ table.ForeignKey(
+ name: "FK_Match_Posts_LostPostId",
+ column: x => x.LostPostId,
+ principalTable: "Posts",
+ principalColumn: "PostId");
+ });
+
+ migrationBuilder.CreateTable(
+ name: "Notifications",
+ columns: table => new
+ {
+ NotificationId = table.Column(type: "int", nullable: false),
+ PostOriginalId = table.Column(type: "int", nullable: false),
+ PostMatchedId = table.Column(type: "int", nullable: false),
+ NotificationType = table.Column(type: "nvarchar(max)", nullable: false),
+ NotificationContent = table.Column(type: "nvarchar(max)", nullable: false),
+ IsRead = table.Column(type: "bit", nullable: false),
+ CreatedAt = table.Column(type: "datetime2", nullable: false),
+ UpdatedAt = table.Column(type: "datetime2", nullable: true)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_Notifications", x => x.NotificationId);
+ table.ForeignKey(
+ name: "FK_Notifications_Posts_PostMatchedId",
+ column: x => x.PostMatchedId,
+ principalTable: "Posts",
+ principalColumn: "PostId");
+ table.ForeignKey(
+ name: "FK_Notifications_Posts_PostOriginalId",
+ column: x => x.PostOriginalId,
+ principalTable: "Posts",
+ principalColumn: "PostId");
+ });
+
+ migrationBuilder.CreateTable(
+ name: "PickUpRequest",
+ columns: table => new
+ {
+ RequestId = table.Column