Skip to content

Commit

Permalink
Upload temp image to file resouces until save
Browse files Browse the repository at this point in the history
  • Loading branch information
anagperal committed Jan 10, 2025
1 parent 444d765 commit 1b3373e
Show file tree
Hide file tree
Showing 15 changed files with 302 additions and 148 deletions.
13 changes: 10 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,22 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />

<link type="text/css" rel="stylesheet" href="/includes/material-design-icons/material-icons.css" />
<link
type="text/css"
rel="stylesheet"
href="/includes/material-design-icons/material-icons.css"
/>
<link type="text/css" rel="stylesheet" href="/includes/roboto-font.css" />
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css" rel="stylesheet" />
<link
href="//maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css"
rel="stylesheet"
/>

<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="theme-color" content="#000000" />

<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>UHC watch- blog administration app</title>
</head>

<body>
Expand Down
10 changes: 10 additions & 0 deletions src/CompositionRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ import { UserRepository } from "./domain/repositories/UserRepository";
import { GetCurrentUserUseCase } from "./domain/usecases/GetCurrentUserUseCase";
import { D2Api } from "./types/d2-api";
import { SavePostUseCase } from "$/domain/usecases/SavePostUseCase";
import { SaveFileResourceUseCase } from "$/domain/usecases/SaveFileResourceUseCase";
import { FileResourcesRepository } from "$/domain/repositories/FileResourcesRepository";
import { FileResourcesD2Repository } from "$/data/repositories/FileResourcesD2Repository";
import { FileResourcesTestRepository } from "$/data/repositories/FileResourcesTestRepository";

export type CompositionRoot = ReturnType<typeof getCompositionRoot>;

type Repositories = {
usersRepository: UserRepository;
postsRepository: PostRepository;
fileResourcesRepository: FileResourcesRepository;
};

function getCompositionRoot(repositories: Repositories) {
Expand All @@ -29,13 +34,17 @@ function getCompositionRoot(repositories: Repositories) {
delete: new DeletePostUseCase(repositories.postsRepository),
save: new SavePostUseCase(repositories.postsRepository),
},
fileResources: {
save: new SaveFileResourceUseCase(repositories.fileResourcesRepository),
},
};
}

export function getWebappCompositionRoot(api: D2Api) {
const repositories: Repositories = {
usersRepository: new UserD2Repository(api),
postsRepository: new PostD2Repository(api),
fileResourcesRepository: new FileResourcesD2Repository(api),
};

return getCompositionRoot(repositories);
Expand All @@ -45,6 +54,7 @@ export function getTestCompositionRoot() {
const repositories: Repositories = {
usersRepository: new UserTestRepository(),
postsRepository: new PostTestRepository(),
fileResourcesRepository: new FileResourcesTestRepository(),
};

return getCompositionRoot(repositories);
Expand Down
45 changes: 45 additions & 0 deletions src/data/repositories/FileResourcesD2Repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { D2Api } from "$/types/d2-api";
import { apiToFuture, FutureData } from "$/data/api-futures";
import { FileResourcesRepository } from "$/domain/repositories/FileResourcesRepository";
import { FileResource } from "$/domain/entities/FileResource";
import { Id } from "$/domain/entities/Ref";
import { Future } from "$/domain/entities/generic/Future";

export class FileResourcesD2Repository implements FileResourcesRepository {
constructor(private api: D2Api) {}

public save(fileResource: FileResource): FutureData<FileResource> {
const formData = new FormData();
formData.append("file", fileResource.data, fileResource.name);
formData.append("contentType", fileResource.data.type);
formData.append("domain", fileResource.domain);
console.log("formData", formData);

return apiToFuture(
this.api.request<FileResponse>({
url: "/fileResources",
method: "post",
requestBodyType: "raw",
data: formData,
})
).flatMap(response => {
console.log("response", response);

const id = response.response?.fileResource?.id;
if (!id) {
return Future.error(new Error("Failed to save file resource"));
} else {
const savedFileResource: FileResource = { ...fileResource, id: id };
return Future.success(savedFileResource);
}
});
}
}

type FileResponse = {
response?: {
fileResource?: {
id?: Id;
};
};
};
13 changes: 13 additions & 0 deletions src/data/repositories/FileResourcesTestRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Future } from "$/domain/entities/generic/Future";
import { FutureData } from "$/data/api-futures";
import { FileResourcesRepository } from "$/domain/repositories/FileResourcesRepository";
import { FileResource } from "$/domain/entities/FileResource";

export class FileResourcesTestRepository implements FileResourcesRepository {
public save(fileResource: FileResource): FutureData<FileResource> {
return Future.success({
...fileResource,
id: "test-id",
});
}
}
52 changes: 29 additions & 23 deletions src/data/repositories/PostD2Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,19 +64,23 @@ export class PostD2Repository implements PostRepository {
pageSize: paging.pageSize,
})
).flatMap(response => {
const posts = response.instances.map(d2Event => this.buildPost(d2Event));

const paginatedPosts: PaginatedReponse<Post> = {
objects: posts,
pager: {
total: response.total || 0,
page: response.page,
pageSize: response.pageSize,
pageCount: response.total ? Math.ceil(response.total / paging.pageSize) : 0,
},
};

return Future.success(paginatedPosts);
try {
const posts = response.instances.map(d2Event => this.buildPost(d2Event));

const paginatedPosts: PaginatedReponse<Post> = {
objects: posts,
pager: {
total: response.total || 0,
page: response.page,
pageSize: response.pageSize,
pageCount: response.total ? Math.ceil(response.total / paging.pageSize) : 0,
},
};

return Future.success(paginatedPosts);
} catch (error) {
return Future.error(new Error(`${error}`));
}
});
}

