♻️ Splitting up services (skip ci)
This commit is contained in:
175
packages/sn/.gitignore
vendored
Normal file
175
packages/sn/.gitignore
vendored
Normal file
@@ -0,0 +1,175 @@
|
||||
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||
|
||||
# Logs
|
||||
|
||||
logs
|
||||
_.log
|
||||
npm-debug.log_
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Caches
|
||||
|
||||
.cache
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
|
||||
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||
|
||||
# Runtime data
|
||||
|
||||
pids
|
||||
_.pid
|
||||
_.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
|
||||
.temp
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
# IntelliJ based IDEs
|
||||
.idea
|
||||
|
||||
# Finder (MacOS) folder config
|
||||
.DS_Store
|
BIN
packages/sn/bun.lockb
Executable file
BIN
packages/sn/bun.lockb
Executable file
Binary file not shown.
32
packages/sn/package.json
Normal file
32
packages/sn/package.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "solar-js-sdk",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"tsup": {
|
||||
"entry": [
|
||||
"src/index.ts"
|
||||
],
|
||||
"splitting": true,
|
||||
"sourcemap": true,
|
||||
"clean": true,
|
||||
"dts": true,
|
||||
"format": "esm"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
"tsup": "^8.3.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.7.9",
|
||||
"axios-case-converter": "^1.1.1",
|
||||
"universal-cookie": "^7.2.2",
|
||||
"zustand": "^5.0.3"
|
||||
}
|
||||
}
|
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
|
||||
}
|
||||
},
|
||||
}))
|
27
packages/sn/tsconfig.json
Normal file
27
packages/sn/tsconfig.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable latest features
|
||||
"lib": ["ESNext", "DOM"],
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"noEmit": true,
|
||||
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user