mirror of
https://github.com/mito-systems/ranger-app.git
synced 2026-03-26 06:24:08 +00:00
auth the whole app
This commit is contained in:
parent
e0e3f79f38
commit
bd44611448
@ -1,5 +1,6 @@
|
||||
// src/app/api/analyze/route.ts
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getSessionFromCookie } from '../../../lib/auth'
|
||||
import { analyzeImageWithVision } from '../../../services/googleVisionCore'
|
||||
import { processAnimalImage } from '../../../services/animalProcessingService'
|
||||
|
||||
@ -14,11 +15,23 @@ export const config = {
|
||||
|
||||
export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
try {
|
||||
// Check authentication
|
||||
const session = await getSessionFromCookie(req)
|
||||
|
||||
if (!session) {
|
||||
console.log('Unauthorized access attempt to analyze API')
|
||||
return NextResponse.json(
|
||||
{ error: 'Unauthorized. Please sign in to use this feature.' },
|
||||
{ status: 401 }
|
||||
)
|
||||
}
|
||||
|
||||
// Log incoming request details
|
||||
console.log('Incoming request:', {
|
||||
method: req.method,
|
||||
contentType: req.headers.get('content-type'),
|
||||
contentLength: req.headers.get('content-length')
|
||||
contentLength: req.headers.get('content-length'),
|
||||
authenticated: true
|
||||
})
|
||||
|
||||
// Parse form data
|
||||
@ -27,7 +40,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
|
||||
// For server-side, check if we have a valid Blob-like object
|
||||
// In Node.js environment, Formidable gives us a Blob-like interface
|
||||
if (!imageFile || !(typeof imageFile === 'object' && 'arrayBuffer' in imageFile)) {
|
||||
if (!imageFile || !(typeof imageFile === 'object' && 'arrayBuffer' in (imageFile as object))) {
|
||||
console.error('Invalid file upload:', {
|
||||
imageFile: imageFile ? 'Exists' : 'Not found',
|
||||
type: imageFile ? typeof imageFile : 'undefined'
|
||||
@ -41,13 +54,13 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
|
||||
// Log file details (some properties may be different from browser File objects)
|
||||
console.log('Uploaded file details:', {
|
||||
name: 'name' in imageFile ? imageFile.name : 'unknown',
|
||||
type: 'type' in imageFile ? imageFile.type : 'unknown',
|
||||
size: 'size' in imageFile ? imageFile.size : 'unknown'
|
||||
name: 'name' in (imageFile as any) ? (imageFile as any).name : 'unknown',
|
||||
type: 'type' in (imageFile as any) ? (imageFile as any).type : 'unknown',
|
||||
size: 'size' in (imageFile as any) ? (imageFile as any).size : 'unknown'
|
||||
})
|
||||
|
||||
// Convert image to buffer using arrayBuffer method
|
||||
const arrayBuffer = await imageFile.arrayBuffer()
|
||||
const arrayBuffer = await (imageFile as any).arrayBuffer()
|
||||
const buffer = Buffer.from(arrayBuffer)
|
||||
|
||||
// Log buffer details
|
||||
@ -87,7 +100,7 @@ export async function POST(req: NextRequest): Promise<NextResponse> {
|
||||
|
||||
// Background processing for animal images
|
||||
if (visionResult.isAnimal) {
|
||||
const fileName = 'name' in imageFile ? imageFile.name : 'unknown-image.jpg'
|
||||
const fileName = 'name' in (imageFile as any) ? (imageFile as any).name : 'unknown-image.jpg'
|
||||
processAnimalImage(
|
||||
buffer,
|
||||
visionResult.description,
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import NextAuth from "next-auth";
|
||||
import GoogleProvider from "next-auth/providers/google";
|
||||
|
||||
export const GET = NextAuth({
|
||||
// Define auth options
|
||||
const authOptions = {
|
||||
providers: [
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID || '',
|
||||
@ -22,27 +23,11 @@ export const GET = NextAuth({
|
||||
return session;
|
||||
},
|
||||
},
|
||||
}).handlers.GET;
|
||||
};
|
||||
|
||||
export const POST = NextAuth({
|
||||
providers: [
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID || '',
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
|
||||
}),
|
||||
],
|
||||
pages: {
|
||||
signIn: '/auth/signin',
|
||||
signOut: '/auth/signout',
|
||||
error: '/auth/error',
|
||||
},
|
||||
callbacks: {
|
||||
async session({ session, token }) {
|
||||
// Add user info to the session
|
||||
if (session.user && token.sub) {
|
||||
session.user.id = token.sub;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
}).handlers.POST;
|
||||
// Create a handler with the auth options
|
||||
const handler = NextAuth(authOptions);
|
||||
|
||||
// Export the handler functions
|
||||
export const GET = handler.handlers.GET;
|
||||
export const POST = handler.handlers.POST;
|
||||
@ -1,18 +1,22 @@
|
||||
// src/app/page.tsx
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
import React from 'react'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import ImageAnalysisCard from '../components/ImageAnalysisCard'
|
||||
import Navigation from '../components/Navigation'
|
||||
import { analyzeImage, VisionAnalysisResult, VISION_CONFIG } from '../services/googleVisionService'
|
||||
import { APP_CONFIG, getThemeColors } from '../config/appConfig'
|
||||
import { normalizeImageUpload } from '../services/imageService'
|
||||
import { normalizeImageUpload } from '../services/imageService'
|
||||
|
||||
const Page: React.FC = (): React.ReactElement => {
|
||||
// Get auth session
|
||||
const { data: session, status } = useSession()
|
||||
const isAuthenticated = status === 'authenticated' && !!session
|
||||
|
||||
const theme = getThemeColors(APP_CONFIG.theme)
|
||||
|
||||
const handleImageAnalysis = () => {
|
||||
const handleImageAnalysis = () => {
|
||||
return async (imageFile: File): Promise<VisionAnalysisResult> => {
|
||||
try {
|
||||
// Normalize the image first
|
||||
@ -32,6 +36,41 @@ const Page: React.FC = (): React.ReactElement => {
|
||||
}
|
||||
}
|
||||
|
||||
// Auth-locked content
|
||||
const renderAuthContent = () => {
|
||||
if (isAuthenticated) {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto relative">
|
||||
{/* Decorative elements */}
|
||||
<div className="absolute -top-4 -left-4 w-8 h-8 bg-emerald-500/10 rounded-full blur-lg" />
|
||||
<div className="absolute -bottom-4 -right-4 w-8 h-8 bg-teal-500/10 rounded-full blur-lg" />
|
||||
|
||||
<ImageAnalysisCard
|
||||
title={VISION_CONFIG.name}
|
||||
description={VISION_CONFIG.description}
|
||||
onAnalyze={handleImageAnalysis()}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// For non-authenticated users, show sign-in prompt
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto bg-emerald-900/20 backdrop-blur-lg rounded-2xl shadow-xl border border-emerald-800/50 p-8 text-center">
|
||||
<h2 className="text-2xl font-bold text-emerald-300 mb-4">Sign in to Document Wildlife</h2>
|
||||
<p className="text-emerald-200 mb-6">
|
||||
You need to sign in with your account to access wildlife documentation features.
|
||||
</p>
|
||||
<a
|
||||
href="/auth/signin"
|
||||
className="inline-block text-white bg-emerald-600 hover:bg-emerald-700 font-medium rounded-lg px-6 py-3 text-center transition-colors"
|
||||
>
|
||||
Sign in with Google
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`min-h-screen w-full flex flex-col items-center bg-gradient-to-b ${theme.gradient}`}>
|
||||
<div className="container max-w-7xl mx-auto px-4 py-8">
|
||||
@ -45,31 +84,22 @@ const Page: React.FC = (): React.ReactElement => {
|
||||
Document sightings of {APP_CONFIG.description} in {APP_CONFIG.location}
|
||||
</p>
|
||||
|
||||
<div className="mb-8 p-4 bg-emerald-900/30 rounded-lg inline-block">
|
||||
<a
|
||||
href="/auth/signin"
|
||||
className="text-white bg-emerald-600 hover:bg-emerald-700 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||
>
|
||||
Sign in to get started
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{!isAuthenticated && status !== 'loading' && (
|
||||
<div className="mb-8 p-4 bg-emerald-900/30 rounded-lg inline-block">
|
||||
<a
|
||||
href="/auth/signin"
|
||||
className="text-white bg-emerald-600 hover:bg-emerald-700 font-medium rounded-lg text-sm px-5 py-2.5 text-center"
|
||||
>
|
||||
Sign in to get started
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Single Analysis Card */}
|
||||
<div className="max-w-2xl mx-auto relative">
|
||||
{/* Decorative elements */}
|
||||
<div className="absolute -top-4 -left-4 w-8 h-8 bg-emerald-500/10 rounded-full blur-lg" />
|
||||
<div className="absolute -bottom-4 -right-4 w-8 h-8 bg-teal-500/10 rounded-full blur-lg" />
|
||||
|
||||
<ImageAnalysisCard
|
||||
title={VISION_CONFIG.name}
|
||||
description={VISION_CONFIG.description}
|
||||
onAnalyze={handleImageAnalysis()} // ?
|
||||
/>
|
||||
</div>
|
||||
{/* Render content based on authentication status */}
|
||||
{renderAuthContent()}
|
||||
|
||||
{/* Info Section (not needed right now) */}
|
||||
{/* Info Section */}
|
||||
<div className="mt-12 text-center text-emerald-300/60">
|
||||
<p className="text-sm">
|
||||
Built by <a href="https://mito.systems">Mito Systems</a> - Powered by <a href="https://laconic.com">Laconic</a>
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { MapPin } from 'lucide-react'
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { fetchAnimalRecords } from '../../services/laconicQueryService'
|
||||
import Navigation from '../../components/Navigation'
|
||||
import { AnimalRecord } from '../../types/records'
|
||||
@ -15,13 +16,22 @@ const hiddenIndices = (process.env.NEXT_PUBLIC_HIDDEN_INDICES?.split(',') || [])
|
||||
.filter(num => !isNaN(num))
|
||||
|
||||
export default function AnimalsPage() {
|
||||
const { data: session, status } = useSession()
|
||||
const isAuthenticated = status === 'authenticated' && !!session
|
||||
|
||||
const [records, setRecords] = useState<AnimalRecord[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
loadRecords()
|
||||
}, [])
|
||||
// Only load records if user is authenticated
|
||||
if (isAuthenticated) {
|
||||
loadRecords()
|
||||
} else if (status !== 'loading') {
|
||||
// If user is not authenticated and we've finished loading auth status
|
||||
setLoading(false)
|
||||
}
|
||||
}, [isAuthenticated, status])
|
||||
|
||||
const loadRecords = async () => {
|
||||
try {
|
||||
@ -49,6 +59,34 @@ export default function AnimalsPage() {
|
||||
}
|
||||
|
||||
const renderContent = () => {
|
||||
// Show loading indicator while authenticating
|
||||
if (status === 'loading') {
|
||||
return (
|
||||
<div className="text-emerald-200 text-center p-8">
|
||||
Checking authentication...
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Show sign-in prompt if not authenticated
|
||||
if (!isAuthenticated) {
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto bg-emerald-900/20 backdrop-blur-lg rounded-2xl shadow-xl border border-emerald-800/50 p-8 text-center">
|
||||
<h2 className="text-2xl font-bold text-emerald-300 mb-4">Authentication Required</h2>
|
||||
<p className="text-emerald-200 mb-6">
|
||||
You need to sign in to view wildlife sightings from our community.
|
||||
</p>
|
||||
<a
|
||||
href="/auth/signin"
|
||||
className="inline-block text-white bg-emerald-600 hover:bg-emerald-700 font-medium rounded-lg px-6 py-3 text-center transition-colors"
|
||||
>
|
||||
Sign in with Google
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Show loading state for records
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="text-emerald-200 text-center">
|
||||
|
||||
7
src/lib/auth.ts
Normal file
7
src/lib/auth.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { NextRequest } from "next/server";
|
||||
|
||||
// Simple helper to check for the auth cookie
|
||||
export async function getSessionFromCookie(req: NextRequest) {
|
||||
const sessionCookie = req.cookies.get('next-auth.session-token');
|
||||
return sessionCookie ? { isAuthenticated: true } : null;
|
||||
}
|
||||
@ -25,7 +25,8 @@
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"strictNullChecks": false
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user