Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled

Full-stack web application for Telegram management
- Frontend: Vue 3 + Vben Admin
- Backend: NestJS
- Features: User management, group broadcast, statistics

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
你的用户名
2025-11-04 15:37:50 +08:00
commit 237c7802e5
3674 changed files with 525172 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import api from '@/api'
export const useABTestingStore = defineStore('abTesting', () => {
// State
const experiments = ref([])
const currentExperiment = ref(null)
const experimentStatus = ref(null)
const loading = ref(false)
// Actions
const fetchExperiments = async (params = {}) => {
try {
const response = await api.get('/api/experiments', { params })
experiments.value = response.data.experiments
return response.data
} catch (error) {
console.error('Failed to fetch experiments:', error)
throw error
}
}
const fetchExperiment = async (experimentId) => {
try {
loading.value = true
const response = await api.get(`/api/experiments/${experimentId}`)
currentExperiment.value = response.data
return response.data
} catch (error) {
console.error('Failed to fetch experiment:', error)
throw error
} finally {
loading.value = false
}
}
const createExperiment = async (experimentData) => {
try {
const response = await api.post('/api/experiments', experimentData)
return response.data
} catch (error) {
console.error('Failed to create experiment:', error)
throw error
}
}
const updateExperiment = async (experimentId, updates) => {
try {
const response = await api.put(`/api/experiments/${experimentId}`, updates)
currentExperiment.value = response.data
return response.data
} catch (error) {
console.error('Failed to update experiment:', error)
throw error
}
}
const deleteExperiment = async (experimentId) => {
try {
await api.delete(`/api/experiments/${experimentId}`)
} catch (error) {
console.error('Failed to delete experiment:', error)
throw error
}
}
const startExperiment = async (experimentId) => {
try {
const response = await api.post(`/api/experiments/${experimentId}/start`)
return response.data
} catch (error) {
console.error('Failed to start experiment:', error)
throw error
}
}
const stopExperiment = async (experimentId, reason) => {
try {
const response = await api.post(`/api/experiments/${experimentId}/stop`, { reason })
return response.data
} catch (error) {
console.error('Failed to stop experiment:', error)
throw error
}
}
const fetchExperimentStatus = async (experimentId) => {
try {
const response = await api.get(`/api/experiments/${experimentId}/status`)
experimentStatus.value = response.data
return response.data
} catch (error) {
console.error('Failed to fetch experiment status:', error)
throw error
}
}
// Tracking APIs
const allocateUser = async (experimentId, userId, userContext = {}) => {
try {
const response = await api.post('/api/tracking/allocate', {
experimentId,
userId,
userContext
})
return response.data
} catch (error) {
console.error('Failed to allocate user:', error)
throw error
}
}
const recordConversion = async (experimentId, userId, value = 1, metadata = {}) => {
try {
const response = await api.post('/api/tracking/convert', {
experimentId,
userId,
value,
metadata
})
return response.data
} catch (error) {
console.error('Failed to record conversion:', error)
throw error
}
}
const batchAllocate = async (experimentId, users) => {
try {
const response = await api.post('/api/tracking/batch/allocate', {
experimentId,
users
})
return response.data
} catch (error) {
console.error('Failed to batch allocate users:', error)
throw error
}
}
const batchRecordConversions = async (experimentId, conversions) => {
try {
const response = await api.post('/api/tracking/batch/convert', {
experimentId,
conversions
})
return response.data
} catch (error) {
console.error('Failed to batch record conversions:', error)
throw error
}
}
// Results APIs
const fetchExperimentResults = async (experimentId) => {
try {
const response = await api.get(`/api/results/${experimentId}`)
return response.data
} catch (error) {
console.error('Failed to fetch experiment results:', error)
throw error
}
}
const fetchVariantDetails = async (experimentId, variantId) => {
try {
const response = await api.get(`/api/results/${experimentId}/variants/${variantId}`)
return response.data
} catch (error) {
console.error('Failed to fetch variant details:', error)
throw error
}
}
const fetchSegmentAnalysis = async (experimentId) => {
try {
const response = await api.get(`/api/results/${experimentId}/segments`)
return response.data
} catch (error) {
console.error('Failed to fetch segment analysis:', error)
throw error
}
}
const exportResults = async (experimentId, format = 'csv') => {
try {
const response = await api.get(`/api/results/${experimentId}/export`, {
params: { format },
responseType: 'blob'
})
// Create download link
const url = window.URL.createObjectURL(new Blob([response.data]))
const link = document.createElement('a')
link.href = url
link.setAttribute('download', `experiment-${experimentId}-results.${format}`)
document.body.appendChild(link)
link.click()
link.remove()
return true
} catch (error) {
console.error('Failed to export results:', error)
throw error
}
}
// Reset store
const reset = () => {
experiments.value = []
currentExperiment.value = null
experimentStatus.value = null
loading.value = false
}
return {
// State
experiments,
currentExperiment,
experimentStatus,
loading,
// Actions
fetchExperiments,
fetchExperiment,
createExperiment,
updateExperiment,
deleteExperiment,
startExperiment,
stopExperiment,
fetchExperimentStatus,
// Tracking
allocateUser,
recordConversion,
batchAllocate,
batchRecordConversions,
// Results
fetchExperimentResults,
fetchVariantDetails,
fetchSegmentAnalysis,
exportResults,
// Utils
reset
}
})

