Initial commit
This commit is contained in:
33
app/stores/avatar.ts
Normal file
33
app/stores/avatar.ts
Normal 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
81
app/stores/counter.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
83
app/stores/notifications.ts
Normal file
83
app/stores/notifications.ts
Normal 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
106
app/stores/user.ts
Normal 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')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user