♻️ Transformed API keys

This commit is contained in:
2025-09-19 01:26:21 +08:00
parent 60e8b1dcfb
commit 48a9a97e18
9 changed files with 171 additions and 95 deletions

View File

@@ -10,7 +10,7 @@ import type { SnAttachment } from '~/types/api'
const props = defineProps<{ item: SnAttachment }>() const props = defineProps<{ item: SnAttachment }>()
const itemType = computed(() => props.item.mime_type.split('/')[0] ?? 'unknown') const itemType = computed(() => props.item.mimeType.split('/')[0] ?? 'unknown')
const apiBase = useSolarNetworkUrl(); const apiBase = useSolarNetworkUrl();
const remoteSource = computed(() => `${apiBase}/drive/files/${props.item.id}?original=true`) const remoteSource = computed(() => `${apiBase}/drive/files/${props.item.id}?original=true`)

View File

@@ -7,9 +7,9 @@
<span class="text-xs">@{{ props.item.publisher.name }}</span> <span class="text-xs">@{{ props.item.publisher.name }}</span>
</p> </p>
<p class="text-xs flex gap-1"> <p class="text-xs flex gap-1">
<span>{{ DateTime.fromISO(props.item.created_at).toRelative() }}</span> <span>{{ DateTime.fromISO(props.item.createdAt).toRelative() }}</span>
<span class="font-bold">·</span> <span class="font-bold">·</span>
<span>{{ DateTime.fromISO(props.item.created_at).toLocaleString() }}</span> <span>{{ DateTime.fromISO(props.item.createdAt).toLocaleString() }}</span>
</p> </p>
</div> </div>
</div> </div>
@@ -18,8 +18,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from 'vue' import { computed } from 'vue'
import { DateTime } from 'luxon' import { DateTime } from 'luxon'
import type { SnPost } from '~/types/api';
const props = defineProps<{ item: any }>() const props = defineProps<{ item: SnPost }>()
const apiBase = useSolarNetworkUrl(); const apiBase = useSolarNetworkUrl();
const publisherAvatar = computed(() => const publisherAvatar = computed(() =>

View File

@@ -1,10 +1,28 @@
// Solar Network aka the api client // Solar Network aka the api client
import { keysToCamel, keysToSnake } from '~/utils/transformKeys'
export const useSolarNetwork = () => { export const useSolarNetwork = () => {
const apiBase = useSolarNetworkUrl(); const apiBase = useSolarNetworkUrl();
return $fetch.create({ baseURL: apiBase, credentials: 'include' })
return $fetch.create({
baseURL: apiBase,
credentials: 'include',
// Transform response keys from snake_case to camelCase
onResponse: ({ response }) => {
if (response._data) {
response._data = keysToCamel(response._data)
}
},
// Transform request data from camelCase to snake_case
onRequest: ({ options }) => {
if (options.body && typeof options.body === 'object') {
options.body = keysToSnake(options.body)
}
}
})
} }
export const useSolarNetworkUrl = () => { export const useSolarNetworkUrl = () => {
const config = useRuntimeConfig() const config = useRuntimeConfig()
return config.public.apiBase return config.public.apiBase
} }

View File

@@ -73,7 +73,7 @@ async function fetchActivites() {
const resp = await api( const resp = await api(
activitesLast.value == null activitesLast.value == null
? '/sphere/activities' ? '/sphere/activities'
: `/sphere/activities?cursor=${new Date(activitesLast.value.created_at).toISOString()}`, : `/sphere/activities?cursor=${new Date(activitesLast.value.createdAt).toISOString()}`,
) )
const data = resp as SnActivity[] const data = resp as SnActivity[]
activites.value = [...activites.value, ...data] activites.value = [...activites.value, ...data]

View File

@@ -4,11 +4,11 @@ import type { SnPost } from './post'
export interface SnActivity { export interface SnActivity {
id: string; id: string;
type: string; type: string;
resource_identifier: string; resourceIdentifier: string;
meta: Record<string, unknown>; meta: Record<string, unknown>;
data: SnPost; data: SnPost;
visibility: number; visibility: number;
created_at: string; createdAt: string;
updated_at: string; updatedAt: string;
deleted_at: string | null; deletedAt: string | null;
} }

View File

@@ -16,26 +16,26 @@ export interface SnFileMeta {
yoffset?: number; yoffset?: number;
filename?: string | null; filename?: string | null;
orientation?: number; orientation?: number;
'vips-loader'?: string; vipsLoader?: string;
interpretation?: number; interpretation?: number;
'bits-per-sample'?: number; bitsPerSample?: number;
'resolution-unit'?: string; resolutionUnit?: string;
} }
// Attachment interface // Attachment interface
export interface SnAttachment { export interface SnAttachment {
id: string; id: string;
name: string; name: string;
file_meta: SnFileMeta; fileMeta: SnFileMeta;
user_meta: Record<string, unknown> | null; userMeta: Record<string, unknown> | null;
sensitive_marks: string[]; sensitiveMarks: string[];
mime_type: string; mimeType: string;
hash: string; hash: string;
size: number; size: number;
has_compression: boolean; hasCompression: boolean;
created_at: string; createdAt: string;
updated_at: string; updatedAt: string;
deleted_at: string | null; deletedAt: string | null;
} }
// Post interface // Post interface
@@ -44,40 +44,40 @@ export interface SnPost {
title: string; title: string;
description: string; description: string;
slug: string | null; slug: string | null;
edited_at: string | null; editedAt: string | null;
published_at: string; publishedAt: string;
visibility: number; visibility: number;
content: string; content: string;
type: number; type: number;
pin_mode: unknown | null; pinMode: unknown | null;
meta: unknown | null; meta: unknown | null;
sensitive_marks: string[]; sensitiveMarks: string[];
embed_view: unknown | null; embedView: unknown | null;
views_unique: number; viewsUnique: number;
views_total: number; viewsTotal: number;
upvotes: number; upvotes: number;
downvotes: number; downvotes: number;
awarded_score: number; awardedScore: number;
reactions_count: Record<string, number>; reactionsCount: Record<string, number>;
replies_count: number; repliesCount: number;
reactions_made: Record<string, unknown>; reactionsMade: Record<string, unknown>;
replied_gone: boolean; repliedGone: boolean;
forwarded_gone: boolean; forwardedGone: boolean;
replied_post_id: string | null; repliedPostId: string | null;
replied_post: SnPost | null; repliedPost: SnPost | null;
forwarded_post_id: string | null; forwardedPostId: string | null;
forwarded_post: SnPost | null; forwardedPost: SnPost | null;
realm_id: string | null; realmId: string | null;
realm: unknown | null; realm: unknown | null;
attachments: SnAttachment[]; attachments: SnAttachment[];
publisher_id: string; publisherId: string;
publisher: SnPublisher; publisher: SnPublisher;
awards: unknown | null; awards: unknown | null;
tags: string[]; tags: string[];
categories: string[]; categories: string[];
is_truncated: boolean; isTruncated: boolean;
resource_identifier: string; resourceIdentifier: string;
created_at: string; createdAt: string;
updated_at: string; updatedAt: string;
deleted_at: string | null; deletedAt: string | null;
} }

View File

@@ -5,7 +5,7 @@ export interface SnVerification {
type: number; type: number;
title: string; title: string;
description: string; description: string;
verified_by: string; verifiedBy: string;
} }
// Publisher interface // Publisher interface
@@ -15,16 +15,16 @@ export interface SnPublisher {
name: string; name: string;
nick: string; nick: string;
bio: string; bio: string;
picture_id: string; pictureId: string;
background_id: string; backgroundId: string;
picture: SnAttachment | null; picture: SnAttachment | null;
background: SnAttachment | null; background: SnAttachment | null;
verification: SnVerification | null; verification: SnVerification | null;
account_id: string; accountId: string;
realm_id: string | null; realmId: string | null;
account: unknown | null; account: unknown | null;
resource_identifier: string; resourceIdentifier: string;
created_at: string; createdAt: string;
updated_at: string; updatedAt: string;
deleted_at: string | null; deletedAt: string | null;
} }

