Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
myieye committed May 6, 2024
2 parents 9ff712c + b0d7857 commit 1c60e0b
Show file tree
Hide file tree
Showing 73 changed files with 2,877 additions and 255 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ If you're running Windows, you may need to add the following lines to your `C:\W
```
127.0.0.1 resumable.localhost
127.0.0.1 hg.localhost
127.0.0.1 admin.localhost
```

On Linux, anything with a `.localhost` domain is automatically mapped to 127.0.0.1 so you don't need to edit your `/etc/hosts` file.
Expand Down
1 change: 1 addition & 0 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ RUN --mount=type=cache,target=/root/.nuget/packages dotnet publish /p:Informatio
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
USER www-data:www-data
ENTRYPOINT ["dotnet", "LexBoxApi.dll"]
15 changes: 0 additions & 15 deletions backend/LexBoxApi/Controllers/ProjectController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,21 +232,6 @@ public async Task<ActionResult<int>> UpdateLexEntryCount(string code)
return result is null ? NotFound() : result;
}

[HttpPost("updateAllLexEntryCounts")]
[AdminRequired]
public async Task<ActionResult<int>> UpdateAllLexEntryCounts(bool onlyUnknown = true, int limit = 100, int delayMs = 10)
{
var projects = lexBoxDbContext.Projects.Where(p => (p.Type == ProjectType.FLEx || p.Type == ProjectType.WeSay) && (!onlyUnknown || p.FlexProjectMetadata == null)).Take(limit).ToArray();
var completed = 0;
foreach (var project in projects)
{
await projectService.UpdateLexEntryCount(project.Code);
completed++;
if (delayMs > 0) await Task.Delay(delayMs);
}
return Ok(completed);
}

