feat: initialize Next.js project with database schema

- Set up Next.js with App Router, TypeScript, Tailwind CSS
- Configure shadcn/ui with dark theme
- Install dependencies: lightweight-charts, papaparse, lucide-react
- Set up Drizzle ORM with better-sqlite3
- Create database schema for candles and annotations tables
- Generate migration SQL
This commit is contained in:
Marko Djordjevic 2026-02-12 10:23:02 +01:00
parent 7d2fc42b73
commit d04b673cfa
25 changed files with 903 additions and 0 deletions

38
src/app/globals.css Normal file
View file

@ -0,0 +1,38 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
--radius: 0.5rem;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
font-family: Arial, Helvetica, sans-serif;
}
}

19
src/app/layout.tsx Normal file
View file

@ -0,0 +1,19 @@
import type { Metadata } from "next";
import "./globals.css";
export const metadata: Metadata = {
title: "Candle Annotator",
description: "Annotate candlestick charts for ML training",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className="antialiased">{children}</body>
</html>
);
}

10
src/app/page.tsx Normal file
View file

@ -0,0 +1,10 @@
export default function Home() {
return (
<main className="min-h-screen p-8">
<h1 className="text-2xl font-bold">Candle Annotator</h1>
<p className="mt-4 text-slate-400">
Upload CSV data and annotate candlestick charts
</p>
</main>
);
}

View file

@ -0,0 +1,51 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View file

@ -0,0 +1,53 @@
"use client"
import * as React from "react"
import { cn } from "@/lib/utils"
const TooltipProvider = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ children, ...props }, ref) => (
<div ref={ref} {...props}>
{children}
</div>
))
TooltipProvider.displayName = "TooltipProvider"
const Tooltip = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ children, ...props }, ref) => (
<div ref={ref} {...props}>
{children}
</div>
))
Tooltip.displayName = "Tooltip"
const TooltipTrigger = React.forwardRef<
HTMLButtonElement,
React.ButtonHTMLAttributes<HTMLButtonElement>
>(({ children, ...props }, ref) => (
<button ref={ref} {...props}>
{children}
</button>
))
TooltipTrigger.displayName = "TooltipTrigger"
const TooltipContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, children, ...props }, ref) => (
<div
ref={ref}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95",
className
)}
{...props}
>
{children}
</div>
))
TooltipContent.displayName = "TooltipContent"
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

15
src/lib/db/index.ts Normal file
View file

@ -0,0 +1,15 @@
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import * as schema from './schema';
import path from 'path';
import fs from 'fs';
// Ensure data directory exists
const dataDir = path.join(process.cwd(), 'data');
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
const dbPath = path.join(dataDir, 'candles.db');
const sqlite = new Database(dbPath);
export const db = drizzle(sqlite, { schema });

23
src/lib/db/migrate.ts Normal file
View file

@ -0,0 +1,23 @@
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
import path from 'path';
import fs from 'fs';
export function runMigrations() {
// Ensure data directory exists
const dataDir = path.join(process.cwd(), 'data');
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
const dbPath = path.join(dataDir, 'candles.db');
const sqlite = new Database(dbPath);
const db = drizzle(sqlite);
// Run migrations
migrate(db, { migrationsFolder: './drizzle' });
sqlite.close();
console.log('✅ Migrations complete');
}

18
src/lib/db/schema.ts Normal file
View file

@ -0,0 +1,18 @@
import { sqliteTable, integer, real, text } from 'drizzle-orm/sqlite-core';
export const candles = sqliteTable('candles', {
id: integer('id').primaryKey({ autoIncrement: true }),
time: integer('time').notNull().unique(),
open: real('open').notNull(),
high: real('high').notNull(),
low: real('low').notNull(),
close: real('close').notNull(),
});
export const annotations = sqliteTable('annotations', {
id: integer('id').primaryKey({ autoIncrement: true }),
timestamp: integer('timestamp').notNull(),
label_type: text('label_type').notNull(),
geometry: text('geometry'), // JSON string for line coordinates
created_at: integer('created_at').notNull(),
});

6
src/lib/utils.ts Normal file
View file

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}