View File

@@ -0,0 +1,102 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import api from '@/api'
import router from '@/router'
export const useAuthStore = defineStore('auth', () => {
const user = ref(null)
const token = ref(localStorage.getItem('token') || '')
const loading = ref(false)
const isAuthenticated = computed(() => !!token.value)
async function login(credentials) {
loading.value = true
try {
const response = await api.auth.login(credentials)
const { data } = response
token.value = data.accessToken
user.value = data.user
localStorage.setItem('token', data.accessToken)
localStorage.setItem('refreshToken', data.refreshToken)
api.setAuthToken(data.accessToken)
return { success: true }
} catch (error) {
return {
success: false,
message: error.response?.data?.error || 'Login failed'
}
} finally {
loading.value = false
}
}
async function register(userData) {
loading.value = true
try {
const response = await api.auth.register(userData)
const { token: newToken, user: newUser } = response.data
token.value = newToken
user.value = newUser
localStorage.setItem('token', newToken)
api.setAuthToken(newToken)
return { success: true }
} catch (error) {
return {
success: false,
message: error.response?.data?.error || 'Registration failed'
}
} finally {
loading.value = false
}
}
async function logout() {
try {
await api.auth.logout()
} catch (error) {
console.error('Logout error:', error)
} finally {
token.value = ''
user.value = null
localStorage.removeItem('token')
api.setAuthToken('')
router.push({ name: 'Login' })
}
}
async function fetchProfile() {
if (!token.value) return
try {
const response = await api.auth.getProfile()
user.value = response.data.user
} catch (error) {
console.error('Failed to fetch profile:', error)
if (error.response?.status === 401) {
await logout()
}
}
}
// Initialize auth token
if (token.value) {
api.setAuthToken(token.value)
fetchProfile()
}
return {
user,
token,
loading,
isAuthenticated,
login,
register,
logout,
fetchProfile
}
})

View File

