Skip to content

Commit

Permalink
Merge branch 'refs/heads/develop' into lf-next
Browse files Browse the repository at this point in the history
# Conflicts:
#	frontend/pnpm-lock.yaml
  • Loading branch information
hahn-kev committed Apr 8, 2024
2 parents 3960f15 + 1d64238 commit cb4d3bb
Show file tree
Hide file tree
Showing 15 changed files with 167 additions and 91 deletions.
53 changes: 53 additions & 0 deletions .github/workflows/deploy-branch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Deploy to Develop
run-name: Deploy ${{ github.ref_name }} to Develop
on:
workflow_dispatch:


jobs:
set-version:
name: Set Version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.setVersion.outputs.VERSION }}
steps:
- name: Set Version
id: setVersion
# set version to date in vYYYY-MM-DD-commitSha format
run: |
shortSha=$(echo ${{ github.sha }} | cut -c1-8)
echo "VERSION=v$(date --rfc-3339=date)-$shortSha" >> ${GITHUB_OUTPUT}
build-api:
name: Build API
needs: [ set-version ]
uses: ./.github/workflows/lexbox-api.yaml
with:
version: ${{ needs.set-version.outputs.version }}
label-latest: true

build-ui:
name: Build UI
needs: [ set-version ]
uses: ./.github/workflows/lexbox-ui.yaml
with:
version: ${{ needs.set-version.outputs.version }}
label-latest: true

build-hgweb:
name: Build hgweb
needs: [ set-version ]
uses: ./.github/workflows/lexbox-hgweb.yaml
with:
version: ${{ needs.set-version.outputs.version }}
label-latest: true

deploy:
name: Deploy Develop
uses: ./.github/workflows/deploy.yaml
needs: [ build-api, build-ui, build-hgweb, set-version ]
secrets: inherit
with:
version: ${{ needs.set-version.outputs.version }}
image: 'ghcr.io/sillsdev/lexbox-*'
k8s-environment: develop
deploy-domain: lexbox.dev.languagetechnology.org
19 changes: 14 additions & 5 deletions backend/LexBoxApi/GraphQL/ProjectMutations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ public record CreateProjectResponse(Guid? Id, CreateProjectResult Result);
[Error<NotFoundException>]
[Error<DbError>]
[Error<ProjectMembersMustBeVerified>]
[Error<ProjectMembersMustBeVerifiedForRole>]
[Error<ProjectMemberInvitedByEmail>]
[Error<AlreadyExistsException>]
[UseMutationConvention]
[UseFirstOrDefault]
[UseProjection]
Expand All @@ -74,14 +76,20 @@ 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");
var user = await dbContext.Users.FindByEmailOrUsername(input.UserEmail);
if (user is null)
var user = await dbContext.Users.Include(u => u.Projects).FindByEmailOrUsername(input.UsernameOrEmail);
if (user is null && input.UsernameOrEmail.Contains('@'))
{
var manager = loggedInContext.User;
await emailService.SendCreateAccountEmail(input.UserEmail, input.ProjectId, input.Role, manager.Name, project.Name);
await emailService.SendCreateAccountEmail(input.UsernameOrEmail, input.ProjectId, input.Role, manager.Name, project.Name);
throw new ProjectMemberInvitedByEmail("Invitation email sent");
}
if (!user.HasVerifiedEmailForRole(input.Role)) throw new ProjectMembersMustBeVerified("Member must verify email first");
if (user is null) throw new NotFoundException("User not found");
if (user.Projects.Any(p => p.ProjectId == input.ProjectId))
{
throw new AlreadyExistsException("User is already a member of this project");
}

