Update `App.tsx` to grant superadmin access to the admin dashboard and refactor navigation links to use `Link` component from `wouter`. Modify `DatabaseStorage` in `server/storage.ts` to update user records based on email instead of ID. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 069d4324-6c40-4355-955e-c714a50de1ea Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/069d4324-6c40-4355-955e-c714a50de1ea/bVdKIaU
293 lines
9.2 KiB
TypeScript
293 lines
9.2 KiB
TypeScript
import {
|
|
users,
|
|
mediaOutlets,
|
|
articles,
|
|
predictionMarkets,
|
|
auctions,
|
|
bids,
|
|
mediaOutletRequests,
|
|
comments,
|
|
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,
|
|
} 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<User | undefined>;
|
|
upsertUser(user: UpsertUser): Promise<User>;
|
|
|
|
// Media outlet operations
|
|
getMediaOutlets(category?: string): Promise<MediaOutlet[]>;
|
|
getMediaOutletBySlug(slug: string): Promise<MediaOutlet | undefined>;
|
|
createMediaOutlet(outlet: InsertMediaOutlet): Promise<MediaOutlet>;
|
|
updateMediaOutlet(id: string, outlet: Partial<InsertMediaOutlet>): Promise<MediaOutlet>;
|
|
|
|
// Article operations
|
|
getArticlesByOutlet(mediaOutletId: string): Promise<Article[]>;
|
|
getArticleBySlug(slug: string): Promise<Article | undefined>;
|
|
createArticle(article: InsertArticle): Promise<Article>;
|
|
updateArticle(id: string, article: Partial<InsertArticle>): Promise<Article>;
|
|
getFeaturedArticles(limit?: number): Promise<Article[]>;
|
|
|
|
// Prediction market operations
|
|
getPredictionMarkets(articleId?: string): Promise<PredictionMarket[]>;
|
|
createPredictionMarket(market: InsertPredictionMarket): Promise<PredictionMarket>;
|
|
|
|
// Auction operations
|
|
getActiveAuctions(): Promise<Auction[]>;
|
|
getAuctionById(id: string): Promise<Auction | undefined>;
|
|
createAuction(auction: InsertAuction): Promise<Auction>;
|
|
placeBid(bid: InsertBid): Promise<Bid>;
|
|
|
|
// Media outlet request operations
|
|
getMediaOutletRequests(status?: string): Promise<MediaOutletRequest[]>;
|
|
createMediaOutletRequest(request: InsertMediaOutletRequest): Promise<MediaOutletRequest>;
|
|
updateMediaOutletRequestStatus(id: string, status: string, reviewerId: string): Promise<MediaOutletRequest>;
|
|
|
|
// Comment operations
|
|
getCommentsByArticle(articleId: string): Promise<Comment[]>;
|
|
createComment(comment: InsertComment): Promise<Comment>;
|
|
|
|
// 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<User | undefined> {
|
|
const [user] = await db.select().from(users).where(eq(users.id, id));
|
|
return user;
|
|
}
|
|
|
|
async upsertUser(userData: UpsertUser): Promise<User> {
|
|
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<MediaOutlet[]> {
|
|
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<MediaOutlet | undefined> {
|
|
const [outlet] = await db.select().from(mediaOutlets).where(eq(mediaOutlets.slug, slug));
|
|
return outlet;
|
|
}
|
|
|
|
async createMediaOutlet(outlet: InsertMediaOutlet): Promise<MediaOutlet> {
|
|
const [newOutlet] = await db.insert(mediaOutlets).values(outlet).returning();
|
|
return newOutlet;
|
|
}
|
|
|
|
async updateMediaOutlet(id: string, outlet: Partial<InsertMediaOutlet>): Promise<MediaOutlet> {
|
|
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<Article[]> {
|
|
return await db
|
|
.select()
|
|
.from(articles)
|
|
.where(eq(articles.mediaOutletId, mediaOutletId))
|
|
.orderBy(desc(articles.publishedAt));
|
|
}
|
|
|
|
async getArticleBySlug(slug: string): Promise<Article | undefined> {
|
|
const [article] = await db.select().from(articles).where(eq(articles.slug, slug));
|
|
return article;
|
|
}
|
|
|
|
async createArticle(article: InsertArticle): Promise<Article> {
|
|
const [newArticle] = await db.insert(articles).values(article).returning();
|
|
return newArticle;
|
|
}
|
|
|
|
async updateArticle(id: string, article: Partial<InsertArticle>): Promise<Article> {
|
|
const [updated] = await db
|
|
.update(articles)
|
|
.set({ ...article, updatedAt: new Date() })
|
|
.where(eq(articles.id, id))
|
|
.returning();
|
|
return updated;
|
|
}
|
|
|
|
async getFeaturedArticles(limit = 10): Promise<Article[]> {
|
|
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<PredictionMarket[]> {
|
|
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<PredictionMarket> {
|
|
const [newMarket] = await db.insert(predictionMarkets).values(market).returning();
|
|
return newMarket;
|
|
}
|
|
|
|
// Auction operations
|
|
async getActiveAuctions(): Promise<Auction[]> {
|
|
return await db
|
|
.select()
|
|
.from(auctions)
|
|
.where(and(eq(auctions.isActive, true), sql`end_date > NOW()`))
|
|
.orderBy(auctions.endDate);
|
|
}
|
|
|
|
async getAuctionById(id: string): Promise<Auction | undefined> {
|
|
const [auction] = await db.select().from(auctions).where(eq(auctions.id, id));
|
|
return auction;
|
|
}
|
|
|
|
async createAuction(auction: InsertAuction): Promise<Auction> {
|
|
const [newAuction] = await db.insert(auctions).values(auction).returning();
|
|
return newAuction;
|
|
}
|
|
|
|
async placeBid(bid: InsertBid): Promise<Bid> {
|
|
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;
|
|
}
|
|
|
|
// Media outlet request operations
|
|
async getMediaOutletRequests(status?: string): Promise<MediaOutletRequest[]> {
|
|
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<MediaOutletRequest> {
|
|
const [newRequest] = await db.insert(mediaOutletRequests).values(request).returning();
|
|
return newRequest;
|
|
}
|
|
|
|
async updateMediaOutletRequestStatus(id: string, status: string, reviewerId: string): Promise<MediaOutletRequest> {
|
|
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<Comment[]> {
|
|
return await db
|
|
.select()
|
|
.from(comments)
|
|
.where(eq(comments.articleId, articleId))
|
|
.orderBy(desc(comments.isPinned), desc(comments.createdAt));
|
|
}
|
|
|
|
async createComment(comment: InsertComment): Promise<Comment> {
|
|
const [newComment] = await db.insert(comments).values(comment).returning();
|
|
return newComment;
|
|
}
|
|
|
|
// Analytics operations
|
|
async getAnalytics(): Promise<{
|
|
totalArticles: number;
|
|
activePredictions: number;
|
|
liveAuctions: number;
|
|
totalRevenue: number;
|
|
}> {
|
|
const [articleCount] = await db
|
|
.select({ count: sql<number>`count(*)` })
|
|
.from(articles);
|
|
|
|
const [predictionCount] = await db
|
|
.select({ count: sql<number>`count(*)` })
|
|
.from(predictionMarkets)
|
|
.where(eq(predictionMarkets.isActive, true));
|
|
|
|
const [auctionCount] = await db
|
|
.select({ count: sql<number>`count(*)` })
|
|
.from(auctions)
|
|
.where(and(eq(auctions.isActive, true), sql`end_date > NOW()`));
|
|
|
|
const [revenueSum] = await db
|
|
.select({ sum: sql<number>`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();
|