जेफिरनेट लोगो

MySQL का उपयोग Node.js और mysql JavaScript क्लाइंट के साथ करना

दिनांक:

NoSQL डेटाबेस नोग डेवलपर्स के बीच लोकप्रिय हैं, जिसमें मोंगोबीडी (MEAN स्टैक में "एम") पैक प्रमुख है। एक नया नोड प्रोजेक्ट शुरू करते समय, हालांकि, आपको केवल मानगो को डिफ़ॉल्ट विकल्प के रूप में स्वीकार नहीं करना चाहिए। बल्कि, आपके द्वारा चुना गया डेटाबेस का प्रकार आपकी परियोजना की आवश्यकताओं पर निर्भर होना चाहिए। यदि, उदाहरण के लिए, आपको डायनेमिक टेबल निर्माण या वास्तविक समय आवेषण की आवश्यकता है, तो एक NoSQL समाधान जाने का रास्ता है। यदि आपका प्रोजेक्ट जटिल प्रश्नों और लेनदेन से निपटता है, तो दूसरी ओर, एक SQL डेटाबेस बहुत अधिक समझ में आता है।

इस ट्यूटोरियल में, हम इस पर एक नज़र डालेंगे mysql मॉड्यूल - MySQL के लिए एक Node.js क्लाइंट, जो जावास्क्रिप्ट में लिखा गया है। मैं समझाता हूं कि संग्रहीत प्रक्रियाओं को देखने और उपयोगकर्ता इनपुट से बचने से पहले, MySQL डेटाबेस से कनेक्ट करने और सामान्य CRUD ऑपरेशन करने के लिए मॉड्यूल का उपयोग कैसे करें।

यह लोकप्रिय लेख 2020 में Node.js. के साथ MySQL का उपयोग करने के लिए वर्तमान प्रथाओं को प्रतिबिंबित करने के लिए अद्यतन किया गया था MySQL पर अधिक जानकारी के लिए पढ़ें प्रारंभ MySQL कूदो.

क्विक स्टार्ट: नोड में MySQL का उपयोग कैसे करें

यदि आप यहां पहुंच गए हैं, तो Node में MySQL के साथ उठने और दौड़ने का एक त्वरित तरीका खोज रहे हैं, हमने आपको कवर कर लिया है!

यहाँ पाँच आसान चरणों में नोड में MySQL का उपयोग कैसे करें:

  1. एक नया प्रोजेक्ट बनाएं: mkdir mysql-test && cd mysql-test.
  2. बनाओ package.json फ़ाइल: npm init -y.
  3. Mysql मॉड्यूल स्थापित करें: npm install mysql.
  4. एक बनाएं app.js नीचे दिए गए स्निपेट में फ़ाइल और कॉपी करें (प्लेसहोल्डर्स को उपयुक्त के रूप में संपादित करें)।
  5. फ़ाइल चलाएँ: node app.js। एक "कनेक्टेड" का निरीक्षण करें! संदेश।
const mysql = require('mysql');
const connection = mysql.createConnection({ host: 'localhost', user: 'user', password: 'password', database: 'database name'
});
connection.connect((err) => { if (err) throw err; console.log('Connected!');
});

Mysql मॉड्यूल इंस्टॉल करना

अब उन प्रत्येक चरणों पर एक करीब से नज़र डालते हैं।

mkdir mysql-test
cd mysql-test
npm init -y
npm install mysql

सबसे पहले हम नई निर्देशिका बनाने और उस पर नेविगेट करने के लिए कमांड लाइन का उपयोग कर रहे हैं। तब हम पैदा कर रहे हैं package.json कमांड का उपयोग करके फ़ाइल npm init -y-y ध्वज का अर्थ है कि npm एक इंटरैक्टिव प्रक्रिया से गुजरे बिना चूक का उपयोग करेगा।

यह चरण यह भी मानता है कि आपके पास आपके सिस्टम पर Node और npm स्थापित है। यदि यह मामला नहीं है, तो यह करने के लिए कैसे पता लगाने के लिए इस SitePoint लेख की जाँच करें: Nvm का उपयोग करके Node.js के एकाधिक संस्करण स्थापित करें.

