feat: better logging
This commit is contained in:
1
packages/frontend/src/app.d.ts
vendored
1
packages/frontend/src/app.d.ts
vendored
@@ -8,6 +8,7 @@ declare global {
|
||||
// interface Error {}
|
||||
interface Locals {
|
||||
authStatus: AuthStatus;
|
||||
requestId: string;
|
||||
}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
|
||||
@@ -1,27 +1,97 @@
|
||||
import { isAuthenticated } from "$lib/services";
|
||||
import { logger, generateRequestId } from "$lib/logger";
|
||||
import type { Handle } from "@sveltejs/kit";
|
||||
|
||||
export async function handle({ event, resolve }) {
|
||||
const { cookies, locals } = event;
|
||||
// Log startup info once
|
||||
let hasLoggedStartup = false;
|
||||
if (!hasLoggedStartup) {
|
||||
logger.startup();
|
||||
hasLoggedStartup = true;
|
||||
}
|
||||
|
||||
export const handle: Handle = async ({ event, resolve }) => {
|
||||
const { cookies, locals, url, request } = event;
|
||||
const startTime = Date.now();
|
||||
|
||||
// Generate unique request ID
|
||||
const requestId = generateRequestId();
|
||||
|
||||
// Add request ID to locals for access in other handlers
|
||||
locals.requestId = requestId;
|
||||
|
||||
// Log incoming request
|
||||
logger.request(request.method, url.pathname, {
|
||||
requestId,
|
||||
context: {
|
||||
userAgent: request.headers.get('user-agent')?.substring(0, 100),
|
||||
referer: request.headers.get('referer'),
|
||||
ip: request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip'),
|
||||
},
|
||||
});
|
||||
|
||||
// Handle authentication
|
||||
const token = cookies.get("directus_session_token");
|
||||
|
||||
if (token) {
|
||||
locals.authStatus = await isAuthenticated(token);
|
||||
// if (locals.authStatus.authenticated) {
|
||||
// cookies.set('directus_refresh_token', locals.authStatus.data!.refresh_token!, {
|
||||
// httpOnly: true,
|
||||
// secure: true,
|
||||
// domain: '.pivoine.art',
|
||||
// path: '/'
|
||||
// })
|
||||
// }
|
||||
try {
|
||||
locals.authStatus = await isAuthenticated(token);
|
||||
|
||||
if (locals.authStatus.authenticated) {
|
||||
logger.auth('Token validated', true, {
|
||||
requestId,
|
||||
userId: locals.authStatus.user?.id,
|
||||
context: {
|
||||
email: locals.authStatus.user?.email,
|
||||
role: locals.authStatus.user?.role?.name,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
logger.auth('Token invalid', false, { requestId });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Authentication check failed', {
|
||||
requestId,
|
||||
error: error instanceof Error ? error : new Error(String(error)),
|
||||
});
|
||||
locals.authStatus = { authenticated: false };
|
||||
}
|
||||
} else {
|
||||
logger.debug('No session token found', { requestId });
|
||||
locals.authStatus = { authenticated: false };
|
||||
}
|
||||
|
||||
return await resolve(event, {
|
||||
filterSerializedResponseHeaders: (key) => {
|
||||
return key.toLowerCase() === "content-type";
|
||||
// Resolve the request
|
||||
let response: Response;
|
||||
try {
|
||||
response = await resolve(event, {
|
||||
filterSerializedResponseHeaders: (key) => {
|
||||
return key.toLowerCase() === "content-type";
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
logger.error('Request handler error', {
|
||||
requestId,
|
||||
method: request.method,
|
||||
path: url.pathname,
|
||||
duration,
|
||||
error: error instanceof Error ? error : new Error(String(error)),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Log response
|
||||
const duration = Date.now() - startTime;
|
||||
logger.response(request.method, url.pathname, response.status, duration, {
|
||||
requestId,
|
||||
userId: locals.authStatus.authenticated ? locals.authStatus.user?.id : undefined,
|
||||
context: {
|
||||
cached: response.headers.get('x-sveltekit-page') === 'true',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Add request ID to response headers (useful for debugging)
|
||||
response.headers.set('x-request-id', requestId);
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
148
packages/frontend/src/lib/logger.ts
Normal file
148
packages/frontend/src/lib/logger.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Server-side logging utility for sexy.pivoine.art
|
||||
* Provides structured logging with context and request tracing
|
||||
*/
|
||||
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
||||
|
||||
interface LogContext {
|
||||
timestamp: string;
|
||||
level: LogLevel;
|
||||
message: string;
|
||||
context?: Record<string, unknown>;
|
||||
requestId?: string;
|
||||
userId?: string;
|
||||
path?: string;
|
||||
method?: string;
|
||||
duration?: number;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
class Logger {
|
||||
private isDev = process.env.NODE_ENV === 'development';
|
||||
private serviceName = 'sexy.pivoine.art';
|
||||
|
||||
private formatLog(ctx: LogContext): string {
|
||||
const { timestamp, level, message, context, requestId, userId, path, method, duration, error } = ctx;
|
||||
|
||||
const parts = [
|
||||
`[${timestamp}]`,
|
||||
`[${level.toUpperCase()}]`,
|
||||
requestId ? `[${requestId}]` : null,
|
||||
method && path ? `${method} ${path}` : null,
|
||||
message,
|
||||
userId ? `user=${userId}` : null,
|
||||
duration !== undefined ? `${duration}ms` : null,
|
||||
].filter(Boolean);
|
||||
|
||||
let logString = parts.join(' ');
|
||||
|
||||
if (context && Object.keys(context).length > 0) {
|
||||
logString += ' ' + JSON.stringify(context);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
logString += `\n Error: ${error.message}\n Stack: ${error.stack}`;
|
||||
}
|
||||
|
||||
return logString;
|
||||
}
|
||||
|
||||
private log(level: LogLevel, message: string, meta: Partial<LogContext> = {}) {
|
||||
const timestamp = new Date().toISOString();
|
||||
const logContext: LogContext = {
|
||||
timestamp,
|
||||
level,
|
||||
message,
|
||||
...meta,
|
||||
};
|
||||
|
||||
const formattedLog = this.formatLog(logContext);
|
||||
|
||||
switch (level) {
|
||||
case 'debug':
|
||||
if (this.isDev) console.debug(formattedLog);
|
||||
break;
|
||||
case 'info':
|
||||
console.info(formattedLog);
|
||||
break;
|
||||
case 'warn':
|
||||
console.warn(formattedLog);
|
||||
break;
|
||||
case 'error':
|
||||
console.error(formattedLog);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
debug(message: string, meta?: Partial<LogContext>) {
|
||||
this.log('debug', message, meta);
|
||||
}
|
||||
|
||||
info(message: string, meta?: Partial<LogContext>) {
|
||||
this.log('info', message, meta);
|
||||
}
|
||||
|
||||
warn(message: string, meta?: Partial<LogContext>) {
|
||||
this.log('warn', message, meta);
|
||||
}
|
||||
|
||||
error(message: string, meta?: Partial<LogContext>) {
|
||||
this.log('error', message, meta);
|
||||
}
|
||||
|
||||
// Request logging helper
|
||||
request(
|
||||
method: string,
|
||||
path: string,
|
||||
meta: Partial<LogContext> = {}
|
||||
) {
|
||||
this.info('→ Request received', { method, path, ...meta });
|
||||
}
|
||||
|
||||
response(
|
||||
method: string,
|
||||
path: string,
|
||||
status: number,
|
||||
duration: number,
|
||||
meta: Partial<LogContext> = {}
|
||||
) {
|
||||
const level = status >= 500 ? 'error' : status >= 400 ? 'warn' : 'info';
|
||||
this.log(level, `← Response ${status}`, { method, path, duration, ...meta });
|
||||
}
|
||||
|
||||
// Authentication logging
|
||||
auth(action: string, success: boolean, meta: Partial<LogContext> = {}) {
|
||||
this.info(`🔐 Auth: ${action} ${success ? 'success' : 'failed'}`, meta);
|
||||
}
|
||||
|
||||
// Startup logging
|
||||
startup() {
|
||||
const env = {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
PUBLIC_API_URL: process.env.PUBLIC_API_URL,
|
||||
PUBLIC_URL: process.env.PUBLIC_URL,
|
||||
PUBLIC_UMAMI_ID: process.env.PUBLIC_UMAMI_ID ? '***set***' : 'not set',
|
||||
LETTERSPACE_API_URL: process.env.LETTERSPACE_API_URL || 'not set',
|
||||
PORT: process.env.PORT || '3000',
|
||||
HOST: process.env.HOST || '0.0.0.0',
|
||||
};
|
||||
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('🍑 sexy.pivoine.art - Server Starting 💜');
|
||||
console.log('='.repeat(60));
|
||||
console.log('\n📋 Environment Configuration:');
|
||||
Object.entries(env).forEach(([key, value]) => {
|
||||
console.log(` ${key}: ${value}`);
|
||||
});
|
||||
console.log('\n' + '='.repeat(60) + '\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
export const logger = new Logger();
|
||||
|
||||
// Generate request ID
|
||||
export function generateRequestId(): string {
|
||||
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
@@ -18,6 +18,32 @@ import {
|
||||
} from "@directus/sdk";
|
||||
import type { Article, Model, Stats, User, Video } from "$lib/types";
|
||||
import { PUBLIC_URL } from "$env/static/public";
|
||||
import { logger } from "$lib/logger";
|
||||
|
||||
// Helper to log API calls
|
||||
async function loggedApiCall<T>(
|
||||
operationName: string,
|
||||
operation: () => Promise<T>,
|
||||
context?: Record<string, unknown>
|
||||
): Promise<T> {
|
||||
const startTime = Date.now();
|
||||
|
||||
try {
|
||||
logger.debug(`🔄 API: ${operationName}`, { context });
|
||||
const result = await operation();
|
||||
const duration = Date.now() - startTime;
|
||||
logger.info(`✅ API: ${operationName} succeeded`, { duration, context });
|
||||
return result;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - startTime;
|
||||
logger.error(`❌ API: ${operationName} failed`, {
|
||||
duration,
|
||||
context,
|
||||
error: error instanceof Error ? error : new Error(String(error)),
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const userFields = [
|
||||
"*",
|
||||
@@ -29,18 +55,24 @@ const userFields = [
|
||||
];
|
||||
|
||||
export async function isAuthenticated(token: string) {
|
||||
try {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
directus.setToken(token);
|
||||
const user = await directus.request(
|
||||
readMe({
|
||||
fields: userFields,
|
||||
}),
|
||||
);
|
||||
return { authenticated: true, user };
|
||||
} catch {
|
||||
return { authenticated: false };
|
||||
}
|
||||
return loggedApiCall(
|
||||
"isAuthenticated",
|
||||
async () => {
|
||||
try {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
directus.setToken(token);
|
||||
const user = await directus.request(
|
||||
readMe({
|
||||
fields: userFields,
|
||||
}),
|
||||
);
|
||||
return { authenticated: true, user };
|
||||
} catch {
|
||||
return { authenticated: false };
|
||||
}
|
||||
},
|
||||
{ hasToken: !!token },
|
||||
);
|
||||
}
|
||||
|
||||
export async function register(
|
||||
@@ -49,119 +81,167 @@ export async function register(
|
||||
firstName: string,
|
||||
lastName: string,
|
||||
) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(
|
||||
registerUser(email, password, {
|
||||
verification_url: `${PUBLIC_URL || "http://localhost:3000"}/signup/verify`,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
}),
|
||||
return loggedApiCall(
|
||||
"register",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(
|
||||
registerUser(email, password, {
|
||||
verification_url: `${PUBLIC_URL || "http://localhost:3000"}/signup/verify`,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
}),
|
||||
);
|
||||
},
|
||||
{ email, firstName, lastName },
|
||||
);
|
||||
}
|
||||
|
||||
export async function verify(token: string, fetch?: typeof globalThis.fetch) {
|
||||
const directus = fetch
|
||||
? getDirectusInstance((args) => fetch(args, { redirect: "manual" }))
|
||||
: getDirectusInstance(fetch);
|
||||
return directus.request(registerUserVerify(token));
|
||||
return loggedApiCall(
|
||||
"verify",
|
||||
async () => {
|
||||
const directus = fetch
|
||||
? getDirectusInstance((args) => fetch(args, { redirect: "manual" }))
|
||||
: getDirectusInstance(fetch);
|
||||
return directus.request(registerUserVerify(token));
|
||||
},
|
||||
{ hasToken: !!token },
|
||||
);
|
||||
}
|
||||
|
||||
export async function login(email: string, password: string) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.login({ email, password });
|
||||
return loggedApiCall(
|
||||
"login",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.login({ email, password });
|
||||
},
|
||||
{ email },
|
||||
);
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.logout();
|
||||
return loggedApiCall("logout", async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.logout();
|
||||
});
|
||||
}
|
||||
|
||||
export async function requestPassword(email: string) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(
|
||||
passwordRequest(email, `${PUBLIC_URL || "http://localhost:3000"}/password/reset`),
|
||||
return loggedApiCall(
|
||||
"requestPassword",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(
|
||||
passwordRequest(email, `${PUBLIC_URL || "http://localhost:3000"}/password/reset`),
|
||||
);
|
||||
},
|
||||
{ email },
|
||||
);
|
||||
}
|
||||
|
||||
export async function resetPassword(token: string, password: string) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(passwordReset(token, password));
|
||||
return loggedApiCall(
|
||||
"resetPassword",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(passwordReset(token, password));
|
||||
},
|
||||
{ hasToken: !!token },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getArticles(fetch?: typeof globalThis.fetch) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<Article[]>(
|
||||
readItems("sexy_articles", {
|
||||
fields: ["*", "author.*"],
|
||||
where: { publish_date: { _lte: new Date().toISOString() } },
|
||||
sort: ["-publish_date"],
|
||||
}),
|
||||
);
|
||||
return loggedApiCall("getArticles", async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<Article[]>(
|
||||
readItems("sexy_articles", {
|
||||
fields: ["*", "author.*"],
|
||||
where: { publish_date: { _lte: new Date().toISOString() } },
|
||||
sort: ["-publish_date"],
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getArticleBySlug(
|
||||
slug: string,
|
||||
fetch?: typeof globalThis.fetch,
|
||||
) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<Article[]>(
|
||||
readItems("sexy_articles", {
|
||||
fields: ["*", "author.*"],
|
||||
filter: { slug: { _eq: slug } },
|
||||
}),
|
||||
)
|
||||
.then((articles) => {
|
||||
if (articles.length === 0) {
|
||||
throw new Error("Article not found");
|
||||
}
|
||||
return articles[0];
|
||||
});
|
||||
return loggedApiCall(
|
||||
"getArticleBySlug",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<Article[]>(
|
||||
readItems("sexy_articles", {
|
||||
fields: ["*", "author.*"],
|
||||
filter: { slug: { _eq: slug } },
|
||||
}),
|
||||
)
|
||||
.then((articles) => {
|
||||
if (articles.length === 0) {
|
||||
throw new Error("Article not found");
|
||||
}
|
||||
return articles[0];
|
||||
});
|
||||
},
|
||||
{ slug },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getVideos(fetch?: typeof globalThis.fetch) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<Video[]>(
|
||||
readItems("sexy_videos", {
|
||||
fields: [
|
||||
"*",
|
||||
{
|
||||
models: [
|
||||
"*",
|
||||
{
|
||||
directus_users_id: ["*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
"movie.*",
|
||||
],
|
||||
filter: { upload_date: { _lte: new Date().toISOString() } },
|
||||
sort: ["-upload_date"],
|
||||
}),
|
||||
)
|
||||
.then((videos) => {
|
||||
videos.forEach((video) => {
|
||||
video.models = video.models.map((u) => u.directus_users_id!);
|
||||
return loggedApiCall("getVideos", async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<Video[]>(
|
||||
readItems("sexy_videos", {
|
||||
fields: [
|
||||
"*",
|
||||
{
|
||||
models: [
|
||||
"*",
|
||||
{
|
||||
directus_users_id: ["*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
"movie.*",
|
||||
],
|
||||
filter: { upload_date: { _lte: new Date().toISOString() } },
|
||||
sort: ["-upload_date"],
|
||||
}),
|
||||
)
|
||||
.then((videos) => {
|
||||
videos.forEach((video) => {
|
||||
video.models = video.models.map((u) => u.directus_users_id!);
|
||||
});
|
||||
return videos;
|
||||
});
|
||||
return videos;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export async function getVideosForModel(id, fetch?: typeof globalThis.fetch) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<Video[]>(
|
||||
readItems("sexy_videos", {
|
||||
fields: ["*", "movie.*"],
|
||||
filter: {
|
||||
models: {
|
||||
directus_users_id: {
|
||||
id,
|
||||
return loggedApiCall(
|
||||
"getVideosForModel",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<Video[]>(
|
||||
readItems("sexy_videos", {
|
||||
fields: ["*", "movie.*"],
|
||||
filter: {
|
||||
models: {
|
||||
directus_users_id: {
|
||||
id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
sort: ["-upload_date"],
|
||||
}),
|
||||
sort: ["-upload_date"],
|
||||
}),
|
||||
);
|
||||
},
|
||||
{ modelId: id },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -169,69 +249,81 @@ export async function getFeaturedVideos(
|
||||
limit: number,
|
||||
fetch?: typeof globalThis.fetch,
|
||||
) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<Video[]>(
|
||||
readItems("sexy_videos", {
|
||||
fields: [
|
||||
"*",
|
||||
{
|
||||
models: [
|
||||
return loggedApiCall(
|
||||
"getFeaturedVideos",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<Video[]>(
|
||||
readItems("sexy_videos", {
|
||||
fields: [
|
||||
"*",
|
||||
{
|
||||
directus_users_id: ["*"],
|
||||
models: [
|
||||
"*",
|
||||
{
|
||||
directus_users_id: ["*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
"movie.*",
|
||||
],
|
||||
},
|
||||
"movie.*",
|
||||
],
|
||||
filter: {
|
||||
upload_date: { _lte: new Date().toISOString() },
|
||||
featured: true,
|
||||
},
|
||||
sort: ["-upload_date"],
|
||||
limit,
|
||||
}),
|
||||
)
|
||||
.then((videos) => {
|
||||
videos.forEach((video) => {
|
||||
video.models = video.models.map((u) => u.directus_users_id!);
|
||||
});
|
||||
return videos;
|
||||
});
|
||||
filter: {
|
||||
upload_date: { _lte: new Date().toISOString() },
|
||||
featured: true,
|
||||
},
|
||||
sort: ["-upload_date"],
|
||||
limit,
|
||||
}),
|
||||
)
|
||||
.then((videos) => {
|
||||
videos.forEach((video) => {
|
||||
video.models = video.models.map((u) => u.directus_users_id!);
|
||||
});
|
||||
return videos;
|
||||
});
|
||||
},
|
||||
{ limit },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getVideoBySlug(
|
||||
slug: string,
|
||||
fetch?: typeof globalThis.fetch,
|
||||
) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<Video[]>(
|
||||
readItems("sexy_videos", {
|
||||
fields: [
|
||||
"*",
|
||||
{
|
||||
models: [
|
||||
return loggedApiCall(
|
||||
"getVideoBySlug",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<Video[]>(
|
||||
readItems("sexy_videos", {
|
||||
fields: [
|
||||
"*",
|
||||
{
|
||||
directus_users_id: ["*"],
|
||||
models: [
|
||||
"*",
|
||||
{
|
||||
directus_users_id: ["*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
"movie.*",
|
||||
],
|
||||
},
|
||||
"movie.*",
|
||||
],
|
||||
filter: { slug },
|
||||
}),
|
||||
)
|
||||
.then((videos) => {
|
||||
if (videos.length === 0) {
|
||||
throw new Error("Video not found");
|
||||
}
|
||||
videos[0].models = videos[0].models.map((u) => u.directus_users_id!);
|
||||
filter: { slug },
|
||||
}),
|
||||
)
|
||||
.then((videos) => {
|
||||
if (videos.length === 0) {
|
||||
throw new Error("Video not found");
|
||||
}
|
||||
videos[0].models = videos[0].models.map((u) => u.directus_users_id!);
|
||||
|
||||
return videos[0];
|
||||
});
|
||||
return videos[0];
|
||||
});
|
||||
},
|
||||
{ slug },
|
||||
);
|
||||
}
|
||||
|
||||
const modelFilter = {
|
||||
@@ -256,28 +348,36 @@ const modelFilter = {
|
||||
};
|
||||
|
||||
export async function getModels(fetch?: typeof globalThis.fetch) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<Model[]>(
|
||||
readUsers({
|
||||
fields: ["*"],
|
||||
filter: modelFilter,
|
||||
sort: ["-join_date"],
|
||||
}),
|
||||
);
|
||||
return loggedApiCall("getModels", async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<Model[]>(
|
||||
readUsers({
|
||||
fields: ["*"],
|
||||
filter: modelFilter,
|
||||
sort: ["-join_date"],
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFeaturedModels(
|
||||
limit = 3,
|
||||
fetch?: typeof globalThis.fetch,
|
||||
) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<Model[]>(
|
||||
readUsers({
|
||||
fields: ["*"],
|
||||
filter: { _and: [modelFilter, { featured: { _eq: true } }] },
|
||||
sort: ["-join_date"],
|
||||
limit,
|
||||
}),
|
||||
return loggedApiCall(
|
||||
"getFeaturedModels",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<Model[]>(
|
||||
readUsers({
|
||||
fields: ["*"],
|
||||
filter: { _and: [modelFilter, { featured: { _eq: true } }] },
|
||||
sort: ["-join_date"],
|
||||
limit,
|
||||
}),
|
||||
);
|
||||
},
|
||||
{ limit },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -285,71 +385,101 @@ export async function getModelBySlug(
|
||||
slug: string,
|
||||
fetch?: typeof globalThis.fetch,
|
||||
) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<Model[]>(
|
||||
readUsers({
|
||||
fields: [
|
||||
"*",
|
||||
{
|
||||
photos: [
|
||||
return loggedApiCall(
|
||||
"getModelBySlug",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<Model[]>(
|
||||
readUsers({
|
||||
fields: [
|
||||
"*",
|
||||
{
|
||||
directus_files_id: ["*"],
|
||||
photos: [
|
||||
"*",
|
||||
{
|
||||
directus_files_id: ["*"],
|
||||
},
|
||||
],
|
||||
},
|
||||
"banner.*",
|
||||
],
|
||||
},
|
||||
"banner.*",
|
||||
],
|
||||
filter: { _and: [modelFilter, { slug: { _eq: slug } }] },
|
||||
}),
|
||||
)
|
||||
.then((models) => {
|
||||
if (models.length === 0) {
|
||||
throw new Error("Model not found");
|
||||
}
|
||||
models[0].photos = models[0].photos.map((p) => p.directus_files_id!);
|
||||
return models[0];
|
||||
});
|
||||
}
|
||||
|
||||
export async function updateProfile(user: Partial<User>) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<User>(updateMe(user as never));
|
||||
}
|
||||
|
||||
export async function getStats(fetch?: typeof globalThis.fetch) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<Stats>(
|
||||
customEndpoint({
|
||||
path: "/sexy/stats",
|
||||
}),
|
||||
filter: { _and: [modelFilter, { slug: { _eq: slug } }] },
|
||||
}),
|
||||
)
|
||||
.then((models) => {
|
||||
if (models.length === 0) {
|
||||
throw new Error("Model not found");
|
||||
}
|
||||
models[0].photos = models[0].photos.map((p) => p.directus_files_id!);
|
||||
return models[0];
|
||||
});
|
||||
},
|
||||
{ slug },
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateProfile(user: Partial<User>) {
|
||||
return loggedApiCall(
|
||||
"updateProfile",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<User>(updateMe(user as never));
|
||||
},
|
||||
{ userId: user.id },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getStats(fetch?: typeof globalThis.fetch) {
|
||||
return loggedApiCall("getStats", async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request<Stats>(
|
||||
customEndpoint({
|
||||
path: "/sexy/stats",
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function getFolders(fetch?: typeof globalThis.fetch) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(readFolders());
|
||||
return loggedApiCall("getFolders", async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(readFolders());
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeFile(id: string) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(deleteFile(id));
|
||||
return loggedApiCall(
|
||||
"removeFile",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(deleteFile(id));
|
||||
},
|
||||
{ fileId: id },
|
||||
);
|
||||
}
|
||||
|
||||
export async function uploadFile(data: FormData) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(uploadFiles(data));
|
||||
return loggedApiCall("uploadFile", async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(uploadFiles(data));
|
||||
});
|
||||
}
|
||||
|
||||
export async function createCommentForVideo(item: string, comment: string) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(
|
||||
createComment({
|
||||
collection: "sexy_videos",
|
||||
item,
|
||||
comment,
|
||||
}),
|
||||
return loggedApiCall(
|
||||
"createCommentForVideo",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(
|
||||
createComment({
|
||||
collection: "sexy_videos",
|
||||
item,
|
||||
comment,
|
||||
}),
|
||||
);
|
||||
},
|
||||
{ videoId: item, commentLength: comment.length },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -357,13 +487,19 @@ export async function getCommentsForVideo(
|
||||
item: string,
|
||||
fetch?: typeof globalThis.fetch,
|
||||
) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(
|
||||
readComments({
|
||||
fields: ["*", { user_created: ["*"] }],
|
||||
filter: { collection: "sexy_videos", item },
|
||||
sort: ["-date_created"],
|
||||
}),
|
||||
return loggedApiCall(
|
||||
"getCommentsForVideo",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus.request(
|
||||
readComments({
|
||||
fields: ["*", { user_created: ["*"] }],
|
||||
filter: { collection: "sexy_videos", item },
|
||||
sort: ["-date_created"],
|
||||
}),
|
||||
);
|
||||
},
|
||||
{ videoId: item },
|
||||
);
|
||||
}
|
||||
|
||||
@@ -371,19 +507,25 @@ export async function countCommentsForModel(
|
||||
user_created: string,
|
||||
fetch?: typeof globalThis.fetch,
|
||||
) {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<[{ count: number }]>(
|
||||
aggregate("directus_comments", {
|
||||
aggregate: {
|
||||
count: "*",
|
||||
},
|
||||
query: {
|
||||
filter: { user_created },
|
||||
},
|
||||
}),
|
||||
)
|
||||
.then((result) => result[0].count);
|
||||
return loggedApiCall(
|
||||
"countCommentsForModel",
|
||||
async () => {
|
||||
const directus = getDirectusInstance(fetch);
|
||||
return directus
|
||||
.request<[{ count: number }]>(
|
||||
aggregate("directus_comments", {
|
||||
aggregate: {
|
||||
count: "*",
|
||||
},
|
||||
query: {
|
||||
filter: { user_created },
|
||||
},
|
||||
}),
|
||||
)
|
||||
.then((result) => result[0].count);
|
||||
},
|
||||
{ userId: user_created },
|
||||
);
|
||||
}
|
||||
|
||||
export async function getItemsByTag(
|
||||
@@ -391,12 +533,18 @@ export async function getItemsByTag(
|
||||
tag: string,
|
||||
fetch?: typeof globalThis.fetch,
|
||||
) {
|
||||
switch (category) {
|
||||
case "video":
|
||||
return getVideos(fetch);
|
||||
case "model":
|
||||
return getModels(fetch);
|
||||
case "article":
|
||||
return getArticles(fetch);
|
||||
}
|
||||
return loggedApiCall(
|
||||
"getItemsByTag",
|
||||
async () => {
|
||||
switch (category) {
|
||||
case "video":
|
||||
return getVideos(fetch);
|
||||
case "model":
|
||||
return getModels(fetch);
|
||||
case "article":
|
||||
return getArticles(fetch);
|
||||
}
|
||||
},
|
||||
{ category, tag },
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user