| @@ -0,0 +1,33 @@ | |||
| const mongoose = require('mongoose'); | |||
| const PersonSchema = new mongoose.Schema({ | |||
| name: { | |||
| type: String, | |||
| required: [true, 'Please provide a name.'], | |||
| maxlength: [60, 'Name cannot be more than 60 characters'], | |||
| trim: true, | |||
| }, | |||
| age: { | |||
| type: Number, | |||
| required: [true, 'Please provide an age.'], | |||
| validate(value) { | |||
| if (value < 0) { | |||
| throw new Error('Age must be a postive number'); | |||
| } | |||
| }, | |||
| }, | |||
| gender: { | |||
| type: String, | |||
| required: [true, 'Please provide a gender.'], | |||
| trim: true, | |||
| }, | |||
| customID: { | |||
| type: String, | |||
| required: true, | |||
| unique: true, | |||
| }, | |||
| }); | |||
| const Person = mongoose.models.Person || mongoose.model('Person', PersonSchema); | |||
| module.exports = Person; | |||
| @@ -0,0 +1,75 @@ | |||
| import { | |||
| hashPassword, | |||
| verifyPassword, | |||
| } from '../utils/helpers/hashPasswordHelpers'; | |||
| const mongoose = require('mongoose'); | |||
| const validator = require('validator'); | |||
| const UserSchema = new mongoose.Schema({ | |||
| fullName: { | |||
| type: String, | |||
| required: [true, 'Please provide a name.'], | |||
| maxlength: [60, 'Name cannot be more than 60 characters'], | |||
| trim: true, | |||
| }, | |||
| username: { | |||
| type: String, | |||
| required: [true, 'Please provide a name.'], | |||
| maxlength: [60, 'Name cannot be more than 60 characters'], | |||
| trim: true, | |||
| unique: true, | |||
| }, | |||
| email: { | |||
| type: String, | |||
| unique: true, | |||
| required: true, | |||
| trim: true, | |||
| lowercase: true, | |||
| validate(value) { | |||
| if (!validator.isEmail(value)) { | |||
| throw new Error('Email is invalid'); | |||
| } | |||
| }, | |||
| }, | |||
| password: { | |||
| type: String, | |||
| required: true, | |||
| minlength: 7, | |||
| trim: true, | |||
| validate(value) { | |||
| if (value.toLowerCase().includes('password')) { | |||
| throw new Error('Password cannot contain "password"'); | |||
| } | |||
| }, | |||
| }, | |||
| }); | |||
| UserSchema.statics.findByCredentials = async (username, password) => { | |||
| const user = await User.findOne({ username }); | |||
| if (!user) { | |||
| throw new Error('Unable to login'); | |||
| } | |||
| const isMatch = await verifyPassword(password, user.password); | |||
| if (!isMatch) { | |||
| throw new Error('Unable to login'); | |||
| } | |||
| return user; | |||
| }; | |||
| UserSchema.pre('save', async function (next) { | |||
| const user = this; | |||
| if (user.isModified('password')) { | |||
| user.password = await hashPassword(user.password); | |||
| } | |||
| next(); | |||
| }); | |||
| const User = mongoose.models.User || mongoose.model('User', UserSchema); | |||
| module.exports = User; | |||
| @@ -23,6 +23,7 @@ | |||
| "date-fns": "^2.29.1", | |||
| "formik": "^2.2.9", | |||
| "mongodb": "^4.8.1", | |||
| "mongoose": "^6.5.2", | |||
| "next": "12.2.3", | |||
| "next-auth": "^4.10.2", | |||
| "next-i18next": "^11.3.0", | |||
| @@ -31,6 +32,7 @@ | |||
| "react-dom": "18.2.0", | |||
| "sass": "^1.54.0", | |||
| "swr": "^1.3.0", | |||
| "validator": "^13.7.0", | |||
| "yup": "^0.32.11" | |||
| }, | |||
| "devDependencies": { | |||
| @@ -1,8 +1,7 @@ | |||
| import NextAuth from 'next-auth'; | |||
| import Credentials from 'next-auth/providers/credentials'; | |||
| import { connectToDatabase } from '../../../utils/helpers/dbHelpers'; | |||
| import { verifyPassword } from '../../../utils/helpers/hashPasswordHelpers'; | |||
| import dbConnect from '../../../utils/helpers/dbHelpers'; | |||
| const User = require('../../../models/user'); | |||
| export default NextAuth({ | |||
| session: { | |||
| @@ -11,30 +10,12 @@ export default NextAuth({ | |||
| providers: [ | |||
| Credentials({ | |||
| async authorize(credentials) { | |||
| const client = await connectToDatabase(); | |||
| const usersCollection = client.db().collection('users'); | |||
| const user = await usersCollection.findOne({ | |||
| username: credentials.username, | |||
| }); | |||
| await dbConnect(); | |||
| if (!user) { | |||
| client.close(); | |||
| throw new Error('No user found!'); | |||
| } | |||
| const isValid = await verifyPassword( | |||
| credentials.password, | |||
| user.password | |||
| const user = await User.findByCredentials( | |||
| credentials.username, | |||
| credentials.password | |||
| ); | |||
| if (!isValid) { | |||
| client.close(); | |||
| throw new Error('Could not log you in!'); | |||
| } | |||
| client.close(); | |||
| return { name: user.fullName }; | |||
| }, | |||
| }), | |||
| @@ -1,51 +1,27 @@ | |||
| import { connectToDatabase } from '../../../utils/helpers/dbHelpers'; | |||
| import { hashPassword } from '../../../utils/helpers/hashPasswordHelpers'; | |||
| import User from '../../../models/user'; | |||
| import dbConnect from '../../../utils/helpers/dbHelpers'; | |||
| async function handler(req, res) { | |||
| if (req.method !== 'POST') { | |||
| return; | |||
| const { method } = req; | |||
| await dbConnect(); | |||
| switch (method) { | |||
| case 'POST': { | |||
| try { | |||
| const user = await User.create(req.body); | |||
| res | |||
| .status(201) | |||
| .json({ message: `User (${user.fullName}) created sucessfully!` }); | |||
| } catch (error) { | |||
| res.status(400).json({ message: error.message }); | |||
| } | |||
| break; | |||
| } | |||
| default: | |||
| res.status(405).json({ message: 'Method not allowed' }); | |||
| break; | |||
| } | |||
| const { fullName, username, email, password } = req.body; | |||
| if ( | |||
| !fullName || | |||
| !username || | |||
| !email || | |||
| !email.includes('@') || | |||
| !password || | |||
| password.trim().length < 7 | |||
| ) { | |||
| res.status(422).json({ | |||
| message: 'Invalid input ', | |||
| }); | |||
| return; | |||
| } | |||
| const client = await connectToDatabase(); | |||
| const db = client.db(); | |||
| const existingUser = await db | |||
| .collection('users') | |||
| .findOne({ $or: [{ email: email }, { username: username }] }); | |||
| if (existingUser) { | |||
| res.status(422).json({ message: 'User exists already!' }); | |||
| client.close(); | |||
| return; | |||
| } | |||
| const hashedPassword = await hashPassword(password); | |||
| const result = await db.collection('users').insertOne({ | |||
| fullName: fullName, | |||
| username: username, | |||
| email: email, | |||
| password: hashedPassword, | |||
| }); | |||
| res.status(201).json({ message: 'Created user!', result: result }); | |||
| client.close(); | |||
| } | |||
| export default handler; | |||
| @@ -1,9 +1,10 @@ | |||
| import { connectToDatabase } from '../../utils/helpers/dbHelpers'; | |||
| const Person = require('../../models/person'); | |||
| import dbConnect from '../../utils/helpers/dbHelpers'; | |||
| async function handler(req, res) { | |||
| if (req.method !== 'GET') { | |||
| return; | |||
| } | |||
| const { method } = req; | |||
| await dbConnect(); | |||
| const pageIndex = req.query.page; | |||
| @@ -14,41 +15,57 @@ async function handler(req, res) { | |||
| return; | |||
| } | |||
| const client = await connectToDatabase(); | |||
| const db = client.db(); | |||
| switch (method) { | |||
| case 'GET': { | |||
| try { | |||
| const dataCount = await Person.countDocuments(); | |||
| const dataCount = await db.collection('randomData').countDocuments(); | |||
| if ((pageIndex - 1) * 4 >= dataCount) { | |||
| res.status(422).json({ | |||
| message: 'Page does not exist ', | |||
| }); | |||
| client.close(); | |||
| return; | |||
| } | |||
| if (dataCount === 0) { | |||
| res.status(200).json({ | |||
| message: 'No data.', | |||
| data: [], | |||
| dataCount: 0, | |||
| }); | |||
| break; | |||
| } | |||
| const dataFromDB = await db | |||
| .collection('randomData') | |||
| .find() | |||
| .skip((pageIndex - 1) * 4) | |||
| .limit(4) | |||
| .toArray(); | |||
| if ((pageIndex - 1) * 4 >= dataCount) { | |||
| throw new Error('Page does not exist!'); | |||
| } | |||
| if (!dataFromDB) { | |||
| res.status(422).json({ message: 'No data!' }); | |||
| client.close(); | |||
| return; | |||
| } | |||
| const dataFromDB = await Person.find({}) | |||
| .skip((pageIndex - 1) * 4) | |||
| .limit(4); | |||
| res.status(201).json({ | |||
| message: 'Created user!', | |||
| data: dataFromDB, | |||
| dataCount: dataCount, | |||
| }); | |||
| if (!dataFromDB) { | |||
| throw new Error('No data!'); | |||
| } | |||
| setTimeout(() => { | |||
| client.close(); | |||
| }, 1500); | |||
| res.status(200).json({ | |||
| message: 'Fetched data succesfully', | |||
| data: dataFromDB, | |||
| dataCount, | |||
| }); | |||
| } catch (error) { | |||
| res.status(400).json({ message: error.message }); | |||
| } | |||
| break; | |||
| } | |||
| case 'POST': { | |||
| try { | |||
| const person = await Person.create( | |||
| req.body | |||
| ); /* create a new model in the database */ | |||
| res.status(201).json({ message: 'Created succesfully!', data: person }); | |||
| } catch (error) { | |||
| res.status(400).json({ success: false }); | |||
| } | |||
| break; | |||
| } | |||
| default: | |||
| res.status(405).json({ message: 'Method not allowed' }); | |||
| break; | |||
| } | |||
| } | |||
| export default handler; | |||
| @@ -1,40 +1,36 @@ | |||
| import { connectToDatabase } from '../../../utils/helpers/dbHelpers'; | |||
| const Person = require('../../../models/person'); | |||
| import dbConnect from '../../../utils/helpers/dbHelpers'; | |||
| async function handler(req, res) { | |||
| if (req.method !== 'GET') { | |||
| return; | |||
| const { method } = req; | |||
| await dbConnect(); | |||
| switch (method) { | |||
| case 'GET': { | |||
| try { | |||
| const dataId = req.query.dataId; | |||
| const person = await Person.findOne({ customID: dataId }); | |||
| console.log(person); | |||
| if (!person) { | |||
| throw new Error('Person with this id does not exist!'); | |||
| } | |||
| res.status(200).json({ | |||
| message: 'Fetch single data successfull!', | |||
| singleData: person, | |||
| }); | |||
| } catch (error) { | |||
| res.status(400).json({ message: error.message }); | |||
| } | |||
| break; | |||
| } | |||
| default: | |||
| res.status(405).json({ message: 'Method not allowed' }); | |||
| break; | |||
| } | |||
| const dataId = req.query.dataId; | |||
| let client; | |||
| try { | |||
| client = await connectToDatabase(); | |||
| } catch (error) { | |||
| res.status(500).json({ message: 'Connecting to the database failed!' }); | |||
| return; | |||
| } | |||
| const db = client.db(); | |||
| const singleData = await db | |||
| .collection('randomData') | |||
| .findOne({ customID: dataId }); | |||
| if (!singleData) { | |||
| res.status(422).json({ message: 'No data!' }); | |||
| client.close(); | |||
| return; | |||
| } | |||
| res.status(201).json({ | |||
| message: 'Fetch single data successfull!', | |||
| singleData: singleData, | |||
| }); | |||
| setTimeout(() => { | |||
| client.close(); | |||
| }, 1500); | |||
| } | |||
| export default handler; | |||
| @@ -1,35 +1,45 @@ | |||
| import { connectToDatabase } from '../../../utils/helpers/dbHelpers'; | |||
| const Person = require('../../../models/person'); | |||
| import dbConnect from '../../../utils/helpers/dbHelpers'; | |||
| async function handler(req, res) { | |||
| if (req.method !== 'GET') { | |||
| return; | |||
| const { method } = req; | |||
| await dbConnect(); | |||
| switch (method) { | |||
| case 'GET': { | |||
| try { | |||
| const dataCount = await Person.countDocuments(); | |||
| if (dataCount === 0) { | |||
| res.status(200).json({ | |||
| message: 'No data.', | |||
| dataIds: [], | |||
| }); | |||
| break; | |||
| } | |||
| const dataFromDB = await Person.find({}).limit(4); | |||
| if (!dataFromDB) { | |||
| throw new Error('No data!'); | |||
| } | |||
| const dataIds = dataFromDB.map((item) => item.customID); | |||
| res.status(200).json({ | |||
| message: 'Fetch ids successfull!', | |||
| dataIds: dataIds, | |||
| }); | |||
| } catch (error) { | |||
| res.status(400).json({ message: error.message }); | |||
| } | |||
| break; | |||
| } | |||
| default: | |||
| res.status(405).json({ message: 'Method not allowed' }); | |||
| break; | |||
| } | |||
| const client = await connectToDatabase(); | |||
| const db = client.db(); | |||
| const dataFromDB = await db | |||
| .collection('randomData') | |||
| .find() | |||
| .limit(4) | |||
| .toArray(); | |||
| if (!dataFromDB) { | |||
| res.status(422).json({ message: 'No data!' }); | |||
| client.close(); | |||
| return; | |||
| } | |||
| const dataIds = dataFromDB.map((item) => item.customID); | |||
| res.status(201).json({ | |||
| message: 'Fetch ids successfull!', | |||
| dataIds: dataIds, | |||
| }); | |||
| setTimeout(() => { | |||
| client.close(); | |||
| }, 1500); | |||
| } | |||
| export default handler; | |||
| @@ -2,14 +2,15 @@ import DataDetailsCard from '../../components/cards/data-details-card/DataDetail | |||
| import { getDataIds } from '../../requests/dataIdsRequest'; | |||
| import { getSingleData } from '../../requests/singleDataRequest'; | |||
| function SignelDataPage(props) { | |||
| const SignelDataPage = (props) => { | |||
| const data = props.selectedData; | |||
| console.log(data); | |||
| if (!data) { | |||
| return <h1>No data!</h1>; | |||
| return <h1>{props.message}</h1>; | |||
| } | |||
| return <DataDetailsCard data={data.singleData} />; | |||
| } | |||
| }; | |||
| export async function getStaticProps(context) { | |||
| const dataId = context.params.dataId; | |||
| @@ -26,6 +27,7 @@ export async function getStaticProps(context) { | |||
| return { | |||
| props: { | |||
| selectedData: null, | |||
| message: error.message, | |||
| }, | |||
| revalidate: 60, | |||
| }; | |||
| @@ -40,6 +42,7 @@ export async function getStaticPaths() { | |||
| params: { dataId: id }, | |||
| })); | |||
| console.log(paths); | |||
| return { | |||
| paths: paths, | |||
| fallback: 'blocking', | |||
| @@ -1,7 +1,47 @@ | |||
| import { MongoClient } from "mongodb"; | |||
| import { MongoClient } from 'mongodb'; | |||
| import mongoose from 'mongoose'; | |||
| export async function connectToDatabase() { | |||
| const client = await MongoClient.connect(process.env.MONGODB_AUTH); | |||
| const client = await MongoClient.connect(process.env.MONGODB_URI); | |||
| return client; | |||
| } | |||
| const MONGODB_URI = process.env.MONGODB_URI; | |||
| if (!MONGODB_URI) { | |||
| throw new Error( | |||
| 'Please define the MONGODB_URI environment variable inside .env.local' | |||
| ); | |||
| } | |||
| /** | |||
| * Global is used here to maintain a cached connection across hot reloads | |||
| * in development. This prevents connections growing exponentially | |||
| * during API Route usage. | |||
| */ | |||
| let cached = global.mongoose; | |||
| if (!cached) { | |||
| cached = global.mongoose = { conn: null, promise: null }; | |||
| } | |||
| async function dbConnect() { | |||
| if (cached.conn) { | |||
| return cached.conn; | |||
| } | |||
| if (!cached.promise) { | |||
| const opts = { | |||
| bufferCommands: false, | |||
| }; | |||
| cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => { | |||
| return mongoose; | |||
| }); | |||
| } | |||
| cached.conn = await cached.promise; | |||
| return cached.conn; | |||
| } | |||
| export default dbConnect; | |||
| @@ -4785,6 +4785,13 @@ [email protected], debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: | |||
| dependencies: | |||
| ms "2.0.0" | |||
| [email protected], debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: | |||
| version "4.3.4" | |||
| resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" | |||
| integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== | |||
| dependencies: | |||
| ms "2.1.2" | |||
| debug@^3.0.0, debug@^3.2.7: | |||
| version "3.2.7" | |||
| resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" | |||
| @@ -4792,13 +4799,6 @@ debug@^3.0.0, debug@^3.2.7: | |||
| dependencies: | |||
| ms "^2.1.1" | |||
| debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: | |||
| version "4.3.4" | |||
| resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" | |||
| integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== | |||
| dependencies: | |||
| ms "2.1.2" | |||
| decamelize-keys@^1.1.0: | |||
| version "1.1.0" | |||
| resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" | |||
| @@ -7275,6 +7275,11 @@ junk@^3.1.0: | |||
| resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" | |||
| integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== | |||
| [email protected]: | |||
| version "2.4.1" | |||
| resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.4.1.tgz#7d81ec518204a48c1cb16554af126806c3cd82b0" | |||
| integrity sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA== | |||
| kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: | |||
| version "3.2.2" | |||
| resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" | |||
| @@ -7842,7 +7847,7 @@ mongodb-connection-string-url@^2.5.2: | |||
| "@types/whatwg-url" "^8.2.1" | |||
| whatwg-url "^11.0.0" | |||
| mongodb@^4.8.1: | |||
| [email protected], mongodb@^4.8.1: | |||
| version "4.8.1" | |||
| resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.8.1.tgz#596de88ff4519128266d9254dbe5b781c4005796" | |||
| integrity sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w== | |||
| @@ -7854,6 +7859,31 @@ mongodb@^4.8.1: | |||
| optionalDependencies: | |||
| saslprep "^1.0.3" | |||
| mongoose@^6.5.2: | |||
| version "6.5.2" | |||
| resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-6.5.2.tgz#630ae67c4d2576635ba1f859b2840560b27b7775" | |||
| integrity sha512-3CFDrSLtK2qjM1pZeZpLTUyqPRkc11Iuh74ZrwS4IwEJ3K2PqGnmyPLw7ex4Kzu37ujIMp3MAuiBlUjfrcb6hw== | |||
| dependencies: | |||
| bson "^4.6.5" | |||
| kareem "2.4.1" | |||
| mongodb "4.8.1" | |||
| mpath "0.9.0" | |||
| mquery "4.0.3" | |||
| ms "2.1.3" | |||
| sift "16.0.0" | |||
| [email protected]: | |||
| version "0.9.0" | |||
| resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.9.0.tgz#0c122fe107846e31fc58c75b09c35514b3871904" | |||
| integrity sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew== | |||
| [email protected]: | |||
| version "4.0.3" | |||
| resolved "https://registry.yarnpkg.com/mquery/-/mquery-4.0.3.tgz#4d15f938e6247d773a942c912d9748bd1965f89d" | |||
| integrity sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA== | |||
| dependencies: | |||
| debug "4.x" | |||
| [email protected]: | |||
| version "2.0.0" | |||
| resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" | |||
| @@ -9693,6 +9723,11 @@ side-channel@^1.0.4: | |||
| get-intrinsic "^1.0.2" | |||
| object-inspect "^1.9.0" | |||
| [email protected]: | |||
| version "16.0.0" | |||
| resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.0.tgz#447991577db61f1a8fab727a8a98a6db57a23eb8" | |||
| integrity sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ== | |||
| signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: | |||
| version "3.0.7" | |||
| resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" | |||
| @@ -10706,6 +10741,11 @@ validate-npm-package-license@^3.0.1: | |||
| spdx-correct "^3.0.0" | |||
| spdx-expression-parse "^3.0.0" | |||
| validator@^13.7.0: | |||
| version "13.7.0" | |||
| resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" | |||
| integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== | |||
| vary@~1.1.2: | |||
| version "1.1.2" | |||
| resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" | |||