diff --git a/src/Analysim.Web/ClientApp/src/app/application-pipes/custom.pipe.ts b/src/Analysim.Web/ClientApp/src/app/application-pipes/custom.pipe.ts
index ba102a2a..51b6ac69 100644
--- a/src/Analysim.Web/ClientApp/src/app/application-pipes/custom.pipe.ts
+++ b/src/Analysim.Web/ClientApp/src/app/application-pipes/custom.pipe.ts
@@ -113,10 +113,14 @@ export class ProjectMemberPipe implements PipeTransform {
return projectUsers.filter(x => {
return x.userRole == "member"
})
+ case "allMembers":
+ return projectUsers.filter(x => {
+ return x.userRole == "owner" || x.userRole == "admin" || x.userRole == "member"
+ })
default:
return projectUsers.filter(x => {
return x.userRole == "member" || x.userRole == "admin"
})
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.html b/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.html
index a14d0960..331d71ea 100644
--- a/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.html
+++ b/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.html
@@ -73,6 +73,7 @@
0"
href="#"
id="navbarNotificationMenuLink"
aria-haspopup="true"
diff --git a/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.scss b/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.scss
index 0fa4b4bd..ded21c37 100644
--- a/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.scss
+++ b/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.scss
@@ -98,6 +98,14 @@
border-radius: var(--nav-link-radius);
}
+.notification-trigger.has-unread-notifications {
+ background: var(--c-danger-background);
+}
+
+.notification-trigger.has-unread-notifications .fa-bell {
+ color: color-mix(in srgb, var(--color-danger) 65%, var(--c-text-inverse));
+}
+
.notification-badge {
position: absolute;
top: 3px;
@@ -106,9 +114,9 @@
height: 17px;
padding: 0 4px;
border-radius: 999px;
- background: #d94a38;
+ background: var(--color-danger);
color: #fff;
- border: 1px solid var(--background-color-tertiary);
+ border: 1px solid var(--background-color-secondary);
font-size: 10px;
font-weight: 700;
line-height: 15px;
diff --git a/src/Analysim.Web/ClientApp/src/app/projects/project/modal-member-list/modal-member-list.component.html b/src/Analysim.Web/ClientApp/src/app/projects/project/modal-member-list/modal-member-list.component.html
index d6f25368..3f426608 100644
--- a/src/Analysim.Web/ClientApp/src/app/projects/project/modal-member-list/modal-member-list.component.html
+++ b/src/Analysim.Web/ClientApp/src/app/projects/project/modal-member-list/modal-member-list.component.html
@@ -10,7 +10,7 @@ Member List
-
diff --git a/src/Analysim.Web/Controllers/ProjectController.cs b/src/Analysim.Web/Controllers/ProjectController.cs
index b0d5b7d6..502041f6 100644
--- a/src/Analysim.Web/Controllers/ProjectController.cs
+++ b/src/Analysim.Web/Controllers/ProjectController.cs
@@ -1055,6 +1055,17 @@ public async Task AddProjectLog([FromForm] CreateProjectLogVM for
var project = await _dbContext.Projects.FindAsync(formdata.ProjectID);
if (project == null) return NotFound(new { message = "Project Not Found." });
+ bool hasProjectLogAccess = await _dbContext.ProjectUsers
+ .AnyAsync(aup =>
+ aup.UserID == user.Id &&
+ aup.ProjectID == project.ProjectID &&
+ (aup.UserRole == "owner" || aup.UserRole == "member"));
+
+ if (!hasProjectLogAccess)
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, new { message = "You must be an owner or member of the project" });
+ }
+
// Validate Content
if (string.IsNullOrWhiteSpace(formdata.Content))
{
@@ -2065,17 +2076,6 @@ public async Task FollowProject([FromRoute] int projectID)
_dbContext.Entry(projectUser).State = EntityState.Modified;
await _dbContext.SaveChangesAsync();
- try
- {
- await _notificationService.PublishAsync(new NotificationEvent
- {
- Type = NotificationTypes.ProjectJoined,
- ActorUserID = user.Id,
- ProjectID = project.ProjectID
- });
- }
- catch (Exception) { }
-
return Ok(new
{
result = projectUser,
@@ -2097,17 +2097,6 @@ await _notificationService.PublishAsync(new NotificationEvent
_dbContext.Entry(projectUser).Reference(pu => pu.User).Load();
_dbContext.Entry(projectUser).Reference(pu => pu.Project).Load();
- try
- {
- await _notificationService.PublishAsync(new NotificationEvent
- {
- Type = NotificationTypes.ProjectJoined,
- ActorUserID = user.Id,
- ProjectID = project.ProjectID
- });
- }
- catch (Exception) { }
-
return Ok(new
{
result = projectUser,
@@ -2332,6 +2321,21 @@ public async Task RequestJoinProject([FromRoute] int projectID)
joinRequest = await ProjectMembershipRequestsWithUsers()
.SingleAsync(pmr => pmr.ProjectMembershipRequestID == joinRequest.ProjectMembershipRequestID);
+ try
+ {
+ await _notificationService.PublishAsync(new NotificationEvent
+ {
+ Type = NotificationTypes.ProjectJoined,
+ ActorUserID = user.Id,
+ ProjectID = project.ProjectID,
+ Data = new Dictionary
+ {
+ ["membershipRequestID"] = joinRequest.ProjectMembershipRequestID.ToString()
+ }
+ });
+ }
+ catch (Exception) { }
+
return Ok(new
{
result = joinRequest,
@@ -2771,13 +2775,16 @@ public async Task UploadFile([FromForm] ProjectFileUploadVM formd
var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.Id == userId);
if (user == null) return NotFound(new { message = "User Not Found" });
- bool isOwner = await _dbContext.Projects
+ bool hasUploadAccess = await _dbContext.Projects
.AnyAsync(p => p.ProjectUsers.Any(aup =>
- aup.User.Id == user.Id &&
- aup.Project.ProjectID == formdata.ProjectID &&
- aup.UserRole == "owner"));
+ aup.UserID == user.Id &&
+ aup.ProjectID == formdata.ProjectID &&
+ (aup.UserRole == "owner" || aup.UserRole == "member")));
- if (!isOwner) return Unauthorized(new { message = "You are not the owner of the project" });
+ if (!hasUploadAccess)
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, new { message = "You must be an owner or member of the project" });
+ }
if (formdata.Directory == null) { formdata.Directory = ""; }
@@ -2887,13 +2894,16 @@ public async Task UploadNotebook([FromForm] ProjectNotebookUpload
if (user == null) return NotFound(new { message = "User Not Found" });
- bool isOwner = await _dbContext.Projects
+ bool hasNotebookUploadAccess = await _dbContext.Projects
.AnyAsync(p => p.ProjectUsers.Any(aup =>
- aup.User.Id == user.Id &&
- aup.Project.ProjectID == noteBookData.ProjectID &&
- aup.UserRole == "owner"));
+ aup.UserID == user.Id &&
+ aup.ProjectID == noteBookData.ProjectID &&
+ (aup.UserRole == "owner" || aup.UserRole == "member")));
- if (!isOwner) return Unauthorized(new { message = "You are not the owner of the project" });
+ if (!hasNotebookUploadAccess)
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, new { message = "You must be an owner or member of the project" });
+ }
// Find Project
@@ -2984,12 +2994,15 @@ public async Task UploadNotebookNewVersion([FromForm] ProjectNote
var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.Id == userId);
if (user == null) return NotFound(new { message = "User Not Found" });
- bool isOwner = await _dbContext.Projects
+ bool hasNotebookUploadAccess = await _dbContext.Projects
.AnyAsync(p => p.ProjectUsers.Any(aup =>
- aup.User.Id == user.Id &&
- aup.Project.ProjectID == noteBookData.ProjectID &&
- aup.UserRole == "owner"));
- if (!isOwner) return Unauthorized(new { message = "You are not the owner of the project" });
+ aup.UserID == user.Id &&
+ aup.ProjectID == noteBookData.ProjectID &&
+ (aup.UserRole == "owner" || aup.UserRole == "member")));
+ if (!hasNotebookUploadAccess)
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, new { message = "You must be an owner or member of the project" });
+ }
// Find Project
@@ -3067,13 +3080,16 @@ public async Task UploadExistingNotebook([FromForm] ExistingProje
var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.Id == userId);
if (user == null) return NotFound(new { message = "User Not Found" });
- bool isOwner = await _dbContext.Projects
+ bool hasNotebookUploadAccess = await _dbContext.Projects
.AnyAsync(p => p.ProjectUsers.Any(aup =>
- aup.User.Id == user.Id &&
- aup.Project.ProjectID == noteBookData.ProjectID &&
- aup.UserRole == "owner"));
+ aup.UserID == user.Id &&
+ aup.ProjectID == noteBookData.ProjectID &&
+ (aup.UserRole == "owner" || aup.UserRole == "member")));
- if (!isOwner) return Unauthorized(new { message = "You are not the owner of the project" });
+ if (!hasNotebookUploadAccess)
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, new { message = "You must be an owner or member of the project" });
+ }
var project = await _dbContext.Projects.FindAsync(noteBookData.ProjectID);
@@ -3204,13 +3220,16 @@ public async Task CreateFolder([FromForm] FolderUploadProfileView
var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.Id == userId);
if (user == null) return NotFound(new { message = "User Not Found" });
- bool isOwner = await _dbContext.Projects
+ bool hasFolderAccess = await _dbContext.Projects
.AnyAsync(p => p.ProjectUsers.Any(aup =>
- aup.User.Id == user.Id &&
- aup.Project.ProjectID == formdata.ProjectID &&
- aup.UserRole == "owner"));
+ aup.UserID == user.Id &&
+ aup.ProjectID == formdata.ProjectID &&
+ (aup.UserRole == "owner" || aup.UserRole == "member")));
- if (!isOwner) return Unauthorized(new { message = "You are not the owner of the project" });
+ if (!hasFolderAccess)
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, new { message = "You must be an owner or member of the project" });
+ }
if (formdata.Directory == null) { formdata.Directory = ""; }
@@ -3234,7 +3253,7 @@ public async Task CreateFolder([FromForm] FolderUploadProfileView
Uri = "",
DateCreated = DateTimeOffset.UtcNow,
LastModified = DateTimeOffset.UtcNow,
- UserID = formdata.UserID,
+ UserID = user.Id,
ProjectID = formdata.ProjectID
};
@@ -3276,13 +3295,16 @@ public async Task CreateNotebookFolder([FromForm] FolderUploadPro
var user = await _dbContext.Users.SingleOrDefaultAsync(u => u.Id == userId);
if (user == null) return NotFound(new { message = "User Not Found" });
- bool isOwner = await _dbContext.Projects
+ bool hasFolderAccess = await _dbContext.Projects
.AnyAsync(p => p.ProjectUsers.Any(aup =>
- aup.User.Id == user.Id &&
- aup.Project.ProjectID == formdata.ProjectID &&
- aup.UserRole == "owner"));
+ aup.UserID == user.Id &&
+ aup.ProjectID == formdata.ProjectID &&
+ (aup.UserRole == "owner" || aup.UserRole == "member")));
- if (!isOwner) return Unauthorized(new { message = "You are not the owner of the project" });
+ if (!hasFolderAccess)
+ {
+ return StatusCode(StatusCodes.Status403Forbidden, new { message = "You must be an owner or member of the project" });
+ }
if (formdata.Directory == null) { formdata.Directory = ""; }
diff --git a/src/Analysim.Web/Services/NotificationService.cs b/src/Analysim.Web/Services/NotificationService.cs
index 98b943c1..55842a5e 100644
--- a/src/Analysim.Web/Services/NotificationService.cs
+++ b/src/Analysim.Web/Services/NotificationService.cs
@@ -202,9 +202,9 @@ private async Task PublishProjectJoinedAsync(NotificationEvent notificationEvent
var actorName = await GetUserDisplayNameAsync(notificationEvent.ActorUserID, "Someone");
var projectName = GetProjectName(project);
- var link = BuildProjectLink(project, null);
- var title = "Someone joined your project";
- var body = $"{actorName} joined {projectName}.";
+ var link = BuildProjectLink(project, "edit");
+ var title = "Someone requested to join your project";
+ var body = $"{actorName} requested to join {projectName}.";
await CreateAndSendAsync(
notificationEvent,