From 91e73f4440a0e51e10dc16c22e4decbe94ccaf88 Mon Sep 17 00:00:00 2001 From: kardelenerdal Date: Sat, 18 Nov 2023 22:13:08 +0300 Subject: [PATCH 01/22] create game pages #460 --- ludos/mobile/lib/create_game.dart | 368 +++++++++------------- ludos/mobile/lib/create_game_second.dart | 383 +++++++++++++++++++++++ ludos/mobile/pubspec.lock | 8 + ludos/mobile/pubspec.yaml | 1 + 4 files changed, 547 insertions(+), 213 deletions(-) create mode 100644 ludos/mobile/lib/create_game_second.dart diff --git a/ludos/mobile/lib/create_game.dart b/ludos/mobile/lib/create_game.dart index 091cb1d9..db368cec 100644 --- a/ludos/mobile/lib/create_game.dart +++ b/ludos/mobile/lib/create_game.dart @@ -1,14 +1,12 @@ -import 'dart:convert'; - +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:http/http.dart' as http; import 'helper/colors.dart'; import 'package:material_tag_editor/tag_editor.dart'; -import 'helper/APIService.dart'; +import 'create_game_second.dart'; -Widget getbox( - String hintText, TextEditingController controller, bool isMandatory) { +Widget getbox(String hintText, TextEditingController controller, + bool isMandatory, bool multiLine) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -31,15 +29,17 @@ Widget getbox( ), ], ), - SizedBox(height: 8.0), // Adjust the spacing as needed + const SizedBox(height: 8.0), // Adjust the spacing as needed TextFormField( controller: controller, style: const TextStyle(color: MyColors.red), + keyboardType: multiLine ? TextInputType.multiline : null, + maxLines: multiLine ? 5 : 1, decoration: InputDecoration( filled: true, fillColor: MyColors.white, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), borderSide: const BorderSide( color: Colors.white, width: 2.0, @@ -49,7 +49,7 @@ Widget getbox( labelText: '', focusedBorder: OutlineInputBorder( borderSide: const BorderSide(color: MyColors.lightBlue, width: 2.0), - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), ), ), cursorColor: MyColors.lightBlue, @@ -68,8 +68,17 @@ class CreateGamePage extends StatefulWidget { class _CreateGamePageState extends State { List predecessorValues = []; List successorValues = []; - List platformValues = []; List tagValues = []; + int selectdAgeRestriction = 0; + List ageRestrictions = [ + 'Early Childhood', + 'Everyone', + 'Everyone 10 and older', + 'Teen', + 'Mature', + 'Adults Only', + 'Rating Pending', + ]; _onDeletepr(index) { setState(() { @@ -83,201 +92,122 @@ class _CreateGamePageState extends State { }); } - _onDeletepl(index) { - setState(() { - platformValues.removeAt(index); - }); - } - _onDeletet(index) { setState(() { tagValues.removeAt(index); }); } + void _showDialog(Widget child) { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) => Container( + height: 216, + padding: const EdgeInsets.only(top: 6.0), + margin: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + color: CupertinoColors.systemBackground.resolveFrom(context), + child: SafeArea( + top: false, + child: child, + ), + ), + ); + } + final TextEditingController titleController = TextEditingController(); final TextEditingController coverLinkController = TextEditingController(); - final TextEditingController systemRequirementsController = - TextEditingController(); final TextEditingController predecessorsController = TextEditingController(); final TextEditingController successorsController = TextEditingController(); - final TextEditingController gameGuideController = TextEditingController(); - final TextEditingController gameStoryController = TextEditingController(); - final TextEditingController platformsController = TextEditingController(); - final TextEditingController ageRestrictionController = - TextEditingController(); final TextEditingController gameBioController = TextEditingController(); final TextEditingController tagsController = TextEditingController(); - final TextEditingController releaseDateController = TextEditingController(); - final TextEditingController developerController = TextEditingController(); - final TextEditingController publisherController = TextEditingController(); - final TextEditingController triviaController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - backgroundColor: MyColors.orange, + backgroundColor: const Color(0xFF2f5b7a), centerTitle: true, title: const Text('Ludos'), - actions: [ - TextButton( - onPressed: () async { - http.Response token = await APIService().createGame( - titleController.text, - coverLinkController.text, - systemRequirementsController.text, - predecessorValues, - successorValues, - gameGuideController.text, - gameStoryController.text, - platformValues, - ageRestrictionController.text, - gameBioController.text, - tagValues, - releaseDateController.text, - developerController.text, - publisherController.text, - triviaController.text); - if (token.statusCode == 201) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Row( - children: [ - Icon( - Icons.check_circle_outline, - color: MyColors.blue, - ), - SizedBox(width: 8), - Text( - 'Your game is created successfully.', - style: TextStyle( - color: MyColors.blue, - fontSize: 16, - ), - ), - ], - ), - backgroundColor: MyColors.blue2, - duration: const Duration(seconds: 10), - action: SnackBarAction( - label: 'OK', - textColor: MyColors.blue, - onPressed: () { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - }, - ), - ), - ); - } else if (token.statusCode == 409) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: SizedBox( - width: MediaQuery.of(context).size.width, - child: Text( - json.decode(token.body)["message"], - style: const TextStyle( - color: MyColors.blue, - fontSize: 16, - ), - ), - ), - backgroundColor: MyColors.blue2, - duration: const Duration(seconds: 10), - action: SnackBarAction( - label: 'OK', - textColor: MyColors.blue, - onPressed: () { - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - }, - ), - ), - ); - } - }, - child: const Text( - 'Save Game', - style: TextStyle(color: Colors.white), - ), - ), - ], ), - backgroundColor: MyColors.blue, + backgroundColor: MyColors.darkBlue, body: SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox(height: 50), - getbox("Title", titleController, true), const SizedBox(height: 20), - getbox("Coverlink", coverLinkController, false), + getbox("Title", titleController, true, false), const SizedBox(height: 20), - getbox( - "System Requirements", systemRequirementsController, false), + getbox("Coverlink", coverLinkController, true, false), const SizedBox(height: 20), - Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - "Predecessors", - style: TextStyle( + getbox("Game Bio", gameBioController, true, true), + const SizedBox(height: 20), + const Row( + children: [ + Text( + '*', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + Text( + "Age Restriction", + style: TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 10), + Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + Container( + padding: const EdgeInsets.only(), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.0), color: MyColors.white, - fontWeight: FontWeight.bold, + border: Border.all( + color: Colors.white, + width: 2.0, + ), ), - ), - SizedBox(height: 8.0), - TagEditor( - length: predecessorValues.length, - controller: predecessorsController, - delimiters: const [',', ' '], - hasAddButton: true, - resetTextOnSubmitted: true, - // This is set to grey just to illustrate the `textStyle` prop - textStyle: const TextStyle( - color: MyColors.red, fontWeight: FontWeight.bold), - onSubmitted: (outstandingValue) { - setState(() { - predecessorValues.add(outstandingValue); - }); - }, - inputDecoration: InputDecoration( - filled: true, - fillColor: MyColors.white, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), - borderSide: const BorderSide( - color: Colors.white, - width: 2.0, + child: CupertinoButton( + padding: const EdgeInsets.symmetric(horizontal: 50.0), + onPressed: () => _showDialog( + CupertinoPicker( + magnification: 1.22, + squeeze: 1.2, + useMagnifier: true, + itemExtent: 32.0, + scrollController: FixedExtentScrollController( + initialItem: selectdAgeRestriction, + ), + onSelectedItemChanged: (int selectedItem) { + setState(() { + selectdAgeRestriction = selectedItem; + }); + }, + children: List.generate(ageRestrictions.length, + (int index) { + return Center(child: Text(ageRestrictions[index])); + }), ), ), - labelStyle: const TextStyle( - color: MyColors.red, fontWeight: FontWeight.bold), - focusedBorder: OutlineInputBorder( - borderSide: const BorderSide( - color: MyColors.lightBlue, width: 2.0), - borderRadius: BorderRadius.circular(10.0), + child: Text( + ageRestrictions[selectdAgeRestriction], + style: const TextStyle(color: MyColors.blue), ), ), - onTagChanged: (newValue) { - setState(() { - predecessorValues.add(newValue); - }); - }, - tagBuilder: (context, index) => _Chip( - index: index, - label: predecessorValues[index], - onDeleted: _onDeletepr, - ), - // InputFormatters example, this disallow \ and / - inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp(r'[/\\]')) - ], ), ]), const SizedBox(height: 20), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( - "Successors", + "Tags", style: TextStyle( color: MyColors.white, fontWeight: FontWeight.bold, @@ -285,24 +215,23 @@ class _CreateGamePageState extends State { ), SizedBox(height: 8.0), TagEditor( - length: successorValues.length, - controller: successorsController, + length: tagValues.length, + controller: tagsController, delimiters: const [',', ' '], hasAddButton: true, resetTextOnSubmitted: true, - // This is set to grey just to illustrate the `textStyle` prop textStyle: const TextStyle( color: MyColors.red, fontWeight: FontWeight.bold), onSubmitted: (outstandingValue) { setState(() { - successorValues.add(outstandingValue); + tagValues.add(outstandingValue); }); }, inputDecoration: InputDecoration( filled: true, fillColor: MyColors.white, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), borderSide: const BorderSide( color: Colors.white, width: 2.0, @@ -313,33 +242,28 @@ class _CreateGamePageState extends State { focusedBorder: OutlineInputBorder( borderSide: const BorderSide( color: MyColors.lightBlue, width: 2.0), - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), ), ), onTagChanged: (newValue) { setState(() { - successorValues.add(newValue); + tagValues.add(newValue); }); }, tagBuilder: (context, index) => _Chip( index: index, - label: successorValues[index], - onDeleted: _onDeletes, + label: tagValues[index], + onDeleted: _onDeletet, ), - // InputFormatters example, this disallow \ and / inputFormatters: [ FilteringTextInputFormatter.deny(RegExp(r'[/\\]')) ], ), ]), const SizedBox(height: 20), - getbox("Game Guide", gameGuideController, false), - const SizedBox(height: 20), - getbox("Game Story", gameStoryController, false), - const SizedBox(height: 20), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( - "Platforms", + "Predecessors", style: TextStyle( color: MyColors.white, fontWeight: FontWeight.bold, @@ -347,24 +271,23 @@ class _CreateGamePageState extends State { ), SizedBox(height: 8.0), TagEditor( - length: platformValues.length, - controller: platformsController, + length: predecessorValues.length, + controller: predecessorsController, delimiters: const [',', ' '], hasAddButton: true, resetTextOnSubmitted: true, - // This is set to grey just to illustrate the `textStyle` prop textStyle: const TextStyle( color: MyColors.red, fontWeight: FontWeight.bold), onSubmitted: (outstandingValue) { setState(() { - platformValues.add(outstandingValue); + predecessorValues.add(outstandingValue); }); }, inputDecoration: InputDecoration( filled: true, fillColor: MyColors.white, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), borderSide: const BorderSide( color: Colors.white, width: 2.0, @@ -375,33 +298,28 @@ class _CreateGamePageState extends State { focusedBorder: OutlineInputBorder( borderSide: const BorderSide( color: MyColors.lightBlue, width: 2.0), - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), ), ), onTagChanged: (newValue) { setState(() { - platformValues.add(newValue); + predecessorValues.add(newValue); }); }, tagBuilder: (context, index) => _Chip( index: index, - label: platformValues[index], - onDeleted: _onDeletepl, + label: predecessorValues[index], + onDeleted: _onDeletepr, ), - // InputFormatters example, this disallow \ and / inputFormatters: [ FilteringTextInputFormatter.deny(RegExp(r'[/\\]')) ], ), ]), const SizedBox(height: 20), - getbox("Age Restriction", ageRestrictionController, false), - const SizedBox(height: 20), - getbox("Game Bio", gameBioController, true), - const SizedBox(height: 20), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( - "Tags", + "Successors", style: TextStyle( color: MyColors.white, fontWeight: FontWeight.bold, @@ -409,24 +327,23 @@ class _CreateGamePageState extends State { ), SizedBox(height: 8.0), TagEditor( - length: tagValues.length, - controller: tagsController, + length: successorValues.length, + controller: successorsController, delimiters: const [',', ' '], hasAddButton: true, resetTextOnSubmitted: true, - // This is set to grey just to illustrate the `textStyle` prop textStyle: const TextStyle( color: MyColors.red, fontWeight: FontWeight.bold), onSubmitted: (outstandingValue) { setState(() { - tagValues.add(outstandingValue); + successorValues.add(outstandingValue); }); }, inputDecoration: InputDecoration( filled: true, fillColor: MyColors.white, border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), borderSide: const BorderSide( color: Colors.white, width: 2.0, @@ -437,33 +354,57 @@ class _CreateGamePageState extends State { focusedBorder: OutlineInputBorder( borderSide: const BorderSide( color: MyColors.lightBlue, width: 2.0), - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(25.0), ), ), onTagChanged: (newValue) { setState(() { - tagValues.add(newValue); + successorValues.add(newValue); }); }, tagBuilder: (context, index) => _Chip( index: index, - label: tagValues[index], - onDeleted: _onDeletet, + label: successorValues[index], + onDeleted: _onDeletes, ), - // InputFormatters example, this disallow \ and / inputFormatters: [ FilteringTextInputFormatter.deny(RegExp(r'[/\\]')) ], ), + const SizedBox( + height: 20, + ), + Center( + child: TextButton( + style: TextButton.styleFrom( + backgroundColor: MyColors.lightBlue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5.0), + ), + ), + onPressed: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => CreateGamePageSecond( + title: titleController.text, + coverLink: coverLinkController.text, + gameBio: gameBioController.text, + ageRestriction: + ageRestrictions[selectdAgeRestriction], + tags: tagValues, + predecessors: predecessorValues, + successors: successorValues), + )); + }, + child: const Text( + 'Next', + style: TextStyle( + color: MyColors.white, + fontSize: 16.0, + ), + ), + ), + ), ]), - const SizedBox(height: 20), - getbox("Release Date", releaseDateController, true), - const SizedBox(height: 20), - getbox("Developer", developerController, true), - const SizedBox(height: 20), - getbox("Publisher", publisherController, true), - const SizedBox(height: 20), - getbox("Trivia", triviaController, false), ], ), ), @@ -486,6 +427,7 @@ class _Chip extends StatelessWidget { @override Widget build(BuildContext context) { return Chip( + backgroundColor: MyColors.blue2, labelPadding: const EdgeInsets.only(left: 8.0), label: Text(label), deleteIcon: const Icon( diff --git a/ludos/mobile/lib/create_game_second.dart b/ludos/mobile/lib/create_game_second.dart new file mode 100644 index 00000000..ad6f08d4 --- /dev/null +++ b/ludos/mobile/lib/create_game_second.dart @@ -0,0 +1,383 @@ +import 'dart:convert'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:ludos_mobile_app/games_page.dart'; +import 'package:multi_dropdown/multiselect_dropdown.dart'; +import 'helper/colors.dart'; +import 'helper/APIService.dart'; + +Widget getbox(String hintText, TextEditingController controller, + bool isMandatory, bool multiLine) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (isMandatory) + const Text( + '*', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + Text( + hintText, + style: const TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 8.0), // Adjust the spacing as needed + TextFormField( + controller: controller, + style: const TextStyle(color: MyColors.red), + keyboardType: multiLine ? TextInputType.multiline : null, + maxLines: multiLine ? 5 : 1, + decoration: InputDecoration( + filled: true, + fillColor: MyColors.white, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(25.0), + borderSide: const BorderSide( + color: Colors.white, + width: 2.0, + ), + ), + hintText: '', + labelText: '', + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: MyColors.lightBlue, width: 2.0), + borderRadius: BorderRadius.circular(25.0), + ), + ), + cursorColor: MyColors.lightBlue, + ), + ], + ); +} + +String formatDateTime(DateTime dateTime) { + final months = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + + String month = months[dateTime.month - 1]; + String day = dateTime.day.toString(); + String year = dateTime.year.toString(); + + return '$month $day, $year'; +} + +class CreateGamePageSecond extends StatefulWidget { + final String title; + final String coverLink; + final String gameBio; + final String ageRestriction; + final List tags; + final List predecessors; + final List successors; + + const CreateGamePageSecond( + {Key? key, + required this.title, + required this.coverLink, + required this.gameBio, + required this.ageRestriction, + required this.tags, + required this.predecessors, + required this.successors}) + : super(key: key); + + @override + State createState() => _CreateGamePageStateSecond(); +} + +class _CreateGamePageStateSecond extends State { + DateTime date = DateTime(2016, 10, 24); + List selectedOptions = []; + List platforms = [ + 'Android', + 'iOS', + 'Windows', + 'macOS', + 'Linux', + 'PlayStation', + 'Xbox', + 'Nintendo', + 'Board Game' + ]; + + void _showDialog(Widget child) { + showCupertinoModalPopup( + context: context, + builder: (BuildContext context) => Container( + height: 216, + padding: const EdgeInsets.only(top: 6.0), + margin: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + color: CupertinoColors.systemBackground.resolveFrom(context), + child: SafeArea( + top: false, + child: child, + ), + ), + ); + } + + final TextEditingController systemRequirementsController = + TextEditingController(); + final TextEditingController gameGuideController = TextEditingController(); + final TextEditingController gameStoryController = TextEditingController(); + final TextEditingController developerController = TextEditingController(); + final TextEditingController publisherController = TextEditingController(); + final TextEditingController triviaController = TextEditingController(); + final MultiSelectController platformsController = MultiSelectController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: const Color(0xFF2f5b7a), + centerTitle: true, + title: const Text('Ludos'), + ), + backgroundColor: MyColors.darkBlue, + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(height: 20), + getbox("Game Story", gameStoryController, true, true), + const SizedBox(height: 20), + getbox("Game Guide", gameGuideController, false, true), + const SizedBox(height: 20), + const Row( + children: [ + Text( + "Release Date", + style: TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + Row(mainAxisAlignment: MainAxisAlignment.start, children: [ + Container( + padding: const EdgeInsets.only(), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.0), + color: MyColors.white, + border: Border.all( + color: Colors.white, + width: 2.0, + ), + ), + child: SizedBox( + height: 30.0, + width: 200.0, + child: CupertinoButton( + padding: const EdgeInsets.symmetric(horizontal: 50.0), + onPressed: () => _showDialog( + CupertinoDatePicker( + dateOrder: DatePickerDateOrder.dmy, + initialDateTime: date, + mode: CupertinoDatePickerMode.date, + use24hFormat: true, + onDateTimeChanged: (DateTime newDate) { + setState(() => date = newDate); + }, + ), + ), + child: Text( + '${date.day}.${date.month}.${date.year}', + style: const TextStyle( + color: MyColors.blue, + fontSize: 18.0, + ), + ), + ), + ), + ), + ]), + const SizedBox(height: 20), + getbox("System Requirements", systemRequirementsController, + true, false), + const SizedBox(height: 20), + const Row( + children: [ + Text( + '*', + style: TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + Text( + "Platforms", + style: TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + const SizedBox(height: 10), + MultiSelectDropDown( + hint: "", + borderRadius: 25.0, + showClearIcon: true, + controller: platformsController, + onOptionSelected: (options) {}, + options: const [ + ValueItem(label: 'Android'), + ValueItem(label: 'iOS'), + ValueItem(label: 'Windows'), + ValueItem(label: 'macOS'), + ValueItem(label: 'Linux'), + ValueItem(label: 'PlayStation'), + ValueItem(label: 'Xbox'), + ValueItem(label: 'Nintendo'), + ValueItem(label: 'Board Game'), + ], + selectionType: SelectionType.multi, + chipConfig: const ChipConfig( + wrapType: WrapType.wrap, + backgroundColor: MyColors.lightBlue), + dropdownHeight: 200, + optionTextStyle: const TextStyle(fontSize: 16), + selectedOptionTextColor: MyColors.blue, + selectedOptionIcon: const Icon( + Icons.check_circle, + color: MyColors.lightBlue, + ), + ), + const SizedBox(height: 20), + getbox("Developer", developerController, true, false), + const SizedBox(height: 20), + getbox("Game Publisher", publisherController, true, false), + const SizedBox(height: 20), + getbox("Trivia", triviaController, false, false), + const SizedBox(height: 20), + TextButton( + style: TextButton.styleFrom( + backgroundColor: MyColors.lightBlue, + ), + onPressed: () async { + http.Response token = await APIService().createGame( + widget.title, + widget.coverLink, + systemRequirementsController.text, + widget.predecessors, + widget.successors, + gameGuideController.text, + gameStoryController.text, + platformsController.selectedOptions + .map((item) => item.label) + .where((element) => element != null) + .map((e) => e!) + .toList(), + widget.ageRestriction, + widget.gameBio, + widget.tags, + formatDateTime(date), + developerController.text, + publisherController.text, + triviaController.text); + if (token.statusCode == 201) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Row( + children: [ + Icon( + Icons.check_circle_outline, + color: MyColors.blue, + ), + SizedBox(width: 8), + Expanded( + child: Text( + 'Your game is created successfully. You will be redirected to the Games Page.', + style: TextStyle( + color: MyColors.blue, + fontSize: 16, + ), + ), + ), + ], + ), + backgroundColor: MyColors.blue2, + duration: const Duration(seconds: 100), + action: SnackBarAction( + label: 'OK', + textColor: MyColors.blue, + onPressed: () { + ScaffoldMessenger.of(context) + .hideCurrentSnackBar(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GamesPage()), + ); + }, + ), + ), + ); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: SizedBox( + width: MediaQuery.of(context).size.width, + child: Text( + json.decode(token.body)["message"], + style: const TextStyle( + color: MyColors.blue, + fontSize: 16, + ), + ), + ), + backgroundColor: MyColors.blue2, + duration: const Duration(seconds: 10), + action: SnackBarAction( + label: 'OK', + textColor: MyColors.blue, + onPressed: () { + ScaffoldMessenger.of(context) + .hideCurrentSnackBar(); + }, + ), + ), + ); + } + }, + child: const Text( + 'Save Game', + style: TextStyle(color: Colors.white), + ), + ), + ]), + ), + ), + ); + } +} diff --git a/ludos/mobile/pubspec.lock b/ludos/mobile/pubspec.lock index 02a5c1f2..a9cf92eb 100644 --- a/ludos/mobile/pubspec.lock +++ b/ludos/mobile/pubspec.lock @@ -155,6 +155,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + multi_dropdown: + dependency: "direct main" + description: + name: multi_dropdown + sha256: "2db9b41ae543a9277defc2b5ba95622acb72ed0fb49d6dead357a1639371bee3" + url: "https://pub.dev" + source: hosted + version: "2.1.0" nested: dependency: transitive description: diff --git a/ludos/mobile/pubspec.yaml b/ludos/mobile/pubspec.yaml index e5bb95b9..6fa84cbc 100644 --- a/ludos/mobile/pubspec.yaml +++ b/ludos/mobile/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: http: ^1.1.0 provider: ^5.0.0 material_tag_editor: 0.1.2 + multi_dropdown: ^2.1.0 # The following adds the Cupertino Icons font to your application. From 804e53465f3dfb098a25dd5400a5fb694d9dbc80 Mon Sep 17 00:00:00 2001 From: kardelenerdal Date: Sun, 19 Nov 2023 15:35:11 +0300 Subject: [PATCH 02/22] default image for games page --- ludos/mobile/assets/images/default_game.jpg | Bin 0 -> 9917 bytes .../lib/reusable_widgets/game_summary.dart | 154 ++++++++++-------- 2 files changed, 85 insertions(+), 69 deletions(-) create mode 100644 ludos/mobile/assets/images/default_game.jpg diff --git a/ludos/mobile/assets/images/default_game.jpg b/ludos/mobile/assets/images/default_game.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e79ffc7e19d5b57c02dec8231e1884a6a16caf49 GIT binary patch literal 9917 zcmeHLX;@Q9*1jQxu!l%kL^eSXL6%lfaUqR#L~y~dDj=wU0=6u&$`%n32ue^^Y1u(G z0ohDMBuZov29Yfwpa>{CLIg~Jkj#zK_B{5?^Nii&^!MYVQs-7~Zspc_&w1ri4S&PyOr@ITxzJ{XJ-B_tq(`oo3&1VHjaTaLoN76=!pBM+RHk6!@nP$dFzL7-4BZYW1N?7^`i91)=gl3RUES24-dBA?!z1tBkB*H`OwvBiFD!nd zGnSS)xF7)ZAFzHy_B&ii5EmCWHR4~hiPz>+CmrS#|d zI=o5-*P5)^V2uN799ZMP8VA-mu*QM^Hyn7JJ5KEDYS^>KFPTt73AZoU%Ri+af_mNb zkW_n3bV5pk4e(n*!?PZ>5U$esZ-*U=v0S*dEo?#g;ROduz_lxb6N%H2N5wHqu`Gq? z(AyZ48IXln`OBnukk9|D$vS(~rpEK)g5|>G4+O8{7BhN+9oOQ9R?PdOoa}d-oNR3y z*Fy6rHn<+|eR{WF>2c*Dl(CkW;nWA$Mr|)co!k1YhMwGh$7;+JT0{l6X*KC=*7o{Q z7Bk|`n~oR|i1ui8_@QnrU7cKw%e}bxVVRnk(vakNPG0h+M_zk%*iz5c^7u3Nw4S^T zvsON|4zE%ug2kYK?+B>6hV4Pok=22wCYB zpNXz>wolzWB^77Ur9(`bT_#r9bgx;xog7YhvQ3zeh)a`Lv^UA0U;|V4TtAV;{e$;n za%vR0hS6!_`+n&h$F&*LE;;x+`oA-cxmu*=!|j9W6lqq;%GbxBjzPYNy?#R`cK_;2 zExm;K_ko0KI!fK%YJ0-viHTyX$sRcDa@RrUuF)9h(HK{LVesladRh2GU?}X5-%V^g zyq{Oy*e`I(> z4~zH(`A>JR2CZPuGt%|rDAo58$J7OaXlQy8)6lr7n2%p2EBQBIJ|R$=^|;lM3*Nou zcgFmf^KQ4716E-d<4vLCI;SSrA9PZb4~q?+80fjqz%`^qL_E16X={UPTkjRhnwVi0 zl~*!y?>1$(5lNvdGmyryo6N$k7B4ze3}!n>Qw02OTi2j`x3g{(XYA70vXxRPW91k% z+u5N$wZg-6d*bPFo6ZSLvTYY5V)$7t`qhJr@X(`YZ<+{F7>4&*K6UMw6C1zpq)vSE z=1@!I+i-yvMVN)m^9-R`D8p+d&;p@4+A)aOoGshJU<0b3kd0gwLAqInL#wK4=1+s% zZnA-z#sXHlzI71w$Uw$tez^$hW~YD^sH6EsSUs~gJCGreU+aH-nGN`jV+vb2rOwK^1NJAatlz`XxNVKh7YDvYeAx znn;+%Y^Eobu>rk=B)irTP2ti=?+4~`6CUq}-OXO(>Y zo7dTnLyR8r-V36pIz@mXTifd(l;e2ajNdg=cIRk{6niHUtBYtq(Cn!N{XnMIMl7wK z@6^qAS9hqbN_^vu5X4&~^>*&x%Z2Zt@kDEc6&`U>HZJ&^`|9F8#L@?$v(&5MDD2_S zhXU1u5w#mw)UJ?MyWOfD=sq29>q1aU-`i5t9W-(Sh3*MdpRjS3>ls%Y(7vFmE^*|_ z3*^U!8Y2CdhiEg?-u4!z!5sNhctKZj+^RBir5j7{?3qL-b=1_>hU+c9OWO-RRbm5u zrw*U6mA)>FtWIEM(-v|iUaoI$$B3N)1I6XtA@h zLtVLU!$BU!D9?1WfmNdt2aZ1~?x8D{?H8gGmLPJzdgN00$CBoXpL6~}wS$y@YxS>w z6n1Zv-5|wEwgYh;zAR2y%>)@XW{19#b44o*m6Wp6#RlyigIweYfP{uAi^Jg*b+(%mP`jzn19Pw5!)!^k(v=uaDxifr3kH>M%( zO>B2gxlWBE+(rn!N-C4H&b6bAxC;_QT>6LX7kVK-q|XXZv1AuVrgM)MaY;&x1mL^( z#43GZ!+(<&R?C5`Ucp()y0U>R{fML(L>3F(Bvp51y~f!X$=I4(S{4?QM&oMzdv!AN zA{O$C&4e}&evW#jj1V#P>s`_wyK*^UuR`V5sP`>R;d1zO>(o0Bhyt}+tj;5*7K=bn z0{#c8at>uqnpq!tZt(HJE~jY>w$zq3c8yHb9@K-iz9%XDC|ymrp92$xo}1KySY2d} z4+eHShASp#*{YIZLYW$Rt+Y{oL(3LoFB5ESFy;?owBs5 z)-mbW5cZiJ`M)V$s>8T3gsW)wu`yrmil9%T!wCnNxOD1cwUn6Q*BTkO*57o*V^o_1 zL|iiE&{!M2EprQLGTl<5iJ>fK$9+AWlWJ0xo51|w)SnMe)@d7FzN7IJixoO5`x&!= z*ZN&-z@#7tZZp?OxxLvkHNZ)pI|N3~8?%eT+SoL@xJzs0AIv^hn0;SNlanuUrbCA$ zClDEG1Am~gyD&`PYTX%`d0xB!g8z~)H1d}sxx2b@2>~YBUPnJ6VK?qr2j}c~snMHt ztPtH6kj^ol!Iv!R*JKLJLUc0(38KzOM)aXT-@3n%wN2%Q+~@X9)t3}IRgiA+#WrsJ z*&_avLha>I5-#$M#ER3yZuQtpG|ky~+`1SxfMwPu7)7NyVNjNwQ1_>@EO=*ephPHB zcQq{cTEdStci>6ghE7a$swY{PxpTE3Ke*b5hdRK_tkzB?YmgHHN<&mP{R zEkyqIo;jyUrxrLf1=YYCnn*2_( zX)>=&cDUw{WyffH6<_gYxti-E`;vVXd#O>kn!T}@V^{v@v@dq44(h8~*b%ES;dCK; zAG|=XuCOORNOx|mcWX@cZWilp$h2%>8kz91UKwtdGvP759JC`AaZ9HmuruXz+^xhJ56k$O|%_gPwQT(^BN#E6Y zyd}liHAHZd8KLkN@pPUVt7m=OwKCnZugg!nr))vcG#N1oGi<=Rfo2skLX%CNyR&s- z-eBYOn{)&7WP)f`4vBb+>asCG@M1%dR{?9V%bZ(HcgP6uoJa!IIb6W^IrEhI;a z02OFC8wk%U!nHk4Uj+j;(?V{pa?yQ>Ucm-x<=6neWCz~T;NdG=U1v9W>`fbsfn%u> z7}fzq=E^HJFo<9S^;R1&;`(0y`8`{&j5)CZ0TW9$kRFI$8DayBLiCW!ij7f>f)FIrgQX~kf=`3Xm-umPI+SA&!KRLTWXlbGuJY*@k5Y+yEm4Gic14uoeV z)=^@Q=GS^+e?Rz$b5@gvul?76v%enPqM;oVYdcN;T2I5ThcCL36)pd*Mk*+z6TetD z4_DU(t{r34PS%n;OO;g3!B)t$=<;mMEud3yH#C{*Ck-CwmdVX&(n^%^pb0%e6xJ)O zAkaWN*nkw4W<4+6OG&4vkX%*Dq1Ez0?e$ODo zMs$Rw-yX&WPMR=Gado{UHjoj7ql2Cg_wdF#!o5S(PM(YAHDn5Q;OeN4Ku<~HX0vT| zP}Bc1vH=ZrWK}9@`2(oNuu4{wa6e1DBpdWw;vj?0=)?`Vy5i<%TtL?nIj%j)acyg@ zs%vU-F createState() => _GameSummaryState( - title: title, - averageRating: averageRating, - coverLink: coverLink, - numOfFollowers: numOfFollowers, - gameStory: gameStory, - tags: tags, - textColor: textColor, - backgroundColor: backgroundColor, - fontSize: fontSize, - ); + title: title, + averageRating: averageRating, + coverLink: coverLink, + numOfFollowers: numOfFollowers, + gameStory: gameStory, + tags: tags, + textColor: textColor, + backgroundColor: backgroundColor, + fontSize: fontSize, + ); } class _GameSummaryState extends State { @@ -69,11 +69,10 @@ class _GameSummaryState extends State { children: [ ElevatedButton( style: ElevatedButton.styleFrom( - backgroundColor: backgroundColor, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15.0), - ) - ), + backgroundColor: backgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15.0), + )), onPressed: () { // Handle button press for the specific game // Navigate to the game's profile page @@ -100,10 +99,17 @@ class _GameSummaryState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Image.network( - coverLink, + FadeInImage( width: 100, height: 160, + image: NetworkImage(coverLink), + placeholder: const AssetImage( + 'assets/images/default_game.jpg', + ), + imageErrorBuilder: (context, error, stackTrace) { + return Image.asset('assets/images/default_game.jpg', + width: 100, height: 160, fit: BoxFit.fill); + }, fit: BoxFit.fill, ), ], @@ -112,55 +118,65 @@ class _GameSummaryState extends State { height: 160, width: 210, child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - const Icon(Icons.favorite, size: 20, color: MyColors.lightBlue), - Text( - "+$numOfFollowers favorites", - style: const TextStyle( - color: MyColors.lightBlue, - fontSize: 16.0, - ) - ) - ] - ), - Row( - children: List.generate( - 5, // Total number of stars - (index) { - double diff = 4.5 - index; - if (diff >= 1.0) { - // Full star - return const SingleRatingIcon(icon: Icons.star, size: 20, iconColor: MyColors.lightBlue, rating: 10.0); - } else if (diff >= 0.5) { - // Floating star - return SingleRatingIcon(icon: Icons.star, size: 20, iconColor: MyColors.lightBlue, rating: diff * 10.0); - } else { - // Empty star - return const SingleRatingIcon(icon: Icons.star, size: 20, iconColor: MyColors.lightBlue, rating: 0.0); - } - }, - ).toList(), - ), - ] - ), - const SizedBox(height: 10), - Text( - "Here will be brief game story.", - softWrap: true, - style: TextStyle( - color: MyColors.darkBlue, - fontSize: fontSize, + const Icon(Icons.favorite, + size: 20, color: MyColors.lightBlue), + Text("+$numOfFollowers favorites", + style: const TextStyle( + color: MyColors.lightBlue, + fontSize: 16.0, + )) + ]), + Row( + children: List.generate( + 5, // Total number of stars + (index) { + double diff = 4.5 - index; + if (diff >= 1.0) { + // Full star + return const SingleRatingIcon( + icon: Icons.star, + size: 20, + iconColor: MyColors.lightBlue, + rating: 10.0); + } else if (diff >= 0.5) { + // Floating star + return SingleRatingIcon( + icon: Icons.star, + size: 20, + iconColor: MyColors.lightBlue, + rating: diff * 10.0); + } else { + // Empty star + return const SingleRatingIcon( + icon: Icons.star, + size: 20, + iconColor: MyColors.lightBlue, + rating: 0.0); + } + }, + ).toList(), ), - ), - ], + ]), + const SizedBox(height: 10), + Text( + "Here will be brief game story.", + softWrap: true, + style: TextStyle( + color: MyColors.darkBlue, + fontSize: fontSize, + ), + ), + ], ), ), ], @@ -179,12 +195,12 @@ class _GameSummaryState extends State { textStyle: const TextStyle(color: MyColors.darkBlue), ), child: Text( - tag, - style: const TextStyle( - color: MyColors.darkBlue, - fontWeight: FontWeight.bold, - fontSize: 17.0, - ), + tag, + style: const TextStyle( + color: MyColors.darkBlue, + fontWeight: FontWeight.bold, + fontSize: 17.0, + ), ), ); }).toList(), From 7a9cc082fca3bdd28efd8207989ccf83fc17e318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Sun, 19 Nov 2023 19:35:21 +0300 Subject: [PATCH 03/22] Comment entity and writeComment and likeComment endpoints --- ludos/backend/src/app.module.ts | 5 ++- .../src/controllers/user.controller.ts | 44 +++++++++++++++++++ .../dtos/comment/request/like-comment.dto.ts | 8 ++++ .../dtos/comment/request/write-comment.dto.ts | 12 +++++ ludos/backend/src/entities/comment.entity.ts | 24 ++++++++++ .../src/repositories/comment.repository.ts | 26 +++++++++++ .../services/config/typeorm-config.service.ts | 3 +- ludos/backend/src/services/user.service.ts | 36 +++++++++++++++ ludos/backend/test/user.controller.spec.ts | 2 + 9 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 ludos/backend/src/dtos/comment/request/like-comment.dto.ts create mode 100644 ludos/backend/src/dtos/comment/request/write-comment.dto.ts create mode 100644 ludos/backend/src/entities/comment.entity.ts create mode 100644 ludos/backend/src/repositories/comment.repository.ts diff --git a/ludos/backend/src/app.module.ts b/ludos/backend/src/app.module.ts index f7b7b4ac..6691a879 100644 --- a/ludos/backend/src/app.module.ts +++ b/ludos/backend/src/app.module.ts @@ -15,8 +15,10 @@ import { GameController } from './controllers/game.controller'; import { GameService } from './services/game.service'; import { GameRepository } from './repositories/game.repository'; import { Game } from './entities/game.entity'; +import { Comment } from './entities/comment.entity'; import { TokenDecoderMiddleware } from './middlewares/tokenDecoder.middleware'; import { ResetPasswordRepository } from './repositories/reset-password.repository'; +import { CommentRepository } from './repositories/comment.repository'; import { S3Service } from './services/s3.service'; import { S3Controller } from './controllers/s3.controller'; @@ -33,7 +35,7 @@ import { S3Controller } from './controllers/s3.controller'; useClass: TypeOrmConfigService, inject: [TypeOrmConfigService], }), - TypeOrmModule.forFeature([User, Game, ResetPassword]), + TypeOrmModule.forFeature([User, Game, ResetPassword, Comment]), ], controllers: [AppController, UserController, GameController, S3Controller], providers: [ @@ -44,6 +46,7 @@ import { S3Controller } from './controllers/s3.controller'; GameService, ResetPasswordRepository, S3Service, + CommentRepository, ], }) export class AppModule implements NestModule { diff --git a/ludos/backend/src/controllers/user.controller.ts b/ludos/backend/src/controllers/user.controller.ts index dd75196f..e173a9f9 100644 --- a/ludos/backend/src/controllers/user.controller.ts +++ b/ludos/backend/src/controllers/user.controller.ts @@ -27,6 +27,8 @@ import { VerifyCodeDto } from '../dtos/user/request/verify-code.dto'; import { ChangePasswordResponseDto } from '../dtos/user/response/change-password-response.dto'; import { ChangePasswordDto } from '../dtos/user/request/change-password.dto'; import { EditUserInfoDto } from '../dtos/user/request/edit-info.dto'; +import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; +import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; import { UserService } from '../services/user.service'; import { AuthGuard } from '../services/guards/auth.guard'; import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; @@ -154,4 +156,46 @@ export class UserController { public async getUserInfoById(@Req() req: AuthorizedRequest) { return await this.userService.getUserInfo(req.user.id); } + + @ApiOperation({ summary: 'Comment on a post' }) + @ApiOkResponse({ + description: 'Comment', + }) + @ApiUnauthorizedResponse({ + description: 'Invalid Credentials', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(200) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Post('/write-comment') + public async writeComment( + @Req() req: AuthorizedRequest, + @Body() input: WriteCommentDto, + ) { + await this.userService.writeComment(req.user.id, input); + } + + @ApiOperation({ summary: 'Like a comment' }) + @ApiOkResponse({ + description: 'Like Comment', + }) + @ApiUnauthorizedResponse({ + description: 'Invalid Credentials', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(200) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Post('/like-comment') + public async likeComment( + @Req() req: AuthorizedRequest, + @Body() input: LikeCommentDto, + ) { + await this.userService.likeComment(req.user.id, input); + } } diff --git a/ludos/backend/src/dtos/comment/request/like-comment.dto.ts b/ludos/backend/src/dtos/comment/request/like-comment.dto.ts new file mode 100644 index 00000000..24406661 --- /dev/null +++ b/ludos/backend/src/dtos/comment/request/like-comment.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; +export class LikeCommentDto { + @ApiProperty() + @IsString() + commentId: string; +} + diff --git a/ludos/backend/src/dtos/comment/request/write-comment.dto.ts b/ludos/backend/src/dtos/comment/request/write-comment.dto.ts new file mode 100644 index 00000000..4a9e95f9 --- /dev/null +++ b/ludos/backend/src/dtos/comment/request/write-comment.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; +export class WriteCommentDto { + @ApiProperty() + @IsString() + postId: string; + + @ApiProperty() + @IsString() + text: string; +} + diff --git a/ludos/backend/src/entities/comment.entity.ts b/ludos/backend/src/entities/comment.entity.ts new file mode 100644 index 00000000..094fbf5b --- /dev/null +++ b/ludos/backend/src/entities/comment.entity.ts @@ -0,0 +1,24 @@ +import { + Entity, + Column, + PrimaryGeneratedColumn, +} from 'typeorm'; +//import { User } from '../entities/user.entity'; + +@Entity('comments') +export class Comment { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + author: string; //User; + + @Column() + postId: string; + + @Column() + text: string; + + @Column() + likes: number; +} diff --git a/ludos/backend/src/repositories/comment.repository.ts b/ludos/backend/src/repositories/comment.repository.ts new file mode 100644 index 00000000..f81c508e --- /dev/null +++ b/ludos/backend/src/repositories/comment.repository.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { Comment } from '../entities/comment.entity'; +import { Repository, DataSource } from 'typeorm'; + +@Injectable() +export class CommentRepository extends Repository { + constructor(dataSource: DataSource) { + super(Comment, dataSource.createEntityManager()); + } + + public async createComment(input: Partial): Promise { + let comment = this.create(input); + await this.insert(comment); + return comment; + } + + public findCommentById(id: string): Promise { + return this.findOneBy({ id }); + } + + public async incrementLikeCount(commentId: string) { + let comment = await this.findCommentById(commentId); + comment.likes += 1; + await this.save(comment); + } +} diff --git a/ludos/backend/src/services/config/typeorm-config.service.ts b/ludos/backend/src/services/config/typeorm-config.service.ts index 619f5723..19604a93 100644 --- a/ludos/backend/src/services/config/typeorm-config.service.ts +++ b/ludos/backend/src/services/config/typeorm-config.service.ts @@ -4,6 +4,7 @@ import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm'; import { User } from '../../entities/user.entity'; import { ResetPassword } from '../../entities/reset-password.entity'; import { Game } from '../../entities/game.entity'; +import { Comment } from '../../entities/comment.entity'; @Injectable() export class TypeOrmConfigService implements TypeOrmOptionsFactory { @@ -17,7 +18,7 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory { password: this.configService.get('DB_PASSWORD'), port: this.configService.get('DB_PORT'), database: this.configService.get('DB_NAME'), - entities: [User, ResetPassword, Game], + entities: [User, ResetPassword, Game, Comment], synchronize: true, }; } diff --git a/ludos/backend/src/services/user.service.ts b/ludos/backend/src/services/user.service.ts index 6efed627..0f16a4d4 100644 --- a/ludos/backend/src/services/user.service.ts +++ b/ludos/backend/src/services/user.service.ts @@ -20,9 +20,12 @@ import { ChangePasswordResponseDto } from '../dtos/user/response/change-password import { LoginResponseDto } from '../dtos/user/response/login-response.dto'; import { RegisterResponseDto } from '../dtos/user/response/register-response.dto'; import { EditUserInfoDto } from '../dtos/user/request/edit-info.dto'; +import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; +import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; import { Payload } from '../interfaces/user/payload.interface'; import { ResetPasswordRepository } from '../repositories/reset-password.repository'; import { UserRepository } from '../repositories/user.repository'; +import { CommentRepository } from '../repositories/comment.repository'; import { GetUserInfoResponseDto } from '../dtos/user/response/get-user-info-response.dto'; @Injectable() @@ -30,6 +33,7 @@ export class UserService { constructor( private readonly userRepository: UserRepository, private readonly resetPasswordRepository: ResetPasswordRepository, + private readonly commentRepository: CommentRepository, private readonly jwtService: JwtService, private readonly configService: ConfigService, ) {} @@ -202,4 +206,36 @@ export class UserService { return response; } + + public async writeComment(userId: string, writeCommentDto: WriteCommentDto) { + let user = await this.userRepository.findUserById(userId); + + if (!user) { + throw new HttpException( + 'No user found with this email', + HttpStatus.FORBIDDEN, + ); + } + + let comment = { + author: userId, //user, + text: writeCommentDto.text, + postId: writeCommentDto.postId, + likes: 0, + } + let c = await this.commentRepository.createComment(comment); + } + + public async likeComment(userId: string, likeCommentDto: LikeCommentDto) { + let comment = await this.commentRepository.findCommentById(likeCommentDto.commentId); + + if (!comment) { + throw new HttpException( + 'No comment found with this id', + HttpStatus.FORBIDDEN, + ); + } + + await this.commentRepository.incrementLikeCount(likeCommentDto.commentId); + } } diff --git a/ludos/backend/test/user.controller.spec.ts b/ludos/backend/test/user.controller.spec.ts index 781e9cb6..1431a52d 100644 --- a/ludos/backend/test/user.controller.spec.ts +++ b/ludos/backend/test/user.controller.spec.ts @@ -14,6 +14,7 @@ import { S3Service } from '../src/services/s3.service'; import { ResetPassword } from '../src/entities/reset-password.entity'; import { ResetDto } from '../src/dtos/user/request/reset.dto'; import { ResetPasswordRepository } from '../src/repositories/reset-password.repository'; +import { CommentRepository } from '../src/repositories/comment.repository'; describe('UserController', () => { let userController: UserController; @@ -38,6 +39,7 @@ describe('UserController', () => { S3Service, UserRepository, ResetPasswordRepository, + CommentRepository, { provide: DataSource, useValue: dataSource, From 885328da45ec4b9a44fc3cb64b5e307feadfb570 Mon Sep 17 00:00:00 2001 From: kardelenerdal Date: Mon, 20 Nov 2023 20:29:37 +0300 Subject: [PATCH 04/22] changed the duration of pop up message --- ludos/mobile/lib/create_game_second.dart | 69 +++++++++++++----------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/ludos/mobile/lib/create_game_second.dart b/ludos/mobile/lib/create_game_second.dart index ad6f08d4..4583d55a 100644 --- a/ludos/mobile/lib/create_game_second.dart +++ b/ludos/mobile/lib/create_game_second.dart @@ -306,43 +306,50 @@ class _CreateGamePageStateSecond extends State { publisherController.text, triviaController.text); if (token.statusCode == 201) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Row( - children: [ - Icon( - Icons.check_circle_outline, - color: MyColors.blue, - ), - SizedBox(width: 8), - Expanded( - child: Text( - 'Your game is created successfully. You will be redirected to the Games Page.', - style: TextStyle( + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: const Row( + children: [ + Icon( + Icons.check_circle_outline, color: MyColors.blue, - fontSize: 16, ), - ), + SizedBox(width: 8), + Expanded( + child: Text( + 'Your game is created successfully. You will be redirected to the Games Page.', + style: TextStyle( + color: MyColors.blue, + fontSize: 16, + ), + ), + ), + ], ), - ], - ), - backgroundColor: MyColors.blue2, - duration: const Duration(seconds: 100), - action: SnackBarAction( - label: 'OK', - textColor: MyColors.blue, - onPressed: () { - ScaffoldMessenger.of(context) - .hideCurrentSnackBar(); - Navigator.push( + backgroundColor: MyColors.blue2, + duration: const Duration(seconds: 5), + action: SnackBarAction( + label: 'OK', + textColor: MyColors.blue, + onPressed: () { + ScaffoldMessenger.of(context) + .hideCurrentSnackBar(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GamesPage()), + ); + }, + ), + ), + ) + .closed + .then((reason) => Navigator.push( context, MaterialPageRoute( builder: (context) => GamesPage()), - ); - }, - ), - ), - ); + )); } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( From d88f00e6a9b9e8382bb2902da04d5cfb0b69e1c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Mon, 20 Nov 2023 21:16:34 +0300 Subject: [PATCH 05/22] add timestamp, parentId fields and dislikeComment endpoint to Comment entity --- .../src/controllers/user.controller.ts | 22 +++++++++++++++++ .../comment/request/dislike-comment.dto.ts | 8 +++++++ .../dtos/comment/request/write-comment.dto.ts | 2 +- ludos/backend/src/entities/comment.entity.ts | 8 ++++++- .../src/repositories/comment.repository.ts | 6 +++++ ludos/backend/src/services/user.service.ts | 24 +++++++++++++++++-- 6 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 ludos/backend/src/dtos/comment/request/dislike-comment.dto.ts diff --git a/ludos/backend/src/controllers/user.controller.ts b/ludos/backend/src/controllers/user.controller.ts index e173a9f9..61e63641 100644 --- a/ludos/backend/src/controllers/user.controller.ts +++ b/ludos/backend/src/controllers/user.controller.ts @@ -29,6 +29,7 @@ import { ChangePasswordDto } from '../dtos/user/request/change-password.dto'; import { EditUserInfoDto } from '../dtos/user/request/edit-info.dto'; import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; +import { DislikeCommentDto } from '../dtos/comment/request/dislike-comment.dto'; import { UserService } from '../services/user.service'; import { AuthGuard } from '../services/guards/auth.guard'; import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; @@ -198,4 +199,25 @@ export class UserController { ) { await this.userService.likeComment(req.user.id, input); } + + @ApiOperation({ summary: 'Dislike a comment' }) + @ApiOkResponse({ + description: 'Dislike Comment', + }) + @ApiUnauthorizedResponse({ + description: 'Invalid Credentials', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(200) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Post('/dislike-comment') + public async dislikeComment( + @Req() req: AuthorizedRequest, + @Body() input: DislikeCommentDto, + ) { + await this.userService.dislikeComment(req.user.id, input); + } } diff --git a/ludos/backend/src/dtos/comment/request/dislike-comment.dto.ts b/ludos/backend/src/dtos/comment/request/dislike-comment.dto.ts new file mode 100644 index 00000000..6dcb5737 --- /dev/null +++ b/ludos/backend/src/dtos/comment/request/dislike-comment.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; +export class DislikeCommentDto { + @ApiProperty() + @IsString() + commentId: string; +} + diff --git a/ludos/backend/src/dtos/comment/request/write-comment.dto.ts b/ludos/backend/src/dtos/comment/request/write-comment.dto.ts index 4a9e95f9..32a9a635 100644 --- a/ludos/backend/src/dtos/comment/request/write-comment.dto.ts +++ b/ludos/backend/src/dtos/comment/request/write-comment.dto.ts @@ -3,7 +3,7 @@ import { IsString } from 'class-validator'; export class WriteCommentDto { @ApiProperty() @IsString() - postId: string; + parentId: string; @ApiProperty() @IsString() diff --git a/ludos/backend/src/entities/comment.entity.ts b/ludos/backend/src/entities/comment.entity.ts index 094fbf5b..60d9c001 100644 --- a/ludos/backend/src/entities/comment.entity.ts +++ b/ludos/backend/src/entities/comment.entity.ts @@ -14,11 +14,17 @@ export class Comment { author: string; //User; @Column() - postId: string; + timestamp: Date; + + @Column() + parentId: string; @Column() text: string; @Column() likes: number; + + @Column() + dislikes: number; } diff --git a/ludos/backend/src/repositories/comment.repository.ts b/ludos/backend/src/repositories/comment.repository.ts index f81c508e..41aff425 100644 --- a/ludos/backend/src/repositories/comment.repository.ts +++ b/ludos/backend/src/repositories/comment.repository.ts @@ -23,4 +23,10 @@ export class CommentRepository extends Repository { comment.likes += 1; await this.save(comment); } + + public async incrementDislikeCount(commentId: string) { + let comment = await this.findCommentById(commentId); + comment.dislikes += 1; + await this.save(comment); + } } diff --git a/ludos/backend/src/services/user.service.ts b/ludos/backend/src/services/user.service.ts index 0f16a4d4..f271b25f 100644 --- a/ludos/backend/src/services/user.service.ts +++ b/ludos/backend/src/services/user.service.ts @@ -22,6 +22,7 @@ import { RegisterResponseDto } from '../dtos/user/response/register-response.dto import { EditUserInfoDto } from '../dtos/user/request/edit-info.dto'; import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; +import { DislikeCommentDto } from '../dtos/comment/request/dislike-comment.dto'; import { Payload } from '../interfaces/user/payload.interface'; import { ResetPasswordRepository } from '../repositories/reset-password.repository'; import { UserRepository } from '../repositories/user.repository'; @@ -217,13 +218,19 @@ export class UserService { ); } + // check parent id + // post / comment / game review + // parent id should be the identifier of one of the above + let comment = { author: userId, //user, text: writeCommentDto.text, - postId: writeCommentDto.postId, + parentId: writeCommentDto.parentId, likes: 0, + dislikes: 0, + timestamp: new Date(), } - let c = await this.commentRepository.createComment(comment); + await this.commentRepository.createComment(comment); } public async likeComment(userId: string, likeCommentDto: LikeCommentDto) { @@ -238,4 +245,17 @@ export class UserService { await this.commentRepository.incrementLikeCount(likeCommentDto.commentId); } + + public async dislikeComment(userId: string, dislikeCommentDto: DislikeCommentDto) { + let comment = await this.commentRepository.findCommentById(dislikeCommentDto.commentId); + + if (!comment) { + throw new HttpException( + 'No comment found with this id', + HttpStatus.FORBIDDEN, + ); + } + + await this.commentRepository.incrementDislikeCount(dislikeCommentDto.commentId); + } } From 725db2f7676fd932c5f79c3771156ceb5c5fefbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Mon, 20 Nov 2023 21:32:27 +0300 Subject: [PATCH 06/22] take comment related things to separate controller and service --- ludos/backend/src/app.module.ts | 5 +- .../src/controllers/comment.controller.ts | 91 +++++++++++++++++++ .../src/controllers/user.controller.ts | 63 ------------- ludos/backend/src/services/comment.service.ts | 69 ++++++++++++++ ludos/backend/src/services/user.service.ts | 55 ----------- ludos/backend/test/user.controller.spec.ts | 2 - 6 files changed, 164 insertions(+), 121 deletions(-) create mode 100644 ludos/backend/src/controllers/comment.controller.ts create mode 100644 ludos/backend/src/services/comment.service.ts diff --git a/ludos/backend/src/app.module.ts b/ludos/backend/src/app.module.ts index 6691a879..d6729502 100644 --- a/ludos/backend/src/app.module.ts +++ b/ludos/backend/src/app.module.ts @@ -21,6 +21,8 @@ import { ResetPasswordRepository } from './repositories/reset-password.repositor import { CommentRepository } from './repositories/comment.repository'; import { S3Service } from './services/s3.service'; import { S3Controller } from './controllers/s3.controller'; +import { CommentService } from './services/comment.service'; +import { CommentController } from './controllers/comment.controller'; @Module({ imports: [ @@ -37,7 +39,7 @@ import { S3Controller } from './controllers/s3.controller'; }), TypeOrmModule.forFeature([User, Game, ResetPassword, Comment]), ], - controllers: [AppController, UserController, GameController, S3Controller], + controllers: [AppController, UserController, GameController, S3Controller, CommentController], providers: [ AppService, UserRepository, @@ -47,6 +49,7 @@ import { S3Controller } from './controllers/s3.controller'; ResetPasswordRepository, S3Service, CommentRepository, + CommentService, ], }) export class AppModule implements NestModule { diff --git a/ludos/backend/src/controllers/comment.controller.ts b/ludos/backend/src/controllers/comment.controller.ts new file mode 100644 index 00000000..57685f34 --- /dev/null +++ b/ludos/backend/src/controllers/comment.controller.ts @@ -0,0 +1,91 @@ +import { + Body, + Controller, + HttpCode, + Post, + Req, + UseGuards, +} from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiOkResponse, + ApiOperation, + ApiTags, + ApiUnauthorizedResponse, +} from '@nestjs/swagger'; +import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; +import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; +import { DislikeCommentDto } from '../dtos/comment/request/dislike-comment.dto'; +import { CommentService } from '../services/comment.service'; +import { AuthGuard } from '../services/guards/auth.guard'; +import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; + +@ApiTags('comment') +@Controller('comment') +export class CommentController { + constructor(private readonly commentService: CommentService) { } + + @ApiOperation({ summary: 'Comment on a post' }) + @ApiOkResponse({ + description: 'Comment', + }) + @ApiUnauthorizedResponse({ + description: 'Invalid Credentials', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(200) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Post('/write-comment') + public async writeComment( + @Req() req: AuthorizedRequest, + @Body() input: WriteCommentDto, + ) { + await this.commentService.writeComment(req.user.id, input); + } + + @ApiOperation({ summary: 'Like a comment' }) + @ApiOkResponse({ + description: 'Like Comment', + }) + @ApiUnauthorizedResponse({ + description: 'Invalid Credentials', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(200) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Post('/like-comment') + public async likeComment( + @Req() req: AuthorizedRequest, + @Body() input: LikeCommentDto, + ) { + await this.commentService.likeComment(req.user.id, input); + } + + @ApiOperation({ summary: 'Dislike a comment' }) + @ApiOkResponse({ + description: 'Dislike Comment', + }) + @ApiUnauthorizedResponse({ + description: 'Invalid Credentials', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(200) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Post('/dislike-comment') + public async dislikeComment( + @Req() req: AuthorizedRequest, + @Body() input: DislikeCommentDto, + ) { + await this.commentService.dislikeComment(req.user.id, input); + } +} diff --git a/ludos/backend/src/controllers/user.controller.ts b/ludos/backend/src/controllers/user.controller.ts index 61e63641..d6793f27 100644 --- a/ludos/backend/src/controllers/user.controller.ts +++ b/ludos/backend/src/controllers/user.controller.ts @@ -157,67 +157,4 @@ export class UserController { public async getUserInfoById(@Req() req: AuthorizedRequest) { return await this.userService.getUserInfo(req.user.id); } - - @ApiOperation({ summary: 'Comment on a post' }) - @ApiOkResponse({ - description: 'Comment', - }) - @ApiUnauthorizedResponse({ - description: 'Invalid Credentials', - }) - @ApiBadRequestResponse({ - description: 'Bad Request', - }) - @HttpCode(200) - @ApiBearerAuth() - @UseGuards(AuthGuard) - @Post('/write-comment') - public async writeComment( - @Req() req: AuthorizedRequest, - @Body() input: WriteCommentDto, - ) { - await this.userService.writeComment(req.user.id, input); - } - - @ApiOperation({ summary: 'Like a comment' }) - @ApiOkResponse({ - description: 'Like Comment', - }) - @ApiUnauthorizedResponse({ - description: 'Invalid Credentials', - }) - @ApiBadRequestResponse({ - description: 'Bad Request', - }) - @HttpCode(200) - @ApiBearerAuth() - @UseGuards(AuthGuard) - @Post('/like-comment') - public async likeComment( - @Req() req: AuthorizedRequest, - @Body() input: LikeCommentDto, - ) { - await this.userService.likeComment(req.user.id, input); - } - - @ApiOperation({ summary: 'Dislike a comment' }) - @ApiOkResponse({ - description: 'Dislike Comment', - }) - @ApiUnauthorizedResponse({ - description: 'Invalid Credentials', - }) - @ApiBadRequestResponse({ - description: 'Bad Request', - }) - @HttpCode(200) - @ApiBearerAuth() - @UseGuards(AuthGuard) - @Post('/dislike-comment') - public async dislikeComment( - @Req() req: AuthorizedRequest, - @Body() input: DislikeCommentDto, - ) { - await this.userService.dislikeComment(req.user.id, input); - } } diff --git a/ludos/backend/src/services/comment.service.ts b/ludos/backend/src/services/comment.service.ts new file mode 100644 index 00000000..5702cf7e --- /dev/null +++ b/ludos/backend/src/services/comment.service.ts @@ -0,0 +1,69 @@ +import { + HttpException, + HttpStatus, + Injectable, +} from '@nestjs/common'; +import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; +import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; +import { DislikeCommentDto } from '../dtos/comment/request/dislike-comment.dto'; +import { UserRepository } from '../repositories/user.repository'; +import { CommentRepository } from '../repositories/comment.repository'; + +@Injectable() +export class CommentService { + constructor( + private readonly userRepository: UserRepository, + private readonly commentRepository: CommentRepository, + ) {} + + public async writeComment(userId: string, writeCommentDto: WriteCommentDto) { + let user = await this.userRepository.findUserById(userId); + + if (!user) { + throw new HttpException( + 'No user found with this email', + HttpStatus.FORBIDDEN, + ); + } + + // check parent id + // post / comment / game review + // parent id should be the identifier of one of the above + + let comment = { + author: userId, //user, + text: writeCommentDto.text, + parentId: writeCommentDto.parentId, + likes: 0, + dislikes: 0, + timestamp: new Date(), + } + await this.commentRepository.createComment(comment); + } + + public async likeComment(userId: string, likeCommentDto: LikeCommentDto) { + let comment = await this.commentRepository.findCommentById(likeCommentDto.commentId); + + if (!comment) { + throw new HttpException( + 'No comment found with this id', + HttpStatus.FORBIDDEN, + ); + } + + await this.commentRepository.incrementLikeCount(likeCommentDto.commentId); + } + + public async dislikeComment(userId: string, dislikeCommentDto: DislikeCommentDto) { + let comment = await this.commentRepository.findCommentById(dislikeCommentDto.commentId); + + if (!comment) { + throw new HttpException( + 'No comment found with this id', + HttpStatus.FORBIDDEN, + ); + } + + await this.commentRepository.incrementDislikeCount(dislikeCommentDto.commentId); + } +} diff --git a/ludos/backend/src/services/user.service.ts b/ludos/backend/src/services/user.service.ts index f271b25f..4ff7846a 100644 --- a/ludos/backend/src/services/user.service.ts +++ b/ludos/backend/src/services/user.service.ts @@ -20,9 +20,6 @@ import { ChangePasswordResponseDto } from '../dtos/user/response/change-password import { LoginResponseDto } from '../dtos/user/response/login-response.dto'; import { RegisterResponseDto } from '../dtos/user/response/register-response.dto'; import { EditUserInfoDto } from '../dtos/user/request/edit-info.dto'; -import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; -import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; -import { DislikeCommentDto } from '../dtos/comment/request/dislike-comment.dto'; import { Payload } from '../interfaces/user/payload.interface'; import { ResetPasswordRepository } from '../repositories/reset-password.repository'; import { UserRepository } from '../repositories/user.repository'; @@ -34,7 +31,6 @@ export class UserService { constructor( private readonly userRepository: UserRepository, private readonly resetPasswordRepository: ResetPasswordRepository, - private readonly commentRepository: CommentRepository, private readonly jwtService: JwtService, private readonly configService: ConfigService, ) {} @@ -207,55 +203,4 @@ export class UserService { return response; } - - public async writeComment(userId: string, writeCommentDto: WriteCommentDto) { - let user = await this.userRepository.findUserById(userId); - - if (!user) { - throw new HttpException( - 'No user found with this email', - HttpStatus.FORBIDDEN, - ); - } - - // check parent id - // post / comment / game review - // parent id should be the identifier of one of the above - - let comment = { - author: userId, //user, - text: writeCommentDto.text, - parentId: writeCommentDto.parentId, - likes: 0, - dislikes: 0, - timestamp: new Date(), - } - await this.commentRepository.createComment(comment); - } - - public async likeComment(userId: string, likeCommentDto: LikeCommentDto) { - let comment = await this.commentRepository.findCommentById(likeCommentDto.commentId); - - if (!comment) { - throw new HttpException( - 'No comment found with this id', - HttpStatus.FORBIDDEN, - ); - } - - await this.commentRepository.incrementLikeCount(likeCommentDto.commentId); - } - - public async dislikeComment(userId: string, dislikeCommentDto: DislikeCommentDto) { - let comment = await this.commentRepository.findCommentById(dislikeCommentDto.commentId); - - if (!comment) { - throw new HttpException( - 'No comment found with this id', - HttpStatus.FORBIDDEN, - ); - } - - await this.commentRepository.incrementDislikeCount(dislikeCommentDto.commentId); - } } diff --git a/ludos/backend/test/user.controller.spec.ts b/ludos/backend/test/user.controller.spec.ts index 1431a52d..781e9cb6 100644 --- a/ludos/backend/test/user.controller.spec.ts +++ b/ludos/backend/test/user.controller.spec.ts @@ -14,7 +14,6 @@ import { S3Service } from '../src/services/s3.service'; import { ResetPassword } from '../src/entities/reset-password.entity'; import { ResetDto } from '../src/dtos/user/request/reset.dto'; import { ResetPasswordRepository } from '../src/repositories/reset-password.repository'; -import { CommentRepository } from '../src/repositories/comment.repository'; describe('UserController', () => { let userController: UserController; @@ -39,7 +38,6 @@ describe('UserController', () => { S3Service, UserRepository, ResetPasswordRepository, - CommentRepository, { provide: DataSource, useValue: dataSource, From ff27aa9f0e84f21d442defdded2eb7f7039f5895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Mon, 20 Nov 2023 21:46:36 +0300 Subject: [PATCH 07/22] add deleteComment and editComment endpoints --- .../src/controllers/comment.controller.ts | 44 +++++++++++++++++++ .../comment/request/delete-comment.dto.ts | 8 ++++ .../dtos/comment/request/edit-comment.dto.ts | 12 +++++ ludos/backend/src/entities/comment.entity.ts | 3 ++ .../src/repositories/comment.repository.ts | 12 +++++ ludos/backend/src/services/comment.service.ts | 28 ++++++++++++ 6 files changed, 107 insertions(+) create mode 100644 ludos/backend/src/dtos/comment/request/delete-comment.dto.ts create mode 100644 ludos/backend/src/dtos/comment/request/edit-comment.dto.ts diff --git a/ludos/backend/src/controllers/comment.controller.ts b/ludos/backend/src/controllers/comment.controller.ts index 57685f34..14c3f5fe 100644 --- a/ludos/backend/src/controllers/comment.controller.ts +++ b/ludos/backend/src/controllers/comment.controller.ts @@ -17,6 +17,8 @@ import { import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; import { DislikeCommentDto } from '../dtos/comment/request/dislike-comment.dto'; +import { DeleteCommentDto } from '../dtos/comment/request/delete-comment.dto'; +import { EditCommentDto } from '../dtos/comment/request/edit-comment.dto'; import { CommentService } from '../services/comment.service'; import { AuthGuard } from '../services/guards/auth.guard'; import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; @@ -88,4 +90,46 @@ export class CommentController { ) { await this.commentService.dislikeComment(req.user.id, input); } + + @ApiOperation({ summary: 'Delete a comment' }) + @ApiOkResponse({ + description: 'Deleted Comment', + }) + @ApiUnauthorizedResponse({ + description: 'Invalid Credentials', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(200) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Post('/delete-comment') + public async deleteComment( + @Req() req: AuthorizedRequest, + @Body() input: DeleteCommentDto, + ) { + await this.commentService.deleteComment(req.user.id, input); + } + + @ApiOperation({ summary: 'Edit a comment' }) + @ApiOkResponse({ + description: 'Edited Comment', + }) + @ApiUnauthorizedResponse({ + description: 'Invalid Credentials', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(200) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Post('/edit-comment') + public async editComment( + @Req() req: AuthorizedRequest, + @Body() input: EditCommentDto, + ) { + await this.commentService.editComment(req.user.id, input); + } } diff --git a/ludos/backend/src/dtos/comment/request/delete-comment.dto.ts b/ludos/backend/src/dtos/comment/request/delete-comment.dto.ts new file mode 100644 index 00000000..c9942957 --- /dev/null +++ b/ludos/backend/src/dtos/comment/request/delete-comment.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; +export class DeleteCommentDto { + @ApiProperty() + @IsString() + commentId: string; +} + diff --git a/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts b/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts new file mode 100644 index 00000000..df1c4c69 --- /dev/null +++ b/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; +export class EditCommentDto { + @ApiProperty() + @IsString() + commentId: string; + + @ApiProperty() + @IsString() + newText: string; +} + diff --git a/ludos/backend/src/entities/comment.entity.ts b/ludos/backend/src/entities/comment.entity.ts index 60d9c001..e9ae7ca5 100644 --- a/ludos/backend/src/entities/comment.entity.ts +++ b/ludos/backend/src/entities/comment.entity.ts @@ -27,4 +27,7 @@ export class Comment { @Column() dislikes: number; + + @Column({default: false}) + edited: boolean; } diff --git a/ludos/backend/src/repositories/comment.repository.ts b/ludos/backend/src/repositories/comment.repository.ts index 41aff425..0c9802d8 100644 --- a/ludos/backend/src/repositories/comment.repository.ts +++ b/ludos/backend/src/repositories/comment.repository.ts @@ -29,4 +29,16 @@ export class CommentRepository extends Repository { comment.dislikes += 1; await this.save(comment); } + + public async deleteComment(commentId: string) { + let comment = await this.findCommentById(commentId); + await this.delete(comment); + } + + public async editComment(commentId: string, newText: string) { + let comment = await this.findCommentById(commentId); + comment.text = newText; + comment.edited = true; + await this.save(comment); + } } diff --git a/ludos/backend/src/services/comment.service.ts b/ludos/backend/src/services/comment.service.ts index 5702cf7e..4341d52f 100644 --- a/ludos/backend/src/services/comment.service.ts +++ b/ludos/backend/src/services/comment.service.ts @@ -6,6 +6,8 @@ import { import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; import { DislikeCommentDto } from '../dtos/comment/request/dislike-comment.dto'; +import { DeleteCommentDto } from '../dtos/comment/request/delete-comment.dto'; +import { EditCommentDto } from '../dtos/comment/request/edit-comment.dto'; import { UserRepository } from '../repositories/user.repository'; import { CommentRepository } from '../repositories/comment.repository'; @@ -66,4 +68,30 @@ export class CommentService { await this.commentRepository.incrementDislikeCount(dislikeCommentDto.commentId); } + + public async deleteComment(userId: string, deleteCommentDto: DeleteCommentDto) { + let comment = await this.commentRepository.findCommentById(deleteCommentDto.commentId); + + if (!comment) { + throw new HttpException( + 'No comment found with this id', + HttpStatus.FORBIDDEN, + ); + } + + await this.commentRepository.deleteComment(deleteCommentDto.commentId); + } + + public async editComment(userId: string, editCommentDto: EditCommentDto) { + let comment = await this.commentRepository.findCommentById(editCommentDto.commentId); + + if (!comment) { + throw new HttpException( + 'No comment found with this id', + HttpStatus.FORBIDDEN, + ); + } + + await this.commentRepository.editComment(editCommentDto.commentId, editCommentDto.newText); + } } From 56cbe69554404bb13860b51b8dfba3041f0198a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Mon, 20 Nov 2023 22:00:38 +0300 Subject: [PATCH 08/22] remove unused imports --- ludos/backend/src/app.module.ts | 2 +- ludos/backend/src/controllers/user.controller.ts | 3 --- ludos/backend/src/services/user.service.ts | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/ludos/backend/src/app.module.ts b/ludos/backend/src/app.module.ts index afa3810a..670aba13 100644 --- a/ludos/backend/src/app.module.ts +++ b/ludos/backend/src/app.module.ts @@ -42,7 +42,7 @@ import { Review } from './entities/review.entity'; useClass: TypeOrmConfigService, inject: [TypeOrmConfigService], }), - TypeOrmModule.forFeature([User, Game, ResetPassword, Comment]), + TypeOrmModule.forFeature([User, Game, ResetPassword, Comment, Review]), ], controllers: [AppController, UserController, GameController, S3Controller, ReviewController, CommentController], providers: [ diff --git a/ludos/backend/src/controllers/user.controller.ts b/ludos/backend/src/controllers/user.controller.ts index d6793f27..dd75196f 100644 --- a/ludos/backend/src/controllers/user.controller.ts +++ b/ludos/backend/src/controllers/user.controller.ts @@ -27,9 +27,6 @@ import { VerifyCodeDto } from '../dtos/user/request/verify-code.dto'; import { ChangePasswordResponseDto } from '../dtos/user/response/change-password-response.dto'; import { ChangePasswordDto } from '../dtos/user/request/change-password.dto'; import { EditUserInfoDto } from '../dtos/user/request/edit-info.dto'; -import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; -import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; -import { DislikeCommentDto } from '../dtos/comment/request/dislike-comment.dto'; import { UserService } from '../services/user.service'; import { AuthGuard } from '../services/guards/auth.guard'; import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; diff --git a/ludos/backend/src/services/user.service.ts b/ludos/backend/src/services/user.service.ts index 4ff7846a..6efed627 100644 --- a/ludos/backend/src/services/user.service.ts +++ b/ludos/backend/src/services/user.service.ts @@ -23,7 +23,6 @@ import { EditUserInfoDto } from '../dtos/user/request/edit-info.dto'; import { Payload } from '../interfaces/user/payload.interface'; import { ResetPasswordRepository } from '../repositories/reset-password.repository'; import { UserRepository } from '../repositories/user.repository'; -import { CommentRepository } from '../repositories/comment.repository'; import { GetUserInfoResponseDto } from '../dtos/user/response/get-user-info-response.dto'; @Injectable() From 4dccaefe3b24c16671e906f1e09ee079e1b59358 Mon Sep 17 00:00:00 2001 From: yunusemrealtug Date: Tue, 21 Nov 2023 01:20:47 +0300 Subject: [PATCH 09/22] added profile update modal, also mock steam external link --- ludos/frontend/src/pages/ProfilePage.js | 251 ++++++++++++++++++++---- 1 file changed, 209 insertions(+), 42 deletions(-) diff --git a/ludos/frontend/src/pages/ProfilePage.js b/ludos/frontend/src/pages/ProfilePage.js index dfc902da..77f6a2aa 100644 --- a/ludos/frontend/src/pages/ProfilePage.js +++ b/ludos/frontend/src/pages/ProfilePage.js @@ -5,6 +5,8 @@ import { Typography, Box, Button, + Modal, + TextField, } from "@mui/material"; import React, { useEffect, useState } from "react"; import steamLogo from "../assets/steam.png"; @@ -15,6 +17,12 @@ import { useNavigate } from "react-router-dom"; function ProfilePage() { const navigate = useNavigate(); + const [formData, setFormData] = useState({ + fullName: "", + steamUrl: "", + aboutMe: "", + }); + const [open, setOpen] = useState(false); const [auth, setAuth] = useState(false); const [profile, setProfile] = useState(""); const [favGames, setFavGames] = useState([]); @@ -49,6 +57,68 @@ function ProfilePage() { } }, []); + const handleOpen = () => { + setOpen(true); + }; + const handleClose = () => { + setOpen(false); + }; + const submitInformation = () => { + console.log(formData); + const link = `http://${process.env.REACT_APP_API_URL}/user/edit-info`; + + axios + .put( + link, + { + fullName: formData.fullName, + steamUrl: formData.steamUrl, + aboutMe: formData.aboutMe, + }, + { + headers: { + Authorization: "Bearer " + localStorage.getItem("accessToken"), + }, + }, + ) + .then(() => {}) + .catch((error) => { + console.log(error); + }); + setOpen(false); + }; + const handleChange = (event) => { + const { id, value } = event.target; + setFormData((prevData) => ({ + ...prevData, + [id]: value, + })); + }; + + const openSteamTab = () => { + window.open( + "https://steamcommunity.com/profiles/76561199020341351/", + "_blank", + ); + }; + + const editProfile = () => {}; + + const modalStyle = { + position: "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: 400, + backgroundColor: "rgba(255,170,0, 0.8)", + border: "2px solid #000", + boxShadow: 24, + p: 4, + display: "flex", + flexDirection: "column", + borderRadius: "5%", + }; + const boxStyle = { backgroundColor: "rgba(30, 30, 30, 0.9)", borderRadius: "10px", @@ -63,6 +133,7 @@ function ProfilePage() { height: "300px", marginTop: "7px", }; + const bioBoxStyle = { backgroundColor: "rgba(255, 255, 255, 0.06)", color: "rgb(0, 150, 255)", @@ -132,7 +203,10 @@ function ProfilePage() { lg={3} style={{ marginLeft: "3%" }} > - + + - {}} - alt="Steam" - src={steamLogo} - /> - {}} - alt="Epic Games" - src={epicLogo} - /> - {}} - alt="Itch.io" - src={itchioLogo} - /> + + + - Cemre Beydirel + {profile.fullName || "Yunus Emre"} @@ -211,7 +273,8 @@ function ProfilePage() { color: "rgb(0, 150, 255)", }} > - A university student who is interested in strategy games. + {profile.aboutMe || + "A university student who is interested in strategy games."} @@ -242,7 +305,7 @@ function ProfilePage() { component="legend" style={{ fontFamily: "Trebuchet MS, sans-serif" }} > - Nnumber of Posts + Number of Posts {}} + onClick={handleOpen} > Edit My Profile + + + + Full Name + + + + Steam ID: + + + + Bio: + + + + + @@ -364,15 +520,26 @@ function ProfilePage() { {favGames.map((game, index1) => ( - + {}} - alt="Steam" + alt={game.title} src={game.coverLink} /> Date: Tue, 21 Nov 2023 01:22:08 +0300 Subject: [PATCH 10/22] #466 tokenization for mobile app --- ludos/mobile/lib/create_game.dart | 4 +- ludos/mobile/lib/create_game_second.dart | 7 +++- ludos/mobile/lib/games_page.dart | 12 +++--- ludos/mobile/lib/helper/APIService.dart | 10 ++--- ludos/mobile/lib/login_page.dart | 2 +- ludos/mobile/lib/main.dart | 47 +++++++++++++++++++++++- 6 files changed, 66 insertions(+), 16 deletions(-) diff --git a/ludos/mobile/lib/create_game.dart b/ludos/mobile/lib/create_game.dart index db368cec..5020d1e3 100644 --- a/ludos/mobile/lib/create_game.dart +++ b/ludos/mobile/lib/create_game.dart @@ -59,7 +59,8 @@ Widget getbox(String hintText, TextEditingController controller, } class CreateGamePage extends StatefulWidget { - const CreateGamePage({Key? key}) : super(key: key); + final String? token; + const CreateGamePage({Key? key, required this.token}) : super(key: key); @override State createState() => _CreateGamePageState(); @@ -385,6 +386,7 @@ class _CreateGamePageState extends State { onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => CreateGamePageSecond( + token: widget.token, title: titleController.text, coverLink: coverLinkController.text, gameBio: gameBioController.text, diff --git a/ludos/mobile/lib/create_game_second.dart b/ludos/mobile/lib/create_game_second.dart index 4583d55a..15ca5f95 100644 --- a/ludos/mobile/lib/create_game_second.dart +++ b/ludos/mobile/lib/create_game_second.dart @@ -84,6 +84,7 @@ String formatDateTime(DateTime dateTime) { } class CreateGamePageSecond extends StatefulWidget { + final String? token; final String title; final String coverLink; final String gameBio; @@ -94,6 +95,7 @@ class CreateGamePageSecond extends StatefulWidget { const CreateGamePageSecond( {Key? key, + required this.token, required this.title, required this.coverLink, required this.gameBio, @@ -286,6 +288,7 @@ class _CreateGamePageStateSecond extends State { ), onPressed: () async { http.Response token = await APIService().createGame( + widget.token, widget.title, widget.coverLink, systemRequirementsController.text, @@ -338,7 +341,7 @@ class _CreateGamePageStateSecond extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => GamesPage()), + builder: (context) => GamesPage(token: widget.token)), ); }, ), @@ -348,7 +351,7 @@ class _CreateGamePageStateSecond extends State { .then((reason) => Navigator.push( context, MaterialPageRoute( - builder: (context) => GamesPage()), + builder: (context) => GamesPage(token: widget.token)), )); } else { ScaffoldMessenger.of(context).showSnackBar( diff --git a/ludos/mobile/lib/games_page.dart b/ludos/mobile/lib/games_page.dart index 39a3aca3..28f80eeb 100644 --- a/ludos/mobile/lib/games_page.dart +++ b/ludos/mobile/lib/games_page.dart @@ -6,7 +6,8 @@ import 'helper/APIService.dart'; import 'create_game.dart'; class GamesPage extends StatefulWidget { - const GamesPage({Key? key}) : super(key: key); + final String? token; + const GamesPage({Key? key, required this.token}) : super(key: key); @override State createState() => _GamesPageState(); @@ -20,11 +21,12 @@ class _GamesPageState extends State { @override void initState() { super.initState(); - games = fetchData(); + games = fetchData(widget.token); } - Future> fetchData() async { - final response = await APIService().listGames(); + Future> fetchData(String? token) async { + //final userProvider = Provider.of(context, listen: false); + final response = await APIService().listGames(token); try { //print(json.decode(response.body)); if (response.statusCode == 200) { @@ -120,7 +122,7 @@ class _GamesPageState extends State { ), onPressed: () { Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const CreateGamePage(), + builder: (context) => CreateGamePage(token: widget.token), )); }, child: const Text( 'Create Game', diff --git a/ludos/mobile/lib/helper/APIService.dart b/ludos/mobile/lib/helper/APIService.dart index ae6a044a..88966d6c 100644 --- a/ludos/mobile/lib/helper/APIService.dart +++ b/ludos/mobile/lib/helper/APIService.dart @@ -39,7 +39,7 @@ class APIService { return response; } - Future createGame(String title, String coverLink, + Future createGame(String? authToken, String title, String coverLink, String systemRequirements, List predecessors, List successors, String gameGuide, String gameStory, List platforms, String ageRestriction, String gameBio, List tags, String releaseDate, String developer, @@ -62,7 +62,7 @@ class APIService { 'publisher': publisher, 'trivia': trivia, }); - final response = await http.post(uri, body: body, headers: {'content-type': "application/json"}); + final response = await http.post(uri, body: body, headers: {'content-type': "application/json", 'Authorization': 'Bearer $authToken'}); return response; } @@ -89,9 +89,9 @@ class APIService { return response; } - Future listGames() async { - var uri = Uri.parse("$baseURL/game?limit=10"); - final response = await http.get(uri, headers: {'content-type': "application/json"}); + Future listGames(String? authToken) async { + var uri = Uri.parse("$baseURL/game?limit=20"); + final response = await http.get(uri, headers: {'content-type': "application/json", 'Authorization': 'Bearer $authToken'}); return response; } diff --git a/ludos/mobile/lib/login_page.dart b/ludos/mobile/lib/login_page.dart index e9ce7bf9..ea6d0889 100644 --- a/ludos/mobile/lib/login_page.dart +++ b/ludos/mobile/lib/login_page.dart @@ -93,7 +93,7 @@ class LoginPageState extends State { onPressed: () async { (String?, int) token = await APIService() .login(emailController.text, passwordController.text); - print(token); + //print(token); if (token.$2 == 200) { Provider.of(context, listen: false) .setLoggedIn(true, emailController.text, token.$1); diff --git a/ludos/mobile/lib/main.dart b/ludos/mobile/lib/main.dart index 86c03f39..9bcb20cb 100644 --- a/ludos/mobile/lib/main.dart +++ b/ludos/mobile/lib/main.dart @@ -432,10 +432,53 @@ class Home extends StatelessWidget { builder: (context) => const CreateGamePage(key: null,), )); */ + if(userProvider.isLoggedIn){ Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const GamesPage(), + builder: (context) => GamesPage(token: userProvider.token), )); - + } + else{ + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: const Row( + children: [ + Icon( + Icons.check_circle_outline, + color: MyColors.blue, + ), + SizedBox(width: 8), + Expanded( + child: Text( + 'Please log in to view games', + style: TextStyle( + color: MyColors.blue, + fontSize: 16, + ), + ), + ), + ], + ), + backgroundColor: MyColors.blue2, + duration: const Duration(seconds: 5), + action: SnackBarAction( + label: 'Log In', + textColor: MyColors.blue, + onPressed: () { + ScaffoldMessenger.of(context) + .hideCurrentSnackBar(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LoginPage()), + ); + }, + ), + ), + ) + .closed + .then((reason) => {}); + } }, icon: const Icon(Icons.games)), IconButton( From c00fc0b99cc7e94a2bec8f013eb33e2e8bf5bebe Mon Sep 17 00:00:00 2001 From: yunusemrealtug Date: Tue, 21 Nov 2023 03:11:17 +0300 Subject: [PATCH 11/22] profile page implementation --- ludos/frontend/src/pages/ProfilePage.js | 141 +++++++++++++++++++++--- 1 file changed, 126 insertions(+), 15 deletions(-) diff --git a/ludos/frontend/src/pages/ProfilePage.js b/ludos/frontend/src/pages/ProfilePage.js index 77f6a2aa..5ee23579 100644 --- a/ludos/frontend/src/pages/ProfilePage.js +++ b/ludos/frontend/src/pages/ProfilePage.js @@ -7,6 +7,8 @@ import { Button, Modal, TextField, + Input, + Snackbar, } from "@mui/material"; import React, { useEffect, useState } from "react"; import steamLogo from "../assets/steam.png"; @@ -14,6 +16,7 @@ import epicLogo from "../assets/epic.png"; import itchioLogo from "../assets/itchio.png"; import axios from "axios"; import { useNavigate } from "react-router-dom"; +import NotificationsIcon from "@mui/icons-material/Notifications"; function ProfilePage() { const navigate = useNavigate(); @@ -26,6 +29,73 @@ function ProfilePage() { const [auth, setAuth] = useState(false); const [profile, setProfile] = useState(""); const [favGames, setFavGames] = useState([]); + const [avatarImage, setAvatarImage] = useState(null); + const [snackbarMessage, setSnackbarMessage] = useState(""); + const [snackbar, setSnackbar] = useState(false); + + const handleImageChange = (event) => { + const file = event.target.files[0]; + console.log(file); + + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setAvatarImage(reader.result); + }; + reader.readAsDataURL(file); + console.log(reader); + try { + const formData = new FormData(); + formData.append("file", file); + console.log(formData); + const link = `http://${process.env.REACT_APP_API_URL}/external/upload`; + axios + .post(link, formData, { + headers: { + Authorization: "Bearer " + localStorage.getItem("accessToken"), + "Content-Type": "multipart/form-data", + }, + }) + .then((response) => { + const link2 = `http://${process.env.REACT_APP_API_URL}/user/edit-info`; + axios + .put( + link2, + { + avatar: response.data.url, + }, + { + headers: { + Authorization: + "Bearer " + localStorage.getItem("accessToken"), + }, + }, + ) + .then(() => { + setSnackbarMessage("Image uploaded successfully!"); + setSnackbar(true); + }) + .catch((error) => { + console.log(error); + setSnackbarMessage("Image could not be uploaded!"); + setSnackbar(true); + }); + + // Now you can update the profile or perform any other action with the response data + }) + .catch((error) => { + console.error("Error uploading file:", error); + setSnackbarMessage("Image could not be uploaded!"); + setSnackbar(true); + }); + + // Now you can update the profile or perform any other action with the response data + } catch (error) { + console.error("Error uploading file:", error); + } + } + }; + useEffect(() => { setAuth(false); if (localStorage.getItem("accessToken")) { @@ -63,6 +133,10 @@ function ProfilePage() { const handleClose = () => { setOpen(false); }; + const closeSnackbar = () => { + setSnackbarMessage(""); + setSnackbar(false); + }; const submitInformation = () => { console.log(formData); const link = `http://${process.env.REACT_APP_API_URL}/user/edit-info`; @@ -81,9 +155,14 @@ function ProfilePage() { }, }, ) - .then(() => {}) + .then(() => { + setSnackbarMessage("Information updated successfully!"); + setSnackbar(true); + }) .catch((error) => { console.log(error); + setSnackbarMessage("Information could not be updated!"); + setSnackbar(true); }); setOpen(false); }; @@ -102,8 +181,6 @@ function ProfilePage() { ); }; - const editProfile = () => {}; - const modalStyle = { position: "absolute", top: "50%", @@ -130,7 +207,7 @@ function ProfilePage() { backgroundColor: "rgba(30, 30, 30, 0.9)", borderRadius: "10px", paddingTop: "15px", - height: "300px", + height: "320px", marginTop: "7px", }; @@ -194,6 +271,12 @@ function ProfilePage() { + - - - + + - @{profile.username} - + + @{profile.username} + + + + + + + +
+ + + Trend Topics + + {/* Render your forum topics below */} + {/* Replace this section with your actual forum topics */} +
+ {trendTopics.map((topic, index) => ( + + ))} +
+
+ + + Latest Topics + + {/* Render your forum topics below */} + {/* Replace this section with your actual forum topics */} +
+
+ {latestTopics.map((topic, index) => ( + + ))} +
+
+
+
+ + ); +}; + +export default ForumsPage; From fbc43755d40856c4266d68e8be0975b2a4f987d2 Mon Sep 17 00:00:00 2001 From: senaal Date: Tue, 21 Nov 2023 04:25:50 +0300 Subject: [PATCH 13/22] feature: game page converted to abstract to fill it according to selected game. Some styling will be fixed. --- ludos/mobile/lib/game_page.dart | 124 +++++++++++------- ludos/mobile/lib/games_page.dart | 1 + ludos/mobile/lib/helper/APIService.dart | 14 ++ ludos/mobile/lib/main.dart | 14 -- .../lib/reusable_widgets/game_summary.dart | 15 +++ 5 files changed, 107 insertions(+), 61 deletions(-) diff --git a/ludos/mobile/lib/game_page.dart b/ludos/mobile/lib/game_page.dart index 9304bdb9..afd0d8b8 100644 --- a/ludos/mobile/lib/game_page.dart +++ b/ludos/mobile/lib/game_page.dart @@ -1,72 +1,100 @@ import 'package:flutter/material.dart'; import 'helper/colors.dart'; import 'package:flutter_rating_bar/flutter_rating_bar.dart'; - +import 'helper/APIService.dart'; class GamePage extends StatefulWidget { - const GamePage({super.key}); + final String id; + const GamePage({required this.id, Key? key}) : super(key: key); @override State createState() => _GamePageState(); } class _GamePageState extends State { + final APIService apiService = APIService(); + Map gameData = {}; @override - Widget build(BuildContext context) { + void initState() { + super.initState(); + loadGameData(); + } + Future loadGameData() async { + try { + gameData = await apiService.getGame(widget.id); + + setState(() {}); + } catch (e) { + print('Error loading game data: $e'); + } + } + @override + Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFF101c2c), appBar: AppBar( backgroundColor: const Color(0xFFf89c34), - title: const Text('God of War (2018)'), + title: Text('${gameData['title']}'), ), body: SingleChildScrollView( + padding: EdgeInsets.all(16.0), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - SizedBox( - width: 200.0, - height: 200.0, - child: Image.asset('assets/images/header_gow.jpg'), + if (gameData['coverLink'] != null) + SizedBox( + width: 200.0, + height: 200.0, + child: Image.network(gameData['coverLink']), + ), + const SizedBox(height: 10), + if (gameData['releaseDate'] != null) + Text('Release Date: ${gameData['releaseDate']}',style: const TextStyle( + color: MyColors.orange, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 10), + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (gameData['tags'] != null) + for (var i = 0; i < gameData['tags'].length; i++) + TextButton( + style: TextButton.styleFrom( + foregroundColor: MyColors.lightBlue), + onPressed: () {}, + child: Text(gameData['tags'][i].toString()), + ), + ], + ), ), const SizedBox(height: 10), Container( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - TextButton( - style: TextButton.styleFrom( - foregroundColor: MyColors.lightBlue), - onPressed: () {}, - child: Text('adventure'), - ), - TextButton( - style: TextButton.styleFrom( - foregroundColor: MyColors.lightBlue), - onPressed: () {}, - child: Text('singleplayer'), - ), - TextButton( - style: TextButton.styleFrom( - foregroundColor: MyColors.lightBlue), - onPressed: () {}, - child: Text('action'), - ), - TextButton( - style: TextButton.styleFrom( - foregroundColor: MyColors.lightBlue), - onPressed: () {}, - child: Text('mythology'), - ), + if (gameData['tags'] != null) + for (var i = 0; i < gameData['platforms'].length; i++) + TextButton( + style: TextButton.styleFrom( + foregroundColor: MyColors.lightBlue), + onPressed: () {}, + child: Text(gameData['platforms'][i].toString()), + ), ], ), ), Row( children: [ + if (gameData['userRating'] != null) RatingBar.builder( - initialRating: 3, - minRating: 1, + initialRating: gameData['userRating'].toDouble(), + minRating: 0, direction: Axis.horizontal, allowHalfRating: true, itemCount: 5, @@ -80,7 +108,8 @@ class _GamePageState extends State { }, ), SizedBox(width: 8), // Add some spacing between RatingBar and Text - const Text(' Rating: 4.3/5',style: TextStyle( + if (gameData['averageRating'] != null) + Text('${gameData['averageRating']}/5'.padLeft(22),style: const TextStyle( color: MyColors.orange, fontWeight: FontWeight.bold, fontSize: 16, @@ -90,23 +119,24 @@ class _GamePageState extends State { ), const SizedBox(height: 10), - - const Text( - ' His vengeance against the Gods of Olympus years behind him, Kratos now lives as a man in the realm of Norse Gods and monsters. It is in this harsh, unforgiving world that he must fight to survive… and teach his son to do the same.', - style: TextStyle( - color: MyColors.white, + if(gameData['gameStory'] != null) + Text( + gameData['gameStory'].toString(), + style: const TextStyle( + color: MyColors.white, + fontWeight: FontWeight.bold, + fontSize: 16, + ), + ), + const SizedBox(height: 20), + if(gameData['averageUserCompilationDuration'] != null) + Text('Average User Compilation Time: ${gameData['averageUserCompilationDuration']}',style: const TextStyle( + color: MyColors.orange, fontWeight: FontWeight.bold, fontSize: 16, ), ), const SizedBox(height: 20), - const Text('Estimating Time: 120 hours',style: TextStyle( - color: MyColors.orange, - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - const SizedBox(height: 20), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/ludos/mobile/lib/games_page.dart b/ludos/mobile/lib/games_page.dart index 39a3aca3..385c0c5c 100644 --- a/ludos/mobile/lib/games_page.dart +++ b/ludos/mobile/lib/games_page.dart @@ -42,6 +42,7 @@ class _GamesPageState extends State { textColor: MyColors.white, backgroundColor: MyColors.red, fontSize: 20, + id: item['id'], )).toList(); } else { print("Error: ${response.statusCode} - ${response.body}"); diff --git a/ludos/mobile/lib/helper/APIService.dart b/ludos/mobile/lib/helper/APIService.dart index ae6a044a..729d42fc 100644 --- a/ludos/mobile/lib/helper/APIService.dart +++ b/ludos/mobile/lib/helper/APIService.dart @@ -4,6 +4,7 @@ import 'dart:convert'; class APIService { var baseURL = "http://3.125.225.39:8080"; + String? token = ""; Future<(String?, int)> login(String username, String password) async { var uri = Uri.parse("$baseURL/user/login"); @@ -15,6 +16,10 @@ class APIService { Map responseBody = jsonDecode(response.body); String? authToken = responseBody['accessToken']; (String?, int) res = (authToken,response.statusCode); + token = authToken; + print("token"); + print(token); + print("token"); return res; } @@ -96,4 +101,13 @@ class APIService { return response; } + Future> getGame(String id) async { + var uri = Uri.parse("$baseURL/game/$id"); + final response = await http.get(uri, headers: {'content-type': "application/json",'Authorization': "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJpZCI6ImE2NWQ0MTZhLTQ0ZmMtNGFjZC1hMDBhLWJjYTFmZWZlMDM0OSIsImVtYWlsIjoic2VuYUBnbWFpbC5jb20iLCJ1c2VybmFtZSI6IlNFIiwiaWF0IjoxNzAwNTE3NDk4LCJleHAiOjE3MDA2MDM4OTh9SrwVz5S9WUBfQgsv6jfXkzBWV84w5Vu4QjdKaOZ0"}); + if (response.statusCode == 200) { + return json.decode(response.body) as Map; + } else { + throw Exception('Failed to load game data'); + } + } } \ No newline at end of file diff --git a/ludos/mobile/lib/main.dart b/ludos/mobile/lib/main.dart index 86c03f39..09a8f324 100644 --- a/ludos/mobile/lib/main.dart +++ b/ludos/mobile/lib/main.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; import 'package:ludos_mobile_app/change_password.dart'; -import 'package:ludos_mobile_app/game_page.dart'; import 'login_page.dart'; import 'games_page.dart'; import 'userProvider.dart'; import 'package:provider/provider.dart'; import 'helper/colors.dart'; -import 'create_game.dart'; void main() => runApp(ChangeNotifierProvider( create: (context) => UserProvider(), @@ -52,18 +50,6 @@ class Home extends StatelessWidget { )); }, ), - if (userProvider.isLoggedIn) - ListTile( - title: const Text( - 'God of War', - style: TextStyle(color: MyColors.white), - ), - onTap: () { - Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const GamePage(), - )); - }, - ), if (userProvider.isLoggedIn) ListTile( title: const Text( diff --git a/ludos/mobile/lib/reusable_widgets/game_summary.dart b/ludos/mobile/lib/reusable_widgets/game_summary.dart index 4db07bf1..d8d62b95 100644 --- a/ludos/mobile/lib/reusable_widgets/game_summary.dart +++ b/ludos/mobile/lib/reusable_widgets/game_summary.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import '../game_page.dart'; import '/helper/colors.dart'; import '/reusable_widgets/single_rating_icon.dart'; @@ -12,6 +13,7 @@ class GameSummary extends StatefulWidget { final Color textColor; final Color backgroundColor; final double fontSize; + final String id; const GameSummary({ Key? key, @@ -24,6 +26,7 @@ class GameSummary extends StatefulWidget { required this.textColor, required this.backgroundColor, required this.fontSize, + required this.id, }) : super(key: key); @override @@ -37,6 +40,7 @@ class GameSummary extends StatefulWidget { textColor: textColor, backgroundColor: backgroundColor, fontSize: fontSize, + id: id, ); } @@ -50,6 +54,7 @@ class _GameSummaryState extends State { final Color textColor; final Color backgroundColor; final double fontSize; + final String id; _GameSummaryState({ required this.title, @@ -61,6 +66,7 @@ class _GameSummaryState extends State { required this.textColor, required this.backgroundColor, required this.fontSize, + required this.id, }); @override @@ -74,6 +80,15 @@ class _GameSummaryState extends State { borderRadius: BorderRadius.circular(15.0), )), onPressed: () { + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GamePage(id: id), + ), + ); + + // Handle button press for the specific game // Navigate to the game's profile page }, From 53de9936c5a3b4ea15ec7be7da9ed398b857fabe Mon Sep 17 00:00:00 2001 From: frknlke Date: Tue, 21 Nov 2023 01:22:08 +0300 Subject: [PATCH 14/22] #466 tokenization for mobile app --- ludos/mobile/lib/create_game.dart | 4 +- ludos/mobile/lib/create_game_second.dart | 7 +++- ludos/mobile/lib/games_page.dart | 12 +++--- ludos/mobile/lib/helper/APIService.dart | 10 ++--- ludos/mobile/lib/login_page.dart | 2 +- ludos/mobile/lib/main.dart | 47 +++++++++++++++++++++++- 6 files changed, 66 insertions(+), 16 deletions(-) diff --git a/ludos/mobile/lib/create_game.dart b/ludos/mobile/lib/create_game.dart index db368cec..5020d1e3 100644 --- a/ludos/mobile/lib/create_game.dart +++ b/ludos/mobile/lib/create_game.dart @@ -59,7 +59,8 @@ Widget getbox(String hintText, TextEditingController controller, } class CreateGamePage extends StatefulWidget { - const CreateGamePage({Key? key}) : super(key: key); + final String? token; + const CreateGamePage({Key? key, required this.token}) : super(key: key); @override State createState() => _CreateGamePageState(); @@ -385,6 +386,7 @@ class _CreateGamePageState extends State { onPressed: () { Navigator.of(context).push(MaterialPageRoute( builder: (context) => CreateGamePageSecond( + token: widget.token, title: titleController.text, coverLink: coverLinkController.text, gameBio: gameBioController.text, diff --git a/ludos/mobile/lib/create_game_second.dart b/ludos/mobile/lib/create_game_second.dart index 4583d55a..15ca5f95 100644 --- a/ludos/mobile/lib/create_game_second.dart +++ b/ludos/mobile/lib/create_game_second.dart @@ -84,6 +84,7 @@ String formatDateTime(DateTime dateTime) { } class CreateGamePageSecond extends StatefulWidget { + final String? token; final String title; final String coverLink; final String gameBio; @@ -94,6 +95,7 @@ class CreateGamePageSecond extends StatefulWidget { const CreateGamePageSecond( {Key? key, + required this.token, required this.title, required this.coverLink, required this.gameBio, @@ -286,6 +288,7 @@ class _CreateGamePageStateSecond extends State { ), onPressed: () async { http.Response token = await APIService().createGame( + widget.token, widget.title, widget.coverLink, systemRequirementsController.text, @@ -338,7 +341,7 @@ class _CreateGamePageStateSecond extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => GamesPage()), + builder: (context) => GamesPage(token: widget.token)), ); }, ), @@ -348,7 +351,7 @@ class _CreateGamePageStateSecond extends State { .then((reason) => Navigator.push( context, MaterialPageRoute( - builder: (context) => GamesPage()), + builder: (context) => GamesPage(token: widget.token)), )); } else { ScaffoldMessenger.of(context).showSnackBar( diff --git a/ludos/mobile/lib/games_page.dart b/ludos/mobile/lib/games_page.dart index 385c0c5c..4365db55 100644 --- a/ludos/mobile/lib/games_page.dart +++ b/ludos/mobile/lib/games_page.dart @@ -6,7 +6,8 @@ import 'helper/APIService.dart'; import 'create_game.dart'; class GamesPage extends StatefulWidget { - const GamesPage({Key? key}) : super(key: key); + final String? token; + const GamesPage({Key? key, required this.token}) : super(key: key); @override State createState() => _GamesPageState(); @@ -20,11 +21,12 @@ class _GamesPageState extends State { @override void initState() { super.initState(); - games = fetchData(); + games = fetchData(widget.token); } - Future> fetchData() async { - final response = await APIService().listGames(); + Future> fetchData(String? token) async { + //final userProvider = Provider.of(context, listen: false); + final response = await APIService().listGames(token); try { //print(json.decode(response.body)); if (response.statusCode == 200) { @@ -121,7 +123,7 @@ class _GamesPageState extends State { ), onPressed: () { Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const CreateGamePage(), + builder: (context) => CreateGamePage(token: widget.token), )); }, child: const Text( 'Create Game', diff --git a/ludos/mobile/lib/helper/APIService.dart b/ludos/mobile/lib/helper/APIService.dart index 729d42fc..268b0a62 100644 --- a/ludos/mobile/lib/helper/APIService.dart +++ b/ludos/mobile/lib/helper/APIService.dart @@ -44,7 +44,7 @@ class APIService { return response; } - Future createGame(String title, String coverLink, + Future createGame(String? authToken, String title, String coverLink, String systemRequirements, List predecessors, List successors, String gameGuide, String gameStory, List platforms, String ageRestriction, String gameBio, List tags, String releaseDate, String developer, @@ -67,7 +67,7 @@ class APIService { 'publisher': publisher, 'trivia': trivia, }); - final response = await http.post(uri, body: body, headers: {'content-type': "application/json"}); + final response = await http.post(uri, body: body, headers: {'content-type': "application/json", 'Authorization': 'Bearer $authToken'}); return response; } @@ -94,9 +94,9 @@ class APIService { return response; } - Future listGames() async { - var uri = Uri.parse("$baseURL/game?limit=10"); - final response = await http.get(uri, headers: {'content-type': "application/json"}); + Future listGames(String? authToken) async { + var uri = Uri.parse("$baseURL/game?limit=20"); + final response = await http.get(uri, headers: {'content-type': "application/json", 'Authorization': 'Bearer $authToken'}); return response; } diff --git a/ludos/mobile/lib/login_page.dart b/ludos/mobile/lib/login_page.dart index e9ce7bf9..ea6d0889 100644 --- a/ludos/mobile/lib/login_page.dart +++ b/ludos/mobile/lib/login_page.dart @@ -93,7 +93,7 @@ class LoginPageState extends State { onPressed: () async { (String?, int) token = await APIService() .login(emailController.text, passwordController.text); - print(token); + //print(token); if (token.$2 == 200) { Provider.of(context, listen: false) .setLoggedIn(true, emailController.text, token.$1); diff --git a/ludos/mobile/lib/main.dart b/ludos/mobile/lib/main.dart index 09a8f324..3110a353 100644 --- a/ludos/mobile/lib/main.dart +++ b/ludos/mobile/lib/main.dart @@ -418,10 +418,53 @@ class Home extends StatelessWidget { builder: (context) => const CreateGamePage(key: null,), )); */ + if(userProvider.isLoggedIn){ Navigator.of(context).push(MaterialPageRoute( - builder: (context) => const GamesPage(), + builder: (context) => GamesPage(token: userProvider.token), )); - + } + else{ + ScaffoldMessenger.of(context) + .showSnackBar( + SnackBar( + content: const Row( + children: [ + Icon( + Icons.check_circle_outline, + color: MyColors.blue, + ), + SizedBox(width: 8), + Expanded( + child: Text( + 'Please log in to view games', + style: TextStyle( + color: MyColors.blue, + fontSize: 16, + ), + ), + ), + ], + ), + backgroundColor: MyColors.blue2, + duration: const Duration(seconds: 5), + action: SnackBarAction( + label: 'Log In', + textColor: MyColors.blue, + onPressed: () { + ScaffoldMessenger.of(context) + .hideCurrentSnackBar(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LoginPage()), + ); + }, + ), + ), + ) + .closed + .then((reason) => {}); + } }, icon: const Icon(Icons.games)), IconButton( From 9629b6bc85bb8e7ec3256ddea6767578a4e055f4 Mon Sep 17 00:00:00 2001 From: senaal Date: Tue, 21 Nov 2023 04:49:20 +0300 Subject: [PATCH 15/22] tokenization for game page --- ludos/mobile/lib/game_page.dart | 6 +++--- ludos/mobile/lib/games_page.dart | 1 + ludos/mobile/lib/helper/APIService.dart | 4 ++-- ludos/mobile/lib/reusable_widgets/game_summary.dart | 7 ++++++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ludos/mobile/lib/game_page.dart b/ludos/mobile/lib/game_page.dart index afd0d8b8..45bce254 100644 --- a/ludos/mobile/lib/game_page.dart +++ b/ludos/mobile/lib/game_page.dart @@ -4,9 +4,9 @@ import 'package:flutter_rating_bar/flutter_rating_bar.dart'; import 'helper/APIService.dart'; class GamePage extends StatefulWidget { - + final String? token; final String id; - const GamePage({required this.id, Key? key}) : super(key: key); + const GamePage({required this.id, required this.token, Key? key}) : super(key: key); @override State createState() => _GamePageState(); } @@ -21,7 +21,7 @@ class _GamePageState extends State { Future loadGameData() async { try { - gameData = await apiService.getGame(widget.id); + gameData = await apiService.getGame(widget.id, widget.token); setState(() {}); } catch (e) { diff --git a/ludos/mobile/lib/games_page.dart b/ludos/mobile/lib/games_page.dart index 4365db55..f3b908b4 100644 --- a/ludos/mobile/lib/games_page.dart +++ b/ludos/mobile/lib/games_page.dart @@ -45,6 +45,7 @@ class _GamesPageState extends State { backgroundColor: MyColors.red, fontSize: 20, id: item['id'], + token: widget.token )).toList(); } else { print("Error: ${response.statusCode} - ${response.body}"); diff --git a/ludos/mobile/lib/helper/APIService.dart b/ludos/mobile/lib/helper/APIService.dart index 268b0a62..a798a38f 100644 --- a/ludos/mobile/lib/helper/APIService.dart +++ b/ludos/mobile/lib/helper/APIService.dart @@ -101,9 +101,9 @@ class APIService { return response; } - Future> getGame(String id) async { + Future> getGame(String id,String? authToken) async { var uri = Uri.parse("$baseURL/game/$id"); - final response = await http.get(uri, headers: {'content-type': "application/json",'Authorization': "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9eyJpZCI6ImE2NWQ0MTZhLTQ0ZmMtNGFjZC1hMDBhLWJjYTFmZWZlMDM0OSIsImVtYWlsIjoic2VuYUBnbWFpbC5jb20iLCJ1c2VybmFtZSI6IlNFIiwiaWF0IjoxNzAwNTE3NDk4LCJleHAiOjE3MDA2MDM4OTh9SrwVz5S9WUBfQgsv6jfXkzBWV84w5Vu4QjdKaOZ0"}); + final response = await http.get(uri, headers: {'content-type': "application/json",'Authorization': 'Bearer $authToken'}); if (response.statusCode == 200) { return json.decode(response.body) as Map; } else { diff --git a/ludos/mobile/lib/reusable_widgets/game_summary.dart b/ludos/mobile/lib/reusable_widgets/game_summary.dart index d8d62b95..4b8b209d 100644 --- a/ludos/mobile/lib/reusable_widgets/game_summary.dart +++ b/ludos/mobile/lib/reusable_widgets/game_summary.dart @@ -14,6 +14,7 @@ class GameSummary extends StatefulWidget { final Color backgroundColor; final double fontSize; final String id; + final String? token; const GameSummary({ Key? key, @@ -27,6 +28,7 @@ class GameSummary extends StatefulWidget { required this.backgroundColor, required this.fontSize, required this.id, + required this.token, }) : super(key: key); @override @@ -41,6 +43,7 @@ class GameSummary extends StatefulWidget { backgroundColor: backgroundColor, fontSize: fontSize, id: id, + token: token ); } @@ -55,6 +58,7 @@ class _GameSummaryState extends State { final Color backgroundColor; final double fontSize; final String id; + final String? token; _GameSummaryState({ required this.title, @@ -67,6 +71,7 @@ class _GameSummaryState extends State { required this.backgroundColor, required this.fontSize, required this.id, + required this.token, }); @override @@ -84,7 +89,7 @@ class _GameSummaryState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => GamePage(id: id), + builder: (context) => GamePage(id: id, token: token), ), ); From 53cbde2f48a78bcb022505adba6a864129d44e97 Mon Sep 17 00:00:00 2001 From: omersafakbebek Date: Tue, 21 Nov 2023 08:06:41 +0300 Subject: [PATCH 16/22] fixed a bug in edit user endpoint (backend). The user password was being hashed on every request --- ludos/backend/src/services/user.service.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ludos/backend/src/services/user.service.ts b/ludos/backend/src/services/user.service.ts index 6efed627..f1a917c6 100644 --- a/ludos/backend/src/services/user.service.ts +++ b/ludos/backend/src/services/user.service.ts @@ -184,8 +184,9 @@ export class UserService { } public async editInfo(userId: string, editInfoDto: EditUserInfoDto) { - let user = await this.userRepository.findUserById(userId); - let updated = Object.assign(user, editInfoDto); + const user = await this.userRepository.findUserById(userId); + const updated = Object.assign(user, editInfoDto); + delete updated.password; await this.userRepository.save(updated); } From 61659d5be6203947a0aa04bdbd682ce41967e64f Mon Sep 17 00:00:00 2001 From: tacettinburakeren Date: Tue, 21 Nov 2023 13:34:08 +0300 Subject: [PATCH 17/22] rating endpoint --- ludos/backend/src/app.module.ts | 13 +- .../src/controllers/rating.controller.ts | 90 ++++++++++++++ .../src/dtos/rating/request/create.dto.ts | 9 ++ .../src/dtos/rating/request/delete.dto.ts | 6 + .../src/dtos/rating/request/edit.dto.ts | 9 ++ .../src/dtos/rating/response/create.dto.ts | 18 +++ .../src/dtos/rating/response/delete.dto.ts | 11 ++ .../src/dtos/rating/response/edit.dto.ts | 12 ++ .../src/dtos/rating/response/list.response.ts | 13 ++ .../response/get-user-info-response.dto.ts | 17 +++ ludos/backend/src/entities/game.entity.ts | 4 + ludos/backend/src/entities/rating.entity.ts | 33 +++++ ludos/backend/src/entities/user.entity.ts | 7 +- .../src/repositories/rating.repository.ts | 30 +++++ .../services/config/typeorm-config.service.ts | 3 +- ludos/backend/src/services/rating.service.ts | 113 ++++++++++++++++++ ludos/backend/src/services/user.service.ts | 11 +- 17 files changed, 392 insertions(+), 7 deletions(-) create mode 100644 ludos/backend/src/controllers/rating.controller.ts create mode 100644 ludos/backend/src/dtos/rating/request/create.dto.ts create mode 100644 ludos/backend/src/dtos/rating/request/delete.dto.ts create mode 100644 ludos/backend/src/dtos/rating/request/edit.dto.ts create mode 100644 ludos/backend/src/dtos/rating/response/create.dto.ts create mode 100644 ludos/backend/src/dtos/rating/response/delete.dto.ts create mode 100644 ludos/backend/src/dtos/rating/response/edit.dto.ts create mode 100644 ludos/backend/src/dtos/rating/response/list.response.ts create mode 100644 ludos/backend/src/entities/rating.entity.ts create mode 100644 ludos/backend/src/repositories/rating.repository.ts create mode 100644 ludos/backend/src/services/rating.service.ts diff --git a/ludos/backend/src/app.module.ts b/ludos/backend/src/app.module.ts index 528a15be..b45b12b2 100644 --- a/ludos/backend/src/app.module.ts +++ b/ludos/backend/src/app.module.ts @@ -23,6 +23,11 @@ import { ReviewRepository } from './repositories/review.repository'; import { ReviewService } from './services/review.service'; import { ReviewController } from './controllers/review.controller'; import { Review } from './entities/review.entity'; +import { RatingController } from './controllers/rating.controller'; +import { RatingRepository } from './repositories/rating.repository'; +import { RatingService } from './services/rating.service'; +import { Rating } from './entities/rating.entity'; + @Module({ imports: [ @@ -37,9 +42,9 @@ import { Review } from './entities/review.entity'; useClass: TypeOrmConfigService, inject: [TypeOrmConfigService], }), - TypeOrmModule.forFeature([User, Game, Review, ResetPassword]), + TypeOrmModule.forFeature([User, Game,Review, ResetPassword,Rating]), ], - controllers: [AppController, UserController, GameController, S3Controller, ReviewController], + controllers: [AppController, UserController, GameController, S3Controller, ReviewController,RatingController], providers: [ AppService, UserRepository, @@ -49,7 +54,9 @@ import { Review } from './entities/review.entity'; ResetPasswordRepository, S3Service, ReviewRepository, - ReviewService + ReviewService, + RatingRepository, + RatingService ], }) export class AppModule implements NestModule { diff --git a/ludos/backend/src/controllers/rating.controller.ts b/ludos/backend/src/controllers/rating.controller.ts new file mode 100644 index 00000000..26a296a1 --- /dev/null +++ b/ludos/backend/src/controllers/rating.controller.ts @@ -0,0 +1,90 @@ +import { + Body, + Controller, + HttpCode, + Delete, + Param, + DefaultValuePipe, + ParseIntPipe, + Post, + Get, + Query, + Put, + Req, + UseGuards, +} from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiCreatedResponse, + ApiNotFoundResponse, + ApiOkResponse, + ApiQuery, + ApiTags, +} from '@nestjs/swagger'; +import { RatingCreateDto } from '../dtos/rating/request/create.dto'; +import { Rating } from '../entities/rating.entity'; +import { AuthGuard } from '../services/guards/auth.guard'; +import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; +import { RatingService } from '../services/rating.service'; +import { RatingEditDto } from '../dtos/rating/request/edit.dto'; + +@UseGuards(AuthGuard) +@ApiTags('rating') +@Controller('rating') +export class RatingController { + constructor(private readonly ratingService: RatingService) { } + + @ApiBearerAuth() + @ApiCreatedResponse({ + description: 'Rating created successfully.', + type: Rating, + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(201) + @Post(':gameId') + public async createRating( + @Req() req: AuthorizedRequest, + @Param('gameId') gameId: string, + @Body() ratingCreateDto: RatingCreateDto, + ) { + const createdRating = await this.ratingService.createRating( + req.user.id, + gameId, + ratingCreateDto, + ); + return createdRating; + } + + @UseGuards(AuthGuard) + @ApiBearerAuth() + @ApiNotFoundResponse({ description: 'Rating is not found!' }) + @HttpCode(204) + @Delete(':ratingId') + public async deleteRating( + @Req() req: AuthorizedRequest, + @Param('ratingId') ratingId: string, + ) { + const deletedRatingResponse = await this.ratingService.deleteRating(req.user.id, ratingId); + return deletedRatingResponse + } + + @UseGuards(AuthGuard) + @ApiBearerAuth() + @HttpCode(200) + @Put(':ratingId') + public async editRating( + @Req() req: AuthorizedRequest, + @Param('ratingId') ratingId: string, + @Body() ratingEditDto: RatingEditDto, + ) { + const editedRating = await this.ratingService.editRating( + req.user.id, + ratingId, + ratingEditDto, + ); + return editedRating; + } +} diff --git a/ludos/backend/src/dtos/rating/request/create.dto.ts b/ludos/backend/src/dtos/rating/request/create.dto.ts new file mode 100644 index 00000000..6d5709a5 --- /dev/null +++ b/ludos/backend/src/dtos/rating/request/create.dto.ts @@ -0,0 +1,9 @@ +import { IsNotEmpty, IsNumber, Min, Max } from 'class-validator'; + +export class RatingCreateDto { + @IsNotEmpty() + @IsNumber() + @Min(0.0) + @Max(5.0) + rating: number; +} diff --git a/ludos/backend/src/dtos/rating/request/delete.dto.ts b/ludos/backend/src/dtos/rating/request/delete.dto.ts new file mode 100644 index 00000000..b6790fbd --- /dev/null +++ b/ludos/backend/src/dtos/rating/request/delete.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class RatingDeleteDto { + @ApiProperty() + id: string; +} diff --git a/ludos/backend/src/dtos/rating/request/edit.dto.ts b/ludos/backend/src/dtos/rating/request/edit.dto.ts new file mode 100644 index 00000000..306c8a27 --- /dev/null +++ b/ludos/backend/src/dtos/rating/request/edit.dto.ts @@ -0,0 +1,9 @@ +import { IsNotEmpty, IsNumber, Min, Max } from 'class-validator'; + +export class RatingEditDto { + @IsNotEmpty() + @IsNumber() + @Min(0.0) + @Max(5.0) + rating: number; +} \ No newline at end of file diff --git a/ludos/backend/src/dtos/rating/response/create.dto.ts b/ludos/backend/src/dtos/rating/response/create.dto.ts new file mode 100644 index 00000000..4ce823b7 --- /dev/null +++ b/ludos/backend/src/dtos/rating/response/create.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class RatingCreateResponseDto { + @ApiProperty() + id: string; + + @ApiProperty() + rating: number; + + @ApiProperty() + createdAt: Date; + + @ApiProperty() + userId: string; + + @ApiProperty() + gameId: string; +} \ No newline at end of file diff --git a/ludos/backend/src/dtos/rating/response/delete.dto.ts b/ludos/backend/src/dtos/rating/response/delete.dto.ts new file mode 100644 index 00000000..d77cc32d --- /dev/null +++ b/ludos/backend/src/dtos/rating/response/delete.dto.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class RatingDeleteResponseDto { + @ApiProperty() + id: string; + + @ApiProperty({ + example: 'Rating is deleted!', + }) + message: string; +} \ No newline at end of file diff --git a/ludos/backend/src/dtos/rating/response/edit.dto.ts b/ludos/backend/src/dtos/rating/response/edit.dto.ts new file mode 100644 index 00000000..6680fa5d --- /dev/null +++ b/ludos/backend/src/dtos/rating/response/edit.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class RatingEditResponseDto { + @ApiProperty() + id: string; + + @ApiProperty() + rating: number; + + @ApiProperty() + updatedAt: Date; +} \ No newline at end of file diff --git a/ludos/backend/src/dtos/rating/response/list.response.ts b/ludos/backend/src/dtos/rating/response/list.response.ts new file mode 100644 index 00000000..3ee3c32a --- /dev/null +++ b/ludos/backend/src/dtos/rating/response/list.response.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Game } from '../../../entities/game.entity'; + +export class RatingListResponseDto { + @ApiProperty() + id: string; + + @ApiProperty() + game: Game; + + @ApiProperty() + rating: number; +} diff --git a/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts b/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts index 482a9109..4e1503f0 100644 --- a/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts +++ b/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts @@ -1,5 +1,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { Game } from '../../../entities/game.entity'; +import { UserType } from '../../../enums/user-type.enum' +import { Rating } from 'entities/rating.entity'; export class GetUserInfoResponseDto { @ApiProperty() @@ -8,4 +10,19 @@ export class GetUserInfoResponseDto { email: string; @ApiProperty() followedGames: Game[]; + @ApiProperty() + ratings: Rating[]; + @ApiProperty() + isNotificationEnabled: boolean; + @ApiProperty() + userType: UserType; + @ApiProperty() + fullName: string; + @ApiProperty() + avatar: string; + @ApiProperty() + aboutMe: string; + @ApiProperty() + steamUrl: string; + } diff --git a/ludos/backend/src/entities/game.entity.ts b/ludos/backend/src/entities/game.entity.ts index ee5ee8eb..c926dd22 100644 --- a/ludos/backend/src/entities/game.entity.ts +++ b/ludos/backend/src/entities/game.entity.ts @@ -9,6 +9,7 @@ import { } from 'typeorm'; import { User } from './user.entity'; import { Review } from './review.entity'; +import { Rating } from './rating.entity'; @Entity('games') export class Game { @@ -98,6 +99,9 @@ export class Game { @OneToMany('Review', 'game') reviews: Review[]; + @OneToMany('Rating', 'game') + ratingList: Rating[]; + @ManyToMany(() => User, (user) => user.followedGames) @JoinTable({ name: 'game_user_follows' }) followerList: User[]; diff --git a/ludos/backend/src/entities/rating.entity.ts b/ludos/backend/src/entities/rating.entity.ts new file mode 100644 index 00000000..01dca12d --- /dev/null +++ b/ludos/backend/src/entities/rating.entity.ts @@ -0,0 +1,33 @@ +import { + Column, + Entity, + ManyToOne, + PrimaryGeneratedColumn, + CreateDateColumn, + ManyToMany, + JoinTable, + UpdateDateColumn, +} from 'typeorm'; +import { Game } from './game.entity'; +import { User } from './user.entity'; + +@Entity('tests') +export class Rating { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column('float') + rating: number; + + @ManyToOne(() => Game, game => game.ratingList, { cascade: true }) + game: Game; + + @ManyToOne(() => User, user => user.ratingList, { cascade: true }) + user: User; + + @CreateDateColumn({type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' }) + createdAt: Date; + + @UpdateDateColumn({ type: 'timestamp',default: () => 'CURRENT_TIMESTAMP(6)', onUpdate: 'CURRENT_TIMESTAMP(6)' }) + updatedAt: Date; +} diff --git a/ludos/backend/src/entities/user.entity.ts b/ludos/backend/src/entities/user.entity.ts index b40066de..282855dd 100644 --- a/ludos/backend/src/entities/user.entity.ts +++ b/ludos/backend/src/entities/user.entity.ts @@ -12,6 +12,8 @@ import * as bcrypt from 'bcrypt'; import { Game } from './game.entity'; import { UserType } from '../enums/user-type.enum'; import { Review } from './review.entity'; +import { Rating } from './rating.entity'; + @Entity('users') export class User { @PrimaryGeneratedColumn('uuid') @@ -51,12 +53,15 @@ export class User { @OneToMany('Review', 'user') reviews: Review[]; + @OneToMany('Rating', 'user') + ratingList: Rating[]; + @ManyToMany('Review', 'likedUsers') likedReviews: Review[]; @ManyToMany('Review', 'dislikedUsers') dislikedReviews: Review[]; - + @BeforeInsert() @BeforeUpdate() async hashPasswordBeforeInsertAndUpdate() { diff --git a/ludos/backend/src/repositories/rating.repository.ts b/ludos/backend/src/repositories/rating.repository.ts new file mode 100644 index 00000000..735dc05f --- /dev/null +++ b/ludos/backend/src/repositories/rating.repository.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource, Repository } from 'typeorm'; +import { Rating } from '../entities/rating.entity'; + +@Injectable() +export class RatingRepository extends Repository { + constructor(dataSource: DataSource) { + super(Rating, dataSource.createEntityManager()); + } + + public async createRating(input: Partial): Promise { + const rating = this.create(input); + await this.save(rating); + return rating; + } + + public findRatingById(id: string): Promise { + return this.findOneBy({ id }); + } + + public async updateRating(input: Partial): Promise { + const rating = this.create(input); + await this.save(rating); + } + + public async deleteRating(id: string): Promise { + await this.delete(id); + } + +} diff --git a/ludos/backend/src/services/config/typeorm-config.service.ts b/ludos/backend/src/services/config/typeorm-config.service.ts index ccc8da49..ae548a74 100644 --- a/ludos/backend/src/services/config/typeorm-config.service.ts +++ b/ludos/backend/src/services/config/typeorm-config.service.ts @@ -5,6 +5,7 @@ import { User } from '../../entities/user.entity'; import { ResetPassword } from '../../entities/reset-password.entity'; import { Game } from '../../entities/game.entity'; import { Review } from '../../entities/review.entity'; +import { Rating } from '../../entities/rating.entity'; @Injectable() export class TypeOrmConfigService implements TypeOrmOptionsFactory { @@ -18,7 +19,7 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory { password: this.configService.get('DB_PASSWORD'), port: this.configService.get('DB_PORT'), database: this.configService.get('DB_NAME'), - entities: [User, ResetPassword, Game, Review], + entities: [User, ResetPassword, Game, Review,Rating], synchronize: true, }; } diff --git a/ludos/backend/src/services/rating.service.ts b/ludos/backend/src/services/rating.service.ts new file mode 100644 index 00000000..ca716466 --- /dev/null +++ b/ludos/backend/src/services/rating.service.ts @@ -0,0 +1,113 @@ +import { + Injectable, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; +import { GameRepository } from '../repositories/game.repository'; +import { UserRepository } from '../repositories/user.repository'; +import { RatingCreateDto } from '../dtos/rating/request/create.dto'; +import { RatingCreateResponseDto } from '../dtos/rating/response/create.dto'; +import { RatingRepository } from '../repositories/rating.repository'; +import { RatingEditDto } from '../dtos/rating/request/edit.dto'; +import { RatingEditResponseDto } from '../dtos/rating/response/edit.dto'; +import { RatingDeleteResponseDto } from 'dtos/rating/response/delete.dto'; + +@Injectable() +export class RatingService { + constructor( + private readonly gameRepository: GameRepository, + private readonly userRepository: UserRepository, + private readonly ratingRepository: RatingRepository, + ) { } + + public async createRating( + userId: string, + gameId: string, + ratingCreateDto: RatingCreateDto, + ): Promise { + const user = await this.userRepository.findUserById(userId); + const game = await this.gameRepository.findGameById(gameId); + + if (!user) { + throw new NotFoundException('User Not Found!'); + } + else if (!game) { + throw new NotFoundException('Game Not Found!'); + } + + const rating = await this.ratingRepository.createRating({ + rating: ratingCreateDto.rating, + user: user, + game: game + }); + + return { + id: rating.id, + rating: rating.rating, + userId: user.id, + gameId: rating.game.id, + createdAt: rating.createdAt, + }; + } + + public async deleteRating(userId: string, ratingId: string): Promise { + const rating = await this.ratingRepository.findRatingById(ratingId); + if (!rating) { + throw new NotFoundException('Rating Not Found!'); + } + + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } + + await this.ratingRepository.deleteRating(ratingId); + + return { + id: rating.id, + message: "Rating is deleted!" + }; + } + + public async editRating( + userId: string, + ratingId: string, + ratingEditDto: RatingEditDto, + ): Promise { + try { + const rating = await this.ratingRepository.findRatingById(ratingId); + if (!rating) { + throw new NotFoundException('Rating Not Found!'); + } + + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } + + if (!ratingEditDto.rating) { + throw new NotFoundException('Please provide at least one field to update!'); + } + + rating.rating = ratingEditDto.rating; + + + await this.ratingRepository.updateRating(rating); + + return { + id: rating.id, + rating: ratingEditDto.rating, + updatedAt: rating.updatedAt, + }; + + } catch (e) { + if (e instanceof NotFoundException) { + throw e; + } else { + console.log(e); + throw new InternalServerErrorException(); + } + } + } + +} diff --git a/ludos/backend/src/services/user.service.ts b/ludos/backend/src/services/user.service.ts index 6efed627..737a6115 100644 --- a/ludos/backend/src/services/user.service.ts +++ b/ludos/backend/src/services/user.service.ts @@ -32,7 +32,7 @@ export class UserService { private readonly resetPasswordRepository: ResetPasswordRepository, private readonly jwtService: JwtService, private readonly configService: ConfigService, - ) {} + ) { } public async register(input: RegisterDto): Promise { try { @@ -188,7 +188,7 @@ export class UserService { let updated = Object.assign(user, editInfoDto); await this.userRepository.save(updated); } - + public async getUserInfo(userId: string): Promise { const user = await this.userRepository.findUserByIdWithRelations(userId); if (!user) { @@ -199,6 +199,13 @@ export class UserService { response.email = user.email; response.username = user.username; response.followedGames = user.followedGames; + response.ratings = user.ratingList; + response.isNotificationEnabled = user.isNotificationEnabled; + response.userType = user.userType; + response.fullName = user.fullName; + response.avatar = user.avatar; + response.aboutMe = user.aboutMe; + response.steamUrl = user.steamUrl; return response; } From 9a4c97bdadc8060a53e4ecc895929d33702bb4b4 Mon Sep 17 00:00:00 2001 From: tacettinburakeren Date: Tue, 21 Nov 2023 13:55:10 +0300 Subject: [PATCH 18/22] some bugs fixed --- ludos/backend/src/controllers/rating.controller.ts | 6 ------ ludos/backend/src/entities/rating.entity.ts | 2 -- 2 files changed, 8 deletions(-) diff --git a/ludos/backend/src/controllers/rating.controller.ts b/ludos/backend/src/controllers/rating.controller.ts index 26a296a1..3efe8ac2 100644 --- a/ludos/backend/src/controllers/rating.controller.ts +++ b/ludos/backend/src/controllers/rating.controller.ts @@ -4,11 +4,7 @@ import { HttpCode, Delete, Param, - DefaultValuePipe, - ParseIntPipe, Post, - Get, - Query, Put, Req, UseGuards, @@ -18,8 +14,6 @@ import { ApiBearerAuth, ApiCreatedResponse, ApiNotFoundResponse, - ApiOkResponse, - ApiQuery, ApiTags, } from '@nestjs/swagger'; import { RatingCreateDto } from '../dtos/rating/request/create.dto'; diff --git a/ludos/backend/src/entities/rating.entity.ts b/ludos/backend/src/entities/rating.entity.ts index 01dca12d..f16453c3 100644 --- a/ludos/backend/src/entities/rating.entity.ts +++ b/ludos/backend/src/entities/rating.entity.ts @@ -4,8 +4,6 @@ import { ManyToOne, PrimaryGeneratedColumn, CreateDateColumn, - ManyToMany, - JoinTable, UpdateDateColumn, } from 'typeorm'; import { Game } from './game.entity'; From 507f9d1f4529a50b8cc120e012eac963a6dfdd20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hakan=20Karaku=C5=9F?= <68596831+Hakkush-07@users.noreply.github.com> Date: Tue, 21 Nov 2023 16:47:57 +0300 Subject: [PATCH 19/22] get comment endpoints --- .../src/controllers/comment.controller.ts | 78 +++++++-- .../comment/request/delete-comment.dto.ts | 8 - .../comment/request/dislike-comment.dto.ts | 8 - .../dtos/comment/request/edit-comment.dto.ts | 4 - .../dtos/comment/request/like-comment.dto.ts | 8 - .../response/get-comment.response.dto.ts | 32 ++++ ludos/backend/src/entities/comment.entity.ts | 19 ++- .../src/repositories/comment.repository.ts | 25 ++- ludos/backend/src/services/comment.service.ts | 154 ++++++++++++++++-- 9 files changed, 254 insertions(+), 82 deletions(-) delete mode 100644 ludos/backend/src/dtos/comment/request/delete-comment.dto.ts delete mode 100644 ludos/backend/src/dtos/comment/request/dislike-comment.dto.ts delete mode 100644 ludos/backend/src/dtos/comment/request/like-comment.dto.ts create mode 100644 ludos/backend/src/dtos/comment/response/get-comment.response.dto.ts diff --git a/ludos/backend/src/controllers/comment.controller.ts b/ludos/backend/src/controllers/comment.controller.ts index 14c3f5fe..67dfe864 100644 --- a/ludos/backend/src/controllers/comment.controller.ts +++ b/ludos/backend/src/controllers/comment.controller.ts @@ -2,9 +2,13 @@ import { Body, Controller, HttpCode, + Get, Post, + Put, + Delete, Req, UseGuards, + Param, } from '@nestjs/common'; import { ApiBadRequestResponse, @@ -15,10 +19,8 @@ import { ApiUnauthorizedResponse, } from '@nestjs/swagger'; import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; -import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; -import { DislikeCommentDto } from '../dtos/comment/request/dislike-comment.dto'; -import { DeleteCommentDto } from '../dtos/comment/request/delete-comment.dto'; import { EditCommentDto } from '../dtos/comment/request/edit-comment.dto'; +import { GetCommentResponseDto } from '../dtos/comment/response/get-comment.response.dto'; import { CommentService } from '../services/comment.service'; import { AuthGuard } from '../services/guards/auth.guard'; import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; @@ -28,7 +30,51 @@ import { AuthorizedRequest } from '../interfaces/common/authorized-request.inter export class CommentController { constructor(private readonly commentService: CommentService) { } - @ApiOperation({ summary: 'Comment on a post' }) + @ApiOperation({ summary: 'Get comment details' }) + @ApiOkResponse({ + description: 'Comment details', + type: GetCommentResponseDto, + }) + @ApiUnauthorizedResponse({ + description: 'Invalid Credentials', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(200) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Get(':commentId/info') + public async getComment( + @Req() req: AuthorizedRequest, + @Param('commentId') commentId: string, + ) { + return await this.commentService.getComment(req.user.id, commentId); + } + + @ApiOperation({ summary: 'Get comments of post/comment/review' }) + @ApiOkResponse({ + description: 'Comments of post/comment/review', + type: [GetCommentResponseDto], + }) + @ApiUnauthorizedResponse({ + description: 'Invalid Credentials', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(200) + @ApiBearerAuth() + @UseGuards(AuthGuard) + @Get(':parentId') + public async getCommentDetails( + @Req() req: AuthorizedRequest, + @Param('parentId') parentId: string, + ) { + return await this.commentService.getCommentsByParent(req.user.id, parentId); + } + + @ApiOperation({ summary: 'Comment on a post/comment/review' }) @ApiOkResponse({ description: 'Comment', }) @@ -62,12 +108,12 @@ export class CommentController { @HttpCode(200) @ApiBearerAuth() @UseGuards(AuthGuard) - @Post('/like-comment') + @Post(':commentId/like-comment') public async likeComment( @Req() req: AuthorizedRequest, - @Body() input: LikeCommentDto, + @Param('commentId') commentId: string, ) { - await this.commentService.likeComment(req.user.id, input); + await this.commentService.likeComment(req.user.id, commentId); } @ApiOperation({ summary: 'Dislike a comment' }) @@ -83,12 +129,12 @@ export class CommentController { @HttpCode(200) @ApiBearerAuth() @UseGuards(AuthGuard) - @Post('/dislike-comment') + @Post(':commentId/dislike-comment') public async dislikeComment( @Req() req: AuthorizedRequest, - @Body() input: DislikeCommentDto, + @Param('commentId') commentId: string, ) { - await this.commentService.dislikeComment(req.user.id, input); + await this.commentService.dislikeComment(req.user.id, commentId); } @ApiOperation({ summary: 'Delete a comment' }) @@ -104,12 +150,13 @@ export class CommentController { @HttpCode(200) @ApiBearerAuth() @UseGuards(AuthGuard) - @Post('/delete-comment') + @Delete(':commentId/delete-comment') public async deleteComment( @Req() req: AuthorizedRequest, - @Body() input: DeleteCommentDto, + @Param('commentId') commentId: string, ) { - await this.commentService.deleteComment(req.user.id, input); + console.log("comment id: ", commentId); + await this.commentService.deleteComment(req.user.id, commentId); } @ApiOperation({ summary: 'Edit a comment' }) @@ -125,11 +172,12 @@ export class CommentController { @HttpCode(200) @ApiBearerAuth() @UseGuards(AuthGuard) - @Post('/edit-comment') + @Put(':commentId/edit-comment') public async editComment( @Req() req: AuthorizedRequest, + @Param('commentId') commentId: string, @Body() input: EditCommentDto, ) { - await this.commentService.editComment(req.user.id, input); + await this.commentService.editComment(req.user.id, commentId, input); } } diff --git a/ludos/backend/src/dtos/comment/request/delete-comment.dto.ts b/ludos/backend/src/dtos/comment/request/delete-comment.dto.ts deleted file mode 100644 index c9942957..00000000 --- a/ludos/backend/src/dtos/comment/request/delete-comment.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString } from 'class-validator'; -export class DeleteCommentDto { - @ApiProperty() - @IsString() - commentId: string; -} - diff --git a/ludos/backend/src/dtos/comment/request/dislike-comment.dto.ts b/ludos/backend/src/dtos/comment/request/dislike-comment.dto.ts deleted file mode 100644 index 6dcb5737..00000000 --- a/ludos/backend/src/dtos/comment/request/dislike-comment.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString } from 'class-validator'; -export class DislikeCommentDto { - @ApiProperty() - @IsString() - commentId: string; -} - diff --git a/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts b/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts index df1c4c69..25064bd7 100644 --- a/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts +++ b/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts @@ -1,10 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; export class EditCommentDto { - @ApiProperty() - @IsString() - commentId: string; - @ApiProperty() @IsString() newText: string; diff --git a/ludos/backend/src/dtos/comment/request/like-comment.dto.ts b/ludos/backend/src/dtos/comment/request/like-comment.dto.ts deleted file mode 100644 index 24406661..00000000 --- a/ludos/backend/src/dtos/comment/request/like-comment.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsString } from 'class-validator'; -export class LikeCommentDto { - @ApiProperty() - @IsString() - commentId: string; -} - diff --git a/ludos/backend/src/dtos/comment/response/get-comment.response.dto.ts b/ludos/backend/src/dtos/comment/response/get-comment.response.dto.ts new file mode 100644 index 00000000..0c2beac8 --- /dev/null +++ b/ludos/backend/src/dtos/comment/response/get-comment.response.dto.ts @@ -0,0 +1,32 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { User } from '../../../entities/user.entity'; +import { IsDate, IsBoolean, IsString, IsNumber } from 'class-validator'; + +export class GetCommentResponseDto { + @ApiProperty({type: () => User}) + author: User; + + @ApiProperty() + @IsDate() + timestamp: Date; + + @ApiProperty() + @IsString() + text: string; + + @ApiProperty() + @IsString() + parentId: string; + + @ApiProperty() + @IsBoolean() + edited: boolean; + + @ApiProperty() + @IsNumber() + likeCount: number; + + @ApiProperty() + @IsNumber() + dislikeCount: number; +} \ No newline at end of file diff --git a/ludos/backend/src/entities/comment.entity.ts b/ludos/backend/src/entities/comment.entity.ts index e9ae7ca5..ff7a8a6b 100644 --- a/ludos/backend/src/entities/comment.entity.ts +++ b/ludos/backend/src/entities/comment.entity.ts @@ -2,16 +2,19 @@ import { Entity, Column, PrimaryGeneratedColumn, + ManyToMany, + JoinTable, + ManyToOne, } from 'typeorm'; -//import { User } from '../entities/user.entity'; +import { User } from '../entities/user.entity'; @Entity('comments') export class Comment { @PrimaryGeneratedColumn('uuid') id: string; - @Column() - author: string; //User; + @ManyToOne(() => User, {eager: true}) + author: User; @Column() timestamp: Date; @@ -22,11 +25,13 @@ export class Comment { @Column() text: string; - @Column() - likes: number; + @ManyToMany('User', {eager: true}) + @JoinTable({ name: 'comment_user_likes' }) + likedUsers: User[] - @Column() - dislikes: number; + @ManyToMany('User', {eager: true}) + @JoinTable({ name: 'comment_user_dislikes' }) + dislikedUsers: User[]; @Column({default: false}) edited: boolean; diff --git a/ludos/backend/src/repositories/comment.repository.ts b/ludos/backend/src/repositories/comment.repository.ts index 0c9802d8..ffa7cad1 100644 --- a/ludos/backend/src/repositories/comment.repository.ts +++ b/ludos/backend/src/repositories/comment.repository.ts @@ -16,23 +16,13 @@ export class CommentRepository extends Repository { public findCommentById(id: string): Promise { return this.findOneBy({ id }); + //return this.findOne({ where: {id}, relations: {likedUsers: true, dislikedUsers: true} }); } - - public async incrementLikeCount(commentId: string) { - let comment = await this.findCommentById(commentId); - comment.likes += 1; - await this.save(comment); - } - - public async incrementDislikeCount(commentId: string) { - let comment = await this.findCommentById(commentId); - comment.dislikes += 1; - await this.save(comment); - } - + public async deleteComment(commentId: string) { - let comment = await this.findCommentById(commentId); - await this.delete(comment); + console.log("delete", commentId); + let x = await this.delete({id: commentId}); + console.log(x); } public async editComment(commentId: string, newText: string) { @@ -41,4 +31,9 @@ export class CommentRepository extends Repository { comment.edited = true; await this.save(comment); } + + public findCommentsByParent(parentId: string): Promise { + return this.findBy({ parentId }); + } + } diff --git a/ludos/backend/src/services/comment.service.ts b/ludos/backend/src/services/comment.service.ts index 4341d52f..6a2c4d3c 100644 --- a/ludos/backend/src/services/comment.service.ts +++ b/ludos/backend/src/services/comment.service.ts @@ -4,10 +4,8 @@ import { Injectable, } from '@nestjs/common'; import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; -import { LikeCommentDto } from '../dtos/comment/request/like-comment.dto'; -import { DislikeCommentDto } from '../dtos/comment/request/dislike-comment.dto'; -import { DeleteCommentDto } from '../dtos/comment/request/delete-comment.dto'; import { EditCommentDto } from '../dtos/comment/request/edit-comment.dto'; +import { GetCommentResponseDto } from '../dtos/comment/response/get-comment.response.dto'; import { UserRepository } from '../repositories/user.repository'; import { CommentRepository } from '../repositories/comment.repository'; @@ -17,13 +15,61 @@ export class CommentService { private readonly userRepository: UserRepository, private readonly commentRepository: CommentRepository, ) {} + + public async getComment(userId: string, commentId: string): Promise { + let user = await this.userRepository.findUserById(userId); + + if (!user) { + throw new HttpException( + 'No user found with this id', + HttpStatus.FORBIDDEN, + ); + } + + let comment = await this.commentRepository.findCommentById(commentId); + + if (!comment) { + throw new HttpException( + 'No comment found with this id', + HttpStatus.FORBIDDEN, + ); + } + return { + author: comment.author, + timestamp: comment.timestamp, + edited: comment.edited, + text: comment.text, + parentId: comment.parentId, + likeCount: comment.likedUsers.length, + dislikeCount: comment.dislikedUsers.length, + } + } + + public async getCommentsByParent(userId: string, parentId: string): Promise { + let user = await this.userRepository.findUserById(userId); + + if (!user) { + throw new HttpException( + 'No user found with this id', + HttpStatus.FORBIDDEN, + ); + } + + return (await this.commentRepository.findCommentsByParent(parentId)).map((comment) => { + return { + likeCount: comment.likedUsers.length, + dislikeCount: comment.dislikedUsers.length, + ...comment, + } + }); + } public async writeComment(userId: string, writeCommentDto: WriteCommentDto) { let user = await this.userRepository.findUserById(userId); if (!user) { throw new HttpException( - 'No user found with this email', + 'No user found with this id', HttpStatus.FORBIDDEN, ); } @@ -33,18 +79,29 @@ export class CommentService { // parent id should be the identifier of one of the above let comment = { - author: userId, //user, + author: user, text: writeCommentDto.text, parentId: writeCommentDto.parentId, likes: 0, dislikes: 0, timestamp: new Date(), + likedUsers: [], + dislikedUsers: [], } await this.commentRepository.createComment(comment); } - public async likeComment(userId: string, likeCommentDto: LikeCommentDto) { - let comment = await this.commentRepository.findCommentById(likeCommentDto.commentId); + public async likeComment(userId: string, commentId: string) { + let user = await this.userRepository.findUserById(userId); + + if (!user) { + throw new HttpException( + 'No user found with this id', + HttpStatus.FORBIDDEN, + ); + } + + let comment = await this.commentRepository.findCommentById(commentId); if (!comment) { throw new HttpException( @@ -53,11 +110,31 @@ export class CommentService { ); } - await this.commentRepository.incrementLikeCount(likeCommentDto.commentId); + if (comment.likedUsers.find(likedUser => likedUser.id === userId)) { + comment.likedUsers = comment.likedUsers.filter(likedUser => likedUser.id !== userId); + } + else if (comment.dislikedUsers.find(dislikedUser => dislikedUser.id === userId)) { + comment.dislikedUsers = comment.dislikedUsers.filter(dislikedUser => dislikedUser.id !== userId); + comment.likedUsers.push(user); + } + else { + comment.likedUsers.push(user); + } + + await this.commentRepository.save(comment); } - public async dislikeComment(userId: string, dislikeCommentDto: DislikeCommentDto) { - let comment = await this.commentRepository.findCommentById(dislikeCommentDto.commentId); + public async dislikeComment(userId: string, commentId: string) { + let user = await this.userRepository.findUserById(userId); + + if (!user) { + throw new HttpException( + 'No user found with this id', + HttpStatus.FORBIDDEN, + ); + } + + let comment = await this.commentRepository.findCommentById(commentId); if (!comment) { throw new HttpException( @@ -66,11 +143,31 @@ export class CommentService { ); } - await this.commentRepository.incrementDislikeCount(dislikeCommentDto.commentId); + if (comment.dislikedUsers.find(dislikedUser => dislikedUser.id === userId)) { + comment.dislikedUsers = comment.dislikedUsers.filter(dislikedUser => dislikedUser.id !== userId); + } + else if (comment.likedUsers.find(likedUser => likedUser.id === userId)) { + comment.likedUsers = comment.likedUsers.filter(likedUser => likedUser.id !== userId); + comment.dislikedUsers.push(user); + } + else { + comment.dislikedUsers.push(user); + } + + await this.commentRepository.save(comment); } - public async deleteComment(userId: string, deleteCommentDto: DeleteCommentDto) { - let comment = await this.commentRepository.findCommentById(deleteCommentDto.commentId); + public async deleteComment(userId: string, commentId: string) { + let user = await this.userRepository.findUserById(userId); + + if (!user) { + throw new HttpException( + 'No user found with this id', + HttpStatus.FORBIDDEN, + ); + } + + let comment = await this.commentRepository.findCommentById(commentId); if (!comment) { throw new HttpException( @@ -79,11 +176,27 @@ export class CommentService { ); } - await this.commentRepository.deleteComment(deleteCommentDto.commentId); + if (comment.author.id !== user.id) { + throw new HttpException( + 'User is not the author, can not delete', + HttpStatus.FORBIDDEN, + ); + } + + await this.commentRepository.deleteComment(commentId); } - public async editComment(userId: string, editCommentDto: EditCommentDto) { - let comment = await this.commentRepository.findCommentById(editCommentDto.commentId); + public async editComment(userId: string, commentId: string, editCommentDto: EditCommentDto) { + let user = await this.userRepository.findUserById(userId); + + if (!user) { + throw new HttpException( + 'No user found with this id', + HttpStatus.FORBIDDEN, + ); + } + + let comment = await this.commentRepository.findCommentById(commentId); if (!comment) { throw new HttpException( @@ -92,6 +205,13 @@ export class CommentService { ); } - await this.commentRepository.editComment(editCommentDto.commentId, editCommentDto.newText); + if (comment.author.id !== user.id) { + throw new HttpException( + 'User is not the author, can not edit', + HttpStatus.FORBIDDEN, + ); + } + + await this.commentRepository.editComment(commentId, editCommentDto.newText); } } From 8507596c9cdfe8f1a9c44babd0909d65c2e36e8b Mon Sep 17 00:00:00 2001 From: omersafakbebek Date: Tue, 21 Nov 2023 20:42:50 +0300 Subject: [PATCH 20/22] added average rate to game --- .../src/controllers/rating.controller.ts | 12 +++--- .../src/dtos/rating/request/create.dto.ts | 2 + ludos/backend/src/entities/game.entity.ts | 6 ++- ludos/backend/src/entities/rating.entity.ts | 4 +- .../src/repositories/rating.repository.ts | 8 +++- ludos/backend/src/services/game.service.ts | 4 ++ ludos/backend/src/services/rating.service.ts | 42 +++++++++++-------- 7 files changed, 50 insertions(+), 28 deletions(-) diff --git a/ludos/backend/src/controllers/rating.controller.ts b/ludos/backend/src/controllers/rating.controller.ts index 3efe8ac2..7613f1e4 100644 --- a/ludos/backend/src/controllers/rating.controller.ts +++ b/ludos/backend/src/controllers/rating.controller.ts @@ -56,27 +56,27 @@ export class RatingController { @ApiBearerAuth() @ApiNotFoundResponse({ description: 'Rating is not found!' }) @HttpCode(204) - @Delete(':ratingId') + @Delete(':gameId') public async deleteRating( @Req() req: AuthorizedRequest, - @Param('ratingId') ratingId: string, + @Param('gameId') gameId: string, ) { - const deletedRatingResponse = await this.ratingService.deleteRating(req.user.id, ratingId); + const deletedRatingResponse = await this.ratingService.deleteRating(req.user.id, gameId); return deletedRatingResponse } @UseGuards(AuthGuard) @ApiBearerAuth() @HttpCode(200) - @Put(':ratingId') + @Put(':gameId') public async editRating( @Req() req: AuthorizedRequest, - @Param('ratingId') ratingId: string, + @Param('gameId') gameId: string, @Body() ratingEditDto: RatingEditDto, ) { const editedRating = await this.ratingService.editRating( req.user.id, - ratingId, + gameId, ratingEditDto, ); return editedRating; diff --git a/ludos/backend/src/dtos/rating/request/create.dto.ts b/ludos/backend/src/dtos/rating/request/create.dto.ts index 6d5709a5..d990d4e2 100644 --- a/ludos/backend/src/dtos/rating/request/create.dto.ts +++ b/ludos/backend/src/dtos/rating/request/create.dto.ts @@ -1,3 +1,4 @@ +import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsNumber, Min, Max } from 'class-validator'; export class RatingCreateDto { @@ -5,5 +6,6 @@ export class RatingCreateDto { @IsNumber() @Min(0.0) @Max(5.0) + @ApiProperty() rating: number; } diff --git a/ludos/backend/src/entities/game.entity.ts b/ludos/backend/src/entities/game.entity.ts index c926dd22..2602df63 100644 --- a/ludos/backend/src/entities/game.entity.ts +++ b/ludos/backend/src/entities/game.entity.ts @@ -22,10 +22,12 @@ export class Game { @Column({ type: 'text' }) coverLink: string; - @Column({ type: 'float', default: 0 }) + @VirtualColumn({ + query: (alias) => `SELECT AVG(rating) FROM ratings WHERE "gameId" = ${alias}.id`, + }) averageRating: number; - @Column({ type: 'float', default: 0 }) + userRating: number; @Column('jsonb') diff --git a/ludos/backend/src/entities/rating.entity.ts b/ludos/backend/src/entities/rating.entity.ts index f16453c3..591820c8 100644 --- a/ludos/backend/src/entities/rating.entity.ts +++ b/ludos/backend/src/entities/rating.entity.ts @@ -5,11 +5,13 @@ import { PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, + Unique } from 'typeorm'; import { Game } from './game.entity'; import { User } from './user.entity'; -@Entity('tests') +@Entity('ratings') +@Unique(["game", "user"]) export class Rating { @PrimaryGeneratedColumn('uuid') id: string; diff --git a/ludos/backend/src/repositories/rating.repository.ts b/ludos/backend/src/repositories/rating.repository.ts index 735dc05f..6b54f698 100644 --- a/ludos/backend/src/repositories/rating.repository.ts +++ b/ludos/backend/src/repositories/rating.repository.ts @@ -18,13 +18,17 @@ export class RatingRepository extends Repository { return this.findOneBy({ id }); } + public findRatingByUserIdAndGameId(userId: string, gameId: string): Promise { + return this.findOneBy({ game: { id: gameId }, user: { id: userId} }); + } + public async updateRating(input: Partial): Promise { const rating = this.create(input); await this.save(rating); } - public async deleteRating(id: string): Promise { - await this.delete(id); + public async deleteRating(userId: string, gameId: string): Promise { + await this.delete({user: {id: userId}, game: {id: gameId}}); } } diff --git a/ludos/backend/src/services/game.service.ts b/ludos/backend/src/services/game.service.ts index 4c5e74a9..97839db2 100644 --- a/ludos/backend/src/services/game.service.ts +++ b/ludos/backend/src/services/game.service.ts @@ -10,12 +10,14 @@ import { GameCreateResponseDto } from '../dtos/game/response/create.response'; import { GameRepository } from '../repositories/game.repository'; import { UserRepository } from '../repositories/user.repository'; import { IPaginationMeta, Pagination } from 'nestjs-typeorm-paginate'; +import { RatingRepository } from '../repositories/rating.repository'; @Injectable() export class GameService { constructor( private readonly gameRepository: GameRepository, private readonly userRepository: UserRepository, + private readonly ratingRepository: RatingRepository ) {} public async createGame( @@ -48,6 +50,8 @@ export class GameService { game.isFollowed = userId ? game.followerList.some((user) => user.id === userId) : false; + const rating = await this.ratingRepository.findRatingByUserIdAndGameId(userId, id); + game.userRating = (userId && rating) ? rating.rating : null; return game; } async followGame(userId: string, gameId: string): Promise { diff --git a/ludos/backend/src/services/rating.service.ts b/ludos/backend/src/services/rating.service.ts index ca716466..d59f99ce 100644 --- a/ludos/backend/src/services/rating.service.ts +++ b/ludos/backend/src/services/rating.service.ts @@ -1,4 +1,5 @@ import { + ConflictException, Injectable, InternalServerErrorException, NotFoundException, @@ -35,23 +36,30 @@ export class RatingService { throw new NotFoundException('Game Not Found!'); } - const rating = await this.ratingRepository.createRating({ - rating: ratingCreateDto.rating, - user: user, - game: game - }); + try{ + const rating = await this.ratingRepository.createRating({ + rating: ratingCreateDto.rating, + user: user, + game: game + }); + + return { + id: rating.id, + rating: rating.rating, + userId: user.id, + gameId: rating.game.id, + createdAt: rating.createdAt, + }; + } catch(e) { + if(e.code == "23505") { + throw new ConflictException("Rating already exists"); + } + } - return { - id: rating.id, - rating: rating.rating, - userId: user.id, - gameId: rating.game.id, - createdAt: rating.createdAt, - }; } - public async deleteRating(userId: string, ratingId: string): Promise { - const rating = await this.ratingRepository.findRatingById(ratingId); + public async deleteRating(userId: string, gameId: string): Promise { + const rating = await this.ratingRepository.findRatingByUserIdAndGameId(userId, gameId); if (!rating) { throw new NotFoundException('Rating Not Found!'); } @@ -61,7 +69,7 @@ export class RatingService { throw new NotFoundException('User Not Found!'); } - await this.ratingRepository.deleteRating(ratingId); + await this.ratingRepository.deleteRating(userId, gameId); return { id: rating.id, @@ -71,11 +79,11 @@ export class RatingService { public async editRating( userId: string, - ratingId: string, + gameId: string, ratingEditDto: RatingEditDto, ): Promise { try { - const rating = await this.ratingRepository.findRatingById(ratingId); + const rating = await this.ratingRepository.findRatingByUserIdAndGameId(userId, gameId); if (!rating) { throw new NotFoundException('Rating Not Found!'); } From 1dc36375dc58d0217aa0be1ccf0958b679bce485 Mon Sep 17 00:00:00 2001 From: omersafakbebek Date: Tue, 21 Nov 2023 20:50:30 +0300 Subject: [PATCH 21/22] fix test error --- ludos/backend/src/app.module.ts | 4 +++- ludos/backend/test/game.controller.spec.ts | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ludos/backend/src/app.module.ts b/ludos/backend/src/app.module.ts index 93eaca18..71f0f2db 100644 --- a/ludos/backend/src/app.module.ts +++ b/ludos/backend/src/app.module.ts @@ -47,7 +47,7 @@ import { Rating } from './entities/rating.entity'; useClass: TypeOrmConfigService, inject: [TypeOrmConfigService], }), - TypeOrmModule.forFeature([User, Game,Review, ResetPassword,Rating]), + TypeOrmModule.forFeature([User, Game,Review, ResetPassword,Rating, Comment]), ], controllers: [AppController, UserController, GameController, S3Controller, ReviewController,RatingController, CommentController], providers: [ @@ -62,6 +62,8 @@ import { Rating } from './entities/rating.entity'; CommentService, ReviewRepository, ReviewService, + RatingRepository, + RatingService ], }) export class AppModule implements NestModule { diff --git a/ludos/backend/test/game.controller.spec.ts b/ludos/backend/test/game.controller.spec.ts index 25e32848..a9bcbdd5 100644 --- a/ludos/backend/test/game.controller.spec.ts +++ b/ludos/backend/test/game.controller.spec.ts @@ -8,6 +8,7 @@ import { User } from '../src/entities/user.entity'; import { GameRepository } from '../src/repositories/game.repository'; import { UserRepository } from '../src/repositories/user.repository'; import { GameService } from '../src/services/game.service'; +import { RatingRepository } from '../src/repositories/rating.repository'; describe('GameController', () => { let gameController: GameController; let gameRepository: GameRepository; @@ -28,6 +29,7 @@ describe('GameController', () => { GameService, GameRepository, UserRepository, + RatingRepository, { provide: DataSource, useValue: dataSource, From 16761ae20261b9b059bb44c86dd075e35fd8dfcd Mon Sep 17 00:00:00 2001 From: omersafakbebek Date: Tue, 21 Nov 2023 20:50:51 +0300 Subject: [PATCH 22/22] run format --- ludos/backend/src/app.module.ts | 23 +- .../src/controllers/comment.controller.ts | 4 +- .../src/controllers/rating.controller.ts | 9 +- .../src/controllers/review.controller.ts | 172 +++++---- .../backend/src/controllers/s3.controller.ts | 7 +- .../src/controllers/user.controller.ts | 6 +- .../dtos/comment/request/edit-comment.dto.ts | 1 - .../dtos/comment/request/write-comment.dto.ts | 1 - .../response/get-comment.response.dto.ts | 4 +- .../src/dtos/game/response/get.response.ts | 2 +- .../src/dtos/rating/request/delete.dto.ts | 4 +- .../src/dtos/rating/request/edit.dto.ts | 2 +- .../src/dtos/rating/response/create.dto.ts | 2 +- .../src/dtos/rating/response/delete.dto.ts | 2 +- .../src/dtos/rating/response/edit.dto.ts | 2 +- .../src/dtos/rating/response/list.response.ts | 2 +- .../src/dtos/review/request/edit.dto.ts | 6 +- .../src/dtos/review/response/create.dto.ts | 2 +- .../src/dtos/review/response/edit.dto.ts | 2 +- .../dtos/user/request/get-user-info.dto.ts | 10 +- .../response/get-user-info-response.dto.ts | 43 ++- ludos/backend/src/entities/comment.entity.ts | 10 +- ludos/backend/src/entities/game.entity.ts | 4 +- ludos/backend/src/entities/rating.entity.ts | 19 +- ludos/backend/src/entities/review.entity.ts | 94 ++--- ludos/backend/src/entities/user.entity.ts | 2 +- .../src/repositories/comment.repository.ts | 11 +- .../src/repositories/rating.repository.ts | 10 +- .../src/repositories/review.repository.ts | 1 - .../src/repositories/user.repository.ts | 6 +- ludos/backend/src/services/comment.service.ts | 110 +++--- .../services/config/typeorm-config.service.ts | 2 +- ludos/backend/src/services/game.service.ts | 11 +- ludos/backend/src/services/rating.service.ts | 42 ++- ludos/backend/src/services/review.service.ts | 355 +++++++++--------- ludos/backend/src/services/s3.service.ts | 30 +- ludos/backend/src/services/user.service.ts | 2 +- practice-app/.DS_Store | Bin 0 -> 6148 bytes 38 files changed, 529 insertions(+), 486 deletions(-) create mode 100644 practice-app/.DS_Store diff --git a/ludos/backend/src/app.module.ts b/ludos/backend/src/app.module.ts index 71f0f2db..499c8fd7 100644 --- a/ludos/backend/src/app.module.ts +++ b/ludos/backend/src/app.module.ts @@ -32,8 +32,6 @@ import { RatingRepository } from './repositories/rating.repository'; import { RatingService } from './services/rating.service'; import { Rating } from './entities/rating.entity'; - - @Module({ imports: [ ConfigModule.forRoot({ @@ -47,9 +45,24 @@ import { Rating } from './entities/rating.entity'; useClass: TypeOrmConfigService, inject: [TypeOrmConfigService], }), - TypeOrmModule.forFeature([User, Game,Review, ResetPassword,Rating, Comment]), + TypeOrmModule.forFeature([ + User, + Game, + Review, + ResetPassword, + Rating, + Comment, + ]), + ], + controllers: [ + AppController, + UserController, + GameController, + S3Controller, + ReviewController, + RatingController, + CommentController, ], - controllers: [AppController, UserController, GameController, S3Controller, ReviewController,RatingController, CommentController], providers: [ AppService, UserRepository, @@ -63,7 +76,7 @@ import { Rating } from './entities/rating.entity'; ReviewRepository, ReviewService, RatingRepository, - RatingService + RatingService, ], }) export class AppModule implements NestModule { diff --git a/ludos/backend/src/controllers/comment.controller.ts b/ludos/backend/src/controllers/comment.controller.ts index 67dfe864..98400a46 100644 --- a/ludos/backend/src/controllers/comment.controller.ts +++ b/ludos/backend/src/controllers/comment.controller.ts @@ -28,7 +28,7 @@ import { AuthorizedRequest } from '../interfaces/common/authorized-request.inter @ApiTags('comment') @Controller('comment') export class CommentController { - constructor(private readonly commentService: CommentService) { } + constructor(private readonly commentService: CommentService) {} @ApiOperation({ summary: 'Get comment details' }) @ApiOkResponse({ @@ -155,7 +155,7 @@ export class CommentController { @Req() req: AuthorizedRequest, @Param('commentId') commentId: string, ) { - console.log("comment id: ", commentId); + console.log('comment id: ', commentId); await this.commentService.deleteComment(req.user.id, commentId); } diff --git a/ludos/backend/src/controllers/rating.controller.ts b/ludos/backend/src/controllers/rating.controller.ts index 7613f1e4..bea0522b 100644 --- a/ludos/backend/src/controllers/rating.controller.ts +++ b/ludos/backend/src/controllers/rating.controller.ts @@ -27,7 +27,7 @@ import { RatingEditDto } from '../dtos/rating/request/edit.dto'; @ApiTags('rating') @Controller('rating') export class RatingController { - constructor(private readonly ratingService: RatingService) { } + constructor(private readonly ratingService: RatingService) {} @ApiBearerAuth() @ApiCreatedResponse({ @@ -61,8 +61,11 @@ export class RatingController { @Req() req: AuthorizedRequest, @Param('gameId') gameId: string, ) { - const deletedRatingResponse = await this.ratingService.deleteRating(req.user.id, gameId); - return deletedRatingResponse + const deletedRatingResponse = await this.ratingService.deleteRating( + req.user.id, + gameId, + ); + return deletedRatingResponse; } @UseGuards(AuthGuard) diff --git a/ludos/backend/src/controllers/review.controller.ts b/ludos/backend/src/controllers/review.controller.ts index f8b6c9ce..9a99eedc 100644 --- a/ludos/backend/src/controllers/review.controller.ts +++ b/ludos/backend/src/controllers/review.controller.ts @@ -1,91 +1,91 @@ import { - Body, - Controller, - HttpCode, - Delete, - Param, - Post, - Put, - Req, - UseGuards, - } from '@nestjs/common'; - import { - ApiBadRequestResponse, - ApiBearerAuth, - ApiConflictResponse, - ApiCreatedResponse, - ApiNotFoundResponse, - ApiTags, - } from '@nestjs/swagger'; - import { ReviewCreateDto } from '../dtos/review/request/create.dto'; - import { Review } from '../entities/review.entity'; - import { AuthGuard } from '../services/guards/auth.guard'; - import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; - import { ReviewService } from '../services/review.service'; - import { ReviewEditDto } from '../dtos/review/request/edit.dto'; - - @ApiBearerAuth() - @UseGuards(AuthGuard) - @ApiTags('review') - @Controller('review') - export class ReviewController { - constructor(private readonly reviewService: ReviewService) {} - - @ApiCreatedResponse({ - description: 'Review created successfully', - type: Review, - }) - @ApiConflictResponse({ - description: 'Conflict in creating the review', - }) - @ApiBadRequestResponse({ - description: 'Bad Request', - }) - @HttpCode(201) - @Post(':gameId') - public async createReview( - @Req() req: AuthorizedRequest, - @Param('gameId') gameId: string, - @Body() reviewCreateDto: ReviewCreateDto, - ) { - const createdReview = await this.reviewService.createReview( - req.user.id, - gameId, - reviewCreateDto, - ); - return createdReview; - } + Body, + Controller, + HttpCode, + Delete, + Param, + Post, + Put, + Req, + UseGuards, +} from '@nestjs/common'; +import { + ApiBadRequestResponse, + ApiBearerAuth, + ApiConflictResponse, + ApiCreatedResponse, + ApiNotFoundResponse, + ApiTags, +} from '@nestjs/swagger'; +import { ReviewCreateDto } from '../dtos/review/request/create.dto'; +import { Review } from '../entities/review.entity'; +import { AuthGuard } from '../services/guards/auth.guard'; +import { AuthorizedRequest } from '../interfaces/common/authorized-request.interface'; +import { ReviewService } from '../services/review.service'; +import { ReviewEditDto } from '../dtos/review/request/edit.dto'; + +@ApiBearerAuth() +@UseGuards(AuthGuard) +@ApiTags('review') +@Controller('review') +export class ReviewController { + constructor(private readonly reviewService: ReviewService) {} + + @ApiCreatedResponse({ + description: 'Review created successfully', + type: Review, + }) + @ApiConflictResponse({ + description: 'Conflict in creating the review', + }) + @ApiBadRequestResponse({ + description: 'Bad Request', + }) + @HttpCode(201) + @Post(':gameId') + public async createReview( + @Req() req: AuthorizedRequest, + @Param('gameId') gameId: string, + @Body() reviewCreateDto: ReviewCreateDto, + ) { + const createdReview = await this.reviewService.createReview( + req.user.id, + gameId, + reviewCreateDto, + ); + return createdReview; + } - @HttpCode(200) - @Post(':reviewId/like') - public async likeReview( - @Req() req: AuthorizedRequest, - @Param('reviewId') reviewId: string, - ) { - await this.reviewService.likeReview(req.user.id, reviewId); - return { message: 'Review liked successfully.' }; - } + @HttpCode(200) + @Post(':reviewId/like') + public async likeReview( + @Req() req: AuthorizedRequest, + @Param('reviewId') reviewId: string, + ) { + await this.reviewService.likeReview(req.user.id, reviewId); + return { message: 'Review liked successfully.' }; + } - @HttpCode(200) - @Post(':reviewId/dislike') - public async dislikeReview( - @Req() req: AuthorizedRequest, - @Param('reviewId') reviewId: string, - ) { - await this.reviewService.dislikeReview(req.user.id, reviewId); - return { message: 'Review disliked successfully.' }; - } + @HttpCode(200) + @Post(':reviewId/dislike') + public async dislikeReview( + @Req() req: AuthorizedRequest, + @Param('reviewId') reviewId: string, + ) { + await this.reviewService.dislikeReview(req.user.id, reviewId); + return { message: 'Review disliked successfully.' }; + } - @ApiNotFoundResponse({ description: 'Review is not found!' }) - @HttpCode(204) - @Delete(':reviewId') - public async deleteReview( - @Req() req: AuthorizedRequest, - @Param('reviewId') reviewId: string, - ) { - await this.reviewService.deleteReview(req.user.id, reviewId); - return { message: 'Review deleted successfully.'}; - } + @ApiNotFoundResponse({ description: 'Review is not found!' }) + @HttpCode(204) + @Delete(':reviewId') + public async deleteReview( + @Req() req: AuthorizedRequest, + @Param('reviewId') reviewId: string, + ) { + await this.reviewService.deleteReview(req.user.id, reviewId); + return { message: 'Review deleted successfully.' }; + } @HttpCode(200) @Put(':reviewId') @@ -101,6 +101,4 @@ import { ); return editedReview; } - - } - \ No newline at end of file +} diff --git a/ludos/backend/src/controllers/s3.controller.ts b/ludos/backend/src/controllers/s3.controller.ts index 391c37c5..314928d0 100644 --- a/ludos/backend/src/controllers/s3.controller.ts +++ b/ludos/backend/src/controllers/s3.controller.ts @@ -52,11 +52,12 @@ export class S3Controller { }, }, }) - @UseInterceptors(FileInterceptor('file', { + @UseInterceptors( + FileInterceptor('file', { storage: diskStorage({ destination: './uploads', }), - }) + }), ) public async uploadFile( @Req() _req: AuthorizedRequest, @@ -64,4 +65,4 @@ export class S3Controller { ) { return await this.s3Service.uploadFile(file); } -} \ No newline at end of file +} diff --git a/ludos/backend/src/controllers/user.controller.ts b/ludos/backend/src/controllers/user.controller.ts index dd75196f..a1dfbc08 100644 --- a/ludos/backend/src/controllers/user.controller.ts +++ b/ludos/backend/src/controllers/user.controller.ts @@ -34,7 +34,7 @@ import { AuthorizedRequest } from '../interfaces/common/authorized-request.inter @ApiTags('user') @Controller('user') export class UserController { - constructor(private readonly userService: UserService) { } + constructor(private readonly userService: UserService) {} @ApiOkResponse({ description: 'Successful Register', type: RegisterResponseDto, @@ -78,7 +78,6 @@ export class UserController { @ApiBadRequestResponse({ description: 'Bad Request', }) - @HttpCode(200) @ApiOperation({ summary: 'Password Reset Request Endpoint' }) @Post('/reset-password') @@ -139,7 +138,7 @@ export class UserController { ) { await this.userService.editInfo(req.user.id, input); } - + @HttpCode(200) @ApiUnauthorizedResponse({ description: 'Invalid User', @@ -147,7 +146,6 @@ export class UserController { @ApiBadRequestResponse({ description: 'Bad Request', }) - @ApiBearerAuth() @ApiOperation({ summary: 'Get User Info Request Endpoint' }) @Get('/info') diff --git a/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts b/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts index 25064bd7..5ba88c73 100644 --- a/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts +++ b/ludos/backend/src/dtos/comment/request/edit-comment.dto.ts @@ -5,4 +5,3 @@ export class EditCommentDto { @IsString() newText: string; } - diff --git a/ludos/backend/src/dtos/comment/request/write-comment.dto.ts b/ludos/backend/src/dtos/comment/request/write-comment.dto.ts index 32a9a635..872337ce 100644 --- a/ludos/backend/src/dtos/comment/request/write-comment.dto.ts +++ b/ludos/backend/src/dtos/comment/request/write-comment.dto.ts @@ -9,4 +9,3 @@ export class WriteCommentDto { @IsString() text: string; } - diff --git a/ludos/backend/src/dtos/comment/response/get-comment.response.dto.ts b/ludos/backend/src/dtos/comment/response/get-comment.response.dto.ts index 0c2beac8..7a6a920e 100644 --- a/ludos/backend/src/dtos/comment/response/get-comment.response.dto.ts +++ b/ludos/backend/src/dtos/comment/response/get-comment.response.dto.ts @@ -3,7 +3,7 @@ import { User } from '../../../entities/user.entity'; import { IsDate, IsBoolean, IsString, IsNumber } from 'class-validator'; export class GetCommentResponseDto { - @ApiProperty({type: () => User}) + @ApiProperty({ type: () => User }) author: User; @ApiProperty() @@ -29,4 +29,4 @@ export class GetCommentResponseDto { @ApiProperty() @IsNumber() dislikeCount: number; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/game/response/get.response.ts b/ludos/backend/src/dtos/game/response/get.response.ts index 728bc302..8fdacc20 100644 --- a/ludos/backend/src/dtos/game/response/get.response.ts +++ b/ludos/backend/src/dtos/game/response/get.response.ts @@ -18,4 +18,4 @@ export class GameGetResponseDto { @ApiProperty() developer: string; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/rating/request/delete.dto.ts b/ludos/backend/src/dtos/rating/request/delete.dto.ts index b6790fbd..fc70ae43 100644 --- a/ludos/backend/src/dtos/rating/request/delete.dto.ts +++ b/ludos/backend/src/dtos/rating/request/delete.dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from '@nestjs/swagger'; export class RatingDeleteDto { - @ApiProperty() - id: string; + @ApiProperty() + id: string; } diff --git a/ludos/backend/src/dtos/rating/request/edit.dto.ts b/ludos/backend/src/dtos/rating/request/edit.dto.ts index 306c8a27..2be43e2c 100644 --- a/ludos/backend/src/dtos/rating/request/edit.dto.ts +++ b/ludos/backend/src/dtos/rating/request/edit.dto.ts @@ -6,4 +6,4 @@ export class RatingEditDto { @Min(0.0) @Max(5.0) rating: number; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/rating/response/create.dto.ts b/ludos/backend/src/dtos/rating/response/create.dto.ts index 4ce823b7..a609bc2d 100644 --- a/ludos/backend/src/dtos/rating/response/create.dto.ts +++ b/ludos/backend/src/dtos/rating/response/create.dto.ts @@ -15,4 +15,4 @@ export class RatingCreateResponseDto { @ApiProperty() gameId: string; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/rating/response/delete.dto.ts b/ludos/backend/src/dtos/rating/response/delete.dto.ts index d77cc32d..01e62cb5 100644 --- a/ludos/backend/src/dtos/rating/response/delete.dto.ts +++ b/ludos/backend/src/dtos/rating/response/delete.dto.ts @@ -8,4 +8,4 @@ export class RatingDeleteResponseDto { example: 'Rating is deleted!', }) message: string; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/rating/response/edit.dto.ts b/ludos/backend/src/dtos/rating/response/edit.dto.ts index 6680fa5d..8ad2980a 100644 --- a/ludos/backend/src/dtos/rating/response/edit.dto.ts +++ b/ludos/backend/src/dtos/rating/response/edit.dto.ts @@ -9,4 +9,4 @@ export class RatingEditResponseDto { @ApiProperty() updatedAt: Date; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/rating/response/list.response.ts b/ludos/backend/src/dtos/rating/response/list.response.ts index 3ee3c32a..6ec2f0b5 100644 --- a/ludos/backend/src/dtos/rating/response/list.response.ts +++ b/ludos/backend/src/dtos/rating/response/list.response.ts @@ -7,7 +7,7 @@ export class RatingListResponseDto { @ApiProperty() game: Game; - + @ApiProperty() rating: number; } diff --git a/ludos/backend/src/dtos/review/request/edit.dto.ts b/ludos/backend/src/dtos/review/request/edit.dto.ts index 24af2b17..c6fed6ed 100644 --- a/ludos/backend/src/dtos/review/request/edit.dto.ts +++ b/ludos/backend/src/dtos/review/request/edit.dto.ts @@ -1,11 +1,10 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsOptional, IsString, IsNumber, Min, Max} from 'class-validator'; +import { IsOptional, IsString, IsNumber, Min, Max } from 'class-validator'; export class ReviewEditDto { @ApiProperty({ example: 'This game is amazing!', }) - @IsOptional() @IsString() content: string; @@ -13,10 +12,9 @@ export class ReviewEditDto { @ApiProperty({ example: 4.5, }) - @IsOptional() @IsNumber() @Min(0.0) @Max(5.0) rating: number; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/review/response/create.dto.ts b/ludos/backend/src/dtos/review/response/create.dto.ts index f887b83c..0e1ae2f4 100644 --- a/ludos/backend/src/dtos/review/response/create.dto.ts +++ b/ludos/backend/src/dtos/review/response/create.dto.ts @@ -18,4 +18,4 @@ export class ReviewCreateResponseDto { @ApiProperty() gameId: string; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/review/response/edit.dto.ts b/ludos/backend/src/dtos/review/response/edit.dto.ts index 76727391..b4cee99f 100644 --- a/ludos/backend/src/dtos/review/response/edit.dto.ts +++ b/ludos/backend/src/dtos/review/response/edit.dto.ts @@ -12,4 +12,4 @@ export class ReviewEditResponseDto { @ApiProperty() updatedAt: Date; -} \ No newline at end of file +} diff --git a/ludos/backend/src/dtos/user/request/get-user-info.dto.ts b/ludos/backend/src/dtos/user/request/get-user-info.dto.ts index 8fac37d5..7b42a590 100644 --- a/ludos/backend/src/dtos/user/request/get-user-info.dto.ts +++ b/ludos/backend/src/dtos/user/request/get-user-info.dto.ts @@ -2,9 +2,9 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; export class GetUserInfoDto { - @ApiProperty({ - example: '1', - }) - @IsString() - id: string; + @ApiProperty({ + example: '1', + }) + @IsString() + id: string; } diff --git a/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts b/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts index 4e1503f0..d68ea66f 100644 --- a/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts +++ b/ludos/backend/src/dtos/user/response/get-user-info-response.dto.ts @@ -1,28 +1,27 @@ import { ApiProperty } from '@nestjs/swagger'; import { Game } from '../../../entities/game.entity'; -import { UserType } from '../../../enums/user-type.enum' +import { UserType } from '../../../enums/user-type.enum'; import { Rating } from 'entities/rating.entity'; export class GetUserInfoResponseDto { - @ApiProperty() - username: string; - @ApiProperty() - email: string; - @ApiProperty() - followedGames: Game[]; - @ApiProperty() - ratings: Rating[]; - @ApiProperty() - isNotificationEnabled: boolean; - @ApiProperty() - userType: UserType; - @ApiProperty() - fullName: string; - @ApiProperty() - avatar: string; - @ApiProperty() - aboutMe: string; - @ApiProperty() - steamUrl: string; - + @ApiProperty() + username: string; + @ApiProperty() + email: string; + @ApiProperty() + followedGames: Game[]; + @ApiProperty() + ratings: Rating[]; + @ApiProperty() + isNotificationEnabled: boolean; + @ApiProperty() + userType: UserType; + @ApiProperty() + fullName: string; + @ApiProperty() + avatar: string; + @ApiProperty() + aboutMe: string; + @ApiProperty() + steamUrl: string; } diff --git a/ludos/backend/src/entities/comment.entity.ts b/ludos/backend/src/entities/comment.entity.ts index ff7a8a6b..34419d7d 100644 --- a/ludos/backend/src/entities/comment.entity.ts +++ b/ludos/backend/src/entities/comment.entity.ts @@ -13,7 +13,7 @@ export class Comment { @PrimaryGeneratedColumn('uuid') id: string; - @ManyToOne(() => User, {eager: true}) + @ManyToOne(() => User, { eager: true }) author: User; @Column() @@ -25,14 +25,14 @@ export class Comment { @Column() text: string; - @ManyToMany('User', {eager: true}) + @ManyToMany('User', { eager: true }) @JoinTable({ name: 'comment_user_likes' }) - likedUsers: User[] + likedUsers: User[]; - @ManyToMany('User', {eager: true}) + @ManyToMany('User', { eager: true }) @JoinTable({ name: 'comment_user_dislikes' }) dislikedUsers: User[]; - @Column({default: false}) + @Column({ default: false }) edited: boolean; } diff --git a/ludos/backend/src/entities/game.entity.ts b/ludos/backend/src/entities/game.entity.ts index 2602df63..f75adf90 100644 --- a/ludos/backend/src/entities/game.entity.ts +++ b/ludos/backend/src/entities/game.entity.ts @@ -23,11 +23,11 @@ export class Game { coverLink: string; @VirtualColumn({ - query: (alias) => `SELECT AVG(rating) FROM ratings WHERE "gameId" = ${alias}.id`, + query: (alias) => + `SELECT AVG(rating) FROM ratings WHERE "gameId" = ${alias}.id`, }) averageRating: number; - userRating: number; @Column('jsonb') diff --git a/ludos/backend/src/entities/rating.entity.ts b/ludos/backend/src/entities/rating.entity.ts index 591820c8..efd56412 100644 --- a/ludos/backend/src/entities/rating.entity.ts +++ b/ludos/backend/src/entities/rating.entity.ts @@ -5,13 +5,13 @@ import { PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, - Unique + Unique, } from 'typeorm'; import { Game } from './game.entity'; import { User } from './user.entity'; @Entity('ratings') -@Unique(["game", "user"]) +@Unique(['game', 'user']) export class Rating { @PrimaryGeneratedColumn('uuid') id: string; @@ -19,15 +19,22 @@ export class Rating { @Column('float') rating: number; - @ManyToOne(() => Game, game => game.ratingList, { cascade: true }) + @ManyToOne(() => Game, (game) => game.ratingList, { cascade: true }) game: Game; - @ManyToOne(() => User, user => user.ratingList, { cascade: true }) + @ManyToOne(() => User, (user) => user.ratingList, { cascade: true }) user: User; - @CreateDateColumn({type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' }) + @CreateDateColumn({ + type: 'timestamp', + default: () => 'CURRENT_TIMESTAMP(6)', + }) createdAt: Date; - @UpdateDateColumn({ type: 'timestamp',default: () => 'CURRENT_TIMESTAMP(6)', onUpdate: 'CURRENT_TIMESTAMP(6)' }) + @UpdateDateColumn({ + type: 'timestamp', + default: () => 'CURRENT_TIMESTAMP(6)', + onUpdate: 'CURRENT_TIMESTAMP(6)', + }) updatedAt: Date; } diff --git a/ludos/backend/src/entities/review.entity.ts b/ludos/backend/src/entities/review.entity.ts index b8c5fd66..15fbe095 100644 --- a/ludos/backend/src/entities/review.entity.ts +++ b/ludos/backend/src/entities/review.entity.ts @@ -1,45 +1,51 @@ import { - Column, - Entity, - ManyToOne, - PrimaryGeneratedColumn, - CreateDateColumn, - ManyToMany, - JoinTable, - UpdateDateColumn, - } from 'typeorm'; - import { Game } from './game.entity'; - import { User } from './user.entity'; - - @Entity('reviews') - export class Review { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column('text') - content: string; - - @Column('float') - rating: number; - - @ManyToOne(() => Game, game => game.reviews, { cascade: true }) - game: Game; - - @ManyToOne(() => User, user => user.reviews, { cascade: true }) - user: User; - - @ManyToMany('User', 'likedReviews') - @JoinTable({ name: 'review_user_likes' }) - likedUsers: User[] - - @ManyToMany('User', 'dislikedReviews') - @JoinTable({ name: 'review_user_dislikes' }) - dislikedUsers: User[]; - - @CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)' }) - createdAt: Date; - - @UpdateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP(6)', onUpdate: 'CURRENT_TIMESTAMP(6)' }) - updatedAt: Date; - } - \ No newline at end of file + Column, + Entity, + ManyToOne, + PrimaryGeneratedColumn, + CreateDateColumn, + ManyToMany, + JoinTable, + UpdateDateColumn, +} from 'typeorm'; +import { Game } from './game.entity'; +import { User } from './user.entity'; + +@Entity('reviews') +export class Review { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column('text') + content: string; + + @Column('float') + rating: number; + + @ManyToOne(() => Game, (game) => game.reviews, { cascade: true }) + game: Game; + + @ManyToOne(() => User, (user) => user.reviews, { cascade: true }) + user: User; + + @ManyToMany('User', 'likedReviews') + @JoinTable({ name: 'review_user_likes' }) + likedUsers: User[]; + + @ManyToMany('User', 'dislikedReviews') + @JoinTable({ name: 'review_user_dislikes' }) + dislikedUsers: User[]; + + @CreateDateColumn({ + type: 'timestamp', + default: () => 'CURRENT_TIMESTAMP(6)', + }) + createdAt: Date; + + @UpdateDateColumn({ + type: 'timestamp', + default: () => 'CURRENT_TIMESTAMP(6)', + onUpdate: 'CURRENT_TIMESTAMP(6)', + }) + updatedAt: Date; +} diff --git a/ludos/backend/src/entities/user.entity.ts b/ludos/backend/src/entities/user.entity.ts index 282855dd..3178d604 100644 --- a/ludos/backend/src/entities/user.entity.ts +++ b/ludos/backend/src/entities/user.entity.ts @@ -61,7 +61,7 @@ export class User { @ManyToMany('Review', 'dislikedUsers') dislikedReviews: Review[]; - + @BeforeInsert() @BeforeUpdate() async hashPasswordBeforeInsertAndUpdate() { diff --git a/ludos/backend/src/repositories/comment.repository.ts b/ludos/backend/src/repositories/comment.repository.ts index ffa7cad1..880cc004 100644 --- a/ludos/backend/src/repositories/comment.repository.ts +++ b/ludos/backend/src/repositories/comment.repository.ts @@ -9,7 +9,7 @@ export class CommentRepository extends Repository { } public async createComment(input: Partial): Promise { - let comment = this.create(input); + const comment = this.create(input); await this.insert(comment); return comment; } @@ -18,15 +18,15 @@ export class CommentRepository extends Repository { return this.findOneBy({ id }); //return this.findOne({ where: {id}, relations: {likedUsers: true, dislikedUsers: true} }); } - + public async deleteComment(commentId: string) { - console.log("delete", commentId); - let x = await this.delete({id: commentId}); + console.log('delete', commentId); + const x = await this.delete({ id: commentId }); console.log(x); } public async editComment(commentId: string, newText: string) { - let comment = await this.findCommentById(commentId); + const comment = await this.findCommentById(commentId); comment.text = newText; comment.edited = true; await this.save(comment); @@ -35,5 +35,4 @@ export class CommentRepository extends Repository { public findCommentsByParent(parentId: string): Promise { return this.findBy({ parentId }); } - } diff --git a/ludos/backend/src/repositories/rating.repository.ts b/ludos/backend/src/repositories/rating.repository.ts index 6b54f698..78fc2518 100644 --- a/ludos/backend/src/repositories/rating.repository.ts +++ b/ludos/backend/src/repositories/rating.repository.ts @@ -18,8 +18,11 @@ export class RatingRepository extends Repository { return this.findOneBy({ id }); } - public findRatingByUserIdAndGameId(userId: string, gameId: string): Promise { - return this.findOneBy({ game: { id: gameId }, user: { id: userId} }); + public findRatingByUserIdAndGameId( + userId: string, + gameId: string, + ): Promise { + return this.findOneBy({ game: { id: gameId }, user: { id: userId } }); } public async updateRating(input: Partial): Promise { @@ -28,7 +31,6 @@ export class RatingRepository extends Repository { } public async deleteRating(userId: string, gameId: string): Promise { - await this.delete({user: {id: userId}, game: {id: gameId}}); + await this.delete({ user: { id: userId }, game: { id: gameId } }); } - } diff --git a/ludos/backend/src/repositories/review.repository.ts b/ludos/backend/src/repositories/review.repository.ts index 75e79a44..00296926 100644 --- a/ludos/backend/src/repositories/review.repository.ts +++ b/ludos/backend/src/repositories/review.repository.ts @@ -34,5 +34,4 @@ export class ReviewRepository extends Repository { public async deleteReview(id: string): Promise { await this.delete(id); } - } diff --git a/ludos/backend/src/repositories/user.repository.ts b/ludos/backend/src/repositories/user.repository.ts index 03a40c47..d0f03add 100644 --- a/ludos/backend/src/repositories/user.repository.ts +++ b/ludos/backend/src/repositories/user.repository.ts @@ -21,17 +21,17 @@ export class UserRepository extends Repository { public findUserByEmail(email: string): Promise { return this.findOneBy({ email: email }); } - + public findUserById(id: string): Promise { return this.findOneBy({ id }); } - public async updateUserPassword(input: Partial, newPassword: string) { + public async updateUserPassword(input: Partial, newPassword: string) { const user = await this.findUserByUsername(input.username); user.password = newPassword; await this.save(user); } - + public async findUserByIdWithRelations(id: string): Promise { const user = await this.findOne({ relations: this.getAllRelationsAsList(), diff --git a/ludos/backend/src/services/comment.service.ts b/ludos/backend/src/services/comment.service.ts index 6a2c4d3c..62508bfc 100644 --- a/ludos/backend/src/services/comment.service.ts +++ b/ludos/backend/src/services/comment.service.ts @@ -1,8 +1,4 @@ -import { - HttpException, - HttpStatus, - Injectable, -} from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { WriteCommentDto } from '../dtos/comment/request/write-comment.dto'; import { EditCommentDto } from '../dtos/comment/request/edit-comment.dto'; import { GetCommentResponseDto } from '../dtos/comment/response/get-comment.response.dto'; @@ -14,10 +10,13 @@ export class CommentService { constructor( private readonly userRepository: UserRepository, private readonly commentRepository: CommentRepository, - ) {} - - public async getComment(userId: string, commentId: string): Promise { - let user = await this.userRepository.findUserById(userId); + ) {} + + public async getComment( + userId: string, + commentId: string, + ): Promise { + const user = await this.userRepository.findUserById(userId); if (!user) { throw new HttpException( @@ -26,7 +25,7 @@ export class CommentService { ); } - let comment = await this.commentRepository.findCommentById(commentId); + const comment = await this.commentRepository.findCommentById(commentId); if (!comment) { throw new HttpException( @@ -42,11 +41,14 @@ export class CommentService { parentId: comment.parentId, likeCount: comment.likedUsers.length, dislikeCount: comment.dislikedUsers.length, - } + }; } - public async getCommentsByParent(userId: string, parentId: string): Promise { - let user = await this.userRepository.findUserById(userId); + public async getCommentsByParent( + userId: string, + parentId: string, + ): Promise { + const user = await this.userRepository.findUserById(userId); if (!user) { throw new HttpException( @@ -55,17 +57,19 @@ export class CommentService { ); } - return (await this.commentRepository.findCommentsByParent(parentId)).map((comment) => { - return { - likeCount: comment.likedUsers.length, - dislikeCount: comment.dislikedUsers.length, - ...comment, - } - }); + return (await this.commentRepository.findCommentsByParent(parentId)).map( + (comment) => { + return { + likeCount: comment.likedUsers.length, + dislikeCount: comment.dislikedUsers.length, + ...comment, + }; + }, + ); } public async writeComment(userId: string, writeCommentDto: WriteCommentDto) { - let user = await this.userRepository.findUserById(userId); + const user = await this.userRepository.findUserById(userId); if (!user) { throw new HttpException( @@ -78,7 +82,7 @@ export class CommentService { // post / comment / game review // parent id should be the identifier of one of the above - let comment = { + const comment = { author: user, text: writeCommentDto.text, parentId: writeCommentDto.parentId, @@ -87,12 +91,12 @@ export class CommentService { timestamp: new Date(), likedUsers: [], dislikedUsers: [], - } + }; await this.commentRepository.createComment(comment); } public async likeComment(userId: string, commentId: string) { - let user = await this.userRepository.findUserById(userId); + const user = await this.userRepository.findUserById(userId); if (!user) { throw new HttpException( @@ -101,7 +105,7 @@ export class CommentService { ); } - let comment = await this.commentRepository.findCommentById(commentId); + const comment = await this.commentRepository.findCommentById(commentId); if (!comment) { throw new HttpException( @@ -110,14 +114,18 @@ export class CommentService { ); } - if (comment.likedUsers.find(likedUser => likedUser.id === userId)) { - comment.likedUsers = comment.likedUsers.filter(likedUser => likedUser.id !== userId); - } - else if (comment.dislikedUsers.find(dislikedUser => dislikedUser.id === userId)) { - comment.dislikedUsers = comment.dislikedUsers.filter(dislikedUser => dislikedUser.id !== userId); + if (comment.likedUsers.find((likedUser) => likedUser.id === userId)) { + comment.likedUsers = comment.likedUsers.filter( + (likedUser) => likedUser.id !== userId, + ); + } else if ( + comment.dislikedUsers.find((dislikedUser) => dislikedUser.id === userId) + ) { + comment.dislikedUsers = comment.dislikedUsers.filter( + (dislikedUser) => dislikedUser.id !== userId, + ); comment.likedUsers.push(user); - } - else { + } else { comment.likedUsers.push(user); } @@ -125,7 +133,7 @@ export class CommentService { } public async dislikeComment(userId: string, commentId: string) { - let user = await this.userRepository.findUserById(userId); + const user = await this.userRepository.findUserById(userId); if (!user) { throw new HttpException( @@ -134,7 +142,7 @@ export class CommentService { ); } - let comment = await this.commentRepository.findCommentById(commentId); + const comment = await this.commentRepository.findCommentById(commentId); if (!comment) { throw new HttpException( @@ -143,14 +151,20 @@ export class CommentService { ); } - if (comment.dislikedUsers.find(dislikedUser => dislikedUser.id === userId)) { - comment.dislikedUsers = comment.dislikedUsers.filter(dislikedUser => dislikedUser.id !== userId); - } - else if (comment.likedUsers.find(likedUser => likedUser.id === userId)) { - comment.likedUsers = comment.likedUsers.filter(likedUser => likedUser.id !== userId); + if ( + comment.dislikedUsers.find((dislikedUser) => dislikedUser.id === userId) + ) { + comment.dislikedUsers = comment.dislikedUsers.filter( + (dislikedUser) => dislikedUser.id !== userId, + ); + } else if ( + comment.likedUsers.find((likedUser) => likedUser.id === userId) + ) { + comment.likedUsers = comment.likedUsers.filter( + (likedUser) => likedUser.id !== userId, + ); comment.dislikedUsers.push(user); - } - else { + } else { comment.dislikedUsers.push(user); } @@ -158,7 +172,7 @@ export class CommentService { } public async deleteComment(userId: string, commentId: string) { - let user = await this.userRepository.findUserById(userId); + const user = await this.userRepository.findUserById(userId); if (!user) { throw new HttpException( @@ -167,7 +181,7 @@ export class CommentService { ); } - let comment = await this.commentRepository.findCommentById(commentId); + const comment = await this.commentRepository.findCommentById(commentId); if (!comment) { throw new HttpException( @@ -186,8 +200,12 @@ export class CommentService { await this.commentRepository.deleteComment(commentId); } - public async editComment(userId: string, commentId: string, editCommentDto: EditCommentDto) { - let user = await this.userRepository.findUserById(userId); + public async editComment( + userId: string, + commentId: string, + editCommentDto: EditCommentDto, + ) { + const user = await this.userRepository.findUserById(userId); if (!user) { throw new HttpException( @@ -196,7 +214,7 @@ export class CommentService { ); } - let comment = await this.commentRepository.findCommentById(commentId); + const comment = await this.commentRepository.findCommentById(commentId); if (!comment) { throw new HttpException( diff --git a/ludos/backend/src/services/config/typeorm-config.service.ts b/ludos/backend/src/services/config/typeorm-config.service.ts index a8320c9d..74624836 100644 --- a/ludos/backend/src/services/config/typeorm-config.service.ts +++ b/ludos/backend/src/services/config/typeorm-config.service.ts @@ -20,7 +20,7 @@ export class TypeOrmConfigService implements TypeOrmOptionsFactory { password: this.configService.get('DB_PASSWORD'), port: this.configService.get('DB_PORT'), database: this.configService.get('DB_NAME'), - entities: [User, ResetPassword, Game, Review,Rating, Comment], + entities: [User, ResetPassword, Game, Review, Rating, Comment], synchronize: true, }; } diff --git a/ludos/backend/src/services/game.service.ts b/ludos/backend/src/services/game.service.ts index 97839db2..39b66073 100644 --- a/ludos/backend/src/services/game.service.ts +++ b/ludos/backend/src/services/game.service.ts @@ -17,7 +17,7 @@ export class GameService { constructor( private readonly gameRepository: GameRepository, private readonly userRepository: UserRepository, - private readonly ratingRepository: RatingRepository + private readonly ratingRepository: RatingRepository, ) {} public async createGame( @@ -34,7 +34,7 @@ export class GameService { developer: game.developer, }; } catch (e) { - console.log(e) + console.log(e); if (e.code == '23505') { throw new ConflictException(e.detail); } @@ -50,8 +50,11 @@ export class GameService { game.isFollowed = userId ? game.followerList.some((user) => user.id === userId) : false; - const rating = await this.ratingRepository.findRatingByUserIdAndGameId(userId, id); - game.userRating = (userId && rating) ? rating.rating : null; + const rating = await this.ratingRepository.findRatingByUserIdAndGameId( + userId, + id, + ); + game.userRating = userId && rating ? rating.rating : null; return game; } async followGame(userId: string, gameId: string): Promise { diff --git a/ludos/backend/src/services/rating.service.ts b/ludos/backend/src/services/rating.service.ts index d59f99ce..db763e43 100644 --- a/ludos/backend/src/services/rating.service.ts +++ b/ludos/backend/src/services/rating.service.ts @@ -19,7 +19,7 @@ export class RatingService { private readonly gameRepository: GameRepository, private readonly userRepository: UserRepository, private readonly ratingRepository: RatingRepository, - ) { } + ) {} public async createRating( userId: string, @@ -31,18 +31,17 @@ export class RatingService { if (!user) { throw new NotFoundException('User Not Found!'); - } - else if (!game) { + } else if (!game) { throw new NotFoundException('Game Not Found!'); } - try{ + try { const rating = await this.ratingRepository.createRating({ rating: ratingCreateDto.rating, user: user, - game: game + game: game, }); - + return { id: rating.id, rating: rating.rating, @@ -50,16 +49,21 @@ export class RatingService { gameId: rating.game.id, createdAt: rating.createdAt, }; - } catch(e) { - if(e.code == "23505") { - throw new ConflictException("Rating already exists"); + } catch (e) { + if (e.code == '23505') { + throw new ConflictException('Rating already exists'); } } - } - public async deleteRating(userId: string, gameId: string): Promise { - const rating = await this.ratingRepository.findRatingByUserIdAndGameId(userId, gameId); + public async deleteRating( + userId: string, + gameId: string, + ): Promise { + const rating = await this.ratingRepository.findRatingByUserIdAndGameId( + userId, + gameId, + ); if (!rating) { throw new NotFoundException('Rating Not Found!'); } @@ -73,7 +77,7 @@ export class RatingService { return { id: rating.id, - message: "Rating is deleted!" + message: 'Rating is deleted!', }; } @@ -83,7 +87,10 @@ export class RatingService { ratingEditDto: RatingEditDto, ): Promise { try { - const rating = await this.ratingRepository.findRatingByUserIdAndGameId(userId, gameId); + const rating = await this.ratingRepository.findRatingByUserIdAndGameId( + userId, + gameId, + ); if (!rating) { throw new NotFoundException('Rating Not Found!'); } @@ -94,12 +101,13 @@ export class RatingService { } if (!ratingEditDto.rating) { - throw new NotFoundException('Please provide at least one field to update!'); + throw new NotFoundException( + 'Please provide at least one field to update!', + ); } rating.rating = ratingEditDto.rating; - await this.ratingRepository.updateRating(rating); return { @@ -107,7 +115,6 @@ export class RatingService { rating: ratingEditDto.rating, updatedAt: rating.updatedAt, }; - } catch (e) { if (e instanceof NotFoundException) { throw e; @@ -117,5 +124,4 @@ export class RatingService { } } } - } diff --git a/ludos/backend/src/services/review.service.ts b/ludos/backend/src/services/review.service.ts index 6fe096b8..7ba54f49 100644 --- a/ludos/backend/src/services/review.service.ts +++ b/ludos/backend/src/services/review.service.ts @@ -1,200 +1,195 @@ import { - ConflictException, - Injectable, - InternalServerErrorException, - NotFoundException, - } from '@nestjs/common'; - import { GameRepository } from '../repositories/game.repository'; - import { UserRepository } from '../repositories/user.repository'; - import { ReviewCreateDto } from '../dtos/review/request/create.dto'; - import { ReviewCreateResponseDto } from '../dtos/review/response/create.dto'; - import { ReviewRepository } from '../repositories/review.repository'; - import { log } from 'console'; - import { ReviewEditDto } from '../dtos/review/request/edit.dto'; + ConflictException, + Injectable, + InternalServerErrorException, + NotFoundException, +} from '@nestjs/common'; +import { GameRepository } from '../repositories/game.repository'; +import { UserRepository } from '../repositories/user.repository'; +import { ReviewCreateDto } from '../dtos/review/request/create.dto'; +import { ReviewCreateResponseDto } from '../dtos/review/response/create.dto'; +import { ReviewRepository } from '../repositories/review.repository'; +import { log } from 'console'; +import { ReviewEditDto } from '../dtos/review/request/edit.dto'; import { ReviewEditResponseDto } from '../dtos/review/response/edit.dto'; - - @Injectable() - export class ReviewService { - constructor( - private readonly gameRepository: GameRepository, - private readonly userRepository: UserRepository, - private readonly reviewRepository: ReviewRepository, - ) {} - - public async createReview( - userId: string, - gameId: string, - reviewCreateDto: ReviewCreateDto, - ): Promise { - try { - const user = await this.userRepository.findUserById(userId); - const game = await this.gameRepository.findGameById(gameId); - const review = await this.reviewRepository.createReview({ - content: reviewCreateDto.content, - rating: reviewCreateDto.rating, - user: user, - game: game - }); - - console.log(game.reviews); - return { - id: review.id, - rating: review.rating, - content: review.content, - userId: review.user.id, - gameId: review.game.id, - createdAt: review.createdAt, - }; - } catch (e) { - console.log(e) - if (e.code == '23505') { - throw new ConflictException(e.detail); - } - throw new InternalServerErrorException(); + +@Injectable() +export class ReviewService { + constructor( + private readonly gameRepository: GameRepository, + private readonly userRepository: UserRepository, + private readonly reviewRepository: ReviewRepository, + ) {} + + public async createReview( + userId: string, + gameId: string, + reviewCreateDto: ReviewCreateDto, + ): Promise { + try { + const user = await this.userRepository.findUserById(userId); + const game = await this.gameRepository.findGameById(gameId); + const review = await this.reviewRepository.createReview({ + content: reviewCreateDto.content, + rating: reviewCreateDto.rating, + user: user, + game: game, + }); + + console.log(game.reviews); + return { + id: review.id, + rating: review.rating, + content: review.content, + userId: review.user.id, + gameId: review.game.id, + createdAt: review.createdAt, + }; + } catch (e) { + console.log(e); + if (e.code == '23505') { + throw new ConflictException(e.detail); } + throw new InternalServerErrorException(); } + } + public async likeReview(userId: string, reviewId: string): Promise { + try { + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } - public async likeReview(userId: string, reviewId: string): Promise { - try { - const user = await this.userRepository.findUserById(userId); - if (!user) { - throw new NotFoundException('User Not Found!'); - } - - const review = await this.reviewRepository.findReviewByIdWithLikedUsers(reviewId); - if (!review) { - throw new NotFoundException('Review Not Found!'); - } - - if (review.likedUsers.find(likedUser => likedUser.id === userId)) { - log("User already liked the review."); - review.likedUsers = review.likedUsers.filter(likedUser => likedUser.id !== userId); - } - else { - review.likedUsers.push(user); - } - - await this.reviewRepository.updateReview(review); - - } catch (e) { - if (e instanceof NotFoundException) { - throw e; - } else { - console.log(e); - throw new InternalServerErrorException(); - } + const review = + await this.reviewRepository.findReviewByIdWithLikedUsers(reviewId); + if (!review) { + throw new NotFoundException('Review Not Found!'); } - } + if (review.likedUsers.find((likedUser) => likedUser.id === userId)) { + log('User already liked the review.'); + review.likedUsers = review.likedUsers.filter( + (likedUser) => likedUser.id !== userId, + ); + } else { + review.likedUsers.push(user); + } - public async dislikeReview(userId: string, reviewId: string): Promise { - try { - const user = await this.userRepository.findUserById(userId); - if (!user) { - throw new NotFoundException('User Not Found!'); - } - - const review = await this.reviewRepository.findReviewByIdWithDislikedUsers(reviewId); - if (!review) { - throw new NotFoundException('Review Not Found!'); - } - - if (review.dislikedUsers.find(dislikedUser => dislikedUser.id === userId)) { - log('User has already disliked the review.'); - review.dislikedUsers = review.dislikedUsers.filter(dislikedUser => dislikedUser.id !== userId); - } - - else { - review.dislikedUsers.push(user); - } - - await this.reviewRepository.updateReview(review); - - } catch (e) { - if (e instanceof NotFoundException) { - throw e; - } else { - console.log(e); - throw new InternalServerErrorException(); - } + await this.reviewRepository.updateReview(review); + } catch (e) { + if (e instanceof NotFoundException) { + throw e; + } else { + console.log(e); + throw new InternalServerErrorException(); } } + } - public async deleteReview(userId: string, reviewId: string): Promise { - try { - - const review = await this.reviewRepository.findReviewById(reviewId); - if (!review) { - throw new NotFoundException('Review Not Found!'); - } - - const user = await this.userRepository.findUserById(userId); - if (!user) { - throw new NotFoundException('User Not Found!'); - } - - await this.reviewRepository.deleteReview(reviewId); - - } catch (e) { - if (e instanceof NotFoundException) { - throw e; - } else { - console.log(e); - throw new InternalServerErrorException(); - } + public async dislikeReview(userId: string, reviewId: string): Promise { + try { + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } + + const review = + await this.reviewRepository.findReviewByIdWithDislikedUsers(reviewId); + if (!review) { + throw new NotFoundException('Review Not Found!'); + } + + if ( + review.dislikedUsers.find((dislikedUser) => dislikedUser.id === userId) + ) { + log('User has already disliked the review.'); + review.dislikedUsers = review.dislikedUsers.filter( + (dislikedUser) => dislikedUser.id !== userId, + ); + } else { + review.dislikedUsers.push(user); + } + + await this.reviewRepository.updateReview(review); + } catch (e) { + if (e instanceof NotFoundException) { + throw e; + } else { + console.log(e); + throw new InternalServerErrorException(); } } + } + + public async deleteReview(userId: string, reviewId: string): Promise { + try { + const review = await this.reviewRepository.findReviewById(reviewId); + if (!review) { + throw new NotFoundException('Review Not Found!'); + } + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } - public async editReview( - userId: string, - reviewId: string, - reviewEditDto: ReviewEditDto, - ): Promise { - try { - const review = await this.reviewRepository.findReviewById(reviewId); - if (!review) { - throw new NotFoundException('Review Not Found!'); - } - - const user = await this.userRepository.findUserById(userId); - if (!user) { - throw new NotFoundException('User Not Found!'); - } - - if (!reviewEditDto.content && !reviewEditDto.rating) { - throw new NotFoundException('Please provide at least one field to update!'); - } - - if (reviewEditDto.content) { - review.content = reviewEditDto.content; - } - - if (reviewEditDto.rating) { - review.rating = reviewEditDto.rating; - } - - await this.reviewRepository.updateReview(review); - - return { - id: review.id, - content: reviewEditDto.content, - rating: reviewEditDto.rating, - updatedAt: review.updatedAt, - }; - - - } catch (e) { - if (e instanceof NotFoundException) { - throw e; - } else { - console.log(e); - throw new InternalServerErrorException(); - } + await this.reviewRepository.deleteReview(reviewId); + } catch (e) { + if (e instanceof NotFoundException) { + throw e; + } else { + console.log(e); + throw new InternalServerErrorException(); } } - + } + public async editReview( + userId: string, + reviewId: string, + reviewEditDto: ReviewEditDto, + ): Promise { + try { + const review = await this.reviewRepository.findReviewById(reviewId); + if (!review) { + throw new NotFoundException('Review Not Found!'); + } + + const user = await this.userRepository.findUserById(userId); + if (!user) { + throw new NotFoundException('User Not Found!'); + } + + if (!reviewEditDto.content && !reviewEditDto.rating) { + throw new NotFoundException( + 'Please provide at least one field to update!', + ); + } + + if (reviewEditDto.content) { + review.content = reviewEditDto.content; + } + + if (reviewEditDto.rating) { + review.rating = reviewEditDto.rating; + } + + await this.reviewRepository.updateReview(review); + + return { + id: review.id, + content: reviewEditDto.content, + rating: reviewEditDto.rating, + updatedAt: review.updatedAt, + }; + } catch (e) { + if (e instanceof NotFoundException) { + throw e; + } else { + console.log(e); + throw new InternalServerErrorException(); + } + } } - \ No newline at end of file +} diff --git a/ludos/backend/src/services/s3.service.ts b/ludos/backend/src/services/s3.service.ts index 6c486ecf..2fc45489 100644 --- a/ludos/backend/src/services/s3.service.ts +++ b/ludos/backend/src/services/s3.service.ts @@ -1,18 +1,16 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { S3 } from "@aws-sdk/client-s3"; -import { Upload } from "@aws-sdk/lib-storage"; +import { S3 } from '@aws-sdk/client-s3'; +import { Upload } from '@aws-sdk/lib-storage'; import { createReadStream } from 'fs'; import { UploadResponseDto } from '../dtos/s3/response/upload-response.dto'; @Injectable() export class S3Service { - constructor( - private readonly configService: ConfigService, - ) {} + constructor(private readonly configService: ConfigService) {} public initializeS3(): S3 { - let s3 = new S3({ + const s3 = new S3({ credentials: { accessKeyId: this.configService.get('AWS_ACCESS_KEY'), secretAccessKey: this.configService.get('AWS_SECRET_KEY'), @@ -22,22 +20,24 @@ export class S3Service { return s3; } - public async uploadFile(file: Express.Multer.File): Promise { - let s3 = this.initializeS3(); - let stream = createReadStream(file.path); - let uploadParams = { + public async uploadFile( + file: Express.Multer.File, + ): Promise { + const s3 = this.initializeS3(); + const stream = createReadStream(file.path); + const uploadParams = { Bucket: this.configService.get('AWS_BUCKET_NAME'), Body: stream, Key: file.filename, - } - let d = await new Upload({ + }; + const d = await new Upload({ client: s3, - params: uploadParams + params: uploadParams, }).done(); - if ("Location" in d) { + if ('Location' in d) { return { url: d.Location, - } + }; } return; } diff --git a/ludos/backend/src/services/user.service.ts b/ludos/backend/src/services/user.service.ts index 6bc6d08e..b89cc6f4 100644 --- a/ludos/backend/src/services/user.service.ts +++ b/ludos/backend/src/services/user.service.ts @@ -32,7 +32,7 @@ export class UserService { private readonly resetPasswordRepository: ResetPasswordRepository, private readonly jwtService: JwtService, private readonly configService: ConfigService, - ) { } + ) {} public async register(input: RegisterDto): Promise { try { diff --git a/practice-app/.DS_Store b/practice-app/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5cfd0b70348d2355e3e322cf377187cd96c47efc GIT binary patch literal 6148 zcmeHKy-veG4EB|%3POku42(B1BJ~B}sL~y&FM!%2KuSo&-<PwKKtT)iQ<@u+~jFFCK?e@3S}JZ!u%k-&f1fKdU(;rJ?`i+zaGyPWl{B_ z&G8=@;Ac0a1?6;0SJZue`_r%PdQoQCJTK?)r*9vp@5j~Wc|T_PYfN?1cux*4um$RP zKn?fw%(nF&B`1g7^=2I}uV?Ez8haGwx{ggTtvZH*>o73}i~(a{+ZjO5W=Z!1%^Cy7 zfHAOQfWHqO$`}-D!T9Nbi6a0ofjbJ0d6wWDuNV|-L99TWqyi4@PZ9e%HIL9rH; zbaFa;IK8sd3B|>$V}BpR$pt~P#(*)j%v xTYERhz1Bl7p)4F%3$9Xd5L+=~xfLHlqrmU^02mZ&L3kkcBM@mYV+{N$1K)pyO(FmQ literal 0 HcmV?d00001