first commit

This commit is contained in:
2026-01-10 04:57:43 +00:00
parent 16a76a2cd6
commit 232968de1e
131 changed files with 43262 additions and 0 deletions

213
models/Item.js Normal file
View File

@@ -0,0 +1,213 @@
import mongoose from "mongoose";
const itemSchema = new mongoose.Schema(
{
// Basic Item Information
name: {
type: String,
required: true,
trim: true,
},
description: {
type: String,
default: "",
},
image: {
type: String,
required: true,
},
// Game Information
game: {
type: String,
required: true,
enum: ["cs2", "rust"],
},
// Category
category: {
type: String,
required: true,
enum: [
"rifles",
"pistols",
"knives",
"gloves",
"stickers",
"cases",
"smgs",
"other",
],
},
// Rarity
rarity: {
type: String,
required: true,
enum: [
"common",
"uncommon",
"rare",
"mythical",
"legendary",
"ancient",
"exceedingly",
],
},
// Wear Condition (for CS2 items)
wear: {
type: String,
enum: ["fn", "mw", "ft", "ww", "bs", null],
default: null,
},
// Float Value (for CS2 items)
float: {
type: Number,
min: 0,
max: 1,
default: null,
},
// Phase (for Doppler, Gamma Doppler, etc.)
phase: {
type: String,
enum: [
"Phase 1",
"Phase 2",
"Phase 3",
"Phase 4",
"Ruby",
"Sapphire",
"Black Pearl",
"Emerald",
null,
],
default: null,
},
// Special Properties
statTrak: {
type: Boolean,
default: false,
},
souvenir: {
type: Boolean,
default: false,
},
// Price (seller's listing price)
price: {
type: Number,
required: true,
min: 0,
},
// Market Price (from SteamAPIs.com)
marketPrice: {
type: Number,
default: null,
},
// Last price update timestamp
priceUpdatedAt: {
type: Date,
default: null,
},
// Price Override (admin-set custom price)
priceOverride: {
type: Boolean,
default: false,
},
// Seller Information
seller: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
// Status
status: {
type: String,
enum: ["active", "sold", "removed"],
default: "active",
},
// Timestamps
listedAt: {
type: Date,
default: Date.now,
},
soldAt: {
type: Date,
default: null,
},
// Buyer (if sold)
buyer: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
default: null,
},
// Featured
featured: {
type: Boolean,
default: false,
},
// Views counter
views: {
type: Number,
default: 0,
},
},
{
timestamps: true,
}
);
// Indexes for better query performance
itemSchema.index({ game: 1, status: 1 });
itemSchema.index({ category: 1, status: 1 });
itemSchema.index({ rarity: 1, status: 1 });
itemSchema.index({ price: 1, status: 1 });
itemSchema.index({ seller: 1, status: 1 });
itemSchema.index({ featured: 1, status: 1 });
itemSchema.index({ listedAt: -1 });
itemSchema.index({ phase: 1 });
itemSchema.index({ name: 1 }); // For price updates
// Virtual for seller details
itemSchema.virtual("sellerDetails", {
ref: "User",
localField: "seller",
foreignField: "_id",
justOne: true,
});
// Methods
itemSchema.methods.markAsSold = function (buyerId) {
this.status = "sold";
this.soldAt = new Date();
this.buyer = buyerId;
return this.save();
};
itemSchema.methods.incrementViews = function () {
this.views += 1;
return this.save();
};
itemSchema.methods.updateMarketPrice = function (newPrice) {
this.marketPrice = newPrice;
this.priceUpdatedAt = new Date();
return this.save();
};
const Item = mongoose.model("Item", itemSchema);
export default Item;

180
models/MarketPrice.js Normal file
View File

