Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Account Timeline #428

Merged
merged 9 commits into from
Jun 11, 2024
Merged
88 changes: 88 additions & 0 deletions pkg/accounts/adaptor/controller/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import type { AuthenticateService } from '../../service/authenticate.js';
import type { EditService } from '../../service/edit.js';
import type { FetchService } from '../../service/fetch.js';
import type { FetchFollowService } from '../../service/fetchFollow.js';
import type { FollowService } from '../../service/follow.js';
import type { FreezeService } from '../../service/freeze.js';
import type { RegisterService } from '../../service/register.js';
Expand All @@ -15,6 +16,8 @@
import type { VerifyAccountTokenService } from '../../service/verifyToken.js';
import {
type CreateAccountResponseSchema,
type GetAccountFollowerSchema,
type GetAccountFollowingSchema,
type GetAccountResponseSchema,
type LoginResponseSchema,
type UpdateAccountResponseSchema,
Expand All @@ -30,6 +33,7 @@
private readonly silenceService: SilenceService;
private readonly followService: FollowService;
private readonly unFollowService: UnfollowService;
private readonly fetchFollowService: FetchFollowService;
private readonly resendTokenService: ResendVerifyTokenService;

constructor(args: {
Expand All @@ -42,6 +46,7 @@
silenceService: SilenceService;
followService: FollowService;
unFollowService: UnfollowService;
fetchFollowService: FetchFollowService;
resendTokenService: ResendVerifyTokenService;
}) {
this.registerService = args.registerService;
Expand All @@ -53,6 +58,7 @@
this.silenceService = args.silenceService;
this.followService = args.followService;
this.unFollowService = args.unFollowService;
this.fetchFollowService = args.fetchFollowService;
this.resendTokenService = args.resendTokenService;
}

Expand Down Expand Up @@ -285,4 +291,86 @@

return Result.ok(undefined);
}

async fetchFollowing(
id: string,
): Promise<Result.Result<Error, z.infer<typeof GetAccountFollowingSchema>>> {
const res = await this.fetchFollowService.fetchFollowingsByID(
id as ID<AccountID>,
);
if (Result.isErr(res)) {
return res;
}
const accounts = await Promise.all(
res[1].map((v) => this.fetchService.fetchAccountByID(v.getTargetID())),
);
return Result.ok(
accounts
.filter((v) => Result.isOk(v))
.map((v) => {
const unwrapped = Result.unwrap(v);
// ToDo: make optional some fields
return {
id: unwrapped.getID(),
email: unwrapped.getMail(),
name: unwrapped.getName(),
nickname: unwrapped.getNickname(),
bio: unwrapped.getBio(),
// ToDo: fill the following fields
avatar: '',
header: '',
followed_count: 0,
following_count: 0,
note_count: 0,
created_at: unwrapped.getCreatedAt(),
role: unwrapped.getRole(),
frozen: unwrapped.getFrozen(),
status: unwrapped.getStatus(),
silenced: unwrapped.getSilenced(),
};
}),
);
}

Check warning on line 333 in pkg/accounts/adaptor/controller/account.ts

View check run for this annotation

Codecov / codecov/patch

pkg/accounts/adaptor/controller/account.ts#L296-L333

Added lines #L296 - L333 were not covered by tests

async fetchFollower(
id: string,
): Promise<Result.Result<Error, z.infer<typeof GetAccountFollowerSchema>>> {
const res = await this.fetchFollowService.fetchFollowersByID(
id as ID<AccountID>,
);
if (Result.isErr(res)) {
return Result.err(res[1]);
}
const accounts = await Promise.all(
res[1].map(
async (v) => await this.fetchService.fetchAccountByID(v.getFromID()),
),
);
return Result.ok(
accounts
.filter((v) => Result.isOk(v))
.map((v) => {
const unwrapped = Result.unwrap(v);
// ToDo: make optional some fields
return {
id: unwrapped.getID(),
email: unwrapped.getMail(),
name: unwrapped.getName(),
nickname: unwrapped.getNickname(),
bio: unwrapped.getBio(),
// ToDo: fill the following fields
avatar: '',
header: '',
followed_count: 0,
following_count: 0,
note_count: 0,
created_at: unwrapped.getCreatedAt(),
role: unwrapped.getRole(),
frozen: unwrapped.getFrozen(),
status: unwrapped.getStatus(),
silenced: unwrapped.getSilenced(),
};
}),
);
}

Check warning on line 375 in pkg/accounts/adaptor/controller/account.ts

View check run for this annotation

Codecov / codecov/patch

pkg/accounts/adaptor/controller/account.ts#L336-L375

Added lines #L336 - L375 were not covered by tests
}
7 changes: 7 additions & 0 deletions pkg/accounts/adaptor/validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,10 @@ export const GetAccountResponseSchema = z
export const FollowAccountResponseSchema = z
.object({})
.openapi('FollowAccountResponse');

export const GetAccountFollowingSchema = z
.array(GetAccountResponseSchema)
.openapi('GetAccountFollowingResponse');
export const GetAccountFollowerSchema = z
.array(GetAccountResponseSchema)
.openapi('GetAccountFollowerResponse');
66 changes: 65 additions & 1 deletion pkg/accounts/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
CreateAccountRoute,
FollowAccountRoute,
FreezeAccountRoute,
GetAccountFollowerRoute,
GetAccountFollowingRoute,
GetAccountRoute,
LoginRoute,
RefreshRoute,
Expand All @@ -39,6 +41,7 @@
import { edit } from './service/edit.js';
import { etag } from './service/etagService.js';
import { fetch } from './service/fetch.js';
import { fetchFollow } from './service/fetchFollow.js';
import { follow } from './service/follow.js';
import { freeze } from './service/freeze.js';
import { register } from './service/register.js';
Expand Down Expand Up @@ -134,6 +137,11 @@
.feed(Ether.compose(verifyAccountTokenService))
.feed(Ether.compose(dummy)).value,
),
fetchFollowService: Ether.runEther(
Cat.cat(fetchFollow)
.feed(Ether.compose(accountFollowRepository))
.feed(Ether.compose(accountRepository)).value,
),
});

