diff --git a/.gitignore b/.gitignore index 78c20b7..04fcd29 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,13 @@ __pycache__/ *.py[cod] *$py.class + +.env +.env.local +node_modules/ +.next/ +out/ +*.log +.DS_Store +db.sqlite3 +postgres_data/ diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..35fddf2 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,18 @@ +FROM python:3.12-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + gcc \ + postgresql-client \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +EXPOSE 8000 + +CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"] diff --git a/backend/keinasystem/__init__.py b/backend/keinasystem/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/keinasystem/asgi.py b/backend/keinasystem/asgi.py new file mode 100644 index 0000000..843191e --- /dev/null +++ b/backend/keinasystem/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for keinasystem project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'keinasystem.settings') + +application = get_asgi_application() diff --git a/backend/keinasystem/settings.py b/backend/keinasystem/settings.py new file mode 100644 index 0000000..806e119 --- /dev/null +++ b/backend/keinasystem/settings.py @@ -0,0 +1,121 @@ +""" +Django settings for keinasystem project. + +Generated by 'django-admin startproject' using Django 5.2.11. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/5.2/ref/settings/ +""" + +import os + +from pathlib import Path + +BASE_DIR = Path(__file__).resolve().parent.parent + +SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-default-key') + +DEBUG = os.environ.get('DEBUG', 'True') == 'True' + +ALLOWED_HOSTS = ['*'] + + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', +] + +MIDDLEWARE = [ + 'django.middleware.security.SecurityMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] + +ROOT_URLCONF = 'keinasystem.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], + }, + }, +] + +WSGI_APPLICATION = 'keinasystem.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/5.2/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.postgresql', + 'NAME': os.environ.get('DB_NAME', 'keinasystem'), + 'USER': os.environ.get('DB_USER', 'keinasystem'), + 'PASSWORD': os.environ.get('DB_PASSWORD', ''), + 'HOST': os.environ.get('DB_HOST', 'db'), + 'PORT': os.environ.get('DB_PORT', '5432'), + } +} + + +# Password validation +# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/5.2/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/5.2/howto/static-files/ + +STATIC_URL = 'static/' + +# Default primary key field type +# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field + +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' diff --git a/backend/keinasystem/urls.py b/backend/keinasystem/urls.py new file mode 100644 index 0000000..9f99a94 --- /dev/null +++ b/backend/keinasystem/urls.py @@ -0,0 +1,22 @@ +""" +URL configuration for keinasystem project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/5.2/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" +from django.contrib import admin +from django.urls import path + +urlpatterns = [ + path('admin/', admin.site.urls), +] diff --git a/backend/keinasystem/wsgi.py b/backend/keinasystem/wsgi.py new file mode 100644 index 0000000..dfcf11a --- /dev/null +++ b/backend/keinasystem/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for keinasystem project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'keinasystem.settings') + +application = get_wsgi_application() diff --git a/backend/manage.py b/backend/manage.py new file mode 100644 index 0000000..13fc166 --- /dev/null +++ b/backend/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'keinasystem.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..1e78787 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,3 @@ +Django>=5.0,<6.0 +psycopg2-binary>=2.9,<3.0 +gunicorn>=21.0,<22.0 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7cbcc80 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,59 @@ +version: '3.8' + +services: + db: + image: postgis/postgis:16-3.4 + container_name: keinasystem_db + environment: + POSTGRES_DB: keinasystem + POSTGRES_USER: keinasystem + POSTGRES_PASSWORD: ${DB_PASSWORD} + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U keinasystem -d keinasystem"] + interval: 5s + timeout: 5s + retries: 5 + + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: keinasystem_backend + environment: + DB_NAME: keinasystem + DB_USER: keinasystem + DB_PASSWORD: ${DB_PASSWORD} + DB_HOST: db + DB_PORT: 5432 + SECRET_KEY: ${SECRET_KEY} + DEBUG: "True" + ports: + - "8000:8000" + volumes: + - ./backend:/app + depends_on: + db: + condition: service_healthy + command: python manage.py runserver 0.0.0.0:8000 + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: keinasystem_frontend + environment: + NEXT_PUBLIC_API_URL: http://localhost:8000 + ports: + - "3000:3000" + volumes: + - ./frontend:/app + - /app/node_modules + depends_on: + - backend + +volumes: + postgres_data: diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..296c998 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,13 @@ +FROM node:20-alpine + +WORKDIR /app + +COPY package*.json ./ + +RUN npm install + +COPY . . + +EXPOSE 3000 + +CMD ["npm", "run", "dev", "--", "-H", "0.0.0.0"] diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts new file mode 100644 index 0000000..4f11a03 --- /dev/null +++ b/frontend/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/frontend/next.config.js b/frontend/next.config.js new file mode 100644 index 0000000..767719f --- /dev/null +++ b/frontend/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {} + +module.exports = nextConfig diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..9197f27 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,27 @@ +{ + "name": "keinasystem-frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "react": "^18", + "react-dom": "^18", + "next": "14.1.0" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "typescript": "^5", + "eslint": "^8", + "eslint-config-next": "14.1.0", + "tailwindcss": "^3.3.0", + "postcss": "^8", + "autoprefixer": "^10.0.0" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css new file mode 100644 index 0000000..1b4ca7b --- /dev/null +++ b/frontend/src/app/globals.css @@ -0,0 +1,19 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient( + to bottom, + transparent, + rgb(var(--background-end-rgb)) + ) + rgb(var(--background-start-rgb)); +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx new file mode 100644 index 0000000..73bfe73 --- /dev/null +++ b/frontend/src/app/layout.tsx @@ -0,0 +1,22 @@ +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import "./globals.css"; + +const inter = Inter({ subsets: ["latin"] }); + +export const metadata: Metadata = { + title: "KeinaSystem", + description: "KeinaSystem Frontend", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx new file mode 100644 index 0000000..6498e8d --- /dev/null +++ b/frontend/src/app/page.tsx @@ -0,0 +1,32 @@ +export default function Home() { + return ( +
+
+

+ Get started by editing  + src/app/page.tsx +

+
+ +
+

KeinaSystem

+
+ +
+ +

+ Docs -> +

+

+ Find in-depth information about Next.js features and API. +

+
+
+
+ ); +} diff --git a/frontend/tailwind.config.ts b/frontend/tailwind.config.ts new file mode 100644 index 0000000..029026a --- /dev/null +++ b/frontend/tailwind.config.ts @@ -0,0 +1,14 @@ +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; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..7b28589 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "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"] +}