From ef3dadc42b07c6acc9f4238de86cac189737d8ba Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Sun, 6 Apr 2025 21:25:34 +0200 Subject: [PATCH 01/25] #375: first draft All bff functions with requester and the ripples it created. --- src/domain/authentication/login/login.ts | 10 +++-- src/domain/authentication/requester.ts | 3 +- src/domain/authentication/types.ts | 1 + src/domain/creator/aggregate/types.ts | 2 +- src/domain/creator/create/create.ts | 4 +- src/domain/creator/create/createData.ts | 3 +- src/domain/creator/create/types.ts | 2 +- src/domain/creator/create/validateData.ts | 5 ++- src/domain/creator/getByEmail/getByEmail.ts | 4 +- src/domain/creator/getById/getById.ts | 21 ++++++++-- .../getByIdAggregated/getByIdAggregated.ts | 6 ++- .../creator/getByNickname/getByNickname.ts | 7 +++- .../getByNicknameAggregated.ts | 6 ++- src/domain/creator/getMe/getMe.ts | 2 +- src/domain/creator/getOthers/getOthers.ts | 13 +++++-- src/domain/creator/register/register.ts | 4 +- src/domain/creator/types.ts | 1 + .../notification/aggregate/aggregate.ts | 2 +- src/domain/post/aggregate/aggregate.ts | 2 +- src/domain/post/create/InvalidPost.ts | 2 +- src/domain/post/create/create.ts | 4 +- src/domain/post/create/createData.ts | 3 +- src/domain/post/create/types.ts | 2 +- src/domain/post/create/validateData.ts | 5 ++- .../post/createWithComic/createWithComic.ts | 2 +- .../createWithComment/createWithComment.ts | 2 +- src/domain/post/explore/explore.ts | 2 +- src/domain/post/explore/retrieveData.ts | 7 ++-- .../post/getRecommended/getRecommended.ts | 5 ++- src/domain/post/types.ts | 1 + src/domain/relation/aggregate/aggregate.ts | 5 ++- src/domain/relation/explore/explore.ts | 2 +- .../exploreAggregated/exploreAggregated.ts | 2 +- .../relation/getAggregated/getAggregated.ts | 5 ++- .../getFollowersAggregated.ts | 2 +- .../getFollowingAggregated.ts | 2 +- .../middlewares/AuthenticationMiddleware.ts | 38 ++++++++++++++++++- .../runtime/middlewares/TenantMiddleware.ts | 20 ++++++++++ src/integrations/runtime/tenantMiddleware.ts | 4 ++ src/integrations/utilities/webbrowser.ts | 7 ++++ src/webui/features/hooks/useCreator.ts | 4 +- vite.config.ts | 5 ++- 42 files changed, 171 insertions(+), 58 deletions(-) create mode 100644 src/integrations/runtime/middlewares/TenantMiddleware.ts create mode 100644 src/integrations/runtime/tenantMiddleware.ts diff --git a/src/domain/authentication/login/login.ts b/src/domain/authentication/login/login.ts index 74a5d451..67b62de3 100644 --- a/src/domain/authentication/login/login.ts +++ b/src/domain/authentication/login/login.ts @@ -6,20 +6,24 @@ import registerCreator from '^/domain/creator/register'; import type { Requester } from '../types'; -export default async function login(identity: Identity): Promise +export default async function login(identity: Identity, tenantId: string): Promise { - const existingCreator = await getCreatorByEmail(identity.email); + // get tenantId from DB using the url string value + + const existingCreator = await getCreatorByEmail(identity.email, tenantId); const loggedInCreator = existingCreator ?? await registerCreator( identity.name, identity.nickname ?? identity.name, identity.email, + tenantId, identity.picture ); return { id: loggedInCreator.id, fullName: loggedInCreator.fullName, - nickname: loggedInCreator.nickname + nickname: loggedInCreator.nickname, + tenantId, }; } diff --git a/src/domain/authentication/requester.ts b/src/domain/authentication/requester.ts index 864a6cc3..07a89afb 100644 --- a/src/domain/authentication/requester.ts +++ b/src/domain/authentication/requester.ts @@ -4,7 +4,8 @@ import type { Requester } from './types'; const requester: Requester = { id: 'id', fullName: 'full name', - nickname: 'nickname' + nickname: 'nickname', + tenantId: 'tenant' }; export default requester; diff --git a/src/domain/authentication/types.ts b/src/domain/authentication/types.ts index ff577824..ebe17a1e 100644 --- a/src/domain/authentication/types.ts +++ b/src/domain/authentication/types.ts @@ -3,6 +3,7 @@ type Requester = { readonly id: string; readonly fullName: string; readonly nickname: string; + readonly tenantId: string; }; export type { Requester }; diff --git a/src/domain/creator/aggregate/types.ts b/src/domain/creator/aggregate/types.ts index 403b76be..039f7456 100644 --- a/src/domain/creator/aggregate/types.ts +++ b/src/domain/creator/aggregate/types.ts @@ -4,7 +4,7 @@ import type { ImageData } from '^/domain/image'; import type { DataModel } from '../types'; -type AggregatedData = Omit & +type AggregatedData = Omit & { readonly portrait?: ImageData; readonly metrics: metricsData; diff --git a/src/domain/creator/create/create.ts b/src/domain/creator/create/create.ts index b09c8a29..7ae36f80 100644 --- a/src/domain/creator/create/create.ts +++ b/src/domain/creator/create/create.ts @@ -5,9 +5,9 @@ import createData from './createData'; import insertData from './insertData'; import validateData from './validateData'; -export default async function create(fullName: string, nickname: string, email: string, portraitId: string | undefined = undefined): Promise +export default async function create(fullName: string, nickname: string, email: string, tenantId: string, portraitId: string | undefined = undefined): Promise { - const data = await createData(fullName, nickname, email, portraitId); + const data = await createData(fullName, nickname, email, tenantId, portraitId); validateData(data); diff --git a/src/domain/creator/create/createData.ts b/src/domain/creator/create/createData.ts index 9b79f0a9..85f8b5a8 100644 --- a/src/domain/creator/create/createData.ts +++ b/src/domain/creator/create/createData.ts @@ -3,13 +3,14 @@ import { generateId } from '^/integrations/utilities/crypto'; import type { DataModel } from '../types'; -export default async function createData(fullName: string, nickname: string, email: string, portraitId: string | undefined = undefined): Promise +export default async function createData(fullName: string, nickname: string, email: string, tenantId: string, portraitId: string | undefined = undefined): Promise { return { id: generateId(), fullName, nickname, email, + tenantId, portraitId: portraitId, joinedAt: new Date().toISOString() }; diff --git a/src/domain/creator/create/types.ts b/src/domain/creator/create/types.ts index 55312de7..695c5013 100644 --- a/src/domain/creator/create/types.ts +++ b/src/domain/creator/create/types.ts @@ -1,6 +1,6 @@ import type { DataModel } from '../types'; -type ValidationModel = Pick; +type ValidationModel = Pick; export type { ValidationModel }; diff --git a/src/domain/creator/create/validateData.ts b/src/domain/creator/create/validateData.ts index a35d4a88..0f2d6184 100644 --- a/src/domain/creator/create/validateData.ts +++ b/src/domain/creator/create/validateData.ts @@ -19,12 +19,13 @@ const schema: ValidationSchema = required: true } }, + tenantId: { STRING: { required: true } }, portraitId: optionalIdValidation }; -export default function validateData({ fullName, email }: ValidationModel): void +export default function validateData({ fullName, email, tenantId }: ValidationModel): void { - const result = validator.validate({ fullName, email }, schema); + const result = validator.validate({ fullName, email, tenantId }, schema); if (result.invalid) { diff --git a/src/domain/creator/getByEmail/getByEmail.ts b/src/domain/creator/getByEmail/getByEmail.ts index 35c2e1b7..cda2679b 100644 --- a/src/domain/creator/getByEmail/getByEmail.ts +++ b/src/domain/creator/getByEmail/getByEmail.ts @@ -4,9 +4,9 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getByEmail(email: string): Promise +export default async function getByEmail(email: string, tenantId: string): Promise { - const query = { email: { EQUALS: email } }; + const query = { email: { EQUALS: email }, tenantId: { EQUALS: tenantId } }; return database.findRecord(RECORD_TYPE, query) as Promise; } diff --git a/src/domain/creator/getById/getById.ts b/src/domain/creator/getById/getById.ts index 954137d2..2d3732ac 100644 --- a/src/domain/creator/getById/getById.ts +++ b/src/domain/creator/getById/getById.ts @@ -1,10 +1,25 @@ -import database from '^/integrations/database'; +import database, { RecordQuery } from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getById(id: string): Promise +type Props = { + tenantId: string, + id: string; +}; + +export default async function getById({ id, tenantId }: Props): Promise { - return database.readRecord(RECORD_TYPE, id) as Promise; + const query: RecordQuery = + { + id: { 'EQUALS': id }, + tenantId: { 'EQUALS': tenantId } + }; + + const creators = await database.findRecord(RECORD_TYPE, query); + + if (creators === undefined) throw new Error('No creator found with that ID'); + + return creators as DataModel; } diff --git a/src/domain/creator/getByIdAggregated/getByIdAggregated.ts b/src/domain/creator/getByIdAggregated/getByIdAggregated.ts index 1615346c..5a1cde54 100644 --- a/src/domain/creator/getByIdAggregated/getByIdAggregated.ts +++ b/src/domain/creator/getByIdAggregated/getByIdAggregated.ts @@ -1,11 +1,13 @@ +import type { Requester } from '^/domain/authentication'; + import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getById from '../getById'; -export default async function getByIdAggregated(id: string): Promise +export default async function getByIdAggregated(requester: Requester, id: string): Promise { - const data = await getById(id); + const data = await getById({ tenantId: requester.tenantId, id }); return aggregate(data); } diff --git a/src/domain/creator/getByNickname/getByNickname.ts b/src/domain/creator/getByNickname/getByNickname.ts index 41387179..c24f47d9 100644 --- a/src/domain/creator/getByNickname/getByNickname.ts +++ b/src/domain/creator/getByNickname/getByNickname.ts @@ -5,9 +5,12 @@ import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; import NicknameNotFound from './NicknameNotFound'; -export default async function getByNickname(nickname: string): Promise +export default async function getByNickname(tenantId: string, nickname: string): Promise { - const query = { nickname: { EQUALS: nickname } }; + const query = { + tenantId: { EQUALS: tenantId }, + nickname: { EQUALS: nickname } + }; const data = await database.findRecord(RECORD_TYPE, query) as DataModel; diff --git a/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts b/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts index f1bc7e17..59cbb9aa 100644 --- a/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts +++ b/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts @@ -1,11 +1,13 @@ +import type { Requester } from '^/domain/authentication'; + import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getByNickname from '../getByNickname'; -export default async function getByNicknameAggregated(nickname: string): Promise +export default async function getByNicknameAggregated(requester: Requester, nickname: string): Promise { - const data = await getByNickname(nickname); + const data = await getByNickname(requester.tenantId, nickname); return aggregate(data); } diff --git a/src/domain/creator/getMe/getMe.ts b/src/domain/creator/getMe/getMe.ts index ed47da31..fdffcf79 100644 --- a/src/domain/creator/getMe/getMe.ts +++ b/src/domain/creator/getMe/getMe.ts @@ -6,5 +6,5 @@ import type { DataModel } from '../types'; export default async function getMe(requester: Requester): Promise { - return getById(requester.id); + return getById({ tenantId: requester.tenantId, id: requester.id }); } diff --git a/src/domain/creator/getOthers/getOthers.ts b/src/domain/creator/getOthers/getOthers.ts index 9b1eac38..6318bbda 100644 --- a/src/domain/creator/getOthers/getOthers.ts +++ b/src/domain/creator/getOthers/getOthers.ts @@ -1,14 +1,19 @@ -import type { QueryStatement, RecordQuery, RecordSort} from '^/integrations/database'; +import type { QueryStatement, RecordQuery, RecordSort } from '^/integrations/database'; import database, { SortDirections } from '^/integrations/database'; -import type { SortOrder} from '../definitions'; +import type { SortOrder } from '../definitions'; import { RECORD_TYPE, SortOrders } from '../definitions'; import type { DataModel } from '../types'; -export default async function getOthers(ids: string[], order: SortOrder, limit: number, offset: number, search: string | undefined = undefined): Promise +export default async function getOthers(tenantId: string, ids: string[], order: SortOrder, limit: number, offset: number, search: string | undefined = undefined): Promise { - const defaultQuery: RecordQuery = { id: { NOT_IN: ids } }; + const defaultQuery: RecordQuery = { + 'AND': [ + { id: { NOT_IN: ids } }, + { tenantId: { EQUALS: tenantId } } + ] + }; const searchQuery: RecordQuery = { 'OR': [ { fullName: { CONTAINS: search } }, diff --git a/src/domain/creator/register/register.ts b/src/domain/creator/register/register.ts index e2893143..ceb6ff5a 100644 --- a/src/domain/creator/register/register.ts +++ b/src/domain/creator/register/register.ts @@ -10,7 +10,7 @@ import type { DataModel } from '../types'; import downloadPortrait from './downloadPortrait'; import publish from './publish'; -export default async function register(fullName: string, nickname: string, email: string, portraitUrl: string | undefined = undefined): Promise +export default async function register(fullName: string, nickname: string, email: string, tenantId: string, portraitUrl: string | undefined = undefined): Promise { let data; @@ -23,7 +23,7 @@ export default async function register(fullName: string, nickname: string, email ? await downloadPortrait(portraitUrl) : undefined; - data = await create(truncatedFullName, generatedNickname, email, portraitId); + data = await create(truncatedFullName, generatedNickname, email, tenantId, portraitId); await publish(data.id); diff --git a/src/domain/creator/types.ts b/src/domain/creator/types.ts index 27a22866..1d819ccb 100644 --- a/src/domain/creator/types.ts +++ b/src/domain/creator/types.ts @@ -6,6 +6,7 @@ type DataModel = BaseDataModel & readonly fullName: string; readonly nickname: string; readonly email: string; + readonly tenantId: string; readonly portraitId?: string; readonly joinedAt: string; }; diff --git a/src/domain/notification/aggregate/aggregate.ts b/src/domain/notification/aggregate/aggregate.ts index ab54dd15..d2a24042 100644 --- a/src/domain/notification/aggregate/aggregate.ts +++ b/src/domain/notification/aggregate/aggregate.ts @@ -9,7 +9,7 @@ import type { AggregatedData } from './types'; export default async function aggregate(requester: Requester, data: DataModel): Promise { const [relationData, postData] = await Promise.all([ - getRelationData(data.receiverId, data.senderId), + getRelationData(requester, data.receiverId, data.senderId), data.postId ? getPostData(requester, data.postId) : Promise.resolve(undefined) ]); diff --git a/src/domain/post/aggregate/aggregate.ts b/src/domain/post/aggregate/aggregate.ts index 75186a28..70bf9068 100644 --- a/src/domain/post/aggregate/aggregate.ts +++ b/src/domain/post/aggregate/aggregate.ts @@ -13,7 +13,7 @@ import type { AggregatedData } from './types'; export default async function aggregate(requester: Requester, data: DataModel): Promise { const [creatorData, isRated, comicData, commentData, metricsData] = await Promise.all([ - getRelationData(requester.id, data.creatorId), + getRelationData(requester, requester.id, data.creatorId), ratingExists(requester.id, data.id), data.comicId ? getComicData(data.comicId) : Promise.resolve(undefined), data.commentId ? getCommentData(data.commentId) : Promise.resolve(undefined), diff --git a/src/domain/post/create/InvalidPost.ts b/src/domain/post/create/InvalidPost.ts index 02c1caaa..f130de73 100644 --- a/src/domain/post/create/InvalidPost.ts +++ b/src/domain/post/create/InvalidPost.ts @@ -1,7 +1,7 @@ import { ValidationError } from '^/integrations/runtime'; -export default class InvalidReaction extends ValidationError +export default class InvalidPost extends ValidationError { } diff --git a/src/domain/post/create/create.ts b/src/domain/post/create/create.ts index 4c1bfe55..f4971ba7 100644 --- a/src/domain/post/create/create.ts +++ b/src/domain/post/create/create.ts @@ -8,13 +8,13 @@ import insertData from './insertData'; import publish from './publish'; import validateData from './validateData'; -export default async function create(creatorId: string, comicId?: string, commentId?: string, parentId?: string): Promise +export default async function create(creatorId: string, tenantId: string, comicId?: string, commentId?: string, parentId?: string): Promise { let postId; try { - const data = createData(creatorId, comicId, commentId, parentId); + const data = createData(creatorId, tenantId, comicId, commentId, parentId); validateData(data); diff --git a/src/domain/post/create/createData.ts b/src/domain/post/create/createData.ts index 2a61129d..202e7d11 100644 --- a/src/domain/post/create/createData.ts +++ b/src/domain/post/create/createData.ts @@ -3,12 +3,13 @@ import { generateId } from '^/integrations/utilities/crypto'; import type { DataModel } from '../types'; -export default function createData(creatorId: string, comicId?: string, commentId?: string, parentId?: string): DataModel +export default function createData(creatorId: string, tenantId: string, comicId?: string, commentId?: string, parentId?: string): DataModel { return { id: generateId(), parentId, creatorId, + tenantId, comicId, commentId, createdAt: new Date().toISOString() diff --git a/src/domain/post/create/types.ts b/src/domain/post/create/types.ts index d8dba069..49fc00a9 100644 --- a/src/domain/post/create/types.ts +++ b/src/domain/post/create/types.ts @@ -3,7 +3,7 @@ import type { Publication, Subscription } from '^/integrations/eventbroker'; import type { DataModel } from '../types'; -export type ValidationModel = Pick; +export type ValidationModel = Pick; export type CreatedEventData = { creatorId: string; diff --git a/src/domain/post/create/validateData.ts b/src/domain/post/create/validateData.ts index 9ae6e3d8..56a7d0db 100644 --- a/src/domain/post/create/validateData.ts +++ b/src/domain/post/create/validateData.ts @@ -10,12 +10,13 @@ import type { ValidationModel } from './types'; const schema: ValidationSchema = { creatorId: requiredIdValidation, + tenantId: { STRING: { required: true } }, comicId: optionalIdValidation, commentId: optionalIdValidation, parentId: optionalIdValidation }; -export default function validateData({ creatorId, comicId, commentId, parentId }: ValidationModel): void +export default function validateData({ creatorId, tenantId, comicId, commentId, parentId }: ValidationModel): void { if (comicId === undefined && commentId === undefined) { @@ -26,7 +27,7 @@ export default function validateData({ creatorId, comicId, commentId, parentId } throw new InvalidPost(messages); } - const result = validator.validate({ creatorId, comicId, commentId, parentId }, schema); + const result = validator.validate({ creatorId, tenantId, comicId, commentId, parentId }, schema); if (result.invalid) { diff --git a/src/domain/post/createWithComic/createWithComic.ts b/src/domain/post/createWithComic/createWithComic.ts index 797e55af..11eb905e 100644 --- a/src/domain/post/createWithComic/createWithComic.ts +++ b/src/domain/post/createWithComic/createWithComic.ts @@ -9,5 +9,5 @@ export default async function createWithComic(requester: Requester, comicImageDa { const comicId = await createComic(comicImageDataUrl); - return createPost(requester.id, comicId, undefined, parentId); + return createPost(requester.id, requester.tenantId, comicId, undefined, parentId); } diff --git a/src/domain/post/createWithComment/createWithComment.ts b/src/domain/post/createWithComment/createWithComment.ts index d32d799a..e0df2c15 100644 --- a/src/domain/post/createWithComment/createWithComment.ts +++ b/src/domain/post/createWithComment/createWithComment.ts @@ -9,5 +9,5 @@ export default async function createWithComment(requester: Requester, message: s { const commentId = await createComment(message); - return createPost(requester.id, undefined, commentId, parentId); + return createPost(requester.id, requester.tenantId, undefined, commentId, parentId); } diff --git a/src/domain/post/explore/explore.ts b/src/domain/post/explore/explore.ts index 7d6e0cc4..42e9e165 100644 --- a/src/domain/post/explore/explore.ts +++ b/src/domain/post/explore/explore.ts @@ -12,5 +12,5 @@ export default async function explore(requester: Requester, limit: number, offse const excludedCreatorIds = relationsData.map(data => data.followingId); excludedCreatorIds.push(requester.id); - return retrieveData(excludedCreatorIds, limit, offset); + return retrieveData(requester.tenantId, excludedCreatorIds, limit, offset); } diff --git a/src/domain/post/explore/retrieveData.ts b/src/domain/post/explore/retrieveData.ts index a3178c6d..659bf747 100644 --- a/src/domain/post/explore/retrieveData.ts +++ b/src/domain/post/explore/retrieveData.ts @@ -1,17 +1,18 @@ -import type { RecordQuery, RecordSort} from '^/integrations/database'; +import type { RecordQuery, RecordSort } from '^/integrations/database'; import database, { SortDirections } from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function retrieveData(excludedCreatorIds: string[], limit: number, offset: number): Promise +export default async function retrieveData(tenantId: string, excludedCreatorIds: string[], limit: number, offset: number): Promise { const query: RecordQuery = { deleted: { 'EQUALS': false }, parentId: { 'EQUALS': undefined }, - creatorId: { NOT_IN: excludedCreatorIds } + creatorId: { NOT_IN: excludedCreatorIds }, + tenantId: { 'EQUALS': tenantId } }; const sort: RecordSort = { createdAt: SortDirections.DESCENDING }; diff --git a/src/domain/post/getRecommended/getRecommended.ts b/src/domain/post/getRecommended/getRecommended.ts index 3bdd8a8a..5641c6db 100644 --- a/src/domain/post/getRecommended/getRecommended.ts +++ b/src/domain/post/getRecommended/getRecommended.ts @@ -1,5 +1,5 @@ -import type { RecordQuery, RecordSort} from '^/integrations/database'; +import type { RecordQuery, RecordSort } from '^/integrations/database'; import database, { SortDirections } from '^/integrations/database'; import type { Requester } from '^/domain/authentication'; @@ -14,7 +14,8 @@ export default async function getRecommended(requester: Requester, limit: number { deleted: { EQUALS: false }, parentId: { EQUALS: undefined }, - creatorId: { NOT_EQUALS: requester.id } + creatorId: { NOT_EQUALS: requester.id }, + tenantId: { EQUALS: requester.tenantId } }; const sort: RecordSort = { createdAt: SortDirections.DESCENDING }; diff --git a/src/domain/post/types.ts b/src/domain/post/types.ts index a948aafc..5bc33c65 100644 --- a/src/domain/post/types.ts +++ b/src/domain/post/types.ts @@ -5,6 +5,7 @@ type DataModel = BaseDataModel & { readonly id: string; readonly creatorId: string; + readonly tenantId: string; readonly comicId?: string; readonly commentId?: string; readonly parentId?: string; diff --git a/src/domain/relation/aggregate/aggregate.ts b/src/domain/relation/aggregate/aggregate.ts index a9db8a23..ede0d41c 100644 --- a/src/domain/relation/aggregate/aggregate.ts +++ b/src/domain/relation/aggregate/aggregate.ts @@ -1,12 +1,13 @@ +import type { Requester } from '^/domain/authentication'; import getCreatorData from '^/domain/creator/getByIdAggregated'; import type { DataModel } from '../types'; import type { AggregatedData } from './types'; -export default async function aggregate(data: DataModel): Promise +export default async function aggregate(requester: Requester, data: DataModel): Promise { - const followingData = await getCreatorData(data.followingId); + const followingData = await getCreatorData(requester, data.followingId); return { id: data.id, diff --git a/src/domain/relation/explore/explore.ts b/src/domain/relation/explore/explore.ts index 5004b639..d95fc1ef 100644 --- a/src/domain/relation/explore/explore.ts +++ b/src/domain/relation/explore/explore.ts @@ -12,7 +12,7 @@ export default async function explore(requester: Requester, order: SortOrder, li const followingIds = followingData.map(data => data.followingId); followingIds.push(requester.id); - const creatorData = await getOtherCreators(followingIds, order, limit, offset, search); + const creatorData = await getOtherCreators(requester.tenantId, followingIds, order, limit, offset, search); return creatorData.map(data => { diff --git a/src/domain/relation/exploreAggregated/exploreAggregated.ts b/src/domain/relation/exploreAggregated/exploreAggregated.ts index f421b2e1..c0ea9320 100644 --- a/src/domain/relation/exploreAggregated/exploreAggregated.ts +++ b/src/domain/relation/exploreAggregated/exploreAggregated.ts @@ -14,5 +14,5 @@ export default async function exploreAggregated(requester: Requester, order: Sor const data = await explore(requester, order, range.limit, range.offset, search); - return Promise.all(data.map(item => aggregate(item))); + return Promise.all(data.map(item => aggregate(requester, item))); } diff --git a/src/domain/relation/getAggregated/getAggregated.ts b/src/domain/relation/getAggregated/getAggregated.ts index 56989265..6e2fc6a1 100644 --- a/src/domain/relation/getAggregated/getAggregated.ts +++ b/src/domain/relation/getAggregated/getAggregated.ts @@ -1,11 +1,12 @@ +import type { Requester } from '^/domain/authentication'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import get from '../get'; -export default async function getAggregated(followerId: string, followingId: string): Promise +export default async function getAggregated(requester: Requester, followerId: string, followingId: string): Promise { const data = await get(followerId, followingId); - return aggregate(data); + return aggregate(requester, data); } diff --git a/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts b/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts index 88ba8558..7aa374bc 100644 --- a/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts +++ b/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts @@ -13,5 +13,5 @@ export default async function getFollowersAggregated(requester: Requester, follo const data = await getFollowers(requester, followingId, range.limit, range.offset); - return Promise.all(data.map(aggregate)); + return Promise.all(data.map(data => aggregate(requester, data))); } diff --git a/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts b/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts index e6d921a8..f52d7245 100644 --- a/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts +++ b/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts @@ -13,5 +13,5 @@ export default async function getFollowingAggregated(requester: Requester, follo const data = await retrieveByFollower(requester, followerId, range.limit, range.offset); - return Promise.all(data.map(aggregate)); + return Promise.all(data.map(data => aggregate(requester, data))); } diff --git a/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts b/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts index 8db31c37..df64e32e 100644 --- a/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts +++ b/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts @@ -1,5 +1,5 @@ -import type { Middleware, NextHandler, Request} from 'jitar'; +import type { Middleware, NextHandler, Request } from 'jitar'; import { Response } from 'jitar'; import type { IdentityProvider, Session } from '^/integrations/authentication'; @@ -14,8 +14,10 @@ type AuthProcedures = { }; const IDENTITY_PARAMETER = 'identity'; +const TENANT_ID_PARAMETER = 'tenantId'; const REQUESTER_PARAMETER = '*requester'; const JITAR_TRUST_HEADER_KEY = 'X-Jitar-Trust-Key'; +const COMIFY_HOST_COOKIE_KEY = 'x-comify-host'; const sessions = new Map(); @@ -60,10 +62,12 @@ export default class AuthenticationMiddleware implements Middleware async #createSession(request: Request, next: NextHandler): Promise { const data = Object.fromEntries(request.args); + const tenantId = this.#getTenantId(request); const session = await this.#identityProvider.login(data); request.args.clear(); request.setArgument(IDENTITY_PARAMETER, session.identity); + request.setArgument(TENANT_ID_PARAMETER, tenantId); const response = await next(); @@ -216,4 +220,36 @@ export default class AuthenticationMiddleware implements Middleware { response.setHeader('Location', `${this.#redirectUrl}?key=${key}`); } + + #getTenantId(request: Request): string | undefined + { + const cookie = request.getHeader('cookie'); + + if (cookie === undefined) + { + return undefined; + } + + const cookies = this.#getCookies(cookie); + + return cookies.get(COMIFY_HOST_COOKIE_KEY); + } + + #getCookies(input: string): Map + { + const cookies = input.split(';'); + const cookieMap = new Map(); + + for (const cookie of cookies) + { + const parts = cookie.trim().split('='); + + const key = parts[0]?.toLocaleLowerCase(); + const value = parts.length > 2 ? parts.slice(1).join('=') : parts[1]; + + cookieMap.set(key, value); + } + + return cookieMap; + } } diff --git a/src/integrations/runtime/middlewares/TenantMiddleware.ts b/src/integrations/runtime/middlewares/TenantMiddleware.ts new file mode 100644 index 00000000..d5dbc147 --- /dev/null +++ b/src/integrations/runtime/middlewares/TenantMiddleware.ts @@ -0,0 +1,20 @@ + +import { setCookie } from '^/integrations/utilities/webbrowser'; +import type { Middleware, NextHandler, Request, Response } from 'jitar'; + +const hostHeader = 'x-comify-host'; + +export default class TenantMiddleware implements Middleware +{ + constructor() + { + const clientHost = document.location.host; + + setCookie(hostHeader, clientHost); + } + + handle(request: Request, next: NextHandler): Promise + { + return next(); + } +} diff --git a/src/integrations/runtime/tenantMiddleware.ts b/src/integrations/runtime/tenantMiddleware.ts new file mode 100644 index 00000000..0d8d6704 --- /dev/null +++ b/src/integrations/runtime/tenantMiddleware.ts @@ -0,0 +1,4 @@ + +import TenantMiddleware from './middlewares/TenantMiddleware'; + +export default new TenantMiddleware(); diff --git a/src/integrations/utilities/webbrowser.ts b/src/integrations/utilities/webbrowser.ts index b8630573..33b0cebb 100644 --- a/src/integrations/utilities/webbrowser.ts +++ b/src/integrations/utilities/webbrowser.ts @@ -5,3 +5,10 @@ export function getQueryParameter(name: string): string | undefined return queryParameters.get(name) ?? undefined; } + +export function setCookie(key: string, value: string): void +{ + const cookie = `${key}=${value}`; + + document.cookie = cookie; +} diff --git a/src/webui/features/hooks/useCreator.ts b/src/webui/features/hooks/useCreator.ts index 5d13bb66..571812b9 100644 --- a/src/webui/features/hooks/useCreator.ts +++ b/src/webui/features/hooks/useCreator.ts @@ -2,7 +2,7 @@ import { useCallback } from 'react'; import { useParams } from 'react-router-dom'; -import type { AggregatedData as AggregatedCreatorData } from '^/domain/creator/aggregate'; +import { requester } from '^/domain/authentication'; import getCreator from '^/domain/creator/getByNicknameAggregated'; import getRelation from '^/domain/relation/getAggregated'; @@ -21,7 +21,7 @@ export default function useCreator() return undefined; } - const creator: AggregatedCreatorData = await getCreator(nickname); + const creator = await getCreator(requester, nickname); return getRelation(identity.id, creator.id); diff --git a/vite.config.ts b/vite.config.ts index 075552a6..cba83cd2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,7 +6,10 @@ import tsconfigPaths from 'vite-tsconfig-paths'; const JITAR_URL = 'http://localhost:3000'; const JITAR_SEGMENTS = []; -const JITAR_MIDDLEWARES = ['./integrations/runtime/requesterMiddleware']; +const JITAR_MIDDLEWARES = [ + './integrations/runtime/requesterMiddleware', + './integrations/runtime/tenantMiddleware' +]; const jitarConfig: JitarConfig = { sourceDir: 'src', From 8698695522881e6bf4113412592d3dfd5d4769fa Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Thu, 10 Apr 2025 13:20:00 +0200 Subject: [PATCH 02/25] #375: added tenant and host concept --- segments/reads.json | 2 ++ src/domain/authentication/login/login.ts | 10 ++++--- src/domain/authentication/types.ts | 2 +- src/domain/creator/create/create.ts | 4 +-- src/domain/creator/create/createData.ts | 4 +-- src/domain/creator/create/validateData.ts | 2 +- src/domain/creator/getByEmail/getByEmail.ts | 7 +++-- src/domain/creator/getById/CreatorNotFound.ts | 10 +++++++ src/domain/creator/getById/getById.ts | 19 +++++++------- .../getByIdAggregated/getByIdAggregated.ts | 2 +- .../creator/getByNickname/getByNickname.ts | 12 ++++----- .../getByNicknameAggregated.ts | 2 +- src/domain/creator/getMe/getMe.ts | 2 +- src/domain/creator/getOthers/getOthers.ts | 2 +- src/domain/creator/register/register.ts | 4 +-- src/domain/creator/types.ts | 2 +- src/domain/host/definitions.ts | 2 ++ src/domain/host/getByName/getByName.ts | 26 +++++++++++++++++++ src/domain/host/getByName/index.ts | 2 ++ src/domain/host/index.ts | 4 +++ src/domain/host/types.ts | 7 +++++ src/domain/post/create/create.ts | 4 +-- src/domain/post/create/createData.ts | 2 +- src/domain/post/create/validateData.ts | 2 +- .../post/createWithComic/createWithComic.ts | 2 +- .../createWithComment/createWithComment.ts | 2 +- src/domain/post/explore/explore.ts | 2 +- src/domain/post/explore/retrieveData.ts | 2 +- src/domain/post/types.ts | 2 +- src/domain/relation/explore/explore.ts | 2 +- src/domain/tenant/definitions.ts | 2 ++ src/domain/tenant/getById/getById.ts | 10 +++++++ src/domain/tenant/getById/index.ts | 2 ++ src/domain/tenant/index.ts | 4 +++ src/domain/tenant/types.ts | 8 ++++++ .../middlewares/AuthenticationMiddleware.ts | 8 +++--- src/webui/features/hooks/useCreator.ts | 2 +- 37 files changed, 133 insertions(+), 50 deletions(-) create mode 100644 src/domain/creator/getById/CreatorNotFound.ts create mode 100644 src/domain/host/definitions.ts create mode 100644 src/domain/host/getByName/getByName.ts create mode 100644 src/domain/host/getByName/index.ts create mode 100644 src/domain/host/index.ts create mode 100644 src/domain/host/types.ts create mode 100644 src/domain/tenant/definitions.ts create mode 100644 src/domain/tenant/getById/getById.ts create mode 100644 src/domain/tenant/getById/index.ts create mode 100644 src/domain/tenant/index.ts create mode 100644 src/domain/tenant/types.ts diff --git a/segments/reads.json b/segments/reads.json index 9138f068..57b4ebfe 100644 --- a/segments/reads.json +++ b/segments/reads.json @@ -10,6 +10,8 @@ "./domain/creator.metrics/getByCreator": { "default": { "access": "protected" } }, + "./domain/host/getByName": { "default": { "access": "protected" } }, + "./domain/image/getById": { "default": { "access": "protected" } }, "./domain/post/explore": { "default": { "access": "protected" } }, diff --git a/src/domain/authentication/login/login.ts b/src/domain/authentication/login/login.ts index 67b62de3..aef603d1 100644 --- a/src/domain/authentication/login/login.ts +++ b/src/domain/authentication/login/login.ts @@ -3,12 +3,14 @@ import type { Identity } from '^/integrations/authentication'; import getCreatorByEmail from '^/domain/creator/getByEmail'; import registerCreator from '^/domain/creator/register'; +import getByHostname from '^/domain/host/getByName'; import type { Requester } from '../types'; -export default async function login(identity: Identity, tenantId: string): Promise +export default async function login(identity: Identity, hostname: string): Promise { - // get tenantId from DB using the url string value + const tenant = await getByHostname(hostname); + const tenantId = tenant?.id; const existingCreator = await getCreatorByEmail(identity.email, tenantId); @@ -16,8 +18,8 @@ export default async function login(identity: Identity, tenantId: string): Promi identity.name, identity.nickname ?? identity.name, identity.email, - tenantId, - identity.picture + identity.picture, + tenantId ); return { diff --git a/src/domain/authentication/types.ts b/src/domain/authentication/types.ts index ebe17a1e..162e85ab 100644 --- a/src/domain/authentication/types.ts +++ b/src/domain/authentication/types.ts @@ -3,7 +3,7 @@ type Requester = { readonly id: string; readonly fullName: string; readonly nickname: string; - readonly tenantId: string; + readonly tenantId?: string; }; export type { Requester }; diff --git a/src/domain/creator/create/create.ts b/src/domain/creator/create/create.ts index 7ae36f80..b3ca4fb5 100644 --- a/src/domain/creator/create/create.ts +++ b/src/domain/creator/create/create.ts @@ -5,9 +5,9 @@ import createData from './createData'; import insertData from './insertData'; import validateData from './validateData'; -export default async function create(fullName: string, nickname: string, email: string, tenantId: string, portraitId: string | undefined = undefined): Promise +export default async function create(fullName: string, nickname: string, email: string, portraitId: string | undefined = undefined, tenantId: string | undefined = undefined): Promise { - const data = await createData(fullName, nickname, email, tenantId, portraitId); + const data = await createData(fullName, nickname, email, portraitId, tenantId); validateData(data); diff --git a/src/domain/creator/create/createData.ts b/src/domain/creator/create/createData.ts index 85f8b5a8..66eb98c0 100644 --- a/src/domain/creator/create/createData.ts +++ b/src/domain/creator/create/createData.ts @@ -3,15 +3,15 @@ import { generateId } from '^/integrations/utilities/crypto'; import type { DataModel } from '../types'; -export default async function createData(fullName: string, nickname: string, email: string, tenantId: string, portraitId: string | undefined = undefined): Promise +export default async function createData(fullName: string, nickname: string, email: string, portraitId?: string, tenantId?: string): Promise { return { id: generateId(), fullName, nickname, email, - tenantId, portraitId: portraitId, + tenantId, joinedAt: new Date().toISOString() }; } diff --git a/src/domain/creator/create/validateData.ts b/src/domain/creator/create/validateData.ts index 0f2d6184..a7c0aa15 100644 --- a/src/domain/creator/create/validateData.ts +++ b/src/domain/creator/create/validateData.ts @@ -19,7 +19,7 @@ const schema: ValidationSchema = required: true } }, - tenantId: { STRING: { required: true } }, + tenantId: optionalIdValidation, portraitId: optionalIdValidation }; diff --git a/src/domain/creator/getByEmail/getByEmail.ts b/src/domain/creator/getByEmail/getByEmail.ts index cda2679b..3ba21108 100644 --- a/src/domain/creator/getByEmail/getByEmail.ts +++ b/src/domain/creator/getByEmail/getByEmail.ts @@ -4,9 +4,12 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getByEmail(email: string, tenantId: string): Promise +export default async function getByEmail(email: string, tenantId: string | undefined = undefined): Promise { - const query = { email: { EQUALS: email }, tenantId: { EQUALS: tenantId } }; + const query = { + email: { EQUALS: email }, + tenantId: { EQUALS: tenantId } + }; return database.findRecord(RECORD_TYPE, query) as Promise; } diff --git a/src/domain/creator/getById/CreatorNotFound.ts b/src/domain/creator/getById/CreatorNotFound.ts new file mode 100644 index 00000000..2b291eb2 --- /dev/null +++ b/src/domain/creator/getById/CreatorNotFound.ts @@ -0,0 +1,10 @@ + +import { NotFound } from '^/integrations/runtime'; + +export default class CreatorNotFound extends NotFound +{ + constructor(id: string, tenantId?: string) + { + super(`No creator for id: ${id} and tenant ${tenantId || 'undefined'}`); + } +} diff --git a/src/domain/creator/getById/getById.ts b/src/domain/creator/getById/getById.ts index 2d3732ac..97c88723 100644 --- a/src/domain/creator/getById/getById.ts +++ b/src/domain/creator/getById/getById.ts @@ -1,15 +1,11 @@ -import database, { RecordQuery } from '^/integrations/database'; +import database, { type RecordQuery } from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; +import CreatorNotFound from './CreatorNotFound'; -type Props = { - tenantId: string, - id: string; -}; - -export default async function getById({ id, tenantId }: Props): Promise +export default async function getById(id: string, tenantId: string | undefined = undefined): Promise { const query: RecordQuery = { @@ -17,9 +13,12 @@ export default async function getById({ id, tenantId }: Props): Promise { - const data = await getById({ tenantId: requester.tenantId, id }); + const data = await getById(id, requester.tenantId); return aggregate(data); } diff --git a/src/domain/creator/getByNickname/getByNickname.ts b/src/domain/creator/getByNickname/getByNickname.ts index c24f47d9..3d23e882 100644 --- a/src/domain/creator/getByNickname/getByNickname.ts +++ b/src/domain/creator/getByNickname/getByNickname.ts @@ -5,19 +5,19 @@ import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; import NicknameNotFound from './NicknameNotFound'; -export default async function getByNickname(tenantId: string, nickname: string): Promise +export default async function getByNickname(nickname: string, tenantId: string | undefined = undefined): Promise { const query = { - tenantId: { EQUALS: tenantId }, - nickname: { EQUALS: nickname } + nickname: { EQUALS: nickname }, + tenantId: { EQUALS: tenantId } }; - const data = await database.findRecord(RECORD_TYPE, query) as DataModel; + const creator = await database.findRecord(RECORD_TYPE, query); - if (data === undefined) + if (creator === undefined) { throw new NicknameNotFound(nickname); } - return data; + return creator as DataModel; } diff --git a/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts b/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts index 59cbb9aa..d82a3bf2 100644 --- a/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts +++ b/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts @@ -7,7 +7,7 @@ import getByNickname from '../getByNickname'; export default async function getByNicknameAggregated(requester: Requester, nickname: string): Promise { - const data = await getByNickname(requester.tenantId, nickname); + const data = await getByNickname(nickname, requester.tenantId); return aggregate(data); } diff --git a/src/domain/creator/getMe/getMe.ts b/src/domain/creator/getMe/getMe.ts index fdffcf79..e7fc081b 100644 --- a/src/domain/creator/getMe/getMe.ts +++ b/src/domain/creator/getMe/getMe.ts @@ -6,5 +6,5 @@ import type { DataModel } from '../types'; export default async function getMe(requester: Requester): Promise { - return getById({ tenantId: requester.tenantId, id: requester.id }); + return getById(requester.id, requester.tenantId); } diff --git a/src/domain/creator/getOthers/getOthers.ts b/src/domain/creator/getOthers/getOthers.ts index 6318bbda..4dccf26b 100644 --- a/src/domain/creator/getOthers/getOthers.ts +++ b/src/domain/creator/getOthers/getOthers.ts @@ -6,7 +6,7 @@ import type { SortOrder } from '../definitions'; import { RECORD_TYPE, SortOrders } from '../definitions'; import type { DataModel } from '../types'; -export default async function getOthers(tenantId: string, ids: string[], order: SortOrder, limit: number, offset: number, search: string | undefined = undefined): Promise +export default async function getOthers(ids: string[], order: SortOrder, limit: number, offset: number, search: string | undefined = undefined, tenantId: string | undefined = undefined): Promise { const defaultQuery: RecordQuery = { 'AND': [ diff --git a/src/domain/creator/register/register.ts b/src/domain/creator/register/register.ts index ceb6ff5a..24945b9b 100644 --- a/src/domain/creator/register/register.ts +++ b/src/domain/creator/register/register.ts @@ -10,7 +10,7 @@ import type { DataModel } from '../types'; import downloadPortrait from './downloadPortrait'; import publish from './publish'; -export default async function register(fullName: string, nickname: string, email: string, tenantId: string, portraitUrl: string | undefined = undefined): Promise +export default async function register(fullName: string, nickname: string, email: string, portraitUrl: string | undefined = undefined, tenantId: string | undefined = undefined): Promise { let data; @@ -23,7 +23,7 @@ export default async function register(fullName: string, nickname: string, email ? await downloadPortrait(portraitUrl) : undefined; - data = await create(truncatedFullName, generatedNickname, email, tenantId, portraitId); + data = await create(truncatedFullName, generatedNickname, email, portraitId, tenantId); await publish(data.id); diff --git a/src/domain/creator/types.ts b/src/domain/creator/types.ts index 1d819ccb..cdc298e4 100644 --- a/src/domain/creator/types.ts +++ b/src/domain/creator/types.ts @@ -6,8 +6,8 @@ type DataModel = BaseDataModel & readonly fullName: string; readonly nickname: string; readonly email: string; - readonly tenantId: string; readonly portraitId?: string; + readonly tenantId?: string; readonly joinedAt: string; }; diff --git a/src/domain/host/definitions.ts b/src/domain/host/definitions.ts new file mode 100644 index 00000000..e122c8b2 --- /dev/null +++ b/src/domain/host/definitions.ts @@ -0,0 +1,2 @@ + +export const RECORD_TYPE = 'host'; diff --git a/src/domain/host/getByName/getByName.ts b/src/domain/host/getByName/getByName.ts new file mode 100644 index 00000000..4b0af0e3 --- /dev/null +++ b/src/domain/host/getByName/getByName.ts @@ -0,0 +1,26 @@ + +import type { RecordQuery } from '^/integrations/database'; +import database from '^/integrations/database'; + +import type { DataModel } from '^/domain/tenant'; +import getById from '^/domain/tenant/getById'; + +import { RECORD_TYPE } from '../definitions'; +import type { DataModel as Host } from '../types'; + +export default async function getByHostname(name: string): Promise +{ + const query: RecordQuery = + { + hostname: { 'EQUALS': name } + }; + + const host = await database.findRecord(RECORD_TYPE, query) as Host; + + if (host === undefined) + { + return; + } + + return getById(host.tenantId); +} diff --git a/src/domain/host/getByName/index.ts b/src/domain/host/getByName/index.ts new file mode 100644 index 00000000..4080bf86 --- /dev/null +++ b/src/domain/host/getByName/index.ts @@ -0,0 +1,2 @@ + +export { default } from './getByName'; diff --git a/src/domain/host/index.ts b/src/domain/host/index.ts new file mode 100644 index 00000000..273c9eea --- /dev/null +++ b/src/domain/host/index.ts @@ -0,0 +1,4 @@ + +export { RECORD_TYPE } from './definitions'; + +export type { DataModel } from './types'; diff --git a/src/domain/host/types.ts b/src/domain/host/types.ts new file mode 100644 index 00000000..11529947 --- /dev/null +++ b/src/domain/host/types.ts @@ -0,0 +1,7 @@ + +type DataModel = { + readonly tenantId: string; + readonly hostname: string; +}; + +export type { DataModel }; diff --git a/src/domain/post/create/create.ts b/src/domain/post/create/create.ts index f4971ba7..74ec60de 100644 --- a/src/domain/post/create/create.ts +++ b/src/domain/post/create/create.ts @@ -8,13 +8,13 @@ import insertData from './insertData'; import publish from './publish'; import validateData from './validateData'; -export default async function create(creatorId: string, tenantId: string, comicId?: string, commentId?: string, parentId?: string): Promise +export default async function create(creatorId: string, comicId?: string, commentId?: string, parentId?: string, tenantId?: string): Promise { let postId; try { - const data = createData(creatorId, tenantId, comicId, commentId, parentId); + const data = createData(creatorId, comicId, commentId, parentId, tenantId); validateData(data); diff --git a/src/domain/post/create/createData.ts b/src/domain/post/create/createData.ts index 202e7d11..d6dba49b 100644 --- a/src/domain/post/create/createData.ts +++ b/src/domain/post/create/createData.ts @@ -3,7 +3,7 @@ import { generateId } from '^/integrations/utilities/crypto'; import type { DataModel } from '../types'; -export default function createData(creatorId: string, tenantId: string, comicId?: string, commentId?: string, parentId?: string): DataModel +export default function createData(creatorId: string, comicId?: string, commentId?: string, parentId?: string, tenantId?: string): DataModel { return { id: generateId(), diff --git a/src/domain/post/create/validateData.ts b/src/domain/post/create/validateData.ts index 56a7d0db..2b7dc061 100644 --- a/src/domain/post/create/validateData.ts +++ b/src/domain/post/create/validateData.ts @@ -10,7 +10,7 @@ import type { ValidationModel } from './types'; const schema: ValidationSchema = { creatorId: requiredIdValidation, - tenantId: { STRING: { required: true } }, + tenantId: optionalIdValidation, comicId: optionalIdValidation, commentId: optionalIdValidation, parentId: optionalIdValidation diff --git a/src/domain/post/createWithComic/createWithComic.ts b/src/domain/post/createWithComic/createWithComic.ts index 11eb905e..642bb786 100644 --- a/src/domain/post/createWithComic/createWithComic.ts +++ b/src/domain/post/createWithComic/createWithComic.ts @@ -9,5 +9,5 @@ export default async function createWithComic(requester: Requester, comicImageDa { const comicId = await createComic(comicImageDataUrl); - return createPost(requester.id, requester.tenantId, comicId, undefined, parentId); + return createPost(requester.id, comicId, undefined, parentId, requester.tenantId); } diff --git a/src/domain/post/createWithComment/createWithComment.ts b/src/domain/post/createWithComment/createWithComment.ts index e0df2c15..01e89934 100644 --- a/src/domain/post/createWithComment/createWithComment.ts +++ b/src/domain/post/createWithComment/createWithComment.ts @@ -9,5 +9,5 @@ export default async function createWithComment(requester: Requester, message: s { const commentId = await createComment(message); - return createPost(requester.id, requester.tenantId, undefined, commentId, parentId); + return createPost(requester.id, undefined, commentId, parentId, requester.tenantId); } diff --git a/src/domain/post/explore/explore.ts b/src/domain/post/explore/explore.ts index 42e9e165..37aba7a5 100644 --- a/src/domain/post/explore/explore.ts +++ b/src/domain/post/explore/explore.ts @@ -12,5 +12,5 @@ export default async function explore(requester: Requester, limit: number, offse const excludedCreatorIds = relationsData.map(data => data.followingId); excludedCreatorIds.push(requester.id); - return retrieveData(requester.tenantId, excludedCreatorIds, limit, offset); + return retrieveData(excludedCreatorIds, limit, offset, requester.tenantId); } diff --git a/src/domain/post/explore/retrieveData.ts b/src/domain/post/explore/retrieveData.ts index 659bf747..50337884 100644 --- a/src/domain/post/explore/retrieveData.ts +++ b/src/domain/post/explore/retrieveData.ts @@ -5,7 +5,7 @@ import database, { SortDirections } from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function retrieveData(tenantId: string, excludedCreatorIds: string[], limit: number, offset: number): Promise +export default async function retrieveData(excludedCreatorIds: string[], limit: number, offset: number, tenantId: string | undefined = undefined): Promise { const query: RecordQuery = { diff --git a/src/domain/post/types.ts b/src/domain/post/types.ts index 5bc33c65..8722fd41 100644 --- a/src/domain/post/types.ts +++ b/src/domain/post/types.ts @@ -5,10 +5,10 @@ type DataModel = BaseDataModel & { readonly id: string; readonly creatorId: string; - readonly tenantId: string; readonly comicId?: string; readonly commentId?: string; readonly parentId?: string; + readonly tenantId?: string; readonly createdAt: string; }; diff --git a/src/domain/relation/explore/explore.ts b/src/domain/relation/explore/explore.ts index d95fc1ef..42db2f34 100644 --- a/src/domain/relation/explore/explore.ts +++ b/src/domain/relation/explore/explore.ts @@ -12,7 +12,7 @@ export default async function explore(requester: Requester, order: SortOrder, li const followingIds = followingData.map(data => data.followingId); followingIds.push(requester.id); - const creatorData = await getOtherCreators(requester.tenantId, followingIds, order, limit, offset, search); + const creatorData = await getOtherCreators(followingIds, order, limit, offset, search, requester.tenantId); return creatorData.map(data => { diff --git a/src/domain/tenant/definitions.ts b/src/domain/tenant/definitions.ts new file mode 100644 index 00000000..3d7c927c --- /dev/null +++ b/src/domain/tenant/definitions.ts @@ -0,0 +1,2 @@ + +export const RECORD_TYPE = 'tenant'; diff --git a/src/domain/tenant/getById/getById.ts b/src/domain/tenant/getById/getById.ts new file mode 100644 index 00000000..a51218b4 --- /dev/null +++ b/src/domain/tenant/getById/getById.ts @@ -0,0 +1,10 @@ + +import database from '^/integrations/database'; + +import { RECORD_TYPE } from '../definitions'; +import type { DataModel } from '../types'; + +export default async function getById(id: string): Promise +{ + return await database.readRecord(RECORD_TYPE, id) as DataModel; +} diff --git a/src/domain/tenant/getById/index.ts b/src/domain/tenant/getById/index.ts new file mode 100644 index 00000000..da399eb0 --- /dev/null +++ b/src/domain/tenant/getById/index.ts @@ -0,0 +1,2 @@ + +export { default } from './getById'; diff --git a/src/domain/tenant/index.ts b/src/domain/tenant/index.ts new file mode 100644 index 00000000..273c9eea --- /dev/null +++ b/src/domain/tenant/index.ts @@ -0,0 +1,4 @@ + +export { RECORD_TYPE } from './definitions'; + +export type { DataModel } from './types'; diff --git a/src/domain/tenant/types.ts b/src/domain/tenant/types.ts new file mode 100644 index 00000000..47202fa8 --- /dev/null +++ b/src/domain/tenant/types.ts @@ -0,0 +1,8 @@ + +import type { BaseDataModel } from '../types'; + +type DataModel = BaseDataModel & { + readonly name: string; +}; + +export type { DataModel }; diff --git a/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts b/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts index df64e32e..8a463fb0 100644 --- a/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts +++ b/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts @@ -14,7 +14,7 @@ type AuthProcedures = { }; const IDENTITY_PARAMETER = 'identity'; -const TENANT_ID_PARAMETER = 'tenantId'; +const HOSTNAME_PARAMETER = 'hostname'; const REQUESTER_PARAMETER = '*requester'; const JITAR_TRUST_HEADER_KEY = 'X-Jitar-Trust-Key'; const COMIFY_HOST_COOKIE_KEY = 'x-comify-host'; @@ -62,12 +62,12 @@ export default class AuthenticationMiddleware implements Middleware async #createSession(request: Request, next: NextHandler): Promise { const data = Object.fromEntries(request.args); - const tenantId = this.#getTenantId(request); + const hostname = this.#getHostname(request); const session = await this.#identityProvider.login(data); request.args.clear(); request.setArgument(IDENTITY_PARAMETER, session.identity); - request.setArgument(TENANT_ID_PARAMETER, tenantId); + request.setArgument(HOSTNAME_PARAMETER, hostname); const response = await next(); @@ -221,7 +221,7 @@ export default class AuthenticationMiddleware implements Middleware response.setHeader('Location', `${this.#redirectUrl}?key=${key}`); } - #getTenantId(request: Request): string | undefined + #getHostname(request: Request): string | undefined { const cookie = request.getHeader('cookie'); diff --git a/src/webui/features/hooks/useCreator.ts b/src/webui/features/hooks/useCreator.ts index 571812b9..fd3a4cb7 100644 --- a/src/webui/features/hooks/useCreator.ts +++ b/src/webui/features/hooks/useCreator.ts @@ -23,7 +23,7 @@ export default function useCreator() const creator = await getCreator(requester, nickname); - return getRelation(identity.id, creator.id); + return getRelation(requester, identity.id, creator.id); }, [identity, nickname]); From d072c0b11d74ab4032f7b11f1cef91a7be8acce2 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Fri, 25 Apr 2025 10:27:54 +0200 Subject: [PATCH 03/25] #375: renamed host to origin The origin contains the protocol of the incoming request as well. We need the origin so we can pass this to the authentication service as a redirect url. Authentication is updated to configure paths, and dynamically get the origin passed in. --- docker/keycloak/realm-export.json | 1 + docs/integrations/AUTHENTICATION.md | 2 +- example.env | 4 +- segments/reads.json | 2 +- src/domain/authentication/login/login.ts | 10 ++-- src/domain/host/definitions.ts | 2 - src/domain/host/getByName/getByName.ts | 26 ---------- src/domain/host/getByName/index.ts | 2 - src/domain/origin/definitions.ts | 2 + src/domain/origin/get/get.ts | 16 ++++++ src/domain/origin/get/index.ts | 2 + src/domain/{host => origin}/index.ts | 0 src/domain/{host => origin}/types.ts | 2 +- src/domain/tenant/getById/getById.ts | 2 +- src/domain/tenant/getByOrigin/getByOrigin.ts | 17 ++++++ src/domain/tenant/getByOrigin/index.ts | 2 + .../authentication/definitions/interfaces.ts | 4 +- .../implementations/openid/OpenID.ts | 10 ++-- .../implementations/openid/create.ts | 4 +- .../runtime/authenticationMiddleware.ts | 2 +- .../middlewares/AuthenticationMiddleware.ts | 52 +++++++++++++------ ...enantMiddleware.ts => OriginMiddleware.ts} | 11 ++-- src/integrations/runtime/originMiddleware.ts | 4 ++ src/integrations/runtime/tenantMiddleware.ts | 4 -- src/integrations/utilities/webbrowser.ts | 7 +++ vite.config.ts | 2 +- 26 files changed, 115 insertions(+), 77 deletions(-) delete mode 100644 src/domain/host/definitions.ts delete mode 100644 src/domain/host/getByName/getByName.ts delete mode 100644 src/domain/host/getByName/index.ts create mode 100644 src/domain/origin/definitions.ts create mode 100644 src/domain/origin/get/get.ts create mode 100644 src/domain/origin/get/index.ts rename src/domain/{host => origin}/index.ts (100%) rename src/domain/{host => origin}/types.ts (72%) create mode 100644 src/domain/tenant/getByOrigin/getByOrigin.ts create mode 100644 src/domain/tenant/getByOrigin/index.ts rename src/integrations/runtime/middlewares/{TenantMiddleware.ts => OriginMiddleware.ts} (59%) create mode 100644 src/integrations/runtime/originMiddleware.ts delete mode 100644 src/integrations/runtime/tenantMiddleware.ts diff --git a/docker/keycloak/realm-export.json b/docker/keycloak/realm-export.json index acf72774..74553bde 100644 --- a/docker/keycloak/realm-export.json +++ b/docker/keycloak/realm-export.json @@ -666,6 +666,7 @@ "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ + "http://a.comify.local:3000/rpc/domain/authentication/login", "http://localhost:3000/rpc/domain/authentication/login", "http://localhost:5173/rpc/domain/authentication/login" ], diff --git a/docs/integrations/AUTHENTICATION.md b/docs/integrations/AUTHENTICATION.md index 64aff9c1..4458eca5 100644 --- a/docs/integrations/AUTHENTICATION.md +++ b/docs/integrations/AUTHENTICATION.md @@ -33,7 +33,7 @@ In case of OpenID, additional configuration is required. OPENID_ISSUER="http://localhost:8080/realms/comify" OPENID_CLIENT_ID="openid" OPENID_CLIENT_SECRET="" -OPENID_REDIRECT_URI="http://localhost:3000/rpc/domain/authentication/login" +OPENID_REDIRECT_OPENID_REDIRECT_PATH="/rpc/domain/authentication/login" OPENID_ALLOW_INSECURE_REQUESTS=true ``` diff --git a/example.env b/example.env index 59bd0f5e..c69e7fa4 100644 --- a/example.env +++ b/example.env @@ -23,12 +23,12 @@ MINIO_ACCESS_KEY="development" MINIO_SECRET_KEY="development" # AUTHENTICATION (openid) -AUTHENTICATION_CLIENT_URI="http://localhost:5173/identify" +AUTHENTICATION_CLIENT_PATH="/identify" AUTHENTICATION_IMPLEMENTATION="openid" OPENID_ISSUER="http://localhost:8080/realms/comify" OPENID_CLIENT_ID="openid" OPENID_CLIENT_SECRET="" -OPENID_REDIRECT_URI="http://localhost:3000/rpc/domain/authentication/login" +OPENID_REDIRECT_PATH="/rpc/domain/authentication/login" OPENID_ALLOW_INSECURE_REQUESTS=false # HTTP (fetch) diff --git a/segments/reads.json b/segments/reads.json index 57b4ebfe..53383870 100644 --- a/segments/reads.json +++ b/segments/reads.json @@ -10,7 +10,7 @@ "./domain/creator.metrics/getByCreator": { "default": { "access": "protected" } }, - "./domain/host/getByName": { "default": { "access": "protected" } }, + "./domain/origin/get": { "default": { "access": "protected" } }, "./domain/image/getById": { "default": { "access": "protected" } }, diff --git a/src/domain/authentication/login/login.ts b/src/domain/authentication/login/login.ts index aef603d1..a9418523 100644 --- a/src/domain/authentication/login/login.ts +++ b/src/domain/authentication/login/login.ts @@ -1,15 +1,17 @@ -import type { Identity } from '^/integrations/authentication'; +import { type Identity } from '^/integrations/authentication'; import getCreatorByEmail from '^/domain/creator/getByEmail'; import registerCreator from '^/domain/creator/register'; -import getByHostname from '^/domain/host/getByName'; +import getTenantByOrigin from '^/domain/tenant/getByOrigin'; import type { Requester } from '../types'; -export default async function login(identity: Identity, hostname: string): Promise +const MULTI_TENANT_MODE = process.env.MULTI_TENANT_MODE === 'true'; + +export default async function login(identity: Identity, origin: string): Promise { - const tenant = await getByHostname(hostname); + const tenant = await getTenantByOrigin(origin); const tenantId = tenant?.id; const existingCreator = await getCreatorByEmail(identity.email, tenantId); diff --git a/src/domain/host/definitions.ts b/src/domain/host/definitions.ts deleted file mode 100644 index e122c8b2..00000000 --- a/src/domain/host/definitions.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export const RECORD_TYPE = 'host'; diff --git a/src/domain/host/getByName/getByName.ts b/src/domain/host/getByName/getByName.ts deleted file mode 100644 index 4b0af0e3..00000000 --- a/src/domain/host/getByName/getByName.ts +++ /dev/null @@ -1,26 +0,0 @@ - -import type { RecordQuery } from '^/integrations/database'; -import database from '^/integrations/database'; - -import type { DataModel } from '^/domain/tenant'; -import getById from '^/domain/tenant/getById'; - -import { RECORD_TYPE } from '../definitions'; -import type { DataModel as Host } from '../types'; - -export default async function getByHostname(name: string): Promise -{ - const query: RecordQuery = - { - hostname: { 'EQUALS': name } - }; - - const host = await database.findRecord(RECORD_TYPE, query) as Host; - - if (host === undefined) - { - return; - } - - return getById(host.tenantId); -} diff --git a/src/domain/host/getByName/index.ts b/src/domain/host/getByName/index.ts deleted file mode 100644 index 4080bf86..00000000 --- a/src/domain/host/getByName/index.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export { default } from './getByName'; diff --git a/src/domain/origin/definitions.ts b/src/domain/origin/definitions.ts new file mode 100644 index 00000000..4485d40a --- /dev/null +++ b/src/domain/origin/definitions.ts @@ -0,0 +1,2 @@ + +export const RECORD_TYPE = 'origin'; diff --git a/src/domain/origin/get/get.ts b/src/domain/origin/get/get.ts new file mode 100644 index 00000000..be5e6b54 --- /dev/null +++ b/src/domain/origin/get/get.ts @@ -0,0 +1,16 @@ + +import type { RecordQuery } from '^/integrations/database'; +import database from '^/integrations/database'; + +import { RECORD_TYPE } from '../definitions'; +import type { DataModel } from '../types'; + +export default async function getByName(origin: string): Promise +{ + const query: RecordQuery = + { + origin: { 'EQUALS': origin } + }; + + return database.findRecord(RECORD_TYPE, query) as Promise; +} diff --git a/src/domain/origin/get/index.ts b/src/domain/origin/get/index.ts new file mode 100644 index 00000000..d7a2b2b0 --- /dev/null +++ b/src/domain/origin/get/index.ts @@ -0,0 +1,2 @@ + +export { default } from './get'; diff --git a/src/domain/host/index.ts b/src/domain/origin/index.ts similarity index 100% rename from src/domain/host/index.ts rename to src/domain/origin/index.ts diff --git a/src/domain/host/types.ts b/src/domain/origin/types.ts similarity index 72% rename from src/domain/host/types.ts rename to src/domain/origin/types.ts index 11529947..e1a5cfa4 100644 --- a/src/domain/host/types.ts +++ b/src/domain/origin/types.ts @@ -1,7 +1,7 @@ type DataModel = { readonly tenantId: string; - readonly hostname: string; + readonly origin: string; }; export type { DataModel }; diff --git a/src/domain/tenant/getById/getById.ts b/src/domain/tenant/getById/getById.ts index a51218b4..00a57264 100644 --- a/src/domain/tenant/getById/getById.ts +++ b/src/domain/tenant/getById/getById.ts @@ -6,5 +6,5 @@ import type { DataModel } from '../types'; export default async function getById(id: string): Promise { - return await database.readRecord(RECORD_TYPE, id) as DataModel; + return database.readRecord(RECORD_TYPE, id) as Promise; } diff --git a/src/domain/tenant/getByOrigin/getByOrigin.ts b/src/domain/tenant/getByOrigin/getByOrigin.ts new file mode 100644 index 00000000..7727df09 --- /dev/null +++ b/src/domain/tenant/getByOrigin/getByOrigin.ts @@ -0,0 +1,17 @@ + +import getByName from '^/domain/origin/get'; +import type { DataModel } from '^/domain/tenant'; + +import getById from '../getById'; + +export default async function getByOrigin(name: string): Promise +{ + const origin = await getByName(name); + + if (origin === undefined) + { + return; + } + + return getById(origin.tenantId); +} diff --git a/src/domain/tenant/getByOrigin/index.ts b/src/domain/tenant/getByOrigin/index.ts new file mode 100644 index 00000000..78296ea0 --- /dev/null +++ b/src/domain/tenant/getByOrigin/index.ts @@ -0,0 +1,2 @@ + +export { default } from './getByOrigin'; diff --git a/src/integrations/authentication/definitions/interfaces.ts b/src/integrations/authentication/definitions/interfaces.ts index ada2bb26..09ce7f99 100644 --- a/src/integrations/authentication/definitions/interfaces.ts +++ b/src/integrations/authentication/definitions/interfaces.ts @@ -9,9 +9,9 @@ export interface IdentityProvider disconnect(): Promise; - getLoginUrl(): Promise; + getLoginUrl(origin: string): Promise; - login(data: Record): Promise; + login(origin: string, data: Record): Promise; refresh(session: Session): Promise; diff --git a/src/integrations/authentication/implementations/openid/OpenID.ts b/src/integrations/authentication/implementations/openid/OpenID.ts index c31a97f4..21570038 100644 --- a/src/integrations/authentication/implementations/openid/OpenID.ts +++ b/src/integrations/authentication/implementations/openid/OpenID.ts @@ -16,7 +16,7 @@ type OpenIDConfiguration = { issuer: string; clientId: string; clientSecret: string; - redirectUri: string; + redirectPath: string; allowInsecureRequests: boolean; }; @@ -52,9 +52,9 @@ export default class OpenID implements IdentityProvider this.#clientConfiguration = undefined; } - async getLoginUrl(): Promise + async getLoginUrl(origin: string): Promise { - const redirect_uri = this.#providerConfiguration.redirectUri; + const redirect_uri = new URL(this.#providerConfiguration.redirectPath, origin).href; const scope = 'openid profile email'; const code_challenge = await calculatePKCECodeChallenge(this.#codeVerifier); const code_challenge_method = 'S256'; @@ -72,10 +72,10 @@ export default class OpenID implements IdentityProvider return redirectTo.href; } - async login(data: Record): Promise + async login(origin: string, data: Record): Promise { const clientConfiguration = this.#getClientConfiguration(); - const currentUrl = new URL(`${this.#providerConfiguration.redirectUri}?session_state=${data.session_state}&iss=${data.iss}&code=${data.code}`); + const currentUrl = new URL(`${this.#providerConfiguration.redirectPath}?session_state=${data.session_state}&iss=${data.iss}&code=${data.code}`, origin); const tokens = await authorizationCodeGrant(clientConfiguration, currentUrl, { pkceCodeVerifier: this.#codeVerifier, diff --git a/src/integrations/authentication/implementations/openid/create.ts b/src/integrations/authentication/implementations/openid/create.ts index f742f9ac..5475f9e9 100644 --- a/src/integrations/authentication/implementations/openid/create.ts +++ b/src/integrations/authentication/implementations/openid/create.ts @@ -6,8 +6,8 @@ export default function create(): OpenID const issuer = process.env.OPENID_ISSUER ?? 'undefined'; const clientId = process.env.OPENID_CLIENT_ID ?? 'undefined'; const clientSecret = process.env.OPENID_CLIENT_SECRET ?? 'undefined'; - const redirectUri = process.env.OPENID_REDIRECT_URI ?? 'undefined'; + const redirectPath = process.env.OPENID_REDIRECT_PATH ?? 'undefined'; const allowInsecureRequests = process.env.OPENID_ALLOW_INSECURE_REQUESTS === 'true'; - return new OpenID({ issuer, clientId, clientSecret, redirectUri, allowInsecureRequests }); + return new OpenID({ issuer, clientId, clientSecret, redirectPath, allowInsecureRequests }); } diff --git a/src/integrations/runtime/authenticationMiddleware.ts b/src/integrations/runtime/authenticationMiddleware.ts index b0bec483..2a1bb547 100644 --- a/src/integrations/runtime/authenticationMiddleware.ts +++ b/src/integrations/runtime/authenticationMiddleware.ts @@ -9,7 +9,7 @@ const authProcedures = { logout: 'domain/authentication/logout' }; -const redirectUrl = process.env.AUTHENTICATION_CLIENT_URI || 'undefined'; +const redirectUrl = process.env.AUTHENTICATION_CLIENT_PATH || 'undefined'; const whiteList: string[] = []; diff --git a/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts b/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts index 8a463fb0..c9f235ac 100644 --- a/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts +++ b/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts @@ -14,10 +14,10 @@ type AuthProcedures = { }; const IDENTITY_PARAMETER = 'identity'; -const HOSTNAME_PARAMETER = 'hostname'; +const ORIGIN_PARAMETER = 'origin'; const REQUESTER_PARAMETER = '*requester'; const JITAR_TRUST_HEADER_KEY = 'X-Jitar-Trust-Key'; -const COMIFY_HOST_COOKIE_KEY = 'x-comify-host'; +const COMIFY_HOST_COOKIE_KEY = 'x-comify-origin'; const sessions = new Map(); @@ -25,14 +25,14 @@ export default class AuthenticationMiddleware implements Middleware { readonly #identityProvider: IdentityProvider; readonly #authProcedures: AuthProcedures; - readonly #redirectUrl: string; + readonly #redirectPath: string; readonly #whiteList: string[]; - constructor(identityProvider: IdentityProvider, authProcedures: AuthProcedures, redirectUrl: string, whiteList: string[]) + constructor(identityProvider: IdentityProvider, authProcedures: AuthProcedures, redirectPath: string, whiteList: string[]) { this.#identityProvider = identityProvider; this.#authProcedures = authProcedures; - this.#redirectUrl = redirectUrl; + this.#redirectPath = redirectPath; this.#whiteList = whiteList; } @@ -45,16 +45,23 @@ export default class AuthenticationMiddleware implements Middleware switch (request.fqn) { - case this.#authProcedures.loginUrl: return this.#getLoginUrl(); + case this.#authProcedures.loginUrl: return this.#getLoginUrl(request); case this.#authProcedures.login: return this.#createSession(request, next); case this.#authProcedures.logout: return this.#destroySession(request, next); default: return this.#handleRequest(request, next); } } - async #getLoginUrl(): Promise + async #getLoginUrl(request: Request): Promise { - const url = await this.#identityProvider.getLoginUrl(); + const origin = this.#getOrigin(request); + + if (origin === undefined) + { + throw new Unauthorized('Invalid origin'); + } + + const url = await this.#identityProvider.getLoginUrl(origin); return new Response(200, url); } @@ -62,22 +69,27 @@ export default class AuthenticationMiddleware implements Middleware async #createSession(request: Request, next: NextHandler): Promise { const data = Object.fromEntries(request.args); - const hostname = this.#getHostname(request); - const session = await this.#identityProvider.login(data); + const origin = this.#getOrigin(request); + const session = await this.#identityProvider.login(origin, data); request.args.clear(); request.setArgument(IDENTITY_PARAMETER, session.identity); - request.setArgument(HOSTNAME_PARAMETER, hostname); + request.setArgument(ORIGIN_PARAMETER, origin); const response = await next(); + // if (response.status !== 200) + // { + // throw new Unauthorized('Invalid tenant'); + // } + session.key = generateKey(); session.requester = response.result; sessions.set(session.key, session); this.#setAuthorizationHeader(response, session); - this.#setRedirectHeader(response, session.key); + this.#setRedirectHeader(response, session.key, origin); return response; } @@ -216,23 +228,29 @@ export default class AuthenticationMiddleware implements Middleware response.setHeader('Authorization', `Bearer ${session.key}`); } - #setRedirectHeader(response: Response, key: string): void + #setRedirectHeader(response: Response, key: string, origin: string): void { - response.setHeader('Location', `${this.#redirectUrl}?key=${key}`); + response.setHeader('Location', new URL(`${this.#redirectPath}?key=${key}`, origin).href); } - #getHostname(request: Request): string | undefined + #getOrigin(request: Request): string { const cookie = request.getHeader('cookie'); if (cookie === undefined) { - return undefined; + throw new Unauthorized('Invalid origin'); } const cookies = this.#getCookies(cookie); + const origin = cookies.get(COMIFY_HOST_COOKIE_KEY); + + if (origin === undefined) + { + throw new Unauthorized('Invalid origin'); + } - return cookies.get(COMIFY_HOST_COOKIE_KEY); + return origin; } #getCookies(input: string): Map diff --git a/src/integrations/runtime/middlewares/TenantMiddleware.ts b/src/integrations/runtime/middlewares/OriginMiddleware.ts similarity index 59% rename from src/integrations/runtime/middlewares/TenantMiddleware.ts rename to src/integrations/runtime/middlewares/OriginMiddleware.ts index d5dbc147..cd0af9de 100644 --- a/src/integrations/runtime/middlewares/TenantMiddleware.ts +++ b/src/integrations/runtime/middlewares/OriginMiddleware.ts @@ -1,16 +1,17 @@ -import { setCookie } from '^/integrations/utilities/webbrowser'; import type { Middleware, NextHandler, Request, Response } from 'jitar'; -const hostHeader = 'x-comify-host'; +import { setCookie } from '^/integrations/utilities/webbrowser'; + +const ORIGIN_HEADER = 'X-Comify-Origin'; -export default class TenantMiddleware implements Middleware +export default class HostMiddleware implements Middleware { constructor() { - const clientHost = document.location.host; + const origin = document.location.origin; - setCookie(hostHeader, clientHost); + setCookie(ORIGIN_HEADER, origin); } handle(request: Request, next: NextHandler): Promise diff --git a/src/integrations/runtime/originMiddleware.ts b/src/integrations/runtime/originMiddleware.ts new file mode 100644 index 00000000..a6f56685 --- /dev/null +++ b/src/integrations/runtime/originMiddleware.ts @@ -0,0 +1,4 @@ + +import OriginMiddleware from './middlewares/OriginMiddleware'; + +export default new OriginMiddleware(); diff --git a/src/integrations/runtime/tenantMiddleware.ts b/src/integrations/runtime/tenantMiddleware.ts deleted file mode 100644 index 0d8d6704..00000000 --- a/src/integrations/runtime/tenantMiddleware.ts +++ /dev/null @@ -1,4 +0,0 @@ - -import TenantMiddleware from './middlewares/TenantMiddleware'; - -export default new TenantMiddleware(); diff --git a/src/integrations/utilities/webbrowser.ts b/src/integrations/utilities/webbrowser.ts index 33b0cebb..607fca4e 100644 --- a/src/integrations/utilities/webbrowser.ts +++ b/src/integrations/utilities/webbrowser.ts @@ -8,6 +8,13 @@ export function getQueryParameter(name: string): string | undefined export function setCookie(key: string, value: string): void { + const documentCookie = document.cookie; + + if (documentCookie.includes(`${key}=`)) + { + return; + } + const cookie = `${key}=${value}`; document.cookie = cookie; diff --git a/vite.config.ts b/vite.config.ts index cba83cd2..ba18b0a6 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,7 +8,7 @@ const JITAR_URL = 'http://localhost:3000'; const JITAR_SEGMENTS = []; const JITAR_MIDDLEWARES = [ './integrations/runtime/requesterMiddleware', - './integrations/runtime/tenantMiddleware' + './integrations/runtime/originMiddleware' ]; const jitarConfig: JitarConfig = { From 508307271d65f33c274f09f9098526802a208beb Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Fri, 20 Jun 2025 13:46:41 +0200 Subject: [PATCH 04/25] Refactor origin domain and add creation logic with tests Introduces a new origin creation flow with validation, error handling, and database insertion. Refactors the origin 'get' logic to 'find', updates related tenant/origin lookups, and adds comprehensive tests and fixtures for origin and tenant modules. Also includes minor improvements and bug fixes in authentication, middleware, and utility code. --- docs/integrations/AUTHENTICATION.md | 2 +- src/domain/authentication/login/login.ts | 17 ++++++++-- src/domain/origin/create/InvalidOrigin.ts | 7 ++++ src/domain/origin/create/create.ts | 26 +++++++++++++++ src/domain/origin/create/createData.ts | 13 ++++++++ src/domain/origin/create/index.ts | 2 ++ src/domain/origin/create/insertData.ts | 10 ++++++ src/domain/origin/create/types.ts | 4 +++ src/domain/origin/create/validateData.ts | 24 ++++++++++++++ .../origin/{get/get.ts => find/find.ts} | 4 +-- src/domain/origin/find/index.ts | 2 ++ src/domain/origin/get/index.ts | 2 -- src/domain/origin/types.ts | 4 ++- .../getFollowingAggregated.ts | 2 +- src/domain/tenant/getById/getById.ts | 4 +-- src/domain/tenant/getByOrigin/getByOrigin.ts | 10 +++--- .../runtime/authenticationMiddleware.ts | 4 +-- .../middlewares/AuthenticationMiddleware.ts | 23 +++++++------ .../runtime/middlewares/OriginMiddleware.ts | 2 +- .../middlewares/RequesterMiddleware.ts | 22 +++---------- src/integrations/utilities/webbrowser.ts | 4 +-- test/domain/origin/create.spec.ts | 29 ++++++++++++++++ .../origin/fixtures/databases.fixtures.ts | 15 +++++++++ test/domain/origin/fixtures/index.ts | 4 +++ .../origin/fixtures/records.fixtures.ts | 13 ++++++++ .../domain/origin/fixtures/values.fixtures.ts | 20 +++++++++++ test/domain/origin/get.spec.ts | 33 +++++++++++++++++++ .../tenant/fixtures/databases.fixtures.ts | 17 ++++++++++ test/domain/tenant/fixtures/index.ts | 4 +++ .../tenant/fixtures/records.fixtures.ts | 18 ++++++++++ .../domain/tenant/fixtures/values.fixtures.ts | 16 +++++++++ test/domain/tenant/getByOrigin.spec.ts | 29 ++++++++++++++++ .../database/implementation.spec.ts | 6 ++-- 33 files changed, 340 insertions(+), 52 deletions(-) create mode 100644 src/domain/origin/create/InvalidOrigin.ts create mode 100644 src/domain/origin/create/create.ts create mode 100644 src/domain/origin/create/createData.ts create mode 100644 src/domain/origin/create/index.ts create mode 100644 src/domain/origin/create/insertData.ts create mode 100644 src/domain/origin/create/types.ts create mode 100644 src/domain/origin/create/validateData.ts rename src/domain/origin/{get/get.ts => find/find.ts} (62%) create mode 100644 src/domain/origin/find/index.ts delete mode 100644 src/domain/origin/get/index.ts create mode 100644 test/domain/origin/create.spec.ts create mode 100644 test/domain/origin/fixtures/databases.fixtures.ts create mode 100644 test/domain/origin/fixtures/index.ts create mode 100644 test/domain/origin/fixtures/records.fixtures.ts create mode 100644 test/domain/origin/fixtures/values.fixtures.ts create mode 100644 test/domain/origin/get.spec.ts create mode 100644 test/domain/tenant/fixtures/databases.fixtures.ts create mode 100644 test/domain/tenant/fixtures/index.ts create mode 100644 test/domain/tenant/fixtures/records.fixtures.ts create mode 100644 test/domain/tenant/fixtures/values.fixtures.ts create mode 100644 test/domain/tenant/getByOrigin.spec.ts diff --git a/docs/integrations/AUTHENTICATION.md b/docs/integrations/AUTHENTICATION.md index 4458eca5..1d42319a 100644 --- a/docs/integrations/AUTHENTICATION.md +++ b/docs/integrations/AUTHENTICATION.md @@ -33,7 +33,7 @@ In case of OpenID, additional configuration is required. OPENID_ISSUER="http://localhost:8080/realms/comify" OPENID_CLIENT_ID="openid" OPENID_CLIENT_SECRET="" -OPENID_REDIRECT_OPENID_REDIRECT_PATH="/rpc/domain/authentication/login" +OPENID_REDIRECT_PATH="/rpc/domain/authentication/login" OPENID_ALLOW_INSECURE_REQUESTS=true ``` diff --git a/src/domain/authentication/login/login.ts b/src/domain/authentication/login/login.ts index a9418523..0aca700c 100644 --- a/src/domain/authentication/login/login.ts +++ b/src/domain/authentication/login/login.ts @@ -1,5 +1,6 @@ import { type Identity } from '^/integrations/authentication'; +import { NotFound } from '^/integrations/runtime'; import getCreatorByEmail from '^/domain/creator/getByEmail'; import registerCreator from '^/domain/creator/register'; @@ -11,8 +12,18 @@ const MULTI_TENANT_MODE = process.env.MULTI_TENANT_MODE === 'true'; export default async function login(identity: Identity, origin: string): Promise { - const tenant = await getTenantByOrigin(origin); - const tenantId = tenant?.id; + let tenantId: string | undefined; + + if (MULTI_TENANT_MODE) + { + const tenant = await getTenantByOrigin(origin); + tenantId = tenant?.id; + + if (tenantId === undefined) + { + throw new NotFound(`Tenant not found for origin: ${origin}`); + } + } const existingCreator = await getCreatorByEmail(identity.email, tenantId); @@ -28,6 +39,6 @@ export default async function login(identity: Identity, origin: string): Promise id: loggedInCreator.id, fullName: loggedInCreator.fullName, nickname: loggedInCreator.nickname, - tenantId, + tenantId: loggedInCreator.tenantId }; } diff --git a/src/domain/origin/create/InvalidOrigin.ts b/src/domain/origin/create/InvalidOrigin.ts new file mode 100644 index 00000000..87a6a643 --- /dev/null +++ b/src/domain/origin/create/InvalidOrigin.ts @@ -0,0 +1,7 @@ + +import { ValidationError } from '^/integrations/runtime'; + +export default class InvalidOrigin extends ValidationError +{ + +} diff --git a/src/domain/origin/create/create.ts b/src/domain/origin/create/create.ts new file mode 100644 index 00000000..c41b5356 --- /dev/null +++ b/src/domain/origin/create/create.ts @@ -0,0 +1,26 @@ + +import find from '../find'; +import createData from './createData'; +import insertData from './insertData'; +import InvalidOrigin from './InvalidOrigin'; +import validateData from './validateData'; + +export default async function create(tenantId: string, origin: string): Promise +{ + const data = createData(tenantId, origin); + + validateData(data); + + const exists = await find(origin); + + if (exists) + { + const messages = new Map([ + ['origin', 'Origin already exists'], + ]); + + throw new InvalidOrigin(messages); + } + + return insertData(data); +} diff --git a/src/domain/origin/create/createData.ts b/src/domain/origin/create/createData.ts new file mode 100644 index 00000000..46d33261 --- /dev/null +++ b/src/domain/origin/create/createData.ts @@ -0,0 +1,13 @@ + +import { generateId } from '^/integrations/utilities/crypto'; + +import type { DataModel } from '../types'; + +export default function createData(tenantId: string, origin: string): DataModel +{ + return { + id: generateId(), + tenantId, + origin, + }; +} diff --git a/src/domain/origin/create/index.ts b/src/domain/origin/create/index.ts new file mode 100644 index 00000000..01487567 --- /dev/null +++ b/src/domain/origin/create/index.ts @@ -0,0 +1,2 @@ + +export { default } from './create'; diff --git a/src/domain/origin/create/insertData.ts b/src/domain/origin/create/insertData.ts new file mode 100644 index 00000000..2194461e --- /dev/null +++ b/src/domain/origin/create/insertData.ts @@ -0,0 +1,10 @@ + +import database from '^/integrations/database'; + +import { RECORD_TYPE } from '../definitions'; +import type { DataModel } from '../types'; + +export default async function insertData(data: DataModel): Promise +{ + return await database.createRecord(RECORD_TYPE, data); +} diff --git a/src/domain/origin/create/types.ts b/src/domain/origin/create/types.ts new file mode 100644 index 00000000..c0e69579 --- /dev/null +++ b/src/domain/origin/create/types.ts @@ -0,0 +1,4 @@ + +import type { DataModel } from '../types'; + +export type ValidationModel = DataModel; diff --git a/src/domain/origin/create/validateData.ts b/src/domain/origin/create/validateData.ts new file mode 100644 index 00000000..3a2a3929 --- /dev/null +++ b/src/domain/origin/create/validateData.ts @@ -0,0 +1,24 @@ + +import type { ValidationSchema } from '^/integrations/validation'; +import validator from '^/integrations/validation'; + +import { requiredIdValidation } from '^/domain/definitions'; + +import InvalidOrigin from './InvalidOrigin'; +import type { ValidationModel } from './types'; + +const schema: ValidationSchema = +{ + tenantId: requiredIdValidation, + origin: { URL: { required: true } } +}; + +export default function validateData({ tenantId, origin }: ValidationModel): void +{ + const result = validator.validate({ tenantId, origin }, schema); + + if (result.invalid) + { + throw new InvalidOrigin(result.messages); + } +} diff --git a/src/domain/origin/get/get.ts b/src/domain/origin/find/find.ts similarity index 62% rename from src/domain/origin/get/get.ts rename to src/domain/origin/find/find.ts index be5e6b54..6fb7bd96 100644 --- a/src/domain/origin/get/get.ts +++ b/src/domain/origin/find/find.ts @@ -5,12 +5,12 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getByName(origin: string): Promise +export default async function getByOrigin(origin: string): Promise { const query: RecordQuery = { origin: { 'EQUALS': origin } }; - return database.findRecord(RECORD_TYPE, query) as Promise; + return await database.findRecord(RECORD_TYPE, query) as DataModel | undefined; } diff --git a/src/domain/origin/find/index.ts b/src/domain/origin/find/index.ts new file mode 100644 index 00000000..0d616d7b --- /dev/null +++ b/src/domain/origin/find/index.ts @@ -0,0 +1,2 @@ + +export { default } from './find'; diff --git a/src/domain/origin/get/index.ts b/src/domain/origin/get/index.ts deleted file mode 100644 index d7a2b2b0..00000000 --- a/src/domain/origin/get/index.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export { default } from './get'; diff --git a/src/domain/origin/types.ts b/src/domain/origin/types.ts index e1a5cfa4..fd2ba4fd 100644 --- a/src/domain/origin/types.ts +++ b/src/domain/origin/types.ts @@ -1,5 +1,7 @@ -type DataModel = { +import type { BaseDataModel } from '../types'; + +type DataModel = BaseDataModel & { readonly tenantId: string; readonly origin: string; }; diff --git a/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts b/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts index f52d7245..ca49d866 100644 --- a/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts +++ b/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts @@ -13,5 +13,5 @@ export default async function getFollowingAggregated(requester: Requester, follo const data = await retrieveByFollower(requester, followerId, range.limit, range.offset); - return Promise.all(data.map(data => aggregate(requester, data))); + return Promise.all(data.map(item => aggregate(requester, item))); } diff --git a/src/domain/tenant/getById/getById.ts b/src/domain/tenant/getById/getById.ts index 00a57264..591bbec1 100644 --- a/src/domain/tenant/getById/getById.ts +++ b/src/domain/tenant/getById/getById.ts @@ -4,7 +4,7 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getById(id: string): Promise +export default async function getById(id: string): Promise { - return database.readRecord(RECORD_TYPE, id) as Promise; + return await database.readRecord(RECORD_TYPE, id) as DataModel; } diff --git a/src/domain/tenant/getByOrigin/getByOrigin.ts b/src/domain/tenant/getByOrigin/getByOrigin.ts index 7727df09..b5dd0430 100644 --- a/src/domain/tenant/getByOrigin/getByOrigin.ts +++ b/src/domain/tenant/getByOrigin/getByOrigin.ts @@ -1,17 +1,17 @@ -import getByName from '^/domain/origin/get'; +import findOrigin from '^/domain/origin/find'; import type { DataModel } from '^/domain/tenant'; import getById from '../getById'; -export default async function getByOrigin(name: string): Promise +export default async function getByOrigin(origin: string): Promise { - const origin = await getByName(name); + const data = await findOrigin(origin); - if (origin === undefined) + if (data === undefined) { return; } - return getById(origin.tenantId); + return await getById(data.tenantId); } diff --git a/src/integrations/runtime/authenticationMiddleware.ts b/src/integrations/runtime/authenticationMiddleware.ts index 2a1bb547..e9bf8ea5 100644 --- a/src/integrations/runtime/authenticationMiddleware.ts +++ b/src/integrations/runtime/authenticationMiddleware.ts @@ -9,8 +9,8 @@ const authProcedures = { logout: 'domain/authentication/logout' }; -const redirectUrl = process.env.AUTHENTICATION_CLIENT_PATH || 'undefined'; +const redirectPath = process.env.AUTHENTICATION_CLIENT_PATH || 'undefined'; const whiteList: string[] = []; -export default new AuthenticationMiddleware(identityProvider, authProcedures, redirectUrl, whiteList); +export default new AuthenticationMiddleware(identityProvider, authProcedures, redirectPath, whiteList); diff --git a/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts b/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts index c9f235ac..af63a35e 100644 --- a/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts +++ b/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts @@ -56,11 +56,6 @@ export default class AuthenticationMiddleware implements Middleware { const origin = this.#getOrigin(request); - if (origin === undefined) - { - throw new Unauthorized('Invalid origin'); - } - const url = await this.#identityProvider.getLoginUrl(origin); return new Response(200, url); @@ -78,10 +73,12 @@ export default class AuthenticationMiddleware implements Middleware const response = await next(); - // if (response.status !== 200) - // { - // throw new Unauthorized('Invalid tenant'); - // } + if (response.status !== 200) + { + await this.#identityProvider.logout(session); + + return response; + } session.key = generateKey(); session.requester = response.result; @@ -233,6 +230,11 @@ export default class AuthenticationMiddleware implements Middleware response.setHeader('Location', new URL(`${this.#redirectPath}?key=${key}`, origin).href); } + // The origin, retrieved from a user's device cookie, is an untrusted source. It's crucial to validate this origin before using it in redirects. + // Validation occurs in two steps: + + // IDP Check: Verifies the origin against a predefined list of allowed origins. + // Domain Check: Validates the origin against a list of registered origins. #getOrigin(request: Request): string { const cookie = request.getHeader('cookie'); @@ -263,7 +265,8 @@ export default class AuthenticationMiddleware implements Middleware const parts = cookie.trim().split('='); const key = parts[0]?.toLocaleLowerCase(); - const value = parts.length > 2 ? parts.slice(1).join('=') : parts[1]; + const rawValue = parts.length > 2 ? parts.slice(1).join('=') : parts[1]; + const value = rawValue ? decodeURIComponent(rawValue) : ''; cookieMap.set(key, value); } diff --git a/src/integrations/runtime/middlewares/OriginMiddleware.ts b/src/integrations/runtime/middlewares/OriginMiddleware.ts index cd0af9de..3e8cd152 100644 --- a/src/integrations/runtime/middlewares/OriginMiddleware.ts +++ b/src/integrations/runtime/middlewares/OriginMiddleware.ts @@ -5,7 +5,7 @@ import { setCookie } from '^/integrations/utilities/webbrowser'; const ORIGIN_HEADER = 'X-Comify-Origin'; -export default class HostMiddleware implements Middleware +export default class OriginMiddleware implements Middleware { constructor() { diff --git a/src/integrations/runtime/middlewares/RequesterMiddleware.ts b/src/integrations/runtime/middlewares/RequesterMiddleware.ts index b2f7624e..611a283a 100644 --- a/src/integrations/runtime/middlewares/RequesterMiddleware.ts +++ b/src/integrations/runtime/middlewares/RequesterMiddleware.ts @@ -17,25 +17,13 @@ export default class RequesterMiddleware implements Middleware request.setHeader('Authorization', this.#authorization); } - try - { - const response = await next(); - - if (response.hasHeader('Authorization')) - { - this.#authorization = response.getHeader('Authorization')!; - } + const response = await next(); - return response; - } - catch (error) + if (response.hasHeader('Authorization')) { - if (error?.constructor?.name === 'Unauthorized') - { - this.#authorization = undefined; - } - - throw error; + this.#authorization = response.getHeader('Authorization')!; } + + return response; } } diff --git a/src/integrations/utilities/webbrowser.ts b/src/integrations/utilities/webbrowser.ts index 607fca4e..a24a956d 100644 --- a/src/integrations/utilities/webbrowser.ts +++ b/src/integrations/utilities/webbrowser.ts @@ -10,12 +10,12 @@ export function setCookie(key: string, value: string): void { const documentCookie = document.cookie; - if (documentCookie.includes(`${key}=`)) + if (documentCookie.split('; ').some(cookie => cookie.startsWith(`${key}=`))) { return; } - const cookie = `${key}=${value}`; + const cookie = `${key}=${value}; path=/; SameSite=Strict;`; document.cookie = cookie; } diff --git a/test/domain/origin/create.spec.ts b/test/domain/origin/create.spec.ts new file mode 100644 index 00000000..6788a3a2 --- /dev/null +++ b/test/domain/origin/create.spec.ts @@ -0,0 +1,29 @@ + +import { beforeEach, describe, expect, it } from 'vitest'; + +import create from '^/domain/origin/create'; +import InvalidOrigin from '^/domain/origin/create/InvalidOrigin'; + +import { DATABASES, VALUES } from './fixtures'; + +beforeEach(async () => +{ + await DATABASES.origins(); +}); + +describe('domain/origin/create', () => +{ + it('should add an new origin', async () => + { + const id = await create(VALUES.IDS.NEW, VALUES.ORIGINS.NEW); + + expect(id).toBeDefined(); + }); + + it('should NOT add an existing origin', async () => + { + const promise = create(VALUES.IDS.EXISTING, VALUES.ORIGINS.EXISTING); + + await expect(promise).rejects.toThrow(InvalidOrigin); + }); +}); diff --git a/test/domain/origin/fixtures/databases.fixtures.ts b/test/domain/origin/fixtures/databases.fixtures.ts new file mode 100644 index 00000000..4381565d --- /dev/null +++ b/test/domain/origin/fixtures/databases.fixtures.ts @@ -0,0 +1,15 @@ + +import database from '^/integrations/database'; + +import { RECORD_TYPE as ORIGIN_RECORD_TYPE } from '^/domain/origin'; + +import { RECORDS } from './records.fixtures'; + +database.connect(); + +async function origins(): Promise +{ + RECORDS.ORIGINS.map(origins => database.createRecord(ORIGIN_RECORD_TYPE, { ...origins })); +} + +export const DATABASES = { origins }; diff --git a/test/domain/origin/fixtures/index.ts b/test/domain/origin/fixtures/index.ts new file mode 100644 index 00000000..945a801c --- /dev/null +++ b/test/domain/origin/fixtures/index.ts @@ -0,0 +1,4 @@ + +export * from './databases.fixtures'; +export * from './values.fixtures'; + diff --git a/test/domain/origin/fixtures/records.fixtures.ts b/test/domain/origin/fixtures/records.fixtures.ts new file mode 100644 index 00000000..d93d3976 --- /dev/null +++ b/test/domain/origin/fixtures/records.fixtures.ts @@ -0,0 +1,13 @@ + +import type { RecordData } from '^/integrations/database'; + +import type { DataModel as OriginDataModel } from '^/domain/origin'; + +import { VALUES } from './values.fixtures'; + +export const ORIGINS: OriginDataModel[] = [ + { id: VALUES.IDS.ORIGIN1, origin: VALUES.ORIGINS.ORIGIN1, tenantId: VALUES.IDS.TENANT1 }, + { id: VALUES.IDS.ORIGIN2, origin: VALUES.ORIGINS.ORIGIN2, tenantId: VALUES.IDS.TENANT1 }, +]; + +export const RECORDS: Record = { ORIGINS }; diff --git a/test/domain/origin/fixtures/values.fixtures.ts b/test/domain/origin/fixtures/values.fixtures.ts new file mode 100644 index 00000000..3fed4d54 --- /dev/null +++ b/test/domain/origin/fixtures/values.fixtures.ts @@ -0,0 +1,20 @@ + +export const VALUES = +{ + IDS: { + ORIGIN1: 'origin1', + ORIGIN2: 'origin2', + EXISTING: 'origin1', + NEW: 'origin3', + + TENANT1: 'tenant1' + }, + + ORIGINS: { + ORIGIN1: 'https://origin1.com', + ORIGIN2: 'https://origin2.com', + EXISTING: 'https://origin1.com', + NEW: 'https://origin3.com', + UNKNOWN: 'https://unknown.com' + } +}; diff --git a/test/domain/origin/get.spec.ts b/test/domain/origin/get.spec.ts new file mode 100644 index 00000000..869bea88 --- /dev/null +++ b/test/domain/origin/get.spec.ts @@ -0,0 +1,33 @@ + +import { beforeEach, describe, expect, it } from 'vitest'; + +import find from '^/domain/origin/find'; + +import { DATABASES, VALUES } from './fixtures'; + +beforeEach(async () => +{ + await DATABASES.origins(); +}); + +describe('domain/origin/get', () => +{ + it('should get multiple origin for single tenant', async () => + { + const origin1 = await find(VALUES.ORIGINS.ORIGIN1); + const origin2 = await find(VALUES.ORIGINS.ORIGIN2); + + expect(origin1).toBeDefined(); + expect(origin1?.tenantId).toBe(VALUES.IDS.TENANT1); + + expect(origin2).toBeDefined(); + expect(origin2?.tenantId).toBe(VALUES.IDS.TENANT1); + }); + + it('should ', async () => + { + const origin = await find(VALUES.ORIGINS.UNKNOWN); + + expect(origin).toBeUndefined(); + }); +}); diff --git a/test/domain/tenant/fixtures/databases.fixtures.ts b/test/domain/tenant/fixtures/databases.fixtures.ts new file mode 100644 index 00000000..db538c8d --- /dev/null +++ b/test/domain/tenant/fixtures/databases.fixtures.ts @@ -0,0 +1,17 @@ + +import database from '^/integrations/database'; + +import { RECORD_TYPE as ORIGIN_RECORD_TYPE } from '^/domain/origin'; +import { RECORD_TYPE as TENANT_RECORD_TYPE } from '^/domain/tenant'; + +import { RECORDS } from './records.fixtures'; + +database.connect(); + +async function tenantsAndOrigins(): Promise +{ + RECORDS.TENANTS.map(tenants => database.createRecord(TENANT_RECORD_TYPE, { ...tenants })); + RECORDS.ORIGINS.map(origins => database.createRecord(ORIGIN_RECORD_TYPE, { ...origins })); +} + +export const DATABASES = { tenantsAndOrigins }; diff --git a/test/domain/tenant/fixtures/index.ts b/test/domain/tenant/fixtures/index.ts new file mode 100644 index 00000000..945a801c --- /dev/null +++ b/test/domain/tenant/fixtures/index.ts @@ -0,0 +1,4 @@ + +export * from './databases.fixtures'; +export * from './values.fixtures'; + diff --git a/test/domain/tenant/fixtures/records.fixtures.ts b/test/domain/tenant/fixtures/records.fixtures.ts new file mode 100644 index 00000000..9a1afc13 --- /dev/null +++ b/test/domain/tenant/fixtures/records.fixtures.ts @@ -0,0 +1,18 @@ + +import type { RecordData } from '^/integrations/database'; + +import type { DataModel as OriginDataModel } from '^/domain/origin'; +import type { DataModel as TenantDataModel } from '^/domain/tenant'; + +import { VALUES } from './values.fixtures'; + +export const TENANTS: TenantDataModel[] = [ + { id: VALUES.IDS.TENANT1, name: VALUES.NAMES.TENANT1 } +]; + +export const ORIGINS: OriginDataModel[] = [ + { id: VALUES.IDS.ORIGIN1, origin: VALUES.NAMES.ORIGIN1, tenantId: VALUES.IDS.TENANT1 }, + { id: VALUES.IDS.ORIGIN2, origin: VALUES.NAMES.ORIGIN2, tenantId: VALUES.IDS.TENANT1 } +]; + +export const RECORDS: Record = { TENANTS, ORIGINS }; diff --git a/test/domain/tenant/fixtures/values.fixtures.ts b/test/domain/tenant/fixtures/values.fixtures.ts new file mode 100644 index 00000000..b09663e9 --- /dev/null +++ b/test/domain/tenant/fixtures/values.fixtures.ts @@ -0,0 +1,16 @@ + +export const VALUES = +{ + IDS: { + TENANT1: 'tenant1', + ORIGIN1: 'origin1', + ORIGIN2: 'origin2' + }, + + NAMES: { + TENANT1: 'Tenant 1', + ORIGIN1: 'Origin 1', + ORIGIN2: 'Origin 2', + UNKNOWN: 'Unknown Origin' + } +}; diff --git a/test/domain/tenant/getByOrigin.spec.ts b/test/domain/tenant/getByOrigin.spec.ts new file mode 100644 index 00000000..06fbfa2e --- /dev/null +++ b/test/domain/tenant/getByOrigin.spec.ts @@ -0,0 +1,29 @@ + +import { beforeEach, describe, expect, it } from 'vitest'; + +import getByOrigin from '^/domain/tenant/getByOrigin'; + +import { DATABASES, VALUES } from './fixtures'; + +beforeEach(async () => +{ + await DATABASES.tenantsAndOrigins(); +}); + +describe('domain/tenant/getByOrigin', () => +{ + it('should get an existing', async () => + { + const tenant = await getByOrigin(VALUES.NAMES.ORIGIN1); + + expect(tenant).toBeDefined(); + expect(tenant?.id).toEqual(VALUES.IDS.TENANT1); + }); + + it('should NOT get an unknown tenant', async () => + { + const tenant = await getByOrigin(VALUES.NAMES.UNKNOWN); + + expect(tenant).toBeUndefined(); + }); +}); diff --git a/test/integrations/database/implementation.spec.ts b/test/integrations/database/implementation.spec.ts index 3286687a..66b1a647 100644 --- a/test/integrations/database/implementation.spec.ts +++ b/test/integrations/database/implementation.spec.ts @@ -229,9 +229,9 @@ describe('integrations/database/implementation', () => { const data: RecordData = VALUES.NO_MATCH_SIZE; - // This should not throw an error - await expect(database.updateRecords(RECORD_TYPES.PIZZAS, QUERIES.NO_MATCH, data)) - .resolves.toBeUndefined(); + const promise = database.updateRecords(RECORD_TYPES.PIZZAS, QUERIES.NO_MATCH, data); + + await expect(promise).resolves.toBeUndefined(); }); }); From c660607ac9ca360f785fa350cd70c83ea086e8fd Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Fri, 20 Jun 2025 15:48:28 +0200 Subject: [PATCH 05/25] #375: updated segment config --- segments/reads.json | 4 +++- segments/writes.json | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/segments/reads.json b/segments/reads.json index 578d5548..a7538e2c 100644 --- a/segments/reads.json +++ b/segments/reads.json @@ -10,7 +10,7 @@ "./domain/creator.metrics/getByCreator": { "default": { "access": "protected" } }, - "./domain/origin/get": { "default": { "access": "protected" } }, + "./domain/origin/find": { "default": { "access": "protected" } }, "./domain/image/getById": { "default": { "access": "protected" } }, @@ -23,6 +23,8 @@ "./domain/post.metrics/getByPost": { "default": { "access": "protected" } }, + "./domain/tenant/getById": { "default": { "access": "protected" } }, + "./domain/rating/exists": { "default": { "access": "protected" } }, "./domain/rating/toggle/getData": { "default": { "access": "protected" } }, diff --git a/segments/writes.json b/segments/writes.json index c1f09fa1..566a2875 100644 --- a/segments/writes.json +++ b/segments/writes.json @@ -14,6 +14,8 @@ "./domain/image/save": { "default": { "access": "protected" } }, "./domain/image/erase": { "default": { "access": "protected" } }, + "./domain/origin/create/insertData": { "default": { "access": "protected" } }, + "./domain/post/create/insertData": { "default": { "access": "protected" } }, "./domain/post/erase": { "default": { "access": "protected" } }, "./domain/post/remove/deleteData": { "default": { "access": "protected" }}, From c1ebbca2b5df87b87b7c8e10f2c5084017863609 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Fri, 20 Jun 2025 15:48:46 +0200 Subject: [PATCH 06/25] #374: copyright updated (boyscout) --- src/webui/components/application/LegalInfo.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/webui/components/application/LegalInfo.tsx b/src/webui/components/application/LegalInfo.tsx index 428ba91c..4bfb1e1e 100644 --- a/src/webui/components/application/LegalInfo.tsx +++ b/src/webui/components/application/LegalInfo.tsx @@ -8,7 +8,7 @@ export default function Component() By getting in, you agree to our terms of service and privacy policy. - Copyright © 2024 - Masking Technology. + Copyright © 2025 - Masking Technology. ; } From f599911b5f74ae496251735f94d1465dab3925b7 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Fri, 18 Jul 2025 23:44:20 +0200 Subject: [PATCH 07/25] #375: first draft --- segments/bff.json | 4 +- segments/reads.json | 8 +-- segments/writes.json | 2 - services/bff.json | 9 ++- services/gateway.json | 9 --- services/proxy.json | 2 +- services/standalone.json | 4 +- src/assets/localhost.css | 2 + src/domain/authentication/login/login.ts | 27 ++------ src/domain/authentication/requester.ts | 3 +- src/domain/authentication/types.ts | 1 - .../generateNickname/generateNickname.ts | 6 +- .../generateNickname/retrieveByNickname.ts | 7 +- .../retrieveByStartNickname.ts | 10 ++- .../getByIdAggregated/getByIdAggregated.ts | 6 +- .../getByNicknameAggregated.ts | 5 +- src/domain/creator/getMe/getMe.ts | 5 +- .../getMeAggregated/getMeAggregated.ts | 5 +- src/domain/creator/register/register.ts | 2 +- .../notification/aggregate/aggregate.ts | 7 +- .../getByIdAggregated/getByIdAggregated.ts | 5 +- .../getRecentAggregated.ts | 5 +- src/domain/origin/create/InvalidOrigin.ts | 7 -- src/domain/origin/create/create.ts | 26 ------- src/domain/origin/create/createData.ts | 13 ---- src/domain/origin/create/index.ts | 2 - src/domain/origin/create/insertData.ts | 10 --- src/domain/origin/create/types.ts | 4 -- src/domain/origin/create/validateData.ts | 24 ------- src/domain/origin/definitions.ts | 2 - src/domain/origin/find/find.ts | 16 ----- src/domain/origin/find/index.ts | 2 - src/domain/origin/index.ts | 4 -- src/domain/origin/types.ts | 9 --- src/domain/post/aggregate/aggregate.ts | 5 +- .../post/createWithComic/createWithComic.ts | 5 +- .../createWithComment/createWithComment.ts | 5 +- src/domain/post/explore/explore.ts | 5 +- .../exploreAggregated/exploreAggregated.ts | 7 +- .../getByCreatorAggregated.ts | 5 +- .../getByFollowingAggregated.ts | 5 +- .../getByIdAggregated/getByIdAggregated.ts | 5 +- .../getByParentAggregated.ts | 5 +- .../post/getRecommended/getRecommended.ts | 8 +-- .../getRecommendedAggregated.ts | 7 +- src/domain/relation/aggregate/aggregate.ts | 6 +- src/domain/relation/explore/explore.ts | 5 +- .../exploreAggregated/exploreAggregated.ts | 7 +- .../relation/getAggregated/getAggregated.ts | 7 +- .../getFollowersAggregated.ts | 5 +- .../getFollowingAggregated.ts | 5 +- src/domain/tenant/getById/getById.ts | 10 --- src/domain/tenant/getById/index.ts | 2 - .../tenant/getByOrigin/TenantNotFound.ts | 10 +++ src/domain/tenant/getByOrigin/getByOrigin.ts | 24 ++++--- .../getByOriginConverted.ts | 13 ++++ .../tenant/getByOriginConverted/index.ts | 2 + src/domain/tenant/index.ts | 4 +- src/domain/tenant/tenant.ts | 9 +++ src/domain/tenant/types.ts | 9 ++- .../runtime/authenticationMiddleware.ts | 2 +- .../middlewares/AuthenticationMiddleware.ts | 48 +------------ .../runtime/middlewares/OriginMiddleware.ts | 67 ++++++++++++++++--- .../runtime/middlewares/TenantMiddleware.ts | 64 ++++++++++++++++++ src/integrations/runtime/tenantMiddleware.ts | 6 ++ src/integrations/utilities/webbrowser.ts | 14 ---- .../components/common/TenantContainer.tsx | 29 ++++++++ .../components/common/hooks/useTenant.ts | 19 ++++++ src/webui/components/index.ts | 1 + src/webui/contexts/AppContext.tsx | 2 +- src/webui/features/hooks/useAddComicPost.ts | 3 +- .../hooks/useCreatePostComicReaction.ts | 5 +- .../hooks/useCreatePostCommentReaction.ts | 5 +- src/webui/features/hooks/useCreator.ts | 5 +- .../features/hooks/useCreatorFollowers.ts | 3 +- .../features/hooks/useCreatorFollowing.ts | 3 +- src/webui/features/hooks/useCreatorPosts.ts | 3 +- .../features/hooks/useExploreCreators.ts | 3 +- src/webui/features/hooks/useExplorePosts.ts | 3 +- src/webui/features/hooks/useHighlight.ts | 3 +- src/webui/features/hooks/useIdentify.ts | 3 +- src/webui/features/hooks/useNotifications.ts | 3 +- src/webui/features/hooks/usePost.ts | 3 +- src/webui/features/hooks/usePostReactions.ts | 3 +- src/webui/features/hooks/usePostsFollowing.ts | 3 +- .../features/hooks/usePostsRecommended.ts | 3 +- src/webui/main.tsx | 9 ++- test/domain/authentication/login.spec.ts | 4 +- test/domain/origin/create.spec.ts | 29 -------- .../origin/fixtures/databases.fixtures.ts | 15 ----- test/domain/origin/fixtures/index.ts | 4 -- .../origin/fixtures/records.fixtures.ts | 13 ---- .../domain/origin/fixtures/values.fixtures.ts | 20 ------ test/domain/origin/get.spec.ts | 33 --------- .../tenant/fixtures/databases.fixtures.ts | 2 - .../tenant/fixtures/records.fixtures.ts | 10 +-- test/domain/tenant/getByOrigin.spec.ts | 2 +- vite.config.ts | 3 +- 98 files changed, 406 insertions(+), 477 deletions(-) create mode 100644 src/assets/localhost.css delete mode 100644 src/domain/origin/create/InvalidOrigin.ts delete mode 100644 src/domain/origin/create/create.ts delete mode 100644 src/domain/origin/create/createData.ts delete mode 100644 src/domain/origin/create/index.ts delete mode 100644 src/domain/origin/create/insertData.ts delete mode 100644 src/domain/origin/create/types.ts delete mode 100644 src/domain/origin/create/validateData.ts delete mode 100644 src/domain/origin/definitions.ts delete mode 100644 src/domain/origin/find/find.ts delete mode 100644 src/domain/origin/find/index.ts delete mode 100644 src/domain/origin/index.ts delete mode 100644 src/domain/origin/types.ts delete mode 100644 src/domain/tenant/getById/getById.ts delete mode 100644 src/domain/tenant/getById/index.ts create mode 100644 src/domain/tenant/getByOrigin/TenantNotFound.ts create mode 100644 src/domain/tenant/getByOriginConverted/getByOriginConverted.ts create mode 100644 src/domain/tenant/getByOriginConverted/index.ts create mode 100644 src/domain/tenant/tenant.ts create mode 100644 src/integrations/runtime/middlewares/TenantMiddleware.ts create mode 100644 src/integrations/runtime/tenantMiddleware.ts create mode 100644 src/webui/components/common/TenantContainer.tsx create mode 100644 src/webui/components/common/hooks/useTenant.ts delete mode 100644 test/domain/origin/create.spec.ts delete mode 100644 test/domain/origin/fixtures/databases.fixtures.ts delete mode 100644 test/domain/origin/fixtures/index.ts delete mode 100644 test/domain/origin/fixtures/records.fixtures.ts delete mode 100644 test/domain/origin/fixtures/values.fixtures.ts delete mode 100644 test/domain/origin/get.spec.ts diff --git a/segments/bff.json b/segments/bff.json index 29429e63..1abc8b9c 100644 --- a/segments/bff.json +++ b/segments/bff.json @@ -38,5 +38,7 @@ "./domain/relation/establish": { "default": { "access": "public" }, "subscribe": { "access": "private" } }, "./domain/relation/getAggregated": { "default": { "access": "public" } }, "./domain/relation/getFollowersAggregated": { "default": { "access": "public" } }, - "./domain/relation/getFollowingAggregated": { "default": { "access": "public" } } + "./domain/relation/getFollowingAggregated": { "default": { "access": "public" } }, + + "./domain/tenant/getByOriginConverted": { "default": { "access": "public" } } } \ No newline at end of file diff --git a/segments/reads.json b/segments/reads.json index a7538e2c..3e8dce19 100644 --- a/segments/reads.json +++ b/segments/reads.json @@ -10,8 +10,6 @@ "./domain/creator.metrics/getByCreator": { "default": { "access": "protected" } }, - "./domain/origin/find": { "default": { "access": "protected" } }, - "./domain/image/getById": { "default": { "access": "protected" } }, "./domain/post/explore": { "default": { "access": "protected" } }, @@ -23,8 +21,6 @@ "./domain/post.metrics/getByPost": { "default": { "access": "protected" } }, - "./domain/tenant/getById": { "default": { "access": "protected" } }, - "./domain/rating/exists": { "default": { "access": "protected" } }, "./domain/rating/toggle/getData": { "default": { "access": "protected" } }, @@ -32,5 +28,7 @@ "./domain/relation/explore": { "default": { "access": "protected" } }, "./domain/relation/get": { "default": { "access": "protected" } }, "./domain/relation/getFollowers": { "default": { "access": "protected" } }, - "./domain/relation/getFollowing": { "default": { "access": "protected" } } + "./domain/relation/getFollowing": { "default": { "access": "protected" } }, + + "./domain/tenant/getByOrigin": { "default": { "access": "protected" } } } \ No newline at end of file diff --git a/segments/writes.json b/segments/writes.json index 566a2875..c1f09fa1 100644 --- a/segments/writes.json +++ b/segments/writes.json @@ -14,8 +14,6 @@ "./domain/image/save": { "default": { "access": "protected" } }, "./domain/image/erase": { "default": { "access": "protected" } }, - "./domain/origin/create/insertData": { "default": { "access": "protected" } }, - "./domain/post/create/insertData": { "default": { "access": "protected" } }, "./domain/post/erase": { "default": { "access": "protected" } }, "./domain/post/remove/deleteData": { "default": { "access": "protected" }}, diff --git a/services/bff.json b/services/bff.json index 07f49c17..94e10f91 100644 --- a/services/bff.json +++ b/services/bff.json @@ -1,11 +1,18 @@ { "url": "http://127.0.0.1:4000", "setUp": [ - "./integrations/runtime/setUpBff" + "./integrations/runtime/setUpBff", + "./integrations/runtime/setUpGateway" ], "tearDown": [ + "./integrations/runtime/tearDownGateway", "./integrations/runtime/tearDownBff" ], + "middleware": [ + "./integrations/runtime/originMiddleware", + "./integrations/runtime/authenticationMiddleware", + "./integrations/runtime/tenantMiddleware" + ], "worker": { "gateway": "http://127.0.0.1:2000", diff --git a/services/gateway.json b/services/gateway.json index e0950fe4..b8286672 100644 --- a/services/gateway.json +++ b/services/gateway.json @@ -1,14 +1,5 @@ { "url": "http://127.0.0.1:2000", - "setUp": [ - "./integrations/runtime/setUpGateway" - ], - "tearDown": [ - "./integrations/runtime/tearDownGateway" - ], - "middleware": [ - "./integrations/runtime/authenticationMiddleware" - ], "gateway": { "trustKey": "${JITAR_TRUST_KEY}" diff --git a/services/proxy.json b/services/proxy.json index 456a3edb..d85c89eb 100644 --- a/services/proxy.json +++ b/services/proxy.json @@ -3,6 +3,6 @@ "proxy": { "repository": "http://127.0.0.1:1000", - "gateway": "http://127.0.0.1:2000" + "gateway": "http://127.0.0.1:4000" } } \ No newline at end of file diff --git a/services/standalone.json b/services/standalone.json index 0fd741ce..c4405564 100644 --- a/services/standalone.json +++ b/services/standalone.json @@ -15,7 +15,9 @@ "./integrations/runtime/databaseHealthCheck" ], "middleware": [ - "./integrations/runtime/authenticationMiddleware" + "./integrations/runtime/originMiddleware", + "./integrations/runtime/authenticationMiddleware", + "./integrations/runtime/tenantMiddleware" ], "standalone": { diff --git a/src/assets/localhost.css b/src/assets/localhost.css new file mode 100644 index 00000000..2f86c386 --- /dev/null +++ b/src/assets/localhost.css @@ -0,0 +1,2 @@ +.ds +{} \ No newline at end of file diff --git a/src/domain/authentication/login/login.ts b/src/domain/authentication/login/login.ts index 0aca700c..77f3bbb9 100644 --- a/src/domain/authentication/login/login.ts +++ b/src/domain/authentication/login/login.ts @@ -1,44 +1,27 @@ import { type Identity } from '^/integrations/authentication'; -import { NotFound } from '^/integrations/runtime'; import getCreatorByEmail from '^/domain/creator/getByEmail'; import registerCreator from '^/domain/creator/register'; -import getTenantByOrigin from '^/domain/tenant/getByOrigin'; +import { type Tenant } from '^/domain/tenant'; import type { Requester } from '../types'; -const MULTI_TENANT_MODE = process.env.MULTI_TENANT_MODE === 'true'; - -export default async function login(identity: Identity, origin: string): Promise +export default async function login(identity: Identity, tenant: Tenant): Promise { - let tenantId: string | undefined; - - if (MULTI_TENANT_MODE) - { - const tenant = await getTenantByOrigin(origin); - tenantId = tenant?.id; - - if (tenantId === undefined) - { - throw new NotFound(`Tenant not found for origin: ${origin}`); - } - } - - const existingCreator = await getCreatorByEmail(identity.email, tenantId); + const existingCreator = await getCreatorByEmail(identity.email, tenant.id); const loggedInCreator = existingCreator ?? await registerCreator( identity.name, identity.nickname ?? identity.name, identity.email, identity.picture, - tenantId + tenant.id ); return { id: loggedInCreator.id, fullName: loggedInCreator.fullName, - nickname: loggedInCreator.nickname, - tenantId: loggedInCreator.tenantId + nickname: loggedInCreator.nickname }; } diff --git a/src/domain/authentication/requester.ts b/src/domain/authentication/requester.ts index 07a89afb..864a6cc3 100644 --- a/src/domain/authentication/requester.ts +++ b/src/domain/authentication/requester.ts @@ -4,8 +4,7 @@ import type { Requester } from './types'; const requester: Requester = { id: 'id', fullName: 'full name', - nickname: 'nickname', - tenantId: 'tenant' + nickname: 'nickname' }; export default requester; diff --git a/src/domain/authentication/types.ts b/src/domain/authentication/types.ts index 162e85ab..ff577824 100644 --- a/src/domain/authentication/types.ts +++ b/src/domain/authentication/types.ts @@ -3,7 +3,6 @@ type Requester = { readonly id: string; readonly fullName: string; readonly nickname: string; - readonly tenantId?: string; }; export type { Requester }; diff --git a/src/domain/creator/generateNickname/generateNickname.ts b/src/domain/creator/generateNickname/generateNickname.ts index 3a6b315d..e81cf7f3 100644 --- a/src/domain/creator/generateNickname/generateNickname.ts +++ b/src/domain/creator/generateNickname/generateNickname.ts @@ -6,18 +6,18 @@ import retrieveByStartNickname from './retrieveByStartNickname'; const MAX_NICKNAME_NUMBER = 1000; -export default async function generateNickname(nickname: string): Promise +export default async function generateNickname(nickname: string, tenantId: string | undefined = undefined): Promise { const cleanedNickname = cleanNickname(nickname); - const existingData = await retrieveByNickname(cleanedNickname); + const existingData = await retrieveByNickname(cleanedNickname, tenantId); if (existingData === undefined) { return cleanedNickname; } - const foundData = await retrieveByStartNickname(`${existingData.nickname}_`); + const foundData = await retrieveByStartNickname(`${existingData.nickname}_`, tenantId); if (foundData === undefined) { diff --git a/src/domain/creator/generateNickname/retrieveByNickname.ts b/src/domain/creator/generateNickname/retrieveByNickname.ts index 27201487..29c65f9f 100644 --- a/src/domain/creator/generateNickname/retrieveByNickname.ts +++ b/src/domain/creator/generateNickname/retrieveByNickname.ts @@ -4,9 +4,12 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function retrieveByNickname(nickname: string): Promise +export default async function retrieveByNickname(nickname: string, tenantId: string | undefined = undefined): Promise { - const query = { nickname: { EQUALS: nickname } }; + const query = { + nickname: { 'EQUALS': nickname }, + tenantId: { 'EQUALS': tenantId } + }; return database.findRecord(RECORD_TYPE, query) as Promise; } diff --git a/src/domain/creator/generateNickname/retrieveByStartNickname.ts b/src/domain/creator/generateNickname/retrieveByStartNickname.ts index 6e318828..23f78b2a 100644 --- a/src/domain/creator/generateNickname/retrieveByStartNickname.ts +++ b/src/domain/creator/generateNickname/retrieveByStartNickname.ts @@ -1,13 +1,17 @@ -import type { RecordSort} from '^/integrations/database'; +import type { RecordSort } from '^/integrations/database'; import database, { SortDirections } from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function retrieveByStartNickname(nickname: string): Promise +export default async function retrieveByStartNickname(nickname: string, tenantId: string | undefined = undefined): Promise { - const query = { nickname: { STARTS_WITH: nickname } }; + const query = { + nickname: { 'STARTS_WITH': nickname }, + tenantId: { 'EQUALS': tenantId } + }; + const sort: RecordSort = { 'nickname': SortDirections.DESCENDING }; return database.findRecord(RECORD_TYPE, query, undefined, sort) as Promise; diff --git a/src/domain/creator/getByIdAggregated/getByIdAggregated.ts b/src/domain/creator/getByIdAggregated/getByIdAggregated.ts index db00e5c4..961def42 100644 --- a/src/domain/creator/getByIdAggregated/getByIdAggregated.ts +++ b/src/domain/creator/getByIdAggregated/getByIdAggregated.ts @@ -1,13 +1,11 @@ -import type { Requester } from '^/domain/authentication'; - import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getById from '../getById'; -export default async function getByIdAggregated(requester: Requester, id: string): Promise +export default async function getByIdAggregated(tenantId: string, id: string): Promise { - const data = await getById(id, requester.tenantId); + const data = await getById(id, tenantId); return aggregate(data); } diff --git a/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts b/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts index d82a3bf2..05a7fe99 100644 --- a/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts +++ b/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts @@ -1,13 +1,14 @@ import type { Requester } from '^/domain/authentication'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getByNickname from '../getByNickname'; -export default async function getByNicknameAggregated(requester: Requester, nickname: string): Promise +export default async function getByNicknameAggregated(requester: Requester, tenant: Tenant, nickname: string): Promise { - const data = await getByNickname(nickname, requester.tenantId); + const data = await getByNickname(nickname, tenant.id); return aggregate(data); } diff --git a/src/domain/creator/getMe/getMe.ts b/src/domain/creator/getMe/getMe.ts index e7fc081b..4befe20e 100644 --- a/src/domain/creator/getMe/getMe.ts +++ b/src/domain/creator/getMe/getMe.ts @@ -1,10 +1,11 @@ import type { Requester } from '^/domain/authentication'; +import type { Tenant } from '^/domain/tenant'; import getById from '../getById'; import type { DataModel } from '../types'; -export default async function getMe(requester: Requester): Promise +export default async function getMe(requester: Requester, tenant: Tenant): Promise { - return getById(requester.id, requester.tenantId); + return getById(requester.id, tenant.id); } diff --git a/src/domain/creator/getMeAggregated/getMeAggregated.ts b/src/domain/creator/getMeAggregated/getMeAggregated.ts index d9850093..4cecc406 100644 --- a/src/domain/creator/getMeAggregated/getMeAggregated.ts +++ b/src/domain/creator/getMeAggregated/getMeAggregated.ts @@ -1,13 +1,14 @@ import type { Requester } from '^/domain/authentication'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getMe from '../getMe'; -export default async function getMeAggregated(requester: Requester): Promise +export default async function getMeAggregated(requester: Requester, tenant: Tenant): Promise { - const data = await getMe(requester); + const data = await getMe(requester, tenant); return aggregate(data); } diff --git a/src/domain/creator/register/register.ts b/src/domain/creator/register/register.ts index d56c498d..55be2d0a 100644 --- a/src/domain/creator/register/register.ts +++ b/src/domain/creator/register/register.ts @@ -16,7 +16,7 @@ export default async function register(fullName: string, nickname: string, email try { const truncatedFullName = fullName.substring(0, FULL_NAME_MAX_LENGTH); - const generatedNickname = await generateNickname(nickname); + const generatedNickname = await generateNickname(nickname, tenantId); const portraitId = portraitUrl !== undefined ? await downloadPortrait(portraitUrl) diff --git a/src/domain/notification/aggregate/aggregate.ts b/src/domain/notification/aggregate/aggregate.ts index d2a24042..93410b16 100644 --- a/src/domain/notification/aggregate/aggregate.ts +++ b/src/domain/notification/aggregate/aggregate.ts @@ -2,15 +2,16 @@ import type { Requester } from '^/domain/authentication'; import { default as getPostData } from '^/domain/post/getByIdAggregated'; import getRelationData from '^/domain/relation/getAggregated'; +import type { Tenant } from '^/domain/tenant'; import type { DataModel } from '../types'; import type { AggregatedData } from './types'; -export default async function aggregate(requester: Requester, data: DataModel): Promise +export default async function aggregate(requester: Requester, tenant: Tenant, data: DataModel): Promise { const [relationData, postData] = await Promise.all([ - getRelationData(requester, data.receiverId, data.senderId), - data.postId ? getPostData(requester, data.postId) : Promise.resolve(undefined) + getRelationData(requester, tenant, data.receiverId, data.senderId), + data.postId ? getPostData(requester, tenant, data.postId) : Promise.resolve(undefined) ]); return { diff --git a/src/domain/notification/getByIdAggregated/getByIdAggregated.ts b/src/domain/notification/getByIdAggregated/getByIdAggregated.ts index 1cf39382..6b49d7f8 100644 --- a/src/domain/notification/getByIdAggregated/getByIdAggregated.ts +++ b/src/domain/notification/getByIdAggregated/getByIdAggregated.ts @@ -1,13 +1,14 @@ import type { Requester } from '^/domain/authentication'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getById from '../getById'; -export default async function getByIdAggregated(requester: Requester, id: string): Promise +export default async function getByIdAggregated(requester: Requester, tenant: Tenant, id: string): Promise { const data = await getById(id); - return aggregate(requester, data); + return aggregate(requester, tenant, data); } diff --git a/src/domain/notification/getRecentAggregated/getRecentAggregated.ts b/src/domain/notification/getRecentAggregated/getRecentAggregated.ts index 0d9384f3..0c1a6cf9 100644 --- a/src/domain/notification/getRecentAggregated/getRecentAggregated.ts +++ b/src/domain/notification/getRecentAggregated/getRecentAggregated.ts @@ -3,18 +3,19 @@ import type { Requester } from '^/domain/authentication'; import filterResolved from '^/domain/common/filterResolved'; import type { Range } from '^/domain/common/validateRange'; import validateRange from '^/domain/common/validateRange'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getRecent from '../getRecent'; -export default async function getRecentAggregated(requester: Requester, range: Range): Promise +export default async function getRecentAggregated(requester: Requester, tenant: Tenant, range: Range): Promise { validateRange(range); const data = await getRecent(requester.id, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, item)); + const aggregates = data.map(item => aggregate(requester, tenant, item)); return filterResolved(aggregates); } diff --git a/src/domain/origin/create/InvalidOrigin.ts b/src/domain/origin/create/InvalidOrigin.ts deleted file mode 100644 index 87a6a643..00000000 --- a/src/domain/origin/create/InvalidOrigin.ts +++ /dev/null @@ -1,7 +0,0 @@ - -import { ValidationError } from '^/integrations/runtime'; - -export default class InvalidOrigin extends ValidationError -{ - -} diff --git a/src/domain/origin/create/create.ts b/src/domain/origin/create/create.ts deleted file mode 100644 index c41b5356..00000000 --- a/src/domain/origin/create/create.ts +++ /dev/null @@ -1,26 +0,0 @@ - -import find from '../find'; -import createData from './createData'; -import insertData from './insertData'; -import InvalidOrigin from './InvalidOrigin'; -import validateData from './validateData'; - -export default async function create(tenantId: string, origin: string): Promise -{ - const data = createData(tenantId, origin); - - validateData(data); - - const exists = await find(origin); - - if (exists) - { - const messages = new Map([ - ['origin', 'Origin already exists'], - ]); - - throw new InvalidOrigin(messages); - } - - return insertData(data); -} diff --git a/src/domain/origin/create/createData.ts b/src/domain/origin/create/createData.ts deleted file mode 100644 index 46d33261..00000000 --- a/src/domain/origin/create/createData.ts +++ /dev/null @@ -1,13 +0,0 @@ - -import { generateId } from '^/integrations/utilities/crypto'; - -import type { DataModel } from '../types'; - -export default function createData(tenantId: string, origin: string): DataModel -{ - return { - id: generateId(), - tenantId, - origin, - }; -} diff --git a/src/domain/origin/create/index.ts b/src/domain/origin/create/index.ts deleted file mode 100644 index 01487567..00000000 --- a/src/domain/origin/create/index.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export { default } from './create'; diff --git a/src/domain/origin/create/insertData.ts b/src/domain/origin/create/insertData.ts deleted file mode 100644 index 2194461e..00000000 --- a/src/domain/origin/create/insertData.ts +++ /dev/null @@ -1,10 +0,0 @@ - -import database from '^/integrations/database'; - -import { RECORD_TYPE } from '../definitions'; -import type { DataModel } from '../types'; - -export default async function insertData(data: DataModel): Promise -{ - return await database.createRecord(RECORD_TYPE, data); -} diff --git a/src/domain/origin/create/types.ts b/src/domain/origin/create/types.ts deleted file mode 100644 index c0e69579..00000000 --- a/src/domain/origin/create/types.ts +++ /dev/null @@ -1,4 +0,0 @@ - -import type { DataModel } from '../types'; - -export type ValidationModel = DataModel; diff --git a/src/domain/origin/create/validateData.ts b/src/domain/origin/create/validateData.ts deleted file mode 100644 index 3a2a3929..00000000 --- a/src/domain/origin/create/validateData.ts +++ /dev/null @@ -1,24 +0,0 @@ - -import type { ValidationSchema } from '^/integrations/validation'; -import validator from '^/integrations/validation'; - -import { requiredIdValidation } from '^/domain/definitions'; - -import InvalidOrigin from './InvalidOrigin'; -import type { ValidationModel } from './types'; - -const schema: ValidationSchema = -{ - tenantId: requiredIdValidation, - origin: { URL: { required: true } } -}; - -export default function validateData({ tenantId, origin }: ValidationModel): void -{ - const result = validator.validate({ tenantId, origin }, schema); - - if (result.invalid) - { - throw new InvalidOrigin(result.messages); - } -} diff --git a/src/domain/origin/definitions.ts b/src/domain/origin/definitions.ts deleted file mode 100644 index 4485d40a..00000000 --- a/src/domain/origin/definitions.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export const RECORD_TYPE = 'origin'; diff --git a/src/domain/origin/find/find.ts b/src/domain/origin/find/find.ts deleted file mode 100644 index 6fb7bd96..00000000 --- a/src/domain/origin/find/find.ts +++ /dev/null @@ -1,16 +0,0 @@ - -import type { RecordQuery } from '^/integrations/database'; -import database from '^/integrations/database'; - -import { RECORD_TYPE } from '../definitions'; -import type { DataModel } from '../types'; - -export default async function getByOrigin(origin: string): Promise -{ - const query: RecordQuery = - { - origin: { 'EQUALS': origin } - }; - - return await database.findRecord(RECORD_TYPE, query) as DataModel | undefined; -} diff --git a/src/domain/origin/find/index.ts b/src/domain/origin/find/index.ts deleted file mode 100644 index 0d616d7b..00000000 --- a/src/domain/origin/find/index.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export { default } from './find'; diff --git a/src/domain/origin/index.ts b/src/domain/origin/index.ts deleted file mode 100644 index 273c9eea..00000000 --- a/src/domain/origin/index.ts +++ /dev/null @@ -1,4 +0,0 @@ - -export { RECORD_TYPE } from './definitions'; - -export type { DataModel } from './types'; diff --git a/src/domain/origin/types.ts b/src/domain/origin/types.ts deleted file mode 100644 index fd2ba4fd..00000000 --- a/src/domain/origin/types.ts +++ /dev/null @@ -1,9 +0,0 @@ - -import type { BaseDataModel } from '../types'; - -type DataModel = BaseDataModel & { - readonly tenantId: string; - readonly origin: string; -}; - -export type { DataModel }; diff --git a/src/domain/post/aggregate/aggregate.ts b/src/domain/post/aggregate/aggregate.ts index 3d69a836..9eba8062 100644 --- a/src/domain/post/aggregate/aggregate.ts +++ b/src/domain/post/aggregate/aggregate.ts @@ -5,14 +5,15 @@ import getCommentData from '^/domain/comment/getById'; import getMetrics from '^/domain/post.metrics/getByPost'; import ratingExists from '^/domain/rating/exists'; import getRelationData from '^/domain/relation/getAggregated'; +import type { Tenant } from '^/domain/tenant'; import type { DataModel } from '../types'; import type { AggregatedData } from './types'; -export default async function aggregate(requester: Requester, data: DataModel): Promise +export default async function aggregate(requester: Requester, tenant: Tenant, data: DataModel): Promise { const [creatorData, isRated, comicData, commentData, metricsData] = await Promise.all([ - getRelationData(requester, requester.id, data.creatorId), + getRelationData(requester, tenant, requester.id, data.creatorId), ratingExists(requester.id, data.id), data.comicId ? getComicData(data.comicId) : Promise.resolve(undefined), data.commentId ? getCommentData(data.commentId) : Promise.resolve(undefined), diff --git a/src/domain/post/createWithComic/createWithComic.ts b/src/domain/post/createWithComic/createWithComic.ts index 25f0db34..fb25bd57 100644 --- a/src/domain/post/createWithComic/createWithComic.ts +++ b/src/domain/post/createWithComic/createWithComic.ts @@ -1,12 +1,13 @@ import type { Requester } from '^/domain/authentication'; import createComic from '^/domain/comic/create'; +import type { Tenant } from '^/domain/tenant'; import createPost from '../create'; -export default async function createWithComic(requester: Requester, comicImageDataUrl: string, parentId: string | undefined = undefined): Promise +export default async function createWithComic(requester: Requester, tenant: Tenant, comicImageDataUrl: string, parentId: string | undefined = undefined): Promise { const comicId = await createComic(comicImageDataUrl); - return createPost(requester.id, comicId, undefined, parentId, requester.tenantId); + return createPost(requester.id, comicId, undefined, parentId, tenant.id); } diff --git a/src/domain/post/createWithComment/createWithComment.ts b/src/domain/post/createWithComment/createWithComment.ts index 5ccfb392..883f18ab 100644 --- a/src/domain/post/createWithComment/createWithComment.ts +++ b/src/domain/post/createWithComment/createWithComment.ts @@ -1,12 +1,13 @@ import type { Requester } from '^/domain/authentication'; import createComment from '^/domain/comment/create'; +import type { Tenant } from '^/domain/tenant'; import createPost from '../create'; -export default async function createWithComment(requester: Requester, message: string, parentId: string | undefined = undefined): Promise +export default async function createWithComment(requester: Requester, tenant: Tenant, message: string, parentId: string | undefined = undefined): Promise { const commentId = await createComment(message); - return createPost(requester.id, undefined, commentId, parentId, requester.tenantId); + return createPost(requester.id, undefined, commentId, parentId, tenant.id); } diff --git a/src/domain/post/explore/explore.ts b/src/domain/post/explore/explore.ts index 37aba7a5..8442514e 100644 --- a/src/domain/post/explore/explore.ts +++ b/src/domain/post/explore/explore.ts @@ -1,16 +1,17 @@ import type { Requester } from '^/domain/authentication'; import retrieveRelationsByFollower from '^/domain/relation/getFollowing'; +import type { Tenant } from '^/domain/tenant'; import type { DataModel } from '../types'; import retrieveData from './retrieveData'; -export default async function explore(requester: Requester, limit: number, offset: number): Promise +export default async function explore(requester: Requester, tenant: Tenant, limit: number, offset: number): Promise { const relationsData = await retrieveRelationsByFollower(requester, requester.id); const excludedCreatorIds = relationsData.map(data => data.followingId); excludedCreatorIds.push(requester.id); - return retrieveData(excludedCreatorIds, limit, offset, requester.tenantId); + return retrieveData(excludedCreatorIds, limit, offset, tenant.id); } diff --git a/src/domain/post/exploreAggregated/exploreAggregated.ts b/src/domain/post/exploreAggregated/exploreAggregated.ts index 2c22398a..0c56bb8e 100644 --- a/src/domain/post/exploreAggregated/exploreAggregated.ts +++ b/src/domain/post/exploreAggregated/exploreAggregated.ts @@ -3,18 +3,19 @@ import type { Requester } from '^/domain/authentication'; import filterResolved from '^/domain/common/filterResolved'; import type { Range } from '^/domain/common/validateRange'; import validateRange from '^/domain/common/validateRange'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import explore from '../explore'; -export default async function exploreAggregated(requester: Requester, range: Range): Promise +export default async function exploreAggregated(requester: Requester, tenant: Tenant, range: Range): Promise { validateRange(range); - const data = await explore(requester, range.limit, range.offset); + const data = await explore(requester, tenant, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, item)); + const aggregates = data.map(item => aggregate(requester, tenant, item)); return filterResolved(aggregates); } diff --git a/src/domain/post/getByCreatorAggregated/getByCreatorAggregated.ts b/src/domain/post/getByCreatorAggregated/getByCreatorAggregated.ts index 92557fbd..2b12cd21 100644 --- a/src/domain/post/getByCreatorAggregated/getByCreatorAggregated.ts +++ b/src/domain/post/getByCreatorAggregated/getByCreatorAggregated.ts @@ -3,6 +3,7 @@ import type { Requester } from '^/domain/authentication'; import filterResolved from '^/domain/common/filterResolved'; import type { Range } from '^/domain/common/validateRange'; import validateRange from '^/domain/common/validateRange'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; @@ -10,13 +11,13 @@ import getByCreator from '../getByCreator'; export { type AggregatedData }; -export default async function getByCreatorAggregated(requester: Requester, creatorId: string, range: Range): Promise +export default async function getByCreatorAggregated(requester: Requester, tenant: Tenant, creatorId: string, range: Range): Promise { validateRange(range); const data = await getByCreator(creatorId, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, item)); + const aggregates = data.map(item => aggregate(requester, tenant, item)); return filterResolved(aggregates); } diff --git a/src/domain/post/getByFollowingAggregated/getByFollowingAggregated.ts b/src/domain/post/getByFollowingAggregated/getByFollowingAggregated.ts index 9de65dd7..29893e0a 100644 --- a/src/domain/post/getByFollowingAggregated/getByFollowingAggregated.ts +++ b/src/domain/post/getByFollowingAggregated/getByFollowingAggregated.ts @@ -3,18 +3,19 @@ import type { Requester } from '^/domain/authentication'; import filterResolved from '^/domain/common/filterResolved'; import type { Range } from '^/domain/common/validateRange'; import validateRange from '^/domain/common/validateRange'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getByFollowing from '../getByFollowing'; -export default async function getByFollowingAggregated(requester: Requester, range: Range): Promise +export default async function getByFollowingAggregated(requester: Requester, tenant: Tenant, range: Range): Promise { validateRange(range); const data = await getByFollowing(requester, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, item)); + const aggregates = data.map(item => aggregate(requester, tenant, item)); return filterResolved(aggregates); } diff --git a/src/domain/post/getByIdAggregated/getByIdAggregated.ts b/src/domain/post/getByIdAggregated/getByIdAggregated.ts index f3583dcb..1807a1df 100644 --- a/src/domain/post/getByIdAggregated/getByIdAggregated.ts +++ b/src/domain/post/getByIdAggregated/getByIdAggregated.ts @@ -1,5 +1,6 @@ import type { Requester } from '^/domain/authentication'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; @@ -7,9 +8,9 @@ import getById from '../getById'; export { type AggregatedData }; -export default async function getByIdAggregated(requester: Requester, id: string): Promise +export default async function getByIdAggregated(requester: Requester, tenant: Tenant, id: string): Promise { const data = await getById(id); - return aggregate(requester, data); + return aggregate(requester, tenant, data); } diff --git a/src/domain/post/getByParentAggregated/getByParentAggregated.ts b/src/domain/post/getByParentAggregated/getByParentAggregated.ts index 55d3783e..05b75445 100644 --- a/src/domain/post/getByParentAggregated/getByParentAggregated.ts +++ b/src/domain/post/getByParentAggregated/getByParentAggregated.ts @@ -2,16 +2,17 @@ import type { Requester } from '^/domain/authentication'; import filterResolved from '^/domain/common/filterResolved'; import type { Range } from '^/domain/common/validateRange'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getByParent from '../getByParent'; -export default async function getByParentAggregated(requester: Requester, postId: string, range: Range): Promise +export default async function getByParentAggregated(requester: Requester, tenant: Tenant, postId: string, range: Range): Promise { const data = await getByParent(postId, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, item)); + const aggregates = data.map(item => aggregate(requester, tenant, item)); return filterResolved(aggregates); } diff --git a/src/domain/post/getRecommended/getRecommended.ts b/src/domain/post/getRecommended/getRecommended.ts index 17300b37..ec144f65 100644 --- a/src/domain/post/getRecommended/getRecommended.ts +++ b/src/domain/post/getRecommended/getRecommended.ts @@ -2,19 +2,17 @@ import type { RecordQuery, RecordSort } from '^/integrations/database'; import database, { SortDirections } from '^/integrations/database'; -import type { Requester } from '^/domain/authentication'; - import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getRecommended(requester: Requester, limit: number, offset: number): Promise +export default async function getRecommended(requesterId: string, tenantId: string, limit: number, offset: number): Promise { const query: RecordQuery = { deleted: { EQUALS: false }, parentId: { EQUALS: undefined }, - creatorId: { NOT_EQUALS: requester.id }, - tenantId: { EQUALS: requester.tenantId } + creatorId: { NOT_EQUALS: requesterId }, + tenantId: { EQUALS: tenantId } }; const sort: RecordSort = { createdAt: SortDirections.DESCENDING }; diff --git a/src/domain/post/getRecommendedAggregated/getRecommendedAggregated.ts b/src/domain/post/getRecommendedAggregated/getRecommendedAggregated.ts index bd820212..af78feb8 100644 --- a/src/domain/post/getRecommendedAggregated/getRecommendedAggregated.ts +++ b/src/domain/post/getRecommendedAggregated/getRecommendedAggregated.ts @@ -3,18 +3,19 @@ import type { Requester } from '^/domain/authentication'; import filterResolved from '^/domain/common/filterResolved'; import type { Range } from '^/domain/common/validateRange'; import validateRange from '^/domain/common/validateRange'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getRecommended from '../getRecommended'; -export default async function getRecommendedAggregated(requester: Requester, range: Range): Promise +export default async function getRecommendedAggregated(requester: Requester, tenant: Tenant, range: Range): Promise { validateRange(range); - const data = await getRecommended(requester, range.limit, range.offset); + const data = await getRecommended(requester.id, tenant.id, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, item)); + const aggregates = data.map(item => aggregate(requester, tenant, item)); return filterResolved(aggregates); } diff --git a/src/domain/relation/aggregate/aggregate.ts b/src/domain/relation/aggregate/aggregate.ts index ede0d41c..fbfc8624 100644 --- a/src/domain/relation/aggregate/aggregate.ts +++ b/src/domain/relation/aggregate/aggregate.ts @@ -1,13 +1,13 @@ -import type { Requester } from '^/domain/authentication'; import getCreatorData from '^/domain/creator/getByIdAggregated'; +import type { Tenant } from '^/domain/tenant'; import type { DataModel } from '../types'; import type { AggregatedData } from './types'; -export default async function aggregate(requester: Requester, data: DataModel): Promise +export default async function aggregate(tenant: Tenant, data: DataModel): Promise { - const followingData = await getCreatorData(requester, data.followingId); + const followingData = await getCreatorData(tenant.id, data.followingId); return { id: data.id, diff --git a/src/domain/relation/explore/explore.ts b/src/domain/relation/explore/explore.ts index 42db2f34..9777f213 100644 --- a/src/domain/relation/explore/explore.ts +++ b/src/domain/relation/explore/explore.ts @@ -1,18 +1,19 @@ import type { Requester } from '^/domain/authentication'; import getOtherCreators from '^/domain/creator/getOthers'; +import type { Tenant } from '^/domain/tenant'; import type { SortOrder } from '../definitions'; import getFollowing from '../getFollowing'; import type { DataModel } from '../types'; -export default async function explore(requester: Requester, order: SortOrder, limit: number, offset: number, search: string | undefined = undefined): Promise +export default async function explore(requester: Requester, tenant: Tenant, order: SortOrder, limit: number, offset: number, search: string | undefined = undefined): Promise { const followingData = await getFollowing(requester, requester.id); const followingIds = followingData.map(data => data.followingId); followingIds.push(requester.id); - const creatorData = await getOtherCreators(followingIds, order, limit, offset, search, requester.tenantId); + const creatorData = await getOtherCreators(followingIds, order, limit, offset, search, tenant.id); return creatorData.map(data => { diff --git a/src/domain/relation/exploreAggregated/exploreAggregated.ts b/src/domain/relation/exploreAggregated/exploreAggregated.ts index c0ea9320..86d7003b 100644 --- a/src/domain/relation/exploreAggregated/exploreAggregated.ts +++ b/src/domain/relation/exploreAggregated/exploreAggregated.ts @@ -2,17 +2,18 @@ import type { Requester } from '^/domain/authentication'; import type { Range } from '^/domain/common/validateRange'; import validateRange from '^/domain/common/validateRange'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import type { SortOrder } from '../definitions'; import explore from '../explore'; -export default async function exploreAggregated(requester: Requester, order: SortOrder, range: Range, search: string | undefined = undefined): Promise +export default async function exploreAggregated(requester: Requester, tenant: Tenant, order: SortOrder, range: Range, search: string | undefined = undefined): Promise { validateRange(range); - const data = await explore(requester, order, range.limit, range.offset, search); + const data = await explore(requester, tenant, order, range.limit, range.offset, search); - return Promise.all(data.map(item => aggregate(requester, item))); + return Promise.all(data.map(item => aggregate(tenant, item))); } diff --git a/src/domain/relation/getAggregated/getAggregated.ts b/src/domain/relation/getAggregated/getAggregated.ts index 6e2fc6a1..a9f08505 100644 --- a/src/domain/relation/getAggregated/getAggregated.ts +++ b/src/domain/relation/getAggregated/getAggregated.ts @@ -1,12 +1,15 @@ import type { Requester } from '^/domain/authentication'; +import type { Tenant } from '^/domain/tenant'; + import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import get from '../get'; -export default async function getAggregated(requester: Requester, followerId: string, followingId: string): Promise + +export default async function getAggregated(requester: Requester, tenant: Tenant, followerId: string, followingId: string): Promise { const data = await get(followerId, followingId); - return aggregate(requester, data); + return aggregate(tenant, data); } diff --git a/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts b/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts index 7aa374bc..64e85890 100644 --- a/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts +++ b/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts @@ -2,16 +2,17 @@ import type { Requester } from '^/domain/authentication'; import type { Range } from '^/domain/common/validateRange'; import validateRange from '^/domain/common/validateRange'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getFollowers from '../getFollowers'; -export default async function getFollowersAggregated(requester: Requester, followingId: string, range: Range): Promise +export default async function getFollowersAggregated(requester: Requester, tenant: Tenant, followingId: string, range: Range): Promise { validateRange(range); const data = await getFollowers(requester, followingId, range.limit, range.offset); - return Promise.all(data.map(data => aggregate(requester, data))); + return Promise.all(data.map(data => aggregate(tenant, data))); } diff --git a/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts b/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts index ca49d866..781fa99b 100644 --- a/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts +++ b/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts @@ -2,16 +2,17 @@ import type { Requester } from '^/domain/authentication'; import type { Range } from '^/domain/common/validateRange'; import validateRange from '^/domain/common/validateRange'; +import type { Tenant } from '^/domain/tenant'; import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import retrieveByFollower from '../getFollowing'; -export default async function getFollowingAggregated(requester: Requester, followerId: string, range: Range): Promise +export default async function getFollowingAggregated(requester: Requester, tenant: Tenant, followerId: string, range: Range): Promise { validateRange(range); const data = await retrieveByFollower(requester, followerId, range.limit, range.offset); - return Promise.all(data.map(item => aggregate(requester, item))); + return Promise.all(data.map(item => aggregate(tenant, item))); } diff --git a/src/domain/tenant/getById/getById.ts b/src/domain/tenant/getById/getById.ts deleted file mode 100644 index 591bbec1..00000000 --- a/src/domain/tenant/getById/getById.ts +++ /dev/null @@ -1,10 +0,0 @@ - -import database from '^/integrations/database'; - -import { RECORD_TYPE } from '../definitions'; -import type { DataModel } from '../types'; - -export default async function getById(id: string): Promise -{ - return await database.readRecord(RECORD_TYPE, id) as DataModel; -} diff --git a/src/domain/tenant/getById/index.ts b/src/domain/tenant/getById/index.ts deleted file mode 100644 index da399eb0..00000000 --- a/src/domain/tenant/getById/index.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export { default } from './getById'; diff --git a/src/domain/tenant/getByOrigin/TenantNotFound.ts b/src/domain/tenant/getByOrigin/TenantNotFound.ts new file mode 100644 index 00000000..25482e1b --- /dev/null +++ b/src/domain/tenant/getByOrigin/TenantNotFound.ts @@ -0,0 +1,10 @@ + +import { NotFound } from '^/integrations/runtime'; + +export default class TenantNotFound extends NotFound +{ + constructor(origin: string) + { + super(`No tenant found for origin: ${origin}`); + } +} diff --git a/src/domain/tenant/getByOrigin/getByOrigin.ts b/src/domain/tenant/getByOrigin/getByOrigin.ts index b5dd0430..c3160449 100644 --- a/src/domain/tenant/getByOrigin/getByOrigin.ts +++ b/src/domain/tenant/getByOrigin/getByOrigin.ts @@ -1,17 +1,25 @@ -import findOrigin from '^/domain/origin/find'; -import type { DataModel } from '^/domain/tenant'; +import { RECORD_TYPE } from '^/domain/tenant'; -import getById from '../getById'; +import database, { type RecordQuery } from '^/integrations/database'; -export default async function getByOrigin(origin: string): Promise +import type { DataModel } from '../types'; + +import TenantNotFound from './TenantNotFound'; + +export default async function getByOrigin(origin: string): Promise { - const data = await findOrigin(origin); + const query: RecordQuery = + { + origins: { 'EQUALS': origin } + }; + + const record = await database.findRecord(RECORD_TYPE, query); - if (data === undefined) + if (record === undefined) { - return; + throw new TenantNotFound(origin); } - return await getById(data.tenantId); + return record as DataModel; } diff --git a/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts b/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts new file mode 100644 index 00000000..8656c069 --- /dev/null +++ b/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts @@ -0,0 +1,13 @@ + +import getByOrigin from '../getByOrigin'; +import type { Tenant } from '../types'; + +export default async function getFormatted(origin: string): Promise +{ + const tenant = await getByOrigin(origin); + + return { + id: tenant.id, + origin: origin + }; +} \ No newline at end of file diff --git a/src/domain/tenant/getByOriginConverted/index.ts b/src/domain/tenant/getByOriginConverted/index.ts new file mode 100644 index 00000000..db225e15 --- /dev/null +++ b/src/domain/tenant/getByOriginConverted/index.ts @@ -0,0 +1,2 @@ + +export { default } from './getByOriginConverted'; diff --git a/src/domain/tenant/index.ts b/src/domain/tenant/index.ts index 273c9eea..7504bb46 100644 --- a/src/domain/tenant/index.ts +++ b/src/domain/tenant/index.ts @@ -1,4 +1,6 @@ export { RECORD_TYPE } from './definitions'; -export type { DataModel } from './types'; +export type { DataModel, Tenant } from './types'; + +export { default as tenant } from './tenant'; diff --git a/src/domain/tenant/tenant.ts b/src/domain/tenant/tenant.ts new file mode 100644 index 00000000..cd6b70b4 --- /dev/null +++ b/src/domain/tenant/tenant.ts @@ -0,0 +1,9 @@ + +import type { Tenant } from './types'; + +const tenant: Tenant = { + id: 'id', + origin: 'origin' +}; + +export default tenant; diff --git a/src/domain/tenant/types.ts b/src/domain/tenant/types.ts index 47202fa8..7f74c576 100644 --- a/src/domain/tenant/types.ts +++ b/src/domain/tenant/types.ts @@ -2,7 +2,12 @@ import type { BaseDataModel } from '../types'; type DataModel = BaseDataModel & { - readonly name: string; + readonly origins: string[]; }; -export type { DataModel }; +type Tenant = { + readonly id: string; + readonly origin: string; +}; + +export type { DataModel, Tenant }; diff --git a/src/integrations/runtime/authenticationMiddleware.ts b/src/integrations/runtime/authenticationMiddleware.ts index e9bf8ea5..80c361c6 100644 --- a/src/integrations/runtime/authenticationMiddleware.ts +++ b/src/integrations/runtime/authenticationMiddleware.ts @@ -11,6 +11,6 @@ const authProcedures = { const redirectPath = process.env.AUTHENTICATION_CLIENT_PATH || 'undefined'; -const whiteList: string[] = []; +const whiteList: string[] = ['domain/tenant/getByOriginConverted']; export default new AuthenticationMiddleware(identityProvider, authProcedures, redirectPath, whiteList); diff --git a/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts b/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts index af63a35e..487e4b60 100644 --- a/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts +++ b/src/integrations/runtime/middlewares/AuthenticationMiddleware.ts @@ -1,12 +1,10 @@ import type { Middleware, NextHandler, Request } from 'jitar'; -import { Response } from 'jitar'; +import { Response, Unauthorized } from 'jitar'; import type { IdentityProvider, Session } from '^/integrations/authentication'; import { generateKey } from '^/integrations/utilities/crypto'; -import Unauthorized from '../errors/Unauthorized'; - type AuthProcedures = { loginUrl: string; login: string; @@ -14,10 +12,8 @@ type AuthProcedures = { }; const IDENTITY_PARAMETER = 'identity'; -const ORIGIN_PARAMETER = 'origin'; const REQUESTER_PARAMETER = '*requester'; const JITAR_TRUST_HEADER_KEY = 'X-Jitar-Trust-Key'; -const COMIFY_HOST_COOKIE_KEY = 'x-comify-origin'; const sessions = new Map(); @@ -69,7 +65,6 @@ export default class AuthenticationMiddleware implements Middleware request.args.clear(); request.setArgument(IDENTITY_PARAMETER, session.identity); - request.setArgument(ORIGIN_PARAMETER, origin); const response = await next(); @@ -230,47 +225,8 @@ export default class AuthenticationMiddleware implements Middleware response.setHeader('Location', new URL(`${this.#redirectPath}?key=${key}`, origin).href); } - // The origin, retrieved from a user's device cookie, is an untrusted source. It's crucial to validate this origin before using it in redirects. - // Validation occurs in two steps: - - // IDP Check: Verifies the origin against a predefined list of allowed origins. - // Domain Check: Validates the origin against a list of registered origins. #getOrigin(request: Request): string { - const cookie = request.getHeader('cookie'); - - if (cookie === undefined) - { - throw new Unauthorized('Invalid origin'); - } - - const cookies = this.#getCookies(cookie); - const origin = cookies.get(COMIFY_HOST_COOKIE_KEY); - - if (origin === undefined) - { - throw new Unauthorized('Invalid origin'); - } - - return origin; - } - - #getCookies(input: string): Map - { - const cookies = input.split(';'); - const cookieMap = new Map(); - - for (const cookie of cookies) - { - const parts = cookie.trim().split('='); - - const key = parts[0]?.toLocaleLowerCase(); - const rawValue = parts.length > 2 ? parts.slice(1).join('=') : parts[1]; - const value = rawValue ? decodeURIComponent(rawValue) : ''; - - cookieMap.set(key, value); - } - - return cookieMap; + return request.getHeader('origin') as string; } } diff --git a/src/integrations/runtime/middlewares/OriginMiddleware.ts b/src/integrations/runtime/middlewares/OriginMiddleware.ts index 3e8cd152..6cb98092 100644 --- a/src/integrations/runtime/middlewares/OriginMiddleware.ts +++ b/src/integrations/runtime/middlewares/OriginMiddleware.ts @@ -1,21 +1,68 @@ -import type { Middleware, NextHandler, Request, Response } from 'jitar'; +import type { Middleware, NextHandler, Request } from 'jitar'; +import { BadRequest, Response } from 'jitar'; -import { setCookie } from '^/integrations/utilities/webbrowser'; - -const ORIGIN_HEADER = 'X-Comify-Origin'; +const TENANT_COOKIE_NAME = 'x-tenant-origin'; export default class OriginMiddleware implements Middleware { - constructor() + async handle(request: Request, next: NextHandler): Promise { - const origin = document.location.origin; + let fromCookie = true; + + let origin = this.#getOriginFromCookie(request); + + if (origin === undefined) + { + fromCookie = false; + + origin = this.#getOriginFromHeader(request); + } + + if (origin === undefined) + { + throw new BadRequest('Missing origin'); + } + + request.setHeader('origin', origin); + + const response = await next(); + + if (fromCookie === false) + { + this.#setOriginCookie(response, origin); + } + + return response; + } + + #getOriginFromHeader(request: Request): string | undefined + { + return request.getHeader('origin'); + } + + #getOriginFromCookie(request: Request): string | undefined + { + const header = request.getHeader('cookie'); + + if (header === undefined) + { + return; + } + + for (const cookie of header.split('; ')) + { + const [key, value] = cookie.split('='); - setCookie(ORIGIN_HEADER, origin); + if (key === TENANT_COOKIE_NAME) + { + return value; + } + } } - handle(request: Request, next: NextHandler): Promise + #setOriginCookie(response: Response, origin: string): void { - return next(); + response.setHeader('Set-Cookie', `${TENANT_COOKIE_NAME}=${origin}; Path=/; HttpOnly=true; SameSite=Strict; Secure`); } -} +} \ No newline at end of file diff --git a/src/integrations/runtime/middlewares/TenantMiddleware.ts b/src/integrations/runtime/middlewares/TenantMiddleware.ts new file mode 100644 index 00000000..6020c7ea --- /dev/null +++ b/src/integrations/runtime/middlewares/TenantMiddleware.ts @@ -0,0 +1,64 @@ + +import type { Middleware, NextHandler, Request, Response } from 'jitar'; + +const TENANT_PARAMETER = '*tenant'; + +export default class TenantMiddleware implements Middleware +{ + readonly #cache = new Map(); + readonly #getTenantPath: string; + + constructor(tenantPath: string) + { + this.#getTenantPath = tenantPath; + } + + async handle(request: Request, next: NextHandler): Promise + { + return request.fqn === this.#getTenantPath + ? this.#getTenant(request, next) + : this.#handleRequest(request, next); + } + + async #getTenant(request: Request, next: NextHandler): Promise + { + const origin = this.#getOrigin(request); + const cached = this.#cache.get(origin); + + if (cached === undefined) + { + request.setArgument('origin', origin); + + const response = await next(); + + if (response.status >= 500) + { + return response; + } + + this.#cache.set(origin, response); + + return response; + } + + return cached; + } + + async #handleRequest(request: Request, next: NextHandler): Promise + { + const origin = this.#getOrigin(request); + const cached = this.#cache.get(origin); + + if (cached !== undefined) + { + request.setArgument(TENANT_PARAMETER, cached.result); + } + + return next(); + } + + #getOrigin(request: Request): string + { + return request.getHeader('origin') as string; + } +} diff --git a/src/integrations/runtime/tenantMiddleware.ts b/src/integrations/runtime/tenantMiddleware.ts new file mode 100644 index 00000000..e05d64f8 --- /dev/null +++ b/src/integrations/runtime/tenantMiddleware.ts @@ -0,0 +1,6 @@ + +import TenantMiddleware from './middlewares/TenantMiddleware'; + +const tenantPath = 'domain/tenant/getByOriginConverted'; + +export default new TenantMiddleware(tenantPath); diff --git a/src/integrations/utilities/webbrowser.ts b/src/integrations/utilities/webbrowser.ts index a24a956d..b8630573 100644 --- a/src/integrations/utilities/webbrowser.ts +++ b/src/integrations/utilities/webbrowser.ts @@ -5,17 +5,3 @@ export function getQueryParameter(name: string): string | undefined return queryParameters.get(name) ?? undefined; } - -export function setCookie(key: string, value: string): void -{ - const documentCookie = document.cookie; - - if (documentCookie.split('; ').some(cookie => cookie.startsWith(`${key}=`))) - { - return; - } - - const cookie = `${key}=${value}; path=/; SameSite=Strict;`; - - document.cookie = cookie; -} diff --git a/src/webui/components/common/TenantContainer.tsx b/src/webui/components/common/TenantContainer.tsx new file mode 100644 index 00000000..23099c66 --- /dev/null +++ b/src/webui/components/common/TenantContainer.tsx @@ -0,0 +1,29 @@ + +import { useEffect, type ReactNode } from 'react'; + +import { useTenant } from './hooks/useTenant'; + +type Props = { + children: ReactNode; +}; + +export default function Component({ children }: Props) +{ + const [tenant] = useTenant(); + + useEffect(() => + { + if (tenant === undefined) return; + + const link = document.createElement('link'); + link.setAttribute('rel', 'stylesheet'); + link.setAttribute('href', `/assets/${tenant.id}.css`); + + document.head.appendChild(link); + + }), [tenant]; + + if (tenant === undefined) return; + + return children; +} diff --git a/src/webui/components/common/hooks/useTenant.ts b/src/webui/components/common/hooks/useTenant.ts new file mode 100644 index 00000000..0541f2e4 --- /dev/null +++ b/src/webui/components/common/hooks/useTenant.ts @@ -0,0 +1,19 @@ + +import { useCallback } from 'react'; + +import getByOriginConverted from '^/domain/tenant/getByOriginConverted'; + +import { useLoadData } from '^/webui/hooks'; + +export function useTenant() +{ + const getTenant = useCallback(async () => + { + const tenant = await getByOriginConverted(''); + + return tenant; + + }, []); + + return useLoadData(getTenant, []); +} \ No newline at end of file diff --git a/src/webui/components/index.ts b/src/webui/components/index.ts index 1cf71646..461238e8 100644 --- a/src/webui/components/index.ts +++ b/src/webui/components/index.ts @@ -19,6 +19,7 @@ export { default as OrderRow } from './common/OrderRow'; export { default as PullToRefresh } from './common/PullToRefresh'; export { default as ResultSet } from './common/ResultSet'; export { default as ScrollLoader } from './common/ScrollLoader'; +export { default as TenantContainer } from './common/TenantContainer'; export { default as CreatorFullNameForm } from './creator/FullNameForm'; export { default as CreatorNicknameForm } from './creator/NicknameForm'; export { default as NotificationPanelList } from './notification/PanelList'; diff --git a/src/webui/contexts/AppContext.tsx b/src/webui/contexts/AppContext.tsx index 9b6ff325..a12e2843 100644 --- a/src/webui/contexts/AppContext.tsx +++ b/src/webui/contexts/AppContext.tsx @@ -1,5 +1,5 @@ -import type { ReactNode} from 'react'; +import type { ReactNode } from 'react'; import { createContext, useContext } from 'react'; import type { AggregatedData as AggregatedCreatorData } from '^/domain/creator/aggregate'; diff --git a/src/webui/features/hooks/useAddComicPost.ts b/src/webui/features/hooks/useAddComicPost.ts index e188954a..d802d017 100644 --- a/src/webui/features/hooks/useAddComicPost.ts +++ b/src/webui/features/hooks/useAddComicPost.ts @@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom'; import { requester } from '^/domain/authentication'; import createPostWithComic from '^/domain/post/createWithComic'; +import { tenant } from '^/domain/tenant'; import { useAppContext } from '^/webui/contexts'; @@ -14,7 +15,7 @@ export default function useAddComicPost() return useCallback(async (imageData: string) => { - await createPostWithComic(requester, imageData); + await createPostWithComic(requester, tenant, imageData); navigate(`/profile/${identity?.nickname}`); diff --git a/src/webui/features/hooks/useCreatePostComicReaction.ts b/src/webui/features/hooks/useCreatePostComicReaction.ts index dd51ff41..38aa4d4e 100644 --- a/src/webui/features/hooks/useCreatePostComicReaction.ts +++ b/src/webui/features/hooks/useCreatePostComicReaction.ts @@ -5,13 +5,14 @@ import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedPostData } from '^/domain/post/aggregate'; import createComicReaction from '^/domain/post/createWithComic'; import getReaction from '^/domain/post/getByIdAggregated'; +import { tenant } from '^/domain/tenant'; export default function useCreatePostComicReaction(post: AggregatedPostData, handleDone: (reaction?: AggregatedPostData) => void) { return useCallback(async (imageData: string) => { - const reactionId = await createComicReaction(requester, imageData, post.id); - const reaction = await getReaction(requester, reactionId); + const reactionId = await createComicReaction(requester, tenant, imageData, post.id); + const reaction = await getReaction(requester, tenant, reactionId); handleDone(reaction); diff --git a/src/webui/features/hooks/useCreatePostCommentReaction.ts b/src/webui/features/hooks/useCreatePostCommentReaction.ts index c0c0d784..0a52deca 100644 --- a/src/webui/features/hooks/useCreatePostCommentReaction.ts +++ b/src/webui/features/hooks/useCreatePostCommentReaction.ts @@ -5,13 +5,14 @@ import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedPostData } from '^/domain/post/aggregate'; import createCommentReaction from '^/domain/post/createWithComment'; import getReaction from '^/domain/post/getByIdAggregated'; +import { tenant } from '^/domain/tenant'; export default function useCreateCommentReaction(post: AggregatedPostData, handleDone: (reaction?: AggregatedPostData) => void) { return useCallback(async (comment: string) => { - const reactionId = await createCommentReaction(requester, comment, post.id); - const reaction = await getReaction(requester, reactionId); + const reactionId = await createCommentReaction(requester, tenant, comment, post.id); + const reaction = await getReaction(requester, tenant, reactionId); handleDone(reaction); diff --git a/src/webui/features/hooks/useCreator.ts b/src/webui/features/hooks/useCreator.ts index fd3a4cb7..02c7ca38 100644 --- a/src/webui/features/hooks/useCreator.ts +++ b/src/webui/features/hooks/useCreator.ts @@ -5,6 +5,7 @@ import { useParams } from 'react-router-dom'; import { requester } from '^/domain/authentication'; import getCreator from '^/domain/creator/getByNicknameAggregated'; import getRelation from '^/domain/relation/getAggregated'; +import { tenant } from '^/domain/tenant'; import { useAppContext } from '^/webui/contexts'; import { useLoadData } from '^/webui/hooks'; @@ -21,9 +22,9 @@ export default function useCreator() return undefined; } - const creator = await getCreator(requester, nickname); + const creator = await getCreator(requester, tenant, nickname); - return getRelation(requester, identity.id, creator.id); + return getRelation(requester, tenant, identity.id, creator.id); }, [identity, nickname]); diff --git a/src/webui/features/hooks/useCreatorFollowers.ts b/src/webui/features/hooks/useCreatorFollowers.ts index 563c2935..25a62627 100644 --- a/src/webui/features/hooks/useCreatorFollowers.ts +++ b/src/webui/features/hooks/useCreatorFollowers.ts @@ -4,6 +4,7 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedCreatorData } from '^/domain/creator/aggregate'; import getFollowers from '^/domain/relation/getFollowersAggregated'; +import { tenant } from '^/domain/tenant'; import { usePagination } from '^/webui/hooks'; @@ -13,7 +14,7 @@ export default function useCreatorFollowers(creator: AggregatedCreatorData) const getData = useCallback((page: number) => { - return getFollowers(requester, creator.id, { limit, offset: page * limit }); + return getFollowers(requester, tenant, creator.id, { limit, offset: page * limit }); }, [creator]); diff --git a/src/webui/features/hooks/useCreatorFollowing.ts b/src/webui/features/hooks/useCreatorFollowing.ts index 0aaf924f..6e6861b0 100644 --- a/src/webui/features/hooks/useCreatorFollowing.ts +++ b/src/webui/features/hooks/useCreatorFollowing.ts @@ -4,6 +4,7 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedCreatorData } from '^/domain/creator/aggregate'; import getFollowing from '^/domain/relation/getFollowingAggregated'; +import { tenant } from '^/domain/tenant'; import { usePagination } from '^/webui/hooks'; @@ -13,7 +14,7 @@ export default function useCreatorFollowing(creator: AggregatedCreatorData) const getData = useCallback((page: number) => { - return getFollowing(requester, creator.id, { limit, offset: page * limit }); + return getFollowing(requester, tenant, creator.id, { limit, offset: page * limit }); }, [creator]); diff --git a/src/webui/features/hooks/useCreatorPosts.ts b/src/webui/features/hooks/useCreatorPosts.ts index 4fe60905..1e77730a 100644 --- a/src/webui/features/hooks/useCreatorPosts.ts +++ b/src/webui/features/hooks/useCreatorPosts.ts @@ -4,6 +4,7 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedCreatorData } from '^/domain/creator/aggregate'; import getCreatorPosts from '^/domain/post/getByCreatorAggregated'; +import { tenant } from '^/domain/tenant'; import { usePagination } from '^/webui/hooks'; @@ -13,7 +14,7 @@ export default function useCreatorPosts(creator: AggregatedCreatorData) const getData = useCallback((page: number) => { - return getCreatorPosts(requester, creator.id, { limit, offset: page * limit }); + return getCreatorPosts(requester, tenant, creator.id, { limit, offset: page * limit }); }, [creator]); diff --git a/src/webui/features/hooks/useExploreCreators.ts b/src/webui/features/hooks/useExploreCreators.ts index c3ba3b70..1811243f 100644 --- a/src/webui/features/hooks/useExploreCreators.ts +++ b/src/webui/features/hooks/useExploreCreators.ts @@ -3,6 +3,7 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import exploreRelations from '^/domain/relation/exploreAggregated'; +import { tenant } from '^/domain/tenant'; import { usePagination } from '^/webui/hooks'; @@ -12,7 +13,7 @@ export default function useExploreCreators() const getData = useCallback((page: number) => { - return exploreRelations(requester, 'popular', { limit, offset: page * limit }); + return exploreRelations(requester, tenant, 'popular', { limit, offset: page * limit }); }, []); diff --git a/src/webui/features/hooks/useExplorePosts.ts b/src/webui/features/hooks/useExplorePosts.ts index 3a25f126..1ce27f39 100644 --- a/src/webui/features/hooks/useExplorePosts.ts +++ b/src/webui/features/hooks/useExplorePosts.ts @@ -3,6 +3,7 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import explorePosts from '^/domain/post/exploreAggregated'; +import { tenant } from '^/domain/tenant'; import { usePagination } from '^/webui/hooks'; @@ -12,7 +13,7 @@ export default function useExplorePosts() const getData = useCallback((page: number) => { - return explorePosts(requester, { limit, offset: page * limit }); + return explorePosts(requester, tenant, { limit, offset: page * limit }); }, []); diff --git a/src/webui/features/hooks/useHighlight.ts b/src/webui/features/hooks/useHighlight.ts index ee6a9722..eb67e01d 100644 --- a/src/webui/features/hooks/useHighlight.ts +++ b/src/webui/features/hooks/useHighlight.ts @@ -4,6 +4,7 @@ import { useParams } from 'react-router-dom'; import requester from '^/domain/authentication/requester'; import get from '^/domain/post/getByIdAggregated'; +import { tenant } from '^/domain/tenant'; import { useLoadData } from '^/webui/hooks'; @@ -14,7 +15,7 @@ export default function useReaction() const getReaction = useCallback(async () => { return highlightId !== undefined - ? get(requester, highlightId) + ? get(requester, tenant, highlightId) : undefined; }, [highlightId]); diff --git a/src/webui/features/hooks/useIdentify.ts b/src/webui/features/hooks/useIdentify.ts index 42e2956b..77ced38b 100644 --- a/src/webui/features/hooks/useIdentify.ts +++ b/src/webui/features/hooks/useIdentify.ts @@ -5,6 +5,7 @@ import { useNavigate } from 'react-router-dom'; import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedCreatorData } from '^/domain/creator/aggregate'; import getMe from '^/domain/creator/getMeAggregated'; +import { tenant } from '^/domain/tenant'; import { useAppContext } from '^/webui/contexts'; @@ -26,7 +27,7 @@ export default function useIdentify() const getIdentity = async () => { - const identity = await getMe(requester); + const identity = await getMe(requester, tenant); setIdentity(identity); }; diff --git a/src/webui/features/hooks/useNotifications.ts b/src/webui/features/hooks/useNotifications.ts index abc31b9b..4e77e81f 100644 --- a/src/webui/features/hooks/useNotifications.ts +++ b/src/webui/features/hooks/useNotifications.ts @@ -3,6 +3,7 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import getRecentNotifications from '^/domain/notification/getRecentAggregated'; +import { tenant } from '^/domain/tenant'; import { usePagination } from '^/webui/hooks'; @@ -12,7 +13,7 @@ export default function useNotifications() const getNotifications = useCallback((page: number) => { - return getRecentNotifications(requester, { limit, offset: page * limit }); + return getRecentNotifications(requester, tenant, { limit, offset: page * limit }); }, []); diff --git a/src/webui/features/hooks/usePost.ts b/src/webui/features/hooks/usePost.ts index a9f2cd7b..461e98fb 100644 --- a/src/webui/features/hooks/usePost.ts +++ b/src/webui/features/hooks/usePost.ts @@ -4,6 +4,7 @@ import { useParams } from 'react-router-dom'; import { requester } from '^/domain/authentication'; import get from '^/domain/post/getByIdAggregated'; +import { tenant } from '^/domain/tenant'; import { useLoadData } from '^/webui/hooks'; @@ -14,7 +15,7 @@ export default function usePost() const getPost = useCallback(async () => { return postId !== undefined - ? get(requester, postId) + ? get(requester, tenant, postId) : undefined; }, [postId]); diff --git a/src/webui/features/hooks/usePostReactions.ts b/src/webui/features/hooks/usePostReactions.ts index 4aa5fc18..b8fe7089 100644 --- a/src/webui/features/hooks/usePostReactions.ts +++ b/src/webui/features/hooks/usePostReactions.ts @@ -4,6 +4,7 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedPostData } from '^/domain/post/aggregate'; import getReactionsByPost from '^/domain/post/getByParentAggregated'; +import { tenant } from '^/domain/tenant'; import { usePagination } from '^/webui/hooks'; @@ -13,7 +14,7 @@ export default function usePostReactions(post: AggregatedPostData) const getData = useCallback((page: number) => { - return getReactionsByPost(requester, post.id, { limit, offset: page * limit }); + return getReactionsByPost(requester, tenant, post.id, { limit, offset: page * limit }); }, [post]); diff --git a/src/webui/features/hooks/usePostsFollowing.ts b/src/webui/features/hooks/usePostsFollowing.ts index 0d361b25..7da00081 100644 --- a/src/webui/features/hooks/usePostsFollowing.ts +++ b/src/webui/features/hooks/usePostsFollowing.ts @@ -3,6 +3,7 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import getPostsFollowing from '^/domain/post/getByFollowingAggregated'; +import { tenant } from '^/domain/tenant'; import { usePagination } from '^/webui/hooks'; @@ -12,7 +13,7 @@ export default function usePostsFollowing() const getData = useCallback((page: number) => { - return getPostsFollowing(requester, { limit, offset: page * limit }); + return getPostsFollowing(requester, tenant, { limit, offset: page * limit }); }, []); diff --git a/src/webui/features/hooks/usePostsRecommended.ts b/src/webui/features/hooks/usePostsRecommended.ts index a5119bdc..af8fa34d 100644 --- a/src/webui/features/hooks/usePostsRecommended.ts +++ b/src/webui/features/hooks/usePostsRecommended.ts @@ -3,6 +3,7 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import getPostsRecommended from '^/domain/post/getRecommendedAggregated'; +import { tenant } from '^/domain/tenant'; import { usePagination } from '^/webui/hooks'; @@ -12,7 +13,7 @@ export default function usePostsRecommended() const getData = useCallback((page: number) => { - return getPostsRecommended(requester, { limit, offset: page * limit }); + return getPostsRecommended(requester, tenant, { limit, offset: page * limit }); }, []); diff --git a/src/webui/main.tsx b/src/webui/main.tsx index 70d85189..cd143246 100644 --- a/src/webui/main.tsx +++ b/src/webui/main.tsx @@ -3,6 +3,7 @@ import { StrictMode } from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; +import { TenantContainer } from './components'; import { AppContextProvider } from './contexts'; import './designsystem/designsystem.css'; @@ -10,8 +11,10 @@ import './main.css'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( - - - + + + + + ); diff --git a/test/domain/authentication/login.spec.ts b/test/domain/authentication/login.spec.ts index 394bf6f6..35144200 100644 --- a/test/domain/authentication/login.spec.ts +++ b/test/domain/authentication/login.spec.ts @@ -64,8 +64,8 @@ describe('domain/authentication', () => it('should register with a valid profile picture', async () => { - const requestor = await login(IDENTITIES.WITH_PICTURE); - expect(requestor.nickname).toBe(VALUES.NICKNAMES.WITH_PICTURE); + const requester = await login(IDENTITIES.WITH_PICTURE); + expect(requester.nickname).toBe(VALUES.NICKNAMES.WITH_PICTURE); }); }); }); diff --git a/test/domain/origin/create.spec.ts b/test/domain/origin/create.spec.ts deleted file mode 100644 index 6788a3a2..00000000 --- a/test/domain/origin/create.spec.ts +++ /dev/null @@ -1,29 +0,0 @@ - -import { beforeEach, describe, expect, it } from 'vitest'; - -import create from '^/domain/origin/create'; -import InvalidOrigin from '^/domain/origin/create/InvalidOrigin'; - -import { DATABASES, VALUES } from './fixtures'; - -beforeEach(async () => -{ - await DATABASES.origins(); -}); - -describe('domain/origin/create', () => -{ - it('should add an new origin', async () => - { - const id = await create(VALUES.IDS.NEW, VALUES.ORIGINS.NEW); - - expect(id).toBeDefined(); - }); - - it('should NOT add an existing origin', async () => - { - const promise = create(VALUES.IDS.EXISTING, VALUES.ORIGINS.EXISTING); - - await expect(promise).rejects.toThrow(InvalidOrigin); - }); -}); diff --git a/test/domain/origin/fixtures/databases.fixtures.ts b/test/domain/origin/fixtures/databases.fixtures.ts deleted file mode 100644 index 4381565d..00000000 --- a/test/domain/origin/fixtures/databases.fixtures.ts +++ /dev/null @@ -1,15 +0,0 @@ - -import database from '^/integrations/database'; - -import { RECORD_TYPE as ORIGIN_RECORD_TYPE } from '^/domain/origin'; - -import { RECORDS } from './records.fixtures'; - -database.connect(); - -async function origins(): Promise -{ - RECORDS.ORIGINS.map(origins => database.createRecord(ORIGIN_RECORD_TYPE, { ...origins })); -} - -export const DATABASES = { origins }; diff --git a/test/domain/origin/fixtures/index.ts b/test/domain/origin/fixtures/index.ts deleted file mode 100644 index 945a801c..00000000 --- a/test/domain/origin/fixtures/index.ts +++ /dev/null @@ -1,4 +0,0 @@ - -export * from './databases.fixtures'; -export * from './values.fixtures'; - diff --git a/test/domain/origin/fixtures/records.fixtures.ts b/test/domain/origin/fixtures/records.fixtures.ts deleted file mode 100644 index d93d3976..00000000 --- a/test/domain/origin/fixtures/records.fixtures.ts +++ /dev/null @@ -1,13 +0,0 @@ - -import type { RecordData } from '^/integrations/database'; - -import type { DataModel as OriginDataModel } from '^/domain/origin'; - -import { VALUES } from './values.fixtures'; - -export const ORIGINS: OriginDataModel[] = [ - { id: VALUES.IDS.ORIGIN1, origin: VALUES.ORIGINS.ORIGIN1, tenantId: VALUES.IDS.TENANT1 }, - { id: VALUES.IDS.ORIGIN2, origin: VALUES.ORIGINS.ORIGIN2, tenantId: VALUES.IDS.TENANT1 }, -]; - -export const RECORDS: Record = { ORIGINS }; diff --git a/test/domain/origin/fixtures/values.fixtures.ts b/test/domain/origin/fixtures/values.fixtures.ts deleted file mode 100644 index 3fed4d54..00000000 --- a/test/domain/origin/fixtures/values.fixtures.ts +++ /dev/null @@ -1,20 +0,0 @@ - -export const VALUES = -{ - IDS: { - ORIGIN1: 'origin1', - ORIGIN2: 'origin2', - EXISTING: 'origin1', - NEW: 'origin3', - - TENANT1: 'tenant1' - }, - - ORIGINS: { - ORIGIN1: 'https://origin1.com', - ORIGIN2: 'https://origin2.com', - EXISTING: 'https://origin1.com', - NEW: 'https://origin3.com', - UNKNOWN: 'https://unknown.com' - } -}; diff --git a/test/domain/origin/get.spec.ts b/test/domain/origin/get.spec.ts deleted file mode 100644 index 869bea88..00000000 --- a/test/domain/origin/get.spec.ts +++ /dev/null @@ -1,33 +0,0 @@ - -import { beforeEach, describe, expect, it } from 'vitest'; - -import find from '^/domain/origin/find'; - -import { DATABASES, VALUES } from './fixtures'; - -beforeEach(async () => -{ - await DATABASES.origins(); -}); - -describe('domain/origin/get', () => -{ - it('should get multiple origin for single tenant', async () => - { - const origin1 = await find(VALUES.ORIGINS.ORIGIN1); - const origin2 = await find(VALUES.ORIGINS.ORIGIN2); - - expect(origin1).toBeDefined(); - expect(origin1?.tenantId).toBe(VALUES.IDS.TENANT1); - - expect(origin2).toBeDefined(); - expect(origin2?.tenantId).toBe(VALUES.IDS.TENANT1); - }); - - it('should ', async () => - { - const origin = await find(VALUES.ORIGINS.UNKNOWN); - - expect(origin).toBeUndefined(); - }); -}); diff --git a/test/domain/tenant/fixtures/databases.fixtures.ts b/test/domain/tenant/fixtures/databases.fixtures.ts index db538c8d..d0650587 100644 --- a/test/domain/tenant/fixtures/databases.fixtures.ts +++ b/test/domain/tenant/fixtures/databases.fixtures.ts @@ -1,7 +1,6 @@ import database from '^/integrations/database'; -import { RECORD_TYPE as ORIGIN_RECORD_TYPE } from '^/domain/origin'; import { RECORD_TYPE as TENANT_RECORD_TYPE } from '^/domain/tenant'; import { RECORDS } from './records.fixtures'; @@ -11,7 +10,6 @@ database.connect(); async function tenantsAndOrigins(): Promise { RECORDS.TENANTS.map(tenants => database.createRecord(TENANT_RECORD_TYPE, { ...tenants })); - RECORDS.ORIGINS.map(origins => database.createRecord(ORIGIN_RECORD_TYPE, { ...origins })); } export const DATABASES = { tenantsAndOrigins }; diff --git a/test/domain/tenant/fixtures/records.fixtures.ts b/test/domain/tenant/fixtures/records.fixtures.ts index 9a1afc13..8f6047c6 100644 --- a/test/domain/tenant/fixtures/records.fixtures.ts +++ b/test/domain/tenant/fixtures/records.fixtures.ts @@ -1,18 +1,12 @@ import type { RecordData } from '^/integrations/database'; -import type { DataModel as OriginDataModel } from '^/domain/origin'; import type { DataModel as TenantDataModel } from '^/domain/tenant'; import { VALUES } from './values.fixtures'; export const TENANTS: TenantDataModel[] = [ - { id: VALUES.IDS.TENANT1, name: VALUES.NAMES.TENANT1 } + { id: VALUES.IDS.TENANT1, name: VALUES.NAMES.TENANT1, origins: [] } ]; -export const ORIGINS: OriginDataModel[] = [ - { id: VALUES.IDS.ORIGIN1, origin: VALUES.NAMES.ORIGIN1, tenantId: VALUES.IDS.TENANT1 }, - { id: VALUES.IDS.ORIGIN2, origin: VALUES.NAMES.ORIGIN2, tenantId: VALUES.IDS.TENANT1 } -]; - -export const RECORDS: Record = { TENANTS, ORIGINS }; +export const RECORDS: Record = { TENANTS }; diff --git a/test/domain/tenant/getByOrigin.spec.ts b/test/domain/tenant/getByOrigin.spec.ts index 06fbfa2e..bedf2bd8 100644 --- a/test/domain/tenant/getByOrigin.spec.ts +++ b/test/domain/tenant/getByOrigin.spec.ts @@ -17,7 +17,7 @@ describe('domain/tenant/getByOrigin', () => const tenant = await getByOrigin(VALUES.NAMES.ORIGIN1); expect(tenant).toBeDefined(); - expect(tenant?.id).toEqual(VALUES.IDS.TENANT1); + expect(tenant.id).toEqual(VALUES.IDS.TENANT1); }); it('should NOT get an unknown tenant', async () => diff --git a/vite.config.ts b/vite.config.ts index ba18b0a6..386d22b8 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,8 +7,7 @@ import tsconfigPaths from 'vite-tsconfig-paths'; const JITAR_URL = 'http://localhost:3000'; const JITAR_SEGMENTS = []; const JITAR_MIDDLEWARES = [ - './integrations/runtime/requesterMiddleware', - './integrations/runtime/originMiddleware' + './integrations/runtime/requesterMiddleware' ]; const jitarConfig: JitarConfig = { From 33a315be0372ce92be86a62b82f27d1310e16ad5 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Sat, 19 Jul 2025 20:02:50 +0200 Subject: [PATCH 08/25] #375: bumped jitar for middleware execution fix --- package-lock.json | 86 +++++++++++++++++++++++------------------------ package.json | 4 +-- 2 files changed, 45 insertions(+), 45 deletions(-) diff --git a/package-lock.json b/package-lock.json index 82eec8a3..e772f7af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "dependencies": { "dayjs": "^1.11.13", - "jitar": "^0.9.3", + "jitar": "^0.10.0", "minio": "^8.0.5", "mongodb": "^6.17.0", "openid-client": "^6.5.1", @@ -22,7 +22,7 @@ }, "devDependencies": { "@eslint/js": "^9.30.1", - "@jitar/plugin-vite": "^0.9.3", + "@jitar/plugin-vite": "^0.10.0", "@types/node": "24.0.10", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", @@ -2208,14 +2208,14 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", - "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", "dev": true, "license": "Apache-2.0", "peer": true, "dependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { @@ -2223,9 +2223,9 @@ } }, "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", - "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "peer": true, @@ -2356,13 +2356,13 @@ } }, "node_modules/@jitar/plugin-vite": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/@jitar/plugin-vite/-/plugin-vite-0.9.3.tgz", - "integrity": "sha512-S/x4aiL6X4Sv3WCehcpR1H2EtTQjir6clnk5NJLmphMNZhPMuRVvpbLt8JPemXdu1FGGBWfgUI/vzCmUzxiAOQ==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@jitar/plugin-vite/-/plugin-vite-0.10.0.tgz", + "integrity": "sha512-/BI6EcO/jnPh1OLnuTb8fgj4ttN0ef247o3m9JdIN9SP4yLgcuOib2Ixx/rut9CB/SWGwoRjSd7QLTQ/eoOOzw==", "dev": true, "license": "MIT", "peerDependencies": { - "vite": ">=4.0.0 || >=5.0.0 || >=6.0.0" + "vite": ">=4.0.0 || >=5.0.0 || >=6.0.0 || >=7.0.0" }, "peerDependenciesMeta": { "vite": { @@ -4694,9 +4694,9 @@ } }, "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz", + "integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -5766,6 +5766,7 @@ "version": "11.3.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -5934,7 +5935,6 @@ "version": "11.0.3", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", - "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.3.1", @@ -6061,6 +6061,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -7004,16 +7005,15 @@ } }, "node_modules/jitar": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/jitar/-/jitar-0.9.3.tgz", - "integrity": "sha512-U352BwjuGSt6g7FMyQ1H7tvl66AMZ3JDVObehcEDUeUOifb1+x/OvUKz2X0sfIH8qRRDATjn9lHQ9S4bsTrbFA==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/jitar/-/jitar-0.10.0.tgz", + "integrity": "sha512-a73wwht+ornsOG4OBd+oec0qS1ev0PCPIHrcwfMHI+iF9aOgBtVNR3cbY/ziZGKUawVWcMzSRMZsYSDE4h798Q==", "license": "MIT", "dependencies": { - "dotenv": "^16.5.0", - "express": "^5.0.1", - "fs-extra": "^11.3.0", - "glob": "11.0.1", - "mime-types": "^2.1.35" + "dotenv": "^17.0.1", + "express": "^5.1.0", + "glob": "11.0.3", + "mime-types": "^3.0.1" }, "bin": { "jitar": "dist/cli.js" @@ -7022,27 +7022,25 @@ "node": ">=20.0" } }, - "node_modules/jitar/node_modules/glob": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", - "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", - "license": "ISC", + "node_modules/jitar/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/jitar/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" - }, - "bin": { - "glob": "dist/esm/bin.mjs" + "mime-db": "^1.54.0" }, "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 0.6" } }, "node_modules/jose": { @@ -7136,6 +7134,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, "license": "MIT", "dependencies": { "universalify": "^2.0.0" @@ -10170,6 +10169,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 10.0.0" diff --git a/package.json b/package.json index 7242e7a9..5d348922 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ ], "dependencies": { "dayjs": "^1.11.13", - "jitar": "^0.9.3", + "jitar": "^0.10.0", "minio": "^8.0.5", "mongodb": "^6.17.0", "openid-client": "^6.5.1", @@ -51,7 +51,7 @@ "zod": "^3.25.64" }, "devDependencies": { - "@jitar/plugin-vite": "^0.9.3", + "@jitar/plugin-vite": "^0.10.0", "@types/node": "24.0.10", "@types/react": "^19.1.8", "@types/react-dom": "^19.1.6", From 57c0557833c2baa3f1e967f6227221644f3c87e2 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Sat, 19 Jul 2025 22:28:48 +0200 Subject: [PATCH 09/25] #375: default tenant added to db on initialization --- docker-compose.yml | 1 + docker/mongodb/init.js | 9 +++++++++ example.env | 1 + 3 files changed, 11 insertions(+) create mode 100644 docker/mongodb/init.js diff --git a/docker-compose.yml b/docker-compose.yml index 43bc0b5a..a4b9b485 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,7 @@ services: restart: always volumes: - comify-mongo-data:/data/db + - "${MONGO_INIT_PATH}:/docker-entrypoint-initdb.d" ports: - "${MONGODB_PORT_NUMBER:-27017}:27017" environment: diff --git a/docker/mongodb/init.js b/docker/mongodb/init.js new file mode 100644 index 00000000..e8e933e7 --- /dev/null +++ b/docker/mongodb/init.js @@ -0,0 +1,9 @@ + +db = db.getSiblingDB('comify'); +db.tenant.insertOne({ + _id: 'localhost', + origins: [ + 'http://localhost:3000', + 'http://localhost:5173' + ] +}); diff --git a/example.env b/example.env index 3c9a4735..87f68fdd 100644 --- a/example.env +++ b/example.env @@ -63,6 +63,7 @@ MONGODB_ROOT_PASSWORD="development" MONGO_EXPRESS_PORT_NUMBER=8081 MONGO_EXPRESS_USERNAME="development" MONGO_EXPRESS_PASSWORD="development" +MONGO_INIT_PATH="./docker/mongodb" # MINIO MINIO_ADMIN_PORT_NUMBER=9001 From 85bab70f7fc66f79b86dbe320f78ceb2a72b673e Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Sat, 19 Jul 2025 22:29:56 +0200 Subject: [PATCH 10/25] #375: tenantId as required parameter --- src/domain/authentication/login/login.ts | 4 ++-- src/domain/creator/create/create.ts | 4 ++-- src/domain/creator/create/createData.ts | 2 +- src/domain/creator/generateNickname/generateNickname.ts | 2 +- src/domain/creator/generateNickname/retrieveByNickname.ts | 2 +- .../creator/generateNickname/retrieveByStartNickname.ts | 4 ++-- src/domain/creator/getByEmail/getByEmail.ts | 2 +- src/domain/creator/getById/CreatorNotFound.ts | 4 ++-- src/domain/creator/getById/getById.ts | 2 +- src/domain/creator/getByNickname/getByNickname.ts | 2 +- src/domain/creator/getOthers/getOthers.ts | 2 +- src/domain/creator/register/register.ts | 4 ++-- src/domain/creator/types.ts | 2 +- src/domain/post/create/create.ts | 4 ++-- src/domain/post/create/createData.ts | 2 +- src/domain/post/createWithComic/createWithComic.ts | 2 +- src/domain/post/createWithComment/createWithComment.ts | 2 +- src/domain/post/explore/explore.ts | 2 +- src/domain/post/explore/retrieveData.ts | 2 +- src/domain/post/types.ts | 2 +- src/domain/relation/explore/explore.ts | 2 +- src/domain/tenant/getByOrigin/getByOrigin.ts | 5 ++--- src/domain/tenant/getByOrigin/index.ts | 1 + .../tenant/getByOriginConverted/getByOriginConverted.ts | 2 +- src/integrations/runtime/middlewares/OriginMiddleware.ts | 4 ++-- src/webui/components/common/TenantContainer.tsx | 2 +- src/webui/components/common/hooks/useTenant.ts | 2 +- 27 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/domain/authentication/login/login.ts b/src/domain/authentication/login/login.ts index 77f3bbb9..f208f69e 100644 --- a/src/domain/authentication/login/login.ts +++ b/src/domain/authentication/login/login.ts @@ -12,11 +12,11 @@ export default async function login(identity: Identity, tenant: Tenant): Promise const existingCreator = await getCreatorByEmail(identity.email, tenant.id); const loggedInCreator = existingCreator ?? await registerCreator( + tenant.id, identity.name, identity.nickname ?? identity.name, identity.email, - identity.picture, - tenant.id + identity.picture ); return { diff --git a/src/domain/creator/create/create.ts b/src/domain/creator/create/create.ts index 55d06d6d..5665e881 100644 --- a/src/domain/creator/create/create.ts +++ b/src/domain/creator/create/create.ts @@ -4,9 +4,9 @@ import createData from './createData'; import insertData from './insertData'; import validateData from './validateData'; -export default async function create(fullName: string, nickname: string, email: string, portraitId: string | undefined = undefined, tenantId: string | undefined = undefined): Promise +export default async function create(tenantId: string, fullName: string, nickname: string, email: string, portraitId: string | undefined = undefined): Promise { - const data = await createData(fullName, nickname, email, portraitId, tenantId); + const data = await createData(tenantId, fullName, nickname, email, portraitId); validateData(data); diff --git a/src/domain/creator/create/createData.ts b/src/domain/creator/create/createData.ts index 66eb98c0..aaed975e 100644 --- a/src/domain/creator/create/createData.ts +++ b/src/domain/creator/create/createData.ts @@ -3,7 +3,7 @@ import { generateId } from '^/integrations/utilities/crypto'; import type { DataModel } from '../types'; -export default async function createData(fullName: string, nickname: string, email: string, portraitId?: string, tenantId?: string): Promise +export default async function createData(tenantId: string, fullName: string, nickname: string, email: string, portraitId?: string): Promise { return { id: generateId(), diff --git a/src/domain/creator/generateNickname/generateNickname.ts b/src/domain/creator/generateNickname/generateNickname.ts index e81cf7f3..74118ad7 100644 --- a/src/domain/creator/generateNickname/generateNickname.ts +++ b/src/domain/creator/generateNickname/generateNickname.ts @@ -6,7 +6,7 @@ import retrieveByStartNickname from './retrieveByStartNickname'; const MAX_NICKNAME_NUMBER = 1000; -export default async function generateNickname(nickname: string, tenantId: string | undefined = undefined): Promise +export default async function generateNickname(nickname: string, tenantId: string): Promise { const cleanedNickname = cleanNickname(nickname); diff --git a/src/domain/creator/generateNickname/retrieveByNickname.ts b/src/domain/creator/generateNickname/retrieveByNickname.ts index 29c65f9f..1a59a131 100644 --- a/src/domain/creator/generateNickname/retrieveByNickname.ts +++ b/src/domain/creator/generateNickname/retrieveByNickname.ts @@ -4,7 +4,7 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function retrieveByNickname(nickname: string, tenantId: string | undefined = undefined): Promise +export default async function retrieveByNickname(nickname: string, tenantId: string): Promise { const query = { nickname: { 'EQUALS': nickname }, diff --git a/src/domain/creator/generateNickname/retrieveByStartNickname.ts b/src/domain/creator/generateNickname/retrieveByStartNickname.ts index 23f78b2a..01bc4494 100644 --- a/src/domain/creator/generateNickname/retrieveByStartNickname.ts +++ b/src/domain/creator/generateNickname/retrieveByStartNickname.ts @@ -5,7 +5,7 @@ import database, { SortDirections } from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function retrieveByStartNickname(nickname: string, tenantId: string | undefined = undefined): Promise +export default async function retrieveByStartNickname(nickname: string, tenantId: string): Promise { const query = { nickname: { 'STARTS_WITH': nickname }, @@ -15,4 +15,4 @@ export default async function retrieveByStartNickname(nickname: string, tenantId const sort: RecordSort = { 'nickname': SortDirections.DESCENDING }; return database.findRecord(RECORD_TYPE, query, undefined, sort) as Promise; -} +}; diff --git a/src/domain/creator/getByEmail/getByEmail.ts b/src/domain/creator/getByEmail/getByEmail.ts index 3ba21108..550d3dd2 100644 --- a/src/domain/creator/getByEmail/getByEmail.ts +++ b/src/domain/creator/getByEmail/getByEmail.ts @@ -4,7 +4,7 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getByEmail(email: string, tenantId: string | undefined = undefined): Promise +export default async function getByEmail(email: string, tenantId: string): Promise { const query = { email: { EQUALS: email }, diff --git a/src/domain/creator/getById/CreatorNotFound.ts b/src/domain/creator/getById/CreatorNotFound.ts index 2b291eb2..50bc8c0c 100644 --- a/src/domain/creator/getById/CreatorNotFound.ts +++ b/src/domain/creator/getById/CreatorNotFound.ts @@ -3,8 +3,8 @@ import { NotFound } from '^/integrations/runtime'; export default class CreatorNotFound extends NotFound { - constructor(id: string, tenantId?: string) + constructor(id: string, tenantId: string) { - super(`No creator for id: ${id} and tenant ${tenantId || 'undefined'}`); + super(`No creator for id: ${id} and tenant ${tenantId}`); } } diff --git a/src/domain/creator/getById/getById.ts b/src/domain/creator/getById/getById.ts index 97c88723..bdf0b5c3 100644 --- a/src/domain/creator/getById/getById.ts +++ b/src/domain/creator/getById/getById.ts @@ -5,7 +5,7 @@ import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; import CreatorNotFound from './CreatorNotFound'; -export default async function getById(id: string, tenantId: string | undefined = undefined): Promise +export default async function getById(id: string, tenantId: string): Promise { const query: RecordQuery = { diff --git a/src/domain/creator/getByNickname/getByNickname.ts b/src/domain/creator/getByNickname/getByNickname.ts index 3d23e882..af616408 100644 --- a/src/domain/creator/getByNickname/getByNickname.ts +++ b/src/domain/creator/getByNickname/getByNickname.ts @@ -5,7 +5,7 @@ import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; import NicknameNotFound from './NicknameNotFound'; -export default async function getByNickname(nickname: string, tenantId: string | undefined = undefined): Promise +export default async function getByNickname(nickname: string, tenantId: string): Promise { const query = { nickname: { EQUALS: nickname }, diff --git a/src/domain/creator/getOthers/getOthers.ts b/src/domain/creator/getOthers/getOthers.ts index 4dccf26b..6318bbda 100644 --- a/src/domain/creator/getOthers/getOthers.ts +++ b/src/domain/creator/getOthers/getOthers.ts @@ -6,7 +6,7 @@ import type { SortOrder } from '../definitions'; import { RECORD_TYPE, SortOrders } from '../definitions'; import type { DataModel } from '../types'; -export default async function getOthers(ids: string[], order: SortOrder, limit: number, offset: number, search: string | undefined = undefined, tenantId: string | undefined = undefined): Promise +export default async function getOthers(tenantId: string, ids: string[], order: SortOrder, limit: number, offset: number, search: string | undefined = undefined): Promise { const defaultQuery: RecordQuery = { 'AND': [ diff --git a/src/domain/creator/register/register.ts b/src/domain/creator/register/register.ts index 55be2d0a..038fb0b0 100644 --- a/src/domain/creator/register/register.ts +++ b/src/domain/creator/register/register.ts @@ -9,7 +9,7 @@ import type { DataModel } from '../types'; import downloadPortrait from './downloadPortrait'; import publish from './publish'; -export default async function register(fullName: string, nickname: string, email: string, portraitUrl: string | undefined = undefined, tenantId: string | undefined = undefined): Promise +export default async function register(tenantId: string, fullName: string, nickname: string, email: string, portraitUrl: string | undefined = undefined): Promise { let data; @@ -22,7 +22,7 @@ export default async function register(fullName: string, nickname: string, email ? await downloadPortrait(portraitUrl) : undefined; - data = await create(truncatedFullName, generatedNickname, email, portraitId, tenantId); + data = await create(tenantId, truncatedFullName, generatedNickname, email, portraitId); await publish(data.id); diff --git a/src/domain/creator/types.ts b/src/domain/creator/types.ts index cdc298e4..97bb2a60 100644 --- a/src/domain/creator/types.ts +++ b/src/domain/creator/types.ts @@ -7,7 +7,7 @@ type DataModel = BaseDataModel & readonly nickname: string; readonly email: string; readonly portraitId?: string; - readonly tenantId?: string; + readonly tenantId: string; readonly joinedAt: string; }; diff --git a/src/domain/post/create/create.ts b/src/domain/post/create/create.ts index e2631158..41eee454 100644 --- a/src/domain/post/create/create.ts +++ b/src/domain/post/create/create.ts @@ -7,13 +7,13 @@ import insertData from './insertData'; import publish from './publish'; import validateData from './validateData'; -export default async function create(creatorId: string, comicId?: string, commentId?: string, parentId?: string, tenantId?: string): Promise +export default async function create(creatorId: string, tenantId: string, comicId?: string, commentId?: string, parentId?: string): Promise { let postId; try { - const data = createData(creatorId, comicId, commentId, parentId, tenantId); + const data = createData(creatorId, tenantId, comicId, commentId, parentId); validateData(data); diff --git a/src/domain/post/create/createData.ts b/src/domain/post/create/createData.ts index d6dba49b..202e7d11 100644 --- a/src/domain/post/create/createData.ts +++ b/src/domain/post/create/createData.ts @@ -3,7 +3,7 @@ import { generateId } from '^/integrations/utilities/crypto'; import type { DataModel } from '../types'; -export default function createData(creatorId: string, comicId?: string, commentId?: string, parentId?: string, tenantId?: string): DataModel +export default function createData(creatorId: string, tenantId: string, comicId?: string, commentId?: string, parentId?: string): DataModel { return { id: generateId(), diff --git a/src/domain/post/createWithComic/createWithComic.ts b/src/domain/post/createWithComic/createWithComic.ts index fb25bd57..b13e6efd 100644 --- a/src/domain/post/createWithComic/createWithComic.ts +++ b/src/domain/post/createWithComic/createWithComic.ts @@ -9,5 +9,5 @@ export default async function createWithComic(requester: Requester, tenant: Tena { const comicId = await createComic(comicImageDataUrl); - return createPost(requester.id, comicId, undefined, parentId, tenant.id); + return createPost(requester.id, tenant.id, comicId, undefined, parentId); } diff --git a/src/domain/post/createWithComment/createWithComment.ts b/src/domain/post/createWithComment/createWithComment.ts index 883f18ab..5311e4ee 100644 --- a/src/domain/post/createWithComment/createWithComment.ts +++ b/src/domain/post/createWithComment/createWithComment.ts @@ -9,5 +9,5 @@ export default async function createWithComment(requester: Requester, tenant: Te { const commentId = await createComment(message); - return createPost(requester.id, undefined, commentId, parentId, tenant.id); + return createPost(requester.id, tenant.id, undefined, commentId, parentId); } diff --git a/src/domain/post/explore/explore.ts b/src/domain/post/explore/explore.ts index 8442514e..078c153c 100644 --- a/src/domain/post/explore/explore.ts +++ b/src/domain/post/explore/explore.ts @@ -13,5 +13,5 @@ export default async function explore(requester: Requester, tenant: Tenant, limi const excludedCreatorIds = relationsData.map(data => data.followingId); excludedCreatorIds.push(requester.id); - return retrieveData(excludedCreatorIds, limit, offset, tenant.id); + return retrieveData(tenant.id, excludedCreatorIds, limit, offset); } diff --git a/src/domain/post/explore/retrieveData.ts b/src/domain/post/explore/retrieveData.ts index 50337884..659bf747 100644 --- a/src/domain/post/explore/retrieveData.ts +++ b/src/domain/post/explore/retrieveData.ts @@ -5,7 +5,7 @@ import database, { SortDirections } from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function retrieveData(excludedCreatorIds: string[], limit: number, offset: number, tenantId: string | undefined = undefined): Promise +export default async function retrieveData(tenantId: string, excludedCreatorIds: string[], limit: number, offset: number): Promise { const query: RecordQuery = { diff --git a/src/domain/post/types.ts b/src/domain/post/types.ts index 8722fd41..72d0dbd7 100644 --- a/src/domain/post/types.ts +++ b/src/domain/post/types.ts @@ -8,7 +8,7 @@ type DataModel = BaseDataModel & readonly comicId?: string; readonly commentId?: string; readonly parentId?: string; - readonly tenantId?: string; + readonly tenantId: string; readonly createdAt: string; }; diff --git a/src/domain/relation/explore/explore.ts b/src/domain/relation/explore/explore.ts index 9777f213..93669fe6 100644 --- a/src/domain/relation/explore/explore.ts +++ b/src/domain/relation/explore/explore.ts @@ -13,7 +13,7 @@ export default async function explore(requester: Requester, tenant: Tenant, orde const followingIds = followingData.map(data => data.followingId); followingIds.push(requester.id); - const creatorData = await getOtherCreators(followingIds, order, limit, offset, search, tenant.id); + const creatorData = await getOtherCreators(tenant.id, followingIds, order, limit, offset, search); return creatorData.map(data => { diff --git a/src/domain/tenant/getByOrigin/getByOrigin.ts b/src/domain/tenant/getByOrigin/getByOrigin.ts index c3160449..ac1e2342 100644 --- a/src/domain/tenant/getByOrigin/getByOrigin.ts +++ b/src/domain/tenant/getByOrigin/getByOrigin.ts @@ -1,8 +1,7 @@ -import { RECORD_TYPE } from '^/domain/tenant'; - import database, { type RecordQuery } from '^/integrations/database'; +import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; import TenantNotFound from './TenantNotFound'; @@ -11,7 +10,7 @@ export default async function getByOrigin(origin: string): Promise { const query: RecordQuery = { - origins: { 'EQUALS': origin } + origins: { 'CONTAINS': origin } }; const record = await database.findRecord(RECORD_TYPE, query); diff --git a/src/domain/tenant/getByOrigin/index.ts b/src/domain/tenant/getByOrigin/index.ts index 78296ea0..f940e37a 100644 --- a/src/domain/tenant/getByOrigin/index.ts +++ b/src/domain/tenant/getByOrigin/index.ts @@ -1,2 +1,3 @@ export { default } from './getByOrigin'; +export { default as TenantNotFound } from './TenantNotFound'; diff --git a/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts b/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts index 8656c069..6985218e 100644 --- a/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts +++ b/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts @@ -10,4 +10,4 @@ export default async function getFormatted(origin: string): Promise id: tenant.id, origin: origin }; -} \ No newline at end of file +} diff --git a/src/integrations/runtime/middlewares/OriginMiddleware.ts b/src/integrations/runtime/middlewares/OriginMiddleware.ts index 6cb98092..ee03dbbe 100644 --- a/src/integrations/runtime/middlewares/OriginMiddleware.ts +++ b/src/integrations/runtime/middlewares/OriginMiddleware.ts @@ -1,6 +1,6 @@ import type { Middleware, NextHandler, Request } from 'jitar'; -import { BadRequest, Response } from 'jitar'; +import { BadRequest, type Response } from 'jitar'; const TENANT_COOKIE_NAME = 'x-tenant-origin'; @@ -65,4 +65,4 @@ export default class OriginMiddleware implements Middleware { response.setHeader('Set-Cookie', `${TENANT_COOKIE_NAME}=${origin}; Path=/; HttpOnly=true; SameSite=Strict; Secure`); } -} \ No newline at end of file +} diff --git a/src/webui/components/common/TenantContainer.tsx b/src/webui/components/common/TenantContainer.tsx index 23099c66..61c1970c 100644 --- a/src/webui/components/common/TenantContainer.tsx +++ b/src/webui/components/common/TenantContainer.tsx @@ -21,7 +21,7 @@ export default function Component({ children }: Props) document.head.appendChild(link); - }), [tenant]; + }, [tenant]); if (tenant === undefined) return; diff --git a/src/webui/components/common/hooks/useTenant.ts b/src/webui/components/common/hooks/useTenant.ts index 0541f2e4..9d2c0495 100644 --- a/src/webui/components/common/hooks/useTenant.ts +++ b/src/webui/components/common/hooks/useTenant.ts @@ -16,4 +16,4 @@ export function useTenant() }, []); return useLoadData(getTenant, []); -} \ No newline at end of file +} From 8526f2d882fdc302bb3eb418ac08a8eec200a913 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Sat, 19 Jul 2025 22:30:27 +0200 Subject: [PATCH 11/25] #375: added tests for tenant domain --- .../tenant/fixtures/databases.fixtures.ts | 14 ++++++++---- test/domain/tenant/fixtures/index.ts | 1 - .../tenant/fixtures/records.fixtures.ts | 2 +- .../domain/tenant/fixtures/values.fixtures.ts | 14 +++++------- test/domain/tenant/getByOrigin.spec.ts | 18 +++++---------- .../tenant/getByOriginConverted.spec.ts | 22 +++++++++++++++++++ 6 files changed, 43 insertions(+), 28 deletions(-) create mode 100644 test/domain/tenant/getByOriginConverted.spec.ts diff --git a/test/domain/tenant/fixtures/databases.fixtures.ts b/test/domain/tenant/fixtures/databases.fixtures.ts index d0650587..6b63eef8 100644 --- a/test/domain/tenant/fixtures/databases.fixtures.ts +++ b/test/domain/tenant/fixtures/databases.fixtures.ts @@ -5,11 +5,17 @@ import { RECORD_TYPE as TENANT_RECORD_TYPE } from '^/domain/tenant'; import { RECORDS } from './records.fixtures'; -database.connect(); +await database.connect(); -async function tenantsAndOrigins(): Promise +async function tenants(): Promise { - RECORDS.TENANTS.map(tenants => database.createRecord(TENANT_RECORD_TYPE, { ...tenants })); + await database.clear(); + + const promises = [ + RECORDS.TENANTS.map(tenant => database.createRecord(TENANT_RECORD_TYPE, { ...tenant })) + ]; + + await Promise.all(promises.flat()); } -export const DATABASES = { tenantsAndOrigins }; +export const DATABASES = { tenants }; diff --git a/test/domain/tenant/fixtures/index.ts b/test/domain/tenant/fixtures/index.ts index 945a801c..715d332e 100644 --- a/test/domain/tenant/fixtures/index.ts +++ b/test/domain/tenant/fixtures/index.ts @@ -1,4 +1,3 @@ export * from './databases.fixtures'; export * from './values.fixtures'; - diff --git a/test/domain/tenant/fixtures/records.fixtures.ts b/test/domain/tenant/fixtures/records.fixtures.ts index 8f6047c6..c47ff262 100644 --- a/test/domain/tenant/fixtures/records.fixtures.ts +++ b/test/domain/tenant/fixtures/records.fixtures.ts @@ -6,7 +6,7 @@ import type { DataModel as TenantDataModel } from '^/domain/tenant'; import { VALUES } from './values.fixtures'; export const TENANTS: TenantDataModel[] = [ - { id: VALUES.IDS.TENANT1, name: VALUES.NAMES.TENANT1, origins: [] } + { id: VALUES.IDS.TENANT1, origins: [VALUES.ORIGINS.FIRST, VALUES.ORIGINS.SECOND] } ]; export const RECORDS: Record = { TENANTS }; diff --git a/test/domain/tenant/fixtures/values.fixtures.ts b/test/domain/tenant/fixtures/values.fixtures.ts index b09663e9..f16be8a2 100644 --- a/test/domain/tenant/fixtures/values.fixtures.ts +++ b/test/domain/tenant/fixtures/values.fixtures.ts @@ -2,15 +2,11 @@ export const VALUES = { IDS: { - TENANT1: 'tenant1', - ORIGIN1: 'origin1', - ORIGIN2: 'origin2' + TENANT1: 'example.com' }, - - NAMES: { - TENANT1: 'Tenant 1', - ORIGIN1: 'Origin 1', - ORIGIN2: 'Origin 2', - UNKNOWN: 'Unknown Origin' + ORIGINS: { + FIRST: 'alpha.example.com', + SECOND: 'beta.example.com', + UNKNOWN: 'unknown' } }; diff --git a/test/domain/tenant/getByOrigin.spec.ts b/test/domain/tenant/getByOrigin.spec.ts index bedf2bd8..b71ffbba 100644 --- a/test/domain/tenant/getByOrigin.spec.ts +++ b/test/domain/tenant/getByOrigin.spec.ts @@ -1,29 +1,21 @@ import { beforeEach, describe, expect, it } from 'vitest'; -import getByOrigin from '^/domain/tenant/getByOrigin'; +import getByOrigin, { TenantNotFound } from '^/domain/tenant/getByOrigin'; import { DATABASES, VALUES } from './fixtures'; beforeEach(async () => { - await DATABASES.tenantsAndOrigins(); + await DATABASES.tenants(); }); describe('domain/tenant/getByOrigin', () => { - it('should get an existing', async () => + it('Should reject an invalid origin', async () => { - const tenant = await getByOrigin(VALUES.NAMES.ORIGIN1); + const promise = getByOrigin(VALUES.ORIGINS.UNKNOWN); - expect(tenant).toBeDefined(); - expect(tenant.id).toEqual(VALUES.IDS.TENANT1); - }); - - it('should NOT get an unknown tenant', async () => - { - const tenant = await getByOrigin(VALUES.NAMES.UNKNOWN); - - expect(tenant).toBeUndefined(); + await expect(promise).rejects.toThrow(TenantNotFound); }); }); diff --git a/test/domain/tenant/getByOriginConverted.spec.ts b/test/domain/tenant/getByOriginConverted.spec.ts new file mode 100644 index 00000000..7e9cde07 --- /dev/null +++ b/test/domain/tenant/getByOriginConverted.spec.ts @@ -0,0 +1,22 @@ + +import { beforeEach, describe, expect, it } from 'vitest'; + +import getByOriginConverted from '^/domain/tenant/getByOriginConverted'; + +import { DATABASES, VALUES } from './fixtures'; + +beforeEach(async () => +{ + await DATABASES.tenants(); +}); + +describe('domain/tenant/getByOriginConverted', () => +{ + it('Should return a multi-origin tenant identified by a single origin', async () => + { + const tenant = await getByOriginConverted(VALUES.ORIGINS.FIRST); + + expect(tenant.id).toEqual(VALUES.IDS.TENANT1); + expect(tenant.origin).toEqual(VALUES.ORIGINS.FIRST); + }); +}); From ca63b1da5eac8e007ce927b2416fcd8d635f60bd Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Sat, 19 Jul 2025 22:31:00 +0200 Subject: [PATCH 12/25] #375: updated tests for required tenant(Id) --- test/domain/authentication/fixtures/index.ts | 2 +- .../fixtures/records.fixture.ts | 3 ++- .../fixtures/tenants.fixture.ts | 4 ++++ test/domain/authentication/login.spec.ts | 20 +++++++++---------- test/domain/notification/fixtures/index.ts | 2 +- .../notification/fixtures/records.fixture.ts | 7 ++++--- .../notification/fixtures/tenants.fixture.ts | 4 ++++ .../notification/getRecentAggregated.spec.ts | 6 +++--- test/domain/post/createWithComic.spec.ts | 4 ++-- .../domain/post/fixtures/databases.fixture.ts | 8 ++++---- test/domain/post/fixtures/index.ts | 1 + test/domain/post/fixtures/records.fixture.ts | 13 ++++++------ test/domain/post/fixtures/tenants.fixture.ts | 4 ++++ .../post/getByFollowingAggregated.spec.ts | 4 ++-- .../post/getRecommendedAggregated.spec.ts | 4 ++-- .../domain/relation/exploreAggregated.spec.ts | 12 +++++------ test/domain/relation/fixtures/index.ts | 1 + .../relation/fixtures/records.fixture.ts | 13 ++++++------ .../relation/fixtures/tenants.fixture.ts | 4 ++++ .../relation/getFollowersAggregated.spec.ts | 4 ++-- .../relation/getFollowingAggregated.spec.ts | 4 ++-- 21 files changed, 73 insertions(+), 51 deletions(-) create mode 100644 test/domain/authentication/fixtures/tenants.fixture.ts create mode 100644 test/domain/notification/fixtures/tenants.fixture.ts create mode 100644 test/domain/post/fixtures/tenants.fixture.ts create mode 100644 test/domain/relation/fixtures/tenants.fixture.ts diff --git a/test/domain/authentication/fixtures/index.ts b/test/domain/authentication/fixtures/index.ts index 875f8973..cef99487 100644 --- a/test/domain/authentication/fixtures/index.ts +++ b/test/domain/authentication/fixtures/index.ts @@ -5,5 +5,5 @@ export * from './httpClients.fixture'; export * from './identities.fixture'; export * from './images.fixture'; export * from './records.fixture'; +export * from './tenants.fixture'; export * from './values.fixture'; - diff --git a/test/domain/authentication/fixtures/records.fixture.ts b/test/domain/authentication/fixtures/records.fixture.ts index 37428def..297719d5 100644 --- a/test/domain/authentication/fixtures/records.fixture.ts +++ b/test/domain/authentication/fixtures/records.fixture.ts @@ -3,9 +3,10 @@ import type { RecordData } from '^/integrations/database'; import type { DataModel as CreatorDataModel } from '^/domain/creator'; +import { TENANTS } from './tenants.fixture'; import { VALUES } from './values.fixture'; -const DEFAULT_DATA = { portraitId: undefined, joinedAt: new Date().toISOString() }; +const DEFAULT_DATA = { tenantId: TENANTS.default.id, portraitId: undefined, joinedAt: new Date().toISOString() }; const CREATORS: CreatorDataModel[] = [ { id: VALUES.IDS.FIRST, fullName: VALUES.FULL_NAMES.FIRST, nickname: VALUES.NICKNAMES.FIRST, email: VALUES.EMAILS.FIRST, ...DEFAULT_DATA }, diff --git a/test/domain/authentication/fixtures/tenants.fixture.ts b/test/domain/authentication/fixtures/tenants.fixture.ts new file mode 100644 index 00000000..ea725060 --- /dev/null +++ b/test/domain/authentication/fixtures/tenants.fixture.ts @@ -0,0 +1,4 @@ + +import { tenant } from '^/domain/tenant'; + +export const TENANTS = { default: tenant }; diff --git a/test/domain/authentication/login.spec.ts b/test/domain/authentication/login.spec.ts index 35144200..a6fc5018 100644 --- a/test/domain/authentication/login.spec.ts +++ b/test/domain/authentication/login.spec.ts @@ -4,7 +4,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; import login from '^/domain/authentication/login'; import { TooManySimilarNicknames } from '^/domain/creator/generateNickname'; -import { DATABASES, FILE_STORES, HTTP_CLIENTS, IDENTITIES, VALUES } from './fixtures'; +import { DATABASES, FILE_STORES, HTTP_CLIENTS, IDENTITIES, TENANTS, VALUES } from './fixtures'; beforeEach(async () => { @@ -18,53 +18,53 @@ beforeEach(async () => describe('domain/authentication', () => { - describe('.login(identity)', () => + describe('.login(identity, tenant)', () => { it('should login with an existing email', async () => { - const requester = await login(IDENTITIES.EXISTING); + const requester = await login(IDENTITIES.EXISTING, TENANTS.default); expect(requester.nickname).toBe(VALUES.NICKNAMES.FIRST); }); it('should register without a nickname', async () => { - const requester = await login(IDENTITIES.NO_NICKNAME); + const requester = await login(IDENTITIES.NO_NICKNAME, TENANTS.default); expect(requester.nickname).toBe(VALUES.NICKNAMES.FROM_FULL_NAME); }); it('should register with a duplicate nickname', async () => { - const requester = await login(IDENTITIES.DUPLICATE_NICKNAME); + const requester = await login(IDENTITIES.DUPLICATE_NICKNAME, TENANTS.default); expect(requester.nickname).toBe(VALUES.NICKNAMES.DEDUPLICATED); }); it('should register with multiple occurrences of nickname', async () => { - const requester = await login(IDENTITIES.MULTIPLE_OCCURRENCES_NICKNAME); + const requester = await login(IDENTITIES.MULTIPLE_OCCURRENCES_NICKNAME, TENANTS.default); expect(requester.nickname).toBe(VALUES.NICKNAMES.NEXT_OCCURRED); }); it('should NOT register with too many occurrences nickname', async () => { - const promise = login(IDENTITIES.TOO_MANY_SIMILAR_NICKNAMES); + const promise = login(IDENTITIES.TOO_MANY_SIMILAR_NICKNAMES, TENANTS.default); await expect(promise).rejects.toStrictEqual(new TooManySimilarNicknames()); }); it('should register with spaces in nickname', async () => { - const requester = await login(IDENTITIES.SPACED_NICKNAME); + const requester = await login(IDENTITIES.SPACED_NICKNAME, TENANTS.default); expect(requester.nickname).toBe(VALUES.NICKNAMES.DESPACED); }); it('should register with underscores in nickname', async () => { - const requester = await login(IDENTITIES.UNDERSCORED_NICKNAME); + const requester = await login(IDENTITIES.UNDERSCORED_NICKNAME, TENANTS.default); expect(requester.nickname).toBe(VALUES.NICKNAMES.DEUNDERSCORED); }); it('should register with a valid profile picture', async () => { - const requester = await login(IDENTITIES.WITH_PICTURE); + const requester = await login(IDENTITIES.WITH_PICTURE, TENANTS.default); expect(requester.nickname).toBe(VALUES.NICKNAMES.WITH_PICTURE); }); }); diff --git a/test/domain/notification/fixtures/index.ts b/test/domain/notification/fixtures/index.ts index 9d946137..e7fd662c 100644 --- a/test/domain/notification/fixtures/index.ts +++ b/test/domain/notification/fixtures/index.ts @@ -4,5 +4,5 @@ export * from './dataUrls.fixture'; export * from './fileStores.fixture'; export * from './records.fixture'; export * from './requesters.fixture'; +export * from './tenants.fixture'; export * from './values.fixture'; - diff --git a/test/domain/notification/fixtures/records.fixture.ts b/test/domain/notification/fixtures/records.fixture.ts index 04ed7602..65c8d300 100644 --- a/test/domain/notification/fixtures/records.fixture.ts +++ b/test/domain/notification/fixtures/records.fixture.ts @@ -12,12 +12,13 @@ import type { DataModel as PostMetricsModel } from '^/domain/post.metrics'; import type { DataModel as RatingDataModel } from '^/domain/rating'; import type { DataModel as RelationDataModel } from '^/domain/relation'; +import { TENANTS } from './tenants.fixture'; import { VALUES } from './values.fixture'; const CREATORS: CreatorDataModel[] = [ - { id: VALUES.IDS.CREATOR1, fullName: VALUES.FULL_NAMES.CREATOR1, nickname: VALUES.NICKNAMES.CREATOR1, email: VALUES.EMAILS.CREATOR1, portraitId: undefined, joinedAt: new Date().toISOString() }, - { id: VALUES.IDS.CREATOR2, fullName: VALUES.FULL_NAMES.CREATOR2, nickname: VALUES.NICKNAMES.CREATOR2, email: VALUES.EMAILS.CREATOR2, portraitId: undefined, joinedAt: new Date().toISOString() }, - { id: VALUES.IDS.CREATOR3, fullName: VALUES.FULL_NAMES.CREATOR3, nickname: VALUES.NICKNAMES.CREATOR3, email: VALUES.EMAILS.CREATOR3, portraitId: undefined, joinedAt: new Date().toISOString() } + { id: VALUES.IDS.CREATOR1, fullName: VALUES.FULL_NAMES.CREATOR1, nickname: VALUES.NICKNAMES.CREATOR1, email: VALUES.EMAILS.CREATOR1, portraitId: undefined, tenantId: TENANTS.default.id, joinedAt: new Date().toISOString() }, + { id: VALUES.IDS.CREATOR2, fullName: VALUES.FULL_NAMES.CREATOR2, nickname: VALUES.NICKNAMES.CREATOR2, email: VALUES.EMAILS.CREATOR2, portraitId: undefined, tenantId: TENANTS.default.id, joinedAt: new Date().toISOString() }, + { id: VALUES.IDS.CREATOR3, fullName: VALUES.FULL_NAMES.CREATOR3, nickname: VALUES.NICKNAMES.CREATOR3, email: VALUES.EMAILS.CREATOR3, portraitId: undefined, tenantId: TENANTS.default.id, joinedAt: new Date().toISOString() } ]; const CREATOR_METRICS: CreatorMetricsDataModel[] = [ diff --git a/test/domain/notification/fixtures/tenants.fixture.ts b/test/domain/notification/fixtures/tenants.fixture.ts new file mode 100644 index 00000000..ea725060 --- /dev/null +++ b/test/domain/notification/fixtures/tenants.fixture.ts @@ -0,0 +1,4 @@ + +import { tenant } from '^/domain/tenant'; + +export const TENANTS = { default: tenant }; diff --git a/test/domain/notification/getRecentAggregated.spec.ts b/test/domain/notification/getRecentAggregated.spec.ts index 13e40f61..3ba30bed 100644 --- a/test/domain/notification/getRecentAggregated.spec.ts +++ b/test/domain/notification/getRecentAggregated.spec.ts @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { Types } from '^/domain/notification'; import getRecentAggregated from '^/domain/notification/getRecentAggregated'; -import { DATABASES, FILE_STORES, REQUESTERS, VALUES } from './fixtures'; +import { DATABASES, FILE_STORES, REQUESTERS, TENANTS, VALUES } from './fixtures'; beforeEach(async () => { @@ -17,7 +17,7 @@ describe('domain/notification/getallAggregated', () => { it('should give all notifications under normal circumstances', async () => { - const result = await getRecentAggregated(REQUESTERS.CREATOR2, { offset: 0, limit: 7 }); + const result = await getRecentAggregated(REQUESTERS.CREATOR2, TENANTS.default, { offset: 0, limit: 7 }); expect(result).toHaveLength(2); @@ -36,7 +36,7 @@ describe('domain/notification/getallAggregated', () => it('should give only the notifications that aggregate without errors', async () => { - const result = await getRecentAggregated(REQUESTERS.CREATOR1, { offset: 0, limit: 7 }); + const result = await getRecentAggregated(REQUESTERS.CREATOR1, TENANTS.default, { offset: 0, limit: 7 }); expect(result).toHaveLength(2); diff --git a/test/domain/post/createWithComic.spec.ts b/test/domain/post/createWithComic.spec.ts index 1c577991..6c24e608 100644 --- a/test/domain/post/createWithComic.spec.ts +++ b/test/domain/post/createWithComic.spec.ts @@ -6,7 +6,7 @@ import createWithComic from '^/domain/post/createWithComic'; import database from '^/integrations/database'; -import { DATABASES, DATA_URLS, FILE_STORES, REQUESTERS } from './fixtures'; +import { DATABASES, DATA_URLS, FILE_STORES, REQUESTERS, TENANTS } from './fixtures'; beforeEach(async () => { @@ -20,7 +20,7 @@ describe('domain/post/add', () => { it('should create a post', async () => { - await createWithComic(REQUESTERS.CREATOR1, DATA_URLS.COMIC_IMAGE); + await createWithComic(REQUESTERS.CREATOR1, TENANTS.default, DATA_URLS.COMIC_IMAGE); const posts = await database.searchRecords(POST_RECORD_TYPE, {}); expect(posts.length).toBe(1); diff --git a/test/domain/post/fixtures/databases.fixture.ts b/test/domain/post/fixtures/databases.fixture.ts index c4c15c7d..31fd61e8 100644 --- a/test/domain/post/fixtures/databases.fixture.ts +++ b/test/domain/post/fixtures/databases.fixture.ts @@ -11,11 +11,11 @@ import database from '^/integrations/database'; import { RECORDS } from './records.fixture'; -database.connect(); +await database.connect(); async function withCreators(): Promise { - database.clear(); + await database.clear(); const promises = RECORDS.CREATORS.map(creator => database.createRecord(CREATOR_RECORD_TYPE, { ...creator })); @@ -24,7 +24,7 @@ async function withCreators(): Promise async function withCreatorsPostsAndRelations(): Promise { - database.clear(); + await database.clear(); const promises = [ RECORDS.CREATORS.map(creator => database.createRecord(CREATOR_RECORD_TYPE, { ...creator })), @@ -41,7 +41,7 @@ async function withCreatorsPostsAndRelations(): Promise async function withPostsAndCreators(): Promise { - database.clear(); + await database.clear(); const promises = [ RECORDS.CREATORS.map(creator => database.createRecord(CREATOR_RECORD_TYPE, { ...creator })), diff --git a/test/domain/post/fixtures/index.ts b/test/domain/post/fixtures/index.ts index 44991aaa..098e997b 100644 --- a/test/domain/post/fixtures/index.ts +++ b/test/domain/post/fixtures/index.ts @@ -5,4 +5,5 @@ export * from './fileStores.fixture'; export * from './queries.fixture'; export * from './records.fixture'; export * from './requesters.fixture'; +export * from './tenants.fixture'; export * from './values.fixture'; diff --git a/test/domain/post/fixtures/records.fixture.ts b/test/domain/post/fixtures/records.fixture.ts index d74617d3..06fa3728 100644 --- a/test/domain/post/fixtures/records.fixture.ts +++ b/test/domain/post/fixtures/records.fixture.ts @@ -11,13 +11,14 @@ import type { DataModel as RatingDataModel } from '^/domain/rating'; import type { DataModel as RelationDataModel } from '^/domain/relation'; import { REQUESTERS } from './requesters.fixture'; +import { TENANTS } from './tenants.fixture'; import { VALUES } from './values.fixture'; const NOW = new Date().toISOString(); const CREATORS: CreatorDataModel[] = [ - { id: VALUES.IDS.CREATOR1, fullName: VALUES.FULL_NAMES.CREATOR1, nickname: VALUES.NICKNAMES.CREATOR1, email: VALUES.EMAILS.CREATOR1, joinedAt: NOW }, - { id: VALUES.IDS.CREATOR2, fullName: VALUES.FULL_NAMES.CREATOR2, nickname: VALUES.NICKNAMES.CREATOR2, email: VALUES.EMAILS.CREATOR2, joinedAt: NOW } + { id: VALUES.IDS.CREATOR1, fullName: VALUES.FULL_NAMES.CREATOR1, nickname: VALUES.NICKNAMES.CREATOR1, email: VALUES.EMAILS.CREATOR1, tenantId: TENANTS.default.id, joinedAt: NOW }, + { id: VALUES.IDS.CREATOR2, fullName: VALUES.FULL_NAMES.CREATOR2, nickname: VALUES.NICKNAMES.CREATOR2, email: VALUES.EMAILS.CREATOR2, tenantId: TENANTS.default.id, joinedAt: NOW } ]; const CREATOR_METRICS: CreatorMetricsDataModel[] = [ @@ -38,10 +39,10 @@ const COMICS: ComicDataModel[] = [ ]; const POSTS: (PostDataModel & { deleted: boolean; })[] = [ - { id: VALUES.IDS.POST_RATED, creatorId: REQUESTERS.CREATOR1.id, comicId: VALUES.IDS.COMIC, createdAt: NOW, deleted: false }, - { id: VALUES.IDS.POST_UNRATED, creatorId: REQUESTERS.CREATOR1.id, comicId: VALUES.IDS.COMIC, createdAt: NOW, deleted: false }, - { id: VALUES.IDS.POST_EXTRA1, creatorId: REQUESTERS.CREATOR2.id, comicId: VALUES.IDS.COMIC, createdAt: NOW, deleted: false }, - { id: VALUES.IDS.POST_DELETED, creatorId: REQUESTERS.CREATOR1.id, comicId: VALUES.IDS.COMIC, createdAt: NOW, deleted: true }, + { id: VALUES.IDS.POST_RATED, creatorId: REQUESTERS.CREATOR1.id, comicId: VALUES.IDS.COMIC, tenantId: TENANTS.default.id, createdAt: NOW, deleted: false }, + { id: VALUES.IDS.POST_UNRATED, creatorId: REQUESTERS.CREATOR1.id, comicId: VALUES.IDS.COMIC, tenantId: TENANTS.default.id, createdAt: NOW, deleted: false }, + { id: VALUES.IDS.POST_EXTRA1, creatorId: REQUESTERS.CREATOR2.id, comicId: VALUES.IDS.COMIC, tenantId: TENANTS.default.id, createdAt: NOW, deleted: false }, + { id: VALUES.IDS.POST_DELETED, creatorId: REQUESTERS.CREATOR1.id, comicId: VALUES.IDS.COMIC, tenantId: TENANTS.default.id, createdAt: NOW, deleted: true }, ]; const POST_METRICS: PostMetricsDataModel[] = [ diff --git a/test/domain/post/fixtures/tenants.fixture.ts b/test/domain/post/fixtures/tenants.fixture.ts new file mode 100644 index 00000000..ea725060 --- /dev/null +++ b/test/domain/post/fixtures/tenants.fixture.ts @@ -0,0 +1,4 @@ + +import { tenant } from '^/domain/tenant'; + +export const TENANTS = { default: tenant }; diff --git a/test/domain/post/getByFollowingAggregated.spec.ts b/test/domain/post/getByFollowingAggregated.spec.ts index 436a53dd..59a0db73 100644 --- a/test/domain/post/getByFollowingAggregated.spec.ts +++ b/test/domain/post/getByFollowingAggregated.spec.ts @@ -1,7 +1,7 @@ import getByFollowingAggregated from '^/domain/post/getByFollowingAggregated'; import { beforeEach, describe, expect, it } from 'vitest'; -import { DATABASES, FILE_STORES, REQUESTERS } from './fixtures'; +import { DATABASES, FILE_STORES, REQUESTERS, TENANTS } from './fixtures'; beforeEach(async () => { @@ -15,7 +15,7 @@ describe('domain/post/getByFollowingAggregated', () => { it('should get posts from everyone followed by the requester', async () => { - const result = await getByFollowingAggregated(REQUESTERS.CREATOR1, { offset: 0, limit: 7 }); + const result = await getByFollowingAggregated(REQUESTERS.CREATOR1, TENANTS.default, { offset: 0, limit: 7 }); expect(result).toHaveLength(1); expect(result[0].creator.following.id).toBe(REQUESTERS.CREATOR2.id); diff --git a/test/domain/post/getRecommendedAggregated.spec.ts b/test/domain/post/getRecommendedAggregated.spec.ts index 5eedc6f8..fda63109 100644 --- a/test/domain/post/getRecommendedAggregated.spec.ts +++ b/test/domain/post/getRecommendedAggregated.spec.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; import getRecommendedAggregated from '^/domain/post/getRecommendedAggregated'; -import { DATA_URLS, DATABASES, FILE_STORES, REQUESTERS } from './fixtures'; +import { DATA_URLS, DATABASES, FILE_STORES, REQUESTERS, TENANTS } from './fixtures'; beforeEach(async () => { @@ -16,7 +16,7 @@ describe('domain/post/getRecommendedAggregated', () => { it('should give all posts except those created by the requester', async () => { - const result = await getRecommendedAggregated(REQUESTERS.CREATOR1, { offset: 0, limit: 7 }); + const result = await getRecommendedAggregated(REQUESTERS.CREATOR1, TENANTS.default, { offset: 0, limit: 7 }); expect(result).toHaveLength(1); expect(result[0].creator.following.id).toBe(REQUESTERS.CREATOR2.id); diff --git a/test/domain/relation/exploreAggregated.spec.ts b/test/domain/relation/exploreAggregated.spec.ts index c70f1904..436e28fb 100644 --- a/test/domain/relation/exploreAggregated.spec.ts +++ b/test/domain/relation/exploreAggregated.spec.ts @@ -4,7 +4,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { SortOrders } from '^/domain/relation/definitions'; import explore from '^/domain/relation/exploreAggregated'; -import { DATABASES, REQUESTERS, VALUES } from './fixtures'; +import { DATABASES, REQUESTERS, TENANTS, VALUES } from './fixtures'; beforeEach(async () => { @@ -15,7 +15,7 @@ describe('domain/relation/exploreAggregated', () => { it('should explore relations based on recent', async () => { - const relations = await explore(REQUESTERS.FIRST, SortOrders.RECENT, VALUES.RANGE); + const relations = await explore(REQUESTERS.FIRST, TENANTS.default, SortOrders.RECENT, VALUES.RANGE); expect(relations).toHaveLength(3); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR4); expect(relations[1].following?.id).toBe(VALUES.IDS.CREATOR6); @@ -24,27 +24,27 @@ describe('domain/relation/exploreAggregated', () => it('should find no relations based on search', async () => { - const relations = await explore(REQUESTERS.FIRST, SortOrders.POPULAR, VALUES.RANGE, 'or2'); + const relations = await explore(REQUESTERS.FIRST, TENANTS.default, SortOrders.POPULAR, VALUES.RANGE, 'or2'); expect(relations).toHaveLength(0); }); it('should find relations based on search full name', async () => { - const relations = await explore(REQUESTERS.FIRST, SortOrders.POPULAR, VALUES.RANGE, 'or 4'); + const relations = await explore(REQUESTERS.FIRST, TENANTS.default, SortOrders.POPULAR, VALUES.RANGE, 'or 4'); expect(relations).toHaveLength(1); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR4); }); it('should find relations based on search nickname', async () => { - const relations = await explore(REQUESTERS.FIRST, SortOrders.POPULAR, VALUES.RANGE, 'creator4'); + const relations = await explore(REQUESTERS.FIRST, TENANTS.default, SortOrders.POPULAR, VALUES.RANGE, 'creator4'); expect(relations).toHaveLength(1); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR4); }); it('should find relations based on search full name and nickname', async () => { - const relations = await explore(REQUESTERS.FIRST, SortOrders.POPULAR, VALUES.RANGE, 'five'); + const relations = await explore(REQUESTERS.FIRST, TENANTS.default, SortOrders.POPULAR, VALUES.RANGE, 'five'); expect(relations).toHaveLength(2); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR5); expect(relations[1].following?.id).toBe(VALUES.IDS.CREATOR6); diff --git a/test/domain/relation/fixtures/index.ts b/test/domain/relation/fixtures/index.ts index 93c1e1ba..e8454b7b 100644 --- a/test/domain/relation/fixtures/index.ts +++ b/test/domain/relation/fixtures/index.ts @@ -3,4 +3,5 @@ export * from './databases.fixture'; export * from './queries.fixture'; export * from './records.fixture'; export * from './requesters.fixture'; +export * from './tenants.fixture'; export * from './values.fixture'; diff --git a/test/domain/relation/fixtures/records.fixture.ts b/test/domain/relation/fixtures/records.fixture.ts index 09165545..5227826f 100644 --- a/test/domain/relation/fixtures/records.fixture.ts +++ b/test/domain/relation/fixtures/records.fixture.ts @@ -5,15 +5,16 @@ import type { DataModel as CreatorDataModel } from '^/domain/creator'; import type { DataModel as CreatorMetricsDataModel } from '^/domain/creator.metrics'; import type { DataModel as RelationDataModel } from '^/domain/relation'; +import { TENANTS } from './tenants.fixture'; import { VALUES } from './values.fixture'; const CREATORS: CreatorDataModel[] = [ - { id: VALUES.IDS.CREATOR1, fullName: 'Creator 1', nickname: 'creator1', email: 'creator1@mail.com', joinedAt: new Date(2024, 5, 23).toISOString(), portraitId: undefined }, - { id: VALUES.IDS.CREATOR2, fullName: 'Creator 2', nickname: 'creator2', email: 'creator2@mail.com', joinedAt: new Date(2024, 7, 11).toISOString(), portraitId: undefined }, - { id: VALUES.IDS.CREATOR3, fullName: 'Creator 3', nickname: 'creator3', email: 'creator3@mail.com', joinedAt: new Date(2024, 1, 24).toISOString(), portraitId: undefined }, - { id: VALUES.IDS.CREATOR4, fullName: 'Creator 4', nickname: 'creator4', email: 'creator4@mail.com', joinedAt: new Date(2024, 2, 12).toISOString(), portraitId: undefined }, - { id: VALUES.IDS.CREATOR5, fullName: 'Creator five', nickname: 'creator5', email: 'creator5@mail.com', joinedAt: new Date(2024, 4, 9).toISOString(), portraitId: undefined }, - { id: VALUES.IDS.CREATOR6, fullName: 'Creator 6', nickname: 'not_five', email: 'creator6@mail.com', joinedAt: new Date(2024, 3, 18).toISOString(), portraitId: undefined } + { id: VALUES.IDS.CREATOR1, fullName: 'Creator 1', nickname: 'creator1', email: 'creator1@mail.com', joinedAt: new Date(2024, 5, 23).toISOString(), tenantId: TENANTS.default.id, portraitId: undefined }, + { id: VALUES.IDS.CREATOR2, fullName: 'Creator 2', nickname: 'creator2', email: 'creator2@mail.com', joinedAt: new Date(2024, 7, 11).toISOString(), tenantId: TENANTS.default.id, portraitId: undefined }, + { id: VALUES.IDS.CREATOR3, fullName: 'Creator 3', nickname: 'creator3', email: 'creator3@mail.com', joinedAt: new Date(2024, 1, 24).toISOString(), tenantId: TENANTS.default.id, portraitId: undefined }, + { id: VALUES.IDS.CREATOR4, fullName: 'Creator 4', nickname: 'creator4', email: 'creator4@mail.com', joinedAt: new Date(2024, 2, 12).toISOString(), tenantId: TENANTS.default.id, portraitId: undefined }, + { id: VALUES.IDS.CREATOR5, fullName: 'Creator five', nickname: 'creator5', email: 'creator5@mail.com', joinedAt: new Date(2024, 4, 9).toISOString(), tenantId: TENANTS.default.id, portraitId: undefined }, + { id: VALUES.IDS.CREATOR6, fullName: 'Creator 6', nickname: 'not_five', email: 'creator6@mail.com', joinedAt: new Date(2024, 3, 18).toISOString(), tenantId: TENANTS.default.id, portraitId: undefined } ]; const CREATOR_METRICS: CreatorMetricsDataModel[] = [ diff --git a/test/domain/relation/fixtures/tenants.fixture.ts b/test/domain/relation/fixtures/tenants.fixture.ts new file mode 100644 index 00000000..ea725060 --- /dev/null +++ b/test/domain/relation/fixtures/tenants.fixture.ts @@ -0,0 +1,4 @@ + +import { tenant } from '^/domain/tenant'; + +export const TENANTS = { default: tenant }; diff --git a/test/domain/relation/getFollowersAggregated.spec.ts b/test/domain/relation/getFollowersAggregated.spec.ts index 07344572..884a5c4f 100644 --- a/test/domain/relation/getFollowersAggregated.spec.ts +++ b/test/domain/relation/getFollowersAggregated.spec.ts @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; import getFollowers from '^/domain/relation/getFollowersAggregated'; -import { DATABASES, REQUESTERS, VALUES } from './fixtures'; +import { DATABASES, REQUESTERS, TENANTS, VALUES } from './fixtures'; beforeEach(async () => { @@ -14,7 +14,7 @@ describe('domain/relation/getFollowers', () => { it('should retrieve follower relations for a following creator', async () => { - const relations = await getFollowers(REQUESTERS.FIRST, VALUES.IDS.CREATOR3, VALUES.RANGE); + const relations = await getFollowers(REQUESTERS.FIRST, TENANTS.default, VALUES.IDS.CREATOR3, VALUES.RANGE); expect(relations).toHaveLength(2); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR1); expect(relations[1].following?.id).toBe(VALUES.IDS.CREATOR2); diff --git a/test/domain/relation/getFollowingAggregated.spec.ts b/test/domain/relation/getFollowingAggregated.spec.ts index 3000e943..0534835c 100644 --- a/test/domain/relation/getFollowingAggregated.spec.ts +++ b/test/domain/relation/getFollowingAggregated.spec.ts @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; import getFollowing from '^/domain/relation/getFollowingAggregated'; -import { DATABASES, REQUESTERS, VALUES } from './fixtures'; +import { DATABASES, REQUESTERS, TENANTS, VALUES } from './fixtures'; beforeEach(async () => { @@ -14,7 +14,7 @@ describe('domain/relation/getFollowing', () => { it('should retrieve relations for a follower', async () => { - const relations = await getFollowing(REQUESTERS.FIRST, VALUES.IDS.CREATOR1, VALUES.RANGE); + const relations = await getFollowing(REQUESTERS.FIRST, TENANTS.default, VALUES.IDS.CREATOR1, VALUES.RANGE); expect(relations).toHaveLength(2); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR2); expect(relations[1].following?.id).toBe(VALUES.IDS.CREATOR3); From e331c9920ae17c564d49ec8ff1f33f3372abc24a Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Sat, 19 Jul 2025 23:01:36 +0200 Subject: [PATCH 13/25] #375: reset dependency that got lost in merge --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index bcdaaed1..8139594c 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "zod": "^3.25.64" }, "devDependencies": { + "@eslint/js": "^9.30.1", "@jitar/plugin-vite": "^0.10.0", "@types/node": "24.0.10", "@types/react": "^19.1.8", From 856a9d50ba53b89d0b9fa0d8cc991357d3083e93 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Tue, 22 Jul 2025 14:14:34 +0200 Subject: [PATCH 14/25] #375: processed feedback --- docker/mongodb/init.js | 19 +++++---- eslint.config.js | 3 +- .../relation/getAggregated/getAggregated.ts | 1 - .../getByOriginConverted/InvalidOrigin.ts | 7 ++++ .../getByOriginConverted.ts | 5 ++- .../tenant/getByOriginConverted/types.ts | 4 ++ .../getByOriginConverted/validateData.ts | 28 +++++++++++++ .../runtime/middlewares/OriginMiddleware.ts | 40 ++++++++++++++----- .../runtime/middlewares/TenantMiddleware.ts | 6 +-- .../components/common/TenantContainer.tsx | 2 +- 10 files changed, 91 insertions(+), 24 deletions(-) create mode 100644 src/domain/tenant/getByOriginConverted/InvalidOrigin.ts create mode 100644 src/domain/tenant/getByOriginConverted/types.ts create mode 100644 src/domain/tenant/getByOriginConverted/validateData.ts diff --git a/docker/mongodb/init.js b/docker/mongodb/init.js index e8e933e7..6f000f8a 100644 --- a/docker/mongodb/init.js +++ b/docker/mongodb/init.js @@ -1,9 +1,14 @@ db = db.getSiblingDB('comify'); -db.tenant.insertOne({ - _id: 'localhost', - origins: [ - 'http://localhost:3000', - 'http://localhost:5173' - ] -}); +db.tenant.updateOne( + { _id: 'localhost' }, + { + $set: { + origins: [ + 'http://localhost:3000', + 'http://localhost:5173' + ] + } + }, + { upsert: true } +); diff --git a/eslint.config.js b/eslint.config.js index 4ba6a4cb..314cfb54 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -15,7 +15,8 @@ export default tseslint.config( "**/dist/**/*", "**/node_modules/**/*", "**/coverage/**/*", - "**/*config*" + "**/*config*", + "docker" ] }, { diff --git a/src/domain/relation/getAggregated/getAggregated.ts b/src/domain/relation/getAggregated/getAggregated.ts index a9f08505..506967d4 100644 --- a/src/domain/relation/getAggregated/getAggregated.ts +++ b/src/domain/relation/getAggregated/getAggregated.ts @@ -6,7 +6,6 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import get from '../get'; - export default async function getAggregated(requester: Requester, tenant: Tenant, followerId: string, followingId: string): Promise { const data = await get(followerId, followingId); diff --git a/src/domain/tenant/getByOriginConverted/InvalidOrigin.ts b/src/domain/tenant/getByOriginConverted/InvalidOrigin.ts new file mode 100644 index 00000000..87a6a643 --- /dev/null +++ b/src/domain/tenant/getByOriginConverted/InvalidOrigin.ts @@ -0,0 +1,7 @@ + +import { ValidationError } from '^/integrations/runtime'; + +export default class InvalidOrigin extends ValidationError +{ + +} diff --git a/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts b/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts index 6985218e..7f8cc791 100644 --- a/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts +++ b/src/domain/tenant/getByOriginConverted/getByOriginConverted.ts @@ -1,9 +1,12 @@ import getByOrigin from '../getByOrigin'; import type { Tenant } from '../types'; +import validateData from './validateData'; -export default async function getFormatted(origin: string): Promise +export default async function getByOriginConverted(origin: string): Promise { + validateData({ origin }); + const tenant = await getByOrigin(origin); return { diff --git a/src/domain/tenant/getByOriginConverted/types.ts b/src/domain/tenant/getByOriginConverted/types.ts new file mode 100644 index 00000000..cb9ea482 --- /dev/null +++ b/src/domain/tenant/getByOriginConverted/types.ts @@ -0,0 +1,4 @@ + +import { type Tenant } from '../types'; + +export type ValidationModel = Pick; diff --git a/src/domain/tenant/getByOriginConverted/validateData.ts b/src/domain/tenant/getByOriginConverted/validateData.ts new file mode 100644 index 00000000..f4dc75ab --- /dev/null +++ b/src/domain/tenant/getByOriginConverted/validateData.ts @@ -0,0 +1,28 @@ + +import type { ValidationSchema } from '^/integrations/validation'; +import validator from '^/integrations/validation'; + +import InvalidOrigin from './InvalidOrigin'; +import { type ValidationModel } from './types'; + +const schema: ValidationSchema = +{ + origin: + { + message: 'Invalid origin', + URL: + { + required: true + } + } +}; + +export default function validateData({ origin }: ValidationModel): void +{ + const result = validator.validate({ origin }, schema); + + if (result.invalid) + { + throw new InvalidOrigin(result.messages); + } +} diff --git a/src/integrations/runtime/middlewares/OriginMiddleware.ts b/src/integrations/runtime/middlewares/OriginMiddleware.ts index ee03dbbe..3f8dfeea 100644 --- a/src/integrations/runtime/middlewares/OriginMiddleware.ts +++ b/src/integrations/runtime/middlewares/OriginMiddleware.ts @@ -2,7 +2,21 @@ import type { Middleware, NextHandler, Request } from 'jitar'; import { BadRequest, type Response } from 'jitar'; +import type { ValidationSchema } from '^/integrations/validation'; +import validator from '^/integrations/validation'; + const TENANT_COOKIE_NAME = 'x-tenant-origin'; +const schema: ValidationSchema = +{ + origin: + { + message: 'Invalid origin', + URL: + { + required: true + } + } +}; export default class OriginMiddleware implements Middleware { @@ -19,18 +33,16 @@ export default class OriginMiddleware implements Middleware origin = this.#getOriginFromHeader(request); } - if (origin === undefined) - { - throw new BadRequest('Missing origin'); - } + this.#validateOriginValue(origin); - request.setHeader('origin', origin); + // The origin header is validated and set here for use in other middlewares + request.setHeader('origin', origin as string); const response = await next(); if (fromCookie === false) { - this.#setOriginCookie(response, origin); + this.#setOriginCookie(response, origin as string); } return response; @@ -50,17 +62,27 @@ export default class OriginMiddleware implements Middleware return; } - for (const cookie of header.split('; ')) + for (const cookie of header.split(';')) { const [key, value] = cookie.split('='); - if (key === TENANT_COOKIE_NAME) + if (key.trim() === TENANT_COOKIE_NAME) { - return value; + return value?.trim(); } } } + #validateOriginValue(value: string | undefined): void + { + const result = validator.validate({ url: value }, schema); + + if (result.invalid) + { + throw new BadRequest('Invalid origin'); + } + } + #setOriginCookie(response: Response, origin: string): void { response.setHeader('Set-Cookie', `${TENANT_COOKIE_NAME}=${origin}; Path=/; HttpOnly=true; SameSite=Strict; Secure`); diff --git a/src/integrations/runtime/middlewares/TenantMiddleware.ts b/src/integrations/runtime/middlewares/TenantMiddleware.ts index 6020c7ea..6db5c568 100644 --- a/src/integrations/runtime/middlewares/TenantMiddleware.ts +++ b/src/integrations/runtime/middlewares/TenantMiddleware.ts @@ -31,13 +31,11 @@ export default class TenantMiddleware implements Middleware const response = await next(); - if (response.status >= 500) + if (response.status < 500) { - return response; + this.#cache.set(origin, response); } - this.#cache.set(origin, response); - return response; } diff --git a/src/webui/components/common/TenantContainer.tsx b/src/webui/components/common/TenantContainer.tsx index 61c1970c..4e54256f 100644 --- a/src/webui/components/common/TenantContainer.tsx +++ b/src/webui/components/common/TenantContainer.tsx @@ -23,7 +23,7 @@ export default function Component({ children }: Props) }, [tenant]); - if (tenant === undefined) return; + if (tenant === undefined) return null; return children; } From 99bb8f2533445e6262c6e8ae208e9db4f03cdafa Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Tue, 22 Jul 2025 15:27:26 +0200 Subject: [PATCH 15/25] #375: required URL validation for origin --- docker/keycloak/comify-realm.json | 1 - src/domain/authentication/login/login.ts | 2 +- src/domain/creator/create/validateData.ts | 4 ++-- src/domain/post/create/validateData.ts | 2 +- .../runtime/middlewares/OriginMiddleware.ts | 2 +- .../middlewares/RequesterMiddleware.ts | 22 ++++++++++++++----- .../components/common/hooks/useTenant.ts | 5 ++--- .../domain/tenant/fixtures/values.fixtures.ts | 4 ++-- 8 files changed, 26 insertions(+), 16 deletions(-) diff --git a/docker/keycloak/comify-realm.json b/docker/keycloak/comify-realm.json index 73b2d521..d1c797c4 100644 --- a/docker/keycloak/comify-realm.json +++ b/docker/keycloak/comify-realm.json @@ -666,7 +666,6 @@ "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "http://a.comify.local:3000/rpc/domain/authentication/login", "http://localhost:3000/rpc/domain/authentication/login", "http://localhost:5173/rpc/domain/authentication/login" ], diff --git a/src/domain/authentication/login/login.ts b/src/domain/authentication/login/login.ts index f208f69e..430bc27f 100644 --- a/src/domain/authentication/login/login.ts +++ b/src/domain/authentication/login/login.ts @@ -5,7 +5,7 @@ import getCreatorByEmail from '^/domain/creator/getByEmail'; import registerCreator from '^/domain/creator/register'; import { type Tenant } from '^/domain/tenant'; -import type { Requester } from '../types'; +import { type Requester } from '../types'; export default async function login(identity: Identity, tenant: Tenant): Promise { diff --git a/src/domain/creator/create/validateData.ts b/src/domain/creator/create/validateData.ts index a7c0aa15..2602457e 100644 --- a/src/domain/creator/create/validateData.ts +++ b/src/domain/creator/create/validateData.ts @@ -2,7 +2,7 @@ import type { ValidationSchema } from '^/integrations/validation'; import validator from '^/integrations/validation'; -import { optionalIdValidation } from '^/domain/definitions'; +import { optionalIdValidation, requiredIdValidation } from '^/domain/definitions'; import InvalidCreator from '../InvalidCreator'; import { fullNameValidation } from '../definitions'; @@ -19,7 +19,7 @@ const schema: ValidationSchema = required: true } }, - tenantId: optionalIdValidation, + tenantId: requiredIdValidation, portraitId: optionalIdValidation }; diff --git a/src/domain/post/create/validateData.ts b/src/domain/post/create/validateData.ts index 2b7dc061..bd7d3fb9 100644 --- a/src/domain/post/create/validateData.ts +++ b/src/domain/post/create/validateData.ts @@ -10,7 +10,7 @@ import type { ValidationModel } from './types'; const schema: ValidationSchema = { creatorId: requiredIdValidation, - tenantId: optionalIdValidation, + tenantId: requiredIdValidation, comicId: optionalIdValidation, commentId: optionalIdValidation, parentId: optionalIdValidation diff --git a/src/integrations/runtime/middlewares/OriginMiddleware.ts b/src/integrations/runtime/middlewares/OriginMiddleware.ts index 3f8dfeea..9e9c53c1 100644 --- a/src/integrations/runtime/middlewares/OriginMiddleware.ts +++ b/src/integrations/runtime/middlewares/OriginMiddleware.ts @@ -75,7 +75,7 @@ export default class OriginMiddleware implements Middleware #validateOriginValue(value: string | undefined): void { - const result = validator.validate({ url: value }, schema); + const result = validator.validate({ origin: value }, schema); if (result.invalid) { diff --git a/src/integrations/runtime/middlewares/RequesterMiddleware.ts b/src/integrations/runtime/middlewares/RequesterMiddleware.ts index 611a283a..b2f7624e 100644 --- a/src/integrations/runtime/middlewares/RequesterMiddleware.ts +++ b/src/integrations/runtime/middlewares/RequesterMiddleware.ts @@ -17,13 +17,25 @@ export default class RequesterMiddleware implements Middleware request.setHeader('Authorization', this.#authorization); } - const response = await next(); - - if (response.hasHeader('Authorization')) + try { - this.#authorization = response.getHeader('Authorization')!; + const response = await next(); + + if (response.hasHeader('Authorization')) + { + this.#authorization = response.getHeader('Authorization')!; + } + + return response; } + catch (error) + { + if (error?.constructor?.name === 'Unauthorized') + { + this.#authorization = undefined; + } - return response; + throw error; + } } } diff --git a/src/webui/components/common/hooks/useTenant.ts b/src/webui/components/common/hooks/useTenant.ts index 9d2c0495..f21da56b 100644 --- a/src/webui/components/common/hooks/useTenant.ts +++ b/src/webui/components/common/hooks/useTenant.ts @@ -1,6 +1,7 @@ import { useCallback } from 'react'; +import { tenant } from '^/domain/tenant'; import getByOriginConverted from '^/domain/tenant/getByOriginConverted'; import { useLoadData } from '^/webui/hooks'; @@ -9,9 +10,7 @@ export function useTenant() { const getTenant = useCallback(async () => { - const tenant = await getByOriginConverted(''); - - return tenant; + return await getByOriginConverted(tenant.origin); }, []); diff --git a/test/domain/tenant/fixtures/values.fixtures.ts b/test/domain/tenant/fixtures/values.fixtures.ts index f16be8a2..120926d9 100644 --- a/test/domain/tenant/fixtures/values.fixtures.ts +++ b/test/domain/tenant/fixtures/values.fixtures.ts @@ -5,8 +5,8 @@ export const VALUES = TENANT1: 'example.com' }, ORIGINS: { - FIRST: 'alpha.example.com', - SECOND: 'beta.example.com', + FIRST: 'http://alpha.example.com', + SECOND: 'http://beta.example.com', UNKNOWN: 'unknown' } }; From 00263afad0b1c7211bddc3d6c7edceb90480b0ca Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Tue, 22 Jul 2025 16:49:34 +0200 Subject: [PATCH 16/25] #375: moved bff before the gateway --- services/bff.json | 4 +--- services/standalone.json | 4 +--- src/integrations/runtime/setUpBff.ts | 5 ++++- src/integrations/runtime/setUpGateway.ts | 4 ---- src/integrations/runtime/tearDownBff.ts | 2 ++ src/integrations/runtime/tearDownGateway.ts | 4 ---- 6 files changed, 8 insertions(+), 15 deletions(-) delete mode 100644 src/integrations/runtime/setUpGateway.ts delete mode 100644 src/integrations/runtime/tearDownGateway.ts diff --git a/services/bff.json b/services/bff.json index 94e10f91..7e8c0c3d 100644 --- a/services/bff.json +++ b/services/bff.json @@ -1,11 +1,9 @@ { "url": "http://127.0.0.1:4000", "setUp": [ - "./integrations/runtime/setUpBff", - "./integrations/runtime/setUpGateway" + "./integrations/runtime/setUpBff" ], "tearDown": [ - "./integrations/runtime/tearDownGateway", "./integrations/runtime/tearDownBff" ], "middleware": [ diff --git a/services/standalone.json b/services/standalone.json index c4405564..e93fad1f 100644 --- a/services/standalone.json +++ b/services/standalone.json @@ -2,11 +2,9 @@ "url": "http://127.0.0.1:3000", "setUp": [ "./integrations/runtime/setUpBff", - "./integrations/runtime/setUpWorker", - "./integrations/runtime/setUpGateway" + "./integrations/runtime/setUpWorker" ], "tearDown": [ - "./integrations/runtime/tearDownGateway", "./integrations/runtime/tearDownWorker", "./integrations/runtime/tearDownBff" ], diff --git a/src/integrations/runtime/setUpBff.ts b/src/integrations/runtime/setUpBff.ts index 5c1f24ff..1a917ae8 100644 --- a/src/integrations/runtime/setUpBff.ts +++ b/src/integrations/runtime/setUpBff.ts @@ -1,10 +1,12 @@ +import identityProvider from '^/integrations/authentication'; import eventBroker from '^/integrations/eventbroker'; try { await Promise.allSettled([ - eventBroker.connect() + eventBroker.connect(), + identityProvider.connect() ]); } catch (error) @@ -12,6 +14,7 @@ catch (error) const disconnections = []; if (eventBroker.connected) disconnections.push(eventBroker.disconnect()); + if (identityProvider.connected) disconnections.push(identityProvider.disconnect()); await Promise.allSettled(disconnections); diff --git a/src/integrations/runtime/setUpGateway.ts b/src/integrations/runtime/setUpGateway.ts deleted file mode 100644 index 3f639155..00000000 --- a/src/integrations/runtime/setUpGateway.ts +++ /dev/null @@ -1,4 +0,0 @@ - -import identityProvider from '^/integrations/authentication'; - -await identityProvider.connect(); diff --git a/src/integrations/runtime/tearDownBff.ts b/src/integrations/runtime/tearDownBff.ts index 4aa38270..93f3a758 100644 --- a/src/integrations/runtime/tearDownBff.ts +++ b/src/integrations/runtime/tearDownBff.ts @@ -1,8 +1,10 @@ +import identityProvider from '^/integrations/authentication'; import eventBroker from '^/integrations/eventbroker'; const disconnections = []; if (eventBroker.connected) disconnections.push(eventBroker.disconnect()); +if (identityProvider.connected) disconnections.push(identityProvider.disconnect()); await Promise.allSettled(disconnections); diff --git a/src/integrations/runtime/tearDownGateway.ts b/src/integrations/runtime/tearDownGateway.ts deleted file mode 100644 index f4c67229..00000000 --- a/src/integrations/runtime/tearDownGateway.ts +++ /dev/null @@ -1,4 +0,0 @@ - -import identityProvider from '^/integrations/authentication'; - -if (identityProvider.connected) await identityProvider.disconnect(); From bde92637f6d4181356b92c8fadfe3a6d5e919b72 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Tue, 22 Jul 2025 17:02:59 +0200 Subject: [PATCH 17/25] #375: fixed sonar issues --- src/assets/localhost.css | 2 -- src/integrations/runtime/middlewares/OriginMiddleware.ts | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/assets/localhost.css b/src/assets/localhost.css index 2f86c386..e69de29b 100644 --- a/src/assets/localhost.css +++ b/src/assets/localhost.css @@ -1,2 +0,0 @@ -.ds -{} \ No newline at end of file diff --git a/src/integrations/runtime/middlewares/OriginMiddleware.ts b/src/integrations/runtime/middlewares/OriginMiddleware.ts index 9e9c53c1..963d221e 100644 --- a/src/integrations/runtime/middlewares/OriginMiddleware.ts +++ b/src/integrations/runtime/middlewares/OriginMiddleware.ts @@ -1,6 +1,6 @@ -import type { Middleware, NextHandler, Request } from 'jitar'; -import { BadRequest, type Response } from 'jitar'; +import type { Middleware, NextHandler, Request, Response } from 'jitar'; +import { BadRequest } from 'jitar'; import type { ValidationSchema } from '^/integrations/validation'; import validator from '^/integrations/validation'; From 23d5422666aea9cebafe2b54b3f7c56e52784f91 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Tue, 22 Jul 2025 21:59:39 +0200 Subject: [PATCH 18/25] #375: added tenant information for more bff functions --- src/domain/creator/getById/CreatorNotFound.ts | 2 +- .../creator/updateNickname/retrieveByNickname.ts | 7 +++++-- .../creator/updateNickname/updateNickname.ts | 6 ++++-- src/domain/notification/getById/getById.ts | 4 +++- .../getByIdAggregated/getByIdAggregated.ts | 2 +- src/domain/notification/notify/createdPost.ts | 4 ++-- src/domain/notification/notify/ratedPost.ts | 4 ++-- src/domain/notification/notify/subscriptions.ts | 4 ++-- src/domain/post/create/create.ts | 2 +- src/domain/post/create/publish.ts | 4 ++-- src/domain/post/create/types.ts | 1 + src/domain/post/getById/getById.ts | 7 ++++--- .../post/getByIdAggregated/getByIdAggregated.ts | 2 +- src/domain/post/getByParent/getByParent.ts | 9 +++++---- .../getByParentAggregated/getByParentAggregated.ts | 2 +- src/domain/post/remove/remove.ts | 5 +++-- src/domain/rating/toggle/publish.ts | 4 ++-- src/domain/rating/toggle/switchOff.ts | 4 ++-- src/domain/rating/toggle/switchOn.ts | 4 ++-- src/domain/rating/toggle/toggle.ts | 7 ++++--- src/domain/rating/toggle/types.ts | 1 + src/domain/relation/establish/establish.ts | 6 +++++- src/webui/features/hooks/useEstablishRelation.ts | 3 ++- src/webui/features/hooks/useRemovePost.ts | 3 ++- src/webui/features/hooks/useTogglePostRating.ts | 3 ++- src/webui/features/hooks/useUpdateNickname.ts | 3 ++- test/domain/creator/fixtures/index.ts | 1 + test/domain/creator/fixtures/records.fixture.ts | 3 ++- test/domain/creator/fixtures/tenants.fixture.ts | 4 ++++ test/domain/creator/updateNickname.spec.ts | 6 +++--- .../notification/fixtures/records.fixture.ts | 6 +++--- test/domain/post/remove.spec.ts | 8 ++++---- test/domain/rating/fixtures/index.ts | 1 + test/domain/rating/fixtures/tenants.fixture.ts | 4 ++++ test/domain/rating/toggle.spec.ts | 6 +++--- test/domain/relation/establish.spec.ts | 14 +++----------- 36 files changed, 90 insertions(+), 66 deletions(-) create mode 100644 test/domain/creator/fixtures/tenants.fixture.ts create mode 100644 test/domain/rating/fixtures/tenants.fixture.ts diff --git a/src/domain/creator/getById/CreatorNotFound.ts b/src/domain/creator/getById/CreatorNotFound.ts index 50bc8c0c..a57252e2 100644 --- a/src/domain/creator/getById/CreatorNotFound.ts +++ b/src/domain/creator/getById/CreatorNotFound.ts @@ -5,6 +5,6 @@ export default class CreatorNotFound extends NotFound { constructor(id: string, tenantId: string) { - super(`No creator for id: ${id} and tenant ${tenantId}`); + super(`No creator for id: ${id} and tenant '${tenantId}'`); } } diff --git a/src/domain/creator/updateNickname/retrieveByNickname.ts b/src/domain/creator/updateNickname/retrieveByNickname.ts index 27201487..d0d8caf2 100644 --- a/src/domain/creator/updateNickname/retrieveByNickname.ts +++ b/src/domain/creator/updateNickname/retrieveByNickname.ts @@ -4,9 +4,12 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function retrieveByNickname(nickname: string): Promise +export default async function retrieveByNickname(tenantId: string, nickname: string): Promise { - const query = { nickname: { EQUALS: nickname } }; + const query = { + nickname: { EQUALS: nickname }, + tenantId: { EQUALS: tenantId } + }; return database.findRecord(RECORD_TYPE, query) as Promise; } diff --git a/src/domain/creator/updateNickname/updateNickname.ts b/src/domain/creator/updateNickname/updateNickname.ts index 21696e69..1a15487e 100644 --- a/src/domain/creator/updateNickname/updateNickname.ts +++ b/src/domain/creator/updateNickname/updateNickname.ts @@ -1,16 +1,18 @@ import type { Requester } from '^/domain/authentication'; +import type { Tenant } from '^/domain/tenant'; import cleanNickname from '../cleanNickname'; import update from '../update'; import NicknameAlreadyExists from './NicknameAlreadyExists'; import retrieveByNickname from './retrieveByNickname'; -export default async function updateNickname(requester: Requester, nickname: string): Promise + +export default async function updateNickname(requester: Requester, tenant: Tenant, nickname: string): Promise { const cleanedNickname = cleanNickname(nickname); - const data = await retrieveByNickname(cleanedNickname); + const data = await retrieveByNickname(tenant.id, cleanedNickname); if (data !== undefined) { diff --git a/src/domain/notification/getById/getById.ts b/src/domain/notification/getById/getById.ts index 4f4f52db..cd18b6e3 100644 --- a/src/domain/notification/getById/getById.ts +++ b/src/domain/notification/getById/getById.ts @@ -5,12 +5,14 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getById(id: string): Promise +export default async function getById(receiverId: string, id: string): Promise { const query: RecordQuery = { id: { EQUALS: id }, + receiverId: { EQUALS: receiverId }, deleted: { EQUALS: false } }; + return database.findRecord(RECORD_TYPE, query) as Promise; } diff --git a/src/domain/notification/getByIdAggregated/getByIdAggregated.ts b/src/domain/notification/getByIdAggregated/getByIdAggregated.ts index 6b49d7f8..35add7b3 100644 --- a/src/domain/notification/getByIdAggregated/getByIdAggregated.ts +++ b/src/domain/notification/getByIdAggregated/getByIdAggregated.ts @@ -8,7 +8,7 @@ import getById from '../getById'; export default async function getByIdAggregated(requester: Requester, tenant: Tenant, id: string): Promise { - const data = await getById(id); + const data = await getById(requester.id, id); return aggregate(requester, tenant, data); } diff --git a/src/domain/notification/notify/createdPost.ts b/src/domain/notification/notify/createdPost.ts index 70036001..476c3bc3 100644 --- a/src/domain/notification/notify/createdPost.ts +++ b/src/domain/notification/notify/createdPost.ts @@ -4,14 +4,14 @@ import getPost from '^/domain/post/getById'; import create from '../create'; import { Types } from '../definitions'; -export default async function createdPost(creatorId: string, postId: string, parentId?: string): Promise +export default async function createdPost(tenantId: string, creatorId: string, postId: string, parentId?: string): Promise { if (parentId === undefined) { return; } - const parentPost = await getPost(parentId); + const parentPost = await getPost(tenantId, parentId); return create(Types.REACTED_TO_POST, creatorId, parentPost.creatorId, postId); } diff --git a/src/domain/notification/notify/ratedPost.ts b/src/domain/notification/notify/ratedPost.ts index 8c008037..e787fcd6 100644 --- a/src/domain/notification/notify/ratedPost.ts +++ b/src/domain/notification/notify/ratedPost.ts @@ -4,14 +4,14 @@ import getPost from '^/domain/post/getById'; import create from '../create'; import { Types } from '../definitions'; -export default async function ratedPost(creatorId: string, postId: string, rated: boolean): Promise +export default async function ratedPost(tenantId: string, creatorId: string, postId: string, rated: boolean): Promise { if (rated === false) { return; } - const post = await getPost(postId); + const post = await getPost(tenantId, postId); return create(Types.RATED_POST, creatorId, post.creatorId, postId); } diff --git a/src/domain/notification/notify/subscriptions.ts b/src/domain/notification/notify/subscriptions.ts index 884fc682..66c87f36 100644 --- a/src/domain/notification/notify/subscriptions.ts +++ b/src/domain/notification/notify/subscriptions.ts @@ -12,8 +12,8 @@ import startedFollowing from './startedFollowing'; async function subscribe(): Promise { await Promise.all([ - subscribeToPostRated(({ creatorId, postId, rated }) => ratedPost(creatorId, postId, rated)), - subscribeToPostCreated(({ creatorId, postId, parentId }) => reactedToPost(creatorId, postId, parentId)), + subscribeToPostRated(({ tenantId, creatorId, postId, rated }) => ratedPost(tenantId, creatorId, postId, rated)), + subscribeToPostCreated(({ tenantId, creatorId, postId, parentId }) => reactedToPost(tenantId, creatorId, postId, parentId)), subscribeToRelationEstablished(({ followerId, followingId }) => startedFollowing(followerId, followingId)), subscribeToPostRemoved(({ postId }) => removedPost(postId)), ]); diff --git a/src/domain/post/create/create.ts b/src/domain/post/create/create.ts index 41eee454..57df91de 100644 --- a/src/domain/post/create/create.ts +++ b/src/domain/post/create/create.ts @@ -19,7 +19,7 @@ export default async function create(creatorId: string, tenantId: string, comicI postId = await insertData(data); - await publish(creatorId, postId, parentId); + await publish(tenantId, creatorId, postId, parentId); return postId; } diff --git a/src/domain/post/create/publish.ts b/src/domain/post/create/publish.ts index a9eefc41..86883d9a 100644 --- a/src/domain/post/create/publish.ts +++ b/src/domain/post/create/publish.ts @@ -5,12 +5,12 @@ import { EVENT_CHANNEL } from '../definitions'; import { EVENT_NAME } from './definitions'; import type { CreatedPublication } from './types'; -export default async function publish(creatorId: string, postId: string, parentId?: string): Promise +export default async function publish(tenantId: string, creatorId: string, postId: string, parentId?: string): Promise { const publication: CreatedPublication = { channel: EVENT_CHANNEL, name: EVENT_NAME, - data: { creatorId, postId, parentId } + data: { tenantId, creatorId, postId, parentId } }; return eventBroker.publish(publication); diff --git a/src/domain/post/create/types.ts b/src/domain/post/create/types.ts index 49fc00a9..d5c1da1a 100644 --- a/src/domain/post/create/types.ts +++ b/src/domain/post/create/types.ts @@ -6,6 +6,7 @@ import type { DataModel } from '../types'; export type ValidationModel = Pick; export type CreatedEventData = { + tenantId: string; creatorId: string; postId: string; parentId?: string; diff --git a/src/domain/post/getById/getById.ts b/src/domain/post/getById/getById.ts index 9a8f3288..b4658a00 100644 --- a/src/domain/post/getById/getById.ts +++ b/src/domain/post/getById/getById.ts @@ -6,12 +6,13 @@ import { RECORD_TYPE } from '../definitions'; import PostNotFound from '../PostNotFound'; import type { DataModel } from '../types'; -export default async function getById(id: string): Promise +export default async function getById(tenantId: string, id: string): Promise { const query: RecordQuery = { - id: { 'EQUALS': id }, - deleted: { 'EQUALS': false } + id: { EQUALS: id }, + tenantId: { EQUALS: tenantId }, + deleted: { EQUALS: false } }; const record = await database.findRecord(RECORD_TYPE, query); diff --git a/src/domain/post/getByIdAggregated/getByIdAggregated.ts b/src/domain/post/getByIdAggregated/getByIdAggregated.ts index 1807a1df..da48444e 100644 --- a/src/domain/post/getByIdAggregated/getByIdAggregated.ts +++ b/src/domain/post/getByIdAggregated/getByIdAggregated.ts @@ -10,7 +10,7 @@ export { type AggregatedData }; export default async function getByIdAggregated(requester: Requester, tenant: Tenant, id: string): Promise { - const data = await getById(id); + const data = await getById(tenant.id, id); return aggregate(requester, tenant, data); } diff --git a/src/domain/post/getByParent/getByParent.ts b/src/domain/post/getByParent/getByParent.ts index 600083ff..66fcf029 100644 --- a/src/domain/post/getByParent/getByParent.ts +++ b/src/domain/post/getByParent/getByParent.ts @@ -1,16 +1,17 @@ -import type { RecordQuery, RecordSort} from '^/integrations/database'; +import type { RecordQuery, RecordSort } from '^/integrations/database'; import database, { SortDirections } from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getByParent(parentId: string, limit: number, offset: number): Promise +export default async function getByParent(tenantId: string, parentId: string, limit: number, offset: number): Promise { const query: RecordQuery = { - parentId: { 'EQUALS': parentId }, - deleted: { 'EQUALS': false } + parentId: { EQUALS: parentId }, + tenantId: { EQUALS: tenantId }, + deleted: { EQUALS: false } }; const sort: RecordSort = { createdAt: SortDirections.DESCENDING }; diff --git a/src/domain/post/getByParentAggregated/getByParentAggregated.ts b/src/domain/post/getByParentAggregated/getByParentAggregated.ts index 05b75445..ad1d5687 100644 --- a/src/domain/post/getByParentAggregated/getByParentAggregated.ts +++ b/src/domain/post/getByParentAggregated/getByParentAggregated.ts @@ -10,7 +10,7 @@ import getByParent from '../getByParent'; export default async function getByParentAggregated(requester: Requester, tenant: Tenant, postId: string, range: Range): Promise { - const data = await getByParent(postId, range.limit, range.offset); + const data = await getByParent(tenant.id, postId, range.limit, range.offset); const aggregates = data.map(item => aggregate(requester, tenant, item)); diff --git a/src/domain/post/remove/remove.ts b/src/domain/post/remove/remove.ts index 278631c6..70f0e810 100644 --- a/src/domain/post/remove/remove.ts +++ b/src/domain/post/remove/remove.ts @@ -2,6 +2,7 @@ import logger from '^/integrations/logging'; import type { Requester } from '^/domain/authentication'; +import type { Tenant } from '^/domain/tenant'; import getById from '../getById'; import PostNotFound from '../PostNotFound'; @@ -10,7 +11,7 @@ import isNotOwner from './isNotOwner'; import publish from './publish'; import undeleteData from './undeleteData'; -export default async function remove(requester: Requester, id: string): Promise +export default async function remove(requester: Requester, tenant: Tenant, id: string): Promise { // We only delete the post itself and do not cascade it towards it's children as it doesn't add // any value, and it would make the code more complex. @@ -19,7 +20,7 @@ export default async function remove(requester: Requester, id: string): Promise< try { - const post = await getById(id); + const post = await getById(tenant.id, id); if (isNotOwner(post, requester.id)) { diff --git a/src/domain/rating/toggle/publish.ts b/src/domain/rating/toggle/publish.ts index edd6adb5..0869bc6b 100644 --- a/src/domain/rating/toggle/publish.ts +++ b/src/domain/rating/toggle/publish.ts @@ -5,12 +5,12 @@ import { EVENT_CHANNEL } from '../definitions'; import { EVENT_NAME } from './definitions'; import type { ToggledPublication } from './types'; -export default async function publish(creatorId: string, postId: string, rated: boolean): Promise +export default async function publish(tenantId: string, creatorId: string, postId: string, rated: boolean): Promise { const publication: ToggledPublication = { channel: EVENT_CHANNEL, name: EVENT_NAME, - data: { creatorId, postId, rated } + data: { tenantId, creatorId, postId, rated } }; return eventBroker.publish(publication); diff --git a/src/domain/rating/toggle/switchOff.ts b/src/domain/rating/toggle/switchOff.ts index fb7ef1cb..d82acb7b 100644 --- a/src/domain/rating/toggle/switchOff.ts +++ b/src/domain/rating/toggle/switchOff.ts @@ -4,13 +4,13 @@ import erase from '../erase'; import type { DataModel } from '../types'; import publish from './publish'; -export default async function switchOff(rating: DataModel): Promise +export default async function switchOff(tenantId: string, rating: DataModel): Promise { await erase(rating.id); try { - await publish(rating.creatorId, rating.postId, false); + await publish(tenantId, rating.creatorId, rating.postId, false); return false; } diff --git a/src/domain/rating/toggle/switchOn.ts b/src/domain/rating/toggle/switchOn.ts index f04942a0..5819f5be 100644 --- a/src/domain/rating/toggle/switchOn.ts +++ b/src/domain/rating/toggle/switchOn.ts @@ -3,13 +3,13 @@ import create from '../create'; import erase from '../erase'; import publish from './publish'; -export default async function switchOn(creatorId: string, postId: string): Promise +export default async function switchOn(tenantId: string, creatorId: string, postId: string): Promise { const id = await create(creatorId, postId); try { - await publish(creatorId, postId, true); + await publish(tenantId, creatorId, postId, true); return true; } diff --git a/src/domain/rating/toggle/toggle.ts b/src/domain/rating/toggle/toggle.ts index 9e3f7709..c3e5fee3 100644 --- a/src/domain/rating/toggle/toggle.ts +++ b/src/domain/rating/toggle/toggle.ts @@ -1,15 +1,16 @@ import type { Requester } from '^/domain/authentication'; +import type { Tenant } from '^/domain/tenant'; import getData from './getData'; import switchOff from './switchOff'; import switchOn from './switchOn'; -export default async function toggle(requester: Requester, postId: string): Promise +export default async function toggle(requester: Requester, tenant: Tenant, postId: string): Promise { const data = await getData(requester.id, postId); return data === undefined - ? switchOn(requester.id, postId) - : switchOff(data); + ? switchOn(tenant.id, requester.id, postId) + : switchOff(tenant.id, data); } diff --git a/src/domain/rating/toggle/types.ts b/src/domain/rating/toggle/types.ts index b80162af..ae7673c9 100644 --- a/src/domain/rating/toggle/types.ts +++ b/src/domain/rating/toggle/types.ts @@ -2,6 +2,7 @@ import type { Publication, Subscription } from '^/integrations/eventbroker'; export type ToggledEventData = { + tenantId: string; creatorId: string; postId: string; rated: boolean; diff --git a/src/domain/relation/establish/establish.ts b/src/domain/relation/establish/establish.ts index 5716e0d6..523f35cc 100644 --- a/src/domain/relation/establish/establish.ts +++ b/src/domain/relation/establish/establish.ts @@ -2,6 +2,8 @@ import logger from '^/integrations/logging'; import type { Requester } from '^/domain/authentication'; +import getCreator from '^/domain/creator/getById'; +import type { Tenant } from '^/domain/tenant'; import create from '../create'; import erase from '../erase'; @@ -9,12 +11,14 @@ import exists from '../exists'; import publish from './publish'; import RelationAlreadyExists from './RelationAlreadyExists'; -export default async function establish(requester: Requester, followingId: string): Promise +export default async function establish(requester: Requester, tenant: Tenant, followingId: string): Promise { let id; try { + await getCreator(followingId, tenant.id); + const relationExists = await exists(requester.id, followingId); if (relationExists) diff --git a/src/webui/features/hooks/useEstablishRelation.ts b/src/webui/features/hooks/useEstablishRelation.ts index b72fc8ee..efaf2f1c 100644 --- a/src/webui/features/hooks/useEstablishRelation.ts +++ b/src/webui/features/hooks/useEstablishRelation.ts @@ -4,12 +4,13 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedRelationData } from '^/domain/relation/aggregate'; import establishRelation from '^/domain/relation/establish'; +import { tenant } from '^/domain/tenant'; export default function useEstablishRelation() { return useCallback((relation: AggregatedRelationData) => { - return establishRelation(requester, relation.following.id); + return establishRelation(requester, tenant, relation.following.id); }, []); } diff --git a/src/webui/features/hooks/useRemovePost.ts b/src/webui/features/hooks/useRemovePost.ts index 13398a8e..fca47d40 100644 --- a/src/webui/features/hooks/useRemovePost.ts +++ b/src/webui/features/hooks/useRemovePost.ts @@ -7,6 +7,7 @@ import { useAppContext } from '^/webui/contexts'; import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedPostData } from '^/domain/post/aggregate'; import remove from '^/domain/post/remove'; +import { tenant } from '^/domain/tenant'; export default function useRemovePost() { @@ -15,7 +16,7 @@ export default function useRemovePost() return useCallback(async (post: AggregatedPostData) => { - await remove(requester, post.id); + await remove(requester, tenant, post.id); navigate(`/profile/${identity?.nickname}`); diff --git a/src/webui/features/hooks/useTogglePostRating.ts b/src/webui/features/hooks/useTogglePostRating.ts index 759a21d8..b37687bb 100644 --- a/src/webui/features/hooks/useTogglePostRating.ts +++ b/src/webui/features/hooks/useTogglePostRating.ts @@ -4,12 +4,13 @@ import { useCallback } from 'react'; import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedPostData } from '^/domain/post/aggregate'; import toggleRating from '^/domain/rating/toggle'; +import { tenant } from '^/domain/tenant'; export default function useTogglePostRating() { return useCallback((post: AggregatedPostData) => { - return toggleRating(requester, post.id); + return toggleRating(requester, tenant, post.id); }, []); } diff --git a/src/webui/features/hooks/useUpdateNickname.ts b/src/webui/features/hooks/useUpdateNickname.ts index 78a8fb2e..467f2bf2 100644 --- a/src/webui/features/hooks/useUpdateNickname.ts +++ b/src/webui/features/hooks/useUpdateNickname.ts @@ -5,6 +5,7 @@ import { requester } from '^/domain/authentication'; import type { AggregatedData as AggregatedCreatorData } from '^/domain/creator/aggregate'; import updateNickname from '^/domain/creator/updateNickname'; import NicknameAlreadyExists from '^/domain/creator/updateNickname/NicknameAlreadyExists'; +import { tenant } from '^/domain/tenant'; import { useAppContext } from '^/webui/contexts'; @@ -17,7 +18,7 @@ export default function useUpdateNickname() { try { - await updateNickname(requester, nickname); + await updateNickname(requester, tenant, nickname); setIdentity({ ...identity, nickname } as AggregatedCreatorData); setAlreadyInUse(false); diff --git a/test/domain/creator/fixtures/index.ts b/test/domain/creator/fixtures/index.ts index ad06c48a..0b405ed6 100644 --- a/test/domain/creator/fixtures/index.ts +++ b/test/domain/creator/fixtures/index.ts @@ -2,4 +2,5 @@ export * from './databases.fixture'; export * from './records.fixture'; export * from './requesters.fixture'; +export * from './tenants.fixture'; export * from './values.fixture'; diff --git a/test/domain/creator/fixtures/records.fixture.ts b/test/domain/creator/fixtures/records.fixture.ts index 18b3fc36..3fd20407 100644 --- a/test/domain/creator/fixtures/records.fixture.ts +++ b/test/domain/creator/fixtures/records.fixture.ts @@ -3,9 +3,10 @@ import type { RecordData } from '^/integrations/database'; import type { DataModel as CreatorDataModel } from '^/domain/creator'; +import { TENANTS } from './tenants.fixture'; import { VALUES } from './values.fixture'; -const DEFAULT_DATA = { portraitId: undefined, joinedAt: new Date().toISOString() }; +const DEFAULT_DATA = { tenantId: TENANTS.default.id, portraitId: undefined, joinedAt: new Date().toISOString() }; const CREATORS: CreatorDataModel[] = [ { id: VALUES.IDS.CREATOR, fullName: VALUES.FULL_NAMES.CREATOR, nickname: VALUES.NICKNAMES.CREATOR, email: VALUES.EMAILS.CREATOR, ...DEFAULT_DATA } diff --git a/test/domain/creator/fixtures/tenants.fixture.ts b/test/domain/creator/fixtures/tenants.fixture.ts new file mode 100644 index 00000000..ea725060 --- /dev/null +++ b/test/domain/creator/fixtures/tenants.fixture.ts @@ -0,0 +1,4 @@ + +import { tenant } from '^/domain/tenant'; + +export const TENANTS = { default: tenant }; diff --git a/test/domain/creator/updateNickname.spec.ts b/test/domain/creator/updateNickname.spec.ts index 6bc36368..8bd2d537 100644 --- a/test/domain/creator/updateNickname.spec.ts +++ b/test/domain/creator/updateNickname.spec.ts @@ -6,7 +6,7 @@ import updateNickname, { NicknameAlreadyExists } from '^/domain/creator/updateNi import database from '^/integrations/database'; -import { DATABASES, REQUESTERS, VALUES } from './fixtures'; +import { DATABASES, REQUESTERS, TENANTS, VALUES } from './fixtures'; beforeEach(async () => { @@ -17,7 +17,7 @@ describe('domain/creator/updateNickname', () => { it('should update the nickname', async () => { - await updateNickname(REQUESTERS.CREATOR, VALUES.NICKNAMES.NEW); + await updateNickname(REQUESTERS.CREATOR, TENANTS.default, VALUES.NICKNAMES.NEW); const creator = await database.readRecord(CREATOR_RECORD_TYPE, REQUESTERS.CREATOR.id); expect(creator?.nickname).toBe(VALUES.NICKNAMES.NEW); @@ -25,7 +25,7 @@ describe('domain/creator/updateNickname', () => it('should NOT update the nickname because of a duplicate', async () => { - const promise = updateNickname(REQUESTERS.CREATOR, VALUES.NICKNAMES.DUPLICATE); + const promise = updateNickname(REQUESTERS.CREATOR, TENANTS.default, VALUES.NICKNAMES.DUPLICATE); await expect(promise).rejects.toStrictEqual(new NicknameAlreadyExists(VALUES.NICKNAMES.DUPLICATE)); }); diff --git a/test/domain/notification/fixtures/records.fixture.ts b/test/domain/notification/fixtures/records.fixture.ts index 65c8d300..c94d6806 100644 --- a/test/domain/notification/fixtures/records.fixture.ts +++ b/test/domain/notification/fixtures/records.fixture.ts @@ -41,9 +41,9 @@ const COMICS: ComicDataModel[] = [ ]; const POSTS: (PostDataModel & { deleted: boolean; })[] = [ - { id: VALUES.IDS.POST_RATED, creatorId: VALUES.IDS.CREATOR1, comicId: VALUES.IDS.COMIC, createdAt: new Date().toISOString(), deleted: false }, - { id: VALUES.IDS.POST_DELETED, creatorId: VALUES.IDS.CREATOR1, comicId: VALUES.IDS.COMIC, createdAt: new Date().toISOString(), deleted: true }, - { id: VALUES.IDS.REACTION_LIKED, creatorId: VALUES.IDS.CREATOR2, comicId: VALUES.IDS.COMIC, parentId: VALUES.IDS.POST_RATED, createdAt: new Date().toISOString(), deleted: false } + { id: VALUES.IDS.POST_RATED, creatorId: VALUES.IDS.CREATOR1, comicId: VALUES.IDS.COMIC, tenantId: TENANTS.default.id, createdAt: new Date().toISOString(), deleted: false }, + { id: VALUES.IDS.POST_DELETED, creatorId: VALUES.IDS.CREATOR1, comicId: VALUES.IDS.COMIC, tenantId: TENANTS.default.id, createdAt: new Date().toISOString(), deleted: true }, + { id: VALUES.IDS.REACTION_LIKED, creatorId: VALUES.IDS.CREATOR2, comicId: VALUES.IDS.COMIC, tenantId: TENANTS.default.id, parentId: VALUES.IDS.POST_RATED, createdAt: new Date().toISOString(), deleted: false } ]; const POST_METRICS: PostMetricsModel[] = [ diff --git a/test/domain/post/remove.spec.ts b/test/domain/post/remove.spec.ts index b7f97167..68adcb06 100644 --- a/test/domain/post/remove.spec.ts +++ b/test/domain/post/remove.spec.ts @@ -6,7 +6,7 @@ import remove from '^/domain/post/remove'; import database from '^/integrations/database'; -import { DATABASES, REQUESTERS, VALUES } from './fixtures'; +import { DATABASES, REQUESTERS, TENANTS, VALUES } from './fixtures'; beforeEach(async () => { @@ -17,7 +17,7 @@ describe('domain/post/remove', () => { it('should soft delete a post', async () => { - await remove(REQUESTERS.CREATOR1, VALUES.IDS.POST_RATED); + await remove(REQUESTERS.CREATOR1, TENANTS.default, VALUES.IDS.POST_RATED); const reaction = await database.readRecord(RECORD_TYPE, VALUES.IDS.POST_RATED); expect(reaction.deleted).toBeTruthy(); @@ -25,13 +25,13 @@ describe('domain/post/remove', () => it('should not delete an already deleted post', async () => { - const promise = remove(REQUESTERS.CREATOR1, VALUES.IDS.POST_DELETED); + const promise = remove(REQUESTERS.CREATOR1, TENANTS.default, VALUES.IDS.POST_DELETED); await expect(promise).rejects.toThrow(PostNotFound); }); it('should not delete a post from another creator', async () => { - const promise = remove(REQUESTERS.VIEWER, VALUES.IDS.POST_RATED); + const promise = remove(REQUESTERS.VIEWER, TENANTS.default, VALUES.IDS.POST_RATED); await expect(promise).rejects.toThrow(PostNotFound); }); }); diff --git a/test/domain/rating/fixtures/index.ts b/test/domain/rating/fixtures/index.ts index ad06c48a..0b405ed6 100644 --- a/test/domain/rating/fixtures/index.ts +++ b/test/domain/rating/fixtures/index.ts @@ -2,4 +2,5 @@ export * from './databases.fixture'; export * from './records.fixture'; export * from './requesters.fixture'; +export * from './tenants.fixture'; export * from './values.fixture'; diff --git a/test/domain/rating/fixtures/tenants.fixture.ts b/test/domain/rating/fixtures/tenants.fixture.ts new file mode 100644 index 00000000..ea725060 --- /dev/null +++ b/test/domain/rating/fixtures/tenants.fixture.ts @@ -0,0 +1,4 @@ + +import { tenant } from '^/domain/tenant'; + +export const TENANTS = { default: tenant }; diff --git a/test/domain/rating/toggle.spec.ts b/test/domain/rating/toggle.spec.ts index 4f93773b..38f5e3ce 100644 --- a/test/domain/rating/toggle.spec.ts +++ b/test/domain/rating/toggle.spec.ts @@ -3,7 +3,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; import toggle from '^/domain/rating/toggle'; -import { DATABASES, REQUESTERS, VALUES } from './fixtures'; +import { DATABASES, REQUESTERS, TENANTS, VALUES } from './fixtures'; beforeEach(async () => { @@ -14,13 +14,13 @@ describe('domain/post/toggleRating', () => { it('should add a rating', async () => { - const isRated = await toggle(REQUESTERS.CREATOR1, VALUES.IDS.POST_UNRATED); + const isRated = await toggle(REQUESTERS.CREATOR1, TENANTS.default, VALUES.IDS.POST_UNRATED); expect(isRated).toBeTruthy(); }); it('should remove a rating', async () => { - const isRated = await toggle(REQUESTERS.CREATOR1, VALUES.IDS.POST_RATED); + const isRated = await toggle(REQUESTERS.CREATOR1, TENANTS.default, VALUES.IDS.POST_RATED); expect(isRated).toBeFalsy(); }); }); diff --git a/test/domain/relation/establish.spec.ts b/test/domain/relation/establish.spec.ts index 1e37cc9d..ab88d20c 100644 --- a/test/domain/relation/establish.spec.ts +++ b/test/domain/relation/establish.spec.ts @@ -2,12 +2,11 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { RECORD_TYPE as RELATION_RECORD_TYPE } from '^/domain/relation'; -import { InvalidRelation } from '^/domain/relation/create'; import establish, { RelationAlreadyExists } from '^/domain/relation/establish'; import database from '^/integrations/database'; -import { DATABASES, QUERIES, REQUESTERS, VALUES } from './fixtures'; +import { DATABASES, QUERIES, REQUESTERS, TENANTS, VALUES } from './fixtures'; beforeEach(async () => { @@ -18,7 +17,7 @@ describe('domain/relation/establish', () => { it('should establish a relation', async () => { - await establish(REQUESTERS.SECOND, VALUES.IDS.CREATOR1); + await establish(REQUESTERS.SECOND, TENANTS.default, VALUES.IDS.CREATOR1); const relation = await database.findRecord(RELATION_RECORD_TYPE, QUERIES.EXISTING_RELATION); expect(relation?.id).toBeDefined(); @@ -26,15 +25,8 @@ describe('domain/relation/establish', () => it('should NOT establish a duplicate relation', async () => { - const promise = establish(REQUESTERS.FIRST, VALUES.IDS.CREATOR2); + const promise = establish(REQUESTERS.FIRST, TENANTS.default, VALUES.IDS.CREATOR2); await expect(promise).rejects.toStrictEqual(new RelationAlreadyExists()); }); - - it('should fail when invalid data is provided', async () => - { - const promise = establish(REQUESTERS.FIRST, VALUES.IDS.INVALID); - - await expect(promise).rejects.toThrow(InvalidRelation); - }); }); From 918ad2d5eecbf437b781473ae3a0ec57fbf1181e Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Tue, 22 Jul 2025 21:59:59 +0200 Subject: [PATCH 19/25] #375: SameSite to None for social logins --- src/integrations/runtime/middlewares/OriginMiddleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/integrations/runtime/middlewares/OriginMiddleware.ts b/src/integrations/runtime/middlewares/OriginMiddleware.ts index 963d221e..01ceabb0 100644 --- a/src/integrations/runtime/middlewares/OriginMiddleware.ts +++ b/src/integrations/runtime/middlewares/OriginMiddleware.ts @@ -85,6 +85,6 @@ export default class OriginMiddleware implements Middleware #setOriginCookie(response: Response, origin: string): void { - response.setHeader('Set-Cookie', `${TENANT_COOKIE_NAME}=${origin}; Path=/; HttpOnly=true; SameSite=Strict; Secure`); + response.setHeader('Set-Cookie', `${TENANT_COOKIE_NAME}=${origin}; Path=/; HttpOnly=true; SameSite=None; Secure`); } } From 07eb225073be6da8b305ecfac59e0084c9d33e8d Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Tue, 22 Jul 2025 22:15:39 +0200 Subject: [PATCH 20/25] #375: fixed invalid segment configuration --- segments/bff.json | 9 +++++++-- segments/reads.json | 6 +++++- segments/writes.json | 4 +++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/segments/bff.json b/segments/bff.json index 1abc8b9c..9b733ecc 100644 --- a/segments/bff.json +++ b/segments/bff.json @@ -3,6 +3,8 @@ "./domain/authentication/login": { "default": { "access": "public" } }, "./domain/authentication/logout": { "default": { "access": "public" } }, + "./domain/creator/aggregate": { "default": { "access": "private" }}, + "./domain/creator/getByIdAggregated": { "default": { "access": "private" } }, "./domain/creator/getByNicknameAggregated": { "default": { "access": "public" } }, "./domain/creator/getMeAggregated": { "default": { "access": "public" } }, "./domain/creator/updateFullName": { "default": { "access": "public" } }, @@ -13,18 +15,20 @@ "./domain/creator.metrics/updateFollowing": { "subscriptions": { "access": "private" } }, "./domain/creator.metrics/updatePosts": { "subscriptions": { "access": "private" } }, + "./domain/notification/aggregate": { "default": { "access": "private" } }, "./domain/notification/notify": { "subscriptions": { "access": "private" } }, "./domain/notification/getRecentAggregated": { "default": { "access": "public" } }, "./domain/notification/getByIdAggregated": { "default": { "access": "public" } }, + "./domain/post/aggregate": { "default": { "access": "private" } }, "./domain/post/create": { "default": { "access": "private" }, "subscribe": { "access": "private" } }, "./domain/post/createWithComic": { "default": { "access": "public" } }, "./domain/post/createWithComment": { "default": { "access": "public" } }, + "./domain/post/getByCreatorAggregated": { "default": { "access": "public" } }, + "./domain/post/getByFollowingAggregated": { "default": { "access": "public" } }, "./domain/post/exploreAggregated": { "default": { "access": "public" } }, "./domain/post/getByIdAggregated": { "default": { "access": "public" } }, "./domain/post/getByParentAggregated": { "default": { "access": "public" } }, - "./domain/post/getByCreatorAggregated": { "default": { "access": "public" } }, - "./domain/post/getByFollowingAggregated": { "default": { "access": "public" } }, "./domain/post/getRecommendedAggregated": { "default": { "access": "public"}}, "./domain/post/remove": { "default": { "access": "public" }, "subscribe": { "access": "private" } }, @@ -34,6 +38,7 @@ "./domain/rating/toggle": { "default": { "access": "public" }, "subscribe": { "access": "private" } }, + "./domain/relation/aggregate": { "default": { "access": "private" }}, "./domain/relation/exploreAggregated": { "default": { "access": "public" } }, "./domain/relation/establish": { "default": { "access": "public" }, "subscribe": { "access": "private" } }, "./domain/relation/getAggregated": { "default": { "access": "public" } }, diff --git a/segments/reads.json b/segments/reads.json index 3e8dce19..5bb1b0a5 100644 --- a/segments/reads.json +++ b/segments/reads.json @@ -3,10 +3,14 @@ "./domain/comment/getById": { "default": { "access": "protected" } }, - "./domain/creator/getById": { "default": { "access": "protected" } }, + "./domain/creator/generateNickname/retrieveByNickname": { "default": { "access": "protected" } }, + "./domain/creator/generateNickname/retrieveByStartNickname": { "default": { "access": "protected" } }, "./domain/creator/getByEmail": { "default": { "access": "protected" } }, + "./domain/creator/getById": { "default": { "access": "protected" } }, "./domain/creator/getByNickname": { "default": { "access": "protected" } }, "./domain/creator/getMe": { "default": { "access": "protected" } }, + "./domain/creator/getOthers": { "default": { "access": "private" }}, + "./domain/creator/updateNickname/retrieveByNickname": { "default": { "access": "protected" } }, "./domain/creator.metrics/getByCreator": { "default": { "access": "protected" } }, diff --git a/segments/writes.json b/segments/writes.json index c1f09fa1..11e4fa36 100644 --- a/segments/writes.json +++ b/segments/writes.json @@ -6,17 +6,19 @@ "./domain/comment/erase": { "default": { "access": "protected" } }, "./domain/creator/create": { "default": { "access": "protected" } }, + "./domain/creator/erase": { "default": { "access": "protected" } }, "./domain/creator/update": { "default": { "access": "protected" } }, "./domain/creator.metrics/create/insertData": { "default": { "access": "protected" } }, "./domain/creator.metrics/update": { "default": { "access": "protected" } }, - "./domain/image/save": { "default": { "access": "protected" } }, "./domain/image/erase": { "default": { "access": "protected" } }, + "./domain/image/save": { "default": { "access": "protected" } }, "./domain/post/create/insertData": { "default": { "access": "protected" } }, "./domain/post/erase": { "default": { "access": "protected" } }, "./domain/post/remove/deleteData": { "default": { "access": "protected" }}, + "./domain/post/remove/undeleteData": { "default": { "access": "protected" }}, "./domain/post/update": { "default": { "access": "protected" } }, "./domain/post.metrics/create/insertData": { "default": { "access": "protected" } }, From 9340c75d9e949daa65f33ee4778eeff41d9d166c Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Fri, 25 Jul 2025 15:57:02 +0100 Subject: [PATCH 21/25] #375: picked up some nitpicks --- segments/reads.json | 2 +- src/domain/definitions.ts | 2 ++ src/domain/tenant/tenant.ts | 4 ++-- .../authentication/implementations/openid/OpenID.ts | 8 ++++++-- src/integrations/runtime/authenticationMiddleware.ts | 4 +++- src/integrations/runtime/middlewares/OriginMiddleware.ts | 2 +- src/integrations/runtime/middlewares/TenantMiddleware.ts | 2 +- src/integrations/runtime/tenantMiddleware.ts | 4 +++- 8 files changed, 19 insertions(+), 9 deletions(-) diff --git a/segments/reads.json b/segments/reads.json index 5bb1b0a5..a5e99a36 100644 --- a/segments/reads.json +++ b/segments/reads.json @@ -34,5 +34,5 @@ "./domain/relation/getFollowers": { "default": { "access": "protected" } }, "./domain/relation/getFollowing": { "default": { "access": "protected" } }, - "./domain/tenant/getByOrigin": { "default": { "access": "protected" } } + "./domain/tenant/getByOrigin": { "default": { "access": "protected" }, "TenantNotFound": { } } } \ No newline at end of file diff --git a/src/domain/definitions.ts b/src/domain/definitions.ts index 83bc06a8..ddf98127 100644 --- a/src/domain/definitions.ts +++ b/src/domain/definitions.ts @@ -1,6 +1,8 @@ import type { Validation } from '^/integrations/validation'; +export const TENANT_BY_ORIGIN_PATH = 'domain/tenant/getByOriginConverted'; + export const SortOrders = { POPULAR: 'popular', RECENT: 'recent' diff --git a/src/domain/tenant/tenant.ts b/src/domain/tenant/tenant.ts index cd6b70b4..664fe246 100644 --- a/src/domain/tenant/tenant.ts +++ b/src/domain/tenant/tenant.ts @@ -2,8 +2,8 @@ import type { Tenant } from './types'; const tenant: Tenant = { - id: 'id', - origin: 'origin' + id: 'default', + origin: 'localhost' }; export default tenant; diff --git a/src/integrations/authentication/implementations/openid/OpenID.ts b/src/integrations/authentication/implementations/openid/OpenID.ts index 6018d5fc..a49e80a0 100644 --- a/src/integrations/authentication/implementations/openid/OpenID.ts +++ b/src/integrations/authentication/implementations/openid/OpenID.ts @@ -75,9 +75,13 @@ export default class OpenID implements IdentityProvider async login(origin: string, data: Record): Promise { const clientConfiguration = this.#getClientConfiguration(); - const currentUrl = new URL(`${this.#providerConfiguration.redirectPath}?session_state=${data.session_state}&iss=${data.iss}&code=${data.code}`, origin); - const tokens = await authorizationCodeGrant(clientConfiguration, currentUrl, { + const url = new URL(this.#providerConfiguration.redirectPath, origin); + url.searchParams.set('session_state', data.session_state as string); + url.searchParams.set('iss', data.iss as string); + url.searchParams.set('code', data.code as string); + + const tokens = await authorizationCodeGrant(clientConfiguration, url, { pkceCodeVerifier: this.#codeVerifier, idTokenExpected: true }); diff --git a/src/integrations/runtime/authenticationMiddleware.ts b/src/integrations/runtime/authenticationMiddleware.ts index 80c361c6..db4475e1 100644 --- a/src/integrations/runtime/authenticationMiddleware.ts +++ b/src/integrations/runtime/authenticationMiddleware.ts @@ -1,6 +1,8 @@ import identityProvider from '^/integrations/authentication'; +import { TENANT_BY_ORIGIN_PATH } from '^/domain/definitions'; + import AuthenticationMiddleware from './middlewares/AuthenticationMiddleware'; const authProcedures = { @@ -11,6 +13,6 @@ const authProcedures = { const redirectPath = process.env.AUTHENTICATION_CLIENT_PATH || 'undefined'; -const whiteList: string[] = ['domain/tenant/getByOriginConverted']; +const whiteList: string[] = [TENANT_BY_ORIGIN_PATH]; export default new AuthenticationMiddleware(identityProvider, authProcedures, redirectPath, whiteList); diff --git a/src/integrations/runtime/middlewares/OriginMiddleware.ts b/src/integrations/runtime/middlewares/OriginMiddleware.ts index 01ceabb0..0db21991 100644 --- a/src/integrations/runtime/middlewares/OriginMiddleware.ts +++ b/src/integrations/runtime/middlewares/OriginMiddleware.ts @@ -36,7 +36,7 @@ export default class OriginMiddleware implements Middleware this.#validateOriginValue(origin); // The origin header is validated and set here for use in other middlewares - request.setHeader('origin', origin as string); + request.setHeader('origin', origin!); const response = await next(); diff --git a/src/integrations/runtime/middlewares/TenantMiddleware.ts b/src/integrations/runtime/middlewares/TenantMiddleware.ts index 6db5c568..6fa5e36d 100644 --- a/src/integrations/runtime/middlewares/TenantMiddleware.ts +++ b/src/integrations/runtime/middlewares/TenantMiddleware.ts @@ -31,7 +31,7 @@ export default class TenantMiddleware implements Middleware const response = await next(); - if (response.status < 500) + if (response.status === 200) { this.#cache.set(origin, response); } diff --git a/src/integrations/runtime/tenantMiddleware.ts b/src/integrations/runtime/tenantMiddleware.ts index e05d64f8..3d4bc2c8 100644 --- a/src/integrations/runtime/tenantMiddleware.ts +++ b/src/integrations/runtime/tenantMiddleware.ts @@ -1,6 +1,8 @@ +import { TENANT_BY_ORIGIN_PATH } from '^/domain/definitions'; + import TenantMiddleware from './middlewares/TenantMiddleware'; -const tenantPath = 'domain/tenant/getByOriginConverted'; +const tenantPath = TENANT_BY_ORIGIN_PATH; export default new TenantMiddleware(tenantPath); From 24714d931169116c931c768a61077598851c31a3 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Fri, 25 Jul 2025 18:06:44 +0100 Subject: [PATCH 22/25] #375: tenant always as first function parameter --- docker/mongodb/init.js | 19 +++++++------------ src/domain/authentication/login/login.ts | 10 +++++----- src/domain/creator/aggregate/types.ts | 2 +- src/domain/creator/create/types.ts | 2 +- src/domain/creator/create/validateData.ts | 6 +++--- .../generateNickname/generateNickname.ts | 6 +++--- .../generateNickname/retrieveByNickname.ts | 6 +++--- .../retrieveByStartNickname.ts | 6 +++--- src/domain/creator/getByEmail/getByEmail.ts | 6 +++--- src/domain/creator/getById/CreatorNotFound.ts | 2 +- src/domain/creator/getById/getById.ts | 11 +++++------ .../getByIdAggregated/getByIdAggregated.ts | 2 +- .../creator/getByNickname/getByNickname.ts | 6 +++--- .../getByNicknameAggregated.ts | 4 ++-- src/domain/creator/getMe/getMe.ts | 4 ++-- .../getMeAggregated/getMeAggregated.ts | 4 ++-- src/domain/creator/getOthers/getOthers.ts | 4 ++-- src/domain/creator/register/register.ts | 2 +- src/domain/creator/types.ts | 2 +- .../updateNickname/retrieveByNickname.ts | 4 ++-- .../creator/updateNickname/updateNickname.ts | 2 +- .../notification/aggregate/aggregate.ts | 6 +++--- .../getByIdAggregated/getByIdAggregated.ts | 4 ++-- .../getRecentAggregated.ts | 4 ++-- src/domain/post/aggregate/aggregate.ts | 4 ++-- src/domain/post/create/create.ts | 4 ++-- src/domain/post/create/createData.ts | 6 +++--- src/domain/post/create/types.ts | 2 +- src/domain/post/create/validateData.ts | 6 +++--- .../post/createWithComic/createWithComic.ts | 4 ++-- .../createWithComment/createWithComment.ts | 4 ++-- src/domain/post/explore/explore.ts | 2 +- src/domain/post/explore/retrieveData.ts | 6 +++--- .../exploreAggregated/exploreAggregated.ts | 6 +++--- .../getByCreatorAggregated.ts | 4 ++-- .../getByFollowingAggregated.ts | 4 ++-- src/domain/post/getById/getById.ts | 2 +- .../getByIdAggregated/getByIdAggregated.ts | 4 ++-- src/domain/post/getByParent/getByParent.ts | 2 +- .../getByParentAggregated.ts | 4 ++-- .../post/getRecommended/getRecommended.ts | 8 ++++---- .../getRecommendedAggregated.ts | 6 +++--- src/domain/post/remove/remove.ts | 2 +- src/domain/post/types.ts | 2 +- src/domain/rating/toggle/toggle.ts | 2 +- src/domain/relation/establish/establish.ts | 4 ++-- src/domain/relation/explore/explore.ts | 2 +- .../exploreAggregated/exploreAggregated.ts | 4 ++-- .../relation/getAggregated/getAggregated.ts | 2 +- .../getFollowersAggregated.ts | 2 +- .../getFollowingAggregated.ts | 2 +- .../tenant/getByOriginConverted/types.ts | 2 +- .../getByOriginConverted/validateData.ts | 2 +- src/webui/editor/model/Bubble.ts | 2 +- src/webui/features/hooks/useAddComicPost.ts | 2 +- .../hooks/useCreatePostComicReaction.ts | 4 ++-- .../hooks/useCreatePostCommentReaction.ts | 4 ++-- src/webui/features/hooks/useCreator.ts | 4 ++-- .../features/hooks/useCreatorFollowers.ts | 2 +- .../features/hooks/useCreatorFollowing.ts | 2 +- src/webui/features/hooks/useCreatorPosts.ts | 2 +- .../features/hooks/useEstablishRelation.ts | 2 +- .../features/hooks/useExploreCreators.ts | 2 +- src/webui/features/hooks/useExplorePosts.ts | 2 +- src/webui/features/hooks/useHighlight.ts | 2 +- src/webui/features/hooks/useIdentify.ts | 2 +- src/webui/features/hooks/useNotifications.ts | 2 +- src/webui/features/hooks/usePost.ts | 2 +- src/webui/features/hooks/usePostReactions.ts | 2 +- src/webui/features/hooks/usePostsFollowing.ts | 2 +- .../features/hooks/usePostsRecommended.ts | 2 +- src/webui/features/hooks/useRemovePost.ts | 2 +- .../features/hooks/useTogglePostRating.ts | 2 +- src/webui/features/hooks/useUpdateNickname.ts | 2 +- test/domain/authentication/login.spec.ts | 16 ++++++++-------- test/domain/creator/updateNickname.spec.ts | 4 ++-- .../notification/getRecentAggregated.spec.ts | 4 ++-- test/domain/post/createWithComic.spec.ts | 2 +- .../post/getByFollowingAggregated.spec.ts | 2 +- .../post/getRecommendedAggregated.spec.ts | 2 +- test/domain/post/remove.spec.ts | 6 +++--- test/domain/rating/toggle.spec.ts | 4 ++-- test/domain/relation/establish.spec.ts | 4 ++-- .../domain/relation/exploreAggregated.spec.ts | 10 +++++----- .../relation/getFollowersAggregated.spec.ts | 2 +- .../relation/getFollowingAggregated.spec.ts | 2 +- 86 files changed, 164 insertions(+), 170 deletions(-) diff --git a/docker/mongodb/init.js b/docker/mongodb/init.js index 6f000f8a..e8e933e7 100644 --- a/docker/mongodb/init.js +++ b/docker/mongodb/init.js @@ -1,14 +1,9 @@ db = db.getSiblingDB('comify'); -db.tenant.updateOne( - { _id: 'localhost' }, - { - $set: { - origins: [ - 'http://localhost:3000', - 'http://localhost:5173' - ] - } - }, - { upsert: true } -); +db.tenant.insertOne({ + _id: 'localhost', + origins: [ + 'http://localhost:3000', + 'http://localhost:5173' + ] +}); diff --git a/src/domain/authentication/login/login.ts b/src/domain/authentication/login/login.ts index 430bc27f..e943695d 100644 --- a/src/domain/authentication/login/login.ts +++ b/src/domain/authentication/login/login.ts @@ -1,15 +1,15 @@ -import { type Identity } from '^/integrations/authentication'; +import type { Identity } from '^/integrations/authentication'; import getCreatorByEmail from '^/domain/creator/getByEmail'; import registerCreator from '^/domain/creator/register'; -import { type Tenant } from '^/domain/tenant'; +import type { Tenant } from '^/domain/tenant'; -import { type Requester } from '../types'; +import type { Requester } from '../types'; -export default async function login(identity: Identity, tenant: Tenant): Promise +export default async function login(tenant: Tenant, identity: Identity): Promise { - const existingCreator = await getCreatorByEmail(identity.email, tenant.id); + const existingCreator = await getCreatorByEmail(tenant.id, identity.email); const loggedInCreator = existingCreator ?? await registerCreator( tenant.id, diff --git a/src/domain/creator/aggregate/types.ts b/src/domain/creator/aggregate/types.ts index 039f7456..d5ec66c3 100644 --- a/src/domain/creator/aggregate/types.ts +++ b/src/domain/creator/aggregate/types.ts @@ -4,7 +4,7 @@ import type { ImageData } from '^/domain/image'; import type { DataModel } from '../types'; -type AggregatedData = Omit & +type AggregatedData = Omit & { readonly portrait?: ImageData; readonly metrics: metricsData; diff --git a/src/domain/creator/create/types.ts b/src/domain/creator/create/types.ts index 695c5013..d199269c 100644 --- a/src/domain/creator/create/types.ts +++ b/src/domain/creator/create/types.ts @@ -1,6 +1,6 @@ import type { DataModel } from '../types'; -type ValidationModel = Pick; +type ValidationModel = Pick; export type { ValidationModel }; diff --git a/src/domain/creator/create/validateData.ts b/src/domain/creator/create/validateData.ts index 2602457e..bf6d6fb7 100644 --- a/src/domain/creator/create/validateData.ts +++ b/src/domain/creator/create/validateData.ts @@ -10,6 +10,7 @@ import type { ValidationModel } from './types'; const schema: ValidationSchema = { + tenantId: requiredIdValidation, fullName: fullNameValidation, email: { @@ -19,13 +20,12 @@ const schema: ValidationSchema = required: true } }, - tenantId: requiredIdValidation, portraitId: optionalIdValidation }; -export default function validateData({ fullName, email, tenantId }: ValidationModel): void +export default function validateData({ tenantId, fullName, email }: ValidationModel): void { - const result = validator.validate({ fullName, email, tenantId }, schema); + const result = validator.validate({ tenantId, fullName, email }, schema); if (result.invalid) { diff --git a/src/domain/creator/generateNickname/generateNickname.ts b/src/domain/creator/generateNickname/generateNickname.ts index 74118ad7..ef1cdcee 100644 --- a/src/domain/creator/generateNickname/generateNickname.ts +++ b/src/domain/creator/generateNickname/generateNickname.ts @@ -6,18 +6,18 @@ import retrieveByStartNickname from './retrieveByStartNickname'; const MAX_NICKNAME_NUMBER = 1000; -export default async function generateNickname(nickname: string, tenantId: string): Promise +export default async function generateNickname(tenantId: string, nickname: string): Promise { const cleanedNickname = cleanNickname(nickname); - const existingData = await retrieveByNickname(cleanedNickname, tenantId); + const existingData = await retrieveByNickname(tenantId, cleanedNickname); if (existingData === undefined) { return cleanedNickname; } - const foundData = await retrieveByStartNickname(`${existingData.nickname}_`, tenantId); + const foundData = await retrieveByStartNickname(tenantId, `${existingData.nickname}_`); if (foundData === undefined) { diff --git a/src/domain/creator/generateNickname/retrieveByNickname.ts b/src/domain/creator/generateNickname/retrieveByNickname.ts index 1a59a131..be71189a 100644 --- a/src/domain/creator/generateNickname/retrieveByNickname.ts +++ b/src/domain/creator/generateNickname/retrieveByNickname.ts @@ -4,11 +4,11 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function retrieveByNickname(nickname: string, tenantId: string): Promise +export default async function retrieveByNickname(tenantId: string, nickname: string): Promise { const query = { - nickname: { 'EQUALS': nickname }, - tenantId: { 'EQUALS': tenantId } + tenantId: { 'EQUALS': tenantId }, + nickname: { 'EQUALS': nickname } }; return database.findRecord(RECORD_TYPE, query) as Promise; diff --git a/src/domain/creator/generateNickname/retrieveByStartNickname.ts b/src/domain/creator/generateNickname/retrieveByStartNickname.ts index 01bc4494..6cb8466a 100644 --- a/src/domain/creator/generateNickname/retrieveByStartNickname.ts +++ b/src/domain/creator/generateNickname/retrieveByStartNickname.ts @@ -5,11 +5,11 @@ import database, { SortDirections } from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function retrieveByStartNickname(nickname: string, tenantId: string): Promise +export default async function retrieveByStartNickname(tenantId: string, nickname: string): Promise { const query = { - nickname: { 'STARTS_WITH': nickname }, - tenantId: { 'EQUALS': tenantId } + tenantId: { 'EQUALS': tenantId }, + nickname: { 'STARTS_WITH': nickname } }; const sort: RecordSort = { 'nickname': SortDirections.DESCENDING }; diff --git a/src/domain/creator/getByEmail/getByEmail.ts b/src/domain/creator/getByEmail/getByEmail.ts index 550d3dd2..f303dde9 100644 --- a/src/domain/creator/getByEmail/getByEmail.ts +++ b/src/domain/creator/getByEmail/getByEmail.ts @@ -4,11 +4,11 @@ import database from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getByEmail(email: string, tenantId: string): Promise +export default async function getByEmail(tenantId: string, email: string): Promise { const query = { - email: { EQUALS: email }, - tenantId: { EQUALS: tenantId } + tenantId: { EQUALS: tenantId }, + email: { EQUALS: email } }; return database.findRecord(RECORD_TYPE, query) as Promise; diff --git a/src/domain/creator/getById/CreatorNotFound.ts b/src/domain/creator/getById/CreatorNotFound.ts index a57252e2..fa11b77a 100644 --- a/src/domain/creator/getById/CreatorNotFound.ts +++ b/src/domain/creator/getById/CreatorNotFound.ts @@ -3,7 +3,7 @@ import { NotFound } from '^/integrations/runtime'; export default class CreatorNotFound extends NotFound { - constructor(id: string, tenantId: string) + constructor(tenantId: string, id: string) { super(`No creator for id: ${id} and tenant '${tenantId}'`); } diff --git a/src/domain/creator/getById/getById.ts b/src/domain/creator/getById/getById.ts index bdf0b5c3..d4999d3e 100644 --- a/src/domain/creator/getById/getById.ts +++ b/src/domain/creator/getById/getById.ts @@ -5,19 +5,18 @@ import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; import CreatorNotFound from './CreatorNotFound'; -export default async function getById(id: string, tenantId: string): Promise +export default async function getById(tenantId: string, id: string): Promise { - const query: RecordQuery = - { - id: { 'EQUALS': id }, - tenantId: { 'EQUALS': tenantId } + const query: RecordQuery = { + tenantId: { 'EQUALS': tenantId }, + id: { 'EQUALS': id } }; const creator = await database.findRecord(RECORD_TYPE, query); if (creator === undefined) { - throw new CreatorNotFound(id, tenantId); + throw new CreatorNotFound(tenantId, id); } return creator as DataModel; diff --git a/src/domain/creator/getByIdAggregated/getByIdAggregated.ts b/src/domain/creator/getByIdAggregated/getByIdAggregated.ts index 961def42..a9d16971 100644 --- a/src/domain/creator/getByIdAggregated/getByIdAggregated.ts +++ b/src/domain/creator/getByIdAggregated/getByIdAggregated.ts @@ -5,7 +5,7 @@ import getById from '../getById'; export default async function getByIdAggregated(tenantId: string, id: string): Promise { - const data = await getById(id, tenantId); + const data = await getById(tenantId, id); return aggregate(data); } diff --git a/src/domain/creator/getByNickname/getByNickname.ts b/src/domain/creator/getByNickname/getByNickname.ts index af616408..702ab04b 100644 --- a/src/domain/creator/getByNickname/getByNickname.ts +++ b/src/domain/creator/getByNickname/getByNickname.ts @@ -5,11 +5,11 @@ import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; import NicknameNotFound from './NicknameNotFound'; -export default async function getByNickname(nickname: string, tenantId: string): Promise +export default async function getByNickname(tenantId: string, nickname: string): Promise { const query = { - nickname: { EQUALS: nickname }, - tenantId: { EQUALS: tenantId } + tenantId: { EQUALS: tenantId }, + nickname: { EQUALS: nickname } }; const creator = await database.findRecord(RECORD_TYPE, query); diff --git a/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts b/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts index 05a7fe99..7c4d61fd 100644 --- a/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts +++ b/src/domain/creator/getByNicknameAggregated/getByNicknameAggregated.ts @@ -6,9 +6,9 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getByNickname from '../getByNickname'; -export default async function getByNicknameAggregated(requester: Requester, tenant: Tenant, nickname: string): Promise +export default async function getByNicknameAggregated(tenant: Tenant, requester: Requester, nickname: string): Promise { - const data = await getByNickname(nickname, tenant.id); + const data = await getByNickname(tenant.id, nickname); return aggregate(data); } diff --git a/src/domain/creator/getMe/getMe.ts b/src/domain/creator/getMe/getMe.ts index 4befe20e..79f71b3f 100644 --- a/src/domain/creator/getMe/getMe.ts +++ b/src/domain/creator/getMe/getMe.ts @@ -5,7 +5,7 @@ import type { Tenant } from '^/domain/tenant'; import getById from '../getById'; import type { DataModel } from '../types'; -export default async function getMe(requester: Requester, tenant: Tenant): Promise +export default async function getMe(tenant: Tenant, requester: Requester): Promise { - return getById(requester.id, tenant.id); + return getById(tenant.id, requester.id); } diff --git a/src/domain/creator/getMeAggregated/getMeAggregated.ts b/src/domain/creator/getMeAggregated/getMeAggregated.ts index 4cecc406..1b170185 100644 --- a/src/domain/creator/getMeAggregated/getMeAggregated.ts +++ b/src/domain/creator/getMeAggregated/getMeAggregated.ts @@ -6,9 +6,9 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getMe from '../getMe'; -export default async function getMeAggregated(requester: Requester, tenant: Tenant): Promise +export default async function getMeAggregated(tenant: Tenant, requester: Requester): Promise { - const data = await getMe(requester, tenant); + const data = await getMe(tenant, requester); return aggregate(data); } diff --git a/src/domain/creator/getOthers/getOthers.ts b/src/domain/creator/getOthers/getOthers.ts index 6318bbda..60e2913d 100644 --- a/src/domain/creator/getOthers/getOthers.ts +++ b/src/domain/creator/getOthers/getOthers.ts @@ -10,8 +10,8 @@ export default async function getOthers(tenantId: string, ids: string[], order: { const defaultQuery: RecordQuery = { 'AND': [ - { id: { NOT_IN: ids } }, - { tenantId: { EQUALS: tenantId } } + { tenantId: { EQUALS: tenantId } }, + { id: { NOT_IN: ids } } ] }; const searchQuery: RecordQuery = { diff --git a/src/domain/creator/register/register.ts b/src/domain/creator/register/register.ts index 038fb0b0..477d5363 100644 --- a/src/domain/creator/register/register.ts +++ b/src/domain/creator/register/register.ts @@ -16,7 +16,7 @@ export default async function register(tenantId: string, fullName: string, nickn try { const truncatedFullName = fullName.substring(0, FULL_NAME_MAX_LENGTH); - const generatedNickname = await generateNickname(nickname, tenantId); + const generatedNickname = await generateNickname(tenantId, nickname); const portraitId = portraitUrl !== undefined ? await downloadPortrait(portraitUrl) diff --git a/src/domain/creator/types.ts b/src/domain/creator/types.ts index 97bb2a60..ff7a5f72 100644 --- a/src/domain/creator/types.ts +++ b/src/domain/creator/types.ts @@ -3,11 +3,11 @@ import type { BaseDataModel, CountOperation } from '../types'; type DataModel = BaseDataModel & { + readonly tenantId: string; readonly fullName: string; readonly nickname: string; readonly email: string; readonly portraitId?: string; - readonly tenantId: string; readonly joinedAt: string; }; diff --git a/src/domain/creator/updateNickname/retrieveByNickname.ts b/src/domain/creator/updateNickname/retrieveByNickname.ts index d0d8caf2..c4dbc750 100644 --- a/src/domain/creator/updateNickname/retrieveByNickname.ts +++ b/src/domain/creator/updateNickname/retrieveByNickname.ts @@ -7,8 +7,8 @@ import type { DataModel } from '../types'; export default async function retrieveByNickname(tenantId: string, nickname: string): Promise { const query = { - nickname: { EQUALS: nickname }, - tenantId: { EQUALS: tenantId } + tenantId: { EQUALS: tenantId }, + nickname: { EQUALS: nickname } }; return database.findRecord(RECORD_TYPE, query) as Promise; diff --git a/src/domain/creator/updateNickname/updateNickname.ts b/src/domain/creator/updateNickname/updateNickname.ts index 1a15487e..48c53f51 100644 --- a/src/domain/creator/updateNickname/updateNickname.ts +++ b/src/domain/creator/updateNickname/updateNickname.ts @@ -8,7 +8,7 @@ import NicknameAlreadyExists from './NicknameAlreadyExists'; import retrieveByNickname from './retrieveByNickname'; -export default async function updateNickname(requester: Requester, tenant: Tenant, nickname: string): Promise +export default async function updateNickname(tenant: Tenant, requester: Requester, nickname: string): Promise { const cleanedNickname = cleanNickname(nickname); diff --git a/src/domain/notification/aggregate/aggregate.ts b/src/domain/notification/aggregate/aggregate.ts index 93410b16..41664b0a 100644 --- a/src/domain/notification/aggregate/aggregate.ts +++ b/src/domain/notification/aggregate/aggregate.ts @@ -7,11 +7,11 @@ import type { Tenant } from '^/domain/tenant'; import type { DataModel } from '../types'; import type { AggregatedData } from './types'; -export default async function aggregate(requester: Requester, tenant: Tenant, data: DataModel): Promise +export default async function aggregate(tenant: Tenant, requester: Requester, data: DataModel): Promise { const [relationData, postData] = await Promise.all([ - getRelationData(requester, tenant, data.receiverId, data.senderId), - data.postId ? getPostData(requester, tenant, data.postId) : Promise.resolve(undefined) + getRelationData(tenant, requester, data.receiverId, data.senderId), + data.postId ? getPostData(tenant, requester, data.postId) : Promise.resolve(undefined) ]); return { diff --git a/src/domain/notification/getByIdAggregated/getByIdAggregated.ts b/src/domain/notification/getByIdAggregated/getByIdAggregated.ts index 35add7b3..8fd7c56f 100644 --- a/src/domain/notification/getByIdAggregated/getByIdAggregated.ts +++ b/src/domain/notification/getByIdAggregated/getByIdAggregated.ts @@ -6,9 +6,9 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getById from '../getById'; -export default async function getByIdAggregated(requester: Requester, tenant: Tenant, id: string): Promise +export default async function getByIdAggregated(tenant: Tenant, requester: Requester, id: string): Promise { const data = await getById(requester.id, id); - return aggregate(requester, tenant, data); + return aggregate(tenant, requester, data); } diff --git a/src/domain/notification/getRecentAggregated/getRecentAggregated.ts b/src/domain/notification/getRecentAggregated/getRecentAggregated.ts index 0c1a6cf9..e7dbb559 100644 --- a/src/domain/notification/getRecentAggregated/getRecentAggregated.ts +++ b/src/domain/notification/getRecentAggregated/getRecentAggregated.ts @@ -9,13 +9,13 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getRecent from '../getRecent'; -export default async function getRecentAggregated(requester: Requester, tenant: Tenant, range: Range): Promise +export default async function getRecentAggregated(tenant: Tenant, requester: Requester, range: Range): Promise { validateRange(range); const data = await getRecent(requester.id, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, tenant, item)); + const aggregates = data.map(item => aggregate(tenant, requester, item)); return filterResolved(aggregates); } diff --git a/src/domain/post/aggregate/aggregate.ts b/src/domain/post/aggregate/aggregate.ts index 9eba8062..473c28da 100644 --- a/src/domain/post/aggregate/aggregate.ts +++ b/src/domain/post/aggregate/aggregate.ts @@ -10,10 +10,10 @@ import type { Tenant } from '^/domain/tenant'; import type { DataModel } from '../types'; import type { AggregatedData } from './types'; -export default async function aggregate(requester: Requester, tenant: Tenant, data: DataModel): Promise +export default async function aggregate(tenant: Tenant, requester: Requester, data: DataModel): Promise { const [creatorData, isRated, comicData, commentData, metricsData] = await Promise.all([ - getRelationData(requester, tenant, requester.id, data.creatorId), + getRelationData(tenant, requester, requester.id, data.creatorId), ratingExists(requester.id, data.id), data.comicId ? getComicData(data.comicId) : Promise.resolve(undefined), data.commentId ? getCommentData(data.commentId) : Promise.resolve(undefined), diff --git a/src/domain/post/create/create.ts b/src/domain/post/create/create.ts index 57df91de..0fde64b3 100644 --- a/src/domain/post/create/create.ts +++ b/src/domain/post/create/create.ts @@ -7,13 +7,13 @@ import insertData from './insertData'; import publish from './publish'; import validateData from './validateData'; -export default async function create(creatorId: string, tenantId: string, comicId?: string, commentId?: string, parentId?: string): Promise +export default async function create(tenantId: string, creatorId: string, comicId?: string, commentId?: string, parentId?: string): Promise { let postId; try { - const data = createData(creatorId, tenantId, comicId, commentId, parentId); + const data = createData(tenantId, creatorId, comicId, commentId, parentId); validateData(data); diff --git a/src/domain/post/create/createData.ts b/src/domain/post/create/createData.ts index 202e7d11..91ade3f8 100644 --- a/src/domain/post/create/createData.ts +++ b/src/domain/post/create/createData.ts @@ -3,15 +3,15 @@ import { generateId } from '^/integrations/utilities/crypto'; import type { DataModel } from '../types'; -export default function createData(creatorId: string, tenantId: string, comicId?: string, commentId?: string, parentId?: string): DataModel +export default function createData(tenantId: string, creatorId: string, comicId?: string, commentId?: string, parentId?: string): DataModel { return { id: generateId(), - parentId, - creatorId, tenantId, + creatorId, comicId, commentId, + parentId, createdAt: new Date().toISOString() }; } diff --git a/src/domain/post/create/types.ts b/src/domain/post/create/types.ts index d5c1da1a..7de8b5d6 100644 --- a/src/domain/post/create/types.ts +++ b/src/domain/post/create/types.ts @@ -3,7 +3,7 @@ import type { Publication, Subscription } from '^/integrations/eventbroker'; import type { DataModel } from '../types'; -export type ValidationModel = Pick; +export type ValidationModel = Pick; export type CreatedEventData = { tenantId: string; diff --git a/src/domain/post/create/validateData.ts b/src/domain/post/create/validateData.ts index bd7d3fb9..3d8fce64 100644 --- a/src/domain/post/create/validateData.ts +++ b/src/domain/post/create/validateData.ts @@ -9,14 +9,14 @@ import type { ValidationModel } from './types'; const schema: ValidationSchema = { - creatorId: requiredIdValidation, tenantId: requiredIdValidation, + creatorId: requiredIdValidation, comicId: optionalIdValidation, commentId: optionalIdValidation, parentId: optionalIdValidation }; -export default function validateData({ creatorId, tenantId, comicId, commentId, parentId }: ValidationModel): void +export default function validateData({ tenantId, creatorId, comicId, commentId, parentId }: ValidationModel): void { if (comicId === undefined && commentId === undefined) { @@ -27,7 +27,7 @@ export default function validateData({ creatorId, tenantId, comicId, commentId, throw new InvalidPost(messages); } - const result = validator.validate({ creatorId, tenantId, comicId, commentId, parentId }, schema); + const result = validator.validate({ tenantId, creatorId, comicId, commentId, parentId }, schema); if (result.invalid) { diff --git a/src/domain/post/createWithComic/createWithComic.ts b/src/domain/post/createWithComic/createWithComic.ts index b13e6efd..32b01d8f 100644 --- a/src/domain/post/createWithComic/createWithComic.ts +++ b/src/domain/post/createWithComic/createWithComic.ts @@ -5,9 +5,9 @@ import type { Tenant } from '^/domain/tenant'; import createPost from '../create'; -export default async function createWithComic(requester: Requester, tenant: Tenant, comicImageDataUrl: string, parentId: string | undefined = undefined): Promise +export default async function createWithComic(tenant: Tenant, requester: Requester, comicImageDataUrl: string, parentId: string | undefined = undefined): Promise { const comicId = await createComic(comicImageDataUrl); - return createPost(requester.id, tenant.id, comicId, undefined, parentId); + return createPost(tenant.id, requester.id, comicId, undefined, parentId); } diff --git a/src/domain/post/createWithComment/createWithComment.ts b/src/domain/post/createWithComment/createWithComment.ts index 5311e4ee..6d36295e 100644 --- a/src/domain/post/createWithComment/createWithComment.ts +++ b/src/domain/post/createWithComment/createWithComment.ts @@ -5,9 +5,9 @@ import type { Tenant } from '^/domain/tenant'; import createPost from '../create'; -export default async function createWithComment(requester: Requester, tenant: Tenant, message: string, parentId: string | undefined = undefined): Promise +export default async function createWithComment(tenant: Tenant, requester: Requester, message: string, parentId: string | undefined = undefined): Promise { const commentId = await createComment(message); - return createPost(requester.id, tenant.id, undefined, commentId, parentId); + return createPost(tenant.id, requester.id, undefined, commentId, parentId); } diff --git a/src/domain/post/explore/explore.ts b/src/domain/post/explore/explore.ts index 078c153c..b08cb69e 100644 --- a/src/domain/post/explore/explore.ts +++ b/src/domain/post/explore/explore.ts @@ -6,7 +6,7 @@ import type { Tenant } from '^/domain/tenant'; import type { DataModel } from '../types'; import retrieveData from './retrieveData'; -export default async function explore(requester: Requester, tenant: Tenant, limit: number, offset: number): Promise +export default async function explore(tenant: Tenant, requester: Requester, limit: number, offset: number): Promise { const relationsData = await retrieveRelationsByFollower(requester, requester.id); diff --git a/src/domain/post/explore/retrieveData.ts b/src/domain/post/explore/retrieveData.ts index 659bf747..db243979 100644 --- a/src/domain/post/explore/retrieveData.ts +++ b/src/domain/post/explore/retrieveData.ts @@ -9,10 +9,10 @@ export default async function retrieveData(tenantId: string, excludedCreatorIds: { const query: RecordQuery = { - deleted: { 'EQUALS': false }, - parentId: { 'EQUALS': undefined }, + tenantId: { EQUALS: tenantId }, creatorId: { NOT_IN: excludedCreatorIds }, - tenantId: { 'EQUALS': tenantId } + parentId: { EQUALS: undefined }, + deleted: { EQUALS: false }, }; const sort: RecordSort = { createdAt: SortDirections.DESCENDING }; diff --git a/src/domain/post/exploreAggregated/exploreAggregated.ts b/src/domain/post/exploreAggregated/exploreAggregated.ts index 0c56bb8e..27e7a8d9 100644 --- a/src/domain/post/exploreAggregated/exploreAggregated.ts +++ b/src/domain/post/exploreAggregated/exploreAggregated.ts @@ -9,13 +9,13 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import explore from '../explore'; -export default async function exploreAggregated(requester: Requester, tenant: Tenant, range: Range): Promise +export default async function exploreAggregated(tenant: Tenant, requester: Requester, range: Range): Promise { validateRange(range); - const data = await explore(requester, tenant, range.limit, range.offset); + const data = await explore(tenant, requester, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, tenant, item)); + const aggregates = data.map(item => aggregate(tenant, requester, item)); return filterResolved(aggregates); } diff --git a/src/domain/post/getByCreatorAggregated/getByCreatorAggregated.ts b/src/domain/post/getByCreatorAggregated/getByCreatorAggregated.ts index 2b12cd21..312f1d1b 100644 --- a/src/domain/post/getByCreatorAggregated/getByCreatorAggregated.ts +++ b/src/domain/post/getByCreatorAggregated/getByCreatorAggregated.ts @@ -11,13 +11,13 @@ import getByCreator from '../getByCreator'; export { type AggregatedData }; -export default async function getByCreatorAggregated(requester: Requester, tenant: Tenant, creatorId: string, range: Range): Promise +export default async function getByCreatorAggregated(tenant: Tenant, requester: Requester, creatorId: string, range: Range): Promise { validateRange(range); const data = await getByCreator(creatorId, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, tenant, item)); + const aggregates = data.map(item => aggregate(tenant, requester, item)); return filterResolved(aggregates); } diff --git a/src/domain/post/getByFollowingAggregated/getByFollowingAggregated.ts b/src/domain/post/getByFollowingAggregated/getByFollowingAggregated.ts index 29893e0a..108d4c0c 100644 --- a/src/domain/post/getByFollowingAggregated/getByFollowingAggregated.ts +++ b/src/domain/post/getByFollowingAggregated/getByFollowingAggregated.ts @@ -9,13 +9,13 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getByFollowing from '../getByFollowing'; -export default async function getByFollowingAggregated(requester: Requester, tenant: Tenant, range: Range): Promise +export default async function getByFollowingAggregated(tenant: Tenant, requester: Requester, range: Range): Promise { validateRange(range); const data = await getByFollowing(requester, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, tenant, item)); + const aggregates = data.map(item => aggregate(tenant, requester, item)); return filterResolved(aggregates); } diff --git a/src/domain/post/getById/getById.ts b/src/domain/post/getById/getById.ts index b4658a00..360439a0 100644 --- a/src/domain/post/getById/getById.ts +++ b/src/domain/post/getById/getById.ts @@ -10,8 +10,8 @@ export default async function getById(tenantId: string, id: string): Promise +export default async function getByIdAggregated(tenant: Tenant, requester: Requester, id: string): Promise { const data = await getById(tenant.id, id); - return aggregate(requester, tenant, data); + return aggregate(tenant, requester, data); } diff --git a/src/domain/post/getByParent/getByParent.ts b/src/domain/post/getByParent/getByParent.ts index 66fcf029..1e46e1ba 100644 --- a/src/domain/post/getByParent/getByParent.ts +++ b/src/domain/post/getByParent/getByParent.ts @@ -9,8 +9,8 @@ export default async function getByParent(tenantId: string, parentId: string, li { const query: RecordQuery = { - parentId: { EQUALS: parentId }, tenantId: { EQUALS: tenantId }, + parentId: { EQUALS: parentId }, deleted: { EQUALS: false } }; diff --git a/src/domain/post/getByParentAggregated/getByParentAggregated.ts b/src/domain/post/getByParentAggregated/getByParentAggregated.ts index ad1d5687..f68457d1 100644 --- a/src/domain/post/getByParentAggregated/getByParentAggregated.ts +++ b/src/domain/post/getByParentAggregated/getByParentAggregated.ts @@ -8,11 +8,11 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getByParent from '../getByParent'; -export default async function getByParentAggregated(requester: Requester, tenant: Tenant, postId: string, range: Range): Promise +export default async function getByParentAggregated(tenant: Tenant, requester: Requester, postId: string, range: Range): Promise { const data = await getByParent(tenant.id, postId, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, tenant, item)); + const aggregates = data.map(item => aggregate(tenant, requester, item)); return filterResolved(aggregates); } diff --git a/src/domain/post/getRecommended/getRecommended.ts b/src/domain/post/getRecommended/getRecommended.ts index ec144f65..93dbcd9c 100644 --- a/src/domain/post/getRecommended/getRecommended.ts +++ b/src/domain/post/getRecommended/getRecommended.ts @@ -5,14 +5,14 @@ import database, { SortDirections } from '^/integrations/database'; import { RECORD_TYPE } from '../definitions'; import type { DataModel } from '../types'; -export default async function getRecommended(requesterId: string, tenantId: string, limit: number, offset: number): Promise +export default async function getRecommended(tenantId: string, requesterId: string, limit: number, offset: number): Promise { const query: RecordQuery = { - deleted: { EQUALS: false }, - parentId: { EQUALS: undefined }, + tenantId: { EQUALS: tenantId }, creatorId: { NOT_EQUALS: requesterId }, - tenantId: { EQUALS: tenantId } + parentId: { EQUALS: undefined }, + deleted: { EQUALS: false } }; const sort: RecordSort = { createdAt: SortDirections.DESCENDING }; diff --git a/src/domain/post/getRecommendedAggregated/getRecommendedAggregated.ts b/src/domain/post/getRecommendedAggregated/getRecommendedAggregated.ts index af78feb8..23be9c13 100644 --- a/src/domain/post/getRecommendedAggregated/getRecommendedAggregated.ts +++ b/src/domain/post/getRecommendedAggregated/getRecommendedAggregated.ts @@ -9,13 +9,13 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getRecommended from '../getRecommended'; -export default async function getRecommendedAggregated(requester: Requester, tenant: Tenant, range: Range): Promise +export default async function getRecommendedAggregated(tenant: Tenant, requester: Requester, range: Range): Promise { validateRange(range); - const data = await getRecommended(requester.id, tenant.id, range.limit, range.offset); + const data = await getRecommended(tenant.id, requester.id, range.limit, range.offset); - const aggregates = data.map(item => aggregate(requester, tenant, item)); + const aggregates = data.map(item => aggregate(tenant, requester, item)); return filterResolved(aggregates); } diff --git a/src/domain/post/remove/remove.ts b/src/domain/post/remove/remove.ts index 70f0e810..86a850d0 100644 --- a/src/domain/post/remove/remove.ts +++ b/src/domain/post/remove/remove.ts @@ -11,7 +11,7 @@ import isNotOwner from './isNotOwner'; import publish from './publish'; import undeleteData from './undeleteData'; -export default async function remove(requester: Requester, tenant: Tenant, id: string): Promise +export default async function remove(tenant: Tenant, requester: Requester, id: string): Promise { // We only delete the post itself and do not cascade it towards it's children as it doesn't add // any value, and it would make the code more complex. diff --git a/src/domain/post/types.ts b/src/domain/post/types.ts index 72d0dbd7..00104857 100644 --- a/src/domain/post/types.ts +++ b/src/domain/post/types.ts @@ -3,12 +3,12 @@ import type { BaseDataModel, CountOperation } from '../types'; type DataModel = BaseDataModel & { + readonly tenantId: string; readonly id: string; readonly creatorId: string; readonly comicId?: string; readonly commentId?: string; readonly parentId?: string; - readonly tenantId: string; readonly createdAt: string; }; diff --git a/src/domain/rating/toggle/toggle.ts b/src/domain/rating/toggle/toggle.ts index c3e5fee3..9b9d023c 100644 --- a/src/domain/rating/toggle/toggle.ts +++ b/src/domain/rating/toggle/toggle.ts @@ -6,7 +6,7 @@ import getData from './getData'; import switchOff from './switchOff'; import switchOn from './switchOn'; -export default async function toggle(requester: Requester, tenant: Tenant, postId: string): Promise +export default async function toggle(tenant: Tenant, requester: Requester, postId: string): Promise { const data = await getData(requester.id, postId); diff --git a/src/domain/relation/establish/establish.ts b/src/domain/relation/establish/establish.ts index 523f35cc..68a68da5 100644 --- a/src/domain/relation/establish/establish.ts +++ b/src/domain/relation/establish/establish.ts @@ -11,13 +11,13 @@ import exists from '../exists'; import publish from './publish'; import RelationAlreadyExists from './RelationAlreadyExists'; -export default async function establish(requester: Requester, tenant: Tenant, followingId: string): Promise +export default async function establish(tenant: Tenant, requester: Requester, followingId: string): Promise { let id; try { - await getCreator(followingId, tenant.id); + await getCreator(tenant.id, followingId); const relationExists = await exists(requester.id, followingId); diff --git a/src/domain/relation/explore/explore.ts b/src/domain/relation/explore/explore.ts index 93669fe6..520de360 100644 --- a/src/domain/relation/explore/explore.ts +++ b/src/domain/relation/explore/explore.ts @@ -7,7 +7,7 @@ import type { SortOrder } from '../definitions'; import getFollowing from '../getFollowing'; import type { DataModel } from '../types'; -export default async function explore(requester: Requester, tenant: Tenant, order: SortOrder, limit: number, offset: number, search: string | undefined = undefined): Promise +export default async function explore(tenant: Tenant, requester: Requester, order: SortOrder, limit: number, offset: number, search: string | undefined = undefined): Promise { const followingData = await getFollowing(requester, requester.id); const followingIds = followingData.map(data => data.followingId); diff --git a/src/domain/relation/exploreAggregated/exploreAggregated.ts b/src/domain/relation/exploreAggregated/exploreAggregated.ts index 86d7003b..d6d9270e 100644 --- a/src/domain/relation/exploreAggregated/exploreAggregated.ts +++ b/src/domain/relation/exploreAggregated/exploreAggregated.ts @@ -9,11 +9,11 @@ import aggregate from '../aggregate'; import type { SortOrder } from '../definitions'; import explore from '../explore'; -export default async function exploreAggregated(requester: Requester, tenant: Tenant, order: SortOrder, range: Range, search: string | undefined = undefined): Promise +export default async function exploreAggregated(tenant: Tenant, requester: Requester, order: SortOrder, range: Range, search: string | undefined = undefined): Promise { validateRange(range); - const data = await explore(requester, tenant, order, range.limit, range.offset, search); + const data = await explore(tenant, requester, order, range.limit, range.offset, search); return Promise.all(data.map(item => aggregate(tenant, item))); } diff --git a/src/domain/relation/getAggregated/getAggregated.ts b/src/domain/relation/getAggregated/getAggregated.ts index 506967d4..5af6c16a 100644 --- a/src/domain/relation/getAggregated/getAggregated.ts +++ b/src/domain/relation/getAggregated/getAggregated.ts @@ -6,7 +6,7 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import get from '../get'; -export default async function getAggregated(requester: Requester, tenant: Tenant, followerId: string, followingId: string): Promise +export default async function getAggregated(tenant: Tenant, requester: Requester, followerId: string, followingId: string): Promise { const data = await get(followerId, followingId); diff --git a/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts b/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts index 64e85890..03017f8a 100644 --- a/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts +++ b/src/domain/relation/getFollowersAggregated/getFollowersAggregated.ts @@ -8,7 +8,7 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import getFollowers from '../getFollowers'; -export default async function getFollowersAggregated(requester: Requester, tenant: Tenant, followingId: string, range: Range): Promise +export default async function getFollowersAggregated(tenant: Tenant, requester: Requester, followingId: string, range: Range): Promise { validateRange(range); diff --git a/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts b/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts index 781fa99b..0ec87187 100644 --- a/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts +++ b/src/domain/relation/getFollowingAggregated/getFollowingAggregated.ts @@ -8,7 +8,7 @@ import type { AggregatedData } from '../aggregate'; import aggregate from '../aggregate'; import retrieveByFollower from '../getFollowing'; -export default async function getFollowingAggregated(requester: Requester, tenant: Tenant, followerId: string, range: Range): Promise +export default async function getFollowingAggregated(tenant: Tenant, requester: Requester, followerId: string, range: Range): Promise { validateRange(range); diff --git a/src/domain/tenant/getByOriginConverted/types.ts b/src/domain/tenant/getByOriginConverted/types.ts index cb9ea482..1739df5a 100644 --- a/src/domain/tenant/getByOriginConverted/types.ts +++ b/src/domain/tenant/getByOriginConverted/types.ts @@ -1,4 +1,4 @@ -import { type Tenant } from '../types'; +import type { Tenant } from '../types'; export type ValidationModel = Pick; diff --git a/src/domain/tenant/getByOriginConverted/validateData.ts b/src/domain/tenant/getByOriginConverted/validateData.ts index f4dc75ab..1d3e6582 100644 --- a/src/domain/tenant/getByOriginConverted/validateData.ts +++ b/src/domain/tenant/getByOriginConverted/validateData.ts @@ -3,7 +3,7 @@ import type { ValidationSchema } from '^/integrations/validation'; import validator from '^/integrations/validation'; import InvalidOrigin from './InvalidOrigin'; -import { type ValidationModel } from './types'; +import type { ValidationModel } from './types'; const schema: ValidationSchema = { diff --git a/src/webui/editor/model/Bubble.ts b/src/webui/editor/model/Bubble.ts index 6ed51248..add7b3cb 100644 --- a/src/webui/editor/model/Bubble.ts +++ b/src/webui/editor/model/Bubble.ts @@ -1,6 +1,6 @@ import Element from '../elements/Element'; -import { type Point } from '../utils/Geometry'; +import type { Point } from '../utils/Geometry'; export default abstract class Bubble extends Element { diff --git a/src/webui/features/hooks/useAddComicPost.ts b/src/webui/features/hooks/useAddComicPost.ts index d802d017..f7e34786 100644 --- a/src/webui/features/hooks/useAddComicPost.ts +++ b/src/webui/features/hooks/useAddComicPost.ts @@ -15,7 +15,7 @@ export default function useAddComicPost() return useCallback(async (imageData: string) => { - await createPostWithComic(requester, tenant, imageData); + await createPostWithComic(tenant, requester, imageData); navigate(`/profile/${identity?.nickname}`); diff --git a/src/webui/features/hooks/useCreatePostComicReaction.ts b/src/webui/features/hooks/useCreatePostComicReaction.ts index 38aa4d4e..f65f6792 100644 --- a/src/webui/features/hooks/useCreatePostComicReaction.ts +++ b/src/webui/features/hooks/useCreatePostComicReaction.ts @@ -11,8 +11,8 @@ export default function useCreatePostComicReaction(post: AggregatedPostData, han { return useCallback(async (imageData: string) => { - const reactionId = await createComicReaction(requester, tenant, imageData, post.id); - const reaction = await getReaction(requester, tenant, reactionId); + const reactionId = await createComicReaction(tenant, requester, imageData, post.id); + const reaction = await getReaction(tenant, requester, reactionId); handleDone(reaction); diff --git a/src/webui/features/hooks/useCreatePostCommentReaction.ts b/src/webui/features/hooks/useCreatePostCommentReaction.ts index 0a52deca..3818eb27 100644 --- a/src/webui/features/hooks/useCreatePostCommentReaction.ts +++ b/src/webui/features/hooks/useCreatePostCommentReaction.ts @@ -11,8 +11,8 @@ export default function useCreateCommentReaction(post: AggregatedPostData, handl { return useCallback(async (comment: string) => { - const reactionId = await createCommentReaction(requester, tenant, comment, post.id); - const reaction = await getReaction(requester, tenant, reactionId); + const reactionId = await createCommentReaction(tenant, requester, comment, post.id); + const reaction = await getReaction(tenant, requester, reactionId); handleDone(reaction); diff --git a/src/webui/features/hooks/useCreator.ts b/src/webui/features/hooks/useCreator.ts index 02c7ca38..27a0b0d7 100644 --- a/src/webui/features/hooks/useCreator.ts +++ b/src/webui/features/hooks/useCreator.ts @@ -22,9 +22,9 @@ export default function useCreator() return undefined; } - const creator = await getCreator(requester, tenant, nickname); + const creator = await getCreator(tenant, requester, nickname); - return getRelation(requester, tenant, identity.id, creator.id); + return getRelation(tenant, requester, identity.id, creator.id); }, [identity, nickname]); diff --git a/src/webui/features/hooks/useCreatorFollowers.ts b/src/webui/features/hooks/useCreatorFollowers.ts index 25a62627..c1d7183a 100644 --- a/src/webui/features/hooks/useCreatorFollowers.ts +++ b/src/webui/features/hooks/useCreatorFollowers.ts @@ -14,7 +14,7 @@ export default function useCreatorFollowers(creator: AggregatedCreatorData) const getData = useCallback((page: number) => { - return getFollowers(requester, tenant, creator.id, { limit, offset: page * limit }); + return getFollowers(tenant, requester, creator.id, { limit, offset: page * limit }); }, [creator]); diff --git a/src/webui/features/hooks/useCreatorFollowing.ts b/src/webui/features/hooks/useCreatorFollowing.ts index 6e6861b0..b9c846cd 100644 --- a/src/webui/features/hooks/useCreatorFollowing.ts +++ b/src/webui/features/hooks/useCreatorFollowing.ts @@ -14,7 +14,7 @@ export default function useCreatorFollowing(creator: AggregatedCreatorData) const getData = useCallback((page: number) => { - return getFollowing(requester, tenant, creator.id, { limit, offset: page * limit }); + return getFollowing(tenant, requester, creator.id, { limit, offset: page * limit }); }, [creator]); diff --git a/src/webui/features/hooks/useCreatorPosts.ts b/src/webui/features/hooks/useCreatorPosts.ts index 1e77730a..2a20ab95 100644 --- a/src/webui/features/hooks/useCreatorPosts.ts +++ b/src/webui/features/hooks/useCreatorPosts.ts @@ -14,7 +14,7 @@ export default function useCreatorPosts(creator: AggregatedCreatorData) const getData = useCallback((page: number) => { - return getCreatorPosts(requester, tenant, creator.id, { limit, offset: page * limit }); + return getCreatorPosts(tenant, requester, creator.id, { limit, offset: page * limit }); }, [creator]); diff --git a/src/webui/features/hooks/useEstablishRelation.ts b/src/webui/features/hooks/useEstablishRelation.ts index efaf2f1c..65eab050 100644 --- a/src/webui/features/hooks/useEstablishRelation.ts +++ b/src/webui/features/hooks/useEstablishRelation.ts @@ -10,7 +10,7 @@ export default function useEstablishRelation() { return useCallback((relation: AggregatedRelationData) => { - return establishRelation(requester, tenant, relation.following.id); + return establishRelation(tenant, requester, relation.following.id); }, []); } diff --git a/src/webui/features/hooks/useExploreCreators.ts b/src/webui/features/hooks/useExploreCreators.ts index 1811243f..1321f238 100644 --- a/src/webui/features/hooks/useExploreCreators.ts +++ b/src/webui/features/hooks/useExploreCreators.ts @@ -13,7 +13,7 @@ export default function useExploreCreators() const getData = useCallback((page: number) => { - return exploreRelations(requester, tenant, 'popular', { limit, offset: page * limit }); + return exploreRelations(tenant, requester, 'popular', { limit, offset: page * limit }); }, []); diff --git a/src/webui/features/hooks/useExplorePosts.ts b/src/webui/features/hooks/useExplorePosts.ts index 1ce27f39..fad0bf1c 100644 --- a/src/webui/features/hooks/useExplorePosts.ts +++ b/src/webui/features/hooks/useExplorePosts.ts @@ -13,7 +13,7 @@ export default function useExplorePosts() const getData = useCallback((page: number) => { - return explorePosts(requester, tenant, { limit, offset: page * limit }); + return explorePosts(tenant, requester, { limit, offset: page * limit }); }, []); diff --git a/src/webui/features/hooks/useHighlight.ts b/src/webui/features/hooks/useHighlight.ts index eb67e01d..6da4335c 100644 --- a/src/webui/features/hooks/useHighlight.ts +++ b/src/webui/features/hooks/useHighlight.ts @@ -15,7 +15,7 @@ export default function useReaction() const getReaction = useCallback(async () => { return highlightId !== undefined - ? get(requester, tenant, highlightId) + ? get(tenant, requester, highlightId) : undefined; }, [highlightId]); diff --git a/src/webui/features/hooks/useIdentify.ts b/src/webui/features/hooks/useIdentify.ts index 77ced38b..004c0920 100644 --- a/src/webui/features/hooks/useIdentify.ts +++ b/src/webui/features/hooks/useIdentify.ts @@ -27,7 +27,7 @@ export default function useIdentify() const getIdentity = async () => { - const identity = await getMe(requester, tenant); + const identity = await getMe(tenant, requester); setIdentity(identity); }; diff --git a/src/webui/features/hooks/useNotifications.ts b/src/webui/features/hooks/useNotifications.ts index 4e77e81f..330bbd73 100644 --- a/src/webui/features/hooks/useNotifications.ts +++ b/src/webui/features/hooks/useNotifications.ts @@ -13,7 +13,7 @@ export default function useNotifications() const getNotifications = useCallback((page: number) => { - return getRecentNotifications(requester, tenant, { limit, offset: page * limit }); + return getRecentNotifications(tenant, requester, { limit, offset: page * limit }); }, []); diff --git a/src/webui/features/hooks/usePost.ts b/src/webui/features/hooks/usePost.ts index 461e98fb..3ec6fe71 100644 --- a/src/webui/features/hooks/usePost.ts +++ b/src/webui/features/hooks/usePost.ts @@ -15,7 +15,7 @@ export default function usePost() const getPost = useCallback(async () => { return postId !== undefined - ? get(requester, tenant, postId) + ? get(tenant, requester, postId) : undefined; }, [postId]); diff --git a/src/webui/features/hooks/usePostReactions.ts b/src/webui/features/hooks/usePostReactions.ts index b8fe7089..085aafd8 100644 --- a/src/webui/features/hooks/usePostReactions.ts +++ b/src/webui/features/hooks/usePostReactions.ts @@ -14,7 +14,7 @@ export default function usePostReactions(post: AggregatedPostData) const getData = useCallback((page: number) => { - return getReactionsByPost(requester, tenant, post.id, { limit, offset: page * limit }); + return getReactionsByPost(tenant, requester, post.id, { limit, offset: page * limit }); }, [post]); diff --git a/src/webui/features/hooks/usePostsFollowing.ts b/src/webui/features/hooks/usePostsFollowing.ts index 7da00081..212988ce 100644 --- a/src/webui/features/hooks/usePostsFollowing.ts +++ b/src/webui/features/hooks/usePostsFollowing.ts @@ -13,7 +13,7 @@ export default function usePostsFollowing() const getData = useCallback((page: number) => { - return getPostsFollowing(requester, tenant, { limit, offset: page * limit }); + return getPostsFollowing(tenant, requester, { limit, offset: page * limit }); }, []); diff --git a/src/webui/features/hooks/usePostsRecommended.ts b/src/webui/features/hooks/usePostsRecommended.ts index af8fa34d..e01d8c6a 100644 --- a/src/webui/features/hooks/usePostsRecommended.ts +++ b/src/webui/features/hooks/usePostsRecommended.ts @@ -13,7 +13,7 @@ export default function usePostsRecommended() const getData = useCallback((page: number) => { - return getPostsRecommended(requester, tenant, { limit, offset: page * limit }); + return getPostsRecommended(tenant, requester, { limit, offset: page * limit }); }, []); diff --git a/src/webui/features/hooks/useRemovePost.ts b/src/webui/features/hooks/useRemovePost.ts index fca47d40..fd441f2d 100644 --- a/src/webui/features/hooks/useRemovePost.ts +++ b/src/webui/features/hooks/useRemovePost.ts @@ -16,7 +16,7 @@ export default function useRemovePost() return useCallback(async (post: AggregatedPostData) => { - await remove(requester, tenant, post.id); + await remove(tenant, requester, post.id); navigate(`/profile/${identity?.nickname}`); diff --git a/src/webui/features/hooks/useTogglePostRating.ts b/src/webui/features/hooks/useTogglePostRating.ts index b37687bb..13ba8ed1 100644 --- a/src/webui/features/hooks/useTogglePostRating.ts +++ b/src/webui/features/hooks/useTogglePostRating.ts @@ -10,7 +10,7 @@ export default function useTogglePostRating() { return useCallback((post: AggregatedPostData) => { - return toggleRating(requester, tenant, post.id); + return toggleRating(tenant, requester, post.id); }, []); } diff --git a/src/webui/features/hooks/useUpdateNickname.ts b/src/webui/features/hooks/useUpdateNickname.ts index 467f2bf2..9a74d3a7 100644 --- a/src/webui/features/hooks/useUpdateNickname.ts +++ b/src/webui/features/hooks/useUpdateNickname.ts @@ -18,7 +18,7 @@ export default function useUpdateNickname() { try { - await updateNickname(requester, tenant, nickname); + await updateNickname(tenant, requester, nickname); setIdentity({ ...identity, nickname } as AggregatedCreatorData); setAlreadyInUse(false); diff --git a/test/domain/authentication/login.spec.ts b/test/domain/authentication/login.spec.ts index a6fc5018..d063babe 100644 --- a/test/domain/authentication/login.spec.ts +++ b/test/domain/authentication/login.spec.ts @@ -22,49 +22,49 @@ describe('domain/authentication', () => { it('should login with an existing email', async () => { - const requester = await login(IDENTITIES.EXISTING, TENANTS.default); + const requester = await login(TENANTS.default, IDENTITIES.EXISTING); expect(requester.nickname).toBe(VALUES.NICKNAMES.FIRST); }); it('should register without a nickname', async () => { - const requester = await login(IDENTITIES.NO_NICKNAME, TENANTS.default); + const requester = await login(TENANTS.default, IDENTITIES.NO_NICKNAME); expect(requester.nickname).toBe(VALUES.NICKNAMES.FROM_FULL_NAME); }); it('should register with a duplicate nickname', async () => { - const requester = await login(IDENTITIES.DUPLICATE_NICKNAME, TENANTS.default); + const requester = await login(TENANTS.default, IDENTITIES.DUPLICATE_NICKNAME); expect(requester.nickname).toBe(VALUES.NICKNAMES.DEDUPLICATED); }); it('should register with multiple occurrences of nickname', async () => { - const requester = await login(IDENTITIES.MULTIPLE_OCCURRENCES_NICKNAME, TENANTS.default); + const requester = await login(TENANTS.default, IDENTITIES.MULTIPLE_OCCURRENCES_NICKNAME); expect(requester.nickname).toBe(VALUES.NICKNAMES.NEXT_OCCURRED); }); it('should NOT register with too many occurrences nickname', async () => { - const promise = login(IDENTITIES.TOO_MANY_SIMILAR_NICKNAMES, TENANTS.default); + const promise = login(TENANTS.default, IDENTITIES.TOO_MANY_SIMILAR_NICKNAMES); await expect(promise).rejects.toStrictEqual(new TooManySimilarNicknames()); }); it('should register with spaces in nickname', async () => { - const requester = await login(IDENTITIES.SPACED_NICKNAME, TENANTS.default); + const requester = await login(TENANTS.default, IDENTITIES.SPACED_NICKNAME); expect(requester.nickname).toBe(VALUES.NICKNAMES.DESPACED); }); it('should register with underscores in nickname', async () => { - const requester = await login(IDENTITIES.UNDERSCORED_NICKNAME, TENANTS.default); + const requester = await login(TENANTS.default, IDENTITIES.UNDERSCORED_NICKNAME); expect(requester.nickname).toBe(VALUES.NICKNAMES.DEUNDERSCORED); }); it('should register with a valid profile picture', async () => { - const requester = await login(IDENTITIES.WITH_PICTURE, TENANTS.default); + const requester = await login(TENANTS.default, IDENTITIES.WITH_PICTURE); expect(requester.nickname).toBe(VALUES.NICKNAMES.WITH_PICTURE); }); }); diff --git a/test/domain/creator/updateNickname.spec.ts b/test/domain/creator/updateNickname.spec.ts index 8bd2d537..eeb07147 100644 --- a/test/domain/creator/updateNickname.spec.ts +++ b/test/domain/creator/updateNickname.spec.ts @@ -17,7 +17,7 @@ describe('domain/creator/updateNickname', () => { it('should update the nickname', async () => { - await updateNickname(REQUESTERS.CREATOR, TENANTS.default, VALUES.NICKNAMES.NEW); + await updateNickname(TENANTS.default, REQUESTERS.CREATOR, VALUES.NICKNAMES.NEW); const creator = await database.readRecord(CREATOR_RECORD_TYPE, REQUESTERS.CREATOR.id); expect(creator?.nickname).toBe(VALUES.NICKNAMES.NEW); @@ -25,7 +25,7 @@ describe('domain/creator/updateNickname', () => it('should NOT update the nickname because of a duplicate', async () => { - const promise = updateNickname(REQUESTERS.CREATOR, TENANTS.default, VALUES.NICKNAMES.DUPLICATE); + const promise = updateNickname(TENANTS.default, REQUESTERS.CREATOR, VALUES.NICKNAMES.DUPLICATE); await expect(promise).rejects.toStrictEqual(new NicknameAlreadyExists(VALUES.NICKNAMES.DUPLICATE)); }); diff --git a/test/domain/notification/getRecentAggregated.spec.ts b/test/domain/notification/getRecentAggregated.spec.ts index 3ba30bed..cb9a8687 100644 --- a/test/domain/notification/getRecentAggregated.spec.ts +++ b/test/domain/notification/getRecentAggregated.spec.ts @@ -17,7 +17,7 @@ describe('domain/notification/getallAggregated', () => { it('should give all notifications under normal circumstances', async () => { - const result = await getRecentAggregated(REQUESTERS.CREATOR2, TENANTS.default, { offset: 0, limit: 7 }); + const result = await getRecentAggregated(TENANTS.default, REQUESTERS.CREATOR2, { offset: 0, limit: 7 }); expect(result).toHaveLength(2); @@ -36,7 +36,7 @@ describe('domain/notification/getallAggregated', () => it('should give only the notifications that aggregate without errors', async () => { - const result = await getRecentAggregated(REQUESTERS.CREATOR1, TENANTS.default, { offset: 0, limit: 7 }); + const result = await getRecentAggregated(TENANTS.default, REQUESTERS.CREATOR1, { offset: 0, limit: 7 }); expect(result).toHaveLength(2); diff --git a/test/domain/post/createWithComic.spec.ts b/test/domain/post/createWithComic.spec.ts index 6c24e608..3bc7c820 100644 --- a/test/domain/post/createWithComic.spec.ts +++ b/test/domain/post/createWithComic.spec.ts @@ -20,7 +20,7 @@ describe('domain/post/add', () => { it('should create a post', async () => { - await createWithComic(REQUESTERS.CREATOR1, TENANTS.default, DATA_URLS.COMIC_IMAGE); + await createWithComic(TENANTS.default, REQUESTERS.CREATOR1, DATA_URLS.COMIC_IMAGE); const posts = await database.searchRecords(POST_RECORD_TYPE, {}); expect(posts.length).toBe(1); diff --git a/test/domain/post/getByFollowingAggregated.spec.ts b/test/domain/post/getByFollowingAggregated.spec.ts index 59a0db73..b4e6c46a 100644 --- a/test/domain/post/getByFollowingAggregated.spec.ts +++ b/test/domain/post/getByFollowingAggregated.spec.ts @@ -15,7 +15,7 @@ describe('domain/post/getByFollowingAggregated', () => { it('should get posts from everyone followed by the requester', async () => { - const result = await getByFollowingAggregated(REQUESTERS.CREATOR1, TENANTS.default, { offset: 0, limit: 7 }); + const result = await getByFollowingAggregated(TENANTS.default, REQUESTERS.CREATOR1, { offset: 0, limit: 7 }); expect(result).toHaveLength(1); expect(result[0].creator.following.id).toBe(REQUESTERS.CREATOR2.id); diff --git a/test/domain/post/getRecommendedAggregated.spec.ts b/test/domain/post/getRecommendedAggregated.spec.ts index fda63109..18fb02d4 100644 --- a/test/domain/post/getRecommendedAggregated.spec.ts +++ b/test/domain/post/getRecommendedAggregated.spec.ts @@ -16,7 +16,7 @@ describe('domain/post/getRecommendedAggregated', () => { it('should give all posts except those created by the requester', async () => { - const result = await getRecommendedAggregated(REQUESTERS.CREATOR1, TENANTS.default, { offset: 0, limit: 7 }); + const result = await getRecommendedAggregated(TENANTS.default, REQUESTERS.CREATOR1, { offset: 0, limit: 7 }); expect(result).toHaveLength(1); expect(result[0].creator.following.id).toBe(REQUESTERS.CREATOR2.id); diff --git a/test/domain/post/remove.spec.ts b/test/domain/post/remove.spec.ts index 68adcb06..8907ba7e 100644 --- a/test/domain/post/remove.spec.ts +++ b/test/domain/post/remove.spec.ts @@ -17,7 +17,7 @@ describe('domain/post/remove', () => { it('should soft delete a post', async () => { - await remove(REQUESTERS.CREATOR1, TENANTS.default, VALUES.IDS.POST_RATED); + await remove(TENANTS.default, REQUESTERS.CREATOR1, VALUES.IDS.POST_RATED); const reaction = await database.readRecord(RECORD_TYPE, VALUES.IDS.POST_RATED); expect(reaction.deleted).toBeTruthy(); @@ -25,13 +25,13 @@ describe('domain/post/remove', () => it('should not delete an already deleted post', async () => { - const promise = remove(REQUESTERS.CREATOR1, TENANTS.default, VALUES.IDS.POST_DELETED); + const promise = remove(TENANTS.default, REQUESTERS.CREATOR1, VALUES.IDS.POST_DELETED); await expect(promise).rejects.toThrow(PostNotFound); }); it('should not delete a post from another creator', async () => { - const promise = remove(REQUESTERS.VIEWER, TENANTS.default, VALUES.IDS.POST_RATED); + const promise = remove(TENANTS.default, REQUESTERS.VIEWER, VALUES.IDS.POST_RATED); await expect(promise).rejects.toThrow(PostNotFound); }); }); diff --git a/test/domain/rating/toggle.spec.ts b/test/domain/rating/toggle.spec.ts index 38f5e3ce..4a0be2c2 100644 --- a/test/domain/rating/toggle.spec.ts +++ b/test/domain/rating/toggle.spec.ts @@ -14,13 +14,13 @@ describe('domain/post/toggleRating', () => { it('should add a rating', async () => { - const isRated = await toggle(REQUESTERS.CREATOR1, TENANTS.default, VALUES.IDS.POST_UNRATED); + const isRated = await toggle(TENANTS.default, REQUESTERS.CREATOR1, VALUES.IDS.POST_UNRATED); expect(isRated).toBeTruthy(); }); it('should remove a rating', async () => { - const isRated = await toggle(REQUESTERS.CREATOR1, TENANTS.default, VALUES.IDS.POST_RATED); + const isRated = await toggle(TENANTS.default, REQUESTERS.CREATOR1, VALUES.IDS.POST_RATED); expect(isRated).toBeFalsy(); }); }); diff --git a/test/domain/relation/establish.spec.ts b/test/domain/relation/establish.spec.ts index ab88d20c..fd5a2553 100644 --- a/test/domain/relation/establish.spec.ts +++ b/test/domain/relation/establish.spec.ts @@ -17,7 +17,7 @@ describe('domain/relation/establish', () => { it('should establish a relation', async () => { - await establish(REQUESTERS.SECOND, TENANTS.default, VALUES.IDS.CREATOR1); + await establish(TENANTS.default, REQUESTERS.SECOND, VALUES.IDS.CREATOR1); const relation = await database.findRecord(RELATION_RECORD_TYPE, QUERIES.EXISTING_RELATION); expect(relation?.id).toBeDefined(); @@ -25,7 +25,7 @@ describe('domain/relation/establish', () => it('should NOT establish a duplicate relation', async () => { - const promise = establish(REQUESTERS.FIRST, TENANTS.default, VALUES.IDS.CREATOR2); + const promise = establish(TENANTS.default, REQUESTERS.FIRST, VALUES.IDS.CREATOR2); await expect(promise).rejects.toStrictEqual(new RelationAlreadyExists()); }); diff --git a/test/domain/relation/exploreAggregated.spec.ts b/test/domain/relation/exploreAggregated.spec.ts index 436e28fb..e525a2f2 100644 --- a/test/domain/relation/exploreAggregated.spec.ts +++ b/test/domain/relation/exploreAggregated.spec.ts @@ -15,7 +15,7 @@ describe('domain/relation/exploreAggregated', () => { it('should explore relations based on recent', async () => { - const relations = await explore(REQUESTERS.FIRST, TENANTS.default, SortOrders.RECENT, VALUES.RANGE); + const relations = await explore(TENANTS.default, REQUESTERS.FIRST, SortOrders.RECENT, VALUES.RANGE); expect(relations).toHaveLength(3); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR4); expect(relations[1].following?.id).toBe(VALUES.IDS.CREATOR6); @@ -24,27 +24,27 @@ describe('domain/relation/exploreAggregated', () => it('should find no relations based on search', async () => { - const relations = await explore(REQUESTERS.FIRST, TENANTS.default, SortOrders.POPULAR, VALUES.RANGE, 'or2'); + const relations = await explore(TENANTS.default, REQUESTERS.FIRST, SortOrders.POPULAR, VALUES.RANGE, 'or2'); expect(relations).toHaveLength(0); }); it('should find relations based on search full name', async () => { - const relations = await explore(REQUESTERS.FIRST, TENANTS.default, SortOrders.POPULAR, VALUES.RANGE, 'or 4'); + const relations = await explore(TENANTS.default, REQUESTERS.FIRST, SortOrders.POPULAR, VALUES.RANGE, 'or 4'); expect(relations).toHaveLength(1); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR4); }); it('should find relations based on search nickname', async () => { - const relations = await explore(REQUESTERS.FIRST, TENANTS.default, SortOrders.POPULAR, VALUES.RANGE, 'creator4'); + const relations = await explore(TENANTS.default, REQUESTERS.FIRST, SortOrders.POPULAR, VALUES.RANGE, 'creator4'); expect(relations).toHaveLength(1); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR4); }); it('should find relations based on search full name and nickname', async () => { - const relations = await explore(REQUESTERS.FIRST, TENANTS.default, SortOrders.POPULAR, VALUES.RANGE, 'five'); + const relations = await explore(TENANTS.default, REQUESTERS.FIRST, SortOrders.POPULAR, VALUES.RANGE, 'five'); expect(relations).toHaveLength(2); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR5); expect(relations[1].following?.id).toBe(VALUES.IDS.CREATOR6); diff --git a/test/domain/relation/getFollowersAggregated.spec.ts b/test/domain/relation/getFollowersAggregated.spec.ts index 884a5c4f..fa1d16fb 100644 --- a/test/domain/relation/getFollowersAggregated.spec.ts +++ b/test/domain/relation/getFollowersAggregated.spec.ts @@ -14,7 +14,7 @@ describe('domain/relation/getFollowers', () => { it('should retrieve follower relations for a following creator', async () => { - const relations = await getFollowers(REQUESTERS.FIRST, TENANTS.default, VALUES.IDS.CREATOR3, VALUES.RANGE); + const relations = await getFollowers(TENANTS.default, REQUESTERS.FIRST, VALUES.IDS.CREATOR3, VALUES.RANGE); expect(relations).toHaveLength(2); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR1); expect(relations[1].following?.id).toBe(VALUES.IDS.CREATOR2); diff --git a/test/domain/relation/getFollowingAggregated.spec.ts b/test/domain/relation/getFollowingAggregated.spec.ts index 0534835c..b50187e4 100644 --- a/test/domain/relation/getFollowingAggregated.spec.ts +++ b/test/domain/relation/getFollowingAggregated.spec.ts @@ -14,7 +14,7 @@ describe('domain/relation/getFollowing', () => { it('should retrieve relations for a follower', async () => { - const relations = await getFollowing(REQUESTERS.FIRST, TENANTS.default, VALUES.IDS.CREATOR1, VALUES.RANGE); + const relations = await getFollowing(TENANTS.default, REQUESTERS.FIRST, VALUES.IDS.CREATOR1, VALUES.RANGE); expect(relations).toHaveLength(2); expect(relations[0].following?.id).toBe(VALUES.IDS.CREATOR2); expect(relations[1].following?.id).toBe(VALUES.IDS.CREATOR3); From 28327639860f11aae5e3e9ed2764d39ec88abb79 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Fri, 25 Jul 2025 21:32:09 +0200 Subject: [PATCH 23/25] #375: forgot to update the test description --- test/domain/authentication/login.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/domain/authentication/login.spec.ts b/test/domain/authentication/login.spec.ts index d063babe..679680ad 100644 --- a/test/domain/authentication/login.spec.ts +++ b/test/domain/authentication/login.spec.ts @@ -18,7 +18,7 @@ beforeEach(async () => describe('domain/authentication', () => { - describe('.login(identity, tenant)', () => + describe('.login(tenant, identity)', () => { it('should login with an existing email', async () => { From b93be8b4a21b062d05d30cb73db8c09fc38b2dc3 Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Sat, 26 Jul 2025 23:02:57 +0200 Subject: [PATCH 24/25] #375: removed obsolete features --- segments/bff.json | 1 - segments/notification.json | 1 - src/domain/notification/getById/getById.ts | 18 ------------------ src/domain/notification/getById/index.ts | 2 -- .../getByIdAggregated/getByIdAggregated.ts | 14 -------------- .../notification/getByIdAggregated/index.ts | 2 -- 6 files changed, 38 deletions(-) delete mode 100644 src/domain/notification/getById/getById.ts delete mode 100644 src/domain/notification/getById/index.ts delete mode 100644 src/domain/notification/getByIdAggregated/getByIdAggregated.ts delete mode 100644 src/domain/notification/getByIdAggregated/index.ts diff --git a/segments/bff.json b/segments/bff.json index 9b733ecc..000d9812 100644 --- a/segments/bff.json +++ b/segments/bff.json @@ -18,7 +18,6 @@ "./domain/notification/aggregate": { "default": { "access": "private" } }, "./domain/notification/notify": { "subscriptions": { "access": "private" } }, "./domain/notification/getRecentAggregated": { "default": { "access": "public" } }, - "./domain/notification/getByIdAggregated": { "default": { "access": "public" } }, "./domain/post/aggregate": { "default": { "access": "private" } }, "./domain/post/create": { "default": { "access": "private" }, "subscribe": { "access": "private" } }, diff --git a/segments/notification.json b/segments/notification.json index 00a16a4b..a6c6a98c 100644 --- a/segments/notification.json +++ b/segments/notification.json @@ -1,6 +1,5 @@ { "./domain/notification/create": { "default": { "access": "protected" } }, - "./domain/notification/getById": { "default": { "access": "protected" } }, "./domain/notification/getByPostId": { "default": { "access": "protected" } }, "./domain/notification/getRecent": { "default": { "access": "protected" } }, "./domain/notification/remove": { "default": { "access": "protected" } } diff --git a/src/domain/notification/getById/getById.ts b/src/domain/notification/getById/getById.ts deleted file mode 100644 index cd18b6e3..00000000 --- a/src/domain/notification/getById/getById.ts +++ /dev/null @@ -1,18 +0,0 @@ - -import type { RecordQuery } from '^/integrations/database'; -import database from '^/integrations/database'; - -import { RECORD_TYPE } from '../definitions'; -import type { DataModel } from '../types'; - -export default async function getById(receiverId: string, id: string): Promise -{ - const query: RecordQuery = - { - id: { EQUALS: id }, - receiverId: { EQUALS: receiverId }, - deleted: { EQUALS: false } - }; - - return database.findRecord(RECORD_TYPE, query) as Promise; -} diff --git a/src/domain/notification/getById/index.ts b/src/domain/notification/getById/index.ts deleted file mode 100644 index da399eb0..00000000 --- a/src/domain/notification/getById/index.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export { default } from './getById'; diff --git a/src/domain/notification/getByIdAggregated/getByIdAggregated.ts b/src/domain/notification/getByIdAggregated/getByIdAggregated.ts deleted file mode 100644 index 8fd7c56f..00000000 --- a/src/domain/notification/getByIdAggregated/getByIdAggregated.ts +++ /dev/null @@ -1,14 +0,0 @@ - -import type { Requester } from '^/domain/authentication'; -import type { Tenant } from '^/domain/tenant'; - -import type { AggregatedData } from '../aggregate'; -import aggregate from '../aggregate'; -import getById from '../getById'; - -export default async function getByIdAggregated(tenant: Tenant, requester: Requester, id: string): Promise -{ - const data = await getById(requester.id, id); - - return aggregate(tenant, requester, data); -} diff --git a/src/domain/notification/getByIdAggregated/index.ts b/src/domain/notification/getByIdAggregated/index.ts deleted file mode 100644 index 8f7f2b94..00000000 --- a/src/domain/notification/getByIdAggregated/index.ts +++ /dev/null @@ -1,2 +0,0 @@ - -export { default } from './getByIdAggregated'; From 3797ad56fdca6f5aea52d8a39c17f895a8c5d92f Mon Sep 17 00:00:00 2001 From: Bas Meeuwissen Date: Sat, 26 Jul 2025 23:36:30 +0200 Subject: [PATCH 25/25] #375: processed feedback --- docker-compose.yml | 2 +- example.env | 2 +- src/integrations/runtime/middlewares/OriginMiddleware.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index a4b9b485..3c81717c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: restart: always volumes: - comify-mongo-data:/data/db - - "${MONGO_INIT_PATH}:/docker-entrypoint-initdb.d" + - "${MONGODB_INIT_PATH}:/docker-entrypoint-initdb.d" ports: - "${MONGODB_PORT_NUMBER:-27017}:27017" environment: diff --git a/example.env b/example.env index 87f68fdd..23b2eca3 100644 --- a/example.env +++ b/example.env @@ -58,12 +58,12 @@ VALIDATION_IMPLEMENTATION="zod" MONGODB_PORT_NUMBER=27017 MONGODB_ROOT_USERNAME="development" MONGODB_ROOT_PASSWORD="development" +MONGODB_INIT_PATH="./docker/mongodb" # MONGO EXPRESS MONGO_EXPRESS_PORT_NUMBER=8081 MONGO_EXPRESS_USERNAME="development" MONGO_EXPRESS_PASSWORD="development" -MONGO_INIT_PATH="./docker/mongodb" # MINIO MINIO_ADMIN_PORT_NUMBER=9001 diff --git a/src/integrations/runtime/middlewares/OriginMiddleware.ts b/src/integrations/runtime/middlewares/OriginMiddleware.ts index 0db21991..6ecba918 100644 --- a/src/integrations/runtime/middlewares/OriginMiddleware.ts +++ b/src/integrations/runtime/middlewares/OriginMiddleware.ts @@ -5,7 +5,7 @@ import { BadRequest } from 'jitar'; import type { ValidationSchema } from '^/integrations/validation'; import validator from '^/integrations/validation'; -const TENANT_COOKIE_NAME = 'x-tenant-origin'; +const ORIGIN_COOKIE_NAME = 'x-client-origin'; const schema: ValidationSchema = { origin: @@ -66,7 +66,7 @@ export default class OriginMiddleware implements Middleware { const [key, value] = cookie.split('='); - if (key.trim() === TENANT_COOKIE_NAME) + if (key.trim() === ORIGIN_COOKIE_NAME) { return value?.trim(); } @@ -85,6 +85,6 @@ export default class OriginMiddleware implements Middleware #setOriginCookie(response: Response, origin: string): void { - response.setHeader('Set-Cookie', `${TENANT_COOKIE_NAME}=${origin}; Path=/; HttpOnly=true; SameSite=None; Secure`); + response.setHeader('Set-Cookie', `${ORIGIN_COOKIE_NAME}=${origin}; Path=/; HttpOnly=true; SameSite=None; Secure`); } }