types#NotificationCategories TypeScript Examples
The following examples show how to use
types#NotificationCategories.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: notifications.ts From commonwealth with GNU General Public License v3.0 | 6 votes |
sortNotifications = (unsortedNotifications: Notification[]) => {
// The .sort method here is crucial; the sortNotifications fn only works when
// notifications are sorted in advance by createdAt
const n = unsortedNotifications.sort((a, b) => b.createdAt.unix() - a.createdAt.unix());
const batched = batchNotifications(n, 'subscription', 'objectId');
const unbatchChainEvents = [];
batched.forEach((a: Notification[]) => {
if (a[0].chainEvent
// unbatch chain-events, comments, and mentions
|| a[0].subscription.category === NotificationCategories.NewComment
|| a[0].subscription.category === NotificationCategories.NewMention
|| a[0].subscription.category === NotificationCategories.NewCollaboration
) {
a.forEach((n2) => unbatchChainEvents.push([n2]));
} else if (!a[0].isRead) {
// unbatch unread notifications.
const b: Notification[] = [];
a.forEach((n2) => {
if (n2.isRead) {
b.push(n2);
} else {
unbatchChainEvents.push([n2]);
}
});
if (b.length) unbatchChainEvents.push(b);
} else {
// don't unbatch at all
if (a.length) unbatchChainEvents.push(a);
}
});
unbatchChainEvents.sort((a, b) => {
return a[0].createdAt - b[0].createdAt;
});
return unbatchChainEvents;
}
Example #2
Source File: subscription_button.tsx From commonwealth with GNU General Public License v3.0 | 6 votes |
view() {
const subscriptions = app.user.notifications;
const communitySubscription = subscriptions.subscriptions.find(
(v) =>
v.category === NotificationCategories.NewThread &&
v.objectId === app.activeChainId()
);
const communityOrChain = app.activeChainId();
return (
<CWButton
onclick={(e) => {
e.preventDefault();
if (isNotUndefined(communitySubscription)) {
subscriptions.deleteSubscription(communitySubscription).then(() => {
m.redraw();
});
} else {
subscriptions
.subscribe(NotificationCategories.NewThread, communityOrChain)
.then(() => {
m.redraw();
});
}
}}
label={
isNotUndefined(communitySubscription)
? 'Notifications on'
: 'Notifications off'
}
buttonType={
isNotUndefined(communitySubscription)
? 'primary-blue'
: 'secondary-blue'
}
/>
);
}
Example #3
Source File: user_dashboard_row.ts From commonwealth with GNU General Public License v3.0 | 6 votes |
subscribeToThread = async (
threadId: string,
bothActive: boolean,
commentSubscription: NotificationSubscription,
reactionSubscription: NotificationSubscription
) => {
const adjustedId = `discussion_${threadId}`;
if (bothActive) {
await app.user.notifications.disableSubscriptions([
commentSubscription,
reactionSubscription,
]);
notifySuccess('Unsubscribed!');
} else if (!commentSubscription || !reactionSubscription) {
await Promise.all([
app.user.notifications.subscribe(
NotificationCategories.NewReaction,
adjustedId
),
app.user.notifications.subscribe(
NotificationCategories.NewComment,
adjustedId
),
]);
notifySuccess('Subscribed!');
} else {
await app.user.notifications.enableSubscriptions([
commentSubscription,
reactionSubscription,
]);
notifySuccess('Subscribed!');
}
}
Example #4
Source File: notification_settings.ts From commonwealth with GNU General Public License v3.0 | 6 votes |
NewThreadRow: m.Component<{
subscriptions: NotificationSubscription[];
community: ChainInfo;
}> = {
view: (vnode) => {
const { subscriptions, community } = vnode.attrs;
const subscription = subscriptions.find(
(s) =>
s.category === NotificationCategories.NewThread &&
s.objectId === community.id
);
return (
subscription &&
m(BatchedSubscriptionRow, {
subscriptions: [subscription],
label: NEW_THREADS_LABEL,
})
);
},
}
Example #5
Source File: threads.ts From commonwealth with GNU General Public License v3.0 | 5 votes |
public async create(
address: string,
kind: string,
stage: string,
chainId: string,
title: string,
topicName: string,
topicId: number,
body?: string,
url?: string,
attachments?: string[],
readOnly?: boolean
) {
try {
// TODO: Change to POST /thread
const response = await $.post(`${app.serverUrl()}/createThread`, {
author_chain: app.user.activeAccount.chain.id,
author: JSON.stringify(app.user.activeAccount.profile),
chain: chainId,
address,
title: encodeURIComponent(title),
body: encodeURIComponent(body),
kind,
stage,
'attachments[]': attachments,
topic_name: topicName,
topic_id: topicId,
url,
readOnly,
jwt: app.user.jwt,
});
const result = modelFromServer(response.result);
this._store.add(result);
// Update stage counts
if (result.stage === OffchainThreadStage.Voting) this.numVotingThreads++;
// New posts are added to both the topic and allProposals sub-store
this._listingStore.add(result);
const activeEntity = app.chain;
updateLastVisited(activeEntity.meta, true);
// synthesize new subscription rather than hitting backend
const subscriptionJSON = {
id: null,
category_id: NotificationCategories.NewComment,
object_id: `discussion_${result.id}`,
is_active: true,
created_at: Date.now(),
immediate_email: false,
chain_id: result.chain,
offchain_thread_id: result.id,
};
app.user.notifications.subscriptions.push(
NotificationSubscription.fromJSON(subscriptionJSON)
);
return result;
} catch (err) {
console.log('Failed to create thread');
throw new Error(
err.responseJSON && err.responseJSON.error
? err.responseJSON.error
: 'Failed to create thread'
);
}
}
Example #6
Source File: notification_settings.ts From commonwealth with GNU General Public License v3.0 | 5 votes |
AllCommunitiesNotifications: m.Component<
{
subscriptions: NotificationSubscription[];
communities: string[];
},
{
expanded: boolean;
}
> = {
view: (vnode) => {
const { subscriptions, communities } = vnode.attrs;
const mentionsSubscription = subscriptions.find(
(s) => s.category === NotificationCategories.NewMention
);
const collaborationsSubscription = subscriptions.find(
(s) => s.category === NotificationCategories.NewCollaboration
);
const chainIds = app.config.chains.getAll().map((c) => c.id);
const communityIds = communities;
const batchedSubscriptions = sortSubscriptions(
subscriptions.filter((s) => {
return (
!chainIds.includes(s.objectId) &&
s.category !== NotificationCategories.NewMention &&
s.category !== NotificationCategories.NewThread &&
s.category !== NotificationCategories.ChainEvent &&
!s.OffchainComment
);
}),
'objectId'
);
return [
m(BatchedSubscriptionRow, {
subscriptions: subscriptions.filter((s) =>
communityIds.includes(s.objectId)
),
label: NEW_THREADS_LABEL,
}),
mentionsSubscription &&
m(BatchedSubscriptionRow, {
subscriptions: [mentionsSubscription],
label: NEW_MENTIONS_LABEL,
}),
collaborationsSubscription &&
m(BatchedSubscriptionRow, {
subscriptions: [collaborationsSubscription],
label: NEW_COLLABORATIONS_LABEL,
}),
batchedSubscriptions.length > 0 &&
m('tr.NewActivityRow', [m('td', NEW_ACTIVITY_LABEL), m('td')]),
vnode.state.expanded &&
batchedSubscriptions.map(
(subscriptions2: NotificationSubscription[]) => {
return m(BatchedSubscriptionRow, { subscriptions: subscriptions2 });
}
),
batchedSubscriptions.length > 0 &&
m('tr', [
m('td', { colspan: 2 }, [
m(
'a.expand-notifications-link',
{
href: '#',
onclick: (e) => {
e.preventDefault();
vnode.state.expanded = !vnode.state.expanded;
},
},
[
`${vnode.state.expanded ? 'Hide' : 'Show'} ${pluralize(
batchedSubscriptions.length,
'thread'
)}`,
]
),
]),
]),
];
},
}
Example #7
Source File: notification_settings.ts From commonwealth with GNU General Public License v3.0 | 5 votes |
IndividualCommunityNotifications: m.Component<
{
community: ChainInfo;
subscriptions: NotificationSubscription[];
},
{
expanded: boolean;
}
> = {
view: (vnode) => {
const { community, subscriptions } = vnode.attrs;
const filteredSubscriptions = subscriptions.filter(
(s) =>
s.Chain === community.id &&
s.category !== NotificationCategories.NewThread &&
s.category !== NotificationCategories.NewMention &&
s.category !== NotificationCategories.NewCollaboration &&
s.category !== NotificationCategories.ChainEvent &&
!s.OffchainComment
);
const newThreads = subscriptions.find(
(s) =>
s.category === NotificationCategories.NewThread &&
s.objectId === community.id
);
const batchedSubscriptions = sortSubscriptions(
filteredSubscriptions,
'objectId'
);
return [
newThreads && m(NewThreadRow, { community, subscriptions }),
batchedSubscriptions.length > 0 &&
m('tr.NewActivityRow', [m('td', NEW_ACTIVITY_LABEL), m('td')]),
// TODO: Filter community past-thread/comment subscriptions here into SubscriptionRows.
vnode.state.expanded &&
batchedSubscriptions.map(
(subscriptions2: NotificationSubscription[]) => {
return m(BatchedSubscriptionRow, {
subscriptions: subscriptions2,
key: subscriptions2[0].id,
});
}
),
batchedSubscriptions.length > 0 &&
m('tr', [
m('td', { colspan: 2 }, [
m(
'a.expand-notifications-link',
{
href: '#',
onclick: (e) => {
e.preventDefault();
vnode.state.expanded = !vnode.state.expanded;
},
},
[
`${vnode.state.expanded ? 'Hide' : 'Show'} ${pluralize(
batchedSubscriptions.length,
'thread'
)}`,
]
),
]),
]),
];
},
}
Example #8
Source File: discussion_row_menu.tsx From commonwealth with GNU General Public License v3.0 | 5 votes |
view(vnode) {
const { proposal } = vnode.attrs;
const commentSubscription = app.user.notifications.subscriptions.find(
(v) =>
v.objectId === proposal.uniqueIdentifier &&
v.category === NotificationCategories.NewComment
);
const reactionSubscription = app.user.notifications.subscriptions.find(
(v) =>
v.objectId === proposal.uniqueIdentifier &&
v.category === NotificationCategories.NewReaction
);
const bothActive =
commentSubscription?.isActive && reactionSubscription?.isActive;
return (
<MenuItem
onclick={async (e) => {
e.preventDefault();
if (!commentSubscription || !reactionSubscription) {
await Promise.all([
app.user.notifications.subscribe(
NotificationCategories.NewReaction,
proposal.uniqueIdentifier
),
app.user.notifications.subscribe(
NotificationCategories.NewComment,
proposal.uniqueIdentifier
),
]);
notifySuccess('Subscribed!');
} else if (bothActive) {
await app.user.notifications.disableSubscriptions([
commentSubscription,
reactionSubscription,
]);
notifySuccess('Unsubscribed!');
} else {
await app.user.notifications.enableSubscriptions([
commentSubscription,
reactionSubscription,
]);
notifySuccess('Subscribed!');
}
m.redraw();
}}
label={
bothActive
? 'Unsubscribe from notifications'
: 'Subscribe to notifications'
}
/>
);
}
Example #9
Source File: subscriptions.spec.ts From commonwealth with GNU General Public License v3.0 | 4 votes |
describe('Subscriptions Tests', () => {
let jwtToken;
let loggedInAddr;
let loggedInAddrId;
const chain = 'ethereum';
// const community = chain;
before('reset database', async () => {
await resetDatabase();
// get logged in address/user with JWT
const result = await modelUtils.createAndVerifyAddress({ chain });
loggedInAddr = result.address;
loggedInAddrId = result.address_id;
jwtToken = jwt.sign(
{ id: result.user_id, email: result.email },
JWT_SECRET
);
});
describe('/createSubscription test', () => {
it('should create new-thread subscription on community', async () => {
const object_id = chain;
const is_active = true;
const category = NotificationCategories.NewThread;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.category_id).to.be.equal(category);
expect(res.body.result.object_id).to.equal(object_id);
expect(res.body.result.is_active).to.be.equal(true);
});
it('should create new-thread subscription on chain', async () => {
const object_id = chain;
const is_active = true;
const category = NotificationCategories.NewThread;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.category_id).to.be.equal(category);
expect(res.body.result.object_id).to.equal(object_id);
expect(res.body.result.is_active).to.be.equal(true);
});
it('should make new-comment subscription on thread in community', async () => {
let res = await modelUtils.createThread({
chainId: chain,
address: loggedInAddr,
jwt: jwtToken,
title: 't',
body: 't',
kind: 'forum',
stage: 'discussion',
topicName: 't',
topicId: undefined,
});
const object_id = `discussion_${res.result.id}`;
const is_active = true;
const category = NotificationCategories.NewComment;
res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.category_id).to.be.equal(category);
expect(res.body.result.object_id).to.equal(`${object_id}`);
expect(res.body.result.is_active).to.be.equal(true);
});
it('should make new-comment subscription on thread in chain', async () => {
let res = await modelUtils.createThread({
chainId: chain,
address: loggedInAddr,
jwt: jwtToken,
title: 't2',
body: 't2',
kind: 'forum',
stage: 'discussion',
topicName: 't',
topicId: undefined,
});
const object_id = `discussion_${res.result.id}`;
const is_active = true;
const category = NotificationCategories.NewComment;
res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.category_id).to.be.equal(category);
expect(res.body.result.object_id).to.equal(`${object_id}`);
expect(res.body.result.is_active).to.be.equal(true);
});
it('should make new-comment subscription on comment on thread in chain', async () => {
const res1 = await modelUtils.createThread({
chainId: chain,
address: loggedInAddr,
jwt: jwtToken,
title: 't2',
body: 't2',
kind: 'forum',
stage: 'discussion',
topicName: 't',
topicId: undefined,
});
let res = await modelUtils.createComment({
chain,
address: loggedInAddr,
jwt: jwtToken,
text: 'cw4eva',
root_id: `discussion_${res1.result.id}`,
});
const object_id = `comment-${res.result.id}`;
const is_active = true;
const category = NotificationCategories.NewComment;
res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.category_id).to.be.equal(category);
expect(res.body.result.object_id).to.equal(`${object_id}`);
expect(res.body.result.is_active).to.be.equal(true);
});
it('should make new-comment subscription on comment on thread in community', async () => {
let res = await modelUtils.createThread({
chainId: chain,
address: loggedInAddr,
jwt: jwtToken,
title: 't3',
body: 't3',
kind: 'forum',
stage: 'discussion',
topicName: 't',
topicId: undefined,
});
res = await modelUtils.createComment({
chain,
address: loggedInAddr,
jwt: jwtToken,
text: 'hi',
root_id: `discussion_${res.result.id}`,
});
const object_id = `comment-${res.result.id}`;
const is_active = true;
const category = NotificationCategories.NewComment;
res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.category_id).to.be.equal(category);
expect(res.body.result.object_id).to.equal(`${object_id}`);
expect(res.body.result.is_active).to.be.equal(true);
});
it('should make new-comment subscription on chainEntity', async () => {
const entityInstance = await models['ChainEntity'].create({
chain: 'edgeware',
type: 'treasury-proposal',
type_id: '6',
completed: false,
});
const object_id = `treasuryproposal_${entityInstance.type_id}`;
const is_active = true;
const category = NotificationCategories.NewComment;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
category,
is_active,
object_id,
chain_id: 'edgeware',
});
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.category_id).to.be.equal(category);
expect(res.body.result.object_id).to.equal(`${object_id}`);
expect(res.body.result.is_active).to.be.equal(true);
});
it('should fail to make new-comment subscription on chainEntity without chain', async () => {
const chainEntity = await models['ChainEntity'].create({
chain: 'edgeware',
type: 'treasury-proposal',
type_id: '6',
completed: false,
});
const object_id = `treasuryproposal_${chainEntity.type_id}`;
const is_active = true;
const category = NotificationCategories.NewComment;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.ChainRequiredForEntity);
});
it('should fail to make new-comment subscription on nonexistent chainEntity', async () => {
const object_id = `treasuryproposal_${10}`;
const is_active = true;
const category = NotificationCategories.NewComment;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
category,
is_active,
object_id,
chain_id: 'edgeware',
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NoChainEntity);
});
it('should fail to make new-comment subscription on nonexistent comment', async () => {
const object_id = 'comment-420';
const is_active = true;
const category = NotificationCategories.NewComment;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
category,
is_active,
object_id,
chain_id: 'edgeware',
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NoComment);
});
it('should fail to make new-comment subscription on nonexistent thread', async () => {
const object_id = 'discussion_420';
const is_active = true;
const category = NotificationCategories.NewComment;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
category,
is_active,
object_id,
chain_id: 'edgeware',
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NoThread);
});
it.skip('should make chain-event subscription', async () => {
const object_id = 'edgeware-democracy-proposed';
const is_active = true;
const category = NotificationCategories.ChainEvent;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body).to.not.be.null;
expect(res.body.result.category_id).to.be.equal(category);
expect(res.body.result.object_id).to.equal(`${object_id}`);
expect(res.body.result.is_active).to.be.equal(true);
});
it('should fail to make chain-event subscription with invalid type', async () => {
const object_id = 'edgeware-onchain-party';
const is_active = true;
const category = NotificationCategories.ChainEvent;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.InvalidChainEventId);
});
it('should fail to make chain-event subscription with invalid chain', async () => {
const object_id = 'zakchain-treasury-proposal';
const is_active = true;
const category = NotificationCategories.ChainEvent;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.InvalidChain);
});
it('should fail to make new-mention subscription generally', async () => {
const object_id = 'user-2020';
const is_active = true;
const category = NotificationCategories.NewMention;
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NoMentions);
});
it('should fail to make subscription with nonexistent category_id', async () => {
const object_id = 'treasuryproposal_6';
const is_active = true;
const category = 'offchain-event';
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.InvalidNotificationCategory);
});
it('should fail to make subscription with nonexistent object_id', async () => {
const object_id = undefined;
const is_active = true;
const category = 'offchain-event';
const res = await chai
.request(app)
.post('/api/createSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, category, is_active, object_id });
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NoCategoryAndObjectId);
});
it.skip('should check /viewSubscriptions for all', async () => {
const subscription = await modelUtils.createSubscription({
object_id: chain,
jwt: jwtToken,
is_active: true,
category: NotificationCategories.NewThread,
});
const res = await chai
.request(app)
.get('/api/viewSubscriptions')
.set('Accept', 'application/json')
.send({ jwt: jwtToken });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
});
});
describe('/disableSubscriptions + /enableSubscriptions', () => {
let subscription: NotificationSubscription;
beforeEach('creating a subscription', async () => {
subscription = await modelUtils.createSubscription({
object_id: chain,
jwt: jwtToken,
is_active: true,
category: NotificationCategories.NewThread,
});
});
it('should pause a subscription', async () => {
expect(subscription).to.not.be.null;
const res = await chai
.request(app)
.post('/api/disableSubscriptions')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, 'subscription_ids[]': [subscription.id] });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
});
it('should unpause a subscription', async () => {
expect(subscription).to.not.be.null;
const res = await chai
.request(app)
.post('/api/enableSubscriptions')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, 'subscription_ids[]': [subscription.id] });
expect(res.body.status).to.be.equal('Success');
});
it('should pause and unpause a subscription with just the id as string (not array)', async () => {
expect(subscription).to.not.be.null;
let res = await chai
.request(app)
.post('/api/disableSubscriptions')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
'subscription_ids[]': subscription.id.toString(),
});
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
res = await chai
.request(app)
.post('/api/enableSubscriptions')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
'subscription_ids[]': subscription.id.toString(),
});
expect(res.body.status).to.be.equal('Success');
});
it('should pause and unpause an array of subscription', async () => {
const subscriptions = [];
for (let i = 0; i < 3; i++) {
subscriptions.push(
modelUtils.createSubscription({
object_id: chain,
jwt: jwtToken,
is_active: true,
category: NotificationCategories.NewThread,
})
);
}
const subscriptionIds = (await Promise.all(subscriptions)).map(
(s) => s.id
);
let res = await chai
.request(app)
.post('/api/disableSubscriptions')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, 'subscription_ids[]': subscriptionIds });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
res = await chai
.request(app)
.post('/api/enableSubscriptions')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, 'subscription_ids[]': subscriptionIds });
expect(res.body.status).to.be.equal('Success');
});
it('should fail to enable and disable subscriptions not owned by the requester', async () => {
expect(subscription).to.not.be.null;
const result = await modelUtils.createAndVerifyAddress({ chain });
const newJWT = jwt.sign(
{ id: result.user_id, email: result.email },
JWT_SECRET
);
let res = await chai
.request(app)
.post('/api/enableSubscriptions')
.set('Accept', 'application/json')
.send({ jwt: newJWT, 'subscription_ids[]': [subscription.id] });
expect(res.body).to.not.be.null;
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NotUsersSubscription);
res = await chai
.request(app)
.post('/api/disableSubscriptions')
.set('Accept', 'application/json')
.send({ jwt: newJWT, 'subscription_ids[]': [subscription.id] });
expect(res.body).to.not.be.null;
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NotUsersSubscription);
});
it('should fail to enable and disable subscription when no subscriptions are passed to route', async () => {
let res = await chai
.request(app)
.post('/api/enableSubscriptions')
.set('Accept', 'application/json')
.send({ jwt: jwtToken });
expect(res.body).to.not.be.null;
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NoSubscriptionId);
res = await chai
.request(app)
.post('/api/disableSubscriptions')
.set('Accept', 'application/json')
.send({ jwt: jwtToken });
expect(res.body).to.not.be.null;
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NoSubscriptionId);
});
});
describe('/enableImmediateEmails and /disableImmediateEmails', () => {
let subscription: NotificationSubscription;
beforeEach('creating a subscription', async () => {
subscription = await modelUtils.createSubscription({
object_id: chain,
jwt: jwtToken,
is_active: true,
category: NotificationCategories.NewThread,
});
});
it('should turn on immediate emails, /enableImmediateEmails', async () => {
expect(subscription).to.not.be.null;
const res = await chai
.request(app)
.post('/api/enableImmediateEmails')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, 'subscription_ids[]': [subscription.id] });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
});
it('should turn off immediate emails, /disableImmediateEmails', async () => {
expect(subscription).to.not.be.null;
const res = await chai
.request(app)
.post('/api/disableImmediateEmails')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, 'subscription_ids[]': [subscription.id] });
expect(res.body.status).to.be.equal('Success');
});
it('should fail to enable and disable immediate emails when not passed ids', async () => {
expect(subscription).to.not.be.null;
let res = await chai
.request(app)
.post('/api/enableImmediateEmails')
.set('Accept', 'application/json')
.send({ jwt: jwtToken });
expect(res.body).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NoSubscriptionId);
res = await chai
.request(app)
.post('/api/disableImmediateEmails')
.set('Accept', 'application/json')
.send({ jwt: jwtToken });
expect(res.body).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NoSubscriptionId);
});
it('should successfully enable and disable with just a string id', async () => {
expect(subscription).to.not.be.null;
let res = await chai
.request(app)
.post('/api/enableImmediateEmails')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
'subscription_ids[]': subscription.id.toString(),
});
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
res = await chai
.request(app)
.post('/api/disableImmediateEmails')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
'subscription_ids[]': subscription.id.toString(),
});
expect(res.body.status).to.be.equal('Success');
});
it('should fail to enable and disable immediate emails when requester does not own the subscription', async () => {
const result = await modelUtils.createAndVerifyAddress({ chain });
const newJwt = jwt.sign(
{ id: result.user_id, email: result.email },
JWT_SECRET
);
expect(subscription).to.not.be.null;
let res = await chai
.request(app)
.post('/api/enableImmediateEmails')
.set('Accept', 'application/json')
.send({ jwt: newJwt, 'subscription_ids[]': [subscription.id] });
expect(res.body).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NotUsersSubscription);
res = await chai
.request(app)
.post('/api/disableImmediateEmails')
.set('Accept', 'application/json')
.send({ jwt: newJwt, 'subscription_ids[]': [subscription.id] });
expect(res.body.error).to.be.equal(Errors.NotUsersSubscription);
});
});
describe('/deleteSubscription', () => {
let subscription;
beforeEach('make subscription', async () => {
subscription = await modelUtils.createSubscription({
object_id: chain,
jwt: jwtToken,
is_active: true,
category: NotificationCategories.NewThread,
});
});
it('should delete an active subscription', async () => {
expect(subscription).to.not.be.null;
const res = await chai
.request(app)
.post('/api/deleteSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, subscription_id: subscription.id });
expect(res.body.status).to.be.equal('Success');
});
it('should fail to delete when no subscription id is passed', async () => {
expect(subscription).to.not.be.null;
const res = await chai
.request(app)
.post('/api/deleteSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken });
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(Errors.NoSubscriptionId);
});
it('should fail to find a bad subscription id', async () => {
expect(subscription).to.not.be.null;
const res = await chai
.request(app)
.post('/api/deleteSubscription')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, subscription_id: 'hello' });
expect(res.body.error).to.not.be.null;
});
});
describe('Notification Routes', () => {
let subscription;
let thread;
let notifications;
it('emitting a notification', async () => {
// Subscription for Default User in 'Staking'
subscription = await modelUtils.createSubscription({
object_id: chain,
jwt: jwtToken,
is_active: true,
category: NotificationCategories.NewThread,
});
// New User makes a thread in 'Staking', should emit notification to Default User
const result = await modelUtils.createAndVerifyAddress({ chain });
const newAddress = result.address;
const newJWT = jwt.sign(
{ id: result.user_id, email: result.email },
JWT_SECRET
);
thread = await modelUtils.createThread({
chainId: chain,
jwt: newJWT,
address: newAddress,
title: 'hi',
body: 'hi you!',
kind: 'forum',
stage: 'discussion',
topicName: 't',
topicId: undefined,
});
expect(subscription).to.not.be.null;
expect(thread).to.not.be.null;
});
describe('/viewNotifications: return notifications to user', () => {
it("should return all notifications with just a user's jwt", async () => {
const res = await chai
.request(app)
.post('/api/viewNotifications')
.set('Accept', 'application/json')
.send({ jwt: jwtToken });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.length).to.be.greaterThan(0);
notifications = res.body.result;
});
it('should return only unread notifications', async () => {
const res = await chai
.request(app)
.post('/api/viewNotifications')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, unread_only: true });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.length).to.be.greaterThan(0);
notifications = res.body.result;
});
it('should return only notifications with active_only turned on', async () => {
const res = await chai
.request(app)
.post('/api/viewNotifications')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, active_only: true });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.length).to.be.greaterThan(0);
notifications = res.body.result;
});
});
describe('/markNotificationsRead', async () => {
it('should pass when query formatted correctly', async () => {
// Mark Notifications Read for Default User
expect(notifications).to.not.be.null;
const notification_ids = notifications.map((n) => {
return n.id;
});
const res = await chai
.request(app)
.post('/api/markNotificationsRead')
.set('Accept', 'application/json')
.send({ jwt: jwtToken, 'notification_ids[]': notification_ids });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
});
it('should pass when notification id is string', async () => {
// Mark Notifications Read for Default User
expect(notifications).to.not.be.null;
const notification_ids = notifications.map((n) => {
return n.id;
});
const res = await chai
.request(app)
.post('/api/markNotificationsRead')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
'notification_ids[]': notification_ids[0].toString(),
});
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
});
it('should fail when no notifications are passed', async () => {
const res = await chai
.request(app)
.post('/api/markNotificationsRead')
.set('Accept', 'application/json')
.send({ jwt: jwtToken });
expect(res.body).to.not.be.null;
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(MarkNotifErrors.NoNotificationIds);
});
});
describe('/clearReadNotifications', async () => {
it('should pass when query formatted correctly', async () => {
// Clear Read for Default User
expect(notifications).to.not.be.null;
const res = await chai
.request(app)
.post('/api/clearReadNotifications')
.set('Accept', 'application/json')
.send({ jwt: jwtToken });
expect(res.body).to.not.be.null;
expect(res.body.status).to.be.equal('Success');
});
});
});
});
Example #10
Source File: roles.spec.ts From commonwealth with GNU General Public License v3.0 | 4 votes |
describe('Roles Test', () => {
let jwtToken;
let loggedInAddr;
let loggedInAddrId;
let adminUserId;
const chain = 'ethereum';
before('reset database', async () => {
await resetDatabase();
const res = await modelUtils.createAndVerifyAddress({ chain });
loggedInAddr = res.address;
loggedInAddrId = res.address_id;
jwtToken = jwt.sign({ id: res.user_id, email: res.email }, JWT_SECRET);
adminUserId = res.user_id;
const isAdmin = await modelUtils.updateRole({
address_id: res.address_id,
chainOrCommObj: { chain_id: chain },
role: 'admin',
});
});
describe('/createRole route tests', () => {
it('should create a member role for a public community', async () => {
const user = await modelUtils.createAndVerifyAddress({ chain });
const res = await chai
.request(app)
.post('/api/createRole')
.set('Accept', 'application/json')
.send({
jwt: jwt.sign({ id: user.user_id, email: user.email }, JWT_SECRET),
chain,
address_id: user.address_id,
});
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.role.address_id).to.be.equal(user.address_id);
expect(res.body.result.role.chain_id).to.be.equal(chain);
expect(res.body.result.subscription).to.not.be.null;
expect(res.body.result.subscription.object_id).to.be.equal(chain);
expect(res.body.result.subscription.category_id).to.be.equal(
NotificationCategories.NewThread
);
});
it('should return existing role for a public community a user is already a member of', async () => {
const res = await chai
.request(app)
.post('/api/createRole')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
address_id: loggedInAddrId,
});
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.role.address_id).to.be.equal(loggedInAddrId);
expect(res.body.result.role.chain_id).to.be.equal(chain);
expect(res.body.result.role.permission).to.be.equal('admin');
});
it('should fail to create a role without address_id', async () => {
const res = await chai
.request(app)
.post('/api/createRole')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(createErrors.InvalidAddress);
});
});
describe('/upgradeMember route tests', () => {
let newUserAddress;
let newUserAddressId;
let newJwt;
let newUserId;
beforeEach(
'Create a new user as member of community to invite or upgrade',
async () => {
const res = await modelUtils.createAndVerifyAddress({ chain });
newUserAddress = res.address;
newUserAddressId = res.address_id;
newJwt = jwt.sign({ id: res.user_id, email: res.email }, JWT_SECRET);
newUserId = res.user_id;
const isMember = await modelUtils.updateRole({
address_id: newUserAddressId,
chainOrCommObj: { chain_id: chain },
role: 'member',
});
}
);
it('should pass when admin upgrades new member', async () => {
const role = 'moderator';
const res = await chai
.request(app)
.post('/api/upgradeMember')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
address: newUserAddress,
new_role: role,
});
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.permission).to.be.equal(role);
});
it('should fail when admin upgrades without address', async () => {
const role = 'moderator';
const res = await chai
.request(app)
.post('/api/upgradeMember')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
new_role: role,
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(upgradeErrors.InvalidAddress);
});
it('should fail when admin upgrades invalid address', async () => {
const role = 'moderator';
const res = await chai
.request(app)
.post('/api/upgradeMember')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
address: 'invalid address',
new_role: role,
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(upgradeErrors.NoMember);
});
it('should fail when admin upgrades without role', async () => {
const res = await chai
.request(app)
.post('/api/upgradeMember')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
address: newUserAddress,
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(upgradeErrors.InvalidRole);
});
it('should fail when admin upgrades with invalid role', async () => {
const res = await chai
.request(app)
.post('/api/upgradeMember')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
address: newUserAddress,
new_role: 'commander in chief',
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(upgradeErrors.InvalidRole);
});
it('should fail when a non-admin upgrades a member', async () => {
const res = await chai
.request(app)
.post('/api/upgradeMember')
.set('Accept', 'application/json')
.send({
jwt: newJwt,
chain,
address: newUserAddress,
new_role: 'moderator',
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(upgradeErrors.MustBeAdmin);
});
it('should fail to upgrade a nonexistent member', async () => {
const temp = await modelUtils.createAndVerifyAddress({ chain });
const tempJwt = jwt.sign(
{ id: temp.user_id, email: temp.email },
JWT_SECRET
);
const res = await chai
.request(app)
.post('/api/upgradeMember')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
address: generateEthAddress().address,
new_role: 'admin',
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(upgradeErrors.NoMember);
});
it('should fail when the only admin demotes self', async () => {
const role = 'member';
const res = await chai
.request(app)
.post('/api/upgradeMember')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
address: loggedInAddr,
new_role: role,
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(upgradeErrors.MustHaveAdmin);
});
it('should pass when admin demotes self', async () => {
// set new user as new admin
const role = 'admin';
const res = await chai
.request(app)
.post('/api/upgradeMember')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
address: newUserAddress,
new_role: role,
});
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.permission).to.be.equal(role);
const newAdminRole = 'member';
const demoteRes = await chai
.request(app)
.post('/api/upgradeMember')
.set('Accept', 'application/json')
.send({
jwt: jwtToken,
chain,
address: loggedInAddr,
new_role: newAdminRole,
});
expect(demoteRes.body.status).to.be.equal('Success');
expect(demoteRes.body.result.permission).to.be.equal(newAdminRole);
});
});
describe('/deleteRole route tests', () => {
let memberAddress;
let memberAddressId;
let memberJwt;
let memberUserId;
beforeEach('Create a member role to delete', async () => {
const res = await modelUtils.createAndVerifyAddress({ chain });
memberAddress = res.address;
memberAddressId = res.address_id;
memberJwt = jwt.sign({ id: res.user_id, email: res.email }, JWT_SECRET);
memberUserId = res.user_id;
const isMember = await modelUtils.updateRole({
address_id: memberAddressId,
chainOrCommObj: { chain_id: chain },
role: 'member',
});
});
it('should delete member role', async () => {
const res = await chai
.request(app)
.post('/api/deleteRole')
.set('Accept', 'application/json')
.send({
jwt: memberJwt,
chain,
address_id: memberAddressId,
});
expect(res.body.status).to.be.equal('Success');
});
it('should fail to delete role without address_id', async () => {
const res = await chai
.request(app)
.post('/api/deleteRole')
.set('Accept', 'application/json')
.send({
jwt: memberJwt,
chain,
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(deleteErrors.InvalidAddress);
});
it('should fail to delete role with invalid address_id', async () => {
const res = await chai
.request(app)
.post('/api/deleteRole')
.set('Accept', 'application/json')
.send({
jwt: memberJwt,
chain,
address_id: 123456,
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(deleteErrors.InvalidAddress);
});
it('should fail to delete role where there is none in chain community', async () => {
// ensure does not exist before attempting to delete
await chai
.request(app)
.post('/api/deleteRole')
.set('Accept', 'application/json')
.send({
jwt: memberJwt,
chain,
address_id: memberAddressId,
});
const res = await chai
.request(app)
.post('/api/deleteRole')
.set('Accept', 'application/json')
.send({
jwt: memberJwt,
chain,
address_id: memberAddressId,
});
expect(res.body.error).to.not.be.null;
expect(res.body.error).to.be.equal(deleteErrors.RoleDNE);
});
});
describe('/bulkMembers route test', () => {
it('should grab bulk members for a public community', async () => {
const res = await chai.request
.agent(app)
.get('/api/bulkMembers')
.set('Accept', 'application/json')
.query({
chain,
jwt: jwtToken,
});
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.length).to.be.greaterThan(0);
});
it.skip('should fail to grab bulk members if community is not visible to user', async () => {
const communityArgs: modelUtils.CommunityArgs = {
jwt: jwtToken,
isAuthenticatedForum: 'false',
privacyEnabled: 'true',
invitesEnabled: 'true',
id: 'test',
name: 'test community',
creator_address: loggedInAddr,
creator_chain: chain,
description: 'test enabled community',
default_chain: chain,
};
const testCommunity = await modelUtils.createCommunity(communityArgs);
const res = await chai.request
.agent(app)
.get('/api/bulkMembers')
.set('Accept', 'application/json')
.query({
chain,
jwt: jwtToken,
});
expect(res.body.status).to.be.equal('Success');
expect(res.body.result.length).to.be.greaterThan(0);
});
});
});
Example #11
Source File: notification_settings.ts From commonwealth with GNU General Public License v3.0 | 4 votes |
ChainEventSubscriptionRow: m.Component<
{
title: string;
notificationTypeArray: string[];
recommended?: boolean;
},
{ option: string; loading: boolean }
> = {
view: (vnode) => {
const { title, notificationTypeArray, recommended } = vnode.attrs;
const subscriptions = app.user.notifications.subscriptions.filter((s) => {
return (
s.category === NotificationCategories.ChainEvent &&
notificationTypeArray.includes(s.objectId)
);
});
const everySubscriptionActive = subscriptions.every((s) => s.isActive);
const someSubscriptionsActive = subscriptions.some((s) => s.isActive);
const everySubscriptionEmail = subscriptions.every((s) => s.immediateEmail);
const someSubscriptionsEmail = subscriptions.some((s) => s.immediateEmail);
const allSubscriptionsCreated =
subscriptions.length === notificationTypeArray.length;
if (
allSubscriptionsCreated &&
everySubscriptionActive &&
everySubscriptionEmail
) {
vnode.state.option = NOTIFICATION_ON_IMMEDIATE_EMAIL_OPTION;
} else if (allSubscriptionsCreated && everySubscriptionActive) {
vnode.state.option = NOTIFICATION_ON_OPTION;
} else {
vnode.state.option = NOTIFICATION_OFF_OPTION;
}
return m('tr.ChainEventSubscriptionRow', [
m('td.subscription-label', [
title,
recommended && m(Tag, { size: 'xs', label: 'Recommended' }),
m('.ChainEventDetails', [
notificationTypeArray
.filter((s) => s.indexOf('reward') === -1) // filter out treasury-reward and reward events (they are silent)
.map((s) => `${s.replace(/^[a-z]+-/, '')}, `)
.join('')
.replace(/, $/, ''),
]),
]),
m('td.subscription-setting', [
m(SelectList, {
class: 'EventSubscriptionTypeSelectList',
filterable: false,
checkmark: false,
emptyContent: null,
popoverAttrs: {
transitionDuration: 0,
},
inputAttrs: {
class: 'EventSubscriptionTypeSelectRow',
},
itemRender: (option: string) => {
return m(ListItem, {
label: option,
selected: vnode.state.option === option,
});
},
items: [
NOTIFICATION_ON_IMMEDIATE_EMAIL_OPTION,
NOTIFICATION_ON_OPTION,
NOTIFICATION_OFF_OPTION,
],
trigger: m(Button, {
align: 'left',
compact: true,
rounded: true,
disabled: !app.user.emailVerified || vnode.state.loading,
iconRight: Icons.CHEVRON_DOWN,
label: vnode.state.option,
}),
onSelect: async (option: string) => {
vnode.state.option = option;
vnode.state.loading = true;
if (option === NOTIFICATION_OFF_OPTION) {
await app.user.notifications.disableImmediateEmails(
subscriptions
);
await app.user.notifications.disableSubscriptions(subscriptions);
} else if (option === NOTIFICATION_ON_OPTION) {
if (!allSubscriptionsCreated) {
await Promise.all(
notificationTypeArray.map((obj) => {
return app.user.notifications.subscribe(
NotificationCategories.ChainEvent,
obj
);
})
);
} else {
if (!everySubscriptionActive)
await app.user.notifications.enableSubscriptions(
subscriptions
);
}
if (someSubscriptionsEmail)
await app.user.notifications.disableImmediateEmails(
subscriptions
);
} else if (option === NOTIFICATION_ON_IMMEDIATE_EMAIL_OPTION) {
if (!allSubscriptionsCreated) {
await Promise.all(
notificationTypeArray.map((obj) => {
return app.user.notifications.subscribe(
NotificationCategories.ChainEvent,
obj
);
})
).then(async () => {
const newSubscriptions =
app.user.notifications.subscriptions.filter((s) => {
return (
s.category === NotificationCategories.ChainEvent &&
notificationTypeArray.includes(s.objectId)
);
});
await app.user.notifications.enableImmediateEmails(
newSubscriptions
);
m.redraw();
});
} else {
if (!everySubscriptionActive)
await app.user.notifications.enableSubscriptions(
subscriptions
);
if (!everySubscriptionEmail)
await app.user.notifications.enableImmediateEmails(
subscriptions
);
}
}
vnode.state.loading = false;
m.redraw();
},
}),
]),
]);
},
}
Example #12
Source File: notification_settings.ts From commonwealth with GNU General Public License v3.0 | 4 votes |
BatchedSubscriptionRow: m.Component<
{
subscriptions: NotificationSubscription[];
label?: string;
},
{
option: string;
loading: boolean;
}
> = {
view: (vnode) => {
const { label, subscriptions } = vnode.attrs;
const someActive = subscriptions.some((s) => s.isActive);
const everyActive = subscriptions.every((s) => s.isActive);
const someEmail = subscriptions.some((s) => s.immediateEmail);
const everyEmail = subscriptions.every((s) => s.immediateEmail);
if (everyActive && everyEmail) {
vnode.state.option = NOTIFICATION_ON_IMMEDIATE_EMAIL_OPTION;
} else if (everyActive && !someEmail) {
vnode.state.option = NOTIFICATION_ON_OPTION;
} else if (someActive || someEmail) {
vnode.state.option = NOTIFICATION_ON_SOMETIMES_OPTION;
} else {
vnode.state.option = NOTIFICATION_OFF_OPTION;
}
if (!subscriptions) return;
const singleLabel = (subscription: NotificationSubscription) => {
const chain = subscription.Chain || null;
switch (subscription.category) {
case NotificationCategories.NewComment: {
const threadOrComment = subscription.OffchainThread
? decodeURIComponent(subscription.OffchainThread.title)
: subscription.OffchainComment
? decodeURIComponent(subscription.OffchainComment.id)
: subscription.objectId;
return subscription.OffchainThread
? [
link(
'a',
`/${chain}${getProposalUrlPath(
ProposalType.OffchainThread,
subscription.OffchainThread.id,
true
)}`,
threadOrComment.toString(),
{ target: '_blank' }
),
m(
'span.item-metadata',
moment(subscription.OffchainThread.created_at).fromNow()
),
m('span.item-metadata', NEW_COMMENTS_LABEL_SUFFIX),
]
: [
threadOrComment.toString(),
m('span.item-metadata', NEW_COMMENTS_LABEL_SUFFIX),
];
}
case NotificationCategories.NewReaction: {
const threadOrComment = subscription.OffchainThread
? decodeURIComponent(subscription.OffchainThread.title)
: subscription.OffchainComment
? decodeURIComponent(subscription.OffchainComment.id)
: subscription.objectId;
return subscription.OffchainThread
? [
link(
'a',
`/${chain}${getProposalUrlPath(
ProposalType.OffchainThread,
subscription.OffchainThread.id,
true
)}`,
threadOrComment.toString(),
{ target: '_blank' }
),
m(
'span.item-metadata',
moment(subscription.OffchainThread.created_at).fromNow()
),
m('span.item-metadata', NEW_REACTIONS_LABEL_SUFFIX),
]
: [
threadOrComment.toString(),
m('span.item-metadata', NEW_REACTIONS_LABEL_SUFFIX),
];
}
default:
break;
}
};
const batchLabel = (
batchLabelSubscriptions: NotificationSubscription[]
) => {
const subscription = batchLabelSubscriptions[0];
const chain = subscription.Chain || null;
const threadOrComment = subscription.OffchainThread
? decodeURIComponent(subscription.OffchainThread.title)
: subscription.OffchainComment
? decodeURIComponent(subscription.OffchainComment.id)
: subscription.objectId;
return subscription.OffchainThread
? [
link(
'a',
`/${chain}${getProposalUrlPath(
ProposalType.OffchainThread,
subscription.OffchainThread.id,
true
)}`,
threadOrComment.toString(),
{ target: '_blank' }
),
m(
'span.item-metadata',
moment(subscription.OffchainThread.created_at).fromNow()
),
]
: [threadOrComment.toString()];
};
// hide subscriptions on threads/comments that have been deleted
if (
_.every(
subscriptions,
(s) =>
!s.OffchainComment &&
!s.OffchainThread &&
(s.category === NotificationCategories.NewComment ||
s.category === NotificationCategories.NewReaction)
)
) {
return;
}
return m('tr.BatchedSubscriptionRow', [
m('td.subscription-label', [
label ||
(subscriptions?.length > 1
? batchLabel(subscriptions)
: singleLabel(subscriptions[0])),
]),
m('td.subscription-setting', [
m(SelectList, {
class: 'BatchedNotificationSelectList',
filterable: false,
checkmark: false,
emptyContent: null,
inputAttrs: {
class: 'BatchedNotificationSelectRow',
},
popoverAttrs: {
transitionDuration: 0,
},
itemRender: (option: string) => {
return m(ListItem, {
label: option,
selected: vnode.state.option === option,
});
},
items: [
NOTIFICATION_ON_IMMEDIATE_EMAIL_OPTION,
NOTIFICATION_ON_OPTION,
NOTIFICATION_OFF_OPTION,
],
trigger: m(Button, {
align: 'left',
compact: true,
rounded: true,
disabled: !app.user.emailVerified || vnode.state.loading,
iconRight: Icons.CHEVRON_DOWN,
label: vnode.state.option,
class:
vnode.state.option === NOTIFICATION_ON_SOMETIMES_OPTION
? 'sometimes'
: '',
}),
onSelect: async (option: string) => {
vnode.state.option = option;
vnode.state.loading = true;
try {
if (subscriptions.length < 1) return;
if (option === NOTIFICATION_OFF_OPTION) {
if (someEmail)
await app.user.notifications.disableImmediateEmails(
subscriptions
);
if (someActive)
await app.user.notifications.disableSubscriptions(
subscriptions
);
} else if (option === NOTIFICATION_ON_OPTION) {
await app.user.notifications.enableSubscriptions(subscriptions);
if (someEmail)
await app.user.notifications.disableImmediateEmails(
subscriptions
);
} else if (option === NOTIFICATION_ON_IMMEDIATE_EMAIL_OPTION) {
if (!everyActive)
await app.user.notifications.enableSubscriptions(
subscriptions
);
await app.user.notifications.enableImmediateEmails(
subscriptions
);
}
} catch (err) {
notifyError(err.toString());
}
vnode.state.loading = false;
m.redraw();
},
}),
]),
]);
},
}
Example #13
Source File: webhook_settings_modal.tsx From commonwealth with GNU General Public License v3.0 | 4 votes |
view(vnode) {
const { webhook } = vnode.attrs;
const isChain = !!webhook.chain_id;
// TODO: @ZAK make this generic or based on chain-event listening status on backend
const chainNotifications =
webhook.chain_id === 'edgeware'
? EdgewareChainNotificationTypes
: webhook.chain_id === 'kusama'
? KusamaChainNotificationTypes
: webhook.chain_id === 'kulupu'
? KulupuChainNotificationTypes
: webhook.chain_id === 'polkadot'
? PolkadotChainNotificationTypes
: webhook.chain_id === 'dydx'
? DydxChainNotificationTypes
: {};
const row = (label: string, values: string[]) => {
const allValuesPresent = values.every((v) =>
this.selectedCategories.includes(v)
);
const someValuesPresent =
values.length > 1 &&
values.some((v) => this.selectedCategories.includes(v));
return (
<ListItem
contentLeft={label}
contentRight={
<Checkbox
checked={allValuesPresent}
indeterminate={someValuesPresent && !allValuesPresent}
onchange={() => {
if (allValuesPresent) {
this.selectedCategories = this.selectedCategories.filter(
(v) => !values.includes(v)
);
m.redraw();
} else {
values.forEach((v) => {
if (!this.selectedCategories.includes(v)) {
this.selectedCategories.push(v);
}
});
m.redraw();
}
}}
/>
}
/>
);
};
return (
<div class="WebhookSettingsModal">
<div class="compact-modal-title">
<h3>Webhook Settings</h3>
<CompactModalExitButton />
</div>
<div class="compact-modal-body">
<p>Which events should trigger this webhook?</p>
<div class="forum-events">
<h4>Off-chain discussions</h4>
<List interactive={false} size="sm">
{row('New thread', [NotificationCategories.NewThread])}
{row('New comment', [NotificationCategories.NewComment])}
{row('New reaction', [NotificationCategories.NewReaction])}
</List>
</div>
{isChain && Object.keys(chainNotifications).length > 0 && (
<div class="chain-events">
<h4>On-chain events</h4>
<List interactive={false} size="sm">
{/* iterate over chain events */}
{Object.keys(chainNotifications).map((k) =>
row(`${k} event`, chainNotifications[k])
)}
</List>
</div>
)}
<Button
label="Save webhook settings"
intent="primary"
rounded={true}
onclick={(e) => {
e.preventDefault();
const chainOrCommObj = { chain: webhook.chain_id };
$.ajax({
url: `${app.serverUrl()}/updateWebhook`,
data: {
webhookId: webhook.id,
categories: this.selectedCategories,
...chainOrCommObj,
jwt: app.user.jwt,
},
type: 'POST',
success: (result) => {
const updatedWebhook = Webhook.fromJSON(result.result);
vnode.attrs.updateSuccessCallback(updatedWebhook);
$(e.target).trigger('modalexit');
},
error: (err) => {
notifyError(err.statusText);
m.redraw();
},
});
}}
/>
</div>
</div>
);
}
Example #14
Source File: user_dashboard_row.ts From commonwealth with GNU General Public License v3.0 | 4 votes |
ButtonRow: m.Component<{
path: string;
threadId: string;
showDiscussion: boolean;
showShare: boolean;
showSubscribe: boolean;
}> = {
view: (vnode) => {
const { path, threadId, showDiscussion, showShare, showSubscribe } =
vnode.attrs;
const adjustedId = `discussion_${threadId}`;
const commentSubscription = app.user.notifications.subscriptions.find(
(v) =>
v.objectId === adjustedId &&
v.category === NotificationCategories.NewComment
);
const reactionSubscription = app.user.notifications.subscriptions.find(
(v) =>
v.objectId === adjustedId &&
v.category === NotificationCategories.NewReaction
);
const bothActive =
commentSubscription?.isActive && reactionSubscription?.isActive;
return m('.icon-row-left', [
showDiscussion &&
m(Button, {
label: 'Discuss',
buttonSize: 'sm',
iconLeft: Icons.PLUS,
rounded: true,
onclick: (e) => {
e.stopPropagation();
m.route.set(path);
},
}),
showShare &&
m(
'.share-button',
{
onclick: (e) => {
e.stopPropagation();
},
},
[
m(PopoverMenu, {
transitionDuration: 0,
closeOnOutsideClick: true,
closeOnContentClick: true,
menuAttrs: { size: 'default' },
content: [
m(MenuItem, {
iconLeft: Icons.COPY,
label: 'Copy URL',
onclick: async (e) => {
await navigator.clipboard.writeText(path);
},
}),
m(MenuItem, {
iconLeft: Icons.TWITTER,
label: 'Share on Twitter',
onclick: async (e) => {
await window.open(
`https://twitter.com/intent/tweet?text=${path}`,
'_blank'
);
},
}),
],
trigger: m(Button, {
buttonSize: 'sm',
label: 'Share',
iconLeft: Icons.SHARE,
rounded: true,
}),
}),
]
),
showSubscribe &&
m(Button, {
buttonSize: 'sm',
label: bothActive ? 'Unsubscribe' : 'Subscribe',
iconLeft: Icons.BELL,
rounded: true,
class: bothActive ? 'subscribe-button' : '',
onclick: (e) => {
e.stopPropagation();
subscribeToThread(
threadId,
bothActive,
commentSubscription,
reactionSubscription
);
},
}),
]);
},
}
Example #15
Source File: notification_row.ts From commonwealth with GNU General Public License v3.0 | 4 votes |
NotificationRow: m.Component<
{
notifications: Notification[];
onListPage?: boolean;
},
{
scrollOrStop: boolean;
markingRead: boolean;
}
> = {
oncreate: (vnode) => {
if (
m.route.param('id') &&
vnode.attrs.onListPage &&
m.route.param('id') === vnode.attrs.notifications[0].id.toString()
) {
vnode.state.scrollOrStop = true;
}
},
view: (vnode) => {
const { notifications } = vnode.attrs;
const notification = notifications[0];
const { category } = notifications[0].subscription;
if (category === NotificationCategories.ChainEvent) {
if (!notification.chainEvent) {
throw new Error('chain event notification does not have expected data');
}
const chainId = notification.chainEvent.type.chain;
// construct compatible CW event from DB by inserting network from type
const chainEvent: CWEvent = {
blockNumber: notification.chainEvent.blockNumber,
network: notification.chainEvent.type.eventNetwork,
data: notification.chainEvent.data,
};
const chainName = app.config.chains.getById(chainId)?.name;
if (app.isCustomDomain() && chainId !== app.customDomainId()) return;
const label = ChainEventLabel(chainId, chainEvent);
if (vnode.state.scrollOrStop) {
setTimeout(() => {
const el = document.getElementById(m.route.param('id'));
if (el) el.scrollIntoView();
}, 1);
vnode.state.scrollOrStop = false;
}
if (!label) {
return m(
'li.NotificationRow',
{
class: notification.isRead ? '' : 'unread',
key: notification.id,
id: notification.id,
},
[m('.comment-body', [m('.comment-body-top', 'Loading...')])]
);
}
return link(
'a.NotificationRow',
`/notifications?id=${notification.id}`,
[
m('.comment-body', [
m('.comment-body-top.chain-event-notification-top', [
`${label.heading} on ${chainName}`,
!vnode.attrs.onListPage &&
m(CWIcon, {
iconName: 'close',
onmousedown: (e) => {
e.preventDefault();
e.stopPropagation();
},
onclick: (e) => {
e.preventDefault();
e.stopPropagation();
vnode.state.scrollOrStop = true;
app.user.notifications.clear([notification]).then(() => {
m.redraw();
});
},
}),
]),
m(
'.comment-body-bottom',
`Block ${notification.chainEvent.blockNumber}`
),
m('.comment-body-excerpt', label.label),
]),
],
{
class: notification.isRead ? '' : 'unread',
key: notification.id,
id: notification.id,
},
null,
() => {
if (vnode.state.scrollOrStop) {
vnode.state.scrollOrStop = false;
return;
}
app.user.notifications
.markAsRead([notification])
.then(() => m.redraw());
},
() => m.redraw.sync()
);
} else {
const notificationData = notifications.map((notif) =>
typeof notif.data === 'string' ? JSON.parse(notif.data) : notif.data
);
let {
authorInfo,
createdAt,
notificationHeader,
notificationBody,
path,
pageJump,
} = getBatchNotificationFields(category, notificationData);
if (app.isCustomDomain()) {
if (
path.indexOf(`https://commonwealth.im/${app.customDomainId()}/`) !==
0 &&
path.indexOf(`http://localhost:8080/${app.customDomainId()}/`) !== 0
)
return;
path = path
.replace(`https://commonwealth.im/${app.customDomainId()}/`, '/')
.replace(`http://localhost:8080/${app.customDomainId()}/`, '/');
}
return link(
'a.NotificationRow',
path.replace(/ /g, '%20'),
[
authorInfo.length === 1
? m(User, {
user: new AddressInfo(
null,
(authorInfo[0] as [string, string])[1],
(authorInfo[0] as [string, string])[0],
null
),
avatarOnly: true,
avatarSize: 26,
})
: m(UserGallery, {
users: authorInfo.map(
(auth) => new AddressInfo(null, auth[1], auth[0], null)
),
avatarSize: 26,
}),
m('.comment-body', [
m('.comment-body-title', notificationHeader),
notificationBody &&
category !== `${NotificationCategories.NewReaction}` &&
category !== `${NotificationCategories.NewThread}` &&
m('.comment-body-excerpt', notificationBody),
m('.comment-body-bottom-wrap', [
m('.comment-body-created', moment(createdAt).fromNow()),
!notification.isRead &&
m(
'.comment-body-mark-as-read',
{
onclick: (e) => {
e.preventDefault();
e.stopPropagation();
vnode.state.markingRead = true;
app.user.notifications
.markAsRead(notifications)
?.then(() => {
vnode.state.markingRead = false;
m.redraw();
})
.catch(() => {
vnode.state.markingRead = false;
m.redraw();
});
},
},
[
vnode.state.markingRead
? m(Spinner, { size: 'xs', active: true })
: 'Mark as read',
]
),
]),
]),
],
{
class: notification.isRead ? '' : 'unread',
key: notification.id,
id: notification.id,
},
null,
() => app.user.notifications.markAsRead(notifications),
pageJump ? () => setTimeout(() => pageJump(), 1) : null
);
}
},
}
Example #16
Source File: notification_row.ts From commonwealth with GNU General Public License v3.0 | 4 votes |
getBatchNotificationFields = (
category,
data: IPostNotificationData[]
) => {
if (data.length === 1) {
return getNotificationFields(category, data[0]);
}
const {
created_at,
root_id,
root_title,
root_type,
comment_id,
comment_text,
parent_comment_id,
parent_comment_text,
chain_id,
author_address,
author_chain,
} = data[0];
const authorInfo = _.uniq(
data.map((d) => `${d.author_chain}#${d.author_address}`)
).map((u) => u.split('#'));
const length = authorInfo.length - 1;
const community_name =
app.config.chains.getById(chain_id)?.name || 'Unknown chain';
let notificationHeader;
let notificationBody;
const decoded_title = decodeURIComponent(root_title).trim();
if (comment_text) {
notificationBody = getCommentPreview(comment_text);
} else if (root_type === ProposalType.OffchainThread) {
notificationBody = null;
}
const actorName = m(User, {
user: new AddressInfo(null, author_address, author_chain, null),
hideAvatar: true,
hideIdentityIcon: true,
});
if (category === NotificationCategories.NewComment) {
// Needs logic for notifications issued to parents of nested comments
notificationHeader = parent_comment_id
? m('span', [
actorName,
length > 0 && ` and ${pluralize(length, 'other')}`,
' commented on ',
m('span.commented-obj', decoded_title),
])
: m('span', [
actorName,
length > 0 && ` and ${pluralize(length, 'other')}`,
' responded in ',
m('span.commented-obj', decoded_title),
]);
} else if (category === NotificationCategories.NewThread) {
notificationHeader = m('span', [
actorName,
length > 0 && ` and ${pluralize(length, 'other')}`,
' created new threads in ',
m('span.commented-obj', community_name),
]);
} else if (category === `${NotificationCategories.NewMention}`) {
notificationHeader = !comment_id
? m('span', [
actorName,
length > 0 && ` and ${pluralize(length, 'other')}`,
' mentioned you in ',
m('span.commented-obj', community_name),
])
: m('span', [
actorName,
length > 0 && ` and ${pluralize(length, 'other')}`,
' mentioned you in ',
m('span.commented-obj', decoded_title || community_name),
]);
} else if (category === `${NotificationCategories.NewReaction}`) {
notificationHeader = !comment_id
? m('span', [
actorName,
length > 0 && ` and ${pluralize(length, 'other')}`,
' liked the post ',
m('span.commented-obj', decoded_title),
])
: m('span', [
actorName,
length > 0 && ` and ${pluralize(length, 'other')}`,
' liked your comment in ',
m('span.commented-obj', decoded_title || community_name),
]);
}
const pseudoProposal = {
id: root_id,
title: root_title,
chain: chain_id,
};
const args = comment_id
? [root_type, pseudoProposal, { id: comment_id }]
: [root_type, pseudoProposal];
const path =
category === NotificationCategories.NewThread
? (getCommunityUrl as any)(chain_id)
: (getProposalUrl as any)(...args);
const pageJump = comment_id
? () => jumpHighlightComment(comment_id)
: () => jumpHighlightComment('parent');
return {
authorInfo,
createdAt: moment.utc(created_at),
notificationHeader,
notificationBody,
path,
pageJump,
};
}
Example #17
Source File: notification_row.ts From commonwealth with GNU General Public License v3.0 | 4 votes |
getNotificationFields = (category, data: IPostNotificationData) => {
const {
created_at,
root_id,
root_title,
root_type,
comment_id,
comment_text,
parent_comment_id,
parent_comment_text,
chain_id,
author_address,
author_chain,
} = data;
const community_name =
app.config.chains.getById(chain_id)?.name || 'Unknown chain';
let notificationHeader;
let notificationBody;
const decoded_title = decodeURIComponent(root_title).trim();
if (comment_text) {
notificationBody = getCommentPreview(comment_text);
} else if (root_type === ProposalType.OffchainThread) {
notificationBody = null;
}
const actorName = m(User, {
user: new AddressInfo(null, author_address, author_chain, null),
hideAvatar: true,
hideIdentityIcon: true,
});
if (category === NotificationCategories.NewComment) {
// Needs logic for notifications issued to parents of nested comments
notificationHeader = parent_comment_id
? m('span', [
actorName,
' commented on ',
m('span.commented-obj', decoded_title),
])
: m('span', [
actorName,
' responded in ',
m('span.commented-obj', decoded_title),
]);
} else if (category === NotificationCategories.NewThread) {
notificationHeader = m('span', [
actorName,
' created a new thread ',
m('span.commented-obj', decoded_title),
]);
} else if (category === `${NotificationCategories.NewMention}`) {
notificationHeader = m('span', [
actorName,
' mentioned you in ',
m('span.commented-obj', decoded_title),
]);
} else if (category === `${NotificationCategories.NewCollaboration}`) {
notificationHeader = m('span', [
actorName,
' added you as a collaborator on ',
m('span.commented-obj', decoded_title),
]);
} else if (category === `${NotificationCategories.NewReaction}`) {
notificationHeader = !comment_id
? m('span', [
actorName,
' liked the post ',
m('span.commented-obj', decoded_title),
])
: m('span', [
actorName,
' liked your comment in ',
m('span.commented-obj', decoded_title || community_name),
]);
}
const pseudoProposal = {
id: root_id,
title: root_title,
chain: chain_id,
};
const args = comment_id
? [root_type, pseudoProposal, { id: comment_id }]
: [root_type, pseudoProposal];
const path = (getProposalUrl as any)(...args);
const pageJump = comment_id
? () => jumpHighlightComment(comment_id)
: () => jumpHighlightComment('parent');
return {
authorInfo: [[author_chain, author_address]],
createdAt: moment.utc(created_at),
notificationHeader,
notificationBody,
path,
pageJump,
};
}