Skip to content

Commit

Permalink
feat: adds global announcements
Browse files Browse the repository at this point in the history
  • Loading branch information
cmeessen authored and dmijatovic committed Aug 28, 2023
1 parent ab1edac commit decff62
Show file tree
Hide file tree
Showing 10 changed files with 375 additions and 1 deletion.
43 changes: 43 additions & 0 deletions database/022-create-maintenance-tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
-- SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]>
-- SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences
--
-- SPDX-License-Identifier: Apache-2.0
-- SPDX-License-Identifier: EUPL-1.2

CREATE TABLE global_announcement (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
text VARCHAR(200),
enabled BOOLEAN DEFAULT FALSE NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL
);

CREATE FUNCTION sanitise_insert_global_announcement () RETURNS TRIGGER LANGUAGE plpgsql AS
$$
BEGIN
NEW.id = gen_random_uuid();
NEW.created_at = LOCALTIMESTAMP;
NEW.updated_at = NEW.created_at;
return NEW;
END
$$;

CREATE TRIGGER sanitise_insert_global_announcement BEFORE INSERT ON global_announcement FOR EACH ROW EXECUTE PROCEDURE sanitise_insert_global_announcement();

CREATE FUNCTION sanitise_update_global_announcement() RETURNS TRIGGER LANGUAGE plpgsql AS
$$
BEGIN
NEW.id = OLD.id;
NEW.created_at = OLD.created_at;
NEW.updated_at = LOCALTIMESTAMP;
return NEW;
END
$$;

CREATE TRIGGER sanitise_update_global_announcement BEFORE UPDATE ON global_announcement FOR EACH ROW EXECUTE PROCEDURE sanitise_update_global_announcement();

ALTER TABLE global_announcement ENABLE ROW LEVEL SECURITY;

CREATE POLICY anyone_can_read ON global_announcement FOR SELECT TO rsd_web_anon, rsd_user USING (TRUE);´

CREATE POLICY admin_all_rights ON global_announcement TO rsd_admin USING (TRUE) WITH CHECK (TRUE);
36 changes: 36 additions & 0 deletions frontend/components/Announcement/Announcement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]>
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences
//
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: EUPL-1.2

import Button from '@mui/material/Button'
import CancelIcon from '@mui/icons-material/Cancel'
import ErrorIcon from '@mui/icons-material/Error'
import {useState} from 'react'

export default function Announcement({announcement}: {announcement: string | null}) {
const [open, setOpen] = useState(true)
if (announcement === null || announcement === undefined) return null

if (!open) return null

return (
<div
className="fixed bottom-0 right-0 w-full bg-warning text-white font-extrabold flex"
>
<div className='my-auto pl-5'>
<ErrorIcon className='align-middle' />
</div>
<div className="w-full p-5 inline-block">
<span className='align-middle'>{announcement}</span>
</div>
<Button
className='ml-auto'
onClick={() => {setOpen(false)}}
>
<CancelIcon className='text-white' />
</Button>
</div>
)
}
8 changes: 8 additions & 0 deletions frontend/components/admin/AdminNav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]>
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (Netherlands eScience Center)
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all)
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences
// SPDX-FileCopyrightText: 2023 Netherlands eScience Center
// SPDX-FileCopyrightText: 2023 dv4all
//
Expand All @@ -21,6 +23,12 @@ import AccountCircleIcon from '@mui/icons-material/AccountCircle'
import FluorescentIcon from '@mui/icons-material/Fluorescent'

export const adminPages = {
notifications: {
title: 'Announcements',
subtitle: 'Show global notifications to all users',
icon: <DescriptionIcon />,
path: '/admin/announcements',
},
pages:{
title: 'Public pages',
subtitle: 'Manage markdown pages',
Expand Down
127 changes: 127 additions & 0 deletions frontend/components/admin/announcements/AnnouncementsForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]>
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all)
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences
// SPDX-FileCopyrightText: 2023 dv4all
//
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: EUPL-1.2

