Add core UI components and layout for media platform

Initializes the client-side application with fundamental UI components, including navigation, cards for articles and auctions, and various elements for user interaction and display.

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
This commit is contained in:
kimjaehyeon0101
2025-09-29 14:35:50 +00:00
parent 4d252ca7a6
commit 2cbad88faa
89 changed files with 17584 additions and 0 deletions

15
server/db.ts Normal file
View File

@ -0,0 +1,15 @@
import { Pool, neonConfig } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-serverless';
import ws from "ws";
import * as schema from "@shared/schema";
neonConfig.webSocketConstructor = ws;
if (!process.env.DATABASE_URL) {
throw new Error(
"DATABASE_URL must be set. Did you forget to provision a database?",
);
}
export const pool = new Pool({ connectionString: process.env.DATABASE_URL });
export const db = drizzle({ client: pool, schema });

71
server/index.ts Normal file
View File

@ -0,0 +1,71 @@
import express, { type Request, Response, NextFunction } from "express";
import { registerRoutes } from "./routes";
import { setupVite, serveStatic, log } from "./vite";
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use((req, res, next) => {
const start = Date.now();
const path = req.path;
let capturedJsonResponse: Record<string, any> | undefined = undefined;
const originalResJson = res.json;
res.json = function (bodyJson, ...args) {
capturedJsonResponse = bodyJson;
return originalResJson.apply(res, [bodyJson, ...args]);
};
res.on("finish", () => {
const duration = Date.now() - start;
if (path.startsWith("/api")) {
let logLine = `${req.method} ${path} ${res.statusCode} in ${duration}ms`;
if (capturedJsonResponse) {
logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`;
}
if (logLine.length > 80) {
logLine = logLine.slice(0, 79) + "…";
}
log(logLine);
}
});
next();
});
(async () => {
const server = await registerRoutes(app);
app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
const status = err.status || err.statusCode || 500;
const message = err.message || "Internal Server Error";
res.status(status).json({ message });
throw err;
});
// importantly only setup vite in development and after
// setting up all the other routes so the catch-all route
// doesn't interfere with the other routes
if (app.get("env") === "development") {
await setupVite(app, server);
} else {
serveStatic(app);
}
// ALWAYS serve the app on the port specified in the environment variable PORT
// Other ports are firewalled. Default to 5000 if not specified.
// this serves both the API and the client.
// It is the only port that is not firewalled.
const port = parseInt(process.env.PORT || '5000', 10);
server.listen({
port,
host: "0.0.0.0",
reusePort: true,
}, () => {
log(`serving on port ${port}`);
});
})();

157
server/replitAuth.ts Normal file
View File

@ -0,0 +1,157 @@
import * as client from "openid-client";
import { Strategy, type VerifyFunction } from "openid-client/passport";
import passport from "passport";
import session from "express-session";
import type { Express, RequestHandler } from "express";
import memoize from "memoizee";
import connectPg from "connect-pg-simple";
import { storage } from "./storage";
if (!process.env.REPLIT_DOMAINS) {
throw new Error("Environment variable REPLIT_DOMAINS not provided");
}
const getOidcConfig = memoize(
async () => {
return await client.discovery(
new URL(process.env.ISSUER_URL ?? "https://replit.com/oidc"),
process.env.REPL_ID!
);
},
{ maxAge: 3600 * 1000 }
);
export function getSession() {
const sessionTtl = 7 * 24 * 60 * 60 * 1000; // 1 week
const pgStore = connectPg(session);
const sessionStore = new pgStore({
conString: process.env.DATABASE_URL,
createTableIfMissing: false,
ttl: sessionTtl,
tableName: "sessions",
});
return session({
secret: process.env.SESSION_SECRET!,
store: sessionStore,
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
secure: true,
maxAge: sessionTtl,
},
});
}
function updateUserSession(
user: any,
tokens: client.TokenEndpointResponse & client.TokenEndpointResponseHelpers
) {
user.claims = tokens.claims();
user.access_token = tokens.access_token;
user.refresh_token = tokens.refresh_token;
user.expires_at = user.claims?.exp;
}
async function upsertUser(
claims: any,
) {
await storage.upsertUser({
id: claims["sub"],
email: claims["email"],
firstName: claims["first_name"],
lastName: claims["last_name"],
profileImageUrl: claims["profile_image_url"],
});
}
export async function setupAuth(app: Express) {
app.set("trust proxy", 1);
app.use(getSession());
app.use(passport.initialize());
app.use(passport.session());
const config = await getOidcConfig();
const verify: VerifyFunction = async (
tokens: client.TokenEndpointResponse & client.TokenEndpointResponseHelpers,
verified: passport.AuthenticateCallback
) => {
const user = {};
updateUserSession(user, tokens);
await upsertUser(tokens.claims());
verified(null, user);
};
for (const domain of process.env
.REPLIT_DOMAINS!.split(",")) {
const strategy = new Strategy(
{
name: `replitauth:${domain}`,
config,
scope: "openid email profile offline_access",
callbackURL: `https://${domain}/api/callback`,
},
verify,
);
passport.use(strategy);
}
passport.serializeUser((user: Express.User, cb) => cb(null, user));
passport.deserializeUser((user: Express.User, cb) => cb(null, user));
app.get("/api/login", (req, res, next) => {
passport.authenticate(`replitauth:${req.hostname}`, {
prompt: "login consent",
scope: ["openid", "email", "profile", "offline_access"],
})(req, res, next);
});
app.get("/api/callback", (req, res, next) => {
passport.authenticate(`replitauth:${req.hostname}`, {
successReturnToOrRedirect: "/",
failureRedirect: "/api/login",
})(req, res, next);
});
app.get("/api/logout", (req, res) => {
req.logout(() => {
res.redirect(
client.buildEndSessionUrl(config, {
client_id: process.env.REPL_ID!,
post_logout_redirect_uri: `${req.protocol}://${req.hostname}`,
}).href
);
});
});
}
export const isAuthenticated: RequestHandler = async (req, res, next) => {
const user = req.user as any;
if (!req.isAuthenticated() || !user.expires_at) {
return res.status(401).json({ message: "Unauthorized" });
}
const now = Math.floor(Date.now() / 1000);
if (now <= user.expires_at) {
return next();
}
const refreshToken = user.refresh_token;
if (!refreshToken) {
res.status(401).json({ message: "Unauthorized" });
return;
}
try {
const config = await getOidcConfig();
const tokenResponse = await client.refreshTokenGrant(config, refreshToken);
updateUserSession(user, tokenResponse);
return next();
} catch (error) {
res.status(401).json({ message: "Unauthorized" });
return;
}
};

246
server/routes.ts Normal file
View File

@ -0,0 +1,246 @@
import type { Express } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage";
import { setupAuth, isAuthenticated } from "./replitAuth";
import { insertArticleSchema, insertMediaOutletRequestSchema, insertBidSchema, insertCommentSchema } from "@shared/schema";
export async function registerRoutes(app: Express): Promise<Server> {
// Auth middleware
await setupAuth(app);
// Auth routes
app.get('/api/auth/user', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims.sub;
const user = await storage.getUser(userId);
res.json(user);
} catch (error) {
console.error("Error fetching user:", error);
res.status(500).json({ message: "Failed to fetch user" });
}
});
// Media outlet routes
app.get('/api/media-outlets', async (req, res) => {
try {
const category = req.query.category as string;
const outlets = await storage.getMediaOutlets(category);
res.json(outlets);
} catch (error) {
console.error("Error fetching media outlets:", error);
res.status(500).json({ message: "Failed to fetch media outlets" });
}
});
app.get('/api/media-outlets/:slug', async (req, res) => {
try {
const outlet = await storage.getMediaOutletBySlug(req.params.slug);
if (!outlet) {
return res.status(404).json({ message: "Media outlet not found" });
}
res.json(outlet);
} catch (error) {
console.error("Error fetching media outlet:", error);
res.status(500).json({ message: "Failed to fetch media outlet" });
}
});
// Article routes
app.get('/api/media-outlets/:outletId/articles', async (req, res) => {
try {
const articles = await storage.getArticlesByOutlet(req.params.outletId);
res.json(articles);
} catch (error) {
console.error("Error fetching articles:", error);
res.status(500).json({ message: "Failed to fetch articles" });
}
});
app.get('/api/articles/:slug', async (req, res) => {
try {
const article = await storage.getArticleBySlug(req.params.slug);
if (!article) {
return res.status(404).json({ message: "Article not found" });
}
res.json(article);
} catch (error) {
console.error("Error fetching article:", error);
res.status(500).json({ message: "Failed to fetch article" });
}
});
app.get('/api/articles/featured', async (req, res) => {
try {
const limit = parseInt(req.query.limit as string) || 10;
const articles = await storage.getFeaturedArticles(limit);
res.json(articles);
} catch (error) {
console.error("Error fetching featured articles:", error);
res.status(500).json({ message: "Failed to fetch featured articles" });
}
});
app.post('/api/articles', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims.sub;
const user = await storage.getUser(userId);
if (!user || (user.role !== 'admin' && user.role !== 'superadmin')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const articleData = insertArticleSchema.parse({
...req.body,
authorId: userId
});
const article = await storage.createArticle(articleData);
res.status(201).json(article);
} catch (error) {
console.error("Error creating article:", error);
res.status(500).json({ message: "Failed to create article" });
}
});
// Prediction market routes
app.get('/api/prediction-markets', async (req, res) => {
try {
const articleId = req.query.articleId as string;
const markets = await storage.getPredictionMarkets(articleId);
res.json(markets);
} catch (error) {
console.error("Error fetching prediction markets:", error);
res.status(500).json({ message: "Failed to fetch prediction markets" });
}
});
// Auction routes
app.get('/api/auctions', async (req, res) => {
try {
const auctions = await storage.getActiveAuctions();
res.json(auctions);
} catch (error) {
console.error("Error fetching auctions:", error);
res.status(500).json({ message: "Failed to fetch auctions" });
}
});
app.post('/api/auctions/:id/bid', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims.sub;
const bidData = insertBidSchema.parse({
...req.body,
auctionId: req.params.id,
bidderId: userId
});
const bid = await storage.placeBid(bidData);
res.status(201).json(bid);
} catch (error) {
console.error("Error placing bid:", error);
res.status(500).json({ message: "Failed to place bid" });
}
});
// Media outlet request routes
app.get('/api/media-outlet-requests', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims.sub;
const user = await storage.getUser(userId);
if (!user || user.role !== 'superadmin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const status = req.query.status as string;
const requests = await storage.getMediaOutletRequests(status);
res.json(requests);
} catch (error) {
console.error("Error fetching requests:", error);
res.status(500).json({ message: "Failed to fetch requests" });
}
});
app.post('/api/media-outlet-requests', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims.sub;
const requestData = insertMediaOutletRequestSchema.parse({
...req.body,
requesterId: userId
});
const request = await storage.createMediaOutletRequest(requestData);
res.status(201).json(request);
} catch (error) {
console.error("Error creating request:", error);
res.status(500).json({ message: "Failed to create request" });
}
});
app.patch('/api/media-outlet-requests/:id', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims.sub;
const user = await storage.getUser(userId);
if (!user || user.role !== 'superadmin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const { status } = req.body;
const request = await storage.updateMediaOutletRequestStatus(req.params.id, status, userId);
res.json(request);
} catch (error) {
console.error("Error updating request:", error);
res.status(500).json({ message: "Failed to update request" });
}
});
// Comment routes
app.get('/api/articles/:articleId/comments', async (req, res) => {
try {
const comments = await storage.getCommentsByArticle(req.params.articleId);
res.json(comments);
} catch (error) {
console.error("Error fetching comments:", error);
res.status(500).json({ message: "Failed to fetch comments" });
}
});
app.post('/api/articles/:articleId/comments', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims.sub;
const commentData = insertCommentSchema.parse({
...req.body,
articleId: req.params.articleId,
authorId: userId
});
const comment = await storage.createComment(commentData);
res.status(201).json(comment);
} catch (error) {
console.error("Error creating comment:", error);
res.status(500).json({ message: "Failed to create comment" });
}
});
// Analytics routes
app.get('/api/analytics', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims.sub;
const user = await storage.getUser(userId);
if (!user || (user.role !== 'admin' && user.role !== 'superadmin')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const analytics = await storage.getAnalytics();
res.json(analytics);
} catch (error) {
console.error("Error fetching analytics:", error);
res.status(500).json({ message: "Failed to fetch analytics" });
}
});
const httpServer = createServer(app);
return httpServer;
}

