diff --git a/admin/src/App.js b/admin/src/App.js index 7214dfc..9f606aa 100644 --- a/admin/src/App.js +++ b/admin/src/App.js @@ -7,8 +7,9 @@ import { BASE_URL } from "./configs/BaseUrl"; import { OpenGardenAdminLayout } from './themes/Layout'; -import { IoFlower } from "react-icons/io5"; +import { IoFlower, IoFlowerOutline } from "react-icons/io5"; import { PlantCreate, PlantEdit, PlantShow, PlantsList } from "./views/plants"; +import { VarietyCreate, VarietyEdit, VarietyShow, VarietiesList } from "./views/varieties"; const App = () => ( ( list={PlantsList} show={PlantShow} icon={IoFlower} /> + )} diff --git a/admin/src/configs/Provider.js b/admin/src/configs/Provider.js index 6d99ba3..8618a5f 100644 --- a/admin/src/configs/Provider.js +++ b/admin/src/configs/Provider.js @@ -41,7 +41,7 @@ export default (apiUrl, httpClient = fetchUtils.fetchJson): DataProvider => ({ id: params.ids, }; const url = `${apiUrl}/${resource}?${stringify(query)}`; - return httpClient(url).then(({ json }) => ({ data: json })); + return httpClient(url).then(({ json }) => ({ data: json[resource] })) }, getManyReference: (resource, params) => { diff --git a/admin/src/views/plants.js b/admin/src/views/plants.js index d949fdd..8055cd2 100644 --- a/admin/src/views/plants.js +++ b/admin/src/views/plants.js @@ -31,7 +31,7 @@ export const PlantCreate = (props) => ( - + @@ -55,7 +55,7 @@ export const PlantEdit = (props) => ( - + @@ -86,6 +86,7 @@ export const PlantsList = (props) => { + {permissions.includes('ADMIN') && } @@ -117,8 +118,16 @@ export const PlantShow = (props) => ( + + + + + + + + diff --git a/admin/src/views/varieties.js b/admin/src/views/varieties.js new file mode 100644 index 0000000..403595d --- /dev/null +++ b/admin/src/views/varieties.js @@ -0,0 +1,258 @@ +import * as React from "react"; +import { + List, + Datagrid, + TextField, + DateField, + Show, + RichTextField, + ReferenceOneField, + ShowButton, + EditButton, + Create, + TextInput, + SingleFieldList, + ChipField, + ArrayField, + Edit, + TabbedForm, + FormTab, + TabbedShowLayout, + Tab, + ReferenceArrayInput, + SelectArrayInput, + ReferenceInput, + SelectInput, + NumberInput, + ReferenceField, + ReferenceArrayField +} from "react-admin"; +import { RichTextInput } from 'ra-input-rich-text'; +import { StringToLabelObject } from '../helpers/StringToLabelObject'; +import { GetPermissions } from "../helpers/GetPermissions"; + +const monthsSelectItems = [ + { id: 1, name: 'January' }, + { id: 2, name: 'February' }, + { id: 3, name: 'March' }, + { id: 4, name: 'April' }, + { id: 5, name: 'May' }, + { id: 6, name: 'June' }, + { id: 7, name: 'July' }, + { id: 8, name: 'August' }, + { id: 9, name: 'September' }, + { id: 10, name: 'October' }, + { id: 11, name: 'November' }, + { id: 12, name: 'December' }, +]; + +const origins = ["AF", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "KH", "CM", "CA", "CV", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", "CR", "CI", "HR", "CU", "CY", "CZ", "DK", "DJ", "DM", "DO", "EC", "EG", "SV", "GQ", "ER", "EE", "ET", "FK", "FO", "FJ", "FI", "FR", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GN", "GW", "GY", "HT", "HM", "VA", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IL", "IT", "JM", "JP", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MK", "MP", "NO", "OM", "PK", "PW", "PS", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "SH", "KN", "LC", "PM", "VC", "WS", "SM", "ST", "SA", "SN", "SC", "SL", "SG", "SK", "SI", "SB", "SO", "ZA", "GS", "ES", "LK", "SD", "SR", "SJ", "SZ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TL", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "ZM", "ZW", "AX", "BQ", "CW", "GG", "IM", "JE", "ME", "BL", "MF", "RS", "SX", "SS", "XK "]; +const originSelectItem = origins.map((origin) => ({ id: origin, name: origin })); + +export const VarietyCreate = (props) => ( + + + + + + + + + + + + +

Water

+ + +

Sun

+ + +

Floors

+ + + +
+ +

Types

+ + + + + + + + + +
+
+
+); + +export const VarietyEdit = (props) => ( + + + + + + + + + + + + +

Water

+ + +

Sun

+ + +

Floors

+ + + +
+ +

Types

+ + + + + + + + + +
+
+
+); + +export const VarietiesList = (props) => { + const permissions = GetPermissions(); + return ( + + permissions.includes('ADMIN')}> + + + + + + + + {permissions.includes('ADMIN') && } + + + + ); +}; + +export const VarietyShow = (props) => ( + + + + + + + + + + + + +

Water

+ + +

Sun

+ + +

Floors

+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+); diff --git a/core/src/controllers/plants/plants.controller.ts b/core/src/controllers/plants/plants.controller.ts index 5771533..a5c4925 100644 --- a/core/src/controllers/plants/plants.controller.ts +++ b/core/src/controllers/plants/plants.controller.ts @@ -68,12 +68,14 @@ export class PlantsController { @Get(':plantId') @ApiResponse({ status: 200, type: PlantResponseBody }) @ApiResponse({ status: 404, description: 'Not Found', type: ErrorsRequestBody }) - async getPlantById(@Param('plantId') plantId: string) { + async getPlantById(@Response() res: Res, @Param('plantId') plantId: string) { const plant = await this.plantsService.findOneById(plantId); if (!plant) { throw new NotFoundException(); } - return this.mapper.map(plant, Plant, PlantResponseBody); + + const body = this.mapper.map(plant, Plant, PlantResponseBody); + return res.set({ 'Content-Range': `elements 0-1/1` }).json(body); } @Get() @@ -91,10 +93,6 @@ export class PlantsController { const body: PlantSearchResponseBody = { plants: this.mapper.mapArray(elements, Plant, PlantResponseBody), }; - return res - .set({ - 'Content-Range': `elements ${offset}-${offset + limit}/${count}`, - }) - .json(body); + return res.set({ 'Content-Range': `elements ${offset}-${offset + limit}/${count}` }).json(body); } } diff --git a/core/src/controllers/varieties/models/mapper.profiles.ts b/core/src/controllers/varieties/models/mapper.profiles.ts index 80d4e08..52c9b76 100644 --- a/core/src/controllers/varieties/models/mapper.profiles.ts +++ b/core/src/controllers/varieties/models/mapper.profiles.ts @@ -119,7 +119,7 @@ export class VarietyMapperProfiles extends AutomapperProfile { mapFrom((s) => s.plant.toString()), ), forMember( - (d) => d.variety, + (d) => d.name, mapFrom((s) => escapeHtml(s.name)), ), forMember( diff --git a/core/src/controllers/varieties/models/variety.response.body.ts b/core/src/controllers/varieties/models/variety.response.body.ts index e9d7028..d6b4a1e 100644 --- a/core/src/controllers/varieties/models/variety.response.body.ts +++ b/core/src/controllers/varieties/models/variety.response.body.ts @@ -63,7 +63,7 @@ export class VarietyResponseBody { plant: string; @ApiProperty() - variety: string; + name: string; @ApiProperty({ enum: Country }) origin: Country; diff --git a/core/src/controllers/varieties/varieties.controller.ts b/core/src/controllers/varieties/varieties.controller.ts index 7072713..03a9221 100644 --- a/core/src/controllers/varieties/varieties.controller.ts +++ b/core/src/controllers/varieties/varieties.controller.ts @@ -1,4 +1,15 @@ -import { Body, Request, Controller, Get, NotFoundException, Param, Post, Query } from '@nestjs/common'; +import { + Body, + Request, + Controller, + Get, + NotFoundException, + Param, + Post, + Query, + Response, + Delete, +} from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { Mapper } from '@automapper/core'; import { InjectMapper } from '@automapper/nestjs'; @@ -13,6 +24,7 @@ import { ErrorsRequestBody } from '../models/errors.response.body'; import { Roles } from '../../auth/roles/roles.decorator'; import { Role } from '../../auth/roles/role.enum'; import { PublishedState } from '../../entities/base.published.entity'; +import { Response as Res } from 'express'; @ApiBearerAuth() @ApiTags('Varieties') @@ -20,7 +32,7 @@ import { PublishedState } from '../../entities/base.published.entity'; @ApiResponse({ status: 429, description: 'Too Many Requests' }) @Controller('varieties') export class VarietiesController { - constructor(private plantsService: VarietiesService, @InjectMapper() private mapper: Mapper) {} + constructor(private varietiesService: VarietiesService, @InjectMapper() private mapper: Mapper) {} @Post() @Roles(Role.ADMIN) @@ -47,34 +59,50 @@ export class VarietiesController { updatedAt: new Date(), createdBy: req.user._id, }; - const plant = await this.plantsService.create(createVariety); + const plant = await this.varietiesService.create(createVariety); return this.mapper.map(plant, Variety, VarietyResponseBody); } + @Delete(':varietyId') + @Roles(Role.ADMIN) + @ApiResponse({ status: 403, description: 'Forbidden', type: ErrorsRequestBody }) + @ApiResponse({ status: 404, description: 'Not Found', type: ErrorsRequestBody }) + @ApiResponse({ status: 200, type: VarietyResponseBody }) + async deletePlant(@Request() req, @Param('varietyId') varietyId: string) { + const variety = await this.varietiesService.delete(varietyId); + if (!variety) { + throw new NotFoundException(); + } + + return this.mapper.map(variety, Variety, VarietyResponseBody); + } + @Get(':varietyId') @ApiResponse({ status: 200, type: VarietyResponseBody }) - async getVarietyById(@Param('varietyId') varietyId: string) { - const plant = await this.plantsService.findOneById(varietyId); + async getVarietyById(@Response() res: Res, @Param('varietyId') varietyId: string) { + const plant = await this.varietiesService.findOneById(varietyId); if (!plant) { throw new NotFoundException(); } - return this.mapper.map(plant, Variety, VarietyResponseBody); + + const body = this.mapper.map(plant, Variety, VarietyResponseBody); + return res.set({ 'Content-Range': `elements 0-1/1` }).json(body); } @Get() @ApiResponse({ status: 200, type: VarietySearchResponseBody }) - async getVarieties(@Query() plantsSearchRequestQuery: VarietiesSearchRequestQuery) { + async getVarieties(@Response() res: Res, @Query() plantsSearchRequestQuery: VarietiesSearchRequestQuery) { const { limit, offset, ...filters } = plantsSearchRequestQuery; - const plants = await this.plantsService.search({ + const [elements, count] = await this.varietiesService.search({ pagination: { limit, offset, }, ...filters, }); - const result: VarietySearchResponseBody = { - varieties: this.mapper.mapArray(plants, Variety, VarietyResponseBody), + const body: VarietySearchResponseBody = { + varieties: this.mapper.mapArray(elements, Variety, VarietyResponseBody), }; - return result; + return res.set({ 'Content-Range': `elements ${offset}-${offset + limit}/${count}` }).json(body); } } diff --git a/core/src/entities/varieties/varieties.service.ts b/core/src/entities/varieties/varieties.service.ts index 81e31fe..5061c08 100644 --- a/core/src/entities/varieties/varieties.service.ts +++ b/core/src/entities/varieties/varieties.service.ts @@ -25,14 +25,14 @@ export class VarietySearchParams extends BaseSearchParams { @Injectable() export class VarietiesService extends BaseEntityService { - constructor(@InjectModel(Variety.name) private plantModel: Model) { + constructor(@InjectModel(Variety.name) private varietyModel: Model) { super(); } async create(plant: Variety): Promise { try { plant._id = new mongoose.Types.ObjectId(); - return await this.plantModel.create(plant); + return await this.varietyModel.create(plant); } catch (exception) { if (exception.code === 11000) { throw new ConflictException(); @@ -41,11 +41,19 @@ export class VarietiesService extends BaseEntityService { } } + async delete(varietyId: string): Promise { + try { + return await this.varietyModel.findByIdAndDelete(varietyId); + } catch { + return null; + } + } + async findOneById(id: string): Promise { - return await this.plantModel.findById(id); + return await this.varietyModel.findById(id); } - async search(params: VarietySearchParams): Promise { + async search(params: VarietySearchParams): Promise<[Variety[], number]> { const { pagination, sunNeed, @@ -90,6 +98,9 @@ export class VarietiesService extends BaseEntityService { findParam = this._generateFilter(findParam, 'culture.sowingPeriod', sowingPeriod); findParam = this._generateFilter(findParam, 'culture.growingOnPeriod', growingOnPeriod); findParam = this._generateFilter(findParam, 'culture.harvestPeriod', harvestPeriod); - return await this.plantModel.find(findParam).skip(pagination.offset).limit(pagination.limit); + + const elements = await this.varietyModel.find(findParam).skip(pagination.offset).limit(pagination.limit); + const count = await this.varietyModel.count(findParam); + return [elements, count]; } }