Skip to content

Commit

Permalink
refactor: intermodule, use direct function calls (#585)
Browse files Browse the repository at this point in the history
  • Loading branch information
laminne authored Jul 24, 2024
1 parent 029df27 commit 6375c27
Show file tree
Hide file tree
Showing 13 changed files with 187 additions and 159 deletions.
181 changes: 103 additions & 78 deletions pkg/intermodule/account.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { Result } from '@mikuroxina/mini-fn';
import { hc } from 'hono/client';

import type { AccountModuleHandlerType } from '../accounts/mod.js';
import { Cat, Ether } from '@mikuroxina/mini-fn';
import {
InMemoryAccountRepository,
newFollowRepo,
} from '../accounts/adaptor/repository/dummy.js';
import {
PrismaAccountRepository,
prismaFollowRepo,
} from '../accounts/adaptor/repository/prisma.js';
import type {
Account,
type AccountFrozen,
type AccountID,
type AccountName,
type AccountRole,
type AccountSilenced,
type AccountStatus,
AccountID,
AccountName,
} from '../accounts/model/account.js';
import { accountRepoSymbol } from '../accounts/model/repository.js';
import type { FetchService } from '../accounts/service/fetch.js';
import { fetch } from '../accounts/service/fetch.js';
import type { FetchFollowService } from '../accounts/service/fetchFollow.js';
import { fetchFollow } from '../accounts/service/fetchFollow.js';
import { prismaClient } from '../adaptors/prisma.js';

export type { Account } from '../accounts/model/account.js';

export interface PartialAccount {
id: AccountID;
Expand All @@ -19,94 +29,109 @@ export interface PartialAccount {
bio: string;
}

export class AccountModule {
// NOTE: This is a temporary solution to use hono client
// ToDo: base url should be configurable
private readonly client = hc<AccountModuleHandlerType>(
'http://localhost:3000',
);
export class AccountModuleFacade {
constructor(
private readonly fetchService: FetchService,
private readonly fetchFollowService: FetchFollowService,
) {}

async fetchAccount(id: AccountID): Promise<Result.Result<Error, Account>> {
const res = await this.client.accounts[':id'].$get({
param: { id },
});

if (!res.ok) {
return Result.err(new Error('Failed to fetch account'));
}

const body = await res.json();
if ('error' in body) {
return Result.err(new Error((body as { error: string }).error));
}

const account = Account.new({
id: body.id as AccountID,
mail: body.email as string,
name: body.name as AccountName,
nickname: body.nickname,
bio: body.bio,
role: body.role as AccountRole,
frozen: body.frozen as AccountFrozen,
silenced: body.silenced as AccountSilenced,
status: body.status as AccountStatus,
// biome-ignore lint/style/noNonNullAssertion: Use assertion to avoid compile errors because type inference has failed.
createdAt: new Date(body.created_at!),
passphraseHash: undefined,
});

return Result.ok(account);
return await this.fetchService.fetchAccountByID(id);
}

async fetchFollowings(
id: AccountID,
): Promise<Result.Result<Error, PartialAccount[]>> {
const res = await this.client.accounts[':id'].following.$get({
param: { id },
});
if (!res.ok) {
return Result.err(new Error('Failed to fetch followings'));
const res = await this.fetchFollowService.fetchFollowingsByID(id);
if (Result.isErr(res)) {
return res;
}

const body = await res.json();
if ('error' in body) {
return Result.err(new Error((body as { error: string }).error));
}
const accounts = await Promise.all(
Result.unwrap(res).map((v) =>
this.fetchService.fetchAccountByID(v.getTargetID()),
),
);

return Result.ok(
body.map((v): PartialAccount => {
return {
id: v.id as AccountID,
name: v.name as AccountName,
nickname: v.nickname,
bio: v.bio,
};
}),
accounts
.filter((v) => Result.isOk(v))
.map((v): PartialAccount => {
const unwrapped = Result.unwrap(v);
return {
id: unwrapped.getID(),
name: unwrapped.getName(),
nickname: unwrapped.getNickname(),
bio: unwrapped.getBio(),
};
}),
);
}

async fetchFollowers(
id: AccountID,
): Promise<Result.Result<Error, PartialAccount[]>> {
const res = await this.client.accounts[':id'].follower.$get({
param: { id },
});
if (!res.ok) {
return Result.err(new Error('Failed to fetch followers'));
const res = await this.fetchFollowService.fetchFollowersByID(id);
if (Result.isErr(res)) {
return res;
}

const body = await res.json();
if ('error' in body) {
return Result.err(new Error((body as { error: string }).error));
}
const accounts = await Promise.all(
Result.unwrap(res).map((v) =>
this.fetchService.fetchAccountByID(v.getFromID()),
),
);

return Result.ok(
body.map((v): PartialAccount => {
return {
id: v.id as AccountID,
name: v.name as AccountName,
nickname: v.nickname,
bio: v.bio,
};
}),
accounts
.filter((v) => Result.isOk(v))
.map((v): PartialAccount => {
const unwrapped = Result.unwrap(v);
return {
id: unwrapped.getID(),
name: unwrapped.getName(),
nickname: unwrapped.getNickname(),
bio: unwrapped.getBio(),
};
}),
);
}
}

const isProduction = process.env.NODE_ENV === 'production';
const accountRepoObject = isProduction
? new PrismaAccountRepository(prismaClient)
: new InMemoryAccountRepository([]);
const accountRepository = Ether.newEther(
accountRepoSymbol,
() => accountRepoObject,
);

const accountFollowRepository = isProduction
? prismaFollowRepo(prismaClient)
: newFollowRepo();

export const accountModule = new AccountModuleFacade(
Ether.runEther(Cat.cat(fetch).feed(Ether.compose(accountRepository)).value),
Ether.runEther(
Cat.cat(fetchFollow)
.feed(Ether.compose(accountFollowRepository))
.feed(Ether.compose(accountRepository)).value,
),
);

const inMemoryAccountRepository = Ether.newEther(
accountRepoSymbol,
() => new InMemoryAccountRepository([]),
);
const inMemoryFollowRepository = newFollowRepo();
export const dummyAccountModuleFacade = new AccountModuleFacade(
Ether.runEther(
Cat.cat(fetch).feed(Ether.compose(inMemoryAccountRepository)).value,
),
Ether.runEther(
Cat.cat(fetchFollow)
.feed(Ether.compose(inMemoryFollowRepository))
.feed(Ether.compose(inMemoryAccountRepository)).value,
),
);
21 changes: 6 additions & 15 deletions pkg/intermodule/timeline.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
import { Result } from '@mikuroxina/mini-fn';
import { hc } from 'hono/client';

import type { Note } from '../notes/model/note.js';
import type { TimelineModuleHandlerType } from '../timeline/mod.js';
import type { PushTimelineService } from '../timeline/service/push.js';

export class TimelineModule {
private readonly client = hc<TimelineModuleHandlerType>(
'http://localhost:3000',
);
export class TimelineModuleFacade {
constructor(private readonly pushTimelineService: PushTimelineService) {}

/*
* @description Push note to timeline
* @param note to be pushed
* */
async pushNoteToTimeline(note: Note): Promise<Result.Result<Error, void>> {
const res = await this.client.timeline.index.$post({
json: {
id: note.getID(),
authorId: note.getAuthorID(),
},
});
if (!res.ok) {
return Result.err(new Error('Failed to push note'));
const res = await this.pushTimelineService.handle(note);
if (Result.isErr(res)) {
return res;
}

return Result.ok(undefined);
Expand Down
4 changes: 2 additions & 2 deletions pkg/notes/adaptor/controller/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Option, Result } from '@mikuroxina/mini-fn';

import type { AccountID } from '../../../accounts/model/account.js';
import type { MediumID } from '../../../drive/model/medium.js';
import type { AccountModule } from '../../../intermodule/account.js';
import type { AccountModuleFacade } from '../../../intermodule/account.js';
import type { NoteID, NoteVisibility } from '../../model/note.js';
import type { CreateService } from '../../service/create.js';
import type { FetchService } from '../../service/fetch.js';
Expand All @@ -19,7 +19,7 @@ export class NoteController {
private readonly createService: CreateService,
private readonly fetchService: FetchService,
private readonly renoteService: RenoteService,
private readonly accountModule: AccountModule,
private readonly accountModule: AccountModuleFacade,
) {}

async createNote(args: {
Expand Down
5 changes: 1 addition & 4 deletions pkg/notes/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import { prismaClient } from '../adaptors/prisma.js';
import { SnowflakeIDGenerator } from '../id/mod.js';
import type { ID } from '../id/type.js';
import { AccountModule } from '../intermodule/account.js';
import { accountModule } from '../intermodule/account.js';
import { BookmarkController } from './adaptor/controller/bookmark.js';
import { NoteController } from './adaptor/controller/note.js';
import {
Expand Down Expand Up @@ -67,9 +67,6 @@ const AuthMiddleware = await Ether.runEtherT(
).value,
);

// Account
const accountModule = new AccountModule();

// Note
const createService = new CreateService(
noteRepository,
Expand Down
21 changes: 12 additions & 9 deletions pkg/notes/service/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest';

import { InMemoryAccountRepository } from '../../accounts/adaptor/repository/dummy.js';
import { Account, type AccountID } from '../../accounts/model/account.js';
import { AccountModule } from '../../intermodule/account.js';
import { dummyAccountModuleFacade } from '../../intermodule/account.js';
import { InMemoryNoteRepository } from '../adaptor/repository/dummy.js';
import { Note, type NoteID } from '../model/note.js';
import { FetchService } from './fetch.js';
Expand Down Expand Up @@ -85,16 +85,17 @@ const accountRepository = new InMemoryAccountRepository([
testAccount,
frozenAccount,
]);
const accountModule = new AccountModule();
const service = new FetchService(repository, accountModule);
const service = new FetchService(repository, dummyAccountModuleFacade);

describe('FetchService', () => {
afterEach(() => accountRepository.reset());

it('should fetch notes', async () => {
vi.spyOn(accountModule, 'fetchAccount').mockImplementation(async () => {
return Result.ok(testAccount);
});
vi.spyOn(dummyAccountModuleFacade, 'fetchAccount').mockImplementation(
async () => {
return Result.ok(testAccount);
},
);
const res = await service.fetchNoteByID('1' as NoteID);

expect(Option.isSome(res)).toBe(true);
Expand All @@ -114,9 +115,11 @@ describe('FetchService', () => {
});

it('account frozen', async () => {
vi.spyOn(accountModule, 'fetchAccount').mockImplementation(async () => {
return Result.ok(frozenAccount);
});
vi.spyOn(dummyAccountModuleFacade, 'fetchAccount').mockImplementation(
async () => {
return Result.ok(frozenAccount);
},
);
const res = await service.fetchNoteByID(frozenUserNote.getID());

expect(Option.isNone(res)).toBe(true);
Expand Down
4 changes: 2 additions & 2 deletions pkg/notes/service/fetch.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Option, Result } from '@mikuroxina/mini-fn';

import type { AccountModule } from '../../intermodule/account.js';
import type { AccountModuleFacade } from '../../intermodule/account.js';
import type { Note, NoteID } from '../model/note.js';
import type { NoteRepository } from '../model/repository.js';

export class FetchService {
constructor(
private readonly noteRepository: NoteRepository,
private readonly accountModule: AccountModule,
private readonly accountModule: AccountModuleFacade,
) {}

async fetchNoteByID(noteID: NoteID): Promise<Option.Option<Note>> {
Expand Down
6 changes: 3 additions & 3 deletions pkg/timeline/adaptor/controller/timeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { z } from '@hono/zod-openapi';
import { Result } from '@mikuroxina/mini-fn';

import type { Account, AccountID } from '../../../accounts/model/account.js';
import type { AccountModule } from '../../../intermodule/account.js';
import type { AccountModuleFacade } from '../../../intermodule/account.js';
import type { NoteID } from '../../../notes/model/note.js';
import type { ListID } from '../../model/list.js';
import type { AccountTimelineService } from '../../service/account.js';
Expand All @@ -15,12 +15,12 @@ import type {

export class TimelineController {
private readonly accountTimelineService: AccountTimelineService;
private readonly accountModule: AccountModule;
private readonly accountModule: AccountModuleFacade;
private readonly createListService: CreateListService;
private readonly deleteListService: DeleteListService;
constructor(args: {
accountTimelineService: AccountTimelineService;
accountModule: AccountModule;
accountModule: AccountModuleFacade;
createListService: CreateListService;
deleteListService: DeleteListService;
}) {
Expand Down
3 changes: 1 addition & 2 deletions pkg/timeline/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Option, Result } from '@mikuroxina/mini-fn';

import type { AccountID } from '../accounts/model/account.js';
import { SnowflakeIDGenerator } from '../id/mod.js';
import { AccountModule } from '../intermodule/account.js';
import { accountModule } from '../intermodule/account.js';
import { Note, type NoteID } from '../notes/model/note.js';
import { TimelineController } from './adaptor/controller/timeline.js';
import {
Expand All @@ -27,7 +27,6 @@ const idGenerator = new SnowflakeIDGenerator(0, {
now: () => BigInt(Date.now()),
});

const accountModule = new AccountModule();
const timelineRepository = new InMemoryTimelineRepository();
const listRepository = new InMemoryListRepository();
const timelineNotesCacheRepository = new InMemoryTimelineCacheRepository();
Expand Down
Loading

0 comments on commit 6375c27

Please sign in to comment.