@@ -0,0 +1,180 @@
import mongoose from "mongoose";
/**
* MarketPrice Model
* Stores reference prices from Steam market for quick lookups
* Used when loading inventory or updating item prices
*/
const marketPriceSchema = new mongoose.Schema(
{
// Item name (market_name from Steam API)
name: {
type: String,
required: true,
index: true,
},
// Game identifier
game: {
type: String,
required: true,
enum: ["cs2", "rust"],
index: true,
},
// Steam App ID
appId: {
type: Number,
required: true,
index: true,
},
// Market hash name (unique identifier from Steam)
marketHashName: {
type: String,
required: true,
unique: true,
},
// Price in USD
price: {
type: Number,
required: true,
min: 0,
},
// Type of price used (safe, median, mean, avg, latest)
priceType: {
type: String,
enum: ["safe", "median", "mean", "avg", "latest"],
default: "safe",
},
// Item image URL
image: {
type: String,
default: null,
},
// Border color (rarity indicator)
borderColor: {
type: String,
default: null,
},
// Steam name ID
nameId: {
type: Number,
default: null,
},
// Last updated timestamp
lastUpdated: {
type: Date,
default: Date.now,
index: true,
},
},
{
timestamps: true,
collection: "marketprices",
}
);
// Compound indexes for fast lookups
marketPriceSchema.index({ game: 1, name: 1 });
marketPriceSchema.index({ game: 1, marketHashName: 1 });
marketPriceSchema.index({ game: 1, price: -1 }); // For sorting by price
marketPriceSchema.index({ lastUpdated: -1 }); // For finding outdated prices
// Static method to find price by market hash name
marketPriceSchema.statics.findByMarketHashName = async function (
marketHashName,
game = null
) {
const query = { marketHashName };
if (game) query.game = game;
return await this.findOne(query);
};
// Static method to find price by name (partial match)
marketPriceSchema.statics.findByName = async function (name, game = null) {
const query = {
$or: [
{ name: name },
{ name: { $regex: name, $options: "i" } },
{ marketHashName: name },
{ marketHashName: { $regex: name, $options: "i" } },
],
};
if (game) query.game = game;
return await this.find(query).limit(10);
};
// Static method to get items by game
marketPriceSchema.statics.getByGame = async function (game, options = {}) {
const { limit = 100, skip = 0, minPrice = 0, maxPrice = null } = options;
const query = { game };
if (minPrice > 0) query.price = { $gte: minPrice };
if (maxPrice) {
query.price = query.price || {};
query.price.$lte = maxPrice;
}
return await this.find(query)
.sort({ price: -1 })
.limit(limit)
.skip(skip);
};
// Static method to get price statistics
marketPriceSchema.statics.getStats = async function (game = null) {
const match = game ? { game } : {};
const stats = await this.aggregate([
{ $match: match },
{
$group: {
_id: null,
count: { $sum: 1 },
avgPrice: { $avg: "$price" },
minPrice: { $min: "$price" },
maxPrice: { $max: "$price" },
totalValue: { $sum: "$price" },
},
},
]);
return stats[0] || {
count: 0,
avgPrice: 0,
minPrice: 0,
maxPrice: 0,
totalValue: 0,
};
};
// Instance method to check if price is outdated
marketPriceSchema.methods.isOutdated = function (hours = 24) {
const now = new Date();
const diff = now - this.lastUpdated;
const hoursDiff = diff / (1000 * 60 * 60);
return hoursDiff > hours;
};
// Instance method to update price
marketPriceSchema.methods.updatePrice = async function (newPrice, priceType) {
this.price = newPrice;
if (priceType) this.priceType = priceType;
this.lastUpdated = new Date();
return await this.save();
};
const MarketPrice = mongoose.model("MarketPrice", marketPriceSchema);
export default MarketPrice;

136
models/Session.js Normal file
View File

