Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
65c382f
Initial plan
Copilot Dec 18, 2025
373a8cd
Initial plan for streak tracking and user login features
Copilot Dec 18, 2025
5636d56
Add streak tracking, user menu, and login prompts
Copilot Dec 18, 2025
b35dfed
Fix date comparison in streak tracking and add migration doc
Copilot Dec 18, 2025
7d306a5
Fix date calculation to avoid month boundary issues
Copilot Dec 18, 2025
1c3b3dc
Add implementation summary documentation
Copilot Dec 18, 2025
31b2f72
Add user experience documentation
Copilot Dec 18, 2025
d063750
Add comprehensive quick reference guide
Copilot Dec 18, 2025
8256774
add debug search params for preventing localStorage interaction
NicolasWinsten Dec 18, 2025
def408a
Submit score after unauthenticated user logs in
Copilot Dec 18, 2025
43507c6
Improve error handling for pending score submission
Copilot Dec 18, 2025
c0ffe2e
lift out getMilliseconds
NicolasWinsten Dec 18, 2025
16b0fcc
Use localStorage instead of React state for pending score submission
Copilot Dec 18, 2025
6696cec
Add explicit scoreSubmitted parameter to all saveLocalState calls
Copilot Dec 18, 2025
119089f
Merge branch 'copilot/add-user-daily-scores-tracking' of https://gith…
NicolasWinsten Dec 18, 2025
80a999a
simplified submit scores and update streak logic. smh copilot
NicolasWinsten Dec 18, 2025
8fbb8e0
Add comprehensive e2e tests with database integration for streak trac…
Copilot Dec 18, 2025
cdbd1fa
Address code review feedback: improve error handling and test reliabi…
Copilot Dec 18, 2025
233fe6c
neon db branch workflow yaml
NicolasWinsten Dec 19, 2025
f2b4311
Integrate Neon database branching with Playwright e2e tests
Copilot Dec 19, 2025
fa7d6a5
add jest tests to neon workflow
NicolasWinsten Dec 19, 2025
f41aed8
streak tracking
NicolasWinsten Jan 19, 2026
12c014c
add dotenv
NicolasWinsten Jan 19, 2026
f7ce998
debug workflow statement
NicolasWinsten Jan 19, 2026
2c546bd
workflow debugging
NicolasWinsten Jan 19, 2026
268b73b
pooled url is not returned by neondb action, just use unpooled url
NicolasWinsten Jan 19, 2026
31258a2
put e2e tests in branch job
NicolasWinsten Jan 19, 2026
11ed8fe
.
NicolasWinsten Jan 19, 2026
b60df48
set NEXTAUTH_URL in workflow
NicolasWinsten Jan 20, 2026
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
141 changes: 141 additions & 0 deletions .github/workflows/neon_workflow.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
name: Create/Delete Branch for Pull Request

on:
pull_request:
types:
- opened
- reopened
- synchronize
- closed

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}

jobs:
setup:
name: Setup
outputs:
branch: ${{ steps.branch_name.outputs.current_branch }}
runs-on: ubuntu-latest
steps:
- name: Get branch name
id: branch_name
uses: tj-actions/branch-names@v8

jest:
name: Jest Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Run Jest tests
run: npm test

create_and_test_neon_branch:
name: Create Neon Branch and Run E2E Tests
needs: setup
if: |
github.event_name == 'pull_request' && (
github.event.action == 'synchronize'
|| github.event.action == 'opened'
|| github.event.action == 'reopened')
runs-on: ubuntu-latest
env:
NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }}
NEXTAUTH_URL: http://localhost:3001 # URL of main page for testing after authoriztion redirect
steps:
- name: Get branch expiration date as an env variable (2 weeks from now)
id: get_expiration_date
run: echo "EXPIRES_AT=$(date -u --date '+14 days' +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_ENV"
- name: Create Neon Branch
id: create_neon_branch
uses: neondatabase/create-branch-action@v6
with:
project_id: ${{ vars.NEON_PROJECT_ID }}
branch_name: preview/pr-${{ github.event.number }}-${{ needs.setup.outputs.branch }}
api_key: ${{ secrets.NEON_API_KEY }}
expires_at: ${{ env.EXPIRES_AT }}
- name: Set DATABASE_URL from Neon branch
run: echo "DATABASE_URL=${{ steps.create_neon_branch.outputs.db_url }}" >> $GITHUB_ENV
- name: Debug - Print Neon outputs
run: |
echo "db_url: ${{ steps.create_neon_branch.outputs.db_url }}"
echo "db_url_with_pooler: ${{ steps.create_neon_branch.outputs.db_url_with_pooler }}"
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Debug - Check DATABASE_URL
env:
DATABASE_URL: ${{ steps.create_neon_branch.outputs.db_url }}
run: |
echo "DATABASE_URL is set: ${DATABASE_URL:+yes}"
echo "DATABASE_URL length: ${#DATABASE_URL}"
- name: Run Playwright E2E tests
env:
POSTGRES_URL: ${{ steps.create_neon_branch.outputs.db_url }}
run: npm run test:e2e
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report-neon-branch
path: playwright-report/
retention-days: 30

