Skip to content

Commit 9066ecf

Browse files
committed
Entry lock as table
1 parent 7c77591 commit 9066ecf

5 files changed

Lines changed: 176 additions & 96 deletions

File tree

alembic/versions/5d4ec1330a98_entry_lock.py

Lines changed: 0 additions & 36 deletions
This file was deleted.
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Entry lock
2+
3+
Revision ID: f909b4acb52f
4+
Revises: 245914e1ddf9
5+
Create Date: 2022-09-05 10:25:46.047276
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import postgresql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = 'f909b4acb52f'
14+
down_revision = '245914e1ddf9'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade():
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.create_table('journal_entry_locks',
22+
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
23+
sa.Column('journal_entry_id', postgresql.UUID(as_uuid=True), nullable=True),
24+
sa.Column('locked_by', sa.String(), nullable=False),
25+
sa.Column('locked_at', sa.DateTime(timezone=True), server_default=sa.text("TIMEZONE('utc', statement_timestamp())"), nullable=False),
26+
sa.ForeignKeyConstraint(['journal_entry_id'], ['journal_entries.id'], name=op.f('fk_journal_entry_locks_journal_entry_id_journal_entries'), ondelete='CASCADE'),
27+
sa.PrimaryKeyConstraint('id', name=op.f('pk_journal_entry_locks')),
28+
sa.UniqueConstraint('id', name=op.f('uq_journal_entry_locks_id')),
29+
sa.UniqueConstraint('journal_entry_id', name=op.f('uq_journal_entry_locks_journal_entry_id'))
30+
)
31+
op.create_index(op.f('ix_journal_entry_locks_locked_at'), 'journal_entry_locks', ['locked_at'], unique=False)
32+
op.create_index(op.f('ix_journal_entry_locks_locked_by'), 'journal_entry_locks', ['locked_by'], unique=False)
33+
op.alter_column('journal_ttls', 'journal_id',
34+
existing_type=postgresql.UUID(),
35+
nullable=True)
36+
# ### end Alembic commands ###
37+
38+
39+
def downgrade():
40+
# ### commands auto generated by Alembic - please adjust! ###
41+
op.alter_column('journal_ttls', 'journal_id',
42+
existing_type=postgresql.UUID(),
43+
nullable=False)
44+
op.drop_index(op.f('ix_journal_entry_locks_locked_by'), table_name='journal_entry_locks')
45+
op.drop_index(op.f('ix_journal_entry_locks_locked_at'), table_name='journal_entry_locks')
46+
op.drop_table('journal_entry_locks')
47+
# ### end Alembic commands ###