Expand Down Expand Up @@ -128,8 +132,6 @@ export class PostD2Repository implements PostRepository {
}

save(post: Post): FutureData<Post> {
// TODO: Handle image upload too

const d2TrackerEvent: D2TrackerEvent = this.mapPostToD2EventTracker(post);

return Future.success(post);
Expand Down Expand Up @@ -163,14 +165,15 @@ export class PostD2Repository implements PostRepository {
updatedAt: new Date().toISOString(),
dataValues: [
{ dataElement: BLOG_DATA_ELEMENTS.title, value: post.title },
{ dataElement: BLOG_DATA_ELEMENTS.image, value: post.image },
{ dataElement: BLOG_DATA_ELEMENTS.image, value: post.imageResourceId },
{ dataElement: BLOG_DATA_ELEMENTS.description, value: post.description },
{ dataElement: BLOG_DATA_ELEMENTS.content, value: post.content },
],
orgUnit: BLOG_ORG_UNIT_ID,
};
}

// TODO: build post correctly, this is for testing
private buildPost(d2Event: D2TrackerEvent): Post {
const id = d2Event.event;
const createdDateString = d2Event.occurredAt;
Expand All @@ -179,9 +182,11 @@ export class PostD2Repository implements PostRepository {
const title =
d2Event.dataValues.find(dv => dv.dataElement === BLOG_DATA_ELEMENTS.title)?.value ||
"Title test";
const image =

const imageUrl = `${this.api.baseUrl}/api/events/files?dataElementUid=${BLOG_DATA_ELEMENTS.image}&eventUid=${id}`;
const imageResourceId =
d2Event.dataValues.find(dv => dv.dataElement === BLOG_DATA_ELEMENTS.image)?.value ||
"/dhis2/api/events/files?dataElementUid=fIrafA0Qbh3&eventUid=losyBXc2SGp";
"eaV6o9kGKJa";

const description =
d2Event.dataValues.find(dv => dv.dataElement === BLOG_DATA_ELEMENTS.description)
Expand All @@ -207,13 +212,14 @@ Este es un ejemplo de **Markdown** con _React_.
markdownContentTest;

return Post.createPost({
id,
title,
description,
id: id,
title: title,
description: description,
createdDate: createdDateString,
updatedDate: updatedDateString ?? createdDateString,
image,
content,
imageUrl: imageUrl,
imageResourceId: imageResourceId,
content: content,
}).match({
success: post => post,
error: errors => {
Expand Down
8 changes: 8 additions & 0 deletions src/domain/entities/FileResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Id } from "$/domain/entities/Ref";

export type FileResource = {
id: Id;
name: string;
data: Blob;
domain: "DATA_VALUE";
};
12 changes: 9 additions & 3 deletions src/domain/entities/Post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export interface PostAttrs {
description: string;
createdDate: string;
updatedDate: string;
image: string;
imageUrl: string;
imageResourceId: Id;
content: string;
}

Expand All @@ -20,7 +21,7 @@ export class Post extends Struct<PostAttrs>() {
}

private static validateAndCreate(attrs: PostAttrs): Either<ValidationError<Post>[], Post> {
const { id, title, description, image, content } = attrs;
const { id, title, description, imageUrl, imageResourceId, content } = attrs;

const errors: ValidationError<Post>[] = [
{ property: "id" as const, errors: validateRequired(id), value: id },
Expand All @@ -30,7 +31,12 @@ export class Post extends Struct<PostAttrs>() {
errors: validateRequired(description),
value: description,
},
{ property: "image" as const, errors: validateRequired(image), value: image },
{ property: "imageUrl" as const, errors: validateRequired(imageUrl), value: imageUrl },
{
property: "imageResourceId" as const,
errors: validateRequired(imageResourceId),
value: imageResourceId,
},
{ property: "content" as const, errors: validateRequired(content), value: content },
].filter(validation => validation.errors.length > 0);

Expand Down
3 changes: 2 additions & 1 deletion src/domain/entities/__tests__/postFixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export function createPost(id: string): Post {
description: `Description test ${id}`,
createdDate: "2021-01-01",
updatedDate: "2021-01-01",
image: `Image test ${id}`,
imageUrl: `https://example.com/image-${id}.png`,
imageResourceId: `image-${id}`,
content: `Content test ${id}`,
}).match({
success: post => post,
Expand Down
6 changes: 6 additions & 0 deletions src/domain/repositories/FileResourcesRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { FutureData } from "$/data/api-futures";
import { FileResource } from "$/domain/entities/FileResource";

export interface FileResourcesRepository {
save(fileResource: FileResource): FutureData<FileResource>;
}
11 changes: 11 additions & 0 deletions src/domain/usecases/SaveFileResourceUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { FutureData } from "$/data/api-futures";
import { FileResource } from "$/domain/entities/FileResource";
import { FileResourcesRepository } from "$/domain/repositories/FileResourcesRepository";

export class SaveFileResourceUseCase {
constructor(private fileResourcesRepository: FileResourcesRepository) {}

public execute(fileResource: FileResource): FutureData<FileResource> {
return this.fileResourcesRepository.save(fileResource);
}
}
22 changes: 22 additions & 0 deletions src/webapp/components/loader/Loader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Backdrop, CircularProgress } from "@material-ui/core";
import React from "react";
import styled from "styled-components";

export const Loader: React.FC = () => (
<StyledBackdrop open={true}>
<StyledLoaderContainer>
<CircularProgress color="primary" size={50} />
</StyledLoaderContainer>
</StyledBackdrop>
);

const StyledLoaderContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
`;

const StyledBackdrop = styled(Backdrop)`
color: ${props => props.theme.palette.common.white};
z-index: 2;
`;
20 changes: 20 additions & 0 deletions src/webapp/components/loader/LoaderContainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";
import { Backdrop, CircularProgress } from "@material-ui/core";

interface LoaderContainerProps {
loading: boolean;
children: React.ReactNode;
}

const LoaderContainer: React.FC<LoaderContainerProps> = ({ loading, children }) => {
return (
<div style={{ position: "relative" }}>
<Backdrop style={{ position: "absolute", zIndex: 1 }} open={loading} invisible={false}>
<CircularProgress color="primary" />
</Backdrop>
<div style={{ opacity: loading ? 0.5 : 1 }}>{children}</div>
</div>
);
};

export default LoaderContainer;
6 changes: 3 additions & 3 deletions src/webapp/pages/admin-blog/AdminBlogPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,12 +63,12 @@ export const AdminBlogPage: React.FC = React.memo(() => {
sortable: false,
},
{
name: "image",
name: "imageUrl",
text: i18n.t("Image"),
sortable: false,
getValue: post => {
return post.image ? (
<img src={post.image} alt={post.title} width={100} />
return post.imageUrl ? (
<img src={post.imageUrl} alt={post.title} width={100} />
) : null;
},
},
Expand Down
Loading

0 comments on commit 1b3373e

Please sign in to comment.