Spaces:
Runtime error
Runtime error
Commit
·
c13f601
1
Parent(s):
e4ce9c9
umm
Browse files- client/src/components/Chat/Chat.jsx +42 -18
- client/src/components/ChatFooter/ChatFooter.jsx +3 -7
- client/src/components/ChatMain/ChatMain.jsx +4 -4
- client/src/components/Contact/Contact.jsx +8 -1
- client/src/components/Contacts/Contacts.jsx +3 -3
- client/src/components/Search/Search.jsx +5 -2
- client/src/components/SideBar/SideBar.jsx +23 -3
- client/src/contexts/ChatProvider.jsx +2 -0
- server/index.ts +0 -399
- server/package.json +4 -4
- server/{auth.ts → src/auth.ts} +0 -0
- server/src/authHandler.ts +163 -0
- server/src/chatHandler.ts +175 -0
- server/{db.ts → src/db.ts} +0 -0
- server/src/index.ts +176 -0
- server/{middleware.ts → src/middleware.ts} +0 -0
- server/{schemas.ts → src/schemas.ts} +0 -0
- server/{scripts/init_db.ts → src/scripts/init_db.js} +13 -15
- server/src/scripts/init_db.ts +55 -0
- server/src/seeder.ts +69 -0
- server/tsconfig.json +1 -1
client/src/components/Chat/Chat.jsx
CHANGED
|
@@ -19,10 +19,10 @@ function Chat({ }) {
|
|
| 19 |
|
| 20 |
const [messages, setMessages] = useState([]);
|
| 21 |
const socket = useContext(SocketContext);
|
| 22 |
-
const {user} = useContext(UserContext);
|
| 23 |
const chatMainRef = useRef();
|
| 24 |
-
|
| 25 |
-
|
| 26 |
|
| 27 |
|
| 28 |
// to get Messages of currently opened chat
|
|
@@ -36,8 +36,8 @@ function Chat({ }) {
|
|
| 36 |
if (!socket) return;
|
| 37 |
// console.log()
|
| 38 |
socket.on("messages", (messages) => {
|
| 39 |
-
|
| 40 |
-
setMessages(messages);
|
| 41 |
});
|
| 42 |
|
| 43 |
socket.emit("get_messages", currentChat.id);
|
|
@@ -54,25 +54,44 @@ function Chat({ }) {
|
|
| 54 |
useEffect(() => {
|
| 55 |
if (socket) {
|
| 56 |
socket.on("receive_message", (message) => {
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
setMessages((prev) => [...prev, message]);
|
| 59 |
}
|
| 60 |
|
| 61 |
-
const ismessageOfContact = (contact) => (contact.id == message.send_to) || (contact.id == message.send_from);
|
| 62 |
-
var updatedContacts = contacts.map((contact) => (
|
| 63 |
-
ismessageOfContact(contact) ?
|
| 64 |
-
{ ...contact, last_message: message } : contact
|
| 65 |
-
));
|
| 66 |
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
// console.log(updatedContacts);
|
| 69 |
|
| 70 |
setContacts(updatedContacts);
|
| 71 |
-
|
| 72 |
// if message from a new Contact
|
| 73 |
-
const contact = contacts.find(contact=>contact.id==message.
|
| 74 |
-
if(!contact && message.
|
| 75 |
-
socket.emit("get_contact",message.
|
| 76 |
}
|
| 77 |
|
| 78 |
});
|
|
@@ -95,7 +114,12 @@ function Chat({ }) {
|
|
| 95 |
scrollToBottom();
|
| 96 |
}, [messages]);
|
| 97 |
|
| 98 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
|
| 100 |
// Until no chat is selected
|
| 101 |
if (Object.keys(currentChat).length === 0) {
|
|
@@ -110,7 +134,7 @@ function Chat({ }) {
|
|
| 110 |
// console.log("currentChat",currentChat);
|
| 111 |
// console.log("currentChat",currentChat.id);
|
| 112 |
// once a chat is selected
|
| 113 |
-
return (
|
| 114 |
<div className='chat'>
|
| 115 |
<ChatHeader name={currentChat.username} />
|
| 116 |
<ChatMain messages={messages} ref={chatMainRef} />
|
|
|
|
| 19 |
|
| 20 |
const [messages, setMessages] = useState([]);
|
| 21 |
const socket = useContext(SocketContext);
|
| 22 |
+
const { user } = useContext(UserContext);
|
| 23 |
const chatMainRef = useRef();
|
| 24 |
+
|
| 25 |
+
|
| 26 |
|
| 27 |
|
| 28 |
// to get Messages of currently opened chat
|
|
|
|
| 36 |
if (!socket) return;
|
| 37 |
// console.log()
|
| 38 |
socket.on("messages", (messages) => {
|
| 39 |
+
console.log("on-messages", messages);
|
| 40 |
+
setMessages(messages.reverse());
|
| 41 |
});
|
| 42 |
|
| 43 |
socket.emit("get_messages", currentChat.id);
|
|
|
|
| 54 |
useEffect(() => {
|
| 55 |
if (socket) {
|
| 56 |
socket.on("receive_message", (message) => {
|
| 57 |
+
console.log("receive_message:", message);
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
// if message is received in the current open chat
|
| 61 |
+
if (message.sender_id == currentChat.id || message.sender_id == user.id) {
|
| 62 |
setMessages((prev) => [...prev, message]);
|
| 63 |
}
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
+
const isMsgOfOpenedChat = (contact) => (contact.id == message.receiver_id) || (contact.id == message.sender_id);
|
| 67 |
+
const updateUnseenCount = (contact) => {
|
| 68 |
+
if (isMsgOfOpenedChat(contact)) {
|
| 69 |
+
return contact.unseen_count;
|
| 70 |
+
}
|
| 71 |
+
else {
|
| 72 |
+
return contact.unseen_count + 1;
|
| 73 |
+
}
|
| 74 |
+
};
|
| 75 |
+
var updatedContacts = contacts.map((contact) => {
|
| 76 |
+
var chat_data = { ...contact.chat_data };
|
| 77 |
+
// console.log(contact);
|
| 78 |
+
if (contact.id == message.receiver_id || contact.id == message.sender_id) {
|
| 79 |
+
chat_data.last_message = message;
|
| 80 |
+
}
|
| 81 |
+
chat_data.unseen_count = updateUnseenCount(contact);
|
| 82 |
+
|
| 83 |
+
return { ...contact, chat_data };
|
| 84 |
+
});
|
| 85 |
+
|
| 86 |
+
updatedContacts.sort((a, b) => Date.parse(b.chat_data.last_message.timestamp) - Date.parse(a.chat_data.last_message.timestamp));
|
| 87 |
// console.log(updatedContacts);
|
| 88 |
|
| 89 |
setContacts(updatedContacts);
|
| 90 |
+
|
| 91 |
// if message from a new Contact
|
| 92 |
+
const contact = contacts.find(contact => contact.id == message.sender_id);
|
| 93 |
+
if (!contact && message.sender_id != user.id) {
|
| 94 |
+
socket.emit("get_contact", message.sender_id);
|
| 95 |
}
|
| 96 |
|
| 97 |
});
|
|
|
|
| 114 |
scrollToBottom();
|
| 115 |
}, [messages]);
|
| 116 |
|
| 117 |
+
useEffect(() => {
|
| 118 |
+
if (messages.length === 0) return;
|
| 119 |
+
const unseen_messages = messages.filter(message => message.is_seen == 0);
|
| 120 |
+
console.log(unseen_messages);
|
| 121 |
+
socket.emit("mark_messages_seen", unseen_messages.map(msg => msg.id));
|
| 122 |
+
}, [messages]);
|
| 123 |
|
| 124 |
// Until no chat is selected
|
| 125 |
if (Object.keys(currentChat).length === 0) {
|
|
|
|
| 134 |
// console.log("currentChat",currentChat);
|
| 135 |
// console.log("currentChat",currentChat.id);
|
| 136 |
// once a chat is selected
|
| 137 |
+
return (
|
| 138 |
<div className='chat'>
|
| 139 |
<ChatHeader name={currentChat.username} />
|
| 140 |
<ChatMain messages={messages} ref={chatMainRef} />
|
client/src/components/ChatFooter/ChatFooter.jsx
CHANGED
|
@@ -12,11 +12,6 @@ function ChatFooter({
|
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
-
useEffect(() => {
|
| 16 |
-
if (messageRef.current) {
|
| 17 |
-
messageRef.current.focus();
|
| 18 |
-
}
|
| 19 |
-
}) // risky this running all the time
|
| 20 |
|
| 21 |
const sendMessage = (message) => {
|
| 22 |
|
|
@@ -24,8 +19,9 @@ function ChatFooter({
|
|
| 24 |
|
| 25 |
|
| 26 |
const message_bundle = {
|
| 27 |
-
message,
|
| 28 |
-
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
if (socket) {
|
|
|
|
| 12 |
|
| 13 |
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
const sendMessage = (message) => {
|
| 17 |
|
|
|
|
| 19 |
|
| 20 |
|
| 21 |
const message_bundle = {
|
| 22 |
+
content:message,
|
| 23 |
+
receiver_id: currentChatId,
|
| 24 |
+
type:"text",
|
| 25 |
}
|
| 26 |
|
| 27 |
if (socket) {
|
client/src/components/ChatMain/ChatMain.jsx
CHANGED
|
@@ -10,13 +10,13 @@ const ChatMain = React.forwardRef(({
|
|
| 10 |
const { user } = useContext(UserContext);
|
| 11 |
|
| 12 |
const timeToReadable = (timestamp) => {
|
| 13 |
-
var d = new Date(timestamp
|
| 14 |
return d.toLocaleTimeString();
|
| 15 |
// return timestamp;
|
| 16 |
}
|
| 17 |
|
| 18 |
const isReceived = (message) => {
|
| 19 |
-
return user && message.
|
| 20 |
};
|
| 21 |
|
| 22 |
return (
|
|
@@ -26,8 +26,8 @@ const ChatMain = React.forwardRef(({
|
|
| 26 |
messages.map((message, idx) => (
|
| 27 |
<Message
|
| 28 |
key={idx}
|
| 29 |
-
message={message.
|
| 30 |
-
time={timeToReadable(message.
|
| 31 |
isReceived={isReceived(message)}
|
| 32 |
/>
|
| 33 |
))
|
|
|
|
| 10 |
const { user } = useContext(UserContext);
|
| 11 |
|
| 12 |
const timeToReadable = (timestamp) => {
|
| 13 |
+
var d = new Date(timestamp);
|
| 14 |
return d.toLocaleTimeString();
|
| 15 |
// return timestamp;
|
| 16 |
}
|
| 17 |
|
| 18 |
const isReceived = (message) => {
|
| 19 |
+
return user && message.sender_id != user.id;
|
| 20 |
};
|
| 21 |
|
| 22 |
return (
|
|
|
|
| 26 |
messages.map((message, idx) => (
|
| 27 |
<Message
|
| 28 |
key={idx}
|
| 29 |
+
message={message.content}
|
| 30 |
+
time={timeToReadable(message.timestamp)}
|
| 31 |
isReceived={isReceived(message)}
|
| 32 |
/>
|
| 33 |
))
|
client/src/components/Contact/Contact.jsx
CHANGED
|
@@ -7,9 +7,13 @@ function Contact({
|
|
| 7 |
lastmessage = "",
|
| 8 |
onClick,
|
| 9 |
isActive = false,
|
| 10 |
-
|
| 11 |
}) {
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
return (
|
| 15 |
<div className={(isActive ? "active" : "") + ' contact'} onClick={onClick}>
|
|
@@ -20,6 +24,9 @@ function Contact({
|
|
| 20 |
<p className={(is_new ? "new":"") + ' contact__lastmsg'}>{lastmessage}</p>
|
| 21 |
}
|
| 22 |
</div>
|
|
|
|
|
|
|
|
|
|
| 23 |
</div>
|
| 24 |
)
|
| 25 |
}
|
|
|
|
| 7 |
lastmessage = "",
|
| 8 |
onClick,
|
| 9 |
isActive = false,
|
| 10 |
+
unseen_count,
|
| 11 |
}) {
|
| 12 |
|
| 13 |
+
var is_new=false;
|
| 14 |
+
if(unseen_count>0){
|
| 15 |
+
is_new=true;
|
| 16 |
+
}
|
| 17 |
|
| 18 |
return (
|
| 19 |
<div className={(isActive ? "active" : "") + ' contact'} onClick={onClick}>
|
|
|
|
| 24 |
<p className={(is_new ? "new":"") + ' contact__lastmsg'}>{lastmessage}</p>
|
| 25 |
}
|
| 26 |
</div>
|
| 27 |
+
{unseen_count>0 && (
|
| 28 |
+
<span>{unseen_count}</span>
|
| 29 |
+
)}
|
| 30 |
</div>
|
| 31 |
)
|
| 32 |
}
|
client/src/components/Contacts/Contacts.jsx
CHANGED
|
@@ -53,9 +53,9 @@ function Contacts({ }) {
|
|
| 53 |
key={contact.id}
|
| 54 |
onClick={() => { setCurrentChat(contact) }}
|
| 55 |
profile_pic={"logo512.png"}
|
| 56 |
-
name={contact.
|
| 57 |
-
lastmessage={contact.last_message.
|
| 58 |
-
|
| 59 |
isActive={currentChat.id == contact.id}
|
| 60 |
/>
|
| 61 |
))
|
|
|
|
| 53 |
key={contact.id}
|
| 54 |
onClick={() => { setCurrentChat(contact) }}
|
| 55 |
profile_pic={"logo512.png"}
|
| 56 |
+
name={contact.id}
|
| 57 |
+
lastmessage={contact.chat_data.last_message ? contact.chat_data.last_message.content:null}
|
| 58 |
+
unseen_count={contact.chat_data.unseen_count ? contact.chat_data.unseen_count:null}
|
| 59 |
isActive={currentChat.id == contact.id}
|
| 60 |
/>
|
| 61 |
))
|
client/src/components/Search/Search.jsx
CHANGED
|
@@ -1,11 +1,14 @@
|
|
| 1 |
-
import React from 'react';
|
| 2 |
import "./Search.css";
|
| 3 |
import { MagnifyingGlassIcon } from '@heroicons/react/24/solid'
|
|
|
|
| 4 |
|
| 5 |
function Search() {
|
|
|
|
|
|
|
| 6 |
return (
|
| 7 |
<div className='search'>
|
| 8 |
-
<input type="text" placeholder='Search' className='search__input'/>
|
| 9 |
<MagnifyingGlassIcon className='search__icon' color='black' />
|
| 10 |
</div>
|
| 11 |
)
|
|
|
|
| 1 |
+
import React, { useContext } from 'react';
|
| 2 |
import "./Search.css";
|
| 3 |
import { MagnifyingGlassIcon } from '@heroicons/react/24/solid'
|
| 4 |
+
import { ChatContext } from '../../contexts/ChatProvider';
|
| 5 |
|
| 6 |
function Search() {
|
| 7 |
+
const { search, setSearch } = useContext(ChatContext);
|
| 8 |
+
|
| 9 |
return (
|
| 10 |
<div className='search'>
|
| 11 |
+
<input type="text" placeholder='Search' className='search__input' value={search} onChange={e=>setSearch(e.target.value)} />
|
| 12 |
<MagnifyingGlassIcon className='search__icon' color='black' />
|
| 13 |
</div>
|
| 14 |
)
|
client/src/components/SideBar/SideBar.jsx
CHANGED
|
@@ -11,8 +11,10 @@ function SideBar() {
|
|
| 11 |
const socket = useContext(SocketContext);
|
| 12 |
|
| 13 |
const {
|
|
|
|
| 14 |
setContacts,
|
| 15 |
// setLastMessages,
|
|
|
|
| 16 |
} = useContext(ChatContext);
|
| 17 |
|
| 18 |
useEffect(() => {
|
|
@@ -22,9 +24,9 @@ function SideBar() {
|
|
| 22 |
if (!socket) return;
|
| 23 |
socket.on("contacts", (contacts) => {
|
| 24 |
// console.log(contacts);
|
| 25 |
-
var newContacts = contacts;
|
| 26 |
-
|
| 27 |
-
newContacts.sort((a,b)=>b.last_message.send_at - a.last_message.send_at);
|
| 28 |
|
| 29 |
setContacts(newContacts); // set Contacts
|
| 30 |
// setLastMessages(contacts.map((contact) => contact.last_message)); // set Last messages
|
|
@@ -41,6 +43,24 @@ function SideBar() {
|
|
| 41 |
}
|
| 42 |
}, [socket]);
|
| 43 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
return (
|
| 45 |
|
| 46 |
<div className='side_bar'>
|
|
|
|
| 11 |
const socket = useContext(SocketContext);
|
| 12 |
|
| 13 |
const {
|
| 14 |
+
contacts,
|
| 15 |
setContacts,
|
| 16 |
// setLastMessages,
|
| 17 |
+
search,
|
| 18 |
} = useContext(ChatContext);
|
| 19 |
|
| 20 |
useEffect(() => {
|
|
|
|
| 24 |
if (!socket) return;
|
| 25 |
socket.on("contacts", (contacts) => {
|
| 26 |
// console.log(contacts);
|
| 27 |
+
var newContacts = contacts;
|
| 28 |
+
|
| 29 |
+
newContacts.sort((a, b) => b.last_message.send_at - a.last_message.send_at);
|
| 30 |
|
| 31 |
setContacts(newContacts); // set Contacts
|
| 32 |
// setLastMessages(contacts.map((contact) => contact.last_message)); // set Last messages
|
|
|
|
| 43 |
}
|
| 44 |
}, [socket]);
|
| 45 |
|
| 46 |
+
useEffect(() => {
|
| 47 |
+
|
| 48 |
+
if (!socket) return;
|
| 49 |
+
|
| 50 |
+
if (search.trim() == "") return;
|
| 51 |
+
|
| 52 |
+
socket.on("search_results",(contacts)=>{
|
| 53 |
+
setContacts(contacts);
|
| 54 |
+
})
|
| 55 |
+
socket.emit("search_contacts",search);
|
| 56 |
+
|
| 57 |
+
const cleanUp = () => {
|
| 58 |
+
socket.off("search_results");
|
| 59 |
+
}
|
| 60 |
+
return cleanUp;
|
| 61 |
+
}, [socket, search])
|
| 62 |
+
|
| 63 |
+
|
| 64 |
return (
|
| 65 |
|
| 66 |
<div className='side_bar'>
|
client/src/contexts/ChatProvider.jsx
CHANGED
|
@@ -6,12 +6,14 @@ export const ChatProvider = ({ children }) => {
|
|
| 6 |
const [contacts, setContacts] = useState([]);
|
| 7 |
const [currentChat, setCurrentChat] = useState({});
|
| 8 |
const [lastMessages, setLastMessages] = useState([]);
|
|
|
|
| 9 |
|
| 10 |
return (
|
| 11 |
<ChatContext.Provider value={{
|
| 12 |
contacts, setContacts,
|
| 13 |
currentChat, setCurrentChat,
|
| 14 |
lastMessages, setLastMessages,
|
|
|
|
| 15 |
}}>
|
| 16 |
{children}
|
| 17 |
</ChatContext.Provider>
|
|
|
|
| 6 |
const [contacts, setContacts] = useState([]);
|
| 7 |
const [currentChat, setCurrentChat] = useState({});
|
| 8 |
const [lastMessages, setLastMessages] = useState([]);
|
| 9 |
+
const [search,setSearch] = useState("");
|
| 10 |
|
| 11 |
return (
|
| 12 |
<ChatContext.Provider value={{
|
| 13 |
contacts, setContacts,
|
| 14 |
currentChat, setCurrentChat,
|
| 15 |
lastMessages, setLastMessages,
|
| 16 |
+
search,setSearch,
|
| 17 |
}}>
|
| 18 |
{children}
|
| 19 |
</ChatContext.Provider>
|
server/index.ts
DELETED
|
@@ -1,399 +0,0 @@
|
|
| 1 |
-
import express from "express";
|
| 2 |
-
import bodyParser from "body-parser";
|
| 3 |
-
import { getDB } from "./db.js";
|
| 4 |
-
import { lucia } from "./auth.js";
|
| 5 |
-
import { generateId, verifyRequestOrigin } from "lucia";
|
| 6 |
-
import { loginSchema, signUpSchema } from "./schemas.js";
|
| 7 |
-
import bcrypt from "bcrypt";
|
| 8 |
-
import dotenv from "dotenv";
|
| 9 |
-
import cors from "cors";
|
| 10 |
-
import mutler from "multer";
|
| 11 |
-
import { Server, Socket } from "socket.io";
|
| 12 |
-
import http from "http";
|
| 13 |
-
import path from "path";
|
| 14 |
-
|
| 15 |
-
dotenv.config();
|
| 16 |
-
|
| 17 |
-
const __dirname = path.resolve();
|
| 18 |
-
var db = getDB();
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
var app = express();
|
| 22 |
-
const server = http.createServer(app);
|
| 23 |
-
|
| 24 |
-
const allowedOrigins = process.env.ALLOWED_ORIGINS || 'http://localhost:3001'
|
| 25 |
-
const allowed_origins = allowedOrigins.split(",").map(item => item.trim());
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
app.use(cors({
|
| 29 |
-
credentials: true,
|
| 30 |
-
// origin: true,
|
| 31 |
-
origin: allowed_origins,
|
| 32 |
-
}));
|
| 33 |
-
|
| 34 |
-
// parse application/json
|
| 35 |
-
app.use(bodyParser.json());
|
| 36 |
-
|
| 37 |
-
// parse application/x-www-form-urlencoded
|
| 38 |
-
app.use(bodyParser.urlencoded({ extended: false }));
|
| 39 |
-
|
| 40 |
-
// app.use(express.urlencoded());
|
| 41 |
-
app.use(mutler().array(""));
|
| 42 |
-
|
| 43 |
-
// The entire build/web directory is statically served.
|
| 44 |
-
app.use(express.static(path.join(__dirname, "../client/build")));
|
| 45 |
-
|
| 46 |
-
// middleware
|
| 47 |
-
// app.use((req,res,next)=>{
|
| 48 |
-
// if(req.method==="GET"){
|
| 49 |
-
// return next();
|
| 50 |
-
// }
|
| 51 |
-
|
| 52 |
-
// const originHeader = req.headers.origin ?? null;
|
| 53 |
-
// const hostHeader = req.headers.host ?? null;
|
| 54 |
-
// if(!originHeader || !hostHeader || !verifyRequestOrigin(originHeader,[hostHeader])){
|
| 55 |
-
// return res.status(403).end();
|
| 56 |
-
// }
|
| 57 |
-
// return next();
|
| 58 |
-
// })
|
| 59 |
-
|
| 60 |
-
// middleware
|
| 61 |
-
app.use(async (req, res, next) => {
|
| 62 |
-
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
|
| 63 |
-
if (!sessionId) {
|
| 64 |
-
res.locals.user = null;
|
| 65 |
-
res.locals.session = null;
|
| 66 |
-
return next();
|
| 67 |
-
}
|
| 68 |
-
const { session, user } = await lucia.validateSession(sessionId);
|
| 69 |
-
if (session && session.fresh) {
|
| 70 |
-
res.appendHeader("Set-Cookie", lucia.createSessionCookie(session.id).serialize());
|
| 71 |
-
}
|
| 72 |
-
if (!session) {
|
| 73 |
-
res.appendHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
|
| 74 |
-
}
|
| 75 |
-
res.locals.session = session;
|
| 76 |
-
res.locals.user = user;
|
| 77 |
-
// console.log(res.locals.session);
|
| 78 |
-
// console.log(res.locals.user);
|
| 79 |
-
return next();
|
| 80 |
-
|
| 81 |
-
})
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
const isAuthAPIMiddleware = async (req, res, next) => {
|
| 85 |
-
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
|
| 86 |
-
// console.log(sessionId);
|
| 87 |
-
|
| 88 |
-
if (!sessionId) {
|
| 89 |
-
|
| 90 |
-
return res.status(403).send({ error: "unauthorized" });
|
| 91 |
-
}
|
| 92 |
-
const { session, user } = await lucia.validateSession(sessionId);
|
| 93 |
-
if (!session) {
|
| 94 |
-
return res.status(403).send({ error: "unauthorized" });
|
| 95 |
-
}
|
| 96 |
-
return next();
|
| 97 |
-
};
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
// app.get('/', (req, res) => {
|
| 101 |
-
// // res.cookie("msg","hmmmm");
|
| 102 |
-
// res.send('Hello World!');
|
| 103 |
-
// })
|
| 104 |
-
|
| 105 |
-
app.get("/session", (req, res) => {
|
| 106 |
-
if (!res.locals.user) {
|
| 107 |
-
return res.status(403).send();
|
| 108 |
-
}
|
| 109 |
-
return res.status(200).send(res.locals.user);
|
| 110 |
-
})
|
| 111 |
-
app.post("/signup", async (req, res) => {
|
| 112 |
-
|
| 113 |
-
// console.log(req.params);
|
| 114 |
-
// console.log(req.body);
|
| 115 |
-
const parsedData = signUpSchema.safeParse(req.body);
|
| 116 |
-
if (!parsedData.success) {
|
| 117 |
-
return res.status(403).json({ field_error: parsedData.error.flatten().fieldErrors }); // 403 is for validation error
|
| 118 |
-
}
|
| 119 |
-
|
| 120 |
-
var data = parsedData.data;
|
| 121 |
-
// check if password and confirm_password matchs
|
| 122 |
-
if (data.password != data.confirm_password) {
|
| 123 |
-
return res.status(403).json({
|
| 124 |
-
field_error: {
|
| 125 |
-
confirm_password: [
|
| 126 |
-
"password doesn't match"
|
| 127 |
-
]
|
| 128 |
-
}
|
| 129 |
-
});
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
// check if username is already used
|
| 133 |
-
var row = db.prepare("select * from users where username=?").get(data.username);
|
| 134 |
-
if (row) {
|
| 135 |
-
return res.status(403).json({
|
| 136 |
-
field_error: {
|
| 137 |
-
username: [
|
| 138 |
-
"username already used."
|
| 139 |
-
]
|
| 140 |
-
}
|
| 141 |
-
})
|
| 142 |
-
}
|
| 143 |
-
|
| 144 |
-
// save to data se
|
| 145 |
-
const userId = generateId(15);
|
| 146 |
-
const hashedPassword = await bcrypt.hash(data.password, 10);
|
| 147 |
-
|
| 148 |
-
await db.prepare("insert into users(id,name,username,password) values(?,?,?,?)").run(
|
| 149 |
-
userId,
|
| 150 |
-
data.name,
|
| 151 |
-
data.username,
|
| 152 |
-
hashedPassword
|
| 153 |
-
);
|
| 154 |
-
|
| 155 |
-
const session = await lucia.createSession(userId, {});
|
| 156 |
-
const sessionCookie = lucia.createSessionCookie(session.id);
|
| 157 |
-
|
| 158 |
-
res.cookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
|
| 159 |
-
|
| 160 |
-
res.json({
|
| 161 |
-
"status": "success",
|
| 162 |
-
"msg": "user created successfully",
|
| 163 |
-
});
|
| 164 |
-
|
| 165 |
-
})
|
| 166 |
-
|
| 167 |
-
app.post("/login", async (req, res) => {
|
| 168 |
-
console.log(req.body);
|
| 169 |
-
|
| 170 |
-
const parsedData = loginSchema.safeParse(req.body);
|
| 171 |
-
|
| 172 |
-
if (!parsedData.success) {
|
| 173 |
-
return res.status(403).json({
|
| 174 |
-
"field_error": parsedData.error.flatten().fieldErrors
|
| 175 |
-
})
|
| 176 |
-
}
|
| 177 |
-
|
| 178 |
-
// check if user exists
|
| 179 |
-
const row = db.prepare("select * from users where username=?").get(parsedData.data.username);
|
| 180 |
-
// console.log(row);
|
| 181 |
-
|
| 182 |
-
if (!row) {
|
| 183 |
-
return res.status(403).json({
|
| 184 |
-
"error": "Incorrect username or password"
|
| 185 |
-
})
|
| 186 |
-
}
|
| 187 |
-
|
| 188 |
-
// check password
|
| 189 |
-
if (!await bcrypt.compare(parsedData.data.password, row.password)) {
|
| 190 |
-
return res.status(403).json({
|
| 191 |
-
"error": "Incorrect username or password"
|
| 192 |
-
})
|
| 193 |
-
}
|
| 194 |
-
|
| 195 |
-
// save session
|
| 196 |
-
const session = await lucia.createSession(row.id, {});
|
| 197 |
-
const sessionCookie = lucia.createSessionCookie(session.id);
|
| 198 |
-
|
| 199 |
-
// console.log(sessionCookie.attributes);
|
| 200 |
-
res.cookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
|
| 201 |
-
|
| 202 |
-
res.json({
|
| 203 |
-
"status": "success",
|
| 204 |
-
"msg": "logged in successfully",
|
| 205 |
-
});
|
| 206 |
-
|
| 207 |
-
})
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
// get all contacts route
|
| 212 |
-
app.get("/contacts", isAuthAPIMiddleware, async (req, res) => {
|
| 213 |
-
const contacts = await db.prepare("select id,username from users where id!=?").all(res.locals.user.id);
|
| 214 |
-
// console.log(contacts);
|
| 215 |
-
res.json({ "contacts": contacts });
|
| 216 |
-
})
|
| 217 |
-
|
| 218 |
-
// get currentContact's messsages route
|
| 219 |
-
app.get("/messages/:userid", isAuthAPIMiddleware, async (req, res) => {
|
| 220 |
-
|
| 221 |
-
// req.query.id
|
| 222 |
-
|
| 223 |
-
var messages = await db.prepare(`select * from messages
|
| 224 |
-
where (send_to=? and send_from=?)
|
| 225 |
-
or
|
| 226 |
-
(send_to=? and send_from=?)`).all(
|
| 227 |
-
res.locals.user.id,
|
| 228 |
-
req.params.userid,
|
| 229 |
-
|
| 230 |
-
req.params.userid,
|
| 231 |
-
res.locals.user.id,
|
| 232 |
-
);
|
| 233 |
-
// const messages = await db.prepare("select * from messages").all();
|
| 234 |
-
|
| 235 |
-
res.json({ "messages": messages });
|
| 236 |
-
})
|
| 237 |
-
|
| 238 |
-
// Catch all. If we want to add pages later, then we should probably change this.
|
| 239 |
-
app.get("*", (_req, res) => {
|
| 240 |
-
res.sendFile(path.join(__dirname + "/../client/build/index.html"));
|
| 241 |
-
});
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
const io = new Server(server, {
|
| 245 |
-
cors: {
|
| 246 |
-
origin: allowed_origins,
|
| 247 |
-
methods: ["GET", "POST"],
|
| 248 |
-
credentials: true,
|
| 249 |
-
}
|
| 250 |
-
});
|
| 251 |
-
|
| 252 |
-
const isAuth = async (req: Request, res: Response, next) => {
|
| 253 |
-
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
|
| 254 |
-
// console.log(sessionId);
|
| 255 |
-
|
| 256 |
-
if (!sessionId) {
|
| 257 |
-
|
| 258 |
-
return next(new Error("error"));
|
| 259 |
-
}
|
| 260 |
-
const { session, user } = await lucia.validateSession(sessionId);
|
| 261 |
-
if (!session) {
|
| 262 |
-
// return res.end();
|
| 263 |
-
return next(new Error("error"));
|
| 264 |
-
}
|
| 265 |
-
|
| 266 |
-
return next();
|
| 267 |
-
};
|
| 268 |
-
|
| 269 |
-
io.engine.use(isAuth);
|
| 270 |
-
|
| 271 |
-
async function getLastMessage({ user_id, contact_id }: { user_id: string, contact_id: string }) {
|
| 272 |
-
|
| 273 |
-
var last_message = await db.prepare(`select * from messages
|
| 274 |
-
where (send_to=? and send_from=?)
|
| 275 |
-
or
|
| 276 |
-
(send_to=? and send_from=?)
|
| 277 |
-
ORDER BY send_at DESC
|
| 278 |
-
`).get(
|
| 279 |
-
user_id,
|
| 280 |
-
contact_id,
|
| 281 |
-
|
| 282 |
-
contact_id,
|
| 283 |
-
user_id,
|
| 284 |
-
);
|
| 285 |
-
|
| 286 |
-
// console.log(last_message);
|
| 287 |
-
if (last_message) {
|
| 288 |
-
return last_message;
|
| 289 |
-
}
|
| 290 |
-
else {
|
| 291 |
-
return {};
|
| 292 |
-
}
|
| 293 |
-
|
| 294 |
-
}
|
| 295 |
-
|
| 296 |
-
io.on("connection", async (socket) => {
|
| 297 |
-
console.log(`socket ${socket.id} connected`);
|
| 298 |
-
|
| 299 |
-
// add user and session to the socket.data object
|
| 300 |
-
const sessionId = lucia.readSessionCookie(socket.handshake.headers.cookie ?? "");
|
| 301 |
-
const { session, user } = await lucia.validateSession(sessionId as string);
|
| 302 |
-
socket.data.user = user;
|
| 303 |
-
socket.data.session = session;
|
| 304 |
-
|
| 305 |
-
socket.on("get_contacts", async () => {
|
| 306 |
-
|
| 307 |
-
// get all contacts to client
|
| 308 |
-
var contacts = await db.prepare("select id,username from users where id!=?").all(socket.data.user.id);
|
| 309 |
-
contacts = await Promise.all(contacts.map(async (contact: any) => {
|
| 310 |
-
return { ...contact, last_message: await getLastMessage({ user_id: socket.data.user.id, contact_id: contact.id }) }
|
| 311 |
-
}))
|
| 312 |
-
|
| 313 |
-
// console.log(contacts);
|
| 314 |
-
socket.emit("contacts", contacts);
|
| 315 |
-
// socket
|
| 316 |
-
})
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
socket.on("get_messages", async (contact_id) => {
|
| 321 |
-
var messages = await db.prepare(`select * from messages
|
| 322 |
-
where (send_to=? and send_from=?)
|
| 323 |
-
or
|
| 324 |
-
(send_to=? and send_from=?)
|
| 325 |
-
ORDER BY send_at ASC
|
| 326 |
-
`).all(
|
| 327 |
-
socket.data.user.id,
|
| 328 |
-
contact_id,
|
| 329 |
-
|
| 330 |
-
contact_id,
|
| 331 |
-
socket.data.user.id,
|
| 332 |
-
);
|
| 333 |
-
|
| 334 |
-
socket.emit("messages", messages);
|
| 335 |
-
});
|
| 336 |
-
|
| 337 |
-
socket.on("send_message", async (data) => {
|
| 338 |
-
// an event was received from the client
|
| 339 |
-
|
| 340 |
-
const msg_id = generateId(15);
|
| 341 |
-
const send_at = Math.floor(new Date().getTime() / 1000);
|
| 342 |
-
|
| 343 |
-
const stmt = db.prepare("insert into messages(id,message,send_to,send_from,send_at) values(?,?,?,?,?)");
|
| 344 |
-
|
| 345 |
-
await stmt.run(
|
| 346 |
-
msg_id,
|
| 347 |
-
data.message,
|
| 348 |
-
data.send_to,
|
| 349 |
-
socket.data.user.id,
|
| 350 |
-
send_at,
|
| 351 |
-
);
|
| 352 |
-
|
| 353 |
-
console.log("saved message:", data.message);
|
| 354 |
-
|
| 355 |
-
const sockets = await io.fetchSockets();
|
| 356 |
-
const message_bundle = {
|
| 357 |
-
...data,
|
| 358 |
-
"id": msg_id,
|
| 359 |
-
"send_to": data.send_to,
|
| 360 |
-
"send_from": socket.data.user.id,
|
| 361 |
-
"send_at": send_at,
|
| 362 |
-
"is_seen": 0,
|
| 363 |
-
};
|
| 364 |
-
|
| 365 |
-
socket.emit("receive_message", message_bundle);
|
| 366 |
-
sockets.forEach((client) => {
|
| 367 |
-
if (client.data.user.id == data.send_to) {
|
| 368 |
-
// console.log("heyy");
|
| 369 |
-
return socket.to(client.id).emit("receive_message", message_bundle);
|
| 370 |
-
}
|
| 371 |
-
})
|
| 372 |
-
|
| 373 |
-
});
|
| 374 |
-
|
| 375 |
-
socket.on("get_contact", async contact_id => {
|
| 376 |
-
const contact = await db.prepare("select * from users where id = ?").get(contact_id);
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
socket.emit("contact", {
|
| 380 |
-
...contact,
|
| 381 |
-
last_message: await getLastMessage({ user_id: socket.data.user.id, contact_id: contact_id }),
|
| 382 |
-
});
|
| 383 |
-
})
|
| 384 |
-
|
| 385 |
-
// upon disconnection
|
| 386 |
-
socket.on("disconnect", (reason) => {
|
| 387 |
-
console.log(`socket ${socket.id} disconnected due to ${reason}`);
|
| 388 |
-
});
|
| 389 |
-
});
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
const port = 3000;
|
| 396 |
-
|
| 397 |
-
server.listen(port, () => {
|
| 398 |
-
console.log(`Running at port ${port}`);
|
| 399 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
server/package.json
CHANGED
|
@@ -4,10 +4,10 @@
|
|
| 4 |
"main": "index.js",
|
| 5 |
"type": "module",
|
| 6 |
"scripts": {
|
| 7 |
-
"
|
| 8 |
-
"
|
| 9 |
-
"
|
| 10 |
-
"dbinit": "npx tsx scripts/init_db.ts",
|
| 11 |
"both": "cd .. && cd client && npm run build && cd .. && cd server && npm run dev",
|
| 12 |
"test": "echo \"Error: no test specified\" && exit 1"
|
| 13 |
},
|
|
|
|
| 4 |
"main": "index.js",
|
| 5 |
"type": "module",
|
| 6 |
"scripts": {
|
| 7 |
+
"dev": "npx tsx watch src/index.ts",
|
| 8 |
+
"server": "npx tsx src/index.ts",
|
| 9 |
+
"seed": "npx tsx src/seeder.ts",
|
| 10 |
+
"dbinit": "npx tsx src/scripts/init_db.ts",
|
| 11 |
"both": "cd .. && cd client && npm run build && cd .. && cd server && npm run dev",
|
| 12 |
"test": "echo \"Error: no test specified\" && exit 1"
|
| 13 |
},
|
server/{auth.ts → src/auth.ts}
RENAMED
|
File without changes
|
server/src/authHandler.ts
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { lucia } from "./auth.js";
|
| 2 |
+
import { generateId, Lucia } from "lucia";
|
| 3 |
+
import { loginSchema, signUpSchema } from "./schemas.js";
|
| 4 |
+
import bcrypt from "bcrypt";
|
| 5 |
+
import { Database } from "better-sqlite3";
|
| 6 |
+
import { Socket } from "socket.io";
|
| 7 |
+
import { getDB } from "./db.js";
|
| 8 |
+
|
| 9 |
+
var db: Database;
|
| 10 |
+
db = getDB();
|
| 11 |
+
|
| 12 |
+
// middleware
|
| 13 |
+
export const authCookieMiddleware = (async (req: any, res: any, next: any) => {
|
| 14 |
+
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
|
| 15 |
+
if (!sessionId) {
|
| 16 |
+
res.locals.user = null;
|
| 17 |
+
res.locals.session = null;
|
| 18 |
+
return next();
|
| 19 |
+
}
|
| 20 |
+
const { session, user } = await lucia.validateSession(sessionId);
|
| 21 |
+
if (session && session.fresh) {
|
| 22 |
+
res.appendHeader("Set-Cookie", lucia.createSessionCookie(session.id).serialize());
|
| 23 |
+
}
|
| 24 |
+
if (!session) {
|
| 25 |
+
res.appendHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
|
| 26 |
+
}
|
| 27 |
+
res.locals.session = session;
|
| 28 |
+
res.locals.user = user;
|
| 29 |
+
// console.log(res.locals.session);
|
| 30 |
+
// console.log(res.locals.user);
|
| 31 |
+
return next();
|
| 32 |
+
|
| 33 |
+
})
|
| 34 |
+
|
| 35 |
+
export const isAuthAPIMiddleware = async (req: any, res: any, next: any) => {
|
| 36 |
+
const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
|
| 37 |
+
// console.log(sessionId);
|
| 38 |
+
|
| 39 |
+
if (!sessionId) {
|
| 40 |
+
|
| 41 |
+
return res.status(403).send({ error: "unauthorized" });
|
| 42 |
+
}
|
| 43 |
+
const { session, user } = await lucia.validateSession(sessionId);
|
| 44 |
+
if (!session) {
|
| 45 |
+
return res.status(403).send({ error: "unauthorized" });
|
| 46 |
+
}
|
| 47 |
+
return next();
|
| 48 |
+
};
|
| 49 |
+
|
| 50 |
+
export const isAuthSocketMiddleware = async (socket: Socket, next: any) => {
|
| 51 |
+
const sessionId = lucia.readSessionCookie(socket.handshake.headers.cookie ?? "");
|
| 52 |
+
console.log(sessionId);
|
| 53 |
+
if (!sessionId) return next(new Error("error"));
|
| 54 |
+
|
| 55 |
+
const { session, user } = await lucia.validateSession(sessionId);
|
| 56 |
+
if (!session) {
|
| 57 |
+
// return res.end();
|
| 58 |
+
return next(new Error("error"));
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// add user and session to the socket.data object
|
| 62 |
+
socket.data.user = user;
|
| 63 |
+
socket.data.session = session;
|
| 64 |
+
|
| 65 |
+
return next();
|
| 66 |
+
};
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
export const signup_handler = async (req: any, res: any) => {
|
| 70 |
+
|
| 71 |
+
// console.log(req.params);
|
| 72 |
+
// console.log(req.body);
|
| 73 |
+
const parsedData = signUpSchema.safeParse(req.body);
|
| 74 |
+
if (!parsedData.success) {
|
| 75 |
+
return res.status(403).json({ field_error: parsedData.error.flatten().fieldErrors }); // 403 is for validation error
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
var data = parsedData.data;
|
| 79 |
+
// check if password and confirm_password matchs
|
| 80 |
+
if (data.password != data.confirm_password) {
|
| 81 |
+
return res.status(403).json({
|
| 82 |
+
field_error: {
|
| 83 |
+
confirm_password: [
|
| 84 |
+
"password doesn't match"
|
| 85 |
+
]
|
| 86 |
+
}
|
| 87 |
+
});
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
// check if username is already used
|
| 91 |
+
var row = db.prepare("select * from users where id=?").get(data.username);
|
| 92 |
+
if (row) {
|
| 93 |
+
return res.status(403).json({
|
| 94 |
+
field_error: {
|
| 95 |
+
username: [
|
| 96 |
+
"username already used."
|
| 97 |
+
]
|
| 98 |
+
}
|
| 99 |
+
})
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
// save to data se
|
| 103 |
+
const hashedPassword = await bcrypt.hash(data.password, 10);
|
| 104 |
+
|
| 105 |
+
await db.prepare("insert into users(id,name,password) values(?,?,?)").run(
|
| 106 |
+
data.username,
|
| 107 |
+
data.name,
|
| 108 |
+
hashedPassword
|
| 109 |
+
);
|
| 110 |
+
|
| 111 |
+
const session = await lucia.createSession(data.username, {});
|
| 112 |
+
const sessionCookie = lucia.createSessionCookie(session.id);
|
| 113 |
+
|
| 114 |
+
res.cookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
|
| 115 |
+
|
| 116 |
+
res.json({
|
| 117 |
+
"status": "success",
|
| 118 |
+
"msg": "user created successfully",
|
| 119 |
+
});
|
| 120 |
+
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
export const login_handler = (async (req: any, res: any) => {
|
| 124 |
+
// console.log(req.body);
|
| 125 |
+
|
| 126 |
+
const parsedData = loginSchema.safeParse(req.body);
|
| 127 |
+
|
| 128 |
+
if (!parsedData.success) {
|
| 129 |
+
return res.status(403).json({
|
| 130 |
+
"field_error": parsedData.error.flatten().fieldErrors
|
| 131 |
+
})
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
// check if user exists
|
| 135 |
+
const row: any = db.prepare("select * from users where id=?").get(parsedData.data.username);
|
| 136 |
+
// console.log(row);
|
| 137 |
+
|
| 138 |
+
if (!row) {
|
| 139 |
+
return res.status(403).json({
|
| 140 |
+
"error": "Incorrect username or password"
|
| 141 |
+
})
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
// check password
|
| 145 |
+
if (!await bcrypt.compare(parsedData.data.password, row.password)) {
|
| 146 |
+
return res.status(403).json({
|
| 147 |
+
"error": "Incorrect username or password"
|
| 148 |
+
})
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
// save session
|
| 152 |
+
const session = await lucia.createSession(row.id, {});
|
| 153 |
+
const sessionCookie = lucia.createSessionCookie(session.id);
|
| 154 |
+
|
| 155 |
+
// console.log(sessionCookie.attributes);
|
| 156 |
+
res.cookie(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
|
| 157 |
+
|
| 158 |
+
res.json({
|
| 159 |
+
"status": "success",
|
| 160 |
+
"msg": "logged in successfully",
|
| 161 |
+
});
|
| 162 |
+
|
| 163 |
+
})
|
server/src/chatHandler.ts
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { lucia } from "./auth.js";
|
| 2 |
+
import { generateId, Lucia } from "lucia";
|
| 3 |
+
import bcrypt from "bcrypt";
|
| 4 |
+
import { Database } from "better-sqlite3";
|
| 5 |
+
import { getDB } from "./db.js";
|
| 6 |
+
|
| 7 |
+
var db: Database;
|
| 8 |
+
db = getDB();
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
type MessageType = {
|
| 13 |
+
id: string,
|
| 14 |
+
content: string,
|
| 15 |
+
type: string,
|
| 16 |
+
timestamp: number,
|
| 17 |
+
sender_id: string,
|
| 18 |
+
receiver_id: string,
|
| 19 |
+
status: "sent" | "received" | "seen",
|
| 20 |
+
};
|
| 21 |
+
|
| 22 |
+
type ContactType = {
|
| 23 |
+
id: string,
|
| 24 |
+
name: string,
|
| 25 |
+
chat_data: {
|
| 26 |
+
last_message: MessageType,
|
| 27 |
+
unseen_count: number,
|
| 28 |
+
}
|
| 29 |
+
};
|
| 30 |
+
|
| 31 |
+
export function searchContacts(user_id: string, search_string: string) {
|
| 32 |
+
var contacts = db.prepare(`SELECT id,name FROM users WHERE id LIKE ? AND id!=?`)
|
| 33 |
+
.all("%" + search_string + "%",user_id);
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
// console.log(contacts);
|
| 37 |
+
contacts = contacts.map((contact: any) => {
|
| 38 |
+
var last_msg: any = getLastMessage(user_id, contact.id);
|
| 39 |
+
|
| 40 |
+
// console.log(user_id, contact.id, last_msg);
|
| 41 |
+
|
| 42 |
+
var chat_data: any = {};
|
| 43 |
+
if (last_msg.id) {
|
| 44 |
+
chat_data.last_message = last_msg;
|
| 45 |
+
chat_data.unseen_count = getUnseenCount(user_id, contact.id);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
return { ...contact, chat_data }
|
| 49 |
+
})
|
| 50 |
+
|
| 51 |
+
return contacts as ContactType[];
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
export function getContact(user_id: string, other_contact_id: string) {
|
| 56 |
+
var contact: any = {};
|
| 57 |
+
var user: any = db.prepare(`SELECT id,name from users
|
| 58 |
+
WHERE id=?
|
| 59 |
+
`).get(other_contact_id);
|
| 60 |
+
|
| 61 |
+
contact.id = user.id;
|
| 62 |
+
contact.name = user.name;
|
| 63 |
+
contact.chat_data = {
|
| 64 |
+
last_message: getLastMessage(user_id, other_contact_id),
|
| 65 |
+
unseen_count: getUnseenCount(user_id, other_contact_id),
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
return contact as ContactType;
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
export const getMyChats = (user_id: string) => {
|
| 72 |
+
// var contacts = db.prepare("SELECT send_to,send_from FROM messages WHERE send_to=? or send_from=?").all(user_id,user_id);
|
| 73 |
+
|
| 74 |
+
var chat_ids: any = db.prepare(`
|
| 75 |
+
SELECT Distinct(sender_id) as id FROM messages WHERE receiver_id=?
|
| 76 |
+
UNION
|
| 77 |
+
SELECT Distinct(receiver_id) as id FROM messages WHERE sender_id=?
|
| 78 |
+
`).all(user_id, user_id);
|
| 79 |
+
|
| 80 |
+
// get data of all
|
| 81 |
+
var contacts: any = [];
|
| 82 |
+
for (var i = 0; i < chat_ids.length; i++) {
|
| 83 |
+
contacts.push(getContact(user_id, chat_ids[i].id));
|
| 84 |
+
}
|
| 85 |
+
return contacts as ContactType[];
|
| 86 |
+
};
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
export function getLastMessage(user_id: string, other_contact_id: string) {
|
| 90 |
+
const msg_stmt = db.prepare(`SELECT id,content,type,MAX(timestamp) as timestamp,sender_id,receiver_id,status
|
| 91 |
+
FROM messages
|
| 92 |
+
WHERE sender_id in (?,?) AND receiver_id in (?,?)`)
|
| 93 |
+
|
| 94 |
+
const last_message = msg_stmt.get(user_id, other_contact_id, user_id, other_contact_id);
|
| 95 |
+
return last_message as MessageType;
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
export function getUnseenCount(user_id: string, other_contact_id: string) {
|
| 99 |
+
const stmt = db.prepare(`SELECT COUNT(id) as unseen_count FROM messages
|
| 100 |
+
WHERE sender_id=? AND receiver_id=? AND status!='seen'
|
| 101 |
+
`)
|
| 102 |
+
|
| 103 |
+
const unseen_count: any = stmt.get(other_contact_id, user_id);
|
| 104 |
+
return unseen_count.unseen_count;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
export function addMessage({
|
| 108 |
+
content,
|
| 109 |
+
type = "text",
|
| 110 |
+
sender_id,
|
| 111 |
+
receiver_id,
|
| 112 |
+
}: {
|
| 113 |
+
content: string,
|
| 114 |
+
type: string,
|
| 115 |
+
sender_id: string,
|
| 116 |
+
receiver_id: string,
|
| 117 |
+
|
| 118 |
+
}) {
|
| 119 |
+
|
| 120 |
+
type = type.toLowerCase();
|
| 121 |
+
const id = generateId(10);
|
| 122 |
+
const stmt = db.prepare(`INSERT INTO messages(id,content,type,sender_id,receiver_id) VALUES(?,?,?,?,?)`);
|
| 123 |
+
|
| 124 |
+
stmt.run(
|
| 125 |
+
id,
|
| 126 |
+
content,
|
| 127 |
+
type,
|
| 128 |
+
sender_id,
|
| 129 |
+
receiver_id
|
| 130 |
+
);
|
| 131 |
+
|
| 132 |
+
return db.prepare(`SELECT * FROM messages WHERE id=?`).get(id) as MessageType;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
export function markMessageSeen(user_id: string, msg_id: string) {
|
| 136 |
+
const stmt = db.prepare(`UPDATE messages SET status='seen' WHERE sender_id!=? AND id=?`);
|
| 137 |
+
stmt.run(user_id, msg_id);
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
export function getAllMessages(user_id: string, other_contact_id: string) {
|
| 141 |
+
const stmt = db.prepare(`SELECT * from messages
|
| 142 |
+
WHERE sender_id in (?,?) AND receiver_id in (?,?)
|
| 143 |
+
ORDER BY messages.timestamp DESC
|
| 144 |
+
`);
|
| 145 |
+
|
| 146 |
+
const msgs = stmt.all(user_id, other_contact_id, user_id, other_contact_id);
|
| 147 |
+
return msgs as MessageType[];
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
export function deleteChat(user_id: string, other_contact_id: string) {
|
| 151 |
+
db.prepare(`DELETE FROM message WHERE sender_id in (?,?) AND receiver_id in (?,?) `)
|
| 152 |
+
.run(user_id, other_contact_id, user_id, other_contact_id);
|
| 153 |
+
return true;
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
// console.log(searchContacts("user1","u"))
|
| 157 |
+
// console.log(getMyChats("user1"))
|
| 158 |
+
// console.log(getMyChats("user2"))
|
| 159 |
+
// console.log(getMyChats("user3"))
|
| 160 |
+
|
| 161 |
+
// console.log(getLastMessage("user1","user1"))
|
| 162 |
+
// console.log(getUnseenCount("user1", "user2"))
|
| 163 |
+
|
| 164 |
+
// // console.log(addMessage({
|
| 165 |
+
// // message: "new_msg",
|
| 166 |
+
// // type: "text",
|
| 167 |
+
// // send_from: "user3",
|
| 168 |
+
// // send_to: "wx6uv",
|
| 169 |
+
// // }))
|
| 170 |
+
|
| 171 |
+
// console.log(getUnseenCount("user1", "wx6uv"))
|
| 172 |
+
// // markMessageSeen("user1", "33w87");
|
| 173 |
+
// console.log(getAllMessages("user1", "wx6uv"))
|
| 174 |
+
|
| 175 |
+
// console.log(deleteChat('user2',"sblua"))
|
server/{db.ts → src/db.ts}
RENAMED
|
File without changes
|
server/src/index.ts
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import express from "express";
|
| 2 |
+
import bodyParser from "body-parser";
|
| 3 |
+
import { getDB } from "./db.js";
|
| 4 |
+
import { lucia } from "./auth.js";
|
| 5 |
+
import { generateId, verifyRequestOrigin } from "lucia";
|
| 6 |
+
import { loginSchema, signUpSchema } from "./schemas.js";
|
| 7 |
+
import bcrypt from "bcrypt";
|
| 8 |
+
import dotenv from "dotenv";
|
| 9 |
+
import cors from "cors";
|
| 10 |
+
import mutler from "multer";
|
| 11 |
+
import { Server, Socket } from "socket.io";
|
| 12 |
+
import http from "http";
|
| 13 |
+
import path from "path";
|
| 14 |
+
import { authCookieMiddleware, isAuthAPIMiddleware, isAuthSocketMiddleware, login_handler, signup_handler } from "./authHandler.js";
|
| 15 |
+
import {
|
| 16 |
+
getMyChats,
|
| 17 |
+
getLastMessage,
|
| 18 |
+
getUnseenCount,
|
| 19 |
+
|
| 20 |
+
searchContacts,
|
| 21 |
+
|
| 22 |
+
addMessage,
|
| 23 |
+
markMessageSeen,
|
| 24 |
+
getAllMessages,
|
| 25 |
+
getContact,
|
| 26 |
+
} from "./chatHandler.js";
|
| 27 |
+
|
| 28 |
+
dotenv.config();
|
| 29 |
+
|
| 30 |
+
const __dirname = path.resolve();
|
| 31 |
+
var db = getDB();
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
var app = express();
|
| 35 |
+
const server = http.createServer(app);
|
| 36 |
+
|
| 37 |
+
const allowedOrigins = process.env.ALLOWED_ORIGINS || 'http://localhost:3001'
|
| 38 |
+
const allowed_origins = allowedOrigins.split(",").map(item => item.trim());
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
app.use(cors({
|
| 42 |
+
credentials: true,
|
| 43 |
+
// origin: true,
|
| 44 |
+
origin: allowed_origins,
|
| 45 |
+
}));
|
| 46 |
+
|
| 47 |
+
// parse application/json
|
| 48 |
+
app.use(bodyParser.json());
|
| 49 |
+
|
| 50 |
+
// parse application/x-www-form-urlencoded
|
| 51 |
+
app.use(bodyParser.urlencoded({ extended: false }));
|
| 52 |
+
|
| 53 |
+
// app.use(express.urlencoded());
|
| 54 |
+
app.use(mutler().array(""));
|
| 55 |
+
|
| 56 |
+
// The entire build/web directory is statically served.
|
| 57 |
+
app.use(express.static(path.join(__dirname, "../client/build")));
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
app.use(authCookieMiddleware);
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
// http://localhost:3000/session
|
| 65 |
+
app.get("/session", (req, res) => {
|
| 66 |
+
if (!res.locals.user) {
|
| 67 |
+
return res.status(403).send();
|
| 68 |
+
}
|
| 69 |
+
return res.status(200).send(res.locals.user);
|
| 70 |
+
})
|
| 71 |
+
|
| 72 |
+
app.post("/signup", signup_handler);
|
| 73 |
+
app.post("/login", login_handler);
|
| 74 |
+
|
| 75 |
+
app.get("/test", (req,res)=>{
|
| 76 |
+
console.log(req);
|
| 77 |
+
// console.log(res);
|
| 78 |
+
res.send("ok");
|
| 79 |
+
});
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
// Catch all. If we want to add pages later, then we should probably change this.
|
| 83 |
+
app.get("*", (_req, res) => {
|
| 84 |
+
res.sendFile(path.join(__dirname + "/../client/build/index.html"));
|
| 85 |
+
});
|
| 86 |
+
|
| 87 |
+
// socket.io server
|
| 88 |
+
const io = new Server(server, {
|
| 89 |
+
cors: {
|
| 90 |
+
origin: allowed_origins,
|
| 91 |
+
methods: ["GET", "POST"],
|
| 92 |
+
credentials: true,
|
| 93 |
+
}
|
| 94 |
+
});
|
| 95 |
+
|
| 96 |
+
io.use(isAuthSocketMiddleware);
|
| 97 |
+
|
| 98 |
+
io.on("connection", async (socket) => {
|
| 99 |
+
console.log(`socket ${socket.id} connected`);
|
| 100 |
+
// client.emit("search_contacts","ankit")
|
| 101 |
+
socket.on("search_contacts",(search_string:string)=>{
|
| 102 |
+
const contacts = searchContacts(socket.data.user.id,search_string);
|
| 103 |
+
socket.emit("search_results",contacts);
|
| 104 |
+
})
|
| 105 |
+
|
| 106 |
+
//Old chats
|
| 107 |
+
socket.on("get_chats", async () => {
|
| 108 |
+
|
| 109 |
+
// get all chats of client
|
| 110 |
+
const chats = getMyChats(socket.data.user.id);
|
| 111 |
+
socket.emit("chats", chats);
|
| 112 |
+
})
|
| 113 |
+
|
| 114 |
+
socket.on("get_messages", async (chat_id) => {
|
| 115 |
+
var messages = getAllMessages(socket.data.user.id, chat_id);
|
| 116 |
+
// console.log(socket.data.user.id, chat_id);
|
| 117 |
+
// console.log(messages);
|
| 118 |
+
socket.emit("messages", messages);
|
| 119 |
+
});
|
| 120 |
+
|
| 121 |
+
socket.on("send_message", async (data) => {
|
| 122 |
+
// an event was received from the client
|
| 123 |
+
|
| 124 |
+
console.log(data);
|
| 125 |
+
const message = addMessage({
|
| 126 |
+
content: data.content,
|
| 127 |
+
type: data.type,
|
| 128 |
+
sender_id: socket.data.user.id,
|
| 129 |
+
receiver_id: data.receiver_id,
|
| 130 |
+
})
|
| 131 |
+
|
| 132 |
+
console.log("saved message:", message.content , "from:",message.sender_id);
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
const sockets = await io.fetchSockets();
|
| 137 |
+
const receivers = sockets.filter((client)=>client.data.user.id==data.receiver_id);
|
| 138 |
+
|
| 139 |
+
receivers.forEach((client)=>{
|
| 140 |
+
client.emit("receive_message",message);
|
| 141 |
+
})
|
| 142 |
+
socket.emit("receive_message",message);
|
| 143 |
+
|
| 144 |
+
});
|
| 145 |
+
|
| 146 |
+
socket.on("mark_messages_seen", async (msg_ids: any) => {
|
| 147 |
+
// console.log(messages.length)
|
| 148 |
+
|
| 149 |
+
msg_ids.forEach((msg_id:any)=>{
|
| 150 |
+
markMessageSeen(socket.data.user.id,msg_id);
|
| 151 |
+
})
|
| 152 |
+
|
| 153 |
+
})
|
| 154 |
+
|
| 155 |
+
socket.on("get_contact", async contact_id => {
|
| 156 |
+
const contact: any = getContact(socket.data.user.id,contact_id);
|
| 157 |
+
|
| 158 |
+
|
| 159 |
+
socket.emit("contact", contact);
|
| 160 |
+
})
|
| 161 |
+
|
| 162 |
+
// upon disconnection
|
| 163 |
+
socket.on("disconnect", (reason) => {
|
| 164 |
+
console.log(`socket ${socket.id} disconnected due to ${reason}`);
|
| 165 |
+
});
|
| 166 |
+
});
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
const port = 3000;
|
| 173 |
+
|
| 174 |
+
server.listen(port, () => {
|
| 175 |
+
console.log(`Running at port ${port}`);
|
| 176 |
+
});
|
server/{middleware.ts → src/middleware.ts}
RENAMED
|
File without changes
|
server/{schemas.ts → src/schemas.ts}
RENAMED
|
File without changes
|
server/{scripts/init_db.ts → src/scripts/init_db.js}
RENAMED
|
@@ -1,21 +1,16 @@
|
|
| 1 |
import database from "better-sqlite3";
|
| 2 |
import dotenv from "dotenv";
|
| 3 |
import fs from "fs";
|
| 4 |
-
|
| 5 |
dotenv.config();
|
| 6 |
-
|
| 7 |
-
const db_path = process.env.DB_NAME as string;
|
| 8 |
console.log(db_path);
|
| 9 |
fs.unlink(db_path, (err) => {
|
| 10 |
if (err) {
|
| 11 |
console.error(err);
|
| 12 |
}
|
| 13 |
else {
|
| 14 |
-
console.log(`deleted old database:\t${db_path}`)
|
| 15 |
-
|
| 16 |
-
|
| 17 |
const db = database(db_path);
|
| 18 |
-
|
| 19 |
db.exec(`
|
| 20 |
create table users(
|
| 21 |
id Text not null primary key,
|
|
@@ -24,7 +19,6 @@ fs.unlink(db_path, (err) => {
|
|
| 24 |
password varchar(20) NOT NULL
|
| 25 |
);
|
| 26 |
`);
|
| 27 |
-
|
| 28 |
db.exec(`
|
| 29 |
create table sessions(
|
| 30 |
id Text not null primary key,
|
|
@@ -33,20 +27,24 @@ fs.unlink(db_path, (err) => {
|
|
| 33 |
FOREIGN KEY (user_id) REFERENCES users(id)
|
| 34 |
);
|
| 35 |
`);
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
db.exec(`
|
| 38 |
create table messages(
|
| 39 |
id Text not null primary key,
|
| 40 |
send_at INTEGER NOT NULL,
|
| 41 |
message Text not null,
|
| 42 |
-
send_to TEXT NOT NULL,
|
| 43 |
send_from TEXT NOT NULL,
|
|
|
|
| 44 |
is_seen TINYINT DEFAULT 0,
|
| 45 |
-
FOREIGN KEY (
|
| 46 |
-
FOREIGN KEY (
|
| 47 |
);
|
| 48 |
`);
|
| 49 |
-
|
| 50 |
-
|
| 51 |
}
|
| 52 |
-
})
|
|
|
|
| 1 |
import database from "better-sqlite3";
|
| 2 |
import dotenv from "dotenv";
|
| 3 |
import fs from "fs";
|
|
|
|
| 4 |
dotenv.config();
|
| 5 |
+
const db_path = process.env.DB_NAME;
|
|
|
|
| 6 |
console.log(db_path);
|
| 7 |
fs.unlink(db_path, (err) => {
|
| 8 |
if (err) {
|
| 9 |
console.error(err);
|
| 10 |
}
|
| 11 |
else {
|
| 12 |
+
console.log(`deleted old database:\t${db_path}`);
|
|
|
|
|
|
|
| 13 |
const db = database(db_path);
|
|
|
|
| 14 |
db.exec(`
|
| 15 |
create table users(
|
| 16 |
id Text not null primary key,
|
|
|
|
| 19 |
password varchar(20) NOT NULL
|
| 20 |
);
|
| 21 |
`);
|
|
|
|
| 22 |
db.exec(`
|
| 23 |
create table sessions(
|
| 24 |
id Text not null primary key,
|
|
|
|
| 27 |
FOREIGN KEY (user_id) REFERENCES users(id)
|
| 28 |
);
|
| 29 |
`);
|
| 30 |
+
db.exec(`
|
| 31 |
+
create table chats(
|
| 32 |
+
id Text not null primary key,
|
| 33 |
+
member_id TEXT NOT NULL,
|
| 34 |
+
FOREIGN KEY (member_id) REFERENCES users(id)
|
| 35 |
+
);
|
| 36 |
+
`);
|
| 37 |
db.exec(`
|
| 38 |
create table messages(
|
| 39 |
id Text not null primary key,
|
| 40 |
send_at INTEGER NOT NULL,
|
| 41 |
message Text not null,
|
|
|
|
| 42 |
send_from TEXT NOT NULL,
|
| 43 |
+
chat_id TEXT NOT NULL,
|
| 44 |
is_seen TINYINT DEFAULT 0,
|
| 45 |
+
FOREIGN KEY (send_from) REFERENCES users(id),
|
| 46 |
+
FOREIGN KEY (chat_id) REFERENCES users(id)
|
| 47 |
);
|
| 48 |
`);
|
|
|
|
|
|
|
| 49 |
}
|
| 50 |
+
});
|
server/src/scripts/init_db.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import database from "better-sqlite3";
|
| 2 |
+
import dotenv from "dotenv";
|
| 3 |
+
import fs from "fs";
|
| 4 |
+
|
| 5 |
+
dotenv.config();
|
| 6 |
+
|
| 7 |
+
const db_path = process.env.DB_NAME as string;
|
| 8 |
+
console.log(db_path);
|
| 9 |
+
fs.unlink(db_path, (err) => {
|
| 10 |
+
if (err) {
|
| 11 |
+
console.error(err);
|
| 12 |
+
}
|
| 13 |
+
else {
|
| 14 |
+
console.log(`deleted old database:\t${db_path}`)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
const db = database(db_path);
|
| 18 |
+
|
| 19 |
+
db.exec(`
|
| 20 |
+
create table users(
|
| 21 |
+
id Text not null PRIMARY KEY,
|
| 22 |
+
name varchar(40) NOT NULL,
|
| 23 |
+
password varchar(20) NOT NULL
|
| 24 |
+
);
|
| 25 |
+
`);
|
| 26 |
+
|
| 27 |
+
db.exec(`
|
| 28 |
+
create table sessions(
|
| 29 |
+
id Text not null PRIMARY KEY,
|
| 30 |
+
expires_at INTEGER NOT NULL,
|
| 31 |
+
user_id TEXT NOT NULL,
|
| 32 |
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
| 33 |
+
);
|
| 34 |
+
`);
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
db.exec(`
|
| 38 |
+
create table messages(
|
| 39 |
+
id Text not null primary key,
|
| 40 |
+
content Text not null,
|
| 41 |
+
type Text NOT NULL DEFAULT "text",
|
| 42 |
+
sender_id TEXT NOT NULL,
|
| 43 |
+
receiver_id TEXT NOT NULL,
|
| 44 |
+
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 45 |
+
status DEFAULT "sent", /* "sent", "received", "seen" */
|
| 46 |
+
FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE,
|
| 47 |
+
FOREIGN KEY (receiver_id) REFERENCES users(id) ON DELETE CASCADE
|
| 48 |
+
);
|
| 49 |
+
`);
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
}
|
| 55 |
+
})
|
server/src/seeder.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getDB } from "./db.js";
|
| 2 |
+
import bcrypt from "bcrypt";
|
| 3 |
+
import { generateId, verifyRequestOrigin } from "lucia";
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
import dotenv from "dotenv";
|
| 7 |
+
dotenv.config();
|
| 8 |
+
|
| 9 |
+
var db = getDB();
|
| 10 |
+
|
| 11 |
+
const users = [
|
| 12 |
+
{
|
| 13 |
+
username: "user1",
|
| 14 |
+
password: await bcrypt.hash("123", 10),
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
username: "user2",
|
| 18 |
+
password: await bcrypt.hash("123", 10),
|
| 19 |
+
},
|
| 20 |
+
{
|
| 21 |
+
username: "user3",
|
| 22 |
+
password: await bcrypt.hash("123", 10),
|
| 23 |
+
},
|
| 24 |
+
];
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
function seedUsers() {
|
| 29 |
+
|
| 30 |
+
const stmt = db.prepare("INSERT INTO users(id,name,password) VALUES(?,?,?)");
|
| 31 |
+
|
| 32 |
+
users.forEach(user => {
|
| 33 |
+
stmt.run(user.username, user.username, user.password);
|
| 34 |
+
})
|
| 35 |
+
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
function seedMessages() {
|
| 39 |
+
|
| 40 |
+
const stmt = db.prepare(`INSERT INTO messages(id,content,type,sender_id,receiver_id) VALUES(?,?,?,?,?)`);
|
| 41 |
+
|
| 42 |
+
for (var i = 0; i < users.length; i++) {
|
| 43 |
+
for (var j = 0; j < users.length; j++) {
|
| 44 |
+
if(i==j) continue;
|
| 45 |
+
var message_id = generateId(5);
|
| 46 |
+
var content = "hi";
|
| 47 |
+
var type = "text";
|
| 48 |
+
var sender_id = users[i].username;
|
| 49 |
+
var receiver_id = users[j].username;
|
| 50 |
+
|
| 51 |
+
stmt.run(
|
| 52 |
+
message_id,
|
| 53 |
+
content,
|
| 54 |
+
type,
|
| 55 |
+
sender_id,
|
| 56 |
+
receiver_id,
|
| 57 |
+
);
|
| 58 |
+
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
db.prepare("delete from users").run();
|
| 67 |
+
db.prepare("delete from messages").run();
|
| 68 |
+
seedUsers()
|
| 69 |
+
seedMessages()
|
server/tsconfig.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
| 27 |
/* Modules */
|
| 28 |
"module": "ESNext" /* Specify what module code is generated. */,
|
| 29 |
"moduleResolution": "NodeNext",
|
| 30 |
-
|
| 31 |
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
| 32 |
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
| 33 |
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
|
|
|
| 27 |
/* Modules */
|
| 28 |
"module": "ESNext" /* Specify what module code is generated. */,
|
| 29 |
"moduleResolution": "NodeNext",
|
| 30 |
+
"rootDir": "./src/", /* Specify the root folder within your source files. */
|
| 31 |
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
| 32 |
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
| 33 |
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|