[HttpPost("queueUpdateProjectMetadataTask")]
public async Task<ActionResult> QueueUpdateProjectMetadataTask(string projectCode)
{
Expand Down
54 changes: 17 additions & 37 deletions backend/LexBoxApi/Controllers/TestingController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LexBoxApi.Auth;
using LexBoxApi.Auth.Attributes;
using LexBoxApi.Services;
using LexCore.Auth;
using LexCore.Exceptions;
Expand All @@ -12,21 +13,12 @@ namespace LexBoxApi.Controllers;

[ApiController]
[Route("/api/[controller]")]
public class TestingController : ControllerBase
public class TestingController(
LexAuthService lexAuthService,
LexBoxDbContext lexBoxDbContext,
SeedingData seedingData)
: ControllerBase
{
private readonly LexAuthService _lexAuthService;
private readonly LexBoxDbContext _lexBoxDbContext;
private readonly SeedingData _seedingData;

public TestingController(LexAuthService lexAuthService,
LexBoxDbContext lexBoxDbContext,
SeedingData seedingData)
{
_lexAuthService = lexAuthService;
_lexBoxDbContext = lexBoxDbContext;
_seedingData = seedingData;
}

#if DEBUG
[AllowAnonymous]
[HttpGet("makeJwt")]
Expand All @@ -37,34 +29,17 @@ public async Task<ActionResult<string>> MakeJwt(string usernameOrEmail,
UserRole userRole,
LexboxAudience audience = LexboxAudience.LexboxApi)
{
var user = await _lexBoxDbContext.Users.Include(u => u.Projects).ThenInclude(p => p.Project)
var user = await lexBoxDbContext.Users.Include(u => u.Projects).ThenInclude(p => p.Project)
.FindByEmailOrUsername(usernameOrEmail);
if (user is null) return NotFound();
var (token, _, _) = _lexAuthService.GenerateJwt(new LexAuthUser(user) { Role = userRole, Audience = audience });
var (token, _, _) = lexAuthService.GenerateJwt(new LexAuthUser(user) { Role = userRole, Audience = audience });
return token;
}

[HttpPost("seedDatabase")]
public async Task<ActionResult<TestingControllerProject>> SeedDatabase()
{
await _seedingData.SeedDatabase();
var project = await _lexBoxDbContext.Projects
.Include(p => p.Users)
.ThenInclude(u => u.User)
.FirstOrDefaultAsync(p => p.Code == "sena-3");
ArgumentNullException.ThrowIfNull(project);
return new TestingControllerProject(project.Id,
project.Name,
project.Code,
project.Users.Select(u =>
new TestingControllerProjectUser(u.User!.Username, u.Role.ToString(), u.User.Email, u.UserId))
.ToList());
}

[HttpPost("cleanupSeedData")]
public async Task<ActionResult> CleanupSeedData()
{
await _seedingData.CleanUpSeedData();
await seedingData.CleanUpSeedData();
return Ok();
}

Expand All @@ -82,11 +57,16 @@ public ActionResult DebugConfiguration()
return Ok(configurationRoot.GetDebugView());
}

public record TestingControllerProject(Guid Id, string Name, string Code, List<TestingControllerProjectUser> Users);
#endif

public record TestingControllerProjectUser(string? Username, string Role, string? Email, Guid Id);
[HttpPost("seedDatabase")]
[AdminRequired]
public async Task<ActionResult> SeedDatabase()
{
await seedingData.SeedDatabase();
return Ok();
}

#endif
[HttpGet("throwsException")]
[AllowAnonymous]
public ActionResult ThrowsException()
Expand Down
17 changes: 0 additions & 17 deletions backend/LexBoxApi/GraphQL/LexMutations.cs

This file was deleted.

25 changes: 24 additions & 1 deletion backend/LexBoxApi/GraphQL/LexQueries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace LexBoxApi.GraphQL;
[QueryType]
public class LexQueries
{

[UseProjection]
[UseSorting]
public IQueryable<Project> MyProjects(LoggedInContext loggedInContext, LexBoxDbContext context)
Expand Down Expand Up @@ -70,6 +69,30 @@ public IQueryable<DraftProject> DraftProjectByCode(LexBoxDbContext context, IPer
return context.DraftProjects.Where(p => p.Code == code);
}

[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Organization> Orgs(LexBoxDbContext context)
{
return context.Orgs;
}

[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Organization> MyOrgs(LexBoxDbContext context, LoggedInContext loggedInContext)
{
var userId = loggedInContext.User.Id;
return context.Orgs.Where(o => o.Members.Any(m => m.UserId == userId));
}

[UseSingleOrDefault]
[UseProjection]
public IQueryable<Organization> OrgById(LexBoxDbContext context, Guid orgId)
{
return context.Orgs.Where(o => o.Id == orgId);
}

[UseOffsetPaging]
[UseProjection]
[UseFiltering]
Expand Down
88 changes: 88 additions & 0 deletions backend/LexBoxApi/GraphQL/OrgMutations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using LexBoxApi.Auth;
using LexCore.Entities;
using LexCore.Exceptions;
using LexCore.ServiceInterfaces;
using LexData;
using LexData.Entities;
using Microsoft.EntityFrameworkCore;

namespace LexBoxApi.GraphQL;

[MutationType]
public class OrgMutations
{
[Error<DbError>]
[UseMutationConvention]
[UseFirstOrDefault]
[UseProjection]
public async Task<IQueryable<Organization>> CreateOrganization(string name,
LexBoxDbContext dbContext,
LoggedInContext loggedInContext,
IPermissionService permissionService)
{
permissionService.AssertCanCreateOrg();
var userId = loggedInContext.User.Id;
var orgId = Guid.NewGuid();
dbContext.Orgs.Add(new Organization()
{
Id = orgId,
Name = name,
Members =
[
new OrgMember() { Role = OrgRole.Admin, UserId = userId }
]
});
await dbContext.SaveChangesAsync();
return dbContext.Orgs.Where(o => o.Id == orgId);
}

/// <summary>
/// set the role of a member in an organization, if the member does not exist it will be created
/// </summary>
/// <param name="dbContext"></param>
/// <param name="permissionService"></param>
/// <param name="orgId"></param>
/// <param name="role">set to null to remove the member</param>
/// <param name="emailOrUsername">either an email or a username for the user whos membership to update</param>
[Error<DbError>]
[Error<NotFoundException>]
[UseMutationConvention]
[UseFirstOrDefault]
[UseProjection]
public async Task<IQueryable<Organization>> SetOrgMemberRole(
LexBoxDbContext dbContext,
IPermissionService permissionService,
Guid orgId,
OrgRole? role,
string emailOrUsername)
{
var org = await dbContext.Orgs.Include(o => o.Members).FirstOrDefaultAsync(o => o.Id == orgId);
NotFoundException.ThrowIfNull(org);
var user = await dbContext.Users.FindByEmailOrUsername(emailOrUsername);
NotFoundException.ThrowIfNull(user);

permissionService.AssertCanEditOrg(org);
await UpdateOrgMemberRole(dbContext, org, role, user.Id);
return dbContext.Orgs.Where(o => o.Id == orgId);
}

private async Task UpdateOrgMemberRole(LexBoxDbContext dbContext, Organization org, OrgRole? role, Guid userId)
{
var member = org.Members.FirstOrDefault(m => m.UserId == userId);
if (member is null && role is null) return;
if (role is not null && member is not null)
{
member.Role = role.Value;
}
else if (role is null && member is not null)
{
org.Members.Remove(member);
}
else if (role is not null && member is null)
{
org.Members.Add(new OrgMember { UserId = userId, Role = role.Value });
}

await dbContext.SaveChangesAsync();
}
}
35 changes: 27 additions & 8 deletions backend/LexBoxApi/GraphQL/ProjectMutations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ public async Task<IQueryable<Project>> AddProjectMember(IPermissionService permi
{
permissionService.AssertCanManageProject(input.ProjectId);
var project = await dbContext.Projects.FindAsync(input.ProjectId);
if (project is null) throw new NotFoundException("Project not found");
NotFoundException.ThrowIfNull(project);
var user = await dbContext.Users.Include(u => u.Projects).FindByEmailOrUsername(input.UsernameOrEmail);
if (user is null)
{
var (_, email, _) = ExtractNameAndAddressFromUsernameOrEmail(input.UsernameOrEmail);
// We don't try to catch InvalidEmailException; if it happens, we let it get sent to the frontend
if (email is null)
{
throw new NotFoundException("User not found");
throw NotFoundException.ForType<User>();
}
else
{
Expand Down Expand Up @@ -123,7 +123,7 @@ public async Task<BulkAddProjectMembersResult> BulkAddProjectMembers(
LexBoxDbContext dbContext)
{
var project = await dbContext.Projects.FindAsync(input.ProjectId);
if (project is null) throw new NotFoundException("Project not found");
if (project is null) throw new NotFoundException("Project not found", "project");
List<UserProjectRole> AddedMembers = [];
List<UserProjectRole> CreatedMembers = [];
List<UserProjectRole> ExistingMembers = [];
Expand Down Expand Up @@ -225,7 +225,7 @@ await dbContext.ProjectUsers
.Include(r => r.Project)
.Include(r => r.User)
.FirstOrDefaultAsync(u => u.ProjectId == input.ProjectId && u.UserId == input.UserId);
if (projectUser?.User is null || projectUser.Project is null) throw new NotFoundException("Project member not found");
if (projectUser?.User is null || projectUser.Project is null) throw NotFoundException.ForType<ProjectUsers>();
projectUser.User.AssertHasVerifiedEmailForRole(input.Role);
projectUser.Role = input.Role;
projectUser.User.UpdateCreateProjectsPermission(input.Role);
Expand All @@ -251,7 +251,7 @@ public async Task<IQueryable<Project>> ChangeProjectName(ChangeProjectNameInput
if (input.Name.IsNullOrEmpty()) throw new RequiredException("Project name cannot be empty");

var project = await dbContext.Projects.FindAsync(input.ProjectId);
if (project is null) throw new NotFoundException("Project not found");
NotFoundException.ThrowIfNull(project);

project.Name = input.Name;
project.UpdateUpdatedDate();
Expand All @@ -270,14 +270,33 @@ public async Task<IQueryable<Project>> ChangeProjectDescription(ChangeProjectDes
{
permissionService.AssertCanManageProject(input.ProjectId);
var project = await dbContext.Projects.FindAsync(input.ProjectId);
if (project is null) throw new NotFoundException("Project not found");
NotFoundException.ThrowIfNull(project);

project.Description = input.Description;
project.UpdateUpdatedDate();
await dbContext.SaveChangesAsync();
return dbContext.Projects.Where(p => p.Id == input.ProjectId);
}

[Error<NotFoundException>]
[Error<DbError>]
[UseMutationConvention]
[UseFirstOrDefault]
[UseProjection]
public async Task<IQueryable<Project>> SetProjectConfidentiality(SetProjectConfidentialityInput input,
IPermissionService permissionService,
LexBoxDbContext dbContext)
{
permissionService.AssertCanManageProject(input.ProjectId);
var project = await dbContext.Projects.FindAsync(input.ProjectId);
NotFoundException.ThrowIfNull(project);

project.IsConfidential = input.IsConfidential;
project.UpdateUpdatedDate();
await dbContext.SaveChangesAsync();
return dbContext.Projects.Where(p => p.Id == input.ProjectId);
}

[Error<NotFoundException>]
[Error<LastMemberCantLeaveException>]
[UseMutationConvention]
Expand All @@ -290,7 +309,7 @@ public async Task<Project> LeaveProject(
var project = await dbContext.Projects.Where(p => p.Id == projectId)
.Include(p => p.Users)
.SingleOrDefaultAsync();
if (project is null) throw new NotFoundException("Project not found");
NotFoundException.ThrowIfNull(project);
var member = project.Users.FirstOrDefault(u => u.UserId == loggedInContext.User.Id);
if (member is null) return project;
if (member.Role == ProjectRole.Manager && project.Users.Count(m => m.Role == ProjectRole.Manager) == 1)
Expand Down Expand Up @@ -342,7 +361,7 @@ public async Task<IQueryable<Project>> SoftDeleteProject(
if (deletedDraftCount == 0)
{
// No draft project either, so return standard project not found error
throw new NotFoundException("Project not found");
throw NotFoundException.ForType<Project>();
}
else
{
Expand Down
6 changes: 3 additions & 3 deletions backend/LexBoxApi/GraphQL/UserMutations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ EmailService emailService
)
{
var user = await dbContext.Users.FindAsync(input.UserId);
if (user is null) throw new NotFoundException("User not found");
NotFoundException.ThrowIfNull(user);

if (!input.Name.IsNullOrEmpty())
{
Expand Down Expand Up @@ -130,7 +130,7 @@ public async Task<User> DeleteUserByAdminOrSelf(DeleteUserByAdminOrSelfInput inp
{
permissionService.AssertCanDeleteAccount(input.UserId);
var user = await dbContext.Users.FindAsync(input.UserId);
if (user is null) throw new NotFoundException("User not found");
NotFoundException.ThrowIfNull(user);
dbContext.Users.Remove(user);
await dbContext.SaveChangesAsync();
return user;
Expand All @@ -145,7 +145,7 @@ public async Task<User> SetUserLocked(SetUserLockedInput input, LexBoxDbContext
{
permissionService.AssertCanLockOrUnlockUser(input.UserId);
var user = await dbContext.Users.FindAsync(input.UserId);
if (user is null) throw new NotFoundException("User not found");
NotFoundException.ThrowIfNull(user);
user.Locked = input.Locked;
user.UpdateUpdatedDate();
await dbContext.SaveChangesAsync();
Expand Down
Loading

0 comments on commit 1c60e0b

Please sign in to comment.