290
server/storage.ts Normal file
View File

@ -0,0 +1,290 @@
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.id,
set: {
...userData,
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();

85
server/vite.ts Normal file
View File

@ -0,0 +1,85 @@
import express, { type Express } from "express";
import fs from "fs";
import path from "path";
import { createServer as createViteServer, createLogger } from "vite";
import { type Server } from "http";
import viteConfig from "../vite.config";
import { nanoid } from "nanoid";
const viteLogger = createLogger();
export function log(message: string, source = "express") {
const formattedTime = new Date().toLocaleTimeString("en-US", {
hour: "numeric",
minute: "2-digit",
second: "2-digit",
hour12: true,
});
console.log(`${formattedTime} [${source}] ${message}`);
}
export async function setupVite(app: Express, server: Server) {
const serverOptions = {
middlewareMode: true,
hmr: { server },
allowedHosts: true as const,
};
const vite = await createViteServer({
...viteConfig,
configFile: false,
customLogger: {
...viteLogger,
error: (msg, options) => {
viteLogger.error(msg, options);
process.exit(1);
},
},
server: serverOptions,
appType: "custom",
});
app.use(vite.middlewares);
app.use("*", async (req, res, next) => {
const url = req.originalUrl;
try {
const clientTemplate = path.resolve(
import.meta.dirname,
"..",
"client",
"index.html",
);
// always reload the index.html file from disk incase it changes
let template = await fs.promises.readFile(clientTemplate, "utf-8");
template = template.replace(
`src="/src/main.tsx"`,
`src="/src/main.tsx?v=${nanoid()}"`,
);
const page = await vite.transformIndexHtml(url, template);
res.status(200).set({ "Content-Type": "text/html" }).end(page);
} catch (e) {
vite.ssrFixStacktrace(e as Error);
next(e);
}
});
}
export function serveStatic(app: Express) {
const distPath = path.resolve(import.meta.dirname, "public");
if (!fs.existsSync(distPath)) {
throw new Error(
`Could not find the build directory: ${distPath}, make sure to build the client first`,
);
}
app.use(express.static(distPath));
// fall through to index.html if the file doesn't exist
app.use("*", (_req, res) => {
res.sendFile(path.resolve(distPath, "index.html"));
});
}