mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
added unread and read all function with endpoints for notifications
This commit is contained in:
parent
2b09e1459c
commit
2d67843901
7
.changes/unreleased/Feature-20240705-152111.yaml
Normal file
7
.changes/unreleased/Feature-20240705-152111.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
kind: Feature
|
||||
body: |+
|
||||
Add the possibility to mark all notifications as read
|
||||
|
||||
time: 2024-07-05T15:21:11.730543489+02:00
|
||||
custom:
|
||||
Issue: "273"
|
@ -104,4 +104,38 @@ class NotificationApiController
|
||||
|
||||
return new JsonResponse(null, JsonResponse::HTTP_ACCEPTED, [], false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/mark/allread", name="chill_api_main_notification_mark_allread", methods={"POST"})
|
||||
*/
|
||||
public function markAllRead(): JsonResponse
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new \RuntimeException('Invalid user');
|
||||
}
|
||||
|
||||
$modifiedNotificationIds = $this->notificationRepository->markAllNotificationAsReadForUser($user);
|
||||
|
||||
return new JsonResponse($modifiedNotificationIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/mark/undoallread", name="chill_api_main_notification_mark_undoallread", methods={"POST"})
|
||||
*/
|
||||
public function undoAllRead(Request $request): JsonResponse
|
||||
{
|
||||
$user = $this->security->getUser();
|
||||
|
||||
if (!$user instanceof User) {
|
||||
throw new \RuntimeException('Invalid user');
|
||||
}
|
||||
|
||||
$ids = json_decode($request->getContent(), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
$touchedIds = $this->notificationRepository->markAllNotificationAsUnreadForUser($user, $ids);
|
||||
|
||||
return new JsonResponse($touchedIds);
|
||||
}
|
||||
}
|
||||
|
@ -193,13 +193,17 @@ class NotificationController extends AbstractController
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
$currentUser = $this->security->getUser();
|
||||
|
||||
if (!$currentUser instanceof User) {
|
||||
throw new AccessDeniedHttpException('Only regular user should access this page');
|
||||
}
|
||||
|
||||
$notificationsNbr = $this->notificationRepository->countAllForAttendee($currentUser);
|
||||
$paginator = $this->paginatorFactory->create($notificationsNbr);
|
||||
|
||||
$notifications = $this->notificationRepository->findAllForAttendee(
|
||||
$currentUser,
|
||||
$limit = $paginator->getItemsPerPage(),
|
||||
$offset = $paginator->getCurrentPage()->getFirstItemNumber()
|
||||
$paginator->getItemsPerPage(),
|
||||
$paginator->getCurrentPage()->getFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render('@ChillMain/Notification/list.html.twig', [
|
||||
|
@ -13,6 +13,8 @@ namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Result;
|
||||
use Doctrine\DBAL\Statement;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
@ -81,10 +83,7 @@ final class NotificationRepository implements ObjectRepository
|
||||
$results->free();
|
||||
} else {
|
||||
$wheres = [];
|
||||
foreach ([
|
||||
['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId],
|
||||
...$more,
|
||||
] as $k => ['relatedEntityClass' => $relClass, 'relatedEntityId' => $relId]) {
|
||||
foreach ([['relatedEntityClass' => $relatedEntityClass, 'relatedEntityId' => $relatedEntityId], ...$more] as $k => ['relatedEntityClass' => $relClass, 'relatedEntityId' => $relId]) {
|
||||
$wheres[] = "(relatedEntityClass = :relatedEntityClass_{$k} AND relatedEntityId = :relatedEntityId_{$k})";
|
||||
$sqlParams["relatedEntityClass_{$k}"] = $relClass;
|
||||
$sqlParams["relatedEntityId_{$k}"] = $relId;
|
||||
@ -228,11 +227,11 @@ final class NotificationRepository implements ObjectRepository
|
||||
$rsm->addRootEntityFromClassMetadata(Notification::class, 'cmn');
|
||||
|
||||
$sql = 'SELECT '.$rsm->generateSelectClause(['cmn' => 'cmn']).' '.
|
||||
'FROM chill_main_notification cmn '.
|
||||
'WHERE '.
|
||||
'EXISTS (select 1 FROM chill_main_notification_addresses_unread cmnau WHERE cmnau.user_id = :userId and cmnau.notification_id = cmn.id) '.
|
||||
'ORDER BY cmn.date DESC '.
|
||||
'LIMIT :limit OFFSET :offset';
|
||||
'FROM chill_main_notification cmn '.
|
||||
'WHERE '.
|
||||
'EXISTS (select 1 FROM chill_main_notification_addresses_unread cmnau WHERE cmnau.user_id = :userId and cmnau.notification_id = cmn.id) '.
|
||||
'ORDER BY cmn.date DESC '.
|
||||
'LIMIT :limit OFFSET :offset';
|
||||
|
||||
$nq = $this->em->createNativeQuery($sql, $rsm)
|
||||
->setParameter('userId', $user->getId())
|
||||
@ -255,10 +254,12 @@ final class NotificationRepository implements ObjectRepository
|
||||
$qb = $this->repository->createQueryBuilder('n');
|
||||
|
||||
// add condition for related entity (in main arguments, and in more)
|
||||
$or = $qb->expr()->orX($qb->expr()->andX(
|
||||
$qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'),
|
||||
$qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')
|
||||
));
|
||||
$or = $qb->expr()->orX(
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('n.relatedEntityClass', ':relatedEntityClass'),
|
||||
$qb->expr()->eq('n.relatedEntityId', ':relatedEntityId')
|
||||
)
|
||||
);
|
||||
$qb
|
||||
->setParameter('relatedEntityClass', $relatedEntityClass)
|
||||
->setParameter('relatedEntityId', $relatedEntityId);
|
||||
@ -310,4 +311,86 @@ final class NotificationRepository implements ObjectRepository
|
||||
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<int> the ids of the notifications marked as unread
|
||||
*/
|
||||
public function markAllNotificationAsReadForUser(User $user): array
|
||||
{
|
||||
// Get the database connection from the entity manager
|
||||
$connection = $this->em->getConnection();
|
||||
|
||||
/** @var Result $results */
|
||||
$results = $connection->transactional(function (Connection $connection) use ($user) {
|
||||
// Define the SQL query
|
||||
$sql = <<<'SQL'
|
||||
DELETE FROM chill_main_notification_addresses_unread
|
||||
WHERE user_id = :user_id
|
||||
RETURNING notification_id
|
||||
SQL;
|
||||
|
||||
return $connection->executeQuery($sql, ['user_id' => $user->getId()]);
|
||||
});
|
||||
|
||||
$notificationIdsTouched = [];
|
||||
foreach ($results->iterateAssociative() as $row) {
|
||||
$notificationIdsTouched[] = $row['notification_id'];
|
||||
}
|
||||
|
||||
return array_values($notificationIdsTouched);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<int> $notificationIds
|
||||
*/
|
||||
public function markAllNotificationAsUnreadForUser(User $user, array $notificationIds): array
|
||||
{
|
||||
// Get the database connection from the entity manager
|
||||
$connection = $this->em->getConnection();
|
||||
|
||||
/** @var Result $results */
|
||||
$results = $connection->transactional(function (Connection $connection) use ($user, $notificationIds) {
|
||||
// This query double-check that the user is one of the addresses of the notification or the sender,
|
||||
// if the notification is already marked as unread, this query does not fails.
|
||||
// this query return the list of notification id which are affected
|
||||
$sql = <<<'SQL'
|
||||
INSERT INTO chill_main_notification_addresses_unread (user_id, notification_id)
|
||||
SELECT ?, chill_main_notification_addresses_user.notification_id
|
||||
FROM chill_main_notification_addresses_user JOIN chill_main_notification ON chill_main_notification_addresses_user.notification_id = chill_main_notification.id
|
||||
WHERE (chill_main_notification_addresses_user.user_id = ? OR chill_main_notification.sender_id = ?)
|
||||
AND chill_main_notification_addresses_user.notification_id IN ({ notification_ids })
|
||||
ON CONFLICT (user_id, notification_id) DO NOTHING
|
||||
RETURNING notification_id
|
||||
SQL;
|
||||
|
||||
$params = [$user->getId(), $user->getId(), $user->getId(), ...array_values($notificationIds)];
|
||||
$sql = strtr($sql, ['{ notification_ids }' => implode(', ', array_fill(0, count($notificationIds), '?'))]);
|
||||
|
||||
return $connection->executeQuery($sql, $params);
|
||||
});
|
||||
|
||||
$notificationIdsTouched = [];
|
||||
foreach ($results->iterateAssociative() as $row) {
|
||||
$notificationIdsTouched[] = $row['notification_id'];
|
||||
}
|
||||
|
||||
return array_values($notificationIdsTouched);
|
||||
}
|
||||
|
||||
public function findAllUnreadByUser(User $user): array
|
||||
{
|
||||
$rsm = new Query\ResultSetMappingBuilder($this->em);
|
||||
$rsm->addRootEntityFromClassMetadata(Notification::class, 'cmn');
|
||||
|
||||
$sql = 'SELECT '.$rsm->generateSelectClause(['cmn' => 'cmn']).' '.
|
||||
'FROM chill_main_notification cmn '.
|
||||
'WHERE '.
|
||||
'EXISTS (SELECT 1 FROM chill_main_notification_addresses_unread cmnau WHERE cmnau.user_id = :userId AND cmnau.notification_id = cmn.id) '.
|
||||
'ORDER BY cmn.date DESC';
|
||||
|
||||
$nq = $this->em->createNativeQuery($sql, $rsm)
|
||||
->setParameter('userId', $user->getId());
|
||||
|
||||
return $nq->getResult();
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
import {createApp} from "vue";
|
||||
import { createApp } from "vue";
|
||||
import NotificationReadToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadToggle.vue";
|
||||
import { _createI18n } from "ChillMainAssets/vuejs/_js/i18n";
|
||||
import NotificationReadAllToggle from "ChillMainAssets/vuejs/_components/Notification/NotificationReadAllToggle.vue";
|
||||
|
||||
const i18n = _createI18n({});
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function (e) {
|
||||
document.querySelectorAll('.notification_toggle_read_status')
|
||||
.forEach(function (el, i) {
|
||||
createApp({
|
||||
template: `<notification-read-toggle
|
||||
window.addEventListener("DOMContentLoaded", function (e) {
|
||||
document
|
||||
.querySelectorAll(".notification_toggle_read_status")
|
||||
.forEach(function (el, i) {
|
||||
createApp({
|
||||
template: `<notification-read-toggle
|
||||
:notificationId="notificationId"
|
||||
:buttonClass="buttonClass"
|
||||
:buttonNoText="buttonNoText"
|
||||
@ -17,40 +19,45 @@ window.addEventListener('DOMContentLoaded', function (e) {
|
||||
@markRead="onMarkRead"
|
||||
@markUnread="onMarkUnread">
|
||||
</notification-read-toggle>`,
|
||||
components: {
|
||||
NotificationReadToggle,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
notificationId: el.dataset.notificationId,
|
||||
buttonClass: el.dataset.buttonClass,
|
||||
buttonNoText: 'false' === el.dataset.buttonText,
|
||||
showUrl: el.dataset.showButtonUrl,
|
||||
isRead: 1 === Number.parseInt(el.dataset.notificationCurrentIsRead),
|
||||
container: el.dataset.container
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getContainer() {
|
||||
return document.querySelectorAll(`div.${this.container}`);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onMarkRead() {
|
||||
if (typeof this.getContainer[i] !== 'undefined') {
|
||||
this.getContainer[i].classList.replace('read', 'unread');
|
||||
} else { throw 'data-container attribute is missing' }
|
||||
this.isRead = false;
|
||||
},
|
||||
onMarkUnread() {
|
||||
if (typeof this.getContainer[i] !== 'undefined') {
|
||||
this.getContainer[i].classList.replace('unread', 'read');
|
||||
} else { throw 'data-container attribute is missing' }
|
||||
this.isRead = true;
|
||||
},
|
||||
}
|
||||
})
|
||||
.use(i18n)
|
||||
.mount(el);
|
||||
});
|
||||
components: {
|
||||
NotificationReadToggle,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
notificationId: parseInt(el.dataset.notificationId),
|
||||
buttonClass: el.dataset.buttonClass,
|
||||
buttonNoText: "false" === el.dataset.buttonText,
|
||||
showUrl: el.dataset.showButtonUrl,
|
||||
isRead: 1 === Number.parseInt(el.dataset.notificationCurrentIsRead),
|
||||
container: el.dataset.container,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
getContainer() {
|
||||
return document.querySelectorAll(`div.${this.container}`);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onMarkRead() {
|
||||
if (typeof this.getContainer[i] !== "undefined") {
|
||||
this.getContainer[i].classList.replace("read", "unread");
|
||||
} else {
|
||||
throw "data-container attribute is missing";
|
||||
}
|
||||
this.isRead = false;
|
||||
},
|
||||
onMarkUnread() {
|
||||
if (typeof this.getContainer[i] !== "undefined") {
|
||||
this.getContainer[i].classList.replace("unread", "read");
|
||||
} else {
|
||||
throw "data-container attribute is missing";
|
||||
}
|
||||
this.isRead = true;
|
||||
},
|
||||
},
|
||||
})
|
||||
.use(i18n)
|
||||
.mount(el);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
import { createApp } from "vue";
|
||||
import { _createI18n } from "../../vuejs/_js/i18n";
|
||||
import NotificationReadAllToggle from "../../vuejs/_components/Notification/NotificationReadAllToggle.vue";
|
||||
|
||||
const i18n = _createI18n({});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const elements = document.querySelectorAll(".notification_all_read");
|
||||
|
||||
elements.forEach((element) => {
|
||||
console.log('launch');
|
||||
createApp({
|
||||
template: `<notification-read-all-toggle @markAsRead="markAsRead" @markAsUnRead="markAsUnread"></notification-read-all-toggle>`,
|
||||
components: {
|
||||
NotificationReadAllToggle,
|
||||
},
|
||||
methods: {
|
||||
markAsRead(id: number) {
|
||||
const el = document.querySelector<HTMLDivElement>(`div.notification-status[data-notification-id="${id}"]`);
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
el.classList.add('read');
|
||||
el.classList.remove('unread');
|
||||
},
|
||||
markAsUnread(id: number) {
|
||||
const el = document.querySelector<HTMLDivElement>(`div.notification-status[data-notification-id="${id}"]`);
|
||||
if (el === null) {
|
||||
return;
|
||||
}
|
||||
el.classList.remove('read');
|
||||
el.classList.add('unread');
|
||||
},
|
||||
}
|
||||
})
|
||||
.use(i18n)
|
||||
.mount(element);
|
||||
});
|
||||
});
|
@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<div>
|
||||
<button v-if="idsMarkedAsRead.length === 0"
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
@click="markAllRead"
|
||||
>
|
||||
<i class="fa fa-sm fa-envelope-open-o"></i> Marquer tout comme lu
|
||||
</button>
|
||||
<button v-else
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
@click="undo"
|
||||
>
|
||||
<i class="fa fa-sm fa-envelope-open-o"></i> Annuler
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { makeFetch } from "../../../lib/api/apiMethods";
|
||||
import { ref } from "vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'markAsRead', id: number): void,
|
||||
(e: 'markAsUnRead', id: number): void,
|
||||
}>();
|
||||
|
||||
const idsMarkedAsRead = ref([] as number[]);
|
||||
|
||||
async function markAllRead() {
|
||||
const ids: number[] = await makeFetch("POST", `/api/1.0/main/notification/mark/allread`, null);
|
||||
for (let i of ids) {
|
||||
idsMarkedAsRead.value.push(i);
|
||||
emit('markAsRead', i);
|
||||
}
|
||||
}
|
||||
|
||||
async function undo() {
|
||||
const touched: number[] = await makeFetch("POST", `/api/1.0/main/notification/mark/undoallread`, idsMarkedAsRead.value);
|
||||
while (idsMarkedAsRead.value.length > 0) {
|
||||
idsMarkedAsRead.value.pop();
|
||||
}
|
||||
for (let t of touched) {
|
||||
emit('markAsUnRead', t);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
@ -1,47 +1,66 @@
|
||||
<template>
|
||||
<div :class="{'btn-group btn-group-sm float-end': isButtonGroup }"
|
||||
role="group" aria-label="Notification actions">
|
||||
|
||||
<button v-if="isRead"
|
||||
class="btn"
|
||||
:class="overrideClass"
|
||||
type="button"
|
||||
:title="$t('markAsUnread')"
|
||||
@click="markAsUnread"
|
||||
>
|
||||
<div
|
||||
:class="{ 'btn-group btn-group-sm float-end': isButtonGroup }"
|
||||
role="group"
|
||||
aria-label="Notification actions"
|
||||
>
|
||||
<button
|
||||
v-if="isRead"
|
||||
class="btn"
|
||||
:class="overrideClass"
|
||||
type="button"
|
||||
:title="$t('markAsUnread')"
|
||||
@click="markAsUnread"
|
||||
>
|
||||
<i class="fa fa-sm fa-envelope-o"></i>
|
||||
<span v-if="!buttonNoText" class="ps-2">
|
||||
{{ $t('markAsUnread') }}
|
||||
{{ $t("markAsUnread") }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button v-if="!isRead"
|
||||
class="btn"
|
||||
:class="overrideClass"
|
||||
type="button"
|
||||
:title="$t('markAsRead')"
|
||||
@click="markAsRead"
|
||||
>
|
||||
<button
|
||||
v-if="!isRead"
|
||||
class="btn"
|
||||
:class="overrideClass"
|
||||
type="button"
|
||||
:title="$t('markAsRead')"
|
||||
@click="markAsRead"
|
||||
>
|
||||
<i class="fa fa-sm fa-envelope-open-o"></i>
|
||||
<span v-if="!buttonNoText" class="ps-2">
|
||||
{{ $t('markAsRead') }}
|
||||
{{ $t("markAsRead") }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<a v-if="isButtonGroup"
|
||||
<a
|
||||
v-if="isButtonGroup"
|
||||
type="button"
|
||||
class="btn btn-outline-primary"
|
||||
:href="showUrl"
|
||||
:title="$t('action.show')"
|
||||
>
|
||||
>
|
||||
<i class="fa fa-sm fa-comment-o"></i>
|
||||
</a>
|
||||
|
||||
<!-- "Mark All Read" button -->
|
||||
<button
|
||||
v-if="showMarkAllButton"
|
||||
class="btn"
|
||||
:class="overrideClass"
|
||||
type="button"
|
||||
:title="$t('markAllRead')"
|
||||
@click="markAllRead"
|
||||
>
|
||||
<i class="fa fa-sm fa-envelope-o"></i>
|
||||
<span v-if="!buttonNoText" class="ps-2">
|
||||
{{ $t("markAllRead") }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { makeFetch } from 'ChillMainAssets/lib/api/apiMethods.ts';
|
||||
import { makeFetch } from "ChillMainAssets/lib/api/apiMethods.ts";
|
||||
|
||||
export default {
|
||||
name: "NotificationReadToggle",
|
||||
@ -57,7 +76,7 @@ export default {
|
||||
// Optional
|
||||
buttonClass: {
|
||||
required: false,
|
||||
type: String
|
||||
type: String,
|
||||
},
|
||||
buttonNoText: {
|
||||
required: false,
|
||||
@ -65,14 +84,14 @@ export default {
|
||||
},
|
||||
showUrl: {
|
||||
required: false,
|
||||
type: String
|
||||
}
|
||||
type: String,
|
||||
},
|
||||
},
|
||||
emits: ['markRead', 'markUnread'],
|
||||
emits: ["markRead", "markUnread"],
|
||||
computed: {
|
||||
/// [Option] override default button appearance (btn-misc)
|
||||
overrideClass() {
|
||||
return this.buttonClass ? this.buttonClass : 'btn-misc'
|
||||
return this.buttonClass ? this.buttonClass : "btn-misc";
|
||||
},
|
||||
/// [Option] don't display text on button
|
||||
buttonHideText() {
|
||||
@ -82,31 +101,48 @@ export default {
|
||||
// When passed, the component return a button-group with 2 buttons.
|
||||
isButtonGroup() {
|
||||
return this.showUrl;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
markAsUnread() {
|
||||
makeFetch('POST', `/api/1.0/main/notification/${this.notificationId}/mark/unread`, []).then(response => {
|
||||
this.$emit('markRead', { notificationId: this.notificationId });
|
||||
})
|
||||
makeFetch(
|
||||
"POST",
|
||||
`/api/1.0/main/notification/${this.notificationId}/mark/unread`,
|
||||
[]
|
||||
).then((response) => {
|
||||
this.$emit("markRead", {notificationId: this.notificationId});
|
||||
});
|
||||
},
|
||||
markAsRead() {
|
||||
makeFetch('POST', `/api/1.0/main/notification/${this.notificationId}/mark/read`, []).then(response => {
|
||||
this.$emit('markUnread', { notificationId: this.notificationId });
|
||||
})
|
||||
makeFetch(
|
||||
"POST",
|
||||
`/api/1.0/main/notification/${this.notificationId}/mark/read`,
|
||||
[]
|
||||
).then((response) => {
|
||||
this.$emit("markUnread", {
|
||||
notificationId: this.notificationId,
|
||||
});
|
||||
});
|
||||
},
|
||||
markAllRead() {
|
||||
makeFetch(
|
||||
"POST",
|
||||
`/api/1.0/main/notification/markallread`,
|
||||
[]
|
||||
).then((response) => {
|
||||
this.$emit("markAllRead");
|
||||
});
|
||||
},
|
||||
},
|
||||
i18n: {
|
||||
messages: {
|
||||
fr: {
|
||||
markAsUnread: 'Marquer comme non-lu',
|
||||
markAsRead: 'Marquer comme lu'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
markAsUnread: "Marquer comme non-lu",
|
||||
markAsRead: "Marquer comme lu",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
<style lang="scss"></style>
|
||||
|
@ -1,30 +1,33 @@
|
||||
{% macro title(c) %}
|
||||
<div class="item-row title">
|
||||
<h2 class="notification-title">
|
||||
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">
|
||||
<a
|
||||
href="{{ chill_path_add_return_path('chill_main_notification_show', {
|
||||
id: c.notification.id
|
||||
}) }}"
|
||||
>
|
||||
{{ c.notification.title }}
|
||||
</a>
|
||||
</h2>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro header(c) %}
|
||||
<div class="item-row notification-header mt-2">
|
||||
<div class="item-col">
|
||||
<ul class="small_in_title">
|
||||
{% if c.step is not defined or c.step == 'inbox' %}
|
||||
<li class="notification-from">
|
||||
<span class="item-key">
|
||||
<abbr title="{{ 'notification.received_from'|trans }}">
|
||||
{{ 'notification.from'|trans }} :
|
||||
</abbr>
|
||||
</span>
|
||||
<span class="item-key">
|
||||
<abbr title="{{ 'notification.received_from' | trans }}">
|
||||
{{ "notification.from" | trans }} :
|
||||
</abbr>
|
||||
</span>
|
||||
{% if not c.notification.isSystem %}
|
||||
<span class="badge-user">
|
||||
{{ c.notification.sender|chill_entity_render_string({'at_date': c.notification.date}) }}
|
||||
</span>
|
||||
{{ c.notification.sender | chill_entity_render_string({'at_date': c.notification.date}) }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="badge-user system">{{ 'notification.is_system'|trans }}</span>
|
||||
<span class="badge-user system">{{ "notification.is_system" | trans }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -32,34 +35,37 @@
|
||||
<li class="notification-to">
|
||||
{% if c.notification_cc is defined %}
|
||||
{% if c.notification_cc %}
|
||||
<span class="item-key">
|
||||
<abbr title="{{ 'notification.sent_cc'|trans }}">
|
||||
{{ 'notification.cc'|trans }} :
|
||||
</abbr>
|
||||
</span>
|
||||
<span class="item-key">
|
||||
<abbr title="{{ 'notification.sent_cc' | trans }}">
|
||||
{{ "notification.cc" | trans }} :
|
||||
</abbr>
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="item-key">
|
||||
<abbr title="{{ 'notification.sent_to'|trans }}">
|
||||
{{ 'notification.to'|trans }} :
|
||||
</abbr>
|
||||
</span>
|
||||
<span class="item-key">
|
||||
<abbr title="{{ 'notification.sent_to' | trans }}">
|
||||
{{ "notification.to" | trans }} :
|
||||
</abbr>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="item-key">
|
||||
<abbr title="{{ 'notification.sent_to'|trans }}">
|
||||
{{ 'notification.to'|trans }} :
|
||||
</abbr>
|
||||
</span>
|
||||
<span class="item-key">
|
||||
<abbr title="{{ 'notification.sent_to' | trans }}">
|
||||
{{ "notification.to" | trans }} :
|
||||
</abbr>
|
||||
</span>
|
||||
{% endif %}
|
||||
{% for a in c.notification.addressees %}
|
||||
<span class="badge-user">
|
||||
{{ a|chill_entity_render_string({'at_date': c.notification.date}) }}
|
||||
</span>
|
||||
{{ a | chill_entity_render_string({'at_date': c.notification.date}) }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
{% for a in c.notification.addressesEmails %}
|
||||
<span class="badge-user" title="{{ 'notification.Email with access link'|trans|e('html_attr') }}">
|
||||
{{ a }}
|
||||
</span>
|
||||
<span
|
||||
class="badge-user"
|
||||
title="{{ 'notification.Email with access link'|trans|e('html_attr') }}"
|
||||
>
|
||||
{{ a }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</li>
|
||||
{% endif %}
|
||||
@ -70,7 +76,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro content(c) %}
|
||||
<div class="item-row separator">
|
||||
{% if c.data is defined %}
|
||||
@ -83,60 +88,77 @@
|
||||
<div class="notification-content">
|
||||
{% if c.full_content is defined and c.full_content == true %}
|
||||
{% if c.notification.message is not empty %}
|
||||
{{ c.notification.message|chill_markdown_to_html }}
|
||||
{{ c.notification.message | chill_markdown_to_html }}
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'Any comment'|trans }}</p>
|
||||
<p class="chill-no-data-statement">{{ "Any comment" | trans }}</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if c.notification.message is not empty %}
|
||||
{{ c.notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
|
||||
<p class="read-more"><a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}">{{ 'Read more'|trans }}</a></p>
|
||||
<p class="read-more">
|
||||
<a
|
||||
href="{{ chill_path_add_return_path('chill_main_notification_show', {
|
||||
id: c.notification.id
|
||||
}) }}"
|
||||
>{{ "Read more" | trans }}</a>
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'Any comment'|trans }}</p>
|
||||
<p class="chill-no-data-statement">{{ "Any comment" | trans }}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro actions(c) %}
|
||||
{% if c.action_button is not defined or c.action_button != false %}
|
||||
<div class="item-row separator">
|
||||
<div class="item-col item-meta">
|
||||
|
||||
{% if c.notification.comments|length > 0 %}
|
||||
<div class="comment-counter">
|
||||
<span class="counter">
|
||||
{{ 'notification.counter comments'|trans({'nb': c.notification.comments|length }) }}
|
||||
</span>
|
||||
<span class="counter">
|
||||
{{ 'notification.counter comments'|trans({'nb': c.notification.comments|length }) }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<div class="item-col">
|
||||
<ul class="record_actions">
|
||||
<li>
|
||||
{# Vue component #}
|
||||
<span class="notification_toggle_read_status"
|
||||
data-notification-id="{{ c.notification.id }}"
|
||||
data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}"
|
||||
data-container="notification-status"
|
||||
<span
|
||||
class="notification_toggle_read_status"
|
||||
data-notification-id="{{ c.notification.id }}"
|
||||
data-notification-current-is-read="{{ c.notification.isReadBy(app.user) }}"
|
||||
data-container="notification-status"
|
||||
></span>
|
||||
</li>
|
||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_UPDATE', c.notification) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_notification_edit', {'id': c.notification.id}) }}"
|
||||
class="btn btn-edit" title="{{ 'Edit'|trans }}"></a>
|
||||
<a
|
||||
href="{{ chill_path_add_return_path(
|
||||
'chill_main_notification_edit',
|
||||
{ id: c.notification.id }
|
||||
) }}"
|
||||
class="btn btn-edit"
|
||||
title="{{ 'Edit' | trans }}"
|
||||
></a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE', c.notification) %}
|
||||
{% if is_granted('CHILL_MAIN_NOTIFICATION_SEE',
|
||||
c.notification) %}
|
||||
<li>
|
||||
<a href="{{ chill_path_add_return_path('chill_main_notification_show', {'id': c.notification.id}) }}"
|
||||
class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}" title="{{ 'notification.see_comments_thread'|trans }}">
|
||||
<a
|
||||
href="{{ chill_path_add_return_path(
|
||||
'chill_main_notification_show',
|
||||
{ id: c.notification.id }
|
||||
) }}"
|
||||
class="btn {% if not c.notification.isSystem %}btn-show change-icon{% else %}btn-misc{% endif %}"
|
||||
title="{{ 'notification.see_comments_thread' | trans }}"
|
||||
>
|
||||
{% if not c.notification.isSystem() %}
|
||||
<i class="fa fa-comment"></i>
|
||||
{% else %}
|
||||
{{ 'Read more'|trans }}
|
||||
{{ "Read more" | trans }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
@ -147,24 +169,30 @@
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
<div class="item-bloc notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}">
|
||||
|
||||
<div
|
||||
class="item-bloc notification-status {% if notification.isReadBy(app.user) %}read{% else %}unread{% endif %}"
|
||||
data-notification-id="{{ notification.id|escape('html_attr') }}"
|
||||
>
|
||||
{% if fold_item is defined and fold_item != false %}
|
||||
<div class="accordion-header" id="flush-heading-{{ notification.id }}">
|
||||
<button type="button" class="accordion-button collapsed"
|
||||
data-bs-toggle="collapse" data-bs-target="#flush-collapse-{{ notification.id }}"
|
||||
aria-expanded="false" aria-controls="flush-collapse-{{ notification.id }}">
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="accordion-button collapsed"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#flush-collapse-{{ notification.id }}"
|
||||
aria-expanded="false"
|
||||
aria-controls="flush-collapse-{{ notification.id }}"
|
||||
>
|
||||
{{ _self.title(_context) }}
|
||||
</button>
|
||||
{{ _self.header(_context) }}
|
||||
|
||||
</div>
|
||||
<div id="flush-collapse-{{ notification.id }}"
|
||||
<div
|
||||
id="flush-collapse-{{ notification.id }}"
|
||||
class="accordion-collapse collapse"
|
||||
aria-labelledby="flush-heading-{{ notification.id }}"
|
||||
data-bs-parent="#notification-fold">
|
||||
|
||||
data-bs-parent="#notification-fold"
|
||||
>
|
||||
{{ _self.content(_context) }}
|
||||
</div>
|
||||
{{ _self.actions(_context) }}
|
||||
@ -174,5 +202,4 @@
|
||||
{{ _self.content(_context) }}
|
||||
{{ _self.actions(_context) }}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
@ -1,62 +1,78 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title 'notification.My own notifications'|trans %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags('mod_notification_toggle_read_status') }}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_script_tags("mod_notification_toggle_read_status") }}
|
||||
{{ encore_entry_script_tags("mod_notification_toggle_read_all_status") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
{{ encore_entry_link_tags('mod_notification_toggle_read_status') }}
|
||||
{{ encore_entry_link_tags("mod_notification_toggle_read_status") }}
|
||||
{{ encore_entry_link_tags("mod_notification_toggle_read_all_status") }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-10 notification notification-list">
|
||||
<h1>{{ block('title') }}</h1>
|
||||
<div class="col-10 notification notification-list">
|
||||
<h1>{{ block("title") }}</h1>
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link {% if step == 'inbox' %}active{% endif %}"
|
||||
href="{{ path('chill_main_notification_my') }}"
|
||||
>
|
||||
{{ "notification.Notifications received" | trans }}
|
||||
{% if unreads['inbox'] > 0 %}
|
||||
<span class="badge rounded-pill bg-danger">
|
||||
{{ unreads["inbox"] }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a
|
||||
class="nav-link {% if step == 'sent' %}active{% endif %}"
|
||||
href="{{ path('chill_main_notification_sent') }}"
|
||||
>
|
||||
{{ "notification.Notifications sent" | trans }}
|
||||
{% if unreads['sent'] > 0 %}
|
||||
<span class="badge rounded-pill bg-danger">
|
||||
{{ unreads["sent"] }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav nav-pills justify-content-center">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if step == 'inbox' %}active{% endif %}" href="{{ path('chill_main_notification_my') }}">
|
||||
{{ 'notification.Notifications received'|trans }}
|
||||
{% if unreads['inbox'] > 0 %}
|
||||
<span class="badge rounded-pill bg-danger">
|
||||
{{ unreads['inbox'] }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if step == 'sent' %}active{% endif %}" href="{{ path('chill_main_notification_sent') }}">
|
||||
{{ 'notification.Notifications sent'|trans }}
|
||||
{% if unreads['sent'] > 0 %}
|
||||
<span class="badge rounded-pill bg-danger">
|
||||
{{ unreads['sent'] }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
{% if datas|length == 0 %}
|
||||
{% if step == 'inbox' %}
|
||||
<p class="chill-no-data-statement">{{ 'notification.Any notification received'|trans }}</p>
|
||||
{% if datas|length == 0 %} {% if step == 'inbox' %}
|
||||
<p class="chill-no-data-statement">
|
||||
{{ "notification.Any notification received" | trans }}
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'notification.Any notification sent'|trans }}</p>
|
||||
<p class="chill-no-data-statement">
|
||||
{{ "notification.Any notification sent" | trans }}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="flex-table accordion accordion-flush" id="notification-fold">
|
||||
{% for data in datas %}
|
||||
{% set notification = data.notification %}
|
||||
{% include '@ChillMain/Notification/_list_item.html.twig' with {
|
||||
'fold_item': true,
|
||||
'notification_cc': data.template_data.notificationCc is defined ? data.template_data.notificationCc : false
|
||||
} %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="flex-table accordion accordion-flush" id="notification-fold">
|
||||
{% for data in datas %}
|
||||
{% set notification = data.notification %}
|
||||
{% include '@ChillMain/Notification/_list_item.html.twig' with {
|
||||
'fold_item': true, 'notification_cc': data.template_data.notificationCc
|
||||
is defined ? data.template_data.notificationCc : false } %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
{% endif %}
|
||||
|
||||
<ul class="record_actions sticky-form-buttons justify-content-end">
|
||||
<li class="ml-auto d-flex align-items-center gap-2">
|
||||
<span class="notification_all_read"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{{ chill_pagination(paginator) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
@ -9,7 +9,7 @@ declare(strict_types=1);
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Repository;
|
||||
namespace Chill\MainBundle\Tests\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\NewsItem;
|
||||
use Chill\MainBundle\Repository\NewsItemRepository;
|
||||
|
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Tests\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Repository\NotificationRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @coversNothing
|
||||
*/
|
||||
class NotificationRepositoryTest extends KernelTestCase
|
||||
{
|
||||
private EntityManagerInterface $entityManager;
|
||||
private NotificationRepository $repository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
self::bootKernel();
|
||||
$this->entityManager = static::$kernel->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$this->repository = new NotificationRepository($this->entityManager);
|
||||
}
|
||||
|
||||
public function testMarkAllNotificationAsReadForUser(): void
|
||||
{
|
||||
$user = $this->entityManager->createQuery('SELECT u FROM '.User::class.' u')
|
||||
->setMaxResults(1)->getSingleResult();
|
||||
|
||||
$notification = (new Notification())
|
||||
->setRelatedEntityClass('\Dummy')
|
||||
->setRelatedEntityId(0)
|
||||
;
|
||||
$notification->addAddressee($user)->markAsUnreadBy($user);
|
||||
|
||||
$this->entityManager->persist($notification);
|
||||
$this->entityManager->flush();
|
||||
$notification->markAsUnreadBy($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->entityManager->refresh($notification);
|
||||
|
||||
if ($notification->isReadBy($user)) {
|
||||
throw new \LogicException('Notification should not be marked as read');
|
||||
}
|
||||
|
||||
$notificationsIds = $this->repository->markAllNotificationAsReadForUser($user);
|
||||
self::assertContains($notification->getId(), $notificationsIds);
|
||||
|
||||
$this->entityManager->clear();
|
||||
|
||||
$notification = $this->entityManager->find(Notification::class, $notification->getId());
|
||||
|
||||
self::assertTrue($notification->isReadBy($user));
|
||||
}
|
||||
|
||||
public function testMarkAllNotificationAsUnreadForUser(): void
|
||||
{
|
||||
$user = $this->entityManager->createQuery('SELECT u FROM '.User::class.' u')
|
||||
->setMaxResults(1)->getSingleResult();
|
||||
|
||||
$notification = (new Notification())
|
||||
->setRelatedEntityClass('\Dummy')
|
||||
->setRelatedEntityId(0)
|
||||
;
|
||||
$notification->addAddressee($user); // we do not mark the notification as unread by the user
|
||||
|
||||
$this->entityManager->persist($notification);
|
||||
$this->entityManager->flush();
|
||||
$notification->markAsReadBy($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->entityManager->refresh($notification);
|
||||
|
||||
if (!$notification->isReadBy($user)) {
|
||||
throw new \LogicException('Notification should be marked as read');
|
||||
}
|
||||
|
||||
$notificationsIds = $this->repository->markAllNotificationAsUnreadForUser($user, [$notification->getId()]);
|
||||
|
||||
self::assertContains($notification->getId(), $notificationsIds);
|
||||
}
|
||||
}
|
@ -5,8 +5,8 @@ info:
|
||||
title: "Chill api"
|
||||
description: "Api documentation for chill. Currently, work in progress"
|
||||
servers:
|
||||
- url: "/api"
|
||||
description: "Your current dev server"
|
||||
- url: "/api"
|
||||
description: "Your current dev server"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
@ -165,7 +165,6 @@ components:
|
||||
endDate:
|
||||
$ref: "#/components/schemas/Date"
|
||||
|
||||
|
||||
paths:
|
||||
/1.0/search.json:
|
||||
get:
|
||||
@ -182,25 +181,25 @@ paths:
|
||||
The results are ordered by relevance, from the most to the lowest relevant.
|
||||
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
required: true
|
||||
description: the pattern to search
|
||||
schema:
|
||||
type: string
|
||||
- name: type[]
|
||||
in: query
|
||||
required: true
|
||||
description: the type entities amongst the search is performed
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- person
|
||||
- thirdparty
|
||||
- user
|
||||
- household
|
||||
- name: q
|
||||
in: query
|
||||
required: true
|
||||
description: the pattern to search
|
||||
schema:
|
||||
type: string
|
||||
- name: type[]
|
||||
in: query
|
||||
required: true
|
||||
description: the type entities amongst the search is performed
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
enum:
|
||||
- person
|
||||
- thirdparty
|
||||
- user
|
||||
- household
|
||||
responses:
|
||||
200:
|
||||
description: "OK"
|
||||
@ -237,7 +236,7 @@ paths:
|
||||
minItems: 2
|
||||
maxItems: 2
|
||||
postcode:
|
||||
$ref: '#/components/schemas/PostalCode'
|
||||
$ref: "#/components/schemas/PostalCode"
|
||||
steps:
|
||||
type: string
|
||||
street:
|
||||
@ -261,21 +260,21 @@ paths:
|
||||
- address
|
||||
summary: Return an address by id
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The address id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The address id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Address'
|
||||
$ref: "#/components/schemas/Address"
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
@ -285,14 +284,14 @@ paths:
|
||||
- address
|
||||
summary: patch an address
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The address id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The address id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
@ -321,7 +320,7 @@ paths:
|
||||
minItems: 2
|
||||
maxItems: 2
|
||||
postcode:
|
||||
$ref: '#/components/schemas/PostalCode'
|
||||
$ref: "#/components/schemas/PostalCode"
|
||||
steps:
|
||||
type: string
|
||||
street:
|
||||
@ -344,28 +343,27 @@ paths:
|
||||
400:
|
||||
description: "transition cannot be applyed"
|
||||
|
||||
|
||||
/1.0/main/address/{id}/duplicate.json:
|
||||
post:
|
||||
tags:
|
||||
- address
|
||||
summary: Duplicate an existing address
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The address id that will be duplicated
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The address id that will be duplicated
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Address'
|
||||
$ref: "#/components/schemas/Address"
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
@ -377,12 +375,12 @@ paths:
|
||||
- address
|
||||
summary: Return a list of all reference addresses
|
||||
parameters:
|
||||
- in: query
|
||||
name: postal_code
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
description: The id of a postal code to filter the reference addresses
|
||||
- in: query
|
||||
name: postal_code
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
description: The id of a postal code to filter the reference addresses
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
@ -392,21 +390,21 @@ paths:
|
||||
- address
|
||||
summary: Return a reference address by id
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The reference address id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The reference address id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AddressReference'
|
||||
$ref: "#/components/schemas/AddressReference"
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
@ -419,27 +417,27 @@ paths:
|
||||
- search
|
||||
summary: Return a reference address by id
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The reference address id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: q
|
||||
in: query
|
||||
required: true
|
||||
description: The search pattern
|
||||
schema:
|
||||
type: string
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The reference address id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: q
|
||||
in: query
|
||||
required: true
|
||||
description: The search pattern
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/AddressReference'
|
||||
$ref: "#/components/schemas/AddressReference"
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
@ -452,12 +450,12 @@ paths:
|
||||
- address
|
||||
summary: Return a list of all postal-code
|
||||
parameters:
|
||||
- in: query
|
||||
name: country
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
description: The id of a country to filter the postal code
|
||||
- in: query
|
||||
name: country
|
||||
required: false
|
||||
schema:
|
||||
type: integer
|
||||
description: The id of a country to filter the postal code
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
@ -477,7 +475,7 @@ paths:
|
||||
code:
|
||||
type: string
|
||||
country:
|
||||
$ref: '#/components/schemas/Country'
|
||||
$ref: "#/components/schemas/Country"
|
||||
responses:
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
@ -496,21 +494,21 @@ paths:
|
||||
- address
|
||||
summary: Return a postal code by id
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The postal code id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The postal code id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PostalCode'
|
||||
$ref: "#/components/schemas/PostalCode"
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
@ -523,25 +521,25 @@ paths:
|
||||
- search
|
||||
summary: Search a postal code
|
||||
parameters:
|
||||
- name: q
|
||||
in: query
|
||||
required: true
|
||||
description: The search pattern
|
||||
schema:
|
||||
type: string
|
||||
- name: country
|
||||
in: query
|
||||
required: false
|
||||
description: The country id
|
||||
schema:
|
||||
type: integer
|
||||
- name: q
|
||||
in: query
|
||||
required: true
|
||||
description: The search pattern
|
||||
schema:
|
||||
type: string
|
||||
- name: country
|
||||
in: query
|
||||
required: false
|
||||
description: The country id
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PostalCode'
|
||||
$ref: "#/components/schemas/PostalCode"
|
||||
404:
|
||||
description: "not found"
|
||||
400:
|
||||
@ -561,27 +559,26 @@ paths:
|
||||
- address
|
||||
summary: Return a country by id
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The country id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The country id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Country'
|
||||
$ref: "#/components/schemas/Country"
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
description: "Unauthorized"
|
||||
|
||||
|
||||
/1.0/main/user.json:
|
||||
get:
|
||||
tags:
|
||||
@ -612,21 +609,21 @@ paths:
|
||||
- user
|
||||
summary: Return a user by id
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The user id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The user id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
$ref: "#/components/schemas/User"
|
||||
404:
|
||||
description: "not found"
|
||||
401:
|
||||
@ -649,14 +646,14 @@ paths:
|
||||
- scope
|
||||
summary: return a list of scopes
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The scope id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The scope id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
@ -724,14 +721,14 @@ paths:
|
||||
- location
|
||||
summary: Return the given location
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The location id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The location id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
200:
|
||||
description: "ok"
|
||||
@ -784,21 +781,21 @@ paths:
|
||||
id: 1
|
||||
class: 'Chill\PersonBundle\Entity\AccompanyingPeriod'
|
||||
roles:
|
||||
- 'CHILL_PERSON_ACCOMPANYING_PERIOD_SEE'
|
||||
- "CHILL_PERSON_ACCOMPANYING_PERIOD_SEE"
|
||||
/1.0/main/notification/{id}/mark/read:
|
||||
post:
|
||||
tags:
|
||||
- notification
|
||||
summary: mark a notification as read
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The notification id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The notification id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
202:
|
||||
description: "accepted"
|
||||
@ -810,23 +807,55 @@ paths:
|
||||
- notification
|
||||
summary: mark a notification as unread
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The notification id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: The notification id
|
||||
schema:
|
||||
type: integer
|
||||
format: integer
|
||||
minimum: 1
|
||||
responses:
|
||||
202:
|
||||
description: "accepted"
|
||||
403:
|
||||
description: "unauthorized"
|
||||
/1.0/main/notification/mark/allread:
|
||||
post:
|
||||
tags:
|
||||
- notification
|
||||
summary: Mark all notifications as read
|
||||
responses:
|
||||
202:
|
||||
description: "accepted"
|
||||
403:
|
||||
description: "unauthorized"
|
||||
/1.0/main/notification/mark/undoallread:
|
||||
post: # Use POST method for creating resources
|
||||
tags:
|
||||
- notification
|
||||
summary: Mark notifications as unread
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
ids:
|
||||
type: array
|
||||
items:
|
||||
type: integer
|
||||
example: [1, 2, 3] # Example array of IDs
|
||||
responses:
|
||||
"202":
|
||||
description: Notifications marked as unread successfully
|
||||
"403":
|
||||
description: Unauthorized
|
||||
/1.0/main/civility.json:
|
||||
get:
|
||||
tags:
|
||||
- civility
|
||||
- civility
|
||||
summary: Return all civility types
|
||||
responses:
|
||||
200:
|
||||
@ -844,7 +873,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserJob'
|
||||
$ref: "#/components/schemas/UserJob"
|
||||
/1.0/main/workflow/my:
|
||||
get:
|
||||
tags:
|
||||
@ -858,7 +887,7 @@ paths:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Workflow'
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
403:
|
||||
description: "Unauthorized"
|
||||
/1.0/main/workflow/my-cc:
|
||||
@ -874,7 +903,7 @@ paths:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Workflow'
|
||||
$ref: "#/components/schemas/Workflow"
|
||||
403:
|
||||
description: "Unauthorized"
|
||||
/1.0/main/dashboard-config-item.json:
|
||||
@ -888,7 +917,7 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DashboardConfigItem'
|
||||
$ref: "#/components/schemas/DashboardConfigItem"
|
||||
403:
|
||||
description: "Unauthorized"
|
||||
|
||||
@ -905,6 +934,6 @@ paths:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/NewsItem'
|
||||
$ref: "#/components/schemas/NewsItem"
|
||||
403:
|
||||
description: "Unauthorized"
|
||||
|
@ -70,6 +70,7 @@ module.exports = function(encore, entries)
|
||||
encore.addEntry('mod_blur', __dirname + '/Resources/public/module/blur/index.js');
|
||||
encore.addEntry('mod_input_address', __dirname + '/Resources/public/vuejs/Address/mod_input_address_index.js');
|
||||
encore.addEntry('mod_notification_toggle_read_status', __dirname + '/Resources/public/module/notification/toggle_read.js');
|
||||
encore.addEntry('mod_notification_toggle_read_all_status', __dirname + '/Resources/public/module/notification/toggle_read_all.ts');
|
||||
encore.addEntry('mod_pickentity_type', __dirname + '/Resources/public/module/pick-entity/index.js');
|
||||
encore.addEntry('mod_entity_workflow_subscribe', __dirname + '/Resources/public/module/entity-workflow-subscribe/index.js');
|
||||
encore.addEntry('mod_entity_workflow_pick', __dirname + '/Resources/public/module/entity-workflow-pick/index.js');
|
||||
|
Loading…
x
Reference in New Issue
Block a user