♻️ 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 itemType = computed(() => props.item.mime_type.split('/')[0] ?? 'unknown')
const itemType = computed(() => props.item.mimeType.split('/')[0] ?? 'unknown')
const apiBase = useSolarNetworkUrl();
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>
</p>
<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>{{ DateTime.fromISO(props.item.created_at).toLocaleString() }}</span>
<span>{{ DateTime.fromISO(props.item.createdAt).toLocaleString() }}</span>
</p>
</div>
</div>
@@ -18,8 +18,9 @@
<script lang="ts" setup>
import { computed } from 'vue'
import { DateTime } from 'luxon'
import type { SnPost } from '~/types/api';
const props = defineProps<{ item: any }>()
const props = defineProps<{ item: SnPost }>()
const apiBase = useSolarNetworkUrl();
const publisherAvatar = computed(() =>

View File

@@ -1,7 +1,25 @@
// Solar Network aka the api client
import { keysToCamel, keysToSnake } from '~/utils/transformKeys'
export const useSolarNetwork = () => {
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 = () => {

View File

@@ -73,7 +73,7 @@ async function fetchActivites() {
const resp = await api(
activitesLast.value == null
? '/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[]
activites.value = [...activites.value, ...data]

View File

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

View File

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

View File

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

View File

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