195 lines
8.2 KiB
JavaScript
195 lines
8.2 KiB
JavaScript
"use strict";
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || (function () {
|
|
var ownKeys = function(o) {
|
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
var ar = [];
|
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
return ar;
|
|
};
|
|
return ownKeys(o);
|
|
};
|
|
return function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.DeckImportExportService = void 0;
|
|
const crypto = __importStar(require("crypto"));
|
|
const DeckAggregate_1 = require("../../Domain/Deck/DeckAggregate");
|
|
const Logger_1 = require("./Logger");
|
|
class DeckImportExportService {
|
|
constructor(deckRepo) {
|
|
this.deckRepo = deckRepo;
|
|
this.algorithm = 'aes-256-gcm';
|
|
this.encryptionKey = process.env.DECK_ENCRYPTION_KEY || 'your-32-byte-encryption-key-here!!';
|
|
if (this.encryptionKey.length !== 32) {
|
|
throw new Error('DECK_ENCRYPTION_KEY must be exactly 32 characters long');
|
|
}
|
|
}
|
|
async exportDeckToSpr(deckId, userId) {
|
|
try {
|
|
const deck = await this.deckRepo.findByIdIncludingDeleted(deckId);
|
|
if (!deck) {
|
|
throw new Error('Deck not found');
|
|
}
|
|
if (deck.userid !== userId) {
|
|
throw new Error('Unauthorized: You can only export your own decks');
|
|
}
|
|
const deckData = {
|
|
name: deck.name,
|
|
type: deck.type,
|
|
cards: deck.cards,
|
|
ctype: deck.ctype,
|
|
exportDate: new Date().toISOString(),
|
|
version: '1.0'
|
|
};
|
|
const jsonString = JSON.stringify(deckData);
|
|
const encrypted = this.encrypt(jsonString);
|
|
(0, Logger_1.logAuth)('Deck exported to SPR format', userId, {
|
|
deckId: deck.id,
|
|
deckName: deck.name,
|
|
cardCount: deck.cards.length
|
|
});
|
|
return encrypted;
|
|
}
|
|
catch (error) {
|
|
(0, Logger_1.logError)('Failed to export deck to SPR', error);
|
|
throw error;
|
|
}
|
|
}
|
|
async importDeckFromSpr(sprData, userId) {
|
|
try {
|
|
const decrypted = this.decrypt(sprData);
|
|
const deckData = JSON.parse(decrypted);
|
|
// Validate required fields
|
|
if (!deckData.name || !deckData.cards || deckData.type === undefined) {
|
|
throw new Error('Invalid SPR file format: missing required fields');
|
|
}
|
|
// Create new deck
|
|
const newDeck = new DeckAggregate_1.DeckAggregate();
|
|
newDeck.name = deckData.name;
|
|
newDeck.type = deckData.type;
|
|
newDeck.userid = userId;
|
|
newDeck.cards = deckData.cards;
|
|
newDeck.ctype = deckData.ctype || DeckAggregate_1.CType.PUBLIC;
|
|
newDeck.state = DeckAggregate_1.State.ACTIVE;
|
|
const createdDeck = await this.deckRepo.create(newDeck);
|
|
(0, Logger_1.logAuth)('Deck imported from SPR format', userId, {
|
|
deckId: createdDeck.id,
|
|
deckName: createdDeck.name,
|
|
cardCount: createdDeck.cards.length,
|
|
originalExportDate: deckData.exportDate
|
|
});
|
|
return createdDeck;
|
|
}
|
|
catch (error) {
|
|
(0, Logger_1.logError)('Failed to import deck from SPR', error);
|
|
throw error;
|
|
}
|
|
}
|
|
async importDeckFromJson(jsonData, userId) {
|
|
try {
|
|
// Validate required fields
|
|
if (!jsonData.name || !jsonData.cards || jsonData.type === undefined) {
|
|
throw new Error('Invalid JSON format: missing required fields (name, cards, type)');
|
|
}
|
|
// Create new deck
|
|
const newDeck = new DeckAggregate_1.DeckAggregate();
|
|
newDeck.name = jsonData.name;
|
|
newDeck.type = jsonData.type;
|
|
newDeck.userid = userId;
|
|
newDeck.cards = jsonData.cards;
|
|
newDeck.ctype = jsonData.ctype || DeckAggregate_1.CType.PUBLIC;
|
|
newDeck.state = DeckAggregate_1.State.ACTIVE;
|
|
const createdDeck = await this.deckRepo.create(newDeck);
|
|
(0, Logger_1.logAuth)('Deck imported from JSON format', userId, {
|
|
deckId: createdDeck.id,
|
|
deckName: createdDeck.name,
|
|
cardCount: createdDeck.cards.length
|
|
});
|
|
return createdDeck;
|
|
}
|
|
catch (error) {
|
|
(0, Logger_1.logError)('Failed to import deck from JSON', error);
|
|
throw error;
|
|
}
|
|
}
|
|
// Admin-only function to import JSON without encryption
|
|
async adminImportFromJson(jsonData, targetUserId, adminUserId) {
|
|
try {
|
|
if (!jsonData.name || !jsonData.cards || jsonData.type === undefined) {
|
|
throw new Error('Invalid JSON format: missing required fields (name, cards, type)');
|
|
}
|
|
const newDeck = new DeckAggregate_1.DeckAggregate();
|
|
newDeck.name = jsonData.name;
|
|
newDeck.type = jsonData.type;
|
|
newDeck.userid = targetUserId;
|
|
newDeck.cards = jsonData.cards;
|
|
newDeck.ctype = jsonData.ctype || DeckAggregate_1.CType.PUBLIC;
|
|
newDeck.state = jsonData.state || DeckAggregate_1.State.ACTIVE;
|
|
const createdDeck = await this.deckRepo.create(newDeck);
|
|
(0, Logger_1.logAuth)('Deck imported by admin from JSON', adminUserId, {
|
|
deckId: createdDeck.id,
|
|
deckName: createdDeck.name,
|
|
cardCount: createdDeck.cards.length,
|
|
targetUserId: targetUserId
|
|
});
|
|
return createdDeck;
|
|
}
|
|
catch (error) {
|
|
(0, Logger_1.logError)('Failed to admin import deck from JSON', error);
|
|
throw error;
|
|
}
|
|
}
|
|
encrypt(text) {
|
|
const iv = crypto.randomBytes(16);
|
|
const cipher = crypto.createCipheriv(this.algorithm, this.encryptionKey, iv);
|
|
cipher.setAAD(Buffer.from('SerpentRace-Deck', 'utf8'));
|
|
let encrypted = cipher.update(text, 'utf8');
|
|
encrypted = Buffer.concat([encrypted, cipher.final()]);
|
|
const authTag = cipher.getAuthTag();
|
|
return Buffer.concat([iv, authTag, encrypted]);
|
|
}
|
|
decrypt(encryptedData) {
|
|
if (encryptedData.length < 32) {
|
|
throw new Error('Invalid SPR file: file too short');
|
|
}
|
|
const iv = encryptedData.slice(0, 16);
|
|
const authTag = encryptedData.slice(16, 32);
|
|
const encrypted = encryptedData.slice(32);
|
|
const decipher = crypto.createDecipheriv(this.algorithm, this.encryptionKey, iv);
|
|
decipher.setAAD(Buffer.from('SerpentRace-Deck', 'utf8'));
|
|
decipher.setAuthTag(authTag);
|
|
let decrypted = decipher.update(encrypted, undefined, 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
return decrypted;
|
|
}
|
|
generateFilename(deckName) {
|
|
// Sanitize deck name for filename
|
|
const sanitized = deckName.replace(/[^a-zA-Z0-9\-_]/g, '_');
|
|
const timestamp = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
return `${sanitized}_${timestamp}.spr`;
|
|
}
|
|
}
|
|
exports.DeckImportExportService = DeckImportExportService;
|
|
//# sourceMappingURL=DeckImportExportService.js.map
|