Skip to content
Draft
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
42 changes: 34 additions & 8 deletions app/api/build-app/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ async function createGitHubRepo(
body: JSON.stringify({
name: repoName,
description,
private: false,
private: true,
auto_init: false,
}),
})
Expand Down Expand Up @@ -128,7 +128,7 @@ async function pushFileToGitHub(

if (!res.ok) {
const err = (await res.json()) as { message?: string }
console.warn(`[build-app] Failed to push ${path}: ${err.message}`)
throw new Error(err.message ?? `Failed to push ${path}`)
}
}

Expand Down Expand Up @@ -190,7 +190,7 @@ async function pushFileToGitLab(

if (!res.ok) {
const err = (await res.json()) as { message?: string }
console.warn(`[build-app] Failed to push ${path} to GitLab: ${err.message}`)
throw new Error(err.message ?? `Failed to push ${path} to GitLab`)
}
}

Expand Down Expand Up @@ -285,14 +285,40 @@ export async function POST(request: NextRequest) {
content = await generateSingleFile(blueprint, path, purpose, user.id)
} catch (e) {
console.warn(`[build-app] Failed to generate ${path}:`, e)
content = `# Error generating ${path}\n# ${e instanceof Error ? e.message : String(e)}\n`
send({
step: 'error',
message: `Failed to generate ${path}: ${e instanceof Error ? e.message : String(e)}`,
repoUrl,
})
return
}

if (!content.trim()) {
send({
step: 'error',
message: `Failed to generate ${path}: empty content returned.`,
repoUrl,
})
return
}

// Push to platform
if (platform === 'github') {
await pushFileToGitHub(accessToken, user.github_username, cleanRepoName, path, content)
} else if (gitlabProjectId !== null) {
await pushFileToGitLab(accessToken, gitlabProjectId, gitlabBranch, path, content)
try {
if (platform === 'github') {
await pushFileToGitHub(accessToken, user.github_username, cleanRepoName, path, content)
} else if (gitlabProjectId !== null) {
await pushFileToGitLab(accessToken, gitlabProjectId, gitlabBranch, path, content)
} else {
throw new Error('GitLab project was not initialized.')
}
} catch (e) {
console.warn(`[build-app] Failed to push ${path}:`, e)
send({
step: 'error',
message: `Failed to push ${path}: ${e instanceof Error ? e.message : String(e)}`,
repoUrl,
})
return
}

pushed++
Expand Down
29 changes: 21 additions & 8 deletions app/api/code-completion/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
CodeSnippet,
batchGenerateCompletions,
} from '@/lib/code-completion'
import { getCurrentUser } from '@/lib/auth'
import { getDb } from '@/lib/db'

