diff --git a/compose.yml b/compose.yml index 1a8a1dcdd9..12462ffb72 100644 --- a/compose.yml +++ b/compose.yml @@ -125,8 +125,8 @@ services: - worker-node-modules:/usr/src/app/node_modules - ./packages/@liexp:/usr/src/app/packages/@liexp:ro - ./services/worker:/usr/src/app/services/worker:ro - - ./services/worker/config:/usr/src/app/services/worker/config:rw - - ./services/worker/temp:/usr/src/app/services/worker/temp:rw + - ./services/worker/config:/usr/src/app/services/worker/config:cached + - ./services/worker/temp:/usr/src/app/services/worker/temp:cached mem_limit: 512M restart: always networks: diff --git a/lies.exposed.code-workspace b/lies.exposed.code-workspace index 0910aba2d8..8d3f0d851e 100644 --- a/lies.exposed.code-workspace +++ b/lies.exposed.code-workspace @@ -63,6 +63,7 @@ "**/bower_components": true, "**/*.code-search": true, "build/**": true, + "coverage/**": true, "lib/**": true, }, "eslint.workingDirectories": [ diff --git a/packages/@liexp/backend/src/flows/event/scientific-study/createFromURL.flow.ts b/packages/@liexp/backend/src/flows/event/createEventFromURL.flow.ts similarity index 54% rename from packages/@liexp/backend/src/flows/event/scientific-study/createFromURL.flow.ts rename to packages/@liexp/backend/src/flows/event/createEventFromURL.flow.ts index abfa3cf02a..e81d4091ad 100644 --- a/packages/@liexp/backend/src/flows/event/scientific-study/createFromURL.flow.ts +++ b/packages/@liexp/backend/src/flows/event/createEventFromURL.flow.ts @@ -1,24 +1,24 @@ import { fp, pipe } from "@liexp/core/lib/fp/index.js"; -import { type URL } from "@liexp/shared/lib/io/http/Common/index.js"; -import { SCIENTIFIC_STUDY } from "@liexp/shared/lib/io/http/Events/EventType.js"; +import { type UUID, type URL } from "@liexp/shared/lib/io/http/Common/index.js"; +import { type EventType } from "@liexp/shared/lib/io/http/Events/EventType.js"; import * as O from "fp-ts/lib/Option.js"; import { type ReaderTaskEither } from "fp-ts/lib/ReaderTaskEither.js"; import * as TE from "fp-ts/lib/TaskEither.js"; import { Equal } from "typeorm"; -import { type ConfigContext } from "../../../context/config.context.js"; -import { type DatabaseContext } from "../../../context/db.context.js"; -import { type FSClientContext } from "../../../context/fs.context.js"; +import { type ConfigContext } from "../../context/config.context.js"; +import { type DatabaseContext } from "../../context/db.context.js"; +import { type FSClientContext } from "../../context/fs.context.js"; import { type NERProviderContext, type URLMetadataContext, -} from "../../../context/index.js"; -import { type LoggerContext } from "../../../context/logger.context.js"; -import { type PuppeteerProviderContext } from "../../../context/puppeteer.context.js"; -import { EventV2Entity } from "../../../entities/Event.v2.entity.js"; -import { type UserEntity } from "../../../entities/User.entity.js"; -import { ServerError } from "../../../errors/ServerError.js"; -import { findByURL } from "../../../queries/events/scientificStudy.query.js"; -import { extractEventFromURL } from "../extractFromURL.flow.js"; +} from "../../context/index.js"; +import { type LoggerContext } from "../../context/logger.context.js"; +import { type PuppeteerProviderContext } from "../../context/puppeteer.context.js"; +import { EventV2Entity } from "../../entities/Event.v2.entity.js"; +import { type UserEntity } from "../../entities/User.entity.js"; +import { ServerError } from "../../errors/ServerError.js"; +import { findByURL } from "../../queries/events/scientificStudy.query.js"; +import { extractEventFromURL } from "./extractFromURL.flow.js"; export const createEventFromURL = < C extends LoggerContext & @@ -30,7 +30,9 @@ export const createEventFromURL = < PuppeteerProviderContext, >( user: UserEntity, + eventId: UUID, url: URL, + type: EventType, ): ReaderTaskEither => { return pipe( findByURL(url), @@ -43,30 +45,32 @@ export const createEventFromURL = < fp.RTE.ask(), fp.RTE.chainTaskEitherK((ctx) => pipe( - ctx.puppeteer.getBrowser({}), - TE.mapLeft(ServerError.fromUnknown), - TE.chain((b) => - pipe( + ctx.puppeteer.execute({}, (b) => { + return pipe( TE.tryCatch( () => b.pages().then((p) => p[0]), ServerError.fromUnknown, ), TE.chain((p) => extractEventFromURL(p, user, { - type: SCIENTIFIC_STUDY.value, + type, url, })(ctx), ), - TE.chainFirst(() => { - return TE.tryCatch(() => b.close(), ServerError.fromUnknown); - }), - ), - ), + ); + }), TE.chain((meta) => { if (O.isSome(meta)) { - return ctx.db.save(EventV2Entity, [meta.value]); + return ctx.db.save(EventV2Entity, [ + { ...meta.value, id: eventId }, + ]); } - return TE.left(ServerError.of()); + + return TE.left( + ServerError.fromUnknown( + new Error(`No event can be extracted from url #{url} `), + ), + ); }), TE.chain(([result]) => ctx.db.findOneOrFail(EventV2Entity, { diff --git a/packages/@liexp/backend/src/flows/event/extractFromURL.flow.ts b/packages/@liexp/backend/src/flows/event/extractFromURL.flow.ts index eceb13fd51..2bdeb445f4 100644 --- a/packages/@liexp/backend/src/flows/event/extractFromURL.flow.ts +++ b/packages/@liexp/backend/src/flows/event/extractFromURL.flow.ts @@ -28,7 +28,7 @@ import { ServerError } from "../../errors/ServerError.js"; import { extractRelationsFromURL } from "../admin/nlp/extractRelationsFromURL.flow.js"; import { fetchAndSave } from "../links/link.flow.js"; -const extractEventFromProviderLink = +const extractPageMetadataFromProviderLink = ( p: puppeteer.Page, host: string, @@ -170,6 +170,7 @@ const extractByProvider = p: puppeteer.Page, host: string, l: LinkEntity, + type: EventType, ): ReaderTaskEither> => (ctx) => { return pipe( @@ -177,7 +178,7 @@ const extractByProvider = TE.bind("relations", () => sequenceS(TE.ApplicativePar)({ relations: extractRelationsFromURL(p, l.url)(ctx), - provider: extractEventFromProviderLink(p, host, l)(ctx), + provider: extractPageMetadataFromProviderLink(p, host, l)(ctx), }), ), TE.bind( @@ -240,7 +241,7 @@ const extractByProvider = ); }, ServerError.fromUnknown), TE.map((suggestions) => - suggestions.find((s) => s.event.type === "ScientificStudy"), + suggestions.find((s) => s.event.type === type), ), TE.map(O.fromNullable), ); @@ -305,7 +306,10 @@ export const extractEventFromURL = ctx.logger.debug.log("Extracting event from host %s (%s)", host, l.url); return pipe( - fetchAndSave(user, l.url)(ctx), - TE.chain((le) => extractByProvider(p, host, le)(ctx)), - ); + fp.RTE.Do, + fp.RTE.bind("event", () => fetchAndSave(user, l.url)), + fp.RTE.chain(({ event }) => { + return pipe(extractByProvider(p, host, event, l.type)); + }), + )(ctx); }; diff --git a/packages/@liexp/backend/src/pubsub/events/createEventFromURL.pubSub.ts b/packages/@liexp/backend/src/pubsub/events/createEventFromURL.pubSub.ts new file mode 100644 index 0000000000..a4020e84ee --- /dev/null +++ b/packages/@liexp/backend/src/pubsub/events/createEventFromURL.pubSub.ts @@ -0,0 +1,15 @@ +import { URL } from "@liexp/shared/lib/io/http/Common/URL.js"; +import { UUID } from "@liexp/shared/lib/io/http/Common/UUID.js"; +import { EventType } from "@liexp/shared/lib/io/http/Events/EventType.js"; +import * as t from "io-ts"; +import { RedisPubSub } from "../../providers/redis/redis.provider.js"; + +export const CreateEventFromURLPubSub = RedisPubSub( + "event:create-from-url", + t.strict({ + url: URL, + type: EventType, + eventId: UUID, + userId: UUID, + }), +); diff --git a/packages/@liexp/backend/src/pubsub/events/scientific-study/createFromURL.pubSub.ts b/packages/@liexp/backend/src/pubsub/events/scientific-study/createFromURL.pubSub.ts deleted file mode 100644 index 4a495a34f5..0000000000 --- a/packages/@liexp/backend/src/pubsub/events/scientific-study/createFromURL.pubSub.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { EventType } from "@liexp/shared/lib/io/http/Events/EventType.js"; -import * as t from "io-ts"; -import { RedisPubSub } from "../../../providers/redis/redis.provider.js"; - -export const CreateScientificStudyFromURLPubSub = RedisPubSub( - "scientific-study:create-from-url", - t.strict({ - url: t.string, - type: EventType, - }), -); diff --git a/packages/@liexp/backend/src/queries/events/createEvent.query.ts b/packages/@liexp/backend/src/queries/events/createEvent.query.ts index bbf296fe67..2e55a3adc9 100644 --- a/packages/@liexp/backend/src/queries/events/createEvent.query.ts +++ b/packages/@liexp/backend/src/queries/events/createEvent.query.ts @@ -14,7 +14,7 @@ import { fetchRelationIds } from "./fetchEventRelations.query.js"; export const createEventQuery = < C extends DatabaseContext & URLMetadataContext & LoggerContext, >( - input: http.Events.CreateEventBody, + input: http.Events.CreateEventPlainBody, ): ReaderTaskEither> => { return pipe( fetchRelationIds({ diff --git a/packages/@liexp/shared/src/endpoints/events/scientificStudy.endpoint.ts b/packages/@liexp/shared/src/endpoints/events/scientificStudy.endpoint.ts index f665a2c0d7..45b38a08ba 100644 --- a/packages/@liexp/shared/src/endpoints/events/scientificStudy.endpoint.ts +++ b/packages/@liexp/shared/src/endpoints/events/scientificStudy.endpoint.ts @@ -75,7 +75,7 @@ export const Edit = Endpoint({ Input: { Params: t.type({ id: UUID }), Body: t.strict( - propsOmit(CreateScientificStudyBody.types[0], ["type"]), + propsOmit(CreateScientificStudyBody, ["type"]), "CreateScientificStudyBody", ), }, diff --git a/packages/@liexp/shared/src/io/http/Events/ScientificStudy.ts b/packages/@liexp/shared/src/io/http/Events/ScientificStudy.ts index 32a27548d5..6052152efe 100644 --- a/packages/@liexp/shared/src/io/http/Events/ScientificStudy.ts +++ b/packages/@liexp/shared/src/io/http/Events/ScientificStudy.ts @@ -1,6 +1,5 @@ import * as t from "io-ts"; import { UUID } from "io-ts-types/lib/UUID.js"; -import { URL } from "../Common/URL.js"; import { CreateEventCommon, EditEventCommon, @@ -21,20 +20,12 @@ export const ScientificStudyPayload = t.strict( export type ScientificStudyPayload = t.TypeOf; -export const CreateScientificStudyPlainBody = t.strict( +export const CreateScientificStudyBody = t.strict( { ...CreateEventCommon.type.props, type: SCIENTIFIC_STUDY, payload: ScientificStudyPayload, }, - "CreateScientificStudyPlainBody", -); -export type CreateScientificStudyPlainBody = t.TypeOf< - typeof CreateScientificStudyPlainBody ->; - -export const CreateScientificStudyBody = t.union( - [CreateScientificStudyPlainBody, t.strict({ url: URL })], "CreateScientificStudyBody", ); diff --git a/packages/@liexp/shared/src/io/http/Events/index.ts b/packages/@liexp/shared/src/io/http/Events/index.ts index 77c924daad..4c404ee61e 100644 --- a/packages/@liexp/shared/src/io/http/Events/index.ts +++ b/packages/@liexp/shared/src/io/http/Events/index.ts @@ -1,7 +1,7 @@ import * as t from "io-ts"; import { type Actor } from "../Actor.js"; import { type Area } from "../Area.js"; -import { type UUID } from "../Common/index.js"; +import { URL, type UUID } from "../Common/index.js"; import { type Group } from "../Group.js"; import { type GroupMember } from "../GroupMember.js"; import { type Keyword } from "../Keyword.js"; @@ -43,17 +43,33 @@ export const EventMap: { [key in Event["type"]]: t.Mixed } = { Quote: Quote.Quote, }; -export const CreateEventBody = t.union( +export const EventFromURLBody = t.strict( + { + url: URL, + type: EventType, + }, + "EventFromURLBody", +); + +export type EventFromURLBody = t.TypeOf; + +export const CreateEventPlainBody = t.union( [ Book.CreateBookBody, Death.CreateDeathBody, Patent.CreatePatentBody, - ScientificStudy.CreateScientificStudyBody.types[0], + ScientificStudy.CreateScientificStudyBody, Uncategorized.CreateEventBody, Documentary.CreateDocumentaryBody, Transaction.CreateTransactionBody, Quote.CreateQuoteBody, ], + "CreateEventPlainBody", +); +export type CreateEventPlainBody = t.TypeOf; + +export const CreateEventBody = t.union( + [CreateEventPlainBody, EventFromURLBody], "CreateEventBody", ); diff --git a/packages/@liexp/shared/src/tests/arbitrary/Event.arbitrary.ts b/packages/@liexp/shared/src/tests/arbitrary/Event.arbitrary.ts index c6c2103072..b41301d822 100644 --- a/packages/@liexp/shared/src/tests/arbitrary/Event.arbitrary.ts +++ b/packages/@liexp/shared/src/tests/arbitrary/Event.arbitrary.ts @@ -13,7 +13,7 @@ interface CreateEventBodyArbOpts { keywordIds?: boolean; } -const createEventProps = propsOmit(http.Events.CreateEventBody.types[4], [ +const createEventProps = propsOmit(http.Events.CreateEventPlainBody.types[4], [ "excerpt", "body", "date", @@ -27,7 +27,7 @@ export const CreateEventBodyArb = ({ linksIds = false, mediaIds = false, keywordIds = false, -}: CreateEventBodyArbOpts = {}): fc.Arbitrary => +}: CreateEventBodyArbOpts = {}): fc.Arbitrary => getArbitrary(t.strict(createEventProps)).map((b) => ({ ...b, excerpt: undefined, diff --git a/packages/@liexp/shared/src/tests/arbitrary/events/ScientificStudy.arbitrary.ts b/packages/@liexp/shared/src/tests/arbitrary/events/ScientificStudy.arbitrary.ts index eeea1edc0b..378e0e9d3e 100644 --- a/packages/@liexp/shared/src/tests/arbitrary/events/ScientificStudy.arbitrary.ts +++ b/packages/@liexp/shared/src/tests/arbitrary/events/ScientificStudy.arbitrary.ts @@ -7,7 +7,7 @@ import { URLArb } from "../URL.arbitrary.js"; import { UUIDArb } from "../common/UUID.arbitrary.js"; const createScientificStudyProps = propsOmit( - http.Events.ScientificStudy.CreateScientificStudyBody.types[0], + http.Events.ScientificStudy.CreateScientificStudyBody, ["excerpt", "body", "date", "draft", "payload", "media", "links", "keywords"], ); diff --git a/packages/@liexp/ui/src/components/admin/events/CreateEventFromLinkButton.tsx b/packages/@liexp/ui/src/components/admin/events/CreateEventFromLinkButton.tsx index 75e326a922..48961e9809 100644 --- a/packages/@liexp/ui/src/components/admin/events/CreateEventFromLinkButton.tsx +++ b/packages/@liexp/ui/src/components/admin/events/CreateEventFromLinkButton.tsx @@ -1,18 +1,13 @@ import { flow, fp } from "@liexp/core/lib/fp/index.js"; -import { getRelationIdsFromEventRelations } from "@liexp/shared/lib/helpers/event/getEventRelationIds.js"; -import { getSuggestions } from "@liexp/shared/lib/helpers/event-suggestion.js"; import { type Link } from "@liexp/shared/lib/io/http/Link.js"; -import { type Media } from "@liexp/shared/lib/io/http/Media/Media.js"; import { type EventSuggestion } from "@liexp/shared/lib/io/http/index.js"; import * as io from "@liexp/shared/lib/io/index.js"; import { type Either } from "fp-ts/lib/Either.js"; -import * as O from "fp-ts/lib/Option.js"; import { useRecordContext } from "ra-core"; import * as React from "react"; import { Button } from "react-admin"; import { useNavigate } from "react-router"; import { useDataProvider } from "../../../hooks/useDataProvider.js"; -import { toBNDocument } from "../../Common/BlockNote/utils/utils.js"; import { ErrorBox } from "../../Common/ErrorBox.js"; import { Box, MenuItem, Select } from "../../mui/index.js"; import EventPreview from "../previews/EventPreview.js"; @@ -40,24 +35,8 @@ export const CreateEventFromLinkButton: React.FC = () => { return Promise.resolve(fp.E.right(suggestion)); } - const result = await apiProvider - .get("open-graph/metadata", { url: link.url, type: "Link" }) - .then(async ({ data: { metadata: m, relations } }: any) => { - const suggestions = await getSuggestions(toBNDocument)( - m, - O.some(link), - O.fromNullable(link.image as Media), - getRelationIdsFromEventRelations(relations.entities), - ); - - const suggestEvent = suggestions.find((t) => t.event.type === type); - if (suggestEvent) { - return fp.E.right(suggestEvent); - } - return fp.E.left(new Error("No suggestion found")); - }); - - return result; + const result = await apiProvider.post("events", { url: link.url, type }); + return result.data; }, [record, type], ); diff --git a/services/api/src/flows/events/scientific-studies/createFromPlainObject.flow.ts b/services/api/src/flows/events/scientific-studies/createFromPlainObject.flow.ts index a74f3bc8a2..76b95acd8c 100644 --- a/services/api/src/flows/events/scientific-studies/createFromPlainObject.flow.ts +++ b/services/api/src/flows/events/scientific-studies/createFromPlainObject.flow.ts @@ -1,18 +1,19 @@ import { EventV2Entity } from "@liexp/backend/lib/entities/Event.v2.entity.js"; import { type KeywordEntity } from "@liexp/backend/lib/entities/Keyword.entity.js"; import { type MediaEntity } from "@liexp/backend/lib/entities/Media.entity.js"; +import { type UserEntity } from "@liexp/backend/lib/entities/User.entity.js"; import { pipe } from "@liexp/core/lib/fp/index.js"; import { UUID } from "@liexp/shared/lib/io/http/Common/index.js"; -import { type CreateScientificStudyPlainBody } from "@liexp/shared/lib/io/http/Events/ScientificStudy.js"; +import { type CreateScientificStudyBody } from "@liexp/shared/lib/io/http/Events/ScientificStudy.js"; import * as TE from "fp-ts/lib/TaskEither.js"; import { type DeepPartial, Equal } from "typeorm"; import { type TEReader } from "#flows/flow.types.js"; export const createScientificStudyFromPlainObject = - ({ - payload, - ...body - }: CreateScientificStudyPlainBody): TEReader => + ( + { payload, ...body }: CreateScientificStudyBody, + user: UserEntity, + ): TEReader => (ctx) => { const scientificStudyData = { ...body, @@ -53,6 +54,7 @@ export const createScientificStudyFromPlainObject = ctx.db.save(EventV2Entity, [ { ...scientificStudyData, + user, }, ]), TE.chain(([result]) => diff --git a/services/api/src/flows/events/scientific-studies/createScientificStudy.flow.ts b/services/api/src/flows/events/scientific-studies/createScientificStudy.flow.ts index 7b7dc17ced..04e245f9fe 100644 --- a/services/api/src/flows/events/scientific-studies/createScientificStudy.flow.ts +++ b/services/api/src/flows/events/scientific-studies/createScientificStudy.flow.ts @@ -1,27 +1,22 @@ import { type EventV2Entity } from "@liexp/backend/lib/entities/Event.v2.entity.js"; -import { UserEntity } from "@liexp/backend/lib/entities/User.entity.js"; -import { createEventFromURL } from "@liexp/backend/lib/flows/event/scientific-study/createFromURL.flow.js"; +import { UserRepository } from "@liexp/backend/lib/services/entity-repository.service.js"; import { fp, pipe } from "@liexp/core/lib/fp/index.js"; -import { CreateScientificStudyBody } from "@liexp/shared/lib/io/http/Events/ScientificStudy.js"; +import { type ScientificStudy } from "@liexp/shared/lib/io/http/Events/index.js"; +import { Equal } from "typeorm"; import { createScientificStudyFromPlainObject } from "./createFromPlainObject.flow.js"; import { type TEReader } from "#flows/flow.types.js"; import { ensureUserExists } from "#utils/user.utils.js"; export const createScientificStudy = ( - body: CreateScientificStudyBody, + body: ScientificStudy.CreateScientificStudyBody, req: Express.Request, ): TEReader => { - if (CreateScientificStudyBody.types[1].is(body)) { - return pipe( - ensureUserExists(req.user), - fp.RTE.fromEither, - fp.RTE.map((u) => { - const user = new UserEntity(); - user.id = u.id; - return user; - }), - fp.RTE.chain((u) => createEventFromURL(u, body.url)), - ); - } - return createScientificStudyFromPlainObject(body); + return pipe( + ensureUserExists(req.user), + fp.RTE.fromEither, + fp.RTE.chain((u) => + UserRepository.findOneOrFail({ where: { id: Equal(u.id) } }), + ), + fp.RTE.chain((u) => createScientificStudyFromPlainObject(body, u)), + ); }; diff --git a/services/api/src/routes/events/createEvent.controller.ts b/services/api/src/routes/events/createEvent.controller.ts index 1f41ba8476..1ee0da29bc 100644 --- a/services/api/src/routes/events/createEvent.controller.ts +++ b/services/api/src/routes/events/createEvent.controller.ts @@ -1,21 +1,56 @@ import { EventV2Entity } from "@liexp/backend/lib/entities/Event.v2.entity.js"; import { EventV2IO } from "@liexp/backend/lib/io/event/eventV2.io.js"; +import { CreateEventFromURLPubSub } from "@liexp/backend/lib/pubsub/events/createEventFromURL.pubSub.js"; import { createEventQuery } from "@liexp/backend/lib/queries/events/createEvent.query.js"; +import { UserRepository } from "@liexp/backend/lib/services/entity-repository.service.js"; import { LoggerService } from "@liexp/backend/lib/services/logger/logger.service.js"; import { pipe } from "@liexp/core/lib/fp/index.js"; import { Endpoints } from "@liexp/shared/lib/endpoints/index.js"; +import { uuid } from "@liexp/shared/lib/io/http/Common/UUID.js"; +import { EventFromURLBody } from "@liexp/shared/lib/io/http/Events/index.js"; +import { AdminCreate } from "@liexp/shared/lib/io/http/User.js"; import * as TE from "fp-ts/lib/TaskEither.js"; -import { Equal } from "typeorm"; +import { type DeepPartial, Equal } from "typeorm"; import { AddEndpoint } from "#routes/endpoint.subscriber.js"; import { type Route } from "#routes/route.types.js"; -import { authenticationHandler } from "#utils/authenticationHandler.js"; +import { + authenticationHandler, + RequestDecoder, +} from "#utils/authenticationHandler.js"; export const CreateEventRoute: Route = (r, ctx) => { AddEndpoint(r, authenticationHandler(["admin:create"])(ctx))( Endpoints.Event.Create, - ({ body }) => { + ({ body }, req) => { return pipe( - createEventQuery(body)(ctx), + RequestDecoder.decodeUserFromRequest(req, [AdminCreate.value])(ctx), + TE.fromIOEither, + TE.chain((u) => + UserRepository.findOneOrFail({ where: { id: Equal(u.id) } })(ctx), + ), + TE.chain((user) => + EventFromURLBody.is(body) + ? pipe( + uuid(), + TE.right, + TE.chainFirst((id) => + CreateEventFromURLPubSub.publish({ + ...body, + eventId: id, + userId: user.id, + })(ctx), + ), + TE.map( + (id) => + ({ + id, + type: body.type, + date: new Date(), + }) as DeepPartial, + ), + ) + : createEventQuery(body)(ctx), + ), LoggerService.TE.debug(ctx, "Create data %O"), TE.chain((data) => ctx.db.save(EventV2Entity, [data])), TE.chain(([event]) => diff --git a/services/api/src/routes/events/scientific-study/extractScientificStudyFromUrl.controller.ts b/services/api/src/routes/events/scientific-study/extractScientificStudyFromUrl.controller.ts index c19fbcbb98..2dcde9af26 100644 --- a/services/api/src/routes/events/scientific-study/extractScientificStudyFromUrl.controller.ts +++ b/services/api/src/routes/events/scientific-study/extractScientificStudyFromUrl.controller.ts @@ -1,7 +1,7 @@ import { EventV2Entity } from "@liexp/backend/lib/entities/Event.v2.entity.js"; import { EventV2IO } from "@liexp/backend/lib/io/event/eventV2.io.js"; -import { CreateScientificStudyFromURLPubSub } from "@liexp/backend/lib/pubsub/events/scientific-study/createFromURL.pubSub.js"; -import { pipe } from "@liexp/core/lib/fp/index.js"; +import { CreateEventFromURLPubSub } from "@liexp/backend/lib/pubsub/events/createEventFromURL.pubSub.js"; +import { fp, pipe } from "@liexp/core/lib/fp/index.js"; import { Endpoints } from "@liexp/shared/lib/endpoints/index.js"; import { SCIENTIFIC_STUDY } from "@liexp/shared/lib/io/http/Events/EventType.js"; import { @@ -13,7 +13,10 @@ import * as TE from "fp-ts/lib/TaskEither.js"; import { Equal } from "typeorm"; import { AddEndpoint } from "#routes/endpoint.subscriber.js"; import { type Route } from "#routes/route.types.js"; -import { authenticationHandler } from "#utils/authenticationHandler.js"; +import { + authenticationHandler, + RequestDecoder, +} from "#utils/authenticationHandler.js"; export const MakeExtractScientificStudyFromURLRoute: Route = (r, ctx) => { AddEndpoint( @@ -25,17 +28,27 @@ export const MakeExtractScientificStudyFromURLRoute: Route = (r, ctx) => { ])(ctx), )( Endpoints.ScientificStudy.Custom.ExtractFromURL, - ({ params: { id } }, req) => { return pipe( - ctx.db.findOneOrFail(EventV2Entity, { where: { id: Equal(id) } }), - TE.chainFirst((event) => - CreateScientificStudyFromURLPubSub.publish({ + TE.Do, + TE.bind("event", () => + ctx.db.findOneOrFail(EventV2Entity, { where: { id: Equal(id) } }), + ), + TE.bind("user", () => + pipe( + RequestDecoder.decodeUserFromRequest(req, [AdminCreate.value])(ctx), + fp.TE.fromIOEither, + ), + ), + TE.chainFirst(({ user, event }) => + CreateEventFromURLPubSub.publish({ type: SCIENTIFIC_STUDY.value, url: (event.payload as any).url, + eventId: event.id, + userId: user.id, })(ctx), ), - TE.chainEitherK(EventV2IO.decodeSingle), + TE.chainEitherK(({ event }) => EventV2IO.decodeSingle(event)), TE.map((data) => ({ body: { data, diff --git a/services/api/test/AppTest.ts b/services/api/test/AppTest.ts index 2c2cfd594c..b44bc4b5d0 100644 --- a/services/api/test/AppTest.ts +++ b/services/api/test/AppTest.ts @@ -79,7 +79,7 @@ export const loadAppContext = async ( ), }), TE.map(({ db, env }) => { - const config = Config(env, path.resolve(__dirname, "..")) + const config = Config(env, path.resolve(__dirname, "../temp")) return ({ env, diff --git a/services/api/test/vitest.config.e2e.ts b/services/api/test/vitest.config.e2e.ts index c69058dc80..e3b85bddfb 100644 --- a/services/api/test/vitest.config.e2e.ts +++ b/services/api/test/vitest.config.e2e.ts @@ -14,11 +14,11 @@ const config = mergeConfig( setupFiles: [toAlias(`testSetup.ts`)], globalSetup: [toAlias(`globalSetup.ts`)], exclude: ["**/build", "**/src/migrations", "**/src/scripts"], - pool: "threads", + pool: "vmThreads", poolOptions: { threads: { - singleThread: process.env.CI ? true : false, - isolate: process.env.CI ? true : false, + singleThread: process.env.CI === "true" ? true : false, + isolate: false, }, }, }, diff --git a/services/api/vitest.workspace.js b/services/api/vitest.workspace.js index 2b651e41e1..8f9cb512c8 100644 --- a/services/api/vitest.workspace.js +++ b/services/api/vitest.workspace.js @@ -1,4 +1 @@ -export default [ - './test/vitest.config.spec.ts', - './test/vitest.config.e2e.ts', -] \ No newline at end of file +export default ["./test/vitest.config.spec.ts", "./test/vitest.config.e2e.ts"]; diff --git a/services/worker/src/services/subscribers/WorkerSubscribers.ts b/services/worker/src/services/subscribers/WorkerSubscribers.ts index 63ad788199..7fbbc220fc 100644 --- a/services/worker/src/services/subscribers/WorkerSubscribers.ts +++ b/services/worker/src/services/subscribers/WorkerSubscribers.ts @@ -1,6 +1,7 @@ import { type Subscriber } from "@liexp/backend/lib/providers/redis/redis.provider.js"; import { fp, pipe } from "@liexp/core/lib/fp/index.js"; import { type RTE } from "../../types.js"; +import { CreateEventFromURLSubscriber } from "./event/createEventFromURL.subscriber.js"; import { ExtractMediaExtraSubscriber } from "./media/extractMediaExtra.subscriber.js"; import { GenerateThumbnailSubscriber } from "./media/generateThumbnail.subscriber.js"; import { TransferFromExternalProviderSubscriber } from "./media/transferFromExternalProvider.subscriber.js"; @@ -15,6 +16,8 @@ export const WorkerSubscribers: RTE = (ctx) => { GenerateThumbnailSubscriber, ExtractMediaExtraSubscriber, TransferFromExternalProviderSubscriber, + // event + CreateEventFromURLSubscriber, // social posts PostToSocialPlatformsSubscriber, // admin diff --git a/services/worker/src/services/subscribers/event/createEventFromURL.subscriber.ts b/services/worker/src/services/subscribers/event/createEventFromURL.subscriber.ts new file mode 100644 index 0000000000..4c417246e1 --- /dev/null +++ b/services/worker/src/services/subscribers/event/createEventFromURL.subscriber.ts @@ -0,0 +1,20 @@ +import { createEventFromURL } from "@liexp/backend/lib/flows/event/createEventFromURL.flow.js"; +import { Subscriber } from "@liexp/backend/lib/providers/redis/redis.provider.js"; +import { CreateEventFromURLPubSub } from "@liexp/backend/lib/pubsub/events/createEventFromURL.pubSub.js"; +import { UserRepository } from "@liexp/backend/lib/services/entity-repository.service.js"; +import { fp } from "@liexp/core/lib/fp/index.js"; +import { pipe } from "fp-ts/lib/function.js"; +import { Equal } from "typeorm"; +import { type RTE } from "../../../types.js"; + +export const CreateEventFromURLSubscriber = Subscriber( + CreateEventFromURLPubSub, + (payload): RTE => + pipe( + UserRepository.findOneOrFail({ where: { id: Equal(payload.userId) } }), + fp.RTE.chain((user) => + createEventFromURL(user, payload.eventId, payload.url, payload.type), + ), + fp.RTE.map(() => undefined), + ), +);