├── svelte ├── .npmrc ├── .prettierignore ├── data.sqlite ├── src │ ├── hooks.server.ts │ ├── app.d.ts │ ├── lib │ │ ├── db.ts │ │ ├── api.ts │ │ └── components │ │ │ ├── CsvUpload.svelte │ │ │ └── Table.svelte │ ├── app.html │ └── routes │ │ ├── api │ │ ├── data │ │ │ ├── +server.ts │ │ │ └── [id] │ │ │ │ └── +server.ts │ │ ├── download │ │ │ └── +server.ts │ │ └── upload │ │ │ └── +server.ts │ │ └── +page.svelte ├── vite.config.ts ├── .prettierrc ├── tsconfig.json ├── svelte.config.js ├── eslint.config.js ├── README.md └── package.json ├── fastapi+svelte ├── svelte-app │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── .gitignore │ ├── src │ │ ├── app.d.ts │ │ ├── app.html │ │ └── routes │ │ │ └── +page.svelte │ ├── vite.config.ts │ ├── tsconfig.json │ ├── svelte.config.js │ ├── eslint.config.js │ ├── package.json │ └── README.md ├── database.sqlite ├── logger.py └── main.py ├── .learning ├── fasthtml │ ├── .sesskey │ ├── todos.db │ ├── todos.db-shm │ ├── todos.db-wal │ ├── main.py │ ├── todo.py │ ├── logger.py │ └── imagegen.py ├── nextjs │ └── nextjs-dashboard │ │ ├── postcss.config.js │ │ ├── app │ │ ├── favicon.ico │ │ ├── opengraph-image.png │ │ ├── actions │ │ │ └── auth.ts │ │ ├── dashboard │ │ │ ├── (overview) │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ │ ├── customers │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ └── invoices │ │ │ │ ├── create │ │ │ │ └── page.tsx │ │ │ │ ├── [id] │ │ │ │ └── edit │ │ │ │ │ ├── not-found.tsx │ │ │ │ │ └── page.tsx │ │ │ │ ├── error.tsx │ │ │ │ └── page.tsx │ │ ├── ui │ │ │ ├── home.module.css │ │ │ ├── fonts.ts │ │ │ ├── global.css │ │ │ ├── acme-logo.tsx │ │ │ ├── button.tsx │ │ │ ├── invoices │ │ │ │ ├── status.tsx │ │ │ │ ├── breadcrumbs.tsx │ │ │ │ ├── buttons.tsx │ │ │ │ ├── pagination.tsx │ │ │ │ ├── edit-form.tsx │ │ │ │ ├── create-form.tsx │ │ │ │ └── table.tsx │ │ │ ├── dashboard │ │ │ │ ├── sidenav.tsx │ │ │ │ ├── nav-links.tsx │ │ │ │ ├── cards.tsx │ │ │ │ ├── revenue-chart.tsx │ │ │ │ └── latest-invoices.tsx │ │ │ ├── search.tsx │ │ │ ├── login-form.tsx │ │ │ ├── customers │ │ │ │ └── table.tsx │ │ │ └── skeletons.tsx │ │ ├── layout.tsx │ │ ├── login │ │ │ └── page.tsx │ │ ├── page.tsx │ │ └── seed │ │ │ └── route.ts │ │ ├── public │ │ ├── hero-mobile.png │ │ ├── hero-desktop.png │ │ └── customers │ │ │ ├── amy-burns.png │ │ │ ├── evil-rabbit.png │ │ │ ├── balazs-orban.png │ │ │ ├── lee-robinson.png │ │ │ ├── michael-novotny.png │ │ │ └── delba-de-oliveira.png │ │ ├── .eslintrc.json │ │ ├── next.config.mjs │ │ ├── README.md │ │ ├── middleware.ts │ │ ├── .gitignore │ │ ├── auth.config.ts │ │ ├── tailwind.config.ts │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── auth.ts └── react │ ├── package.json │ ├── app │ ├── layout.js │ ├── like-button.jsx │ └── page.jsx │ └── package-lock.json ├── nextjs ├── .eslintrc.json ├── app │ ├── globals.css │ ├── layout.tsx │ ├── api │ │ ├── download │ │ │ └── route.ts │ │ ├── upload │ │ │ └── route.ts │ │ └── data │ │ │ └── route.ts │ └── page.tsx ├── next.config.mjs ├── lib │ └── db.ts ├── postcss.config.mjs ├── tailwind.config.ts ├── tsconfig.json ├── components │ ├── DownloadButton.tsx │ ├── FileUpload.tsx │ └── DataTable.tsx ├── package.json └── README.md ├── pyproject.toml ├── fastapi ├── templates │ └── index.html ├── static │ ├── style.css │ └── script.js ├── logger.py └── main.py ├── LICENSE ├── fasthtml ├── static │ └── style.css ├── logger.py ├── main-jeremy.py └── main.py ├── README.md └── .gitignore /svelte/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /.learning/fasthtml/.sesskey: -------------------------------------------------------------------------------- 1 | ed779431-f5ee-4604-a7cf-780d20ee9855 -------------------------------------------------------------------------------- /nextjs/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /nextjs/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /svelte/.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /svelte/data.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/svelte/data.sqlite -------------------------------------------------------------------------------- /svelte/src/hooks.server.ts: -------------------------------------------------------------------------------- 1 | export async function handle({ event, resolve }) { 2 | return resolve(event); 3 | } -------------------------------------------------------------------------------- /.learning/fasthtml/todos.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/fasthtml/todos.db -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # Package Managers 2 | package-lock.json 3 | pnpm-lock.yaml 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /.learning/fasthtml/todos.db-shm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/fasthtml/todos.db-shm -------------------------------------------------------------------------------- /.learning/fasthtml/todos.db-wal: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/fasthtml/todos.db-wal -------------------------------------------------------------------------------- /fastapi+svelte/database.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/fastapi+svelte/database.sqlite -------------------------------------------------------------------------------- /nextjs/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /nextjs/lib/db.ts: -------------------------------------------------------------------------------- 1 | import Database from 'better-sqlite3'; 2 | 3 | const db = new Database('your_database.sqlite'); 4 | 5 | export { db }; 6 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/nextjs/nextjs-dashboard/app/favicon.ico -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/public/hero-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/nextjs/nextjs-dashboard/public/hero-mobile.png -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/opengraph-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/nextjs/nextjs-dashboard/app/opengraph-image.png -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/public/hero-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/nextjs/nextjs-dashboard/public/hero-desktop.png -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/actions/auth.ts: -------------------------------------------------------------------------------- 1 | 'use server'; 2 | 3 | import { signOut } from '@/auth'; 4 | 5 | export async function handleSignOut() { 6 | await signOut(); 7 | } 8 | -------------------------------------------------------------------------------- /nextjs/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /svelte/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/public/customers/amy-burns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/nextjs/nextjs-dashboard/public/customers/amy-burns.png -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/public/customers/evil-rabbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/nextjs/nextjs-dashboard/public/customers/evil-rabbit.png -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/public/customers/balazs-orban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/nextjs/nextjs-dashboard/public/customers/balazs-orban.png -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/public/customers/lee-robinson.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/nextjs/nextjs-dashboard/public/customers/lee-robinson.png -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/dashboard/(overview)/loading.tsx: -------------------------------------------------------------------------------- 1 | import DashboardSkeleton from '@/app/ui/skeletons'; 2 | 3 | export default function Loading() { 4 | return ; 5 | } -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/public/customers/michael-novotny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/nextjs/nextjs-dashboard/public/customers/michael-novotny.png -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/public/customers/delba-de-oliveira.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eugeneyan/framework-comparison/HEAD/.learning/nextjs/nextjs-dashboard/public/customers/delba-de-oliveira.png -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals" 4 | ], 5 | "parser": "@typescript-eslint/parser", 6 | "plugins": ["@typescript-eslint"], 7 | "rules": {} 8 | } 9 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/home.module.css: -------------------------------------------------------------------------------- 1 | .shape { 2 | height: 0; 3 | width: 0; 4 | border-bottom: 30px solid black; 5 | border-left: 20px solid transparent; 6 | border-right: 20px solid transparent; 7 | } -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | experimental: { 5 | ppr: 'incremental', 6 | }, 7 | }; 8 | 9 | export default nextConfig; 10 | -------------------------------------------------------------------------------- /.learning/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "next dev" 4 | }, 5 | "dependencies": { 6 | "next": "^14.2.7", 7 | "react": "^18.3.1", 8 | "react-dom": "^18.3.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /svelte/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/dashboard/customers/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from 'next'; 2 | 3 | export const metadata: Metadata = { 4 | title: 'Customers', 5 | }; 6 | 7 | export default function Page() { 8 | return

Customers Page

