今回はメッセージの投稿機能を作っていきます。
API
server/models/message.py
from datetime import datetime
from typing import List
from bson.objectid import ObjectId
from database import db
class Message:
_collection = db["messages"]
def __init__(
self,
content: str,
chat: str,
sender: str,
created_at: datetime = None,
updated_at: datetime = None,
):
self.content = content
self.chat = chat
self.sender = sender
self.read_by = []
if not created_at:
created_at = datetime.now()
self.created_at = created_at
if not updated_at:
updated_at = datetime.now()
self.updated_at = updated_at
def create(self):
data = vars(self)
data["chat"] = ObjectId(data["chat"])
data["sender"] = ObjectId(data["sender"])
return self._collection.insert_one(data)
@classmethod
def validate(cls, body, logged_in_user_id):
if not body.get("sender"):
return "投稿者は必須です。"
if body["sender"] != logged_in_user_id:
return "この操作は許可されていません。"
if not body.get("content", "").trim():
return "本文が指定されていません。"
return
server/apis/chats.py
...
@app.route("/api/chats/<chat_id>/messages", methods=["POST"])
@login_required()
def create_message(chat_id):
body = request.json
user_id = session["user"]["_id"]
error_message = Message.validate(body, user_id)
if error_message:
return error(error_message)
chat = Chat._collection.find_one({"_id": ObjectId(chat_id)})
if not chat or ObjectId(user_id) not in chat.get("users", []):
return error("チャットが存在しないか、参加していないチャットです")
message = Message(content=body["content"], chat=chat_id, sender=user_id)
message.create()
message = vars(message)
message["sender"] = session["user"]
return jsonify(message)
画面
画面から投稿できるようにしましょう。
チャット画面下部のテキストボックス内でエンターキーを押しても投稿できるようにしたいので、以前と同様に変換の確定では投稿しないような工夫をしておきます。
client/src/views/ChatView.vue
<template>
<div class="uk-margin-right uk-flex uk-flex-column uk-height-1-1">
<div>
<h3 class="uk-margin-top">チャット</h3>
</div>
<div class="uk-text-bold">チャットのタイトル</div>
<hr class="top-hr" />
<div class="uk-flex-1 uk-flex uk-flex-column content">
<div class="message mine uk-flex">
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image"></div>
<div class="container uk-flex uk-flex-column">
<span class="sender uk-text-muted uk-text-small">hoge</span>
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image"></div>
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image">
<img src="https://ui-avatars.com/api/?name=?" />
</div>
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message mine uk-flex">
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image"></div>
<div class="container uk-flex uk-flex-column">
<span class="sender uk-text-muted uk-text-small">hoge</span>
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image"></div>
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image">
<img src="https://ui-avatars.com/api/?name=?" />
</div>
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message mine uk-flex">
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image"></div>
<div class="container uk-flex uk-flex-column">
<span class="sender uk-text-muted uk-text-small">hoge</span>
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image"></div>
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image">
<img src="https://ui-avatars.com/api/?name=?" />
</div>
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message mine uk-flex">
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image"></div>
<div class="container uk-flex uk-flex-column">
<span class="sender uk-text-muted uk-text-small">hoge</span>
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image"></div>
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image">
<img src="https://ui-avatars.com/api/?name=?" />
</div>
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image"></div>
<div class="container uk-flex uk-flex-column">
<span class="sender uk-text-muted uk-text-small">hoge</span>
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image"></div>
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
<div class="message theirs uk-flex">
<div class="image">
<img src="https://ui-avatars.com/api/?name=?" />
</div>
<div class="container uk-flex uk-flex-column">
<span class="body uk-text-small">
hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi hi
</span>
</div>
</div>
</div>
<hr class="bottom-hr" />
<div class="uk-flex uk-flex-row uk-flex-middle uk-margin-bottom">
<input
type="text"
class="uk-input uk-flex-1 uk-margin-right"
v-model="text"
@keypress.prevent.enter.exact="enableSubmit"
@keyup.prevent.enter.exact="submit"
/>
<span
uk-icon="icon: comment; ratio: 1.5"
@click="onSendClick"
:class="{ clickable: isValid }"
:disabled="!isValid"
></span>
</div>
</div>
</template>
<script>
import { computed, ref } from "vue";
import { useRoute } from "vue-router";
import { useStore } from "vuex";
export default {
setup() {
const store = useStore();
const route = useRoute();
const text = ref("");
const canSubmit = ref(false);
const enableSubmit = () => {
canSubmit.value = true;
};
const _post = () => {
store
.dispatch("sendMessage", {
content: text.value,
sender: store.state.userLoggedIn._id,
chat: route.params["chatId"],
})
.then(() => (text.value = ""));
};
const submit = () => {
if (!canSubmit.value || !text.value.trim()) {
return;
}
_post();
canSubmit.value = false;
};
const onSendClick = () => {
if (!text.value.trim()) {
return;
}
_post();
canSubmit.value = false;
};
const isValid = computed(() => {
return Boolean(text.value);
});
return {
text,
enableSubmit,
submit,
onSendClick,
isValid,
};
},
};
</script>
<style scoped>
.content {
overflow-y: auto;
margin: 0;
padding: 5px;
}
.top-hr {
margin-bottom: 0;
}
.bottom-hr {
margin-top: 0;
}
.message {
align-items: flex-end;
padding-bottom: 4px;
}
.message.mine {
flex-direction: row-reverse;
}
.container {
max-width: 55%;
}
.body {
background-color: #f1f0f0;
padding: 6px 12px;
border-radius: 18px;
}
.mine .body {
background-color: #1fa2f1;
color: white;
}
.image {
height: 24px;
width: 24px;
margin-right: 7px;
}
.image img {
height: 100%;
border-radius: 50%;
vertical-align: bottom;
}
.clickable {
cursor: pointer;
}
</style>
これでメッセージを投稿できるようになりました。
次回はチャットページを開いたときにチャット情報とメッセージを読み込んで表示するようにしていきます。