उसके बाद, हम स्थापित कर रहे हैं mysql मॉड्यूल npm से और इसे परियोजना निर्भरता के रूप में सहेजना। प्रोजेक्ट निर्भरताएँ (जैसा कि devD dependencies के विपरीत है) वे पैकेज हैं जो अनुप्रयोग को चलाने के लिए आवश्यक हैं। तुम पढ़ सकते हो दोनों के बीच अंतर के बारे में यहाँ और अधिक.

यदि आपको npm का उपयोग करने में और सहायता की आवश्यकता है, तो जांचना सुनिश्चित करें इस गाइडया में पूछते हैं हमारे मंच.

Getting Started

इससे पहले कि हम किसी डेटाबेस से जुड़ते हैं, यह महत्वपूर्ण है कि आपके पास MySQL स्थापित हो और आपकी मशीन पर कॉन्फ़िगर हो। यदि ऐसा नहीं है, तो कृपया परामर्श करें उनके मुख पृष्ठ पर स्थापना निर्देश.

अगली चीज़ जो हमें करने की ज़रूरत है वह है काम करने के लिए डेटाबेस और डेटाबेस तालिका बनाना। आप एक का उपयोग कर ऐसा कर सकते हैं
ग्राफ़िकल इंटरफ़ेस, जैसे कि व्यवस्थापक, या कमांड लाइन का उपयोग कर। इस लेख के लिए मैं नामक एक डेटाबेस का उपयोग करूँगा sitepoint और एक मेज बुलाया authors। यहाँ डेटाबेस का एक डंप है, ताकि आप यदि आप साथ चलना चाहें तो जल्दी से उठ सकते हैं:

CREATE DATABASE sitepoint CHARACTER SET utf8 COLLATE utf8_general_ci;
USE sitepoint; CREATE TABLE authors ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50), city varchar(50), PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=5 ; INSERT IGNORE INTO authors (id, name, city) VALUES
(1, 'Michaela Lehr', 'Berlin'),
(2, 'Michael Wanyoike', 'Nairobi'),
(3, 'James Hibbard', 'Munich'),
(4, 'Karolina Gawron', 'Wrocław');

MySQL का उपयोग Node.js और mysql JavaScript क्लाइंट के साथ करना

डेटाबेस से कनेक्ट करना

अब, एक फाइल बनाते हैं जिसका नाम है app.js हमारे में mysql-test निर्देशिका और देखें कि MySQL से Node.js. से कैसे जुड़ें

const mysql = require('mysql'); // First you need to create a connection to the database
// Be sure to replace 'user' and 'password' with the correct values
const con = mysql.createConnection({ host: 'localhost', user: 'user', password: 'password',
}); con.connect((err) => { if(err){ console.log('Error connecting to Db'); return; } console.log('Connection established');
}); con.end((err) => { // The connection is terminated gracefully // Ensures all remaining queries are executed // Then sends a quit packet to the MySQL server.
});

अब एक टर्मिनल खोलें और दर्ज करें node app.js। एक बार कनेक्शन सफलतापूर्वक स्थापित हो जाने के बाद आपको कंसोल में "कनेक्शन स्थापित" संदेश देखने में सक्षम होना चाहिए। यदि कुछ गलत हो जाता है (उदाहरण के लिए, आप गलत पासवर्ड दर्ज करते हैं), तो कॉलबैक निकाल दिया जाता है, जिसे जावास्क्रिप्ट त्रुटि ऑब्जेक्ट का एक उदाहरण दिया जाता है (err)। यह देखने के लिए कंसोल में लॉग इन करने का प्रयास करें कि इसमें क्या अतिरिक्त उपयोगी जानकारी है।

परिवर्तन के लिए फ़ाइलें देखने के लिए नोडन का उपयोग करना

रनिंग node app.js हर बार जब हम अपने कोड में बदलाव करते हैं तो थोड़ा थकाऊ होने वाला है, तो चलिए इसे स्वचालित करते हैं। यह हिस्सा बाकी ट्यूटोरियल के साथ पालन करने के लिए आवश्यक नहीं है, लेकिन निश्चित रूप से आपको कुछ कीस्ट्रोक्स से बचाएगा।

चलो एक स्थापित करके शुरू करते हैं नीड़ पैकेज। यह एक उपकरण है जो स्वचालित रूप से एक नोड अनुप्रयोग को पुनरारंभ करता है जब एक निर्देशिका में फ़ाइल परिवर्तन का पता लगाया जाता है:

