해결된 질문
작성
·
315
0
주문 시 계속해서 주문에 실패했다고 떠서, 무엇이 문제이지 하고 봤는데, Status 500 에러가 던져집니다.
그래서 서버 쪽 로그를 봤는데, 서버에서 이런 Exception이 던져지고 있습니다.
[Nest] 63134 - 2023. 08. 13. 오전 2:35:36 ERROR [ExceptionsHandler] Cannot read properties of undefined (reading 'restaurant')
TypeError: Cannot read properties of undefined (reading 'restaurant')
at OrderService.postOrder (/Users/nx006/Documents/vscode/flutter-lv2-server/src/order/order.service.ts:36:8)
at OrderController.postOrder (/Users/nx006/Documents/vscode/flutter-lv2-server/src/order/order.controller.ts:63:30)
at /Users/nx006/Documents/vscode/flutter-lv2-server/node_modules/@nestjs/core/router/router-execution-context.js:38:29
at processTicksAndRejections (node:internal/process/task_queues:95:5)
'restaurant' 속성을 읽지 못한다는 게 무슨 말일까요? 애초에 Request Body에 Restaurant 속성 자체가 없는데, 400 에러도 아니고 왜 이런 에러가 서버 쪽에서 나는 지 모르겠습니다.
일단은, 현재 PostBody와 Response Body에 대한 모델입니다:
// ignore_for_file: invalid_annotation_target
/// order_model.dart
import 'package:delivery_app/common/model/model_with_id.dart';
import 'package:delivery_app/common/utils/data_utils.dart';
import 'package:delivery_app/restaurant/model/restaurant_model.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'order_model.freezed.dart';
part 'order_model.g.dart';
@freezed
class OrderProductModel with _$OrderProductModel {
factory OrderProductModel({
required String id,
required String name,
required String detail,
@JsonKey(fromJson: DataUtils.pathToUrl) required String imgUrl,
required int price,
}) = _OrderProductModel;
factory OrderProductModel.fromJson(Map<String, dynamic> json) =>
_$OrderProductModelFromJson(json);
}
@freezed
class OrderProductAndCountModel with _$OrderProductAndCountModel {
factory OrderProductAndCountModel({
required OrderProductModel product,
required int count,
}) = _OrderProductAndCount;
factory OrderProductAndCountModel.fromJson(Map<String, dynamic> json) =>
_$OrderProductAndCountModelFromJson(json);
}
@freezed
class OrderModel with _$OrderModel implements IModelWithId {
factory OrderModel({
required String id,
required RestaurantModel restaurant,
required List<OrderProductAndCountModel> products,
required int totalPrice,
@JsonKey(fromJson: DataUtils.stringToDateTime) required DateTime createdAt,
}) = _OrderModel;
factory OrderModel.fromJson(Map<String, dynamic> json) =>
_$OrderModelFromJson(json);
}
/// post_order_body.dart
import 'package:freezed_annotation/freezed_annotation.dart';
part 'post_order_body.freezed.dart';
part 'post_order_body.g.dart';
@freezed
class PostOrderBody with _$PostOrderBody {
const factory PostOrderBody({
required String id,
required List<PostOrderBodyProduct> products,
required int totalPrice,
required String createdAt,
}) = _PostOrderBody;
factory PostOrderBody.fromJson(Map<String, dynamic> json) =>
_$PostOrderBodyFromJson(json);
}
@freezed
class PostOrderBodyProduct with _$PostOrderBodyProduct {
const factory PostOrderBodyProduct({
required String id,
required int count,
}) = _PostOrderBodyProduct;
factory PostOrderBodyProduct.fromJson(Map<String, dynamic> json) =>
_$PostOrderBodyProductFromJson(json);
}
그리고 다음은 Repository 코드입니다.
/// order_provider.dart
import 'package:delivery_app/common/const/data.dart';
import 'package:delivery_app/common/dio/dio.dart';
import 'package:delivery_app/order/model/order_model.dart';
import 'package:delivery_app/order/model/post_order_body.dart';
import 'package:dio/dio.dart' hide Headers;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:retrofit/retrofit.dart';
part 'order_repository.g.dart';
final orderRepositoryProvider = Provider((ref) {
final dio = ref.watch(dioProvider);
return OrderRepository(dio, baseUrl: 'http://$ip/order');
});
// baseUrl : http://$ip/order
@RestApi()
abstract class OrderRepository {
factory OrderRepository(Dio dio, {String baseUrl}) = _OrderRepository;
@POST('/')
@Headers({'accessToken': 'true'})
Future<OrderModel> postOrder({
@Body() required PostOrderBody body,
});
}
다음은 Provider 입니다.
/// order_provider.dart
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:delivery_app/order/model/post_order_body.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:uuid/uuid.dart';
import 'package:delivery_app/order/model/order_model.dart';
import 'package:delivery_app/order/repository/order_repository.dart';
import 'package:delivery_app/user/provider/basket_provider.dart';
final orderProvider =
StateNotifierProvider<OrderStateNotifier, List<OrderModel>>(
(ref) => OrderStateNotifier(
ref: ref,
repository: ref.watch(orderRepositoryProvider),
));
class OrderStateNotifier extends StateNotifier<List<OrderModel>> {
final Ref ref;
final OrderRepository repository;
OrderStateNotifier({
required this.ref,
required this.repository,
}) : super([]);
Future<bool> postOrder() async {
const uuid = Uuid();
final id = uuid.v4();
final state = ref.read(basketProvider);
try {
await repository.postOrder(
body: PostOrderBody(
id: id,
products: state
.map((e) => PostOrderBodyProduct(
id: e.product.id,
count: e.count,
))
.toList(),
totalPrice: state.fold<int>(
0,
(previousValue, element) =>
previousValue + element.product.price * element.count,
),
createdAt: DateTime.now().toString(),
),
);
return true;
} catch (e) {
print('error: $e');
return false;
}
}
}
그리고 View 단, 특 주문하기 버튼입니다:
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: primaryColor,
),
onPressed: basket.isEmpty
? null
: () async {
final response = await ref
.read(orderProvider.notifier)
.postOrder();
if (context.mounted) {
if (response) {
context.goNamed(OrderDonePage.routeName);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('주문에 실패했습니다.'),
),
);
}
}
},
child: const Text('결제하기'),
),
혹시 몰라서, 에러가 발생하는 서버 쪽 코드 역시 첨부합니다. 일단 제가 건든 코드는 없습니다.
import { Injectable } from '@nestjs/common';
import { CreateOrderDto } from './dto/create-order.dto';
import { CacheService } from '../cache/cache.service';
import { User } from '../user/entities/user.entity';
import { Order } from './entities/order.entity';
import { CoreService } from '../core/core.service';
import { PaginationDto } from '../core/dto/pagination.dto';
import { Pagination } from '../core/entity/pagination.entity';
import { OrderProduct } from './entities/order-product-entity';
@Injectable()
export class OrderService {
constructor(
private cacheService: CacheService,
private coreService: CoreService,
) {}
paginateOrders(user: User, paginationDto: PaginationDto): Pagination<Order> {
const result = this.coreService.paginate(
this.cacheService.orders,
paginationDto,
);
return {
...result,
data: result.data.map((item) => new Order(item)),
};
}
postOrder(user: User, createOrderDto: CreateOrderDto): Order {
const newOrder = new Order({
id: createOrderDto.id,
user,
restaurant: this.cacheService.products.find(
(x) => createOrderDto.products[0].productId === x.id,
).restaurant,
products: createOrderDto.products.map((basketItem) => ({
product: new OrderProduct(
this.cacheService.products.find(
(product) => basketItem.productId === product.id,
),
),
count: basketItem.count,
})),
totalPrice: createOrderDto.totalPrice,
createdAt: createOrderDto.createdAt,
});
this.cacheService.orders = [newOrder, ...this.cacheService.orders];
return newOrder;
}
}
@ApiTags('order')
@ApiExtraModels(PaginationDto, Order)
@Controller('order')
export class OrderController {
constructor(private readonly orderService: OrderService) {}
@UseGuards(AccessTokenGuard)
@ApiOperation({
summary: '주문 Pagination',
})
@ApiPaginatedOkResponseDecorator(Order, {
description: 'Pagination 결과',
})
@Get()
paginateOrder(
@Request() req,
@Query() paginationDto: PaginationDto,
): Pagination<Order> {
return this.orderService.paginateOrders(req.user, paginationDto);
}
@UseGuards(AccessTokenGuard)
@Post()
@ApiOperation({
summary: '주문 생성하기',
})
@ApiBody({
type: CreateOrderDto,
})
@ApiOkResponse({
status: 201,
type: Order,
})
postOrder(@Request() req, @Body() body: CreateOrderDto): Order {
return this.orderService.postOrder(req.user, body);
}
}