Compare commits
10 Commits
b50643a3e2
...
1ea9e64667
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ea9e64667 | |||
| 51b9a99f95 | |||
| a57fc28825 | |||
| 4ff50f465a | |||
| 5decf048bf | |||
| 272cd484b8 | |||
| d998aef91e | |||
| 0ae67bd63f | |||
| 12192aa5c7 | |||
| 6b671f8881 |
@@ -15,3 +15,6 @@ __pycache__/
|
|||||||
|
|
||||||
# python compiled files
|
# python compiled files
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
# mac
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
ChocoMae (초코마에) is a Korean/English typing and mouse practice web application. It features gamified typing exercises (using Phaser 2.6.2), classroom management for teachers (Maestros), and a ranking/record system.
|
||||||
|
|
||||||
|
## Tech Stack
|
||||||
|
|
||||||
|
- **Frontend:** HTML5, JavaScript (ES5), jQuery 3.3.1, Phaser 2.6.2 game engine, Bootstrap
|
||||||
|
- **Backend:** PHP 7.x with MySQLi, Python (batch scripts)
|
||||||
|
- **Database:** MariaDB/MySQL (`chocomae` database)
|
||||||
|
- **Server:** Apache HTTP Server with SSL on AWS EC2 (Docker container)
|
||||||
|
|
||||||
|
## Development Commands
|
||||||
|
|
||||||
|
This project has **no Node.js/npm build step** for the main application. It runs directly on Apache + PHP.
|
||||||
|
|
||||||
|
**Phaser input plugin** (only if modifying `/src/util/phaser-input-master/`):
|
||||||
|
```sh
|
||||||
|
cd src/util/phaser-input-master
|
||||||
|
npm install # install grunt + TypeScript deps
|
||||||
|
grunt # build TypeScript → JS
|
||||||
|
```
|
||||||
|
|
||||||
|
**Database setup** (initialize schema):
|
||||||
|
```sh
|
||||||
|
mysql -u <user> -p chocomae < src/web/sql/make_db.sql
|
||||||
|
mysql -u <user> -p chocomae < src/web/sql/make_db_license_timer.sql
|
||||||
|
mysql -u <user> -p chocomae < src/web/sql/insert_app.sql
|
||||||
|
mysql -u <user> -p chocomae < src/web/sql/insert_active_app.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deployment** — push to `release` branch; the git post-receive hook automatically:
|
||||||
|
1. Checks out files to `/volume1/docker/chocomae/html/mouse_typing/`
|
||||||
|
2. Copies `NA_service_db_setting.php` → `service_db_setting.php` and replaces `localhost` with `mysql` (Docker hostname)
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── game/ # Phaser game modules (client-side JS)
|
||||||
|
├── web/
|
||||||
|
│ ├── main/ # Entry point (index.html) + top-level HTML pages
|
||||||
|
│ ├── module/ # HTML fragments loaded dynamically via jQuery .load()
|
||||||
|
│ ├── js/ # Frontend JavaScript
|
||||||
|
│ ├── css/ # Stylesheets
|
||||||
|
│ ├── server/ # PHP backend endpoints
|
||||||
|
│ ├── admin/ # Admin panel HTML
|
||||||
|
│ ├── popup/ # Popup/modal HTML
|
||||||
|
│ └── sql/ # Database schema and seed files
|
||||||
|
├── php-cli/ # PHP batch scripts (run via cron)
|
||||||
|
└── util/ # Third-party utilities (phaser-input plugin source)
|
||||||
|
resources/ # Static assets: Bootstrap, jQuery, fonts, images
|
||||||
|
test/ # Manual test HTML/JS files (no automated test runner)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Frontend Architecture
|
||||||
|
|
||||||
|
- **Entry point:** `src/web/main/index.html` — loads header/section/footer modules dynamically
|
||||||
|
- **Module loading:** jQuery `.load()` fetches HTML fragments from `src/web/module/`
|
||||||
|
- **State:** `sessionStorage` used for `maestroID` and game session data
|
||||||
|
- **Game engine:** All interactive games live in `src/game/` and use Phaser 2.6.2; each game directory has its own `main.js` with Phaser states (Boot, Load, Game, Result)
|
||||||
|
|
||||||
|
### Backend Architecture
|
||||||
|
|
||||||
|
PHP endpoints in `src/web/server/` are organized by domain:
|
||||||
|
- `player/` — CRUD for player accounts
|
||||||
|
- `record/` — Save/retrieve game results and rankings
|
||||||
|
- `maestro/` — Teacher classroom management (17 endpoints)
|
||||||
|
- `admin/` — Admin operations (11 endpoints)
|
||||||
|
- `license_timer/` — Subscription/license management
|
||||||
|
- `mail/` — Email sending via PHPMailer + Gmail SMTP
|
||||||
|
- `lib/` — Shared PHP libraries (`send_reply_json.php`, `db_maestro.php`, etc.)
|
||||||
|
- `setup/connect_db.php` — Database connection; reads from `service_db_setting.php`
|
||||||
|
|
||||||
|
All endpoints return JSON. UTF-8 encoding is set on all responses.
|
||||||
|
|
||||||
|
### Database Configuration
|
||||||
|
|
||||||
|
- **Development:** Edit `src/web/server/setup/NA_service_db_setting.php` (this is the template; `service_db_setting.php` is git-ignored and generated at deploy time)
|
||||||
|
- The deploy hook replaces `localhost` → `mysql` for Docker networking
|
||||||
|
|
||||||
|
### User Roles
|
||||||
|
|
||||||
|
| Role | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| Player | End users (free or paid) who play games |
|
||||||
|
| Maestro | Teachers who manage groups of student players |
|
||||||
|
| Admin | System administrators with full access |
|
||||||
|
|
||||||
|
### Python Batch Scripts
|
||||||
|
|
||||||
|
Located in `src/web/server/python/chocomae/` and `src/php-cli/batch/` — handle scheduled tasks like sending expiration warning emails to unpaid/expiring Maestros. These are run via cron on the server.
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 278 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 440 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 193 KiB |
@@ -0,0 +1,62 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<title>개인정보 처리방침 | 초코마에</title>
|
||||||
|
<meta name="description" CONTENT="개인정보 처리방침 | 초코마에 ‧ Privacy policy | ChocoMae">
|
||||||
|
|
||||||
|
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=G-CMCJ6N2208"></script>
|
||||||
|
<script>
|
||||||
|
window.dataLayer = window.dataLayer || [];
|
||||||
|
function gtag(){dataLayer.push(arguments);}
|
||||||
|
gtag('js', new Date());
|
||||||
|
|
||||||
|
gtag('config', 'G-CMCJ6N2208');
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<link rel="icon" href="/mouse_typing/resources/image/icon/favicon.ico" type="image/x-icon" />
|
||||||
|
<link rel="shortcut icon" href="/mouse_typing/resources/image/icon/favicon.ico" type="image/x-icon" />
|
||||||
|
|
||||||
|
<link href="../../../resources/font/open-iconic-master/font/css/open-iconic-bootstrap.css" rel="stylesheet">
|
||||||
|
<link href="../../../resources/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<script type="text/javascript" src="./../../../resources/jquery/jquery-3.3.1.min.js"></script>
|
||||||
|
<script type="text/javascript" src="./../js/main.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(document).ready(function() {
|
||||||
|
if(maestroID === -1)
|
||||||
|
$("#header").load("./../module/header.html");
|
||||||
|
else
|
||||||
|
$("#header").load("./../module/maestro_header.html");
|
||||||
|
$("#section").load("./../module/home_section_privacy_policy.html");
|
||||||
|
$("#footer").load("./../module/footer.html");
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<body style="background-color: white">
|
||||||
|
|
||||||
|
<div id="wrapper">
|
||||||
|
|
||||||
|
<header id="header">
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section id="section">
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer id="footer">
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -13,6 +13,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4 text-right my-auto">
|
<div class="col-md-4 text-right my-auto">
|
||||||
|
<a class="btn btn-lg btn-warning px-4" href="./../main/privacy_policy.html">
|
||||||
|
개인정보 처리방침
|
||||||
|
</a>
|
||||||
<a class="btn btn-lg btn-warning px-4" href="./../main/faq.html">
|
<a class="btn btn-lg btn-warning px-4" href="./../main/faq.html">
|
||||||
<span class="oi oi-bullhorn" title="icon bullhorn" aria-hidden="true"></span>
|
<span class="oi oi-bullhorn" title="icon bullhorn" aria-hidden="true"></span>
|
||||||
FAQ
|
FAQ
|
||||||
|
|||||||
@@ -1,20 +1,15 @@
|
|||||||
<script>
|
<script>
|
||||||
|
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
/*
|
|
||||||
showErrorMessage(
|
showErrorMessage(
|
||||||
'(06월 01일) 알림<br/>\
|
'(01월 28일) 알림<br/>\
|
||||||
* 한글 타자 입력 오류<br/>\
|
* 개인정보 처리방침 화면 추가<br/>\
|
||||||
5월 중순 이후로 갑자기 한글 타자 입력에 문제가 발생하고 있습니다. (Windows의 한글 입력기 영향으로 보입니다)<br/>\
|
초코마에 서비스의 개인정보 처리방침 화면을 등록했습니다.<br/>\
|
||||||
한글 타자 입력이 잘 안될 때 <b>윈도우키를 두 번</b> 누르거나, <b>[Alt + Tab] 키를 두 번</b> 눌러보세요.<br/>\
|
화면 제일 아래에 있는 푸터 오른쪽에 [개인정보 처리방침] 버튼을 추가했습니다.<br/>\
|
||||||
+ 더 많은 대응법을 확인해 보실 분은 아래 버튼을 눌러주세요.<br/>\
|
[개인정보 처리방침] 버튼을 클릭하시고 자세한 내용을 확인하세요.<br/>\
|
||||||
<br/>\
|
<br/>'
|
||||||
<a class="btn btn-danger" roll="button" target="_blank" \
|
|
||||||
href="https://jisangs.notion.site/dbd01211c8154581bed14c4a8c157ed2?pvs=4">\
|
|
||||||
초코마에-한글 타자 문제 해결 매뉴얼</a>'
|
|
||||||
, 'danger'
|
, 'danger'
|
||||||
);
|
);
|
||||||
*/
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
<div class="container my-3">
|
||||||
|
|
||||||
|
<div class="px-5 py-4 mb-3" style="background-color: #ffce54">
|
||||||
|
<h1 class="display-3 font-weight-bold" style="color: #630;">개인정보 처리방침</span></h1>
|
||||||
|
</div> <!-- jumbotron -->
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 1조(개인정보의 처리목적)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">다음의 목적을 위하여 개인정보를 처리합니다. 처리하고 있는 개인정보는 이외의 용도로 이용하지 않습니다.</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
홈페이지 회원 가입 및 관리
|
||||||
|
<ul>
|
||||||
|
<li>마에스트로 계정 생성시 본인 확인을 위한 목적으로 개인정보를 입력받습니다.</li>
|
||||||
|
<li>계정 생성 및 회원 정보 수정에 따른 정보 알림이 필요할 때, 이메일로 해당 내용을 전송합니다.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
민원 사무 처리
|
||||||
|
<ul>
|
||||||
|
<li>서비스와 관련된 의견 접수, 처리, 수정 요청시 이메일로 내용을 주고 받습니다.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 2조(개인정보의 처리 및 보유기간)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">개인정보는 아래와 같이 처리됩니다.</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
수집하는 개인정보 대상
|
||||||
|
<ul>
|
||||||
|
<li>이메일 계정: 마에스트로 회원 가입시 이메일 계정을 입력받습니다.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
개인정보 수집 및 변경
|
||||||
|
<ul>
|
||||||
|
<li>서비스 이용을 위해 마에스트로 계정을 생성할 때 이메일로 개인정보를 받습니다.</li>
|
||||||
|
<li>마에스트로 계정 생성 후, 본인이 희망할 경우 관리자 화면에서 개인정보를 변경할 수 있습니다.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
개인정보 보유기간
|
||||||
|
<ul>
|
||||||
|
<li>회원 탈퇴 전까지 개인정보가 유효합니다.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 3조(개인정보의 제3자 제공)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">초코마에 서비스에서 수집한 개인정보는 제3자에 제공되지 않고 있습니다.</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
다음의 경우를 제외하고는 정보 주체의 사전 동의 없이는 제3자에게 제공하지 않습니다.
|
||||||
|
<ol>
|
||||||
|
<li>정보주체로부터 별도의 동의를 받는 경우</li>
|
||||||
|
<li>법률에 특별한 규정이 있는 경우</li>
|
||||||
|
<li>정보주체 또는 법정대리인의 의사표시를 할 수 없는 상태에 있거나 주소불명 등으로 사전동의를 받을 수 없는 경우로서 명백히 정보주체 또는 제3자의 급박한 생명, 신체, 재산의 이익을 위하여 필요하다고 인정되는 경우</li>
|
||||||
|
<li>통계작성 및 학술연구 등의 목적을 위하여 필요한 경우로서 특정 개인을 알아 볼 수 없는 형태로 개인정보를 제공하는 경우</li>
|
||||||
|
<li>개인정보를 목적 외의 용도로 이용하거나 이를 제3자에게 제공하지 아니하면 다른 법률에서 정하는 소관 업무를 수행할 수 없는 경우</li>
|
||||||
|
<li>조약, 그 밖의 국제협정의 이행을 위하여 외국정보 또는 국제기구에 제공하기 위하여 필요한 경우</li>
|
||||||
|
<li>범죄의 수사와 공소의 제기 및 유지를 위하여 필요한 경우</li>
|
||||||
|
<li>법원의 재판업무 수행을 위하여 필요한 경우</li>
|
||||||
|
<li>형 및 감호, 보호처분의 집행을 위하여 필요한 경우</li>
|
||||||
|
</ol>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 4조(개인정보처리의 위탁)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">초코마에 서비스에서는 수집한 개인정보 처리를 위탁하고 있지 않습니다.</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 5조(정보주체의 권리, 의무 및 행사방법)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">개인정보에 대한 처리를 요청하시면, 아래와 같은 작업을 수행합니다.</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
정보주체의 요구
|
||||||
|
<ul>
|
||||||
|
<li>개인정보 열람요구</li>
|
||||||
|
<li>정정요구</li>
|
||||||
|
<li>삭제요구</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>초코마에 관리자 이메일 계정으로 요청 내용을 작성하고 보내주시면, 지체 없이 조치하겠습니다.</li>
|
||||||
|
<li>법정대리인이나 위임을 받은 자 등 대리인을 통해서도 요청할 수 있습니다. 이 경우 위임장을 함께 제출하셔야 합니다.</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 6조(처리하는 개인정보 항목)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">다음과 같은 개인정보를 수집하고 처리하고 있습니다.</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
수집하는 개인정보 대상
|
||||||
|
<ul>
|
||||||
|
<li>이메일 계정</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
개인정보 처리 내용
|
||||||
|
<ul>
|
||||||
|
<li>서비스 이용을 위해 마에스트로 계정을 생성할 때 이메일로 계정 정보를 전송합니다.</li>
|
||||||
|
<li>마에스트로 계정 정보를 변경했을 때 이메일로 변경 내용을 전송합니다.</li>
|
||||||
|
<li>마에스트로 계정 결제 요청 및 결제 완료시 이메일로 결제 진행 안내 및 결제 완료 내용을 전송합니다.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 7조(개인정보의 파기)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">계정 삭제를 요청하시면 해당 계정의 개인정보를 파기합니다.</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
파기 방법
|
||||||
|
<ul>
|
||||||
|
<li>DB에 저장된 개인정보를 삭제합니다.</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 8조(개인정보 보호책임자 및 열람청구)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">개인정보 처리에 관한 업무를 개발자 본인이 책임지고 수행하고 있습니다.</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
개인정보 보호 담당자
|
||||||
|
<ul>
|
||||||
|
<li>지나주 개인사업자</li>
|
||||||
|
<li>담당자명: 박지상</li>
|
||||||
|
<li>이메일: support@jinaju.com</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 9조(만14세 미만 아동의 개인정보 보호)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">만14세 미만 아동의 개인정보를 별도로 수집하지 않고 있습니다.</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<ol>
|
||||||
|
<li>초코마에 관리자 마에스트로 계정이 학생 계정을 생성할 때, 학생의 개인정보를 수집하지 않습니다.</li>
|
||||||
|
<li>만14세 미만 아동의 학생 계정 생성도 관리자인 마에스트로 계정이 자율적으로 처리하고 관리하는 시스템입니다.</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 10조(권익침해 구제방법)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">아래 기관에서 개인정보 침해에 대한 피해주게, 상담 등을 문의하실 수 있습니다.</h5>
|
||||||
|
<p class="card-text">
|
||||||
|
<ol>
|
||||||
|
<li>개인정보 침해신고센터 : (국번없이) 118 (privacy.kisa.or.kr)</li>
|
||||||
|
<li>개인정보 분쟁조정위원회 : (국번없이) 118 (privacy.kisa.or.kr)</li>
|
||||||
|
<li>대검찰청 사이버범죄수사단 : 02-3480-3571 (cybercid@spo.go.kr)</li>
|
||||||
|
<li>경찰청 사이버테러대응센터 : 1566-0112 (www.netan.go.kr)</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<div class="card border-warning">
|
||||||
|
<h5 class="card-header text-danger font-weight-bold">제 11조(개인정보 처리방침 변경)</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title text-primary">이 개인정보 처리방침은 2026. 01. 28. 부터 적용됩니다</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
</div>
|
||||||
@@ -182,7 +182,9 @@ function makeTypingAppSubjectList(appList) {
|
|||||||
|
|
||||||
for(var i = 0; i < appList.length; i++) {
|
for(var i = 0; i < appList.length; i++) {
|
||||||
var appData = appList[i];
|
var appData = appList[i];
|
||||||
if(appData.appID < 50)
|
if(appData.appID == 200) // 긴 글 시험
|
||||||
|
continue;
|
||||||
|
else if(appData.appID < 50) // 손가락 연습, 단어/문장 연습
|
||||||
continue;
|
continue;
|
||||||
else if(appData.koreanName == "")
|
else if(appData.koreanName == "")
|
||||||
continue;
|
continue;
|
||||||
@@ -269,7 +271,7 @@ function getPlayerRecordList(limitCount) {
|
|||||||
+ "&AppID=" + appID + "&LimitCount=" + limitCount,
|
+ "&AppID=" + appID + "&LimitCount=" + limitCount,
|
||||||
|
|
||||||
(function(jsonData) {
|
(function(jsonData) {
|
||||||
// console.log(jsonData);
|
console.log(jsonData);
|
||||||
showRecordCount(jsonData);
|
showRecordCount(jsonData);
|
||||||
makeRecordTableContent(jsonData["RecordList"]);
|
makeRecordTableContent(jsonData["RecordList"]);
|
||||||
}),
|
}),
|
||||||
@@ -303,43 +305,92 @@ function makeRecordTableContent(recordList) {
|
|||||||
// console.log(recordList);
|
// console.log(recordList);
|
||||||
for(var i = 0; i < recordList.length; i++) {
|
for(var i = 0; i < recordList.length; i++) {
|
||||||
var record = recordList[i];
|
var record = recordList[i];
|
||||||
addRecordListItem(record.Date, record.Time, record.PlayerName, record.Subject, record.Record);
|
addRecordListItem(record.Id, record.Date, record.Time, record.PlayerName, record.Subject, record.Record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showNoResultContent() {
|
function showNoResultContent() {
|
||||||
|
$("#record_table_content").empty();
|
||||||
$("#record_table_content").append('\
|
$("#record_table_content").append('\
|
||||||
<tr>\
|
<tr>\
|
||||||
<th scope="row" class="table-warning text-center" colspan="5">데이터가 없습니다.</th>\
|
<th scope="row" class="table-warning text-center" colspan="6">데이터가 없습니다.</th>\
|
||||||
</tr>\
|
</tr>\
|
||||||
');
|
');
|
||||||
}
|
}
|
||||||
|
|
||||||
function addRecordListItem(date, time, name, subject, record) {
|
function addRecordListItem(id, date, time, name, subject, record) {
|
||||||
if(record >= 0) {
|
if(record >= 0) {
|
||||||
$("#record_table_content").append('\
|
$("#record_table_content").append('\
|
||||||
<tr>\
|
<tr>\
|
||||||
|
<td class="text-center d-none">' + id + '</td>\
|
||||||
<th scope="row" class="text-center">' + date + '</th>\
|
<th scope="row" class="text-center">' + date + '</th>\
|
||||||
<td class="text-center">' + time + '</td>\
|
<td class="text-center">' + time + '</td>\
|
||||||
<td class="text-center">' + name + '</td>\
|
<td class="text-center">' + name + '</td>\
|
||||||
<td class="text-center">' + subject + '</td>\
|
<td class="text-center">' + subject + '</td>\
|
||||||
<td class="text-right">' + record + '</td>\
|
<td class="text-right">' + record + '</td>\
|
||||||
|
<td class="text-center"><button class="btn btn-danger" type="button" onClick="deleteRecord(this)"> X </button></td>\
|
||||||
</tr>\
|
</tr>\
|
||||||
');
|
');
|
||||||
} else {
|
} else {
|
||||||
var disqualificationRecord = -1 * record;
|
var disqualificationRecord = -1 * record;
|
||||||
$("#record_table_content").append('\
|
$("#record_table_content").append('\
|
||||||
<tr>\
|
<tr>\
|
||||||
|
<td class="text-center d-none">' + id + '</td>\
|
||||||
<th scope="row" class="text-center">' + date + '</th>\
|
<th scope="row" class="text-center">' + date + '</th>\
|
||||||
<td class="text-center">' + time + '</td>\
|
<td class="text-center">' + time + '</td>\
|
||||||
<td class="text-center">' + name + '</td>\
|
<td class="text-center">' + name + '</td>\
|
||||||
<td class="text-center">' + subject + '</td>\
|
<td class="text-center">' + subject + '</td>\
|
||||||
<td class="text-right text-danger">(아깝) ' + disqualificationRecord + '</td>\
|
<td class="text-right text-danger">(아깝) ' + disqualificationRecord + '</td>\
|
||||||
|
<td class="text-center"><button class="btn btn-danger" type="button" onClick="deleteRecord(this)"> X </button></td>\
|
||||||
</tr>\
|
</tr>\
|
||||||
');
|
');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteRecord(target) {
|
||||||
|
console.log(target);
|
||||||
|
var id = $(target).parent().siblings().eq(0).text();
|
||||||
|
var date = $(target).parent().siblings().eq(1).text();
|
||||||
|
var time = $(target).parent().siblings().eq(2).text();
|
||||||
|
var name = $(target).parent().siblings().eq(3).text();
|
||||||
|
var subject = $(target).parent().siblings().eq(4).text();
|
||||||
|
var record = $(target).parent().siblings().eq(5).text();
|
||||||
|
|
||||||
|
var message = "선택한 기록을 삭제하시겠습니까?\n"
|
||||||
|
+ "(경고: 기록을 삭제한 후에는 되살릴 수 없습니다)\n\n"
|
||||||
|
+ "* 날짜: " + date + "\n"
|
||||||
|
+ "* 시간: " + time + "\n"
|
||||||
|
+ "* 이름: " + name + "\n"
|
||||||
|
+ "* 과목: " + subject + "\n"
|
||||||
|
+ "* 기록: " + record;
|
||||||
|
if (confirm(message)) {
|
||||||
|
console.log("delete record: " + id);
|
||||||
|
|
||||||
|
var appID = $(":radio[name='subject_group']:checked").val();
|
||||||
|
if(typeof(appID) == "undefined") {
|
||||||
|
showErrorMessage("검색할 과목을 선택하세요.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRequestServer( //url, sendParams, onSuccess, onFail, isDebugMode);
|
||||||
|
"./../server/record/delete_record.php",
|
||||||
|
"MaestroID=" + maestroID
|
||||||
|
+ "&AppID=" + appID
|
||||||
|
+ "&BestRecordID=" + id,
|
||||||
|
|
||||||
|
(function(jsonData) {
|
||||||
|
console.log(jsonData);
|
||||||
|
requestPlayerRecordListAll();
|
||||||
|
}),
|
||||||
|
|
||||||
|
(function(errorMessage, errorCode) {
|
||||||
|
showErrorMessage(errorMessage, "error");
|
||||||
|
})
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
@@ -491,17 +542,19 @@ function addRecordListItem(date, time, name, subject, record) {
|
|||||||
<table class="table table-striped" id="dataTable">
|
<table class="table table-striped" id="dataTable">
|
||||||
<thead class="thead-dark">
|
<thead class="thead-dark">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th scope="col" class="text-center d-none">RecordID</th>
|
||||||
<th scope="col" class="text-center">날짜</th>
|
<th scope="col" class="text-center">날짜</th>
|
||||||
<th scope="col" class="text-center">시간</th>
|
<th scope="col" class="text-center">시간</th>
|
||||||
<th scope="col" class="text-center">이름</th>
|
<th scope="col" class="text-center">이름</th>
|
||||||
<th scope="col" class="text-center">과목</th>
|
<th scope="col" class="text-center">과목</th>
|
||||||
<th scope="col" class="text-center">점수</th>
|
<th scope="col" class="text-center">점수</th>
|
||||||
|
<th scope="col" class="text-center">기록 삭제</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="record_table_content">
|
<tbody id="record_table_content">
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="row" class="table-warning text-center" colspan="5">데이터가 없습니다.</th>
|
<th scope="row" class="table-warning text-center" colspan="6">데이터가 없습니다.</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ if ($has_file) {
|
|||||||
class DBConnector
|
class DBConnector
|
||||||
{
|
{
|
||||||
private $mysqli, $stmt, $query, $paramTypeList, $paramValueArray;
|
private $mysqli, $stmt, $query, $paramTypeList, $paramValueArray;
|
||||||
private $db_host, $db_user, $db_passwd, $db_name;
|
private $db_host, $db_user, $db_passwd, $db_name, $db_port;
|
||||||
|
|
||||||
function __construct()
|
function __construct()
|
||||||
{
|
{
|
||||||
global $db_host, $db_user, $db_passwd, $db_name;
|
global $db_host, $db_user, $db_passwd, $db_name, $db_port;
|
||||||
|
|
||||||
$this->stmt = null;
|
$this->stmt = null;
|
||||||
$this->paramTypeList = "";
|
$this->paramTypeList = "";
|
||||||
@@ -24,11 +24,13 @@ class DBConnector
|
|||||||
$this->db_user = $db_user;
|
$this->db_user = $db_user;
|
||||||
$this->db_passwd = $db_passwd;
|
$this->db_passwd = $db_passwd;
|
||||||
$this->db_name = $db_name;
|
$this->db_name = $db_name;
|
||||||
|
$this->db_port = isset($db_port) ? $db_port : 3306;
|
||||||
} else {
|
} else {
|
||||||
$this->db_host = "localhost";
|
$this->db_host = "localhost";
|
||||||
$this->db_user = "chocomae";
|
$this->db_user = "chocomae";
|
||||||
$this->db_passwd = "sEobMPuJ2A8KTfwU";
|
$this->db_passwd = "sEobMPuJ2A8KTfwU";
|
||||||
$this->db_name = "chocomae";
|
$this->db_name = "chocomae";
|
||||||
|
$this->db_port = 3306;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +41,7 @@ class DBConnector
|
|||||||
public function connectDB()
|
public function connectDB()
|
||||||
{
|
{
|
||||||
// connect db
|
// connect db
|
||||||
$this->mysqli = new mysqli($this->db_host, $this->db_user, $this->db_passwd, $this->db_name);
|
$this->mysqli = new mysqli($this->db_host, $this->db_user, $this->db_passwd, $this->db_name, $this->db_port);
|
||||||
// if ($this->mysqli->connect_error) {
|
// if ($this->mysqli->connect_error) {
|
||||||
if (mysqli_connect_errno()) {
|
if (mysqli_connect_errno()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
# SQL Injection 방어 - Apache 레벨 WAF
|
||||||
|
# Time-based Blind SQL Injection 공격 패턴 차단
|
||||||
|
|
||||||
|
<IfModule mod_rewrite.c>
|
||||||
|
RewriteEngine On
|
||||||
|
|
||||||
|
# SLEEP(), BENCHMARK() 등 시간 지연 함수 차단 (Time-based Blind SQLi 핵심 패턴)
|
||||||
|
RewriteCond %{QUERY_STRING} (sleep|benchmark|waitfor\s+delay|pg_sleep) [NC]
|
||||||
|
RewriteRule .* - [F,L]
|
||||||
|
|
||||||
|
RewriteCond %{REQUEST_URI} (sleep|benchmark|waitfor\s+delay|pg_sleep) [NC]
|
||||||
|
RewriteRule .* - [F,L]
|
||||||
|
|
||||||
|
# POST body의 SQLi 패턴은 mod_security로 처리 (아래 참조)
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# PHP 에러 메시지 노출 방지
|
||||||
|
<IfModule mod_php7.c>
|
||||||
|
php_flag display_errors Off
|
||||||
|
php_flag log_errors On
|
||||||
|
</IfModule>
|
||||||
|
<IfModule mod_php.c>
|
||||||
|
php_flag display_errors Off
|
||||||
|
php_flag log_errors On
|
||||||
|
</IfModule>
|
||||||
|
|
||||||
|
# 서버 정보 숨기기
|
||||||
|
ServerSignature Off
|
||||||
@@ -0,0 +1,386 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
$maestro_id = $_POST["MaestroID"];
|
||||||
|
$selected_app_id = $_POST["AppID"];
|
||||||
|
$best_record_id = $_POST["BestRecordID"];
|
||||||
|
|
||||||
|
include "./../lib/send_reply_json.php";
|
||||||
|
include "./../setup/connect_db.php";
|
||||||
|
include "./../lib/app_highest_record.php";
|
||||||
|
include "./../lib/util_app.php";
|
||||||
|
|
||||||
|
|
||||||
|
function sendSuccessReplyForTest() {
|
||||||
|
global $db_conn;
|
||||||
|
global $maestro_id;
|
||||||
|
global $selected_app_id;
|
||||||
|
global $best_record_id;
|
||||||
|
|
||||||
|
set_data("maestro_id", $maestro_id);
|
||||||
|
set_data("selected_app_id", $selected_app_id);
|
||||||
|
set_data("best_record_id", $best_record_id);
|
||||||
|
send_result_success();
|
||||||
|
|
||||||
|
$db_conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
//sendSuccessReplyForTest();
|
||||||
|
|
||||||
|
|
||||||
|
// 앱 종류에 따라
|
||||||
|
if($selected_app_id == 3000) { // 자격증 타이머
|
||||||
|
// 기록 삭제 : license_score
|
||||||
|
$deletedRecordCount = delete_license_score($best_record_id);
|
||||||
|
if($deletedRecordCount < 1) {
|
||||||
|
sendErrorMessageAndExit("자격증 타이머 기록 삭제 실패");
|
||||||
|
}
|
||||||
|
} else if($selected_app_id == 1004 || $selected_app_id == 1005) { // 긴글 (시험)
|
||||||
|
$record_info_array = get_typing_exam_record_info($best_record_id);
|
||||||
|
$player_id = $record_info_array["PlayerID"];
|
||||||
|
$writing_id = $record_info_array["WritingID"];
|
||||||
|
if($player_id === null || $writing_id === null) {
|
||||||
|
sendErrorMessageAndExit("긴글 (시험) 기록 오류");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기록 삭제 : typing_exam_record
|
||||||
|
$deletedRecordCount = delete_typing_exam_record($best_record_id);
|
||||||
|
if($deletedRecordCount < 1) {
|
||||||
|
sendErrorMessageAndExit("긴글 (시험) 기록 삭제 실패");
|
||||||
|
}
|
||||||
|
|
||||||
|
// typing_exam_highest_record 업데이트
|
||||||
|
updateTypingExamHighestRecord($maestro_id, $player_id, $writing_id);
|
||||||
|
}
|
||||||
|
else { // 일반 앱
|
||||||
|
$record_info_array = get_best_record_info($best_record_id);
|
||||||
|
$player_id = $record_info_array["PlayerID"];
|
||||||
|
$app_id = $record_info_array["AppID"];
|
||||||
|
if($player_id === null || $app_id === null) {
|
||||||
|
sendErrorMessageAndExit("기록 오류");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기록 삭제 : best_record
|
||||||
|
$deletedRecordCount = delete_best_record($best_record_id);
|
||||||
|
if($deletedRecordCount < 1) {
|
||||||
|
sendErrorMessageAndExit("기록 삭제 실패");
|
||||||
|
}
|
||||||
|
|
||||||
|
// app_highest_record 업데이트
|
||||||
|
updateAppHighestRecord($maestro_id, $player_id, $app_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_data("message", "기록 삭제 성공");
|
||||||
|
send_result_success();
|
||||||
|
|
||||||
|
$db_conn->close();
|
||||||
|
exit;
|
||||||
|
|
||||||
|
|
||||||
|
function sendErrorMessageAndExit($message) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
set_error_message($message);
|
||||||
|
send_result_fail();
|
||||||
|
$db_conn->close();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateTypingExamHighestRecord($maestro_id, $player_id, $writing_id) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$record_info_array = get_typing_exam_best_record_info($maestro_id, $player_id, $writing_id);
|
||||||
|
$record = $record_info_array["Record"];
|
||||||
|
$record_date_time = $record_info_array["RecordDateTime"];
|
||||||
|
if ($record === null || $record_date_time === null) {
|
||||||
|
delete_typing_exam_highest_record($maestro_id, $player_id, $writing_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$highest_record_id = get_typing_exam_highest_record_id($maestro_id, $player_id, $writing_id);
|
||||||
|
if ($highest_record_id !== null && $highest_record_id > 0) {
|
||||||
|
update_typing_exam_highest_record($highest_record_id, $record, $record_date_time);
|
||||||
|
} else {
|
||||||
|
insert_new_typing_exam_highest_record($maestro_id, $player_id, $writing_id, $record, $record_date_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAppHighestRecord($maestro_id, $player_id, $app_id) {
|
||||||
|
$record_info_array = get_best_record_record_info($maestro_id, $player_id, $app_id);
|
||||||
|
$best_record = $record_info_array["BestRecord"];
|
||||||
|
$record_date_time = $record_info_array["RecordDateTime"];
|
||||||
|
if ($best_record === null || $record_date_time === null) {
|
||||||
|
delete_app_highest_record($maestro_id, $player_id, $app_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$highest_record_id = get_app_highest_record_id($maestro_id, $player_id, $app_id);
|
||||||
|
if ($highest_record_id !== null && $highest_record_id > 0) {
|
||||||
|
update_app_highest_record($highest_record_id, $best_record, $record_date_time);
|
||||||
|
} else {
|
||||||
|
insert_new_app_highest_record($maestro_id, $player_id, $app_id, $best_record, $record_date_time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_license_score($best_record_id) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
DELETE FROM license_score
|
||||||
|
WHERE LicenseScoreID = ?
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("i", $best_record_id);
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_typing_exam_record_info($best_record_id) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
SELECT PlayerID, WritingID
|
||||||
|
FROM typing_exam_record
|
||||||
|
WHERE TypingExamRecordID = ?
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("i", $best_record_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->bind_result($player_id, $writing_id);
|
||||||
|
$stmt->fetch();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return array("PlayerID" => $player_id, "WritingID" => $writing_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_typing_exam_record($best_record_id) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
DELETE FROM typing_exam_record
|
||||||
|
WHERE TypingExamRecordID = ?
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("i", $best_record_id);
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_typing_exam_best_record_info($maestro_id, $player_id, $writing_id) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
SELECT Record, RecordDateTime
|
||||||
|
FROM typing_exam_record
|
||||||
|
WHERE MaestroID = ? AND PlayerID = ? AND WritingID = ? AND Record > 0
|
||||||
|
ORDER BY Record DESC
|
||||||
|
LIMIT 1
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("iii", $maestro_id, $player_id, $writing_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->bind_result($record, $record_date_time);
|
||||||
|
$stmt->fetch();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return array("Record" => $record, "RecordDateTime" => $record_date_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_typing_exam_highest_record_id($maestro_id, $player_id, $writing_id) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
SELECT TypingExamHighestRecordID
|
||||||
|
FROM typing_exam_highest_record
|
||||||
|
WHERE MaestroID = ? AND PlayerID = ? AND WritingID = ? AND HighestRecord > 0
|
||||||
|
ORDER BY HighestRecord DESC
|
||||||
|
LIMIT 1
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("iii", $maestro_id, $player_id, $writing_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->bind_result($new_best_record_id);
|
||||||
|
$stmt->fetch();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $new_best_record_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_typing_exam_highest_record($maestro_id, $player_id, $writing_id)
|
||||||
|
{
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
DELETE FROM typing_exam_highest_record
|
||||||
|
WHERE MaestroID = ? AND PlayerID = ? AND WritingID = ?
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("iii", $maestro_id, $player_id, $writing_id);
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_typing_exam_highest_record($highest_record_id, $record, $record_date_time) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
UPDATE typing_exam_highest_record
|
||||||
|
SET HighestRecord = ?, RecordDateTime = ?
|
||||||
|
WHERE TypingExamHighestRecordID = ?
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("dsi", $record, $record_date_time, $highest_record_id);
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insert_new_typing_exam_highest_record($maestro_id, $player_id, $writing_id, $record, $record_date_time) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
INSERT INTO typing_exam_highest_record (MaestroID, PlayerID, WritingID, HighestRecord, RecordDateTime)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("iiids", $maestro_id, $player_id, $writing_id, $record, $record_date_time);
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_best_record_info($best_record_id) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
SELECT PlayerID, AppID
|
||||||
|
FROM best_record
|
||||||
|
WHERE BestRecordID = ?
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("i", $best_record_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->bind_result($player_id, $app_id);
|
||||||
|
$stmt->fetch();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return array("PlayerID" => $player_id, "AppID" => $app_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_best_record($best_record_id) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
DELETE FROM best_record
|
||||||
|
WHERE BestRecordID = ?
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("i", $best_record_id);
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_best_record_record_info($maestro_id, $player_id, $app_id) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$orderStatement = is_highest_record_prefer_app($app_id) ? "ORDER BY BestRecord DESC" : "ORDER BY BestRecord ASC";
|
||||||
|
$query = "
|
||||||
|
SELECT BestRecord, RecordDateTime
|
||||||
|
FROM best_record
|
||||||
|
WHERE MaestroID = ? AND PlayerID = ? AND AppID = ?
|
||||||
|
".$orderStatement."
|
||||||
|
LIMIT 1
|
||||||
|
";
|
||||||
|
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("iii", $maestro_id, $player_id, $app_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->bind_result($best_record, $record_date_time);
|
||||||
|
$stmt->fetch();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return array("BestRecord" => $best_record, "RecordDateTime" => $record_date_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_app_highest_record_id($maestro_id, $player_id, $app_id)
|
||||||
|
{
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$orderStatement = is_highest_record_prefer_app($app_id) ? "ORDER BY HighestRecord DESC" : "ORDER BY HighestRecord ASC";
|
||||||
|
$query = "
|
||||||
|
SELECT AppHighestRecordID
|
||||||
|
FROM app_highest_record
|
||||||
|
WHERE MaestroID = ? AND PlayerID = ? AND AppID = ?
|
||||||
|
".$orderStatement."
|
||||||
|
LIMIT 1
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("iii", $maestro_id, $player_id, $app_id);
|
||||||
|
$stmt->execute();
|
||||||
|
$stmt->bind_result($highest_record_id);
|
||||||
|
$stmt->fetch();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $highest_record_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_app_highest_record($maestro_id, $player_id, $app_id)
|
||||||
|
{
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
DELETE FROM app_highest_record
|
||||||
|
WHERE MaestroID = ? AND PlayerID = ? AND AppID = ?
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("iii", $maestro_id, $player_id, $app_id);
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_app_highest_record($highest_record_id, $record, $record_date_time) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
UPDATE app_highest_record
|
||||||
|
SET HighestRecord = ?, RecordDateTime = ?
|
||||||
|
WHERE AppHighestRecordID = ?
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param("dsi", $record, $record_date_time, $highest_record_id);
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insert_new_app_highest_record($maestro_id, $player_id, $app_id, $record, $record_date_time) {
|
||||||
|
global $db_conn;
|
||||||
|
|
||||||
|
$query = "
|
||||||
|
INSERT INTO app_highest_record (MaestroID, PlayerID, AppID, HighestRecord, RecordDateTime)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
";
|
||||||
|
$stmt = $db_conn->prepare($query);
|
||||||
|
$stmt->bind_param('iiids', $maestro_id, $player_id, $app_id, $record, $record_date_time);
|
||||||
|
$result = $stmt->execute();
|
||||||
|
$stmt->close();
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
||||||
@@ -165,7 +165,7 @@ function get_player_record_list($maestro_id, $where_statement, $limit_count) {
|
|||||||
global $db_conn;
|
global $db_conn;
|
||||||
|
|
||||||
$query = "
|
$query = "
|
||||||
SELECT DATE(BR.RecordDateTime) AS Date, TIME(BR.RecordDateTime) AS Time,
|
SELECT BR.BestRecordID AS Id, DATE(BR.RecordDateTime) AS Date, TIME(BR.RecordDateTime) AS Time,
|
||||||
P.Name AS Name, A.KoreanName AS Subject, BR.BestRecord AS Record
|
P.Name AS Name, A.KoreanName AS Subject, BR.BestRecord AS Record
|
||||||
FROM best_record BR, player P, app A
|
FROM best_record BR, player P, app A
|
||||||
WHERE BR.MaestroID = ? AND ".$where_statement."
|
WHERE BR.MaestroID = ? AND ".$where_statement."
|
||||||
@@ -181,10 +181,11 @@ function get_player_record_list($maestro_id, $where_statement, $limit_count) {
|
|||||||
$stmt = $db_conn->prepare($query);
|
$stmt = $db_conn->prepare($query);
|
||||||
$stmt->bind_param('i', $maestro_id);
|
$stmt->bind_param('i', $maestro_id);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($date, $time, $player_name, $subject, $record);
|
$stmt->bind_result($id, $date, $time, $player_name, $subject, $record);
|
||||||
|
|
||||||
$record_array = array();
|
$record_array = array();
|
||||||
while($stmt->fetch()) {
|
while($stmt->fetch()) {
|
||||||
|
$record_date['Id'] = $id;
|
||||||
$record_date['Date'] = $date;
|
$record_date['Date'] = $date;
|
||||||
$record_date['Time'] = $time;
|
$record_date['Time'] = $time;
|
||||||
$record_date['PlayerName'] = $player_name;
|
$record_date['PlayerName'] = $player_name;
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ function get_player_record_list($maestro_id, $where_statement, $limit_count) {
|
|||||||
global $db_conn;
|
global $db_conn;
|
||||||
|
|
||||||
$query = "
|
$query = "
|
||||||
SELECT DATE(LS.ScoreDateTime) AS Date, TIME(LS.ScoreDateTime) AS Time,
|
SELECT LS.LicenseScoreID AS ID, DATE(LS.ScoreDateTime) AS Date, TIME(LS.ScoreDateTime) AS Time,
|
||||||
P.Name AS Name, LS.SubjectName AS Subject, LS.Score AS Record
|
P.Name AS Name, LS.SubjectName AS Subject, LS.Score AS Record
|
||||||
FROM license_score LS, player P
|
FROM license_score LS, player P
|
||||||
WHERE LS.MaestroID = ? AND ".$where_statement."
|
WHERE LS.MaestroID = ? AND ".$where_statement."
|
||||||
@@ -183,10 +183,11 @@ function get_player_record_list($maestro_id, $where_statement, $limit_count) {
|
|||||||
$stmt = $db_conn->prepare($query);
|
$stmt = $db_conn->prepare($query);
|
||||||
$stmt->bind_param('i', $maestro_id);
|
$stmt->bind_param('i', $maestro_id);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($date, $time, $player_name, $subject, $record);
|
$stmt->bind_result($id, $date, $time, $player_name, $subject, $record);
|
||||||
|
|
||||||
$record_array = array();
|
$record_array = array();
|
||||||
while($stmt->fetch()) {
|
while($stmt->fetch()) {
|
||||||
|
$record_date['Id'] = $id;
|
||||||
$record_date['Date'] = $date;
|
$record_date['Date'] = $date;
|
||||||
$record_date['Time'] = $time;
|
$record_date['Time'] = $time;
|
||||||
$record_date['PlayerName'] = $player_name;
|
$record_date['PlayerName'] = $player_name;
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ function get_player_record_list($maestro_id, $where_statement, $limit_count) {
|
|||||||
global $db_conn;
|
global $db_conn;
|
||||||
|
|
||||||
$query = "
|
$query = "
|
||||||
SELECT DATE(TE.RecordDateTime) AS Date, TIME(TE.RecordDateTime) AS Time,
|
SELECT TE.TypingExamRecordID as Id, DATE(TE.RecordDateTime) AS Date, TIME(TE.RecordDateTime) AS Time,
|
||||||
P.Name AS Name, WR.Name AS Subject, TE.Record AS Record
|
P.Name AS Name, WR.Name AS Subject, TE.Record AS Record
|
||||||
FROM typing_exam_record TE, player P, writing WR
|
FROM typing_exam_record TE, player P, writing WR
|
||||||
WHERE TE.MaestroID = ? AND TE.WritingID=WR.WritingID AND TE.PlayerID=P.PlayerID AND ".$where_statement."
|
WHERE TE.MaestroID = ? AND TE.WritingID=WR.WritingID AND TE.PlayerID=P.PlayerID AND ".$where_statement."
|
||||||
@@ -149,10 +149,11 @@ function get_player_record_list($maestro_id, $where_statement, $limit_count) {
|
|||||||
$stmt = $db_conn->prepare($query);
|
$stmt = $db_conn->prepare($query);
|
||||||
$stmt->bind_param('i', $maestro_id);
|
$stmt->bind_param('i', $maestro_id);
|
||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$stmt->bind_result($date, $time, $player_name, $subject, $record);
|
$stmt->bind_result($id, $date, $time, $player_name, $subject, $record);
|
||||||
|
|
||||||
$record_array = array();
|
$record_array = array();
|
||||||
while($stmt->fetch()) {
|
while($stmt->fetch()) {
|
||||||
|
$record_date['Id'] = $id;
|
||||||
$record_date['Date'] = $date;
|
$record_date['Date'] = $date;
|
||||||
$record_date['Time'] = $time;
|
$record_date['Time'] = $time;
|
||||||
$record_date['PlayerName'] = $player_name;
|
$record_date['PlayerName'] = $player_name;
|
||||||
|
|||||||
@@ -16,21 +16,16 @@ $prev_best_record_id = -1;
|
|||||||
$prev_best_record = 0;
|
$prev_best_record = 0;
|
||||||
|
|
||||||
$prev_best_record_id = get_best_record($maestro_id, $app_id, $player_id);
|
$prev_best_record_id = get_best_record($maestro_id, $app_id, $player_id);
|
||||||
echo "id : ".$prev_best_record_id." ".$prev_best_record;
|
|
||||||
|
|
||||||
if($prev_best_record_id === null || $prev_best_record_id < 0) {
|
if($prev_best_record_id === null || $prev_best_record_id < 0) {
|
||||||
echo "insert";
|
|
||||||
insert_best_record($maestro_id, $app_id, $player_id, $record);
|
insert_best_record($maestro_id, $app_id, $player_id, $record);
|
||||||
} else {
|
} else {
|
||||||
echo $record." ".$prev_best_record;
|
|
||||||
if(is_highest_record_prefer_app($app_id)) {
|
if(is_highest_record_prefer_app($app_id)) {
|
||||||
if($record > $prev_best_record) {
|
if($record > $prev_best_record) {
|
||||||
echo "update";
|
|
||||||
update_best_record($prev_best_record_id, $record);
|
update_best_record($prev_best_record_id, $record);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if($record < $prev_best_record) {
|
if($record < $prev_best_record) {
|
||||||
echo "update";
|
|
||||||
update_best_record($prev_best_record_id, $record);
|
update_best_record($prev_best_record_id, $record);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,9 +33,6 @@ if($prev_best_record_id === null || $prev_best_record_id < 0) {
|
|||||||
|
|
||||||
// highest record is the highest record
|
// highest record is the highest record
|
||||||
$app_highest_record = get_app_highest_record($maestro_id, $app_id, $player_id);
|
$app_highest_record = get_app_highest_record($maestro_id, $app_id, $player_id);
|
||||||
echo "\napp_highest_record : $app_highest_record";
|
|
||||||
|
|
||||||
// echo "\nrecord : $record";
|
|
||||||
if($app_highest_record == null) {
|
if($app_highest_record == null) {
|
||||||
insert_app_highest_record($maestro_id, $app_id, $player_id, $record);
|
insert_app_highest_record($maestro_id, $app_id, $player_id, $record);
|
||||||
// echo "\ninsert_app_highest_record : $record";
|
// echo "\ninsert_app_highest_record : $record";
|
||||||
|
|||||||
@@ -4,5 +4,6 @@ $db_host = "localhost";
|
|||||||
$db_user = "chocomae";
|
$db_user = "chocomae";
|
||||||
$db_passwd = "sEobMPuJ2A8KTfwU";
|
$db_passwd = "sEobMPuJ2A8KTfwU";
|
||||||
$db_name = "chocomae";
|
$db_name = "chocomae";
|
||||||
|
$db_port = 3306;
|
||||||
|
|
||||||
?>
|
?>
|
||||||
@@ -10,12 +10,13 @@ if($hasServiceDBSetting == true) {
|
|||||||
$db_passwd = "Fr12nds95";
|
$db_passwd = "Fr12nds95";
|
||||||
$db_name = "jisangs";
|
$db_name = "jisangs";
|
||||||
}
|
}
|
||||||
|
if (!isset($db_port)) $db_port = 3306;
|
||||||
|
|
||||||
|
|
||||||
// connect db
|
// connect db
|
||||||
$db_conn = new mysqli($db_host, $db_user, $db_passwd, $db_name);
|
$db_conn = new mysqli($db_host, $db_user, $db_passwd, $db_name, $db_port);
|
||||||
if ($db_conn->connect_error) {
|
if ($db_conn->connect_error) {
|
||||||
set_error_message("Failed to connect DB: ".$db_conn->connect_error);
|
set_error_message("DB connection failed.");
|
||||||
send_result_fail();
|
send_result_fail();
|
||||||
exit;
|
exit;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user