npm install --save-dev nodemon

अब भागो ./node_modules/.bin/nodemon app.js और एक परिवर्तन करें app.js। नोडम को परिवर्तन का पता लगाना चाहिए और ऐप को पुनरारंभ करना चाहिए।

नोट: हम सीधे से नोडमॉन चला रहे हैं node_modules फ़ोल्डर। आप इसे विश्व स्तर पर भी स्थापित कर सकते हैं, या इसे बंद करने के लिए एक npm स्क्रिप्ट बना सकते हैं।

निष्पादन योग्य क्वेरी

पढ़ना

अब जब आप जानते हैं कि Node.js से MySQL डेटाबेस से कनेक्शन कैसे स्थापित किया जाए, तो आइए देखें कि SQL क्वेरी को कैसे निष्पादित करें। हम डेटाबेस नाम निर्दिष्ट करके शुरू करेंगे (sitepointमें) createConnection आदेश:

const con = mysql.createConnection({ host: 'localhost', user: 'user', password: 'password', database: 'sitepoint'
});

एक बार कनेक्शन स्थापित हो जाने के बाद, हम इसका उपयोग करेंगे con डेटाबेस तालिका के खिलाफ क्वेरी निष्पादित करने के लिए चर authors:

con.query('SELECT * FROM authors', (err,rows) => { if(err) throw err; console.log('Data received from Db:'); console.log(rows);
});

जब आप दौड़ते हैं app.js (या तो नोडन का उपयोग करके या टाइप करके node app.js अपने टर्मिनल में), आपको डेटाबेस से लॉग इन किए गए डेटा को टर्मिनल में देखने में सक्षम होना चाहिए:

[ RowDataPacket { id: 1, name: 'Michaela Lehr', city: 'Berlin' }, RowDataPacket { id: 2, name: 'Michael Wanyoike', city: 'Nairobi' }, RowDataPacket { id: 3, name: 'James Hibbard', city: 'Munich' }, RowDataPacket { id: 4, name: 'Karolina Gawron', city: 'Wrocław' } ]

MySQL डेटाबेस से लौटाए गए डेटा को केवल ओवर लूपिंग द्वारा पार्स किया जा सकता है rows वस्तु।

rows.forEach( (row) => { console.log(`${row.name} lives in ${row.city}`);
});

यह आपको निम्नलिखित देता है:

Michaela Lehr lives in Berlin
Michael Wanyoike lives in Nairobi
James Hibbard lives in Munich
Karolina Gawron lives in Wrocław

बनाना

आप किसी डेटाबेस के विरुद्ध सम्मिलित क्वेरी निष्पादित कर सकते हैं, जैसे:

const author = { name: 'Craig Buckler', city: 'Exmouth' };
con.query('INSERT IGNORE INTO authors SET ?', author, (err, res) => { if(err) throw err; console.log('Last insert ID:', res.insertId);
});

ध्यान दें कि कॉलबैक पैरामीटर का उपयोग करके हम सम्मिलित रिकॉर्ड की आईडी कैसे प्राप्त कर सकते हैं।

अद्यतन कर रहा है

इसी तरह, एक अपडेट क्वेरी निष्पादित करते समय, प्रभावित पंक्तियों की संख्या का उपयोग करके पुनर्प्राप्त किया जा सकता है result.affectedRows:

con.query( 'UPDATE authors SET city = ? Where ID = ?', ['Leipzig', 3], (err, result) => { if (err) throw err; console.log(`Changed ${result.changedRows} row(s)`); }
);

नष्ट

यही बात डिलीट क्वेरी के लिए जाती है:

con.query( 'DELETE FROM authors WHERE id = ?', [5], (err, result) => { if (err) throw err; console.log(`Deleted ${result.affectedRows} row(s)`); }
);

उन्नत उपयोग

मैं यह देख कर समाप्त करना चाहूंगा कि कैसे mysql मॉड्यूल संग्रहीत प्रक्रियाओं और उपयोगकर्ता इनपुट से बचने को संभालता है।

संग्रहित प्रक्रियाएं

