diff --git a/config/conf.yml b/config/conf.yml index fc22322..4168638 100644 --- a/config/conf.yml +++ b/config/conf.yml @@ -28,4 +28,7 @@ logging: datefmt: "%Y-%m-%d %H:%M:%S" host: localhost port: 5921 -lbrynet: http://localhost:5279 \ No newline at end of file +lbrynet: http://localhost:5279 +notifications: + url: https://api.lbry.com/event/comment + auth_token: mytoken \ No newline at end of file diff --git a/src/database/comments_ddl.sql b/src/database/comments_ddl.sql index f488406..cebe0c2 100644 --- a/src/database/comments_ddl.sql +++ b/src/database/comments_ddl.sql @@ -48,3 +48,33 @@ ALTER TABLE COMMENT CREATE INDEX `claim_comment_index` ON `COMMENT` (`lbryclaimid`, `commentid`); CREATE INDEX `channel_comment_index` ON `COMMENT` (`channelid`, `commentid`); + +CREATE TABLE `COMMENTOPINION` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `commentid` char(64) COLLATE utf8mb4_unicode_ci NOT NULL, + `channelid` char(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `signature` char(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `signingts` varchar(22) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `timestamp` int NOT NULL, + `rating` tinyint DEFAULT '1', + + PRIMARY KEY (`id`), + KEY `comment_channel_fk` (`channelid`), + CONSTRAINT `comment_fk` FOREIGN KEY (`commentid`) REFERENCES `COMMENT` (`commentid`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `comment_channel_fk` FOREIGN KEY (`channelid`) REFERENCES `CHANNEL` (`claimid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci + +CREATE TABLE `CONTENTOPINION` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `claimid` char(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `channelid` char(40) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `signature` char(128) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `signingts` varchar(22) COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `timestamp` int NOT NULL, + `rating` tinyint DEFAULT '1', + + PRIMARY KEY (`id`), + KEY `comment_channel_fk` (`channelid`), + KEY `lbryclaimid` (`lbryclaimid`), + CONSTRAINT `comment_channel_fk` FOREIGN KEY (`channelid`) REFERENCES `CHANNEL` (`claimid`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci \ No newline at end of file diff --git a/src/database/models.py b/src/database/models.py index b35f122..f0ddfc2 100644 --- a/src/database/models.py +++ b/src/database/models.py @@ -51,6 +51,50 @@ class Comment(Model): ) +class CommentOpinion(Model): + id = BigAutoField(column_name='id', primary_key=True) + comment = ForeignKeyField( + backref='opinions', + column_name='commentid', + field='comment_id', + model=Comment, + null=True + ) + channel = ForeignKeyField( + backref='opinions', + column_name='channelid', + field='claim_id', + model=Channel, + null=True + ) + signature = FixedCharField(max_length=128, column_name='signature', null=True, unique=True) + signing_ts = TextField(column_name='signingts', null=True) + timestamp = IntegerField(column_name='timestamp') + rating = SmallIntegerField(column_name='rating', null=False) + + class Meta: + table_name = 'COMMENTOPINION' + + +class ContentOpinion(Model): + id = BigAutoField(column_name='id', primary_key=True) + channel = ForeignKeyField( + backref='opinions', + column_name='channelid', + field='claim_id', + model=Channel, + null=True + ) + claim_id = FixedCharField(max_length=40, column_name='lbryclaimid') + signature = FixedCharField(max_length=128, column_name='signature', null=True, unique=True) + signing_ts = TextField(column_name='signingts', null=True) + timestamp = IntegerField(column_name='timestamp') + rating = SmallIntegerField(column_name='rating', null=False, default=0) + + class Meta: + table_name = 'CONTENTOPINION' + + FIELDS = { 'comment': Comment.comment, 'comment_id': Comment.comment_id, @@ -66,17 +110,21 @@ FIELDS = { } -def comment_list(claim_id: str = None, parent_id: str = None, - top_level: bool = False, exclude_mode: str = None, - page: int = 1, page_size: int = 50, expressions=None, - select_fields: list = None, exclude_fields: list = None) -> dict: +def comment_list(claim_id: str = None, + parent_id: str = None, + top_level: bool = False, + exclude_mode: str = None, + page: int = 1, + page_size: int = 50, expressions=None, + select_fields: list = None, + exclude_fields: list = None) -> dict: fields = FIELDS.keys() if exclude_fields: fields -= set(exclude_fields) if select_fields: fields &= set(select_fields) attributes = [FIELDS[field] for field in fields] - query = Comment.select(*attributes) + query = Comment.select(fn.SUM(CommentOpinion.rating).alias('rating'), *attributes) # todo: allow this process to be more automated, so it can just be an expression if claim_id: @@ -97,6 +145,8 @@ def comment_list(claim_id: str = None, parent_id: str = None, total = query.count() query = (query .join(Channel, JOIN.LEFT_OUTER) + .join(CommentOpinion, JOIN.LEFT_OUTER) + .group_by(Comment.comment_id) .order_by(Comment.timestamp.desc()) .paginate(page, page_size)) items = [clean(item) for item in query.dicts()] @@ -203,6 +253,26 @@ def set_hidden_flag(comment_ids: typing.List[str], hidden=True) -> bool: return update.execute() > 0 +def create_comment_opinion(comment_id: str = None, + channel_id: str = None, + channel_name: str = None, + signature: str = None, + signing_ts: str = None, + rating: int = None) -> dict: + + channel, _ = Channel.get_or_create(name=channel_name, claim_id=channel_id) + + timestamp = int(time.time()) + new_comment_opinion = CommentOpinion.create( + commentid=comment_id, + channel=channel, + signature=signature, + signing_ts=signing_ts, + timestamp=timestamp, + rating=rating, + ) + return new_comment_opinion + if __name__ == '__main__': logger = logging.getLogger('peewee') logger.addHandler(logging.StreamHandler()) diff --git a/src/server/app.py b/src/server/app.py index 37e89da..6aecf9a 100644 --- a/src/server/app.py +++ b/src/server/app.py @@ -10,9 +10,9 @@ from aiohttp import web from peewee import * from src.server.handles import api_endpoint, get_api_endpoint -from src.database.models import Comment, Channel +from src.database.models import Comment, Channel, CommentOpinion, ContentOpinion -MODELS = [Comment, Channel] +MODELS = [Comment, Channel, CommentOpinion, ContentOpinion] logger = logging.getLogger(__name__) diff --git a/src/server/handles.py b/src/server/handles.py index a7fe43b..b8c8952 100644 --- a/src/server/handles.py +++ b/src/server/handles.py @@ -11,7 +11,7 @@ from src.server.external import send_notification from src.server.validation import validate_signature_from_claim from src.misc import clean_input_params, get_claim_from_id from src.server.errors import make_error, report_error -from src.database.models import Comment, Channel +from src.database.models import Comment, Channel, create_comment_opinion from src.database.models import get_comment from src.database.models import comment_list from src.database.models import create_comment @@ -19,7 +19,6 @@ from src.database.models import edit_comment from src.database.models import delete_comment from src.database.models import set_hidden_flag - logger = logging.getLogger(__name__) @@ -151,10 +150,10 @@ async def handle_hide_comments(app: web.Application, pieces: list, hide: bool = channel = claims[claim_id]['signing_channel'] piece = pieces_by_id[comment_id] is_valid_signature = validate_signature_from_claim( - claim=channel, - signature=piece['signature'], - signing_ts=piece['signing_ts'], - data=piece['comment_id'] + claim=channel, + signature=piece['signature'], + signing_ts=piece['signing_ts'], + data=piece['comment_id'] ) if not is_valid_signature: raise ValueError(f'could not validate signature on comment_id: {comment_id}') @@ -192,8 +191,8 @@ async def handle_edit_comment(app, comment: str = None, comment_id: str = None, # TODO: retrieve stake amounts for each channel & store in db async def handle_create_comment(app, comment: str = None, claim_id: str = None, - parent_id: str = None, channel_id: str = None, channel_name: str = None, - signature: str = None, signing_ts: str = None) -> dict: + parent_id: str = None, channel_id: str = None, channel_name: str = None, + signature: str = None, signing_ts: str = None) -> dict: with app['db'].atomic(): comment = create_comment( comment=comment, @@ -208,18 +207,37 @@ async def handle_create_comment(app, comment: str = None, claim_id: str = None, return comment +async def handle_create_comment_opinion(app, + comment_id: str = None, + channel_id: str = None, + channel_name: str = None, + signature: str = None, + signing_ts: str = None, + rating: int = None) -> dict: + with app['db'].atomic(): + return create_comment_opinion( + comment_id=comment_id, + channel_id=channel_id, + channel_name=channel_name, + signature=signature, + signing_ts=signing_ts, + rating=rating, + ) + + METHODS = { 'ping': ping, - 'get_claim_comments': handle_get_claim_comments, # this gets used + 'get_claim_comments': handle_get_claim_comments, # this gets used 'get_claim_hidden_comments': handle_get_claim_hidden_comments, # this gets used 'get_comment_ids': handle_get_comment_ids, - 'get_comments_by_id': handle_get_comments_by_id, # this gets used + 'get_comments_by_id': handle_get_comments_by_id, # this gets used 'get_channel_from_comment_id': handle_get_channel_from_comment_id, # this gets used - 'create_comment': handle_create_comment, # this gets used + 'create_comment': handle_create_comment, # this gets used + 'create_comment_opinion': handle_create_comment_opinion, # this gets used 'delete_comment': handle_abandon_comment, 'abandon_comment': handle_abandon_comment, # this gets used 'hide_comments': handle_hide_comments, # this gets used - 'edit_comment': handle_edit_comment # this gets used + 'edit_comment': handle_edit_comment # this gets used } diff --git a/test/http_requests/comment-request_local.http b/test/http_requests/comment-request_local.http new file mode 100644 index 0000000..98018c7 --- /dev/null +++ b/test/http_requests/comment-request_local.http @@ -0,0 +1,21 @@ +# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection). +# +# Following HTTP Request Live Templates are available: +# * 'gtrp' and 'gtr' create a GET request with or without query parameters; +# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body; +# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data); +POST http://localhost:5921/api +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "id": null, + "method": "get_claim_comments", + "params": { + "claim_id": "6d266af6c25c80fa2ac6cc7662921ad2e90a07e7" + } +} + + + +### diff --git a/test/http_requests/create-comment-opinion-local.http b/test/http_requests/create-comment-opinion-local.http new file mode 100644 index 0000000..50f743f --- /dev/null +++ b/test/http_requests/create-comment-opinion-local.http @@ -0,0 +1,24 @@ +# For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection). +# +# Following HTTP Request Live Templates are available: +# * 'gtrp' and 'gtr' create a GET request with or without query parameters; +# * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body; +# * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data); +POST http://localhost:5921/api +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "id": null, + "method": "create_comment_opinion", + "params": { + "comment_id": "34c7b8266e12eb4887da31ef5401c36484e5c450e979736c1caa19a0d2fd1470", + "channel_name": "@bbbbb", + "channel_id": "9cb713f01bf247a0e03170b5ed00d5161340c486", + "signing_ts": "1234567", + "rating": 1 + } +} + +### + diff --git a/test/http_requests/create_comment_local.http b/test/http_requests/create_comment_local.http index f73c488..0b0b4f8 100644 --- a/test/http_requests/create_comment_local.http +++ b/test/http_requests/create_comment_local.http @@ -4,7 +4,7 @@ # * 'gtrp' and 'gtr' create a GET request with or without query parameters; # * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body; # * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data); -POST http://localhost:5922/api +POST http://localhost:5921/api Content-Type: application/json {