Skip to content

Commit 7bcdeeb

Browse files
conversation
1 parent 3880aff commit 7bcdeeb

File tree

21 files changed

+1302
-5
lines changed

21 files changed

+1302
-5
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
from datetime import datetime
2+
from enum import auto
3+
from typing import TypeAlias
4+
from uuid import UUID
5+
6+
from conftest import UserID
7+
from models_library.groups import GroupID
8+
from models_library.projects import ProjectID
9+
from pydantic import BaseModel, ConfigDict
10+
from simcore_postgres_database.models.conversation_messages import (
11+
ConversationMessageType as PostgresConversationMessageType,
12+
)
13+
from simcore_postgres_database.models.conversations import (
14+
ConversationType as PostgresConversationType,
15+
)
16+
17+
from .products import ProductName
18+
from .utils.enums import StrAutoEnum
19+
20+
ConversationID: TypeAlias = UUID
21+
ConversationMessageID: TypeAlias = UUID
22+
23+
24+
class ConversationType(StrAutoEnum):
25+
PROJECT_STATIC = auto() # Static conversation for the project
26+
PROJECT_ANNOTATION = (
27+
auto()
28+
) # Something like sticky note, can be located anywhere in the pipeline UI
29+
30+
31+
class ConversationMessageType(StrAutoEnum):
32+
MESSAGE = auto()
33+
NOTIFICATION = (
34+
auto()
35+
) # Special type of message used for storing notifications in the conversation
36+
37+
38+
assert [member.value for member in ConversationType] == [
39+
member.value for member in PostgresConversationType
40+
] # nosec
41+
assert [member.value for member in ConversationMessageType] == [
42+
member.value for member in PostgresConversationMessageType
43+
] # nosec
44+
45+
46+
#
47+
# DB
48+
#
49+
50+
51+
class ConversationDB(BaseModel):
52+
conversation_id: ConversationID
53+
product_name: ProductName
54+
name: str
55+
project_uuid: ProjectID | None
56+
user_id: UserID
57+
type: ConversationType
58+
59+
# states
60+
created: datetime
61+
modified: datetime
62+
63+
model_config = ConfigDict(from_attributes=True)
64+
65+
66+
class ConversationMessageDB(BaseModel):
67+
message_id: ConversationMessageID
68+
conversation_id: ConversationID
69+
user_group_id: GroupID
70+
content: str
71+
type: ConversationMessageType
72+
73+
# states
74+
created: datetime
75+
modified: datetime
76+
77+
model_config = ConfigDict(from_attributes=True)
78+
79+
80+
class ConversationPatchDB(BaseModel):
81+
name: str | None = None
82+
83+
84+
class ConversationMessagePatchDB(BaseModel):
85+
content: str | None = None
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
"""add conversations
2+
3+
Revision ID: 5921537e608d
4+
Revises: 742123f0933a
5+
Create Date: 2025-04-24 11:25:11.868406+00:00
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = "5921537e608d"
15+
down_revision = "742123f0933a"
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.create_table(
23+
"conversations",
24+
sa.Column(
25+
"conversation_id",
26+
postgresql.UUID(as_uuid=True),
27+
server_default=sa.text("gen_random_uuid()"),
28+
nullable=False,
29+
),
30+
sa.Column("name", sa.String(), nullable=False),
31+
sa.Column("project_uuid", sa.String(), nullable=True),
32+
sa.Column("user_id", sa.BigInteger(), nullable=True),
33+
sa.Column(
34+
"type",
35+
sa.Enum("PROJECT_STATIC", "PROJECT_ANNOTATION", name="conversationtype"),
36+
nullable=True,
37+
),
38+
sa.Column(
39+
"created",
40+
sa.DateTime(timezone=True),
41+
server_default=sa.text("now()"),
42+
nullable=False,
43+
),
44+
sa.Column(
45+
"modified",
46+
sa.DateTime(timezone=True),
47+
server_default=sa.text("now()"),
48+
nullable=False,
49+
),
50+
sa.ForeignKeyConstraint(
51+
["project_uuid"],
52+
["projects.uuid"],
53+
name="fk_projects_comments_project_uuid",
54+
onupdate="CASCADE",
55+
ondelete="CASCADE",
56+
),
57+
sa.ForeignKeyConstraint(
58+
["user_id"],
59+
["users.id"],
60+
name="fk_projects_comments_user_id",
61+
ondelete="SET NULL",
62+
),
63+
sa.PrimaryKeyConstraint("conversation_id"),
64+
)
65+
op.create_index(
66+
op.f("ix_conversations_project_uuid"),
67+
"conversations",
68+
["project_uuid"],
69+
unique=False,
70+
)
71+
op.create_table(
72+
"conversation_messages",
73+
sa.Column(
74+
"message_id",
75+
postgresql.UUID(as_uuid=True),
76+
server_default=sa.text("gen_random_uuid()"),
77+
nullable=False,
78+
),
79+
sa.Column("conversation_id", postgresql.UUID(as_uuid=True), nullable=False),
80+
sa.Column("user_group_id", sa.BigInteger(), nullable=False),
81+
sa.Column("content", sa.String(), nullable=False),
82+
sa.Column(
83+
"type",
84+
sa.Enum("MESSAGE", "NOTIFICATION", name="conversationmessagetype"),
85+
nullable=True,
86+
),
87+
sa.Column(
88+
"created",
89+
sa.DateTime(timezone=True),
90+
server_default=sa.text("now()"),
91+
nullable=False,
92+
),
93+
sa.Column(
94+
"modified",
95+
sa.DateTime(timezone=True),
96+
server_default=sa.text("now()"),
97+
nullable=False,
98+
),
99+
sa.ForeignKeyConstraint(
100+
["conversation_id"],
101+
["conversations.conversation_id"],
102+
name="fk_conversation_messages_project_uuid",
103+
onupdate="CASCADE",
104+
ondelete="CASCADE",
105+
),
106+
sa.ForeignKeyConstraint(
107+
["user_group_id"],
108+
["users.id"],
109+
name="fk_conversation_messages_user_id",
110+
ondelete="SET NULL",
111+
),
112+
sa.PrimaryKeyConstraint("message_id"),
113+
)
114+
op.create_index(
115+
op.f("ix_conversation_messages_conversation_id"),
116+
"conversation_messages",
117+
["conversation_id"],
118+
unique=False,
119+
)
120+
# ### end Alembic commands ###
121+
122+
123+
def downgrade():
124+
# ### commands auto generated by Alembic - please adjust! ###
125+
op.drop_index(
126+
op.f("ix_conversation_messages_conversation_id"),
127+
table_name="conversation_messages",
128+
)
129+
op.drop_table("conversation_messages")
130+
op.drop_index(op.f("ix_conversations_project_uuid"), table_name="conversations")
131+
op.drop_table("conversations")
132+
# ### end Alembic commands ###
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import enum
2+
3+
import sqlalchemy as sa
4+
from sqlalchemy.dialects.postgresql import UUID
5+
6+
from ._common import RefActions, column_created_datetime, column_modified_datetime
7+
from .base import metadata
8+
from .conversations import conversations
9+
from .users import users
10+
11+
12+
class ConversationMessageType(enum.Enum):
13+
MESSAGE = "MESSAGE"
14+
NOTIFICATION = "NOTIFICATION" # Special type of message used for storing notifications in the conversation
15+
16+
17+
conversation_messages = sa.Table(
18+
"conversation_messages",
19+
metadata,
20+
sa.Column(
21+
"message_id",
22+
UUID(as_uuid=True),
23+
nullable=False,
24+
primary_key=True,
25+
server_default=sa.text("gen_random_uuid()"),
26+
),
27+
sa.Column(
28+
"conversation_id",
29+
UUID(as_uuid=True),
30+
sa.ForeignKey(
31+
conversations.c.conversation_id,
32+
name="fk_conversation_messages_project_uuid",
33+
ondelete=RefActions.CASCADE,
34+
onupdate=RefActions.CASCADE,
35+
),
36+
index=True,
37+
nullable=False,
38+
),
39+
# NOTE: if the user primary group ID gets deleted, it sets to null which should be interpreted as "unknown" user
40+
sa.Column(
41+
"user_group_id",
42+
sa.BigInteger,
43+
sa.ForeignKey(
44+
users.c.id,
45+
name="fk_conversation_messages_user_id",
46+
ondelete=RefActions.SET_NULL,
47+
),
48+
doc="user primary group ID who created the message",
49+
nullable=False,
50+
),
51+
sa.Column(
52+
"content",
53+
sa.String,
54+
nullable=False,
55+
),
56+
sa.Column(
57+
"type",
58+
sa.Enum(ConversationMessageType),
59+
doc="Classification of the node associated to this task",
60+
),
61+
column_created_datetime(timezone=True),
62+
column_modified_datetime(timezone=True),
63+
# indexes
64+
sa.Index(
65+
"idx_conversation_messages_created_desc",
66+
sa.desc("created"),
67+
),
68+
)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import enum
2+
3+
import sqlalchemy as sa
4+
from sqlalchemy.dialects.postgresql import UUID
5+
6+
from ._common import RefActions, column_created_datetime, column_modified_datetime
7+
from .base import metadata
8+
from .projects import projects
9+
from .users import users
10+
11+
12+
class ConversationType(enum.Enum):
13+
PROJECT_STATIC = "PROJECT_STATIC" # Static conversation for the project
14+
PROJECT_ANNOTATION = "PROJECT_ANNOTATION" # Something like sticky note, can be located anywhere in the pipeline UI
15+
16+
17+
conversations = sa.Table(
18+
"conversations",
19+
metadata,
20+
sa.Column(
21+
"conversation_id",
22+
UUID(as_uuid=True),
23+
nullable=False,
24+
primary_key=True,
25+
server_default=sa.text("gen_random_uuid()"),
26+
),
27+
sa.Column(
28+
"name",
29+
sa.String,
30+
nullable=False,
31+
doc="project reference for this table",
32+
),
33+
sa.Column(
34+
"project_uuid",
35+
sa.String,
36+
sa.ForeignKey(
37+
projects.c.uuid,
38+
name="fk_projects_comments_project_uuid",
39+
ondelete=RefActions.CASCADE,
40+
onupdate=RefActions.CASCADE,
41+
),
42+
index=True,
43+
nullable=True,
44+
),
45+
# NOTE: if the user gets deleted, it sets to null which should be interpreted as "unknown" user
46+
sa.Column(
47+
"user_id",
48+
sa.BigInteger,
49+
sa.ForeignKey(
50+
users.c.id,
51+
name="fk_projects_comments_user_id",
52+
ondelete=RefActions.SET_NULL,
53+
),
54+
doc="user who created the comment",
55+
nullable=True,
56+
),
57+
sa.Column(
58+
"type",
59+
sa.Enum(ConversationType),
60+
doc="Classification of the node associated to this task",
61+
),
62+
sa.Column(
63+
"product_name",
64+
sa.String,
65+
sa.ForeignKey(
66+
"products.name",
67+
onupdate=RefActions.CASCADE,
68+
ondelete=RefActions.CASCADE,
69+
name="fk_resource_tracker_license_packages_product_name",
70+
),
71+
nullable=False,
72+
doc="Product name identifier. If None, then the item is not exposed",
73+
),
74+
column_created_datetime(timezone=True),
75+
column_modified_datetime(timezone=True),
76+
)

packages/postgres-database/src/simcore_postgres_database/models/projects.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,9 +173,7 @@ class ProjectType(enum.Enum):
173173
### INDEXES ----------------------------
174174
sa.Index(
175175
"idx_projects_last_change_date_desc",
176-
"last_change_date",
177-
postgresql_using="btree",
178-
postgresql_ops={"last_change_date": "DESC"},
176+
sa.desc("last_change_date"),
179177
),
180178
)
181179

services/web/server/src/simcore_service_webserver/application.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from .api_keys.plugin import setup_api_keys
1515
from .application_settings import get_application_settings, setup_settings
1616
from .catalog.plugin import setup_catalog
17+
from .conversations.plugin import setup_conversations
1718
from .db.plugin import setup_db
1819
from .db_listener.plugin import setup_db_listener
1920
from .diagnostics.plugin import setup_diagnostics, setup_profiling_middleware
@@ -140,6 +141,9 @@ def create_application() -> web.Application:
140141
# projects
141142
setup_projects(app)
142143

144+
# conversations
145+
setup_conversations(app)
146+
143147
# licenses
144148
setup_licenses(app)
145149

0 commit comments

Comments
 (0)