├── .eslintrc.js ├── .example.env ├── .gitignore ├── Graphql.postman_collection.json-OpenApi3Json.json ├── LICENSE ├── README.md ├── app.js ├── controllers ├── authController.js ├── clubController.js └── userController.js ├── database └── conn.js ├── graphql ├── resolvers │ └── index.js └── schema │ └── index.js ├── middleware ├── 404ErrorHandler.js └── jwtAuth.js ├── models ├── club.js └── user.js ├── npm.txt ├── package-lock.json ├── package.json ├── routes ├── admin │ ├── index.js │ └── userRoute.js ├── auth │ ├── authRoute.js │ └── index.js ├── common │ ├── bookRoute.js │ └── index.js └── index.js ├── seeders └── seed.js └── utils ├── errorHandler.js ├── responseHandler.js ├── validateRequest.js └── validation ├── bookValidation.js ├── index.js ├── loginUserValidation.js ├── registerUserValidation.js ├── updateBookValidation.js └── updateUserValidation.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es2021: true 6 | }, 7 | extends: [ 8 | 'standard' 9 | ], 10 | parserOptions: { 11 | ecmaVersion: 'latest' 12 | }, 13 | rules: { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.example.env: -------------------------------------------------------------------------------- 1 | MONGO_URL= 2 | ADMIN_EMAIL= 3 | ADMIN_PASSWORD= 4 | ALLOW_ORIGIN= 5 | SECRET_KEY= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .idea 18 | .vscode 19 | 20 | # Directory for instrumented libs generated by jscoverage/JSCover 21 | lib-cov 22 | 23 | # Coverage directory used by tools like istanbul 24 | coverage 25 | *.lcov 26 | 27 | # nyc test.json coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | jspm_packages/ 45 | 46 | # TypeScript v1 declaration files 47 | typings/ 48 | 49 | # TypeScript cache 50 | *.tsbuildinfo 51 | 52 | # Optional npm cache directory 53 | .npm 54 | 55 | # Optional eslint cache 56 | .eslintcache 57 | 58 | # Microbundle cache 59 | .rpt2_cache/ 60 | .rts2_cache_cjs/ 61 | .rts2_cache_es/ 62 | .rts2_cache_umd/ 63 | 64 | # Optional REPL history 65 | .node_repl_history 66 | 67 | # Output of 'npm pack' 68 | *.tgz 69 | 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | # dotenv environment variables file 74 | .env 75 | .env.test 76 | 77 | # parcel-bundler cache (https://parceljs.org/) 78 | .cache 79 | 80 | # Next.js build output 81 | .next 82 | 83 | # Nuxt.js build / generate output 84 | .nuxt 85 | dist 86 | 87 | # Gatsby files 88 | .cache/ 89 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 90 | # https://nextjs.org/blog/next-9-1#public-directory-support 91 | # public 92 | 93 | # vuepress build output 94 | .vuepress/dist 95 | 96 | # Serverless directories 97 | .serverless/ 98 | 99 | # FuseBox cache 100 | .fusebox/ 101 | 102 | # DynamoDB Local files 103 | .dynamodb/ 104 | 105 | # TernJS port file 106 | .tern-port 107 | -------------------------------------------------------------------------------- /Graphql.postman_collection.json-OpenApi3Json.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "Graphql", 5 | "contact": {}, 6 | "version": "1.0" 7 | }, 8 | "servers": [ 9 | { 10 | "url": "http://localhost:3000", 11 | "variables": {} 12 | } 13 | ], 14 | "paths": { 15 | "/graphql": { 16 | "post": { 17 | "tags": [ 18 | "CLUB" 19 | ], 20 | "summary": "CREATE", 21 | "operationId": "CREATE", 22 | "parameters": [], 23 | "responses": { 24 | "200": { 25 | "description": "", 26 | "headers": {} 27 | } 28 | }, 29 | "deprecated": false 30 | }, 31 | "get": { 32 | "tags": [ 33 | "CLUB" 34 | ], 35 | "summary": "READ ALL", 36 | "operationId": "READALL", 37 | "parameters": [], 38 | "responses": { 39 | "200": { 40 | "description": "", 41 | "headers": {} 42 | } 43 | }, 44 | "deprecated": false 45 | } 46 | } 47 | }, 48 | "components": { 49 | "securitySchemes": { 50 | "bearer": { 51 | "type": "http", 52 | "scheme": "bearer" 53 | } 54 | } 55 | }, 56 | "security": [ 57 | { 58 | "bearer": [] 59 | } 60 | ], 61 | "tags": [ 62 | { 63 | "name": "CLUB" 64 | }, 65 | { 66 | "name": "AUTH" 67 | } 68 | ] 69 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 axit vadi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GraphQL 2 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | require('./database/conn') 3 | const express = require('express') 4 | const app = express() 5 | const { graphqlHTTP } = require('express-graphql') 6 | const PORT = process.env.PORT || 3000 7 | const swaggerUi = require('swagger-ui-express') 8 | const cors = require('cors') 9 | const { seedAdmin } = require('./seeders/seed') 10 | const { errorHandler } = require('./utils/errorHandler') 11 | const graphQlSchema = require('./graphql/schema/index') 12 | const graphQlResolvers = require('./graphql/resolvers/index') 13 | const corsOptions = { origin: process.env.ALLOW_ORIGIN } 14 | const jwtAuth = require('./middleware/jwtAuth') 15 | const notFound = require('./middleware/404ErrorHandler') 16 | const swaggerDocument = require('./Graphql.postman_collection.json-OpenApi3Json.json') 17 | app.use(cors(corsOptions)) 18 | 19 | app.use(express.json()) 20 | app.use(express.urlencoded({ extended: false })) 21 | 22 | /* for redirect to graphql */ 23 | app.get('/', (req, res, next) => { res.redirect('/graphql') }) 24 | 25 | /* for swagger */ 26 | app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)) 27 | 28 | app.use(jwtAuth) 29 | 30 | app.use('/graphql', graphqlHTTP({ 31 | schema: graphQlSchema, 32 | rootValue: graphQlResolvers, 33 | graphiql: true // for visual ui interface 34 | })) 35 | 36 | seedAdmin() 37 | app.use(notFound) 38 | app.use(errorHandler) 39 | 40 | app.listen(PORT, () => { 41 | console.log(`Server running on http://localhost:${PORT}`) 42 | }) 43 | -------------------------------------------------------------------------------- /controllers/authController.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/user') 2 | const bcrypt = require('bcryptjs') 3 | const jwt = require('jsonwebtoken') 4 | 5 | exports.login = async ({ authInput }) => { 6 | const { email, password } = authInput 7 | 8 | const user = await User.findOne({ email }) 9 | if (!user) { 10 | const error = new Error('Invalid username or password.') 11 | error.statusCode = 422 12 | throw error 13 | } 14 | 15 | const isEqual = await bcrypt.compare(password, user.password) 16 | 17 | if (!isEqual) { 18 | const error = new Error('Invalid username or password.') 19 | error.statusCode = 422 20 | throw error 21 | } 22 | 23 | const token = jwt.sign( 24 | { 25 | id: user._id, 26 | email: user.email 27 | }, 28 | process.env.SECRET_KEY, 29 | { expiresIn: '12h' } 30 | ) 31 | 32 | return { 33 | token, 34 | user: { 35 | id: user._id, 36 | first_name: user.first_name, 37 | last_name: user.last_name, 38 | email: user.email 39 | }, 40 | message: 'user successfully login' 41 | } 42 | } 43 | 44 | exports.registerUser = async ({ userInput }, req) => { 45 | const hashPassword = await bcrypt.hash(userInput.password, 12) 46 | const user = { 47 | first_name: userInput.first_name, 48 | last_name: userInput.last_name, 49 | address: userInput.address, 50 | email: userInput.email, 51 | phone: userInput.phone, 52 | password: hashPassword 53 | } 54 | const newUser = await User.create(user) 55 | return newUser._doc 56 | } 57 | -------------------------------------------------------------------------------- /controllers/clubController.js: -------------------------------------------------------------------------------- 1 | const Club = require('../models/club') 2 | 3 | exports.clubs = async (_, req) => { 4 | const user = req.user 5 | if (!user) { 6 | const error = new Error('you are not authorize!') 7 | error.statusCode = 401 8 | throw error 9 | } 10 | return Club.find().lean() 11 | } 12 | 13 | exports.createClub = async ({ clubInput }, req) => { 14 | const user = req.user 15 | if (!user) { 16 | const error = new Error('you are not authorize !') 17 | error.statusCode = 401 18 | throw error 19 | } 20 | const newClub = await Club.create(clubInput) 21 | return newClub._doc 22 | } 23 | 24 | exports.updateClub = async ({ id, clubUpdateInput }, req) => { 25 | const updatedClub = await Club.findByIdAndUpdate(id, clubUpdateInput, { new: true }) 26 | 27 | if (!updatedClub) { 28 | const error = new Error('Could not find a club') 29 | error.statusCode = 422 30 | throw error 31 | } 32 | 33 | return updatedClub 34 | } 35 | 36 | exports.getClubById = async ({ id }, req) => { 37 | const club = await Club.findById(id) 38 | 39 | if (!club) { 40 | const error = new Error('Could not find a club') 41 | error.statusCode = 422 42 | throw error 43 | } 44 | 45 | return club 46 | } 47 | 48 | exports.deleteClubById = async ({ id }, req) => { 49 | const club = await Club.findByIdAndDelete(id) 50 | 51 | if (!club) { 52 | const error = new Error('Could not find a club') 53 | error.statusCode = 422 54 | throw error 55 | } 56 | 57 | return { 58 | message: 'Successfully deleted club !' 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /controllers/userController.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/user') 2 | const { prepareSuccessResponse } = require('../utils/responseHandler') 3 | 4 | exports.getUser = async (req, res) => { 5 | const id = req.params.id 6 | const user = await User.findById(id).select('-password') 7 | if (!user) { 8 | const error = new Error('User not found.') 9 | error.statusCode = 404 10 | throw error 11 | } 12 | 13 | const result = { 14 | id: user._id, 15 | first_name: user.first_name, 16 | last_name: user.last_name, 17 | email: user.email, 18 | phone: user.phone, 19 | address: user.address 20 | } 21 | 22 | return res 23 | .status(200) 24 | .json(prepareSuccessResponse(result, 'User retrieved successfully.')) 25 | } 26 | 27 | exports.users = async () => { 28 | const users = await User.find().select('-password') 29 | return users.map((user) => { 30 | return { 31 | _id: user._id, 32 | first_name: user.first_name, 33 | last_name: user.last_name, 34 | address: user.address, 35 | email: user.email, 36 | phone: user.phone 37 | } 38 | }) 39 | } 40 | 41 | exports.updateUser = async (req, res) => { 42 | const id = req.params.id 43 | 44 | const preUser = { 45 | first_name: req.body.first_name, 46 | last_name: req.body.last_name, 47 | email: req.body.email, 48 | phone: req.body.phone 49 | } 50 | 51 | const user = await User.findByIdAndUpdate(id, preUser, { new: true }) 52 | if (!user) { 53 | const error = new Error('Could not find user.') 54 | error.statusCode = 404 55 | throw error 56 | } 57 | 58 | const result = { 59 | id: user._id, 60 | first_name: user.first_name, 61 | last_name: user.last_name, 62 | email: user.email, 63 | phone: user.phone, 64 | address: user.address 65 | } 66 | 67 | return res 68 | .status(200) 69 | .json(prepareSuccessResponse(result, 'User updated successfully.')) 70 | } 71 | 72 | exports.deleteUser = async (req, res) => { 73 | const id = req.params.id 74 | const user = await User.findByIdAndRemove(id) 75 | if (!user) { 76 | const error = new Error('Could not find user.') 77 | error.statusCode = 404 78 | throw error 79 | } 80 | return res 81 | .status(200) 82 | .json(prepareSuccessResponse({}, 'User deleted successfully.')) 83 | } 84 | -------------------------------------------------------------------------------- /database/conn.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const mongoUrl = process.env.MONGO_URL 4 | 5 | mongoose 6 | .connect(mongoUrl) 7 | .then(() => { 8 | console.log('Database Connected...') 9 | }) 10 | .catch((err) => { 11 | console.log(err) 12 | }) 13 | -------------------------------------------------------------------------------- /graphql/resolvers/index.js: -------------------------------------------------------------------------------- 1 | const { login, registerUser } = require('../../controllers/authController') 2 | const { users } = require('../../controllers/userController') 3 | const { clubs, createClub, getClubById, deleteClubById, updateClub } = require('../../controllers/clubController') 4 | 5 | module.exports = { 6 | createClub, 7 | clubs, 8 | getClubById, 9 | updateClub, 10 | deleteClubById, 11 | registerUser, 12 | login, 13 | users 14 | } 15 | -------------------------------------------------------------------------------- /graphql/schema/index.js: -------------------------------------------------------------------------------- 1 | const { buildSchema } = require('graphql') 2 | 3 | module.exports = buildSchema(` 4 | scalar Date 5 | type AuthUser { 6 | id: String! 7 | first_name: String! 8 | last_name: String! 9 | email: String! 10 | } 11 | 12 | type AuthData { 13 | token:String! 14 | user:AuthUser! 15 | message:String! 16 | } 17 | 18 | input AuthInput { 19 | email:String! 20 | password:String! 21 | } 22 | 23 | type User { 24 | _id: ID! 25 | first_name: String 26 | last_name: String 27 | address: String 28 | email: String! 29 | phone: Int 30 | } 31 | 32 | input UserInput { 33 | first_name: String 34 | last_name: String 35 | address: String 36 | email: String! 37 | phone: Int 38 | password: String! 39 | } 40 | 41 | 42 | type Cost { 43 | cost: Int 44 | currency: String 45 | } 46 | 47 | type ExpectedDuration { 48 | duration: Int 49 | unit: String 50 | } 51 | 52 | type Location { 53 | latitude: Float 54 | longitude: Float 55 | } 56 | 57 | type Address { 58 | address_line_1: String 59 | street: String 60 | area: String 61 | landmark: String 62 | city: String 63 | state: String 64 | country: String 65 | location: Location 66 | } 67 | 68 | type Category { 69 | category_id: ID 70 | category_name: String 71 | category_l1:String 72 | category_l1_id:String 73 | } 74 | 75 | type Weblinks { 76 | email:String 77 | website:String 78 | facebook:String 79 | instagram:String 80 | youtube:String 81 | twitter:String 82 | } 83 | 84 | type EndingType { 85 | type:String 86 | } 87 | 88 | type TimeSlot { 89 | start_time: String 90 | end_time: String 91 | start_date: String 92 | ending_type:EndingType 93 | repeat_on: [String] 94 | repeat_every: Int 95 | repeat_frequency: String 96 | } 97 | 98 | type ExtraService { 99 | service: String 100 | cost: String 101 | currency: String 102 | } 103 | 104 | type KidService { 105 | service: String 106 | cost: String 107 | currency: String 108 | } 109 | 110 | type Service { 111 | extra_services: [ExtraService] 112 | kid_services: [KidService] 113 | pets_allowed: Boolean 114 | } 115 | 116 | type BookingMethod { 117 | method: String 118 | contact:String 119 | } 120 | 121 | type Booking { 122 | booking_requirement: String 123 | booking_methods : [BookingMethod] 124 | } 125 | 126 | type AcceptedAges { 127 | min: String 128 | max: String 129 | } 130 | 131 | type Age { 132 | accepted_ages: AcceptedAges 133 | } 134 | 135 | type Documents { 136 | documents_requirement: [String] 137 | } 138 | 139 | type EquipmentInformation { 140 | item: String 141 | provided: Boolean 142 | cost: String 143 | currency: String 144 | } 145 | 146 | type DressCodeEquipment { 147 | dress_code: [String] 148 | equipment_requirement: String 149 | equipment_information: [EquipmentInformation] 150 | } 151 | 152 | type Payment { 153 | payment_method: [String] 154 | } 155 | 156 | type Languages { 157 | languages_spoken: [String] 158 | } 159 | 160 | type Parking { 161 | parking_availability: Boolean 162 | valet_parking: Boolean 163 | } 164 | 165 | type Weather { 166 | weather_condition_tolerant: Boolean 167 | } 168 | 169 | type Traits { 170 | services: Service 171 | booking: Booking 172 | age: Age 173 | documents: Documents 174 | dress_code_equipment: DressCodeEquipment, 175 | payment: Payment 176 | languages: Languages 177 | parking: Parking 178 | weather: Weather 179 | } 180 | type Source { 181 | address: String 182 | time_slots: String 183 | images: String 184 | } 185 | 186 | type Issue { 187 | impact: String 188 | field: String 189 | source: String 190 | message: String 191 | } 192 | 193 | type Club { 194 | _id: ID 195 | activity_id_enc: String 196 | activity_name: String 197 | description: String 198 | visibility: Boolean 199 | manual_validation_flag: Boolean 200 | provider_name: String 201 | provider_id: ID 202 | branch_name: String 203 | branch_id: ID 204 | max_capacity: Int 205 | cost: Cost 206 | expected_duration: ExpectedDuration 207 | address: Address 208 | category: Category 209 | weblinks: Weblinks 210 | images: [String] 211 | is_visit_flag: Boolean 212 | type: String 213 | time_slots: [TimeSlot] 214 | flags: [String] 215 | traits: Traits 216 | source: Source 217 | ts_create : Date 218 | ts_update : String 219 | last_updated_by: Date 220 | ts_etl_run: Date 221 | ts_validate: Date 222 | last_validated_by: String 223 | system_validation_flag: Boolean 224 | validity: Boolean 225 | expired: Boolean 226 | issues: [Issue] 227 | } 228 | 229 | input CostInput { 230 | cost: Int 231 | currency: String 232 | } 233 | 234 | input LocationInput { 235 | latitude: Float 236 | longitude: Float 237 | } 238 | 239 | input AddressInput { 240 | address_line_1: String 241 | street: String 242 | area: String 243 | landmark: String 244 | city: String 245 | state: String 246 | country: String 247 | location: LocationInput 248 | } 249 | 250 | input CategoryInput { 251 | category_id: ID 252 | category_name: String 253 | category_l1:String 254 | category_l1_id:String 255 | } 256 | 257 | input WeblinksInput { 258 | email:String 259 | website:String 260 | facebook:String 261 | instagram:String 262 | youtube:String 263 | twitter:String 264 | } 265 | 266 | input EndingTypeInput { 267 | type:String 268 | } 269 | 270 | input TimeSlotInput { 271 | start_time: String 272 | end_time: String 273 | start_date: String 274 | ending_type:EndingTypeInput 275 | repeat_on: [String] 276 | repeat_every: Int 277 | repeat_frequency: String 278 | } 279 | 280 | input ExtraServiceInput { 281 | service: String 282 | cost: String 283 | currency: String 284 | } 285 | 286 | input KidServiceInput { 287 | service: String 288 | cost: String 289 | currency: String 290 | } 291 | 292 | input ServiceInput { 293 | extra_services: [ExtraServiceInput] 294 | kid_services: [KidServiceInput] 295 | pets_allowed: Boolean 296 | } 297 | 298 | input BookingMethodInput { 299 | method: String 300 | contact:String 301 | } 302 | 303 | input BookingInput { 304 | booking_requirement: String 305 | booking_methods : [BookingMethodInput] 306 | } 307 | 308 | input AcceptedAgesInput { 309 | min: String 310 | max: String 311 | } 312 | 313 | input AgeInput { 314 | accepted_ages: AcceptedAgesInput 315 | } 316 | 317 | input DocumentsInput { 318 | documents_requirement: [String] 319 | } 320 | 321 | input EquipmentInformationInput { 322 | item: String 323 | provided: Boolean 324 | cost: String 325 | currency: String 326 | } 327 | 328 | input DressCodeEquipmentInput { 329 | dress_code: [String] 330 | equipment_requirement: String 331 | equipment_information: [EquipmentInformationInput] 332 | } 333 | 334 | input PaymentInput { 335 | payment_method: [String] 336 | } 337 | 338 | input LanguagesInput { 339 | languages_spoken: [String] 340 | } 341 | 342 | input ParkingInput { 343 | parking_availability: Boolean 344 | valet_parking: Boolean 345 | } 346 | input WeatherInput { 347 | weather_condition_tolerant: Boolean 348 | } 349 | 350 | input TraitsInput { 351 | services: ServiceInput 352 | booking: BookingInput 353 | age: AgeInput 354 | documents: DocumentsInput 355 | dress_code_equipment: DressCodeEquipmentInput, 356 | payment: PaymentInput 357 | languages: LanguagesInput 358 | parking: ParkingInput, 359 | weather: WeatherInput 360 | } 361 | input SourceInput { 362 | address: String 363 | time_slots: String 364 | images: String 365 | } 366 | 367 | input IssueInput { 368 | impact: String 369 | field: String 370 | source: String 371 | message: String 372 | } 373 | 374 | input ExpectedDurationInput { 375 | duration: Int 376 | unit: String 377 | } 378 | 379 | input ClubInput { 380 | activity_id_enc: String 381 | activity_name: String 382 | description: String 383 | visibility: Boolean 384 | manual_validation_flag: Boolean 385 | provider_name: String 386 | provider_id: ID 387 | branch_name: String 388 | branch_id: ID 389 | max_capacity: Int 390 | cost: CostInput 391 | expected_duration: ExpectedDurationInput 392 | address: AddressInput 393 | category: CategoryInput 394 | weblinks: WeblinksInput 395 | images: [String] 396 | is_visit_flag: Boolean 397 | type: String 398 | time_slots: [TimeSlotInput] 399 | flags: [String] 400 | traits: TraitsInput 401 | source: SourceInput 402 | ts_create : Date 403 | ts_update : String 404 | last_updated_by: Date 405 | ts_etl_run: Date 406 | ts_validate: Date 407 | last_validated_by: String 408 | system_validation_flag: Boolean 409 | validity: Boolean 410 | expired: Boolean 411 | issues: [IssueInput] 412 | } 413 | 414 | type deleteResponse { 415 | message: String 416 | } 417 | 418 | type RootQuery { 419 | clubs: [Club!]! 420 | users: [User!]! 421 | login(authInput: AuthInput): AuthData! 422 | getClubById(id: String!): Club! 423 | deleteClubById(id: String!): deleteResponse! 424 | } 425 | 426 | type RootMutation { 427 | createClub(clubInput: ClubInput): Club 428 | registerUser(userInput: UserInput): User 429 | updateClub(id: String, clubUpdateInput: ClubInput): Club 430 | } 431 | 432 | schema { 433 | query: RootQuery 434 | mutation: RootMutation 435 | } 436 | `) 437 | -------------------------------------------------------------------------------- /middleware/404ErrorHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This moddleware return 404 error if route not found 3 | */ 4 | module.exports = (req, res, next) => { 5 | const error = new Error('404: Not Found') 6 | error.statusCode = 404 7 | return next(error) 8 | } 9 | -------------------------------------------------------------------------------- /middleware/jwtAuth.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken') 2 | 3 | module.exports = (req, res, next) => { 4 | const authHeader = req.get('Authorization') 5 | 6 | if (authHeader) { 7 | const token = authHeader.split(' ')[1] 8 | let decodedToken 9 | 10 | try { 11 | decodedToken = jwt.verify(token, process.env.SECRET_KEY) 12 | } catch (error) { 13 | error.statusCode = 401 14 | throw error 15 | } 16 | 17 | if (!decodedToken) { 18 | const error = new Error('Not authenticated.') 19 | error.statusCode = 401 20 | throw error 21 | } 22 | 23 | req.user = decodedToken 24 | next() 25 | } else { 26 | next() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /models/club.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const clubSchema = new Schema( 5 | { 6 | activity_id_enc: { type: String }, 7 | activity_name: { type: String }, 8 | description: { type: String }, 9 | visibility: { type: Boolean }, 10 | manual_validation_flag: { type: Boolean }, 11 | provider_name: { type: String }, 12 | provider_id: { type: String }, 13 | branch_name: { type: String }, 14 | branch_id: { type: String }, 15 | max_capacity: { type: Number }, 16 | cost: { type: Object }, 17 | expected_duration: { type: Object }, 18 | address: { type: Object }, 19 | category: { type: Object }, 20 | weblinks: { type: Object }, 21 | images: { type: Array }, 22 | is_visit_flag: { type: Boolean }, 23 | time_slots: { type: Array }, 24 | flags: { type: Array }, 25 | traits: { type: Object }, 26 | source: { type: Object }, 27 | ts_create: { type: Date }, 28 | ts_update: { type: Date }, 29 | last_updated_by: { type: String, default: null }, 30 | ts_etl_run: { type: Date }, 31 | ts_validate: { type: Date }, 32 | last_validated_by: { type: String }, 33 | system_validation_flag: { type: Boolean }, 34 | validity: { type: Boolean }, 35 | expired: { type: Boolean }, 36 | issues: { type: Array } 37 | 38 | }, 39 | { timestamps: true } 40 | ) 41 | 42 | module.exports = mongoose.model('club', clubSchema) 43 | -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | const Schema = mongoose.Schema 3 | 4 | const userSchema = new Schema( 5 | { 6 | first_name: { type: String }, 7 | last_name: { type: String }, 8 | address: { type: String }, 9 | email: { type: String }, 10 | phone: { type: Number }, 11 | password: { type: String } 12 | }, 13 | { timestamps: true } 14 | ) 15 | 16 | module.exports = mongoose.model('user', userSchema) 17 | -------------------------------------------------------------------------------- /npm.txt: -------------------------------------------------------------------------------- 1 | // for update npm packages 2 | npm install -g npm-check-updates 3 | ncu -u 4 | npm install 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-express-graph-ql", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \\\"Error: no test.json specified\\\" && exit 1", 8 | "start": "node app.js", 9 | "dev": "nodemon app.js", 10 | "lint:check": "eslint .", 11 | "lint:fix": "eslint --fix ." 12 | }, 13 | "keywords": [], 14 | "author": "[axitvadi@gmail.com,smitgajera6@gmail.com]", 15 | "license": "ISC", 16 | "dependencies": { 17 | "bcryptjs": "^2.4.3", 18 | "cors": "^2.8.5", 19 | "dotenv": "^16.0.2", 20 | "express": "^4.18.1", 21 | "express-graphql": "^0.12.0", 22 | "graphql": "^15.8.0", 23 | "graphql-scalars": "^1.18.0", 24 | "joi": "^17.6.0", 25 | "jsonwebtoken": "^8.5.1", 26 | "mongoose": "^6.6.0", 27 | "swagger-ui-express": "^4.5.0" 28 | }, 29 | "devDependencies": { 30 | "eslint": "8.22.0", 31 | "eslint-config-standard": "^17.0.0", 32 | "eslint-plugin-import": "^2.26.0", 33 | "eslint-plugin-n": "^15.2.5", 34 | "eslint-plugin-promise": "^6.0.1", 35 | "nodemon": "^2.0.19" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /routes/admin/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const userRoutes = require('./userRoute') 4 | 5 | router.use('/users', userRoutes) 6 | 7 | module.exports = router 8 | -------------------------------------------------------------------------------- /routes/admin/userRoute.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | 4 | const userController = require('../../controllers/userController') 5 | const Validator = require('../../utils/validateRequest') 6 | 7 | const use = (fn) => (req, res, next) => { 8 | Promise.resolve(fn(req, res, next)).catch(next) 9 | } 10 | 11 | router.get('/:id', use(userController.getUser)) 12 | 13 | router.get('/', use(userController.getAllUsers)) 14 | 15 | router.put( 16 | '/:id', 17 | Validator('updateUser'), 18 | use(userController.updateUser) 19 | ) 20 | 21 | router.delete('/:id', use(userController.deleteUser)) 22 | 23 | module.exports = router 24 | -------------------------------------------------------------------------------- /routes/auth/authRoute.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | 4 | const { login, register } = require('../../controllers/authController.js') 5 | const Validator = require('../../utils/validateRequest') 6 | 7 | const use = (fn) => (req, res, next) => { 8 | Promise.resolve(fn(req, res, next)).catch(next) 9 | } 10 | 11 | router.post('/login', Validator('login'), use(login)) 12 | router.post('/register', Validator('registerUser'), use(register)) 13 | 14 | module.exports = router 15 | -------------------------------------------------------------------------------- /routes/auth/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const authRoute = require('../auth/authRoute') 4 | 5 | router.use('/', authRoute) 6 | 7 | module.exports = router 8 | -------------------------------------------------------------------------------- /routes/common/bookRoute.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | 4 | const userController = require('../../controllers/bookController') 5 | const Validator = require('../../utils/validateRequest') 6 | 7 | const use = (fn) => (req, res, next) => { 8 | Promise.resolve(fn(req, res, next)).catch(next) 9 | } 10 | 11 | router.post('/', Validator('book'), use(userController.addBook)) 12 | 13 | router.get('/:id', use(userController.getBook)) 14 | 15 | router.get('/', use(userController.getAllBooks)) 16 | 17 | router.put('/:id', Validator('updateBook'), use(userController.updateBook)) 18 | 19 | router.delete('/:id', use(userController.deleteBook)) 20 | 21 | module.exports = router 22 | -------------------------------------------------------------------------------- /routes/common/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | const bookRoutes = require('./bookRoute') 4 | 5 | router.use('/books', bookRoutes) 6 | 7 | module.exports = router 8 | -------------------------------------------------------------------------------- /routes/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const router = express.Router() 3 | 4 | const auth = require('./auth/index') 5 | const admin = require('./admin/index') 6 | const common = require('./common/index') 7 | 8 | const authorize = require('../middleware/jwtAuth') 9 | 10 | router.use('/auth', auth) 11 | router.use('/admin', authorize(['admin']), admin) 12 | router.use('/common', authorize(['admin', 'user']), common) 13 | 14 | module.exports = router 15 | -------------------------------------------------------------------------------- /seeders/seed.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcryptjs') 2 | const User = require('../models/user') 3 | 4 | exports.seedAdmin = async () => { 5 | const admin = await User.findOne({ email: process.env.ADMIN_EMAIL }) 6 | if (!admin) { 7 | const hashedPw = await bcrypt.hash(process.env.ADMIN_PASSWORD, 10) 8 | await User.create({ 9 | first_name: 'graphql', 10 | last_name: 'Admin', 11 | email: process.env.ADMIN_EMAIL, 12 | role: 'admin', 13 | password: hashedPw 14 | }) 15 | console.log('Admin Seeded') 16 | } else { 17 | console.log('Admin exist') 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /utils/errorHandler.js: -------------------------------------------------------------------------------- 1 | const responseHelper = require('./responseHandler') 2 | 3 | const errorHandler = (error, req, res, next) => { 4 | // console.log(error); 5 | const status = error.statusCode || 500 6 | const message = error.message 7 | return res.status(status).json(responseHelper.prepareErrorResponse(message)) 8 | } 9 | 10 | module.exports = { errorHandler } 11 | -------------------------------------------------------------------------------- /utils/responseHandler.js: -------------------------------------------------------------------------------- 1 | function prepareSuccessResponse (data, message) { 2 | return { 3 | success: true, 4 | data, 5 | message, 6 | totalRecords: data.length 7 | } 8 | } 9 | 10 | function prepareErrorResponse (message) { 11 | return { 12 | success: false, 13 | message 14 | } 15 | } 16 | 17 | module.exports = { 18 | prepareSuccessResponse, 19 | prepareErrorResponse 20 | } 21 | -------------------------------------------------------------------------------- /utils/validateRequest.js: -------------------------------------------------------------------------------- 1 | //* Include joi to check error type 2 | // const Joi = require('joi') 3 | //* Include all validators 4 | const Validators = require('./validation') 5 | 6 | module.exports = function (validator) { 7 | //! If validator is not exist, throw err 8 | if (!Validators.hasOwnProperty(validator)) { 9 | throw new Error(`'${validator}' validator is not exist`) 10 | } 11 | 12 | return async function (req, res, next) { 13 | try { 14 | const validated = await Validators[validator].validateAsync(req.body) 15 | req.body = validated 16 | next() 17 | } catch (error) { 18 | //* Pass err to next 19 | //! If validation error occurs call next with HTTP 422. Otherwise HTTP 500 20 | if (error.isJoi) { 21 | error.statusCode = 422 22 | next(error) 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /utils/validation/bookValidation.js: -------------------------------------------------------------------------------- 1 | // Purpose : Request Validation 2 | // Description : Validate each POST and PUT request as per mongoose model 3 | 4 | const Joi = require('joi') 5 | 6 | const bookSchema = Joi.object({ 7 | name: Joi.string().empty().min(2).trim().required().messages({ 8 | 'string.empty': 'Book name title must be required.', 9 | 'any.required': 'Book name title must be required.', 10 | 'string.min': 'Book name title should have a minimum length of 2' 11 | }), 12 | description: Joi.string().allow(null).allow('').trim(), 13 | published_on: Joi.string().allow(null).allow('').trim(), 14 | isbn: Joi.string().empty().max(13).trim().required().messages({ 15 | 'string.empty': 'Book ISBN No. must be required.', 16 | 'any.required': 'Book ISBN No. must be required.', 17 | 'string.max': 'The isbn may not be greater than 13 characters.' 18 | }) 19 | }) 20 | 21 | module.exports = bookSchema 22 | -------------------------------------------------------------------------------- /utils/validation/index.js: -------------------------------------------------------------------------------- 1 | const registerUser = require('./registerUserValidation') 2 | const updateUser = require('./updateUserValidation') 3 | const book = require('./bookValidation') // book api validation 4 | const updateBook = require('./updateBookValidation') // update book api validation 5 | const login = require('./loginUserValidation') 6 | 7 | module.exports = { 8 | registerUser, 9 | login, 10 | updateUser, 11 | book, 12 | updateBook 13 | } 14 | -------------------------------------------------------------------------------- /utils/validation/loginUserValidation.js: -------------------------------------------------------------------------------- 1 | // Purpose : Request Validation 2 | // Description : Validate each POST and PUT request as per mongoose model 3 | 4 | const Joi = require('joi') 5 | 6 | const loginUserSchema = Joi.object({ 7 | email: Joi.string().empty().required().email().trim().messages({ 8 | 'string.empty': 'Email must be required.', 9 | 'any.required': 'Email must be required.', 10 | 'string.email': 'Invalid email address.' 11 | }), 12 | password: Joi.string().empty().min(6).required().trim().messages({ 13 | 'string.empty': 'Password must be required.', 14 | 'string.min': 'Password must be at least 6 characters', 15 | 'any.required': 'Password must be required.' 16 | }) 17 | }) 18 | 19 | module.exports = loginUserSchema 20 | -------------------------------------------------------------------------------- /utils/validation/registerUserValidation.js: -------------------------------------------------------------------------------- 1 | // Purpose : Request Validation 2 | // Description : Validate each POST and PUT request as per mongoose model 3 | 4 | const Joi = require('joi') 5 | 6 | const userSchema = Joi.object({ 7 | first_name: Joi.string().empty().min(2).max(30).required().trim().messages({ 8 | 'string.empty': 'First name must be required.', 9 | 'string.min': 'First name should have a minimum length of 2', 10 | 'any.required': 'First name must be required.' 11 | }), 12 | last_name: Joi.string().empty().min(2).max(30).required().trim().messages({ 13 | 'string.empty': 'Last name must be required.', 14 | 'string.min': 'Last name must be at least 2 characters', 15 | 'any.required': 'Last name must be required.' 16 | }), 17 | email: Joi.string().empty().required().email().trim().messages({ 18 | 'string.empty': 'Email must be required.', 19 | 'any.required': 'Email must be required.', 20 | 'string.email': 'Invalid email address.' 21 | }), 22 | password: Joi.string().empty().min(6).required().trim().messages({ 23 | 'string.empty': 'Password must be required.', 24 | 'string.min': 'Password must be at least 6 characters', 25 | 'any.required': 'Password must be required.' 26 | }), 27 | confirm_password: Joi.string() 28 | .equal(Joi.ref('password')) 29 | .required() 30 | .label('Confirm password') 31 | .trim() 32 | .messages({ 33 | 'any.only': 'Confirm Password and Password should be Same', 34 | 'any.required': 'Confirm Password must be required.' 35 | }), 36 | phone: Joi.string() 37 | .regex(/^[0-9]{10}$/) 38 | .allow(null) 39 | .allow('') 40 | .trim() 41 | .messages({ 'string.pattern.base': 'Phone number must have 10 digits.' }) 42 | }) 43 | 44 | module.exports = userSchema 45 | -------------------------------------------------------------------------------- /utils/validation/updateBookValidation.js: -------------------------------------------------------------------------------- 1 | // Purpose : Request Validation 2 | // Description : Validate PUT request as per mongoose model 3 | 4 | const Joi = require('joi') 5 | 6 | const updateBookSchema = Joi.object({ 7 | name: Joi.string().empty().min(2).trim().messages({ 8 | 'string.empty': 'Book name title must be required.', 9 | // 'any.required': 'Book name title must be required.', 10 | 'string.min': 'Book name title should have a minimum length of 2' 11 | }), 12 | description: Joi.string().allow(null).allow('').trim(), 13 | published_on: Joi.string().allow(null).allow('').trim(), 14 | isbn: Joi.string().empty().max(13).trim().messages({ 15 | 'string.empty': 'Book ISBN No. must be required.', 16 | // 'any.required': 'Book ISBN No. must be required.', 17 | 'string.max': 'The isbn may not be greater than 13 characters.' 18 | }) 19 | }) 20 | 21 | module.exports = updateBookSchema 22 | -------------------------------------------------------------------------------- /utils/validation/updateUserValidation.js: -------------------------------------------------------------------------------- 1 | // Purpose : Request Validation 2 | // Description : Validate each POST and PUT request as per mongoose model 3 | 4 | const Joi = require('joi') 5 | 6 | const userSchema = Joi.object({ 7 | first_name: Joi.string().empty().min(2).max(30).required().trim().messages({ 8 | 'string.empty': 'First name must be required.', 9 | 'string.min': 'First name should have a minimum length of 2', 10 | 'any.required': 'First name must be required.' 11 | }), 12 | last_name: Joi.string().empty().min(2).max(30).required().trim().messages({ 13 | 'string.empty': 'Last name must be required.', 14 | 'string.min': 'Last name must be at least 2 characters', 15 | 'any.required': 'Last name must be required.' 16 | }), 17 | email: Joi.string().empty().required().email().trim().messages({ 18 | 'string.empty': 'Email must be required.', 19 | 'any.required': 'Email must be required.', 20 | 'string.email': 'Invalid email address.' 21 | }), 22 | phone: Joi.string() 23 | .regex(/^[0-9]{10}$/) 24 | .allow(null) 25 | .allow('') 26 | .trim() 27 | .messages({ 'string.pattern.base': 'Phone number must have 10 digits.' }) 28 | }) 29 | 30 | module.exports = userSchema 31 | --------------------------------------------------------------------------------