diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..b1ba9ff
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,32 @@
+name: CI
+
+on:
+ push:
+ branches: [ main, master ]
+ pull_request:
+
+jobs:
+ frontend-build:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: async-code-web
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'yarn'
+ cache-dependency-path: async-code-web/yarn.lock
+ - run: yarn install
+ - run: yarn build
+
+ backend-static-check:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.11'
+ - name: Static analysis
+ run: python -m compileall -q server
diff --git a/async-code-web/app/projects/[id]/tasks/page.tsx b/async-code-web/app/projects/[id]/tasks/page.tsx
index ca5b6a2..9075a9d 100644
--- a/async-code-web/app/projects/[id]/tasks/page.tsx
+++ b/async-code-web/app/projects/[id]/tasks/page.tsx
@@ -205,7 +205,7 @@ export default function ProjectTasksPage() {
- {task.chat_messages?.[0]?.content || 'No prompt available'}
+ {(task.chat_messages as any[])?.[0]?.content || 'No prompt available'}
Created: {new Date(task.created_at || '').toLocaleString()}
diff --git a/async-code-web/app/signin/page.tsx b/async-code-web/app/signin/page.tsx
index e2270a1..c318f93 100644
--- a/async-code-web/app/signin/page.tsx
+++ b/async-code-web/app/signin/page.tsx
@@ -2,7 +2,7 @@
import { Auth } from '@supabase/auth-ui-react'
import { ThemeSupa } from '@supabase/auth-ui-shared'
-import { supabase } from '@/lib/supabase'
+import { getSupabase } from '@/lib/supabase'
import { useAuth } from '@/contexts/auth-context'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'
@@ -57,7 +57,7 @@ export default function SignIn() {
{
+ const supabase = getSupabase()
+
// Get initial session
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session)
@@ -51,6 +53,7 @@ export function AuthProvider({ children }: AuthProviderProps) {
}, [])
const signOut = async () => {
+ const supabase = getSupabase()
await supabase.auth.signOut()
}
diff --git a/async-code-web/eslint.config.mjs b/async-code-web/eslint.config.mjs
index c85fb67..a838e41 100644
--- a/async-code-web/eslint.config.mjs
+++ b/async-code-web/eslint.config.mjs
@@ -11,6 +11,14 @@ const compat = new FlatCompat({
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
+ {
+ rules: {
+ "react/no-unescaped-entities": "off",
+ "@typescript-eslint/no-unused-vars": "off",
+ "react-hooks/exhaustive-deps": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ },
+ },
];
export default eslintConfig;
diff --git a/async-code-web/lib/supabase-service.ts b/async-code-web/lib/supabase-service.ts
index adebcb9..0d985d2 100644
--- a/async-code-web/lib/supabase-service.ts
+++ b/async-code-web/lib/supabase-service.ts
@@ -1,10 +1,13 @@
-import { supabase } from './supabase'
+import { getSupabase } from './supabase'
import { Project, Task, ProjectWithStats, ChatMessage } from '@/types'
export class SupabaseService {
+ private static get supabase() {
+ return getSupabase()
+ }
// Project operations
static async getProjects(): Promise {
- const { data, error } = await supabase
+ const { data, error } = await this.supabase
.from('projects')
.select(`
*,
@@ -35,10 +38,10 @@ export class SupabaseService {
settings?: any
}): Promise {
// Get current authenticated user
- const { data: { user } } = await supabase.auth.getUser()
+ const { data: { user } } = await this.supabase.auth.getUser()
if (!user) throw new Error('No authenticated user')
- const { data, error } = await supabase
+ const { data, error } = await this.supabase
.from('projects')
.insert([{ ...projectData, user_id: user.id }])
.select()
@@ -49,7 +52,7 @@ export class SupabaseService {
}
static async updateProject(id: number, updates: Partial): Promise {
- const { data, error } = await supabase
+ const { data, error } = await this.supabase
.from('projects')
.update(updates)
.eq('id', id)
@@ -61,7 +64,7 @@ export class SupabaseService {
}
static async deleteProject(id: number): Promise {
- const { error } = await supabase
+ const { error } = await this.supabase
.from('projects')
.delete()
.eq('id', id)
@@ -70,7 +73,7 @@ export class SupabaseService {
}
static async getProject(id: number): Promise {
- const { data, error } = await supabase
+ const { data, error } = await this.supabase
.from('projects')
.select('*')
.eq('id', id)
@@ -86,10 +89,10 @@ export class SupabaseService {
// Task operations
static async getTasks(projectId?: number): Promise {
// Get current authenticated user
- const { data: { user } } = await supabase.auth.getUser()
+ const { data: { user } } = await this.supabase.auth.getUser()
if (!user) throw new Error('No authenticated user')
- let query = supabase
+ let query = this.supabase
.from('tasks')
.select(`
*,
@@ -113,7 +116,7 @@ export class SupabaseService {
}
static async getTask(id: number): Promise {
- const { data, error } = await supabase
+ const { data, error } = await this.supabase
.from('tasks')
.select(`
*,
@@ -142,10 +145,10 @@ export class SupabaseService {
chat_messages?: ChatMessage[]
}): Promise {
// Get current authenticated user
- const { data: { user } } = await supabase.auth.getUser()
+ const { data: { user } } = await this.supabase.auth.getUser()
if (!user) throw new Error('No authenticated user')
- const { data, error } = await supabase
+ const { data, error } = await this.supabase
.from('tasks')
.insert([{
...taskData,
@@ -161,7 +164,7 @@ export class SupabaseService {
}
static async updateTask(id: number, updates: Partial): Promise {
- const { data, error } = await supabase
+ const { data, error } = await this.supabase
.from('tasks')
.update(updates)
.eq('id', id)
@@ -174,7 +177,7 @@ export class SupabaseService {
static async addChatMessage(taskId: number, message: ChatMessage): Promise {
// First get the current task to get existing messages
- const { data: task, error: fetchError } = await supabase
+ const { data: task, error: fetchError } = await this.supabase
.from('tasks')
.select('chat_messages')
.eq('id', taskId)
@@ -185,7 +188,7 @@ export class SupabaseService {
const existingMessages = (task.chat_messages as unknown as ChatMessage[]) || []
const updatedMessages = [...existingMessages, message]
- const { data, error } = await supabase
+ const { data, error } = await this.supabase
.from('tasks')
.update({
chat_messages: updatedMessages as any,
@@ -201,15 +204,15 @@ export class SupabaseService {
// User operations
static async getCurrentUser() {
- const { data: { user } } = await supabase.auth.getUser()
+ const { data: { user } } = await this.supabase.auth.getUser()
return user
}
static async getUserProfile() {
- const { data: { user } } = await supabase.auth.getUser()
+ const { data: { user } } = await this.supabase.auth.getUser()
if (!user) return null
- const { data, error } = await supabase
+ const { data, error } = await this.supabase
.from('users')
.select('*')
.eq('id', user.id)
@@ -228,10 +231,10 @@ export class SupabaseService {
github_token?: string
preferences?: any
}) {
- const { data: { user } } = await supabase.auth.getUser()
+ const { data: { user } } = await this.supabase.auth.getUser()
if (!user) throw new Error('No authenticated user')
- const { data, error } = await supabase
+ const { data, error } = await this.supabase
.from('users')
.update(updates)
.eq('id', user.id)
diff --git a/async-code-web/lib/supabase.ts b/async-code-web/lib/supabase.ts
index f5e286f..cb47e63 100644
--- a/async-code-web/lib/supabase.ts
+++ b/async-code-web/lib/supabase.ts
@@ -1,13 +1,21 @@
import { createClient } from '@supabase/supabase-js'
import { Database } from '@/types/supabase'
-const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
-const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+let supabaseInstance: ReturnType> | null = null
-export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
- auth: {
- autoRefreshToken: true,
- persistSession: true,
- detectSessionInUrl: true
+export const getSupabase = () => {
+ if (!supabaseInstance) {
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
+ const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
+
+ supabaseInstance = createClient(supabaseUrl, supabaseAnonKey, {
+ auth: {
+ autoRefreshToken: true,
+ persistSession: true,
+ detectSessionInUrl: true
+ }
+ })
}
-})
\ No newline at end of file
+
+ return supabaseInstance
+}