Enhance deployment script and update package dependencies
- Added detailed labels to the deployment script for IndeedHub, including title, version, description, license, icon, and repository URL. - Updated package dependencies in package.json and package-lock.json, including upgrading 'nostr-tools' to version 2.23.0 and adding 'axios' and '@tanstack/vue-query'. - Improved README with a modern description of the platform and updated project structure details. This commit enhances the clarity of the deployment process and ensures the project is using the latest dependencies for better performance and features.
This commit is contained in:
412
src/stores/auth.ts
Normal file
412
src/stores/auth.ts
Normal file
@@ -0,0 +1,412 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { authService } from '../services/auth.service'
|
||||
import type { ApiUser } from '../types/api'
|
||||
|
||||
export type AuthType = 'cognito' | 'nostr' | null
|
||||
|
||||
export interface AuthState {
|
||||
user: ApiUser | null
|
||||
authType: AuthType
|
||||
isAuthenticated: boolean
|
||||
nostrPubkey: string | null
|
||||
cognitoToken: string | null
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication Store
|
||||
* Manages user authentication state with dual Cognito/Nostr support
|
||||
*/
|
||||
export const useAuthStore = defineStore('auth', () => {
|
||||
// State
|
||||
const user = ref<ApiUser | null>(null)
|
||||
const authType = ref<AuthType>(null)
|
||||
const isAuthenticated = ref(false)
|
||||
const nostrPubkey = ref<string | null>(null)
|
||||
const cognitoToken = ref<string | null>(null)
|
||||
const isLoading = ref(false)
|
||||
|
||||
/**
|
||||
* Initialize auth state from stored tokens
|
||||
*/
|
||||
async function initialize() {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
// Check for existing tokens
|
||||
const storedCognitoToken = sessionStorage.getItem('auth_token')
|
||||
const storedNostrToken = sessionStorage.getItem('nostr_token')
|
||||
|
||||
if (storedCognitoToken || storedNostrToken) {
|
||||
// Validate session and fetch user
|
||||
const isValid = await authService.validateSession()
|
||||
|
||||
if (isValid) {
|
||||
await fetchCurrentUser()
|
||||
|
||||
if (storedCognitoToken) {
|
||||
authType.value = 'cognito'
|
||||
cognitoToken.value = storedCognitoToken
|
||||
} else {
|
||||
authType.value = 'nostr'
|
||||
}
|
||||
|
||||
isAuthenticated.value = true
|
||||
} else {
|
||||
// Session invalid - clear auth
|
||||
await logout()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize auth:', error)
|
||||
await logout()
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login with email and password (Cognito)
|
||||
*/
|
||||
async function loginWithCognito(email: string, password: string) {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
// Check if we're in development mode without backend
|
||||
const useMockData = import.meta.env.VITE_USE_MOCK_DATA === 'true' || import.meta.env.DEV
|
||||
|
||||
if (useMockData) {
|
||||
// Mock Cognito login for development
|
||||
console.log('🔧 Development mode: Using mock Cognito authentication')
|
||||
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// Create a mock user with active subscription
|
||||
const mockUser = {
|
||||
id: 'mock-user-' + email.split('@')[0],
|
||||
email: email,
|
||||
legalName: email.split('@')[0].charAt(0).toUpperCase() + email.split('@')[0].slice(1),
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
subscriptions: [{
|
||||
id: 'mock-sub-cinephile',
|
||||
userId: 'mock-user-' + email.split('@')[0],
|
||||
tier: 'cinephile' as const,
|
||||
status: 'active' as const,
|
||||
currentPeriodStart: new Date().toISOString(),
|
||||
currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days from now
|
||||
cancelAtPeriodEnd: false,
|
||||
stripePriceId: 'mock-price-cinephile',
|
||||
stripeCustomerId: 'mock-customer-' + email.split('@')[0],
|
||||
}],
|
||||
}
|
||||
|
||||
console.log('✅ Mock user created with Cinephile subscription (full access)')
|
||||
|
||||
cognitoToken.value = 'mock-jwt-token-' + Date.now()
|
||||
authType.value = 'cognito'
|
||||
user.value = mockUser
|
||||
isAuthenticated.value = true
|
||||
|
||||
// Store mock tokens
|
||||
sessionStorage.setItem('auth_token', cognitoToken.value)
|
||||
sessionStorage.setItem('refresh_token', 'mock-refresh-token')
|
||||
|
||||
return {
|
||||
accessToken: cognitoToken.value,
|
||||
idToken: 'mock-id-token',
|
||||
refreshToken: 'mock-refresh-token',
|
||||
expiresIn: 3600,
|
||||
}
|
||||
}
|
||||
|
||||
// Real API call
|
||||
const response = await authService.login({ email, password })
|
||||
|
||||
cognitoToken.value = response.accessToken
|
||||
authType.value = 'cognito'
|
||||
|
||||
await fetchCurrentUser()
|
||||
|
||||
isAuthenticated.value = true
|
||||
|
||||
return response
|
||||
} catch (error: any) {
|
||||
// Provide helpful error message
|
||||
if (error.message?.includes('Unable to connect')) {
|
||||
throw new Error(
|
||||
'Backend API not available. To use real authentication, start the backend server and set VITE_USE_MOCK_DATA=false in .env'
|
||||
)
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Login with Nostr signature
|
||||
*/
|
||||
async function loginWithNostr(pubkey: string, signature: string, event: any) {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
// Check if we're in development mode without backend
|
||||
const useMockData = import.meta.env.VITE_USE_MOCK_DATA === 'true' || import.meta.env.DEV
|
||||
|
||||
if (useMockData) {
|
||||
// Mock Nostr login for development
|
||||
console.log('🔧 Development mode: Using mock Nostr authentication')
|
||||
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// Create a mock Nostr user with active subscription
|
||||
const mockUser = {
|
||||
id: 'mock-nostr-user-' + pubkey.slice(0, 8),
|
||||
email: `${pubkey.slice(0, 8)}@nostr.local`,
|
||||
legalName: 'Nostr User',
|
||||
nostrPubkey: pubkey,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
subscriptions: [{
|
||||
id: 'mock-sub-cinephile',
|
||||
userId: 'mock-nostr-user-' + pubkey.slice(0, 8),
|
||||
tier: 'cinephile' as const,
|
||||
status: 'active' as const,
|
||||
currentPeriodStart: new Date().toISOString(),
|
||||
currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days from now
|
||||
cancelAtPeriodEnd: false,
|
||||
stripePriceId: 'mock-price-cinephile',
|
||||
stripeCustomerId: 'mock-customer-' + pubkey.slice(0, 8),
|
||||
}],
|
||||
}
|
||||
|
||||
console.log('✅ Mock Nostr user created with Cinephile subscription (full access)')
|
||||
console.log('📝 Nostr Pubkey:', pubkey)
|
||||
|
||||
nostrPubkey.value = pubkey
|
||||
authType.value = 'nostr'
|
||||
user.value = mockUser
|
||||
isAuthenticated.value = true
|
||||
|
||||
// Store mock session
|
||||
sessionStorage.setItem('nostr_token', 'mock-nostr-token-' + pubkey.slice(0, 16))
|
||||
|
||||
return {
|
||||
token: 'mock-nostr-token',
|
||||
user: mockUser,
|
||||
}
|
||||
}
|
||||
|
||||
// Real API call
|
||||
const response = await authService.createNostrSession({
|
||||
pubkey,
|
||||
signature,
|
||||
event,
|
||||
})
|
||||
|
||||
nostrPubkey.value = pubkey
|
||||
authType.value = 'nostr'
|
||||
user.value = response.user
|
||||
isAuthenticated.value = true
|
||||
|
||||
return response
|
||||
} catch (error: any) {
|
||||
// Provide helpful error message
|
||||
if (error.message?.includes('Unable to connect')) {
|
||||
throw new Error(
|
||||
'Backend API not available. To use real Nostr authentication, start the backend server and set VITE_USE_MOCK_DATA=false in .env'
|
||||
)
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register new user
|
||||
*/
|
||||
async function register(email: string, password: string, legalName: string) {
|
||||
isLoading.value = true
|
||||
|
||||
try {
|
||||
// Check if we're in development mode without backend
|
||||
const useMockData = import.meta.env.VITE_USE_MOCK_DATA === 'true' || import.meta.env.DEV
|
||||
|
||||
if (useMockData) {
|
||||
// Mock registration for development
|
||||
console.log('🔧 Development mode: Using mock registration')
|
||||
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
|
||||
// Create a mock user with active subscription
|
||||
const mockUser = {
|
||||
id: 'mock-user-' + email.split('@')[0],
|
||||
email: email,
|
||||
legalName: legalName,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
subscriptions: [{
|
||||
id: 'mock-sub-cinephile',
|
||||
userId: 'mock-user-' + email.split('@')[0],
|
||||
tier: 'cinephile' as const,
|
||||
status: 'active' as const,
|
||||
currentPeriodStart: new Date().toISOString(),
|
||||
currentPeriodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days from now
|
||||
cancelAtPeriodEnd: false,
|
||||
stripePriceId: 'mock-price-cinephile',
|
||||
stripeCustomerId: 'mock-customer-' + email.split('@')[0],
|
||||
}],
|
||||
}
|
||||
|
||||
console.log('✅ Mock user registered with Cinephile subscription (full access)')
|
||||
|
||||
cognitoToken.value = 'mock-jwt-token-' + Date.now()
|
||||
authType.value = 'cognito'
|
||||
user.value = mockUser
|
||||
isAuthenticated.value = true
|
||||
|
||||
// Store mock tokens
|
||||
sessionStorage.setItem('auth_token', cognitoToken.value)
|
||||
sessionStorage.setItem('refresh_token', 'mock-refresh-token')
|
||||
|
||||
return {
|
||||
accessToken: cognitoToken.value,
|
||||
idToken: 'mock-id-token',
|
||||
refreshToken: 'mock-refresh-token',
|
||||
expiresIn: 3600,
|
||||
}
|
||||
}
|
||||
|
||||
// Real API call
|
||||
const response = await authService.register({
|
||||
email,
|
||||
password,
|
||||
legalName,
|
||||
})
|
||||
|
||||
cognitoToken.value = response.accessToken
|
||||
authType.value = 'cognito'
|
||||
|
||||
await fetchCurrentUser()
|
||||
|
||||
isAuthenticated.value = true
|
||||
|
||||
return response
|
||||
} catch (error: any) {
|
||||
// Provide helpful error message
|
||||
if (error.message?.includes('Unable to connect')) {
|
||||
throw new Error(
|
||||
'Backend API not available. To use real authentication, start the backend server and set VITE_USE_MOCK_DATA=false in .env'
|
||||
)
|
||||
}
|
||||
throw error
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch current user data
|
||||
*/
|
||||
async function fetchCurrentUser() {
|
||||
try {
|
||||
const userData = await authService.getCurrentUser()
|
||||
user.value = userData
|
||||
|
||||
if (userData.nostrPubkey) {
|
||||
nostrPubkey.value = userData.nostrPubkey
|
||||
}
|
||||
|
||||
return userData
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch user:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout user
|
||||
*/
|
||||
async function logout() {
|
||||
await authService.logout()
|
||||
|
||||
user.value = null
|
||||
authType.value = null
|
||||
isAuthenticated.value = false
|
||||
nostrPubkey.value = null
|
||||
cognitoToken.value = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Link Nostr pubkey to account
|
||||
*/
|
||||
async function linkNostr(pubkey: string, signature: string) {
|
||||
try {
|
||||
const updatedUser = await authService.linkNostrPubkey(pubkey, signature)
|
||||
user.value = updatedUser
|
||||
nostrPubkey.value = pubkey
|
||||
return updatedUser
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlink Nostr pubkey from account
|
||||
*/
|
||||
async function unlinkNostr() {
|
||||
try {
|
||||
const updatedUser = await authService.unlinkNostrPubkey()
|
||||
user.value = updatedUser
|
||||
nostrPubkey.value = null
|
||||
return updatedUser
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is filmmaker
|
||||
*/
|
||||
function isFilmmaker(): boolean {
|
||||
return !!user.value?.filmmaker
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user has active subscription
|
||||
*/
|
||||
function hasActiveSubscription(): boolean {
|
||||
if (!user.value?.subscriptions) return false
|
||||
return user.value.subscriptions.some((sub) => sub.status === 'active')
|
||||
}
|
||||
|
||||
return {
|
||||
// State
|
||||
user,
|
||||
authType,
|
||||
isAuthenticated,
|
||||
nostrPubkey,
|
||||
cognitoToken,
|
||||
isLoading,
|
||||
|
||||
// Actions
|
||||
initialize,
|
||||
loginWithCognito,
|
||||
loginWithNostr,
|
||||
register,
|
||||
fetchCurrentUser,
|
||||
logout,
|
||||
linkNostr,
|
||||
unlinkNostr,
|
||||
|
||||
// Getters
|
||||
isFilmmaker,
|
||||
hasActiveSubscription,
|
||||
}
|
||||
})
|
||||
@@ -2,6 +2,10 @@ import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import type { Content } from '../types/content'
|
||||
import { indeeHubFilms, bitcoinFilms, documentaries, dramas } from '../data/indeeHubFilms'
|
||||
import { contentService } from '../services/content.service'
|
||||
import { mapApiProjectsToContents } from '../utils/mappers'
|
||||
|
||||
const USE_MOCK_DATA = import.meta.env.VITE_USE_MOCK_DATA === 'true' || import.meta.env.DEV
|
||||
|
||||
export const useContentStore = defineStore('content', () => {
|
||||
const featuredContent = ref<Content | null>(null)
|
||||
@@ -17,40 +21,102 @@ export const useContentStore = defineStore('content', () => {
|
||||
const loading = ref(false)
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
/**
|
||||
* Fetch content from API
|
||||
*/
|
||||
async function fetchContentFromApi() {
|
||||
try {
|
||||
// Fetch all published projects
|
||||
const projects = await contentService.getProjects({ status: 'published' })
|
||||
|
||||
if (projects.length === 0) {
|
||||
throw new Error('No content available')
|
||||
}
|
||||
|
||||
// Map API data to content format
|
||||
const allContent = mapApiProjectsToContents(projects)
|
||||
|
||||
// Set featured content (first film project or first project)
|
||||
const featuredFilm = allContent.find(c => c.type === 'film') || allContent[0]
|
||||
featuredContent.value = featuredFilm
|
||||
|
||||
// Organize into rows
|
||||
const films = allContent.filter(c => c.type === 'film')
|
||||
const bitcoinContent = allContent.filter(c =>
|
||||
c.categories?.some(cat => cat.toLowerCase().includes('bitcoin'))
|
||||
)
|
||||
const docs = allContent.filter(c =>
|
||||
c.categories?.some(cat => cat.toLowerCase().includes('documentary'))
|
||||
)
|
||||
const dramaContent = allContent.filter(c =>
|
||||
c.categories?.some(cat => cat.toLowerCase().includes('drama'))
|
||||
)
|
||||
|
||||
contentRows.value = {
|
||||
featured: allContent.slice(0, 10),
|
||||
newReleases: films.slice(0, 8),
|
||||
bitcoin: bitcoinContent.length > 0 ? bitcoinContent : films.slice(0, 6),
|
||||
documentaries: docs.length > 0 ? docs : films.slice(0, 6),
|
||||
dramas: dramaContent.length > 0 ? dramaContent : films.slice(0, 6),
|
||||
independent: films.slice(0, 10)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('API fetch failed:', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch content from mock data
|
||||
*/
|
||||
function fetchContentFromMock() {
|
||||
// Set featured content immediately - God Bless Bitcoin
|
||||
const godBlessBitcoin = bitcoinFilms.find(f => f.title === 'God Bless Bitcoin') || bitcoinFilms[0]
|
||||
if (godBlessBitcoin) {
|
||||
featuredContent.value = {
|
||||
...godBlessBitcoin,
|
||||
backdrop: '/images/god-bless-bitcoin-backdrop.jpg'
|
||||
}
|
||||
} else {
|
||||
featuredContent.value = indeeHubFilms[0]
|
||||
}
|
||||
|
||||
// Organize content into rows
|
||||
contentRows.value = {
|
||||
featured: indeeHubFilms.slice(0, 10),
|
||||
newReleases: indeeHubFilms.slice(0, 8).reverse(),
|
||||
bitcoin: bitcoinFilms,
|
||||
documentaries: documentaries.slice(0, 10),
|
||||
dramas: dramas.slice(0, 10),
|
||||
independent: indeeHubFilms.filter(f =>
|
||||
!f.categories.includes('Bitcoin') && !f.categories.includes('Documentary')
|
||||
).slice(0, 10)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main fetch content method
|
||||
*/
|
||||
async function fetchContent() {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
// Set featured content immediately - God Bless Bitcoin
|
||||
const godBlessBitcoin = bitcoinFilms.find(f => f.title === 'God Bless Bitcoin') || bitcoinFilms[0]
|
||||
if (godBlessBitcoin) {
|
||||
// Override backdrop to use the public folder image
|
||||
featuredContent.value = {
|
||||
...godBlessBitcoin,
|
||||
backdrop: '/images/god-bless-bitcoin-backdrop.jpg'
|
||||
}
|
||||
if (USE_MOCK_DATA) {
|
||||
// Use mock data in development or when flag is set
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
fetchContentFromMock()
|
||||
} else {
|
||||
featuredContent.value = indeeHubFilms[0]
|
||||
// Fetch from API
|
||||
await fetchContentFromApi()
|
||||
}
|
||||
} catch (e: any) {
|
||||
error.value = e.message || 'Failed to load content'
|
||||
console.error('Content fetch error:', e)
|
||||
|
||||
// Small delay for content rows only
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
|
||||
// Organize content into rows
|
||||
contentRows.value = {
|
||||
featured: indeeHubFilms.slice(0, 10),
|
||||
newReleases: indeeHubFilms.slice(0, 8).reverse(),
|
||||
bitcoin: bitcoinFilms,
|
||||
documentaries: documentaries.slice(0, 10),
|
||||
dramas: dramas.slice(0, 10),
|
||||
independent: indeeHubFilms.filter(f =>
|
||||
!f.categories.includes('Bitcoin') && !f.categories.includes('Documentary')
|
||||
).slice(0, 10)
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = 'Failed to load content'
|
||||
console.error(e)
|
||||
// Fallback to mock data on error
|
||||
console.log('Falling back to mock data...')
|
||||
fetchContentFromMock()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user