Allow to join an open group

Also:

* Refactor interacting with a remote event so that you can interact with
  a remote group as well
* Add a setting for group admins to pick between an open and invite-only
  group
* Fix new groups without posts/todos/resources/events/conversations URL
  set
* Repair local groups that haven't got their
  posts/todos/resources/events/conversations URL set
* Add a scheduled job to refresh remote groups every hour
* Add a user setting to pick when to receive notifications when there's
  new members to approve (will be used when this feature is available)
* Fix pagination for members

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-11-06 11:34:32 +01:00
parent 7baad7cafc
commit 7c11807c14
74 changed files with 1174 additions and 626 deletions

View File

@@ -42,7 +42,7 @@ export class Actor implements IActor {
type: ActorType = ActorType.PERSON;
constructor(hash: IActor | {} = {}) {
constructor(hash: IActor | Record<any, unknown> = {}) {
Object.assign(this, hash);
}

View File

@@ -18,13 +18,10 @@ export enum MemberRole {
REJECTED = "REJECTED",
}
export interface IGroup extends IActor {
members: Paginate<IMember>;
resources: Paginate<IResource>;
todoLists: Paginate<ITodoList>;
discussions: Paginate<IDiscussion>;
organizedEvents: Paginate<IEvent>;
physicalAddress: IAddress;
export enum Openness {
INVITE_ONLY = "INVITE_ONLY",
MODERATED = "MODERATED",
OPEN = "OPEN",
}
export interface IMember {
@@ -37,6 +34,16 @@ export interface IMember {
updatedAt: string;
}
export interface IGroup extends IActor {
members: Paginate<IMember>;
resources: Paginate<IResource>;
todoLists: Paginate<ITodoList>;
discussions: Paginate<IDiscussion>;
organizedEvents: Paginate<IEvent>;
physicalAddress: IAddress;
openness: Openness;
}
export class Group extends Actor implements IGroup {
members: Paginate<IMember> = { elements: [], total: 0 };
@@ -50,16 +57,18 @@ export class Group extends Actor implements IGroup {
posts: Paginate<IPost> = { elements: [], total: 0 };
constructor(hash: IGroup | {} = {}) {
constructor(hash: IGroup | Record<string, unknown> = {}) {
super(hash);
this.type = ActorType.GROUP;
this.patch(hash);
}
openness: Openness = Openness.INVITE_ONLY;
physicalAddress: IAddress = new Address();
patch(hash: any) {
patch(hash: IGroup | Record<string, unknown>): void {
Object.assign(this, hash);
}
}

View File

@@ -1,8 +1,9 @@
import { ICurrentUser } from "../current-user.model";
import { IEvent, IParticipant } from "../event.model";
import { IEvent } from "../event.model";
import { Actor, IActor } from "./actor.model";
import { Paginate } from "../paginate";
import { IMember } from "./group.model";
import { IParticipant } from "../participant.model";
export interface IFeedToken {
token: string;
@@ -29,13 +30,13 @@ export class Person extends Actor implements IPerson {
user!: ICurrentUser;
constructor(hash: IPerson | {} = {}) {
constructor(hash: IPerson | Record<string, unknown> = {}) {
super(hash);
this.patch(hash);
}
patch(hash: any) {
patch(hash: IPerson | Record<string, unknown>): void {
Object.assign(this, hash);
}
}

View File

@@ -1,6 +1,7 @@
import { IEvent, IParticipant } from "@/types/event.model";
import { IEvent } from "@/types/event.model";
import { IPerson } from "@/types/actor/person.model";
import { Paginate } from "./paginate";
import { IParticipant } from "./participant.model";
export enum ICurrentUserRole {
USER = "USER",
@@ -16,7 +17,7 @@ export interface ICurrentUser {
defaultActor?: IPerson;
}
export enum INotificationPendingParticipationEnum {
export enum INotificationPendingEnum {
NONE = "NONE",
DIRECT = "DIRECT",
ONE_DAY = "ONE_DAY",
@@ -28,7 +29,8 @@ export interface IUserSettings {
notificationOnDay: boolean;
notificationEachWeek: boolean;
notificationBeforeEvent: boolean;
notificationPendingParticipation: INotificationPendingParticipationEnum;
notificationPendingParticipation: INotificationPendingEnum;
notificationPendingMembership: INotificationPendingEnum;
}
export interface IUser extends ICurrentUser {

View File

@@ -0,0 +1,61 @@
export interface IParticipationCondition {
title: string;
content: string;
url: string;
}
export interface IOffer {
price: number;
priceCurrency: string;
url: string;
}
export enum CommentModeration {
ALLOW_ALL = "ALLOW_ALL",
MODERATED = "MODERATED",
CLOSED = "CLOSED",
}
export interface IEventOptions {
maximumAttendeeCapacity: number;
remainingAttendeeCapacity: number;
showRemainingAttendeeCapacity: boolean;
anonymousParticipation: boolean;
hideOrganizerWhenGroupEvent: boolean;
offers: IOffer[];
participationConditions: IParticipationCondition[];
attendees: string[];
program: string;
commentModeration: CommentModeration;
showParticipationPrice: boolean;
showStartTime: boolean;
showEndTime: boolean;
}
export class EventOptions implements IEventOptions {
maximumAttendeeCapacity = 0;
remainingAttendeeCapacity = 0;
showRemainingAttendeeCapacity = false;
anonymousParticipation = false;
hideOrganizerWhenGroupEvent = false;
offers: IOffer[] = [];
participationConditions: IParticipationCondition[] = [];
attendees: string[] = [];
program = "";
commentModeration = CommentModeration.ALLOW_ALL;
showParticipationPrice = false;
showStartTime = true;
showEndTime = true;
}

View File

@@ -3,7 +3,9 @@ import { ITag } from "@/types/tag.model";
import { IPicture } from "@/types/picture.model";
import { IComment } from "@/types/comment.model";
import { Paginate } from "@/types/paginate";
import { Actor, Group, IActor, IPerson } from "./actor";
import { Actor, Group, IActor, IGroup, IPerson } from "./actor";
import { IParticipant } from "./participant.model";
import { EventOptions, IEventOptions } from "./event-options.model";
export enum EventStatus {
TENTATIVE = "TENTATIVE",
@@ -30,16 +32,6 @@ export enum EventVisibilityJoinOptions {
LIMITED = "LIMITED",
}
export enum ParticipantRole {
NOT_APPROVED = "NOT_APPROVED",
NOT_CONFIRMED = "NOT_CONFIRMED",
REJECTED = "REJECTED",
PARTICIPANT = "PARTICIPANT",
MODERATOR = "MODERATOR",
ADMINISTRATOR = "ADMINISTRATOR",
CREATOR = "CREATOR",
}
export enum Category {
BUSINESS = "business",
CONFERENCE = "conference",
@@ -56,58 +48,6 @@ export interface IEventCardOptions {
memberofGroup: boolean;
}
export interface IParticipant {
id?: string;
role: ParticipantRole;
actor: IActor;
event: IEvent;
metadata: { cancellationToken?: string; message?: string };
insertedAt?: Date;
}
export class Participant implements IParticipant {
id?: string;
event!: IEvent;
actor!: IActor;
role: ParticipantRole = ParticipantRole.NOT_APPROVED;
metadata = {};
insertedAt?: Date;
constructor(hash?: IParticipant) {
if (!hash) return;
this.id = hash.id;
this.event = new EventModel(hash.event);
this.actor = new Actor(hash.actor);
this.role = hash.role;
this.metadata = hash.metadata;
this.insertedAt = hash.insertedAt;
}
}
export interface IOffer {
price: number;
priceCurrency: string;
url: string;
}
export interface IParticipationCondition {
title: string;
content: string;
url: string;
}
export enum CommentModeration {
ALLOW_ALL = "ALLOW_ALL",
MODERATED = "MODERATED",
CLOSED = "CLOSED",
}
export interface IEventParticipantStats {
notApproved: number;
notConfirmed: number;
@@ -119,6 +59,26 @@ export interface IEventParticipantStats {
going: number;
}
interface IEventEditJSON {
id?: string;
title: string;
description: string;
beginsOn: string;
endsOn: string | null;
status: EventStatus;
visibility: EventVisibility;
joinOptions: EventJoinOptions;
draft: boolean;
picture: IPicture | { pictureId: string } | null;
attributedToId: string | null;
onlineAddress?: string;
phoneAddress?: string;
physicalAddress?: IAddress;
tags: string[];
options: IEventOptions;
contacts: { id?: string }[];
}
export interface IEvent {
id?: string;
uuid: string;
@@ -139,7 +99,7 @@ export interface IEvent {
picture: IPicture | null;
organizerActor?: IActor;
attributedTo?: IActor;
attributedTo?: IGroup;
participantStats: IEventParticipantStats;
participants: Paginate<IParticipant>;
@@ -157,50 +117,6 @@ export interface IEvent {
toEditJSON(): IEventEditJSON;
}
export interface IEventOptions {
maximumAttendeeCapacity: number;
remainingAttendeeCapacity: number;
showRemainingAttendeeCapacity: boolean;
anonymousParticipation: boolean;
hideOrganizerWhenGroupEvent: boolean;
offers: IOffer[];
participationConditions: IParticipationCondition[];
attendees: string[];
program: string;
commentModeration: CommentModeration;
showParticipationPrice: boolean;
showStartTime: boolean;
showEndTime: boolean;
}
export class EventOptions implements IEventOptions {
maximumAttendeeCapacity = 0;
remainingAttendeeCapacity = 0;
showRemainingAttendeeCapacity = false;
anonymousParticipation = false;
hideOrganizerWhenGroupEvent = false;
offers: IOffer[] = [];
participationConditions: IParticipationCondition[] = [];
attendees: string[] = [];
program = "";
commentModeration = CommentModeration.ALLOW_ALL;
showParticipationPrice = false;
showStartTime = true;
showEndTime = true;
}
export class EventModel implements IEvent {
id?: string;
@@ -255,7 +171,7 @@ export class EventModel implements IEvent {
comments: IComment[] = [];
attributedTo?: IActor = new Actor();
attributedTo?: IGroup = new Group();
organizerActor?: IActor = new Actor();
@@ -323,7 +239,6 @@ export class EventModel implements IEvent {
phoneAddress: this.phoneAddress,
physicalAddress: this.physicalAddress,
options: this.options,
// organizerActorId: this.organizerActor && this.organizerActor.id ? this.organizerActor.id : null,
attributedToId: this.attributedTo && this.attributedTo.id ? this.attributedTo.id : null,
contacts: this.contacts.map(({ id }) => ({
id,
@@ -331,23 +246,3 @@ export class EventModel implements IEvent {
};
}
}
interface IEventEditJSON {
id?: string;
title: string;
description: string;
beginsOn: string;
endsOn: string | null;
status: EventStatus;
visibility: EventVisibility;
joinOptions: EventJoinOptions;
draft: boolean;
picture: IPicture | { pictureId: string } | null;
attributedToId: string | null;
onlineAddress?: string;
phoneAddress?: string;
physicalAddress?: IAddress;
tags: string[];
options: IEventOptions;
contacts: { id?: string }[];
}

View File

@@ -0,0 +1,46 @@
import { Actor, IActor } from "./actor";
import { EventModel, IEvent } from "./event.model";
export enum ParticipantRole {
NOT_APPROVED = "NOT_APPROVED",
NOT_CONFIRMED = "NOT_CONFIRMED",
REJECTED = "REJECTED",
PARTICIPANT = "PARTICIPANT",
MODERATOR = "MODERATOR",
ADMINISTRATOR = "ADMINISTRATOR",
CREATOR = "CREATOR",
}
export interface IParticipant {
id?: string;
role: ParticipantRole;
actor: IActor;
event: IEvent;
metadata: { cancellationToken?: string; message?: string };
insertedAt?: Date;
}
export class Participant implements IParticipant {
id?: string;
event!: IEvent;
actor!: IActor;
role: ParticipantRole = ParticipantRole.NOT_APPROVED;
metadata = {};
insertedAt?: Date;
constructor(hash?: IParticipant) {
if (!hash) return;
this.id = hash.id;
this.event = new EventModel(hash.event);
this.actor = new Actor(hash.actor);
this.role = hash.role;
this.metadata = hash.metadata;
this.insertedAt = hash.insertedAt;
}
}