60 | );
61 | };
62 |
63 | export default FeedList;
64 |
--------------------------------------------------------------------------------
/backend/middleware/auth.ts:
--------------------------------------------------------------------------------
1 | import { createClient, SupabaseClient } from '@supabase/supabase-js';
2 | import type { User } from '@supabase/supabase-js'; // Use type-only import for User
3 |
4 | let supabase: SupabaseClient | null = null;
5 |
6 | // Initialize Supabase client for backend usage
7 | const getSupabaseBackendClient = (): SupabaseClient => {
8 | if (supabase) {
9 | return supabase;
10 | }
11 | const supabaseUrl = process.env.SUPABASE_URL;
12 | const supabaseAnonKey = process.env.SUPABASE_ANON_KEY;
13 |
14 | if (!supabaseUrl || !supabaseAnonKey) {
15 | console.error('[AuthMiddleware] Missing Supabase ENV VARS for backend client!');
16 | // In a real scenario, this might be a fatal error preventing startup
17 | // but for now, we rely on runtime checks.
18 | throw new Error('Supabase backend client configuration missing.');
19 | }
20 |
21 | console.log('[AuthMiddleware] Initializing Supabase backend client.');
22 | supabase = createClient(supabaseUrl, supabaseAnonKey, {
23 | // It's crucial to prevent the backend client from storing sessions
24 | auth: { persistSession: false }
25 | });
26 | return supabase;
27 | };
28 |
29 | /**
30 | * Verifies the JWT from the Authorization header.
31 | * Returns the authenticated User object or null if verification fails.
32 | */
33 | export const verifyAuth = async (req: Request): Promise => {
34 | const authHeader = req.headers.get('Authorization');
35 | if (!authHeader || !authHeader.startsWith('Bearer ')) {
36 | console.warn('[AuthMiddleware] Missing or invalid Authorization header.');
37 | return null;
38 | }
39 |
40 | const token = authHeader.split(' ')[1];
41 | if (!token) {
42 | console.warn('[AuthMiddleware] Token missing after Bearer.');
43 | return null;
44 | }
45 |
46 | try {
47 | const client = getSupabaseBackendClient();
48 | const { data: { user }, error } = await client.auth.getUser(token);
49 |
50 | if (error) {
51 | console.warn(`[AuthMiddleware] Token verification error: ${error.message}`);
52 | return null;
53 | }
54 | if (!user) {
55 | console.warn('[AuthMiddleware] Token valid but no user found.');
56 | return null;
57 | }
58 |
59 | console.log(`[AuthMiddleware] User verified: ${user.id} (${user.email})`);
60 | return user;
61 | } catch (error: any) {
62 | console.error('[AuthMiddleware] Exception during token verification:', error);
63 | return null;
64 | }
65 | };
--------------------------------------------------------------------------------
/media-player-plan.md:
--------------------------------------------------------------------------------
1 | Here’s a plain-English, step-by-step summary you can hand off to your colleague:
2 |
3 | ---
4 |
5 | ## 1. Analysis Worker (`worker-analyze.ts`)
6 |
7 | 1. **Build a “schedule”**
8 | - For each transcript utterance, capture its start time and compute a playback rate by dividing that utterance’s measured WPM by the speaker’s average WPM.
9 |
10 | 2. **Determine the “maxRate”**
11 | - Find the highest rate in your schedule array.
12 |
13 | 3. **Persist for later**
14 | - Store both the full schedule and the maxRate in Redis under the job’s data key.
15 |
16 | 4. **Gate client vs. server work**
17 | - If maxRate is ≤ 2.5, mark the job as ready for client-side playback and return immediately.
18 | - Otherwise, mark it as queued for RubberBand processing, enqueue the adjustment job on your audio-adjust queue, and return.
19 |
20 | ---
21 |
22 | ## 2. Status Endpoint (`/api/status/:id`)
23 |
24 | 1. **Read back schedule & maxRate**
25 | - When someone calls your status API, pull those two values out of Redis.
26 |
27 | 2. **Include them in the JSON**
28 | - If they exist, add `schedule` (an array of `{ time, rate }` entries) and `maxRate` to the response alongside your usual `status` field.
29 |
30 | ---
31 |
32 | ## 3. Frontend Changes (`App.tsx`)
33 |
34 | 1. **New state**
35 | - Add a place to hold the schedule array and a ref for your audio player element.
36 | - Also track a “download URL” for the high-quality path.
37 |
38 | 2. **Handle new statuses**
39 | - When status = `READY_FOR_CLIENT_PLAYBACK`, save the returned schedule and switch the UI to your “PLAYER” view.
40 | - When status = `COMPLETE`, save the output URL and switch to a “PLAYER_HQ” view.
41 |
42 | 3. **Adaptive playback logic**
43 | - After you load the schedule, attach a single `timeupdate` listener to your audio element.
44 | - On every tick, check whether you’ve reached the next scheduled time, and if so, update `audio.playbackRate` to that entry’s rate.
45 |
46 | 4. **UI swap-out**
47 | - Replace your old progress/download components with a plain `