@@ -0,0 +1,268 @@
// Pinia plugin for persisted state with performance optimizations
import { debounce } from '@/utils/performance'
const STORAGE_KEY_PREFIX = 'marketing_agent_'
/**
* Create persisted state plugin for Pinia
*/
export function createPersistedState(options = {}) {
const {
storage = localStorage,
paths = [],
debounceTime = 1000,
serializer = JSON,
filter = () => true
} = options
return (context) => {
const { store } = context
const storeId = store.$id
const storageKey = STORAGE_KEY_PREFIX + storeId
// Load initial state
try {
const savedState = storage.getItem(storageKey)
if (savedState) {
const parsed = serializer.parse(savedState)
// Only restore specified paths or all if paths is empty
if (paths.length === 0) {
store.$patch(parsed)
} else {
const filtered = {}
paths.forEach(path => {
const keys = path.split('.')
let source = parsed
let target = filtered
keys.forEach((key, index) => {
if (index === keys.length - 1) {
if (source && key in source) {
target[key] = source[key]
}
} else {
if (!target[key]) target[key] = {}
target = target[key]
source = source ? source[key] : undefined
}
})
})
store.$patch(filtered)
}
}
} catch (error) {
console.error(`Failed to load persisted state for ${storeId}:`, error)
}
// Save state changes with debouncing
const saveState = debounce(() => {
try {
const state = store.$state
let toSave = state
// Filter state if paths are specified
if (paths.length > 0) {
toSave = {}
paths.forEach(path => {
const keys = path.split('.')
let source = state
let target = toSave
keys.forEach((key, index) => {
if (index === keys.length - 1) {
if (source && key in source) {
target[key] = source[key]
}
} else {
if (!target[key]) target[key] = {}
target = target[key]
source = source ? source[key] : undefined
}
})
})
}
// Apply custom filter
if (filter(toSave)) {
storage.setItem(storageKey, serializer.stringify(toSave))
}
} catch (error) {
console.error(`Failed to save state for ${storeId}:`, error)
}
}, debounceTime)
// Subscribe to state changes
store.$subscribe((mutation, state) => {
saveState()
})
// Subscribe to actions for immediate save on specific actions
store.$onAction(({ name, after }) => {
// Save immediately after logout or critical actions
if (['logout', 'clearData', 'resetStore'].includes(name)) {
after(() => {
saveState.cancel()
try {
if (name === 'logout' || name === 'clearData') {
storage.removeItem(storageKey)
} else {
storage.setItem(storageKey, serializer.stringify(store.$state))
}
} catch (error) {
console.error(`Failed to handle action ${name}:`, error)
}
})
}
})
}
}
/**
* Secure storage wrapper with encryption
*/
export class SecureStorage {
constructor(secretKey) {
this.secretKey = secretKey
}
async encrypt(data) {
const encoder = new TextEncoder()
const dataBuffer = encoder.encode(JSON.stringify(data))
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(this.secretKey),
{ name: 'AES-GCM', length: 256 },
false,
['encrypt']
)
const iv = crypto.getRandomValues(new Uint8Array(12))
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
dataBuffer
)
const combined = new Uint8Array(iv.length + encrypted.byteLength)
combined.set(iv)
combined.set(new Uint8Array(encrypted), iv.length)
return btoa(String.fromCharCode(...combined))
}
async decrypt(encryptedData) {
const combined = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0))
const iv = combined.slice(0, 12)
const encrypted = combined.slice(12)
const encoder = new TextEncoder()
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(this.secretKey),
{ name: 'AES-GCM', length: 256 },
false,
['decrypt']
)
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
encrypted
)
const decoder = new TextDecoder()
return JSON.parse(decoder.decode(decrypted))
}
setItem(key, value) {
return this.encrypt(value).then(encrypted => {
localStorage.setItem(key, encrypted)
})
}
getItem(key) {
const encrypted = localStorage.getItem(key)
if (!encrypted) return Promise.resolve(null)
return this.decrypt(encrypted).catch(error => {
console.error('Decryption failed:', error)
localStorage.removeItem(key)
return null
})
}
removeItem(key) {
localStorage.removeItem(key)
}
}
/**
* IndexedDB storage for large data
*/
export class IndexedDBStorage {
constructor(dbName = 'marketing_agent_store', storeName = 'state') {
this.dbName = dbName
this.storeName = storeName
this.db = null
}
async init() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, 1)
request.onerror = () => reject(request.error)
request.onsuccess = () => {
this.db = request.result
resolve()
}
request.onupgradeneeded = (event) => {
const db = event.target.result
if (!db.objectStoreNames.contains(this.storeName)) {
db.createObjectStore(this.storeName)
}
}
})
}
async setItem(key, value) {
if (!this.db) await this.init()
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite')
const store = transaction.objectStore(this.storeName)
const request = store.put(value, key)
request.onsuccess = () => resolve()
request.onerror = () => reject(request.error)
})
}
async getItem(key) {
if (!this.db) await this.init()
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readonly')
const store = transaction.objectStore(this.storeName)
const request = store.get(key)
request.onsuccess = () => resolve(request.result)
request.onerror = () => reject(request.error)
})
}
async removeItem(key) {
if (!this.db) await this.init()
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([this.storeName], 'readwrite')
const store = transaction.objectStore(this.storeName)
const request = store.delete(key)
request.onsuccess = () => resolve()
request.onerror = () => reject(request.error)
})
}
}