♻️ Splitting up services (skip ci)
This commit is contained in:
48
packages/sn/src/attachment.ts
Normal file
48
packages/sn/src/attachment.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { sni } from './network'
|
||||
|
||||
export interface SnAttachment {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date | null
|
||||
rid: string
|
||||
uuid: string
|
||||
size: number
|
||||
name: string
|
||||
alt: string
|
||||
mimetype: string
|
||||
hash: string
|
||||
destination: number
|
||||
refCount: number
|
||||
contentRating: number
|
||||
qualityRating: number
|
||||
cleanedAt?: Date | null
|
||||
isAnalyzed: boolean
|
||||
isSelfRef: boolean
|
||||
isIndexable: boolean
|
||||
ref?: SnAttachment | null
|
||||
refId?: number | null
|
||||
poolId?: number | null
|
||||
accountId: number
|
||||
thumbnailId?: number | null
|
||||
thumbnail?: SnAttachment | null
|
||||
compressedId?: number | null
|
||||
compressed?: SnAttachment | null
|
||||
usermeta: Record<string, any>
|
||||
metadata: Record<string, any>
|
||||
}
|
||||
|
||||
export async function getAttachment(id: string | number): Promise<SnAttachment> {
|
||||
const resp = await sni.get<SnAttachment>('/cgi/uc/attachments/' + id + '/meta')
|
||||
return resp.data
|
||||
}
|
||||
|
||||
export async function listAttachment(id: string[]): Promise<SnAttachment[]> {
|
||||
const resp = await sni.get<{ data: SnAttachment[] }>('/cgi/uc/attachments', {
|
||||
params: {
|
||||
id: id.join(','),
|
||||
take: id.length,
|
||||
},
|
||||
})
|
||||
return resp.data.data
|
||||
}
|
57
packages/sn/src/auth.ts
Normal file
57
packages/sn/src/auth.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import Cookies from 'universal-cookie'
|
||||
|
||||
export interface SnAuthResult {
|
||||
isFinished: boolean
|
||||
ticket: SnAuthTicket
|
||||
}
|
||||
|
||||
export interface SnAuthTicket {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date | null
|
||||
stepRemain: number
|
||||
grantToken?: string | null
|
||||
accessToken?: string | null
|
||||
refreshToken?: string | null
|
||||
ipAddress: string
|
||||
location: string
|
||||
userAgent: string
|
||||
expiredAt?: Date | null
|
||||
lastGrantAt?: Date | null
|
||||
availableAt?: Date | null
|
||||
nonce?: string | null
|
||||
accountId?: number | null
|
||||
factorTrail: number[]
|
||||
}
|
||||
|
||||
export interface SnAuthFactor {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date | null
|
||||
type: number
|
||||
config?: Record<string, any> | null
|
||||
accountId?: number | null
|
||||
}
|
||||
|
||||
export function setTokenCookies(atk: string, rtk: string) {
|
||||
const cookies = new Cookies()
|
||||
cookies.set('nex_user_atk', atk, { path: '/', maxAge: 2592000 })
|
||||
cookies.set('nex_user_rtk', rtk, { path: '/', maxAge: 2592000 })
|
||||
}
|
||||
|
||||
export function removeTokenCookies() {
|
||||
const cookies = new Cookies()
|
||||
cookies.remove('nex_user_atk')
|
||||
cookies.remove('nex_user_rtk')
|
||||
}
|
||||
|
||||
export function checkAuthenticatedClient(): boolean {
|
||||
const cookies = new Cookies()
|
||||
return !!cookies.get('nex_user_atk')
|
||||
}
|
||||
|
||||
export function redirectToLogin() {
|
||||
window.open('/auth/login?redirect_uri=' + encodeURIComponent(window.location.pathname), '_self')
|
||||
}
|
10
packages/sn/src/checkIn.ts
Normal file
10
packages/sn/src/checkIn.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface SnCheckInRecord {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date | null
|
||||
resultTier: number
|
||||
resultExperience: number
|
||||
resultModifiers: number[]
|
||||
accountId: number
|
||||
}
|
7
packages/sn/src/index.ts
Normal file
7
packages/sn/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from './matrix/product'
|
||||
export * from './attachment'
|
||||
export * from './auth'
|
||||
export * from './checkIn'
|
||||
export * from './network'
|
||||
export * from './post'
|
||||
export * from './user'
|
25
packages/sn/src/matrix/product.ts
Normal file
25
packages/sn/src/matrix/product.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export interface MaProduct {
|
||||
id: number
|
||||
created_at: Date
|
||||
updated_at: Date
|
||||
deleted_at?: Date
|
||||
icon: string
|
||||
name: string
|
||||
alias: string
|
||||
description: string
|
||||
previews: string[]
|
||||
tags: string[]
|
||||
meta: MaProductMeta
|
||||
releases: null
|
||||
account_id: number
|
||||
}
|
||||
|
||||
export interface MaProductMeta {
|
||||
id: number
|
||||
created_at: Date
|
||||
updated_at: Date
|
||||
deleted_at?: Date
|
||||
introduction: string
|
||||
attachments: string[]
|
||||
product_id: number
|
||||
}
|
78
packages/sn/src/network.ts
Normal file
78
packages/sn/src/network.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import axios, { type AxiosInstance } from 'axios'
|
||||
import applyCaseMiddleware from 'axios-case-converter'
|
||||
import Cookies from 'universal-cookie'
|
||||
import { setTokenCookies } from './auth'
|
||||
|
||||
const baseURL = 'https://api.sn.solsynth.dev'
|
||||
|
||||
export const sni: AxiosInstance = (() => {
|
||||
const inst = axios.create({
|
||||
baseURL,
|
||||
})
|
||||
|
||||
inst.interceptors.request.use(
|
||||
async (config) => {
|
||||
const tk = await refreshToken()
|
||||
if (tk) config.headers['Authorization'] = `Bearer ${tk}`
|
||||
return config
|
||||
},
|
||||
(error) => error,
|
||||
)
|
||||
|
||||
applyCaseMiddleware(inst, {
|
||||
ignoreParams: true,
|
||||
ignoreHeaders: true,
|
||||
})
|
||||
return inst
|
||||
})()
|
||||
|
||||
async function refreshToken(): Promise<string | undefined> {
|
||||
const cookies = new Cookies()
|
||||
if (!cookies.get('nex_user_atk') || !cookies.get('nex_user_rtk')) return
|
||||
|
||||
const ogTk: string = cookies.get('nex_user_atk')!
|
||||
if (!isTokenExpired(ogTk)) return ogTk
|
||||
|
||||
const resp = await axios.post(
|
||||
'/cgi/id/auth/token',
|
||||
{
|
||||
refresh_token: cookies.get('nex_user_rtk')!,
|
||||
grant_type: 'refresh_token',
|
||||
},
|
||||
{ baseURL },
|
||||
)
|
||||
const atk: string = resp.data['access_token']
|
||||
const rtk: string = resp.data['refresh_token']
|
||||
setTokenCookies(atk, rtk)
|
||||
|
||||
console.log('[Authenticator] Refreshed token...')
|
||||
|
||||
return atk
|
||||
}
|
||||
|
||||
function isTokenExpired(token: string): boolean {
|
||||
try {
|
||||
const parts = token.split('.')
|
||||
if (parts.length !== 3) {
|
||||
throw new Error('Invalid JWT format')
|
||||
}
|
||||
|
||||
const payload = JSON.parse(atob(parts[1]))
|
||||
|
||||
if (!payload.exp) {
|
||||
throw new Error("'exp' claim is missing in the JWT payload")
|
||||
}
|
||||
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
|
||||
return now >= payload.exp
|
||||
} catch (error) {
|
||||
console.error('[Authenticator] Something went wrong with token: ', error)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
export function getAttachmentUrl(identifer: string): string {
|
||||
if (identifer.startsWith('http')) return identifer
|
||||
return `${baseURL}/cgi/uc/attachments/${identifer}`
|
||||
}
|
85
packages/sn/src/post.ts
Normal file
85
packages/sn/src/post.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
export interface SnPost {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date | null
|
||||
type: string
|
||||
body: SnPostBody & Record<string, any>
|
||||
language: string
|
||||
alias?: string | null
|
||||
aliasPrefix?: string | null
|
||||
tags: SnPostTag[]
|
||||
categories: SnPostCategory[]
|
||||
replies?: SnPost[] | null
|
||||
replyId?: number | null
|
||||
repostId?: number | null
|
||||
replyTo?: SnPost | null
|
||||
repostTo?: SnPost | null
|
||||
visibleUsersList?: number[] | null
|
||||
invisibleUsersList?: number[] | null
|
||||
visibility: number
|
||||
editedAt?: Date | null
|
||||
pinnedAt?: Date | null
|
||||
lockedAt?: Date | null
|
||||
isDraft: boolean
|
||||
publishedAt?: Date | null
|
||||
publishedUntil?: Date | null
|
||||
totalUpvote: number
|
||||
totalDownvote: number
|
||||
publisherId: number
|
||||
publisher: SnPublisher
|
||||
metric: SnMetric
|
||||
}
|
||||
|
||||
export interface SnPostTag {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date
|
||||
alias: string
|
||||
name: string
|
||||
description: string
|
||||
posts?: SnPost[]
|
||||
}
|
||||
|
||||
export interface SnPostCategory {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date
|
||||
alias: string
|
||||
name: string
|
||||
description: string
|
||||
posts?: SnPost[]
|
||||
}
|
||||
|
||||
export interface SnPostBody {
|
||||
attachments: string[]
|
||||
content: string
|
||||
location?: string
|
||||
thumbnail?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
export interface SnMetric {
|
||||
replyCount: number
|
||||
reactionCount: number
|
||||
reactionList: Record<string, number>
|
||||
}
|
||||
|
||||
export interface SnPublisher {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date | null
|
||||
type: number
|
||||
name: string
|
||||
nick: string
|
||||
description: string
|
||||
avatar: string
|
||||
banner: string
|
||||
totalUpvote: number
|
||||
totalDownvote: number
|
||||
realmId?: number | null
|
||||
accountId: number
|
||||
}
|
83
packages/sn/src/user.ts
Normal file
83
packages/sn/src/user.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { create } from 'zustand'
|
||||
import { sni } from './network'
|
||||
import Cookies from 'universal-cookie'
|
||||
|
||||
export interface SnAccount {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date | null
|
||||
confirmedAt?: Date | null
|
||||
contacts?: SnAccountContact[] | null
|
||||
avatar: string
|
||||
banner: string
|
||||
description: string
|
||||
name: string
|
||||
nick: string
|
||||
permNodes: Record<string, any>
|
||||
profile?: SnAccountProfile | null
|
||||
badges: SnAccountBadge[]
|
||||
suspendedAt?: Date | null
|
||||
affiliatedId?: number | null
|
||||
affiliatedTo?: number | null
|
||||
automatedBy?: number | null
|
||||
automatedId?: number | null
|
||||
}
|
||||
|
||||
export interface SnAccountContact {
|
||||
accountId: number
|
||||
content: string
|
||||
createdAt: Date
|
||||
deletedAt?: Date | null
|
||||
id: number
|
||||
isPrimary: boolean
|
||||
isPublic: boolean
|
||||
type: number
|
||||
updatedAt: Date
|
||||
verifiedAt?: Date | null
|
||||
}
|
||||
|
||||
export interface SnAccountProfile {
|
||||
id: number
|
||||
accountId: number
|
||||
birthday?: Date | null
|
||||
createdAt: Date
|
||||
deletedAt?: Date | null
|
||||
experience: number
|
||||
firstName: string
|
||||
lastName: string
|
||||
lastSeenAt?: Date | null
|
||||
updatedAt: Date
|
||||
}
|
||||
|
||||
export interface SnAccountBadge {
|
||||
id: number
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
deletedAt?: Date | null
|
||||
type: string
|
||||
accountId: number
|
||||
metadata: Record<string, any>
|
||||
}
|
||||
|
||||
export interface UserStore {
|
||||
account: SnAccount | undefined
|
||||
fetchUser: () => Promise<SnAccount | undefined>
|
||||
}
|
||||
|
||||
export const useUserStore = create<UserStore>((set) => ({
|
||||
account: undefined,
|
||||
fetchUser: async (): Promise<SnAccount | undefined> => {
|
||||
const cookies = new Cookies()
|
||||
if (!cookies.get('nex_user_atk')) return
|
||||
try {
|
||||
const resp = await sni.get<SnAccount>('/cgi/id/users/me')
|
||||
set({ account: resp.data })
|
||||
console.log('[Authenticator] Logged in as @' + resp.data.name)
|
||||
return resp.data
|
||||
} catch (err) {
|
||||
console.error('[Authenticator] Unable to get user profile: ', err)
|
||||
return
|
||||
}
|
||||
},
|
||||
}))
|
Reference in New Issue
Block a user