सीधे शब्दों में कहें, एक संग्रहीत कार्यविधि SQL कोड तैयार किया जाता है जिसे आप एक डेटाबेस में सहेज सकते हैं, ताकि इसे आसानी से पुन: उपयोग किया जा सके। यदि आप संग्रहीत प्रक्रियाओं पर एक पुनश्चर्या की जरूरत है, तो बाहर की जाँच करें इस ट्यूटोरियल.

चलो हमारे लिए एक संग्रहीत प्रक्रिया बनाते हैं sitepoint डेटाबेस जो सभी लेखक विवरण प्राप्त करता है। हम इसे फोन करेंगे sp_get_authors। ऐसा करने के लिए, आपको डेटाबेस में किसी प्रकार के इंटरफ़ेस की आवश्यकता होगी। मैं उपयोग कर रहा हूँ व्यवस्थापक। के विरुद्ध निम्न क्वेरी चलाएँ sitepoint डेटाबेस, यह सुनिश्चित करना कि आपके उपयोगकर्ता के पास MySQL सर्वर पर व्यवस्थापक अधिकार हैं:

DELIMITER $$ CREATE PROCEDURE `sp_get_authors`()
BEGIN SELECT id, name, city FROM authors;
END $$

यह प्रक्रिया को बनाएगा और संग्रहीत करेगा information_schema में डेटाबेस ROUTINES तालिका.

व्यवस्थापक में संग्रहीत कार्यविधि बनाना

नोट: यदि सीमांकक सिंटैक्स आपको अजीब लगता है, तो इसे समझाया गया है यहाँ उत्पन्न करें.

अगला, एक कनेक्शन स्थापित करें और स्टोर किए गए प्रक्रिया को कॉल करने के लिए कनेक्शन ऑब्जेक्ट का उपयोग करें जैसा कि दिखाया गया है:

con.query('CALL sp_get_authors()',function(err, rows){ if (err) throw err; console.log('Data received from Db:'); console.log(rows);
});

परिवर्तन सहेजें और फ़ाइल चलाएँ। एक बार निष्पादित होने के बाद, आपको डेटाबेस से लौटाए गए डेटा को देखने में सक्षम होना चाहिए:

[ [ RowDataPacket { id: 1, name: 'Michaela Lehr', city: 'Berlin' }, RowDataPacket { id: 2, name: 'Michael Wanyoike', city: 'Nairobi' }, RowDataPacket { id: 3, name: 'James Hibbard', city: 'Leipzig' }, RowDataPacket { id: 4, name: 'Karolina Gawron', city: 'Wrocław' }, OkPacket { fieldCount: 0, affectedRows: 0, insertId: 0, serverStatus: 34, warningCount: 0, message: '', protocol41: true, changedRows: 0 } ]

डेटा के साथ, यह कुछ अतिरिक्त जानकारी लौटाता है, जैसे कि पंक्तियों की प्रभावित संख्या, insertId आदि आपको कर्मचारी के विवरण को बाकी जानकारी से अलग करने के लिए लौटाए गए डेटा के 0 वें सूचकांक पर पुनरावृति करने की आवश्यकता है:

rows[0].forEach( (row) => { console.log(`${row.name} lives in ${row.city}`);
});

यह आपको निम्नलिखित देता है:

Michaela Lehr lives in Berlin
Michael Wanyoike lives in Nairobi
James Hibbard lives in Leipzig
Karolina Gawron lives in Wrocław

अब आइए एक संग्रहीत प्रक्रिया पर विचार करें जिसके लिए इनपुट पैरामीटर की आवश्यकता होती है:

DELIMITER $$ CREATE PROCEDURE `sp_get_author_details`( in author_id int
)
BEGIN SELECT name, city FROM authors where id = author_id;
END $$

हम संग्रहीत कार्यविधि को कॉल करते समय इनपुट पैरामीटर पास कर सकते हैं:

con.query('CALL sp_get_author_details(1)', (err, rows) => { if(err) throw err; console.log('Data received from Db:n'); console.log(rows[0]);
});

यह आपको निम्नलिखित देता है:

[ RowDataPacket { name: 'Michaela Lehr', city: 'Berlin' } ]

अधिकांश समय जब हम डेटाबेस में रिकॉर्ड डालने की कोशिश करते हैं, तो हमें अंतिम सम्मिलित आईडी की आवश्यकता होती है, जिसे एक आउट पैरामीटर के रूप में लौटाया जाना चाहिए। एक आउट पैरामीटर के साथ निम्नलिखित सम्मिलित संग्रहीत कार्यविधि पर विचार करें:

DELIMITER $$ CREATE PROCEDURE `sp_insert_author`( out author_id int, in author_name varchar(25), in author_city varchar(25)
)
BEGIN insert into authors(name, city) values(author_name, author_city); set author_id = LAST_INSERT_ID();
END $$

आउट पैरामीटर के साथ एक प्रक्रिया कॉल करने के लिए, हमें पहले कनेक्शन बनाते समय कई कॉल सक्षम करने की आवश्यकता होती है। इसलिए, कई स्टेटमेंट एक्जीक्यूशन को सेट करके कनेक्शन को संशोधित करें true:

const con = mysql.createConnection({ host: 'localhost', user: 'user', password: 'password', database: 'sitepoint', multipleStatements: true
});

इसके बाद, प्रक्रिया को कॉल करते समय, एक आउट पैरामीटर सेट करें और इसे इसमें पास करें:

con.query( "SET @author_id = 0; CALL sp_insert_author(@author_id, 'Craig Buckler', 'Exmouth'); SELECT @author_id", (err, rows) => { if (err) throw err; console.log('Data received from Db:n'); console.log(rows); }
);

जैसा कि उपरोक्त कोड में देखा गया है, हमने एक सेट किया है @author_id संग्रहीत कार्यविधि के लिए कॉल करते समय पैरामीटर और इसे पास किया गया। एक बार कॉल करने के बाद हमें लौटी हुई आईडी तक पहुंचने के लिए आउट पैरामीटर का चयन करना होगा।

रन app.js। सफल निष्पादन पर आपको चयनित आउट पैरामीटर को विभिन्न अन्य जानकारी के साथ देखने में सक्षम होना चाहिए। rows[2] आपको चयनित आउट पैरामीटर तक पहुंच प्रदान करनी चाहिए:

 [ RowDataPacket { '@author_id': 6 } ] ]

