Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Contribute to GitLab
Sign in
Toggle navigation
I
initial_project
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Нұрасыл Қайратұлы
initial_project
Commits
3144fc0a
Commit
3144fc0a
authored
Aug 02, 2024
by
Nurasyl
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update
parent
9eff7f53
Hide whitespace changes
Inline
Side-by-side
Showing
23 changed files
with
303 additions
and
30 deletions
+303
-30
ProductForm.tsx
client/src/components/ProductForm.tsx
+29
-3
CategorySelect.tsx
client/src/components/UI/Form/CategorySelect.tsx
+37
-0
ProductItem.tsx
client/src/containers/ProductItem.tsx
+4
-4
Products.tsx
client/src/containers/Products.tsx
+2
-1
productsSlice.ts
client/src/features/productsSlice.ts
+13
-3
package-lock.json
server/package-lock.json
+29
-0
package.json
server/package.json
+1
-0
e03c1e0a-3cf2-4498-8d6f-e62c482d4f61.jpg
...r/public/uploads/e03c1e0a-3cf2-4498-8d6f-e62c482d4f61.jpg
+0
-0
f97f67c1-2134-4d2c-9445-470c32cc38d4.jpg
...r/public/uploads/f97f67c1-2134-4d2c-9445-470c32cc38d4.jpg
+0
-0
appDataSource.ts
server/src/appDataSource.ts
+4
-3
category.controller.ts
server/src/controllers/category.controller.ts
+28
-0
product.controller.ts
server/src/controllers/product.controller.ts
+16
-7
category.dto.ts
server/src/dto/category.dto.ts
+7
-0
product.dto.ts
server/src/dto/product.dto.ts
+20
-4
category.entity.ts
server/src/entities/category.entity.ts
+17
-0
product.entity.ts
server/src/entities/product.entity.ts
+5
-1
formatErrors.ts
server/src/helpers/formatErrors.ts
+16
-0
index.ts
server/src/index.ts
+2
-1
category.repository.ts
server/src/repositories/category.repository.ts
+26
-0
product.repository.ts
server/src/repositories/product.repository.ts
+6
-2
category.route.ts
server/src/routes/category.route.ts
+19
-0
category.service.ts
server/src/services/category.service.ts
+18
-0
product.service.ts
server/src/services/product.service.ts
+4
-1
No files found.
client/src/components/ProductForm.tsx
View file @
3144fc0a
import
{
IProduct
}
from
"@/containers/Products"
;
import
{
IProduct
}
from
"@/containers/Products"
;
import
{
Box
,
Button
,
Grid
,
TextField
}
from
"@mui/material"
;
import
{
Box
,
Button
,
Grid
,
SelectChangeEvent
,
TextField
}
from
"@mui/material"
;
import
{
ChangeEvent
,
FormEvent
,
useState
}
from
"react"
;
import
{
ChangeEvent
,
FormEvent
,
use
Callback
,
useEffect
,
use
State
}
from
"react"
;
import
{
FileInput
}
from
"./UI/Form/FileInput"
;
import
{
FileInput
}
from
"./UI/Form/FileInput"
;
import
CategorySelect
,
{
ICategory
}
from
"./UI/Form/CategorySelect"
;
import
{
axiosApiClient
}
from
"@/helpers/axiosApiClient"
;
interface
Props
{
interface
Props
{
...
@@ -9,13 +11,24 @@ interface Props {
...
@@ -9,13 +11,24 @@ interface Props {
}
}
export
function
ProductForm
({
onProductFormSubmit
}:
Props
)
{
export
function
ProductForm
({
onProductFormSubmit
}:
Props
)
{
const
[
categories
,
setCategories
]
=
useState
<
ICategory
[]
|
null
>
(
null
);
const
[
product
,
setProduct
]
=
useState
<
Omit
<
IProduct
,
"id"
>>
({
const
[
product
,
setProduct
]
=
useState
<
Omit
<
IProduct
,
"id"
>>
({
description
:
""
,
description
:
""
,
price
:
0
,
price
:
0
,
title
:
""
,
title
:
""
,
image
:
""
image
:
""
,
categoryId
:
""
});
});
const
getCategories
=
useCallback
(
async
()
=>
{
const
{
data
}
=
await
axiosApiClient
.
get
(
'/category'
)
return
data
},
[])
useEffect
(()
=>
{
getCategories
().
then
(
res
=>
setCategories
(
res
))
},
[
getCategories
])
const
submitFormHandler
=
(
e
:
FormEvent
<
HTMLFormElement
>
)
=>
{
const
submitFormHandler
=
(
e
:
FormEvent
<
HTMLFormElement
>
)
=>
{
e
.
preventDefault
();
e
.
preventDefault
();
...
@@ -48,6 +61,11 @@ export function ProductForm({onProductFormSubmit}: Props) {
...
@@ -48,6 +61,11 @@ export function ProductForm({onProductFormSubmit}: Props) {
}
}
}
}
const
handleChangeSelect
=
(
event
:
SelectChangeEvent
)
=>
{
const
val
=
event
.
target
.
value
as
string
setProduct
(
prevState
=>
({...
prevState
,
categoryId
:
val
}))
}
return
<
Box
return
<
Box
component=
{
"form"
}
component=
{
"form"
}
autoComplete=
"off"
autoComplete=
"off"
...
@@ -95,6 +113,14 @@ export function ProductForm({onProductFormSubmit}: Props) {
...
@@ -95,6 +113,14 @@ export function ProductForm({onProductFormSubmit}: Props) {
label=
"image"
label=
"image"
/>
/>
</
Grid
>
</
Grid
>
<
Grid
item
xs
>
<
CategorySelect
handleChange=
{
handleChangeSelect
}
value=
{
product
.
categoryId
}
label=
"Category"
categories=
{
categories
||
[]
}
/>
</
Grid
>
<
Grid
item
xs
>
<
Grid
item
xs
>
<
Button
type=
"submit"
color=
"primary"
variant=
"contained"
>
<
Button
type=
"submit"
color=
"primary"
variant=
"contained"
>
Create
Create
...
...
client/src/components/UI/Form/CategorySelect.tsx
0 → 100644
View file @
3144fc0a
import
{
FormControl
,
InputLabel
,
Select
,
MenuItem
,
SelectChangeEvent
}
from
"@mui/material"
export
interface
ICategory
{
id
:
string
title
:
string
description
:
string
}
type
TProps
=
{
handleChange
:
(
event
:
SelectChangeEvent
)
=>
void
value
:
string
label
:
string
categories
:
ICategory
[]
}
const
CategorySelect
=
({
handleChange
,
value
,
label
,
categories
}:
TProps
)
=>
{
return
(
<
FormControl
fullWidth
>
<
InputLabel
id=
"demo-simple-select-label"
>
{
label
}
</
InputLabel
>
<
Select
labelId=
"demo-simple-select-label"
id=
"demo-simple-select"
value=
{
value
}
label=
{
label
}
onChange=
{
handleChange
}
>
{
categories
.
map
(
item
=>
(
<
MenuItem
key=
{
item
.
id
}
value=
{
item
.
id
}
>
{
item
.
title
}
</
MenuItem
>
))
}
</
Select
>
</
FormControl
>
)
}
export
default
CategorySelect
\ No newline at end of file
client/src/containers/ProductItem.tsx
View file @
3144fc0a
...
@@ -8,18 +8,18 @@ import {
...
@@ -8,18 +8,18 @@ import {
IconButton
,
IconButton
,
Typography
,
Typography
,
}
from
'@mui/material'
;
}
from
'@mui/material'
;
import
{
IProduct
}
from
'./Products'
;
import
{
ArrowForward
}
from
'@mui/icons-material'
;
import
{
ArrowForward
}
from
'@mui/icons-material'
;
import
{
Link
}
from
'react-router-dom'
;
import
{
Link
}
from
'react-router-dom'
;
import
{
IProductState
}
from
'@/features/productsSlice'
;
interface
Props
{
interface
Props
{
product
:
IProduct
;
product
:
IProduct
State
;
}
}
const
apiUrl
=
'http://localhost:8000'
const
apiUrl
=
'http://localhost:8000'
export
function
ProductItem
({
product
}:
Props
)
{
export
function
ProductItem
({
product
}:
Props
)
{
const
{
title
,
price
,
id
,
description
,
image
}
=
product
;
const
{
title
,
price
,
id
,
description
,
image
,
category
}
=
product
;
let
cardImage
=
`
${
apiUrl
}
/uploads/unnamed.png`
let
cardImage
=
`
${
apiUrl
}
/uploads/unnamed.png`
if
(
image
&&
image
!==
''
)
{
if
(
image
&&
image
!==
''
)
{
...
@@ -29,7 +29,7 @@ export function ProductItem({ product }: Props) {
...
@@ -29,7 +29,7 @@ export function ProductItem({ product }: Props) {
return
(
return
(
<
Grid
item
xs=
{
12
}
sm=
{
12
}
md=
{
6
}
lg=
{
4
}
>
<
Grid
item
xs=
{
12
}
sm=
{
12
}
md=
{
6
}
lg=
{
4
}
>
<
Card
sx=
{
{
minWidth
:
275
}
}
>
<
Card
sx=
{
{
minWidth
:
275
}
}
>
<
CardHeader
title=
{
title
}
/>
<
CardHeader
title=
{
`Категория: ${category.title} - ${title}`
}
/>
<
CardMedia
<
CardMedia
image=
{
cardImage
}
image=
{
cardImage
}
title=
{
title
}
title=
{
title
}
...
...
client/src/containers/Products.tsx
View file @
3144fc0a
...
@@ -41,5 +41,6 @@ export interface IProduct {
...
@@ -41,5 +41,6 @@ export interface IProduct {
title
:
string
;
title
:
string
;
description
:
string
;
description
:
string
;
price
:
number
;
price
:
number
;
image
:
string
image
:
string
;
categoryId
:
string
}
}
client/src/features/productsSlice.ts
View file @
3144fc0a
import
{
createAsyncThunk
,
createSlice
}
from
"@reduxjs/toolkit"
;
import
{
createAsyncThunk
,
createSlice
}
from
"@reduxjs/toolkit"
;
import
{
IProduct
}
from
"@/containers/Products"
;
import
{
axiosApiClient
}
from
"../helpers/axiosApiClient"
;
import
{
axiosApiClient
}
from
"../helpers/axiosApiClient"
;
import
{
ICategory
}
from
"@/components/UI/Form/CategorySelect"
;
import
{
IProduct
}
from
"@/containers/Products"
;
export
interface
IProductState
{
id
:
string
;
title
:
string
;
description
:
string
;
price
:
number
;
image
:
string
;
category
:
ICategory
}
interface
State
{
interface
State
{
products
:
IProduct
[];
products
:
IProduct
State
[];
error
:
Error
|
null
;
error
:
Error
|
null
;
loading
:
boolean
;
loading
:
boolean
;
}
}
...
@@ -15,7 +25,7 @@ const initialState: State = {
...
@@ -15,7 +25,7 @@ const initialState: State = {
};
};
export
const
fetchProducts
=
createAsyncThunk
(
'fetch/products'
,
async
()
=>
{
export
const
fetchProducts
=
createAsyncThunk
(
'fetch/products'
,
async
()
=>
{
return
await
axiosApiClient
.
get
<
IProduct
[]
>
(
'/products'
).
then
(
res
=>
res
.
data
);
return
await
axiosApiClient
.
get
<
IProduct
State
[]
>
(
'/products'
).
then
(
res
=>
res
.
data
);
});
});
export
const
createProduct
=
createAsyncThunk
(
'create/product'
,
export
const
createProduct
=
createAsyncThunk
(
'create/product'
,
...
...
server/package-lock.json
View file @
3144fc0a
...
@@ -11,6 +11,7 @@
...
@@ -11,6 +11,7 @@
"dependencies"
:
{
"dependencies"
:
{
"@types/multer"
:
"^1.4.11"
,
"@types/multer"
:
"^1.4.11"
,
"class-transformer"
:
"^0.5.1"
,
"class-transformer"
:
"^0.5.1"
,
"class-validator"
:
"^0.14.1"
,
"cors"
:
"^2.8.5"
,
"cors"
:
"^2.8.5"
,
"express"
:
"^4.18.2"
,
"express"
:
"^4.18.2"
,
"multer"
:
"^1.4.5-lts.1"
,
"multer"
:
"^1.4.5-lts.1"
,
...
@@ -499,6 +500,11 @@
...
@@ -499,6 +500,11 @@
"@types/node"
:
"*"
"@types/node"
:
"*"
}
}
},
},
"node_modules/@types/validator"
:
{
"version"
:
"13.12.0"
,
"resolved"
:
"https://registry.npmjs.org/@types/validator/-/validator-13.12.0.tgz"
,
"integrity"
:
"sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag=="
},
"node_modules/@typescript-eslint/eslint-plugin"
:
{
"node_modules/@typescript-eslint/eslint-plugin"
:
{
"version"
:
"6.21.0"
,
"version"
:
"6.21.0"
,
"resolved"
:
"https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz"
,
...
@@ -1070,6 +1076,16 @@
...
@@ -1070,6 +1076,16 @@
"resolved"
:
"https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz"
,
"integrity"
:
"sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
"integrity"
:
"sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
},
},
"node_modules/class-validator"
:
{
"version"
:
"0.14.1"
,
"resolved"
:
"https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz"
,
"integrity"
:
"sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ=="
,
"dependencies"
:
{
"@types/validator"
:
"^13.11.8"
,
"libphonenumber-js"
:
"^1.10.53"
,
"validator"
:
"^13.9.0"
}
},
"node_modules/cli-highlight"
:
{
"node_modules/cli-highlight"
:
{
"version"
:
"2.1.11"
,
"version"
:
"2.1.11"
,
"resolved"
:
"https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz"
,
"resolved"
:
"https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz"
,
...
@@ -2360,6 +2376,11 @@
...
@@ -2360,6 +2376,11 @@
"node"
:
">= 0.8.0"
"node"
:
">= 0.8.0"
}
}
},
},
"node_modules/libphonenumber-js"
:
{
"version"
:
"1.11.5"
,
"resolved"
:
"https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.5.tgz"
,
"integrity"
:
"sha512-TwHR5BZxGRODtAfz03szucAkjT5OArXr+94SMtAM2pYXIlQNVMrxvb6uSCbnaJJV6QXEyICk7+l6QPgn72WHhg=="
},
"node_modules/locate-path"
:
{
"node_modules/locate-path"
:
{
"version"
:
"6.0.0"
,
"version"
:
"6.0.0"
,
"resolved"
:
"https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
,
...
@@ -4038,6 +4059,14 @@
...
@@ -4038,6 +4059,14 @@
"resolved"
:
"https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
,
"integrity"
:
"sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
"integrity"
:
"sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
},
},
"node_modules/validator"
:
{
"version"
:
"13.12.0"
,
"resolved"
:
"https://registry.npmjs.org/validator/-/validator-13.12.0.tgz"
,
"integrity"
:
"sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg=="
,
"engines"
:
{
"node"
:
">= 0.10"
}
},
"node_modules/vary"
:
{
"node_modules/vary"
:
{
"version"
:
"1.1.2"
,
"version"
:
"1.1.2"
,
"resolved"
:
"https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/vary/-/vary-1.1.2.tgz"
,
...
...
server/package.json
View file @
3144fc0a
...
@@ -15,6 +15,7 @@
...
@@ -15,6 +15,7 @@
"dependencies"
:
{
"dependencies"
:
{
"@types/multer"
:
"^1.4.11"
,
"@types/multer"
:
"^1.4.11"
,
"class-transformer"
:
"^0.5.1"
,
"class-transformer"
:
"^0.5.1"
,
"class-validator"
:
"^0.14.1"
,
"cors"
:
"^2.8.5"
,
"cors"
:
"^2.8.5"
,
"express"
:
"^4.18.2"
,
"express"
:
"^4.18.2"
,
"multer"
:
"^1.4.5-lts.1"
,
"multer"
:
"^1.4.5-lts.1"
,
...
...
server/public/uploads/e03c1e0a-3cf2-4498-8d6f-e62c482d4f61.jpg
0 → 100644
View file @
3144fc0a
7.29 KB
server/public/uploads/f97f67c1-2134-4d2c-9445-470c32cc38d4.jpg
0 → 100644
View file @
3144fc0a
7.29 KB
server/src/appDataSource.ts
View file @
3144fc0a
import
{
DataSource
}
from
"typeorm"
;
import
{
DataSource
}
from
"typeorm"
;
import
{
Product
}
from
"./entities/product.entity"
;
import
{
Product
}
from
"./entities/product.entity"
;
import
{
Category
}
from
"./entities/category.entity"
;
export
const
AppDataSource
=
new
DataSource
({
export
const
AppDataSource
=
new
DataSource
({
type
:
"postgres"
,
type
:
"postgres"
,
...
@@ -7,8 +8,8 @@ export const AppDataSource = new DataSource({
...
@@ -7,8 +8,8 @@ export const AppDataSource = new DataSource({
port
:
5432
,
port
:
5432
,
username
:
"postgres"
,
username
:
"postgres"
,
password
:
"root"
,
password
:
"root"
,
database
:
"
classwork
"
,
database
:
"
postgres
"
,
schema
:
"
classwork
"
,
schema
:
"
public
"
,
synchronize
:
true
,
synchronize
:
true
,
entities
:
[
Product
]
entities
:
[
Product
,
Category
]
})
})
\ No newline at end of file
server/src/controllers/category.controller.ts
0 → 100644
View file @
3144fc0a
import
{
CategoryDto
}
from
'@/dto/category.dto'
;
import
{
CategoryService
}
from
'@/services/category.service'
;
import
{
plainToInstance
}
from
'class-transformer'
;
import
{
RequestHandler
}
from
'express'
;
export
class
CategoryController
{
private
service
:
CategoryService
;
constructor
()
{
this
.
service
=
new
CategoryService
();
}
getAllCategories
:
RequestHandler
=
async
(
req
,
res
):
Promise
<
void
>
=>
{
const
categories
=
await
this
.
service
.
getAllCategories
();
res
.
send
(
categories
);
};
getCategory
:
RequestHandler
=
async
(
req
,
res
):
Promise
<
void
>
=>
{
const
category
=
await
this
.
service
.
getCategory
(
req
.
params
.
id
);
res
.
send
(
category
);
};
createCategory
:
RequestHandler
=
async
(
req
,
res
):
Promise
<
void
>
=>
{
const
categoryDto
=
plainToInstance
(
CategoryDto
,
req
.
body
);
const
Category
=
await
this
.
service
.
createCategory
(
categoryDto
);
res
.
send
(
Category
);
};
}
server/src/controllers/product.controller.ts
View file @
3144fc0a
...
@@ -2,6 +2,7 @@ import { ProductService } from '@/services/product.service';
...
@@ -2,6 +2,7 @@ import { ProductService } from '@/services/product.service';
import
{
RequestHandler
}
from
'express'
;
import
{
RequestHandler
}
from
'express'
;
import
{
plainToInstance
}
from
'class-transformer'
;
import
{
plainToInstance
}
from
'class-transformer'
;
import
{
ProductDto
}
from
'@/dto/product.dto'
;
import
{
ProductDto
}
from
'@/dto/product.dto'
;
import
{
formatErrors
}
from
'@/helpers/formatErrors'
;
export
class
ProductController
{
export
class
ProductController
{
private
service
:
ProductService
;
private
service
:
ProductService
;
...
@@ -15,15 +16,23 @@ export class ProductController {
...
@@ -15,15 +16,23 @@ export class ProductController {
res
.
send
(
products
);
res
.
send
(
products
);
};
};
getProduct
:
RequestHandler
=
(
req
,
res
):
void
=>
{
getProduct
:
RequestHandler
=
async
(
req
,
res
):
Promise
<
void
>
=>
{
const
product
=
this
.
service
.
getProduct
(
req
.
params
.
id
);
const
product
=
await
this
.
service
.
getProduct
(
req
.
params
.
id
);
res
.
send
(
product
);
res
.
send
(
product
);
};
};
createProduct
:
RequestHandler
=
(
req
,
res
):
void
=>
{
createProduct
:
RequestHandler
=
async
(
req
,
res
):
Promise
<
void
>
=>
{
const
productDto
=
plainToInstance
(
ProductDto
,
req
.
body
);
try
{
if
(
req
.
file
)
productDto
.
image
=
req
.
file
.
filename
;
const
productDto
=
plainToInstance
(
ProductDto
,
req
.
body
);
const
product
=
this
.
service
.
createProduct
(
productDto
);
if
(
req
.
file
)
productDto
.
image
=
req
.
file
.
filename
;
res
.
send
(
product
);
const
product
=
await
this
.
service
.
createProduct
(
productDto
);
res
.
send
(
product
);
}
catch
(
e
)
{
if
(
Array
.
isArray
(
e
))
{
res
.
status
(
400
).
send
(
formatErrors
(
e
));
}
else
{
res
.
status
(
500
).
send
(
e
);
}
}
};
};
}
}
server/src/dto/category.dto.ts
0 → 100644
View file @
3144fc0a
import
{
Expose
}
from
'class-transformer'
;
export
class
CategoryDto
{
@
Expose
()
title
!
:
string
;
@
Expose
()
description
!
:
string
;
}
server/src/dto/product.dto.ts
View file @
3144fc0a
import
{
Expose
}
from
'class-transformer'
;
import
{
Expose
}
from
'class-transformer'
;
import
{
IsNotEmpty
,
IsNumberString
,
IsOptional
,
IsString
}
from
'class-validator'
;
export
class
ProductDto
{
export
class
ProductDto
{
@
Expose
()
title
!
:
string
;
@
IsNotEmpty
({
message
:
'Продукт не может быть создан без названия!'
})
@
IsString
({
message
:
'Название должно быть строкой'
})
@
Expose
()
title
!
:
string
;
@
Expose
()
description
!
:
string
;
@
IsOptional
()
@
Expose
()
description
!
:
string
;
@
Expose
()
price
!
:
number
;
@
IsNotEmpty
({
message
:
'Укажите цену продукта'
})
@
IsNumberString
({},
{
message
:
'Укажите корректную цену'
})
@
Expose
()
price
!
:
number
;
@
Expose
()
image
!
:
string
;
@
IsOptional
()
@
Expose
()
image
!
:
string
;
@
IsNotEmpty
({
message
:
'Не указана категория товара'
})
@
IsNumberString
({},
{
message
:
'Укажите корректную категорию'
})
@
Expose
()
categoryId
!
:
string
;
}
}
server/src/entities/category.entity.ts
0 → 100644
View file @
3144fc0a
import
{
Entity
,
PrimaryGeneratedColumn
,
Column
,
OneToMany
}
from
"typeorm"
;
import
{
Product
}
from
"./product.entity"
;
@
Entity
({
name
:
'categories'
})
export
class
Category
{
@
PrimaryGeneratedColumn
()
id
!
:
number
;
@
Column
()
title
!
:
string
@
Column
()
description
!
:
string
@
OneToMany
(()
=>
Product
,
(
product
)
=>
product
.
category
)
products
!
:
Product
[]
}
\ No newline at end of file
server/src/entities/product.entity.ts
View file @
3144fc0a
import
{
Entity
,
PrimaryGeneratedColumn
,
Column
}
from
"typeorm"
;
import
{
Entity
,
PrimaryGeneratedColumn
,
Column
,
ManyToOne
}
from
"typeorm"
;
import
{
Category
}
from
"./category.entity"
;
@
Entity
()
@
Entity
()
export
class
Product
{
export
class
Product
{
...
@@ -16,4 +17,7 @@ export class Product {
...
@@ -16,4 +17,7 @@ export class Product {
@
Column
({
nullable
:
true
})
@
Column
({
nullable
:
true
})
image
!
:
string
image
!
:
string
@
ManyToOne
(()
=>
Category
,
(
category
)
=>
category
.
products
)
category
!
:
Category
}
}
\ No newline at end of file
server/src/helpers/formatErrors.ts
0 → 100644
View file @
3144fc0a
import
{
ValidationError
}
from
"class-validator"
;
export
const
formatErrors
=
(
errors
:
ValidationError
[])
=>
{
const
updatedErrors
:
{
type
:
string
;
messages
:
string
[]
}[]
=
[];
errors
.
forEach
((
e
)
=>
{
if
(
e
.
constraints
)
{
const
error
=
{
type
:
e
.
property
,
messages
:
Object
.
values
(
e
.
constraints
),
};
updatedErrors
.
push
(
error
);
}
});
return
updatedErrors
;
};
\ No newline at end of file
server/src/index.ts
View file @
3144fc0a
import
{
CategoryRoute
}
from
'./routes/category.route'
;
import
cors
from
'cors'
;
import
cors
from
'cors'
;
import
App
from
'./app'
;
import
App
from
'./app'
;
import
logger
from
'./middlewares/logger'
;
import
logger
from
'./middlewares/logger'
;
...
@@ -7,7 +8,7 @@ import { ProductRoute } from './routes/product.route';
...
@@ -7,7 +8,7 @@ import { ProductRoute } from './routes/product.route';
const
app
=
new
App
({
const
app
=
new
App
({
port
:
8000
,
port
:
8000
,
middlewares
:
[
logger
(),
cors
()],
middlewares
:
[
logger
(),
cors
()],
controllers
:
[
new
ArticleRoute
(),
new
ProductRoute
()],
controllers
:
[
new
ArticleRoute
(),
new
ProductRoute
()
,
new
CategoryRoute
()
],
});
});
app
.
listen
();
app
.
listen
();
server/src/repositories/category.repository.ts
0 → 100644
View file @
3144fc0a
import
{
AppDataSource
}
from
"@/appDataSource"
;
import
{
CategoryDto
}
from
"@/dto/category.dto"
;
import
{
Category
}
from
"@/entities/category.entity"
;
import
{
Repository
}
from
"typeorm"
;
class
CategoryRepo
{
private
repo
:
Repository
<
Category
>
constructor
()
{
this
.
repo
=
AppDataSource
.
getRepository
(
Category
)
}
async
create
(
body
:
CategoryDto
)
{
return
await
this
.
repo
.
save
(
body
)
}
async
getAll
()
{
return
await
this
.
repo
.
find
()
}
async
getOne
(
id
:
number
)
{
return
await
this
.
repo
.
findOne
({
where
:
{
id
:
id
}})
}
}
export
const
categoryRepo
=
new
CategoryRepo
()
\ No newline at end of file
server/src/repositories/product.repository.ts
View file @
3144fc0a
...
@@ -2,6 +2,7 @@ import { AppDataSource } from "@/appDataSource";
...
@@ -2,6 +2,7 @@ import { AppDataSource } from "@/appDataSource";
import
{
ProductDto
}
from
"@/dto/product.dto"
;
import
{
ProductDto
}
from
"@/dto/product.dto"
;
import
{
Product
}
from
"@/entities/product.entity"
;
import
{
Product
}
from
"@/entities/product.entity"
;
import
{
Repository
}
from
"typeorm"
;
import
{
Repository
}
from
"typeorm"
;
import
{
categoryRepo
}
from
"./category.repository"
;
class
ProductRepo
{
class
ProductRepo
{
private
repo
:
Repository
<
Product
>
private
repo
:
Repository
<
Product
>
...
@@ -11,11 +12,14 @@ class ProductRepo {
...
@@ -11,11 +12,14 @@ class ProductRepo {
}
}
async
create
(
body
:
ProductDto
)
{
async
create
(
body
:
ProductDto
)
{
return
await
this
.
repo
.
save
(
body
)
const
category
=
await
categoryRepo
.
getOne
(
parseInt
(
body
.
categoryId
))
if
(
!
category
)
throw
Error
(
'Category Not Found.'
)
return
await
this
.
repo
.
save
({...
body
,
category
:
category
})
}
}
async
getAll
()
{
async
getAll
()
{
return
await
this
.
repo
.
find
()
return
await
this
.
repo
.
find
(
{
relations
:
{
category
:
true
}}
)
}
}
async
getOne
(
id
:
number
)
{
async
getOne
(
id
:
number
)
{
...
...
server/src/routes/category.route.ts
0 → 100644
View file @
3144fc0a
import
{
CategoryController
}
from
'@/controllers/category.controller'
;
import
{
Router
}
from
'express'
;
export
class
CategoryRoute
{
public
path
=
'/category'
;
public
router
=
Router
();
private
controller
:
CategoryController
;
constructor
()
{
this
.
controller
=
new
CategoryController
();
this
.
init
();
}
private
init
()
{
this
.
router
.
get
(
'/'
,
this
.
controller
.
getAllCategories
);
this
.
router
.
get
(
'/:id'
,
this
.
controller
.
getCategory
);
this
.
router
.
post
(
'/create'
,
this
.
controller
.
createCategory
);
}
}
server/src/services/category.service.ts
0 → 100644
View file @
3144fc0a
import
{
CategoryDto
}
from
"@/dto/category.dto"
;
import
{
Category
}
from
"@/entities/category.entity"
;
import
{
categoryRepo
}
from
"@/repositories/category.repository"
;
export
class
CategoryService
{
getAllCategories
=
async
():
Promise
<
Category
[]
>
=>
{
return
await
categoryRepo
.
getAll
()
};
getCategory
=
async
(
id
:
string
):
Promise
<
Category
|
null
>
=>
{
return
await
categoryRepo
.
getOne
(
parseInt
(
id
))
};
createCategory
=
async
(
data
:
CategoryDto
):
Promise
<
Category
>
=>
{
return
await
categoryRepo
.
create
(
data
)
};
}
server/src/services/product.service.ts
View file @
3144fc0a
import
{
ProductDto
}
from
'@/dto/product.dto'
;
import
{
ProductDto
}
from
'@/dto/product.dto'
;
import
{
productRepo
}
from
'@/repositories/product.repository'
;
import
{
productRepo
}
from
'@/repositories/product.repository'
;
import
{
Product
}
from
'@/entities/product.entity'
;
import
{
Product
}
from
'@/entities/product.entity'
;
import
{
validate
}
from
'class-validator'
;
export
class
ProductService
{
export
class
ProductService
{
getAllProducts
=
async
():
Promise
<
Product
[]
>
=>
{
getAllProducts
=
async
():
Promise
<
Product
[]
>
=>
{
...
@@ -12,6 +13,8 @@ export class ProductService {
...
@@ -12,6 +13,8 @@ export class ProductService {
};
};
createProduct
=
async
(
data
:
ProductDto
):
Promise
<
Product
>
=>
{
createProduct
=
async
(
data
:
ProductDto
):
Promise
<
Product
>
=>
{
return
productRepo
.
create
(
data
)
const
errors
=
await
validate
(
data
,
{
whitelist
:
true
});
if
(
errors
.
length
)
throw
errors
;
return
await
productRepo
.
create
(
data
)
};
};
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment