Compare commits

...

3 Commits

Author SHA1 Message Date
5d6a018c63 🐛 Switch bundler and bug fixes 2025-01-10 00:26:52 +08:00
c1140b4e2f ♻️ Splitting up services (skip ci) 2025-01-10 00:18:26 +08:00
df6679bbe3 💄 Optimize card of product 2025-01-09 23:42:37 +08:00
38 changed files with 511 additions and 222 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -9,6 +9,7 @@
"lint": "next lint" "lint": "next lint"
}, },
"dependencies": { "dependencies": {
"solar-js-sdk": "./packages/sn",
"@emotion/cache": "^11.14.0", "@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/server": "^11.11.0", "@emotion/server": "^11.11.0",

175
packages/sn/.gitignore vendored Normal file
View 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

Binary file not shown.

31
packages/sn/package.json Normal file
View File

@ -0,0 +1,31 @@
{
"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",
"universal-cookie": "^7.2.2",
"zustand": "^5.0.3"
}
}

View File

@ -1,4 +1,4 @@
import { hasCookie } from 'cookies-next/client' import Cookies from 'universal-cookie'
export interface SnAuthResult { export interface SnAuthResult {
isFinished: boolean isFinished: boolean
@ -35,8 +35,21 @@ export interface SnAuthFactor {
accountId?: number | 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 { export function checkAuthenticatedClient(): boolean {
return !!hasCookie('nex_user_atk') const cookies = new Cookies()
return !!cookies.get('nex_user_atk')
} }
export function redirectToLogin() { export function redirectToLogin() {

7
packages/sn/src/index.ts Normal file
View 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'

113
packages/sn/src/network.ts Normal file
View File

@ -0,0 +1,113 @@
import axios, { type AxiosInstance } from 'axios'
import Cookies from 'universal-cookie'
import { setTokenCookies } from './auth'
function toCamelCase(obj: any): any {
if (Array.isArray(obj)) {
return obj.map(toCamelCase)
} else if (obj && typeof obj === 'object') {
return Object.keys(obj).reduce((result: any, key) => {
const camelKey = key.replace(/_([a-z])/g, (_, char) => char.toUpperCase())
result[camelKey] = toCamelCase(obj[key])
return result
}, {})
}
return obj
}
// function toSnakeCase(obj: any): any {
// if (Array.isArray(obj)) {
// return obj.map(toSnakeCase)
// } else if (obj && typeof obj === 'object') {
// return Object.keys(obj).reduce((result: any, key) => {
// const snakeKey = key.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`)
// result[snakeKey] = toSnakeCase(obj[key])
// return result
// }, {})
// }
// return obj
// }
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,
)
inst.interceptors.response.use(
(response) => {
if (response.data) {
response.data = toCamelCase(response.data)
}
return response
},
(error) => {
if (error.response && error.response.data) {
error.response.data = toCamelCase(error.response.data)
}
return Promise.reject(error)
},
)
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}`
}

83
packages/sn/src/user.ts Normal file
View 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
View 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
}
}

View File

@ -1,6 +1,6 @@
import { useUserStore } from '@/services/user' import { useUserStore } from 'solar-js-sdk'
import { AppBar, AppBarProps, Avatar, IconButton, Toolbar, Typography, useScrollTrigger, useTheme } from '@mui/material' import { AppBar, AppBarProps, Avatar, IconButton, Toolbar, Typography, useScrollTrigger, useTheme } from '@mui/material'
import { getAttachmentUrl } from '@/services/network' import { getAttachmentUrl } from 'solar-js-sdk'
import MenuIcon from '@mui/icons-material/Menu' import MenuIcon from '@mui/icons-material/Menu'
import AccountCircle from '@mui/icons-material/AccountCircle' import AccountCircle from '@mui/icons-material/AccountCircle'
import Link from 'next/link' import Link from 'next/link'

View File

@ -1,5 +1,5 @@
import { SnAttachment } from '@/services/attachment' import { SnAttachment } from 'solar-js-sdk'
import { getAttachmentUrl } from '@/services/network' import { getAttachmentUrl } from 'solar-js-sdk'
import { QuestionMark } from '@mui/icons-material' import { QuestionMark } from '@mui/icons-material'
import { Link, Paper, Typography } from '@mui/material' import { Link, Paper, Typography } from '@mui/material'
import { ComponentProps } from 'react' import { ComponentProps } from 'react'

View File

@ -1,14 +1,13 @@
'use client' 'use client'
import { SnAuthFactor, SnAuthResult, SnAuthTicket } from '@/services/auth' import { setTokenCookies, SnAuthFactor, SnAuthResult, SnAuthTicket } from 'solar-js-sdk'
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { ArrowForward } from '@mui/icons-material' import { ArrowForward } from '@mui/icons-material'
import { Collapse, Alert, Box, TextField, Button } from '@mui/material' import { Collapse, Alert, Box, TextField, Button } from '@mui/material'
import { useState } from 'react' import { useState } from 'react'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import ErrorIcon from '@mui/icons-material/Error' import ErrorIcon from '@mui/icons-material/Error'
import { setCookie } from 'cookies-next/client'
export interface SnLoginCheckpointForm { export interface SnLoginCheckpointForm {
password: string password: string
@ -44,8 +43,7 @@ export function SnLoginCheckpoint({
}) })
const atk: string = tokenResp.data['accessToken'] const atk: string = tokenResp.data['accessToken']
const rtk: string = tokenResp.data['refreshToken'] const rtk: string = tokenResp.data['refreshToken']
setCookie('nex_user_atk', atk, { path: '/', maxAge: 2592000 }) setTokenCookies(atk, rtk)
setCookie('nex_user_rtk', rtk, { path: '/', maxAge: 2592000 })
console.log('[Authenticator] User has been logged in. Result atk: ', atk) console.log('[Authenticator] User has been logged in. Result atk: ', atk)
} }

View File

@ -1,7 +1,7 @@
'use client' 'use client'
import { SnAuthFactor, SnAuthTicket } from '@/services/auth' import { SnAuthFactor, SnAuthTicket } from 'solar-js-sdk'
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { Collapse, Alert, Box, Button, Typography, ButtonGroup } from '@mui/material' import { Collapse, Alert, Box, Button, Typography, ButtonGroup } from '@mui/material'
import { useState } from 'react' import { useState } from 'react'

View File

@ -1,10 +1,10 @@
'use client' 'use client'
import { useState } from 'react' import { useState } from 'react'
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { ArrowForward } from '@mui/icons-material' import { ArrowForward } from '@mui/icons-material'
import { Alert, Box, Button, Collapse, Link, TextField, Typography } from '@mui/material' import { Alert, Box, Button, Collapse, Link, TextField, Typography } from '@mui/material'
import { SnAuthFactor, SnAuthResult, SnAuthTicket } from '@/services/auth' import { SnAuthFactor, SnAuthResult, SnAuthTicket } from 'solar-js-sdk'
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import NextLink from 'next/link' import NextLink from 'next/link'

View File

@ -1,4 +1,4 @@
import { checkAuthenticatedClient, redirectToLogin } from '@/services/auth' import { checkAuthenticatedClient, redirectToLogin } from 'solar-js-sdk'
import { JSX, useEffect } from 'react' import { JSX, useEffect } from 'react'
import { DashboardLayout, Navigation } from '@toolpad/core' import { DashboardLayout, Navigation } from '@toolpad/core'
import { Box, Stack, Typography } from '@mui/material' import { Box, Stack, Typography } from '@mui/material'

View File

@ -5,7 +5,7 @@ import { Roboto } from 'next/font/google'
import { CapAppBar } from '@/components/CapAppBar' import { CapAppBar } from '@/components/CapAppBar'
import { PagesProgressBar as ProgressBar } from 'next-nprogress-bar' import { PagesProgressBar as ProgressBar } from 'next-nprogress-bar'
import { AppProvider } from '@toolpad/core/nextjs' import { AppProvider } from '@toolpad/core/nextjs'
import { useUserStore } from '@/services/user' import { useUserStore } from 'solar-js-sdk'
import { useEffect } from 'react' import { useEffect } from 'react'
import Head from 'next/head' import Head from 'next/head'

View File

@ -1,6 +1,6 @@
import { AttachmentItem } from '@/components/attachments/AttachmentItem' import { AttachmentItem } from '@/components/attachments/AttachmentItem'
import { SnAttachment } from '@/services/attachment' import { SnAttachment } from 'solar-js-sdk'
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { Box, ImageList, ImageListItem, Pagination, useMediaQuery, useTheme } from '@mui/material' import { Box, ImageList, ImageListItem, Pagination, useMediaQuery, useTheme } from '@mui/material'
import { GetServerSideProps, InferGetServerSidePropsType } from 'next' import { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'

View File

@ -1,8 +1,8 @@
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { Container, Box, Typography, Alert, Collapse, Button, CircularProgress, Card, CardContent } from '@mui/material' import { Container, Box, Typography, Alert, Collapse, Button, CircularProgress, Card, CardContent } from '@mui/material'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { checkAuthenticatedClient, redirectToLogin, SnAuthTicket } from '@/services/auth' import { checkAuthenticatedClient, redirectToLogin, SnAuthTicket } from 'solar-js-sdk'
import ErrorIcon from '@mui/icons-material/Error' import ErrorIcon from '@mui/icons-material/Error'
import CloseIcon from '@mui/icons-material/Close' import CloseIcon from '@mui/icons-material/Close'

View File

@ -1,8 +1,8 @@
import { SnLoginCheckpoint } from '@/components/auth/SnLoginCheckpoint' import { SnLoginCheckpoint } from '@/components/auth/SnLoginCheckpoint'
import { SnLoginRouter } from '@/components/auth/SnLoginRouter' import { SnLoginRouter } from '@/components/auth/SnLoginRouter'
import { SnLoginStart } from '@/components/auth/SnLoginStart' import { SnLoginStart } from '@/components/auth/SnLoginStart'
import { SnAuthFactor, SnAuthTicket } from '@/services/auth' import { SnAuthFactor, SnAuthTicket } from 'solar-js-sdk'
import { useUserStore } from '@/services/user' import { useUserStore } from 'solar-js-sdk'
import { Box, Container, Typography } from '@mui/material' import { Box, Container, Typography } from '@mui/material'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useState } from 'react' import { useState } from 'react'

View File

@ -26,7 +26,7 @@ export default function ConsoleLanding() {
<Typography variant="subtitle1">of the Solar Network</Typography> <Typography variant="subtitle1">of the Solar Network</Typography>
</Box> </Box>
<Grid container columns={{ xs: 2, sm: 2, md: 3, lg: 4 }} spacing={4}> <Grid container columns={{ xs: 1, sm: 2, md: 3 }} spacing={4}>
<Grid size={1}> <Grid size={1}>
<NextLink passHref href="/console/matrix"> <NextLink passHref href="/console/matrix">
<CardActionArea> <CardActionArea>

View File

@ -1,7 +1,7 @@
import { ConsoleLayout, getConsoleStaticProps } from '@/components/layouts/ConsoleLayout' import { ConsoleLayout, getConsoleStaticProps } from '@/components/layouts/ConsoleLayout'
import { MaProduct } from '@/services/matrix/product' import { MaProduct } from 'solar-js-sdk'
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { Typography, Container, Box, Button, Grid2 as Grid, Card, CardActionArea, CardContent } from '@mui/material' import { Typography, Container, Box, Button, Grid2 as Grid, Card, CardContent, CardActions } from '@mui/material'
import NextLink from 'next/link' import NextLink from 'next/link'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
@ -36,11 +36,9 @@ export default function MatrixMarketplace() {
Matrix Marketplace Matrix Marketplace
</Typography> </Typography>
<Grid container columns={{ xs: 2, sm: 2, md: 3, lg: 4 }} spacing={4}> <Grid container columns={{ xs: 1, sm: 2, md: 3 }} spacing={4}>
{products.map((p) => ( {products.map((p) => (
<Grid size={1} key={p.id}> <Grid size={1} key={p.id}>
<NextLink passHref href="/console/matrix">
<CardActionArea>
<Card sx={{ width: '100%' }}> <Card sx={{ width: '100%' }}>
<CardContent> <CardContent>
<Typography variant="h5" gutterBottom> <Typography variant="h5" gutterBottom>
@ -48,9 +46,11 @@ export default function MatrixMarketplace() {
</Typography> </Typography>
<Typography variant="body1">{p.description}</Typography> <Typography variant="body1">{p.description}</Typography>
</CardContent> </CardContent>
<CardActions>
<Button size="small">Details</Button>
<Button size="small">Edit</Button>
</CardActions>
</Card> </Card>
</CardActionArea>
</NextLink>
</Grid> </Grid>
))} ))}
</Grid> </Grid>

View File

@ -3,8 +3,8 @@ import { Typography, Container, Box, Button, TextField, Collapse, Alert } from '
import { useForm } from 'react-hook-form' import { useForm } from 'react-hook-form'
import { useState } from 'react' import { useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { sni } from 'solar-js-sdk'
import NextLink from 'next/link' import NextLink from 'next/link'
import { sni } from '@/services/network'
import ErrorIcon from '@mui/icons-material/Error' import ErrorIcon from '@mui/icons-material/Error'

View File

@ -1,4 +1,4 @@
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { Container, Box, Typography, CircularProgress, Alert, Collapse } from '@mui/material' import { Container, Box, Typography, CircularProgress, Alert, Collapse } from '@mui/material'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'

View File

@ -1,4 +1,4 @@
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { Container, Box, Typography, Alert, Collapse, Button } from '@mui/material' import { Container, Box, Typography, Alert, Collapse, Button } from '@mui/material'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useState } from 'react' import { useState } from 'react'

View File

@ -1,4 +1,4 @@
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { Container, Box, Typography, Alert, Collapse, Button, TextField } from '@mui/material' import { Container, Box, Typography, Alert, Collapse, Button, TextField } from '@mui/material'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useState } from 'react' import { useState } from 'react'

View File

@ -1,6 +1,6 @@
import { getAttachmentUrl, sni } from '@/services/network' import { getAttachmentUrl, sni } from 'solar-js-sdk'
import { SnPost } from '@/services/post' import { SnPost } from 'solar-js-sdk'
import { listAttachment, SnAttachment } from '@/services/attachment' import { listAttachment, SnAttachment } from 'solar-js-sdk'
import { import {
Grid2 as Grid, Grid2 as Grid,
Alert, Alert,

View File

@ -1,5 +1,5 @@
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { SnPost } from '@/services/post' import { SnPost } from 'solar-js-sdk'
import { GetServerSideProps } from 'next' import { GetServerSideProps } from 'next'
import { Feed } from 'feed' import { Feed } from 'feed'

View File

@ -1,7 +1,7 @@
import { AttachmentItem } from '@/components/attachments/AttachmentItem' import { AttachmentItem } from '@/components/attachments/AttachmentItem'
import { SnAttachment, listAttachment } from '@/services/attachment' import { SnAttachment, listAttachment } from 'solar-js-sdk'
import { getAttachmentUrl, sni } from '@/services/network' import { getAttachmentUrl, sni } from 'solar-js-sdk'
import { SnPost } from '@/services/post' import { SnPost } from 'solar-js-sdk'
import { Avatar, Box, Container, Divider, Grid2 as Grid, Link, Pagination, Paper, Typography } from '@mui/material' import { Avatar, Box, Container, Divider, Grid2 as Grid, Link, Pagination, Paper, Typography } from '@mui/material'
import { GetServerSideProps, InferGetServerSidePropsType } from 'next' import { GetServerSideProps, InferGetServerSidePropsType } from 'next'
import NextLink from 'next/link' import NextLink from 'next/link'

View File

@ -1,5 +1,5 @@
import { sni } from '@/services/network' import { sni } from 'solar-js-sdk'
import { SnPost } from '@/services/post' import { SnPost } from 'solar-js-sdk'
import { GetServerSideProps } from 'next' import { GetServerSideProps } from 'next'
import { EnumChangefreq, SitemapItem, SitemapStream, streamToPromise } from 'sitemap' import { EnumChangefreq, SitemapItem, SitemapStream, streamToPromise } from 'sitemap'
import { Readable } from 'stream' import { Readable } from 'stream'

View File

@ -1,9 +1,10 @@
import { SnCheckInRecord } from '@/services/checkIn' import { SnCheckInRecord } from 'solar-js-sdk'
import { getAttachmentUrl, sni } from '@/services/network' import { getAttachmentUrl, sni } from 'solar-js-sdk'
import { SnAccount, SnAccountBadgeMapping } from '@/services/user' import { SnAccount } from 'solar-js-sdk'
import { Avatar, Box, Card, CardContent, Container, Grid2 as Grid, Typography } from '@mui/material' import { Avatar, Box, Card, CardContent, Container, Grid2 as Grid, Typography } from '@mui/material'
import { LineChart } from '@mui/x-charts' import { LineChart } from '@mui/x-charts'
import type { InferGetServerSidePropsType, GetServerSideProps } from 'next' import type { InferGetServerSidePropsType, GetServerSideProps } from 'next'
import { SnAccountBadgeMapping } from '@/services/user'
import Image from 'next/image' import Image from 'next/image'
export const getServerSideProps = (async (context) => { export const getServerSideProps = (async (context) => {

View File

@ -1,14 +1,14 @@
import { checkAuthenticatedClient, redirectToLogin } from '@/services/auth' import { checkAuthenticatedClient, redirectToLogin } from 'solar-js-sdk'
import { useUserStore } from '@/services/user' import { useUserStore } from 'solar-js-sdk'
import { Avatar, Box, Button, Container, Typography } from '@mui/material' import { Avatar, Box, Button, Container, Typography } from '@mui/material'
import { getAttachmentUrl } from '@/services/network' import { getAttachmentUrl } from 'solar-js-sdk'
import { useEffect } from 'react' import { useEffect } from 'react'
import { removeTokenCookies } from 'solar-js-sdk'
import Image from 'next/image' import Image from 'next/image'
import LogoutIcon from '@mui/icons-material/Logout' import LogoutIcon from '@mui/icons-material/Logout'
import LaunchIcon from '@mui/icons-material/Launch' import LaunchIcon from '@mui/icons-material/Launch'
import Link from 'next/link' import Link from 'next/link'
import { deleteCookie } from 'cookies-next/client'
export default function UserItself() { export default function UserItself() {
useEffect(() => { useEffect(() => {
@ -18,8 +18,7 @@ export default function UserItself() {
const userStore = useUserStore() const userStore = useUserStore()
function logout() { function logout() {
deleteCookie('nex_user_atk') removeTokenCookies()
deleteCookie('nex_user_rtk')
window.location.reload() window.location.reload()
} }

View File

@ -1,77 +0,0 @@
import axios, { AxiosInstance } from 'axios'
import applyCaseMiddleware from 'axios-case-converter'
import { hasCookie, getCookie, setCookie } from 'cookies-next/client'
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> {
if (!hasCookie('nex_user_atk') || !hasCookie('nex_user_rtk')) return
const ogTk: string = getCookie('nex_user_atk')!
if (!isTokenExpired(ogTk)) return ogTk
const resp = await axios.post(
'/cgi/id/auth/token',
{
refresh_token: getCookie('nex_user_rtk')!,
grant_type: 'refresh_token',
},
{ baseURL },
)
const atk: string = resp.data['access_token']
const rtk: string = resp.data['refresh_token']
setCookie('nex_user_atk', atk, { path: '/', maxAge: 2592000 })
setCookie('nex_user_rtk', rtk, { path: '/', maxAge: 2592000 })
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}`
}

View File

@ -1,69 +1,8 @@
import { create } from 'zustand'
import { sni } from './network'
import { hasCookie } from 'cookies-next/client'
import { JSX } from 'react' import { JSX } from 'react'
import ConstructionIcon from '@mui/icons-material/Construction' import ConstructionIcon from '@mui/icons-material/Construction'
import FlagIcon from '@mui/icons-material/Flag' import FlagIcon from '@mui/icons-material/Flag'
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 const SnAccountBadgeMapping: Record<string, { icon: JSX.Element; name: string }> = { export const SnAccountBadgeMapping: Record<string, { icon: JSX.Element; name: string }> = {
'company.staff': { 'company.staff': {
icon: <ConstructionIcon />, icon: <ConstructionIcon />,
@ -74,24 +13,3 @@ export const SnAccountBadgeMapping: Record<string, { icon: JSX.Element; name: st
name: 'Solar Network Natives', name: 'Solar Network Natives',
}, },
} }
export interface UserStore {
account: SnAccount | undefined
fetchUser: () => Promise<SnAccount | undefined>
}
export const useUserStore = create<UserStore>((set) => ({
account: undefined,
fetchUser: async (): Promise<SnAccount | undefined> => {
if (!hasCookie('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
}
},
}))