init project structer
This commit is contained in:
2
apps/server-go/.gitkeep
Normal file
2
apps/server-go/.gitkeep
Normal file
@@ -0,0 +1,2 @@
|
||||
# This ensures the directory is tracked by git
|
||||
|
||||
5
apps/server-go/go.mod
Normal file
5
apps/server-go/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/catsave/server
|
||||
|
||||
go 1.21
|
||||
|
||||
require github.com/gorilla/mux v1.8.1
|
||||
2
apps/server-go/go.sum
Normal file
2
apps/server-go/go.sum
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
80
apps/server-go/main.go
Normal file
80
apps/server-go/main.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"io/fs"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
//go:embed static/*
|
||||
var staticFiles embed.FS
|
||||
|
||||
type HealthResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
r := mux.NewRouter()
|
||||
|
||||
// API Routes
|
||||
api := r.PathPrefix("/api").Subrouter()
|
||||
api.HandleFunc("/health", healthHandler).Methods("GET")
|
||||
api.HandleFunc("/cats", catsHandler).Methods("GET")
|
||||
|
||||
// Serve static files (Next.js build)
|
||||
staticFS, err := fs.Sub(staticFiles, "static")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Serve static files
|
||||
r.PathPrefix("/").Handler(http.FileServer(http.FS(staticFS)))
|
||||
|
||||
port := os.Getenv("PORT")
|
||||
if port == "" {
|
||||
port = "8080"
|
||||
}
|
||||
|
||||
log.Printf("🚀 CatSave server starting on http://localhost:%s\n", port)
|
||||
log.Printf("📱 Web UI: http://localhost:%s\n", port)
|
||||
log.Printf("🔌 API: http://localhost:%s/api/health\n", port)
|
||||
|
||||
if err := http.ListenAndServe(":"+port, r); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func healthHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
response := HealthResponse{
|
||||
Status: "ok",
|
||||
Message: "Go backend is running! 🐹",
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func catsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
cats := []map[string]interface{}{
|
||||
{"id": 1, "name": "Whiskers", "breed": "Persian"},
|
||||
{"id": 2, "name": "Shadow", "breed": "Siamese"},
|
||||
{"id": 3, "name": "Luna", "breed": "British Shorthair"},
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"message": "Hello from Go!",
|
||||
"cats": cats,
|
||||
})
|
||||
}
|
||||
4
apps/web/.eslintrc.json
Normal file
4
apps/web/.eslintrc.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
|
||||
5
apps/web/next-env.d.ts
vendored
Normal file
5
apps/web/next-env.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
21
apps/web/next.config.js
Normal file
21
apps/web/next.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
|
||||
// Static Export - Build to pure HTML/CSS/JS
|
||||
output: 'export',
|
||||
|
||||
// Disable features not supported in static export
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
|
||||
// Optional: add trailing slash
|
||||
trailingSlash: true,
|
||||
|
||||
// Optional: if Go serves from /app path
|
||||
// basePath: '/app',
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
|
||||
27
apps/web/package.json
Normal file
27
apps/web/package.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "@catsave/web",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^14.2.5",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.14.12",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "^14.2.5",
|
||||
"postcss": "^8.4.39",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"typescript": "^5.5.4"
|
||||
}
|
||||
}
|
||||
|
||||
10
apps/web/postcss.config.mjs
Normal file
10
apps/web/postcss.config.mjs
Normal file
@@ -0,0 +1,10 @@
|
||||
/** @type {import('postcss-load-config').Config} */
|
||||
const config = {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
24
apps/web/src/app/globals.css
Normal file
24
apps/web/src/app/globals.css
Normal file
@@ -0,0 +1,24 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
max-width: 100vw;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
20
apps/web/src/app/layout.tsx
Normal file
20
apps/web/src/app/layout.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { Metadata } from 'next'
|
||||
import './globals.css'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'CatSave',
|
||||
description: 'Go + Next.js Single Binary Application',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
|
||||
72
apps/web/src/app/page.tsx
Normal file
72
apps/web/src/app/page.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
|
||||
export default function Home() {
|
||||
const [apiData, setApiData] = useState<any>(null)
|
||||
|
||||
useEffect(() => {
|
||||
// Call Go API
|
||||
fetch('/api/health')
|
||||
.then(res => res.json())
|
||||
.then(data => setApiData(data))
|
||||
.catch(err => console.error(err))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-gradient-to-br from-gray-50 via-blue-50 to-indigo-50 flex items-center justify-center p-6">
|
||||
<div className="max-w-2xl w-full">
|
||||
<div className="text-center mb-8">
|
||||
<div className="inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-3xl shadow-lg mb-6">
|
||||
<span className="text-5xl">🐱</span>
|
||||
</div>
|
||||
<h1 className="text-5xl font-bold text-gray-900 mb-4">
|
||||
CatSave
|
||||
</h1>
|
||||
<p className="text-xl text-gray-600 mb-8">
|
||||
Go + Next.js Single Binary
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-white rounded-3xl shadow-2xl p-8 space-y-6">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl p-6 border-2 border-blue-100">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-2">Frontend</h3>
|
||||
<p className="text-sm text-gray-600">Next.js 14 + React 18</p>
|
||||
<p className="text-xs text-blue-600 mt-2">✅ Static Export</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-green-50 to-emerald-50 rounded-xl p-6 border-2 border-green-100">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-2">Backend</h3>
|
||||
<p className="text-sm text-gray-600">Go 1.21+</p>
|
||||
<p className="text-xs text-green-600 mt-2">✅ Embedded</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-gradient-to-br from-purple-50 to-pink-50 rounded-xl p-6 border-2 border-purple-100">
|
||||
<h3 className="text-lg font-bold text-gray-900 mb-3">API Status</h3>
|
||||
{apiData ? (
|
||||
<div className="space-y-2 text-sm">
|
||||
<p>✅ Status: <span className="font-mono text-green-600">{apiData.status}</span></p>
|
||||
<p>⏰ Time: <span className="font-mono text-gray-600">{apiData.timestamp}</span></p>
|
||||
<p>🐹 Server: <span className="font-mono text-blue-600">{apiData.message}</span></p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-gray-500">Loading...</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="text-center pt-4">
|
||||
<a
|
||||
href="/setup"
|
||||
className="inline-block px-8 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 text-white font-semibold rounded-xl shadow-lg transition-all"
|
||||
>
|
||||
Setup Wizard →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
25
apps/web/src/app/setup/page.tsx
Normal file
25
apps/web/src/app/setup/page.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
'use client'
|
||||
|
||||
export default function SetupPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-gradient-to-br from-gray-50 via-blue-50 to-indigo-50 flex items-center justify-center p-6">
|
||||
<div className="max-w-2xl w-full">
|
||||
<div className="bg-white rounded-3xl shadow-2xl p-8">
|
||||
<h1 className="text-3xl font-bold text-gray-900 mb-4">
|
||||
Setup Wizard
|
||||
</h1>
|
||||
<p className="text-gray-600 mb-6">
|
||||
Configuration page coming soon...
|
||||
</p>
|
||||
<a
|
||||
href="/"
|
||||
className="inline-block px-6 py-2 bg-gray-200 hover:bg-gray-300 text-gray-800 font-medium rounded-lg transition-all"
|
||||
>
|
||||
← Back to Home
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
16
apps/web/tailwind.config.ts
Normal file
16
apps/web/tailwind.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { Config } from "tailwindcss";
|
||||
|
||||
const config: Config = {
|
||||
content: [
|
||||
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
28
apps/web/tsconfig.json
Normal file
28
apps/web/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user