From c8ea4d55959789ad77efc149ad899752af842e6a Mon Sep 17 00:00:00 2001 From: ascariandrea Date: Mon, 13 Nov 2023 20:04:30 +0100 Subject: [PATCH] fix(admin): display social posts count for sharable resources --- packages/@liexp/core/src/fp/index.ts | 4 +- .../shared/src/helpers/event-suggestion.ts | 14 +- .../@liexp/shared/src/helpers/event/event.ts | 1 + .../shared/src/helpers/event/search-event.ts | 1 + .../shared/src/io/http/Events/BaseEvent.ts | 1 + .../src/io/http/Events/SearchEventsQuery.ts | 3 + packages/@liexp/shared/src/io/http/Link.ts | 2 + packages/@liexp/shared/src/io/http/Media.ts | 4 + .../src/tests/arbitrary/Event.arbitrary.ts | 2 + .../src/tests/arbitrary/Link.arbitrary.ts | 2 + .../src/tests/arbitrary/Media.arbitrary.ts | 2 + .../src/components/admin/Modal/ShareModal.tsx | 1 + .../SocialPost/SocialPostFormTabContent.tsx | 4 +- .../components/admin/events/EditEventForm.tsx | 2 +- .../src/components/admin/links/AdminLinks.tsx | 9 ++ .../src/components/admin/media/MediaEdit.tsx | 28 ++-- .../src/components/admin/media/MediaList.tsx | 9 ++ .../components/admin/stories/AdminStories.tsx | 4 + packages/@liexp/ui/src/i18n/en-US.ts | 1 + .../ui/src/templates/links/LinkTemplateUI.tsx | 1 + services/admin-web/src/pages/AdminEvents.tsx | 4 + services/api/.env | 2 +- services/api/__mocks__/ig.mock.ts | 4 +- services/api/bin/add-event-default-links.ts | 2 +- services/api/src/entities/Event.v2.entity.ts | 9 +- services/api/src/entities/Link.entity.ts | 3 + services/api/src/entities/Media.entity.ts | 5 +- .../api/src/entities/SocialPost.entity.ts | 6 + .../__tests__/createFromTGMessage.flow.e2e.ts | 2 +- .../src/flows/events/extractFromURL.flow.ts | 2 + .../networks/createHierarchyNetwork.flow.ts | 7 +- .../src/flows/stats/createStatsByType.flow.ts | 7 +- .../src/queries/media/fetchManyMedia.query.ts | 20 +++ .../socialPosts/leftJoinSocialPosts.query.ts | 22 ++++ .../routes/events/__tests__/editEvent.e2e.ts | 1 + .../events/deaths/getListDeath.controller.ts | 4 + .../getListDocumentaries.controller.ts | 2 + services/api/src/routes/events/eventV2.io.ts | 1 + .../events/getEventFromLink.controller.ts | 1 + .../patents/getListPatent.controller.ts | 4 + .../events/queries/searchEventsV2.query.ts | 124 ++++++++++++++---- .../events/quotes/listQuotes.controller.ts | 4 + .../listScientificStudy.controller.ts | 4 + .../routes/events/searchEvents.controller.ts | 6 +- .../createEventFromSuggestion.controller.ts | 1 + .../getListTransaction.controller.ts | 2 + services/api/src/routes/links/link.io.ts | 1 + .../src/routes/links/listLinks.controller.ts | 27 +++- .../routes/media/__tests__/createMedia.e2e.ts | 1 + .../routes/media/__tests__/listMedia.e2e.ts | 3 +- services/api/src/routes/media/media.io.ts | 1 + .../src/routes/users/userLogin.controller.ts | 5 + services/api/src/utils/orm.utils.ts | 2 +- 53 files changed, 307 insertions(+), 77 deletions(-) create mode 100644 services/api/src/queries/socialPosts/leftJoinSocialPosts.query.ts diff --git a/packages/@liexp/core/src/fp/index.ts b/packages/@liexp/core/src/fp/index.ts index 22eda013ee..aa20c88f38 100644 --- a/packages/@liexp/core/src/fp/index.ts +++ b/packages/@liexp/core/src/fp/index.ts @@ -13,6 +13,7 @@ import * as TE from "fp-ts/TaskEither"; import { pipe, flow } from "fp-ts/function"; import * as N from "fp-ts/number"; import * as S from "fp-ts/string"; +import * as Void from 'fp-ts/void' export const fp = { A, @@ -28,7 +29,8 @@ export const fp = { N, Ord, Json, - Eq + Eq, + Void }; export { pipe, flow }; diff --git a/packages/@liexp/shared/src/helpers/event-suggestion.ts b/packages/@liexp/shared/src/helpers/event-suggestion.ts index 2a3dd47396..1db239084f 100644 --- a/packages/@liexp/shared/src/helpers/event-suggestion.ts +++ b/packages/@liexp/shared/src/helpers/event-suggestion.ts @@ -59,6 +59,7 @@ export const getSuggestions = ( ), links: [], keywords: relations.keywords, + socialPosts: [], createdAt: new Date(), updatedAt: new Date(), deletedAt: undefined, @@ -80,7 +81,7 @@ export const getSuggestions = ( actors: [], groups: [], } as any, - }, + } }, }, { @@ -132,17 +133,6 @@ export const getSuggestions = ( }, }, }, - // { - // type: http.EventSuggestion.EventSuggestionType.types[0].value, - // event: { - // ...commonSuggestion, - // type: http.Events.EventTypes.DEATH.value, - // payload: { - // victim: uuid() as any, - // location: undefined as any, - // }, - // }, - // }, { type: http.EventSuggestion.EventSuggestionType.types[0].value, event: { diff --git a/packages/@liexp/shared/src/helpers/event/event.ts b/packages/@liexp/shared/src/helpers/event/event.ts index 1c19919ac2..45f206e854 100644 --- a/packages/@liexp/shared/src/helpers/event/event.ts +++ b/packages/@liexp/shared/src/helpers/event/event.ts @@ -165,6 +165,7 @@ export const getRelationIds = (e: Events.Event): Events.EventRelationIds => { keywords: e.keywords, links: e.links, areas: [], + socialPosts: [] }; switch (e.type) { diff --git a/packages/@liexp/shared/src/helpers/event/search-event.ts b/packages/@liexp/shared/src/helpers/event/search-event.ts index 969336d638..a000073fb9 100644 --- a/packages/@liexp/shared/src/helpers/event/search-event.ts +++ b/packages/@liexp/shared/src/helpers/event/search-event.ts @@ -82,6 +82,7 @@ export const getNewRelationIds = ( media: acc.media.concat(newMediaIds), keywords: acc.keywords.concat(newKeywordIds), links: acc.links.concat(newLinkIds), + socialPosts: [] }; }), ); diff --git a/packages/@liexp/shared/src/io/http/Events/BaseEvent.ts b/packages/@liexp/shared/src/io/http/Events/BaseEvent.ts index 662c14c1a5..e1af024515 100644 --- a/packages/@liexp/shared/src/io/http/Events/BaseEvent.ts +++ b/packages/@liexp/shared/src/io/http/Events/BaseEvent.ts @@ -52,6 +52,7 @@ export const EventCommon = t.strict( id: UUID, media: t.array(UUID, "media"), links: t.array(UUID, "links"), + socialPosts: t.union([t.array(UUID), t.undefined]), createdAt: DateFromISOString, updatedAt: DateFromISOString, deletedAt: t.union([DateFromISOString, t.undefined], "deletedAt"), diff --git a/packages/@liexp/shared/src/io/http/Events/SearchEventsQuery.ts b/packages/@liexp/shared/src/io/http/Events/SearchEventsQuery.ts index d85938abc9..57766da67d 100644 --- a/packages/@liexp/shared/src/io/http/Events/SearchEventsQuery.ts +++ b/packages/@liexp/shared/src/io/http/Events/SearchEventsQuery.ts @@ -2,6 +2,7 @@ import * as t from "io-ts"; import { BooleanFromString } from "io-ts-types/BooleanFromString"; import { DateFromISOString } from "io-ts-types/DateFromISOString"; import { UUID } from "io-ts-types/UUID"; +import { NumberFromString } from 'io-ts-types/lib/NumberFromString'; import { optionFromUndefined } from "../../Common/optionFromUndefined"; import { GetListQuery } from "../Query"; import { EventType } from "./EventType"; @@ -30,6 +31,8 @@ export const GetSearchEventsQuery = t.strict( emptyGroups: optionFromUndefined(BooleanFromString), emptyMedia: optionFromUndefined(BooleanFromString), emptyLinks: optionFromUndefined(BooleanFromString), + spCount: optionFromUndefined(NumberFromString), + onlyUnshared: optionFromUndefined(BooleanFromString) }, "GetEventsQueryFilter", ); diff --git a/packages/@liexp/shared/src/io/http/Link.ts b/packages/@liexp/shared/src/io/http/Link.ts index f4d12bc954..f26e8aa998 100644 --- a/packages/@liexp/shared/src/io/http/Link.ts +++ b/packages/@liexp/shared/src/io/http/Link.ts @@ -18,6 +18,7 @@ export const GetListLinkQuery = t.type( creator: optionFromNullable(UUID), emptyEvents: optionFromNullable(BooleanFromString), onlyDeleted: optionFromNullable(BooleanFromString), + onlyUnshared: optionFromNullable(BooleanFromString), }, "GetListLinkQuery", ); @@ -76,6 +77,7 @@ export const Link = t.strict( provider: t.union([UUID, t.undefined]), creator: t.union([UUID, t.undefined]), events: t.array(UUID), + socialPosts: t.array(UUID), createdAt: DateFromISOString, updatedAt: DateFromISOString, deletedAt: t.union([DateFromISOString, t.undefined]), diff --git a/packages/@liexp/shared/src/io/http/Media.ts b/packages/@liexp/shared/src/io/http/Media.ts index 55d478db68..1ee237a95e 100644 --- a/packages/@liexp/shared/src/io/http/Media.ts +++ b/packages/@liexp/shared/src/io/http/Media.ts @@ -1,6 +1,7 @@ import * as t from "io-ts"; import { BooleanFromString } from "io-ts-types/lib/BooleanFromString"; import { DateFromISOString } from "io-ts-types/lib/DateFromISOString"; +import { NumberFromString } from 'io-ts-types/lib/NumberFromString'; import { optionFromNullable } from "io-ts-types/lib/optionFromNullable"; import { UUID } from "./Common/UUID"; import { GetListQuery } from "./Query"; @@ -69,6 +70,8 @@ export const GetListMediaQuery = t.type( deletedOnly: optionFromNullable(BooleanFromString), creator: optionFromNullable(UUID), keywords: optionFromNullable(t.array(UUID)), + spCount: optionFromNullable(NumberFromString), + onlyUnshared: optionFromNullable(BooleanFromString), }, "MediaListQuery", ); @@ -102,6 +105,7 @@ export const Media = t.strict( type: MediaType, creator: t.union([UUID, t.undefined]), featuredIn: t.union([t.array(UUID), t.undefined]), + socialPosts: t.union([t.array(UUID), t.undefined]), createdAt: DateFromISOString, updatedAt: DateFromISOString, deletedAt: t.union([DateFromISOString, t.undefined]), diff --git a/packages/@liexp/shared/src/tests/arbitrary/Event.arbitrary.ts b/packages/@liexp/shared/src/tests/arbitrary/Event.arbitrary.ts index b4ccccb51f..4e18df7188 100644 --- a/packages/@liexp/shared/src/tests/arbitrary/Event.arbitrary.ts +++ b/packages/@liexp/shared/src/tests/arbitrary/Event.arbitrary.ts @@ -82,6 +82,7 @@ const uncategorizedProps = propsOmit(http.Events.Uncategorized.Uncategorized, [ "media", "links", "keywords", + "socialPosts", "createdAt", "updatedAt", "deletedAt", @@ -109,4 +110,5 @@ export const UncategorizedArb: fc.Arbitrary = tests keywords: [], provider: undefined, creator: undefined, + socialPosts: [], createdAt: tests.fc.sample(DateArb, 1)[0], updatedAt: tests.fc.sample(DateArb, 1)[0], deletedAt: undefined, diff --git a/packages/@liexp/shared/src/tests/arbitrary/Media.arbitrary.ts b/packages/@liexp/shared/src/tests/arbitrary/Media.arbitrary.ts index 8916675ea8..f009d98af8 100644 --- a/packages/@liexp/shared/src/tests/arbitrary/Media.arbitrary.ts +++ b/packages/@liexp/shared/src/tests/arbitrary/Media.arbitrary.ts @@ -17,6 +17,7 @@ const mediaProps = propsOmit(http.Media.Media, [ "keywords", "areas", "featuredIn", + "socialPosts" ]); export const placeKitten = (): string => { @@ -43,6 +44,7 @@ export const MediaArb: tests.fc.Arbitrary = tests type: "image/png", creator: undefined, extra: undefined, + socialPosts: undefined, location, thumbnail, id: tests.fc.sample(tests.fc.uuid(), 1)[0] as any, diff --git a/packages/@liexp/ui/src/components/admin/Modal/ShareModal.tsx b/packages/@liexp/ui/src/components/admin/Modal/ShareModal.tsx index 455464fd37..3511f330e5 100644 --- a/packages/@liexp/ui/src/components/admin/Modal/ShareModal.tsx +++ b/packages/@liexp/ui/src/components/admin/Modal/ShareModal.tsx @@ -376,6 +376,7 @@ export const ShareModalContent: React.FC = ({ keywords: [], areas: [], featuredIn: [], + socialPosts: undefined, deletedAt: undefined, label: m.media, description: m.type, diff --git a/packages/@liexp/ui/src/components/admin/SocialPost/SocialPostFormTabContent.tsx b/packages/@liexp/ui/src/components/admin/SocialPost/SocialPostFormTabContent.tsx index 52905e2b49..93955a0435 100644 --- a/packages/@liexp/ui/src/components/admin/SocialPost/SocialPostFormTabContent.tsx +++ b/packages/@liexp/ui/src/components/admin/SocialPost/SocialPostFormTabContent.tsx @@ -5,9 +5,9 @@ import { SocialPostDataGrid } from "./SocialPostDatagrid"; export const SocialPostFormTabContent: React.FC<{ source: string; - target: string; + target?: string; type?: string; -}> = ({ source, target, type }) => { +}> = ({ source, target = 'entity', type }) => { const record = useRecordContext(); if (!record) { return null; diff --git a/packages/@liexp/ui/src/components/admin/events/EditEventForm.tsx b/packages/@liexp/ui/src/components/admin/events/EditEventForm.tsx index 33aa21e39c..a3334ccb3f 100644 --- a/packages/@liexp/ui/src/components/admin/events/EditEventForm.tsx +++ b/packages/@liexp/ui/src/components/admin/events/EditEventForm.tsx @@ -48,7 +48,7 @@ export const EditEventForm: React.FC> = ({ - + diff --git a/packages/@liexp/ui/src/components/admin/links/AdminLinks.tsx b/packages/@liexp/ui/src/components/admin/links/AdminLinks.tsx index 545ebc93b5..e65b689951 100644 --- a/packages/@liexp/ui/src/components/admin/links/AdminLinks.tsx +++ b/packages/@liexp/ui/src/components/admin/links/AdminLinks.tsx @@ -30,6 +30,7 @@ import { type RaRecord, } from "react-admin"; import { Box, Grid, Toolbar } from "../../mui"; +import { SocialPostFormTabContent } from '../SocialPost/SocialPostFormTabContent'; import { DangerZoneField } from "../common/DangerZoneField"; import { EditForm } from "../common/EditForm"; import URLMetadataInput from "../common/URLMetadataInput"; @@ -55,6 +56,7 @@ const linksFilter = [ // , , , + , ]; export const LinkListActions: React.FC = () => { @@ -110,6 +112,10 @@ export const LinkDatagrid: React.FC = (props) => { label="resources.links.fields.events_length" render={(r: any | undefined) => r?.events?.length ?? "-"} /> + r?.socialPosts?.length ?? "-"} + /> @@ -270,6 +276,9 @@ export const LinkEdit: React.FC = () => { + + + ); diff --git a/packages/@liexp/ui/src/components/admin/media/MediaEdit.tsx b/packages/@liexp/ui/src/components/admin/media/MediaEdit.tsx index 72c5be6fa7..8221b2c8f9 100644 --- a/packages/@liexp/ui/src/components/admin/media/MediaEdit.tsx +++ b/packages/@liexp/ui/src/components/admin/media/MediaEdit.tsx @@ -17,7 +17,7 @@ import { useRecordContext, useRefresh, type EditProps, - type FieldProps + type FieldProps, } from "react-admin"; import { transformMedia } from "../../../client/admin/MediaAPI"; import { Box, Button, Grid, alpha } from "../../mui"; @@ -180,7 +180,7 @@ export const MediaEdit: React.FC = (props: EditProps) => { } style={{ - background: record?.deletedAt ? alpha("#ff0000", .3) : undefined, + background: record?.deletedAt ? alpha("#ff0000", 0.3) : undefined, }} > @@ -202,12 +202,24 @@ export const MediaEdit: React.FC = (props: EditProps) => { - + - + - + @@ -224,11 +236,7 @@ export const MediaEdit: React.FC = (props: EditProps) => { - + diff --git a/packages/@liexp/ui/src/components/admin/media/MediaList.tsx b/packages/@liexp/ui/src/components/admin/media/MediaList.tsx index 9d2ca2a998..739de13818 100644 --- a/packages/@liexp/ui/src/components/admin/media/MediaList.tsx +++ b/packages/@liexp/ui/src/components/admin/media/MediaList.tsx @@ -13,6 +13,7 @@ import { useGetIdentity, usePermissions, type ListProps, + NumberInput, } from "react-admin"; import { Box, Typography, amber } from "../../mui"; import { toFormattedDuration } from "./DurationField"; @@ -25,6 +26,8 @@ const mediaFilters = [ , , , + , + , , , ]; @@ -108,6 +111,12 @@ export const MediaDataGrid: React.FC = () => { return r.links.length; }} /> + { + return r.socialPosts?.length ?? 0; + }} + /> diff --git a/packages/@liexp/ui/src/components/admin/stories/AdminStories.tsx b/packages/@liexp/ui/src/components/admin/stories/AdminStories.tsx index 0e0ea1202d..4807c34dcb 100644 --- a/packages/@liexp/ui/src/components/admin/stories/AdminStories.tsx +++ b/packages/@liexp/ui/src/components/admin/stories/AdminStories.tsx @@ -35,6 +35,7 @@ import { import { uploadImages } from "../../../client/admin/MediaAPI"; import { Box, Grid } from "../../mui"; import ReactPageInput from "../ReactPageInput"; +import { SocialPostFormTabContent } from '../SocialPost/SocialPostFormTabContent'; import { EditForm } from "../common/EditForm"; import ReferenceArrayKeywordInput from "../keywords/ReferenceArrayKeywordInput"; import ReferenceMediaInput from "../media/input/ReferenceMediaInput"; @@ -166,6 +167,9 @@ export const StoryEdit: React.FC = (props) => { + + + ); diff --git a/packages/@liexp/ui/src/i18n/en-US.ts b/packages/@liexp/ui/src/i18n/en-US.ts index 0864510af9..b4603b4dee 100644 --- a/packages/@liexp/ui/src/i18n/en-US.ts +++ b/packages/@liexp/ui/src/i18n/en-US.ts @@ -40,6 +40,7 @@ export default { name: "Link |||| Links", fields: { events_length: "N of events", + social_posts_length: "N of social posts" }, actions: { update_metadata: "Update URL Metadata", diff --git a/packages/@liexp/ui/src/templates/links/LinkTemplateUI.tsx b/packages/@liexp/ui/src/templates/links/LinkTemplateUI.tsx index 4da75ffd07..24d2f777a6 100644 --- a/packages/@liexp/ui/src/templates/links/LinkTemplateUI.tsx +++ b/packages/@liexp/ui/src/templates/links/LinkTemplateUI.tsx @@ -40,6 +40,7 @@ export const LinkTemplateUI: React.FC = ({ updatedAt: new Date(), deletedAt: undefined, extra: undefined, + socialPosts: undefined, }} itemStyle={{ maxHeight: 200, minHeight: 200, width: "100%" }} disableZoom diff --git a/services/admin-web/src/pages/AdminEvents.tsx b/services/admin-web/src/pages/AdminEvents.tsx index 7d6615ffc7..0b88b5fb6d 100644 --- a/services/admin-web/src/pages/AdminEvents.tsx +++ b/services/admin-web/src/pages/AdminEvents.tsx @@ -32,6 +32,7 @@ import { SavedQueriesList, TextField, type RaRecord as Record, + NumberInput, } from "@liexp/ui/lib/components/admin/react-admin"; import { Box, @@ -63,6 +64,8 @@ const eventsFilter = [ size="small" />, , + , + , , , , @@ -211,6 +214,7 @@ export const EventList: React.FC = () => ( return 0; }} /> + r.socialPosts?.length ?? 0} /> => { pipe( events, - fp.A.map((e) => { + fp.A.foldMap(fp.Void.Monoid)((e) => { ctx.logger.debug.log( "Updated event %s (%s) => %O", e.id, diff --git a/services/api/src/entities/Event.v2.entity.ts b/services/api/src/entities/Event.v2.entity.ts index 0d54979cd6..39c5d1a475 100644 --- a/services/api/src/entities/Event.v2.entity.ts +++ b/services/api/src/entities/Event.v2.entity.ts @@ -17,6 +17,7 @@ import { type GroupEntity } from "./Group.entity"; import { KeywordEntity } from "./Keyword.entity"; import { LinkEntity } from "./Link.entity"; import { MediaEntity } from "./Media.entity"; +import { type SocialPostEntity } from './SocialPost.entity'; import { StoryEntity } from "./Story.entity"; @Entity("event_v2") @@ -68,15 +69,17 @@ export class EventV2Entity { @JoinTable() keywords: KeywordEntity[]; - actors: ActorEntity[]; - groups: GroupEntity[]; - @ManyToMany(() => StoryEntity, (k) => k.events, { cascade: false, onDelete: "NO ACTION", }) stories: StoryEntity[]; + + actors: ActorEntity[]; + groups: GroupEntity[]; + socialPosts?: SocialPostEntity[]; + @CreateDateColumn() createdAt: Date; diff --git a/services/api/src/entities/Link.entity.ts b/services/api/src/entities/Link.entity.ts index 510f3c599b..464ae59c61 100644 --- a/services/api/src/entities/Link.entity.ts +++ b/services/api/src/entities/Link.entity.ts @@ -14,6 +14,7 @@ import { import { EventV2Entity } from "./Event.v2.entity"; import { KeywordEntity } from "./Keyword.entity"; import { MediaEntity } from "./Media.entity"; +import { type SocialPostEntity } from './SocialPost.entity'; import { UserEntity } from "./User.entity"; @Entity("link") @@ -60,6 +61,8 @@ export class LinkEntity { }) keywords: KeywordEntity[]; + socialPosts?: SocialPostEntity[] + @CreateDateColumn() createdAt: Date; diff --git a/services/api/src/entities/Media.entity.ts b/services/api/src/entities/Media.entity.ts index 961add83c2..dc97412620 100644 --- a/services/api/src/entities/Media.entity.ts +++ b/services/api/src/entities/Media.entity.ts @@ -1,4 +1,4 @@ -import { type MediaExtra, MediaType } from "@liexp/shared/lib/io/http/Media"; +import { MediaType, type MediaExtra } from "@liexp/shared/lib/io/http/Media"; import { type UUID } from "io-ts-types/lib/UUID"; import { Column, @@ -16,6 +16,7 @@ import { AreaEntity } from "./Area.entity"; import { EventV2Entity } from "./Event.v2.entity"; import { KeywordEntity } from "./Keyword.entity"; import { LinkEntity } from "./Link.entity"; +import { type SocialPostEntity } from './SocialPost.entity'; import { StoryEntity } from "./Story.entity"; import { UserEntity } from "./User.entity"; @@ -86,6 +87,8 @@ export class MediaEntity { }) keywords: KeywordEntity[]; + socialPosts?: SocialPostEntity[] + @CreateDateColumn() createdAt: Date; diff --git a/services/api/src/entities/SocialPost.entity.ts b/services/api/src/entities/SocialPost.entity.ts index 4ed7a346aa..e7a655067b 100644 --- a/services/api/src/entities/SocialPost.entity.ts +++ b/services/api/src/entities/SocialPost.entity.ts @@ -6,9 +6,11 @@ import { DeleteDateColumn, Entity, Index, + ManyToMany, PrimaryGeneratedColumn, UpdateDateColumn, } from "typeorm"; +import { LinkEntity } from './Link.entity'; @Entity("social_post") @Index(["type", "entity"]) @@ -37,6 +39,10 @@ export class SocialPostEntity { @Column({ type: "timestamptz" }) scheduledAt: Date; + + @ManyToMany(() => LinkEntity, (l) => l.socialPosts, { cascade: false }) + links: LinkEntity[] + @CreateDateColumn() createdAt: Date; diff --git a/services/api/src/flows/event-suggestion/__tests__/createFromTGMessage.flow.e2e.ts b/services/api/src/flows/event-suggestion/__tests__/createFromTGMessage.flow.e2e.ts index d691813c56..9077ce17f6 100644 --- a/services/api/src/flows/event-suggestion/__tests__/createFromTGMessage.flow.e2e.ts +++ b/services/api/src/flows/event-suggestion/__tests__/createFromTGMessage.flow.e2e.ts @@ -191,7 +191,7 @@ describe("Create From TG Message", () => { ({ id, ...r }) => r, ) as any[]; - const { creator, areas, keywords, stories, ...media } = await throwTE( + const { creator, areas, keywords, stories, socialPosts, ...media } = await throwTE( Test.ctx.db.findOneOrFail(MediaEntity, { where: { description: Equal(message.caption) }, }), diff --git a/services/api/src/flows/events/extractFromURL.flow.ts b/services/api/src/flows/events/extractFromURL.flow.ts index b0e7e657ad..f9e23fcc77 100644 --- a/services/api/src/flows/events/extractFromURL.flow.ts +++ b/services/api/src/flows/events/extractFromURL.flow.ts @@ -301,6 +301,7 @@ const extractByProvider: TEFlow< actors: [], groups: [], media: [], + socialPosts: [], createdAt: new Date(), updatedAt: new Date(), deletedAt: undefined, @@ -330,6 +331,7 @@ const extractByProvider: TEFlow< keywords: [], media: [], events: [], + socialPosts: [], actors: relations.actors, groups: relations.groups, stories: [], diff --git a/services/api/src/flows/networks/createHierarchyNetwork.flow.ts b/services/api/src/flows/networks/createHierarchyNetwork.flow.ts index c5505ba916..909eb85ca9 100644 --- a/services/api/src/flows/networks/createHierarchyNetwork.flow.ts +++ b/services/api/src/flows/networks/createHierarchyNetwork.flow.ts @@ -18,6 +18,7 @@ import { type Media, type Events, } from "@liexp/shared/lib/io/http"; +import { type UUID } from '@liexp/shared/lib/io/http/Common' import { EventType } from "@liexp/shared/lib/io/http/Events"; import { StatsType } from "@liexp/shared/lib/io/http/Stats"; import { walkPaginatedRequest } from "@liexp/shared/lib/utils/fp.utils"; @@ -155,10 +156,10 @@ export const createStatsByEntityType: TEFlow< ({ skip, amount }) => searchEventV2Query(ctx)({ ids: O.none, - actors: type === StatsType.types[1].value ? O.some([id]) : O.none, - groups: type === StatsType.types[2].value ? O.some([id]) : O.none, + actors: type === StatsType.types[1].value ? O.some([id as UUID]) : O.none, + groups: type === StatsType.types[2].value ? O.some([id as UUID]) : O.none, groupsMembers: O.none, - keywords: type === StatsType.types[0].value ? O.some([id]) : O.none, + keywords: type === StatsType.types[0].value ? O.some([id as UUID]) : O.none, links: O.none, locations: O.none, type: O.some(EventType.types.map((t) => t.value)), diff --git a/services/api/src/flows/stats/createStatsByType.flow.ts b/services/api/src/flows/stats/createStatsByType.flow.ts index 8953beb1cf..dc81355b61 100644 --- a/services/api/src/flows/stats/createStatsByType.flow.ts +++ b/services/api/src/flows/stats/createStatsByType.flow.ts @@ -13,6 +13,7 @@ import { type GroupMember, type Media, } from "@liexp/shared/lib/io/http"; +import { type UUID } from '@liexp/shared/lib/io/http/Common'; import { EventType, type SearchEvent } from "@liexp/shared/lib/io/http/Events"; import { walkPaginatedRequest } from "@liexp/shared/lib/utils/fp.utils"; import { sequenceS } from "fp-ts/Apply"; @@ -198,9 +199,9 @@ export const createStatsByType: TEFlow< ({ skip, amount }) => searchEventV2Query(ctx)({ ids: O.none, - actors: type === "actors" ? O.some([id]) : O.none, - groups: type === "groups" ? O.some([id]) : O.none, - keywords: type === "keywords" ? O.some([id]) : O.none, + actors: type === "actors" ? O.some([id as UUID]) : O.none, + groups: type === "groups" ? O.some([id as UUID]) : O.none, + keywords: type === "keywords" ? O.some([id as UUID]) : O.none, groupsMembers: O.none, links: O.none, locations: O.none, diff --git a/services/api/src/queries/media/fetchManyMedia.query.ts b/services/api/src/queries/media/fetchManyMedia.query.ts index 1724af7409..7f651b729c 100644 --- a/services/api/src/queries/media/fetchManyMedia.query.ts +++ b/services/api/src/queries/media/fetchManyMedia.query.ts @@ -4,6 +4,7 @@ import { pipe } from "fp-ts/function"; import * as t from "io-ts"; import { MediaEntity } from "@entities/Media.entity"; import { type TEFlow } from "@flows/flow.types"; +import { leftJoinSocialPosts } from "@queries/socialPosts/leftJoinSocialPosts.query"; import { addOrder, getORMOptions } from "@utils/orm.utils"; const defaultQuery: http.Media.GetListMediaQuery = { @@ -17,6 +18,8 @@ const defaultQuery: http.Media.GetListMediaQuery = { emptyEvents: fp.O.none, emptyLinks: fp.O.none, deletedOnly: fp.O.none, + spCount: fp.O.none, + onlyUnshared: fp.O.none, _sort: fp.O.some("updatedAt"), _order: fp.O.some("DESC"), _end: fp.O.some(20 as any), @@ -38,6 +41,8 @@ export const fetchManyMedia: TEFlow< emptyLinks, deletedOnly, exclude, + spCount, + onlyUnshared, ...ormQuery } = q; @@ -55,6 +60,11 @@ export const fetchManyMedia: TEFlow< .leftJoinAndSelect("media.events", "events") .leftJoinAndSelect("media.keywords", "keywords") .leftJoinAndSelect("media.links", "links") + .leftJoinAndSelect( + leftJoinSocialPosts("media"), + "socialPosts", + '"socialPosts"."socialPosts_entity" = media.id', + ) .loadAllRelationIds({ relations: ["creator"] }), (q) => { let hasWhere = false; @@ -124,6 +134,16 @@ export const fetchManyMedia: TEFlow< hasWhere = true; } + if (fp.O.isSome(spCount)) { + q.andWhere('"socialPosts_spCount" >= :spCount', { + spCount: spCount.value, + }); + } else if (fp.O.isSome(onlyUnshared)) { + q.andWhere( + '"socialPosts_spCount" < 1 OR "socialPosts_spCount" IS NULL', + ); + } + const includeDeleted = pipe( deletedOnly, fp.O.getOrElse(() => false), diff --git a/services/api/src/queries/socialPosts/leftJoinSocialPosts.query.ts b/services/api/src/queries/socialPosts/leftJoinSocialPosts.query.ts new file mode 100644 index 0000000000..d76920720a --- /dev/null +++ b/services/api/src/queries/socialPosts/leftJoinSocialPosts.query.ts @@ -0,0 +1,22 @@ +import { type SelectQueryBuilder } from "typeorm"; +import { SocialPostEntity } from "@entities/SocialPost.entity"; + +export const leftJoinSocialPosts = + (type: string) => + (subQ: SelectQueryBuilder): SelectQueryBuilder => { + return subQ.select("sp.*").from((qb) => { + return qb + .select([ + 'count(socialPost.id) as "socialPosts_spCount"', + 'socialPost.entity as "socialPosts_entity"', + 'array_agg(socialPost.id) as "socialPosts_ids"', + ]) + .from(SocialPostEntity, "socialPost") + .where("socialPost.type = :type", { + type, + }) + .groupBy("socialPost.entity"); + }, "sp"); + + + }; diff --git a/services/api/src/routes/events/__tests__/editEvent.e2e.ts b/services/api/src/routes/events/__tests__/editEvent.e2e.ts index 416639be36..68d1e5d6a1 100644 --- a/services/api/src/routes/events/__tests__/editEvent.e2e.ts +++ b/services/api/src/routes/events/__tests__/editEvent.e2e.ts @@ -151,6 +151,7 @@ describe("Edit Event", () => { ...event.payload, ...eventData.payload, }, + socialPosts: [], date: eventData.date, createdAt: event.createdAt.toISOString(), updatedAt: body.updatedAt, diff --git a/services/api/src/routes/events/deaths/getListDeath.controller.ts b/services/api/src/routes/events/deaths/getListDeath.controller.ts index cf592ed7b3..ffcae53d64 100644 --- a/services/api/src/routes/events/deaths/getListDeath.controller.ts +++ b/services/api/src/routes/events/deaths/getListDeath.controller.ts @@ -29,6 +29,8 @@ export const MakeGetListDeathEventRoute: Route = (r, ctx) => { emptyKeywords, emptyLinks, emptyMedia, + spCount, + onlyUnshared, ...query }, }) => { @@ -48,6 +50,8 @@ export const MakeGetListDeathEventRoute: Route = (r, ctx) => { withDrafts: O.getOrElse(() => false)(withDrafts), emptyLinks, emptyMedia, + spCount, + onlyUnshared, ...ormOptions, }), TE.chain(({ results, totals: { deaths } }) => diff --git a/services/api/src/routes/events/documentary/getListDocumentaries.controller.ts b/services/api/src/routes/events/documentary/getListDocumentaries.controller.ts index d59c76cc2c..0de872585c 100644 --- a/services/api/src/routes/events/documentary/getListDocumentaries.controller.ts +++ b/services/api/src/routes/events/documentary/getListDocumentaries.controller.ts @@ -35,6 +35,8 @@ export const MakeGetListDocumentaryEventRoute: Route = (r, ctx) => { emptyActors, emptyGroups, emptyKeywords, + spCount, + onlyUnshared, ...query }, }) => { diff --git a/services/api/src/routes/events/eventV2.io.ts b/services/api/src/routes/events/eventV2.io.ts index cb2279b577..b2d0cfa27c 100644 --- a/services/api/src/routes/events/eventV2.io.ts +++ b/services/api/src/routes/events/eventV2.io.ts @@ -22,6 +22,7 @@ export const toEventV2IO = ( excerpt: event.excerpt ?? undefined, body: event.body ?? undefined, date: event.date.toISOString(), + socialPosts: event.socialPosts ?? [], createdAt: event.createdAt.toISOString(), updatedAt: event.updatedAt.toISOString(), deletedAt: event.deletedAt?.toISOString() ?? undefined, diff --git a/services/api/src/routes/events/getEventFromLink.controller.ts b/services/api/src/routes/events/getEventFromLink.controller.ts index 3efc725a69..1f28896497 100644 --- a/services/api/src/routes/events/getEventFromLink.controller.ts +++ b/services/api/src/routes/events/getEventFromLink.controller.ts @@ -90,6 +90,7 @@ export const GetEventFromLinkRoute: Route = (r, ctx) => { links: [], newLinks: suggestedEventLinks, keywords: [], + socialPosts: undefined, createdAt: new Date(), updatedAt: new Date(), deletedAt: undefined, diff --git a/services/api/src/routes/events/patents/getListPatent.controller.ts b/services/api/src/routes/events/patents/getListPatent.controller.ts index 4e6dd041c3..8ba534d16a 100644 --- a/services/api/src/routes/events/patents/getListPatent.controller.ts +++ b/services/api/src/routes/events/patents/getListPatent.controller.ts @@ -29,6 +29,8 @@ export const MakeGetListPatentEventRoute: Route = (r, ctx) => { emptyKeywords, emptyLinks, emptyMedia, + spCount, + onlyUnshared, ...query }, }) => { @@ -48,6 +50,8 @@ export const MakeGetListPatentEventRoute: Route = (r, ctx) => { emptyMedia, withDeleted: O.getOrElse(() => false)(withDeleted), withDrafts: O.getOrElse(() => false)(withDrafts), + spCount, + onlyUnshared, ...ormOptions, }), TE.chain(({ results, totals: { patents } }) => diff --git a/services/api/src/routes/events/queries/searchEventsV2.query.ts b/services/api/src/routes/events/queries/searchEventsV2.query.ts index 1b9ec1dbd8..57c7017a73 100644 --- a/services/api/src/routes/events/queries/searchEventsV2.query.ts +++ b/services/api/src/routes/events/queries/searchEventsV2.query.ts @@ -1,8 +1,15 @@ // https://www.postgresql.org/docs/12/functions-json.html import { type DBError } from "@liexp/backend/lib/providers/orm"; -import { EventTypes } from "@liexp/shared/lib/io/http/Events/EventType"; -import { type EventTotals } from "@liexp/shared/lib/io/http/Events/SearchEventsQuery"; +import { fp } from "@liexp/core/lib/fp"; +import { + EventTypes, + type EventType, +} from "@liexp/shared/lib/io/http/Events/EventType"; +import { + type GetSearchEventsQuery, + type EventTotals +} from "@liexp/shared/lib/io/http/Events/SearchEventsQuery"; import { walkPaginatedRequest } from "@liexp/shared/lib/utils/fp.utils"; import { sequenceS } from "fp-ts/Apply"; import * as A from "fp-ts/Array"; @@ -15,7 +22,8 @@ import { EventV2Entity } from "@entities/Event.v2.entity"; import { GroupMemberEntity } from "@entities/GroupMember.entity"; import { type ControllerError } from "@io/ControllerError"; import { type EventsConfig } from "@queries/config"; -import { addOrder } from "@utils/orm.utils"; +import { leftJoinSocialPosts } from "@queries/socialPosts/leftJoinSocialPosts.query"; +import { addOrder, type ORMOrder } from "@utils/orm.utils"; type WhereT = "AND" | "OR"; @@ -128,29 +136,47 @@ export const whereGroupInArray = return q.setParameter("groups", groups); }; -interface SearchEventQuery { - ids: O.Option; - actors: O.Option; - groups: O.Option; - groupsMembers: O.Option; - keywords: O.Option; - links: O.Option; - media: O.Option; - locations: O.Option; - type: O.Option; - title: O.Option; - draft: O.Option; - startDate: O.Option; - endDate: O.Option; - exclude: O.Option; +// interface SearchEventQuery { +// ids: O.Option; +// actors: O.Option; +// groups: O.Option; +// groupsMembers: O.Option; +// keywords: O.Option; +// links: O.Option; +// media: O.Option; +// locations: O.Option; +// type: O.Option; +// title: O.Option; +// draft: O.Option; +// startDate: O.Option; +// endDate: O.Option; +// exclude: O.Option; +// withDeleted: boolean; +// withDrafts: boolean; +// emptyMedia: O.Option; +// emptyLinks: O.Option; +// spCount: O.Option; +// skip: number; +// take: number; +// order?: Record; +// } + +type SearchEventQuery = Omit< + GetSearchEventsQuery, + | "eventType" + | "withDeleted" + | "withDrafts" + | "_start" + | "_end" + | "_sort" + | "_order" +> & { + type: O.Option; withDeleted: boolean; withDrafts: boolean; - emptyMedia: O.Option; - emptyLinks: O.Option; skip: number; take: number; - order?: Record; -} +} & ORMOrder; const searchQueryDefaults: SearchEventQuery = { ids: O.none, @@ -160,6 +186,7 @@ const searchQueryDefaults: SearchEventQuery = { exclude: O.none, withDeleted: false, withDrafts: false, + spCount: O.none, keywords: O.none, groups: O.none, actors: O.none, @@ -173,6 +200,11 @@ const searchQueryDefaults: SearchEventQuery = { groupsMembers: O.none, emptyLinks: O.none, emptyMedia: O.none, + emptyKeywords: O.none, + emptyActors: O.none, + emptyGroups: O.none, + onlyUnshared: O.none, + order: {}, }; export interface SearchEventOutput { @@ -210,11 +242,18 @@ export const searchEventV2Query = draft, emptyLinks, emptyMedia, + spCount, + onlyUnshared: _onlyUnshared, order, skip, take, } = opts; + const onlyUnshared = pipe( + _onlyUnshared, + fp.O.filter((o) => !!o), + ); + const groupsMembersQuery = pipe( O.isSome(actors) ? pipe( @@ -259,7 +298,12 @@ export const searchEventV2Query = .createQueryBuilder(EventV2Entity, "event") .leftJoinAndSelect("event.keywords", "keywords") .leftJoinAndSelect("event.media", "media") - .leftJoinAndSelect("event.links", "links"), + .leftJoinAndSelect("event.links", "links") + .leftJoinAndSelect( + leftJoinSocialPosts("events"), + "socialPosts", + '"socialPosts"."socialPosts_entity" = event.id', + ), (q) => { let hasWhere = false; @@ -367,6 +411,16 @@ export const searchEventV2Query = }); } + if (O.isSome(spCount)) { + q.andWhere('"socialPosts_spCount" >= :spCount', { + spCount: spCount.value, + }); + } else if (O.isSome(onlyUnshared)) { + q.andWhere( + '"socialPosts_spCount" < 1 OR "socialPosts_spCount" IS NULL', + ); + } + if (O.isSome(draft)) { q.andWhere("event.draft = :draft", { draft: draft.value }); } else if (!withDrafts) { @@ -457,10 +511,12 @@ export const searchEventV2Query = }, ); - return sequenceS(TE.ApplicativePar)({ + return sequenceS(fp.TE.ApplicativePar)({ results: db.execQuery(async () => { const resultQ = searchV2Query.resultsQuery - .loadAllRelationIds({ relations: ["keywords", "links", "media"] }) + .loadAllRelationIds({ + relations: ["keywords", "links", "media"], + }) .skip(skip) .take(take); @@ -472,9 +528,21 @@ export const searchEventV2Query = const results = await resultQ.getRawAndEntities(); - // logger.debug.log("Raw results %O", results); - - return results.entities; + // logger.debug.log("Raw results %O", results.raw); + + return results.entities.map((e) => ({ + ...e, + socialPosts: results.raw + .filter((r) => r.event_id === e.id && !!r.socialPosts_ids) + .reduce((acc, r) => { + r.socialPosts_ids.forEach((id: string) => { + if (!acc.includes(id)) { + acc.push(id); + } + }); + return acc; + }, []), + })); }), uncategorized: db.execQuery(() => searchV2Query.uncategorizedCount.getCount(), diff --git a/services/api/src/routes/events/quotes/listQuotes.controller.ts b/services/api/src/routes/events/quotes/listQuotes.controller.ts index dd1a7b9da4..f2dd88b196 100644 --- a/services/api/src/routes/events/quotes/listQuotes.controller.ts +++ b/services/api/src/routes/events/quotes/listQuotes.controller.ts @@ -35,6 +35,8 @@ export const MakeGetListQuoteRoute: Route = (r, ctx) => { emptyKeywords, emptyLinks, emptyMedia, + spCount, + onlyUnshared, ...query }, }) => { @@ -61,6 +63,8 @@ export const MakeGetListQuoteRoute: Route = (r, ctx) => { emptyMedia, withDeleted: O.getOrElse(() => false)(withDeleted), withDrafts: O.getOrElse(() => false)(withDrafts), + spCount, + onlyUnshared, ...ormOptions, }), TE.chain(({ results, totals: { quotes } }) => diff --git a/services/api/src/routes/events/scientific-study/listScientificStudy.controller.ts b/services/api/src/routes/events/scientific-study/listScientificStudy.controller.ts index ccb04f77a0..1b19d545c0 100644 --- a/services/api/src/routes/events/scientific-study/listScientificStudy.controller.ts +++ b/services/api/src/routes/events/scientific-study/listScientificStudy.controller.ts @@ -29,6 +29,8 @@ export const MakeListScientificStudyRoute: Route = ( emptyKeywords, withDeleted, withDrafts, + spCount, + onlyUnshared, ...query }, }) => { @@ -57,6 +59,8 @@ export const MakeListScientificStudyRoute: Route = ( emptyMedia, withDeleted: O.getOrElse(() => false)(withDeleted), withDrafts: O.getOrElse(() => false)(withDeleted), + spCount, + onlyUnshared, ...queryOptions, }), TE.chain(({ results, totals: { scientificStudies } }) => diff --git a/services/api/src/routes/events/searchEvents.controller.ts b/services/api/src/routes/events/searchEvents.controller.ts index f083a3c091..a242527135 100644 --- a/services/api/src/routes/events/searchEvents.controller.ts +++ b/services/api/src/routes/events/searchEvents.controller.ts @@ -37,10 +37,12 @@ export const SearchEventRoute = (r: Router, ctx: RouteContext): void => { emptyLinks, emptyMedia, ids, + spCount, + onlyUnshared, ...queryRest } = query; - ctx.logger.debug.log("query %O", queryRest); + // ctx.logger.debug.log("query %O", queryRest); const findOptions = getORMOptions( { @@ -80,6 +82,8 @@ export const SearchEventRoute = (r: Router, ctx: RouteContext): void => { withDrafts: O.getOrElse(() => false)(withDrafts), emptyMedia, emptyLinks, + spCount, + onlyUnshared, ...findOptions, }), TE.chain(({ results, totals }) => diff --git a/services/api/src/routes/events/suggestions/createEventFromSuggestion.controller.ts b/services/api/src/routes/events/suggestions/createEventFromSuggestion.controller.ts index ce249e45d9..88a33d6c11 100644 --- a/services/api/src/routes/events/suggestions/createEventFromSuggestion.controller.ts +++ b/services/api/src/routes/events/suggestions/createEventFromSuggestion.controller.ts @@ -25,6 +25,7 @@ export const CreateEventFromSuggestionRoute: Route = (r, ctx) => { TE.map((relations) => ({ ...suggestion.payload.event, ...relations, + socialPosts: [], id: suggestion.payload.type === EventSuggestion.EventSuggestionType.types[1].value diff --git a/services/api/src/routes/events/transactions/getListTransaction.controller.ts b/services/api/src/routes/events/transactions/getListTransaction.controller.ts index 8a8d0935b4..3d82e7f739 100644 --- a/services/api/src/routes/events/transactions/getListTransaction.controller.ts +++ b/services/api/src/routes/events/transactions/getListTransaction.controller.ts @@ -26,6 +26,8 @@ export const MakeGetListTransactionEventRoute: Route = (r, ctx) => { emptyKeywords, emptyLinks, emptyMedia, + onlyUnshared, + spCount, ...query }, }) => { diff --git a/services/api/src/routes/links/link.io.ts b/services/api/src/routes/links/link.io.ts index c60bb77932..ae6c1463c7 100644 --- a/services/api/src/routes/links/link.io.ts +++ b/services/api/src/routes/links/link.io.ts @@ -31,6 +31,7 @@ export const toLinkIO = ( creator: UUID.is(link.creator) ? link.creator : undefined, publishDate: link.publishDate?.toISOString() ?? undefined, events: link.events ?? [], + socialPosts: link.socialPosts ?? [], createdAt: link.createdAt.toISOString(), updatedAt: link.updatedAt.toISOString(), deletedAt: link.deletedAt?.toISOString(), diff --git a/services/api/src/routes/links/listLinks.controller.ts b/services/api/src/routes/links/listLinks.controller.ts index 62b3dd8e71..f7be37d9f0 100644 --- a/services/api/src/routes/links/listLinks.controller.ts +++ b/services/api/src/routes/links/listLinks.controller.ts @@ -1,3 +1,4 @@ +import { fp } from '@liexp/core/lib/fp'; import { AddEndpoint, Endpoints } from "@liexp/shared/lib/endpoints"; import { type Router } from "express"; import * as A from "fp-ts/Array"; @@ -9,6 +10,7 @@ import { type RouteContext } from "../route.types"; import { toLinkIO } from "./link.io"; import { LinkEntity } from "@entities/Link.entity"; import { toControllerError } from "@io/ControllerError"; +import { leftJoinSocialPosts } from '@queries/socialPosts/leftJoinSocialPosts.query'; import { addOrder, getORMOptions } from "@utils/orm.utils"; export const MakeListLinksRoute = (r: Router, ctx: RouteContext): void => { @@ -25,6 +27,7 @@ export const MakeListLinksRoute = (r: Router, ctx: RouteContext): void => { onlyDeleted, provider, creator, + onlyUnshared: _onlyUnshared, ...query }, }, @@ -34,12 +37,18 @@ export const MakeListLinksRoute = (r: Router, ctx: RouteContext): void => { { ...query }, ctx.env.DEFAULT_PAGE_SIZE, ); + const onlyUnshared = pipe( + _onlyUnshared, + fp.O.filter(o => !!o) + ) ctx.logger.debug.log(`find Options %O`, { events, ids, search, emptyEvents, + onlyDeleted, + onlyUnshared, ...findOptions, }); @@ -51,7 +60,8 @@ export const MakeListLinksRoute = (r: Router, ctx: RouteContext): void => { .leftJoinAndSelect("link.creator", "creator") .leftJoinAndSelect("link.image", "image") .leftJoinAndSelect("link.events", "events") - .leftJoinAndSelect("link.keywords", "keywords"), + .leftJoinAndSelect("link.keywords", "keywords") + .leftJoinAndSelect(leftJoinSocialPosts('links'), "socialPosts", 'link.id = "socialPosts"."socialPosts_entity"'), (q) => { if (O.isSome(search)) { return q.where( @@ -104,6 +114,10 @@ export const MakeListLinksRoute = (r: Router, ctx: RouteContext): void => { } } + if (O.isSome(onlyUnshared)) { + q.andWhere('"socialPosts_spCount" < 1 OR "socialPosts_spCount" IS NULL') + } + return q; }, (q) => { @@ -113,11 +127,11 @@ export const MakeListLinksRoute = (r: Router, ctx: RouteContext): void => { return q; }, (q) => { - // ctx.logger.debug.log( - // "Get links query %s, %O", - // q.getSql(), - // q.getParameters() - // ); + ctx.logger.debug.log( + "Get links query %s, %O", + q.getSql(), + q.getParameters() + ); return q .skip(findOptions.skip) .take(findOptions.take) @@ -136,6 +150,7 @@ export const MakeListLinksRoute = (r: Router, ctx: RouteContext): void => { creator: (r.creator?.id as any) ?? null, events: r.events.map((e) => e.id) as any[], keywords: r.keywords.map((e) => e.id) as any[], + socialPosts: (r.socialPosts ?? []).map(s => s.id) as any[] })), A.traverse(E.Applicative)(toLinkIO), E.map((data) => ({ data, total })), diff --git a/services/api/src/routes/media/__tests__/createMedia.e2e.ts b/services/api/src/routes/media/__tests__/createMedia.e2e.ts index 65a4f75bff..80bf0c6503 100644 --- a/services/api/src/routes/media/__tests__/createMedia.e2e.ts +++ b/services/api/src/routes/media/__tests__/createMedia.e2e.ts @@ -64,6 +64,7 @@ describe("Create Media", () => { delete media.thumbnail; delete media.extra; delete media.deletedAt; + delete media.socialPosts; expect(response.body.data).toMatchObject({ ...media, diff --git a/services/api/src/routes/media/__tests__/listMedia.e2e.ts b/services/api/src/routes/media/__tests__/listMedia.e2e.ts index bd294931f4..e48235119f 100644 --- a/services/api/src/routes/media/__tests__/listMedia.e2e.ts +++ b/services/api/src/routes/media/__tests__/listMedia.e2e.ts @@ -29,7 +29,7 @@ describe("List Media", () => { await throwTE( Test.ctx.db.save(EventV2Entity, [ - { ...event, links: [], media: [{ id: media[0].id }], keywords: [] }, + { ...event, socialPosts: undefined, links: [], media: [{ id: media[0].id }], keywords: [] }, ]), ); }); @@ -75,6 +75,7 @@ describe("List Media", () => { extra, description, label, + socialPosts, ...expectedMedia } = media[0] as any; diff --git a/services/api/src/routes/media/media.io.ts b/services/api/src/routes/media/media.io.ts index 62869a32b4..5078a95079 100644 --- a/services/api/src/routes/media/media.io.ts +++ b/services/api/src/routes/media/media.io.ts @@ -22,6 +22,7 @@ export const toMediaIO = ( keywords: media.keywords ?? [], areas: media.areas ?? [], featuredIn: media.featuredIn ?? [], + socialPosts: media.socialPosts ?? [], thumbnail: media.thumbnail ? ensureHTTPS(media.thumbnail) : undefined, transferable: !media.location.includes(spaceEndpoint), createdAt: media.createdAt.toISOString(), diff --git a/services/api/src/routes/users/userLogin.controller.ts b/services/api/src/routes/users/userLogin.controller.ts index ae6e62a41c..e11ec9799c 100644 --- a/services/api/src/routes/users/userLogin.controller.ts +++ b/services/api/src/routes/users/userLogin.controller.ts @@ -15,6 +15,11 @@ import * as passwordUtils from "@utils/password.utils"; export const MakeUserLoginRoute = (r: Router, ctx: RouteContext): void => { AddEndpoint(r)(UserLogin, ({ body: { username, password } }) => { ctx.logger.debug.log("Login user with username or email %s", username); + // void pipe( + // passwordUtils.hash(password), + // ctx.logger.debug.logInTaskEither('Password hash %s') + // )() + ; return pipe( ctx.db.findOneOrFail(UserEntity, { where: [{ username }, { email: username }], diff --git a/services/api/src/utils/orm.utils.ts b/services/api/src/utils/orm.utils.ts index 0d86de7ac6..37d8bb9e6e 100644 --- a/services/api/src/utils/orm.utils.ts +++ b/services/api/src/utils/orm.utils.ts @@ -16,7 +16,7 @@ import { type ObjectLiteral, } from "typeorm"; -interface ORMOrder { +export interface ORMOrder { order: Record; }