; 9 | } -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 8 | } 9 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/fonts.ts: -------------------------------------------------------------------------------- 1 | import { Inter } from 'next/font/google'; 2 | import { Lusitana } from 'next/font/google'; 3 | 4 | export const inter = Inter({ subsets: ['latin'] }); 5 | export const lusitana = Lusitana({ 6 | weight: ['400', '700'], 7 | subsets: ['latin'], 8 | }); -------------------------------------------------------------------------------- /.learning/react/app/layout.js: -------------------------------------------------------------------------------- 1 | export const metadata = { 2 | title: 'Next.js', 3 | description: 'Generated by Next.js', 4 | } 5 | 6 | export default function RootLayout({ children }) { 7 | return ( 8 | 9 | {children} 10 | 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/README.md: -------------------------------------------------------------------------------- 1 | ## Next.js App Router Course - Starter 2 | 3 | This is the starter template for the Next.js App Router Course. It contains the starting code for the dashboard application. 4 | 5 | For more information, see the [course curriculum](https://nextjs.org/learn) on the Next.js Website. 6 | -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | # Output 4 | .output 5 | .vercel 6 | /.svelte-kit 7 | /build 8 | 9 | # OS 10 | .DS_Store 11 | Thumbs.db 12 | 13 | # Env 14 | .env 15 | .env.* 16 | !.env.example 17 | !.env.test 18 | 19 | # Vite 20 | vite.config.js.timestamp-* 21 | vite.config.ts.timestamp-* 22 | -------------------------------------------------------------------------------- /svelte/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /.learning/react/app/like-button.jsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import { useState } from 'react'; 4 | 5 | export default function LikeButton() { 6 | const [likes, setLikes] = useState(0); 7 | 8 | function handleClick() { 9 | setLikes(likes + 1); 10 | } 11 | 12 | return 13 | } -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /svelte/src/lib/db.ts: -------------------------------------------------------------------------------- 1 | import Database from 'better-sqlite3'; 2 | 3 | const db = new Database('data.sqlite'); 4 | 5 | export function query(sql: string, params: any[] = []) { 6 | return db.prepare(sql).all(params); 7 | } 8 | 9 | export function run(sql: string, params: any[] = []) { 10 | return db.prepare(sql).run(params); 11 | } 12 | 13 | export { db }; -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/middleware.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth'; 2 | import { authConfig } from './auth.config'; 3 | 4 | export default NextAuth(authConfig).auth; 5 | 6 | export const config = { 7 | // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher 8 | matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'], 9 | }; -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()], 6 | server: { 7 | proxy: { 8 | '/api': { 9 | target: 'http://localhost:8000', 10 | changeOrigin: true, 11 | rewrite: (path) => path.replace(/^\/api/, '') 12 | } 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /svelte/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | input[type='number'] { 6 | -moz-appearance: textfield; 7 | appearance: textfield; 8 | } 9 | 10 | input[type='number']::-webkit-inner-spin-button { 11 | -webkit-appearance: none; 12 | margin: 0; 13 | } 14 | 15 | input[type='number']::-webkit-outer-spin-button { 16 | -webkit-appearance: none; 17 | margin: 0; 18 | } 19 | -------------------------------------------------------------------------------- /svelte/src/routes/api/data/+server.ts: -------------------------------------------------------------------------------- 1 | import { json } from '@sveltejs/kit'; 2 | import { query } from '$lib/db'; 3 | 4 | export async function GET() { 5 | try { 6 | const data = query('SELECT * FROM csv_data'); 7 | return json(data); 8 | } catch (error) { 9 | // If the table doesn't exist yet, return an empty array 10 | if (error.message.includes('no such table')) { 11 | return json([]); 12 | } 13 | throw error; 14 | } 15 | } -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "framework-comparison" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "fastapi>=0.112.2", 9 | "fastlite==0.0.11", 10 | "jinja2>=3.1.4", 11 | "pandas>=2.2.2", 12 | "python-fasthtml>=0.5.1", 13 | "python-multipart>=0.0.9", 14 | "uvicorn>=0.30.6", 15 | ] 16 | 17 | [tool.ruff.lint] 18 | ignore = ["F403", "F405"] 19 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/acme-logo.tsx: -------------------------------------------------------------------------------- 1 | import { GlobeAltIcon } from '@heroicons/react/24/outline'; 2 | import { lusitana } from '@/app/ui/fonts'; 3 | 4 | export default function AcmeLogo() { 5 | return ( 6 |
9 | 10 |

Acme

11 |
12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /nextjs/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import { Inter } from "next/font/google"; 3 | import "./globals.css"; 4 | 5 | const inter = Inter({ subsets: ["latin"] }); 6 | 7 | export const metadata: Metadata = { 8 | title: "Look at Your Data", 9 | }; 10 | 11 | export default function RootLayout({ 12 | children, 13 | }: Readonly<{ 14 | children: React.ReactNode; 15 | }>) { 16 | return ( 17 | 18 | {children} 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/dashboard/layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import SideNav from '@/app/ui/dashboard/sidenav'; 4 | 5 | export const experimental_ppr = true; 6 | 7 | export default function Layout({ children }: { children: React.ReactNode }) { 8 | return ( 9 |
10 |
11 | 12 |
13 |
{children}
14 |
15 | ); 16 | } -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.learning/react/app/page.jsx: -------------------------------------------------------------------------------- 1 | import LikeButton from './like-button'; 2 | 3 | function Header({ title }) { 4 | return

{title ? title : 'Default title'}

; 5 | } 6 | 7 | function HomePage() { 8 | const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton']; 9 | 10 | return ( 11 |
12 |
13 |
    14 | {names.map((name) => ( 15 |
  • {name}
  • 16 | ))} 17 |
18 | 19 | 20 |
21 | ); 22 | } 23 | 24 | export default HomePage; 25 | -------------------------------------------------------------------------------- /svelte/src/lib/api.ts: -------------------------------------------------------------------------------- 1 | export async function fetchData() { 2 | const response = await fetch('/api/data'); 3 | return response.json(); 4 | } 5 | 6 | export async function updateRow(id: number, data: any) { 7 | const response = await fetch(`/api/data/${id}`, { 8 | method: 'PUT', 9 | headers: { 'Content-Type': 'application/json' }, 10 | body: JSON.stringify(data) 11 | }); 12 | return response.json(); 13 | } 14 | 15 | export async function deleteRow(id: number) { 16 | const response = await fetch(`/api/data/${id}`, { method: 'DELETE' }); 17 | return response.json(); 18 | } -------------------------------------------------------------------------------- /nextjs/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /svelte/src/routes/api/data/[id]/+server.ts: -------------------------------------------------------------------------------- 1 | import { json } from '@sveltejs/kit'; 2 | import { run } from '$lib/db'; 3 | 4 | export async function PUT({ params, request }) { 5 | const { id } = params; 6 | const updatedRow = await request.json(); 7 | 8 | const setClause = Object.keys(updatedRow).map(col => `${col} = ?`).join(', '); 9 | const sql = `UPDATE csv_data SET ${setClause} WHERE id = ?`; 10 | 11 | run(sql, [...Object.values(updatedRow), id]); 12 | return json({ success: true }); 13 | } 14 | 15 | export async function DELETE({ params }) { 16 | run('DELETE FROM csv_data WHERE id = ?', [params.id]); 17 | return json({ success: true }); 18 | } -------------------------------------------------------------------------------- /nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import '@/app/ui/global.css'; 2 | import { inter } from '@/app/ui/fonts'; 3 | import { Metadata } from 'next'; 4 | 5 | export const metadata: Metadata = { 6 | title: { 7 | template: '%s | Acme Dashboard', 8 | default: 'Acme Dashboard', 9 | }, 10 | description: 'The official Next.js Learn Dashboard built with App Router.', 11 | metadataBase: new URL('https://next-learn-dashboard.vercel.sh'), 12 | }; 13 | 14 | export default function RootLayout({ 15 | children, 16 | }: { 17 | children: React.ReactNode; 18 | }) { 19 | return ( 20 | 21 | {children} 22 | 23 | ); 24 | } -------------------------------------------------------------------------------- /nextjs/app/api/download/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { db } from '../../../lib/db'; 3 | import { stringify } from 'csv-stringify/sync'; 4 | 5 | export async function GET() { 6 | try { 7 | const data = db.prepare('SELECT * FROM data').all(); 8 | const csv = stringify(data, { header: true }); 9 | 10 | return new NextResponse(csv, { 11 | status: 200, 12 | headers: { 13 | 'Content-Type': 'text/csv', 14 | 'Content-Disposition': 'attachment; filename=data.csv', 15 | }, 16 | }); 17 | } catch (error) { 18 | console.error('Error generating CSV:', error); 19 | return NextResponse.json({ error: 'Error generating CSV' }, { status: 500 }); 20 | } 21 | } -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/dashboard/invoices/create/page.tsx: -------------------------------------------------------------------------------- 1 | import Form from '@/app/ui/invoices/create-form'; 2 | import Breadcrumbs from '@/app/ui/invoices/breadcrumbs'; 3 | import { fetchCustomers } from '@/app/lib/data'; 4 | 5 | export default async function Page() { 6 | const customers = await fetchCustomers(); 7 | 8 | return ( 9 |
10 | 20 |
21 |
22 | ); 23 | } -------------------------------------------------------------------------------- /svelte/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/dashboard/invoices/[id]/edit/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { FaceFrownIcon } from '@heroicons/react/24/outline'; 3 | 4 | export default function NotFound() { 5 | return ( 6 |
7 | 8 |

404 Not Found

9 |

Could not find the requested invoice.

10 | 14 | Go Back 15 | 16 |
17 | ); 18 | } -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "moduleResolution": "bundler" 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // except $lib which is handled by https://kit.svelte.dev/docs/configuration#files 16 | // 17 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 18 | // from the referenced tsconfig.json - TypeScript does not merge them in 19 | } 20 | -------------------------------------------------------------------------------- /svelte/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter() 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | 3 | interface ButtonProps extends React.ButtonHTMLAttributes { 4 | children: React.ReactNode; 5 | } 6 | 7 | export function Button({ children, className, ...rest }: ButtonProps) { 8 | return ( 9 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/auth.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextAuthConfig } from 'next-auth'; 2 | 3 | export const authConfig = { 4 | pages: { 5 | signIn: '/login', 6 | }, 7 | callbacks: { 8 | authorized({ auth, request: { nextUrl } }) { 9 | const isLoggedIn = !!auth?.user; 10 | const isOnDashboard = nextUrl.pathname.startsWith('/dashboard'); 11 | if (isOnDashboard) { 12 | if (isLoggedIn) return true; 13 | return false; // Redirect unauthenticated users to login page 14 | } else if (isLoggedIn) { 15 | return Response.redirect(new URL('/dashboard', nextUrl)); 16 | } 17 | return true; 18 | }, 19 | }, 20 | providers: [], // Add providers with an empty array for now 21 | } satisfies NextAuthConfig; -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/login/page.tsx: -------------------------------------------------------------------------------- 1 | import AcmeLogo from '@/app/ui/acme-logo'; 2 | import LoginForm from '@/app/ui/login-form'; 3 | import { Metadata } from 'next'; 4 | 5 | export const metadata: Metadata = { 6 | title: 'Login', 7 | }; 8 | 9 | export default function LoginPage() { 10 | return ( 11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 | ); 22 | } -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-auto'; 2 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | /** @type {import('@sveltejs/kit').Config} */ 5 | const config = { 6 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 7 | // for more information about preprocessors 8 | preprocess: vitePreprocess(), 9 | 10 | kit: { 11 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 12 | // If your environment is not supported, or you settled on a specific environment, switch out the adapter. 13 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 14 | adapter: adapter() 15 | } 16 | }; 17 | 18 | export default config; 19 | -------------------------------------------------------------------------------- /svelte/src/routes/api/download/+server.ts: -------------------------------------------------------------------------------- 1 | import { query } from '$lib/db'; 2 | import { stringify } from 'csv-stringify/sync'; 3 | 4 | export async function GET() { 5 | try { 6 | const csv = stringify(query('SELECT * FROM csv_data'), { header: true }); 7 | return new Response(csv, { 8 | headers: { 9 | 'Content-Type': 'text/csv', 10 | 'Content-Disposition': 'attachment; filename="table_data.csv"' 11 | } 12 | }); 13 | } catch (error) { 14 | if (error instanceof Error && error.message.includes('no such table')) { 15 | return new Response('', { 16 | headers: { 17 | 'Content-Type': 'text/csv', 18 | 'Content-Disposition': 'attachment; filename="table_data.csv"' 19 | } 20 | }); 21 | } 22 | throw error; 23 | } 24 | } -------------------------------------------------------------------------------- /svelte/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import ts from 'typescript-eslint'; 3 | import svelte from 'eslint-plugin-svelte'; 4 | import prettier from 'eslint-config-prettier'; 5 | import globals from 'globals'; 6 | 7 | /** @type {import('eslint').Linter.Config[]} */ 8 | export default [ 9 | js.configs.recommended, 10 | ...ts.configs.recommended, 11 | ...svelte.configs['flat/recommended'], 12 | prettier, 13 | ...svelte.configs['flat/prettier'], 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.browser, 18 | ...globals.node 19 | } 20 | } 21 | }, 22 | { 23 | files: ['**/*.svelte'], 24 | languageOptions: { 25 | parserOptions: { 26 | parser: ts.parser 27 | } 28 | } 29 | }, 30 | { 31 | ignores: ['build/', '.svelte-kit/', 'dist/'] 32 | } 33 | ]; 34 | -------------------------------------------------------------------------------- /svelte/src/lib/components/CsvUpload.svelte: -------------------------------------------------------------------------------- 1 | 26 | 27 |
28 | 29 |
-------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import ts from 'typescript-eslint'; 3 | import svelte from 'eslint-plugin-svelte'; 4 | import prettier from 'eslint-config-prettier'; 5 | import globals from 'globals'; 6 | 7 | /** @type {import('eslint').Linter.Config[]} */ 8 | export default [ 9 | js.configs.recommended, 10 | ...ts.configs.recommended, 11 | ...svelte.configs['flat/recommended'], 12 | prettier, 13 | ...svelte.configs['flat/prettier'], 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.browser, 18 | ...globals.node 19 | } 20 | } 21 | }, 22 | { 23 | files: ['**/*.svelte'], 24 | languageOptions: { 25 | parserOptions: { 26 | parser: ts.parser 27 | } 28 | } 29 | }, 30 | { 31 | ignores: ['build/', '.svelte-kit/', 'dist/'] 32 | } 33 | ]; 34 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from 'tailwindcss'; 2 | 3 | const config: Config = { 4 | content: [ 5 | './pages/**/*.{js,ts,jsx,tsx,mdx}', 6 | './components/**/*.{js,ts,jsx,tsx,mdx}', 7 | './app/**/*.{js,ts,jsx,tsx,mdx}', 8 | ], 9 | theme: { 10 | extend: { 11 | gridTemplateColumns: { 12 | '13': 'repeat(13, minmax(0, 1fr))', 13 | }, 14 | colors: { 15 | blue: { 16 | 400: '#2589FE', 17 | 500: '#0070F3', 18 | 600: '#2F6FEB', 19 | }, 20 | }, 21 | }, 22 | keyframes: { 23 | shimmer: { 24 | '100%': { 25 | transform: 'translateX(100%)', 26 | }, 27 | }, 28 | }, 29 | }, 30 | plugins: [require('@tailwindcss/forms')], 31 | }; 32 | export default config; 33 | -------------------------------------------------------------------------------- /nextjs/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import FileUpload from '../components/FileUpload'; 5 | import DataTable from '../components/DataTable'; 6 | import DownloadButton from '../components/DownloadButton'; 7 | 8 | export default function Home() { 9 | const [refreshKey, setRefreshKey] = useState(0); 10 | 11 | const handleFileUploaded = () => { 12 | setRefreshKey(prevKey => prevKey + 1); 13 | }; 14 | 15 | return ( 16 |
17 |

Look at Your Data

18 |
19 | 20 | 21 |
22 | 23 |
24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /nextjs/components/DownloadButton.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | export default function DownloadButton() { 4 | const handleDownload = async () => { 5 | try { 6 | const response = await fetch('/api/download'); 7 | const blob = await response.blob(); 8 | const url = window.URL.createObjectURL(blob); 9 | const a = document.createElement('a'); 10 | a.href = url; 11 | a.download = 'data.csv'; 12 | document.body.appendChild(a); 13 | a.click(); 14 | window.URL.revokeObjectURL(url); 15 | } catch (error) { 16 | console.error('Error downloading file:', error); 17 | alert('Failed to download file'); 18 | } 19 | }; 20 | 21 | return ( 22 | 28 | ); 29 | } -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "baseUrl": ".", 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": [ 27 | "next-env.d.ts", 28 | "**/*.ts", 29 | "**/*.tsx", 30 | ".next/types/**/*.ts", 31 | "app/lib/placeholder-data.ts", 32 | "scripts/seed.js" 33 | ], 34 | "exclude": ["node_modules"] 35 | } 36 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/invoices/status.tsx: -------------------------------------------------------------------------------- 1 | import { CheckIcon, ClockIcon } from '@heroicons/react/24/outline'; 2 | import clsx from 'clsx'; 3 | 4 | export default function InvoiceStatus({ status }: { status: string }) { 5 | return ( 6 | 15 | {status === 'pending' ? ( 16 | <> 17 | Pending 18 | 19 | 20 | ) : null} 21 | {status === 'paid' ? ( 22 | <> 23 | Paid 24 | 25 | 26 | ) : null} 27 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/dashboard/invoices/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | 5 | export default function Error({ 6 | error, 7 | reset, 8 | }: { 9 | error: Error & { digest?: string }; 10 | reset: () => void; 11 | }) { 12 | useEffect(() => { 13 | // Optionally log the error to an error reporting service 14 | console.error(error); 15 | }, [error]); 16 | 17 | return ( 18 |
19 |

Something went wrong!

20 | 29 |
30 | ); 31 | } -------------------------------------------------------------------------------- /.learning/fasthtml/main.py: -------------------------------------------------------------------------------- 1 | from fasthtml.common import * 2 | from logger import get_logger 3 | 4 | # Import logger 5 | logger = get_logger("fastapi", "INFO") 6 | 7 | # Set up fastHTML app 8 | css = Style(":root {--pico-font-size:90%,--pico-font-family: Pacifico, cursive;}") 9 | app = FastHTMLWithLiveReload(hdrs=(picolink, css)) 10 | 11 | messages = ["This is a message, which will get rendered as a paragraph"] 12 | 13 | 14 | count = 0 15 | 16 | 17 | @app.get("/") 18 | def home(): 19 | return Title("Count Demo"), Main( 20 | H1("Count Demo"), 21 | P(f"Count is set to {count}", id="count"), 22 | Button( 23 | "Increment", hx_post="/increment", hx_target="#count", hx_swap="innerHTML" 24 | ), 25 | ) 26 | 27 | 28 | @app.post("/increment") 29 | def increment(): 30 | print("incrementing") 31 | global count 32 | count += 1 33 | return f"Count is set to {count}" 34 | 35 | 36 | serve() 37 | -------------------------------------------------------------------------------- /fastapi/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Look at Your Data 7 | 8 | 9 | 10 | 11 | 12 |
13 |

