Compare commits

...

2 Commits

Author SHA1 Message Date
grimhilt
5b6995d6a6 change archi and use schema routes 2023-04-08 00:00:24 +02:00
grimhilt
65db4d8b7e show flags on front 2023-04-07 23:26:19 +02:00
25 changed files with 193 additions and 80 deletions

26
back/abl/Account-abl.ts Normal file
View File

@ -0,0 +1,26 @@
import { Response } from "express";
import { getAccounts, registerAccount } from "../db/api";
import { getAddresseId } from "../db/utils/mail";
import statusCodes from "../utils/statusCodes";
export default class Account {
static async getAll(body, res: Response) {
getAccounts().then((data) => {
res.status(statusCodes.OK).json(data);
});
}
static async register(body, res: Response) {
const { email, pwd, xoauth, xoauth2, host, port, tls } = body;
getAddresseId(email).then((addressId) => {
registerAccount(addressId, pwd, xoauth, xoauth2, host, port, tls)
.then((mailboxId) => {
res.status(statusCodes.OK).json({ id: mailboxId });
})
.catch(() => {
res.status(statusCodes.INTERNAL_SERVER_ERROR);
});
});
// todo change mailbox to account
}
}

View File

@ -1,8 +1,9 @@
import statusCode from "../utils/statusCodes";
import { getRooms } from "../db/api";
import logger from "../system/Logger";
import { Response } from "express";
export async function rooms(body, res) {
export async function rooms(body, res: Response) {
const { mailboxId, offset, limit } = body;
getRooms(mailboxId).then((rooms) => {
res.status(statusCode.OK).json(rooms);

View File

@ -1,18 +0,0 @@
import statusCode from "../utils/statusCodes";
import { registerAccount } from "../db/api";
import { getAddresseId } from "../db/utils/mail";
export async function addAccount(body, res) {
const { email, pwd, xoauth, xoauth2, host, port, tls } = body;
getAddresseId(email).then((addressId) => {
registerAccount(addressId, pwd, xoauth, xoauth2, host, port, tls)
.then((mailboxId) => {
res.status(statusCode.OK).json({ id: mailboxId });
})
.catch(() => {
res.status(statusCode.INTERNAL_SERVER_ERROR);
});
});
}
// todo change mailbox to account

View File

@ -52,7 +52,7 @@ export async function getRooms(mailboxId) {
return await execQueryAsync(query, values);
}
export async function getMessages(roomId) {
export async function getMessages(roomId: number) {
// todo attachements name
const query = `
SELECT
@ -62,7 +62,8 @@ export async function getMessages(roomId) {
GROUP_CONCAT(ccT.address_id) AS ccA,
subjectT.value AS subject,
content.text AS content,
message.idate AS date
message.idate AS date,
GROUP_CONCAT(flagT.flag_name) AS flags
FROM app_room_message msg
${queryFromId} fromT ON msg.message_id = fromT.message_id
@ -88,6 +89,9 @@ export async function getMessages(roomId) {
bodypart.bodypart_id = header_field.bodypart_id
) content ON msg.message_id = content.message_id
LEFT JOIN flag ON flag.message_id = msg.message_id
LEFT JOIN flag_name flagT ON flagT.flag_id = flag.flag_id
INNER JOIN message ON message.message_id = msg.message_id
WHERE msg.room_id = ?

View File

@ -36,7 +36,6 @@ export async function createRoom(
return await execQueryAsyncWithId(query, values);
}
// todo date not good
export async function registerMessageInRoom(messageId: number, roomId: number, idate: string | undefined | null) {
if (!idate) idate = new Date().toString();
const query = `INSERT IGNORE INTO app_room_message (message_id, room_id) VALUES (?, ?)`;

View File

@ -164,7 +164,7 @@ CREATE TABLE app_room_member (
-- 15
create table flag_name (
flag_id INT NOT NULL,
flag_id INT AUTO_INCREMENT,
flag_name VARCHAR(255) NOT NULL,
PRIMARY KEY (flag_id),
UNIQUE KEY (flag_name)

View File

@ -2,9 +2,8 @@ const queryAddress = (type: string): string => `
LEFT JOIN (
SELECT address_field.address_id, address_field.message_id
FROM address_field
INNER JOIN field_name
INNER JOIN field_name ON field_name.field_id = address_field.field_id
WHERE
field_name.field_id = address_field.field_id AND
field_name.field_name = '${type}'
)
`;

View File

@ -77,6 +77,7 @@ export default class RegisterMessageInApp {
async incrementNotSeen(roomId: number) {
// todo it appears there is an error with notifications
console.log("incrementRead", roomId)
if (!this.isSeen) {
await incrementNotSeenRoom(roomId);
}

View File

@ -2,52 +2,76 @@ import statusCodes from "../utils/statusCodes";
import express from "express";
const router = express.Router();
import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
import schema_account from "../schemas/account_schema.json";
import { addAccount } from "../controllers/addAccount";
import { getAccounts } from "../db/api";
import { rooms } from "../controllers/rooms";
import { messages } from "../controllers/messages";
import { members } from "../controllers/members";
const validate_account = ajv.compile(schema_account);
import { rooms } from "../abl/rooms";
import { messages } from "../abl/messages";
import { members } from "../abl/members";
import {
validateCreateAccount,
validateGetAccounts,
validateGetMembers,
validateGetMessages,
validateGetRooms,
} from "../validator/validator";
import Account from "../abl/Account-abl";
/**
* Return all mailboxes and folders for an user
*/
router.get("/accounts", (req, res) => {
getAccounts().then((data) => {
res.status(statusCodes.OK).json(data);
});
router.get("/accounts", async (req, res) => {
const valid = validateGetAccounts(req.params);
if (!valid) {
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateGetAccounts.errors });
} else {
await Account.getAll(req.params, res);
}
});
/**
* Return all rooms from a mailbox
*/
router.get("/:mailboxId/rooms", async (req, res) => {
// todo use offset limit
await rooms(req.params, res);
// todo offet limit
const valid = validateGetRooms(req.params);
if (!valid) {
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateGetRooms.errors });
} else {
await rooms(req.params, res);
}
});
/**
* Return all messages from a room
*/
router.get("/:roomId/messages", async (req, res) => {
const { roomId } = req.params;
await messages(req.params, res);
const valid = validateGetMessages(req.params);
if (!valid) {
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateGetMessages.errors });
} else {
await messages(req.params, res);
}
});
/**
* Return all members from a room
*/
router.get("/:roomId/members", async (req, res) => {
const { roomId } = req.params;
await members(req.params, res);
const valid = validateGetMembers(req.params);
if (!valid) {
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateGetMembers.errors });
} else {
await members(req.params, res);
}
});
/**
* Register a new mailbox inside the app
*/
router.post("/account", async (req, res) => {
const valid = validate_account(req.body);
const valid = validateCreateAccount(req.body);
if (!valid) {
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validate_account.errors });
res.status(statusCodes.NOT_ACCEPTABLE).send({ error: validateCreateAccount.errors });
} else {
await addAccount(req.body, res);
await Account.register(req.body, res);
}
});

View File

@ -14,6 +14,7 @@ app.use(cors());
app.listen(process.env.PORT || 5500);
import mailRouter from "./routes/mail";
import logger from "./system/Logger";
app.use("/api/mail", mailRouter);
const imapSync = new ImapSync();

View File

@ -0,0 +1,6 @@
{
"type": "object",
"properties": {},
"required": [],
"additionalProperties": false
}

View File

@ -0,0 +1,12 @@
{
"type": "object",
"properties": {
"roomId": {
"type": "string"
}
},
"required": [
"roomId"
],
"additionalProperties": false
}

View File

@ -0,0 +1,12 @@
{
"type": "object",
"properties": {
"roomId": {
"type": "string"
}
},
"required": [
"roomId"
],
"additionalProperties": false
}

View File

@ -0,0 +1,12 @@
{
"type": "object",
"properties": {
"mailboxId": {
"type": "string"
}
},
"required": [
"mailboxId"
],
"additionalProperties": false
}

View File

@ -0,0 +1,16 @@
import Ajv from "ajv";
import addFormats from "ajv-formats";
const ajv = new Ajv({ allErrors: true });
addFormats(ajv);
import createAccountSchema from "./schemas/createAccount-schema.json";
import getAccountSchema from "./schemas/getAccounts-schema.json";
import getRoomSchema from "./schemas/getRooms-schema.json";
import getMessagesSchema from "./schemas/getMessages-schema.json";
import getMembersSchema from "./schemas/getMembers-schema.json";
export const validateCreateAccount = ajv.compile(createAccountSchema);
export const validateGetAccounts = ajv.compile(getAccountSchema);
export const validateGetRooms = ajv.compile(getRoomSchema);
export const validateGetMessages = ajv.compile(getMessagesSchema);
export const validateGetMembers = ajv.compile(getMembersSchema);

View File

@ -1,7 +1,6 @@
<script setup lang="ts">
import { defineProps, PropType } from "vue";
import { Address } from "@/store/models/model";
import Badge from "./Badge.vue";
const props = defineProps({ address: Object as PropType<Address> });
const value = props.address?.name ? props.address?.name : props.address?.email;

View File

@ -13,6 +13,7 @@ export interface Message {
subject: string;
content: string;
date: string;
flags: string;
}
export enum LoadingState {

View File

@ -1,7 +1,7 @@
import API from "@/services/imapAPI";
import { decodeEmojis } from "@/utils/string";
import { AxiosError, AxiosResponse } from "axios";
import { createStore, Store } from "vuex";
import { createStore } from "vuex";
import { Room, Account, Address, RoomType, Message, LoadingState } from "./models/model";
interface RoomFromBack {

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { defineProps, onMounted, ref, watch, PropType, Prop } from "vue";
import { defineProps, onMounted, ref, watch, PropType } from "vue";
import { decodeEmojis } from "../../utils/string";
import { removeDuplicates } from "../../utils/array";
import DOMPurify from "dompurify";
@ -9,6 +9,7 @@ const props = defineProps({
msg: Object as PropType<Message>,
members: Array as PropType<Address[]>,
});
console.log(props.msg);
const iframe = ref<HTMLIFrameElement>();
// todo dompurify
@ -62,6 +63,21 @@ const displayAddresses = (addressIds: string[] | undefined): string => {
});
return res;
};
const classes = (): string => {
const flags = props.msg?.flags?.split(",");
// not flags implies no seen flag
if (!flags) return "msg-notSeen";
// Important takes the priority on Seen flag
if (flags.includes("\\Important")) {
return "msg-important";
} else if (!flags.includes("\\Seen")) {
return "msg-notSeen";
}
return "msg-basic";
};
</script>
<!-- to if to is more than me
cc -->
@ -89,7 +105,7 @@ const displayAddresses = (addressIds: string[] | undefined): string => {
}}
</div>
</div>
<div class="content">
<div class="content" :class="[classes()]">
<iframe ref="iframe"></iframe>
<div class="options">options</div>
</div>
@ -126,8 +142,18 @@ const displayAddresses = (addressIds: string[] | undefined): string => {
display: flex;
padding-top: 6px;
flex-direction: row;
/* background-color: #ec7a4342;
background-color: #353c6261; */
}
.msg-important {
background-color: #ec7a4342;
}
.msg-notSeen {
background-color: #222b5b61;
}
.msg-basic {
background-color: var(--tertiary-background);
}
iframe {
@ -135,7 +161,7 @@ iframe {
max-height: 300px;
flex-basis: 100%;
border: none;
max-width: 600px; /* template width being 600px to 640px up to 750px (experiment and test) */
max-width: 640px; /* template width being 600px to 640px up to 750px (experiment and test) */
background-color: rgb(234, 234, 234);
}

View File

@ -37,7 +37,7 @@ const shouldDisplayComposer = () => {
<Message
v-for="(message, index) in room?.messages"
:key="index"
:data="message"
:msg="message"
:members="room?.members"
/>
</div>

View File

@ -1,23 +1,16 @@
<script setup>
<script setup lang="ts">
import { useRouter } from "vue-router";
import { defineProps } from "vue";
import { defineProps, PropType } from "vue";
import BaseAvatar from "../../avatars/BaseAvatar.vue";
import Badge from "@/components/Badge.vue";
import ThreadList from "./threads/ThreadList.vue";
import store from "@/store/store";
import { Room } from "@/store/models/model";
const props = defineProps({
data: {
id: Number,
roomName: String,
user: String,
userId: Number,
notSeen: Number,
mailboxId: Number,
threadIds: Array,
},
room: Object as PropType<Room>,
});
// console.log(props.data.threadIds);
// console.log(props.room?.threadIds);
const router = useRouter();
</script>
@ -26,18 +19,17 @@ const router = useRouter();
<div>
<div
class="room"
@click="router.push(`/${props.data.id}`)"
v-bind:class="store.state.activeRoom == props.data.id ? 'selected' : ''"
@click="router.push(`/${props.room?.id}`)"
v-bind:class="store.state.activeRoom == props.room?.id ? 'selected' : ''"
>
<BaseAvatar url="vue.png" />
<div class="content">
<div class="sender">{{ props.data.user }}</div>
<div class="object">{{ props.data.roomName }}</div>
<div class="sender">{{ props.room?.user }}</div>
<div class="object">{{ props.room?.roomName }}</div>
</div>
{{ props.data.unseen }}
<Badge class="badge" v-if="props.data.notSeen > 0" :value="props.data.notSeen" type="badge-number" />
<Badge class="badge" v-if="props.room?.notSeen ?? 0 > 0" :value="props.room?.notSeen" type="badge-number" />
</div>
<ThreadList :threadIds="props.data.threadIds" />
<ThreadList :threadIds="props.room?.threadIds" />
</div>
</template>

View File

@ -1,6 +1,6 @@
<template>
<div class="content">
<Room v-for="(room, index) in rooms()" :key="index" :data="room" />
<Room v-for="(room, index) in rooms()" :key="index" :room="room" />
</div>
</template>