feat: add shared @sexy.pivoine.art/types package and fix type safety across frontend/backend
- Create packages/types with shared TypeScript domain model interfaces (User, Video, Model, Article, Comment, Recording, etc.) - Wire both frontend and backend packages to use @sexy.pivoine.art/types via workspace:* - Update backend Pothos objectRef types to use shared interfaces instead of inline types - Update frontend $lib/types.ts to re-export from shared package - Fix all type errors introduced by more accurate nullable types (avatar/banner as string|null UUIDs, author nullable, events/device_info as object[]) - Add artist_name to comment user select in backend resolver - Widen utility function signatures (getAssetUrl, getUserInitials, calcReadingTime) to accept null/undefined Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
"check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sexy.pivoine.art/types": "workspace:*",
|
||||
"@fastify/cookie": "^11.0.2",
|
||||
"@fastify/cors": "^10.0.2",
|
||||
"@fastify/multipart": "^9.0.3",
|
||||
|
||||
@@ -25,6 +25,7 @@ builder.queryField("commentsForVideo", (t) =>
|
||||
id: users.id,
|
||||
first_name: users.first_name,
|
||||
last_name: users.last_name,
|
||||
artist_name: users.artist_name,
|
||||
avatar: users.avatar,
|
||||
})
|
||||
.from(users)
|
||||
@@ -66,6 +67,7 @@ builder.mutationField("createCommentForVideo", (t) =>
|
||||
id: users.id,
|
||||
first_name: users.first_name,
|
||||
last_name: users.last_name,
|
||||
artist_name: users.artist_name,
|
||||
avatar: users.avatar,
|
||||
})
|
||||
.from(users)
|
||||
|
||||
@@ -1,383 +1,269 @@
|
||||
import type {
|
||||
MediaFile,
|
||||
User,
|
||||
VideoModel,
|
||||
VideoFile,
|
||||
Video,
|
||||
ModelPhoto,
|
||||
Model,
|
||||
ArticleAuthor,
|
||||
Article,
|
||||
CommentUser,
|
||||
Comment,
|
||||
Stats,
|
||||
Recording,
|
||||
VideoLikeStatus,
|
||||
VideoLikeResponse,
|
||||
VideoPlayResponse,
|
||||
VideoAnalytics,
|
||||
Analytics,
|
||||
LeaderboardEntry,
|
||||
UserStats,
|
||||
UserAchievement,
|
||||
RecentPoint,
|
||||
UserGamification,
|
||||
Achievement,
|
||||
} from "@sexy.pivoine.art/types";
|
||||
import { builder } from "../builder";
|
||||
|
||||
// File type
|
||||
export const FileType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
title: string | null;
|
||||
description: string | null;
|
||||
filename: string;
|
||||
mime_type: string | null;
|
||||
filesize: number | null;
|
||||
duration: number | null;
|
||||
uploaded_by: string | null;
|
||||
date_created: Date;
|
||||
}>("File")
|
||||
export const FileType = builder.objectRef<MediaFile>("File").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
title: t.exposeString("title", { nullable: true }),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
filename: t.exposeString("filename"),
|
||||
mime_type: t.exposeString("mime_type", { nullable: true }),
|
||||
filesize: t.exposeFloat("filesize", { nullable: true }),
|
||||
duration: t.exposeInt("duration", { nullable: true }),
|
||||
uploaded_by: t.exposeString("uploaded_by", { nullable: true }),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const UserType = builder.objectRef<User>("User").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
email: t.exposeString("email"),
|
||||
first_name: t.exposeString("first_name", { nullable: true }),
|
||||
last_name: t.exposeString("last_name", { nullable: true }),
|
||||
artist_name: t.exposeString("artist_name", { nullable: true }),
|
||||
slug: t.exposeString("slug", { nullable: true }),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
role: t.exposeString("role"),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
banner: t.exposeString("banner", { nullable: true }),
|
||||
email_verified: t.exposeBoolean("email_verified"),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
}),
|
||||
});
|
||||
|
||||
// CurrentUser is the same shape as User
|
||||
export const CurrentUserType = builder.objectRef<User>("CurrentUser").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
email: t.exposeString("email"),
|
||||
first_name: t.exposeString("first_name", { nullable: true }),
|
||||
last_name: t.exposeString("last_name", { nullable: true }),
|
||||
artist_name: t.exposeString("artist_name", { nullable: true }),
|
||||
slug: t.exposeString("slug", { nullable: true }),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
role: t.exposeString("role"),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
banner: t.exposeString("banner", { nullable: true }),
|
||||
email_verified: t.exposeBoolean("email_verified"),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const VideoModelType = builder.objectRef<VideoModel>("VideoModel").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
artist_name: t.exposeString("artist_name", { nullable: true }),
|
||||
slug: t.exposeString("slug", { nullable: true }),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const VideoFileType = builder.objectRef<VideoFile>("VideoFile").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
filename: t.exposeString("filename"),
|
||||
mime_type: t.exposeString("mime_type", { nullable: true }),
|
||||
duration: t.exposeInt("duration", { nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const VideoType = builder.objectRef<Video>("Video").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
slug: t.exposeString("slug"),
|
||||
title: t.exposeString("title"),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
image: t.exposeString("image", { nullable: true }),
|
||||
movie: t.exposeString("movie", { nullable: true }),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
upload_date: t.expose("upload_date", { type: "DateTime" }),
|
||||
premium: t.exposeBoolean("premium", { nullable: true }),
|
||||
featured: t.exposeBoolean("featured", { nullable: true }),
|
||||
likes_count: t.exposeInt("likes_count", { nullable: true }),
|
||||
plays_count: t.exposeInt("plays_count", { nullable: true }),
|
||||
models: t.expose("models", { type: [VideoModelType], nullable: true }),
|
||||
movie_file: t.expose("movie_file", { type: VideoFileType, nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ModelPhotoType = builder.objectRef<ModelPhoto>("ModelPhoto").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
filename: t.exposeString("filename"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ModelType = builder.objectRef<Model>("Model").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
slug: t.exposeString("slug", { nullable: true }),
|
||||
artist_name: t.exposeString("artist_name", { nullable: true }),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
banner: t.exposeString("banner", { nullable: true }),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
photos: t.expose("photos", { type: [ModelPhotoType], nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ArticleAuthorType = builder.objectRef<ArticleAuthor>("ArticleAuthor").implement({
|
||||
fields: (t) => ({
|
||||
first_name: t.exposeString("first_name", { nullable: true }),
|
||||
last_name: t.exposeString("last_name", { nullable: true }),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ArticleType = builder.objectRef<Article>("Article").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
slug: t.exposeString("slug"),
|
||||
title: t.exposeString("title"),
|
||||
excerpt: t.exposeString("excerpt", { nullable: true }),
|
||||
content: t.exposeString("content", { nullable: true }),
|
||||
image: t.exposeString("image", { nullable: true }),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
publish_date: t.expose("publish_date", { type: "DateTime" }),
|
||||
category: t.exposeString("category", { nullable: true }),
|
||||
featured: t.exposeBoolean("featured", { nullable: true }),
|
||||
author: t.expose("author", { type: ArticleAuthorType, nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const CommentUserType = builder.objectRef<CommentUser>("CommentUser").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
first_name: t.exposeString("first_name", { nullable: true }),
|
||||
last_name: t.exposeString("last_name", { nullable: true }),
|
||||
artist_name: t.exposeString("artist_name", { nullable: true }),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const CommentType = builder.objectRef<Comment>("Comment").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeInt("id"),
|
||||
collection: t.exposeString("collection"),
|
||||
item_id: t.exposeString("item_id"),
|
||||
comment: t.exposeString("comment"),
|
||||
user_id: t.exposeString("user_id"),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
user: t.expose("user", { type: CommentUserType, nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const StatsType = builder.objectRef<Stats>("Stats").implement({
|
||||
fields: (t) => ({
|
||||
videos_count: t.exposeInt("videos_count"),
|
||||
models_count: t.exposeInt("models_count"),
|
||||
viewers_count: t.exposeInt("viewers_count"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const RecordingType = builder.objectRef<Recording>("Recording").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
title: t.exposeString("title"),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
slug: t.exposeString("slug"),
|
||||
duration: t.exposeInt("duration"),
|
||||
events: t.expose("events", { type: "JSON", nullable: true }),
|
||||
device_info: t.expose("device_info", { type: "JSON", nullable: true }),
|
||||
user_id: t.exposeString("user_id"),
|
||||
status: t.exposeString("status"),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
linked_video: t.exposeString("linked_video", { nullable: true }),
|
||||
featured: t.exposeBoolean("featured", { nullable: true }),
|
||||
public: t.exposeBoolean("public", { nullable: true }),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
date_updated: t.expose("date_updated", { type: "DateTime", nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const VideoLikeResponseType = builder
|
||||
.objectRef<VideoLikeResponse>("VideoLikeResponse")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
title: t.exposeString("title", { nullable: true }),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
filename: t.exposeString("filename"),
|
||||
mime_type: t.exposeString("mime_type", { nullable: true }),
|
||||
filesize: t.exposeFloat("filesize", { nullable: true }),
|
||||
duration: t.exposeInt("duration", { nullable: true }),
|
||||
uploaded_by: t.exposeString("uploaded_by", { nullable: true }),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
liked: t.exposeBoolean("liked"),
|
||||
likes_count: t.exposeInt("likes_count"),
|
||||
}),
|
||||
});
|
||||
|
||||
// User type
|
||||
export const UserType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
email: string;
|
||||
first_name: string | null;
|
||||
last_name: string | null;
|
||||
artist_name: string | null;
|
||||
slug: string | null;
|
||||
description: string | null;
|
||||
tags: string[] | null;
|
||||
role: "model" | "viewer" | "admin";
|
||||
avatar: string | null;
|
||||
banner: string | null;
|
||||
email_verified: boolean;
|
||||
date_created: Date;
|
||||
}>("User")
|
||||
export const VideoPlayResponseType = builder
|
||||
.objectRef<VideoPlayResponse>("VideoPlayResponse")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
email: t.exposeString("email"),
|
||||
first_name: t.exposeString("first_name", { nullable: true }),
|
||||
last_name: t.exposeString("last_name", { nullable: true }),
|
||||
artist_name: t.exposeString("artist_name", { nullable: true }),
|
||||
slug: t.exposeString("slug", { nullable: true }),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
role: t.exposeString("role"),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
banner: t.exposeString("banner", { nullable: true }),
|
||||
email_verified: t.exposeBoolean("email_verified"),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
success: t.exposeBoolean("success"),
|
||||
play_id: t.exposeString("play_id"),
|
||||
plays_count: t.exposeInt("plays_count"),
|
||||
}),
|
||||
});
|
||||
|
||||
// CurrentUser type (same shape, used for auth context)
|
||||
export const CurrentUserType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
email: string;
|
||||
first_name: string | null;
|
||||
last_name: string | null;
|
||||
artist_name: string | null;
|
||||
slug: string | null;
|
||||
description: string | null;
|
||||
tags: string[] | null;
|
||||
role: "model" | "viewer" | "admin";
|
||||
avatar: string | null;
|
||||
banner: string | null;
|
||||
email_verified: boolean;
|
||||
date_created: Date;
|
||||
}>("CurrentUser")
|
||||
export const VideoLikeStatusType = builder
|
||||
.objectRef<VideoLikeStatus>("VideoLikeStatus")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
email: t.exposeString("email"),
|
||||
first_name: t.exposeString("first_name", { nullable: true }),
|
||||
last_name: t.exposeString("last_name", { nullable: true }),
|
||||
artist_name: t.exposeString("artist_name", { nullable: true }),
|
||||
slug: t.exposeString("slug", { nullable: true }),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
role: t.exposeString("role"),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
banner: t.exposeString("banner", { nullable: true }),
|
||||
email_verified: t.exposeBoolean("email_verified"),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
liked: t.exposeBoolean("liked"),
|
||||
}),
|
||||
});
|
||||
|
||||
// Video type
|
||||
export const VideoType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
image: string | null;
|
||||
movie: string | null;
|
||||
tags: string[] | null;
|
||||
upload_date: Date;
|
||||
premium: boolean | null;
|
||||
featured: boolean | null;
|
||||
likes_count: number | null;
|
||||
plays_count: number | null;
|
||||
models?: {
|
||||
id: string;
|
||||
artist_name: string | null;
|
||||
slug: string | null;
|
||||
avatar: string | null;
|
||||
}[];
|
||||
movie_file?: {
|
||||
id: string;
|
||||
filename: string;
|
||||
mime_type: string | null;
|
||||
duration: number | null;
|
||||
} | null;
|
||||
}>("Video")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
slug: t.exposeString("slug"),
|
||||
title: t.exposeString("title"),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
image: t.exposeString("image", { nullable: true }),
|
||||
movie: t.exposeString("movie", { nullable: true }),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
upload_date: t.expose("upload_date", { type: "DateTime" }),
|
||||
premium: t.exposeBoolean("premium", { nullable: true }),
|
||||
featured: t.exposeBoolean("featured", { nullable: true }),
|
||||
likes_count: t.exposeInt("likes_count", { nullable: true }),
|
||||
plays_count: t.exposeInt("plays_count", { nullable: true }),
|
||||
models: t.expose("models", { type: [VideoModelType], nullable: true }),
|
||||
movie_file: t.expose("movie_file", { type: VideoFileType, nullable: true }),
|
||||
}),
|
||||
});
|
||||
export const VideoAnalyticsType = builder.objectRef<VideoAnalytics>("VideoAnalytics").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
title: t.exposeString("title"),
|
||||
slug: t.exposeString("slug"),
|
||||
upload_date: t.expose("upload_date", { type: "DateTime" }),
|
||||
likes: t.exposeInt("likes"),
|
||||
plays: t.exposeInt("plays"),
|
||||
completed_plays: t.exposeInt("completed_plays"),
|
||||
completion_rate: t.exposeFloat("completion_rate"),
|
||||
avg_watch_time: t.exposeInt("avg_watch_time"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const VideoModelType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
artist_name: string | null;
|
||||
slug: string | null;
|
||||
avatar: string | null;
|
||||
}>("VideoModel")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
artist_name: t.exposeString("artist_name", { nullable: true }),
|
||||
slug: t.exposeString("slug", { nullable: true }),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
}),
|
||||
});
|
||||
export const AnalyticsType = builder.objectRef<Analytics>("Analytics").implement({
|
||||
fields: (t) => ({
|
||||
total_videos: t.exposeInt("total_videos"),
|
||||
total_likes: t.exposeInt("total_likes"),
|
||||
total_plays: t.exposeInt("total_plays"),
|
||||
plays_by_date: t.expose("plays_by_date", { type: "JSON" }),
|
||||
likes_by_date: t.expose("likes_by_date", { type: "JSON" }),
|
||||
videos: t.expose("videos", { type: [VideoAnalyticsType] }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const VideoFileType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
filename: string;
|
||||
mime_type: string | null;
|
||||
duration: number | null;
|
||||
}>("VideoFile")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
filename: t.exposeString("filename"),
|
||||
mime_type: t.exposeString("mime_type", { nullable: true }),
|
||||
duration: t.exposeInt("duration", { nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
// Model type (model profile, enriched user)
|
||||
export const ModelType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
slug: string | null;
|
||||
artist_name: string | null;
|
||||
description: string | null;
|
||||
avatar: string | null;
|
||||
banner: string | null;
|
||||
tags: string[] | null;
|
||||
date_created: Date;
|
||||
photos?: { id: string; filename: string }[];
|
||||
}>("Model")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
slug: t.exposeString("slug", { nullable: true }),
|
||||
artist_name: t.exposeString("artist_name", { nullable: true }),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
banner: t.exposeString("banner", { nullable: true }),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
photos: t.expose("photos", { type: [ModelPhotoType], nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ModelPhotoType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
filename: string;
|
||||
}>("ModelPhoto")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
filename: t.exposeString("filename"),
|
||||
}),
|
||||
});
|
||||
|
||||
// Article type
|
||||
export const ArticleType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
slug: string;
|
||||
title: string;
|
||||
excerpt: string | null;
|
||||
content: string | null;
|
||||
image: string | null;
|
||||
tags: string[] | null;
|
||||
publish_date: Date;
|
||||
category: string | null;
|
||||
featured: boolean | null;
|
||||
author?: {
|
||||
first_name: string | null;
|
||||
last_name: string | null;
|
||||
avatar: string | null;
|
||||
description: string | null;
|
||||
} | null;
|
||||
}>("Article")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
slug: t.exposeString("slug"),
|
||||
title: t.exposeString("title"),
|
||||
excerpt: t.exposeString("excerpt", { nullable: true }),
|
||||
content: t.exposeString("content", { nullable: true }),
|
||||
image: t.exposeString("image", { nullable: true }),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
publish_date: t.expose("publish_date", { type: "DateTime" }),
|
||||
category: t.exposeString("category", { nullable: true }),
|
||||
featured: t.exposeBoolean("featured", { nullable: true }),
|
||||
author: t.expose("author", { type: ArticleAuthorType, nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const ArticleAuthorType = builder
|
||||
.objectRef<{
|
||||
first_name: string | null;
|
||||
last_name: string | null;
|
||||
avatar: string | null;
|
||||
description: string | null;
|
||||
}>("ArticleAuthor")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
first_name: t.exposeString("first_name", { nullable: true }),
|
||||
last_name: t.exposeString("last_name", { nullable: true }),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
// Recording type
|
||||
export const RecordingType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
title: string;
|
||||
description: string | null;
|
||||
slug: string;
|
||||
duration: number;
|
||||
events: object[] | null;
|
||||
device_info: object[] | null;
|
||||
user_id: string;
|
||||
status: string;
|
||||
tags: string[] | null;
|
||||
linked_video: string | null;
|
||||
featured: boolean | null;
|
||||
public: boolean | null;
|
||||
date_created: Date;
|
||||
date_updated: Date | null;
|
||||
}>("Recording")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
title: t.exposeString("title"),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
slug: t.exposeString("slug"),
|
||||
duration: t.exposeInt("duration"),
|
||||
events: t.expose("events", { type: "JSON", nullable: true }),
|
||||
device_info: t.expose("device_info", { type: "JSON", nullable: true }),
|
||||
user_id: t.exposeString("user_id"),
|
||||
status: t.exposeString("status"),
|
||||
tags: t.exposeStringList("tags", { nullable: true }),
|
||||
linked_video: t.exposeString("linked_video", { nullable: true }),
|
||||
featured: t.exposeBoolean("featured", { nullable: true }),
|
||||
public: t.exposeBoolean("public", { nullable: true }),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
date_updated: t.expose("date_updated", { type: "DateTime", nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
// Comment type
|
||||
export const CommentType = builder
|
||||
.objectRef<{
|
||||
id: number;
|
||||
collection: string;
|
||||
item_id: string;
|
||||
comment: string;
|
||||
user_id: string;
|
||||
date_created: Date;
|
||||
user?: {
|
||||
id: string;
|
||||
first_name: string | null;
|
||||
last_name: string | null;
|
||||
avatar: string | null;
|
||||
} | null;
|
||||
}>("Comment")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeInt("id"),
|
||||
collection: t.exposeString("collection"),
|
||||
item_id: t.exposeString("item_id"),
|
||||
comment: t.exposeString("comment"),
|
||||
user_id: t.exposeString("user_id"),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
user: t.expose("user", { type: CommentUserType, nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const CommentUserType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
first_name: string | null;
|
||||
last_name: string | null;
|
||||
avatar: string | null;
|
||||
}>("CommentUser")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
first_name: t.exposeString("first_name", { nullable: true }),
|
||||
last_name: t.exposeString("last_name", { nullable: true }),
|
||||
avatar: t.exposeString("avatar", { nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
// Stats type
|
||||
export const StatsType = builder
|
||||
.objectRef<{
|
||||
videos_count: number;
|
||||
models_count: number;
|
||||
viewers_count: number;
|
||||
}>("Stats")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
videos_count: t.exposeInt("videos_count"),
|
||||
models_count: t.exposeInt("models_count"),
|
||||
viewers_count: t.exposeInt("viewers_count"),
|
||||
}),
|
||||
});
|
||||
|
||||
// Gamification types
|
||||
export const LeaderboardEntryType = builder
|
||||
.objectRef<{
|
||||
user_id: string;
|
||||
display_name: string | null;
|
||||
avatar: string | null;
|
||||
total_weighted_points: number | null;
|
||||
total_raw_points: number | null;
|
||||
recordings_count: number | null;
|
||||
playbacks_count: number | null;
|
||||
achievements_count: number | null;
|
||||
rank: number;
|
||||
}>("LeaderboardEntry")
|
||||
.objectRef<LeaderboardEntry>("LeaderboardEntry")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
user_id: t.exposeString("user_id"),
|
||||
@@ -392,60 +278,44 @@ export const LeaderboardEntryType = builder
|
||||
}),
|
||||
});
|
||||
|
||||
export const AchievementType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
icon: string | null;
|
||||
category: string | null;
|
||||
required_count: number;
|
||||
points_reward: number;
|
||||
}>("Achievement")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
code: t.exposeString("code"),
|
||||
name: t.exposeString("name"),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
icon: t.exposeString("icon", { nullable: true }),
|
||||
category: t.exposeString("category", { nullable: true }),
|
||||
required_count: t.exposeInt("required_count"),
|
||||
points_reward: t.exposeInt("points_reward"),
|
||||
}),
|
||||
});
|
||||
export const UserStatsType = builder.objectRef<UserStats>("UserStats").implement({
|
||||
fields: (t) => ({
|
||||
user_id: t.exposeString("user_id"),
|
||||
total_raw_points: t.exposeInt("total_raw_points", { nullable: true }),
|
||||
total_weighted_points: t.exposeFloat("total_weighted_points", { nullable: true }),
|
||||
recordings_count: t.exposeInt("recordings_count", { nullable: true }),
|
||||
playbacks_count: t.exposeInt("playbacks_count", { nullable: true }),
|
||||
comments_count: t.exposeInt("comments_count", { nullable: true }),
|
||||
achievements_count: t.exposeInt("achievements_count", { nullable: true }),
|
||||
rank: t.exposeInt("rank"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const UserAchievementType = builder.objectRef<UserAchievement>("UserAchievement").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
code: t.exposeString("code"),
|
||||
name: t.exposeString("name"),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
icon: t.exposeString("icon", { nullable: true }),
|
||||
category: t.exposeString("category", { nullable: true }),
|
||||
date_unlocked: t.expose("date_unlocked", { type: "DateTime" }),
|
||||
progress: t.exposeInt("progress", { nullable: true }),
|
||||
required_count: t.exposeInt("required_count"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const RecentPointType = builder.objectRef<RecentPoint>("RecentPoint").implement({
|
||||
fields: (t) => ({
|
||||
action: t.exposeString("action"),
|
||||
points: t.exposeInt("points"),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
recording_id: t.exposeString("recording_id", { nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const UserGamificationType = builder
|
||||
.objectRef<{
|
||||
stats: {
|
||||
user_id: string;
|
||||
total_raw_points: number | null;
|
||||
total_weighted_points: number | null;
|
||||
recordings_count: number | null;
|
||||
playbacks_count: number | null;
|
||||
comments_count: number | null;
|
||||
achievements_count: number | null;
|
||||
rank: number;
|
||||
} | null;
|
||||
achievements: {
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
icon: string | null;
|
||||
category: string | null;
|
||||
date_unlocked: Date;
|
||||
progress: number | null;
|
||||
required_count: number;
|
||||
}[];
|
||||
recent_points: {
|
||||
action: string;
|
||||
points: number;
|
||||
date_created: Date;
|
||||
recording_id: string | null;
|
||||
}[];
|
||||
}>("UserGamification")
|
||||
.objectRef<UserGamification>("UserGamification")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
stats: t.expose("stats", { type: UserStatsType, nullable: true }),
|
||||
@@ -454,162 +324,15 @@ export const UserGamificationType = builder
|
||||
}),
|
||||
});
|
||||
|
||||
export const UserStatsType = builder
|
||||
.objectRef<{
|
||||
user_id: string;
|
||||
total_raw_points: number | null;
|
||||
total_weighted_points: number | null;
|
||||
recordings_count: number | null;
|
||||
playbacks_count: number | null;
|
||||
comments_count: number | null;
|
||||
achievements_count: number | null;
|
||||
rank: number;
|
||||
}>("UserStats")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
user_id: t.exposeString("user_id"),
|
||||
total_raw_points: t.exposeInt("total_raw_points", { nullable: true }),
|
||||
total_weighted_points: t.exposeFloat("total_weighted_points", { nullable: true }),
|
||||
recordings_count: t.exposeInt("recordings_count", { nullable: true }),
|
||||
playbacks_count: t.exposeInt("playbacks_count", { nullable: true }),
|
||||
comments_count: t.exposeInt("comments_count", { nullable: true }),
|
||||
achievements_count: t.exposeInt("achievements_count", { nullable: true }),
|
||||
rank: t.exposeInt("rank"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const UserAchievementType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
code: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
icon: string | null;
|
||||
category: string | null;
|
||||
date_unlocked: Date;
|
||||
progress: number | null;
|
||||
required_count: number;
|
||||
}>("UserAchievement")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
code: t.exposeString("code"),
|
||||
name: t.exposeString("name"),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
icon: t.exposeString("icon", { nullable: true }),
|
||||
category: t.exposeString("category", { nullable: true }),
|
||||
date_unlocked: t.expose("date_unlocked", { type: "DateTime" }),
|
||||
progress: t.exposeInt("progress", { nullable: true }),
|
||||
required_count: t.exposeInt("required_count"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const RecentPointType = builder
|
||||
.objectRef<{
|
||||
action: string;
|
||||
points: number;
|
||||
date_created: Date;
|
||||
recording_id: string | null;
|
||||
}>("RecentPoint")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
action: t.exposeString("action"),
|
||||
points: t.exposeInt("points"),
|
||||
date_created: t.expose("date_created", { type: "DateTime" }),
|
||||
recording_id: t.exposeString("recording_id", { nullable: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
// Analytics types
|
||||
export const AnalyticsType = builder
|
||||
.objectRef<{
|
||||
total_videos: number;
|
||||
total_likes: number;
|
||||
total_plays: number;
|
||||
plays_by_date: Record<string, number>;
|
||||
likes_by_date: Record<string, number>;
|
||||
videos: {
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
upload_date: Date;
|
||||
likes: number;
|
||||
plays: number;
|
||||
completed_plays: number;
|
||||
completion_rate: number;
|
||||
avg_watch_time: number;
|
||||
}[];
|
||||
}>("Analytics")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
total_videos: t.exposeInt("total_videos"),
|
||||
total_likes: t.exposeInt("total_likes"),
|
||||
total_plays: t.exposeInt("total_plays"),
|
||||
plays_by_date: t.expose("plays_by_date", { type: "JSON" }),
|
||||
likes_by_date: t.expose("likes_by_date", { type: "JSON" }),
|
||||
videos: t.expose("videos", { type: [VideoAnalyticsType] }),
|
||||
}),
|
||||
});
|
||||
|
||||
export const VideoAnalyticsType = builder
|
||||
.objectRef<{
|
||||
id: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
upload_date: Date;
|
||||
likes: number;
|
||||
plays: number;
|
||||
completed_plays: number;
|
||||
completion_rate: number;
|
||||
avg_watch_time: number;
|
||||
}>("VideoAnalytics")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
title: t.exposeString("title"),
|
||||
slug: t.exposeString("slug"),
|
||||
upload_date: t.expose("upload_date", { type: "DateTime" }),
|
||||
likes: t.exposeInt("likes"),
|
||||
plays: t.exposeInt("plays"),
|
||||
completed_plays: t.exposeInt("completed_plays"),
|
||||
completion_rate: t.exposeFloat("completion_rate"),
|
||||
avg_watch_time: t.exposeInt("avg_watch_time"),
|
||||
}),
|
||||
});
|
||||
|
||||
// Response types
|
||||
export const VideoLikeResponseType = builder
|
||||
.objectRef<{
|
||||
liked: boolean;
|
||||
likes_count: number;
|
||||
}>("VideoLikeResponse")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
liked: t.exposeBoolean("liked"),
|
||||
likes_count: t.exposeInt("likes_count"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const VideoPlayResponseType = builder
|
||||
.objectRef<{
|
||||
success: boolean;
|
||||
play_id: string;
|
||||
plays_count: number;
|
||||
}>("VideoPlayResponse")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
success: t.exposeBoolean("success"),
|
||||
play_id: t.exposeString("play_id"),
|
||||
plays_count: t.exposeInt("plays_count"),
|
||||
}),
|
||||
});
|
||||
|
||||
export const VideoLikeStatusType = builder
|
||||
.objectRef<{
|
||||
liked: boolean;
|
||||
}>("VideoLikeStatus")
|
||||
.implement({
|
||||
fields: (t) => ({
|
||||
liked: t.exposeBoolean("liked"),
|
||||
}),
|
||||
});
|
||||
export const AchievementType = builder.objectRef<Achievement>("Achievement").implement({
|
||||
fields: (t) => ({
|
||||
id: t.exposeString("id"),
|
||||
code: t.exposeString("code"),
|
||||
name: t.exposeString("name"),
|
||||
description: t.exposeString("description", { nullable: true }),
|
||||
icon: t.exposeString("icon", { nullable: true }),
|
||||
category: t.exposeString("category", { nullable: true }),
|
||||
required_count: t.exposeInt("required_count"),
|
||||
points_reward: t.exposeInt("points_reward"),
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user