-
-
Notifications
You must be signed in to change notification settings - Fork 285
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
Notifications Service #696
base: master
Are you sure you want to change the base?
Changes from 13 commits
6eefc25
700e44a
9308089
bea6f7f
454847b
ee6c17c
bc8c9ad
da4a8a6
bcfb0ad
8fb59d6
bb7f790
daba75e
ee041d1
dfb1f54
e0132f6
917d5b9
6cc972d
fd75e84
7c3ba32
baccf88
7989606
44d3e36
f714500
5b462e3
87e4a18
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
BEGIN; | ||
|
||
DROP TABLE IF EXISTS bookbrainz.notification; | ||
DROP TABLE IF EXISTS bookbrainz.entity_subscription; | ||
DROP TABLE IF EXISTS bookbrainz.collection_subscription; | ||
|
||
COMMIT; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
BEGIN; | ||
|
||
CREATE TABLE IF NOT EXISTS bookbrainz.notification ( | ||
id UUID PRIMARY KEY DEFAULT public.uuid_generate_v4(), | ||
subscriber_id INT NOT NULL, | ||
read BOOLEAN NOT NULL DEFAULT FALSE, | ||
notification_text TEXT NOT NULL CHECK (notification_text <> ''), | ||
notification_redirect_link TEXT NOT NULL CHECK (notification_redirect_link <> ''), | ||
timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT timezone('UTC'::TEXT, now()) | ||
); | ||
ALTER TABLE bookbrainz.notification ADD FOREIGN KEY (subscriber_id) REFERENCES bookbrainz.editor (id); | ||
|
||
CREATE TABLE bookbrainz.entity_subscription ( | ||
bbid UUID, | ||
subscriber_id INT, | ||
PRIMARY KEY ( | ||
bbid, | ||
subscriber_id | ||
) | ||
); | ||
ALTER TABLE bookbrainz.entity_subscription ADD FOREIGN KEY (bbid) REFERENCES bookbrainz.entity (bbid); | ||
ALTER TABLE bookbrainz.entity_subscription ADD FOREIGN KEY (subscriber_id) REFERENCES bookbrainz.editor (id); | ||
|
||
|
||
CREATE TABLE bookbrainz.collection_subscription ( | ||
collection_id UUID, | ||
subscriber_id INT, | ||
PRIMARY KEY ( | ||
collection_id, | ||
subscriber_id | ||
) | ||
); | ||
ALTER TABLE bookbrainz.collection_subscription ADD FOREIGN KEY (collection_id) REFERENCES bookbrainz.user_collection (id); | ||
ALTER TABLE bookbrainz.collection_subscription ADD FOREIGN KEY (subscriber_id) REFERENCES bookbrainz.editor (id); | ||
|
||
COMMIT; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,6 +123,7 @@ class CollectionPage extends React.Component { | |
super(props); | ||
this.state = { | ||
entities: this.props.entities, | ||
isSubscribed: false, | ||
message: { | ||
text: null, | ||
type: null | ||
|
@@ -138,13 +139,20 @@ class CollectionPage extends React.Component { | |
this.handleRemoveEntities = this.handleRemoveEntities.bind(this); | ||
this.handleShowDeleteModal = this.handleShowDeleteModal.bind(this); | ||
this.handleCloseDeleteModal = this.handleCloseDeleteModal.bind(this); | ||
this.handleSubscribe = this.handleSubscribe.bind(this); | ||
this.handleShowAddEntityModal = this.handleShowAddEntityModal.bind(this); | ||
this.handleCloseAddEntityModal = this.handleCloseAddEntityModal.bind(this); | ||
this.handleAlertDismiss = this.handleAlertDismiss.bind(this); | ||
this.handleUnsubscribe = this.handleUnsubscribe.bind(this); | ||
this.searchResultsCallback = this.searchResultsCallback.bind(this); | ||
this.setIsSubscribed = this.setIsSubscribed.bind(this); | ||
this.closeAddEntityModalShowMessageAndRefreshTable = this.closeAddEntityModalShowMessageAndRefreshTable.bind(this); | ||
} | ||
|
||
async componentDidMount() { | ||
await this.setIsSubscribed(); | ||
} | ||
|
||
searchResultsCallback(newResults) { | ||
this.setState({entities: newResults}); | ||
} | ||
|
@@ -224,6 +232,59 @@ class CollectionPage extends React.Component { | |
}, this.pagerElementRef.triggerSearch); | ||
} | ||
|
||
handleSubscribe() { | ||
const submissionUrl = '/subscription/subscribe/collection'; | ||
const collectionId = this.props.collection.id; | ||
const subscriberId = this.props.userId; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this not be accessible in the route handler on the server? |
||
request.post(submissionUrl) | ||
.send({collectionId, subscriberId}) | ||
.then((res) => { | ||
const {isSubscribed} = res.body; | ||
this.setState({isSubscribed}); | ||
}, () => { | ||
this.setState({ | ||
message: { | ||
text: 'Something went wrong! Please try again later', | ||
type: 'danger' | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
handleUnsubscribe() { | ||
const submissionUrl = '/subscription/unsubscribe/collection'; | ||
const collectionId = this.props.collection.id; | ||
request.post(submissionUrl) | ||
.send({collectionId}) | ||
.then((res) => { | ||
const {isSubscribed} = res.body; | ||
this.setState({isSubscribed}); | ||
}, () => { | ||
this.setState({ | ||
message: { | ||
text: 'Something went wrong! Please try again later', | ||
type: 'danger' | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
setIsSubscribed() { | ||
const url = `/subscription/collection/isSubscribed/${this.props.collection.id}`; | ||
request.get(url) | ||
.then(res => { | ||
const {isSubscribed} = res.body; | ||
this.setState({isSubscribed}); | ||
}, () => { | ||
this.setState({ | ||
message: { | ||
text: 'Something went wrong! Please try again later', | ||
type: 'danger' | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
render() { | ||
const messageComponent = this.state.message.text ? <Alert bsStyle={this.state.message.type} className="margin-top-1" onDismiss={this.handleAlertDismiss}>{this.state.message.text}</Alert> : null; | ||
const EntityTable = getEntityTable(this.props.collection.entityType); | ||
|
@@ -262,6 +323,30 @@ class CollectionPage extends React.Component { | |
<CollectionAttributes collection={this.props.collection}/> | ||
</Col> | ||
</Row> | ||
{ | ||
!this.state.isSubscribed && | ||
<Button | ||
bsSize="small" | ||
bsStyle="success" | ||
className="margin-bottom-d5" | ||
title="Subscribe" | ||
onClick={this.handleSubscribe} | ||
> | ||
Subscribe | ||
</Button> | ||
} | ||
{ | ||
this.state.isSubscribed && | ||
<Button | ||
bsSize="small" | ||
bsStyle="danger" | ||
className="margin-bottom-d5" | ||
title="Unsubscribe" | ||
onClick={this.handleUnsubscribe} | ||
> | ||
Unsubscribe | ||
</Button> | ||
} | ||
<EntityTable{...propsForTable}/> | ||
{messageComponent} | ||
<div className="margin-top-1 text-left"> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,7 @@ | |
|
||
import * as bootstrap from 'react-bootstrap'; | ||
import * as entityHelper from '../../../helpers/entity'; | ||
|
||
import React, {useEffect, useState} from 'react'; | ||
import EntityAnnotation from './annotation'; | ||
import EntityFooter from './footer'; | ||
import EntityImage from './image'; | ||
|
@@ -27,10 +27,10 @@ import EntityRelatedCollections from './related-collections'; | |
import EntityTitle from './title'; | ||
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; | ||
import PropTypes from 'prop-types'; | ||
import React from 'react'; | ||
import {kebabCase as _kebabCase} from 'lodash'; | ||
import {faPlus} from '@fortawesome/free-solid-svg-icons'; | ||
import {labelsForAuthor} from '../../../helpers/utils'; | ||
import request from 'superagent'; | ||
|
||
|
||
const {deletedEntityMessage, extractAttribute, getTypeAttribute, getEntityUrl, | ||
|
@@ -109,6 +109,38 @@ AuthorAttributes.propTypes = { | |
|
||
function AuthorDisplayPage({entity, identifierTypes, user}) { | ||
const urlPrefix = getEntityUrl(entity); | ||
const [isSubscribed, setIsSubscribed] = useState(false); | ||
useEffect(() => { | ||
request.get(`/subscription/entity/isSubscribed/${entity.bbid}`).then(response => { | ||
if (response.body.isSubscribed) { | ||
setIsSubscribed(true); | ||
} | ||
}); | ||
}); | ||
function handleUnsubscribe(bbid) { | ||
const submissionUrl = '/subscription/unsubscribe/entity'; | ||
request.post(submissionUrl) | ||
.send({bbid}) | ||
.then((res) => { | ||
setIsSubscribed(false); | ||
}, (error) => { | ||
// eslint-disable-next-line no-console | ||
console.log('error thrown'); | ||
}); | ||
} | ||
function handleSubscribe(bbid) { | ||
const submissionUrl = '/subscription/subscribe/entity'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this URL structure would be more appropriate and "RESTy": That would also work for collections: That means a fair amount of refactoring on the routes, I know, sorry about that :/ |
||
request.post(submissionUrl) | ||
.send({bbid}) | ||
.then((res) => { | ||
setIsSubscribed(true); | ||
}, (error) => { | ||
// eslint-disable-next-line no-console | ||
console.log('error thrown'); | ||
}); | ||
} | ||
|
||
/* eslint-disable react/jsx-no-bind */ | ||
return ( | ||
<div> | ||
<Row className="entity-display-background"> | ||
|
@@ -124,6 +156,26 @@ function AuthorDisplayPage({entity, identifierTypes, user}) { | |
<AuthorAttributes author={entity}/> | ||
</Col> | ||
</Row> | ||
{ | ||
!isSubscribed && | ||
<Button | ||
bsStyle="success" | ||
className="margin-top-d15" | ||
onClick={() => handleSubscribe(entity.bbid)} | ||
> | ||
Subscribe | ||
</Button> | ||
} | ||
{ | ||
isSubscribed && | ||
<Button | ||
bsStyle="danger" | ||
className="margin-top-d15" | ||
onClick={() => handleUnsubscribe(entity.bbid)} | ||
> | ||
Unsubscribe | ||
</Button> | ||
} | ||
<EntityAnnotation entity={entity}/> | ||
{!entity.deleted && | ||
<React.Fragment> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I can tell, all this code (
handleSubscribe
,handleUnsubscribe
,setIsSubscribed
and the buttons that go with it) is duplicated for each display page. Instead it would be better if the whole subscription part was a separate component that can then be added to the relevant pages.You'll probably want to pass the submissionUrl as a prop to make it more flexible.
We might end up with separate CollectionSubscription and an EntitySubscription components if they require very different implementations.