Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inviting unregistered users by email #6901

Merged
merged 71 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
a902db3
draft invitation
klakhov Sep 26, 2023
6c8e5fb
sendmail on invite
klakhov Sep 26, 2023
55c47e7
updated message
klakhov Sep 26, 2023
705f9f1
added basic inv serializer, fixed membership creation
klakhov Sep 26, 2023
5b519db
removed lots of requests for invitations
klakhov Sep 26, 2023
5a3bb43
update invitaiton link
klakhov Sep 26, 2023
ebd98b9
added register from invitation page
klakhov Sep 26, 2023
464b814
added accept invitation form
klakhov Sep 26, 2023
499c187
added mock server accept endpoint
klakhov Sep 26, 2023
768b9a8
added sent date field
klakhov Sep 27, 2023
a94e247
updated UI register data type
klakhov Sep 27, 2023
f3aa0e4
added user setup on accept
klakhov Sep 27, 2023
4cbf36a
updated invitation messages
klakhov Sep 27, 2023
7347746
refactore accept endpoint
klakhov Sep 27, 2023
4b63507
returned auto accept for existing users
klakhov Sep 27, 2023
1d97f80
added resend endpoint
klakhov Sep 27, 2023
2ba490f
updated invitation settings
klakhov Sep 27, 2023
f65494a
activate organization after invite on login
klakhov Sep 28, 2023
f3b1f41
typed ui membership and invitation
klakhov Sep 28, 2023
5a119c6
added resend/delete cvat-core methods
klakhov Sep 28, 2023
8735802
added invitation resend/delete actions
klakhov Sep 28, 2023
b27c315
added resend, delete buttons
klakhov Sep 28, 2023
b932980
delete membeship with invitation
klakhov Sep 28, 2023
9881a41
Merge branch 'develop' into kl/invite-users
klakhov Sep 29, 2023
945fcff
added ui error handling
klakhov Sep 29, 2023
1f9761b
added info notification
klakhov Sep 29, 2023
9e576b3
removed comment
klakhov Sep 29, 2023
d7ed980
added checks
klakhov Sep 29, 2023
a57fed7
removed excessive file
klakhov Oct 2, 2023
1ba5703
Merge branch 'develop' into kl/invite-users
klakhov Oct 2, 2023
3d5e38a
updated schema
klakhov Oct 2, 2023
4e14ed2
fixed https link
klakhov Oct 2, 2023
8b6ac0b
updated email template
klakhov Oct 2, 2023
dc37453
added comma
klakhov Oct 2, 2023
d46c9f6
updated setting name
klakhov Oct 2, 2023
b804641
updated email subject
klakhov Oct 2, 2023
2e4a396
removed valiidate username
klakhov Oct 2, 2023
89c3c62
added success info message
klakhov Oct 2, 2023
d8f88f2
updated setting
klakhov Oct 2, 2023
a41d969
removed excessive check
klakhov Oct 2, 2023
c03f114
changed action button
klakhov Oct 2, 2023
be249c6
added transaction
klakhov Oct 2, 2023
1fa4834
updated imports
klakhov Oct 2, 2023
8dec73f
updated schema
klakhov Oct 2, 2023
6f82fea
throw explicit error if email backend is not configured
klakhov Oct 5, 2023
c0e05f0
removed rawuser, keep only serialized user
klakhov Oct 5, 2023
89aef3e
fixed memberships results
klakhov Oct 5, 2023
19dce0d
moved accept invitation to oranization namespace
klakhov Oct 5, 2023
9b56f05
updated email template
klakhov Oct 5, 2023
11114ce
removed delete invitation
klakhov Oct 5, 2023
74a9a73
fixed typo
klakhov Oct 5, 2023
92d0e70
renamed disable navigation
klakhov Oct 5, 2023
e0bedaf
removed accept organization param
klakhov Oct 5, 2023
93a818b
fixed tag in template
klakhov Oct 5, 2023
74ddfdc
fixed owner null
klakhov Oct 5, 2023
cb6bbaf
updated schema
klakhov Oct 6, 2023
aec2763
updated tests db
klakhov Oct 6, 2023
0910c8d
fixed schema version
klakhov Oct 6, 2023
c720b57
Merge branch 'develop' into kl/invite-users
klakhov Oct 9, 2023
faf4ace
updated package versions & changelog
klakhov Oct 9, 2023
47343de
added newline
klakhov Oct 9, 2023
7f5667c
Merge branch 'develop' into kl/invite-users
nmanovic Oct 9, 2023
cd86e3a
updated accept invitation serializer
klakhov Oct 9, 2023
2acb932
updated status code & fixed typo
klakhov Oct 9, 2023
c30588f
reverted invitation field
klakhov Oct 10, 2023
0627576
return object instead of string
klakhov Oct 10, 2023
852bb54
updated ui to object response
klakhov Oct 10, 2023
8676f6e
Merge branch 'develop' into kl/invite-users
klakhov Oct 10, 2023
eeea945
reverted test assets
klakhov Oct 10, 2023
5b59af8
Merge branch 'develop' into kl/invite-users
klakhov Oct 11, 2023
2814477
Merge branch 'develop' into kl/invite-users
nmanovic Oct 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions cvat-core/src/api-implementation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,28 @@ export default function implementAPI(cvat) {
};
};

