Merge branch 'main' of https://github.com/MD1125/rugplay
This commit is contained in:
commit
d066bd9be1
31 changed files with 6196 additions and 109 deletions
|
|
@ -31,7 +31,8 @@ export const user = pgTable("user", {
|
|||
precision: 20,
|
||||
scale: 8,
|
||||
}).notNull().default("0.00000000"),
|
||||
loginStreak: integer("login_streak").notNull().default(0)
|
||||
loginStreak: integer("login_streak").notNull().default(0),
|
||||
prestigeLevel: integer("prestige_level").default(0),
|
||||
});
|
||||
|
||||
export const session = pgTable("session", {
|
||||
|
|
@ -190,6 +191,7 @@ export const predictionQuestion = pgTable("prediction_question", {
|
|||
creatorIdIdx: index("prediction_question_creator_id_idx").on(table.creatorId),
|
||||
statusIdx: index("prediction_question_status_idx").on(table.status),
|
||||
resolutionDateIdx: index("prediction_question_resolution_date_idx").on(table.resolutionDate),
|
||||
statusResolutionIdx: index("prediction_question_status_resolution_idx").on(table.status, table.resolutionDate),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
39
website/src/lib/server/image.ts
Normal file
39
website/src/lib/server/image.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import sharp from 'sharp';
|
||||
|
||||
const MAX_SIZE = 128;
|
||||
const WEBP_QUALITY = 50;
|
||||
|
||||
export interface ProcessedImage {
|
||||
buffer: Buffer;
|
||||
contentType: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
export async function processImage(
|
||||
inputBuffer: Buffer,
|
||||
): Promise<ProcessedImage> {
|
||||
try {
|
||||
const image = sharp(inputBuffer, { animated: true });
|
||||
|
||||
const processedBuffer = await image
|
||||
.resize(MAX_SIZE, MAX_SIZE, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({
|
||||
quality: WEBP_QUALITY,
|
||||
effort: 6
|
||||
})
|
||||
.toBuffer();
|
||||
|
||||
return {
|
||||
buffer: processedBuffer,
|
||||
contentType: 'image/webp',
|
||||
size: processedBuffer.length
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Image processing failed:', error);
|
||||
throw new Error('Failed to process image');
|
||||
}
|
||||
}
|
||||
|
|
@ -38,7 +38,91 @@ export async function resolveExpiredQuestions() {
|
|||
);
|
||||
|
||||
if (resolution.confidence < 50) {
|
||||
console.log(`Skipping question ${question.id} due to low confidence: ${resolution.confidence}`);
|
||||
console.log(`Cancelling question ${question.id} due to low confidence: ${resolution.confidence}`);
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
// Mark question as cancelled
|
||||
await tx
|
||||
.update(predictionQuestion)
|
||||
.set({
|
||||
status: 'CANCELLED',
|
||||
resolvedAt: now,
|
||||
})
|
||||
.where(eq(predictionQuestion.id, question.id));
|
||||
|
||||
// Get all bets for this question
|
||||
const bets = await tx
|
||||
.select({
|
||||
id: predictionBet.id,
|
||||
userId: predictionBet.userId,
|
||||
side: predictionBet.side,
|
||||
amount: predictionBet.amount,
|
||||
})
|
||||
.from(predictionBet)
|
||||
.where(and(
|
||||
eq(predictionBet.questionId, question.id),
|
||||
isNull(predictionBet.settledAt)
|
||||
));
|
||||
|
||||
const notificationsToCreate: Array<{
|
||||
userId: number;
|
||||
amount: number;
|
||||
}> = [];
|
||||
|
||||
// Refund all bets
|
||||
for (const bet of bets) {
|
||||
const refundAmount = Number(bet.amount);
|
||||
|
||||
// Mark bet as settled with full refund
|
||||
await tx
|
||||
.update(predictionBet)
|
||||
.set({
|
||||
actualWinnings: refundAmount.toFixed(8),
|
||||
settledAt: now,
|
||||
})
|
||||
.where(eq(predictionBet.id, bet.id));
|
||||
|
||||
// Refund the user
|
||||
if (bet.userId !== null) {
|
||||
const [userData] = await tx
|
||||
.select({ baseCurrencyBalance: user.baseCurrencyBalance })
|
||||
.from(user)
|
||||
.where(eq(user.id, bet.userId))
|
||||
.limit(1);
|
||||
|
||||
if (userData) {
|
||||
const newBalance = Number(userData.baseCurrencyBalance) + refundAmount;
|
||||
await tx
|
||||
.update(user)
|
||||
.set({
|
||||
baseCurrencyBalance: newBalance.toFixed(8),
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(eq(user.id, bet.userId));
|
||||
}
|
||||
|
||||
notificationsToCreate.push({
|
||||
userId: bet.userId,
|
||||
amount: refundAmount
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Create refund notifications for all users who had bets
|
||||
for (const notifData of notificationsToCreate) {
|
||||
const { userId, amount } = notifData;
|
||||
|
||||
const title = 'Prediction skipped 🥀';
|
||||
const message = `You received a full refund of ${formatValue(amount)} for "${question.question}". We recommend betting on more reliable predictions!`;
|
||||
|
||||
await createNotification(
|
||||
userId.toString(),
|
||||
'HOPIUM',
|
||||
title,
|
||||
message,
|
||||
);
|
||||
}
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +223,6 @@ export async function resolveExpiredQuestions() {
|
|||
}
|
||||
});
|
||||
|
||||
console.log(`Successfully resolved question ${question.id}: ${resolution.resolution ? 'YES' : 'NO'} (confidence: ${resolution.confidence}%)`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to resolve question ${question.id}:`, error);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { S3Client, GetObjectCommand, PutObjectCommand, DeleteObjectCommand } fro
|
|||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
import { PRIVATE_B2_KEY_ID, PRIVATE_B2_APP_KEY } from '$env/static/private';
|
||||
import { PUBLIC_B2_BUCKET, PUBLIC_B2_ENDPOINT, PUBLIC_B2_REGION } from '$env/static/public';
|
||||
import { processImage } from './image.js';
|
||||
|
||||
const s3Client = new S3Client({
|
||||
endpoint: PUBLIC_B2_ENDPOINT,
|
||||
|
|
@ -47,7 +48,6 @@ export async function uploadProfilePicture(
|
|||
identifier: string, // Can be user ID or a unique ID from social provider
|
||||
body: Uint8Array,
|
||||
contentType: string,
|
||||
contentLength?: number
|
||||
): Promise<string> {
|
||||
if (!contentType || !contentType.startsWith('image/')) {
|
||||
throw new Error('Invalid file type. Only images are allowed.');
|
||||
|
|
@ -58,17 +58,16 @@ export async function uploadProfilePicture(
|
|||
throw new Error('Unsupported image format. Only JPEG, PNG, GIF, and WebP are allowed.');
|
||||
}
|
||||
|
||||
let fileExtension = contentType.split('/')[1];
|
||||
if (fileExtension === 'jpeg') fileExtension = 'jpg';
|
||||
|
||||
const key = `avatars/${identifier}.${fileExtension}`;
|
||||
const processedImage = await processImage(Buffer.from(body));
|
||||
|
||||
const key = `avatars/${identifier}.webp`;
|
||||
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: PUBLIC_B2_BUCKET,
|
||||
Key: key,
|
||||
Body: body,
|
||||
ContentType: contentType,
|
||||
...(contentLength && { ContentLength: contentLength }),
|
||||
Body: processedImage.buffer,
|
||||
ContentType: processedImage.contentType,
|
||||
ContentLength: processedImage.size,
|
||||
});
|
||||
|
||||
await s3Client.send(command);
|
||||
|
|
@ -79,7 +78,6 @@ export async function uploadCoinIcon(
|
|||
coinSymbol: string,
|
||||
body: Uint8Array,
|
||||
contentType: string,
|
||||
contentLength?: number
|
||||
): Promise<string> {
|
||||
if (!contentType || !contentType.startsWith('image/')) {
|
||||
throw new Error('Invalid file type. Only images are allowed.');
|
||||
|
|
@ -90,17 +88,16 @@ export async function uploadCoinIcon(
|
|||
throw new Error('Unsupported image format. Only JPEG, PNG, GIF, and WebP are allowed.');
|
||||
}
|
||||
|
||||
let fileExtension = contentType.split('/')[1];
|
||||
if (fileExtension === 'jpeg') fileExtension = 'jpg';
|
||||
const processedImage = await processImage(Buffer.from(body));
|
||||
|
||||
const key = `coins/${coinSymbol.toLowerCase()}.${fileExtension}`;
|
||||
const key = `coins/${coinSymbol.toLowerCase()}.webp`;
|
||||
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: PUBLIC_B2_BUCKET,
|
||||
Key: key,
|
||||
Body: body,
|
||||
ContentType: contentType,
|
||||
...(contentLength && { ContentLength: contentLength }),
|
||||
Body: processedImage.buffer,
|
||||
ContentType: processedImage.contentType,
|
||||
ContentLength: processedImage.size,
|
||||
});
|
||||
|
||||
await s3Client.send(command);
|
||||
|
|
|
|||
Reference in a new issue