View File

@@ -14,62 +14,62 @@ export interface SnAccountBadge {
label: string | null; label: string | null;
caption: string | null; caption: string | null;
meta: Record<string, unknown>; meta: Record<string, unknown>;
activated_at: string; activatedAt: string;
expired_at: string | null; expiredAt: string | null;
account_id: string; accountId: string;
created_at: string; createdAt: string;
updated_at: string; updatedAt: string;
deleted_at: string | null; deletedAt: string | null;
} }
// Account perk subscription interface // Account perk subscription interface
export interface SnAccountPerkSubscription { export interface SnAccountPerkSubscription {
id: string; id: string;
identifier: string; identifier: string;
begun_at: string; begunAt: string;
ended_at: string; endedAt: string;
is_active: boolean; isActive: boolean;
is_available: boolean; isAvailable: boolean;
is_free_trial: boolean; isFreeTrial: boolean;
status: number; status: number;
base_price: number; basePrice: number;
final_price: number; finalPrice: number;
renewal_at: string; renewalAt: string;
account_id: string; accountId: string;
display_name: string; displayName: string;
created_at: string; createdAt: string;
updated_at: string; updatedAt: string;
deleted_at: string | null; deletedAt: string | null;
} }
// Account profile interface // Account profile interface
export interface SnAccountProfile { export interface SnAccountProfile {
id: string; id: string;
first_name: string; firstName: string;
middle_name: string; middleName: string;
last_name: string; lastName: string;
bio: string; bio: string;
gender: string; gender: string;
pronouns: string; pronouns: string;
time_zone: string; timeZone: string;
location: string; location: string;
links: SnAccountLink[]; links: SnAccountLink[];
birthday: string; birthday: string;
last_seen_at: string; lastSeenAt: string;
verification: SnVerification | null; verification: SnVerification | null;
active_badge: unknown | null; activeBadge: unknown | null;
experience: number; experience: number;
level: number; level: number;
social_credits: number; socialCredits: number;
social_credits_level: number; socialCreditsLevel: number;
leveling_progress: number; levelingProgress: number;
picture: SnAttachment | null; picture: SnAttachment | null;
background: SnAttachment | null; background: SnAttachment | null;
account_id: string; accountId: string;
resource_identifier: string; resourceIdentifier: string;
created_at: string; createdAt: string;
updated_at: string; updatedAt: string;
deleted_at: string | null; deletedAt: string | null;
} }
// Account interface // Account interface
@@ -79,14 +79,14 @@ export interface SnAccount {
nick: string; nick: string;
language: string; language: string;
region: string; region: string;
activated_at: string; activatedAt: string;
is_superuser: boolean; isSuperuser: boolean;
automated_id: string | null; automatedId: string | null;
profile: SnAccountProfile; profile: SnAccountProfile;
contacts: unknown[]; contacts: unknown[];
badges: SnAccountBadge[]; badges: SnAccountBadge[];
perk_subscription: SnAccountPerkSubscription | null; perkSubscription: SnAccountPerkSubscription | null;
created_at: string; createdAt: string;
updated_at: string; updatedAt: string;
deleted_at: string | null; deletedAt: string | null;
} }

