Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
# misc
.DS_Store
*.pem
*.p8
# Apple private key for Sign-in with Apple (store outside git)

# debug
npm-debug.log*
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"input-otp": "^1.4.2",
"jsonwebtoken": "^9.0.3",
"lucide-react": "^0.544.0",
"motion": "^12.23.22",
"next": "16.1.6",
Expand Down
89 changes: 89 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions prisma/migrations/20260302100000_add_course_creator/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- Add nullable creatorId column to Course and set up foreign key

-- Add column (nullable so existing rows aren’t broken)
ALTER TABLE "public"."Course" ADD COLUMN "creatorId" TEXT;
ALTER TABLE "public"."Course" ADD COLUMN "category" TEXT;

-- Create index to speed up lookups by creatorId
CREATE INDEX "Course_creatorId_idx" ON "public"."Course"("creatorId");

-- Add foreign key constraint to User table
ALTER TABLE "public"."Course" ADD CONSTRAINT "Course_creatorId_fkey"
FOREIGN KEY ("creatorId") REFERENCES "public"."User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
9 changes: 9 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ model User {
projectMembers ProjectMember[]
donations Donation[]
auditLogs AuditLog[]

// courses the user has created as an instructor
createdCourses Course[]
}

model Session {
Expand Down Expand Up @@ -108,6 +111,12 @@ model Course {
rejectionReason String?
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
// relation to the user who created the course
creatorId String?
creator User? @relation(fields: [creatorId], references: [id], onDelete: Cascade)

// optional category tag for a course
category String?

sections Section[]
lessons Lesson[]
Expand Down
67 changes: 67 additions & 0 deletions scripts/generate-apple-secret.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const jwt = require('jsonwebtoken');
const fs = require('fs');
const path = require('path');

// Configuration - Replace these with your actual values
const TEAM_ID = '2R2CQNX632'; // Found in Apple Developer account membership
const CLIENT_ID = 'com.ethed.webapp.signin';
const KEY_ID = 'M7N46GNRXN';
const PRIVATE_KEY_FILE = 'AuthKey_M7N46GNRXN.p8';

// Path to the private key file (should be in the same directory as this script)
const PRIVATE_KEY_PATH = path.join(__dirname, PRIVATE_KEY_FILE);

try {
// Check if the private key file exists
if (!fs.existsSync(PRIVATE_KEY_PATH)) {
console.error('❌ Error: Private key file not found!');
console.error(`Looking for: ${PRIVATE_KEY_PATH}`);
console.error('\nPlease:');
console.error('1. Download your .p8 file from Apple Developer console');
console.error('2. Place it in the scripts/ directory');
console.error('3. Update PRIVATE_KEY_FILE in this script with the correct filename');
process.exit(1);
}

// Validate configuration
if (TEAM_ID === 'YOUR_TEAM_ID_HERE' || KEY_ID === 'YOUR_KEY_ID_HERE') {
console.error('❌ Error: Please update the configuration in this script!');
console.error('\nYou need to replace:');
console.error('- TEAM_ID: Your Apple Team ID');
console.error('- CLIENT_ID: Your Services ID (e.g., com.ethed.webapp.signin)');
console.error('- KEY_ID: Your Sign In with Apple Key ID');
console.error('- PRIVATE_KEY_FILE: Name of your .p8 file');
process.exit(1);
}

// Read the private key
const privateKey = fs.readFileSync(PRIVATE_KEY_PATH, 'utf8');

// Generate the JWT token
const token = jwt.sign(
{},
privateKey,
{
algorithm: 'ES256',
expiresIn: '180d', // Apple allows max 6 months
audience: 'https://appleid.apple.com',
issuer: TEAM_ID,
subject: CLIENT_ID,
keyid: KEY_ID
}
);

console.log('✅ Apple Client Secret generated successfully!\n');
console.log('Copy this token to your .env file as APPLE_CLIENT_SECRET:\n');
console.log('─'.repeat(80));
console.log(token);
console.log('─'.repeat(80));
console.log('\n⚠️ Important: This token is valid for 180 days');
console.log('You will need to regenerate it after:', new Date(Date.now() + 180 * 24 * 60 * 60 * 1000).toLocaleDateString());
console.log('\nAdd to .env:');
console.log(`APPLE_CLIENT_SECRET=${token}`);

} catch (error) {
console.error('❌ Error generating Apple client secret:', error.message);
process.exit(1);
}
Loading
Loading