import { users, mediaOutlets, articles, predictionMarkets, auctions, bids, mediaOutletRequests, comments, predictionBets, type User, type UpsertUser, type MediaOutlet, type InsertMediaOutlet, type Article, type InsertArticle, type PredictionMarket, type InsertPredictionMarket, type Auction, type InsertAuction, type Bid, type InsertBid, type MediaOutletRequest, type InsertMediaOutletRequest, type Comment, type InsertComment, type PredictionBet, type InsertPredictionBet, } from "@shared/schema"; import { db } from "./db"; import { eq, desc, and, ilike, sql } from "drizzle-orm"; // Interface for storage operations export interface IStorage { // User operations (mandatory for Replit Auth) getUser(id: string): Promise; upsertUser(user: UpsertUser): Promise; // Media outlet operations getMediaOutlets(category?: string): Promise; getMediaOutletBySlug(slug: string): Promise; createMediaOutlet(outlet: InsertMediaOutlet): Promise; updateMediaOutlet(id: string, outlet: Partial): Promise; // Article operations getArticlesByOutlet(mediaOutletId: string): Promise; getArticleBySlug(slug: string): Promise
; createArticle(article: InsertArticle): Promise
; updateArticle(id: string, article: Partial): Promise
; getFeaturedArticles(limit?: number): Promise; // Prediction market operations getPredictionMarkets(articleId?: string): Promise; createPredictionMarket(market: InsertPredictionMarket): Promise; // Auction operations getActiveAuctions(): Promise; getAuctionById(id: string): Promise; getAuctionByMediaOutlet(mediaOutletId: string): Promise; createAuction(auction: InsertAuction): Promise; placeBid(bid: InsertBid): Promise; getBidsByAuctionId(auctionId: string): Promise; // Media outlet request operations getMediaOutletRequests(status?: string): Promise; createMediaOutletRequest(request: InsertMediaOutletRequest): Promise; updateMediaOutletRequestStatus(id: string, status: string, reviewerId: string): Promise; // Prediction bet operations createPredictionBet(bet: InsertPredictionBet): Promise; getPredictionBetsByMarket(marketId: string): Promise; getPredictionBetsByUser(userId: string): Promise; // Comment operations getCommentsByArticle(articleId: string): Promise; createComment(comment: InsertComment): Promise; // Analytics operations getAnalytics(): Promise<{ totalArticles: number; activePredictions: number; liveAuctions: number; totalRevenue: number; }>; } export class DatabaseStorage implements IStorage { // User operations (mandatory for Replit Auth) async getUser(id: string): Promise { const [user] = await db.select().from(users).where(eq(users.id, id)); return user; } async upsertUser(userData: UpsertUser): Promise { const [user] = await db .insert(users) .values(userData) .onConflictDoUpdate({ target: users.email, set: { firstName: userData.firstName, lastName: userData.lastName, profileImageUrl: userData.profileImageUrl, updatedAt: new Date(), }, }) .returning(); return user; } // Media outlet operations async getMediaOutlets(category?: string): Promise { if (category) { return await db.select().from(mediaOutlets).where(and(eq(mediaOutlets.isActive, true), eq(mediaOutlets.category, category))); } return await db.select().from(mediaOutlets).where(eq(mediaOutlets.isActive, true)); } async getMediaOutletBySlug(slug: string): Promise { const [outlet] = await db.select().from(mediaOutlets).where(eq(mediaOutlets.slug, slug)); return outlet; } async createMediaOutlet(outlet: InsertMediaOutlet): Promise { const [newOutlet] = await db.insert(mediaOutlets).values(outlet).returning(); return newOutlet; } async updateMediaOutlet(id: string, outlet: Partial): Promise { const [updated] = await db .update(mediaOutlets) .set({ ...outlet, updatedAt: new Date() }) .where(eq(mediaOutlets.id, id)) .returning(); return updated; } // Article operations async getArticlesByOutlet(mediaOutletId: string): Promise { return await db .select() .from(articles) .where(eq(articles.mediaOutletId, mediaOutletId)) .orderBy(desc(articles.publishedAt)); } async getArticleBySlug(slug: string): Promise
{ const [article] = await db.select().from(articles).where(eq(articles.slug, slug)); return article; } async createArticle(article: InsertArticle): Promise
{ const [newArticle] = await db.insert(articles).values(article).returning(); return newArticle; } async updateArticle(id: string, article: Partial): Promise
{ const [updated] = await db .update(articles) .set({ ...article, updatedAt: new Date() }) .where(eq(articles.id, id)) .returning(); return updated; } async getFeaturedArticles(limit = 10): Promise { return await db .select() .from(articles) .where(eq(articles.isFeatured, true)) .orderBy(desc(articles.publishedAt)) .limit(limit); } // Prediction market operations async getPredictionMarkets(articleId?: string): Promise { if (articleId) { return await db.select().from(predictionMarkets).where(and(eq(predictionMarkets.isActive, true), eq(predictionMarkets.articleId, articleId))); } return await db.select().from(predictionMarkets).where(eq(predictionMarkets.isActive, true)).orderBy(desc(predictionMarkets.createdAt)); } async createPredictionMarket(market: InsertPredictionMarket): Promise { const [newMarket] = await db.insert(predictionMarkets).values(market).returning(); return newMarket; } // Auction operations async getActiveAuctions(): Promise { return await db .select() .from(auctions) .where(and(eq(auctions.isActive, true), sql`end_date > NOW()`)) .orderBy(auctions.endDate); } async getAuctionById(id: string): Promise { const [auction] = await db.select().from(auctions).where(eq(auctions.id, id)); return auction; } async getAuctionByMediaOutlet(mediaOutletId: string): Promise { const [auction] = await db.select().from(auctions).where( and(eq(auctions.mediaOutletId, mediaOutletId), eq(auctions.isActive, true)) ); return auction; } async createAuction(auction: InsertAuction): Promise { const [newAuction] = await db.insert(auctions).values(auction).returning(); return newAuction; } async placeBid(bid: InsertBid): Promise { // First, get the auction to validate const auction = await this.getAuctionById(bid.auctionId); if (!auction) { throw new Error("Auction not found"); } // Validate auction status if (!auction.isActive) { throw new Error("Auction is not active"); } // Validate auction end date if (new Date() > auction.endDate) { throw new Error("Auction has ended"); } // Validate bid amount const currentBidAmount = parseFloat(auction.currentBid || "0"); const bidAmount = parseFloat(bid.amount.toString()); if (bidAmount <= currentBidAmount) { throw new Error(`Bid amount must be higher than current bid of ${currentBidAmount}`); } const [newBid] = await db.insert(bids).values(bid).returning(); // Update auction with highest bid await db .update(auctions) .set({ currentBid: bid.amount, highestBidderId: bid.bidderId, updatedAt: new Date() }) .where(eq(auctions.id, bid.auctionId)); return newBid; } async getBidsByAuctionId(auctionId: string): Promise { return await db .select() .from(bids) .where(eq(bids.auctionId, auctionId)) .orderBy(desc(bids.createdAt)); } // Media outlet request operations async getMediaOutletRequests(status?: string): Promise { const query = db.select().from(mediaOutletRequests); if (status) { return await query.where(eq(mediaOutletRequests.status, status)); } return await query.orderBy(desc(mediaOutletRequests.createdAt)); } async createMediaOutletRequest(request: InsertMediaOutletRequest): Promise { const [newRequest] = await db.insert(mediaOutletRequests).values(request).returning(); return newRequest; } async updateMediaOutletRequestStatus(id: string, status: string, reviewerId: string): Promise { const [updated] = await db .update(mediaOutletRequests) .set({ status, reviewedBy: reviewerId, reviewedAt: new Date() }) .where(eq(mediaOutletRequests.id, id)) .returning(); return updated; } // Comment operations async getCommentsByArticle(articleId: string): Promise { return await db .select() .from(comments) .where(eq(comments.articleId, articleId)) .orderBy(desc(comments.isPinned), desc(comments.createdAt)); } async createComment(comment: InsertComment): Promise { const [newComment] = await db.insert(comments).values(comment).returning(); return newComment; } // Prediction bet operations async createPredictionBet(bet: InsertPredictionBet): Promise { // Get the current market to determine the price const market = await this.getPredictionMarketById(bet.marketId); if (!market) { throw new Error("Prediction market not found"); } // Use current market price for the bet const price = bet.side === "yes" ? market.yesPrice : market.noPrice; const [newBet] = await db.insert(predictionBets).values({ ...bet, price: price.toString() }).returning(); // Update market volume and bet count await db .update(predictionMarkets) .set({ totalVolume: sql`total_volume + ${bet.amount}`, totalBets: sql`total_bets + 1`, updatedAt: new Date() }) .where(eq(predictionMarkets.id, bet.marketId)); return newBet; } async getPredictionBetsByMarket(marketId: string): Promise { return await db .select() .from(predictionBets) .where(eq(predictionBets.marketId, marketId)) .orderBy(desc(predictionBets.createdAt)); } async getPredictionBetsByUser(userId: string): Promise { return await db .select() .from(predictionBets) .where(eq(predictionBets.userId, userId)) .orderBy(desc(predictionBets.createdAt)); } // Analytics operations async getAnalytics(): Promise<{ totalArticles: number; activePredictions: number; liveAuctions: number; totalRevenue: number; }> { const [articleCount] = await db .select({ count: sql`count(*)` }) .from(articles); const [predictionCount] = await db .select({ count: sql`count(*)` }) .from(predictionMarkets) .where(eq(predictionMarkets.isActive, true)); const [auctionCount] = await db .select({ count: sql`count(*)` }) .from(auctions) .where(and(eq(auctions.isActive, true), sql`end_date > NOW()`)); const [revenueSum] = await db .select({ sum: sql`COALESCE(SUM(current_bid), 0)` }) .from(auctions); return { totalArticles: articleCount.count, activePredictions: predictionCount.count, liveAuctions: auctionCount.count, totalRevenue: revenueSum.sum }; } } export const storage = new DatabaseStorage();