feat: Initial commit for Full-Stack Monorepo Starter Template with CockroachDB
This commit is contained in:
commit
05a332ed8b
46
.gitignore
vendored
Normal file
46
.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
# =======================================================
|
||||
# 1. PYTHON / DJANGO BACKEND
|
||||
# =======================================================
|
||||
# Virtual Environments (ไม่ว่าจะชื่อ venv, env, หรือ .venv)
|
||||
*venv/
|
||||
*.env
|
||||
env/
|
||||
.venv/
|
||||
|
||||
# Python Cache
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyd
|
||||
*.pyo
|
||||
|
||||
# Django & Database Files
|
||||
/backend/*.sqlite3
|
||||
/backend/staticfiles/
|
||||
/backend/media/
|
||||
/backend/local_settings.py
|
||||
|
||||
# =======================================================
|
||||
# 2. NODE.JS / REACT FRONTEND (Web & Mobile)
|
||||
# =======================================================
|
||||
/web/node_modules/
|
||||
/mobile/node_modules/
|
||||
/web/dist/ # Build output for web (Vite)
|
||||
/mobile/.expo/ # Expo cache files
|
||||
/mobile/web-build/ # Expo web build output
|
||||
/mobile/android/ # Native build folder
|
||||
/mobile/ios/ # Native build folder
|
||||
|
||||
# IDE Files (IntelliJ / VS Code)
|
||||
.idea/ # IntelliJ/WebStorm specific folder
|
||||
.vscode/
|
||||
|
||||
# =======================================================
|
||||
# 3. DOCKER / INFRASTRUCTURE
|
||||
# =======================================================
|
||||
# ห้าม Commit Volumes และ Secrets
|
||||
# Docker Volumes ถูกสร้างขึ้นในโฟลเดอร์นี้
|
||||
/infra/volumes/
|
||||
*.log
|
||||
|
||||
# Secrets (ถ้าใช้ไฟล์ .env ในอนาคต)
|
||||
.env
|
||||
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
9
.idea/cremation-monorepo.iml
generated
Normal file
9
.idea/cremation-monorepo.iml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="JAVA_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/misc.xml
generated
Normal file
6
.idea/misc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21 (2)" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/cremation-monorepo.iml" filepath="$PROJECT_DIR$/.idea/cremation-monorepo.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
7
.idea/vcs.xml
generated
Normal file
7
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/mobile" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
74
README.md
Normal file
74
README.md
Normal file
@ -0,0 +1,74 @@
|
||||
# MONOREPO STARTER TEMPLATE
|
||||
|
||||
## ภาพรวม
|
||||
โปรเจกต์นี้เป็น Monorepo แบบ Full-Stack ที่มีโครงสร้างพร้อมใช้งานสำหรับการพัฒนาและทดสอบ:
|
||||
|
||||
1. **Backend (Django/DRF):** โครงสร้าง Modular Monolith พร้อมระบบ Authentication ด้วย Djoser/JWT
|
||||
2. **ฐานข้อมูล (HA):** CockroachDB Cluster 3 Node (เข้ากันได้กับ PostgreSQL)
|
||||
3. **Frontend (Web):** React + Vite + Tailwind CSS / DaisyUI และ Mobile ผ่าน Expo
|
||||
4. **Automation:** Docker Compose พร้อมตั้งค่า Database อัตโนมัติเมื่อเริ่มต้น
|
||||
|
||||
## การเริ่มต้นใช้งาน (Run Local)
|
||||
|
||||
1. **Clone the Repository:**
|
||||
```bash
|
||||
git clone [Your Gitea URL]
|
||||
cd cremation-monorepo
|
||||
```
|
||||
|
||||
2. **รัน Infrastructure & Backend:**
|
||||
ไปที่โฟลเดอร์ infra และสตาร์ทบริการทั้งหมด คำสั่งนี้จะ build backend และตั้งค่า CockroachDB Cluster อัตโนมัติ
|
||||
```bash
|
||||
cd infra
|
||||
docker compose up --build -d
|
||||
```
|
||||
3. **เข้าถึง Admin UI:**
|
||||
ตรวจสอบว่า Backend และฐานข้อมูลพร้อมใช้งาน
|
||||
- Django Admin: http://localhost:8000/admin/
|
||||
- CockroachDB Status: http://localhost:8080/
|
||||
|
||||
Username: admin
|
||||
|
||||
Password: Str0ngp@ssword123-
|
||||
|
||||
- การเข้าถึงและตรวจสอบ API Endpoints อื่น ๆ:
|
||||
|
||||
http://localhost:8000/api/v1/auth/jwt/create/
|
||||
http://localhost:8000/api/v1/auth/users/
|
||||
http://localhost:8000/api/v1/auth/users/reset_password/
|
||||
http://localhost:8000/api/v1/auth/users/me/
|
||||
|
||||
|
||||
4. **รัน Frontend (Web):**
|
||||
```bash
|
||||
cd ../web
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
5. **รัน Frontend (Mobile):**
|
||||
- ติดตั้ง Expo Go บนมือถือ
|
||||
- สแกน QR Code โดยมือถือและเครื่อง Notebook ต้องอยู่ใน Lan วงเดียวกัน
|
||||
|
||||
```bash
|
||||
cd ../mobile
|
||||
npm expo start
|
||||
```
|
||||
|
||||
## การหยุดชั่วคราวและรันต่อ
|
||||
ถ้าต้องการหยุดพัฒนาโดยไม่ลบฐานข้อมูล สามารถรัน:
|
||||
|
||||
```bash
|
||||
cd ../infra
|
||||
docker compose down
|
||||
```
|
||||
|
||||
เมื่อต้องการกลับมาพัฒนาต่อ ให้รัน:
|
||||
|
||||
```bash
|
||||
cd ../infra
|
||||
docker compose up -d
|
||||
```
|
||||
คำสั่ง docker compose down -v จะลบ Volume และฐานข้อมูลทั้งหมด ใช้เฉพาะตอนต้องการเริ่มต้นฐานข้อมูลใหม่
|
||||
|
||||
คำเตือนสำคัญ ตรวจสอบให้แน่ใจว่าได้กำหนด AUTH_USER_MODEL ถูกต้องใน settings.py ก่อนรัน migrate ครั้งแรกเสมอ เพื่อหลีกเลี่ยงปัญหา InconsistentMigrationHistory ในภายหลัง
|
||||
27
backend/Dockerfile
Normal file
27
backend/Dockerfile
Normal file
@ -0,0 +1,27 @@
|
||||
# backend/Dockerfile
|
||||
|
||||
# 1. BASE IMAGE
|
||||
FROM python:3.11-slim
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1 # เพื่อให้ Log แสดงผลทันที
|
||||
|
||||
# 2. WORK DIRECTORY
|
||||
WORKDIR /app
|
||||
|
||||
# 3. DEPENDENCIES: Copy และติดตั้ง (ใช้ประโยชน์จาก Docker Layer Caching)
|
||||
COPY requirements.txt /app/
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 4. ENTRYPOINT SCRIPT: Copy และกำหนดสิทธิ์รัน (ใช้สำหรับ Startup Automation)
|
||||
COPY docker-entrypoint.sh /usr/local/bin/
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# 5. CODE: คัดลอกโค้ดโปรเจกต์ที่เหลือทั้งหมดมาไว้ใน /app
|
||||
COPY . /app/
|
||||
|
||||
# 6. EXPOSE:
|
||||
EXPOSE 8000
|
||||
|
||||
# 7. ENTRYPOINT/CMD: กำหนด Entrypoint หลัก
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
|
||||
0
backend/accounts/__init__.py
Normal file
0
backend/accounts/__init__.py
Normal file
13
backend/accounts/admin.py
Normal file
13
backend/accounts/admin.py
Normal file
@ -0,0 +1,13 @@
|
||||
# backend/accounts/admin.py
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from .models import CustomUser
|
||||
|
||||
# ใช้ UserAdmin เพื่อให้ได้ UI ที่ครบถ้วนสำหรับการจัดการผู้ใช้
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
# Fieldsets หรือ list_display (ถ้าต้องการปรับแต่ง UI)
|
||||
pass
|
||||
|
||||
# ลงทะเบียน CustomUser ด้วย CustomUserAdmin
|
||||
admin.site.register(CustomUser, CustomUserAdmin)
|
||||
6
backend/accounts/apps.py
Normal file
6
backend/accounts/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class AccountsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'accounts'
|
||||
45
backend/accounts/migrations/0001_initial.py
Normal file
45
backend/accounts/migrations/0001_initial.py
Normal file
@ -0,0 +1,45 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-26 10:02
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('auth', '0012_alter_user_first_name_max_length'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='CustomUser',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('phone_number', models.CharField(blank=True, max_length=20, null=True, unique=True)),
|
||||
('email', models.EmailField(max_length=254, unique=True)),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
'verbose_name_plural': 'users',
|
||||
'abstract': False,
|
||||
},
|
||||
managers=[
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
]
|
||||
0
backend/accounts/migrations/__init__.py
Normal file
0
backend/accounts/migrations/__init__.py
Normal file
20
backend/accounts/models.py
Normal file
20
backend/accounts/models.py
Normal file
@ -0,0 +1,20 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
|
||||
class CustomUser(AbstractUser):
|
||||
# เพิ่มฟิลด์ที่ต้องการ เช่น phone_number
|
||||
phone_number = models.CharField(max_length=20, blank=True, null=True, unique=True)
|
||||
# ตัวอย่าง:
|
||||
# is_customer = models.BooleanField(default=False)
|
||||
|
||||
# ถ้าต้องการให้ email เป็น Unique (ไม่ซ้ำกัน)
|
||||
email = models.EmailField(unique=True)
|
||||
|
||||
# Field ที่ใช้สำหรับการล็อกอิน (ถ้าไม่ใช่ username)
|
||||
# USERNAME_FIELD = 'email'
|
||||
# REQUIRED_FIELDS = ['username'] # ถ้าเปลี่ยน USERNAME_FIELD ต้องกำหนด REQUIRED_FIELDS
|
||||
|
||||
# ไม่ต้องใส่ pass เพราะมันสืบทอดมาจาก AbstractUser
|
||||
pass
|
||||
18
backend/accounts/serializers.py
Normal file
18
backend/accounts/serializers.py
Normal file
@ -0,0 +1,18 @@
|
||||
# accounts/serializers.py
|
||||
|
||||
from djoser.serializers import UserCreateSerializer as BaseUserCreateSerializer
|
||||
from rest_framework import serializers
|
||||
from .models import CustomUser
|
||||
|
||||
class UserCreateSerializer(BaseUserCreateSerializer):
|
||||
# Serializer สำหรับการลงทะเบียน (Djoser จะใช้ตัวนี้)
|
||||
class Meta(BaseUserCreateSerializer.Meta):
|
||||
model = CustomUser
|
||||
fields = ('id', 'username', 'email', 'phone_number', 'password') # เพิ่ม phone_number
|
||||
|
||||
class UserSerializer(serializers.ModelSerializer):
|
||||
# Serializer สำหรับการดึงข้อมูล (ใช้แสดงข้อมูลผู้ใช้ปัจจุบัน)
|
||||
class Meta:
|
||||
model = CustomUser
|
||||
fields = ('id', 'username', 'email', 'phone_number', 'first_name', 'last_name')
|
||||
read_only_fields = ('id', 'username')
|
||||
3
backend/accounts/tests.py
Normal file
3
backend/accounts/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
backend/accounts/views.py
Normal file
3
backend/accounts/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
0
backend/cremation_api/__init__.py
Normal file
0
backend/cremation_api/__init__.py
Normal file
3
backend/cremation_api/admin.py
Normal file
3
backend/cremation_api/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/cremation_api/apps.py
Normal file
6
backend/cremation_api/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CremationApiConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'cremation_api'
|
||||
0
backend/cremation_api/migrations/__init__.py
Normal file
0
backend/cremation_api/migrations/__init__.py
Normal file
3
backend/cremation_api/models.py
Normal file
3
backend/cremation_api/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
backend/cremation_api/tests.py
Normal file
3
backend/cremation_api/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
backend/cremation_api/views.py
Normal file
3
backend/cremation_api/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
0
backend/cremation_backend/__init__.py
Normal file
0
backend/cremation_backend/__init__.py
Normal file
16
backend/cremation_backend/asgi.py
Normal file
16
backend/cremation_backend/asgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for cremation_backend 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', 'cremation_backend.settings')
|
||||
|
||||
application = get_asgi_application()
|
||||
193
backend/cremation_backend/settings.py
Normal file
193
backend/cremation_backend/settings.py
Normal file
@ -0,0 +1,193 @@
|
||||
"""
|
||||
Django settings for cremation_backend project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 5.2.7.
|
||||
|
||||
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/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-&f#$_k8e766il)kxagku-545x7*ji&%rqx!(&9k@t7qp)+9)t@'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
DJANGO_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
]
|
||||
|
||||
THIRD_PARTY_APPS = [
|
||||
'django_bolt',
|
||||
'rest_framework',
|
||||
'corsheaders',
|
||||
'djoser',
|
||||
'rest_framework_simplejwt'
|
||||
]
|
||||
|
||||
LOCAL_APPS = [
|
||||
'cremation_api',
|
||||
'accounts',
|
||||
'user_profile',
|
||||
'permissions',
|
||||
]
|
||||
|
||||
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
|
||||
|
||||
MIDDLEWARE = [
|
||||
'corsheaders.middleware.CorsMiddleware', # สำคัญมากสำหรับ Frontend
|
||||
'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',
|
||||
]
|
||||
|
||||
# ตั้งค่า CORS: อนุญาตให้ Frontend เข้าถึง API ได้ (ใน Dev Mode)
|
||||
CORS_ALLOWED_ORIGINS = [
|
||||
"http://localhost:5173", # Vite/React Default Port
|
||||
"http://127.0.0.1:5173",
|
||||
]
|
||||
CORS_ALLOW_ALL_ORIGINS = True # ควรเป็น False ใน Production
|
||||
|
||||
ROOT_URLCONF = 'cremation_backend.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 = 'cremation_backend.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||
|
||||
# backend/cremation_backend/settings.py
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django_cockroachdb',
|
||||
'NAME': os.environ.get('DB_NAME', 'my_db'),
|
||||
'HOST': os.environ.get('DB_HOST', 'cockroach-1'),
|
||||
'PORT': os.environ.get('DB_PORT', '26257'),
|
||||
'USER': os.environ.get('DB_USER', 'root'),
|
||||
'PASSWORD': os.environ.get('DB_PASSWORD', ''),
|
||||
|
||||
# อยู่ระดับเดียวกับ 'ENGINE' และ 'OPTIONS'
|
||||
'support_check_by_version': False,
|
||||
|
||||
'OPTIONS': {
|
||||
# ใช้ 'options' เพื่อส่งค่า config ไปยัง CockroachDB/PostgreSQL
|
||||
'options': '-c default_transaction_read_only=off'
|
||||
},
|
||||
'ATOMIC_REQUESTS': False,
|
||||
},
|
||||
}
|
||||
|
||||
# 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'
|
||||
|
||||
# 1. กำหนด Custom User Model
|
||||
AUTH_USER_MODEL = 'accounts.CustomUser' # ต้องชี้ไปที่ Model ใน App accounts/models.py
|
||||
|
||||
# 2. ตั้งค่า REST Framework
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||
# ใช้ JWT เป็นวิธีการยืนยันตัวตนหลัก
|
||||
'rest_framework_simplejwt.authentication.JWTAuthentication',
|
||||
),
|
||||
'DEFAULT_PERMISSION_CLASSES': (
|
||||
'rest_framework.permissions.IsAuthenticated',
|
||||
)
|
||||
}
|
||||
|
||||
# 3. ตั้งค่า DJOSER (เพื่อจัดการ Auth Endpoints)
|
||||
DJOSER = {
|
||||
# ใช้งาน JWT โดยตรง
|
||||
'USER_ID_FIELD': 'id', # ใช้ ID ของ User Model
|
||||
'PASSWORD_RESET_CONFIRM_URL': '#/password/reset/confirm/{uid}/{token}', # URL สำหรับ Frontend
|
||||
'USERNAME_RESET_CONFIRM_URL': '#/username/reset/confirm/{uid}/{token}',
|
||||
'ACTIVATION_URL': '#/activate/{uid}/{token}', # หากต้องการยืนยันอีเมล
|
||||
'SERIALIZERS': {
|
||||
'user_create': 'accounts.serializers.UserCreateSerializer', # จะสร้างในขั้นตอนถัดไป
|
||||
'user': 'accounts.serializers.UserSerializer', # ใช้ UserSerializer เดิม
|
||||
'current_user': 'accounts.serializers.UserSerializer',
|
||||
},
|
||||
'TOKEN_MODEL': None, # ไม่ใช้ TokenAuth แบบเก่า
|
||||
'PERMISSIONS': {
|
||||
'user_create': ['rest_framework.permissions.AllowAny'],
|
||||
}
|
||||
}
|
||||
33
backend/cremation_backend/urls.py
Normal file
33
backend/cremation_backend/urls.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""
|
||||
URL configuration for cremation_backend 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, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
|
||||
# 1. กำหนดตัวแปร router ก่อนใช้งาน
|
||||
router = DefaultRouter()
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
|
||||
# 1. Endpoints สำหรับการยืนยันตัวตน (Login, Logout, Register)
|
||||
path('api/v1/auth/', include('djoser.urls')), # /users/ (Register/Update/Me), /users/set_password
|
||||
path('api/v1/auth/', include('djoser.urls.jwt')), # /jwt/create (Login), /jwt/refresh (Refresh Token)
|
||||
|
||||
# 2. รวม Router API
|
||||
path('api/v1/', include(router.urls)),
|
||||
]
|
||||
16
backend/cremation_backend/wsgi.py
Normal file
16
backend/cremation_backend/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for cremation_backend 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', 'cremation_backend.settings')
|
||||
|
||||
application = get_wsgi_application()
|
||||
51
backend/docker-entrypoint.sh
Normal file
51
backend/docker-entrypoint.sh
Normal file
@ -0,0 +1,51 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Waiting for CockroachDB cluster to be ready..."
|
||||
|
||||
# ใช้ Python แทน client cockroach
|
||||
until python - <<END
|
||||
import psycopg
|
||||
import os
|
||||
try:
|
||||
conn = psycopg.connect(
|
||||
host=os.environ.get("DB_HOST","cockroach-1"),
|
||||
port=os.environ.get("DB_PORT",26257),
|
||||
dbname="defaultdb",
|
||||
user=os.environ.get("DB_USER","root"),
|
||||
password=os.environ.get("DB_PASSWORD","")
|
||||
)
|
||||
conn.close()
|
||||
except:
|
||||
exit(1)
|
||||
END
|
||||
do
|
||||
echo "CockroachDB is unavailable - sleeping"
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "CockroachDB is ready."
|
||||
|
||||
# Create DB
|
||||
python - <<END
|
||||
import psycopg
|
||||
import os
|
||||
conn = psycopg.connect(
|
||||
host=os.environ.get("DB_HOST","cockroach-1"),
|
||||
port=os.environ.get("DB_PORT",26257),
|
||||
dbname="defaultdb",
|
||||
user=os.environ.get("DB_USER","root"),
|
||||
password=os.environ.get("DB_PASSWORD","")
|
||||
)
|
||||
conn.autocommit = True
|
||||
cur = conn.cursor()
|
||||
cur.execute(f"CREATE DATABASE IF NOT EXISTS {os.environ.get('DB_NAME','my_db')};")
|
||||
cur.close()
|
||||
conn.close()
|
||||
END
|
||||
|
||||
# Migrate & Superuser
|
||||
python manage.py migrate --noinput
|
||||
python manage.py shell -c "from django.contrib.auth import get_user_model; User=get_user_model(); User.objects.filter(username='admin').exists() or User.objects.create_superuser('admin','admin@softwarecraft.tech','Str0ngp@ssword123-')"
|
||||
|
||||
echo "Starting Django server..."
|
||||
exec python manage.py runserver 0.0.0.0:8000
|
||||
22
backend/manage.py
Normal file
22
backend/manage.py
Normal file
@ -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', 'cremation_backend.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()
|
||||
0
backend/permissions/__init__.py
Normal file
0
backend/permissions/__init__.py
Normal file
3
backend/permissions/admin.py
Normal file
3
backend/permissions/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/permissions/apps.py
Normal file
6
backend/permissions/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class PermissionsConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'permissions'
|
||||
0
backend/permissions/migrations/__init__.py
Normal file
0
backend/permissions/migrations/__init__.py
Normal file
3
backend/permissions/models.py
Normal file
3
backend/permissions/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
backend/permissions/tests.py
Normal file
3
backend/permissions/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
backend/permissions/views.py
Normal file
3
backend/permissions/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
9
backend/requirements.txt
Normal file
9
backend/requirements.txt
Normal file
@ -0,0 +1,9 @@
|
||||
Django
|
||||
djangorestframework
|
||||
django-cors-headers
|
||||
django-bolt
|
||||
django-cockroachdb
|
||||
psycopg[binary]
|
||||
typing_extensions
|
||||
djoser # สำหรับ endpoints: /users/ (Register), /token/login (Login), /password/reset
|
||||
djangorestframework-simplejwt # สำหรับสร้าง JWT (JSON Web Tokens)
|
||||
0
backend/user_profile/__init__.py
Normal file
0
backend/user_profile/__init__.py
Normal file
3
backend/user_profile/admin.py
Normal file
3
backend/user_profile/admin.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
backend/user_profile/apps.py
Normal file
6
backend/user_profile/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UserProfileConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'user_profile'
|
||||
0
backend/user_profile/migrations/__init__.py
Normal file
0
backend/user_profile/migrations/__init__.py
Normal file
3
backend/user_profile/models.py
Normal file
3
backend/user_profile/models.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
backend/user_profile/tests.py
Normal file
3
backend/user_profile/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
3
backend/user_profile/views.py
Normal file
3
backend/user_profile/views.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
71
infra/docker-compose.yml
Normal file
71
infra/docker-compose.yml
Normal file
@ -0,0 +1,71 @@
|
||||
# infra/docker-compose.yml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Node 1: Primary Node (Bootstrap)
|
||||
cockroach-1:
|
||||
image: cockroachdb/cockroach:latest
|
||||
container_name: cockroach-1
|
||||
command: start --insecure --host=cockroach-1 --listen-addr=cockroach-1:26257 --advertise-addr=cockroach-1:26257 --join=cockroach-1:26257,cockroach-2:26257,cockroach-3:26257
|
||||
restart: always
|
||||
volumes:
|
||||
- cockroach1:/cockroach/data
|
||||
ports:
|
||||
- "26257:26257" # Default SQL Port
|
||||
- "8080:8080" # Web UI Port (สำหรับดูสถานะ Cluster)
|
||||
|
||||
# Node 2: Joining Node
|
||||
cockroach-2:
|
||||
image: cockroachdb/cockroach:latest
|
||||
container_name: cockroach-2
|
||||
command: start --insecure --host=cockroach-2 --listen-addr=cockroach-2:26257 --advertise-addr=cockroach-2:26257 --join=cockroach-1:26257,cockroach-2:26257,cockroach-3:26257
|
||||
restart: always
|
||||
volumes:
|
||||
- cockroach2:/cockroach/data
|
||||
|
||||
# Node 3: Joining Node
|
||||
cockroach-3:
|
||||
image: cockroachdb/cockroach:latest
|
||||
container_name: cockroach-3
|
||||
command: start --insecure --host=cockroach-3 --listen-addr=cockroach-3:26257 --advertise-addr=cockroach-3:26257 --join=cockroach-1:26257,cockroach-2:26257,cockroach-3:26257
|
||||
restart: always
|
||||
volumes:
|
||||
- cockroach3:/cockroach/data
|
||||
|
||||
# 4. Init Cluster (ตั้งค่าครั้งแรก)
|
||||
init-cluster:
|
||||
image: cockroachdb/cockroach:latest
|
||||
container_name: init-cluster
|
||||
command: init --insecure --host=cockroach-1:26257
|
||||
depends_on:
|
||||
- cockroach-1
|
||||
- cockroach-2
|
||||
- cockroach-3
|
||||
# ตั้งค่าให้ Container ตายหลังจากรัน init เสร็จ (ไม่รันซ้ำ)
|
||||
restart: "no"
|
||||
|
||||
# Backend/API (DRF)
|
||||
backend:
|
||||
build:
|
||||
context: ../backend
|
||||
dockerfile: Dockerfile
|
||||
volumes:
|
||||
- ../backend:/app
|
||||
ports:
|
||||
- "8000:8000"
|
||||
# เปลี่ยนให้พึ่งพา Node ฐานข้อมูลโดยตรง (เพราะ entrypoint script จะจัดการการรอ)
|
||||
depends_on:
|
||||
- cockroach-1
|
||||
- cockroach-2
|
||||
- cockroach-3
|
||||
environment:
|
||||
DB_HOST: cockroach-1
|
||||
DB_PORT: 26257
|
||||
DB_NAME: my_db
|
||||
DB_USER: root
|
||||
DB_PASSWORD: ''
|
||||
|
||||
volumes:
|
||||
cockroach1:
|
||||
cockroach2:
|
||||
cockroach3:
|
||||
1
mobile
Submodule
1
mobile
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit b1f5edec5dd804cff3932e1f79b7a90f52dec459
|
||||
24
web/.gitignore
vendored
Normal file
24
web/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
16
web/README.md
Normal file
16
web/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# React + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## React Compiler
|
||||
|
||||
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
||||
29
web/eslint.config.js
Normal file
29
web/eslint.config.js
Normal file
@ -0,0 +1,29 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{js,jsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
reactHooks.configs['recommended-latest'],
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
ecmaFeatures: { jsx: true },
|
||||
sourceType: 'module',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
||||
},
|
||||
},
|
||||
])
|
||||
13
web/index.html
Normal file
13
web/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en" data-theme="corporate">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>web</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
3343
web/package-lock.json
generated
Normal file
3343
web/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
web/package.json
Normal file
30
web/package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "web",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
"daisyui": "^5.3.9",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"tailwindcss": "^4.1.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
"@types/react": "^19.1.16",
|
||||
"@types/react-dom": "^19.1.9",
|
||||
"@vitejs/plugin-react": "^5.0.4",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.22",
|
||||
"globals": "^16.4.0",
|
||||
"vite": "^7.1.7"
|
||||
}
|
||||
}
|
||||
1
web/public/vite.svg
Normal file
1
web/public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
11
web/src/App.jsx
Normal file
11
web/src/App.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
function App() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className={"text-lg"}>ทดสอบนำเข้าฟอนต์ในโปรเจกต์ Daisy UI</h1>
|
||||
<button className="btn btn-neutral">Neutral</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
1
web/src/assets/react.svg
Normal file
1
web/src/assets/react.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
4
web/src/index.css
Normal file
4
web/src/index.css
Normal file
@ -0,0 +1,4 @@
|
||||
@import "tailwindcss";
|
||||
@plugin "daisyui"{
|
||||
themes: light --default, dark --prefersdark, corporate;
|
||||
}
|
||||
11
web/src/main.jsx
Normal file
11
web/src/main.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.jsx'
|
||||
import './styles.css'
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
5
web/src/styles.css
Normal file
5
web/src/styles.css
Normal file
@ -0,0 +1,5 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Noto%20Sans%20Thai:wght@400;500;600;700;800;900&display=swap");
|
||||
|
||||
body {
|
||||
font-family: 'Noto Sans Thai', sans-serif;
|
||||
}
|
||||
8
web/vite.config.js
Normal file
8
web/vite.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import tailwindcss from '@tailwindcss/vite';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user