# The step above creates a new Neon branch.
# You may want to do something with the new branch, such as run migrations, run tests
# on it, or send the connection details to a hosting platform environment.
# The branch DATABASE_URL is available to you via:
# "${{ steps.create_neon_branch.outputs.db_url_with_pooler }}".
# It's important you don't log the DATABASE_URL as output as it contains a username and
# password for your database.
# For example, you can uncomment the lines below to run a database migration command:
# - name: Run Migrations
# run: npm run db:migrate
# env:
# # to use pooled connection
# DATABASE_URL: "${{ steps.create_neon_branch.outputs.db_url_with_pooler }}"
# # OR to use unpooled connection
# # DATABASE_URL: "${{ steps.create_neon_branch.outputs.db_url }}"

# Following the step above, which runs database migrations, you may want to check
# for schema changes in your database. We recommend using the following action to
# post a comment to your pull request with the schema diff. For this action to work,
# you also need to give permissions to the workflow job to be able to post comments
# and read your repository contents. Add the following permissions to the workflow job:
#
# permissions:
# contents: read
# pull-requests: write
#
# You can also check out https://github.com/neondatabase/schema-diff-action for more
# information on how to use the schema diff action.
# You can uncomment the lines below to enable the schema diff action.
# - name: Post Schema Diff Comment to PR
# uses: neondatabase/schema-diff-action@v1
# with:
# project_id: ${{ vars.NEON_PROJECT_ID }}
# compare_branch: preview/pr-${{ github.event.number }}-${{ needs.setup.outputs.branch }}
# api_key: ${{ secrets.NEON_API_KEY }}

delete_neon_branch:
name: Delete Neon Branch
needs: setup
if: github.event_name == 'pull_request' && github.event.action == 'closed'
runs-on: ubuntu-latest
steps:
- name: Delete Neon Branch
uses: neondatabase/delete-branch-action@v3
with:
project_id: ${{ vars.NEON_PROJECT_ID }}
branch: preview/pr-${{ github.event.number }}-${{ needs.setup.outputs.branch }}
api_key: ${{ secrets.NEON_API_KEY }}

43 changes: 0 additions & 43 deletions .github/workflows/test.yml

This file was deleted.

75 changes: 67 additions & 8 deletions app/api/auth/[...nextauth]/route.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,79 @@
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import CredentialsProvider from "next-auth/providers/credentials"

import NeonAdapter from "@auth/neon-adapter"
import { Pool } from "@neondatabase/serverless"

const pool = new Pool({ connectionString: process.env.DATABASE_URL })
const adapter = NeonAdapter(pool)

// Build providers list
const providers = [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
]

// Add test credentials provider only in non-production environments
if (process.env.NODE_ENV !== 'production') {
providers.push(
CredentialsProvider({
id: 'test-credentials',
name: 'Test Login',
credentials: {
email: { label: 'Email', type: 'email' },
name: { label: 'Name', type: 'text' },
},
async authorize(credentials) {
if (!credentials?.email) {
return null
}

const email = credentials.email
const name = credentials.name || 'Test User'

// Check if user already exists
let user = await adapter.getUserByEmail(email)

if (!user) {
// Create user in database (like OAuth would do)
user = await adapter.createUser({
email,
name,
emailVerified: new Date(),
image: null,
})
}

return user
},
})
)
}

export const authOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
adapter: NeonAdapter(pool),

providers,
adapter,
// Use JWT for credentials provider (development), database for production OAuth
session: {
strategy: process.env.NODE_ENV !== 'production' ? 'jwt' : 'database',
},
callbacks: {
// Include user id in the session (for both JWT and database sessions)
async session({ session, token, user }) {
// JWT strategy (credentials provider in development)
if (token?.sub) {
session.user.id = token.sub
}
// Database strategy (OAuth in production)
else if (user?.id) {
session.user.id = user.id
}
return session
},
},
}

const handler = NextAuth(authOptions)
Expand Down
40 changes: 40 additions & 0 deletions app/api/submit-score/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { submitDailyScore, updateStreak } from 'app/lib/db/db';
import { NextResponse } from 'next/server';
import { revalidatePath } from 'next/cache';

export async function POST(request) {
try {
console.log(request);
const { milliseconds, date } = await request.json();
console.log('Received score submission in POST:', milliseconds);
const submissionResult = await submitDailyScore(milliseconds, date);
console.log('Submission result:', submissionResult);
// if no new row is returned then a conflict occurred
const dailyAlreadyHasSubmission = submissionResult === null;
// submission is made only if one new row is inserted
const submissionSuccess = submissionResult !== null;
// Update the streak (completed if milliseconds is not null)
const completed = milliseconds !== null;
console.log('Submission success:', submissionSuccess);
const newStreak = submissionSuccess ? await updateStreak(completed, date) : null;

const streakUpdateSuccess = newStreak !== null;

if (streakUpdateSuccess) {
revalidatePath('/', 'layout');
}

return NextResponse.json({
success: submissionSuccess && streakUpdateSuccess,
submission: submissionResult,
newStreak: streakUpdateSuccess ? newStreak : null,
dailyAlreadyHasSubmission: dailyAlreadyHasSubmission
});
} catch (error) {
console.error('Error submitting score:', error);
return NextResponse.json(
{ success: false, error: error.message },
{ status: error.message === 'User not authenticated' ? 401 : 500 }
);
}
}
Loading