diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..975c435 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +settings.py +.env diff --git a/app/.env.example b/app/.env.example new file mode 100644 index 0000000..e8e7364 --- /dev/null +++ b/app/.env.example @@ -0,0 +1,20 @@ +SECRET_KEY = 장고 시크릿키 + +# db 구조 정의 +ENGINE = django.db.backends.mysql +NAME = +USER = +PASSWORD = +HOST = +PORT = +init_command = SET sql_mode="STRICT_TRANS_TABLES" + + +# 소셜 로그인 api 키와 시크릿값 +SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = +SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = +SOCIAL_AUTH_GOOGLE_OAUTH2_REDIRECT_URI = +SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE=["profile", "email"] + +SOCIAL_AUTH_KAKAO_KEY = +SOCIAL_AUTH_KAKAO_SECRET = diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__pycache__/__init__.cpython-312.pyc b/app/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..ecdc0ce Binary files /dev/null and b/app/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/__pycache__/admin.cpython-312.pyc b/app/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000..9b0aa99 Binary files /dev/null and b/app/__pycache__/admin.cpython-312.pyc differ diff --git a/app/__pycache__/apps.cpython-312.pyc b/app/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000..4e18d09 Binary files /dev/null and b/app/__pycache__/apps.cpython-312.pyc differ diff --git a/app/__pycache__/forms.cpython-312.pyc b/app/__pycache__/forms.cpython-312.pyc new file mode 100644 index 0000000..03225c1 Binary files /dev/null and b/app/__pycache__/forms.cpython-312.pyc differ diff --git a/app/__pycache__/models.cpython-312.pyc b/app/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..63f3a9f Binary files /dev/null and b/app/__pycache__/models.cpython-312.pyc differ diff --git a/app/__pycache__/operator.cpython-312.pyc b/app/__pycache__/operator.cpython-312.pyc new file mode 100644 index 0000000..d27ba32 Binary files /dev/null and b/app/__pycache__/operator.cpython-312.pyc differ diff --git a/app/__pycache__/urls.cpython-312.pyc b/app/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000..e12323d Binary files /dev/null and b/app/__pycache__/urls.cpython-312.pyc differ diff --git a/app/__pycache__/views.cpython-312.pyc b/app/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000..7c69fde Binary files /dev/null and b/app/__pycache__/views.cpython-312.pyc differ diff --git a/app/admin.py b/app/admin.py new file mode 100644 index 0000000..5d3a402 --- /dev/null +++ b/app/admin.py @@ -0,0 +1,6 @@ +# admin.py + +from django.contrib import admin +from .models import LocalUser + +admin.site.register(LocalUser) diff --git a/app/apps.py b/app/apps.py new file mode 100644 index 0000000..f3675a7 --- /dev/null +++ b/app/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig +from django.conf import settings + + +class MainConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'app' + + def ready(self): + if settings.SCHEDULER_DEFAULT: + from . import operator + operator.start() diff --git a/app/forms.py b/app/forms.py new file mode 100644 index 0000000..290a9d9 --- /dev/null +++ b/app/forms.py @@ -0,0 +1,7 @@ +# app/forms.py + +from django import forms + +class LocalLoginForm(forms.Form): + user_id = forms.CharField(label="아이디", max_length=100) + user_pw = forms.CharField(label="비밀번호", widget=forms.PasswordInput) diff --git a/app/migrations/0001_initial.py b/app/migrations/0001_initial.py new file mode 100644 index 0000000..6943faa --- /dev/null +++ b/app/migrations/0001_initial.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2 on 2025-05-07 09:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='LocalUser', + 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')), + ('user_id', models.CharField(max_length=100, unique=True)), + ('user_pw', models.CharField(max_length=100)), + ('user_name', models.CharField(max_length=100)), + ('is_admin', models.BooleanField(default=False)), + ], + options={ + 'abstract': False, + }, + ), + ] diff --git a/app/migrations/0002_remove_localuser_last_login_and_more.py b/app/migrations/0002_remove_localuser_last_login_and_more.py new file mode 100644 index 0000000..7d80e92 --- /dev/null +++ b/app/migrations/0002_remove_localuser_last_login_and_more.py @@ -0,0 +1,21 @@ +# Generated by Django 5.2 on 2025-05-07 10:21 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='localuser', + name='last_login', + ), + migrations.RemoveField( + model_name='localuser', + name='password', + ), + ] diff --git a/app/migrations/0003_alter_localuser_table.py b/app/migrations/0003_alter_localuser_table.py new file mode 100644 index 0000000..18b31f5 --- /dev/null +++ b/app/migrations/0003_alter_localuser_table.py @@ -0,0 +1,17 @@ +# Generated by Django 5.2 on 2025-05-09 02:19 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0002_remove_localuser_last_login_and_more'), + ] + + operations = [ + migrations.AlterModelTable( + name='localuser', + table='user', + ), + ] diff --git a/app/migrations/0004_asset_coin_archive_coin_recent_trade_history_and_more.py b/app/migrations/0004_asset_coin_archive_coin_recent_trade_history_and_more.py new file mode 100644 index 0000000..65592c1 --- /dev/null +++ b/app/migrations/0004_asset_coin_archive_coin_recent_trade_history_and_more.py @@ -0,0 +1,136 @@ +# Generated by Django 5.2 on 2025-05-18 04:47 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0003_alter_localuser_table'), + ] + + operations = [ + migrations.CreateModel( + name='Asset', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('coin_name', models.CharField(max_length=10)), + ('coin_amount', models.FloatField()), + ('coin_price', models.FloatField()), + ('total_value', models.FloatField()), + ('trade_time', models.DateTimeField(auto_now_add=True)), + ('money', models.FloatField()), + ], + options={ + 'db_table': 'asset', + }, + ), + migrations.CreateModel( + name='coin_archive', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('coin_name', models.CharField(max_length=10)), + ('opening_price', models.FloatField()), + ('high_price', models.FloatField()), + ('low_price', models.FloatField()), + ('trade_price', models.FloatField()), + ('time_stamp', models.DateTimeField()), + ('candle_acc_trade_price', models.FloatField()), + ('candle_acc_trade_volume', models.FloatField()), + ('candle_time_kst', models.DateTimeField(auto_now_add=True)), + ('interval', models.CharField(max_length=10)), + ], + options={ + 'db_table': 'coin_archive', + }, + ), + migrations.CreateModel( + name='coin_recent', + fields=[ + ('coin_name', models.CharField(max_length=10, primary_key=True, serialize=False)), + ('opening_price', models.FloatField()), + ('high_price', models.FloatField()), + ('low_price', models.FloatField()), + ('trade_price', models.FloatField()), + ('time_stamp', models.DateTimeField()), + ('candle_acc_trade_price', models.FloatField()), + ('candle_acc_trade_volume', models.FloatField()), + ('candle_time_kst', models.DateTimeField(auto_now_add=True)), + ('interval', models.CharField(max_length=10)), + ], + options={ + 'db_table': 'coin_recent', + }, + ), + migrations.CreateModel( + name='trade_history', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('coin_name', models.CharField(max_length=10)), + ('coin_price', models.FloatField()), + ('coin_amount', models.FloatField()), + ('order_time', models.DateTimeField()), + ('trade_time', models.DateTimeField(auto_now_add=True)), + ('is_deal', models.BooleanField(default=False)), + ('trade_fee', models.FloatField(default=0.0)), + ], + options={ + 'db_table': 'trade_history', + }, + ), + migrations.CreateModel( + name='trade_order_request', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('coin_name', models.CharField(max_length=10)), + ('coin_price', models.FloatField()), + ('coin_amount', models.FloatField()), + ('order_time', models.DateTimeField(auto_now_add=True)), + ('trade_type', models.CharField(max_length=10)), + ('order_id', models.FloatField(unique=True)), + ], + options={ + 'db_table': 'trade_request', + }, + ), + migrations.AddIndex( + model_name='localuser', + index=models.Index(fields=['user_id'], name='user_user_id_31c9cf_idx'), + ), + migrations.AddField( + model_name='asset', + name='user_id', + field=models.ForeignKey(db_column='user_id', on_delete=django.db.models.deletion.CASCADE, to='app.localuser'), + ), + migrations.AddIndex( + model_name='coin_archive', + index=models.Index(fields=['coin_name', 'time_stamp'], name='coin_archiv_coin_na_eaa4b2_idx'), + ), + migrations.AddIndex( + model_name='coin_recent', + index=models.Index(fields=['coin_name', 'time_stamp'], name='coin_recent_coin_na_70d127_idx'), + ), + migrations.AddField( + model_name='trade_history', + name='user_id', + field=models.ForeignKey(db_column='user_id', on_delete=django.db.models.deletion.CASCADE, to='app.localuser'), + ), + migrations.AddField( + model_name='trade_order_request', + name='user_id', + field=models.ForeignKey(db_column='user_id', on_delete=django.db.models.deletion.CASCADE, to='app.localuser'), + ), + migrations.AddIndex( + model_name='asset', + index=models.Index(fields=['user_id'], name='asset_user_id_bcef9f_idx'), + ), + migrations.AddIndex( + model_name='trade_history', + index=models.Index(fields=['user_id'], name='trade_histo_user_id_8687dc_idx'), + ), + migrations.AddIndex( + model_name='trade_order_request', + index=models.Index(fields=['user_id'], name='trade_reque_user_id_5667ec_idx'), + ), + ] diff --git a/app/migrations/__init__.py b/app/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/migrations/__pycache__/0001_initial.cpython-312.pyc b/app/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000..913fc7f Binary files /dev/null and b/app/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/app/migrations/__pycache__/0002_remove_localuser_last_login_and_more.cpython-312.pyc b/app/migrations/__pycache__/0002_remove_localuser_last_login_and_more.cpython-312.pyc new file mode 100644 index 0000000..80ef7c2 Binary files /dev/null and b/app/migrations/__pycache__/0002_remove_localuser_last_login_and_more.cpython-312.pyc differ diff --git a/app/migrations/__pycache__/0003_alter_localuser_table.cpython-312.pyc b/app/migrations/__pycache__/0003_alter_localuser_table.cpython-312.pyc new file mode 100644 index 0000000..65b38db Binary files /dev/null and b/app/migrations/__pycache__/0003_alter_localuser_table.cpython-312.pyc differ diff --git a/app/migrations/__pycache__/0004_asset_coin_archive_coin_recent_trade_history_and_more.cpython-312.pyc b/app/migrations/__pycache__/0004_asset_coin_archive_coin_recent_trade_history_and_more.cpython-312.pyc new file mode 100644 index 0000000..ce3a190 Binary files /dev/null and b/app/migrations/__pycache__/0004_asset_coin_archive_coin_recent_trade_history_and_more.cpython-312.pyc differ diff --git a/app/migrations/__pycache__/__init__.cpython-312.pyc b/app/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..3ff2a65 Binary files /dev/null and b/app/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/app/models.py b/app/models.py new file mode 100644 index 0000000..8c0f17e --- /dev/null +++ b/app/models.py @@ -0,0 +1,125 @@ +from django.db import models +from django.contrib.auth.models import BaseUserManager + +class LocalUserManager(BaseUserManager): + def create_user(self, user_id, user_pw, user_name, is_admin=False): + user = self.model( + user_id=user_id, + user_name=user_name, + is_admin=is_admin + ) + user.set_password(user_pw) + user.save(using=self._db) + return user + + def create_superuser(self, user_id, user_pw, user_name): + user = self.create_user( + user_id=user_id, + user_pw=user_pw, + user_name=user_name, + is_admin=True + ) + user.is_staff = True + user.is_superuser = True + user.save(using=self._db) + return user + +class LocalUser(models.Model): + user_id = models.CharField(max_length=100, unique=True) + user_pw = models.CharField(max_length=100) + user_name = models.CharField(max_length=100) + is_admin = models.BooleanField(default=False) + + def __str__(self): + return self.user_name + + class Meta: + db_table = "user" + indexes = [ + models.Index(fields=["user_id"]), + ] + +class coin_recent(models.Model): + coin_name = models.CharField(max_length=10, primary_key=True) + opening_price = models.FloatField() + high_price = models.FloatField() + low_price = models.FloatField() + trade_price = models.FloatField() + time_stamp = models.DateTimeField() + candle_acc_trade_price = models.FloatField() + candle_acc_trade_volume = models.FloatField() + candle_time_kst = models.DateTimeField(auto_now_add=True) + interval = models.CharField(max_length=10) + + + class Meta: + db_table = "coin_recent" + indexes = [ + models.Index(fields=["coin_name", "time_stamp"]), + ] + + +class coin_archive(models.Model): + coin_name = models.CharField(max_length=10) + opening_price = models.FloatField() + high_price = models.FloatField() + low_price = models.FloatField() + trade_price = models.FloatField() + time_stamp = models.DateTimeField() + candle_acc_trade_price = models.FloatField() + candle_acc_trade_volume = models.FloatField() + candle_time_kst = models.DateTimeField(auto_now_add=True) + interval = models.CharField(max_length=10) + + class Meta: + db_table = "coin_archive" + indexes = [ + models.Index(fields=["coin_name", "time_stamp"]), + ] + +class trade_order_request(models.Model): + user_id = models.ForeignKey(LocalUser, on_delete=models.CASCADE, db_column='user_id') + coin_name = models.CharField(max_length=10) + coin_price = models.FloatField() + coin_amount = models.FloatField() + order_time = models.DateTimeField(auto_now_add=True) + trade_type = models.CharField(max_length=10) # 'buy' or 'sell' + order_id = models.FloatField(unique=True) + + class Meta: + db_table = "trade_request" + indexes = [ + models.Index(fields=["user_id"]) + ] + +class Asset(models.Model): + user_id = models.ForeignKey(LocalUser, on_delete=models.CASCADE,db_column='user_id') + coin_name = models.CharField(max_length=10) + coin_amount = models.FloatField() + coin_price = models.FloatField() + total_value = models.FloatField() + trade_time = models.DateTimeField(auto_now_add=True) + money = models.FloatField() + + class Meta: + db_table = "asset" + indexes = [ + models.Index(fields=["user_id"]), + ] + + +class trade_history(models.Model): + user_id = models.ForeignKey(LocalUser, on_delete=models.CASCADE,db_column='user_id') + coin_name = models.CharField(max_length=10) + coin_price = models.FloatField() + coin_amount = models.FloatField() + order_time = models.DateTimeField() + trade_time = models.DateTimeField(auto_now_add=True) + is_deal = models.BooleanField(default=False) + trade_fee = models.FloatField(default=0.0) + + class Meta: + db_table = "trade_history" + indexes = [ + models.Index(fields=["user_id"]) + ] diff --git a/app/operator.py b/app/operator.py new file mode 100644 index 0000000..1c13b21 --- /dev/null +++ b/app/operator.py @@ -0,0 +1,10 @@ +from apscheduler.schedulers.background import BackgroundScheduler +from app.views import coin_store_scheduler +from app.views import compare_order_and_coin_value_scheduler + + +def start(): + scheduler = BackgroundScheduler(timezone='Asia/Seoul') + scheduler.add_job(coin_store_scheduler, 'interval', seconds=60) + scheduler.add_job(compare_order_and_coin_value_scheduler, 'interval', seconds=3) + scheduler.start() \ No newline at end of file diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..23d7cb9 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,2 @@ +python-dotenv +# pip install -r requirements.txt 명령어로 한번에 설치 가능 \ No newline at end of file diff --git a/css/find_ID.css b/app/static/css/find_page.css similarity index 100% rename from css/find_ID.css rename to app/static/css/find_page.css diff --git a/css/main.css b/app/static/css/index.css similarity index 100% rename from css/main.css rename to app/static/css/index.css diff --git a/style.css b/app/static/css/style.css similarity index 100% rename from style.css rename to app/static/css/style.css diff --git a/app/static/js/RestAPI.js b/app/static/js/RestAPI.js new file mode 100644 index 0000000..e270c86 --- /dev/null +++ b/app/static/js/RestAPI.js @@ -0,0 +1,44 @@ +require('dotenv').config(); +const mysql = require('mysql2/promise'); + +// AWS RDS 연결 설정 +//npm install dotenv 다운로드 필요 +const dbConfig = { + host: awsData.env.DB_HOST, + user: awsData.env.DB_USER, + password: awsData.env.DB_PASSWORD, + database: awsData.env.DB_NAME +}; + +// 최신 데이터 값을 가져오는 함수 +async function getSensorData() { + let connection; + + try { + // 데이터베이스 연결 + connection = await mysql.createConnection(dbConfig); + + // 최신 index 값 조회 쿼리 + const [rows] = await connection.execute( + `SELECT * FROM your_table_name ORDER BY index DESC LIMIT 1` //가장 최근 데이터값들을 가져옴 (움직임, 가스, 온습도) + ); + + if (rows.length > 0) { + console.log("value:", rows); //가져오는데 성공했을 시 나오는 값 + const dataSet = [rows[0].data1, rows[0].data2, rows[0].data3] //각 테이블 이름으로 data1,2,3으로 저장될것으로 예상됨 + return dataSet; + } else { + console.log("No data"); //데이터 테이블이 비어있음. + return null; + } + } catch (error) { + console.error("Database query error:", error); //쿼리문제 + } finally { + if (connection) { + await connection.end(); // 연결 해제 + } + } +} + +// 주기적으로 최신 index 값 호출 +setInterval(getSensorData, 1000); //1초 \ No newline at end of file diff --git a/app/static/js/coin_data.js b/app/static/js/coin_data.js new file mode 100644 index 0000000..22813d0 --- /dev/null +++ b/app/static/js/coin_data.js @@ -0,0 +1,59 @@ +let interval = null; + +function fetch_coin_data() { + console.log("running fetch_coin_data") + function fetch_data() { + fetch('/coin_value') + .then(response => response.json()) + .then(data=> { + document.getElementById('value').innerText = data.value;}) + .catch(error => { + console.error('Error fetching data:', error); + }); + } + if (!interval) { + interval = setInterval(fetch_data, 5000); + } +} + +function coin_trade_request(){ + const amount = document.getElementById('amount') + const price = document.getElementById('price') + const coin_name = document.getElementById("coin-name") + let trade_type = document.getElementById("trade-action") + + if (type.value == "매수"){ + trade_type = "buy"; + } else if (type.value == "매도"){ + trade_type = "sell"; + } + + fetch("trade_request", { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + coin_name: coin_name.value, + coin_price: price.value, + coin_amount: amount.value, + trade_type : trade_type + }) + }) + .then(response => { + if (response.ok) { + return response.json(); + } else { + throw new Error('Network response was not ok'); + } + }) +} + + + +/*function persentage_amount() { + const amount = document.getElementById('amount').value; + const value = document.getElementById('value').innerText; + const total = parseFloat(amount) * parseFloat(value); + document.getElementById('total').innerText = total.toFixed(2); +}*/ \ No newline at end of file diff --git a/app/static/js/index.js b/app/static/js/index.js new file mode 100644 index 0000000..a911567 --- /dev/null +++ b/app/static/js/index.js @@ -0,0 +1,254 @@ +// DOM이 모두 로드된 뒤 실행 +document.addEventListener("DOMContentLoaded", () => { + // 탭 전환 기능 + const tabButtons = document.querySelectorAll(".tab-btn"); + const contentBoxes = document.querySelectorAll(".content-box"); + + tabButtons.forEach((btn) => { + btn.addEventListener("click", () => { + contentBoxes.forEach((box) => box.classList.remove("active")); + const target = document.getElementById(btn.dataset.target); + if (target) target.classList.add("active"); + }); + }); + + const loginBtn = document.getElementById("loginBtn"); + if (loginBtn) { + loginBtn.addEventListener("click", () => { + window.location.href = "login.html"; + }); + } + + const searchBtn = document.getElementById("searchBtn"); + if (searchBtn) { + searchBtn.addEventListener("click", () => { + const coinName = document.getElementById("coinSearch").value; + alert(`'${coinName}'로 검색을 수행합니다(예시).`); + }); + } + + // 예시 데이터 주석 처리 + /* + document.getElementById("total-assets").textContent = "10,000,000"; + document.getElementById("available-balance").textContent = "2,000,000"; + document.getElementById("totalHoldings").textContent = "8,000,000"; + document.getElementById("krwBalance").textContent = "1,000,000"; + + const sampleHistory = [ + { date: "2025-03-01", type: "매수", coin: "BTC", amount: 0.01 }, + { date: "2025-03-02", type: "매도", coin: "ETH", amount: 0.5 }, + ]; + const historyList = document.getElementById("historyList"); + sampleHistory.forEach(item => { + const li = document.createElement("li"); + li.textContent = `${item.date} | ${item.type} | ${item.coin} ${item.amount}`; + historyList.appendChild(li); + }); + + const sampleOpenOrders = [ + { date: "2025-03-03", type: "매수", coin: "XRP", amount: 100 }, + ]; + const openOrdersList = document.getElementById("openOrdersList"); + sampleOpenOrders.forEach(item => { + const li = document.createElement("li"); + li.textContent = `${item.date} | ${item.type} | ${item.coin} ${item.amount}`; + openOrdersList.appendChild(li); + }); + + const samplePending = [ + { date: "2025-03-04", action: "출금대기", coin: "KRW", amount: 500000 }, + ]; + const pendingDepositList = document.getElementById("pendingDepositList"); + samplePending.forEach(item => { + const li = document.createElement("li"); + li.textContent = `${item.date} | ${item.action} | ${item.coin} ${item.amount}`; + pendingDepositList.appendChild(li); + }); + + const sampleProfitLoss = [ + { coin: "BTC", profit: 300000 }, + { coin: "ETH", profit: -50000 }, + ]; + const profitLossList = document.getElementById("profitLossList"); + sampleProfitLoss.forEach(item => { + const li = document.createElement("li"); + li.textContent = `${item.coin} | 손익: ${item.profit} KRW`; + profitLossList.appendChild(li); + }); + + const sampleHoldings = [ + { coin: "BTC", amount: 0.05 }, + { coin: "ETH", amount: 1.2 }, + { coin: "XRP", amount: 500 }, + ]; + const coinHoldingsList = document.getElementById("coinHoldingsList"); + sampleHoldings.forEach(item => { + const li = document.createElement("li"); + li.textContent = `${item.coin} : ${item.amount}`; + coinHoldingsList.appendChild(li); + }); + */ +}); + +// 샘플 데이터 주석 처리 +/* +const sampleData = [ + { time_close: "2025-01-01", open: 49500, high: 50800, low: 49300, close: 50000 }, + ... +]; +const coinListData = [ + { name: "비트코인", price: 59823, change: 2.34, volume: 12456.78 }, + ... +]; +*/ + +// 빈 배열로 대체 (초기 렌더링 방지) +const sampleData = []; +const coinListData = []; + +// 차트 기본 옵션 (빈 데이터) +const options = { + series: [{ + data: [] + }], + chart: { + type: 'candlestick', + height: 500, + toolbar: { tools: {} }, + background: 'transparent' + }, + theme: { mode: "dark" }, + grid: { show: false }, + plotOptions: { + candlestick: { wick: { useFillColor: true } } + }, + xaxis: { + labels: { show: false }, + type: 'datetime', + categories: [], + axisBorder: { show: false }, + axisTicks: { show: false } + }, + yaxis: { show: false }, + tooltip: { + y: { + formatter: v => `$ ${v.toFixed(2)}` + } + } +}; + +const miniOptions = { + series: [{ + name: "가격", + data: [] + }], + chart: { + type: 'area', + height: 150, + toolbar: { show: false }, + background: 'transparent' + }, + stroke: { + curve: 'smooth', + width: 2 + }, + fill: { + type: 'gradient', + gradient: { + shadeIntensity: 1, + opacityFrom: 0.7, + opacityTo: 0.3, + stops: [0, 100] + } + }, + grid: { show: false }, + xaxis: { + type: 'datetime', + categories: [], + labels: { show: false }, + axisBorder: { show: false }, + axisTicks: { show: false } + }, + yaxis: { show: false }, + tooltip: { + y: { + formatter: val => '$' + val.toFixed(2) + }, + theme: 'dark' + }, + colors: ['#007bff'] +}; + +// 차트 생성 +document.addEventListener('DOMContentLoaded', function () { + const chart = new ApexCharts(document.querySelector("#chart"), options); + chart.render(); + + const miniChart = new ApexCharts(document.querySelector("#mini-chart"), miniOptions); + miniChart.render(); + + // 테이블 초기화 + const coinListBody = document.getElementById('coin-list-body'); + if (coinListBody) coinListBody.innerHTML = ''; + + // 거래창 + const priceInput = document.querySelector('.input-group input[placeholder="금액 입력"]'); + const quantityInput = document.querySelector('.input-group input[placeholder="직접 입력"]'); + const totalInput = document.querySelector('.input-group input[disabled]'); + + function calculateTotal() { + const price = parseFloat(priceInput.value) || 0; + const quantity = parseFloat(quantityInput.value) || 0; + totalInput.value = (price * quantity).toLocaleString() + ' 원'; + } + + priceInput.addEventListener('input', calculateTotal); + quantityInput.addEventListener('input', calculateTotal); + + const buyBtn = document.querySelector('.buy-btn'); + const sellBtn = document.querySelector('.sell-btn'); + const tradeActionBtn = document.querySelector('.trade-action'); + + buyBtn.addEventListener('click', function () { + buyBtn.style.opacity = '1'; + sellBtn.style.opacity = '0.5'; + tradeActionBtn.textContent = '매수'; + }); + + sellBtn.addEventListener('click', function () { + sellBtn.style.opacity = '1'; + buyBtn.style.opacity = '0.5'; + tradeActionBtn.textContent = '매도'; + }); + + buyBtn.style.opacity = '1'; + sellBtn.style.opacity = '0.5'; + tradeActionBtn.textContent = '매수'; + + const percentButtons = document.querySelectorAll('.percentage-buttons button'); + percentButtons.forEach(btn => { + btn.addEventListener('click', function () { + const percent = parseInt(btn.textContent) / 100; + const maxQuantity = 1.0; // 실제 로직에서 대체 + quantityInput.value = (maxQuantity * percent).toFixed(4); + calculateTotal(); + }); + }); + + const minusBtn = document.querySelector('.adjust-buttons button:first-child'); + const plusBtn = document.querySelector('.adjust-buttons button:last-child'); + + minusBtn.addEventListener('click', function () { + const currentPrice = parseFloat(priceInput.value) || 0; + priceInput.value = Math.max(0, currentPrice - 100).toString(); + calculateTotal(); + }); + + plusBtn.addEventListener('click', function () { + const currentPrice = parseFloat(priceInput.value) || 0; + priceInput.value = (currentPrice + 100).toString(); + calculateTotal(); + }); + + calculateTotal(); +}); diff --git a/app/static/js/login.js b/app/static/js/login.js new file mode 100644 index 0000000..8d34f37 --- /dev/null +++ b/app/static/js/login.js @@ -0,0 +1,25 @@ +function socialLoginPopup(provider) { + const redirectUrl = '/main_page/'; + const loginUrl = `/accounts/${provider}/login/?next=${redirectUrl}`; + + const width = 500; + const height = 600; + const left = (screen.width / 2) - (width / 2); + const top = (screen.height / 2) - (height / 2); + + const popup = window.open( + loginUrl, + `${provider}_login`, + `width=${width},height=${height},top=${top},left=${left},resizable=yes,scrollbars=yes` + ); + + // ✅ 메시지 수신 처리 + window.addEventListener("message", function(event) { + if (event.data === "social-login-success") { + if (popup && !popup.closed) { + popup.close(); + } + window.location.href = redirectUrl; + } + }); +} diff --git a/app/static/js/script.js b/app/static/js/script.js new file mode 100644 index 0000000..720ac24 --- /dev/null +++ b/app/static/js/script.js @@ -0,0 +1,351 @@ +// DOM이 모두 로드된 뒤 실행 +document.addEventListener("DOMContentLoaded", () => { + // 탭 전환 기능 + const tabButtons = document.querySelectorAll(".tab-btn"); + const contentBoxes = document.querySelectorAll(".content-box"); + + tabButtons.forEach((btn) => { + btn.addEventListener("click", () => { + // 모든 content-box에서 active 제거 + contentBoxes.forEach((box) => box.classList.remove("active")); + // 클릭한 버튼에 해당하는 content-box만 active 추가 + const target = document.getElementById(btn.dataset.target); + if (target) { + target.classList.add("active"); + } + }); + }); + + // 예시: 로그인 버튼 클릭 시 login.html로 이동 + const loginBtn = document.getElementById("loginBtn"); + if (loginBtn) { + loginBtn.addEventListener("click", () => { + window.location.href = "login.html"; + }); + } + + // 검색 버튼 클릭 시 예시 기능 + const searchBtn = document.getElementById("searchBtn"); + if (searchBtn) { + searchBtn.addEventListener("click", () => { + const coinName = document.getElementById("coinSearch").value; + alert(`'${coinName}'로 검색을 수행합니다(예시).`); + // 실제로는 서버나 DB에서 검색 결과를 받아 아래 목록에 반영하는 로직 필요 + }); + } + + // 샘플 데이터 표시(실제론 서버나 DB에서 받아와서 표시) + document.getElementById("total-assets").textContent = "10,000,000"; // 총 투자자산 예시 + document.getElementById("available-balance").textContent = "2,000,000"; // 예수금 예시 + document.getElementById("totalHoldings").textContent = "8,000,000"; // 총 보유자산 예시 + document.getElementById("krwBalance").textContent = "1,000,000"; // 보유 KRW 예시 + + // 예시 거래내역 + const historyList = document.getElementById("historyList"); + if (historyList) { + const sampleHistory = [ + { date: "2025-03-01", type: "매수", coin: "BTC", amount: 0.01 }, + { date: "2025-03-02", type: "매도", coin: "ETH", amount: 0.5 }, + ]; + sampleHistory.forEach((item) => { + const li = document.createElement("li"); + li.textContent = `${item.date} | ${item.type} | ${item.coin} ${item.amount}`; + historyList.appendChild(li); + }); + } + + // 예시 미체결 + const openOrdersList = document.getElementById("openOrdersList"); + if (openOrdersList) { + const sampleOpenOrders = [ + { date: "2025-03-03", type: "매수", coin: "XRP", amount: 100 }, + ]; + sampleOpenOrders.forEach((item) => { + const li = document.createElement("li"); + li.textContent = `${item.date} | ${item.type} | ${item.coin} ${item.amount}`; + openOrdersList.appendChild(li); + }); + } + + // 예시 입출금대기 + const pendingDepositList = document.getElementById("pendingDepositList"); + if (pendingDepositList) { + const samplePending = [ + { date: "2025-03-04", action: "출금대기", coin: "KRW", amount: 500000 }, + ]; + samplePending.forEach((item) => { + const li = document.createElement("li"); + li.textContent = `${item.date} | ${item.action} | ${item.coin} ${item.amount}`; + pendingDepositList.appendChild(li); + }); + } + + // 예시 투자손익 + const profitLossList = document.getElementById("profitLossList"); + if (profitLossList) { + const sampleProfitLoss = [ + { coin: "BTC", profit: 300000 }, + { coin: "ETH", profit: -50000 }, + ]; + sampleProfitLoss.forEach((item) => { + const li = document.createElement("li"); + li.textContent = `${item.coin} | 손익: ${item.profit} KRW`; + profitLossList.appendChild(li); + }); + } + + // 예시 보유 코인 목록 + const coinHoldingsList = document.getElementById("coinHoldingsList"); + if (coinHoldingsList) { + const sampleHoldings = [ + { coin: "BTC", amount: 0.05 }, + { coin: "ETH", amount: 1.2 }, + { coin: "XRP", amount: 500 }, + ]; + sampleHoldings.forEach((item) => { + const li = document.createElement("li"); + li.textContent = `${item.coin} : ${item.amount}`; + coinHoldingsList.appendChild(li); + }); + } + }); + + + // 샘플 데이터 - 캔들스틱 차트용 +const sampleData = [ + { time_close: "2025-01-01", open: 49500, high: 50800, low: 49300, close: 50000 }, + { time_close: "2025-01-02", open: 50100, high: 51500, low: 50000, close: 51200 }, + { time_close: "2025-01-03", open: 51300, high: 52700, low: 51000, close: 52500 }, + { time_close: "2025-01-04", open: 52600, high: 53500, low: 52200, close: 53000 }, + { time_close: "2025-01-05", open: 53100, high: 53200, low: 51500, close: 51800 }, + { time_close: "2025-01-06", open: 51900, high: 53000, low: 51700, close: 52700 }, + { time_close: "2025-01-07", open: 52800, high: 54500, low: 52600, close: 54200 }, + { time_close: "2025-01-08", open: 54300, high: 55300, low: 53900, close: 55100 }, + { time_close: "2025-01-09", open: 55200, high: 55400, low: 54000, close: 54500 }, + { time_close: "2025-01-10", open: 54600, high: 56300, low: 54400, close: 56000 }, + { time_close: "2025-01-11", open: 56100, high: 57500, low: 55800, close: 57200 }, + { time_close: "2025-01-12", open: 57300, high: 58600, low: 57100, close: 58400 }, + { time_close: "2025-01-13", open: 58500, high: 58700, low: 57400, close: 57800 }, + { time_close: "2025-01-14", open: 57900, high: 59400, low: 57700, close: 59100 }, + { time_close: "2025-01-15", open: 59200, high: 60100, low: 59000, close: 59800 }, +]; + +// 코인 목록 데이터 +const coinListData = [ + { name: "비트코인", price: 59823, change: 2.34, volume: 12456.78 }, + { name: "이더리움", price: 3256, change: 1.25, volume: 8549.32 }, + { name: "리플", price: 0.52, change: -0.75, volume: 4852.16 }, + { name: "라이트코인", price: 189.45, change: 0.88, volume: 2154.96 }, + { name: "도지코인", price: 0.08, change: 5.23, volume: 9621.45 }, + { name: "카르다노", price: 0.46, change: -1.32, volume: 3256.78 }, + { name: "폴카닷", price: 5.87, change: 0.54, volume: 1895.34 }, + { name: "솔라나", price: 119.45, change: 3.67, volume: 5632.12 } +]; + +// 메인 차트 설정 (캔들스틱 차트로 변경) +const options = { + series: [{ + data: sampleData.map(price => ({ + x: price.time_close, + y: [price.open, price.high, price.low, price.close], + })) + }], + chart: { + type: 'candlestick', + height: 500, + toolbar: { + tools: {} + }, + background: 'transparent' + }, + theme: { + mode: "dark" + }, + grid: { + show: false + }, + plotOptions: { + candlestick: { + wick: { + useFillColor: true + } + } + }, + xaxis: { + labels: { + show: false, + datetimeFormatter: { + month: "mmm 'yy" + } + }, + type: 'datetime', + categories: sampleData.map(date => date.time_close), + axisBorder: { + show: false + }, + axisTicks: { + show: false + } + }, + yaxis: { + show: false + }, + tooltip: { + y: { + formatter: v => `$ ${v.toFixed(2)}` + } + } +}; + +// 미니 차트 설정 (간단한 영역 차트 유지) +const miniOptions = { + series: [{ + name: "가격", + data: sampleData.slice(-7).map(price => Number(price.close)) + }], + chart: { + type: 'area', + height: 150, + toolbar: { + show: false + }, + background: 'transparent' + }, + stroke: { + curve: 'smooth', + width: 2 + }, + fill: { + type: 'gradient', + gradient: { + shadeIntensity: 1, + opacityFrom: 0.7, + opacityTo: 0.3, + stops: [0, 100] + } + }, + grid: { + show: false + }, + xaxis: { + type: 'datetime', + categories: sampleData.slice(-7).map(date => date.time_close), + labels: { + show: false + }, + axisBorder: { + show: false + }, + axisTicks: { + show: false + } + }, + yaxis: { + show: false + }, + tooltip: { + y: { + formatter: function(val) { + return '$' + val.toFixed(2); + } + }, + theme: 'dark' + }, + colors: ['#007bff'] +}; + +// 차트 생성 및 이벤트 처리 +document.addEventListener('DOMContentLoaded', function() { + const chart = new ApexCharts(document.querySelector("#chart"), options); + chart.render(); + + const miniChart = new ApexCharts(document.querySelector("#mini-chart"), miniOptions); + miniChart.render(); + + // 코인 목록 채우기 + const coinListBody = document.getElementById('coin-list-body'); + + coinListData.forEach(coin => { + const row = document.createElement('tr'); + row.innerHTML = ` +
고가: 0
-저가: 0
-거래량: 0
-거래대금: 0
+고가: 61,245.00
+저가: 58,752.00
+거래량: 12,456.78
+거래대금: 735,487,952