diff --git a/Gordon360.Tests/Controllers_Test/ScheduleControllerTest.cs b/Gordon360.Tests/Controllers_Test/ScheduleControllerTest.cs index 08e9616e5..865821e36 100644 --- a/Gordon360.Tests/Controllers_Test/ScheduleControllerTest.cs +++ b/Gordon360.Tests/Controllers_Test/ScheduleControllerTest.cs @@ -2,13 +2,21 @@ namespace Gordon360.Tests.Controllers_Test; public class ScheduleControllerTest { - private readonly Mock _mockService; + private readonly Mock _mockProfileService; + private readonly Mock _mockScheduleService; + private readonly Mock _mockAccountService; private readonly ScheduleController _controller; public ScheduleControllerTest() { - _mockService = new Mock(); - _controller = new ScheduleController(_mockService.Object); + _mockProfileService = new Mock(); + _mockScheduleService = new Mock(); + _mockAccountService = new Mock(); + _controller = new ScheduleController( + _mockProfileService.Object, + _mockScheduleService.Object, + _mockAccountService.Object + ); } private void SetUser(string username, string group) @@ -57,7 +65,7 @@ public async Task GetAllCoursesByTerm_ReturnsOk_ForSelf() var courses = new List { GetSampleCourse() }; var term = new CoursesByTermViewModel("2024", "SP", "Spring 2024", new DateTime(2024, 1, 10), new DateTime(2024, 5, 10),"B", courses); var terms = new List { term }; - _mockService.Setup(s => s.GetAllCoursesByTermAsync(username)).ReturnsAsync(terms); + _mockScheduleService.Setup(s => s.GetAllCoursesByTermAsync(username)).ReturnsAsync(terms); SetUser(username, "360-Student-SG"); @@ -76,7 +84,7 @@ public async Task GetAllCoursesByTerm_ReturnsOk_ForFacStaff() var courses = new List { GetSampleCourse() }; var term = new CoursesByTermViewModel("2024", "SP", "Spring 2024", new DateTime(2024, 1, 10), new DateTime(2024, 5, 10),"B", courses); var terms = new List { term }; - _mockService.Setup(s => s.GetAllCoursesByTermAsync(username)).ReturnsAsync(terms); + _mockScheduleService.Setup(s => s.GetAllCoursesByTermAsync(username)).ReturnsAsync(terms); SetUser(username, "360-FacStaff-SG"); @@ -96,7 +104,7 @@ public async Task GetAllCoursesByTerm_ReturnsEmpty_ForOtherStudent() SetUser(requestingUsername, "360-Student-SG"); - _mockService.Setup(s => s.GetAllCoursesByTermAsync(It.IsAny())) + _mockScheduleService.Setup(s => s.GetAllCoursesByTermAsync(It.IsAny())) .ReturnsAsync(new List()); var result = await _controller.GetAllCoursesByTerm(targetUsername); @@ -108,4 +116,4 @@ public async Task GetAllCoursesByTerm_ReturnsEmpty_ForOtherStudent() -} \ No newline at end of file +} diff --git a/Gordon360/Authorization/StateYourBusiness.cs b/Gordon360/Authorization/StateYourBusiness.cs index a871c27da..8872c6346 100644 --- a/Gordon360/Authorization/StateYourBusiness.cs +++ b/Gordon360/Authorization/StateYourBusiness.cs @@ -698,7 +698,16 @@ private async Task CanUpdateAsync(string resource) return false; } + case Resource.PROFILE_PRIVACY: + { + // Currently all users can change their privacy settings + // in their profile. To limit this functionality to + // certian user types, change the return statement to be + // something like: + // return user_groups.Contains(AuthGroup.FacStaff); + return true; + } case Resource.ACTIVITY_INFO: { // User is admin diff --git a/Gordon360/Controllers/AdvancedSearchController.cs b/Gordon360/Controllers/AdvancedSearchController.cs index 037371fa1..2aa58880e 100644 --- a/Gordon360/Controllers/AdvancedSearchController.cs +++ b/Gordon360/Controllers/AdvancedSearchController.cs @@ -125,28 +125,13 @@ public ActionResult> GetDepartments() /// All buildings [HttpGet] [Route("building")] + [Route("buildings")] public async Task>> GetBuildingsAsync([FromServices] webSQLContext webSQLContext) { var buildings = await webSQLContext.Procedures.account_list_buildingsAsync(); return Ok(buildings.Select(b => new BuildingViewModel(b.BLDG_CDE, b.BUILDING_DESC))); } - /// - /// Return a list of buildings. - /// - /// All buildings - [Obsolete("Use GetBuildingsAsync that gives structured building data")] - [HttpGet] - [Route("buildings")] - public ActionResult> GetBuildings() - { - var buildings = context.FacStaff.Select(fs => fs.BuildingDescription) - .Distinct() - .Where(d => d != null) - .OrderBy(d => d); - return Ok(buildings); - } - /// /// Return a list of involvements' descriptions. /// diff --git a/Gordon360/Controllers/ProfilesController.cs b/Gordon360/Controllers/ProfilesController.cs index 9edda59cd..b1dffc3b4 100644 --- a/Gordon360/Controllers/ProfilesController.cs +++ b/Gordon360/Controllers/ProfilesController.cs @@ -16,6 +16,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Gordon360.Models.CCT.Context; namespace Gordon360.Controllers; @@ -23,7 +24,8 @@ namespace Gordon360.Controllers; public class ProfilesController(IProfileService profileService, IAccountService accountService, IMembershipService membershipService, - IConfiguration config) : GordonControllerBase + IConfiguration config, + CCTContext context) : GordonControllerBase { /// Get profile info of currently logged in user @@ -44,66 +46,37 @@ public class ProfilesController(IProfileService profileService, return Ok(null); } - var profile = profileService.ComposeProfile(student, alumni, faculty, customInfo); + var profile = (CombinedProfileViewModel) profileService.ComposeProfile(student, alumni, faculty, customInfo); return Ok(profile); } - /// Get public profile info for a user + /// Get another user's profile info. The info returned depends + /// on the permissions of the current user, who is making the request. /// username of the profile info /// [HttpGet] [Route("{username}")] - public ActionResult GetUserProfile(string username) + public ActionResult GetUserProfileAsync(string username) { var viewerGroups = AuthUtils.GetGroups(User); - var _student = profileService.GetStudentProfileByUsername(username); - var _faculty = profileService.GetFacultyStaffProfileByUsername(username); - var _alumni = profileService.GetAlumniProfileByUsername(username); + StudentProfileViewModel? _student = profileService.GetStudentProfileByUsername(username); + FacultyStaffProfileViewModel? _facstaff = profileService.GetFacultyStaffProfileByUsername(username); + AlumniProfileViewModel? _alumni = profileService.GetAlumniProfileByUsername(username); var _customInfo = profileService.GetCustomUserInfo(username); - object? student = null; - object? faculty = null; - object? alumni = null; + var student = accountService.VisibleToMeStudent(viewerGroups, _student); + var facstaff = accountService.VisibleToMeFacstaff(viewerGroups, _facstaff); + var alumni = accountService.VisibleToMeAlumni(viewerGroups, _alumni); - if (viewerGroups.Contains(AuthGroup.SiteAdmin) || viewerGroups.Contains(AuthGroup.Police)) - { - student = _student; - faculty = _faculty; - alumni = _alumni; - } - else if (viewerGroups.Contains(AuthGroup.FacStaff)) - { - student = _student; - faculty = _faculty == null ? null : (PublicFacultyStaffProfileViewModel)_faculty; - alumni = _alumni == null ? null : (PublicAlumniProfileViewModel)_alumni; - } - else if (viewerGroups.Contains(AuthGroup.Student)) - { - student = _student == null ? null : (PublicStudentProfileViewModel)_student; - faculty = _faculty == null ? null : (PublicFacultyStaffProfileViewModel)_faculty; - // If this student is also in Alumni AuthGroup, then s/he can see alumni's - // public profile; if not, return null. - alumni = (_alumni == null) ? null : - viewerGroups.Contains(AuthGroup.Alumni) ? - (PublicAlumniProfileViewModel)_alumni : null; - } - else if (viewerGroups.Contains(AuthGroup.Alumni)) - { - student = null; - faculty = _faculty == null ? null : (PublicFacultyStaffProfileViewModel)_faculty; - alumni = _alumni == null ? null : (PublicAlumniProfileViewModel)_alumni; - } - - if (student is null && alumni is null && faculty is null) + if (student is null && alumni is null && facstaff is null) { return Ok(null); } - - var profile = profileService.ComposeProfile(student, alumni, faculty, _customInfo); - - return Ok(profile); + var profile = profileService.ComposeProfile(student, alumni, facstaff, _customInfo); + var visible_profile = profileService.ImposePrivacySettings(viewerGroups, profile); + return Ok(visible_profile); } ///Get the advisor(s) of a particular student @@ -121,6 +94,20 @@ public async Task>> GetAdvisorsAsync( return Ok(advisors); } + ///Get the privacy settings of a particular user + /// + /// All privacy settings of the given user. + /// + [HttpGet] + [Route("{username}/privacy_settings")] + [StateYourBusiness(operation = Operation.READ_ONE, resource = Resource.PROFILE)] + public ActionResult> GetPrivacySettingsAsync(string username) + { + var privacy = profileService.GetPrivacySettingsAsync(username); + + return Ok(privacy); + } + /// Gets the clifton strengths of a particular user /// The username for which to retrieve info /// Clifton strengths of the given user. @@ -466,6 +453,35 @@ public async Task> UpdateOfficeHours( return Ok(result); } + /// + /// Set visibility group for some piece of personal data + /// + /// Faculty Staff Privacy Decisions (see UserPrivacyUpdateViewModel) + /// + [HttpPut] + [Route("user_privacy")] + [StateYourBusiness(operation = Operation.UPDATE, resource = Resource.PROFILE_PRIVACY)] + public async Task> UpdateUserPrivacyAsync(UserPrivacyUpdateViewModel userPrivacy) + { + var authenticatedUserUsername = AuthUtils.GetUsername(User); + await profileService.UpdateUserPrivacyAsync(authenticatedUserUsername, userPrivacy); + return Ok(); + } + + /// + /// Return a list visibility groups + /// + /// All visibility groups (Public, FacStaff, Private) + [HttpGet] + [Route("visibility_groups")] + public ActionResult> GetVisibilityGroup() + { + var groups = context.UserPrivacy_Visibility_Groups.Select(up_v_g => up_v_g.Group) + .Distinct() + .Where(g => g != null); + return Ok(groups); + } + /// /// Update mail location /// diff --git a/Gordon360/Controllers/ScheduleController.cs b/Gordon360/Controllers/ScheduleController.cs index fd0b14bf2..e95b4308e 100644 --- a/Gordon360/Controllers/ScheduleController.cs +++ b/Gordon360/Controllers/ScheduleController.cs @@ -12,7 +12,9 @@ namespace Gordon360.Controllers; [Route("api/[controller]")] -public class ScheduleController(IScheduleService scheduleService) : GordonControllerBase +public class ScheduleController(IProfileService profileService, + IScheduleService scheduleService, + IAccountService accountService) : GordonControllerBase { /// /// Gets all session objects for a user @@ -24,19 +26,27 @@ public class ScheduleController(IScheduleService scheduleService) : GordonContro public async Task> GetAllCourses(string username) { var groups = AuthUtils.GetGroups(User); - var authenticatedUsername = AuthUtils.GetUsername(User); + FacultyStaffProfileViewModel? fac = profileService.GetFacultyStaffProfileByUsername(username); + StudentProfileViewModel? student = profileService.GetStudentProfileByUsername(username); + AlumniProfileViewModel? alumni = profileService.GetAlumniProfileByUsername(username); - IEnumerable result; - if (authenticatedUsername.EqualsIgnoreCase(username) || groups.Contains(AuthGroup.FacStaff)) + // Some users can see schedules of courses taken, as well as taught, + // so check to see if this user can see all courses for this person. + if ((AuthUtils.GetUsername(User).EqualsIgnoreCase(username)) || + (accountService.CanISeeStudentSchedule(groups) && + student != null && + accountService.CanISeeThisStudent(groups, student)) || + (accountService.CanISeeAlumniSchedule(groups) && alumni != null)) { - result = await scheduleService.GetAllCoursesAsync(username); + IEnumerable result = await scheduleService.GetAllCoursesAsync(username); + return Ok(result); } else { - result = await scheduleService.GetAllInstructorCoursesAsync(username); + // Everyone can see schedules of courses taught. + IEnumerable result = await scheduleService.GetAllInstructorCoursesAsync(username); + return Ok(result); } - - return Ok(result); } /// @@ -48,18 +58,25 @@ public async Task> GetAllCourses(string public async Task>> GetAllCoursesByTerm(string username) { var groups = AuthUtils.GetGroups(User); - var authenticatedUsername = AuthUtils.GetUsername(User); + FacultyStaffProfileViewModel? fac = profileService.GetFacultyStaffProfileByUsername(username); + StudentProfileViewModel? student = profileService.GetStudentProfileByUsername(username); + AlumniProfileViewModel? alumni = profileService.GetAlumniProfileByUsername(username); + // Some users can see schedules of courses taken, as well as taught, + // so check to see if this user can see all courses for this person. IEnumerable result; - if (authenticatedUsername.EqualsIgnoreCase(username) || groups.Contains(AuthGroup.FacStaff)) + if ((AuthUtils.GetUsername(User).EqualsIgnoreCase(username)) || + (accountService.CanISeeStudentSchedule(groups) && + student != null && + accountService.CanISeeThisStudent(groups, student)) || + (accountService.CanISeeAlumniSchedule(groups) && alumni != null)) { result = await scheduleService.GetAllCoursesByTermAsync(username); - } - else + } else { + // Everyone can see schedules of courses taught. result = await scheduleService.GetAllInstructorCoursesByTermAsync(username); } - var publishedResult = result .Where(r => string.Equals(r.ShowOnWeb, "B", StringComparison.OrdinalIgnoreCase)) .ToList(); @@ -75,6 +92,6 @@ public async Task>> GetAllCours public async Task> GetCanReadStudentSchedules() { var groups = AuthUtils.GetGroups(User); - return groups.Contains(AuthGroup.Advisors); + return accountService.CanISeeStudentSchedule(groups); } } \ No newline at end of file diff --git a/Gordon360/Documentation/Gordon360.xml b/Gordon360/Documentation/Gordon360.xml index 98cba8c77..62efae89c 100644 --- a/Gordon360/Documentation/Gordon360.xml +++ b/Gordon360/Documentation/Gordon360.xml @@ -37,11 +37,6 @@ Gets whether the user has checked in or not. True if they have checked in, false if they have not checked in The HTTP status indicating whether the request was completed and returns the check in status of the student - - - Gets the most recent academic term that is either Spring or Fall - - The current term used to fetch final exams Gets all posters that havent been deleted @@ -125,6 +120,12 @@ + + + Gets the most recent academic term that is either Spring or Fall + + The current term used to fetch final exams + Return a list of accounts matching some or all of searchString. @@ -282,12 +283,6 @@ All buildings - - - Return a list of buildings. - - All buildings - Return a list of involvements' descriptions. @@ -1083,8 +1078,9 @@ Get profile info of currently logged in user - - Get public profile info for a user + + Get another user's profile info. The info returned depends + on the permissions of the current user, who is making the request. username of the profile info @@ -1095,6 +1091,12 @@ provides first name, last name, and username. + + Get the privacy settings of a particular user + + All privacy settings of the given user. + + Gets the clifton strengths of a particular user The username for which to retrieve info @@ -1176,6 +1178,19 @@ office hours + + + Set visibility group for some piece of personal data + + Faculty Staff Privacy Decisions (see UserPrivacyUpdateViewModel) + + + + + Return a list visibility groups + + All visibility groups (Public, FacStaff, Private) + Update mail location @@ -1688,6 +1703,13 @@ The accepted TeamInviteViewModel + + + Retrieves the registration window data for the currently logged-in user. + Returns a 404 if registration info is not found (either missing account or date info). + + A RegistrationPeriodViewModel with eligibility and timing details + Gets all Membership Request Objects @@ -1754,9 +1776,9 @@ - Gets all term objects for a user + Gets all term-based course schedules for a user, filtered to only include officially published terms. - A IEnumerable of term objects as well as the schedules + A list of published term schedule objects @@ -2036,12 +2058,24 @@ - Service Class that facilitates data transactions between the AccountsController and the Account database model. + Service Class that facilitates data transactions between the AccountsController and + the Account database model. It also provides methods to enforce access restrictions + on user data. + + The "CanISee..." and "VisibleToMe..." methods encapsulate rules about whether + a requesting user can even find a user (CanISee...) and if so, what fields they + are allowed to see (VisibleToMe...). - Service Class that facilitates data transactions between the AccountsController and the Account database model. + Service Class that facilitates data transactions between the AccountsController and + the Account database model. It also provides methods to enforce access restrictions + on user data. + + The "CanISee..." and "VisibleToMe..." methods encapsulate rules about whether + a requesting user can even find a user (CanISee...) and if so, what fields they + are allowed to see (VisibleToMe...). @@ -2071,6 +2105,95 @@ The AD username associated with the account. the student account information + + Indicates whether a user making a request is authorized to see + profile information for students. + The authentication groups associated with the + user making the request. + True if the user making the request is authorized to see + profile information for students, and false otherwise. + + + Indicates whether a user making a request is authorized to see + course schedule information for students. + The authentication groups associated with the + user making the request. + True if the user making the request is authorized to see + schedule information for students, and false otherwise. + + + Indicates whether a user making a request is authorized to see + profile information for this particular student. Some students are not shown + because of FERPA protections. + The authentication groups associated with the + user making the request. + Profile data for the student whose information + is being requested. + True if the user making the request is authorized to see + profile information for this student, and false otherwise. + + + Indicates whether a user making a request is authorized to see + profile information for faculty and staff (facstaff). + The authentication groups associated with the + user making the request. + True if the user making the request is authorized to see + profile information for facstaff, and false otherwise. + + + Indicates whether a user making a request is authorized to see + profile information for alumni. + The authentication groups associated with the + user making the request. + True if the user making the request is authorized to see + profile information for alumni, and false otherwise. + + + Indicates whether a user making a request is authorized to see + course schedule information for alumni. + The authentication groups associated with the + user making the request. + True if the user making the request is authorized to see + course schedule information for alumni, and false otherwise. + + + Restrict info about a student to those fields which are potentially + viewable by the user making the request. Actual visibility may also depend + on privacy choices made by the user whose data is being viewed. Note that + this takes FERPA restrictions into account in determining whether this student + is visible to the requesting user. + The authentication groups associated with the + user making the request. + Profile data for the student whose information + is being requested. + Information the requesting user is potentially authorized to see. + Null if the requesting user is never allowed to see data about students. + + + + Restrict info about a facstaff person to those fields which are potentially + viewable by the user making the request. Actual visibility may also depend + on privacy choices made by the user whose data is being viewed. + The authentication groups associated with the + user making the request. + Profile data for the facstaff member whose information + is being requested. + Information the requesting user is potentially authorized to see. + Null if the requesting user is never allowed to see data about facstaff. + + + + Restrict info about an alumni person to those fields which are potentially + viewable by the user making the request. Actual visibility may also depend + on privacy choices made by the user whose data is being viewed. + The authentication groups associated with the + user making the request. + Profile data for the alum whose information + is being requested. + Information the requesting user is potentially authorized to see. + Null if the requesting user is never allowed to see data about alumni. + + Given a list of accounts, and search params, return all the accounts that match those search params. @@ -3269,6 +3392,14 @@ The SNID (id of news item) The news item + + + Filters out categories that are not relevant for the student news feed. + Student News categories are set by fetching their respective ids from the databasase + in this instance, "2" and "3" are excluded. which relate to "Lost Items" and "Found Items" + NOTE: the categories are hardcoded here, but could be made more dynamic in the future + + Gets unapproved unexpired news submitted by user. @@ -3446,6 +3577,28 @@ + + + convert combined profile to public profile based on individual privacy settings + + list of AuthGroups the logged-in user belongs to + combined profile of the person being searched + public profile of the person based on individual privacy settings + + + + Get profile fields and visibility settings for a specific user + + AD username + List of field and visibility privacy settings for a specific user + + + + Set privacy setting of some piece of personal data for user. + + AD Username + User Privacy Update View Model + privacy setting of mobile phone. @@ -3500,6 +3653,20 @@ The username of the student GraduationViewModel containing graduation details + + + Change a ProfileItem's privacy setting to true + + Combined profile containing element to update + The ID of the profile element of which to update IsPrivate + + + + Change a ProfileItem to be null (remove it from the profile) + + Combined profile containing element to make null + The ID of the profile element to make null + this function is used because ASP somehow refuses to cast IEnumerables or recognize IEnumerables @@ -3541,6 +3708,16 @@ Matches created as well as number of teams in the next round + + + Gets the registration window for a given user and evaluates their eligibility to register. + + The AD username of the student. + + A RegistrationPeriodViewModel containing registration start/end dates and eligibility status. + Returns null if either the account or registration window is missing. + + Service Class that facilitates data transactions between the SchedulesController and the Schedule part of the database model. @@ -3634,6 +3811,16 @@ The session code of the current session + + + Profile item class contining a single profile datum and privacy flag. + + + + + Profile item class contining a single profile datum and privacy flag. + + Deletes an image from the filesystem, if there is one. diff --git a/Gordon360/Models/CCT/Context/CCTContext.cs b/Gordon360/Models/CCT/Context/CCTContext.cs index e4be2ceaa..000424375 100644 --- a/Gordon360/Models/CCT/Context/CCTContext.cs +++ b/Gordon360/Models/CCT/Context/CCTContext.cs @@ -223,6 +223,12 @@ public CCTContext(DbContextOptions options) public virtual DbSet UserCourses { get; set; } + public virtual DbSet UserPrivacy_Fields { get; set; } + + public virtual DbSet UserPrivacy_Settings { get; set; } + + public virtual DbSet UserPrivacy_Visibility_Groups { get; set; } + public virtual DbSet YearTermTable { get; set; } public virtual DbSet CourseRegistrationDates { get; set; } @@ -930,6 +936,28 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) entity.Property(e => e.WEDNESDAY_CDE).IsFixedLength(); entity.Property(e => e.YR_CDE).IsFixedLength(); }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.ID).HasName("PK_UserPrivacy_Fields_1"); + }); + + modelBuilder.Entity(entity => + { + entity.HasOne(d => d.FieldNavigation).WithMany(p => p.UserPrivacy_Settings) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_UserPrivacy_Settings_UserPrivacy_Fields"); + + entity.HasOne(d => d.VisibilityNavigation).WithMany(p => p.UserPrivacy_Settings) + .OnDelete(DeleteBehavior.ClientSetNull) + .HasConstraintName("FK_UserPrivacy_Settings_UserPrivacy_Visibility_Groups"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.ID).HasName("PK_UserPrivacy_Visibility_Groups_1"); + }); + modelBuilder.HasSequence("Information_Change_Request_Seq", "dbo"); OnModelCreatingPartial(modelBuilder); diff --git a/Gordon360/Models/CCT/Context/efpt.CCT.config.json b/Gordon360/Models/CCT/Context/efpt.CCT.config.json index 5b8047ed4..090ec73af 100644 --- a/Gordon360/Models/CCT/Context/efpt.CCT.config.json +++ b/Gordon360/Models/CCT/Context/efpt.CCT.config.json @@ -78,6 +78,18 @@ "Name": "[dbo].[StudentNewsExpiration]", "ObjectType": 0 }, + { + "Name": "[dbo].[UserPrivacy_Fields]", + "ObjectType": 0 + }, + { + "Name": "[dbo].[UserPrivacy_Settings]", + "ObjectType": 0 + }, + { + "Name": "[dbo].[UserPrivacy_Visibility_Groups]", + "ObjectType": 0 + }, { "Name": "[Housing].[Hall_Assignment_Ranges]", "ObjectType": 0 diff --git a/Gordon360/Models/CCT/dbo/UserPrivacy_Fields.cs b/Gordon360/Models/CCT/dbo/UserPrivacy_Fields.cs new file mode 100644 index 000000000..383c2a76e --- /dev/null +++ b/Gordon360/Models/CCT/dbo/UserPrivacy_Fields.cs @@ -0,0 +1,23 @@ +// This file has been auto generated by EF Core Power Tools. +#nullable disable +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Gordon360.Models.CCT; + +public partial class UserPrivacy_Fields +{ + [Key] + public int ID { get; set; } + + [Required] + [StringLength(50)] + [Unicode(false)] + public string Field { get; set; } + + [InverseProperty("FieldNavigation")] + public virtual ICollection UserPrivacy_Settings { get; set; } = new List(); +} \ No newline at end of file diff --git a/Gordon360/Models/CCT/dbo/UserPrivacy_Settings.cs b/Gordon360/Models/CCT/dbo/UserPrivacy_Settings.cs new file mode 100644 index 000000000..f44f00410 --- /dev/null +++ b/Gordon360/Models/CCT/dbo/UserPrivacy_Settings.cs @@ -0,0 +1,31 @@ +// This file has been auto generated by EF Core Power Tools. +#nullable disable +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Gordon360.Models.CCT; + +[PrimaryKey("gordon_id", "Field")] +public partial class UserPrivacy_Settings +{ + [Key] + [StringLength(10)] + [Unicode(false)] + public string gordon_id { get; set; } + + [Key] + public int Field { get; set; } + + public int Visibility { get; set; } + + [ForeignKey("Field")] + [InverseProperty("UserPrivacy_Settings")] + public virtual UserPrivacy_Fields FieldNavigation { get; set; } + + [ForeignKey("Visibility")] + [InverseProperty("UserPrivacy_Settings")] + public virtual UserPrivacy_Visibility_Groups VisibilityNavigation { get; set; } +} \ No newline at end of file diff --git a/Gordon360/Models/CCT/dbo/UserPrivacy_Visibility_Groups.cs b/Gordon360/Models/CCT/dbo/UserPrivacy_Visibility_Groups.cs new file mode 100644 index 000000000..b9c037b08 --- /dev/null +++ b/Gordon360/Models/CCT/dbo/UserPrivacy_Visibility_Groups.cs @@ -0,0 +1,23 @@ +// This file has been auto generated by EF Core Power Tools. +#nullable disable +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; + +namespace Gordon360.Models.CCT; + +public partial class UserPrivacy_Visibility_Groups +{ + [Key] + public int ID { get; set; } + + [Required] + [StringLength(50)] + [Unicode(false)] + public string Group { get; set; } + + [InverseProperty("VisibilityNavigation")] + public virtual ICollection UserPrivacy_Settings { get; set; } = new List(); +} \ No newline at end of file diff --git a/Gordon360/Models/ViewModels/AlumniProfileViewModel.cs b/Gordon360/Models/ViewModels/AlumniProfileViewModel.cs index 636631853..4aef72b4d 100644 --- a/Gordon360/Models/ViewModels/AlumniProfileViewModel.cs +++ b/Gordon360/Models/ViewModels/AlumniProfileViewModel.cs @@ -20,7 +20,6 @@ public record AlumniProfileViewModel string HomePostalCode, string HomeCountry, string HomePhone, - string HomeFax, string HomeEmail, string JobTitle, string MaritalStatus, @@ -69,7 +68,6 @@ public record AlumniProfileViewModel alu.HomePostalCode ?? "", alu.HomeCountry ?? "", alu.HomePhone ?? "", - alu.HomeFax ?? "", alu.HomeEmail ?? "", alu.JobTitle ?? "", alu.MaritalStatus ?? "", diff --git a/Gordon360/Models/ViewModels/CombinedProfileViewModel.cs b/Gordon360/Models/ViewModels/CombinedProfileViewModel.cs new file mode 100644 index 000000000..bd232a39e --- /dev/null +++ b/Gordon360/Models/ViewModels/CombinedProfileViewModel.cs @@ -0,0 +1,223 @@ +//using Gordon360.Models.ViewModels.RecIM; +using System; +using Gordon360.Static.Methods; + +namespace Gordon360.Models.ViewModels; + +public class CombinedProfileViewModel +{ + const bool PRIVATE = true; + const bool PUBLIC = false; + + public string ID { get; set; } + public string Title { get; set; } + public ProfileItem FirstName { get; set; } + public ProfileItem MiddleName { get; set; } + public ProfileItem LastName { get; set; } + public ProfileItem Suffix { get; set; } + public ProfileItem MaidenName { get; set; } + public ProfileItem NickName { get; set; } + public string Email { get; set; } + public string Gender { get; set; } + public ProfileItem HomeStreet1 { get; set; } + public ProfileItem HomeStreet2 { get; set; } + public ProfileItem HomeCity { get; set; } + public ProfileItem HomeState { get; set; } + public ProfileItem HomePostalCode { get; set; } + public ProfileItem HomeCountry { get; set; } + public ProfileItem HomePhone { get; set; } + public string AD_Username { get; set; } // Leave as string + public Nullable show_pic { get; set; } + public Nullable preferred_photo { get; set; } + public ProfileItem Country { get; set; } + public string Barcode { get; set; } + public string Facebook { get; set; } + public string Twitter { get; set; } + public string Instagram { get; set; } + public string LinkedIn { get; set; } + public string Handshake { get; set; } + public string Calendar { get; set; } + + // Student Only + public string OnOffCampus { get; set; } + public string OffCampusStreet1 { get; set; } + public string OffCampusStreet2 { get; set; } + public string OffCampusCity { get; set; } + public string OffCampusState { get; set; } + public string OffCampusPostalCode { get; set; } + public string OffCampusCountry { get; set; } + public string OffCampusPhone { get; set; } + public string OffCampusFax { get; set; } + public string Major3 { get; set; } + public string Major3Description { get; set; } + public string Minor1 { get; set; } + public string Minor1Description { get; set; } + public string Minor2 { get; set; } + public string Minor2Description { get; set; } + public string Minor3 { get; set; } + public string Minor3Description { get; set; } + public string GradDate { get; set; } + public string PlannedGradYear { get; set; } + public DateTime? Entrance_Date { get; set; } + public ProfileItem MobilePhone { get; set; } + public bool IsMobilePhonePrivate { get; set; } + public Nullable ChapelRequired { get; set; } + public Nullable ChapelAttended { get; set; } + public string Cohort { get; set; } + public string Class { get; set; } + public string AdvisorIDs { get; set; } + public string Married { get; set; } + public string Commuter { get; set; } + + // Alumni Only + public string WebUpdate { get; set; } + public ProfileItem HomeEmail { get; set; } + public string MaritalStatus { get; set; } + public string College { get; set; } + public string ClassYear { get; set; } + public string PreferredClassYear { get; set; } + public string ShareName { get; set; } + public string ShareAddress { get; set; } + + // Student And Alumni Only + public string Major { get; set; } + public string Major1Description { get; set; } + public string Major2 { get; set; } + public string Major2Description { get; set; } + public string grad_student { get; set; } + + // FacStaff Only + public string OnCampusDepartment { get; set; } + public string Type { get; set; } + public string office_hours { get; set; } + public string Dept { get; set; } + public string Mail_Description { get; set; } + public DateTime? FirstHireDt { get; set; } + + // FacStaff and Alumni Only + public string JobTitle { get; set; } + public ProfileItem SpouseName { get; set; } + + // FacStaff and Student Only + public string BuildingDescription { get; set; } + public string Mail_Location { get; set; } + public string OnCampusBuilding { get; set; } + public string OnCampusRoom { get; set; } + public string OnCampusPhone { get; set; } + public string OnCampusPrivatePhone { get; set; } + public string OnCampusFax { get; set; } + public string KeepPrivate { get; set; } + + // ProfileViewModel Only + public string PersonType { get; set; } // Leave as string + + public static implicit operator CombinedProfileViewModel(ProfileViewModel vm) + { + CombinedProfileViewModel new_vm = new CombinedProfileViewModel + { + // All Profiles + ID = vm.ID, + Title = vm.Title, + FirstName = vm.FirstName is null || vm.FirstName == "" ? null : new ProfileItem(vm.FirstName, PUBLIC), + MiddleName = vm.MiddleName is null || vm.MiddleName == "" ? null : new ProfileItem(vm.MiddleName, PUBLIC), + LastName = vm.LastName is null || vm.LastName == "" ? null : new ProfileItem(vm.LastName, PUBLIC), + Suffix = vm.Suffix is null || vm.Suffix == "" ? null : new ProfileItem(vm.Suffix, PUBLIC), + MaidenName = vm.MaidenName is null || vm.MaidenName == "" ? null : new ProfileItem(vm.MaidenName, PUBLIC), + NickName = vm.NickName is null || vm.NickName == "" ? null : new ProfileItem(vm.NickName, PUBLIC), + Email = vm.Email, + Gender = vm.Gender, + HomeStreet1 = vm.HomeStreet1 is null || vm.HomeStreet1 == "" ? null : new ProfileItem(vm.HomeStreet1, PUBLIC), + HomeStreet2 = vm.HomeStreet2 is null || vm.HomeStreet2 == "" ? null : new ProfileItem(vm.HomeStreet2, PUBLIC), + HomeCity = vm.HomeCity is null || vm.HomeCity == "" ? null : new ProfileItem(vm.HomeCity, PUBLIC), + HomeState = vm.HomeState is null || vm.HomeState == "" ? null : new ProfileItem(vm.HomeState, PUBLIC), + HomePostalCode = vm.HomePostalCode is null || vm.HomePostalCode == "" ? null : new ProfileItem(vm.HomePostalCode, PUBLIC), + HomeCountry = vm.HomeCountry is null || vm.HomeCountry == "" ? null : new ProfileItem(vm.HomeCountry, PUBLIC), + HomePhone = vm.HomePhone is null || vm.HomePhone == "" ? null : new ProfileItem(vm.HomePhone, PUBLIC), + AD_Username = vm.AD_Username, + show_pic = vm.show_pic, + preferred_photo = vm.preferred_photo, + Country = vm.Country is null || vm.Country == "" ? null : new ProfileItem(vm.Country, PUBLIC), + Barcode = vm.Barcode, + Facebook = vm.Facebook, + Twitter = vm.Twitter, + Instagram = vm.Instagram, + LinkedIn = vm.LinkedIn, + Handshake = vm.Handshake, + Calendar = vm.Calendar, + + // Student Only + OnOffCampus = vm.OnOffCampus, + OffCampusStreet1 = vm.OffCampusStreet1, + OffCampusStreet2 = vm.OffCampusStreet2, + OffCampusCity = vm.OffCampusCity, + OffCampusState = vm.OffCampusState, + OffCampusPostalCode = vm.OffCampusPostalCode, + OffCampusCountry = vm.OffCampusCountry, + OffCampusPhone = vm.OffCampusPhone, + OffCampusFax = vm.OffCampusFax, + Major3 = vm.Major3, + Major3Description = vm.Major3Description, + Minor1 = vm.Minor1, + Minor1Description = vm.Minor1Description, + Minor2 = vm.Minor2, + Minor2Description = vm.Minor2Description, + Minor3 = vm.Minor3, + Minor3Description = vm.Minor3Description, + GradDate = vm.GradDate, + PlannedGradYear = vm.PlannedGradYear, + Entrance_Date = vm.Entrance_Date, + MobilePhone = vm.MobilePhone is null || vm.MobilePhone == "" ? null : new ProfileItem(vm.MobilePhone, PUBLIC), + IsMobilePhonePrivate = vm.IsMobilePhonePrivate, + ChapelRequired = vm.ChapelRequired, + ChapelAttended = vm.ChapelAttended, + Cohort = vm.Cohort, + Class = vm.Class, + AdvisorIDs = vm.AdvisorIDs, + Married = vm.Married, + Commuter = vm.Commuter, + + // Alumni Only + WebUpdate = vm. WebUpdate, + HomeEmail = vm.HomeEmail is null || vm.HomeEmail == "" ? null : new ProfileItem(vm.HomeEmail, PUBLIC), + MaritalStatus = vm.MaritalStatus, + College = vm.College, + ClassYear = vm.ClassYear, + PreferredClassYear = vm. PreferredClassYear, + ShareName = vm.ShareName, + ShareAddress = vm. ShareAddress, + + // Student And Alumni Only + Major = vm.Major, + Major1Description = vm.Major1Description, + Major2 = vm.Major2, + Major2Description = vm.Major2Description, + grad_student = vm.grad_student, + + // FacStaff Only + OnCampusDepartment = vm. OnCampusDepartment, + Type = vm. Type, + office_hours = vm. office_hours, + Dept = vm.Dept, + Mail_Description = vm.Mail_Description, + FirstHireDt = vm.FirstHireDt, + + // FacStaff and Alumni Only + JobTitle = vm.JobTitle, + SpouseName = vm.SpouseName is null || vm.SpouseName == "" ? null : new ProfileItem(vm.SpouseName, PUBLIC), + + // FacStaff and Student Only + BuildingDescription = vm.BuildingDescription, + Mail_Location = vm.Mail_Location, + OnCampusBuilding = vm.OnCampusBuilding, + OnCampusRoom = vm.OnCampusRoom, + OnCampusPhone = vm.OnCampusPhone, + OnCampusPrivatePhone = vm.OnCampusPrivatePhone, + OnCampusFax = vm.OnCampusFax, + KeepPrivate = vm.KeepPrivate, + + // ProfileViewModel Only + PersonType = vm.PersonType + }; + return new_vm; + } +} diff --git a/Gordon360/Models/ViewModels/FacultyStaffProfileViewModel.cs b/Gordon360/Models/ViewModels/FacultyStaffProfileViewModel.cs index 1c2948d91..b8cefbd4a 100644 --- a/Gordon360/Models/ViewModels/FacultyStaffProfileViewModel.cs +++ b/Gordon360/Models/ViewModels/FacultyStaffProfileViewModel.cs @@ -26,7 +26,7 @@ public record FacultyStaffProfileViewModel string HomePostalCode, string HomeCountry, string HomePhone, - string HomeFax, + string MobilePhone, string KeepPrivate, string JobTitle, string Dept, @@ -74,7 +74,7 @@ public record FacultyStaffProfileViewModel fac.HomePostalCode ?? "", fac.HomeCountry ?? "", fac.HomePhone ?? "", - fac.HomeFax ?? "", + fac.MobilePhone ?? "", fac.KeepPrivate ?? "", fac.JobTitle ?? "", fac.Dept ?? "", diff --git a/Gordon360/Models/ViewModels/ProfileViewModel.cs b/Gordon360/Models/ViewModels/ProfileViewModel.cs index 4aa9321d9..74c37911d 100644 --- a/Gordon360/Models/ViewModels/ProfileViewModel.cs +++ b/Gordon360/Models/ViewModels/ProfileViewModel.cs @@ -19,7 +19,7 @@ public record ProfileViewModel( string HomeCity, string HomeState, string HomePostalCode, - string HomeCountry, + string HomeCountry, // Abbreviation of Country string HomePhone, string HomeFax, string AD_Username, diff --git a/Gordon360/Models/ViewModels/PublicAlumniProfileViewModel.cs b/Gordon360/Models/ViewModels/PublicAlumniProfileViewModel.cs deleted file mode 100644 index 03ec5e47a..000000000 --- a/Gordon360/Models/ViewModels/PublicAlumniProfileViewModel.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; - -namespace Gordon360.Models.ViewModels; - -public class PublicAlumniProfileViewModel -{ - public int WebUpdate { get; set; } - public string Title { get; set; } - public string FirstName { get; set; } - public string MiddleName { get; set; } - public string LastName { get; set; } - public string Suffix { get; set; } - public string MaidenName { get; set; } - public string NickName { get; set; } - public string HomeCity { get; set; } - public string HomeState { get; set; } - public string HomeCountry { get; set; } - public string HomeEmail { get; set; } - public string JobTitle { get; set; } - public string MaritalStatus { get; set; } - public string SpouseName { get; set; } - public string College { get; set; } - public string ClassYear { get; set; } - public string PreferredClassYear { get; set; } - public string ShareName { get; set; } - public string ShareAddress { get; set; } - public string Gender { get; set; } - public string GradDate { get; set; } - public string Email { get; set; } - public string grad_student { get; set; } - public string AD_Username { get; set; } - public Nullable show_pic { get; set; } - public Nullable preferred_photo { get; set; } - public string Country { get; set; } - public string Major2Description { get; set; } - public string Major1Description { get; set; } - public static implicit operator PublicAlumniProfileViewModel(AlumniProfileViewModel alu) - { - PublicAlumniProfileViewModel vm = new PublicAlumniProfileViewModel - { - WebUpdate = alu.WebUpdate, - Title = alu.Title ?? "", - FirstName = alu.FirstName ?? "", - MiddleName = alu.MiddleName ?? "", - LastName = alu.LastName ?? "", - Suffix = alu.Suffix ?? "", - MaidenName = alu.MaidenName ?? "", - NickName = alu.NickName ?? "", - AD_Username = alu.AD_Username ?? "", - HomeCity = alu.HomeCity ?? "", - HomeState = alu.HomeState ?? "", - HomeCountry = alu.HomeCountry ?? "", - HomeEmail = alu.HomeEmail ?? "", - JobTitle = alu.JobTitle ?? "", - MaritalStatus = alu.MaritalStatus ?? "", - SpouseName = alu.SpouseName ?? "", - College = alu.College ?? "", - ClassYear = alu.ClassYear ?? "", - PreferredClassYear = alu.PreferredClassYear ?? "", - ShareName = alu.ShareName ?? "", - ShareAddress = alu.ShareAddress ?? "", - Gender = alu.Gender ?? "", - GradDate = alu.GradDate ?? "", - Email = alu.Email ?? "", - grad_student = alu.grad_student ?? "", - show_pic = alu.show_pic, - preferred_photo = alu.preferred_photo, - Country = alu.Country ?? "", - Major1Description = alu.Major1Description ?? "", - Major2Description = alu.Major2Description ?? "" - }; - if (vm.ShareName.Contains("N") || vm.ShareName.Contains("n")) - { - return null; - } - else if (!vm.ShareAddress.Contains("Y")) - { - vm.HomeCity = "Private as requested."; - vm.HomeCountry = ""; - vm.HomeState = ""; - vm.Country = ""; - } - return vm; - } -} \ No newline at end of file diff --git a/Gordon360/Models/ViewModels/PublicFacultyStaffProfileViewModel.cs b/Gordon360/Models/ViewModels/PublicFacultyStaffProfileViewModel.cs deleted file mode 100644 index 4016e2c6c..000000000 --- a/Gordon360/Models/ViewModels/PublicFacultyStaffProfileViewModel.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System; - -namespace Gordon360.Models.ViewModels; - -public class PublicFacultyStaffProfileViewModel -{ - public string Title { get; set; } - public string FirstName { get; set; } - public string MiddleName { get; set; } - public string LastName { get; set; } - public string Suffix { get; set; } - public string MaidenName { get; set; } - public string NickName { get; set; } - public string OnCampusDepartment { get; set; } - public string OnCampusBuilding { get; set; } - public string OnCampusRoom { get; set; } - public string OnCampusPhone { get; set; } - public string OnCampusPrivatePhone { get; set; } - public string OnCampusFax { get; set; } - public string HomePhone { get; set; } - public string HomeCity { get; set; } - public string HomeState { get; set; } - public string HomeCountry { get; set; } - public string KeepPrivate { get; set; } - public string JobTitle { get; set; } - public string SpouseName { get; set; } - public string Dept { get; set; } - public string Gender { get; set; } - public string Email { get; set; } - public string Type { get; set; } - public DateTime? FirstHireDt { get; set; } - public string AD_Username { get; set; } - public string office_hours { get; set; } - public Nullable preferred_photo { get; set; } - public Nullable show_pic { get; set; } - public string BuildingDescription { get; set; } - public string Country { get; set; } - public string Mail_Location { get; set; } - public string Mail_Description { get; set; } - - - - - public static implicit operator PublicFacultyStaffProfileViewModel(FacultyStaffProfileViewModel fac) - { - PublicFacultyStaffProfileViewModel vm = new PublicFacultyStaffProfileViewModel - { - Title = fac.Title ?? "", - Suffix = fac.Suffix ?? "", - MaidenName = fac.MaidenName ?? "", - FirstName = fac.FirstName ?? "", - LastName = fac.LastName ?? "", - MiddleName = fac.MiddleName ?? "", - NickName = fac.NickName ?? "", // Just in case some random record has a null user_name - AD_Username = fac.AD_Username ?? "", // Just in case some random record has a null email field - OnCampusDepartment = fac.OnCampusDepartment ?? "", - OnCampusBuilding = fac.OnCampusBuilding ?? "", - OnCampusRoom = fac.OnCampusRoom ?? "", - OnCampusPhone = fac.OnCampusPhone ?? "", - OnCampusPrivatePhone = fac.OnCampusPrivatePhone ?? "", - OnCampusFax = fac.OnCampusFax ?? "", - HomePhone = fac.HomePhone ?? "", - HomeCity = fac.HomeCity ?? "", - HomeState = fac.HomeState ?? "", - HomeCountry = fac.HomeCountry ?? "", - KeepPrivate = fac.KeepPrivate ?? "", - JobTitle = fac.JobTitle ?? "", - SpouseName = fac.SpouseName ?? "", - Type = fac.Type ?? "", - FirstHireDt = fac.FirstHireDt, - Dept = fac.Dept ?? "", - Email = fac.Email ?? "", - Gender = fac.Gender ?? "", - office_hours = fac.office_hours ?? "", - preferred_photo = fac.preferred_photo, - show_pic = fac.show_pic, - BuildingDescription = fac.BuildingDescription ?? "", - Country = fac.Country ?? "", - Mail_Location = fac.Mail_Location ?? "", - Mail_Description = fac.Mail_Description ?? "" - }; - if (vm.KeepPrivate.Contains('1')) - { - vm.HomeCity = "Private as requested."; - vm.HomeState = ""; - vm.HomeCountry = ""; - vm.SpouseName = "Private as requested."; - vm.Country = ""; - vm.HomePhone = ""; - } - return vm; - } -} diff --git a/Gordon360/Models/ViewModels/PublicStudentProfileViewModel.cs b/Gordon360/Models/ViewModels/PublicStudentProfileViewModel.cs index e7642a619..2042c4fe2 100644 --- a/Gordon360/Models/ViewModels/PublicStudentProfileViewModel.cs +++ b/Gordon360/Models/ViewModels/PublicStudentProfileViewModel.cs @@ -13,6 +13,7 @@ public class PublicStudentProfileViewModel public string NickName { get; set; } public string OnOffCampus { get; set; } public string Mail_Location { get; set; } + public string HomePhone { get; set; } public string HomeCity { get; set; } public string HomeState { get; set; } public string HomeCountry { get; set; } @@ -77,19 +78,7 @@ public static implicit operator PublicStudentProfileViewModel(StudentProfileView Minor3Description = stu.Minor3Description ?? "", Entrance_Date = stu.Entrance_Date }; - if (vm.IsMobilePhonePrivate) - { - vm.MobilePhone = "Private as requested."; - } - if (vm.KeepPrivate.Contains("S")) - { - vm.HomeCity = "Private as requested."; - vm.HomeState = ""; - vm.HomeCountry = ""; - vm.Country = ""; - vm.OnOffCampus = "P"; //Private, as parsed by front end service user.js - vm.Hall = ""; - } + if (vm.KeepPrivate.Contains("Y") || vm.KeepPrivate.Contains("P")) { return null; diff --git a/Gordon360/Models/ViewModels/StudentProfileViewModel.cs b/Gordon360/Models/ViewModels/StudentProfileViewModel.cs index 34077dd8a..f191238ee 100644 --- a/Gordon360/Models/ViewModels/StudentProfileViewModel.cs +++ b/Gordon360/Models/ViewModels/StudentProfileViewModel.cs @@ -34,7 +34,6 @@ public record StudentProfileViewModel string HomePostalCode, string HomeCountry, string HomePhone, - string HomeFax, string Cohort, string Class, string KeepPrivate, @@ -108,7 +107,6 @@ public record StudentProfileViewModel stu.HomePostalCode ?? "", stu.HomeCountry ?? "", stu.HomePhone ?? "", - stu.HomeFax ?? "", stu.Cohort ?? "", stu.Class ?? "", stu.KeepPrivate ?? "", diff --git a/Gordon360/Models/ViewModels/UserPrivacyUpdateViewModel.cs b/Gordon360/Models/ViewModels/UserPrivacyUpdateViewModel.cs new file mode 100644 index 000000000..c8e1fb712 --- /dev/null +++ b/Gordon360/Models/ViewModels/UserPrivacyUpdateViewModel.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +namespace Gordon360.Models.ViewModels +{ + // ViewModel for updating privacy settings + + // Field: field for which the visibility group will be updated + // (HomeStreet1, HomeStreet2, HomeCity, HomeState, HomeCountry, SpouseName, + // Country, HomePhone, MobilePhone) + // VisibilityGroup: The group of users that can see the information corresponding to Field + // Currently a subset of Public, FacStaff, or Private + public class UserPrivacyUpdateViewModel + { + public UserPrivacyUpdateViewModel(IEnumerable field, string group) + { + Field = field; + VisibilityGroup = group; + } + public IEnumerable Field { get; set; } + public string VisibilityGroup { get; set; } + } +} \ No newline at end of file diff --git a/Gordon360/Models/ViewModels/UserPrivacyViewModel.cs b/Gordon360/Models/ViewModels/UserPrivacyViewModel.cs new file mode 100644 index 000000000..91060d958 --- /dev/null +++ b/Gordon360/Models/ViewModels/UserPrivacyViewModel.cs @@ -0,0 +1,40 @@ +using Gordon360.Models.CCT; + +namespace Gordon360.Models.ViewModels +{ + // ViewModel for retrieving privacy settings + + // Field: field for which the visibility group will be updated + // (HomeStreet1, HomeStreet2, HomeCity, HomeState, HomeCountry, SpouseName, + // Country, HomePhone, MobilePhone) + // VisibilityGroup: The group of users that can see the information corresponding to Field + // Currently a subset of Public, FacStaff, or Private + public class UserPrivacyViewModel + { + // Constants to match IDs in CCT.dboUserPrivacy_Fields table + public const int HomeCityID = 1; + public const int HomeStateID = 2; + public const int HomeCountryID = 3; + public const int SpouseNameID = 4; + public const int CountryID = 5; + public const int HomePhoneID = 6; + public const int MobilePhoneID = 7; + public const int HomeStreet1ID = 8; + public const int HomeStreet2ID = 9; + + public const int Public_GroupID = 1; + public const int FacStaff_GroupID = 2; + public const int Private_GroupID = 3; + + public static implicit operator UserPrivacyViewModel(UserPrivacy_Settings up_s) + { + return new UserPrivacyViewModel + { + Field = up_s.FieldNavigation.Field, + VisibilityGroup = up_s.VisibilityNavigation.Group + }; + } + public string Field { get; set; } + public string VisibilityGroup { get; set; } + } +} \ No newline at end of file diff --git a/Gordon360/Services/AccountService.cs b/Gordon360/Services/AccountService.cs index a39d8f282..50c2a5814 100644 --- a/Gordon360/Services/AccountService.cs +++ b/Gordon360/Services/AccountService.cs @@ -10,12 +10,19 @@ using Gordon360.Extensions.System; using Gordon360.Enums; using System; +using Microsoft.Graph; namespace Gordon360.Services; /// -/// Service Class that facilitates data transactions between the AccountsController and the Account database model. +/// Service Class that facilitates data transactions between the AccountsController and +/// the Account database model. It also provides methods to enforce access restrictions +/// on user data. +/// +/// The "CanISee..." and "VisibleToMe..." methods encapsulate rules about whether +/// a requesting user can even find a user (CanISee...) and if so, what fields they +/// are allowed to see (VisibleToMe...). /// public class AccountService(CCTContext context) : IAccountService { @@ -25,7 +32,7 @@ public class AccountService(CCTContext context) : IAccountService /// /// The person's gordon id /// AccountViewModel if found, null if not found - [StateYourBusiness(operation = Operation.READ_ONE, resource = Resource.ACCOUNT)] + [StateYourBusiness(operation = Static.Names.Operation.READ_ONE, resource = Resource.ACCOUNT)] public AccountViewModel GetAccountByID(string id) { var account = context.ACCOUNT.FirstOrDefault(x => x.gordon_id == id); @@ -42,7 +49,7 @@ public AccountViewModel GetAccountByID(string id) /// Fetches all the account records from storage. /// /// AccountViewModel IEnumerable. If no records were found, an empty IEnumerable is returned. - [StateYourBusiness(operation = Operation.READ_ALL, resource = Resource.ACCOUNT)] + [StateYourBusiness(operation = Static.Names.Operation.READ_ALL, resource = Resource.ACCOUNT)] public IEnumerable GetAll() { return (IEnumerable)context.ACCOUNT; //Map the database model to a more presentable version (a ViewModel) @@ -80,6 +87,174 @@ public AccountViewModel GetAccountByUsername(string username) return account; } + /// Indicates whether a user making a request is authorized to see + /// profile information for students. + /// The authentication groups associated with the + /// user making the request. + /// True if the user making the request is authorized to see + /// profile information for students, and false otherwise. + public bool CanISeeStudents(IEnumerable viewerGroups) + { + return viewerGroups.Contains(AuthGroup.SiteAdmin) || + viewerGroups.Contains(AuthGroup.Police) || + viewerGroups.Contains(AuthGroup.FacStaff) || + viewerGroups.Contains(AuthGroup.Student); + } + + /// Indicates whether a user making a request is authorized to see + /// course schedule information for students. + /// The authentication groups associated with the + /// user making the request. + /// True if the user making the request is authorized to see + /// schedule information for students, and false otherwise. + public bool CanISeeStudentSchedule(IEnumerable viewerGroups) + { + return viewerGroups.Contains(AuthGroup.Advisors); + } + + + /// Indicates whether a user making a request is authorized to see + /// profile information for this particular student. Some students are not shown + /// because of FERPA protections. + /// The authentication groups associated with the + /// user making the request. + /// Profile data for the student whose information + /// is being requested. + /// True if the user making the request is authorized to see + /// profile information for this student, and false otherwise. + public bool CanISeeThisStudent(IEnumerable viewerGroups, StudentProfileViewModel? student) + { + if (!CanISeeStudents(viewerGroups)) + { + return false; + } + + if (viewerGroups.Contains(AuthGroup.SiteAdmin) || + viewerGroups.Contains(AuthGroup.Police) || + viewerGroups.Contains(AuthGroup.FacStaff)) + { + return true; + } + if (viewerGroups.Contains(AuthGroup.Student)) + { + return (student == null) ? false : student.KeepPrivate != "Y" && student.KeepPrivate != "P"; + } + return false; + } + + /// Indicates whether a user making a request is authorized to see + /// profile information for faculty and staff (facstaff). + /// The authentication groups associated with the + /// user making the request. + /// True if the user making the request is authorized to see + /// profile information for facstaff, and false otherwise. + public bool CanISeeFacstaff(IEnumerable viewerGroups) + { + return true; + } + + /// Indicates whether a user making a request is authorized to see + /// profile information for alumni. + /// The authentication groups associated with the + /// user making the request. + /// True if the user making the request is authorized to see + /// profile information for alumni, and false otherwise. + public bool CanISeeAlumni(IEnumerable viewerGroups) + { + return viewerGroups.Contains(AuthGroup.SiteAdmin) || + viewerGroups.Contains(AuthGroup.Police) || + viewerGroups.Contains(AuthGroup.FacStaff) || + viewerGroups.Contains(AuthGroup.Alumni); + } + + /// Indicates whether a user making a request is authorized to see + /// course schedule information for alumni. + /// The authentication groups associated with the + /// user making the request. + /// True if the user making the request is authorized to see + /// course schedule information for alumni, and false otherwise. + public bool CanISeeAlumniSchedule(IEnumerable viewerGroups) + { + return viewerGroups.Contains(AuthGroup.SiteAdmin) || + viewerGroups.Contains(AuthGroup.Police) || + viewerGroups.Contains(AuthGroup.FacStaff); + } + + /// Restrict info about a student to those fields which are potentially + /// viewable by the user making the request. Actual visibility may also depend + /// on privacy choices made by the user whose data is being viewed. Note that + /// this takes FERPA restrictions into account in determining whether this student + /// is visible to the requesting user. + /// The authentication groups associated with the + /// user making the request. + /// Profile data for the student whose information + /// is being requested. + /// Information the requesting user is potentially authorized to see. + /// Null if the requesting user is never allowed to see data about students. + /// + public object? VisibleToMeStudent(IEnumerable viewerGroups, StudentProfileViewModel? student) + { + if (viewerGroups.Contains(AuthGroup.SiteAdmin) || + viewerGroups.Contains(AuthGroup.Police) || + viewerGroups.Contains(AuthGroup.FacStaff)) + { + return student; + } + else if (CanISeeThisStudent(viewerGroups, student)) + { + return (student == null) ? null : (PublicStudentProfileViewModel)student; + } + return null; + } + + /// Restrict info about a facstaff person to those fields which are potentially + /// viewable by the user making the request. Actual visibility may also depend + /// on privacy choices made by the user whose data is being viewed. + /// The authentication groups associated with the + /// user making the request. + /// Profile data for the facstaff member whose information + /// is being requested. + /// Information the requesting user is potentially authorized to see. + /// Null if the requesting user is never allowed to see data about facstaff. + /// + public object? VisibleToMeFacstaff(IEnumerable viewerGroups, FacultyStaffProfileViewModel? facstaff) + { + if (viewerGroups.Contains(AuthGroup.SiteAdmin) || + viewerGroups.Contains(AuthGroup.Police)) + { + return facstaff; + } + else if (CanISeeFacstaff(viewerGroups)) + { + return (facstaff == null) ? null : (FacultyStaffProfileViewModel)facstaff; + } + return null; + } + + /// Restrict info about an alumni person to those fields which are potentially + /// viewable by the user making the request. Actual visibility may also depend + /// on privacy choices made by the user whose data is being viewed. + /// The authentication groups associated with the + /// user making the request. + /// Profile data for the alum whose information + /// is being requested. + /// Information the requesting user is potentially authorized to see. + /// Null if the requesting user is never allowed to see data about alumni. + /// + public object? VisibleToMeAlumni(IEnumerable viewerGroups, AlumniProfileViewModel? alumni) + { + if (viewerGroups.Contains(AuthGroup.SiteAdmin) || + viewerGroups.Contains(AuthGroup.Police)) + { + return alumni; + } + else if (CanISeeAlumni(viewerGroups)) + { + return (alumni == null) ? null : (AlumniProfileViewModel)alumni; + } + return null; + } + /// /// Given a list of accounts, and search params, return all the accounts that match those search params. /// @@ -216,10 +391,10 @@ public IEnumerable GetAccountsToSearch(List acc students = context.Student; } - // Only Faculy and Staff can see Private students + // Only Faculty and Staff can see Private students if (!authGroups.Contains(AuthGroup.FacStaff)) { - students = students.Where(s => s.KeepPrivate != "P"); + students = students.Where(s => (s.KeepPrivate != "Y" && s.KeepPrivate != "P")); } IEnumerable facstaff = Enumerable.Empty(); @@ -237,7 +412,25 @@ public IEnumerable GetAccountsToSearch(List acc // Do not indirectly reveal the address of facstaff and alumni who have requested to keep it private. if (!string.IsNullOrEmpty(homeCity)) { - facstaff = facstaff.Where(a => a.KeepPrivate == "0"); + var homeCityFieldId = context.UserPrivacy_Fields + .Where(f => f.Field == "HomeCity") + .Select(f => f.ID) + .FirstOrDefault(); + var privateVisibilityId = context.UserPrivacy_Visibility_Groups + .Where(v => v.Group == "Private") + .Select(v => v.ID) + .FirstOrDefault(); + var facStaffVisibilityId = context.UserPrivacy_Visibility_Groups + .Where(v => v.Group == "FacStaff") + .Select(v => v.ID) + .FirstOrDefault(); + var homePrivacy = authGroups.Contains(AuthGroup.FacStaff) + ? context.UserPrivacy_Settings.Where(a => (a.Field == homeCityFieldId && a.Visibility != privateVisibilityId)) + : context.UserPrivacy_Settings.Where(a => (a.Field == homeCityFieldId && a.Visibility != privateVisibilityId && a.Visibility != facStaffVisibilityId)); + facstaff = facstaff.Join(homePrivacy, + user => user.ID, privs => privs.gordon_id, + (user, privs) => user + ); alumni = alumni.Where(a => a.ShareAddress != "N"); } diff --git a/Gordon360/Services/ProfileService.cs b/Gordon360/Services/ProfileService.cs index fb86a3ec6..5c1c5974a 100644 --- a/Gordon360/Services/ProfileService.cs +++ b/Gordon360/Services/ProfileService.cs @@ -1,13 +1,11 @@ -using Gordon360.Authorization; -using Gordon360.Exceptions; +using Gordon360.Exceptions; using Gordon360.Models.CCT; using Gordon360.Models.CCT.Context; using Gordon360.Models.ViewModels; using Gordon360.Models.webSQL.Context; -using Microsoft.AspNetCore.Mvc; +using Gordon360.Static.Methods; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; -using Microsoft.Graph; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -15,13 +13,22 @@ using System.Linq; using System.Net; using System.Net.Mail; +using System.Reflection; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Gordon360.Enums; namespace Gordon360.Services; public class ProfileService(CCTContext context, IConfiguration config, IAccountService accountService, webSQLContext webSQLContext) : IProfileService { + // These three-character strings are valid substrings for the PersonType + // field in the profile. These are used in the UI and so cannot be changed + // here unless the corresponding change is made in the UI. + const string FACSTAFF_PROFILE = "fac"; + const string STUDENT_PROFILE = "stu"; + const string ALUMNI_PROFILE = "alu"; + /// /// get student profile info /// @@ -289,6 +296,176 @@ await context.CUSTOM_PROFILE.AddAsync(new CUSTOM_PROFILE await context.SaveChangesAsync(); } + + /// + /// convert combined profile to public profile based on individual privacy settings + /// + /// list of AuthGroups the logged-in user belongs to + /// combined profile of the person being searched + /// public profile of the person based on individual privacy settings + public CombinedProfileViewModel ImposePrivacySettings + (IEnumerable viewerGroups, ProfileViewModel profile) + { + // Convert profile from record to class so we can modify its elements + CombinedProfileViewModel restricted_profile = (CombinedProfileViewModel) profile; + + // Privacy settings are generally heirarchical from bypassing all privacy settings + // to honoring all privacy settings: + // SiteAdmin & Police -> FacStaff -> Students -> Alumni + + // Find the account belonging to person whose profile we are accessing and get their + // privacy settings + var account = accountService.GetAccountByUsername(restricted_profile.AD_Username); + var privacy = context.UserPrivacy_Settings.Where(up_s => up_s.gordon_id == account.GordonID); + + // Determing the viewer and profile user types + bool viewerIsSiteAdmin = viewerGroups.Contains(AuthGroup.SiteAdmin); + bool viewerIsPolice = viewerGroups.Contains(AuthGroup.Police); + bool viewerIsFacStaff = viewerGroups.Contains(AuthGroup.FacStaff); + bool viewerIsStudent = viewerGroups.Contains(AuthGroup.Student); + bool viewerIsAlumni = viewerGroups.Contains(AuthGroup.Alumni); + bool profileIsFacStaff = restricted_profile.PersonType.Contains(FACSTAFF_PROFILE); + bool profileIsStudent = restricted_profile.PersonType.Contains(STUDENT_PROFILE); + bool profileIsAlumni = restricted_profile.PersonType.Contains(ALUMNI_PROFILE); + + // Loop over all privacy fields (MobilePhone, HomePhone, HomeCity, etc.) and use + // visibility data in UserPrivacy_Settings table if exists otherwise use old-style + // privacy settings in profile. + + // NOTE: The "old-style" privacy settings in the profile (e.g. IsMobilePhonePrivate) + // are set by HR or the student matriciulation process. These settings will be used + // for 360 until the user chooses privacy settings in their profile. + + foreach (int fieldID in context.UserPrivacy_Fields.Select(s => s.ID)) + { + var field = context.UserPrivacy_Fields.Where(s => s.ID == fieldID).FirstOrDefault()?.Field; + + // Determine the visibility for the current privacy field + //var privacy.Where(f => f.Field == ) + var visibilityID = privacy.Where(x => x.Field == fieldID).FirstOrDefault()?.Visibility; + if (visibilityID is null) + { + if (profileIsStudent) + { + // honor "semi-private" code: student's home city, state, country and on-off campus status is private + var addressPrivate = restricted_profile.KeepPrivate == "S" + && (fieldID == UserPrivacyViewModel.HomeCityID || fieldID == UserPrivacyViewModel.HomeStateID + || fieldID == UserPrivacyViewModel.HomeCountryID || fieldID == UserPrivacyViewModel.CountryID); + var mobilePhonePrivate = restricted_profile.IsMobilePhonePrivate && fieldID == UserPrivacyViewModel.MobilePhoneID; + visibilityID = mobilePhonePrivate || addressPrivate + ? UserPrivacyViewModel.Private_GroupID + : UserPrivacyViewModel.Public_GroupID; + } + else if (profileIsFacStaff) + { + visibilityID = restricted_profile.KeepPrivate == "1" + ? UserPrivacyViewModel.Private_GroupID + : UserPrivacyViewModel.Public_GroupID; + } + else if (profileIsAlumni) + { + visibilityID = (restricted_profile.ShareName == "N" + || restricted_profile.ShareAddress == "N") + ? UserPrivacyViewModel.Private_GroupID + : UserPrivacyViewModel.Public_GroupID; + } + } + + // Enforce the visibility for the current privacy field + if ((viewerIsSiteAdmin || viewerIsPolice) && visibilityID != UserPrivacyViewModel.Public_GroupID) + { + MarkAsPrivate(restricted_profile, field); + } + else if (viewerIsFacStaff) + { + if (profileIsFacStaff) + { + if (visibilityID == UserPrivacyViewModel.Private_GroupID) + { + MakePrivate(restricted_profile, field); + } + else if (visibilityID == UserPrivacyViewModel.FacStaff_GroupID) + { + MarkAsPrivate(restricted_profile, field); + } + } + else if ((profileIsStudent || profileIsAlumni) && visibilityID != UserPrivacyViewModel.Public_GroupID) + { + MarkAsPrivate(restricted_profile, field); + } + } + else if ((viewerIsStudent || viewerIsAlumni) && visibilityID != UserPrivacyViewModel.Public_GroupID) + { + MakePrivate(restricted_profile, field); + } + } + + // Handle a legacy special case -- if a student has the semi-private flag then + // not only do we need to hide their address information (handled above), but + // also need to change an entry in their profile + if (restricted_profile.KeepPrivate == "S") + { + restricted_profile.OnOffCampus = "P"; + } + + return restricted_profile; + } + + /// + /// Get profile fields and visibility settings for a specific user + /// + /// AD username + /// List of field and visibility privacy settings for a specific user + public IEnumerable GetPrivacySettingsAsync(string username) + { + var account = accountService.GetAccountByUsername(username); + + // select all privacy settings + var privacy = context.UserPrivacy_Settings + .Include(up_s => up_s.VisibilityNavigation) + .Include(up_s => up_s.FieldNavigation) + .Where(up_s => up_s.gordon_id == account.GordonID) + .Select(up_s => (UserPrivacyViewModel)up_s); + + return privacy; + } + + /// + /// Set privacy setting of some piece of personal data for user. + /// + /// AD Username + /// User Privacy Update View Model + public async Task UpdateUserPrivacyAsync(string username, UserPrivacyUpdateViewModel userPrivacy) + { + var account = accountService.GetAccountByUsername(username); + foreach (string field in userPrivacy.Field) + { + var fieldID = context.UserPrivacy_Fields.FirstOrDefault(f => f.Field == field).ID; + var groupID = context.UserPrivacy_Visibility_Groups.FirstOrDefault(v => v.Group == userPrivacy.VisibilityGroup).ID; + var user = context.UserPrivacy_Settings + .Include(up_s => up_s.FieldNavigation) + .Include(up_s => up_s.VisibilityNavigation) + .FirstOrDefault(up_s => up_s.gordon_id == account.GordonID && up_s.Field == fieldID); + if (user is null) + { + var privacy = new UserPrivacy_Settings + { + gordon_id = account.GordonID, + Field = fieldID, + Visibility = groupID + }; + await context.UserPrivacy_Settings.AddAsync(privacy); + } + else + { + user.Visibility = groupID; + } + } + + context.SaveChanges(); + } + + /// /// privacy setting of mobile phone. /// @@ -447,19 +624,19 @@ public async Task UpdateImagePrivacyAsync(string username, string value) if (student != null) { MergeProfile(profile, JObject.FromObject(student)); - personType += "stu"; + personType += STUDENT_PROFILE; } if (alumni != null) { MergeProfile(profile, JObject.FromObject(alumni)); - personType += "alu"; + personType += ALUMNI_PROFILE; } if (faculty != null) { MergeProfile(profile, JObject.FromObject(faculty)); - personType += "fac"; + personType += FACSTAFF_PROFILE; } if (customInfo != null) @@ -534,4 +711,49 @@ public IEnumerable GetMailStopsAsync() return webSQLContext.Mailstops.Select(m => m.code) .OrderBy(d => d); } + + /// + /// Change a ProfileItem's privacy setting to true + /// + /// Combined profile containing element to update + /// The ID of the profile element of which to update IsPrivate + private static void MarkAsPrivate(CombinedProfileViewModel profile, string field) + { + // Profile element will be returned to UI, but should be marked as private + // since the authenticated user is only seeing because they are authorized + // to do so. + Type cpvm = new CombinedProfileViewModel().GetType(); + try + { + PropertyInfo prop = cpvm.GetProperty(field); + ProfileItem profile_item = (ProfileItem) prop.GetValue(profile); + if (profile_item != null) + { + prop.SetValue(profile, new ProfileItem(profile_item.Value, true)); + } + } + catch (Exception e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + } + } + + /// + /// Change a ProfileItem to be null (remove it from the profile) + /// + /// Combined profile containing element to make null + /// The ID of the profile element to make null + private static void MakePrivate(CombinedProfileViewModel profile, string field) + { + // remove profile element if it should not be sent to the UI + try + { + Type cpvm = new CombinedProfileViewModel().GetType(); + cpvm.GetProperty(field).SetValue(profile, null); + } + catch (Exception e) + { + System.Diagnostics.Debug.WriteLine(e.Message); + } + } } diff --git a/Gordon360/Services/ServiceInterfaces.cs b/Gordon360/Services/ServiceInterfaces.cs index 2471dcff6..1a025e269 100644 --- a/Gordon360/Services/ServiceInterfaces.cs +++ b/Gordon360/Services/ServiceInterfaces.cs @@ -27,6 +27,7 @@ public interface IProfileService DateTime GetBirthdate(string username); Task> GetAdvisorsAsync(string username); CliftonStrengthsViewModel? GetCliftonStrengths(int id); + IEnumerable GetPrivacySettingsAsync(string username); GraduationViewModel? GetGraduationInfo(string username); Task ToggleCliftonStrengthsPrivacyAsync(int id); IEnumerable GetEmergencyContact(string username); @@ -36,6 +37,8 @@ public interface IProfileService Task UpdateMobilePhoneNumberAsync(string username, string newMobilePhoneNumber); Task UpdateOfficeLocationAsync(string username, string newBuilding, string newRoom); Task UpdateOfficeHoursAsync(string username, string newHours); + CombinedProfileViewModel ImposePrivacySettings(IEnumerable viewerGroups, ProfileViewModel profile); + Task UpdateUserPrivacyAsync(string username, UserPrivacyUpdateViewModel facultyStaffPrivacy); Task UpdateMailStopAsync(string username, string newMail); Task UpdateMobilePrivacyAsync(string username, string value); Task UpdateImagePrivacyAsync(string username, string value); @@ -74,6 +77,15 @@ public interface IAccountService IEnumerable GetAll(); AccountViewModel GetAccountByEmail(string email); AccountViewModel GetAccountByUsername(string username); + public bool CanISeeStudents(IEnumerable viewerGroups); + public bool CanISeeStudentSchedule(IEnumerable viewerGroups); + public bool CanISeeThisStudent(IEnumerable viewerGroups, StudentProfileViewModel? student); + public bool CanISeeFacstaff(IEnumerable viewerGroups); + public bool CanISeeAlumni(IEnumerable viewerGroups); + public bool CanISeeAlumniSchedule(IEnumerable viewerGroups); + public object? VisibleToMeStudent(IEnumerable viewerGroups, StudentProfileViewModel? student); + public object? VisibleToMeFacstaff(IEnumerable viewerGroups, FacultyStaffProfileViewModel? facstaff); + public object? VisibleToMeAlumni(IEnumerable viewerGroups, AlumniProfileViewModel? alumni); IEnumerable GetAccountsToSearch(List accountTypes, IEnumerable authGroups, string? homeCity); IEnumerable AdvancedSearch( IEnumerable accounts, diff --git a/Gordon360/Static Classes/Names.cs b/Gordon360/Static Classes/Names.cs index 8947a801b..98db01dad 100644 --- a/Gordon360/Static Classes/Names.cs +++ b/Gordon360/Static Classes/Names.cs @@ -6,6 +6,7 @@ public static class Resource { public const string EMERGENCY_CONTACT = "A new emergency contact resource"; public const string PROFILE = "A new profile resource"; + public const string PROFILE_PRIVACY = "An individual's specific profile privacy resource"; public const string MEMBERSHIP_REQUEST = "A Membership Request Resource"; public const string MEMBERSHIP = "A Membership Resource"; public const string MEMBERSHIP_PRIVACY = "A Membership privacy"; diff --git a/Gordon360/Static Classes/ProfileItem.cs b/Gordon360/Static Classes/ProfileItem.cs new file mode 100644 index 000000000..84e90d604 --- /dev/null +++ b/Gordon360/Static Classes/ProfileItem.cs @@ -0,0 +1,6 @@ +namespace Gordon360.Static.Methods; + +/// +/// Profile item class contining a single profile datum and privacy flag. +/// +public record ProfileItem(TValue Value, bool IsPrivate);