diff --git a/packages/@liexp/backend/src/entities/Event.v2.entity.ts b/packages/@liexp/backend/src/entities/Event.v2.entity.ts index a033ddbb8..9c6458093 100644 --- a/packages/@liexp/backend/src/entities/Event.v2.entity.ts +++ b/packages/@liexp/backend/src/entities/Event.v2.entity.ts @@ -1,7 +1,7 @@ +import { type BlockNoteDocument } from "@liexp/shared/lib/io/http/Common/BlockNoteDocument.js"; import { type UUID } from "@liexp/shared/lib/io/http/Common/index.js"; import { UNCATEGORIZED } from "@liexp/shared/lib/io/http/Events/EventType.js"; import * as http from "@liexp/shared/lib/io/http/index.js"; -import { type BNEditorDocument } from "@liexp/shared/lib/providers/blocknote/type.js"; import { Column, CreateDateColumn, @@ -10,10 +10,10 @@ import { Index, JoinTable, ManyToMany, + ManyToOne, PrimaryGeneratedColumn, type Relation, UpdateDateColumn, - ManyToOne, } from "typeorm"; import { type ActorEntity } from "./Actor.entity.js"; import { AreaEntity } from "./Area.entity.js"; @@ -39,10 +39,10 @@ export class EventV2Entity { date: Date; @Column({ type: "json", nullable: true }) - excerpt: BNEditorDocument | null; + excerpt: BlockNoteDocument | null; @Column({ type: "json", nullable: true }) - body: any[] | null; + body: BlockNoteDocument | null; @Column({ type: "enum", diff --git a/packages/@liexp/backend/src/entities/Media.entity.ts b/packages/@liexp/backend/src/entities/Media.entity.ts index fa7f1421d..f7f82654d 100644 --- a/packages/@liexp/backend/src/entities/Media.entity.ts +++ b/packages/@liexp/backend/src/entities/Media.entity.ts @@ -1,4 +1,4 @@ -import { type UUID } from "@liexp/shared/lib/io/http/Common/index.js"; +import { type URL, type UUID } from "@liexp/shared/lib/io/http/Common/index.js"; import { MediaType, type MediaExtra, @@ -35,10 +35,10 @@ export class MediaEntity { label: string | null; @Column({ type: "varchar", nullable: true }) - thumbnail: string | null; + thumbnail: URL | null; @Column({ type: "varchar", nullable: false, unique: true }) - location: string; + location: URL; @Column({ type: "varchar", nullable: true }) description: string | null; diff --git a/packages/@liexp/backend/src/flows/links/takeLinkScreenshot.flow.ts b/packages/@liexp/backend/src/flows/links/takeLinkScreenshot.flow.ts index fa2cab2e2..3d418adfe 100644 --- a/packages/@liexp/backend/src/flows/links/takeLinkScreenshot.flow.ts +++ b/packages/@liexp/backend/src/flows/links/takeLinkScreenshot.flow.ts @@ -1,4 +1,5 @@ import { fp, pipe } from "@liexp/core/lib/fp/index.js"; +import { type URL } from "@liexp/shared/lib/io/http/Common/URL.js"; import { PngType } from "@liexp/shared/lib/io/http/Media/index.js"; import { contentTypeFromFileExt, @@ -48,8 +49,8 @@ const uploadScreenshot = ( links: [], areas: [], type: PngType.value, - location: upload.Location, - thumbnail: upload.Location, + location: upload.Location as URL, + thumbnail: upload.Location as URL, })), ); }; diff --git a/packages/@liexp/backend/src/flows/media/admin/getOrphanMedia.flow.ts b/packages/@liexp/backend/src/flows/media/admin/getOrphanMedia.flow.ts index ce9738f5e..5e76a5a8a 100644 --- a/packages/@liexp/backend/src/flows/media/admin/getOrphanMedia.flow.ts +++ b/packages/@liexp/backend/src/flows/media/admin/getOrphanMedia.flow.ts @@ -1,6 +1,7 @@ import path from "path"; import { type ListObjectsOutput, type _Object } from "@aws-sdk/client-s3"; import { fp, pipe } from "@liexp/core/lib/fp/index.js"; +import { type URL } from "@liexp/shared/lib/io/http/Common/URL.js"; import { walkPaginatedRequest } from "@liexp/shared/lib/utils/fp.utils.js"; import { getResourceAndIdFromLocation } from "@liexp/shared/lib/utils/media.utils.js"; import { type Option } from "fp-ts/lib/Option.js"; @@ -94,7 +95,7 @@ export const getOrphanMediaFlow = < return ctx.db.findOne(MediaEntity, { where: [ { - location: Like(`%${e.Key}`), + location: Like(`%${e.Key}` as URL), }, ], }); diff --git a/packages/@liexp/backend/src/flows/media/createAndUpload.flow.ts b/packages/@liexp/backend/src/flows/media/createAndUpload.flow.ts index a51bedd36..dd22bee36 100644 --- a/packages/@liexp/backend/src/flows/media/createAndUpload.flow.ts +++ b/packages/@liexp/backend/src/flows/media/createAndUpload.flow.ts @@ -1,4 +1,5 @@ import { fp, pipe } from "@liexp/core/lib/fp/index.js"; +import { type URL } from "@liexp/shared/lib/io/http/Common/URL.js"; import { type UUID, uuid } from "@liexp/shared/lib/io/http/Common/UUID.js"; import { IframeVideoType, @@ -58,7 +59,7 @@ export const createAndUpload = ( // ctx.logger.debug.log("Create media and upload %s", createMediaData); if (IframeVideoType.is(createMediaData.type)) { - return fp.RTE.right(createMediaData.location); + return fp.RTE.right(createMediaData.location as URL); } const mediaKey = getMediaKey( @@ -74,7 +75,7 @@ export const createAndUpload = ( ContentType, ACL: "public-read", }), - fp.RTE.map((r) => r.Location), + fp.RTE.map((r) => r.Location as URL), fp.RTE.mapLeft(ServerError.fromUnknown), ); }), diff --git a/packages/@liexp/backend/src/flows/media/extractMediaFromPlatform.flow.ts b/packages/@liexp/backend/src/flows/media/extractMediaFromPlatform.flow.ts index f326b5238..7ec303ecc 100644 --- a/packages/@liexp/backend/src/flows/media/extractMediaFromPlatform.flow.ts +++ b/packages/@liexp/backend/src/flows/media/extractMediaFromPlatform.flow.ts @@ -64,7 +64,7 @@ const extractEmbedFromPlatform = ( url: URL, m: VideoPlatformMatch, page: puppeteer.Page, -): TE.TaskEither => { +): TE.TaskEither => { return pipe( TE.tryCatch(async () => { await page.goto(url); @@ -104,7 +104,7 @@ export const extractMediaFromPlatform = thumbnail: pipe( extractThumbnailFromVideoPlatform(m, page)(ctx), TE.orElse( - (): TE.TaskEither => + (): TE.TaskEither => TE.right(undefined), ), ), diff --git a/packages/@liexp/backend/src/flows/media/findOneByLocationOrElse.flow.ts b/packages/@liexp/backend/src/flows/media/findOneByLocationOrElse.flow.ts index 9cf583e34..8b53dd7b5 100644 --- a/packages/@liexp/backend/src/flows/media/findOneByLocationOrElse.flow.ts +++ b/packages/@liexp/backend/src/flows/media/findOneByLocationOrElse.flow.ts @@ -1,4 +1,5 @@ import { fp, pipe } from "@liexp/core/lib/fp/index.js"; +import { type URL } from "@liexp/shared/lib/io/http/Common/URL.js"; import { uuid } from "@liexp/shared/lib/io/http/Common/UUID.js"; import { type Option } from "fp-ts/lib/Option.js"; import { type ReaderTaskEither } from "fp-ts/lib/ReaderTaskEither.js"; @@ -11,12 +12,12 @@ import { type DatabaseContext } from "context/db.context.js"; export const findOneByLocationOrElse = ( m: Partial, - orElse: (m: string) => Partial, + orElse: (m: URL) => Partial, creator: UserEntity, ): ReaderTaskEither> => (ctx) => { return pipe( - m.image, + m.image as URL, fp.O.fromNullable, fp.O.map((image) => pipe( diff --git a/packages/@liexp/backend/src/flows/media/thumbnails/createThumbnail.flow.ts b/packages/@liexp/backend/src/flows/media/thumbnails/createThumbnail.flow.ts index 9a54fa749..d9b142f1f 100644 --- a/packages/@liexp/backend/src/flows/media/thumbnails/createThumbnail.flow.ts +++ b/packages/@liexp/backend/src/flows/media/thumbnails/createThumbnail.flow.ts @@ -1,5 +1,6 @@ import { type PutObjectCommandInput } from "@aws-sdk/client-s3"; import { fp, pipe } from "@liexp/core/lib/fp/index.js"; +import { type URL } from "@liexp/shared/lib/io/http/Common/URL.js"; import { type ReaderTaskEither } from "fp-ts/lib/ReaderTaskEither.js"; import { type ConfigContext } from "../../../context/config.context.js"; import { type ENVContext } from "../../../context/env.context.js"; @@ -18,7 +19,7 @@ import { extractThumbnail } from "./extractThumbnail.flow.js"; const uploadThumbnails = ( thumbnails: PutObjectCommandInput[], -): ReaderTaskEither => +): ReaderTaskEither => pipe( thumbnails, fp.A.traverse(fp.RTE.ApplicativePar)((thumbnail) => @@ -27,7 +28,7 @@ const uploadThumbnails = ( ...thumbnail, ACL: "public-read", }), - fp.RTE.map(({ Location }) => Location), + fp.RTE.map(({ Location }) => Location as URL), ), ), ); @@ -48,6 +49,6 @@ export const createThumbnail = < SpaceContext, >( media: SimpleMedia, -): ReaderTaskEither => { +): ReaderTaskEither => { return pipe(extractThumbnail(media), fp.RTE.chain(uploadThumbnails)); }; diff --git a/packages/@liexp/backend/src/flows/media/thumbnails/extractThumbnailFromVideoPlatform.flow.ts b/packages/@liexp/backend/src/flows/media/thumbnails/extractThumbnailFromVideoPlatform.flow.ts index c7355897d..6edfab118 100644 --- a/packages/@liexp/backend/src/flows/media/thumbnails/extractThumbnailFromVideoPlatform.flow.ts +++ b/packages/@liexp/backend/src/flows/media/thumbnails/extractThumbnailFromVideoPlatform.flow.ts @@ -3,6 +3,7 @@ import { getPlatform, type VideoPlatformMatch, } from "@liexp/shared/lib/helpers/media.helper.js"; +import { type URL } from "@liexp/shared/lib/io/http/Common/URL.js"; import { type Media } from "@liexp/shared/lib/io/http/index.js"; import * as E from "fp-ts/lib/Either.js"; import { type ReaderTaskEither } from "fp-ts/lib/ReaderTaskEither.js"; @@ -21,7 +22,7 @@ export const extractThumbnailFromVideoPlatform = ( match: VideoPlatformMatch, page: Page, - ): ReaderTaskEither => + ): ReaderTaskEither => (ctx) => { const toError = (m: VideoPlatformMatch): ServerError => { return ServerError.fromUnknown( @@ -50,7 +51,7 @@ export const extractThumbnailFromVideoPlatform = `Thumbnail from selector ${selector}: ${coverUrl}`, ); - return coverUrl; + return coverUrl as URL; } case "youtube": { const selector = 'div[class*="thumbnail-overlay-image"]'; @@ -68,7 +69,7 @@ export const extractThumbnailFromVideoPlatform = return undefined; }); - return coverUrl; + return coverUrl as URL; } case "peertube": case "odysee": { @@ -90,7 +91,7 @@ export const extractThumbnailFromVideoPlatform = ?.replace('background-image: url("', "") .replace('");', ""); - return coverUrl; + return coverUrl as URL; }); } case "rumble": { @@ -111,7 +112,7 @@ export const extractThumbnailFromVideoPlatform = return el.getAttribute("poster"); }); - return videoPosterSrc; + return videoPosterSrc as URL; } case "dailymotion": { if (match.type === "embed") { @@ -121,7 +122,7 @@ export const extractThumbnailFromVideoPlatform = return page.$eval(selector, (el) => { return el.getAttribute("src"); - }); + }) as Promise; } const selector = 'meta[name="og:image:secure_url"]'; @@ -131,10 +132,10 @@ export const extractThumbnailFromVideoPlatform = return el.getAttribute("content"); }); - return url; + return url as URL; } default: { - return undefined; + return null; } } }, ServerError.fromUnknown), diff --git a/packages/@liexp/backend/src/flows/tg/parseVideo.flow.ts b/packages/@liexp/backend/src/flows/tg/parseVideo.flow.ts index 413fe5f2a..1b41fe50c 100644 --- a/packages/@liexp/backend/src/flows/tg/parseVideo.flow.ts +++ b/packages/@liexp/backend/src/flows/tg/parseVideo.flow.ts @@ -1,7 +1,7 @@ import { pipe } from "@liexp/core/lib/fp/index.js"; import { uuid, type UUID } from "@liexp/shared/lib/io/http/Common/UUID.js"; import { MP4Type } from "@liexp/shared/lib/io/http/Media/index.js"; -import { ensureHTTPS } from "@liexp/shared/lib/utils/media.utils.js"; +import { ensureHTTPS } from "@liexp/shared/lib/utils/url.utils.js"; import { sequenceS } from "fp-ts/lib/Apply.js"; import * as O from "fp-ts/lib/Option.js"; import { type ReaderTaskEither } from "fp-ts/lib/ReaderTaskEither.js"; diff --git a/packages/@liexp/backend/src/flows/tg/upsertPinnedMessage.flow.ts b/packages/@liexp/backend/src/flows/tg/upsertPinnedMessage.flow.ts index 2c1d1c23c..9d8393feb 100644 --- a/packages/@liexp/backend/src/flows/tg/upsertPinnedMessage.flow.ts +++ b/packages/@liexp/backend/src/flows/tg/upsertPinnedMessage.flow.ts @@ -3,15 +3,16 @@ import { sequenceS } from "fp-ts/lib/Apply.js"; import { type ReaderTaskEither } from "fp-ts/lib/ReaderTaskEither.js"; import * as TE from "fp-ts/lib/TaskEither.js"; import type TelegramBot from "node-telegram-bot-api"; -import { type DatabaseContext } from "../../context/db.context"; -import { type ENVContext } from "../../context/env.context"; +import { type DatabaseContext } from "../../context/db.context.js"; +import { type ENVContext } from "../../context/env.context.js"; import { type TGBotProviderContext } from "../../context/index.js"; -import { type LoggerContext } from "../../context/logger.context"; -import { EventV2Entity } from "../../entities/Event.v2.entity"; -import { KeywordEntity } from "../../entities/Keyword.entity"; +import { type LoggerContext } from "../../context/logger.context.js"; +import { EventV2Entity } from "../../entities/Event.v2.entity.js"; +import { KeywordEntity } from "../../entities/Keyword.entity.js"; +import { type TG_BOT_ENV } from "../../io/ENV.js"; import { type DBError } from "../../providers/orm/index.js"; -import { type TGError, toTGError } from "../../providers/tg/tg.provider"; -import { LoggerService } from "../../services/logger/logger.service"; +import { type TGError, toTGError } from "../../providers/tg/tg.provider.js"; +import { LoggerService } from "../../services/logger/logger.service.js"; interface ToPinnedMessageOptions { bot: string; @@ -40,7 +41,7 @@ ${keywords.map((k) => `#${k.tag} (${k.eventCount})`).join("\n")} export type UpsertPinnerMessageFlowContext = DatabaseContext & LoggerContext & - ENVContext & + ENVContext & TGBotProviderContext; export const upsertPinnedMessage = diff --git a/packages/@liexp/backend/src/io/ENV.ts b/packages/@liexp/backend/src/io/ENV.ts index 37ca73b6a..dd7348f32 100644 --- a/packages/@liexp/backend/src/io/ENV.ts +++ b/packages/@liexp/backend/src/io/ENV.ts @@ -2,6 +2,15 @@ import { NODE_ENV } from "@liexp/core/lib/env/node-env.js"; import * as t from "io-ts"; import { NumberFromString } from "io-ts-types/lib/NumberFromString.js"; +export const JWT_ENV = t.strict( + { + JWT_SECRET: t.string, + }, + "JWT_ENV", +); + +export type JWT_ENV = t.TypeOf; + export const DATABASE_ENV = t.intersection( [ t.strict( @@ -58,7 +67,6 @@ export const BACKEND_ENV = t.intersection( t.strict({ NODE_ENV, DEFAULT_PAGE_SIZE: NumberFromString }), DATABASE_ENV, SPACE_ENV, - TG_BOT_ENV, ], "BACKEND_ENV", ); diff --git a/packages/@liexp/backend/src/io/media.io.ts b/packages/@liexp/backend/src/io/media.io.ts index 8037f2bf9..d3eacd1fa 100644 --- a/packages/@liexp/backend/src/io/media.io.ts +++ b/packages/@liexp/backend/src/io/media.io.ts @@ -6,7 +6,7 @@ import { import { MediaExtraMonoid } from "@liexp/shared/lib/io/http/Media/MediaExtra.js"; import { type MediaType } from "@liexp/shared/lib/io/http/Media/MediaType.js"; import * as io from "@liexp/shared/lib/io/index.js"; -import { ensureHTTPS } from "@liexp/shared/lib/utils/media.utils.js"; +import { ensureHTTPS } from "@liexp/shared/lib/utils/url.utils.js"; import * as E from "fp-ts/lib/Either.js"; import { type MediaEntity } from "../entities/Media.entity.js"; import { IOCodec } from "./DomainCodec.js"; diff --git a/packages/@liexp/backend/src/providers/wikipedia/wikipedia.provider.ts b/packages/@liexp/backend/src/providers/wikipedia/wikipedia.provider.ts index 4a19eb755..10ddfad78 100644 --- a/packages/@liexp/backend/src/providers/wikipedia/wikipedia.provider.ts +++ b/packages/@liexp/backend/src/providers/wikipedia/wikipedia.provider.ts @@ -1,6 +1,6 @@ import { pipe, fp } from "@liexp/core/lib/fp/index.js"; import { type Logger } from "@liexp/core/lib/logger/index.js"; -import { ensureHTTPS } from "@liexp/shared/lib/utils/media.utils.js"; +import { ensureHTTPS } from "@liexp/shared/lib/utils/url.utils.js"; import { type AxiosRequestConfig, type AxiosInstance, diff --git a/packages/@liexp/shared/src/endpoints/media.endpoints.ts b/packages/@liexp/shared/src/endpoints/media.endpoints.ts index 9ede697a6..13953f81a 100644 --- a/packages/@liexp/shared/src/endpoints/media.endpoints.ts +++ b/packages/@liexp/shared/src/endpoints/media.endpoints.ts @@ -1,9 +1,8 @@ import * as t from "io-ts"; import { UUID } from "io-ts-types/lib/UUID.js"; -import { optionFromNullable } from "io-ts-types/lib/optionFromNullable.js"; import { Endpoint } from "ts-endpoint"; import { ListOutput, Output } from "../io/http/Common/Output.js"; -import { CreateMedia, MediaExtra } from "../io/http/Media/index.js"; +import { CreateMedia, EditMediaBody } from "../io/http/Media/index.js"; import { Media } from "../io/http/index.js"; import { ResourceEndpoints } from "./types.js"; @@ -43,24 +42,7 @@ export const Edit = Endpoint({ getPath: ({ id }) => `/media/${id}`, Input: { Params: t.type({ id: UUID }), - Body: t.strict({ - type: Media.MediaType, - thumbnail: optionFromNullable(t.string), - location: t.string, - label: t.string, - description: optionFromNullable(t.string), - extra: optionFromNullable(MediaExtra), - links: t.array(UUID), - events: t.array(UUID), - keywords: t.array(UUID), - areas: t.array(UUID), - creator: optionFromNullable(UUID), - overrideThumbnail: optionFromNullable(t.boolean), - overrideExtra: optionFromNullable(t.boolean), - transfer: optionFromNullable(t.boolean), - transferThumbnail: optionFromNullable(t.boolean), - restore: optionFromNullable(t.boolean), - }), + Body: EditMediaBody, }, Output: SingleMediaOutput, }); diff --git a/packages/@liexp/shared/src/io/http/Events/index.ts b/packages/@liexp/shared/src/io/http/Events/index.ts index 4c404ee61..dca52a3be 100644 --- a/packages/@liexp/shared/src/io/http/Events/index.ts +++ b/packages/@liexp/shared/src/io/http/Events/index.ts @@ -69,7 +69,7 @@ export const CreateEventPlainBody = t.union( export type CreateEventPlainBody = t.TypeOf; export const CreateEventBody = t.union( - [CreateEventPlainBody, EventFromURLBody], + [EventFromURLBody, CreateEventPlainBody], "CreateEventBody", ); diff --git a/packages/@liexp/shared/src/io/http/Media/Media.ts b/packages/@liexp/shared/src/io/http/Media/Media.ts index d3d060023..ea339a989 100644 --- a/packages/@liexp/shared/src/io/http/Media/Media.ts +++ b/packages/@liexp/shared/src/io/http/Media/Media.ts @@ -100,8 +100,8 @@ export type CreateMedia = t.TypeOf; export const EditMediaBody = t.strict( { type: MediaType, - thumbnail: optionFromNullable(t.string), - location: t.string, + thumbnail: optionFromNullable(URL), + location: URL, label: t.string, description: optionFromNullable(t.string), extra: optionFromNullable(MediaExtra), diff --git a/packages/@liexp/shared/src/io/http/Media/MediaExtra.ts b/packages/@liexp/shared/src/io/http/Media/MediaExtra.ts index 3af1dfc15..9ab22aef6 100644 --- a/packages/@liexp/shared/src/io/http/Media/MediaExtra.ts +++ b/packages/@liexp/shared/src/io/http/Media/MediaExtra.ts @@ -1,5 +1,6 @@ import { type Monoid } from "fp-ts/lib/Monoid"; import * as t from "io-ts"; +import { URL } from "../Common/URL.js"; export const ThumbnailsExtraError = t.strict( { @@ -9,7 +10,7 @@ export const ThumbnailsExtraError = t.strict( ); export type ThumbnailsExtraError = t.TypeOf; -export const ThumbnailsExtraLocations = t.array(t.string, "Thumbnails"); +export const ThumbnailsExtraLocations = t.array(URL, "Thumbnails"); export type ThumbnailsExtraLocations = t.TypeOf< typeof ThumbnailsExtraLocations >; diff --git a/packages/@liexp/shared/src/io/http/Queue/CreateEventFromURLQueue.ts b/packages/@liexp/shared/src/io/http/Queue/CreateEventFromURLQueue.ts index 774b6e883..86151714e 100644 --- a/packages/@liexp/shared/src/io/http/Queue/CreateEventFromURLQueue.ts +++ b/packages/@liexp/shared/src/io/http/Queue/CreateEventFromURLQueue.ts @@ -1,4 +1,5 @@ import * as t from "io-ts"; +import { URL } from "../Common/URL.js"; import { EventType } from "../Events/index.js"; export const OpenAICreateEventFromURLType = t.literal( @@ -10,7 +11,7 @@ export type OpenAICreateEventFromURLType = t.TypeOf< export const CreateEventFromURLQueueData = t.strict( { - url: t.string, + url: URL, type: EventType, }, "CreateEventFromURLQueueData", diff --git a/packages/@liexp/shared/src/io/http/Queue/index.ts b/packages/@liexp/shared/src/io/http/Queue/index.ts index e13222029..f7d7cbf83 100644 --- a/packages/@liexp/shared/src/io/http/Queue/index.ts +++ b/packages/@liexp/shared/src/io/http/Queue/index.ts @@ -1,5 +1,6 @@ import * as t from "io-ts"; import { optionFromUndefined } from "../../Common/optionFromUndefined.js"; +import { URL } from "../Common/URL.js"; import { UUID } from "../Common/UUID.js"; import { PaginationQuery } from "../Query/PaginationQuery.js"; import { SortQuery } from "../Query/SortQuery.js"; @@ -86,7 +87,7 @@ export type GetQueueListQuery = t.TypeOf; export const CreateQueueURLData = t.strict( { - url: t.string, + url: URL, type: t.union([t.literal("link"), t.literal("pdf"), t.undefined]), }, "CreateQueueURLData", diff --git a/packages/@liexp/shared/src/utils/media.utils.ts b/packages/@liexp/shared/src/utils/media.utils.ts index 9fc132931..60b9c8fea 100644 --- a/packages/@liexp/shared/src/utils/media.utils.ts +++ b/packages/@liexp/shared/src/utils/media.utils.ts @@ -93,15 +93,3 @@ export const getMediaThumbKey = ( contentType: Media.ValidContentType, n: number | undefined = 1, ): string => getMediaKey("media", id, `${id}-thumb-${n}`, contentType); - -export const ensureHTTPS = (url: string): string => { - if (url.startsWith("https://") || url.startsWith("http://")) { - return url; - } - - if (url.startsWith("//")) { - return `https:${url}`; - } - - return `https://${url}`; -}; diff --git a/packages/@liexp/shared/src/utils/url.utils.ts b/packages/@liexp/shared/src/utils/url.utils.ts index de0916895..ff1c93fa1 100644 --- a/packages/@liexp/shared/src/utils/url.utils.ts +++ b/packages/@liexp/shared/src/utils/url.utils.ts @@ -19,3 +19,15 @@ export const sanitizeURL = (url: URL): URL => { return cleanURL as URL; }; + +export const ensureHTTPS = (url: string): URL => { + if (url.startsWith("https://") || url.startsWith("http://")) { + return url as URL; + } + + if (url.startsWith("//")) { + return `https:${url}` as URL; + } + + return `https://${url}` as URL; +}; diff --git a/services/ai-bot/src/flows/ai/common/loadDocs.flow.ts b/services/ai-bot/src/flows/ai/common/loadDocs.flow.ts index 3b6866c8f..eebefe541 100644 --- a/services/ai-bot/src/flows/ai/common/loadDocs.flow.ts +++ b/services/ai-bot/src/flows/ai/common/loadDocs.flow.ts @@ -1,6 +1,7 @@ import { fp, pipe } from "@liexp/core/lib/fp/index.js"; import { EVENTS } from "@liexp/shared/lib/io/http/Events/index.js"; import { CreateEventFromTextQueueData } from "@liexp/shared/lib/io/http/Queue/CreateEventFromTextQueueData.js"; +import { CreateEventFromURLQueueData } from "@liexp/shared/lib/io/http/Queue/CreateEventFromURLQueue.js"; import { CreateQueueTextData, CreateQueueURLData, @@ -19,9 +20,16 @@ const loadEventDocs = return pipe( ctx.endpointsRESTClient.Endpoints.Event.get({ id: job.id }), fp.RTE.fromTaskEither, - fp.RTE.map((event) => event.links), + fp.RTE.chainTaskEitherK((event) => + ctx.endpointsRESTClient.Endpoints.Link.getList({ + filter: { ids: event.links }, + }), + ), fp.RTE.chain((links) => - pipe(links, fp.A.traverse(fp.RTE.ApplicativePar)(loadLink)), + pipe( + links.data, + fp.A.traverse(fp.RTE.ApplicativePar)((l) => loadLink(l.url)), + ), ), fp.RTE.map(fp.A.flatten), )(ctx); @@ -40,6 +48,9 @@ export const loadDocs = (job: Queue): ClientContextRTE => { return loadLink(job.data.url); } + case CreateEventFromURLQueueData.is(job.data): { + return loadLink(job.data.url); + } case job.resource === EVENTS.value: { return loadEventDocs(job); } diff --git a/services/ai-bot/src/flows/ai/common/loadLink.flow.ts b/services/ai-bot/src/flows/ai/common/loadLink.flow.ts index fc8b52121..d339bd571 100644 --- a/services/ai-bot/src/flows/ai/common/loadLink.flow.ts +++ b/services/ai-bot/src/flows/ai/common/loadLink.flow.ts @@ -4,12 +4,13 @@ import "cheerio"; import { CheerioWebBaseLoader } from "@langchain/community/document_loaders/web/cheerio"; import { LoggerService } from "@liexp/backend/lib/services/logger/logger.service.js"; import { fp, pipe } from "@liexp/core/lib/fp/index.js"; +import { type URL } from "@liexp/shared/lib/io/http/Common/URL.js"; import { type Document } from "langchain/document"; import { toAIBotError } from "../../../common/error/index.js"; import { type ClientContext } from "../../../context.js"; import { type ClientContextRTE } from "../../../types.js"; -export const loadLink = (url: string): ClientContextRTE => { +export const loadLink = (url: URL): ClientContextRTE => { return pipe( fp.RTE.ask(), LoggerService.RTE.debug(["Loading link from URL %s", url]), diff --git a/services/api/.env b/services/api/.env index 9525b75c6..318ccd619 100644 --- a/services/api/.env +++ b/services/api/.env @@ -37,14 +37,6 @@ TEMP_FOLDER_CLEAN_UP_CRON="0 12 * * *" # every day at midnight GENERATE_MISSING_THUMBNAILS_CRON="0 12 * * *" # every day at midnight PROCESS_DONE_JOB_CRON="*/5 * * * *" # every 5 minutes REGENERATE_MEDIA_THUMBNAILS_CRON="20 0 * * *" # At 00:20, every day -TG_BOT_TOKEN=not-valid -TG_BOT_CHAT=not-valid -TG_BOT_USERNAME=not-valid -TG_BOT_POLLING=false -TG_BOT_BASE_API_URL=http://telegram.liexp.dev:9008 - -TELEGRAM_API_ID=not-real-id30 -TELEGRAM_API_HASH=not-real-hash IG_USERNAME=invalid IG_PASSWORD=invalid diff --git a/services/api/src/io/ENV.ts b/services/api/src/io/ENV.ts index a1e3d6243..a908d39e8 100644 --- a/services/api/src/io/ENV.ts +++ b/services/api/src/io/ENV.ts @@ -1,4 +1,4 @@ -import { BACKEND_ENV, DATABASE_ENV } from "@liexp/backend/lib/io/ENV.js"; +import { BACKEND_ENV, JWT_ENV } from "@liexp/backend/lib/io/ENV.js"; import * as t from "io-ts"; import { BooleanFromString } from "io-ts-types"; import { NumberFromString } from "io-ts-types/lib/NumberFromString.js"; @@ -13,39 +13,21 @@ const ENV = t.intersection( }, "REDIS_ENV", ), - t.intersection([ - t.strict( - { - DEBUG: t.string, - SERVER_HOST: t.string, - SERVER_PORT: NumberFromString, - VIRTUAL_PORT: NumberFromString, - VIRTUAL_HOST: t.string, - DEFAULT_PAGE_SIZE: NumberFromString, - JWT_SECRET: t.string, - WEB_URL: t.string, - // crons - SOCIAL_POSTING_CRON: t.string, - TEMP_FOLDER_CLEAN_UP_CRON: t.string, - GENERATE_MISSING_THUMBNAILS_CRON: t.string, - PROCESS_DONE_JOB_CRON: t.string, - REGENERATE_MEDIA_THUMBNAILS_CRON: t.string, - // unused - DOWNLOAD_VACCINE_DATA_CRON: t.string, - // geo coding - GEO_CODE_BASE_URL: t.string, - GEO_CODE_API_KEY: t.string, - }, - "API_ENV", - ), - t.strict( - { - OPENAI_URL: t.string, - }, - "OPENAI_ENV", - ), - ]), - DATABASE_ENV, + JWT_ENV, + t.strict( + { + DEBUG: t.string, + SERVER_HOST: t.string, + SERVER_PORT: NumberFromString, + VIRTUAL_PORT: NumberFromString, + VIRTUAL_HOST: t.string, + WEB_URL: t.string, + // geo coding + GEO_CODE_BASE_URL: t.string, + GEO_CODE_API_KEY: t.string, + }, + "API_ENV", + ), ], "ENV", ); diff --git a/services/worker/src/bin/extract-actor-and-group-avatar.ts b/services/worker/src/bin/extract-actor-and-group-avatar.ts index 77531decd..17ebb762e 100644 --- a/services/worker/src/bin/extract-actor-and-group-avatar.ts +++ b/services/worker/src/bin/extract-actor-and-group-avatar.ts @@ -1,11 +1,10 @@ -// /* eslint-disable @typescript-eslint/no-var-requires */ - import { ActorEntity } from "@liexp/backend/lib/entities/Actor.entity.js"; import { GroupEntity } from "@liexp/backend/lib/entities/Group.entity.js"; import { MediaEntity } from "@liexp/backend/lib/entities/Media.entity.js"; import { createThumbnail } from "@liexp/backend/lib/flows/media/thumbnails/createThumbnail.flow.js"; import { LoggerService } from "@liexp/backend/lib/services/logger/logger.service.js"; import { flow, fp, pipe } from "@liexp/core/lib/fp/index.js"; +import { type URL } from "@liexp/shared/lib/io/http/Common/URL.js"; import { uuid } from "@liexp/shared/lib/io/http/Common/UUID.js"; import { contentTypeFromFileExt, @@ -20,7 +19,7 @@ import { type CommandFlow } from "./command.type.js"; const convertLocationToMediaEntity = (ctx: WorkerContext) => - (label: string, avatar: string | null): TE => { + (label: string, avatar: URL | null): TE => { if (!avatar) { return fp.TE.right(null); } @@ -59,7 +58,7 @@ const convertLocationToMediaEntity = pipe( createThumbnail(media)(ctx), // if thumbnail fails, we return an empty array - fp.TE.orElse(() => fp.TE.right([])), + fp.TE.orElse(() => fp.TE.right([])), ), ), fp.TE.map(({ media, thumbnail }) => ({ @@ -85,7 +84,10 @@ const convertManyMediaTask = (location as GroupEntity).name; return pipe( - convertLocationToMediaEntity(ctx)(label, location.old_avatar), + convertLocationToMediaEntity(ctx)( + label, + location.old_avatar as URL, + ), fp.TE.map((media) => [{ ...location, avatar: media }] as A), fp.TE.chain(save), ); diff --git a/services/worker/src/jobs/generateMissingMedia.job.ts b/services/worker/src/jobs/generateMissingMedia.job.ts index 901885eb2..13e53fec2 100644 --- a/services/worker/src/jobs/generateMissingMedia.job.ts +++ b/services/worker/src/jobs/generateMissingMedia.job.ts @@ -4,9 +4,10 @@ import { createThumbnail } from "@liexp/backend/lib/flows/media/thumbnails/creat import { MediaRepository } from "@liexp/backend/lib/services/entity-repository.service.js"; import { LoggerService } from "@liexp/backend/lib/services/logger/logger.service.js"; import { fp, pipe } from "@liexp/core/lib/fp/index.js"; +import { type URL } from "@liexp/shared/lib/io/http/Common/URL.js"; import { ImageMediaExtraMonoid } from "@liexp/shared/lib/io/http/Media/MediaExtra.js"; -import { ensureHTTPS } from "@liexp/shared/lib/utils/media.utils.js"; import { throwTE } from "@liexp/shared/lib/utils/task.utils.js"; +import { ensureHTTPS } from "@liexp/shared/lib/utils/url.utils.js"; import Cron from "node-cron"; import { type WorkerContext } from "#context/context.js"; import { toWorkerError, type WorkerError } from "#io/worker.error.js"; @@ -36,7 +37,7 @@ export const generateMissingThumbnailsCron = ( (l) => l.length > 0, () => toWorkerError(new Error("No thumbnail generated")), ), - fp.TE.fold>( + fp.TE.fold>( (e) => fp.T.of({ extra: ImageMediaExtraMonoid.concat( diff --git a/services/worker/src/jobs/processOpenAIJobsDone.job.ts b/services/worker/src/jobs/processOpenAIJobsDone.job.ts index 5c9998b47..cd6d7ae99 100644 --- a/services/worker/src/jobs/processOpenAIJobsDone.job.ts +++ b/services/worker/src/jobs/processOpenAIJobsDone.job.ts @@ -13,6 +13,7 @@ import { } from "@liexp/backend/lib/services/entity-repository.service.js"; import { LoggerService } from "@liexp/backend/lib/services/logger/logger.service.js"; import { fp, pipe } from "@liexp/core/lib/fp/index.js"; +import { type BlockNoteDocument } from "@liexp/shared/lib/io/http/Common/BlockNoteDocument.js"; import { DecodeError } from "@liexp/shared/lib/io/http/Error/DecodeError.js"; import { Event } from "@liexp/shared/lib/io/http/Events/index.js"; import { OpenAICreateEventFromTextType } from "@liexp/shared/lib/io/http/Queue/CreateEventFromTextQueueData.js"; @@ -45,8 +46,10 @@ const processDoneJobBlockNoteResult = return pipe( fp.RTE.asks((ctx: WorkerContext) => ctx.blocknote), fp.RTE.chainTaskEitherK((blocknote) => - fp.TE.tryCatch(() => { - return blocknote.tryParseHTMLToBlocks(result); + fp.TE.tryCatch(async () => { + const docs = await blocknote.tryParseHTMLToBlocks(result); + + return docs as any as BlockNoteDocument; }, toWorkerError), ), );