View File

@@ -0,0 +1,57 @@
/**
* Transform object keys between snake_case and camelCase
*/
type TransformFunction = (key: string) => string
const snakeCase: TransformFunction = (key: string): string => {
return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
}
const camelCase: TransformFunction = (key: string): string => {
return key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())
}
function transformKeys(obj: unknown, transformFn: TransformFunction): unknown {
if (obj === null || obj === undefined) {
return obj
}
if (Array.isArray(obj)) {
return obj.map(item => transformKeys(item, transformFn))
}
if (typeof obj === 'object' && obj.constructor === Object) {
const transformed: Record<string, unknown> = {}
for (const [key, value] of Object.entries(obj)) {
const transformedKey = transformFn(key)
transformed[transformedKey] = transformKeys(value, transformFn)
}
return transformed
}
return obj
}
/**
* Convert snake_case keys to camelCase
*/
export function keysToCamel<T = unknown>(obj: unknown): T {
return transformKeys(obj, camelCase) as T
}
/**
* Convert camelCase keys to snake_case
*/
export function keysToSnake<T = unknown>(obj: unknown): T {
return transformKeys(obj, snakeCase) as T
}
/**
* Deep clone and transform keys
*/
export function deepTransformKeys<T = unknown>(obj: unknown, transformFn: TransformFunction): T {
return JSON.parse(JSON.stringify(transformKeys(obj, transformFn))) as T
}