Skip to content

Commit

Permalink
feat: remove s3
Browse files Browse the repository at this point in the history
  • Loading branch information
solufa committed May 11, 2024
1 parent e65cfaa commit 554a175
Show file tree
Hide file tree
Showing 20 changed files with 779 additions and 3,759 deletions.
6 changes: 0 additions & 6 deletions client/pages/index.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
}

.card {
overflow: hidden;
background: #fff;
border-radius: 8px;
box-shadow: 2px 2px 12px #0004;
Expand Down Expand Up @@ -66,8 +65,3 @@
cursor: pointer;
border-radius: 4px;
}

.taskImage {
width: 100%;
vertical-align: bottom;
}
34 changes: 2 additions & 32 deletions client/pages/index.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,29 @@ import { Loading } from 'components/Loading/Loading';
import { useAtom } from 'jotai';
import { BasicHeader } from 'pages/@components/BasicHeader/BasicHeader';
import type { ChangeEvent, FormEvent } from 'react';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useState } from 'react';
import { apiClient } from 'utils/apiClient';
import { returnNull } from 'utils/returnNull';
import { userAtom } from '../atoms/user';
import styles from './index.module.css';

const Home = () => {
const [user] = useAtom(userAtom);
const fileRef = useRef<HTMLInputElement | null>(null);
const [tasks, setTasks] = useState<TaskEntity[]>();
const [label, setLabel] = useState('');
const [image, setImage] = useState<File>();
const [previewImageUrl, setPreviewImageUrl] = useState<string>();
const inputLabel = (e: ChangeEvent<HTMLInputElement>) => {
setLabel(e.target.value);
};
const inputFile = (e: ChangeEvent<HTMLInputElement>) => {
setImage(e.target.files?.[0]);
};
const fetchTasks = async () => {
const tasks = await apiClient.private.tasks.$get().catch(returnNull);

if (tasks !== null) setTasks(tasks);
};
const createTask = async (e: FormEvent) => {
e.preventDefault();
if (!fileRef.current) return;

await apiClient.private.tasks.post({ body: { label, image } }).catch(returnNull);
await apiClient.private.tasks.post({ body: { label } }).catch(returnNull);
setLabel('');
setImage(undefined);
setPreviewImageUrl(undefined);
fileRef.current.value = '';
await fetchTasks();
};
const toggleDone = async (task: TaskEntity) => {
Expand All @@ -56,16 +46,6 @@ const Home = () => {
fetchTasks();
}, [user]);

useEffect(() => {
if (!image) return;

const newUrl = URL.createObjectURL(image);
setPreviewImageUrl(newUrl);
return () => {
URL.revokeObjectURL(newUrl);
};
}, [image]);

if (!tasks || !user) return <Loading visible />;

return (
Expand All @@ -77,7 +57,6 @@ const Home = () => {
<div className={styles.main}>
<div className={styles.card}>
<form onSubmit={createTask}>
{previewImageUrl && <img src={previewImageUrl} className={styles.taskImage} />}
<div className={styles.controls}>
<input
value={label}
Expand All @@ -86,21 +65,12 @@ const Home = () => {
placeholder="Todo task"
onChange={inputLabel}
/>
<input
type="file"
ref={fileRef}
accept=".png,.jpg,.jpeg,.gif,.webp,.svg"
onChange={inputFile}
/>
<input className={styles.btn} disabled={label === ''} type="submit" value="ADD" />
</div>
</form>
</div>
{tasks.map((task) => (
<div key={task.id} className={styles.card}>
{task.image && (
<img src={task.image.url} alt={task.label} className={styles.taskImage} />
)}
<div className={styles.controls}>
<input type="checkbox" checked={task.done} onChange={() => toggleDone(task)} />
<span>{task.label}</span>
Expand Down
5 changes: 0 additions & 5 deletions server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ API_ORIGIN=http://localhost:31577
CORS_ORIGIN=http://localhost:3000
FIREBASE_AUTH_EMULATOR_HOST=localhost:9099
FIREBASE_SERVER_KEY={}
S3_ENDPOINT=http://localhost:9000
S3_BUCKET=app
S3_ACCESS_KEY=minio
S3_SECRET_KEY=password
S3_REGION=ap-northeast-1
DATABASE_URL=postgresql://root:root@localhost:5432/app
TEST_DATABASE_URL=postgresql://root:root@localhost:5432/test
MINIO_ROOT_USER=minio
Expand Down
3 changes: 1 addition & 2 deletions server/api/@types/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ export type TaskEntity = {
label: string;
done: boolean;
createdTime: number;
image: { url: string; s3Key: string } | undefined;
author: { id: UserId; displayName: string | undefined };
};

export type TaskCreateVal = { label: string; image?: Blob };
export type TaskCreateVal = { label: string };

export type TaskUpdateVal = { taskId: Maybe<TaskId>; label?: string; done?: boolean };
2 changes: 0 additions & 2 deletions server/api/health/controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { prismaClient } from 'service/prismaClient';
import { s3 } from 'service/s3Client';
import { defineController } from './$relay';

export default defineController(() => ({
Expand All @@ -8,7 +7,6 @@ export default defineController(() => ({
body: {
server: 'ok',
db: await prismaClient.$queryRaw`SELECT CURRENT_TIMESTAMP;`.then(() => 'ok' as const),
storage: await s3.health().then(() => 'ok' as const),
},
}),
}));
1 change: 0 additions & 1 deletion server/api/private/tasks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export type Methods = DefineMethods<{
};

post: {
reqFormat: FormData;
reqBody: TaskCreateVal;
resBody: TaskEntity;
};
Expand Down
6 changes: 0 additions & 6 deletions server/domain/task/model/taskEntity.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import type { MultipartFile } from '@fastify/multipart';
import type { DeletableTaskId } from 'api/@types/brandedId';
import type { TaskEntity } from 'api/@types/task';
import type { S3PutParams } from 'service/s3Client';

export type TaskCreateServerVal = { label: string; image?: MultipartFile };

export type TaskSaveVal = { task: TaskEntity; s3Params?: S3PutParams };

export type TaskDeleteVal = { deletableId: DeletableTaskId; task: TaskEntity };
24 changes: 6 additions & 18 deletions server/domain/task/model/taskMethod.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,24 @@
import type { TaskEntity, TaskUpdateVal } from 'api/@types/task';
import type { TaskCreateVal, TaskEntity, TaskUpdateVal } from 'api/@types/task';
import type { UserEntity } from 'api/@types/user';
import assert from 'assert';
import { randomUUID } from 'crypto';
import { deletableTaskIdParser, taskIdParser } from 'service/idParsers';
import { s3 } from 'service/s3Client';
import type { TaskCreateServerVal, TaskDeleteVal, TaskSaveVal } from './taskEntity';
import type { TaskDeleteVal } from './taskEntity';

export const taskMethod = {
create: async (user: UserEntity, val: TaskCreateServerVal): Promise<TaskSaveVal> => {
const task: TaskEntity = {
create: async (user: UserEntity, val: TaskCreateVal): Promise<TaskEntity> => {
return {
id: taskIdParser.parse(randomUUID()),
done: false,
label: val.label,
image: undefined,
createdTime: Date.now(),
author: { id: user.id, displayName: user.displayName },
};

if (val.image === undefined) return { task };

const s3Key = `tasks/images/${randomUUID()}.${val.image.filename.split('.').at(-1)}`;
const url = await s3.getSignedUrl(s3Key);

return {
task: { ...task, image: { s3Key, url } },
s3Params: { key: s3Key, data: val.image },
};
},
update: (user: UserEntity, task: TaskEntity, val: TaskUpdateVal): TaskSaveVal => {
update: (user: UserEntity, task: TaskEntity, val: TaskUpdateVal): TaskEntity => {
assert(user.id === task.author.id);

return { task: { ...task, ...val } };
return { ...task, ...val };
},
delete: (user: UserEntity, task: TaskEntity): TaskDeleteVal => {
assert(user.id === task.author.id);
Expand Down
25 changes: 10 additions & 15 deletions server/domain/task/repository/taskCommand.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,22 @@
import type { Prisma } from '@prisma/client';
import { s3 } from 'service/s3Client';
import type { TaskDeleteVal, TaskSaveVal } from '../model/taskEntity';
import type { TaskEntity } from 'api/@types/task';
import type { TaskDeleteVal } from '../model/taskEntity';

export const taskCommand = {
save: async (tx: Prisma.TransactionClient, val: TaskSaveVal): Promise<void> => {
if (val.s3Params !== undefined) await s3.put(val.s3Params);

save: async (tx: Prisma.TransactionClient, task: TaskEntity): Promise<void> => {
await tx.task.upsert({
where: { id: val.task.id },
update: { label: val.task.label, done: val.task.done, imageKey: val.task.image?.s3Key },
where: { id: task.id },
update: { label: task.label, done: task.done },
create: {
id: val.task.id,
label: val.task.label,
done: val.task.done,
imageKey: val.task.image?.s3Key,
createdAt: new Date(val.task.createdTime),
authorId: val.task.author.id,
id: task.id,
label: task.label,
done: task.done,
createdAt: new Date(task.createdTime),
authorId: task.author.id,
},
});
},
delete: async (tx: Prisma.TransactionClient, val: TaskDeleteVal): Promise<void> => {
await tx.task.delete({ where: { id: val.deletableId } });

if (val.task.image !== undefined) await s3.delete(val.task.image.s3Key);
},
};
5 changes: 0 additions & 5 deletions server/domain/task/repository/taskQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@ import type { Prisma, Task, User } from '@prisma/client';
import type { Maybe, TaskId, UserId } from 'api/@types/brandedId';
import type { TaskEntity } from 'api/@types/task';
import { taskIdParser, userIdParser } from 'service/idParsers';
import { s3 } from 'service/s3Client';
import { depend } from 'velona';

const toModel = async (prismaTask: Task & { Author: User }): Promise<TaskEntity> => ({
id: taskIdParser.parse(prismaTask.id),
label: prismaTask.label,
done: prismaTask.done,
image:
prismaTask.imageKey === null
? undefined
: { url: await s3.getSignedUrl(prismaTask.imageKey), s3Key: prismaTask.imageKey },
author: {
id: userIdParser.parse(prismaTask.authorId),
displayName: prismaTask.Author.displayName ?? undefined,
Expand Down
9 changes: 2 additions & 7 deletions server/domain/task/service/taskValidator.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { multipartFileValidator } from 'api/$relay';
import type { TaskUpdateVal } from 'api/@types/task';
import type { TaskCreateVal, TaskUpdateVal } from 'api/@types/task';
import { taskIdParser } from 'service/idParsers';
import { z } from 'zod';
import type { TaskCreateServerVal } from '../model/taskEntity';

export const taskValidator = {
taskCreate: z.object({
label: z.string(),
image: multipartFileValidator().optional(),
}) satisfies z.ZodType<TaskCreateServerVal>,
taskCreate: z.object({ label: z.string() }) satisfies z.ZodType<TaskCreateVal>,
taskUpdate: z.object({
taskId: taskIdParser,
label: z.string().optional(),
Expand Down
9 changes: 4 additions & 5 deletions server/domain/task/useCase/taskUseCase.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import type { Maybe, TaskId } from 'api/@types/brandedId';
import type { TaskEntity, TaskUpdateVal } from 'api/@types/task';
import type { TaskCreateVal, TaskEntity, TaskUpdateVal } from 'api/@types/task';
import type { UserEntity } from 'api/@types/user';
import { transaction } from 'service/prismaClient';
import type { TaskCreateServerVal } from '../model/taskEntity';
import { taskMethod } from '../model/taskMethod';
import { taskCommand } from '../repository/taskCommand';
import { taskQuery } from '../repository/taskQuery';

export const taskUseCase = {
create: (user: UserEntity, val: TaskCreateServerVal): Promise<TaskEntity> =>
create: (user: UserEntity, val: TaskCreateVal): Promise<TaskEntity> =>
transaction('RepeatableRead', async (tx) => {
const created = await taskMethod.create(user, val);

await taskCommand.save(tx, created);

return created.task;
return created;
}),
update: (user: UserEntity, val: TaskUpdateVal): Promise<TaskEntity> =>
transaction('RepeatableRead', async (tx) => {
Expand All @@ -23,7 +22,7 @@ export const taskUseCase = {

await taskCommand.save(tx, updated);

return updated.task;
return updated;
}),
delete: (user: UserEntity, taskId: Maybe<TaskId>): Promise<TaskEntity> =>
transaction('RepeatableRead', async (tx) => {
Expand Down
Loading

0 comments on commit 554a175

Please sign in to comment.