Look at Your Data

14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 |
24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@types/multer": "^1.4.12", 13 | "better-sqlite3": "^11.2.1", 14 | "csv-parse": "^5.5.6", 15 | "csv-stringify": "^6.5.1", 16 | "multer": "1.4.5-lts.1", 17 | "next": "14.2.7", 18 | "react": "^18.3.1", 19 | "react-dom": "^18.3.1", 20 | "react-table": "^7.8.0", 21 | "sqlite": "^5.1.1", 22 | "sqlite3": "^5.1.7" 23 | }, 24 | "devDependencies": { 25 | "@types/better-sqlite3": "^7.6.11", 26 | "@types/node": "^20.16.5", 27 | "@types/react": "^18.3.5", 28 | "@types/react-dom": "^18.3.0", 29 | "eslint": "^8.57.0", 30 | "eslint-config-next": "14.2.7", 31 | "postcss": "^8.4.45", 32 | "tailwindcss": "^3.4.10", 33 | "typescript": "^5.5.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/dashboard/invoices/[id]/edit/page.tsx: -------------------------------------------------------------------------------- 1 | import Form from '@/app/ui/invoices/edit-form'; 2 | import Breadcrumbs from '@/app/ui/invoices/breadcrumbs'; 3 | import { fetchCustomers, fetchInvoiceById } from '@/app/lib/data'; 4 | import { notFound } from 'next/navigation'; 5 | 6 | export default async function Page( {params}: {params: {id: string}}) { 7 | const id = params.id; 8 | const [invoice, customers] = await Promise.all([ 9 | fetchInvoiceById(id), 10 | fetchCustomers(), 11 | ]); 12 | 13 | if (!invoice) { 14 | notFound(); 15 | } 16 | 17 | return ( 18 |
19 | 29 | 30 |
31 | ); 32 | } -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --check . && eslint .", 12 | "format": "prettier --write ." 13 | }, 14 | "devDependencies": { 15 | "@sveltejs/adapter-auto": "^3.0.0", 16 | "@sveltejs/kit": "^2.0.0", 17 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 18 | "@types/eslint": "^9.6.0", 19 | "eslint": "^9.0.0", 20 | "eslint-config-prettier": "^9.1.0", 21 | "eslint-plugin-svelte": "^2.36.0", 22 | "globals": "^15.0.0", 23 | "prettier": "^3.1.1", 24 | "prettier-plugin-svelte": "^3.1.2", 25 | "svelte": "^4.2.7", 26 | "svelte-check": "^4.0.0", 27 | "typescript": "^5.0.0", 28 | "typescript-eslint": "^8.0.0", 29 | "vite": "^5.0.3" 30 | }, 31 | "type": "module" 32 | } 33 | -------------------------------------------------------------------------------- /svelte/src/routes/api/upload/+server.ts: -------------------------------------------------------------------------------- 1 | import { json } from '@sveltejs/kit'; 2 | import { run, db } from '$lib/db'; 3 | import { parse } from 'csv-parse/sync'; 4 | 5 | export async function POST({ request }) { 6 | const file = await request.formData().then(data => data.get('csv') as File); 7 | 8 | if (!file) return json({ error: 'No file uploaded' }, { status: 400 }); 9 | 10 | const records = parse(await file.text(), { columns: true }); 11 | if (records.length === 0) return json({ error: 'Empty CSV file' }, { status: 400 }); 12 | 13 | const columns = Object.keys(records[0]); 14 | run(`CREATE TABLE IF NOT EXISTS csv_data (${columns.map(col => `${col} TEXT`).join(', ')})`); 15 | run('DELETE FROM csv_data'); 16 | 17 | const insertSQL = `INSERT INTO csv_data (${columns.join(', ')}) VALUES (${columns.map(() => '?').join(', ')})`; 18 | const stmt = db.prepare(insertSQL); 19 | 20 | db.transaction((records: any[]) => { 21 | for (const record of records) { 22 | stmt.run(columns.map(col => record[col])); 23 | } 24 | })(records); 25 | 26 | return json({ success: true }); 27 | } -------------------------------------------------------------------------------- /svelte/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/dashboard/sidenav.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import NavLinks from '@/app/ui/dashboard/nav-links'; 3 | import AcmeLogo from '@/app/ui/acme-logo'; 4 | import { PowerIcon } from '@heroicons/react/24/outline'; 5 | import { handleSignOut } from '@/app/actions/auth'; 6 | 7 | export default function SideNav() { 8 | return ( 9 |
10 | // ... 11 |
12 | 13 |
14 | 15 | 19 | 20 |
21 |
22 | ); 23 | } -------------------------------------------------------------------------------- /fastapi+svelte/svelte-app/README.md: -------------------------------------------------------------------------------- 1 | # create-svelte 2 | 3 | Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). 4 | 5 | ## Creating a project 6 | 7 | If you're seeing this, you've probably already done this step. Congrats! 8 | 9 | ```bash 10 | # create a new project in the current directory 11 | npm create svelte@latest 12 | 13 | # create a new project in my-app 14 | npm create svelte@latest my-app 15 | ``` 16 | 17 | ## Developing 18 | 19 | Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: 20 | 21 | ```bash 22 | npm run dev 23 | 24 | # or start the server and open the app in a new browser tab 25 | npm run dev -- --open 26 | ``` 27 | 28 | ## Building 29 | 30 | To create a production version of your app: 31 | 32 | ```bash 33 | npm run build 34 | ``` 35 | 36 | You can preview the production build with `npm run preview`. 37 | 38 | > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Eugene Yan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/invoices/breadcrumbs.tsx: -------------------------------------------------------------------------------- 1 | import { clsx } from 'clsx'; 2 | import Link from 'next/link'; 3 | import { lusitana } from '@/app/ui/fonts'; 4 | 5 | interface Breadcrumb { 6 | label: string; 7 | href: string; 8 | active?: boolean; 9 | } 10 | 11 | export default function Breadcrumbs({ 12 | breadcrumbs, 13 | }: { 14 | breadcrumbs: Breadcrumb[]; 15 | }) { 16 | return ( 17 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/dashboard/(overview)/page.tsx: -------------------------------------------------------------------------------- 1 | import CardWrapper from '@/app/ui/dashboard/cards'; 2 | import RevenueChart from '@/app/ui/dashboard/revenue-chart'; 3 | import LatestInvoices from '@/app/ui/dashboard/latest-invoices'; 4 | import { lusitana } from '@/app/ui/fonts'; 5 | import { Suspense } from 'react'; 6 | import { RevenueChartSkeleton, LatestInvoicesSkeleton, CardsSkeleton } from '@/app/ui/skeletons'; 7 | 8 | export default async function Page() { 9 | 10 | return ( 11 |
12 |