नोट: संग्रहीत कार्यविधि को हटाने के लिए आपको कमांड चलाने की आवश्यकता है DROP PROCEDURE <procedure-name>; डेटाबेस के लिए आपने इसे बनाया था।

उपयोगकर्ता इनपुट से बचना

SQL इंजेक्शन के हमलों से बचने के लिए, आपको चाहिए हमेशा SQL क्वेरी के अंदर उपयोग करने से पहले उपयोगकर्ताओं से प्राप्त किसी भी डेटा से बच जाएं। आइए प्रदर्शित करें कि क्यों:

const userSubmittedVariable = '1'; con.query( `SELECT * FROM authors WHERE id = ${userSubmittedVariable}`, (err, rows) => { if(err) throw err; console.log(rows); }
);

यह हानिरहित पर्याप्त लगता है और यहां तक ​​कि सही परिणाम देता है:

 { id: 1, name: 'Michaela Lehr', city: 'Berlin' }

हालाँकि, बदलने का प्रयास करें userSubmittedVariable इसके लिए:

const userSubmittedVariable = '1 OR 1=1';

हमारे पास अचानक पूरे डेटा सेट तक पहुंच है। अब इसे इसमें बदलें:

const userSubmittedVariable = '1; DROP TABLE authors';

हम अब उचित परेशानी में हैं!

अच्छी खबर यह है कि मदद हाथ में है। आपको बस इसका उपयोग करना है mysql.escape तरीका:

con.query( `SELECT * FROM authors WHERE id = ${mysql.escape(userSubmittedVariable)}`, (err, rows) => { if(err) throw err; console.log(rows); }
);

आप एक प्रश्न चिह्न प्लेसहोल्डर का उपयोग कर सकते हैं, जैसा कि हमने लेख की शुरुआत में उदाहरणों में किया था:

con.query( 'SELECT * FROM authors WHERE id = ?', [userSubmittedVariable], (err, rows) => { if(err) throw err; console.log(rows); }
);

क्यों नहीं बस एक ORM का उपयोग करें?

इससे पहले कि हम इस दृष्टिकोण के पेशेवरों और विपक्षों में शामिल हों, आइए देखें कि ओआरएम क्या हैं। निम्नलिखित से लिया गया है स्टैक ओवरफ्लो पर एक उत्तर:

