From 4d20b218f7aa0fef146b7466067080b4514e4f50 Mon Sep 17 00:00:00 2001 From: Syco21 Date: Sat, 14 Mar 2026 23:54:17 +0100 Subject: [PATCH] export cards --- node_modules/.package-lock.json | 26 +++ node_modules/cors/LICENSE | 22 ++ node_modules/cors/README.md | 277 +++++++++++++++++++++++ node_modules/cors/lib/index.js | 238 +++++++++++++++++++ node_modules/cors/package.json | 42 ++++ node_modules/object-assign/index.js | 90 ++++++++ node_modules/object-assign/license | 21 ++ node_modules/object-assign/package.json | 42 ++++ node_modules/object-assign/readme.md | 61 +++++ package-lock.json | 27 +++ package.json | 1 + src/controllers/cardImageController.js | 58 +++++ src/controllers/exportCardsController.js | 52 +++++ src/index.js | 16 +- src/routes/cardImageRoutes.js | 8 + src/routes/exportCardsRoutes.js | 8 + 16 files changed, 985 insertions(+), 4 deletions(-) create mode 100644 node_modules/cors/LICENSE create mode 100644 node_modules/cors/README.md create mode 100644 node_modules/cors/lib/index.js create mode 100644 node_modules/cors/package.json create mode 100644 node_modules/object-assign/index.js create mode 100644 node_modules/object-assign/license create mode 100644 node_modules/object-assign/package.json create mode 100644 node_modules/object-assign/readme.md create mode 100644 src/controllers/cardImageController.js create mode 100644 src/controllers/exportCardsController.js create mode 100644 src/routes/cardImageRoutes.js create mode 100644 src/routes/exportCardsRoutes.js diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index c66de44..1cdf67d 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -167,6 +167,23 @@ "node": ">=6.6.0" } }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -745,6 +762,15 @@ "node": ">= 0.6" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/node_modules/cors/LICENSE b/node_modules/cors/LICENSE new file mode 100644 index 0000000..fd10c84 --- /dev/null +++ b/node_modules/cors/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2013 Troy Goode + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/cors/README.md b/node_modules/cors/README.md new file mode 100644 index 0000000..3d206e5 --- /dev/null +++ b/node_modules/cors/README.md @@ -0,0 +1,277 @@ +# cors + +[![NPM Version][npm-image]][npm-url] +[![NPM Downloads][downloads-image]][downloads-url] +[![Build Status][github-actions-ci-image]][github-actions-ci-url] +[![Test Coverage][coveralls-image]][coveralls-url] + +CORS is a [Node.js](https://nodejs.org/en/) middleware for [Express](https://expressjs.com/)/[Connect](https://github.com/senchalabs/connect) that sets [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) response headers. These headers tell browsers which origins can read responses from your server. + +> [!IMPORTANT] +> **How CORS Works:** This package sets response headers—it doesn't block requests. CORS is enforced by browsers: they check the headers and decide if JavaScript can read the response. Non-browser clients (curl, Postman, other servers) ignore CORS entirely. See the [MDN CORS guide](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS) for details. + +* [Installation](#installation) +* [Usage](#usage) + * [Simple Usage](#simple-usage-enable-all-cors-requests) + * [Enable CORS for a Single Route](#enable-cors-for-a-single-route) + * [Configuring CORS](#configuring-cors) + * [Configuring CORS w/ Dynamic Origin](#configuring-cors-w-dynamic-origin) + * [Enabling CORS Pre-Flight](#enabling-cors-pre-flight) + * [Customizing CORS Settings Dynamically per Request](#customizing-cors-settings-dynamically-per-request) +* [Configuration Options](#configuration-options) +* [Common Misconceptions](#common-misconceptions) +* [License](#license) +* [Original Author](#original-author) + +## Installation + +This is a [Node.js](https://nodejs.org/en/) module available through the +[npm registry](https://www.npmjs.com/). Installation is done using the +[`npm install` command](https://docs.npmjs.com/downloading-and-installing-packages-locally): + +```sh +$ npm install cors +``` + +## Usage + +### Simple Usage (Enable *All* CORS Requests) + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +// Adds headers: Access-Control-Allow-Origin: * +app.use(cors()) + +app.get('/products/:id', function (req, res, next) { + res.json({msg: 'Hello'}) +}) + +app.listen(80, function () { + console.log('web server listening on port 80') +}) +``` + +### Enable CORS for a Single Route + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +// Adds headers: Access-Control-Allow-Origin: * +app.get('/products/:id', cors(), function (req, res, next) { + res.json({msg: 'Hello'}) +}) + +app.listen(80, function () { + console.log('web server listening on port 80') +}) +``` + +### Configuring CORS + +See the [configuration options](#configuration-options) for details. + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +var corsOptions = { + origin: 'http://example.com', + optionsSuccessStatus: 200 // some legacy browsers (IE11, various SmartTVs) choke on 204 +} + +// Adds headers: Access-Control-Allow-Origin: http://example.com, Vary: Origin +app.get('/products/:id', cors(corsOptions), function (req, res, next) { + res.json({msg: 'Hello'}) +}) + +app.listen(80, function () { + console.log('web server listening on port 80') +}) +``` + +### Configuring CORS w/ Dynamic Origin + +This module supports validating the origin dynamically using a function provided +to the `origin` option. This function will be passed a string that is the origin +(or `undefined` if the request has no origin), and a `callback` with the signature +`callback(error, origin)`. + +The `origin` argument to the callback can be any value allowed for the `origin` +option of the middleware, except a function. See the +[configuration options](#configuration-options) section for more information on all +the possible value types. + +This function is designed to allow the dynamic loading of allowed origin(s) from +a backing datasource, like a database. + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +var corsOptions = { + origin: function (origin, callback) { + // db.loadOrigins is an example call to load + // a list of origins from a backing database + db.loadOrigins(function (error, origins) { + callback(error, origins) + }) + } +} + +// Adds headers: Access-Control-Allow-Origin: , Vary: Origin +app.get('/products/:id', cors(corsOptions), function (req, res, next) { + res.json({msg: 'Hello'}) +}) + +app.listen(80, function () { + console.log('web server listening on port 80') +}) +``` + +### Enabling CORS Pre-Flight + +Certain CORS requests are considered 'complex' and require an initial +`OPTIONS` request (called the "pre-flight request"). An example of a +'complex' CORS request is one that uses an HTTP verb other than +GET/HEAD/POST (such as DELETE) or that uses custom headers. To enable +pre-flighting, you must add a new OPTIONS handler for the route you want +to support: + +```javascript +var express = require('express') +var cors = require('cors') +var app = express() + +app.options('/products/:id', cors()) // preflight for DELETE +app.del('/products/:id', cors(), function (req, res, next) { + res.json({msg: 'Hello'}) +}) + +app.listen(80, function () { + console.log('web server listening on port 80') +}) +``` + +You can also enable pre-flight across-the-board like so: + +```javascript +app.options('*', cors()) // include before other routes +``` + +NOTE: When using this middleware as an application level middleware (for +example, `app.use(cors())`), pre-flight requests are already handled for all +routes. + +### Customizing CORS Settings Dynamically per Request + +For APIs that require different CORS configurations for specific routes or requests, you can dynamically generate CORS options based on the incoming request. The `cors` middleware allows you to achieve this by passing a function instead of static options. This function is called for each incoming request and must use the callback pattern to return the appropriate CORS options. + +The function accepts: +1. **`req`**: + - The incoming request object. + +2. **`callback(error, corsOptions)`**: + - A function used to return the computed CORS options. + - **Arguments**: + - **`error`**: Pass `null` if there’s no error, or an error object to indicate a failure. + - **`corsOptions`**: An object specifying the CORS policy for the current request. + +Here’s an example that handles both public routes and restricted, credential-sensitive routes: + +```javascript +var dynamicCorsOptions = function(req, callback) { + var corsOptions; + if (req.path.startsWith('/auth/connect/')) { + // Access-Control-Allow-Origin: http://mydomain.com, Access-Control-Allow-Credentials: true, Vary: Origin + corsOptions = { + origin: 'http://mydomain.com', + credentials: true + }; + } else { + // Access-Control-Allow-Origin: * + corsOptions = { origin: '*' }; + } + callback(null, corsOptions); +}; + +app.use(cors(dynamicCorsOptions)); + +app.get('/auth/connect/twitter', function (req, res) { + res.send('Hello'); +}); + +app.get('/public', function (req, res) { + res.send('Hello'); +}); + +app.listen(80, function () { + console.log('web server listening on port 80') +}) +``` + +## Configuration Options + +* `origin`: Configures the **Access-Control-Allow-Origin** CORS header. Possible values: + - `Boolean` - set `origin` to `true` to reflect the [request origin](https://datatracker.ietf.org/doc/html/draft-abarth-origin-09), as defined by `req.header('Origin')`, or set it to `false` to disable CORS. + - `String` - set `origin` to a specific origin. For example, if you set it to + - `"http://example.com"` only requests from "http://example.com" will be allowed. + - `"*"` for all domains to be allowed. + - `RegExp` - set `origin` to a regular expression pattern which will be used to test the request origin. If it's a match, the request origin will be reflected. For example the pattern `/example\.com$/` will reflect any request that is coming from an origin ending with "example.com". + - `Array` - set `origin` to an array of valid origins. Each origin can be a `String` or a `RegExp`. For example `["http://example1.com", /\.example2\.com$/]` will accept any request from "http://example1.com" or from a subdomain of "example2.com". + - `Function` - set `origin` to a function implementing some custom logic. The function takes the request origin as the first parameter and a callback (called as `callback(err, origin)`, where `origin` is a non-function value of the `origin` option) as the second. +* `methods`: Configures the **Access-Control-Allow-Methods** CORS header. Expects a comma-delimited string (ex: 'GET,PUT,POST') or an array (ex: `['GET', 'PUT', 'POST']`). +* `allowedHeaders`: Configures the **Access-Control-Allow-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Type,Authorization') or an array (ex: `['Content-Type', 'Authorization']`). If not specified, defaults to reflecting the headers specified in the request's **Access-Control-Request-Headers** header. +* `exposedHeaders`: Configures the **Access-Control-Expose-Headers** CORS header. Expects a comma-delimited string (ex: 'Content-Range,X-Content-Range') or an array (ex: `['Content-Range', 'X-Content-Range']`). If not specified, no custom headers are exposed. +* `credentials`: Configures the **Access-Control-Allow-Credentials** CORS header. Set to `true` to pass the header, otherwise it is omitted. +* `maxAge`: Configures the **Access-Control-Max-Age** CORS header. Set to an integer to pass the header, otherwise it is omitted. +* `preflightContinue`: Pass the CORS preflight response to the next handler. +* `optionsSuccessStatus`: Provides a status code to use for successful `OPTIONS` requests, since some legacy browsers (IE11, various SmartTVs) choke on `204`. + +The default configuration is the equivalent of: + +```json +{ + "origin": "*", + "methods": "GET,HEAD,PUT,PATCH,POST,DELETE", + "preflightContinue": false, + "optionsSuccessStatus": 204 +} +``` + +## Common Misconceptions + +### "CORS blocks requests from disallowed origins" + +**No.** Your server receives and processes every request. CORS headers tell the browser whether JavaScript can read the response—not whether the request is allowed. + +### "CORS protects my API from unauthorized access" + +**No.** CORS is not access control. Any HTTP client (curl, Postman, another server) can call your API regardless of CORS settings. Use authentication and authorization to protect your API. + +### "Setting `origin: 'http://example.com'` means only that domain can access my server" + +**No.** It means browsers will only let JavaScript from that origin read responses. The server still responds to all requests. + +## License + +[MIT License](http://www.opensource.org/licenses/mit-license.php) + +## Original Author + +[Troy Goode](https://github.com/TroyGoode) ([troygoode@gmail.com](mailto:troygoode@gmail.com)) + +[coveralls-image]: https://img.shields.io/coveralls/expressjs/cors/master.svg +[coveralls-url]: https://coveralls.io/r/expressjs/cors?branch=master +[downloads-image]: https://img.shields.io/npm/dm/cors.svg +[downloads-url]: https://npmjs.com/package/cors +[github-actions-ci-image]: https://img.shields.io/github/actions/workflow/status/expressjs/cors/ci.yml?branch=master&label=ci +[github-actions-ci-url]: https://github.com/expressjs/cors?query=workflow%3Aci +[npm-image]: https://img.shields.io/npm/v/cors.svg +[npm-url]: https://npmjs.com/package/cors diff --git a/node_modules/cors/lib/index.js b/node_modules/cors/lib/index.js new file mode 100644 index 0000000..ad899ca --- /dev/null +++ b/node_modules/cors/lib/index.js @@ -0,0 +1,238 @@ +(function () { + + 'use strict'; + + var assign = require('object-assign'); + var vary = require('vary'); + + var defaults = { + origin: '*', + methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', + preflightContinue: false, + optionsSuccessStatus: 204 + }; + + function isString(s) { + return typeof s === 'string' || s instanceof String; + } + + function isOriginAllowed(origin, allowedOrigin) { + if (Array.isArray(allowedOrigin)) { + for (var i = 0; i < allowedOrigin.length; ++i) { + if (isOriginAllowed(origin, allowedOrigin[i])) { + return true; + } + } + return false; + } else if (isString(allowedOrigin)) { + return origin === allowedOrigin; + } else if (allowedOrigin instanceof RegExp) { + return allowedOrigin.test(origin); + } else { + return !!allowedOrigin; + } + } + + function configureOrigin(options, req) { + var requestOrigin = req.headers.origin, + headers = [], + isAllowed; + + if (!options.origin || options.origin === '*') { + // allow any origin + headers.push([{ + key: 'Access-Control-Allow-Origin', + value: '*' + }]); + } else if (isString(options.origin)) { + // fixed origin + headers.push([{ + key: 'Access-Control-Allow-Origin', + value: options.origin + }]); + headers.push([{ + key: 'Vary', + value: 'Origin' + }]); + } else { + isAllowed = isOriginAllowed(requestOrigin, options.origin); + // reflect origin + headers.push([{ + key: 'Access-Control-Allow-Origin', + value: isAllowed ? requestOrigin : false + }]); + headers.push([{ + key: 'Vary', + value: 'Origin' + }]); + } + + return headers; + } + + function configureMethods(options) { + var methods = options.methods; + if (methods.join) { + methods = options.methods.join(','); // .methods is an array, so turn it into a string + } + return { + key: 'Access-Control-Allow-Methods', + value: methods + }; + } + + function configureCredentials(options) { + if (options.credentials === true) { + return { + key: 'Access-Control-Allow-Credentials', + value: 'true' + }; + } + return null; + } + + function configureAllowedHeaders(options, req) { + var allowedHeaders = options.allowedHeaders || options.headers; + var headers = []; + + if (!allowedHeaders) { + allowedHeaders = req.headers['access-control-request-headers']; // .headers wasn't specified, so reflect the request headers + headers.push([{ + key: 'Vary', + value: 'Access-Control-Request-Headers' + }]); + } else if (allowedHeaders.join) { + allowedHeaders = allowedHeaders.join(','); // .headers is an array, so turn it into a string + } + if (allowedHeaders && allowedHeaders.length) { + headers.push([{ + key: 'Access-Control-Allow-Headers', + value: allowedHeaders + }]); + } + + return headers; + } + + function configureExposedHeaders(options) { + var headers = options.exposedHeaders; + if (!headers) { + return null; + } else if (headers.join) { + headers = headers.join(','); // .headers is an array, so turn it into a string + } + if (headers && headers.length) { + return { + key: 'Access-Control-Expose-Headers', + value: headers + }; + } + return null; + } + + function configureMaxAge(options) { + var maxAge = (typeof options.maxAge === 'number' || options.maxAge) && options.maxAge.toString() + if (maxAge && maxAge.length) { + return { + key: 'Access-Control-Max-Age', + value: maxAge + }; + } + return null; + } + + function applyHeaders(headers, res) { + for (var i = 0, n = headers.length; i < n; i++) { + var header = headers[i]; + if (header) { + if (Array.isArray(header)) { + applyHeaders(header, res); + } else if (header.key === 'Vary' && header.value) { + vary(res, header.value); + } else if (header.value) { + res.setHeader(header.key, header.value); + } + } + } + } + + function cors(options, req, res, next) { + var headers = [], + method = req.method && req.method.toUpperCase && req.method.toUpperCase(); + + if (method === 'OPTIONS') { + // preflight + headers.push(configureOrigin(options, req)); + headers.push(configureCredentials(options)) + headers.push(configureMethods(options)) + headers.push(configureAllowedHeaders(options, req)); + headers.push(configureMaxAge(options)) + headers.push(configureExposedHeaders(options)) + applyHeaders(headers, res); + + if (options.preflightContinue) { + next(); + } else { + // Safari (and potentially other browsers) need content-length 0, + // for 204 or they just hang waiting for a body + res.statusCode = options.optionsSuccessStatus; + res.setHeader('Content-Length', '0'); + res.end(); + } + } else { + // actual response + headers.push(configureOrigin(options, req)); + headers.push(configureCredentials(options)) + headers.push(configureExposedHeaders(options)) + applyHeaders(headers, res); + next(); + } + } + + function middlewareWrapper(o) { + // if options are static (either via defaults or custom options passed in), wrap in a function + var optionsCallback = null; + if (typeof o === 'function') { + optionsCallback = o; + } else { + optionsCallback = function (req, cb) { + cb(null, o); + }; + } + + return function corsMiddleware(req, res, next) { + optionsCallback(req, function (err, options) { + if (err) { + next(err); + } else { + var corsOptions = assign({}, defaults, options); + var originCallback = null; + if (corsOptions.origin && typeof corsOptions.origin === 'function') { + originCallback = corsOptions.origin; + } else if (corsOptions.origin) { + originCallback = function (origin, cb) { + cb(null, corsOptions.origin); + }; + } + + if (originCallback) { + originCallback(req.headers.origin, function (err2, origin) { + if (err2 || !origin) { + next(err2); + } else { + corsOptions.origin = origin; + cors(corsOptions, req, res, next); + } + }); + } else { + next(); + } + } + }); + }; + } + + // can pass either an options hash, an options delegate, or nothing + module.exports = middlewareWrapper; + +}()); diff --git a/node_modules/cors/package.json b/node_modules/cors/package.json new file mode 100644 index 0000000..e90bac8 --- /dev/null +++ b/node_modules/cors/package.json @@ -0,0 +1,42 @@ +{ + "name": "cors", + "description": "Node.js CORS middleware", + "version": "2.8.6", + "author": "Troy Goode (https://github.com/troygoode/)", + "license": "MIT", + "keywords": [ + "cors", + "express", + "connect", + "middleware" + ], + "repository": "expressjs/cors", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + }, + "main": "./lib/index.js", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "devDependencies": { + "after": "0.8.2", + "eslint": "7.30.0", + "express": "4.21.2", + "mocha": "9.2.2", + "nyc": "15.1.0", + "supertest": "6.1.3" + }, + "files": [ + "lib/index.js" + ], + "engines": { + "node": ">= 0.10" + }, + "scripts": { + "test": "npm run lint && npm run test-ci", + "test-ci": "nyc --reporter=lcov --reporter=text mocha --require test/support/env", + "lint": "eslint lib test" + } +} diff --git a/node_modules/object-assign/index.js b/node_modules/object-assign/index.js new file mode 100644 index 0000000..0930cf8 --- /dev/null +++ b/node_modules/object-assign/index.js @@ -0,0 +1,90 @@ +/* +object-assign +(c) Sindre Sorhus +@license MIT +*/ + +'use strict'; +/* eslint-disable no-unused-vars */ +var getOwnPropertySymbols = Object.getOwnPropertySymbols; +var hasOwnProperty = Object.prototype.hasOwnProperty; +var propIsEnumerable = Object.prototype.propertyIsEnumerable; + +function toObject(val) { + if (val === null || val === undefined) { + throw new TypeError('Object.assign cannot be called with null or undefined'); + } + + return Object(val); +} + +function shouldUseNative() { + try { + if (!Object.assign) { + return false; + } + + // Detect buggy property enumeration order in older V8 versions. + + // https://bugs.chromium.org/p/v8/issues/detail?id=4118 + var test1 = new String('abc'); // eslint-disable-line no-new-wrappers + test1[5] = 'de'; + if (Object.getOwnPropertyNames(test1)[0] === '5') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test2 = {}; + for (var i = 0; i < 10; i++) { + test2['_' + String.fromCharCode(i)] = i; + } + var order2 = Object.getOwnPropertyNames(test2).map(function (n) { + return test2[n]; + }); + if (order2.join('') !== '0123456789') { + return false; + } + + // https://bugs.chromium.org/p/v8/issues/detail?id=3056 + var test3 = {}; + 'abcdefghijklmnopqrst'.split('').forEach(function (letter) { + test3[letter] = letter; + }); + if (Object.keys(Object.assign({}, test3)).join('') !== + 'abcdefghijklmnopqrst') { + return false; + } + + return true; + } catch (err) { + // We don't expect any of the above to throw, but better to be safe. + return false; + } +} + +module.exports = shouldUseNative() ? Object.assign : function (target, source) { + var from; + var to = toObject(target); + var symbols; + + for (var s = 1; s < arguments.length; s++) { + from = Object(arguments[s]); + + for (var key in from) { + if (hasOwnProperty.call(from, key)) { + to[key] = from[key]; + } + } + + if (getOwnPropertySymbols) { + symbols = getOwnPropertySymbols(from); + for (var i = 0; i < symbols.length; i++) { + if (propIsEnumerable.call(from, symbols[i])) { + to[symbols[i]] = from[symbols[i]]; + } + } + } + } + + return to; +}; diff --git a/node_modules/object-assign/license b/node_modules/object-assign/license new file mode 100644 index 0000000..654d0bf --- /dev/null +++ b/node_modules/object-assign/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/object-assign/package.json b/node_modules/object-assign/package.json new file mode 100644 index 0000000..503eb1e --- /dev/null +++ b/node_modules/object-assign/package.json @@ -0,0 +1,42 @@ +{ + "name": "object-assign", + "version": "4.1.1", + "description": "ES2015 `Object.assign()` ponyfill", + "license": "MIT", + "repository": "sindresorhus/object-assign", + "author": { + "name": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com" + }, + "engines": { + "node": ">=0.10.0" + }, + "scripts": { + "test": "xo && ava", + "bench": "matcha bench.js" + }, + "files": [ + "index.js" + ], + "keywords": [ + "object", + "assign", + "extend", + "properties", + "es2015", + "ecmascript", + "harmony", + "ponyfill", + "prollyfill", + "polyfill", + "shim", + "browser" + ], + "devDependencies": { + "ava": "^0.16.0", + "lodash": "^4.16.4", + "matcha": "^0.7.0", + "xo": "^0.16.0" + } +} diff --git a/node_modules/object-assign/readme.md b/node_modules/object-assign/readme.md new file mode 100644 index 0000000..1be09d3 --- /dev/null +++ b/node_modules/object-assign/readme.md @@ -0,0 +1,61 @@ +# object-assign [![Build Status](https://travis-ci.org/sindresorhus/object-assign.svg?branch=master)](https://travis-ci.org/sindresorhus/object-assign) + +> ES2015 [`Object.assign()`](http://www.2ality.com/2014/01/object-assign.html) [ponyfill](https://ponyfill.com) + + +## Use the built-in + +Node.js 4 and up, as well as every evergreen browser (Chrome, Edge, Firefox, Opera, Safari), +support `Object.assign()` :tada:. If you target only those environments, then by all +means, use `Object.assign()` instead of this package. + + +## Install + +``` +$ npm install --save object-assign +``` + + +## Usage + +```js +const objectAssign = require('object-assign'); + +objectAssign({foo: 0}, {bar: 1}); +//=> {foo: 0, bar: 1} + +// multiple sources +objectAssign({foo: 0}, {bar: 1}, {baz: 2}); +//=> {foo: 0, bar: 1, baz: 2} + +// overwrites equal keys +objectAssign({foo: 0}, {foo: 1}, {foo: 2}); +//=> {foo: 2} + +// ignores null and undefined sources +objectAssign({foo: 0}, null, {bar: 1}, undefined); +//=> {foo: 0, bar: 1} +``` + + +## API + +### objectAssign(target, [source, ...]) + +Assigns enumerable own properties of `source` objects to the `target` object and returns the `target` object. Additional `source` objects will overwrite previous ones. + + +## Resources + +- [ES2015 spec - Object.assign](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.assign) + + +## Related + +- [deep-assign](https://github.com/sindresorhus/deep-assign) - Recursive `Object.assign()` + + +## License + +MIT © [Sindre Sorhus](https://sindresorhus.com) diff --git a/package-lock.json b/package-lock.json index 4a3badf..4624fac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "axios": "^1.13.6", + "cors": "^2.8.6", "dotenv": "^17.3.1", "express": "^5.2.1", "mysql2": "^3.19.1" @@ -178,6 +179,23 @@ "node": ">=6.6.0" } }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -756,6 +774,15 @@ "node": ">= 0.6" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/package.json b/package.json index 57cec91..7492f7d 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "homepage": "https://github.com/Syco21/YuGiOh-Database-API#readme", "dependencies": { "axios": "^1.13.6", + "cors": "^2.8.6", "dotenv": "^17.3.1", "express": "^5.2.1", "mysql2": "^3.19.1" diff --git a/src/controllers/cardImageController.js b/src/controllers/cardImageController.js new file mode 100644 index 0000000..47b8ff7 --- /dev/null +++ b/src/controllers/cardImageController.js @@ -0,0 +1,58 @@ +const db = require('../config/db'); +const axios = require('axios'); + +// Use your existing insertCardImage for initial URL insert +const { insertCardImage } = require('../models/cardImageModel'); + +/** + * GET /cardImage/:cardId + * Returns the image blob of a card. If not stored, downloads from image_url, saves to DB. + */ +async function getCardImage(req, res) { + const { cardId } = req.params; + + try { + // Check if blob exists + const [rows] = await db.execute( + 'SELECT image_data, image_url FROM card_images WHERE card_id = ? LIMIT 1', + [cardId] + ); + + if (rows.length === 0) { + return res.status(404).json({ error: 'Card image not found' }); + } + + const cardImage = rows[0]; + + if (cardImage.image_data) { + // Blob exists → return as base64 + const base64Image = cardImage.image_data.toString('base64'); + return res.json({ image: `data:image/png;base64,${base64Image}` }); + } + + // Blob does not exist → download from URL + if (!cardImage.image_url) { + return res.status(404).json({ error: 'No image URL available for this card' }); + } + + const response = await axios.get(cardImage.image_url, { + responseType: 'arraybuffer' + }); + + const imageBuffer = Buffer.from(response.data, 'binary'); + + // Save blob to DB + await db.execute( + 'UPDATE card_images SET image_data = ? WHERE card_id = ?', + [imageBuffer, cardId] + ); + + const base64Image = imageBuffer.toString('base64'); + res.json({ image: `data:image/png;base64,${base64Image}` }); + } catch (err) { + console.error('Error fetching card image:', err); + res.status(500).json({ error: 'Failed to fetch card image' }); + } +} + +module.exports = { getCardImage }; diff --git a/src/controllers/exportCardsController.js b/src/controllers/exportCardsController.js new file mode 100644 index 0000000..f7a37b5 --- /dev/null +++ b/src/controllers/exportCardsController.js @@ -0,0 +1,52 @@ +const db = require('../config/db'); + +/** + * GET /exportCards + * Returns all cards with their printings and amount_owned + */ +async function exportCards(req, res) { + try { + // Fetch all cards + const [cards] = await db.execute(` + SELECT id, name, card_type AS type, frame_type, level, race, attribute, link_val, tcg_date, ocg_date + FROM cards + `); + + // Fetch all printings with sets and rarities + const [printings] = await db.execute(` + SELECT csr.card_id, csr.set_id, csr.card_set_code AS set_code, csr.amount_owned, + s.set_name, + r.id AS rarity_id, r.rarity_name, r.rarity_code + FROM card_sets_rarity csr + JOIN sets s ON csr.set_id = s.id + JOIN rarities r ON csr.rarity_id = r.id + `); + + // Map printings to cards + const cardMap = {}; + cards.forEach(card => { + cardMap[card.id] = { ...card, printings: [] }; + }); + + printings.forEach(p => { + if (cardMap[p.card_id]) { + cardMap[p.card_id].printings.push({ + set_id: p.set_id, + set_name: p.set_name, + set_code: p.set_code, + rarity_id: p.rarity_id, + rarity_name: p.rarity_name, + rarity_code: p.rarity_code, + amount_owned: p.amount_owned || 0 + }); + } + }); + + res.json(Object.values(cardMap)); + } catch (err) { + console.error('Error exporting cards:', err); + res.status(500).json({ error: 'Failed to export cards' }); + } +} + +module.exports = { exportCards }; diff --git a/src/index.js b/src/index.js index 05c5c4c..64bd965 100644 --- a/src/index.js +++ b/src/index.js @@ -1,16 +1,24 @@ const express = require('express'); require('dotenv').config(); +const cors = require('cors'); + +// Routes const importRoutes = require('./routes/importRoutes'); +const collectionRoutes = require('./routes/collectionRoutes'); +const exportCardsRoutes = require('./routes/exportCardsRoutes'); +const cardImageRoutes = require('./routes/cardImageRoutes'); // <-- new const app = express(); +app.use(cors()); // enable CORS for frontend app.use(express.json()); +// Mount routes app.use('/import', importRoutes); +app.use('/collection', collectionRoutes); +app.use('/exportCards', exportCardsRoutes); +app.use('/cardImage', cardImageRoutes); // <-- mount the new card image route const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); -}); - -const collectionRoutes = require('./routes/collectionRoutes'); -app.use('/collection', collectionRoutes); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/routes/cardImageRoutes.js b/src/routes/cardImageRoutes.js new file mode 100644 index 0000000..7e99632 --- /dev/null +++ b/src/routes/cardImageRoutes.js @@ -0,0 +1,8 @@ +const express = require('express'); +const router = express.Router(); +const { getCardImage } = require('../controllers/cardImageController'); + +// GET /cardImage/:cardId +router.get('/:cardId', getCardImage); + +module.exports = router; diff --git a/src/routes/exportCardsRoutes.js b/src/routes/exportCardsRoutes.js new file mode 100644 index 0000000..e9fd1b8 --- /dev/null +++ b/src/routes/exportCardsRoutes.js @@ -0,0 +1,8 @@ +const express = require('express'); +const router = express.Router(); +const { exportCards } = require('../controllers/exportCardsController'); + +// GET /exportCards +router.get('/', exportCards); + +module.exports = router;