Initial commit

This commit is contained in:
2026-04-17 23:26:01 +00:00
commit 2ea4ca5d52
409 changed files with 63459 additions and 0 deletions

33
app/stores/avatar.ts Normal file
View File

@@ -0,0 +1,33 @@
export const useAvatar = defineStore('avatar', {
getters: {
name: () => useUser().user?.name,
/**
* Returns the URL of the user's avatar, or null if not available
*/
src: () => {
const user = useUser().user
const fileName = useUser().user?.avatar
if (user && fileName) {
const { pb } = usePocketBase()
return pb.files.getURL(user, fileName, { thumb: '80x80' })
}
return null
}
},
actions: {
/**
* Uploads an avatar for the current user
*/
async uploadAvatar(file: File) {
const { isAuthenticated, userId } = useUser()
if (isAuthenticated) {
const { pb } = usePocketBase()
useUser().user = await pb.collection('users').update(userId!, {
avatar: file
})
} else {
console.warn('Avatar upload failed: user is not authenticated')
}
}
}
})

81
app/stores/counter.ts Normal file
View File

@@ -0,0 +1,81 @@
const COLLECTION = 'counters'
export const useCounter = defineStore('counter', {
state: () => {
return {
recordId: null as string | null,
count: 0
}
},
actions: {
/**
* Subscribes to changes of the count in realtime
* and calls the method to fetch the current count
*/
subscribeToChanges() {
if (useUser().isAuthenticated) {
const { pb } = usePocketBase()
pb.collection(COLLECTION).subscribe('*', () => this.fetchCurrentCount())
}
},
/**
* Unsubscribe from the realtime channel
* Should be done when the user leaves the page for cleanup
*/
async unsubscribe() {
const { pb } = usePocketBase()
pb.collection(COLLECTION).unsubscribe('*')
},
/**
* Fetches the current count and sets the state
*/
async fetchCurrentCount() {
const userId = useUser().userId
if (!userId) return
try {
const { pb } = usePocketBase()
const record = await pb.collection(COLLECTION).getFirstListItem(`userId="${userId}"`)
this.count = record.count
this.recordId = record.id
return
} catch (error) {
console.error(error)
}
},
/**
* Increments the count by the given amount (positive or negative)
*/
async increment(amount: number) {
try {
const { pb } = usePocketBase()
if (this.recordId) {
await pb.collection(COLLECTION).update(this.recordId, {
count: this.count + amount
})
} else {
const record = await pb.collection(COLLECTION).create({
userId: useUser().userId,
count: this.count + amount
})
this.recordId = record.id
}
} catch (error) {
console.error(error)
}
},
/**
* Resets the count to zero
* Uses a custom PocketBase endpoint for demonstration
*/
async reset() {
try {
const { pb } = usePocketBase()
await pb.send('/counter/reset', {
method: 'POST'
})
} catch (error) {
console.error(error)
}
}
}
})

View File

@@ -0,0 +1,83 @@
import type { NotificationsRecord } from '~/types/pocketbase.types'
const COLLECTION = 'notifications'
export const useNotifications = defineStore('notifications', {
state: () => {
return {
notifications: [] as NotificationsRecord[]
}
},
actions: {
/**
* Subscribes to changes of notifications in realtime
* and calls the method to refetch them
*/
subscribeToChanges() {
if (useUser().isAuthenticated) {
const { pb } = usePocketBase()
pb.collection(COLLECTION).subscribe('*', () => this.fetchNotifications())
}
},
/**
* Unsubscribe from the realtime channel
* Should be done when the user leaves the page for cleanup
*/
unsubscribe() {
const { pb } = usePocketBase()
pb.collection(COLLECTION).unsubscribe('*')
},
/**
* Fetches the notifications and sets the state
*/
async fetchNotifications() {
const userId = useUser().userId
if (!userId) return
try {
const { pb } = usePocketBase()
const resultList = await pb.collection(COLLECTION).getList(1, 50, {
filter: `userId="${userId}"`
})
this.notifications = resultList.items
} catch (error) {
console.error(error)
}
},
/**
* Marks a notification as read
*/
async markAsRead(notificationId: string) {
try {
const { pb } = usePocketBase()
await pb.collection(COLLECTION).update(notificationId, {
isRead: true
})
} catch (error) {
console.error(error)
}
},
/**
* Adds a new FCM token for the current user for push notifications
*/
async addFcmToken(token: string) {
const userId = useUser().userId
if (!userId) return
try {
const { pb } = usePocketBase()
const result = await pb.collection('fcm_tokens').getList(1, 0, {
filter: `token = "${token}"`
})
if (result.totalItems > 0) {
// skip if token already exists
return
}
await pb.collection('fcm_tokens').create({
userId,
token
})
} catch (error) {
console.error(error)
}
}
}
})

106
app/stores/user.ts Normal file
View File

@@ -0,0 +1,106 @@
import type { UsersRecord } from '~/types/pocketbase.types'
export const useUser = defineStore('user', {
state: () => ({
user: null as UsersRecord | null
}),
getters: {
isAuthenticated() {
const { authStore } = usePocketBase()
return authStore.value.isValid && authStore.value.record !== null
},
userId() {
const { authStore } = usePocketBase()
return authStore.value.record?.id
}
},
actions: {
/**
* Signs in a user with email (sends OTP)
* @param email The email address of the user
* @returns The OTP ID needed for authenticating with the received OTP
*/
async signInWithEmail(email: string, language?: string) {
const { pb } = usePocketBase()
const req = await pb.collection('users').requestOTP(email, {
headers: {
language: language ?? 'en'
}
})
return req.otpId
},
/**
* Signs in a user with OTP
* @param otpId The OTP ID from the email OTP request
* @param otp The OTP code sent to the user's email
*/
async signInWithOtp(otpId: string, otp: string) {
const { pb } = usePocketBase()
await pb.collection('users').authWithOTP(otpId, otp)
},
/**
* Signs in a user with OAuth provider
* @param provider The OAuth provider to use
*/
async signInWithOAuth(provider: 'apple' | 'google') {
const { pb } = usePocketBase()
await pb.collection('users').authWithOAuth2({
provider
})
},
/**
* Refreshes the auth store for the current user
*/
async authRefresh() {
try {
const { pb } = usePocketBase()
await pb.collection('users').authRefresh()
await this.fetchUser()
} catch (error) {
console.debug('User not authenticated', error)
}
},
/**
* Signs out the current user
*/
signOut() {
const { pb } = usePocketBase()
pb.authStore.clear()
this.user = null
},
/**
* Fetches all data of the currently authenticated user
*/
async fetchUser() {
if (this.isAuthenticated) {
const { pb } = usePocketBase()
this.user = await pb.collection('users').getOne(this.userId!)
} else {
console.warn('Fetch user failed: user is not authenticated')
}
},
/**
* Updates the given fields of the current user
*/
async updateUser(data: Partial<UsersRecord>) {
if (this.isAuthenticated) {
const { pb } = usePocketBase()
this.user = await pb.collection('users').update(this.userId!, data)
} else {
console.warn('Update user failed: user is not authenticated')
}
},
/**
* Deletes the current user's account
*/
async deleteUser() {
if (this.isAuthenticated) {
const { pb } = usePocketBase()
await pb.collection('users').delete(this.userId!)
this.signOut()
} else {
console.warn('Delete user failed: user is not authenticated')
}
}
}
})