13 | Dashboard 14 |

15 |
16 | }> 17 | 18 | 19 |
20 |
21 | }> 22 | 23 | 24 | }> 25 | 26 | 27 |
28 |
29 | ); 30 | } -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "build": "next build", 5 | "dev": "next dev", 6 | "start": "next start", 7 | "lint": "next lint" 8 | }, 9 | "dependencies": { 10 | "@heroicons/react": "^2.1.4", 11 | "@tailwindcss/forms": "^0.5.7", 12 | "@vercel/postgres": "^0.8.0", 13 | "autoprefixer": "10.4.19", 14 | "bcrypt": "^5.1.1", 15 | "clsx": "^2.1.1", 16 | "next": "15.0.0-canary.56", 17 | "next-auth": "5.0.0-beta.20", 18 | "postcss": "8.4.38", 19 | "react": "19.0.0-rc-f38c22b244-20240704", 20 | "react-dom": "19.0.0-rc-f38c22b244-20240704", 21 | "tailwindcss": "3.4.4", 22 | "typescript": "5.5.2", 23 | "use-debounce": "^10.0.1", 24 | "zod": "^3.23.8" 25 | }, 26 | "devDependencies": { 27 | "@types/bcrypt": "^5.0.2", 28 | "@types/node": "20.14.8", 29 | "@types/react": "18.3.3", 30 | "@types/react-dom": "18.3.0", 31 | "@typescript-eslint/eslint-plugin": "^8.4.0", 32 | "@typescript-eslint/parser": "^8.4.0", 33 | "eslint": "^8", 34 | "eslint-config-next": "14.2.7" 35 | }, 36 | "engines": { 37 | "node": ">=20.12.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "lint": "prettier --check . && eslint .", 12 | "format": "prettier --write ." 13 | }, 14 | "devDependencies": { 15 | "@sveltejs/adapter-auto": "^3.0.0", 16 | "@sveltejs/kit": "^2.0.0", 17 | "@sveltejs/vite-plugin-svelte": "^3.0.0", 18 | "@types/eslint": "^9.6.0", 19 | "eslint": "^9.0.0", 20 | "eslint-config-prettier": "^9.1.0", 21 | "eslint-plugin-svelte": "^2.36.0", 22 | "globals": "^15.0.0", 23 | "prettier": "^3.1.1", 24 | "prettier-plugin-svelte": "^3.1.2", 25 | "svelte": "^4.2.7", 26 | "svelte-check": "^4.0.0", 27 | "typescript": "^5.0.0", 28 | "typescript-eslint": "^8.0.0", 29 | "vite": "^5.0.3", 30 | "@types/better-sqlite3": "^7.6.0" 31 | }, 32 | "dependencies": { 33 | "better-sqlite3": "^8.0.0", 34 | "csv-parse": "^5.3.0", 35 | "csv-stringify": "^6.2.0" 36 | }, 37 | "type": "module" 38 | } 39 | -------------------------------------------------------------------------------- /fastapi/static/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | .container { 8 | width: 100%; 9 | max-width: none; 10 | padding: 0 2em; 11 | box-sizing: border-box; 12 | } 13 | 14 | table { 15 | width: 100%; 16 | border-collapse: collapse; 17 | margin-top: 20px; 18 | } 19 | 20 | th, td { 21 | border: 1px solid #ddd; 22 | padding: 8px; 23 | text-align: left; 24 | word-wrap: break-word; 25 | overflow-wrap: break-word; 26 | } 27 | 28 | th { 29 | background-color: #f2f2f2; 30 | position: sticky; 31 | top: 0; 32 | z-index: 10; 33 | } 34 | 35 | input[type="file"], button { 36 | margin-top: 10px; 37 | } 38 | 39 | .col-narrow { 40 | width: 2em; 41 | max-width: 2em; 42 | } 43 | 44 | .col-medium { 45 | width: 20%; 46 | max-width: 20%; 47 | } 48 | 49 | .col-wide { 50 | width: 70%; 51 | max-width: 70%; 52 | } 53 | 54 | td { 55 | height: 10em; 56 | vertical-align: top; 57 | } 58 | 59 | textarea { 60 | width: 100%; 61 | height: 100%; 62 | box-sizing: border-box; 63 | resize: both; 64 | border: none; 65 | background: transparent; 66 | font-family: inherit; 67 | font-size: inherit; 68 | padding: 0; 69 | } 70 | -------------------------------------------------------------------------------- /fasthtml/static/style.css: -------------------------------------------------------------------------------- 1 | .col-narrow { 2 | width: 5%; 3 | min-width: 5em; 4 | max-width: 10em; 5 | } 6 | 7 | .col-medium { 8 | width: 20%; 9 | max-width: 20%; 10 | } 11 | 12 | .col-wide { 13 | width: 70%; 14 | max-width: 70%; 15 | } 16 | 17 | .col-narrow textarea, .col-medium textarea, .col-wide textarea { 18 | width: 100%; 19 | height: 10em; 20 | padding: 5px; 21 | box-sizing: border-box; 22 | background: transparent; 23 | resize: vertical; 24 | overflow-y: auto; 25 | font-family: inherit; 26 | font-size: inherit; 27 | } 28 | 29 | .col-narrow, .col-medium, .col-wide { 30 | padding: 5px; 31 | } 32 | 33 | .table-container { 34 | overflow-x: auto; 35 | } 36 | 37 | .table { 38 | width: 100%; 39 | border-collapse: collapse; 40 | } 41 | 42 | .btn { 43 | display: inline-block; 44 | padding: 10px 20px; 45 | margin: 0 5px; 46 | background-color: #0275d8; 47 | color: white; 48 | text-decoration: none; 49 | border-radius: 5px; 50 | border: none; 51 | cursor: pointer; 52 | font-size: 1em; 53 | text-align: center; 54 | } 55 | 56 | form { 57 | display: flex; 58 | align-items: center; 59 | margin-bottom: 20px; 60 | } 61 | 62 | input[type="file"] { 63 | margin-right: 10px; 64 | } -------------------------------------------------------------------------------- /.learning/fasthtml/todo.py: -------------------------------------------------------------------------------- 1 | from fasthtml.common import * 2 | 3 | 4 | def render_todo(todo): 5 | tid = f"todo-{todo.id}" 6 | toggle = A("Toggle", hx_get=f"/toggle/{todo.id}", target_id=tid) 7 | delete = A("Delete", hx_delete=f"/{todo.id}", hx_swap="outerHTML", target_id=tid) 8 | return Li(toggle, delete, todo.title + (" ✅" if todo.done else ""), id=tid) 9 | 10 | 11 | app, rt, todos, Todo = fast_app( 12 | "todos.db", live=True, render=render_todo, id=int, pk="id", title=str, done=bool 13 | ) # Returns a FastHTML app, a router, a todos table, and a Todos class 14 | 15 | 16 | def make_input(): 17 | return Input(placeholder="Add a todo", id="title", hx_swap_oob="true") 18 | 19 | 20 | @rt("/") 21 | def get(): 22 | frm = Form( 23 | Group(make_input(), Button("Add")), 24 | hx_post="/", 25 | target_id="todo-list", 26 | hx_swap="beforeend", 27 | ) 28 | return Titled("Todos", Card(Ul(*todos(), id="todo-list"), header=frm)) 29 | 30 | 31 | @rt("/{tid}") 32 | def delete(tid: int): 33 | todos.delete(tid) 34 | 35 | 36 | @rt("/") 37 | def post(todo: Todo): 38 | return todos.insert(todo), make_input() 39 | 40 | 41 | @rt("/toggle/{tid}") 42 | def get(tid: int): 43 | todo = todos.get(tid) 44 | todo.done = not todo.done 45 | return todos.update(todo) 46 | 47 | 48 | serve() 49 | -------------------------------------------------------------------------------- /nextjs/components/FileUpload.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | 5 | interface FileUploadProps { 6 | onFileUploaded: () => void; 7 | } 8 | 9 | export default function FileUpload({ onFileUploaded }: FileUploadProps) { 10 | const [file, setFile] = useState(null); 11 | 12 | const handleFileUpload = async (e: React.FormEvent) => { 13 | e.preventDefault(); 14 | if (!file) return; 15 | 16 | const formData = new FormData(); 17 | formData.append('file', file); 18 | 19 | try { 20 | const response = await fetch('/api/upload', { 21 | method: 'POST', 22 | body: formData, 23 | }); 24 | if (response.ok) { 25 | alert('File uploaded successfully'); 26 | onFileUploaded(); 27 | } else { 28 | alert('File upload failed'); 29 | } 30 | } catch (error) { 31 | console.error('Error uploading file:', error); 32 | alert('File upload failed'); 33 | } 34 | }; 35 | 36 | return ( 37 |
38 | setFile(e.target.files?.[0] || null)} 42 | className="mr-2" 43 | /> 44 | 47 |
48 | ); 49 | } -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/invoices/buttons.tsx: -------------------------------------------------------------------------------- 1 | import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline'; 2 | import Link from 'next/link'; 3 | import { deleteInvoice } from '@/app/lib/actions'; 4 | 5 | export function CreateInvoice() { 6 | return ( 7 | 11 | Create Invoice{' '} 12 | 13 | 14 | ); 15 | } 16 | 17 | export function UpdateInvoice({ id }: { id: string }) { 18 | return ( 19 | 23 | 24 | 25 | ); 26 | } 27 | 28 | export function DeleteInvoice({ id }: { id: string }) { 29 | const deleteInvoiceWithId = deleteInvoice.bind(null, id); 30 | 31 | return ( 32 |
33 | 37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/auth.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from 'next-auth'; 2 | import Credentials from 'next-auth/providers/credentials'; 3 | import { authConfig } from './auth.config'; 4 | import { z } from 'zod'; 5 | import { sql } from '@vercel/postgres'; 6 | import type { User } from '@/app/lib/definitions'; 7 | import bcrypt from 'bcrypt'; 8 | 9 | async function getUser(email: string): Promise { 10 | try { 11 | const user = await sql`SELECT * FROM users WHERE email=${email}`; 12 | return user.rows[0]; 13 | } catch (error) { 14 | console.error('Failed to fetch user:', error); 15 | throw new Error('Failed to fetch user.'); 16 | } 17 | } 18 | 19 | export const { auth, signIn, signOut } = NextAuth({ 20 | ...authConfig, 21 | providers: [ 22 | Credentials({ 23 | async authorize(credentials) { 24 | const parsedCredentials = z 25 | .object({ email: z.string().email(), password: z.string().min(6) }) 26 | .safeParse(credentials); 27 | 28 | if (parsedCredentials.success) { 29 | const { email, password } = parsedCredentials.data; 30 | const user = await getUser(email); 31 | if (!user) return null; 32 | const passwordMatch = await bcrypt.compare(password, user.password); 33 | if (passwordMatch) return user; 34 | } 35 | 36 | console.log('Invalid email or password'); 37 | return null; 38 | }, 39 | }), 40 | ], 41 | }); -------------------------------------------------------------------------------- /nextjs/app/api/upload/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { promises as fs } from 'fs'; 3 | import { parse } from 'csv-parse/sync'; 4 | import { db } from '../../../lib/db'; 5 | 6 | export async function POST(req: NextRequest) { 7 | const formData = await req.formData(); 8 | const file = formData.get('file') as File; 9 | 10 | if (!file) { 11 | return NextResponse.json({ error: 'No file uploaded' }, { status: 400 }); 12 | } 13 | 14 | const buffer = Buffer.from(await file.arrayBuffer()); 15 | const content = buffer.toString(); 16 | 17 | try { 18 | const records = parse(content, { columns: true, skip_empty_lines: true }); 19 | 20 | // Create table if it doesn't exist 21 | const columns = Object.keys(records[0]).map((col) => `${col} ${col.toLowerCase() === 'id' ? 'INTEGER PRIMARY KEY' : 'TEXT'}`).join(', '); 22 | await db.exec(`CREATE TABLE IF NOT EXISTS data (${columns})`); 23 | 24 | // Insert data 25 | const columnNames = Object.keys(records[0]); 26 | const stmt = db.prepare(`INSERT INTO data (${columnNames.join(', ')}) VALUES (${columnNames.map(() => '?').join(', ')})`); 27 | records.forEach((record: any) => { 28 | stmt.run(Object.values(record)); 29 | }); 30 | 31 | return NextResponse.json({ message: 'File uploaded and processed successfully' }); 32 | } catch (error) { 33 | console.error('Error processing CSV:', error); 34 | return NextResponse.json({ error: 'Error processing CSV' }, { status: 500 }); 35 | } 36 | } -------------------------------------------------------------------------------- /nextjs/README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/search.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { MagnifyingGlassIcon } from '@heroicons/react/24/outline'; 4 | import { useSearchParams, usePathname, useRouter } from 'next/navigation'; 5 | import { useDebouncedCallback } from 'use-debounce'; 6 | 7 | export default function Search({ placeholder }: { placeholder: string }) { 8 | 9 | const searchParams = useSearchParams(); 10 | const pathname = usePathname(); 11 | const {replace} = useRouter(); 12 | 13 | const handleSearch = useDebouncedCallback((term: string) => { 14 | console.log(`Searching... ${term}`); 15 | const params = new URLSearchParams(searchParams); 16 | params.set('page', '1'); 17 | if (term) { 18 | params.set('query', term); 19 | } else { 20 | params.delete('query'); 21 | } 22 | replace(`${pathname}?${params.toString()}`); 23 | }, 300); 24 | 25 | return ( 26 |
27 | 30 | handleSearch(e.target.value)} 34 | defaultValue={searchParams.get('query')?.toString()} 35 | /> 36 | 37 |
38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /fastapi/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | 5 | def get_logger(name: str = __name__, level: str = "INFO") -> logging.Logger: 6 | """ 7 | Initialize a simple logger that outputs to the console. 8 | 9 | Args: 10 | name (str): The name of the logger. Defaults to the module name. 11 | level (str): The logging level. Defaults to "INFO". 12 | 13 | Returns: 14 | logging.Logger: A configured logger instance. 15 | """ 16 | # Create a logger 17 | logger = logging.getLogger(name) 18 | 19 | # Set the logging level 20 | level = getattr(logging, level.upper(), logging.INFO) 21 | logger.setLevel(level) 22 | 23 | # Create a console handler and set its level 24 | console_handler = logging.StreamHandler(sys.stdout) 25 | console_handler.setLevel(level) 26 | 27 | # Create a formatter 28 | formatter = logging.Formatter( 29 | "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 30 | ) 31 | 32 | # Add the formatter to the console handler 33 | console_handler.setFormatter(formatter) 34 | 35 | # Add the console handler to the logger 36 | logger.addHandler(console_handler) 37 | 38 | return logger 39 | 40 | 41 | # Example usage 42 | if __name__ == "__main__": 43 | logger = get_logger("example_logger", "DEBUG") 44 | logger.debug("This is a debug message") 45 | logger.info("This is an info message") 46 | logger.warning("This is a warning message") 47 | logger.error("This is an error message") 48 | logger.critical("This is a critical message") 49 | -------------------------------------------------------------------------------- /fasthtml/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | 5 | def get_logger(name: str = __name__, level: str = "INFO") -> logging.Logger: 6 | """ 7 | Initialize a simple logger that outputs to the console. 8 | 9 | Args: 10 | name (str): The name of the logger. Defaults to the module name. 11 | level (str): The logging level. Defaults to "INFO". 12 | 13 | Returns: 14 | logging.Logger: A configured logger instance. 15 | """ 16 | # Create a logger 17 | logger = logging.getLogger(name) 18 | 19 | # Set the logging level 20 | level = getattr(logging, level.upper(), logging.INFO) 21 | logger.setLevel(level) 22 | 23 | # Create a console handler and set its level 24 | console_handler = logging.StreamHandler(sys.stdout) 25 | console_handler.setLevel(level) 26 | 27 | # Create a formatter 28 | formatter = logging.Formatter( 29 | "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 30 | ) 31 | 32 | # Add the formatter to the console handler 33 | console_handler.setFormatter(formatter) 34 | 35 | # Add the console handler to the logger 36 | logger.addHandler(console_handler) 37 | 38 | return logger 39 | 40 | 41 | # Example usage 42 | if __name__ == "__main__": 43 | logger = get_logger("example_logger", "DEBUG") 44 | logger.debug("This is a debug message") 45 | logger.info("This is an info message") 46 | logger.warning("This is a warning message") 47 | logger.error("This is an error message") 48 | logger.critical("This is a critical message") 49 | -------------------------------------------------------------------------------- /.learning/fasthtml/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | 5 | def get_logger(name: str = __name__, level: str = "INFO") -> logging.Logger: 6 | """ 7 | Initialize a simple logger that outputs to the console. 8 | 9 | Args: 10 | name (str): The name of the logger. Defaults to the module name. 11 | level (str): The logging level. Defaults to "INFO". 12 | 13 | Returns: 14 | logging.Logger: A configured logger instance. 15 | """ 16 | # Create a logger 17 | logger = logging.getLogger(name) 18 | 19 | # Set the logging level 20 | level = getattr(logging, level.upper(), logging.INFO) 21 | logger.setLevel(level) 22 | 23 | # Create a console handler and set its level 24 | console_handler = logging.StreamHandler(sys.stdout) 25 | console_handler.setLevel(level) 26 | 27 | # Create a formatter 28 | formatter = logging.Formatter( 29 | "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 30 | ) 31 | 32 | # Add the formatter to the console handler 33 | console_handler.setFormatter(formatter) 34 | 35 | # Add the console handler to the logger 36 | logger.addHandler(console_handler) 37 | 38 | return logger 39 | 40 | 41 | # Example usage 42 | if __name__ == "__main__": 43 | logger = get_logger("example_logger", "DEBUG") 44 | logger.debug("This is a debug message") 45 | logger.info("This is an info message") 46 | logger.warning("This is a warning message") 47 | logger.error("This is an error message") 48 | logger.critical("This is a critical message") 49 | -------------------------------------------------------------------------------- /fastapi+svelte/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | 5 | def get_logger(name: str = __name__, level: str = "INFO") -> logging.Logger: 6 | """ 7 | Initialize a simple logger that outputs to the console. 8 | 9 | Args: 10 | name (str): The name of the logger. Defaults to the module name. 11 | level (str): The logging level. Defaults to "INFO". 12 | 13 | Returns: 14 | logging.Logger: A configured logger instance. 15 | """ 16 | # Create a logger 17 | logger = logging.getLogger(name) 18 | 19 | # Set the logging level 20 | level = getattr(logging, level.upper(), logging.INFO) 21 | logger.setLevel(level) 22 | 23 | # Create a console handler and set its level 24 | console_handler = logging.StreamHandler(sys.stdout) 25 | console_handler.setLevel(level) 26 | 27 | # Create a formatter 28 | formatter = logging.Formatter( 29 | "%(asctime)s - %(name)s - %(levelname)s - %(message)s" 30 | ) 31 | 32 | # Add the formatter to the console handler 33 | console_handler.setFormatter(formatter) 34 | 35 | # Add the console handler to the logger 36 | logger.addHandler(console_handler) 37 | 38 | return logger 39 | 40 | 41 | # Example usage 42 | if __name__ == "__main__": 43 | logger = get_logger("example_logger", "DEBUG") 44 | logger.debug("This is a debug message") 45 | logger.info("This is an info message") 46 | logger.warning("This is a warning message") 47 | logger.error("This is an error message") 48 | logger.critical("This is a critical message") 49 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/dashboard/nav-links.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { 4 | UserGroupIcon, 5 | HomeIcon, 6 | DocumentDuplicateIcon, 7 | } from '@heroicons/react/24/outline'; 8 | import Link from 'next/link'; 9 | import { usePathname } from 'next/navigation'; 10 | import clsx from 'clsx'; 11 | 12 | // Map of links to display in the side navigation. 13 | // Depending on the size of the application, this would be stored in a database. 14 | const links = [ 15 | { name: 'Home', href: '/dashboard', icon: HomeIcon }, 16 | { 17 | name: 'Invoices', 18 | href: '/dashboard/invoices', 19 | icon: DocumentDuplicateIcon, 20 | }, 21 | { name: 'Customers', href: '/dashboard/customers', icon: UserGroupIcon }, 22 | ]; 23 | 24 | export default function NavLinks() { 25 | const pathname = usePathname(); 26 | 27 | return ( 28 | <> 29 | {links.map((link) => { 30 | const LinkIcon = link.icon; 31 | return ( 32 | 42 | 43 |

{link.name}

44 | 45 | ); 46 | })} 47 | 48 | ); 49 | } -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/dashboard/invoices/page.tsx: -------------------------------------------------------------------------------- 1 | import Pagination from '@/app/ui/invoices/pagination'; 2 | import Search from '@/app/ui/search'; 3 | import Table from '@/app/ui/invoices/table'; 4 | import { CreateInvoice } from '@/app/ui/invoices/buttons'; 5 | import { lusitana } from '@/app/ui/fonts'; 6 | import { InvoicesTableSkeleton } from '@/app/ui/skeletons'; 7 | import { Suspense } from 'react'; 8 | import { fetchInvoicesPages } from '@/app/lib/data'; 9 | import { Metadata } from 'next'; 10 | 11 | export const metadata: Metadata = { 12 | title: 'Invoices', 13 | }; 14 | 15 | export default async function Page({ 16 | searchParams, 17 | }: { 18 | searchParams?: { 19 | query?: string; 20 | page?: string; 21 | }; 22 | }) { 23 | const query = searchParams?.query || ''; 24 | const currentPage = Number(searchParams?.page) || 1; 25 | const totalPages = await fetchInvoicesPages(query); 26 | return ( 27 |
28 |
29 |

Invoices

30 |
31 |
32 | 33 | 34 |
35 | }> 36 | 37 | 38 |
39 | 40 |
41 | 42 | ); 43 | } -------------------------------------------------------------------------------- /svelte/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 17 | 18 |
19 |