const LANG_EXTENSIONS: Record<string, string[]> = {
Expand All @@ -24,18 +25,20 @@ const LANG_EXTENSIONS: Record<string, string[]> = {
shell: ['.sh', '.bash'],
}

async function fetchSnippetsFromDb(language: string): Promise<CodeSnippet[]> {
async function fetchSnippetsFromDb(language: string, userId: string): Promise<CodeSnippet[]> {
try {
const sql = getDb()
const extensions = LANG_EXTENSIONS[language.toLowerCase()] ?? []
if (extensions.length === 0) return []

const rows = await sql`
SELECT id, path, name, extension, ai_summary, reusability_score, exports, imports
FROM repo_files
WHERE extension = ANY(${extensions}::text[])
AND ai_summary IS NOT NULL
ORDER BY reusability_score DESC
SELECT rf.id, rf.path, rf.name, rf.extension, rf.ai_summary, rf.reusability_score, rf.exports, rf.imports
FROM repo_files rf
INNER JOIN repositories r ON r.id = rf.repository_id
WHERE r.user_id = ${userId}
AND rf.extension = ANY(${extensions}::text[])
AND rf.ai_summary IS NOT NULL
ORDER BY rf.reusability_score DESC
LIMIT 20
`

Expand Down Expand Up @@ -63,6 +66,11 @@ async function fetchSnippetsFromDb(language: string): Promise<CodeSnippet[]> {

export async function POST(request: NextRequest) {
try {
const user = await getCurrentUser()
if (!user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const body = await request.json()
const { incompleteCode, codebaseSnippets = [], language = 'python' } = body

Expand All @@ -75,7 +83,7 @@ export async function POST(request: NextRequest) {

const snippets: CodeSnippet[] = codebaseSnippets.length > 0
? codebaseSnippets
: await fetchSnippetsFromDb(language)
: await fetchSnippetsFromDb(language, user.id)

const result = await generateRelevanceGuidedCompletion(
incompleteCode,
Expand Down Expand Up @@ -120,6 +128,11 @@ export async function POST(request: NextRequest) {
*/
export async function PUT(request: NextRequest) {
try {
const user = await getCurrentUser()
if (!user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}

const body = await request.json()
const { codeSnippets = [], codebaseSnippets = [], language = 'python' } = body

Expand All @@ -132,7 +145,7 @@ export async function PUT(request: NextRequest) {

const snippets: CodeSnippet[] = codebaseSnippets.length > 0
? codebaseSnippets
: await fetchSnippetsFromDb(language)
: await fetchSnippetsFromDb(language, user.id)

const results = await batchGenerateCompletions(
codeSnippets,
Expand Down
9 changes: 5 additions & 4 deletions app/api/projects/[id]/milestones/[milestoneId]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ export async function PATCH(
const user = await getCurrentUser()
if (!user?.id) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })

const { milestoneId } = await params
try {
const { id, milestoneId } = await params
const { completed } = await request.json()
const milestone = await toggleMilestone(milestoneId, Boolean(completed))
const milestone = await toggleMilestone(milestoneId, id, user.id, Boolean(completed))
if (!milestone) return NextResponse.json({ error: 'Not found' }, { status: 404 })
return NextResponse.json({ milestone })
} catch (err) {
Expand All @@ -28,9 +28,10 @@ export async function DELETE(
const user = await getCurrentUser()
if (!user?.id) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })

const { milestoneId } = await params
try {
await deleteMilestone(milestoneId)
const { id, milestoneId } = await params
const deleted = await deleteMilestone(milestoneId, id, user.id)
if (!deleted) return NextResponse.json({ error: 'Not found' }, { status: 404 })
return NextResponse.json({ ok: true })
} catch (err) {
console.error('[milestones/id] DELETE error:', err)
Expand Down
44 changes: 35 additions & 9 deletions lib/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1044,27 +1044,53 @@ export async function createMilestone(data: {
return result[0] as ProjectMilestone
}

export async function toggleMilestone(id: string, completed: boolean): Promise<ProjectMilestone | null> {
export async function toggleMilestone(
id: string,
projectId: string,
userId: string,
completed: boolean,
): Promise<ProjectMilestone | null> {
const sql = getDb()
const result = completed
? await sql`
UPDATE project_milestones
UPDATE project_milestones pm
SET completed = true, completed_at = NOW()
WHERE id = ${id}
RETURNING *
FROM projects p
WHERE pm.id = ${id}
AND pm.project_id = ${projectId}
AND p.id = pm.project_id
AND p.user_id = ${userId}
RETURNING pm.*
`
: await sql`
UPDATE project_milestones
UPDATE project_milestones pm
SET completed = false, completed_at = NULL
WHERE id = ${id}
RETURNING *
FROM projects p
WHERE pm.id = ${id}
AND pm.project_id = ${projectId}
AND p.id = pm.project_id
AND p.user_id = ${userId}
RETURNING pm.*
`
return (result[0] as ProjectMilestone) || null
}

export async function deleteMilestone(id: string): Promise<void> {
export async function deleteMilestone(
id: string,
projectId: string,
userId: string,
): Promise<boolean> {
const sql = getDb()
await sql`DELETE FROM project_milestones WHERE id = ${id}`
const result = await sql`
DELETE FROM project_milestones pm
USING projects p
WHERE pm.id = ${id}
AND pm.project_id = ${projectId}
AND p.id = pm.project_id
AND p.user_id = ${userId}
RETURNING pm.id
`
return result.length > 0
}

export async function seedDefaultMilestones(projectId: string): Promise<void> {
Expand Down
Loading