|
1 | 1 | from db import db |
2 | 2 | from journalist_app import utils |
3 | | -from journalist_app.api2.shared import save_reply |
| 3 | +from journalist_app.api2.shared import json_version, save_reply |
4 | 4 | from journalist_app.api2.types import ( |
5 | 5 | Event, |
6 | 6 | EventResult, |
7 | 7 | EventStatusCode, |
8 | 8 | EventType, |
| 9 | + ItemUUID, |
9 | 10 | ) |
10 | | -from journalist_app.sessions import Session |
| 11 | +from journalist_app.sessions import Session, session |
11 | 12 | from models import Reply, Source, Submission |
12 | 13 | from redis import Redis |
13 | 14 | from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound |
@@ -53,7 +54,12 @@ def process(self, event: Event) -> EventResult: |
53 | 54 |
|
54 | 55 | handler = { |
55 | 56 | EventType.ITEM_DELETED: self.handle_item_deleted, |
| 57 | + EventType.ITEM_SEEN: self.handle_item_seen, |
56 | 58 | EventType.REPLY_SENT: self.handle_reply_sent, |
| 59 | + EventType.SOURCE_DELETED: self.handle_source_deleted, |
| 60 | + EventType.SOURCE_CONVERSATION_DELETED: self.handle_source_conversation_deleted, |
| 61 | + EventType.SOURCE_STARRED: self.handle_source_starred, |
| 62 | + EventType.SOURCE_UNSTARRED: self.handle_source_unstarred, |
57 | 63 | }[event.type] |
58 | 64 | except KeyError: |
59 | 65 | return EventResult( |
@@ -86,24 +92,11 @@ def mark_progress( |
86 | 92 |
|
87 | 93 | @staticmethod |
88 | 94 | def handle_item_deleted(event: Event) -> EventResult: |
89 | | - submission = Submission.query.filter( |
90 | | - Submission.uuid == event.target.item_uuid |
91 | | - ).one_or_none() |
92 | | - reply = Reply.query.filter(Reply.uuid == event.target.item_uuid).one_or_none() |
93 | | - |
94 | | - if submission and reply: |
95 | | - # Fail if we get unlucky and hit a UUID collision between the |
96 | | - # `Submission` and `Reply` tables. This is vanishingly unlikely, |
97 | | - # but SQLite can't enforce uniqueness between them. |
98 | | - raise MultipleResultsFound( |
99 | | - f"found {event.target.item_uuid} in both submissions and replies" |
100 | | - ) |
101 | | - |
102 | | - item = submission or reply |
| 95 | + item = find_item(event.target.item_uuid) |
103 | 96 | if item is None: |
104 | 97 | return EventResult( |
105 | 98 | event_id=event.id, |
106 | | - status=(EventStatusCode.NotFound, f"could not find item: {event.target.item_uuid}"), |
| 99 | + status=(EventStatusCode.Gone, None), |
107 | 100 | ) |
108 | 101 |
|
109 | 102 | utils.delete_file_object(item) |
@@ -135,3 +128,156 @@ def handle_reply_sent(event: Event) -> EventResult: |
135 | 128 | sources={source.uuid: source}, |
136 | 129 | items={reply.uuid: reply}, |
137 | 130 | ) |
| 131 | + |
| 132 | + @staticmethod |
| 133 | + def handle_source_deleted(event: Event) -> EventResult: |
| 134 | + try: |
| 135 | + source = Source.query.filter(Source.uuid == event.target.source_uuid).one() |
| 136 | + except NoResultFound: |
| 137 | + return EventResult( |
| 138 | + event_id=event.id, |
| 139 | + status=( |
| 140 | + EventStatusCode.Gone, |
| 141 | + None, |
| 142 | + ), |
| 143 | + ) |
| 144 | + |
| 145 | + current_version = json_version(source.to_api_v2()) |
| 146 | + if event.target.version != current_version: |
| 147 | + return EventResult( |
| 148 | + event_id=event.id, |
| 149 | + status=( |
| 150 | + EventStatusCode.Conflict, |
| 151 | + f"outdated source: expected {current_version}, got {event.target.version}", |
| 152 | + ), |
| 153 | + ) |
| 154 | + |
| 155 | + # Mark as deleted all the items in the source's collection |
| 156 | + deleted_items = {item.uuid: None for item in source.collection} |
| 157 | + |
| 158 | + utils.delete_collection(source.filesystem_id) |
| 159 | + return EventResult( |
| 160 | + event_id=event.id, |
| 161 | + status=(EventStatusCode.OK, None), |
| 162 | + sources={event.target.source_uuid: None}, |
| 163 | + items=deleted_items, |
| 164 | + ) |
| 165 | + |
| 166 | + @staticmethod |
| 167 | + def handle_source_conversation_deleted(event: Event) -> EventResult: |
| 168 | + try: |
| 169 | + source = Source.query.filter(Source.uuid == event.target.source_uuid).one() |
| 170 | + except NoResultFound: |
| 171 | + return EventResult( |
| 172 | + event_id=event.id, |
| 173 | + status=( |
| 174 | + EventStatusCode.Gone, |
| 175 | + None, |
| 176 | + ), |
| 177 | + ) |
| 178 | + |
| 179 | + current_version = json_version(source.to_api_v2()) |
| 180 | + if event.target.version != current_version: |
| 181 | + return EventResult( |
| 182 | + event_id=event.id, |
| 183 | + status=( |
| 184 | + EventStatusCode.Conflict, |
| 185 | + f"outdated source: expected {current_version}, got {event.target.version}", |
| 186 | + ), |
| 187 | + ) |
| 188 | + |
| 189 | + # Mark as deleted all the items in the source's collection |
| 190 | + deleted_items = {item.uuid: None for item in source.collection} |
| 191 | + |
| 192 | + utils.delete_source_files(source.filesystem_id) |
| 193 | + db.session.refresh(source) |
| 194 | + |
| 195 | + return EventResult( |
| 196 | + event_id=event.id, |
| 197 | + status=(EventStatusCode.OK, None), |
| 198 | + sources={source.uuid: source}, |
| 199 | + items=deleted_items, |
| 200 | + ) |
| 201 | + |
| 202 | + @staticmethod |
| 203 | + def handle_source_starred(event: Event) -> EventResult: |
| 204 | + try: |
| 205 | + source = Source.query.filter(Source.uuid == event.target.source_uuid).one() |
| 206 | + except NoResultFound: |
| 207 | + return EventResult( |
| 208 | + event_id=event.id, |
| 209 | + status=( |
| 210 | + EventStatusCode.NotFound, |
| 211 | + f"could not find source: {event.target.source_uuid}", |
| 212 | + ), |
| 213 | + ) |
| 214 | + |
| 215 | + utils.make_star_true(source.filesystem_id) |
| 216 | + db.session.commit() |
| 217 | + db.session.refresh(source) |
| 218 | + |
| 219 | + return EventResult( |
| 220 | + event_id=event.id, |
| 221 | + status=(EventStatusCode.OK, None), |
| 222 | + sources={source.uuid: source}, |
| 223 | + ) |
| 224 | + |
| 225 | + @staticmethod |
| 226 | + def handle_source_unstarred(event: Event) -> EventResult: |
| 227 | + try: |
| 228 | + source = Source.query.filter(Source.uuid == event.target.source_uuid).one() |
| 229 | + except NoResultFound: |
| 230 | + return EventResult( |
| 231 | + event_id=event.id, |
| 232 | + status=( |
| 233 | + EventStatusCode.NotFound, |
| 234 | + f"could not find source: {event.target.source_uuid}", |
| 235 | + ), |
| 236 | + ) |
| 237 | + |
| 238 | + utils.make_star_false(source.filesystem_id) |
| 239 | + db.session.commit() |
| 240 | + db.session.refresh(source) |
| 241 | + |
| 242 | + return EventResult( |
| 243 | + event_id=event.id, |
| 244 | + status=(EventStatusCode.OK, None), |
| 245 | + sources={source.uuid: source}, |
| 246 | + ) |
| 247 | + |
| 248 | + @staticmethod |
| 249 | + def handle_item_seen(event: Event) -> EventResult: |
| 250 | + item = find_item(event.target.item_uuid) |
| 251 | + if item is None: |
| 252 | + return EventResult( |
| 253 | + event_id=event.id, |
| 254 | + status=(EventStatusCode.NotFound, f"could not find item: {event.target.item_uuid}"), |
| 255 | + ) |
| 256 | + |
| 257 | + # Mark it as seen |
| 258 | + utils.mark_seen([item], session.get_user()) |
| 259 | + |
| 260 | + # Refresh and return |
| 261 | + source = item.source |
| 262 | + db.session.refresh(source) |
| 263 | + db.session.refresh(item) |
| 264 | + |
| 265 | + return EventResult( |
| 266 | + event_id=event.id, |
| 267 | + status=(EventStatusCode.OK, None), |
| 268 | + sources={source.uuid: source}, |
| 269 | + items={item.uuid: item}, |
| 270 | + ) |
| 271 | + |
| 272 | + |
| 273 | +def find_item(item_uuid: ItemUUID) -> Submission | Reply | None: |
| 274 | + submission = Submission.query.filter(Submission.uuid == item_uuid).one_or_none() |
| 275 | + reply = Reply.query.filter(Reply.uuid == item_uuid).one_or_none() |
| 276 | + |
| 277 | + if submission and reply: |
| 278 | + # Fail if we get unlucky and hit a UUID collision between the |
| 279 | + # `Submission` and `Reply` tables. This is vanishingly unlikely, |
| 280 | + # but SQLite can't enforce uniqueness between them. |
| 281 | + raise MultipleResultsFound(f"found {item_uuid} in both submissions and replies") |
| 282 | + |
| 283 | + return submission or reply |
0 commit comments