// ToDo: load secret from config file
Expand All @@ -151,7 +159,10 @@
},
});

export type AccountModuleHandlerType = typeof GetAccountHandler;
export type AccountModuleHandlerType =
| typeof GetAccountHandler
| typeof getAccountFollowingRoute
| typeof getAccountFollowerRoute;

accounts.post('/accounts', CaptchaMiddleware.handle());
accounts.openapi(CreateAccountRoute, async (c) => {
Expand Down Expand Up @@ -313,3 +324,56 @@

return new Response(null, { status: 204 });
});

const getAccountFollowingRoute = accounts.openapi(
GetAccountFollowingRoute,
async (c) => {
const id = c.req.param('id');
const res = await controller.fetchFollowing(id);
if (Result.isErr(res)) {
return c.json({ error: res[1].message }, { status: 400 });
}
const unwrap = Result.unwrap(res);
return c.json(
unwrap.map((v) => {
return {
id: v.id,
name: v.name,
nickname: v.nickname,
bio: v.bio,
avatar: '',
header: '',
followed_count: v.followed_count,
following_count: v.following_count,
note_count: v.note_count,
};
}),
);
},

Check warning on line 352 in pkg/accounts/mod.ts

View check run for this annotation

Codecov / codecov/patch

pkg/accounts/mod.ts#L331-L352

Added lines #L331 - L352 were not covered by tests
);
const getAccountFollowerRoute = accounts.openapi(
GetAccountFollowerRoute,
async (c) => {
const id = c.req.param('id');
const res = await controller.fetchFollower(id);
if (Result.isErr(res)) {
return c.json({ error: res[1].message }, { status: 400 });
}
const unwrap = Result.unwrap(res);
return c.json(
unwrap.map((v) => {
return {
id: v.id,
name: v.name,
nickname: v.nickname,
bio: v.bio,
avatar: '',
header: '',
followed_count: v.followed_count,
following_count: v.following_count,
note_count: v.note_count,
};
}),
);
},

Check warning on line 378 in pkg/accounts/mod.ts

View check run for this annotation

Codecov / codecov/patch

pkg/accounts/mod.ts#L357-L378

Added lines #L357 - L378 were not covered by tests
);
66 changes: 66 additions & 0 deletions pkg/accounts/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
CreateAccountRequestSchema,
CreateAccountResponseSchema,
FollowAccountResponseSchema,
GetAccountFollowingSchema,
GetAccountResponseSchema,
LoginRequestSchema,
LoginResponseSchema,
Expand Down Expand Up @@ -589,3 +590,68 @@ export const UnFollowAccountRoute = createRoute({
},
},
});

export const GetAccountFollowingRoute = createRoute({
method: 'get',
tags: ['accounts'],
path: '/accounts/:id/following',
request: {
params: z.object({
id: z.string().min(3).max(64).openapi({
example: 'example_man',
description:
'Characters must be [A-Za-z0-9-.] The first and last characters must be [A-Za-z0-9-.]',
}),
}),
},
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: GetAccountFollowingSchema,
},
},
},
404: {
description: 'Not Found',
content: {
'application/json': {
schema: CommonErrorResponseSchema,
},
},
},
},
});
export const GetAccountFollowerRoute = createRoute({
method: 'get',
tags: ['accounts'],
path: '/accounts/:id/follower',
request: {
params: z.object({
id: z.string().min(3).max(64).openapi({
example: 'example_man',
description:
'Characters must be [A-Za-z0-9-.] The first and last characters must be [A-Za-z0-9-.]',
}),
}),
},
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: GetAccountFollowingSchema,
},
},
},
404: {
description: 'Not Found',
content: {
'application/json': {
schema: CommonErrorResponseSchema,
},
},
},
},
});
59 changes: 59 additions & 0 deletions pkg/intermodule/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
} from '../accounts/model/account.js';
import type { ID } from '../id/type.js';

export interface PartialAccount {
id: ID<AccountID>;
name: AccountName;
nickname: string;
bio: string;
}

export class AccountModule {
// NOTE: This is a temporary solution to use hono client
// ToDo: base url should be configurable
Expand Down Expand Up @@ -54,4 +61,56 @@

return Result.ok(account);
}

async fetchFollowings(
id: 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 body = await res.json();
if ('error' in body) {
return Result.err(new Error(body.error));
}
return Result.ok(
body.map((v): PartialAccount => {
return {
id: v.id as ID<AccountID>,
name: v.name as AccountName,
nickname: v.nickname,
bio: v.bio,
};
}),
);
}

Check warning on line 89 in pkg/intermodule/account.ts

View check run for this annotation

Codecov / codecov/patch

pkg/intermodule/account.ts#L66-L89

Added lines #L66 - L89 were not covered by tests

async fetchFollowers(
id: 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 body = await res.json();
if ('error' in body) {
return Result.err(new Error(body.error));
}
return Result.ok(
body.map((v): PartialAccount => {
return {
id: v.id as ID<AccountID>,
name: v.name as AccountName,
nickname: v.nickname,
bio: v.bio,
};
}),
);
}

Check warning on line 115 in pkg/intermodule/account.ts

View check run for this annotation

Codecov / codecov/patch

pkg/intermodule/account.ts#L92-L115

Added lines #L92 - L115 were not covered by tests
}
Loading