@@ -0,0 +1,136 @@
import mongoose from 'mongoose';
const SessionSchema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
index: true,
},
steamId: {
type: String,
required: true,
index: true,
},
token: {
type: String,
required: true,
unique: true,
},
refreshToken: {
type: String,
required: true,
unique: true,
},
ip: {
type: String,
default: null,
},
userAgent: {
type: String,
default: null,
},
device: {
type: String,
default: null,
},
browser: {
type: String,
default: null,
},
os: {
type: String,
default: null,
},
location: {
country: { type: String, default: null },
city: { type: String, default: null },
region: { type: String, default: null },
},
isActive: {
type: Boolean,
default: true,
},
lastActivity: {
type: Date,
default: Date.now,
},
expiresAt: {
type: Date,
required: true,
index: { expires: 0 }, // TTL index - automatically delete expired sessions
},
},
{
timestamps: true,
}
);
// Index for cleaning up old sessions
SessionSchema.index({ createdAt: 1 });
SessionSchema.index({ userId: 1, isActive: 1 });
// Method to mark session as inactive
SessionSchema.methods.deactivate = async function () {
this.isActive = false;
return this.save();
};
// Method to update last activity
SessionSchema.methods.updateActivity = async function () {
this.lastActivity = Date.now();
return this.save();
};
// Static method to clean up inactive sessions for a user
SessionSchema.statics.cleanupUserSessions = async function (userId, keepCurrent = null) {
const query = {
userId,
isActive: false,
};
if (keepCurrent) {
query._id = { $ne: keepCurrent };
}
return this.deleteMany(query);
};
// Static method to get active sessions for a user
SessionSchema.statics.getActiveSessions = async function (userId) {
return this.find({
userId,
isActive: true,
expiresAt: { $gt: new Date() },
}).sort({ lastActivity: -1 });
};
// Static method to revoke all sessions except current
SessionSchema.statics.revokeAllExcept = async function (userId, currentSessionId) {
return this.updateMany(
{
userId,
_id: { $ne: currentSessionId },
isActive: true,
},
{
$set: { isActive: false },
}
);
};
// Static method to revoke all sessions
SessionSchema.statics.revokeAll = async function (userId) {
return this.updateMany(
{
userId,
isActive: true,
},
{
$set: { isActive: false },
}
);
};
export default mongoose.model('Session', SessionSchema);

487
models/Trade.js Normal file
View File