cvat.organizations.acceptInvitation.implementation = async (
username,
firstName,
lastName,
email,
password,
userConfirmations,
key,
) => {
const orgSlug = await serverProxy.organizations.acceptInvitation(
username,
firstName,
lastName,
email,
password,
userConfirmations,
key,
);

return orgSlug;
};

cvat.webhooks.get.implementation = async (filter) => {
checkFilter(filter, {
page: isInteger,
Expand Down
13 changes: 13 additions & 0 deletions cvat-core/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,19 @@ function build() {
const result = await PluginRegistry.apiWrapper(cvat.organizations.deactivate);
return result;
},
async acceptInvitation(username, firstName, lastName, email, password, userConfirmations, key) {
const result = await PluginRegistry.apiWrapper(
cvat.organizations.acceptInvitation,
username,
firstName,
lastName,
email,
password,
userConfirmations,
key,
);
return result;
},
},
webhooks: {
async get(filter: any) {
Expand Down
128 changes: 101 additions & 27 deletions cvat-core/src/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//
// SPDX-License-Identifier: MIT

import { SerializedOrganization, SerializedOrganizationContact } from './server-response-types';
import { SerializedOrganization, SerializedOrganizationContact, SerializedUser } from './server-response-types';
import { checkObjectType, isEnum } from './common';
import config from './config';
import { MembershipRole } from './enums';
Expand All @@ -12,15 +12,82 @@ import PluginRegistry from './plugins';
import serverProxy from './server-proxy';
import User from './user';

interface Membership {
user: User;
interface SerializedInvitationData {
created_date: string;
key: string;
owner: SerializedUser;
}

interface SerializedMembershipData {
id: number;
user: SerializedUser;
is_active: boolean;
joined_date: string;
role: MembershipRole;
invitation: {
created_date: string;
owner: User;
} | null;
invitation: SerializedInvitationData | null;
}

export class Invitation {
#createdDate: string;
#owner: User;
#key: string;

constructor(initialData: SerializedInvitationData) {
this.#createdDate = initialData.created_date;
this.#owner = new User(initialData.owner);
this.#key = initialData.key;
}

get owner(): User {
return this.#owner;
}

get createdDate(): string {
return this.#createdDate;
}

get key(): string {
return this.#key;
}
}

export class Membership {
#id: number;
#user: User;
#isActive: boolean;
#joinedDate: string;
#role: MembershipRole;
#invitation: Invitation | null;

constructor(initialData: SerializedMembershipData) {
this.#id = initialData.id;
this.#user = new User(initialData.user);
this.#isActive = initialData.is_active;
this.#joinedDate = initialData.joined_date;
this.#role = initialData.role;
this.#invitation = initialData.invitation ? new Invitation(initialData.invitation) : null;
}

get id(): number {
return this.#id;
}

get user(): User {
return this.#user;
}

get isActive(): boolean {
return this.#isActive;
}
get joinedDate(): string {
return this.#joinedDate;
}
get role(): MembershipRole {
return this.#role;
}
get invitation(): Invitation {
return this.#invitation;
}
}

export default class Organization {
Expand Down Expand Up @@ -193,6 +260,15 @@ export default class Organization {
);
return result;
}

public async resendInvitation(key: string): Promise<void> {
const result = await PluginRegistry.apiWrapper.call(
this,
Organization.prototype.resendInvitation,
key,
);
return result;
}
}

Object.defineProperties(Organization.prototype.save, {
Expand Down Expand Up @@ -234,27 +310,12 @@ Object.defineProperties(Organization.prototype.members, {
checkObjectType('pageSize', pageSize, 'number');

const result = await serverProxy.organizations.members(orgSlug, page, pageSize);
await Promise.all(
result.results.map((membership) => {
const { invitation } = membership;
membership.user = new User(membership.user);
if (invitation) {
return serverProxy.organizations
.invitation(invitation)
.then((invitationData) => {
membership.invitation = invitationData;
})
.catch(() => {
membership.invitation = null;
});
}
const memeberships = result.results.map((rawMembership: SerializedMembershipData) => new Membership(
rawMembership,
));

return Promise.resolve();
}),
);

result.results.count = result.count;
return result.results;
memeberships.count = result.count;
return memeberships;
},
},
});
Expand Down Expand Up @@ -347,3 +408,16 @@ Object.defineProperties(Organization.prototype.leave, {
},
},
});

Object.defineProperties(Organization.prototype.resendInvitation, {
implementation: {
writable: false,
enumerable: false,
value: async function implementation(key: string) {
checkObjectType('key', key, 'string');
if (typeof this.id === 'number') {
await serverProxy.organizations.resendInvitation(key);
}
},
},
});
38 changes: 38 additions & 0 deletions cvat-core/src/server-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,33 @@ async function resetPassword(newPassword1: string, newPassword2: string, uid: st
}
}

async function acceptOrganizationInvitation(
username: string,
firstName: string,
lastName: string,
email: string,
password: string,
confirmations: Record<string, string>,
key: string,
): Promise<SerializedRegister> {
let response = null;

try {
response = await Axios.post(`${config.backendAPI}/invitations/${key}/accept`, {
username,
first_name: firstName,
last_name: lastName,
password1: password,
password2: password,
confirmations,
});
} catch (errorData) {
throw generateError(errorData);
}

return response.data;
}

async function getSelf(): Promise<SerializedUser> {
const { backendAPI } = config;

Expand Down Expand Up @@ -2059,6 +2086,15 @@ async function inviteOrganizationMembers(orgId, data) {
}
}

async function resendOrganizationInvitation(key) {
const { backendAPI } = config;
try {
await Axios.post(`${backendAPI}/invitations/${key}/resend`);
} catch (errorData) {
throw generateError(errorData);
}
}

async function updateOrganizationMembership(membershipId, data) {
const { backendAPI } = config;
let response = null;
Expand Down Expand Up @@ -2481,8 +2517,10 @@ export default Object.freeze({
invitation: getMembershipInvitation,
delete: deleteOrganization,
invite: inviteOrganizationMembers,
resendInvitation: resendOrganizationInvitation,
updateMembership: updateOrganizationMembership,
deleteMembership: deleteOrganizationMembership,
acceptInvitation: acceptOrganizationInvitation,
}),

webhooks: Object.freeze({
Expand Down
1 change: 1 addition & 0 deletions cvat-core/src/server-response-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface SerializedUser {
is_active?: boolean;
last_login?: string;
date_joined?: string;
email_verification_required: boolean;
}

export interface SerializedProject {
Expand Down
21 changes: 4 additions & 17 deletions cvat-core/src/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,23 @@
//
// SPDX-License-Identifier: MIT

interface RawUserData {
id: number;
username: string;
email: string;
first_name: string;
last_name: string;
groups: string[];
last_login: string;
date_joined: string;
is_staff: boolean;
is_superuser: boolean;
is_active: boolean;
email_verification_required: boolean;
}
import { SerializedUser } from './server-response-types';

export default class User {
public readonly id: number;
public readonly username: string;
public readonly email: string;
public readonly firstName: string;
public readonly lastName: string;
public readonly groups: string[];
public readonly groups: ('user' | 'business' | 'admin')[];
public readonly lastLogin: string;
public readonly dateJoined: string;
public readonly isStaff: boolean;
public readonly isSuperuser: boolean;
public readonly isActive: boolean;
public readonly isVerified: boolean;

constructor(initialData: RawUserData) {
constructor(initialData: SerializedUser) {
const data = {
id: null,
username: null,
Expand Down Expand Up @@ -97,7 +84,7 @@ export default class User {
);
}

serialize(): RawUserData {
serialize(): Partial<SerializedUser> {
return {
id: this.id,
username: this.username,
Expand Down
Loading