diff --git a/src/assets/styles.scss b/src/assets/styles.scss
index 2bc1fcd6..5d96e10d 100644
--- a/src/assets/styles.scss
+++ b/src/assets/styles.scss
@@ -2,6 +2,7 @@
a {
color: $purpleColor;
+ cursor: pointer;
text-decoration: none;
&:hover {
diff --git a/src/assets/variables.scss b/src/assets/variables.scss
index 04ad1203..f8e7dc80 100644
--- a/src/assets/variables.scss
+++ b/src/assets/variables.scss
@@ -1,5 +1,5 @@
$footerHeight: 2vh;
$headerHeight: 5vh;
-$padding-lg: 5em;
-$padding-md: 2.5em;
+$padding-lg: 10em;
+$padding-md: 5em;
$padding-sm: 1em;
diff --git a/src/client/app/http-client.ts b/src/client/app/http-client.ts
index bd62d17d..81e7ba94 100644
--- a/src/client/app/http-client.ts
+++ b/src/client/app/http-client.ts
@@ -1,12 +1,12 @@
import type { KyInstance } from 'ky-universal';
import ky from 'ky-universal';
-import { PORT } from 'src/common/common-constants';
+import { API_URL } from 'src/common/common-constants';
export const httpClient: KyInstance = ky.create({
headers: {
'Content-Type': 'application/json',
},
- prefixUrl: `http://localhost:${PORT}/api`,
+ prefixUrl: API_URL,
retry: 0,
});
diff --git a/src/client/features/blog/BlogList.tsx b/src/client/features/blog/BlogList.tsx
index d07bb3f7..e305eaa3 100644
--- a/src/client/features/blog/BlogList.tsx
+++ b/src/client/features/blog/BlogList.tsx
@@ -4,11 +4,11 @@ import BlogListItem from './BlogListItem';
import { useBlogListQuery } from './hooks/use-blog-list-query';
const BlogList = (): JSX.Element => {
- const { data: blogList = [], error, isLoading } = useBlogListQuery();
+ const { data: blogList = [], error, isFetching } = useBlogListQuery();
return (
{error &&
Failed to load
}
- {isLoading ?
Loading...
: blogList.map((item) =>
)}
+ {isFetching ?
Loading...
: blogList.map((item) =>
)}
);
};
diff --git a/src/client/features/blog/BlogListItem.tsx b/src/client/features/blog/BlogListItem.tsx
index 025ff4cd..a7d5027e 100644
--- a/src/client/features/blog/BlogListItem.tsx
+++ b/src/client/features/blog/BlogListItem.tsx
@@ -2,20 +2,21 @@ import type { JSX } from 'react';
import { Button } from '@mui/base';
import Link from 'next/link';
+import { BLOG_PAGE_URL } from 'src/common/common-constants';
import { dayjs } from 'src/common/common-date';
import { BlogListItemProps } from './blog-types';
import styles from './BlogListItem.module.scss';
const BlogListItem = ({ item }: BlogListItemProps): JSX.Element => {
- const { _id: blogId, date, title, link, linkCaption } = item;
+ const { _id: itemId, date, title, link, linkCaption } = item;
return (
-
+
{dayjs(date).utc().format('MMMM DD, YYYY')}
{title}
{link}
{linkCaption}
-
+
diff --git a/src/client/features/blog/blog-api.ts b/src/client/features/blog/blog-api.ts
index 0818d99c..6052c6bb 100644
--- a/src/client/features/blog/blog-api.ts
+++ b/src/client/features/blog/blog-api.ts
@@ -1,18 +1,17 @@
import httpClient from 'src/client/app/http-client';
-import type { IBlog, IBlogDTO } from 'src/common/types/common-blog-types';
+import type { BlogDTO, BlogModel } from 'src/common/types/common-blog-types';
-export const blogListRequest = async (): Promise
=> {
- const blogListDTO = await httpClient.get('blog').json();
- return blogListDTO.map((item) => ({
- ...item,
- date: new Date(item.date),
- }));
+const blogItemAdapter = (dto: BlogDTO): BlogModel => ({
+ ...dto,
+ date: new Date(dto.date),
+});
+
+export const blogListRequest = async (): Promise => {
+ const dto = await httpClient.get('blog').json();
+ return dto.map(blogItemAdapter);
};
-export const blogRequest = async (id: string): Promise => {
- const blogDTO = await httpClient.get(`blog/${id}`).json();
- return {
- ...blogDTO,
- date: new Date(blogDTO.date),
- };
+export const blogItemRequest = async (id: string): Promise => {
+ const dto = await httpClient.get(`blog/${id}`).json();
+ return blogItemAdapter(dto);
};
diff --git a/src/client/features/blog/blog-types.ts b/src/client/features/blog/blog-types.ts
index fdac59a8..a8e85685 100644
--- a/src/client/features/blog/blog-types.ts
+++ b/src/client/features/blog/blog-types.ts
@@ -1,5 +1,5 @@
-import { IBlog } from 'src/common/types/common-blog-types';
+import { BlogModel } from 'src/common/types/common-blog-types';
export interface BlogListItemProps {
- item: IBlog;
+ item: BlogModel;
}
diff --git a/src/client/features/blog/hooks/use-blog-list-query.ts b/src/client/features/blog/hooks/use-blog-list-query.ts
index b7cc6e84..1b8437a0 100644
--- a/src/client/features/blog/hooks/use-blog-list-query.ts
+++ b/src/client/features/blog/hooks/use-blog-list-query.ts
@@ -1,17 +1,21 @@
import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';
-import { IBlog } from '../../../../common/types/common-blog-types';
+import { BlogModel } from '../../../../common/types/common-blog-types';
import { blogListRequest } from '../blog-api';
import { BLOG_LIST_QUERY_KEY } from '../blog-constants';
export const useBlogListQuery = (
- { refetchOnMount = false, enabled = false, ...restProps }: Omit, 'queryKey' | 'queryFn'> = {
+ {
+ refetchOnMount = false,
+ enabled = false,
+ ...restProps
+ }: Omit, 'queryKey' | 'queryFn'> = {
enabled: false,
refetchOnMount: false,
}
-): UseQueryResult =>
- useQuery({
+): UseQueryResult =>
+ useQuery({
...restProps,
enabled: enabled,
queryFn: blogListRequest,
diff --git a/src/client/features/career/CareerList.tsx b/src/client/features/career/CareerList.tsx
new file mode 100644
index 00000000..a5e3c7a2
--- /dev/null
+++ b/src/client/features/career/CareerList.tsx
@@ -0,0 +1,16 @@
+import type { JSX } from 'react';
+
+import CareerListItem from './CareerListItem';
+import { useCareerListQuery } from './hooks/use-career-list-query';
+
+const CareerList = (): JSX.Element => {
+ const { data: careerList = [], error, isFetching } = useCareerListQuery();
+ return (
+
+ {error &&
Failed to load
}
+ {isFetching ?
Loading...
: careerList.map((item) =>
)}
+
+ );
+};
+
+export default CareerList;
diff --git a/src/client/features/career/CareerListItem.module.scss b/src/client/features/career/CareerListItem.module.scss
new file mode 100644
index 00000000..8a79bdf3
--- /dev/null
+++ b/src/client/features/career/CareerListItem.module.scss
@@ -0,0 +1,5 @@
+@import 'src/assets/variables';
+
+.container {
+ padding: $padding-sm 0;
+}
diff --git a/src/client/features/career/CareerListItem.tsx b/src/client/features/career/CareerListItem.tsx
new file mode 100644
index 00000000..1f162f35
--- /dev/null
+++ b/src/client/features/career/CareerListItem.tsx
@@ -0,0 +1,31 @@
+import type { JSX } from 'react';
+
+import { Button } from '@mui/base';
+import Link from 'next/link';
+import { CAREER_PAGE_URL } from 'src/common/common-constants';
+import { dayjs } from 'src/common/common-date';
+
+import { CareerListItemProps } from './career-types';
+import styles from './CareerListItem.module.scss';
+
+const CareerListItem = ({ item }: CareerListItemProps): JSX.Element => {
+ const { _id: itemId, endDate, description, post, site, startDate, title, tools } = item;
+ return (
+
+
{title}
+
+ {dayjs(startDate).utc().format('MMMM DD, YYYY')}
+ {endDate ? - {dayjs(endDate).utc().format('MMMM DD, YYYY')} : null}
+
+
{post}
+
{site}
+
{description}
+
{tools}
+
+
+
+
+ );
+};
+
+export default CareerListItem;
diff --git a/src/client/features/career/career-api.ts b/src/client/features/career/career-api.ts
new file mode 100644
index 00000000..fb684871
--- /dev/null
+++ b/src/client/features/career/career-api.ts
@@ -0,0 +1,18 @@
+import httpClient from 'src/client/app/http-client';
+import { CareerDTO, CareerModel } from 'src/common/types/common-career-types';
+
+const careerItemAdapter = (dto: CareerDTO): CareerModel => ({
+ ...dto,
+ endDate: dto.endDate && new Date(dto.endDate),
+ startDate: new Date(dto.startDate),
+});
+
+export const careerListRequest = async (): Promise => {
+ const dto = await httpClient.get('career').json();
+ return dto.map(careerItemAdapter);
+};
+
+export const careerItemRequest = async (id: string): Promise => {
+ const dto = await httpClient.get(`career/${id}`).json();
+ return careerItemAdapter(dto);
+};
diff --git a/src/client/features/career/career-constants.ts b/src/client/features/career/career-constants.ts
new file mode 100644
index 00000000..9c99a747
--- /dev/null
+++ b/src/client/features/career/career-constants.ts
@@ -0,0 +1,3 @@
+export const CAREER_LIST_QUERY_KEY = 'CAREER_LIST_QUERY_KEY';
+
+export const CAREER_ITEM_QUERY_KEY = 'CAREER_ITEM_QUERY_KEY';
diff --git a/src/client/features/career/career-types.ts b/src/client/features/career/career-types.ts
new file mode 100644
index 00000000..d1b4f654
--- /dev/null
+++ b/src/client/features/career/career-types.ts
@@ -0,0 +1,5 @@
+import { CareerModel } from 'src/common/types/common-career-types';
+
+export interface CareerListItemProps {
+ item: CareerModel;
+}
diff --git a/src/client/features/career/hooks/use-career-list-query.ts b/src/client/features/career/hooks/use-career-list-query.ts
new file mode 100644
index 00000000..8e5cea18
--- /dev/null
+++ b/src/client/features/career/hooks/use-career-list-query.ts
@@ -0,0 +1,24 @@
+import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query';
+import { useQuery } from '@tanstack/react-query';
+import { CareerModel } from 'src/common/types/common-career-types';
+
+import { careerListRequest } from '../career-api';
+import { CAREER_LIST_QUERY_KEY } from '../career-constants';
+
+export const useCareerListQuery = (
+ {
+ refetchOnMount = false,
+ enabled = false,
+ ...restProps
+ }: Omit, 'queryKey' | 'queryFn'> = {
+ enabled: false,
+ refetchOnMount: false,
+ }
+): UseQueryResult =>
+ useQuery({
+ ...restProps,
+ enabled: enabled,
+ queryFn: careerListRequest,
+ queryKey: [CAREER_LIST_QUERY_KEY],
+ refetchOnMount: refetchOnMount,
+ });
diff --git a/src/client/features/summary/Summary.module.scss b/src/client/features/summary/Summary.module.scss
new file mode 100644
index 00000000..6376796d
--- /dev/null
+++ b/src/client/features/summary/Summary.module.scss
@@ -0,0 +1,5 @@
+@import 'src/assets/variables';
+
+.link {
+ padding: $padding-sm 0;
+}
diff --git a/src/client/features/summary/Summary.tsx b/src/client/features/summary/Summary.tsx
new file mode 100644
index 00000000..d49c44a8
--- /dev/null
+++ b/src/client/features/summary/Summary.tsx
@@ -0,0 +1,23 @@
+import type { JSX } from 'react';
+
+import Link from 'next/link';
+import { BLOG_PAGE_URL, CAREER_PAGE_URL } from 'src/common/common-constants';
+import ApiLink from 'src/common/components/ApiLink';
+
+import styles from './Summary.module.scss';
+
+const Summary = (): JSX.Element => (
+
+ -
+ Blog page
+
+ -
+ Career page
+
+ -
+ API documentation page
+
+
+);
+
+export default Summary;
diff --git a/src/common/common-constants.ts b/src/common/common-constants.ts
index d5181332..858292fc 100644
--- a/src/common/common-constants.ts
+++ b/src/common/common-constants.ts
@@ -1,11 +1,21 @@
export const isServer = typeof window === 'undefined';
-
export const isDev = process.env.NODE_ENV === 'development';
-
+export const PROTOCOL = (isServer ? global.location?.protocol : window.location?.protocol) ?? 'http';
+export const HOST = (isServer ? global.location?.hostname : window.location?.hostname) ?? 'localhost';
export const PORT = process.env.PORT || 3000;
+// protocol comes with ":" on client side
+export const API_URL = isServer ? `${PROTOCOL}://${HOST}:${PORT}/api` : `${PROTOCOL}//${HOST}:${PORT}/api`;
+// routes
+export const BLOG_PAGE_ID = 'blog';
+export const BLOG_PAGE_URL = `/${BLOG_PAGE_ID}`;
+export const CAREER_PAGE_ID = 'career';
+export const CAREER_PAGE_URL = `/${CAREER_PAGE_ID}`;
if (isDev) {
console.log('isServer', isServer);
console.log('isDev', isDev);
+ console.log('PROTOCOL', PROTOCOL);
+ console.log('HOST', HOST);
console.log('PORT', PORT);
+ console.log('API_URL', API_URL);
}
diff --git a/src/common/components/ApiLink.tsx b/src/common/components/ApiLink.tsx
new file mode 100644
index 00000000..af724d13
--- /dev/null
+++ b/src/common/components/ApiLink.tsx
@@ -0,0 +1,12 @@
+import type { PropsWithChildren, ReactElement } from 'react';
+
+import { API_URL } from 'src/common/common-constants';
+import { WithClass } from 'src/common/types/common-types';
+
+const ApiLink = ({ children, className = '' }: PropsWithChildren): ReactElement => (
+
+ {children}
+
+);
+
+export default ApiLink;
diff --git a/src/common/components/Navigation.module.scss b/src/common/components/Navigation.module.scss
index 35993b7b..8acd940e 100644
--- a/src/common/components/Navigation.module.scss
+++ b/src/common/components/Navigation.module.scss
@@ -2,6 +2,7 @@
.nav {
height: $headerHeight;
+
&Link {
padding: $padding-sm;
}
diff --git a/src/common/components/Navigation.tsx b/src/common/components/Navigation.tsx
index 86c7b2db..fba2b651 100644
--- a/src/common/components/Navigation.tsx
+++ b/src/common/components/Navigation.tsx
@@ -1,25 +1,23 @@
import type { ReactElement } from 'react';
import Link from 'next/link';
+import { BLOG_PAGE_URL, CAREER_PAGE_URL } from 'src/common/common-constants';
+import ApiLink from './ApiLink';
import styles from './Navigation.module.scss';
const Navigation = (): ReactElement => (
);
diff --git a/src/common/types/common-blog-types.ts b/src/common/types/common-blog-types.ts
index 329ab3cd..390c648d 100644
--- a/src/common/types/common-blog-types.ts
+++ b/src/common/types/common-blog-types.ts
@@ -1,15 +1,15 @@
-import { IBaseEntity } from './common-types';
+import { BaseEntity } from './common-types';
-export interface IBlogBase extends IBaseEntity {
+export interface BlogModelBase extends BaseEntity {
link: string;
linkCaption: string;
title: string;
}
-export interface IBlogDTO extends IBlogBase {
+export interface BlogDTO extends BlogModelBase {
date: string;
}
-export interface IBlog extends IBlogBase {
+export interface BlogModel extends BlogModelBase {
date: Date;
}
diff --git a/src/common/types/common-career-types.ts b/src/common/types/common-career-types.ts
new file mode 100644
index 00000000..ba236d9f
--- /dev/null
+++ b/src/common/types/common-career-types.ts
@@ -0,0 +1,19 @@
+import { BaseEntity } from './common-types';
+
+export interface CareerModelBase extends BaseEntity {
+ description: string;
+ post: string;
+ site: string;
+ title: string;
+ tools: string;
+}
+
+export interface CareerDTO extends CareerModelBase {
+ endDate?: string;
+ startDate: string;
+}
+
+export interface CareerModel extends CareerModelBase {
+ endDate?: Date;
+ startDate: Date;
+}
diff --git a/src/common/types/common-types.ts b/src/common/types/common-types.ts
index dec5dc7a..66cea69e 100644
--- a/src/common/types/common-types.ts
+++ b/src/common/types/common-types.ts
@@ -1,3 +1,7 @@
-export interface IBaseEntity {
+export interface BaseEntity {
_id?: string;
}
+
+export interface WithClass {
+ className?: string;
+}
diff --git a/src/pages/blog/[id].tsx b/src/pages/blog/[id].tsx
index edfa3d98..94c8a32d 100644
--- a/src/pages/blog/[id].tsx
+++ b/src/pages/blog/[id].tsx
@@ -2,20 +2,20 @@ import type { JSX } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
-import { blogRequest } from 'src/client/features/blog/blog-api';
-import type { IBlog } from 'src/common/types/common-blog-types';
+import { blogItemRequest } from 'src/client/features/blog/blog-api';
+import type { BlogModel } from 'src/common/types/common-blog-types';
import { BLOG_ID_QUERY_KEY } from '../../client/features/blog/blog-constants';
const Blog = (): JSX.Element => {
const router = useRouter();
const id = router.query?.id as string;
- const { data, error } = useQuery({
- queryFn: () => blogRequest(id),
+ const { data, error } = useQuery({
+ queryFn: () => blogItemRequest(id),
queryKey: [BLOG_ID_QUERY_KEY, id],
refetchOnMount: false,
});
- const blog = data ?? ({} as IBlog);
+ const blog = data ?? ({} as BlogModel);
if (error) return Failed to load
;
if (!blog) return Loading...
;
diff --git a/src/pages/career.tsx b/src/pages/career.tsx
index 8444a9e1..c6bd7186 100644
--- a/src/pages/career.tsx
+++ b/src/pages/career.tsx
@@ -1,5 +1,22 @@
import type { JSX } from 'react';
+import { useCallback } from 'react';
-const Career = (): JSX.Element => Career
;
+import { Button } from '@mui/base';
+import CareerList from 'src/client/features/career/CareerList';
+import { useCareerListQuery } from 'src/client/features/career/hooks/use-career-list-query';
-export default Career;
+const Careers = (): JSX.Element => {
+ const { refetch } = useCareerListQuery({ enabled: true });
+ const handleRefresh = useCallback(() => refetch(), [refetch]);
+ return (
+
+
+
Career
+
+
+
+
+ );
+};
+
+export default Careers;
diff --git a/src/pages/career/[id].tsx b/src/pages/career/[id].tsx
new file mode 100644
index 00000000..22d80eee
--- /dev/null
+++ b/src/pages/career/[id].tsx
@@ -0,0 +1,32 @@
+import type { JSX } from 'react';
+
+import { useQuery } from '@tanstack/react-query';
+import { useRouter } from 'next/router';
+
+import { BLOG_ID_QUERY_KEY } from '../../client/features/blog/blog-constants';
+import { careerItemRequest } from '../../client/features/career/career-api';
+import { CareerModel } from '../../common/types/common-career-types';
+
+const Career = (): JSX.Element => {
+ const router = useRouter();
+ const id = router.query?.id as string;
+ const { data, error } = useQuery({
+ queryFn: () => careerItemRequest(id),
+ queryKey: [BLOG_ID_QUERY_KEY, id],
+ refetchOnMount: false,
+ });
+ const career = data ?? ({} as CareerModel);
+
+ if (error) return Failed to load
;
+ if (!career) return Loading...
;
+ return (
+ <>
+ {career.title}
+
+
{JSON.stringify(career)}
+
+ >
+ );
+};
+
+export default Career;
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 2077573a..91a9fd4f 100644
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,23 +1,11 @@
import type { JSX } from 'react';
-import Link from 'next/link';
+import Summary from 'src/client/features/summary/Summary';
const Home = (): JSX.Element => (
);
diff --git a/src/server/features/blog/blog-api-controller.ts b/src/server/features/blog/blog-api-controller.ts
index 385a7f48..68e781e1 100644
--- a/src/server/features/blog/blog-api-controller.ts
+++ b/src/server/features/blog/blog-api-controller.ts
@@ -1,30 +1,35 @@
-import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common';
+import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
-import type { CreateBlogDto } from './blog-models';
-import type { Blog } from './blog-schema';
+import { CreateBlogDTO, UpdateBlogDTO } from './blog-models';
+import { Blog } from './blog-schema';
import { BlogService } from './blog-service';
-@Controller()
+@Controller('api/blog')
export class BlogApiController {
constructor(private readonly blogService: BlogService) {}
- @Post('api/blog')
- async create(@Body() dto: CreateBlogDto): Promise {
- return await this.blogService.create(dto);
+ @Get()
+ findAll(): Promise {
+ return this.blogService.findAll();
}
- @Put('api/blog/:id')
- async update(@Param('id') id: string, @Body() dto: CreateBlogDto): Promise {
- return await this.blogService.update(id, dto);
+ @Get(':id')
+ findOne(@Param('id') prodId: string): Promise {
+ return this.blogService.findOne(prodId);
}
- @Get('api/blog')
- async getAll(): Promise {
- return await this.blogService.getAll();
+ @Post()
+ create(@Body() dto: CreateBlogDTO): Promise {
+ return this.blogService.create(dto);
}
- @Get('api/blog/:id')
- getById(@Param('id') prodId: string): Promise {
- return this.blogService.getById(prodId);
+ @Patch(':id')
+ update(@Param('id') id: string, @Body() dto: UpdateBlogDTO): Promise {
+ return this.blogService.update(id, dto);
+ }
+
+ @Delete(':id')
+ delete(@Param('id') id: string): Promise {
+ return this.blogService.delete(id);
}
}
diff --git a/src/server/features/blog/blog-controller.ts b/src/server/features/blog/blog-controller.ts
index 3d9271f6..a7ff207f 100644
--- a/src/server/features/blog/blog-controller.ts
+++ b/src/server/features/blog/blog-controller.ts
@@ -1,10 +1,11 @@
import { Controller, Get, Render, UseInterceptors } from '@nestjs/common';
+import { BLOG_PAGE_ID, BLOG_PAGE_URL } from 'src/common/common-constants';
import { ParamsInterceptor } from 'src/server/common/params/params-interceptor';
@Controller()
export class BlogController {
- @Get('/blog')
- @Render('blog')
+ @Get(BLOG_PAGE_URL)
+ @Render(BLOG_PAGE_ID)
@UseInterceptors(ParamsInterceptor)
blogList() {
return {};
@@ -13,7 +14,7 @@ export class BlogController {
@Get('/blog/:id')
@Render('blog/[id]')
@UseInterceptors(ParamsInterceptor)
- blogPost() {
+ blogItem() {
return {};
}
}
diff --git a/src/server/features/blog/blog-models.ts b/src/server/features/blog/blog-models.ts
index b18d56f4..eca60c79 100644
--- a/src/server/features/blog/blog-models.ts
+++ b/src/server/features/blog/blog-models.ts
@@ -1,8 +1,8 @@
-import { IsString } from '@nestjs/class-validator';
+import { IsDate, IsString } from '@nestjs/class-validator';
import { PartialType } from '@nestjs/mapped-types';
-import type { IBlogDTO } from 'src/common/types/common-blog-types';
+import { BlogModel } from 'src/common/types/common-blog-types';
-export class CreateBlogDto implements IBlogDTO {
+export class CreateBlogDTO implements BlogModel {
@IsString()
readonly title: string;
@@ -12,11 +12,8 @@ export class CreateBlogDto implements IBlogDTO {
@IsString()
readonly linkCaption: string;
- // @IsDate()
- // readonly date: Date;
- // TODO: temporary solution before dates migration
- @IsString()
- readonly date: string;
+ @IsDate()
+ readonly date: Date;
}
-export class UpdateBlogDto extends PartialType(CreateBlogDto) {}
+export class UpdateBlogDTO extends PartialType(CreateBlogDTO) {}
diff --git a/src/server/features/blog/blog-schema.ts b/src/server/features/blog/blog-schema.ts
index 505a83a9..c713cf6a 100644
--- a/src/server/features/blog/blog-schema.ts
+++ b/src/server/features/blog/blog-schema.ts
@@ -1,9 +1,9 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
-import type { IBlog } from 'src/common/types/common-blog-types';
+import type { BlogModel } from 'src/common/types/common-blog-types';
@Schema()
-export class Blog extends Document implements IBlog {
+export class Blog extends Document implements BlogModel {
@Prop({ required: true, type: String })
readonly title: string;
diff --git a/src/server/features/blog/blog-service.ts b/src/server/features/blog/blog-service.ts
index 04e7d1ed..1786ebfd 100644
--- a/src/server/features/blog/blog-service.ts
+++ b/src/server/features/blog/blog-service.ts
@@ -1,47 +1,44 @@
-import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
+import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import type { Model } from 'mongoose';
-import type { CreateBlogDto, UpdateBlogDto } from './blog-models';
+import type { CreateBlogDTO, UpdateBlogDTO } from './blog-models';
import { Blog } from './blog-schema';
@Injectable()
export class BlogService {
constructor(@InjectModel(Blog.name) private readonly blogModel: Model) {}
- async create(createBlogDto: CreateBlogDto): Promise {
- console.log(`BlogService create(${JSON.stringify(createBlogDto, null, 1)})`);
- // TODO: temporary solution before dates migration
- const date = new Date(createBlogDto.date);
- const blog = new this.blogModel({ ...createBlogDto, date });
- return blog.save();
+ // TODO: add filters and pagination support
+ async findAll(): Promise {
+ return await this.blogModel.find({}).sort({ date: 'desc' }).exec();
}
- async update(id: string, updateBlogDto: UpdateBlogDto): Promise {
- console.log(`BlogService update(${id}, ${JSON.stringify(updateBlogDto, null, 1)})`);
- // TODO: temporary solution before dates migration
- const date = new Date(updateBlogDto.date);
- const blog = await this.blogModel.findByIdAndUpdate(id, { ...updateBlogDto, date }, { new: true });
- if (!blog) {
- throw new InternalServerErrorException('Could not update blogReducer record.');
- }
- return blog;
+ async findOne(id: string): Promise {
+ const item = await this.blogModel.findOne({ _id: id }).exec();
+ return this.returnIfExists(id, item);
}
- async getAll(): Promise {
- return await this.blogModel.find({}).sort({ date: 'desc' }).exec();
+ async create(dto: CreateBlogDTO): Promise {
+ console.log(`BlogService create(${JSON.stringify(dto, null, 1)})`);
+ return this.blogModel.create(dto);
}
- async getById(id: string): Promise {
- let blog;
- try {
- blog = await this.blogModel.findById(id).exec();
- } catch (error) {
- throw new NotFoundException('Could not find blogReducer record.');
- }
- if (!blog) {
- throw new NotFoundException('Could not find blogReducer record.');
+ async update(id: string, dto: UpdateBlogDTO): Promise {
+ console.log(`BlogService update(${id}, ${JSON.stringify(dto, null, 1)})`);
+ const item = await this.blogModel.findByIdAndUpdate(id, dto, { new: true }).exec();
+ return this.returnIfExists(id, item);
+ }
+
+ async delete(id: string): Promise {
+ const word = await this.blogModel.findByIdAndRemove(id).exec();
+ return this.returnIfExists(id, word);
+ }
+
+ private returnIfExists(id: string, item: Blog | null): Blog {
+ if (!item) {
+ throw new NotFoundException(`A blog with id: "${id}" is not found`);
}
- return blog;
+ return item;
}
}
diff --git a/src/server/features/career/career-api-controller.ts b/src/server/features/career/career-api-controller.ts
new file mode 100644
index 00000000..82df7fc9
--- /dev/null
+++ b/src/server/features/career/career-api-controller.ts
@@ -0,0 +1,35 @@
+import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
+
+import { CreateCareerDTO, UpdateCareerDTO } from './career-models';
+import { Career } from './career-schema';
+import { CareerService } from './career-service';
+
+@Controller('api/career')
+export class CareerApiController {
+ constructor(private readonly careerService: CareerService) {}
+
+ @Get()
+ findAll(): Promise {
+ return this.careerService.findAll();
+ }
+
+ @Get(':id')
+ findOne(@Param('id') prodId: string): Promise {
+ return this.careerService.findOne(prodId);
+ }
+
+ @Post()
+ create(@Body() dto: CreateCareerDTO): Promise {
+ return this.careerService.create(dto);
+ }
+
+ @Patch(':id')
+ update(@Param('id') id: string, @Body() dto: UpdateCareerDTO): Promise {
+ return this.careerService.update(id, dto);
+ }
+
+ @Delete(':id')
+ delete(@Param('id') id: string): Promise {
+ return this.careerService.delete(id);
+ }
+}
diff --git a/src/server/features/career/career-controller.ts b/src/server/features/career/career-controller.ts
index 7cd8ead6..5ec8e6d9 100644
--- a/src/server/features/career/career-controller.ts
+++ b/src/server/features/career/career-controller.ts
@@ -1,25 +1,20 @@
-import { Body, Controller, Get, Param, Post } from '@nestjs/common';
+import { Controller, Get, Render, UseInterceptors } from '@nestjs/common';
+import { CAREER_PAGE_ID, CAREER_PAGE_URL } from 'src/common/common-constants';
+import { ParamsInterceptor } from 'src/server/common/params/params-interceptor';
-import { CareerService } from './career-service';
-import type { CreateCareerDto } from './dto/create-career.dto';
-import type { Career } from './entities/career.entity';
-
-@Controller('api/career')
+@Controller()
export class CareerController {
- constructor(private readonly careerService: CareerService) {}
-
- @Post()
- async create(@Body() dto: CreateCareerDto): Promise {
- return await this.careerService.create(dto);
- }
-
- @Get()
- async getAll(): Promise {
- return await this.careerService.getAll();
+ @Get(CAREER_PAGE_URL)
+ @Render(CAREER_PAGE_ID)
+ @UseInterceptors(ParamsInterceptor)
+ careerList() {
+ return {};
}
- @Get(':id')
- getById(@Param('id') prodId: string): Promise {
- return this.careerService.getById(prodId);
+ @Get('/career/:id')
+ @Render('career/[id]')
+ @UseInterceptors(ParamsInterceptor)
+ careerItem() {
+ return {};
}
}
diff --git a/src/server/features/career/career-models.ts b/src/server/features/career/career-models.ts
new file mode 100644
index 00000000..1a14f6fd
--- /dev/null
+++ b/src/server/features/career/career-models.ts
@@ -0,0 +1,28 @@
+import { IsDate, IsString } from '@nestjs/class-validator';
+import { PartialType } from '@nestjs/mapped-types';
+import { CareerModel } from 'src/common/types/common-career-types';
+
+export class CreateCareerDTO implements CareerModel {
+ @IsString()
+ readonly title: string;
+
+ @IsString()
+ readonly site: string;
+
+ @IsString()
+ readonly post: string;
+
+ @IsString()
+ readonly description: string;
+
+ @IsString()
+ readonly tools: string;
+
+ @IsDate()
+ readonly startDate: Date;
+
+ @IsDate()
+ readonly endDate: Date;
+}
+
+export class UpdateCareerDTO extends PartialType(CreateCareerDTO) {}
diff --git a/src/server/features/career/career-module.ts b/src/server/features/career/career-module.ts
index d743ea24..de7f3d6e 100644
--- a/src/server/features/career/career-module.ts
+++ b/src/server/features/career/career-module.ts
@@ -1,9 +1,10 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
+import { CareerApiController } from './career-api-controller';
import { CareerController } from './career-controller';
+import { Career, CareerSchema } from './career-schema';
import { CareerService } from './career-service';
-import { Career, CareerSchema } from './entities/career.entity';
@Module({
imports: [
@@ -14,7 +15,7 @@ import { Career, CareerSchema } from './entities/career.entity';
},
]),
],
- controllers: [CareerController],
+ controllers: [CareerApiController, CareerController],
providers: [CareerService],
})
export class CareerModule {}
diff --git a/src/server/features/career/entities/career.entity.ts b/src/server/features/career/career-schema.ts
similarity index 78%
rename from src/server/features/career/entities/career.entity.ts
rename to src/server/features/career/career-schema.ts
index 1791cb94..2c7ff0cd 100644
--- a/src/server/features/career/entities/career.entity.ts
+++ b/src/server/features/career/career-schema.ts
@@ -1,8 +1,9 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
+import { CareerModel } from 'src/common/types/common-career-types';
@Schema()
-export class Career extends Document {
+export class Career extends Document implements CareerModel {
@Prop({ required: true, type: String })
readonly title: string;
@@ -22,7 +23,7 @@ export class Career extends Document {
readonly startDate: Date;
@Prop({ type: Date })
- readonly endDate: Date;
+ readonly endDate?: Date;
}
export const CareerSchema = SchemaFactory.createForClass(Career);
diff --git a/src/server/features/career/career-service.ts b/src/server/features/career/career-service.ts
index 1d220ae8..f0d5bd52 100644
--- a/src/server/features/career/career-service.ts
+++ b/src/server/features/career/career-service.ts
@@ -1,33 +1,44 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
-import type { Model } from 'mongoose';
+import { Model } from 'mongoose';
-import type { CreateCareerDto } from './dto/create-career.dto';
-import { Career } from './entities/career.entity';
+import { CreateCareerDTO, UpdateCareerDTO } from './career-models';
+import { Career } from './career-schema';
@Injectable()
export class CareerService {
constructor(@InjectModel(Career.name) private readonly careerModel: Model) {}
- async create(createCareerDto: CreateCareerDto): Promise {
- const career = new this.careerModel(createCareerDto);
- return career.save();
+ // TODO: add filters and pagination support
+ async findAll(): Promise {
+ return await this.careerModel.find({}).sort({ startDate: 'desc' }).exec();
}
- async getAll(): Promise {
- return await this.careerModel.find({}).sort({ startDate: 'desc' }).exec();
+ async findOne(id: string): Promise {
+ const item = await this.careerModel.findOne({ _id: id }).exec();
+ return this.returnIfExists(id, item);
}
- async getById(id: string): Promise {
- let career;
- try {
- career = await this.careerModel.findById(id).exec();
- } catch (error) {
- throw new NotFoundException('Could not find career record.');
- }
- if (!career) {
- throw new NotFoundException('Could not find career record.');
+ async create(dto: CreateCareerDTO): Promise {
+ console.log(`CareerService create(${JSON.stringify(dto, null, 1)})`);
+ return this.careerModel.create(dto);
+ }
+
+ async update(id: string, dto: UpdateCareerDTO): Promise {
+ console.log(`CareerService update(${id}, ${JSON.stringify(dto, null, 1)})`);
+ const item = await this.careerModel.findByIdAndUpdate(id, dto, { new: true }).exec();
+ return this.returnIfExists(id, item);
+ }
+
+ async delete(id: string): Promise {
+ const item = await this.careerModel.findByIdAndRemove(id).exec();
+ return this.returnIfExists(id, item);
+ }
+
+ private returnIfExists(id: string, item: Career | null): Career {
+ if (!item) {
+ throw new NotFoundException(`A career item with id: "${id}" is not found`);
}
- return career;
+ return item;
}
}
diff --git a/src/server/features/career/dto/create-career.dto.ts b/src/server/features/career/dto/create-career.dto.ts
deleted file mode 100644
index 6b11a1b8..00000000
--- a/src/server/features/career/dto/create-career.dto.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import { IsString, IsDate } from '@nestjs/class-validator';
-
-export class CreateCareerDto {
- @IsString()
- readonly title: string;
-
- @IsString()
- readonly site: string;
-
- @IsString()
- readonly post: string;
-
- @IsString()
- readonly description: string;
-
- @IsString()
- readonly tools: string;
-
- @IsDate()
- readonly startDate: Date;
-
- @IsDate()
- readonly endDate: Date;
-}
diff --git a/src/server/features/career/dto/update-career.dto.ts b/src/server/features/career/dto/update-career.dto.ts
deleted file mode 100644
index 2ed8fdb7..00000000
--- a/src/server/features/career/dto/update-career.dto.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { PartialType } from '@nestjs/mapped-types';
-
-import { CreateCareerDto } from './create-career.dto';
-
-export class UpdateCareerDto extends PartialType(CreateCareerDto) {}