@@ -0,0 +1,487 @@
import mongoose from "mongoose";
/**
* Trade Model
* Tracks Steam trade offers and their states
*/
const tradeSchema = new mongoose.Schema(
{
// Trade offer ID from Steam
offerId: {
type: String,
required: true,
unique: true,
index: true,
},
// User who initiated the trade
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
index: true,
},
steamId: {
type: String,
required: true,
index: true,
},
// Trade state
state: {
type: String,
enum: [
"pending", // Trade offer sent, awaiting user acceptance
"accepted", // User accepted, items transferred
"declined", // User declined the offer
"expired", // Trade offer expired
"canceled", // Trade was canceled (by us or user)
"failed", // Trade failed (invalid items, error, etc.)
"escrow", // Trade in escrow
],
default: "pending",
index: true,
},
// Items involved in the trade
items: [
{
assetId: { type: String, required: true },
name: { type: String, required: true },
image: { type: String },
game: { type: String, enum: ["cs2", "rust"], required: true },
price: { type: Number, required: true },
marketPrice: { type: Number },
category: { type: String },
rarity: { type: String },
wear: { type: String },
statTrak: { type: Boolean, default: false },
souvenir: { type: Boolean, default: false },
phase: { type: String },
},
],
// Financial information
totalValue: {
type: Number,
required: true,
min: 0,
},
fee: {
type: Number,
default: 0,
},
feePercentage: {
type: Number,
default: 0,
},
userReceives: {
type: Number,
required: true,
},
// User's trade URL used
tradeUrl: {
type: String,
required: true,
},
// Steam trade offer URL
tradeOfferUrl: {
type: String,
},
// Verification code (shown on site and in trade message)
verificationCode: {
type: String,
required: true,
index: true,
},
// Timestamps
sentAt: {
type: Date,
default: Date.now,
index: true,
},
acceptedAt: {
type: Date,
},
completedAt: {
type: Date,
},
failedAt: {
type: Date,
},
expiresAt: {
type: Date,
},
// Error information
errorMessage: {
type: String,
},
errorCode: {
type: String,
},
// Transaction reference (created after trade completes)
transactionId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Transaction",
},
// Session tracking
sessionId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Session",
},
// Bot information
botId: {
type: String,
index: true,
},
botUsername: {
type: String,
},
// Retry tracking
retryCount: {
type: Number,
default: 0,
},
lastRetryAt: {
type: Date,
},
// Metadata
metadata: {
type: mongoose.Schema.Types.Mixed,
},
// Notes (internal use)
notes: {
type: String,
},
},
{
timestamps: true,
collection: "trades",
}
);
// Indexes for common queries
tradeSchema.index({ userId: 1, state: 1 });
tradeSchema.index({ state: 1, sentAt: -1 });
tradeSchema.index({ steamId: 1, state: 1 });
tradeSchema.index({ sessionId: 1 });
tradeSchema.index({ createdAt: -1 });
// Virtual for formatted total value
tradeSchema.virtual("formattedTotal").get(function () {
return `$${this.totalValue.toFixed(2)}`;
});
// Virtual for formatted user receives
tradeSchema.virtual("formattedUserReceives").get(function () {
return `$${this.userReceives.toFixed(2)}`;
});
// Virtual for item count
tradeSchema.virtual("itemCount").get(function () {
return this.items.length;
});
// Virtual for time elapsed
tradeSchema.virtual("timeElapsed").get(function () {
const now = new Date();
const start = this.sentAt || this.createdAt;
const diff = now - start;
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return "Just now";
if (minutes < 60) return `${minutes}m ago`;
const hours = Math.floor(minutes / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
});
// Virtual for is expired
tradeSchema.virtual("isExpired").get(function () {
if (!this.expiresAt) return false;
return new Date() > this.expiresAt;
});
// Virtual for is pending
tradeSchema.virtual("isPending").get(function () {
return this.state === "pending";
});
// Instance methods
/**
* Mark trade as accepted
*/
tradeSchema.methods.markAsAccepted = async function () {
this.state = "accepted";
this.acceptedAt = new Date();
return await this.save();
};
/**
* Mark trade as completed (after transaction created)
*/
tradeSchema.methods.markAsCompleted = async function (transactionId) {
this.state = "accepted";
this.completedAt = new Date();
if (transactionId) {
this.transactionId = transactionId;
}
return await this.save();
};
/**
* Mark trade as failed
*/
tradeSchema.methods.markAsFailed = async function (errorMessage, errorCode) {
this.state = "failed";
this.failedAt = new Date();
this.errorMessage = errorMessage;
if (errorCode) {
this.errorCode = errorCode;
}
return await this.save();
};
/**
* Mark trade as declined
*/
tradeSchema.methods.markAsDeclined = async function () {
this.state = "declined";
return await this.save();
};
/**
* Mark trade as expired
*/
tradeSchema.methods.markAsExpired = async function () {
this.state = "expired";
return await this.save();
};
/**
* Mark trade as canceled
*/
tradeSchema.methods.markAsCanceled = async function () {
this.state = "canceled";
return await this.save();
};
/**
* Increment retry count
*/
tradeSchema.methods.incrementRetry = async function () {
this.retryCount += 1;
this.lastRetryAt = new Date();
return await this.save();
};
/**
* Add note
*/
tradeSchema.methods.addNote = async function (note) {
this.notes = this.notes ? `${this.notes}\n${note}` : note;
return await this.save();
};
// Static methods
/**
* Create a new trade record
*/
tradeSchema.statics.createTrade = async function (data) {
const trade = new this({
offerId: data.offerId,
userId: data.userId,
steamId: data.steamId,
state: data.state || "pending",
items: data.items,
totalValue: data.totalValue,
fee: data.fee || 0,
feePercentage: data.feePercentage || 0,
userReceives: data.userReceives,
tradeUrl: data.tradeUrl,
tradeOfferUrl: data.tradeOfferUrl,
verificationCode: data.verificationCode,
sentAt: data.sentAt || new Date(),
expiresAt: data.expiresAt,
sessionId: data.sessionId,
botId: data.botId,
botUsername: data.botUsername,
metadata: data.metadata,
});
return await trade.save();
};
/**
* Get user's trades
*/
tradeSchema.statics.getUserTrades = async function (userId, options = {}) {
const {
limit = 50,
skip = 0,
state = null,
startDate = null,
endDate = null,
} = options;
const query = { userId };
if (state) query.state = state;
if (startDate || endDate) {
query.sentAt = {};
if (startDate) query.sentAt.$gte = new Date(startDate);
if (endDate) query.sentAt.$lte = new Date(endDate);
}
return await this.find(query)
.sort({ sentAt: -1 })
.limit(limit)
.skip(skip)
.populate("userId", "username steamId avatar")
.populate("transactionId")
.exec();
};
/**
* Get trade by offer ID
*/
tradeSchema.statics.getByOfferId = async function (offerId) {
return await this.findOne({ offerId })
.populate("userId", "username steamId avatar")
.populate("transactionId")
.exec();
};
/**
* Get pending trades
*/
tradeSchema.statics.getPendingTrades = async function (limit = 100) {
return await this.find({ state: "pending" })
.sort({ sentAt: -1 })
.limit(limit)
.populate("userId", "username steamId avatar")
.exec();
};
/**
* Get expired trades that need cleanup
*/
tradeSchema.statics.getExpiredTrades = async function () {
const now = new Date();
return await this.find({
state: "pending",
expiresAt: { $lt: now },
}).exec();
};
/**
* Get trade statistics
*/
tradeSchema.statics.getStats = async function (userId = null) {
const match = userId ? { userId: new mongoose.Types.ObjectId(userId) } : {};
const stats = await this.aggregate([
{ $match: match },
{
$group: {
_id: "$state",
count: { $sum: 1 },
totalValue: { $sum: "$totalValue" },
},
},
]);
const result = {
total: 0,
pending: 0,
accepted: 0,
declined: 0,
expired: 0,
canceled: 0,
failed: 0,
totalValue: 0,
acceptedValue: 0,
};
stats.forEach((stat) => {
result.total += stat.count;
result.totalValue += stat.totalValue;
if (stat._id === "accepted") {
result.accepted = stat.count;
result.acceptedValue = stat.totalValue;
} else if (stat._id === "pending") {
result.pending = stat.count;
} else if (stat._id === "declined") {
result.declined = stat.count;
} else if (stat._id === "expired") {
result.expired = stat.count;
} else if (stat._id === "canceled") {
result.canceled = stat.count;
} else if (stat._id === "failed") {
result.failed = stat.count;
}
});
return result;
};
/**
* Cleanup old completed trades
*/
tradeSchema.statics.cleanupOldTrades = async function (daysOld = 30) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysOld);
const result = await this.deleteMany({
state: { $in: ["accepted", "declined", "expired", "canceled", "failed"] },
completedAt: { $lt: cutoffDate },
});
return result.deletedCount;
};
// Pre-save hook
tradeSchema.pre("save", function (next) {
// Set expires at if not set (default 10 minutes)
if (!this.expiresAt && this.state === "pending") {
this.expiresAt = new Date(Date.now() + 10 * 60 * 1000);
}
next();
});
// Ensure virtuals are included in JSON
tradeSchema.set("toJSON", { virtuals: true });
tradeSchema.set("toObject", { virtuals: true });
const Trade = mongoose.model("Trade", tradeSchema);
export default Trade;

