Added Docker
This commit is contained in:
@@ -1,4 +1,13 @@
|
||||
node_modules
|
||||
.nuxt
|
||||
dist
|
||||
.git
|
||||
npm-debug.log
|
||||
package-lock.json
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
.env
|
||||
.nyc_output
|
||||
coverage
|
||||
.vscode
|
||||
.idea
|
||||
*.log
|
||||
.DS_Store
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -10,8 +10,7 @@ dist
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
*.log*
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
@@ -22,3 +21,5 @@ logs
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
|
||||
|
||||
8
.sequelizerc
Normal file
8
.sequelizerc
Normal file
@@ -0,0 +1,8 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
'config': path.resolve('server/database', 'config.js'),
|
||||
'models-path': path.resolve('server/database', 'models'),
|
||||
'seeders-path': path.resolve('server/database', 'seeders'),
|
||||
'migrations-path': path.resolve('server/database', 'migrations')
|
||||
};
|
||||
14
Dockerfile
14
Dockerfile
@@ -1,12 +1,12 @@
|
||||
FROM node:20-alpine
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
WORKDIR /app
|
||||
|
||||
RUN apk add --no-cache git bash
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
|
||||
COPY entrypoint.sh /usr/src/app/entrypoint.sh
|
||||
RUN chmod +x /usr/src/app/entrypoint.sh
|
||||
COPY entrypoint.sh /app/entrypoint.sh
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
|
||||
EXPOSE 1337
|
||||
175
README.md
175
README.md
@@ -1,75 +1,144 @@
|
||||
# Nuxt UI Starter
|
||||
# Nuxt 4 met Database
|
||||
|
||||
Look at [Nuxt docs](https://nuxt.com/docs/getting-started/introduction) and [Nuxt UI docs](https://ui.nuxt.com) to learn more.
|
||||
Een moderne Nuxt 4 applicatie met PostgreSQL database, draaiend via Docker Compose.
|
||||
|
||||
## Setup
|
||||
## Features
|
||||
|
||||
Make sure to install the dependencies:
|
||||
- 🚀 Nuxt 4 met TypeScript
|
||||
- 🐘 PostgreSQL database
|
||||
- 🐳 Docker & Docker Compose
|
||||
- 🎨 Tailwind CSS voor styling
|
||||
- 📊 Sequelize ORM voor database management
|
||||
- 🔄 API routes voor CRUD operaties
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Clone en setup
|
||||
```bash
|
||||
# npm
|
||||
git clone <repository-url>
|
||||
cd nuxt-deploy
|
||||
```
|
||||
|
||||
### 2. Start met Docker Compose
|
||||
```bash
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
### 3. Database migratie (eerste keer)
|
||||
```bash
|
||||
# In een nieuwe terminal
|
||||
docker-compose exec nuxt-app npm run db:migrate
|
||||
```
|
||||
|
||||
### 4. Open de applicatie
|
||||
Ga naar [http://localhost:3000](http://localhost:3000)
|
||||
|
||||
## Development
|
||||
|
||||
### Lokale development (zonder Docker)
|
||||
```bash
|
||||
# Installeer dependencies
|
||||
npm install
|
||||
|
||||
# pnpm
|
||||
pnpm install
|
||||
# Start PostgreSQL (via Docker)
|
||||
docker-compose up postgres -d
|
||||
|
||||
# yarn
|
||||
yarn install
|
||||
# Setup database
|
||||
npm run db:migrate
|
||||
|
||||
# bun
|
||||
bun install
|
||||
```
|
||||
|
||||
## Development Server
|
||||
|
||||
Start the development server on `http://localhost:3000`:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
# Start development server
|
||||
npm run dev
|
||||
|
||||
# pnpm
|
||||
pnpm run dev
|
||||
|
||||
# yarn
|
||||
yarn dev
|
||||
|
||||
# bun
|
||||
bun run dev
|
||||
```
|
||||
|
||||
## Production
|
||||
### Database management
|
||||
```bash
|
||||
# Run migrations
|
||||
npm run db:migrate
|
||||
|
||||
Build the application for production:
|
||||
# Create database
|
||||
npm run db:create
|
||||
|
||||
# Seed database
|
||||
npm run db:seed
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Users
|
||||
- `GET /api/users` - Alle gebruikers ophalen
|
||||
- `POST /api/users` - Nieuwe gebruiker aanmaken
|
||||
|
||||
### Posts
|
||||
- `GET /api/posts` - Alle posts ophalen
|
||||
- `POST /api/posts` - Nieuwe post aanmaken
|
||||
|
||||
## Database Schema
|
||||
|
||||
### User
|
||||
- `id` (Int, Primary Key)
|
||||
- `email` (String, Unique)
|
||||
- `name` (String, Optional)
|
||||
- `createdAt` (DateTime)
|
||||
- `updatedAt` (DateTime)
|
||||
|
||||
### Post
|
||||
- `id` (Int, Primary Key)
|
||||
- `title` (String)
|
||||
- `content` (String, Optional)
|
||||
- `published` (Boolean, Default: false)
|
||||
- `authorId` (Int, Foreign Key)
|
||||
- `createdAt` (DateTime)
|
||||
- `updatedAt` (DateTime)
|
||||
|
||||
## Docker Services
|
||||
|
||||
- **nuxt-app**: Nuxt 4 applicatie (poort 3000)
|
||||
- **postgres**: PostgreSQL database (poort 5432)
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Kopieer `env.example` naar `.env` en pas aan indien nodig:
|
||||
|
||||
```bash
|
||||
# npm
|
||||
npm run build
|
||||
|
||||
# pnpm
|
||||
pnpm run build
|
||||
|
||||
# yarn
|
||||
yarn build
|
||||
|
||||
# bun
|
||||
bun run build
|
||||
cp env.example .env
|
||||
```
|
||||
|
||||
Locally preview production build:
|
||||
## Troubleshooting
|
||||
|
||||
### Database connection issues
|
||||
```bash
|
||||
# npm
|
||||
npm run preview
|
||||
# Check of PostgreSQL draait
|
||||
docker-compose ps
|
||||
|
||||
# pnpm
|
||||
pnpm run preview
|
||||
|
||||
# yarn
|
||||
yarn preview
|
||||
|
||||
# bun
|
||||
bun run preview
|
||||
# Check logs
|
||||
docker-compose logs postgres
|
||||
```
|
||||
|
||||
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
||||
### Sequelize issues
|
||||
```bash
|
||||
# Run migrations
|
||||
docker-compose exec nuxt-app npm run db:migrate
|
||||
|
||||
# Reset en herstart database
|
||||
docker-compose down -v
|
||||
docker-compose up --build
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
Voor productie deployment:
|
||||
|
||||
1. Update `docker-compose.yml` met productie instellingen
|
||||
2. Gebruik environment variables voor secrets
|
||||
3. Setup reverse proxy (nginx)
|
||||
4. Configure SSL certificaten
|
||||
5. Setup database backups
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- **Frontend**: Nuxt 4, Vue 3, TypeScript
|
||||
- **Backend**: Nuxt Server API
|
||||
- **Database**: PostgreSQL 15
|
||||
- **ORM**: Sequelize
|
||||
- **Styling**: Tailwind CSS
|
||||
- **Containerization**: Docker & Docker Compose
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
export default defineAppConfig({
|
||||
// https://ui.nuxt.com/getting-started/theme#design-system
|
||||
ui: {
|
||||
colors: {
|
||||
primary: 'emerald',
|
||||
neutral: 'slate',
|
||||
},
|
||||
button: {
|
||||
defaultVariants: {
|
||||
// Set default button color to neutral
|
||||
// color: 'neutral'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
<template>
|
||||
<UApp>
|
||||
<NuxtPage />
|
||||
</UApp>
|
||||
</template>
|
||||
@@ -1,18 +0,0 @@
|
||||
@import "tailwindcss";
|
||||
@import "@nuxt/ui";
|
||||
|
||||
@theme static {
|
||||
--font-sans: 'Public Sans', sans-serif;
|
||||
|
||||
--color-green-50: #effdf5;
|
||||
--color-green-100: #d9fbe8;
|
||||
--color-green-200: #b3f5d1;
|
||||
--color-green-300: #75edae;
|
||||
--color-green-400: #00dc82;
|
||||
--color-green-500: #00c16a;
|
||||
--color-green-600: #00a155;
|
||||
--color-green-700: #007f45;
|
||||
--color-green-800: #016538;
|
||||
--color-green-900: #0a5331;
|
||||
--color-green-950: #052e16;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center gap-4 h-screen">
|
||||
<h1 class="font-bold text-2xl text-(--ui-primary)">
|
||||
Nuxt UI - Starter
|
||||
</h1>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<UButton
|
||||
label="Documentation"
|
||||
icon="i-lucide-square-play"
|
||||
to="https://ui.nuxt.com/getting-started/installation/nuxt"
|
||||
target="_blank"
|
||||
/>
|
||||
|
||||
<UButton
|
||||
label="GitHub"
|
||||
color="neutral"
|
||||
variant="outline"
|
||||
icon="i-simple-icons-github"
|
||||
to="https://github.com/nuxt/ui"
|
||||
target="_blank"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,28 +1,43 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
nuxt-app:
|
||||
app:
|
||||
container_name: node-nuxt-4
|
||||
build: .
|
||||
container_name: nuxt-app
|
||||
restart: always
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- GIT_REPO_URL=${GIT_REPO_URL}
|
||||
- GIT_BRANCH=${GIT_BRANCH}
|
||||
ports:
|
||||
- "1337:3000"
|
||||
volumes:
|
||||
- nuxt-app-data:/app
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.nuxt-app.rule=Host(`app.tiemen.dev`)"
|
||||
- "traefik.http.routers.nuxt-app.entrypoints=websecure"
|
||||
- "traefik.http.routers.nuxt-app.tls.certresolver=myresolver"
|
||||
- "traefik.http.services.nuxt-app.loadbalancer.server.port=3000"
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
environment:
|
||||
- NODE_ENV=${NODE_ENV}
|
||||
- DB_HOST=mariadb
|
||||
- DB_PORT=3306
|
||||
- DB_NAME=${DB_NAME}
|
||||
- DB_USERNAME=${DB_USERNAME}
|
||||
- DB_PASSWORD=${DB_PASSWORD}
|
||||
command: ["sh", "/app/entrypoint.sh"]
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- mariadb
|
||||
networks:
|
||||
- web
|
||||
- mariadb_network
|
||||
|
||||
mariadb:
|
||||
image: mariadb:latest
|
||||
container_name: mariadb-nuxt-4
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
|
||||
MYSQL_DATABASE: ${DB_NAME}
|
||||
MYSQL_USER: ${DB_USERNAME}
|
||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- mariadb-data:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- mariadb_network
|
||||
|
||||
volumes:
|
||||
nuxt-app-data:
|
||||
mariadb-data:
|
||||
|
||||
networks:
|
||||
web:
|
||||
external: true
|
||||
mariadb_network:
|
||||
driver: bridge
|
||||
@@ -1,26 +1,14 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
#!/bin/sh
|
||||
|
||||
TEMP_DIR="/tmp/nuxt_repo"
|
||||
|
||||
# Als repo nog niet bestaat, clone naar tijdelijke map
|
||||
if [ ! -d ".git" ]; then
|
||||
echo "Cloning repo to temporary folder..."
|
||||
git clone -b ${GIT_BRANCH:-main} ${GIT_REPO_URL} $TEMP_DIR
|
||||
echo "Copying files to /app..."
|
||||
cp -R $TEMP_DIR/* $TEMP_DIR/.[!.]* /app/ 2>/dev/null || true
|
||||
else
|
||||
echo "Pulling latest changes..."
|
||||
git reset --hard
|
||||
git clean -fd
|
||||
git pull origin ${GIT_BRANCH:-main}
|
||||
# Genereer SSL-certificaten als ze niet bestaan
|
||||
if [ ! -f /app/server.crt ] || [ ! -f /app/server.key ]; then
|
||||
openssl req -newkey rsa:2048 -nodes -keyout /app/server.key -x509 -days 365 -out /app/server.crt -subj "/C=US/ST=State/L=City/O=Organization/OU=Unit/CN=example.com"
|
||||
fi
|
||||
|
||||
# Dependencies installeren
|
||||
npm ci
|
||||
|
||||
# Build Nuxt
|
||||
npm run build
|
||||
|
||||
# Start Nuxt
|
||||
exec npm run start
|
||||
# Start de applicatie
|
||||
if [ "$NODE_ENV" = "production" ]; then
|
||||
npm run build && npm run preview
|
||||
else
|
||||
npm install &&
|
||||
npm run dev
|
||||
fi
|
||||
|
||||
5
env.example
Normal file
5
env.example
Normal file
@@ -0,0 +1,5 @@
|
||||
# Database
|
||||
DATABASE_URL="postgresql://nuxtuser:nuxtpassword@localhost:5432/nuxtdb"
|
||||
|
||||
# Nuxt
|
||||
NUXT_PUBLIC_API_BASE="/api"
|
||||
@@ -1,13 +1,27 @@
|
||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||
export default defineNuxtConfig({
|
||||
devtools: { enabled: true },
|
||||
|
||||
modules: [
|
||||
'@nuxt/ui',
|
||||
'@nuxt/eslint'
|
||||
'@nuxtjs/tailwindcss'
|
||||
],
|
||||
|
||||
css: ['~/assets/css/main.css'],
|
||||
|
||||
compatibilityDate: '2025-07-16'
|
||||
})
|
||||
runtimeConfig: {
|
||||
// Private keys (only available on server-side)
|
||||
database: process.env.DB_NAME || "mydatabase",
|
||||
username: process.env.DB_USERNAME || "root",
|
||||
password: process.env.DB_PASSWORD || "",
|
||||
host: process.env.DB_HOST || "localhost",
|
||||
dialect: process.env.DB_DIALECT || "mysql", // Change to "postgres", "sqlite", etc.
|
||||
logging: process.env.DB_LOGGING === "true",
|
||||
// Public keys (exposed to client-side)
|
||||
public: {
|
||||
apiBase: '/api'
|
||||
}
|
||||
},
|
||||
vite: {
|
||||
},
|
||||
nitro: {
|
||||
experimental: {
|
||||
wasm: true
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
9909
package-lock.json
generated
Normal file
9909
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
package.json
35
package.json
@@ -1,25 +1,32 @@
|
||||
{
|
||||
"name": "nuxt-app",
|
||||
"name": "nuxt-deploy",
|
||||
"version": "1.0.0",
|
||||
"description": "Nuxt 4 application with database",
|
||||
"type": "commonjs",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify-json/lucide": "^1.2.64",
|
||||
"@iconify-json/simple-icons": "^1.2.49",
|
||||
"@nuxt/ui": "^3.3.2",
|
||||
"nuxt": "^4.0.3"
|
||||
"db:migrate": "npx sequelize-cli db:migrate",
|
||||
"db:seed": "npx sequelize-cli db:seed:all",
|
||||
"db:create": "npx sequelize-cli db:create"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxt/eslint": "^1.9.0",
|
||||
"eslint": "^9.34.0",
|
||||
"typescript": "^5.9.2"
|
||||
"@nuxt/devtools": "latest",
|
||||
"nuxt": "^3.8.0",
|
||||
"typescript": "^5.4.0",
|
||||
"sequelize-cli": "^6.6.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nuxtjs/tailwindcss": "^6.12.0",
|
||||
"sequelize": "^6.37.0",
|
||||
"pg": "^8.12.0",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"mariadb": "^3.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
169
pages/index.vue
Normal file
169
pages/index.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<div class="min-h-screen bg-gray-50">
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<h1 class="text-4xl font-bold text-center mb-8 text-gray-800">
|
||||
Nuxt 4 met Database
|
||||
</h1>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
|
||||
<!-- Users Section -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<h2 class="text-2xl font-semibold mb-4 text-gray-700">Gebruikers</h2>
|
||||
|
||||
<form @submit.prevent="createUser" class="mb-4">
|
||||
<div class="space-y-3">
|
||||
<input
|
||||
v-model="newUser.name"
|
||||
type="text"
|
||||
placeholder="Naam"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
v-model="newUser.email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
Gebruiker Toevoegen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="space-y-2">
|
||||
<div
|
||||
v-for="user in users"
|
||||
:key="user.id"
|
||||
class="p-3 bg-gray-50 rounded-md"
|
||||
>
|
||||
<h3 class="font-medium">{{ user.name }}</h3>
|
||||
<p class="text-sm text-gray-600">{{ user.email }}</p>
|
||||
<p class="text-xs text-gray-500">{{ user.posts.length }} posts</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Posts Section -->
|
||||
<div class="bg-white rounded-lg shadow-md p-6">
|
||||
<h2 class="text-2xl font-semibold mb-4 text-gray-700">Posts</h2>
|
||||
|
||||
<form @submit.prevent="createPost" class="mb-4">
|
||||
<div class="space-y-3">
|
||||
<input
|
||||
v-model="newPost.title"
|
||||
type="text"
|
||||
placeholder="Titel"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
required
|
||||
/>
|
||||
<textarea
|
||||
v-model="newPost.content"
|
||||
placeholder="Inhoud"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
rows="3"
|
||||
></textarea>
|
||||
<select
|
||||
v-model="newPost.authorId"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
required
|
||||
>
|
||||
<option value="">Selecteer auteur</option>
|
||||
<option v-for="user in users" :key="user.id" :value="user.id">
|
||||
{{ user.name }}
|
||||
</option>
|
||||
</select>
|
||||
<button
|
||||
type="submit"
|
||||
class="w-full bg-green-500 text-white py-2 px-4 rounded-md hover:bg-green-600 transition-colors"
|
||||
>
|
||||
Post Toevoegen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="post in posts"
|
||||
:key="post.id"
|
||||
class="p-4 bg-gray-50 rounded-md"
|
||||
>
|
||||
<h3 class="font-medium text-lg">{{ post.title }}</h3>
|
||||
<p class="text-gray-600 mt-1">{{ post.content }}</p>
|
||||
<div class="flex justify-between items-center mt-2 text-sm text-gray-500">
|
||||
<span>Door: {{ post.author.name }}</span>
|
||||
<span>{{ formatDate(post.createdAt) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// Reactive data
|
||||
const users = ref([])
|
||||
const posts = ref([])
|
||||
const newUser = ref({ name: '', email: '' })
|
||||
const newPost = ref({ title: '', content: '', authorId: '' })
|
||||
|
||||
// Fetch data on mount
|
||||
onMounted(async () => {
|
||||
await Promise.all([fetchUsers(), fetchPosts()])
|
||||
})
|
||||
|
||||
// API functions
|
||||
const fetchUsers = async () => {
|
||||
try {
|
||||
const data = await $fetch('/api/users')
|
||||
users.value = data
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const fetchPosts = async () => {
|
||||
try {
|
||||
const data = await $fetch('/api/posts')
|
||||
posts.value = data
|
||||
} catch (error) {
|
||||
console.error('Error fetching posts:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const createUser = async () => {
|
||||
try {
|
||||
await $fetch('/api/users', {
|
||||
method: 'POST',
|
||||
body: newUser.value
|
||||
})
|
||||
newUser.value = { name: '', email: '' }
|
||||
await fetchUsers()
|
||||
} catch (error) {
|
||||
console.error('Error creating user:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const createPost = async () => {
|
||||
try {
|
||||
await $fetch('/api/posts', {
|
||||
method: 'POST',
|
||||
body: newPost.value
|
||||
})
|
||||
newPost.value = { title: '', content: '', authorId: '' }
|
||||
await fetchPosts()
|
||||
} catch (error) {
|
||||
console.error('Error creating post:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const formatDate = (dateString) => {
|
||||
return new Date(dateString).toLocaleDateString('nl-NL')
|
||||
}
|
||||
</script>
|
||||
16
server/api/db/migrate.post.ts
Normal file
16
server/api/db/migrate.post.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { sequelize } from '~/server/database'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
// Import and run migrations
|
||||
const { execSync } = await import('child_process')
|
||||
execSync('npx sequelize-cli db:migrate', { stdio: 'inherit' })
|
||||
|
||||
return { success: true, message: 'Database migrated successfully' }
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to migrate database'
|
||||
})
|
||||
}
|
||||
})
|
||||
20
server/api/posts/index.get.ts
Normal file
20
server/api/posts/index.get.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Post, User } from '~/server/database/models'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const posts = await Post.findAll({
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'author'
|
||||
}],
|
||||
order: [['createdAt', 'DESC']]
|
||||
})
|
||||
|
||||
return posts
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to fetch posts'
|
||||
})
|
||||
}
|
||||
})
|
||||
29
server/api/posts/index.post.ts
Normal file
29
server/api/posts/index.post.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Post, User } from '~/server/database/models'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const body = await readBody(event)
|
||||
|
||||
const post = await Post.create({
|
||||
title: body.title,
|
||||
content: body.content,
|
||||
published: body.published || false,
|
||||
authorId: body.authorId
|
||||
})
|
||||
|
||||
// Get the post with author information
|
||||
const postWithAuthor = await Post.findByPk(post.id, {
|
||||
include: [{
|
||||
model: User,
|
||||
as: 'author'
|
||||
}]
|
||||
})
|
||||
|
||||
return postWithAuthor
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to create post'
|
||||
})
|
||||
}
|
||||
})
|
||||
19
server/api/users/index.get.ts
Normal file
19
server/api/users/index.get.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { User, Post } from '~/server/database/models';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const users = await User.findAll({
|
||||
include: [{
|
||||
model: Post,
|
||||
as: 'posts'
|
||||
}]
|
||||
})
|
||||
return users
|
||||
} catch (error) {
|
||||
console.error('Error fetching users:', error);
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to fetch users'
|
||||
})
|
||||
}
|
||||
})
|
||||
19
server/api/users/index.post.ts
Normal file
19
server/api/users/index.post.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { User } from '~/server/database/models'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
try {
|
||||
const body = await readBody(event)
|
||||
|
||||
const user = await User.create({
|
||||
email: body.email,
|
||||
name: body.name
|
||||
})
|
||||
|
||||
return user
|
||||
} catch (error) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to create user'
|
||||
})
|
||||
}
|
||||
})
|
||||
28
server/database/config.js
Normal file
28
server/database/config.js
Normal file
@@ -0,0 +1,28 @@
|
||||
require('dotenv').config();
|
||||
|
||||
module.exports = {
|
||||
development: {
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
dialect: 'mariadb'
|
||||
},
|
||||
test: {
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
dialect: 'mariadb'
|
||||
},
|
||||
production: {
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
dialect: 'mariadb'
|
||||
}
|
||||
};
|
||||
11
server/database/index.js
Normal file
11
server/database/index.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { Sequelize } from 'sequelize';
|
||||
const config = useRuntimeConfig();
|
||||
|
||||
// Initialize Sequelize instance
|
||||
const sequelize = new Sequelize(config.database, config.username, config.password, {
|
||||
host: config.host,
|
||||
dialect: config.dialect,
|
||||
logging: config.logging || false,
|
||||
});
|
||||
|
||||
export { sequelize };
|
||||
35
server/database/migrations/20240101000001-create-users.js
Normal file
35
server/database/migrations/20240101000001-create-users.js
Normal file
@@ -0,0 +1,35 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('users', {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: Sequelize.INTEGER
|
||||
},
|
||||
email: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
name: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: true
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('users');
|
||||
}
|
||||
};
|
||||
46
server/database/migrations/20240101000002-create-posts.js
Normal file
46
server/database/migrations/20240101000002-create-posts.js
Normal file
@@ -0,0 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
await queryInterface.createTable('posts', {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: Sequelize.INTEGER
|
||||
},
|
||||
title: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
content: {
|
||||
type: Sequelize.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
published: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
authorId: {
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface, Sequelize) {
|
||||
await queryInterface.dropTable('posts');
|
||||
}
|
||||
};
|
||||
37
server/database/models/Post.js
Normal file
37
server/database/models/Post.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../index.js';
|
||||
|
||||
const Post = sequelize.define('Post', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false
|
||||
},
|
||||
content: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
published: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
authorId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
references: {
|
||||
model: 'users',
|
||||
key: 'id'
|
||||
}
|
||||
}
|
||||
}, {
|
||||
tableName: 'posts',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
});
|
||||
|
||||
export default Post;
|
||||
26
server/database/models/User.js
Normal file
26
server/database/models/User.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../index.js';
|
||||
|
||||
const User = sequelize.define('User', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'users',
|
||||
timestamps: true,
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
});
|
||||
|
||||
export default User;
|
||||
8
server/database/models/index.js
Normal file
8
server/database/models/index.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import User from './User.js';
|
||||
import Post from './Post.js';
|
||||
|
||||
// Define associations
|
||||
User.hasMany(Post, { foreignKey: 'authorId', as: 'posts' });
|
||||
Post.belongsTo(User, { foreignKey: 'authorId', as: 'author' });
|
||||
|
||||
export { User, Post };
|
||||
Reference in New Issue
Block a user