-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
304 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
apps/web/src/app/(authorized)/users/[userId]/@feedbacks/loading.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import * as styles from './page.css'; | ||
import { FeedbackCard } from '../../../../../components/domains/feedbacks/feedback-card'; | ||
|
||
import type { FC } from 'react'; | ||
|
||
export type UserDetailsFeedbacksLoadingPageProps = { | ||
// | ||
}; | ||
|
||
const UserDetailsFeedbacksLoadingPage: FC<UserDetailsFeedbacksLoadingPageProps> = () => { | ||
return ( | ||
<div className={styles.wrapper}> | ||
{Array.from({ length: 5 }).map((_, index) => ( | ||
<FeedbackCard key={index} fragment={undefined} /> | ||
))} | ||
</div> | ||
); | ||
}; | ||
|
||
export default UserDetailsFeedbacksLoadingPage; |
7 changes: 7 additions & 0 deletions
7
apps/web/src/app/(authorized)/users/[userId]/@feedbacks/page.css.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { style } from '@vanilla-extract/css'; | ||
|
||
export const wrapper = style({ | ||
display: 'flex', | ||
flexDirection: 'column', | ||
gap: '16px', | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
apps/web/src/graphql/resolvers/users/fields/received-feedbacks.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { resolveCursorConnection } from '@pothos/plugin-relay'; | ||
import * as v from 'valibot'; | ||
|
||
import { builder } from '../../../core/builder'; | ||
import { deserialize } from '../../../helpers/deserialize'; | ||
import { serialize } from '../../../helpers/serialize'; | ||
import { userReceivedFeedbacksCount } from '../../../usecases/users/fields/received-feedback-count'; | ||
import { userReceivedFeedbacks } from '../../../usecases/users/fields/received-feedbacks'; | ||
import { Feedback } from '../../feedbacks/types/feedback'; | ||
import { User } from '../types/user'; | ||
|
||
import type { ResolveCursorConnectionArgs } from '@pothos/plugin-relay'; | ||
|
||
builder.objectField(User, 'receivedFeedbacks', (t) => t.connection({ | ||
type: Feedback, | ||
description: 'ユーザーが受信したフィードバック', | ||
resolve: async (user, args) => { | ||
const connection = await resolveCursorConnection( | ||
{ | ||
args, | ||
toCursor: (feedback) => serialize(feedback.cursor), | ||
}, | ||
async ({ after, limit }: ResolveCursorConnectionArgs) => { | ||
const result = await userReceivedFeedbacks({ | ||
userId: user.id, | ||
limit: limit, | ||
cursor: after ? deserialize(after, v.object({ | ||
id: v.string(), | ||
createdAt: v.pipe( | ||
v.string(), | ||
v.isoTimestamp(), | ||
v.transform((value) => new Date(value)), | ||
), | ||
})) : undefined, | ||
}); | ||
|
||
if (result.isOk()) { | ||
return result.value; | ||
} | ||
|
||
throw result.error; | ||
}, | ||
); | ||
|
||
const totalCount = async () => { | ||
const result = await userReceivedFeedbacksCount({ | ||
userId: user.id, | ||
}); | ||
|
||
if (result.isOk()) { | ||
return result.value; | ||
} | ||
|
||
throw result.error; | ||
}; | ||
|
||
return { | ||
...connection, | ||
totalCount, | ||
}; | ||
}, | ||
})); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
49 changes: 49 additions & 0 deletions
49
apps/web/src/graphql/usecases/users/fields/received-feedback-count.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { CustomError } from '@feedbackun/package-custom-error'; | ||
import { database, schema } from '@feedbackun/package-database'; | ||
import DataLoader from 'dataloader'; | ||
import { count, eq } from 'drizzle-orm'; | ||
import { ResultAsync } from 'neverthrow'; | ||
|
||
import { serialize } from '../../../helpers/serialize'; | ||
import { dataLoader } from '../../../plugins/dataloader'; | ||
|
||
const symbol = Symbol('UserReceivedFeedbacksCount'); | ||
|
||
export type UserReceivedFeedbacksCountInput = { | ||
userId: string; | ||
}; | ||
|
||
export class UserReceivedFeedbacksCountUnexpectedError extends CustomError({ | ||
name: 'UserReceivedFeedbacksCountUnexpectedError', | ||
message: 'Failed to count received feedbacks for user.', | ||
}) {} | ||
|
||
export type UserReceivedFeedbacksCountError = ( | ||
| UserReceivedFeedbacksCountUnexpectedError | ||
); | ||
|
||
export type UserReceivedFeedbacksCount = ( | ||
input: UserReceivedFeedbacksCountInput, | ||
) => ResultAsync<number, UserReceivedFeedbacksCountError>; | ||
|
||
export const userReceivedFeedbacksCount: UserReceivedFeedbacksCount = (input) => { | ||
const loader = dataLoader(symbol, () => new DataLoader<UserReceivedFeedbacksCountInput, number, string>(async (inputs) => { | ||
const execute = async (input: UserReceivedFeedbacksCountInput): Promise<number> => { | ||
const [row] = await database() | ||
.select({ count: count() }) | ||
.from(schema.feedbacks) | ||
.innerJoin(schema.slackUsers, eq(schema.slackUsers.id, schema.feedbacks.receiveSlackUserId)) | ||
.innerJoin(schema.users, eq(schema.users.id, schema.slackUsers.userId)) | ||
.where(eq(schema.users.id, input.userId)); | ||
|
||
return row?.count ?? 0; | ||
}; | ||
|
||
return await Promise.all(inputs.map((input) => execute(input))); | ||
}, { cacheKeyFn: serialize })); | ||
|
||
return ResultAsync.fromThrowable( | ||
() => loader.load(input), | ||
(error) => new UserReceivedFeedbacksCountUnexpectedError({ cause: error }), | ||
)(); | ||
}; |
85 changes: 85 additions & 0 deletions
85
apps/web/src/graphql/usecases/users/fields/received-feedbacks.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
import { CustomError } from '@feedbackun/package-custom-error'; | ||
import { database, schema } from '@feedbackun/package-database'; | ||
import DataLoader from 'dataloader'; | ||
import { and, desc, eq, lt, or } from 'drizzle-orm'; | ||
import { ResultAsync } from 'neverthrow'; | ||
|
||
import { serialize } from '../../../helpers/serialize'; | ||
import { dataLoader } from '../../../plugins/dataloader'; | ||
|
||
import type { Feedback } from '../../feedbacks/types/feedback'; | ||
|
||
const symbol = Symbol('UserReceivedFeedbacks'); | ||
|
||
export type UserReceivedFeedbacksCursor = { | ||
id: string; | ||
createdAt: Date; | ||
}; | ||
|
||
export type UserReceivedFeedbacksInput = { | ||
userId: string; | ||
limit: number; | ||
cursor: UserReceivedFeedbacksCursor | undefined; | ||
}; | ||
|
||
export class UserReceivedFeedbacksUnexpectedError extends CustomError({ | ||
name: 'UserReceivedFeedbacksUnexpectedError', | ||
message: 'Failed to find received feedbacks for user.', | ||
}) {} | ||
|
||
export type UserReceivedFeedbacksError = ( | ||
| UserReceivedFeedbacksUnexpectedError | ||
); | ||
|
||
export type UserReceivedFeedbacksNode = Feedback & { cursor: UserReceivedFeedbacksCursor }; | ||
|
||
export type UserReceivedFeedbacks = ( | ||
input: UserReceivedFeedbacksInput, | ||
) => ResultAsync<UserReceivedFeedbacksNode[], UserReceivedFeedbacksError>; | ||
|
||
export const userReceivedFeedbacks: UserReceivedFeedbacks = (input) => { | ||
const loader = dataLoader(symbol, () => new DataLoader<UserReceivedFeedbacksInput, UserReceivedFeedbacksNode[], string>(async (inputs) => { | ||
const execute = async (input: UserReceivedFeedbacksInput): Promise<UserReceivedFeedbacksNode[]> => { | ||
const filters: Parameters<typeof and> = [ | ||
eq(schema.users.id, input.userId), | ||
]; | ||
|
||
if (input.cursor) { | ||
filters.push( | ||
or( | ||
lt(schema.feedbacks.createdAt, input.cursor.createdAt), | ||
and( | ||
eq(schema.feedbacks.createdAt, input.cursor.createdAt), | ||
lt(schema.feedbacks.id, input.cursor.id), | ||
), | ||
), | ||
); | ||
} | ||
|
||
const rows = await database() | ||
.select() | ||
.from(schema.feedbacks) | ||
.innerJoin(schema.slackUsers, eq(schema.slackUsers.id, schema.feedbacks.receiveSlackUserId)) | ||
.innerJoin(schema.users, eq(schema.users.id, schema.slackUsers.userId)) | ||
.where(and(...filters)) | ||
.orderBy(desc(schema.feedbacks.createdAt)); | ||
|
||
return rows.map((row) => ({ | ||
id: row.feedbacks.id, | ||
content: row.feedbacks.content, | ||
createdAt: row.feedbacks.createdAt, | ||
cursor: { | ||
id: row.feedbacks.id, | ||
createdAt: row.feedbacks.createdAt, | ||
}, | ||
})); | ||
}; | ||
|
||
return await Promise.all(inputs.map((input) => execute(input))); | ||
}, { cacheKeyFn: serialize })); | ||
|
||
return ResultAsync.fromThrowable( | ||
() => loader.load(input), | ||
(error) => new UserReceivedFeedbacksUnexpectedError({ cause: error }), | ||
)(); | ||
}; |