import Button from '@mui/material/Button'
import AddIcon from '@mui/icons-material/Add'

import {useSession} from '~/auth'
import {Controller, SubmitHandler, UseControllerProps, useController, useForm} from 'react-hook-form'
import AutosaveControlledTextField, {OnSaveProps} from '~/components/form/AutosaveControlledTextField'
import {Input, Switch} from '@mui/material'
import SubmitButtonWithListener from '~/components/form/SubmitButtonWithListener'
import {createJsonHeaders, extractReturnMessage, getBaseUrl} from '~/utils/fetchHelpers'
import {GetServerSidePropsContext} from 'next'
import logger from '~/utils/logger'
import {useEffect, useState} from 'react'
import ControlledSwitch from '~/components/form/ControlledSwitch'
import ControlledTextField from '~/components/form/ControlledTextField'

const formId = 'announcements-form'

type EditAnnouncementItem = {
[id: string]: any,
text: string | null,
enabled: boolean
}

type EditAnnouncementFormProps = {
data: EditAnnouncementItem
}

export default function AnnouncementsForm({data}: EditAnnouncementFormProps) {
const {token} = useSession()
const {handleSubmit, register, control, setValue, formState: {errors}} = useForm<EditAnnouncementItem>({
defaultValues: {
...data
},
mode: 'onChange'
})

function onSubmit(dataToSubmit: EditAnnouncementItem) {
saveAnnouncement(dataToSubmit).then(
(resp) => {
if (resp && [200, 201].includes(resp.status) && resp?.object) {
const newObj = resp.object[0]
setValue('enabled', newObj.enabled)
setValue('id', newObj.id)
setValue('text', newObj.text)
} else {
console.log('Error saving data:', resp)
}
}
)
}

async function saveAnnouncement(item: EditAnnouncementItem) {
try {
let method
let url
if (item.id == '') {
delete item.id
method = 'POST'
url = '/api/v1/global_announcement'
} else {
method = 'PATCH'
url = `/api/v1/global_announcement?id=eq.${item.id}`
}
const resp = await fetch(url, {
method: method,
headers: {
...createJsonHeaders(token),
Prefer: 'return=representation',
},
body: JSON.stringify(item)
})
if ([200, 201].includes(resp.status)) {
return {
status: 201,
object: await resp.json()
}
}
// return extractReturnMessage(resp, item.text ?? '')
} catch (e: any) {
logger(`saveAnnouncement: ${e?.message}`, 'error')
return {
status: 500,
message: e?.message
}
}
}

return (
<form
id={formId}
onSubmit={handleSubmit(onSubmit)}
className="w-full md:w-[42rem]"
>
<Input type="hidden" {...register('id')}/>
<ControlledTextField
control={control}
options={{
name: 'text',
label: 'Announcement'
}}
rules={{
maxLength: {
value: 300,
message: 'Maximum length is 300.'
}
}}
/>
<ControlledSwitch
label='Visible'
name='enabled'
control={control}
/>
<SubmitButtonWithListener
disabled={false}
formId={formId}
/>
</form>
)
}
20 changes: 20 additions & 0 deletions frontend/components/admin/announcements/getAnnouncement.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]>
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences
//
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: EUPL-1.2

import {getBaseUrl} from '~/utils/fetchHelpers'
import logger from '~/utils/logger'

export default async function getAnnouncement() {
const url = getBaseUrl() + '/global_announcement'
const resp = await fetch(url, {method: 'GET',})
const json = await resp.json()
if (json[0] && json[0].enabled) {
return json[0].text
} else {
logger('getAnnouncement failed:')
return null
}
}
92 changes: 92 additions & 0 deletions frontend/components/admin/announcements/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]>
// SPDX-FileCopyrightText: 2023 Dusan Mijatovic (dv4all)
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences
// SPDX-FileCopyrightText: 2023 dv4all
//
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: EUPL-1.2