370
models/Transaction.js Normal file
View File

@@ -0,0 +1,370 @@
import mongoose from "mongoose";
const transactionSchema = new mongoose.Schema(
{
// User information
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
index: true,
},
steamId: {
type: String,
required: true,
index: true,
},
// Transaction type
type: {
type: String,
enum: [
"deposit",
"withdrawal",
"purchase",
"sale",
"trade",
"bonus",
"refund",
],
required: true,
index: true,
},
// Transaction status
status: {
type: String,
enum: ["pending", "completed", "failed", "cancelled", "processing"],
required: true,
default: "pending",
index: true,
},
// Amount
amount: {
type: Number,
required: true,
},
// Currency (for future multi-currency support)
currency: {
type: String,
default: "USD",
},
// Balance before and after (for audit trail)
balanceBefore: {
type: Number,
required: true,
},
balanceAfter: {
type: Number,
required: true,
},
// Session tracking (for security)
sessionId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Session",
required: false, // Optional for backwards compatibility
index: true,
},
sessionIdShort: {
type: String, // Last 6 chars of session ID for display
required: false,
},
// Related entities
itemId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Item",
required: false, // Only for purchase/sale transactions
},
itemName: {
type: String,
required: false,
},
itemImage: {
type: String,
required: false,
},
// Payment method (for deposits/withdrawals)
paymentMethod: {
type: String,
enum: ["stripe", "paypal", "crypto", "balance", "steam", "other", null],
required: false,
},
// External payment reference
externalId: {
type: String,
required: false,
index: true,
},
// Description and notes
description: {
type: String,
required: false,
},
notes: {
type: String,
required: false, // Internal notes (not visible to user)
},
// Fee information
fee: {
type: Number,
default: 0,
},
feePercentage: {
type: Number,
default: 0,
},
// Timestamps
completedAt: {
type: Date,
required: false,
},
failedAt: {
type: Date,
required: false,
},
cancelledAt: {
type: Date,
required: false,
},
// Error information (if failed)
errorMessage: {
type: String,
required: false,
},
errorCode: {
type: String,
required: false,
},
// Metadata (flexible field for additional data)
metadata: {
type: mongoose.Schema.Types.Mixed,
required: false,
},
},
{
timestamps: true, // Adds createdAt and updatedAt
collection: "transactions",
}
);
// Indexes for common queries
transactionSchema.index({ userId: 1, createdAt: -1 });
transactionSchema.index({ steamId: 1, createdAt: -1 });
transactionSchema.index({ type: 1, status: 1 });
transactionSchema.index({ sessionId: 1, createdAt: -1 });
transactionSchema.index({ status: 1, createdAt: -1 });
// Virtual for formatted amount
transactionSchema.virtual("formattedAmount").get(function () {
return `$${this.amount.toFixed(2)}`;
});
// Virtual for transaction direction (+ or -)
transactionSchema.virtual("direction").get(function () {
const positiveTypes = ["deposit", "sale", "bonus", "refund"];
return positiveTypes.includes(this.type) ? "+" : "-";
});
// Virtual for session color (deterministic based on sessionIdShort)
transactionSchema.virtual("sessionColor").get(function () {
if (!this.sessionIdShort) return "#64748b";
let hash = 0;
for (let i = 0; i < this.sessionIdShort.length; i++) {
hash = this.sessionIdShort.charCodeAt(i) + ((hash << 5) - hash);
}
const hue = Math.abs(hash) % 360;
const saturation = 60 + (Math.abs(hash) % 20);
const lightness = 45 + (Math.abs(hash) % 15);
return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
});
// Instance methods
/**
* Mark transaction as completed
*/
transactionSchema.methods.complete = async function () {
this.status = "completed";
this.completedAt = new Date();
return await this.save();
};
/**
* Mark transaction as failed
*/
transactionSchema.methods.fail = async function (errorMessage, errorCode) {
this.status = "failed";
this.failedAt = new Date();
this.errorMessage = errorMessage;
this.errorCode = errorCode;
return await this.save();
};
/**
* Mark transaction as cancelled
*/
transactionSchema.methods.cancel = async function () {
this.status = "cancelled";
this.cancelledAt = new Date();
return await this.save();
};
/**
* Get session ID short (last 6 chars)
*/
transactionSchema.methods.getSessionIdShort = function () {
if (!this.sessionId) return "SYSTEM";
return this.sessionId.toString().slice(-6).toUpperCase();
};
// Static methods
/**
* Create a new transaction with session tracking
*/
transactionSchema.statics.createTransaction = async function (data) {
const transaction = new this({
userId: data.userId,
steamId: data.steamId,
type: data.type,
status: data.status || "completed",
amount: data.amount,
currency: data.currency || "USD",
balanceBefore: data.balanceBefore || 0,
balanceAfter: data.balanceAfter || 0,
sessionId: data.sessionId,
sessionIdShort: data.sessionId
? data.sessionId.toString().slice(-6).toUpperCase()
: "SYSTEM",
itemId: data.itemId,
itemName: data.itemName,
paymentMethod: data.paymentMethod,
description: data.description,
notes: data.notes,
fee: data.fee || 0,
feePercentage: data.feePercentage || 0,
metadata: data.metadata,
});
return await transaction.save();
};
/**
* Get user's transaction history
*/
transactionSchema.statics.getUserTransactions = async function (
userId,
options = {}
) {
const {
limit = 50,
skip = 0,
type = null,
status = null,
startDate = null,
endDate = null,
} = options;
const query = { userId };
if (type) query.type = type;
if (status) query.status = status;
if (startDate || endDate) {
query.createdAt = {};
if (startDate) query.createdAt.$gte = new Date(startDate);
if (endDate) query.createdAt.$lte = new Date(endDate);
}
return await this.find(query)
.sort({ createdAt: -1 })
.limit(limit)
.skip(skip)
.populate("sessionId", "device browser os ip")
.exec();
};
/**
* Get transactions by session
*/
transactionSchema.statics.getSessionTransactions = async function (sessionId) {
return await this.find({ sessionId })
.sort({ createdAt: -1 })
.populate("itemId", "name rarity game")
.exec();
};
/**
* Get user's transaction statistics
*/
transactionSchema.statics.getUserStats = async function (userId) {
const stats = await this.aggregate([
{ $match: { userId: new mongoose.Types.ObjectId(userId) } },
{
$group: {
_id: "$type",
count: { $sum: 1 },
totalAmount: { $sum: "$amount" },
},
},
]);
const result = {
totalDeposits: 0,
totalWithdrawals: 0,
totalPurchases: 0,
totalSales: 0,
depositCount: 0,
withdrawalCount: 0,
purchaseCount: 0,
saleCount: 0,
};
stats.forEach((stat) => {
if (stat._id === "deposit") {
result.totalDeposits = stat.totalAmount;
result.depositCount = stat.count;
} else if (stat._id === "withdrawal") {
result.totalWithdrawals = stat.totalAmount;
result.withdrawalCount = stat.count;
} else if (stat._id === "purchase") {
result.totalPurchases = stat.totalAmount;
result.purchaseCount = stat.count;
} else if (stat._id === "sale") {
result.totalSales = stat.totalAmount;
result.saleCount = stat.count;
}
});
return result;
};
// Pre-save hook to set sessionIdShort
transactionSchema.pre("save", function (next) {
if (this.sessionId && !this.sessionIdShort) {
this.sessionIdShort = this.sessionId.toString().slice(-6).toUpperCase();
}
next();
});
// Ensure virtuals are included in JSON
transactionSchema.set("toJSON", { virtuals: true });
transactionSchema.set("toObject", { virtuals: true });
const Transaction = mongoose.model("Transaction", transactionSchema);
export default Transaction;

43
models/User.js Normal file
View File

@@ -0,0 +1,43 @@
import mongoose from "mongoose";
const UserSchema = new mongoose.Schema(
{
username: String,
steamId: String,
avatar: String,
tradeUrl: { type: String, default: null },
account_creation: Number,
communityvisibilitystate: Number,
balance: { type: Number, default: 0 },
intercom: { type: String, default: null },
email: {
address: { type: String, default: null },
verified: { type: Boolean, default: false },
emailToken: { type: String, default: null },
},
ban: {
banned: { type: Boolean, default: false },
reason: { type: String, default: null },
expires: { type: Date, default: null },
},
staffLevel: { type: Number, default: 0 },
twoFactor: {
enabled: { type: Boolean, default: false },
qrCode: { type: String, default: null },
secret: { type: String, default: null },
revocationCode: { type: String, default: null },
},
},
{ timestamps: true }
);
// Virtual property for admin check
UserSchema.virtual("isAdmin").get(function () {
return this.staffLevel >= 3; // Staff level 3 or higher is admin
});
// Ensure virtuals are included in JSON
UserSchema.set("toJSON", { virtuals: true });
UserSchema.set("toObject", { virtuals: true });
export default mongoose.model("User", UserSchema);