spire/journal/actions.py

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""
22
Journal-related actions in Spire
33
"""
4+
from asyncio import streams
45
from datetime import date, timedelta, datetime
56
import calendar
67
import json
@@ -37,6 +38,7 @@
3738
from .models import (
3839
Journal,
3940
JournalEntry,
41+
JournalEntryLock,
4042
JournalEntryTag,
4143
JournalPermissions,
4244
HolderType,
@@ -484,44 +486,45 @@ async def journal_statistics(
484486

485487
async def create_journal_entry(
486488
db_session: Session,
489+
journal: Journal,
487490
entry_request: CreateJournalEntryRequest,
488-
user_group_id_list: list = None,
489-
locked_by: Optional[str] = None,
490-
) -> JournalEntry:
491+
locked_by: str,
492+
) -> Tuple[JournalEntry, JournalEntryLock]:
491493
"""
492494
Creates an entry in a given journal. Raises InvalidJournalSpec error if the journal is
493495
misspecified in the creation request, and raises a JournalNotFound error if no such journal
494496
is found in the database.
495497
"""
496-
journal = await find_journal(
497-
db_session=db_session,
498-
journal_spec=entry_request.journal_spec,
499-
user_group_id_list=user_group_id_list,
500-
)
498+
commit_list = []
501499

500+
entry_id = uuid4()
502501
entry = JournalEntry(
502+
id=entry_id,
503503
journal_id=journal.id,
504504
title=entry_request.title,
505505
content=entry_request.content,
506506
context_id=entry_request.context_id,
507507
context_url=entry_request.context_url,
508508
context_type=entry_request.context_type,
509509
created_at=entry_request.created_at,
510-
locked_by=locked_by,
511510
)
512-
db_session.add(entry)
513-
db_session.commit()
511+
commit_list.append(entry)
512+
513+
entry_lock = JournalEntryLock(journal_entry_id=entry_id, locked_by=locked_by)
514+
commit_list.append(entry_lock)
514515

515516
if entry_request.tags is not None:
516517
tags = [
517518
JournalEntryTag(journal_entry_id=entry.id, tag=tag)
518519
for tag in entry_request.tags
519520
if tag
520521
]
521-
db_session.add_all(tags)
522-
db_session.commit()
522+
commit_list.extend(tags)
523523

524-
return entry
524+
db_session.add_all(commit_list)
525+
db_session.commit()
526+
527+
return entry, entry_lock
525528

526529

527530
async def create_journal_entries_pack(
@@ -651,47 +654,70 @@ async def get_journal_entry(
651654

652655
async def get_journal_entry_with_tags(
653656
db_session: Session, journal_entry_id: UUID
654-
) -> Tuple[JournalEntry, List[JournalEntryTag]]:
657+
) -> Tuple[JournalEntry, List[JournalEntryTag], JournalEntryLock]:
655658
"""
656659
Returns a journal entry by its id with tags.
657660
"""
658661
objects = (
659-
db_session.query(JournalEntry, JournalEntryTag)
662+
db_session.query(JournalEntry, JournalEntryTag, JournalEntryLock)
660663
.join(
661664
JournalEntryTag,
662665
JournalEntryTag.journal_entry_id == JournalEntry.id,
663666
isouter=True,
664667
)
668+
.join(
669+
JournalEntryLock,
670+
JournalEntryLock.journal_entry_id == JournalEntry.id,
671+
isouter=True,
672+
)
665673
.filter(JournalEntry.id == journal_entry_id)
666674
.all()
667675
)
668676
if len(objects) == 0:
669677
raise EntryNotFound("Entry not found")
670678

671679
entry = objects[0][0]
680+
entry_lock = objects[0][2]
672681
tags: List[JournalEntryTag] = []
673682
for object in objects:
674683
if object[1] is not None:
675684
tags.append(object[1])
676-
return entry, tags
685+
686+
return entry, tags, entry_lock
677687

678688

679-
async def delete_journal_entry_lock(
689+
async def update_journal_entry(
680690
db_session: Session,
681-
journal_entry_id: UUID,
682-
) -> None:
691+
new_title: str,
692+
new_content: str,
693+
locked_by: str,
694+
journal_entry: JournalEntry,
695+
entry_lock: Optional[JournalEntryLock] = None,
696+
) -> Tuple[JournalEntry, JournalEntryLock]:
683697
"""
684-
Releases journal entry lock.
698+
Updates existing journal entry content.
699+
If lock does not exist, it creates new, otherwise update timestamp.
685700
"""
686-
updated_rows = (
687-
db_session.query(JournalEntry)
688-
.filter(JournalEntry.id == journal_entry_id)
689-
.update({JournalEntry.locked_by: None})
690-
)
701+
commit_list = []
702+
update_timestamp = datetime.utcnow()
703+
704+
journal_entry.title = new_title
705+
journal_entry.content = new_content
706+
journal_entry.updated_at = update_timestamp
707+
708+
commit_list.append(journal_entry)
709+
710+
if entry_lock is None:
711+
entry_lock = JournalEntryLock(
712+
journal_entry_id=journal_entry.id, locked_by=locked_by
713+
)
714+
entry_lock.locked_at = update_timestamp
715+
commit_list.append(entry_lock)
716+
717+
db_session.add_all(commit_list)
691718
db_session.commit()
692719

693-
if updated_rows == 0:
694-
raise EntryNotFound(f"There is no entry with id: {journal_entry_id} to unlock")
720+
return journal_entry, entry_lock
695721

696722

697723
async def delete_journal_entry(

spire/journal/api.py

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767
)
6868
from ..data import VersionResponse
6969
from ..middleware import BroodAuthMiddleware
70-
from .models import Journal, JournalEntryTag
70+
from .models import Journal, JournalEntryLock, JournalEntryTag
7171
from . import search
7272
from ..utils.settings import (
7373
DEFAULT_JOURNALS_ES_INDEX,
@@ -865,10 +865,10 @@ async def create_journal_entry(
865865
es_index = journal.search_index
866866

867867
try:
868-
journal_entry = await actions.create_journal_entry(
869-
db_session,
870-
creation_request,
871-
user_group_id_list=request.state.user_group_id_list,
868+
journal_entry, entry_lock = await actions.create_journal_entry(
869+
db_session=db_session,
870+
journal=journal,
871+
entry_request=creation_request,
872872
locked_by=request.state.user_id,
873873
)
874874
except actions.JournalNotFound:
@@ -917,7 +917,7 @@ async def create_journal_entry(
917917
updated_at=journal_entry.updated_at,
918918
context_url=journal_entry.context_url,
919919
context_type=journal_entry.context_type,
920-
locked_by=journal_entry.locked_by,
920+
locked_by=entry_lock.locked_by,
921921
)
922922

923923

@@ -1081,7 +1081,11 @@ async def get_entry(
10811081
)
10821082

10831083
try:
1084-
journal_entry, tag_objects = await actions.get_journal_entry_with_tags(
1084+
(
1085+
journal_entry,
1086+
tag_objects,
1087+
entry_lock,
1088+
) = await actions.get_journal_entry_with_tags(
10851089
db_session=db_session, journal_entry_id=entry_id
10861090
)
10871091
except actions.EntryNotFound:
@@ -1106,7 +1110,7 @@ async def get_entry(
11061110
updated_at=journal_entry.updated_at,
11071111
context_url=journal_entry.context_url,
11081112
context_type=journal_entry.context_type,
1109-
locked_by=journal_entry.locked_by,
1113+
locked_by=None if entry_lock is None else entry_lock.locked_by,
11101114
)
11111115

11121116

@@ -1198,7 +1202,11 @@ async def update_entry_content(
11981202
es_index = journal.search_index
11991203

12001204
try:
1201-
journal_entry, tag_objects = await actions.get_journal_entry_with_tags(
1205+
(
1206+
journal_entry,
1207+
tag_objects,
1208+
entry_lock,
1209+
) = await actions.get_journal_entry_with_tags(
12021210
db_session=db_session, journal_entry_id=entry_id
12031211
)
12041212
if journal_entry is None:
@@ -1211,22 +1219,26 @@ async def update_entry_content(
12111219
logger.error(f"Error listing journal entries: {str(e)}")
12121220
raise HTTPException(status_code=500)
12131221

1214-
if (
1215-
journal_entry.locked_by is not None
1216-
and journal_entry.locked_by != request.state.user_id
1217-
):
1222+
if entry_lock is not None and entry_lock.locked_by != request.state.user_id:
12181223
return JournalEntryContent(
12191224
title=journal_entry.title,
12201225
content=journal_entry.content,
12211226
tags=[tag.tag for tag in tag_objects],
1222-
locked_by=journal_entry.locked_by,
1227+
locked_by=entry_lock.locked_by,
12231228
)
12241229

1225-
journal_entry.title = api_request.title
1226-
journal_entry.content = api_request.content
1227-
journal_entry.locked_by = request.state.user_id
1228-
db_session.add(journal_entry)
1229-
db_session.commit()
1230+
try:
1231+
journal_entry, entry_lock = await actions.update_journal_entry(
1232+
db_session=db_session,
1233+
new_title=api_request.title,
1234+
new_content=api_request.content,
1235+
locked_by=request.state.user_id,
1236+
journal_entry=journal_entry,
1237+
entry_lock=entry_lock,
1238+
)
1239+
except Exception as e:
1240+
logger.error(f"Error updating journal entry: {str(e)}")
1241+
raise HTTPException(status_code=500)
12301242

12311243
updated_tag_objects: List[JournalEntryTag] = []
12321244
try:
@@ -1286,7 +1298,7 @@ async def update_entry_content(
12861298
title=journal_entry.title,
12871299
content=journal_entry.content,
12881300
tags=tags,
1289-
locked_by=request.state.user_id,
1301+
locked_by=entry_lock.locked_by,
12901302
)
12911303

12921304

@@ -1313,27 +1325,31 @@ async def delete_entry_lock(
13131325
{JournalEntryScopes.UPDATE},
13141326
)
13151327
try:
1316-
journal_entry, tag_objects = await actions.get_journal_entry_with_tags(
1328+
(
1329+
journal_entry,
1330+
tag_objects,
1331+
entry_lock,
1332+
) = await actions.get_journal_entry_with_tags(
13171333
db_session=db_session, journal_entry_id=entry_id
13181334
)
13191335
if journal_entry is None:
13201336
raise actions.EntryNotFound(
13211337
f"Entry not found with ID={entry_id} in journal with ID={journal_id}"
13221338
)
1323-
await actions.delete_journal_entry_lock(
1324-
db_session=db_session,
1325-
journal_entry_id=entry_id,
1326-
)
1339+
if entry_lock is not None:
1340+
db_session.delete(entry_lock)
1341+
db_session.commit()
13271342
except actions.EntryNotFound:
13281343
raise HTTPException(status_code=404, detail="Entry not found")
1329-
except Exception:
1344+
except Exception as e:
1345+
logger.error(f"Error listing journal entries: {str(e)}")
13301346
raise HTTPException(status_code=500)
13311347

13321348
return JournalEntryContent(
13331349
title=journal_entry.title,
13341350
content=journal_entry.content,
13351351
tags=[tag.tag for tag in tag_objects],
1336-
locked_by=journal_entry.locked_by,
1352+
locked_by=None,
13371353
)
13381354

13391355

0 commit comments

Comments
 (0)