Skip to content

Commit 91ca162

Browse files
committed
Exclusion event command
1 parent f6f43ef commit 91ca162

File tree

8 files changed

+138
-29
lines changed

8 files changed

+138
-29
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {flow, pipe} from 'fp-ts/lib/function';
2+
import * as E from 'fp-ts/Either';
3+
import {html, safe, toLoggedInContent} from '../../types/html';
4+
import {Form} from '../../types/form';
5+
import { DomainEvent } from '../../types';
6+
import { renderEvent } from '../../queries/shared-render/render-domain-event';
7+
import * as t from 'io-ts';
8+
import * as tt from 'io-ts-types';
9+
import { formatValidationErrors } from 'io-ts-reporters';
10+
import { failureWithStatus } from '../../types/failure-with-status';
11+
import { StatusCodes } from 'http-status-codes';
12+
import * as O from 'fp-ts/Option';
13+
14+
type ViewModel = {
15+
event: O.Option<DomainEvent>,
16+
event_id: tt.UUID,
17+
};
18+
19+
const renderForm = (viewModel: ViewModel) =>
20+
O.isSome(viewModel.event) ?
21+
pipe(
22+
html`
23+
<h1>Exclude an event</h1>
24+
<p>Are you sure you want to exclude (delete) this event? This form should only be used
25+
</p>
26+
${renderEvent(viewModel.event.value)}
27+
<form action="/events/exclude-event" method="post">
28+
<input type="hidden" name="event_id" value="${viewModel.event_id}"/>
29+
<button type="submit">Confirm</button>
30+
</form>
31+
`,
32+
toLoggedInContent(safe('Exclude event'))
33+
) : pipe(
34+
html`
35+
<h1>Unknown event: ${viewModel.event_id}</h1>
36+
`,
37+
toLoggedInContent(safe('Exclude event'))
38+
);
39+
40+
const paramsCodec = t.strict({
41+
event_id: tt.UUID
42+
});
43+
44+
const constructForm: Form<ViewModel>['constructForm'] = input => ({events}) => pipe(
45+
input,
46+
paramsCodec.decode,
47+
E.mapLeft(
48+
flow(
49+
formatValidationErrors,
50+
failureWithStatus(
51+
'Parameters submitted to the form were invalid',
52+
StatusCodes.BAD_REQUEST
53+
)
54+
)
55+
),
56+
E.map(params => params.event_id),
57+
E.map(event_id => ({event_id, event: O.fromNullable(events.findLast((event) => event.event_id === event_id))}))
58+
);
59+
60+
export const excludeEventForm: Form<ViewModel> = {
61+
renderForm,
62+
constructForm,
63+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as t from 'io-ts';
2+
import * as tt from 'io-ts-types';
3+
import {Command} from '../command';
4+
import {isAdminOrSuperUser} from '../is-admin-or-super-user';
5+
6+
const codec = t.strict({
7+
event_id: tt.UUID
8+
});
9+
10+
export type ExcludeEvent = t.TypeOf<typeof codec>;
11+
12+
const process: Command<ExcludeEvent>['process'] = async input => {
13+
14+
};
15+
16+
const resource = () => ({
17+
type: 'ExcludeEvent',
18+
id: 'ExcludeEvent',
19+
});
20+
21+
export const excludeEvent: Command<ExcludeEvent> = {
22+
process,
23+
resource,
24+
decode: codec.decode,
25+
isAuthorized: isAdminOrSuperUser,
26+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { excludeEvent } from "./exclude-event";
2+
import { excludeEventForm } from "./exclude-event-form";
3+
4+
export const events = {
5+
excludeEvent: {
6+
...excludeEvent,
7+
...excludeEventForm,
8+
},
9+
};

src/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {members} from './members';
55
import {memberNumbers} from './member-numbers';
66
import {superUser} from './super-user';
77
import {ownerAgreementInvite} from './owner-agreement-invite';
8+
import {events} from './exclusion-events';
89

910
export const commands = {
1011
area,
@@ -13,6 +14,7 @@ export const commands = {
1314
superUser,
1415
memberNumbers,
1516
members,
17+
events,
1618
};
1719

1820
export const sendEmailCommands = {

src/queries/log/render.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,14 @@
11
import {pipe} from 'fp-ts/lib/function';
22
import * as RA from 'fp-ts/ReadonlyArray';
3-
import {html, safe, joinHtml, sanitizeString} from '../../types/html';
3+
import {html, safe, joinHtml} from '../../types/html';
44
import {ViewModel, LogSearch} from './view-model';
5-
import {DomainEvent} from '../../types';
6-
import {inspect} from 'node:util';
7-
import {displayDate} from '../../templates/display-date';
8-
import {DateTime} from 'luxon';
9-
import {renderActor} from '../../types/actor';
105
import * as qs from 'qs';
11-
12-
const renderPayload = (event: DomainEvent) =>
13-
// eslint-disable-next-line unused-imports/no-unused-vars
14-
pipe(event, ({type, actor, recordedAt, ...payload}) =>
15-
pipe(
16-
payload,
17-
Object.entries,
18-
RA.map(([key, value]) => `${key}: ${inspect(value)}`),
19-
RA.map(sanitizeString),
20-
joinHtml
21-
)
22-
);
23-
24-
const renderEntry = (event: ViewModel['events'][number]) => html`
25-
<li>
26-
<b>${sanitizeString(event.type)}</b> by ${renderActor(event.actor)} at
27-
${displayDate(DateTime.fromJSDate(event.recordedAt))}<br />
28-
${renderPayload(event)}
29-
</li>
30-
`;
6+
import { renderEvent } from '../shared-render/render-domain-event';
317

328
const renderLog = (log: ViewModel['events']) =>
339
pipe(
3410
log,
35-
RA.map(renderEntry),
11+
RA.map(renderEvent),
3612
joinHtml,
3713
items => html`
3814
<ul>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {pipe} from 'fp-ts/lib/function';
2+
import * as RA from 'fp-ts/ReadonlyArray';
3+
import {html, safe, joinHtml, sanitizeString} from '../../types/html';
4+
import {DomainEvent} from '../../types';
5+
import {inspect} from 'node:util';
6+
import {displayDate} from '../../templates/display-date';
7+
import {DateTime} from 'luxon';
8+
import {renderActor} from '../../types/actor';
9+
10+
const renderPayload = (event: DomainEvent) =>
11+
// eslint-disable-next-line unused-imports/no-unused-vars
12+
pipe(event, ({type, actor, recordedAt, ...payload}) =>
13+
pipe(
14+
payload,
15+
Object.entries,
16+
RA.map(([key, value]) => `${key}: ${inspect(value)}`),
17+
RA.map(sanitizeString),
18+
joinHtml
19+
)
20+
);
21+
22+
export const renderEvent = (event: DomainEvent) => html`
23+
<li>
24+
<b>${sanitizeString(event.type)}</b> by ${renderActor(event.actor)} at
25+
${displayDate(DateTime.fromJSDate(event.recordedAt))}<br />
26+
${renderPayload(event)}
27+
</li>
28+
`;

src/routes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ export const initRoutes = (
9999
'rejoined-with-existing',
100100
commands.memberNumbers.markMemberRejoinedWithExistingNumber
101101
),
102+
...command(
103+
'events',
104+
'exclude-event',
105+
commands.events.excludeEvent
106+
),
102107
email('owner-agreement-invite', sendEmailCommands.ownerAgreementInvite),
103108
get('/ping', ping),
104109
query('/db', queries.db),

src/types/form.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {User} from '.';
1+
import {DomainEvent, User} from '.';
22
import {FailureWithStatus} from './failure-with-status';
33
import * as E from 'fp-ts/Either';
44
import {HttpResponse} from './html';
@@ -9,7 +9,7 @@ export type Form<T> = {
99
renderForm: (viewModel: T) => Member<HttpResponse, 'LoggedInContent'>;
1010
constructForm: (input: unknown) => (context: {
1111
user: User;
12-
// events: ReadonlyArray<DomainEvent>;
12+
events: ReadonlyArray<DomainEvent>;
1313
readModel: SharedReadModel;
1414
}) => E.Either<FailureWithStatus, T>;
1515
};

0 commit comments

Comments
 (0)