From 201b822e16f1df43f988a7bb893b38bbc5183de5 Mon Sep 17 00:00:00 2001 From: Kyle Kincer Date: Thu, 14 Dec 2023 18:52:14 -0500 Subject: [PATCH] feat(server): add database migrations and initial notifications --- server/entity/MediaRequest.ts | 3 + server/lib/notifications/agents/agent.ts | 1 + server/lib/notifications/agents/discord.ts | 8 ++ server/lib/notifications/agents/webpush.ts | 7 +- .../1702596723101-AddAdminMessage.ts | 17 ++++ server/routes/request.ts | 6 ++ server/templates/email/media-request/html.pug | 4 + src/components/DeclineRequestModal/index.tsx | 99 ++++++++++++++----- .../RequestList/RequestItem/index.tsx | 20 +++- 9 files changed, 132 insertions(+), 33 deletions(-) create mode 100644 server/migration/1702596723101-AddAdminMessage.ts diff --git a/server/entity/MediaRequest.ts b/server/entity/MediaRequest.ts index ba67ab7bef..0307b33651 100644 --- a/server/entity/MediaRequest.ts +++ b/server/entity/MediaRequest.ts @@ -460,6 +460,9 @@ export class MediaRequest { @Column({ default: false }) public isAutoRequest: boolean; + @Column({ nullable: true, length: 140 }) + public adminMessage?: string; + constructor(init?: Partial) { Object.assign(this, init); } diff --git a/server/lib/notifications/agents/agent.ts b/server/lib/notifications/agents/agent.ts index d2b0b16562..4198e7dd6a 100644 --- a/server/lib/notifications/agents/agent.ts +++ b/server/lib/notifications/agents/agent.ts @@ -19,6 +19,7 @@ export interface NotificationPayload { request?: MediaRequest; issue?: Issue; comment?: IssueComment; + adminMessage?: string; } export abstract class BaseAgent { diff --git a/server/lib/notifications/agents/discord.ts b/server/lib/notifications/agents/discord.ts index 67a278bfb2..31351244f5 100644 --- a/server/lib/notifications/agents/discord.ts +++ b/server/lib/notifications/agents/discord.ts @@ -153,6 +153,14 @@ class DiscordAgent inline: true, }); } + + if (payload.request.adminMessage) { + fields.push({ + name: 'Admin Message', + value: payload.request.adminMessage, + inline: true, + }); + } } else if (payload.comment) { fields.push({ name: `Comment from ${payload.comment.user.displayName}`, diff --git a/server/lib/notifications/agents/webpush.ts b/server/lib/notifications/agents/webpush.ts index 275a77e8ea..b616274fcd 100644 --- a/server/lib/notifications/agents/webpush.ts +++ b/server/lib/notifications/agents/webpush.ts @@ -45,6 +45,9 @@ class WebPushAgent : 'series' : undefined; const is4k = payload.request?.is4k; + const adminMessage = payload.adminMessage + ? payload.adminMessage + : undefined; const issueType = payload.issue ? payload.issue.issueType !== IssueType.OTHER @@ -80,7 +83,9 @@ class WebPushAgent }${mediaType} request is now available!`; break; case Notification.MEDIA_DECLINED: - message = `Your ${is4k ? '4K ' : ''}${mediaType} request was declined.`; + message = `Your ${is4k ? '4K ' : ''}${mediaType} request was declined${ + adminMessage ? `: ${adminMessage}` : '.' + }.`; break; case Notification.MEDIA_FAILED: message = `Failed to process ${is4k ? '4K ' : ''}${mediaType} request.`; diff --git a/server/migration/1702596723101-AddAdminMessage.ts b/server/migration/1702596723101-AddAdminMessage.ts new file mode 100644 index 0000000000..4a9b87927d --- /dev/null +++ b/server/migration/1702596723101-AddAdminMessage.ts @@ -0,0 +1,17 @@ +import type { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddAdminMessage1702596723101 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE media_requests + ADD adminMessage VARCHAR(140) + `); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(` + ALTER TABLE media_requests + DROP COLUMN adminMessage + `); + } +} diff --git a/server/routes/request.ts b/server/routes/request.ts index 83c05b4856..51d47ab2b8 100644 --- a/server/routes/request.ts +++ b/server/routes/request.ts @@ -538,6 +538,12 @@ requestRoutes.post<{ request.status = newStatus; request.modifiedBy = req.user; + + const adminMessage = req.body.adminMessage; + if (adminMessage) { + request.adminMessage = adminMessage; + } + await requestRepository.save(request); return res.status(200).json(request); diff --git a/server/templates/email/media-request/html.pug b/server/templates/email/media-request/html.pug index 334095dfe2..42af108662 100644 --- a/server/templates/email/media-request/html.pug +++ b/server/templates/email/media-request/html.pug @@ -61,6 +61,10 @@ div(style='display: block; background-color: #111827; padding: 2.5rem 0;') td(style='font-size: .85em; color: #9ca3af; line-height: 1em; vertical-align: bottom; margin-right: 1rem') span | #{timestamp} + if adminMessage + tr + td(style='font-size: 1.0em; color: #9ca3af; line-height: 1.5em; vertical-align: top; margin-right: 1rem') + | #{adminMessage} if actionUrl tr td diff --git a/src/components/DeclineRequestModal/index.tsx b/src/components/DeclineRequestModal/index.tsx index eb36e4536f..8b29423593 100644 --- a/src/components/DeclineRequestModal/index.tsx +++ b/src/components/DeclineRequestModal/index.tsx @@ -1,18 +1,24 @@ -import { Transition } from '@headlessui/react'; -import { useState } from 'react'; import Modal from '@app/components/Common/Modal'; -import useSWR from 'swr'; +import globalMessages from '@app/i18n/globalMessages'; +import { Transition } from '@headlessui/react'; import type { MovieDetails } from '@server/models/Movie'; +import { Field, Form, Formik } from 'formik'; +import React from 'react'; import { useIntl } from 'react-intl'; -import globalMessages from '@app/i18n/globalMessages'; +import useSWR from 'swr'; +import * as Yup from 'yup'; interface DeclineRequestModalProps { show: boolean; tmdbId: number; - onDecline?: (declineMessage: string) => void; + onDecline: (declineMessage: string) => void; onCancel?: () => void; } +const validationSchema = Yup.object().shape({ + declineMessage: Yup.string().max(140, 'Message is too long'), +}); + const DeclineRequestModal = ({ show, tmdbId, @@ -20,14 +26,14 @@ const DeclineRequestModal = ({ onCancel, }: DeclineRequestModalProps) => { const intl = useIntl(); - const [declineMessage, setDeclineMessage] = useState(''); const { data, error } = useSWR(`/api/v1/movie/${tmdbId}`, { revalidateOnMount: true, }); - - const handleDecline = () => { - if (onDecline) { - onDecline(declineMessage); + const [characterCount, setCharacterCount] = React.useState(0); + const handleCancel = () => { + setCharacterCount(0); + if (onCancel) { + onCancel(); } }; @@ -42,23 +48,62 @@ const DeclineRequestModal = ({ leaveTo="opacity-0" show={show} > - -