user.AssertHasVerifiedEmailForRole(input.Role);
user.UpdateCreateProjectsPermission(input.Role);
dbContext.ProjectUsers.Add(
new ProjectUsers { Role = input.Role, ProjectId = input.ProjectId, UserId = user.Id });
Expand Down Expand Up @@ -162,6 +170,7 @@ public async Task<BulkAddProjectMembersResult> BulkAddProjectMembers(
[Error<NotFoundException>]
[Error<DbError>]
[Error<ProjectMembersMustBeVerified>]
[Error<ProjectMembersMustBeVerifiedForRole>]
[UseMutationConvention]
[UseFirstOrDefault]
[UseProjection]
Expand All @@ -175,7 +184,7 @@ public async Task<IQueryable<ProjectUsers>> ChangeProjectMemberRole(
await dbContext.ProjectUsers.Include(r => r.Project).Include(r => r.User).FirstOrDefaultAsync(u =>
u.ProjectId == input.ProjectId && u.UserId == input.UserId);
if (projectUser is null) throw new NotFoundException("Project member not found");
if (!projectUser.User.HasVerifiedEmailForRole(input.Role)) throw new ProjectMembersMustBeVerified("Member must verify email first");
projectUser.User.AssertHasVerifiedEmailForRole(input.Role);
projectUser.Role = input.Role;
projectUser.User.UpdateCreateProjectsPermission(input.Role);
projectUser.User.UpdateUpdatedDate();
Expand Down
2 changes: 1 addition & 1 deletion backend/LexBoxApi/Models/Project/ProjectMemberInputs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace LexBoxApi.Models.Project;

public record AddProjectMemberInput(Guid ProjectId, [property: EmailAddress] string UserEmail, ProjectRole Role);
public record AddProjectMemberInput(Guid ProjectId, string UsernameOrEmail, ProjectRole Role);

public record BulkAddProjectMembersInput(Guid ProjectId, string[] Usernames, ProjectRole Role, string PasswordHash);

Expand Down
11 changes: 11 additions & 0 deletions backend/LexCore/Exceptions/ProjectMembersMustBeVerifiedForRole.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using LexCore.Entities;

namespace LexCore.Exceptions;

public class ProjectMembersMustBeVerifiedForRole : Exception
{
public ProjectMembersMustBeVerifiedForRole(string message, ProjectRole role) : base(message)
{
Data["role"] = role;
}
}
15 changes: 9 additions & 6 deletions backend/LexData/Entities/UserEntityConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Linq.Expressions;
using LexCore.Entities;
using LexCore.Exceptions;
using LexData.Configuration;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
Expand Down Expand Up @@ -45,12 +46,14 @@ public static IQueryable<User> FilterByEmailOrUsername(this IQueryable<User> use
return await users.FilterByEmailOrUsername(emailOrUsername).FirstOrDefaultAsync();
}

public static bool HasVerifiedEmailForRole(this User user, ProjectRole forRole = ProjectRole.Unknown)
public static void AssertHasVerifiedEmailForRole(this User user, ProjectRole forRole = ProjectRole.Unknown)
{
// Users bulk-created by admins might not have email addresses, and that's okay
// BUT if they are to be project managers, they must have verified email addresses
if (forRole == ProjectRole.Editor && user.CreatedById is not null) return true;
// Otherwise, we can simply use the EmailVerified property
return user.Email is not null && user.EmailVerified;
// Users with verified emails are the most common case, so check that first
if (user.Email is not null && user.EmailVerified) return;
// Users bulk-created by admins might not have email addresses
// Users who self-registered must verify email in all cases
if (user.CreatedById is null) throw new ProjectMembersMustBeVerified("Member must verify email first");
// Only project editors (basic role) are allowed not to have verified email addresses
if (forRole != ProjectRole.Editor) throw new ProjectMembersMustBeVerifiedForRole("Member must verify email before taking on this role", forRole);
}
}
29 changes: 0 additions & 29 deletions deployment/base/hg-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -131,32 +131,3 @@ spec:
items:
- key: hgweb.hgrc
path: hgweb.hgrc

initContainers:
- name: init-repo-structure
securityContext:
runAsUser: 33
runAsGroup: 33 # www-data
image: busybox:1.36.1
command:
- 'sh'
- '-c'
- |
cd /repos
mkdir -p a b c d e f g h i j k l m n o p q r s t u v w x y z
mkdir -p 0 1 2 3 4 5 6 7 8 9
find . -maxdepth 1 -type d -name '[a-z0-9]?*' | while IFS= read -r folder; do
project=$(basename "$folder")
if [[ -L "$project" ]] || [[ "$project" == "lost+found" ]]; then
echo "Skipping folder: $project"
continue
fi
first_letter=$(echo "$project" | head -c1)
mv "$folder" ./"$first_letter"/
# need to use relative path for symlink to work in multiple pods
ln -s "$first_letter"/"$project" "$project"
done
volumeMounts:
- name: repos
mountPath: /repos
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@
"tslib": "^2.6.2",
"type-fest": "^4.10.2",
"typescript": "^5.3.3",
"vite": "^5.0.12",
"vite": "^5.0.13",
"vite-plugin-graphql-codegen": "^3.3.6",
"vitest": "^1.2.2",
"viewer": "workspace:*",
Expand Down
54 changes: 27 additions & 27 deletions frontend/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit cb4d3bb

Please sign in to comment.