Look at Your Data

20 |
21 | 22 | Download CSV 23 |
24 |
25 |
26 | 27 | 28 | 29 | 70 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/dashboard/cards.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | BanknotesIcon, 3 | ClockIcon, 4 | UserGroupIcon, 5 | InboxIcon, 6 | } from '@heroicons/react/24/outline'; 7 | import { lusitana } from '@/app/ui/fonts'; 8 | import { fetchCardData } from '@/app/lib/data'; 9 | 10 | const iconMap = { 11 | collected: BanknotesIcon, 12 | customers: UserGroupIcon, 13 | pending: ClockIcon, 14 | invoices: InboxIcon, 15 | }; 16 | 17 | export default async function CardWrapper() { 18 | const { 19 | numberOfInvoices, 20 | numberOfCustomers, 21 | totalPaidInvoices, 22 | totalPendingInvoices, 23 | } = await fetchCardData(); 24 | return ( 25 | <> 26 | {/* NOTE: Uncomment this code in Chapter 9 */} 27 | 28 | 29 | 30 | 31 | 36 | 37 | ); 38 | } 39 | 40 | export function Card({ 41 | title, 42 | value, 43 | type, 44 | }: { 45 | title: string; 46 | value: number | string; 47 | type: 'invoices' | 'customers' | 'pending' | 'collected'; 48 | }) { 49 | const Icon = iconMap[type]; 50 | 51 | return ( 52 |
53 |
54 | {Icon ? : null} 55 |

{title}

56 |
57 |

61 | {value} 62 |

63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /fasthtml/main-jeremy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Source: https://gist.github.com/jph00/0590da374a11b8def808c1821abdd42a 3 | """ 4 | from fasthtml.common import * 5 | 6 | db = database(':memory:') 7 | tbl = None 8 | hdrs = (Style(''' 9 | button,input { margin: 0 1rem; } 10 | [role="group"] { border: 1px solid #ccc; } 11 | '''), ) 12 | app, rt = fast_app(live=True, hdrs=hdrs) 13 | 14 | @rt("/") 15 | async def get(): 16 | return Titled("CSV Uploader", 17 | Group( 18 | Input(type="file", name="csv_file", accept=".csv"), 19 | Button("Upload", hx_post="/upload", hx_target="#results", 20 | hx_encoding="multipart/form-data", hx_include='previous input'), 21 | A('Download', href='/download', type="button") 22 | ), 23 | Div(id="results")) 24 | 25 | def render_row(row): 26 | vals = [Td(Input(value=v, name=k)) for k,v in row.items()] 27 | vals.append(Td(Group(Button('delete', hx_get=remove.rt(id=row['id'])), 28 | Button('update', hx_post='/update', hx_include="closest tr")))) 29 | return Tr(*vals, hx_target='closest tr', hx_swap='outerHTML') 30 | 31 | @rt 32 | async def download(): 33 | csv_data = [",".join(map(str, tbl.columns_dict))] 34 | csv_data += [",".join(map(str, row.values())) for row in tbl()] 35 | headers = {'Content-Disposition': 'attachment; filename="data.csv"'} 36 | return Response("\n".join(csv_data), media_type="text/csv", headers=headers) 37 | 38 | @rt('/update') 39 | def post(d:dict): return render_row(tbl.update(d)) 40 | 41 | @rt 42 | def remove(id:int): tbl.delete(id) 43 | 44 | @rt("/upload") 45 | async def post(csv_file: UploadFile): 46 | global tbl 47 | if not csv_file.filename.endswith('.csv'): return "Please upload a CSV file" 48 | tbl = db.import_file('test', await csv_file.read(), pk='id') 49 | header = Tr(*map(Th, tbl.columns_dict)) 50 | vals = [render_row(row) for row in tbl()] 51 | return Table(Thead(header), Tbody(*vals)) 52 | 53 | serve() -------------------------------------------------------------------------------- /nextjs/app/api/data/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { db } from '../../../lib/db'; 3 | 4 | export async function GET() { 5 | try { 6 | const data = db.prepare('SELECT * FROM data').all(); 7 | return NextResponse.json(data); 8 | } catch (error) { 9 | console.error('Error fetching data:', error); 10 | return NextResponse.json({ error: 'Error fetching data' }, { status: 500 }); 11 | } 12 | } 13 | 14 | export async function PUT(req: NextRequest) { 15 | const { id, column, value } = await req.json(); 16 | 17 | try { 18 | // Start a transaction 19 | db.prepare('BEGIN').run(); 20 | 21 | // Update the data 22 | const updateStmt = db.prepare(`UPDATE data SET ${column} = ? WHERE id = ?`); 23 | const result = updateStmt.run(value, id); 24 | 25 | if (result.changes === 0) { 26 | // If no rows were updated, rollback and return an error 27 | db.prepare('ROLLBACK').run(); 28 | return NextResponse.json({ error: 'No rows updated' }, { status: 404 }); 29 | } 30 | 31 | // Commit the transaction 32 | db.prepare('COMMIT').run(); 33 | 34 | return NextResponse.json({ message: 'Data updated successfully' }); 35 | } catch (error) { 36 | // Rollback the transaction in case of an error 37 | db.prepare('ROLLBACK').run(); 38 | console.error('Error updating data:', error); 39 | return NextResponse.json({ error: 'Error updating data' }, { status: 500 }); 40 | } 41 | } 42 | 43 | export async function DELETE(req: NextRequest) { 44 | const { searchParams } = new URL(req.url); 45 | const id = searchParams.get('id'); 46 | 47 | if (!id) { 48 | return NextResponse.json({ error: 'Missing id parameter' }, { status: 400 }); 49 | } 50 | 51 | try { 52 | db.prepare('DELETE FROM data WHERE id = ?').run(id); 53 | return NextResponse.json({ message: 'Row deleted successfully' }); 54 | } catch (error) { 55 | console.error('Error deleting row:', error); 56 | return NextResponse.json({ error: 'Error deleting row' }, { status: 500 }); 57 | } 58 | } -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/page.tsx: -------------------------------------------------------------------------------- 1 | import AcmeLogo from '@/app/ui/acme-logo'; 2 | import { ArrowRightIcon } from '@heroicons/react/24/outline'; 3 | import Link from 'next/link'; 4 | import { lusitana } from '@/app/ui/fonts'; 5 | import Image from 'next/image'; 6 | 7 | 8 | export default function Page() { 9 | return ( 10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 |

18 | Welcome to Acme. This is the example for the{' '} 19 | 20 | Next.js Learn Course 21 | 22 | , brought to you by Vercel. 23 |

24 | 28 | Log in 29 | 30 |
31 |
32 | {/* Add Hero Images Here */} 33 | 40 | 47 |
48 |
49 |
50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building a small app with various frameworks 2 | 3 | My current approach of building apps is based on fastapi + jinja/html + css + some js. 4 | - Pros: Reliable stack that I'm familiar with 5 | - Cons: Outdated? 6 | 7 | Recently, I've been thinking about building apps in typescript/fastHTML and ran a poll [here](https://x.com/eugeneyan/status/1828447283811402006). This is an extension of the poll, where I'll build the same app five times using FastAPI + HTML, FastHTML, Next.js, and Svelte. The intent was to understand which suited me best, both in terms of ease of development, learning, and fun. 8 | 9 | The app will enable users to: 10 | - `upload` a csv file to initialize a sqlite table 11 | - `view` the table via the browser 12 | - `update` a row of the sqlite database 13 | - `delete` a row of the sqlite database 14 | - `download` the table of the sqlite table 15 | 16 | To keep things simple for this comparison, we'll: 17 | - Have a single table for the data 18 | - Not have features for table deletes or overwrites once the single table has been initialized 19 | 20 | > Also see the detailed writeup and some thoughts [here](https://eugeneyan.com/writing/web-frameworks/). 21 | 22 | ## Setup 23 | ``` 24 | # Install Python + FastAPI + FastHTML 25 | # Install uv: https://docs.astral.sh/uv/getting-started/installation/ 26 | uv init # Create a new python project 27 | uv sync # Install dependencies 28 | 29 | # Install Next.js + Svelte 30 | npm install -g pnpm # Install pnpm: https://pnpm.io 31 | 32 | # Next.js 33 | cd nextjs 34 | pnpm install # Install dependencies 35 | 36 | # Svelte 37 | cd svelte 38 | pnpm install # Install dependencies 39 | ``` 40 | 41 | ## Running the apps 42 | 43 | ### FastAPI + Jinja + CSS + JS 44 | ``` 45 | cd fastapi 46 | uv run uvicorn main:app --reload 47 | # Go to http://localhost:8000/ 48 | ``` 49 | 50 | ### FastHTML 51 | ``` 52 | cd fasthtml 53 | uv run python main.py 54 | # Go to http://localhost:5001 55 | ``` 56 | 57 | ### Next.js 58 | ``` 59 | cd nextjs 60 | pnpm run dev 61 | # Go to http://localhost:3000/ 62 | ``` 63 | 64 | ### Svelte 65 | ``` 66 | cd svelte 67 | pnpm run dev 68 | # Go to http://localhost:5173/ 69 | ``` 70 | 71 | ### FastAPI + Svelte 72 | ``` 73 | cd fastapi+svelte 74 | uv run uvicorn main:app --reload 75 | 76 | # Open another terminal 77 | cd svelte-app 78 | pnpm run dev 79 | 80 | # Go to http://localhost:5173/ 81 | ``` -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/dashboard/revenue-chart.tsx: -------------------------------------------------------------------------------- 1 | import { generateYAxis } from '@/app/lib/utils'; 2 | import { CalendarIcon } from '@heroicons/react/24/outline'; 3 | import { lusitana } from '@/app/ui/fonts'; 4 | import { fetchRevenue } from '@/app/lib/data'; 5 | import { Revenue } from '@/app/lib/definitions'; 6 | 7 | // This component is representational only. 8 | // For data visualization UI, check out: 9 | // https://www.tremor.so/ 10 | // https://www.chartjs.org/ 11 | // https://airbnb.io/visx/ 12 | 13 | export default async function RevenueChart() { 14 | const revenue = await fetchRevenue(); 15 | 16 | const chartHeight = 350; 17 | // NOTE: Uncomment this code in Chapter 7 18 | 19 | const { yAxisLabels, topLabel } = generateYAxis(revenue); 20 | 21 | if (!revenue || revenue.length === 0) { 22 | return

No data available.

; 23 | } 24 | 25 | return ( 26 |
27 |

28 | Recent Revenue 29 |

30 | {/* NOTE: Uncomment this code in Chapter 7 */} 31 | 32 |
33 |
34 |
38 | {yAxisLabels.map((label) => ( 39 |

{label}

40 | ))} 41 |
42 | 43 | {revenue.map((month) => ( 44 |
45 |
51 |

52 | {month.month} 53 |

54 |
55 | ))} 56 |
57 |
58 | 59 |

Last 12 months

60 |
61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/dashboard/latest-invoices.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowPathIcon } from '@heroicons/react/24/outline'; 2 | import clsx from 'clsx'; 3 | import Image from 'next/image'; 4 | import { lusitana } from '@/app/ui/fonts'; 5 | import { LatestInvoice } from '@/app/lib/definitions'; 6 | import { fetchLatestInvoices } from '@/app/lib/data'; 7 | 8 | export default async function LatestInvoices() { 9 | const latestInvoices = await fetchLatestInvoices(); 10 | 11 | return ( 12 |
13 |

14 | Latest Invoices 15 |

16 |
17 | {/* NOTE: Uncomment this code in Chapter 7 */} 18 | 19 |
20 | {latestInvoices.map((invoice, i) => { 21 | return ( 22 |
31 |
32 | 39 |
40 |

41 | {invoice.name} 42 |

43 |

44 | {invoice.email} 45 |

46 |
47 |
48 |

51 | {invoice.amount} 52 |

53 |
54 | ); 55 | })} 56 |
57 |
58 | 59 |

Updated just now

60 |
61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /svelte/src/lib/components/Table.svelte: -------------------------------------------------------------------------------- 1 | 41 | 42 |
43 | 44 | 45 | {#each Object.keys(tableData[0] || {}) as header} 46 | 47 | {/each} 48 | 49 | 50 | 51 | 52 | {#each tableData as row, rowIndex} 53 | 54 | {#each Object.entries(row) as [key, value]} 55 | 67 | {/each} 68 | 71 | 72 | {/each} 73 | 74 |
{header}Actions
56 |
startEditing(rowIndex, key, value)} 60 | on:input={(e) => editedValue = e.target.textContent} 61 | on:keydown={(e) => handleKeyDown(e, row.id)} 62 | on:blur={() => saveEdit(row.id)} 63 | > 64 | {value} 65 |
66 |
69 | 70 |
75 | 76 | -------------------------------------------------------------------------------- /.learning/fasthtml/imagegen.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import replicate 4 | import requests 5 | from fastcore.parallel import threaded 6 | from fasthtml.common import * 7 | from PIL import Image 8 | 9 | app = FastHTML(hdrs=(picolink,)) 10 | 11 | # Replicate setup (for image generation) 12 | replicate_api_token = os.environ["REPLICATE_API_KEY"] 13 | client = replicate.Client(api_token=replicate_api_token) 14 | 15 | # Store our generations 16 | generations = [] 17 | folder = "gens/" 18 | os.makedirs(folder, exist_ok=True) 19 | 20 | 21 | # Main page 22 | @app.get("/") 23 | def home(): 24 | inp = Input(id="new-prompt", name="prompt", placeholder="Enter a prompt") 25 | add = Form( 26 | Group(inp, Button("Generate")), 27 | hx_post="/", 28 | target_id="gen-list", 29 | hx_swap="afterbegin", 30 | ) 31 | gen_list = Div(id="gen-list") 32 | return Title("Image Generation Demo"), Main( 33 | H1("Magic Image Generation"), add, gen_list, cls="container" 34 | ) 35 | 36 | 37 | # A pending preview keeps polling this route until we return the image preview 38 | def generation_preview(id): 39 | if os.path.exists(f"gens/{id}.png"): 40 | return Div(Img(src=f"/gens/{id}.png"), id=f"gen-{id}") 41 | else: 42 | return Div( 43 | "Generating...", 44 | id=f"gen-{id}", 45 | hx_post=f"/generations/{id}", 46 | hx_trigger="every 1s", 47 | hx_swap="outerHTML", 48 | ) 49 | 50 | 51 | @app.post("/generations/{id}") 52 | def get(id: int): 53 | return generation_preview(id) 54 | 55 | 56 | # For images, CSS, etc. 57 | @app.get("/{fname:path}.{ext:static}") 58 | def static(fname: str, ext: str): 59 | return FileResponse(f"{fname}.{ext}") 60 | 61 | 62 | # Generation route 63 | @app.post("/") 64 | def post(prompt: str): 65 | id = len(generations) 66 | generate_and_save(prompt, id) 67 | generations.append(prompt) 68 | clear_input = Input( 69 | id="new-prompt", name="prompt", placeholder="Enter a prompt", hx_swap_oob="true" 70 | ) 71 | return generation_preview(id), clear_input 72 | 73 | 74 | # Generate an image and save it to the folder (in a separate thread) 75 | @threaded 76 | def generate_and_save(prompt, id): 77 | output = client.run( 78 | "playgroundai/playground-v2.5-1024px-aesthetic:a45f82a1382bed5c7aeb861dac7c7d191b0fdf74d8d57c4a0e6ed7d4d0bf7d24", 79 | input={ 80 | "width": 1024, 81 | "height": 1024, 82 | "prompt": prompt, 83 | "scheduler": "DPMSolver++", 84 | "num_outputs": 1, 85 | "guidance_scale": 3, 86 | "apply_watermark": True, 87 | "negative_prompt": "ugly, deformed, noisy, blurry, distorted", 88 | "prompt_strength": 0.8, 89 | "num_inference_steps": 25, 90 | }, 91 | ) 92 | Image.open(requests.get(output[0], stream=True).raw).save(f"{folder}/{id}.png") 93 | return True 94 | 95 | 96 | serve() 97 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/login-form.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { lusitana } from '@/app/ui/fonts'; 4 | import { useFormState } from 'react-dom'; 5 | import { authenticate } from '@/app/lib/actions'; 6 | import { 7 | AtSymbolIcon, 8 | KeyIcon, 9 | ExclamationCircleIcon, 10 | } from '@heroicons/react/24/outline'; 11 | import { ArrowRightIcon } from '@heroicons/react/20/solid'; 12 | import { Button } from './button'; 13 | 14 | export default function LoginForm() { 15 | const [errorMessage, dispatch] = useFormState(authenticate, undefined); 16 | 17 | return ( 18 |
19 |
20 |

21 | Please log in to continue. 22 |

23 |
24 |
25 | 31 |
32 | 40 | 41 |
42 |
43 |
44 | 50 |
51 | 60 | 61 |
62 |
63 |
64 | 67 |
68 | {errorMessage && ( 69 | <> 70 | 71 |

{errorMessage}

72 | 73 | )} 74 |
75 |
76 |
77 | ); 78 | } 79 | -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/seed/route.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcrypt'; 2 | import { db } from '@vercel/postgres'; 3 | import { invoices, customers, revenue, users } from '../lib/placeholder-data'; 4 | 5 | const client = await db.connect(); 6 | 7 | async function seedUsers() { 8 | await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`; 9 | await client.sql` 10 | CREATE TABLE IF NOT EXISTS users ( 11 | id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, 12 | name VARCHAR(255) NOT NULL, 13 | email TEXT NOT NULL UNIQUE, 14 | password TEXT NOT NULL 15 | ); 16 | `; 17 | 18 | const insertedUsers = await Promise.all( 19 | users.map(async (user) => { 20 | const hashedPassword = await bcrypt.hash(user.password, 10); 21 | return client.sql` 22 | INSERT INTO users (id, name, email, password) 23 | VALUES (${user.id}, ${user.name}, ${user.email}, ${hashedPassword}) 24 | ON CONFLICT (id) DO NOTHING; 25 | `; 26 | }), 27 | ); 28 | 29 | return insertedUsers; 30 | } 31 | 32 | async function seedInvoices() { 33 | await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`; 34 | 35 | await client.sql` 36 | CREATE TABLE IF NOT EXISTS invoices ( 37 | id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, 38 | customer_id UUID NOT NULL, 39 | amount INT NOT NULL, 40 | status VARCHAR(255) NOT NULL, 41 | date DATE NOT NULL 42 | ); 43 | `; 44 | 45 | const insertedInvoices = await Promise.all( 46 | invoices.map( 47 | (invoice) => client.sql` 48 | INSERT INTO invoices (customer_id, amount, status, date) 49 | VALUES (${invoice.customer_id}, ${invoice.amount}, ${invoice.status}, ${invoice.date}) 50 | ON CONFLICT (id) DO NOTHING; 51 | `, 52 | ), 53 | ); 54 | 55 | return insertedInvoices; 56 | } 57 | 58 | async function seedCustomers() { 59 | await client.sql`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`; 60 | 61 | await client.sql` 62 | CREATE TABLE IF NOT EXISTS customers ( 63 | id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, 64 | name VARCHAR(255) NOT NULL, 65 | email VARCHAR(255) NOT NULL, 66 | image_url VARCHAR(255) NOT NULL 67 | ); 68 | `; 69 | 70 | const insertedCustomers = await Promise.all( 71 | customers.map( 72 | (customer) => client.sql` 73 | INSERT INTO customers (id, name, email, image_url) 74 | VALUES (${customer.id}, ${customer.name}, ${customer.email}, ${customer.image_url}) 75 | ON CONFLICT (id) DO NOTHING; 76 | `, 77 | ), 78 | ); 79 | 80 | return insertedCustomers; 81 | } 82 | 83 | async function seedRevenue() { 84 | await client.sql` 85 | CREATE TABLE IF NOT EXISTS revenue ( 86 | month VARCHAR(4) NOT NULL UNIQUE, 87 | revenue INT NOT NULL 88 | ); 89 | `; 90 | 91 | const insertedRevenue = await Promise.all( 92 | revenue.map( 93 | (rev) => client.sql` 94 | INSERT INTO revenue (month, revenue) 95 | VALUES (${rev.month}, ${rev.revenue}) 96 | ON CONFLICT (month) DO NOTHING; 97 | `, 98 | ), 99 | ); 100 | 101 | return insertedRevenue; 102 | } 103 | 104 | export async function GET() { 105 | try { 106 | await client.sql`BEGIN`; 107 | await seedUsers(); 108 | await seedCustomers(); 109 | await seedInvoices(); 110 | await seedRevenue(); 111 | await client.sql`COMMIT`; 112 | 113 | return Response.json({ message: 'Database seeded successfully' }); 114 | } catch (error) { 115 | await client.sql`ROLLBACK`; 116 | return Response.json({ error }, { status: 500 }); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /fastapi+svelte/main.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import io 3 | import sqlite3 4 | 5 | from logger import get_logger 6 | 7 | from fastapi import FastAPI, File, HTTPException, UploadFile 8 | from fastapi.middleware.cors import CORSMiddleware 9 | from fastapi.responses import FileResponse 10 | 11 | app = FastAPI() 12 | logger = get_logger("fastapi+svelte", "INFO") 13 | 14 | # Enable CORS 15 | app.add_middleware( 16 | CORSMiddleware, 17 | allow_origins=["http://localhost:5173"], # Add your Svelte app's URL 18 | allow_credentials=True, 19 | allow_methods=["*"], 20 | allow_headers=["*"], 21 | ) 22 | 23 | DB_NAME = "database.sqlite" 24 | 25 | 26 | @app.post("/upload") 27 | async def upload_csv(file: UploadFile = File(...)): 28 | if not file.filename.endswith(".csv"): 29 | raise HTTPException(status_code=400, detail="Only CSV files are allowed") 30 | 31 | try: 32 | content = await file.read() 33 | csv_reader = csv.reader(io.StringIO(content.decode("utf-8"))) 34 | headers = next(csv_reader) 35 | 36 | with sqlite3.connect(DB_NAME) as conn: 37 | cursor = conn.cursor() 38 | cursor.execute("DROP TABLE IF EXISTS data") 39 | cursor.execute(f"CREATE TABLE data ({', '.join(headers)})") 40 | 41 | for row in csv_reader: 42 | if len(row) == len(headers): 43 | cursor.execute( 44 | f"INSERT INTO data VALUES ({', '.join(['?' for _ in row])})", 45 | row, 46 | ) 47 | 48 | logger.info("CSV uploaded and database created successfully") 49 | return {"message": "CSV uploaded and database created successfully"} 50 | except Exception as e: 51 | logger.error(f"Error uploading CSV: {str(e)}") 52 | raise HTTPException(status_code=500, detail=f"Error uploading CSV: {str(e)}") 53 | 54 | 55 | @app.get("/data") 56 | async def get_data(): 57 | try: 58 | with sqlite3.connect(DB_NAME) as conn: 59 | cursor = conn.cursor() 60 | cursor.execute("SELECT * FROM data") 61 | data = cursor.fetchall() 62 | headers = [description[0] for description in cursor.description] 63 | return {"headers": headers, "data": data} 64 | except sqlite3.OperationalError: 65 | return {"headers": [], "data": []} 66 | except Exception as e: 67 | logger.error(f"Error fetching data: {str(e)}") 68 | return {"headers": [], "data": [], "error": str(e)} 69 | 70 | 71 | @app.put("/update/{row_id}") 72 | async def update_row(row_id: int, updated_data: dict): 73 | conn = sqlite3.connect(DB_NAME) 74 | cursor = conn.cursor() 75 | 76 | set_clause = ", ".join([f"{key} = ?" for key in updated_data.keys()]) 77 | values = list(updated_data.values()) 78 | values.append(row_id) 79 | 80 | cursor.execute(f"UPDATE data SET {set_clause} WHERE rowid = ?", values) 81 | conn.commit() 82 | conn.close() 83 | 84 | return {"message": f"Row {row_id} updated successfully"} 85 | 86 | 87 | @app.delete("/delete/{row_id}") 88 | async def delete_row(row_id: int): 89 | conn = sqlite3.connect(DB_NAME) 90 | cursor = conn.cursor() 91 | cursor.execute("DELETE FROM data WHERE rowid = ?", (row_id,)) 92 | conn.commit() 93 | conn.close() 94 | return {"message": f"Row {row_id} deleted successfully"} 95 | 96 | 97 | @app.get("/download") 98 | async def download_csv(): 99 | conn = sqlite3.connect(DB_NAME) 100 | cursor = conn.cursor() 101 | cursor.execute("SELECT * FROM data") 102 | data = cursor.fetchall() 103 | headers = [description[0] for description in cursor.description] 104 | conn.close() 105 | 106 | output = io.StringIO() 107 | writer = csv.writer(output) 108 | writer.writerow(headers) 109 | writer.writerows(data) 110 | 111 | output.seek(0) 112 | 113 | return FileResponse( 114 | io.BytesIO(output.getvalue().encode()), 115 | media_type="text/csv", 116 | filename="data.csv", 117 | ) 118 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control 110 | .pdm.toml 111 | .pdm-python 112 | .pdm-build/ 113 | 114 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 115 | __pypackages__/ 116 | 117 | # Celery stuff 118 | celerybeat-schedule 119 | celerybeat.pid 120 | 121 | # SageMath parsed files 122 | *.sage.py 123 | 124 | # Environments 125 | .env 126 | .venv 127 | env/ 128 | venv/ 129 | ENV/ 130 | env.bak/ 131 | venv.bak/ 132 | 133 | # Spyder project settings 134 | .spyderproject 135 | .spyproject 136 | 137 | # Rope project settings 138 | .ropeproject 139 | 140 | # mkdocs documentation 141 | /site 142 | 143 | # mypy 144 | .mypy_cache/ 145 | .dmypy.json 146 | dmypy.json 147 | 148 | # Pyre type checker 149 | .pyre/ 150 | 151 | # pytype static type analyzer 152 | .pytype/ 153 | 154 | # Cython debug symbols 155 | cython_debug/ 156 | 157 | # PyCharm 158 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 159 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 160 | # and can be added to the global gitignore or merged into this file. For a more nuclear 161 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 162 | #.idea/ 163 | 164 | # JS 165 | **/node_modules 166 | **/.next 167 | **/next-env.d.ts 168 | **/.svelte-kit 169 | 170 | # Other files 171 | .DS_Store 172 | .vscode 173 | database.db -------------------------------------------------------------------------------- /.learning/nextjs/nextjs-dashboard/app/ui/invoices/pagination.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ArrowLeftIcon, ArrowRightIcon } from '@heroicons/react/24/outline'; 4 | import clsx from 'clsx'; 5 | import Link from 'next/link'; 6 | import { generatePagination } from '@/app/lib/utils'; 7 | import { usePathname, useRouter, useSearchParams } from 'next/navigation'; 8 | 9 | export default function Pagination({ totalPages }: { totalPages: number }) { 10 | // NOTE: Uncomment this code in Chapter 11 11 | const pathname = usePathname(); 12 | const searchParams = useSearchParams(); 13 | const currentPage = Number(searchParams.get('page')) || 1; 14 | const allPages = generatePagination(currentPage, totalPages); 15 | 16 | const createPageURL = (pageNumber: number | string) => { 17 | const params = new URLSearchParams(searchParams); 18 | params.set('page', pageNumber.toString()); 19 | return `${pathname}?${params.toString()}`; 20 | }; 21 | 22 | return ( 23 | <> 24 | {/* NOTE: Uncomment this code in Chapter 11 */} 25 | 26 |
27 | 32 | 33 |
34 | {allPages.map((page, index) => { 35 | let position: 'first' | 'last' | 'single' | 'middle' | undefined; 36 | 37 | if (index === 0) position = 'first'; 38 | if (index === allPages.length - 1) position = 'last'; 39 | if (allPages.length === 1) position = 'single'; 40 | if (page === '...') position = 'middle'; 41 | 42 | return ( 43 | 50 | ); 51 | })} 52 |
53 | 54 | = totalPages} 58 | /> 59 |
60 | 61 | ); 62 | } 63 | 64 | function PaginationNumber({ 65 | page, 66 | href, 67 | isActive, 68 | position, 69 | }: { 70 | page: number | string; 71 | href: string; 72 | position?: 'first' | 'last' | 'middle' | 'single'; 73 | isActive: boolean; 74 | }) { 75 | const className = clsx( 76 | 'flex h-10 w-10 items-center justify-center text-sm border', 77 | { 78 | 'rounded-l-md': position === 'first' || position === 'single', 79 | 'rounded-r-md': position === 'last' || position === 'single', 80 | 'z-10 bg-blue-600 border-blue-600 text-white': isActive, 81 | 'hover:bg-gray-100': !isActive && position !== 'middle', 82 | 'text-gray-300': position === 'middle', 83 | }, 84 | ); 85 | 86 | return isActive || position === 'middle' ? ( 87 |
{page}
88 | ) : ( 89 | 90 | {page} 91 | 92 | ); 93 | } 94 | 95 | function PaginationArrow({ 96 | href, 97 | direction, 98 | isDisabled, 99 | }: { 100 | href: string; 101 | direction: 'left' | 'right'; 102 | isDisabled?: boolean; 103 | }) { 104 | const className = clsx( 105 | 'flex h-10 w-10 items-center justify-center rounded-md border', 106 | { 107 | 'pointer-events-none text-gray-300': isDisabled, 108 | 'hover:bg-gray-100': !isDisabled, 109 | 'mr-2 md:mr-4': direction === 'left', 110 | 'ml-2 md:ml-4': direction === 'right', 111 | }, 112 | ); 113 | 114 | const icon = 115 | direction === 'left' ? ( 116 | 117 | ) : ( 118 | 119 | ); 120 | 121 | return isDisabled ? ( 122 |
{icon}
123 | ) : ( 124 | 125 | {icon} 126 | 127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /nextjs/components/DataTable.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useEffect } from 'react'; 4 | 5 | interface RowData { 6 | id: number; 7 | [key: string]: any; 8 | } 9 | 10 | export default function DataTable() { 11 | const [data, setData] = useState([]); 12 | const [editingCell, setEditingCell] = useState<{ rowId: number; column: string } | null>(null); 13 | 14 | useEffect(() => { 15 | fetchData(); 16 | }, []); 17 | 18 | const fetchData = async () => { 19 | try { 20 | const response = await fetch('/api/data'); 21 | const jsonData = await response.json(); 22 | setData(Array.isArray(jsonData) ? jsonData : []); 23 | } catch (error) { 24 | console.error('Error fetching data:', error); 25 | setData([]); 26 | } 27 | }; 28 | 29 | const handleCellEdit = async (rowId: number, column: string, value: string) => { 30 | try { 31 | const response = await fetch('/api/data', { 32 | method: 'PUT', 33 | headers: { 'Content-Type': 'application/json' }, 34 | body: JSON.stringify({ id: rowId, column, value }), 35 | }); 36 | if (response.ok) { 37 | // Update local state immediately 38 | setData(prevData => prevData.map(row => 39 | row.id === rowId ? { ...row, [column]: value } : row 40 | )); 41 | } else { 42 | const errorData = await response.json(); 43 | alert(`Failed to update data: ${errorData.error}`); 44 | } 45 | } catch (error) { 46 | console.error('Error updating data:', error); 47 | alert('Failed to update data'); 48 | } 49 | setEditingCell(null); 50 | }; 51 | 52 | const handleDeleteRow = async (rowId: number) => { 53 | try { 54 | const response = await fetch(`/api/data?id=${rowId}`, { method: 'DELETE' }); 55 | if (response.ok) { 56 | fetchData(); 57 | } else { 58 | alert('Failed to delete row'); 59 | } 60 | } catch (error) { 61 | console.error('Error deleting row:', error); 62 | alert('Failed to delete row'); 63 | } 64 | }; 65 | 66 | if (!Array.isArray(data) || data.length === 0) return

No data available

; 67 | 68 | // Include 'id' in the columns 69 | const columns = Object.keys(data[0]); 70 | 71 | return ( 72 | 73 | 74 | 75 | {columns.map((col) => ( 76 | 79 | ))} 80 | 81 | 82 | 83 | 84 | {data.map((row) => ( 85 | 86 | {columns.map((col) => ( 87 |
77 | {col} 78 | Actions
col !== 'id' && setEditingCell({ rowId: row.id, column: col })} 91 | > 92 | {editingCell?.rowId === row.id && editingCell?.column === col ? ( 93 |