ऑब्जेक्ट-रिलेशनल मैपिंग (ओआरएम) एक ऐसी तकनीक है जो आपको ऑब्जेक्ट-ओरिएंटेड प्रतिमान का उपयोग करके डेटाबेस से डेटा को क्वेरी और हेरफेर करने की सुविधा देती है। जब ओआरएम के बारे में बात की जाती है, तो ज्यादातर लोग एक पुस्तकालय का उल्लेख कर रहे हैं जो ऑब्जेक्ट-रिलेशनल मैपिंग तकनीक को लागू करता है, इसलिए "ओआरएम" वाक्यांश।

तो इसका मतलब है कि आप अपने डेटाबेस तर्क को ORM की डोमेन-विशिष्ट भाषा में लिखते हैं, जैसा कि हम अब तक ले जा रहे वेनिला दृष्टिकोण के विपरीत हैं। आपको यह देखने के लिए कि यह कैसा दिख सकता है, इसका उपयोग करने के लिए यहां एक उदाहरण दिया गया है सीक्वेल करें, जो सभी लेखकों के लिए डेटाबेस पर सवाल उठाता है और उन्हें कंसोल में लॉग करता है:

const sequelize = new Sequelize('sitepoint', 'user', 'password', { host: 'localhost', dialect: 'mysql'
}); const Author = sequelize.define('author', { name: { type: Sequelize.STRING, }, city: { type: Sequelize.STRING },
}, { timestamps: false
}); Author.findAll().then(authors => { console.log("All authors:", JSON.stringify(authors, null, 4));
});

ORM का उपयोग करना या न करना आपके लिए समझ में आता है कि आप क्या और किसके साथ काम कर रहे हैं, इस पर बहुत निर्भर करेगा। एक ओर, ओआरएमएस डेवलपर्स को अधिक उत्पादक बनाने के लिए करते हैं, भाग में एसक्यूएल के एक बड़े हिस्से को दूर करके ताकि टीम पर हर किसी को सुपर कुशल डेटाबेस विशिष्ट प्रश्नों को लिखने की जानकारी न हो। विभिन्न डेटाबेस सॉफ़्टवेयर में स्थानांतरित करना भी आसान है, क्योंकि आप अमूर्तता में विकसित हो रहे हैं।

दूसरी ओर, यह समझना संभव है कि ओआरएम क्या करता है, यह समझने के परिणामस्वरूप कुछ वास्तव में गड़बड़ और अक्षम एसक्यूएल लिखना संभव है। प्रदर्शन भी एक समस्या है जिसमें ओआरएम के माध्यम से नहीं जाने वाले प्रश्नों को अनुकूलित करना बहुत आसान है।

आप जो भी रास्ता अपनाते हैं वह आपके ऊपर है, लेकिन यदि यह एक निर्णय है जिसे आप बनाने की प्रक्रिया में हैं, तो इस स्टैकफ़्लो को देखें: आपको ORM का उपयोग क्यों करना चाहिए?। इस पोस्ट को भी साइटप्वाइंट पर देखें: 3 जावास्क्रिप्ट ORM आप नहीं जानते हो सकता है.

निष्कर्ष

इस ट्यूटोरियल में, हमने इंस्टॉल किया है mysql क्लाइंट Node.js के लिए और इसे डेटाबेस से कनेक्ट करने के लिए कॉन्फ़िगर किया गया है। हमने यह भी देखा है कि सीआरयूडी ऑपरेशन कैसे करें, तैयार किए गए बयानों के साथ काम करें और SQL इंजेक्शन हमलों को कम करने के लिए उपयोगकर्ता इनपुट से बच जाएं। और फिर भी, हम केवल mysql क्लाइंट की पेशकश की सतह को खरोंच कर रहे हैं। अधिक विस्तृत जानकारी के लिए, मैं पढ़ने की सलाह देता हूं सरकारी दस्तावेज.

और कृपया ध्यान रखें कि mysql मॉड्यूल शहर का एकमात्र शो नहीं है। अन्य विकल्प भी हैं, जैसे कि लोकप्रिय नोड mysql2.

स्रोत: https://www.sitepoint.com/using-node-mysql-javascript-client/?utm_source=rss

स्पॉट_आईएमजी

नवीनतम खुफिया

स्पॉट_आईएमजी