import Button from '@mui/material/Button'
import AddIcon from '@mui/icons-material/Add'

import {useSession} from '~/auth'
import {SubmitHandler, UseControllerProps, useController, useForm} from 'react-hook-form'
import AutosaveControlledTextField, {OnSaveProps} from '~/components/form/AutosaveControlledTextField'
import {Input, Switch} from '@mui/material'
import SubmitButtonWithListener from '~/components/form/SubmitButtonWithListener'
import {createJsonHeaders, extractReturnMessage, getBaseUrl} from '~/utils/fetchHelpers'
import {GetServerSidePropsContext} from 'next'
import logger from '~/utils/logger'
import {useEffect, useState} from 'react'
import AnnouncementsForm from './AnnouncementsForm'
import ContentLoader from '~/components/layout/ContentLoader'

const formId = 'announcements-form'

type EditAnnouncementItem = {
[id: string]: any,
text: string | null,
enabled: boolean
}


function getData({token}: {token: string}) {
const [loading, setLoading] = useState(true)
const [item, setItem] = useState<EditAnnouncementItem>()

async function getAnnouncement() {
const url = getBaseUrl() + '/global_announcement'
setLoading(true)
const resp = await fetch(url, {
method: 'GET',
headers: {
...createJsonHeaders(token),
}
})

if (resp.status === 200) {
const json = await resp.json()
if (json[0]) {
setItem({
id: json[0].id,
enabled: json[0].enabled,
text: json[0].text
})
} else {
setItem({
enabled: false,
text: ''
})
}
setLoading(false)
}
}

useEffect(() => {
getAnnouncement()
}, [])

return {
formProps: item,
loading
}

}


export default function AnnouncementsPage() {
const {token} = useSession()
const {formProps, loading} = getData({token})

if (loading) return (
<ContentLoader />
)

if (formProps) {
return (
<AnnouncementsForm data={formProps} />
)
}

return <ContentLoader />
}
7 changes: 7 additions & 0 deletions frontend/config/getSettingsServerSide.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// SPDX-FileCopyrightText: 2022 - 2023 Dusan Mijatovic (dv4all)
// SPDX-FileCopyrightText: 2022 - 2023 dv4all
// SPDX-FileCopyrightText: 2023 Christian Meeßen (GFZ) <[email protected]>
// SPDX-FileCopyrightText: 2023 Helmholtz Centre Potsdam - GFZ German Research Centre for Geosciences
//
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: EUPL-1.2

import {IncomingMessage} from 'http'
import {ParsedUrlQuery} from 'querystring'
Expand All @@ -10,6 +13,7 @@ import logger from '~/utils/logger'
import {getPageLinks} from '~/components/admin/pages/useMarkdownPages'
import {defaultRsdSettings, RsdSettingsState} from './rsdSettingsReducer'
import defaultSettings from '~/config/defaultSettings.json'
import getAnnouncement from '~/components/admin/announcements/getAnnouncement'

/**
* getThemeSettings from local json file
Expand Down Expand Up @@ -42,6 +46,8 @@ export async function getSettingsServerSide(req: IncomingMessage | undefined, qu
if (typeof req === 'undefined') return defaultRsdSettings as RsdSettingsState
// get links
const pages = await getPageLinks({is_published: true})
// get announcments
const announcement = await getAnnouncement()
// extract embed flag
const embed = typeof query?.embed !== 'undefined'
// get settings (host and theme)
Expand All @@ -54,6 +60,7 @@ export async function getSettingsServerSide(req: IncomingMessage | undefined, qu
theme: settings.theme,
pages,
embed,
announcement: announcement
}
// console.group('getSettingsServerSide')
// console.log('rsdSettings...', rsdSettings)
Expand Down
Loading

0 comments on commit decff62

Please sign in to comment.