git -C '/home/opc/rocketa.git' show -- commit f70385251317348253f5ebffeffc00e3a1d6d563
Author: Satoshi Ujihara <satoshi_ujihara@fivegate.jp>
Date: Thu Jan 29 17:46:01 2026 +0900
gitignore 更新
compser.json 更新
本番環境oci_func 作成
diff --git a/.gitignore b/.gitignore
index 40e3443..aa558a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,6 +10,5 @@ Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
-.vscode/sftp.json
-.vscode/settings.json
+.vscode
~$n18i.xlsx
diff --git a/composer.json b/composer.json
index e106b89..3727dc5 100644
--- a/composer.json
+++ b/composer.json
@@ -23,6 +23,7 @@
"league/flysystem-aws-s3-v3": "^1.0",
"maatwebsite/excel": "^3.1",
"mpdf/mpdf": "^8.0",
+ "oracle/oci-php-sdk": "^0.1.0",
"php-parallel-lint/php-console-color": "*",
"phpoffice/phpspreadsheet": "^1.11"
},
@@ -71,4 +72,4 @@
"@php artisan key:generate --ansi"
]
}
-}
+}
\ No newline at end of file
diff --git a/oci_func_prod/click/func.py b/oci_func_prod/click/func.py
new file mode 100644
index 0000000..dab5f63
--- /dev/null
+++ b/oci_func_prod/click/func.py
@@ -0,0 +1,640 @@
+
+import io
+import json
+import logging
+import json
+from sqlalchemy import create_engine, text
+import datetime
+import string
+import random
+import urllib.parse
+import hashlib
+import os, time
+import re
+
+from fdk import response
+from urllib.parse import quote
+
+os.environ['TZ'] = 'Asia/Tokyo'
+time.tzset()
+logging.basicConfig(level=logging.INFO)
+
+
+def handler(ctx, data: io.BytesIO = None):
+
+ logging.info("handler started")
+ headers = ctx.Headers()
+ full_url = ctx.RequestURL()
+ query_string = urllib.parse.urlparse(full_url).query
+ query_params = urllib.parse.parse_qs(query_string)
+
+ DB_HOST = "api.rocket-a.com"
+ DB_USER = "root"
+ DB_PASSWORD = "buSDonry4%h6rm-0fy"
+ DB_NAME = "rocketa-api"
+
+ # 接続URLの作成: mysql+pymysql://user:password@host/dbname
+ DB_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
+ engine = create_engine(DB_URL)
+ connection = engine.connect()
+
+ # 変数初期値
+ dt_now = datetime.datetime.now() + datetime.timedelta(hours = 9)
+ date = dt_now.strftime('%Y%m%d%H%M%S')
+ format_date = dt_now.strftime('%Y-%m-%d %H:%M:%S')
+ created_ym = dt_now.strftime('%Y%m')
+ url = ''
+ result_method = 1
+ adjust_token = ''
+ dv_pc = 0
+ dv_and_dc = 0
+ dv_and_au = 0
+ dv_and_sb = 0
+ dv_and_other = 0
+ dv_ios_dc = 0
+ dv_ios_au = 0
+ dv_ios_sb = 0
+ dv_ios_other = 0
+ error = 0
+ ad_id = ''
+ client_id = ''
+ media_id = ''
+ uid = ''
+ banner_id = ''
+ ip = headers.get('x-real-ip')
+ # user_agent = headers.get('User-Agent')
+ user_agent = headers.get('user-agent')[1]
+ referer = ''
+ net_price = ''
+ net_price_unit = ''
+ gross_price = ''
+ gross_price_unit = ''
+ group_id = ''
+ other_parameters = ''
+ hash_value = ''
+ hash_text = ''
+
+ other_parameter_list =[]
+
+ defined_parameters = {
+ 'ad_id',
+ 'client_id',
+ 'media_id',
+ 'media_uid',
+ 'banner_id',
+ 'sid',
+ 'amount',
+ 'reward',
+ 'count',
+ 'status',
+ 'date',
+ 'stage'
+ }
+ if headers.get('referer') is not None:
+ referer = headers.get('referer')
+
+ # GETパラメータチェックと変数化
+ if query_params.get('ad_id') is not None:
+ ad_id = query_params.get('ad_id', [None])[0]
+ else:
+ error = 101
+
+ if query_params.get('client_id') is not None:
+ client_id = query_params.get('client_id', [None])[0]
+
+ else:
+ error = 102
+
+ if query_params.get('media_id') is not None:
+ media_id = query_params.get('media_id', [None])[0]
+
+ else:
+ error = 103
+
+ if query_params.get('media_uid') is not None:
+ uid = query_params.get('media_uid', [None])[0]
+
+ if query_params.get('banner_id') is not None:
+ banner_id = query_params.get('banner_id', [None])[0]
+
+ # パートナー独自パラメータをother_parametersに保存
+ for key, value in query_params.items():
+ logging.info(f"■■■125■■■■{key}={value[0]}")
+ if key not in defined_parameters:
+# other_parameters = other_parameters + key + '=' + value + '&'
+ other_parameter_list.append(f"{key}={value[0]}")
+
+ other_parameters = '&'.join(other_parameter_list)
+ if other_parameters is not None:
+ logging.info(f"■■■131■■■■{other_parameters}")
+# other_parameters = other_parameters.rstrip('&')
+
+ logging.info(f"■■■163■■■■{error}")
+ # 広告詳細取得
+ if error == 0:
+ sql_query = 'SELECT '
+ sql_query += ' a.url, '
+ sql_query += ' a.result_method, '
+ sql_query += ' a.dv_pc, '
+ sql_query += ' a.dv_and_dc, '
+ sql_query += ' a.dv_and_au, '
+ sql_query += ' a.dv_and_sb, '
+ sql_query += ' a.dv_and_other, '
+ sql_query += ' a.dv_ios_dc, '
+ sql_query += ' a.dv_ios_au, '
+ sql_query += ' a.dv_ios_sb, '
+ sql_query += ' a.dv_ios_other, '
+ sql_query += ' a.adjust_token '
+ sql_query += 'FROM '
+ sql_query += ' ad_datas AS a '
+ sql_query += 'INNER JOIN '
+ sql_query += ' ad_join_media_datas AS j '
+ sql_query += 'ON '
+ sql_query += ' a.master_ad_id = j.master_ad_id '
+ sql_query += 'WHERE '
+ sql_query += ' a.master_ad_id = :master_ad_id '
+ sql_query += 'AND '
+ sql_query += ' a.client_id = :client_id '
+ sql_query += 'AND '
+ sql_query += ' j.media_id = :media_id '
+ sql_query += 'AND '
+ sql_query += ' a.status = 1 '
+ sql_query += 'AND '
+ sql_query += ' j.status = 1 '
+ sql_query += 'AND '
+ sql_query += ' a.start_date <= CAST(:start_date AS DATETIME) '
+ sql_query += 'AND '
+ sql_query += ' a.end_date > CAST(:end_date AS DATETIME) '
+ sql_query += 'AND '
+ sql_query += ' a.url != "" '
+ sql_query += 'LIMIT 1 '
+
+ params = {
+ "master_ad_id": int(ad_id),
+ "client_id": int(client_id),
+ "media_id": int(media_id),
+ "start_date": format_date,
+ "end_date": format_date,
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ # 結果を取得
+ ad_data = result.fetchone()
+
+ if ad_data:
+ ad_data_dict = ad_data._asdict()
+ url = ad_data_dict['url']
+ result_method = ad_data_dict['result_method']
+ dv_pc = ad_data_dict['dv_pc']
+ dv_and_dc = ad_data_dict['dv_and_dc']
+ dv_and_au = ad_data_dict['dv_and_au']
+ dv_and_sb = ad_data_dict['dv_and_sb']
+ dv_and_other = ad_data_dict['dv_and_other']
+ dv_ios_dc = ad_data_dict['dv_ios_dc']
+ dv_ios_au = ad_data_dict['dv_ios_au']
+ dv_ios_sb = ad_data_dict['dv_ios_sb']
+ dv_ios_other = ad_data_dict['dv_ios_other']
+
+ try:
+ adjust_token = ad_data_dict['adjust_token']
+ except KeyError:
+ pass
+
+ logging.info(dict(headers))
+ logging.info(f"■■■236■■■■{user_agent}")
+
+ #デバイスチェック
+ # if any("iPhone OS" in item for item in user_agent) or any("iPad" in item for item in user_agent):
+ if 'iPhone OS' in user_agent or 'iPad' in user_agent:
+ if dv_ios_dc == 0 and dv_ios_au == 0 and dv_ios_sb == 0 and dv_ios_other == 0:
+ error = 2
+
+ # elif any("Android" in item for item in user_agent):
+ elif 'Android' in user_agent:
+ if dv_and_dc == 0 and dv_and_au == 0 and dv_and_sb == 0 and dv_and_other == 0:
+ error = 2
+
+ else:
+ if dv_pc == 0:
+ error = 2
+ else:
+ error = 301
+
+
+
+ logging.info("■■■255■■■■")
+ # 特別遷移先設定取得
+ if banner_id != '':
+ sql_query = 'SELECT '
+ sql_query += ' url, '
+ sql_query += ' ios_url, '
+ sql_query += ' and_url '
+ sql_query += 'FROM '
+ sql_query += ' ad_material_datas '
+ sql_query += 'WHERE '
+ sql_query += ' status = 1 '
+ sql_query += 'AND '
+ sql_query += ' master_ad_id = :master_ad_id '
+ sql_query += 'AND '
+ sql_query += ' master_material_id = :master_material_id '
+ sql_query += 'LIMIT 1 '
+
+
+ params = {
+ "master_ad_id": int(ad_id),
+ "master_material_id": int(banner_id),
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ # 結果を取得
+ ad_material_data = result.fetchone()
+
+
+ if ad_material_data:
+ ad_material_data_dict = ad_material_data._asdict()
+ # 特別遷移先に書き換え
+ if ad_material_data_dict['url']:
+ url = ad_material_data_dict['url']
+ # iOS
+ if ad_material_data_dict['ios_url']:
+ url = ad_material_data_dict['ios_url']
+ # Android
+ if ad_material_data_dict['and_url']:
+ url = ad_material_data_dict['and_url']
+ # sid生成
+ if error == 0:
+ while True:
+ sid = ''
+
+ dat = string.digits + string.ascii_lowercase
+ random_string = ''.join([random.choice(dat) for i in range(16)])
+ sid = random_string + date
+
+ sql_query = 'SELECT '
+ sql_query += ' id '
+ sql_query += 'FROM '
+ sql_query += ' click_records '
+ sql_query += 'WHERE '
+ sql_query += ' sid = :sid '
+ sql_query += 'AND '
+ sql_query += ' created_ym = :created_ym '
+ sql_query += 'LIMIT 1'
+
+
+ params = {
+ "sid": sid,
+ "created_ym": int(created_ym)
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ # 結果を取得
+ click_data = result.fetchone()
+
+ if not click_data:
+ break
+
+ # クリック時の報酬を取得
+ logging.info(f"■■■329■■■■{error}")
+ if error == 0:
+ sql_query = 'SELECT '
+ sql_query += ' net_price, '
+ sql_query += ' net_price_unit, '
+ sql_query += ' gross_price, '
+ sql_query += ' gross_price_unit, '
+ sql_query += ' group_id '
+ sql_query += 'FROM '
+ sql_query += ' ad_reward_datas '
+ sql_query += 'WHERE '
+ sql_query += ' ad_id = :ad_id '
+ sql_query += 'AND '
+ sql_query += ' type = 1 '
+ sql_query += 'AND '
+ sql_query += ' stage_id IS NULL '
+ sql_query += 'AND '
+ sql_query += ' product_id IS NULL '
+ sql_query += 'AND '
+ sql_query += ' ( '
+ sql_query += ' media_id = :media_id '
+ sql_query += ' OR '
+ sql_query += ' media_id IS NULL '
+ sql_query += ' ) '
+ sql_query += 'ORDER BY media_id DESC '
+
+ params = {
+ "ad_id": int(ad_id),
+ "media_id": int(media_id),
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ # 結果を取得
+ reward_list = result.fetchall()
+
+ if reward_list:
+ for reward_data in reward_list:
+ try:
+ reward_data_dict = reward_data._asdict()
+ group_id = reward_data_dict['group_id']
+
+ sql_query = 'SELECT '
+ sql_query += ' id '
+ sql_query += 'FROM '
+ sql_query += ' ad_reward_group_datas '
+ sql_query += 'WHERE '
+ sql_query += ' status = 1 '
+ sql_query += 'AND '
+ sql_query += ' master_id = :master_id '
+ sql_query += 'AND '
+ sql_query += ' ad_id = :ad_id '
+ sql_query += 'AND '
+ sql_query += ' valid_start_date <= :valid_start_date '
+ sql_query += 'AND '
+ sql_query += ' valid_end_date > :valid_end_date '
+
+ logging.info(f"■■■386■■■■{reward_data_dict}")
+ params = {
+ "master_id": int(group_id or 0),
+ "ad_id": int(ad_id or 0),
+ "valid_start_date": format_date,
+ "valid_end_date": format_date,
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ # 結果を取得
+ group_data = result.fetchone()
+
+ if group_data:
+ net_price = reward_data_dict['net_price']
+ net_price_unit = reward_data_dict['net_price_unit']
+ gross_price = reward_data_dict['gross_price']
+ gross_price_unit = reward_data_dict['gross_price_unit']
+ break
+
+ else:
+ net_price = reward_data_dict['net_price']
+ net_price_unit = reward_data_dict['net_price_unit']
+ gross_price = reward_data_dict['gross_price']
+ gross_price_unit = reward_data_dict['gross_price_unit']
+ continue
+ except KeyError:
+ logging.info(f"■■■415■■■■KeyError")
+ pass
+
+ else:
+ #報酬額データがない
+ error=302
+
+ #報酬額データがない
+ if net_price is None or net_price_unit is None or gross_price is None or gross_price_unit is None:
+ error=302
+ logging.info(f"■■■424■■■■報酬額ない")
+ # sidと遷移情報を保存
+ if error == 0:
+ sql_query = 'INSERT INTO '
+ sql_query += ' click_records '
+ sql_query += '( '
+ sql_query += ' master_ad_id, '
+ sql_query += ' client_id, '
+ sql_query += ' media_id, '
+ sql_query += ' banner_id, '
+ sql_query += ' uid, '
+ sql_query += ' sid, '
+ sql_query += ' ip, '
+ sql_query += ' user_agent, '
+ sql_query += ' referer, '
+ sql_query += ' net_price, '
+ sql_query += ' net_price_unit, '
+ sql_query += ' gross_price, '
+ sql_query += ' gross_price_unit, '
+ sql_query += ' other_parameters, '
+ sql_query += ' created_ym '
+ sql_query += ') VALUES ( '
+ sql_query += ' :master_ad_id, '
+ sql_query += ' :client_id, '
+ sql_query += ' :media_id, '
+ sql_query += ' :banner_id, '
+ sql_query += ' :uid, '
+ sql_query += ' :sid, '
+ sql_query += ' :ip, '
+ sql_query += ' :user_agent, '
+ sql_query += ' :referer, '
+ sql_query += ' :net_price, '
+ sql_query += ' :net_price_unit, '
+ sql_query += ' :gross_price, '
+ sql_query += ' :gross_price_unit, '
+ sql_query += ' :other_parameters, '
+ sql_query += ' :created_ym '
+ sql_query += ') '
+
+ params = {
+ "master_ad_id": int(ad_id),
+ "client_id": int(client_id),
+ "media_id": int(media_id),
+ "banner_id": int(banner_id) if banner_id else None,
+ "uid": uid if uid else None,
+ "sid": sid,
+ "ip": ip,
+ "user_agent": user_agent,
+ "referer": referer if referer else None,
+ "net_price": net_price if net_price else None,
+ "net_price_unit": net_price_unit if net_price_unit else None,
+ "gross_price": gross_price if gross_price else None,
+ "gross_price_unit": gross_price_unit if gross_price_unit else None,
+ "other_parameters": other_parameters if other_parameters else None,
+ "created_ym": int(created_ym),
+ }
+ result = connection.execute(text(sql_query), params)
+ logging.info(f"■■■445■■■{text(sql_query)}")
+ # トランザクションをコミットして変更を永続化
+ connection.commit()
+ """
+ # 広告ページへ遷移
+ if '?' in url:
+ # ソケット通信の時はパラメータはデフォ
+ if int(result_method) == 2:
+ url += '&sid=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id
+
+ # AppsFlyer特別処理
+ elif int(result_method) == 3:
+ url += '&af_siteid=' + media_id + '&pid=adleap_int&af_click_lookback=7d&af_sub4=' + sid + '&af_sub1=' + ad_id + '&af_sub2=' + client_id + '&af_sub3=' + media_id
+
+ # Adjust特別処理
+ elif int(result_method) == 4:
+ callback_url = 'https://api.rocket-a.com/service/result?sid=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id + '&device_id={idfa||gps_adid}&adjust_id={adid}'
+
+ if uid != '':
+ callback_url += '&media_uid=' + uid
+
+ encode_callback_url = urllib.parse.quote(callback_url, safe='')
+
+ url += '&install_callback=' + encode_callback_url
+
+ # CPE
+ if adjust_token != '':
+ encode_callback_url_add_token = urllib.parse.quote(callback_url + '&adjust_token=' + adjust_token, safe='')
+
+ url += '&event_callback_' + adjust_token + '=' + encode_callback_url_add_token
+
+ # Airbridge特別処理
+ elif int(result_method) == 5:
+ url += '&sid=' + sid + '&custom_ad_id=' + ad_id + '&custom_client_id=' + client_id + '&sub_id=' + media_id
+
+ # Tyrads特別処理
+ elif int(result_method) == 6:
+ url += '&sub1=' + sid + '&sub2=' + ad_id + '&sub3=' + client_id + '&media_id=' + media_id
+
+ # AyeT Studios特別処理
+ elif int(result_method) == 7:
+ url += '&custom_1=' + sid + '&custom_2=' + ad_id + '&custom_3=' + client_id + '&media_id=' + media_id
+
+ # Torox特別処理
+ elif int(result_method) == 8:
+ url += '&user_id=' + sid + '&subid1=' + ad_id + '&subid2=' + client_id + '&subid3=' + media_id
+
+ # Appricot Ads特別処理
+ elif int(result_method) == 9:
+ url += '&sub1=' + sid + '&sub6=' + ad_id + '&sub7=' + client_id + '&media_id=' + media_id
+
+ # Singular特別処理
+ elif int(result_method) == 10:
+ url += '&cl=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id
+ else:
+ pass
+ #client_id独自処理削除
+ else:
+ # ソケット通信の時はパラメータはデフォ
+ if int(result_method) == 2:
+ url += '?sid=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id
+
+ # AppsFlyer特別処理
+ elif int(result_method) == 3:
+ url += '?af_siteid=' + media_id + '&pid=adleap_int&af_click_lookback=7d&af_sub4=' + sid + '&af_sub1=' + ad_id + '&af_sub2=' + client_id + '&af_sub3=' + media_id
+
+ #Adjust特別処理
+ elif int(result_method) == 4:
+ callback_url = 'https://api.rocket-a.com/service/result?sid=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id + '&device_id={idfa||gps_adid}&adjust_id={adid}'
+
+ if uid != '':
+ callback_url += '&media_uid=' + uid
+
+ encode_callback_url = urllib.parse.quote(callback_url, safe='')
+
+ url += '?install_callback=' + encode_callback_url
+
+ # CPE
+ if adjust_token != '':
+ encode_callback_url_add_token = urllib.parse.quote(callback_url + '&adjust_token=' + adjust_token, safe='')
+
+ url += '&event_callback_' + adjust_token + '=' + encode_callback_url_add_token
+
+ # Airbridge特別処理
+ elif int(result_method) == 5:
+ url += '?sid=' + sid + '&custom_ad_id=' + ad_id + '&custom_client_id=' + client_id + '&sub_id=' + media_id
+
+ # Tyrads特別処理
+ elif int(result_method) == 6:
+ url += '?sub1=' + sid + '&sub2=' + ad_id + '&sub3=' + client_id + '&media_id=' + media_id
+
+ # AyeT Studios特別処理
+ elif int(result_method) == 7:
+ url += '?custom_1=' + sid + '&custom_2=' + ad_id + '&custom_3=' + client_id + '&media_id=' + media_id
+
+ # Torox特別処理
+ elif int(result_method) == 8:
+ url += '?user_id=' + sid + '&subid1=' + ad_id + '&subid2=' + client_id + '&subid3=' + media_id
+
+ # Appricot Ads特別処理
+ elif int(result_method) == 9:
+ url += '?sub1=' + sid + '&sub6=' + ad_id + '&sub7=' + client_id + '&media_id=' + media_id
+
+ # Singular特別処理
+ elif int(result_method) == 10:
+ url += '?cl=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id
+
+ else:
+ #client_id独自処理削除
+ url += '?sid=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id
+
+ if uid != '' and int(result_method) != 4:
+ url += '&media_uid=' + uid
+ """
+ """
+ params = {
+ "sid": sid,
+ "ad_id": ad_id,
+ "client_id": client_id,
+ "media_id": media_id,
+ }
+
+
+ formatter = string.Formatter()
+ fields = [fname for _, fname, _, _ in formatter.parse(url) if fname]
+ missing = [f for f in fields if f not in params]
+ if missing:
+ params = {
+ "sid": sid,
+ "ad_id": ad_id,
+ "client_id": client_id,
+ "media_id": media_id,
+ "media_uid": uid,
+ }
+ formatter = string.Formatter()
+ fields = [fname for _, fname, _, _ in formatter.parse(url) if fname]
+ missing = [f for f in fields if f not in params]
+
+ if missing or uid == '':
+ error = 401
+ logging.info(f"■■■589■■■パラメータエラー")
+ else:
+ url = url.format(sid=sid, ad_id=ad_id, client_id=client_id, media_id=media_id, media_uid=uid)
+ else:
+ url = url.format(sid=sid, ad_id=ad_id, client_id=client_id, media_id=media_id)
+ """
+ #master_ad_idという名前だと扱いずらい
+ params["ad_id"] = params.pop("master_ad_id")
+ params["media_uid"] = params.pop("uid")
+ # 置換処理(URLエンコードあり)
+ result = re.sub(
+ r"\{(\w+)\}",
+ lambda m: quote(str(params.get(m.group(1)))) if params.get(m.group(1)) is not None else "",
+ url
+ )
+
+ logging.info(f"■■■595■■■{result}")
+
+ if error == 0:
+ return response.Response(
+ ctx,
+ status_code=302,
+ headers={"Location": result}
+ )
+
+
+ # エラーページへ遷移
+ error_page = 'https://api.rocket-a.com/service/error?error=' + str(error)
+ if error == 2:
+ error_page = 'https://api.rocket-a.com/service/device_error'
+
+
+ return response.Response(
+ ctx,
+ status_code=302,
+ headers={"Location": error_page}
+ )
+
+'''
+ # testdic = click_data._asdict()
+ # logging.info(testdic)
+ return response.Response(
+ ctx,
+ response_data=f"Helloa{dv_ios_other}",
+ headers={"Content-Type": "application/json"}
+ )
+'''
+
+
+
diff --git a/oci_func_prod/click/func.yaml b/oci_func_prod/click/func.yaml
new file mode 100644
index 0000000..9eba686
--- /dev/null
+++ b/oci_func_prod/click/func.yaml
@@ -0,0 +1,8 @@
+schema_version: 20180708
+name: click
+version: 0.0.1
+runtime: python
+build_image: fnproject/python:3.12-dev
+run_image: fnproject/python:3.12
+entrypoint: /python/bin/fdk /function/func.py handler
+memory: 256
\ No newline at end of file
diff --git a/oci_func_prod/click/requirements.txt b/oci_func_prod/click/requirements.txt
new file mode 100644
index 0000000..a12b610
--- /dev/null
+++ b/oci_func_prod/click/requirements.txt
@@ -0,0 +1,3 @@
+fdk>=0.1.103
+sqlalchemy
+pymysql
diff --git a/oci_func_prod/click_test/func.py b/oci_func_prod/click_test/func.py
new file mode 100644
index 0000000..b5109f5
--- /dev/null
+++ b/oci_func_prod/click_test/func.py
@@ -0,0 +1,638 @@
+
+import io
+import json
+import logging
+import json
+from sqlalchemy import create_engine, text
+import datetime
+import string
+import random
+import urllib.parse
+import hashlib
+import os, time
+import re
+
+from fdk import response
+from urllib.parse import quote
+
+os.environ['TZ'] = 'Asia/Tokyo'
+time.tzset()
+logging.basicConfig(level=logging.INFO)
+
+
+def handler(ctx, data: io.BytesIO = None):
+
+ logging.info("handler started")
+ headers = ctx.Headers()
+ full_url = ctx.RequestURL()
+ query_string = urllib.parse.urlparse(full_url).query
+ query_params = urllib.parse.parse_qs(query_string)
+
+ DB_HOST = "api.rocket-a.com"
+ DB_USER = "root"
+ DB_PASSWORD = "buSDonry4%h6rm-0fy"
+ DB_NAME = "rocketa-api"
+
+ # 接続URLの作成: mysql+pymysql://user:password@host/dbname
+ DB_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
+ engine = create_engine(DB_URL)
+ connection = engine.connect()
+
+ # 変数初期値
+ dt_now = datetime.datetime.now() + datetime.timedelta(hours = 9)
+ date = dt_now.strftime('%Y%m%d%H%M%S')
+ format_date = dt_now.strftime('%Y-%m-%d %H:%M:%S')
+ created_ym = dt_now.strftime('%Y%m')
+ url = ''
+ result_method = 1
+ adjust_token = ''
+ dv_pc = 0
+ dv_and_dc = 0
+ dv_and_au = 0
+ dv_and_sb = 0
+ dv_and_other = 0
+ dv_ios_dc = 0
+ dv_ios_au = 0
+ dv_ios_sb = 0
+ dv_ios_other = 0
+ error = 0
+ ad_id = ''
+ client_id = ''
+ media_id = ''
+ uid = ''
+ banner_id = ''
+ ip = headers.get('x-real-ip')
+ # user_agent = headers.get('User-Agent')
+ user_agent = headers.get('user-agent')[1]
+ referer = ''
+ net_price = ''
+ net_price_unit = ''
+ gross_price = ''
+ gross_price_unit = ''
+ group_id = ''
+ other_parameters = ''
+ hash_value = ''
+ hash_text = ''
+
+ other_parameter_list =[]
+
+ defined_parameters = {
+ 'ad_id',
+ 'client_id',
+ 'media_id',
+ 'media_uid',
+ 'banner_id',
+ 'sid',
+ 'amount',
+ 'reward',
+ 'count',
+ 'status',
+ 'date',
+ 'stage'
+ }
+ if headers.get('referer') is not None:
+ referer = headers.get('referer')
+
+ # GETパラメータチェックと変数化
+ if query_params.get('ad_id') is not None:
+ ad_id = query_params.get('ad_id', [None])[0]
+ else:
+ error = 101
+
+ if query_params.get('client_id') is not None:
+ client_id = query_params.get('client_id', [None])[0]
+
+ else:
+ error = 102
+
+ if query_params.get('media_id') is not None:
+ media_id = query_params.get('media_id', [None])[0]
+
+ else:
+ error = 103
+
+ if query_params.get('media_uid') is not None:
+ uid = query_params.get('media_uid', [None])[0]
+
+ if query_params.get('banner_id') is not None:
+ banner_id = query_params.get('banner_id', [None])[0]
+
+ # パートナー独自パラメータをother_parametersに保存
+ for key, value in query_params.items():
+ logging.info(f"■■■125■■■■{key}={value[0]}")
+ if key not in defined_parameters:
+# other_parameters = other_parameters + key + '=' + value + '&'
+ other_parameter_list.append(f"{key}={value[0]}")
+
+ other_parameters = '&'.join(other_parameter_list)
+ if other_parameters is not None:
+ logging.info(f"■■■131■■■■{other_parameters}")
+# other_parameters = other_parameters.rstrip('&')
+
+ logging.info(f"■■■163■■■■{error}")
+ # 広告詳細取得
+ if error == 0:
+ sql_query = 'SELECT '
+ sql_query += ' a.url, '
+ sql_query += ' a.result_method, '
+ sql_query += ' a.dv_pc, '
+ sql_query += ' a.dv_and_dc, '
+ sql_query += ' a.dv_and_au, '
+ sql_query += ' a.dv_and_sb, '
+ sql_query += ' a.dv_and_other, '
+ sql_query += ' a.dv_ios_dc, '
+ sql_query += ' a.dv_ios_au, '
+ sql_query += ' a.dv_ios_sb, '
+ sql_query += ' a.dv_ios_other, '
+ sql_query += ' a.adjust_token '
+ sql_query += 'FROM '
+ sql_query += ' ad_datas AS a '
+ sql_query += 'INNER JOIN '
+ sql_query += ' ad_join_media_datas AS j '
+ sql_query += 'ON '
+ sql_query += ' a.master_ad_id = j.master_ad_id '
+ sql_query += 'WHERE '
+ sql_query += ' a.master_ad_id = :master_ad_id '
+ sql_query += 'AND '
+ sql_query += ' a.client_id = :client_id '
+ sql_query += 'AND '
+ sql_query += ' j.media_id = :media_id '
+ sql_query += 'AND '
+ sql_query += ' a.status = 1 '
+ sql_query += 'AND '
+ sql_query += ' j.status = 1 '
+ sql_query += 'AND '
+ sql_query += ' a.start_date <= CAST(:start_date AS DATETIME) '
+ sql_query += 'AND '
+ sql_query += ' a.end_date > CAST(:end_date AS DATETIME) '
+ sql_query += 'AND '
+ sql_query += ' a.url != "" '
+ sql_query += 'LIMIT 1 '
+
+ params = {
+ "master_ad_id": int(ad_id),
+ "client_id": int(client_id),
+ "media_id": int(media_id),
+ "start_date": format_date,
+ "end_date": format_date,
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ # 結果を取得
+ ad_data = result.fetchone()
+
+ if ad_data:
+ ad_data_dict = ad_data._asdict()
+ url = ad_data_dict['url']
+ result_method = ad_data_dict['result_method']
+ dv_pc = ad_data_dict['dv_pc']
+ dv_and_dc = ad_data_dict['dv_and_dc']
+ dv_and_au = ad_data_dict['dv_and_au']
+ dv_and_sb = ad_data_dict['dv_and_sb']
+ dv_and_other = ad_data_dict['dv_and_other']
+ dv_ios_dc = ad_data_dict['dv_ios_dc']
+ dv_ios_au = ad_data_dict['dv_ios_au']
+ dv_ios_sb = ad_data_dict['dv_ios_sb']
+ dv_ios_other = ad_data_dict['dv_ios_other']
+
+ try:
+ adjust_token = ad_data_dict['adjust_token']
+ except KeyError:
+ pass
+
+ logging.info(dict(headers))
+ logging.info(f"■■■236■■■■{user_agent}")
+
+ #デバイスチェック
+ # if any("iPhone OS" in item for item in user_agent) or any("iPad" in item for item in user_agent):
+ if 'iPhone OS' in user_agent or 'iPad' in user_agent:
+ if dv_ios_dc == 0 and dv_ios_au == 0 and dv_ios_sb == 0 and dv_ios_other == 0:
+ error = 2
+
+ # elif any("Android" in item for item in user_agent):
+ elif 'Android' in user_agent:
+ if dv_and_dc == 0 and dv_and_au == 0 and dv_and_sb == 0 and dv_and_other == 0:
+ error = 2
+
+ else:
+ if dv_pc == 0:
+ error = 2
+ else:
+ error = 301
+
+
+
+ logging.info("■■■255■■■■")
+ # 特別遷移先設定取得
+ if banner_id != '':
+ sql_query = 'SELECT '
+ sql_query += ' url, '
+ sql_query += ' ios_url, '
+ sql_query += ' and_url '
+ sql_query += 'FROM '
+ sql_query += ' ad_material_datas '
+ sql_query += 'WHERE '
+ sql_query += ' status = 1 '
+ sql_query += 'AND '
+ sql_query += ' master_ad_id = :master_ad_id '
+ sql_query += 'AND '
+ sql_query += ' master_material_id = :master_material_id '
+ sql_query += 'LIMIT 1 '
+
+
+ params = {
+ "master_ad_id": int(ad_id),
+ "master_material_id": int(banner_id),
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ # 結果を取得
+ ad_material_data = result.fetchone()
+
+
+ if ad_material_data:
+ ad_material_data_dict = ad_material_data._asdict()
+ # 特別遷移先に書き換え
+ if ad_material_data_dict['url']:
+ url = ad_material_data_dict['url']
+ # iOS
+ if ad_material_data_dict['ios_url']:
+ url = ad_material_data_dict['ios_url']
+ # Android
+ if ad_material_data_dict['and_url']:
+ url = ad_material_data_dict['and_url']
+ # sid生成
+ if error == 0:
+ while True:
+ sid = ''
+
+ dat = string.digits + string.ascii_lowercase
+ random_string = ''.join([random.choice(dat) for i in range(16)])
+ sid = random_string + date
+
+ sql_query = 'SELECT '
+ sql_query += ' id '
+ sql_query += 'FROM '
+ sql_query += ' click_records_test '
+ sql_query += 'WHERE '
+ sql_query += ' sid = :sid '
+ sql_query += 'AND '
+ sql_query += ' created_ym = :created_ym '
+ sql_query += 'LIMIT 1'
+
+
+ params = {
+ "sid": sid,
+ "created_ym": int(created_ym)
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ # 結果を取得
+ click_data = result.fetchone()
+
+ if not click_data:
+ break
+
+ # クリック時の報酬を取得
+ logging.info(f"■■■329■■■■{error}")
+ if error == 0:
+ sql_query = 'SELECT '
+ sql_query += ' net_price, '
+ sql_query += ' net_price_unit, '
+ sql_query += ' gross_price, '
+ sql_query += ' gross_price_unit, '
+ sql_query += ' group_id '
+ sql_query += 'FROM '
+ sql_query += ' ad_reward_datas '
+ sql_query += 'WHERE '
+ sql_query += ' ad_id = :ad_id '
+ sql_query += 'AND '
+ sql_query += ' type = 1 '
+ sql_query += 'AND '
+ sql_query += ' stage_id IS NULL '
+ sql_query += 'AND '
+ sql_query += ' product_id IS NULL '
+ sql_query += 'AND '
+ sql_query += ' ( '
+ sql_query += ' media_id = :media_id '
+ sql_query += ' OR '
+ sql_query += ' media_id IS NULL '
+ sql_query += ' ) '
+ sql_query += 'ORDER BY media_id DESC '
+
+ params = {
+ "ad_id": int(ad_id),
+ "media_id": int(media_id),
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ # 結果を取得
+ reward_list = result.fetchall()
+
+ if reward_list:
+ for reward_data in reward_list:
+ try:
+ reward_data_dict = reward_data._asdict()
+ group_id = reward_data_dict['group_id']
+
+ sql_query = 'SELECT '
+ sql_query += ' id '
+ sql_query += 'FROM '
+ sql_query += ' ad_reward_group_datas '
+ sql_query += 'WHERE '
+ sql_query += ' status = 1 '
+ sql_query += 'AND '
+ sql_query += ' master_id = :master_id '
+ sql_query += 'AND '
+ sql_query += ' ad_id = :ad_id '
+ sql_query += 'AND '
+ sql_query += ' valid_start_date <= :valid_start_date '
+ sql_query += 'AND '
+ sql_query += ' valid_end_date > :valid_end_date '
+
+ logging.info(f"■■■386■■■■{reward_data_dict}")
+ params = {
+ "master_id": int(group_id or 0),
+ "ad_id": int(ad_id or 0),
+ "valid_start_date": format_date,
+ "valid_end_date": format_date,
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ # 結果を取得
+ group_data = result.fetchone()
+
+ if group_data:
+ net_price = reward_data_dict['net_price']
+ net_price_unit = reward_data_dict['net_price_unit']
+ gross_price = reward_data_dict['gross_price']
+ gross_price_unit = reward_data_dict['gross_price_unit']
+ break
+
+ else:
+ net_price = reward_data_dict['net_price']
+ net_price_unit = reward_data_dict['net_price_unit']
+ gross_price = reward_data_dict['gross_price']
+ gross_price_unit = reward_data_dict['gross_price_unit']
+ continue
+ except KeyError:
+ logging.info(f"■■■415■■■■KeyError")
+ pass
+
+ else:
+ #報酬額データがない
+ error=302
+
+ #報酬額データがない
+ if net_price is None or net_price_unit is None or gross_price is None or gross_price_unit is None:
+ error=302
+ logging.info(f"■■■424■■■■報酬額ない")
+ # sidと遷移情報を保存
+ if error == 0:
+ sql_query = 'INSERT INTO '
+ sql_query += ' click_records_test '
+ sql_query += '( '
+ sql_query += ' master_ad_id, '
+ sql_query += ' client_id, '
+ sql_query += ' media_id, '
+ sql_query += ' banner_id, '
+ sql_query += ' uid, '
+ sql_query += ' sid, '
+ sql_query += ' ip, '
+ sql_query += ' user_agent, '
+ sql_query += ' referer, '
+ sql_query += ' net_price, '
+ sql_query += ' net_price_unit, '
+ sql_query += ' gross_price, '
+ sql_query += ' gross_price_unit, '
+ sql_query += ' other_parameters, '
+ sql_query += ' created_ym '
+ sql_query += ') VALUES ( '
+ sql_query += ' :master_ad_id, '
+ sql_query += ' :client_id, '
+ sql_query += ' :media_id, '
+ sql_query += ' :banner_id, '
+ sql_query += ' :uid, '
+ sql_query += ' :sid, '
+ sql_query += ' :ip, '
+ sql_query += ' :user_agent, '
+ sql_query += ' :referer, '
+ sql_query += ' :net_price, '
+ sql_query += ' :net_price_unit, '
+ sql_query += ' :gross_price, '
+ sql_query += ' :gross_price_unit, '
+ sql_query += ' :other_parameters, '
+ sql_query += ' :created_ym '
+ sql_query += ') '
+
+ params = {
+ "master_ad_id": int(ad_id),
+ "client_id": int(client_id),
+ "media_id": int(media_id),
+ "banner_id": int(banner_id) if banner_id else None,
+ "uid": uid if uid else None,
+ "sid": sid,
+ "ip": ip,
+ "user_agent": user_agent,
+ "referer": referer if referer else None,
+ "net_price": net_price if net_price else None,
+ "net_price_unit": net_price_unit if net_price_unit else None,
+ "gross_price": gross_price if gross_price else None,
+ "gross_price_unit": gross_price_unit if gross_price_unit else None,
+ "other_parameters": other_parameters if other_parameters else None,
+ "created_ym": int(created_ym),
+ }
+ result = connection.execute(text(sql_query), params)
+ logging.info(f"■■■445■■■{text(sql_query)}")
+ # トランザクションをコミットして変更を永続化
+ connection.commit()
+ """
+ # 広告ページへ遷移
+ if '?' in url:
+ # ソケット通信の時はパラメータはデフォ
+ if int(result_method) == 2:
+ url += '&sid=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id
+
+ # AppsFlyer特別処理
+ elif int(result_method) == 3:
+ url += '&af_siteid=' + media_id + '&pid=adleap_int&af_click_lookback=7d&af_sub4=' + sid + '&af_sub1=' + ad_id + '&af_sub2=' + client_id + '&af_sub3=' + media_id
+
+ # Adjust特別処理
+ elif int(result_method) == 4:
+ callback_url = 'https://api.rocket-a.com/service/result?sid=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id + '&device_id={idfa||gps_adid}&adjust_id={adid}'
+
+ if uid != '':
+ callback_url += '&media_uid=' + uid
+
+ encode_callback_url = urllib.parse.quote(callback_url, safe='')
+
+ url += '&install_callback=' + encode_callback_url
+
+ # CPE
+ if adjust_token != '':
+ encode_callback_url_add_token = urllib.parse.quote(callback_url + '&adjust_token=' + adjust_token, safe='')
+
+ url += '&event_callback_' + adjust_token + '=' + encode_callback_url_add_token
+
+ # Airbridge特別処理
+ elif int(result_method) == 5:
+ url += '&sid=' + sid + '&custom_ad_id=' + ad_id + '&custom_client_id=' + client_id + '&sub_id=' + media_id
+
+ # Tyrads特別処理
+ elif int(result_method) == 6:
+ url += '&sub1=' + sid + '&sub2=' + ad_id + '&sub3=' + client_id + '&media_id=' + media_id
+
+ # AyeT Studios特別処理
+ elif int(result_method) == 7:
+ url += '&custom_1=' + sid + '&custom_2=' + ad_id + '&custom_3=' + client_id + '&media_id=' + media_id
+
+ # Torox特別処理
+ elif int(result_method) == 8:
+ url += '&user_id=' + sid + '&subid1=' + ad_id + '&subid2=' + client_id + '&subid3=' + media_id
+
+ # Appricot Ads特別処理
+ elif int(result_method) == 9:
+ url += '&sub1=' + sid + '&sub6=' + ad_id + '&sub7=' + client_id + '&media_id=' + media_id
+
+ # Singular特別処理
+ elif int(result_method) == 10:
+ url += '&cl=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id
+ else:
+ pass
+ #client_id独自処理削除
+ else:
+ # ソケット通信の時はパラメータはデフォ
+ if int(result_method) == 2:
+ url += '?sid=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id
+
+ # AppsFlyer特別処理
+ elif int(result_method) == 3:
+ url += '?af_siteid=' + media_id + '&pid=adleap_int&af_click_lookback=7d&af_sub4=' + sid + '&af_sub1=' + ad_id + '&af_sub2=' + client_id + '&af_sub3=' + media_id
+
+ #Adjust特別処理
+ elif int(result_method) == 4:
+ callback_url = 'https://api.rocket-a.com/service/result?sid=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id + '&device_id={idfa||gps_adid}&adjust_id={adid}'
+
+ if uid != '':
+ callback_url += '&media_uid=' + uid
+
+ encode_callback_url = urllib.parse.quote(callback_url, safe='')
+
+ url += '?install_callback=' + encode_callback_url
+
+ # CPE
+ if adjust_token != '':
+ encode_callback_url_add_token = urllib.parse.quote(callback_url + '&adjust_token=' + adjust_token, safe='')
+
+ url += '&event_callback_' + adjust_token + '=' + encode_callback_url_add_token
+
+ # Airbridge特別処理
+ elif int(result_method) == 5:
+ url += '?sid=' + sid + '&custom_ad_id=' + ad_id + '&custom_client_id=' + client_id + '&sub_id=' + media_id
+
+ # Tyrads特別処理
+ elif int(result_method) == 6:
+ url += '?sub1=' + sid + '&sub2=' + ad_id + '&sub3=' + client_id + '&media_id=' + media_id
+
+ # AyeT Studios特別処理
+ elif int(result_method) == 7:
+ url += '?custom_1=' + sid + '&custom_2=' + ad_id + '&custom_3=' + client_id + '&media_id=' + media_id
+
+ # Torox特別処理
+ elif int(result_method) == 8:
+ url += '?user_id=' + sid + '&subid1=' + ad_id + '&subid2=' + client_id + '&subid3=' + media_id
+
+ # Appricot Ads特別処理
+ elif int(result_method) == 9:
+ url += '?sub1=' + sid + '&sub6=' + ad_id + '&sub7=' + client_id + '&media_id=' + media_id
+
+ # Singular特別処理
+ elif int(result_method) == 10:
+ url += '?cl=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id
+
+ else:
+ #client_id独自処理削除
+ url += '?sid=' + sid + '&ad_id=' + ad_id + '&client_id=' + client_id + '&media_id=' + media_id
+
+ if uid != '' and int(result_method) != 4:
+ url += '&media_uid=' + uid
+ """
+ """
+ params = {
+ "sid": sid,
+ "ad_id": ad_id,
+ "client_id": client_id,
+ "media_id": media_id,
+ }
+
+
+ formatter = string.Formatter()
+ fields = [fname for _, fname, _, _ in formatter.parse(url) if fname]
+ missing = [f for f in fields if f not in params]
+ if missing:
+ params = {
+ "sid": sid,
+ "ad_id": ad_id,
+ "client_id": client_id,
+ "media_id": media_id,
+ "media_uid": uid,
+ }
+ formatter = string.Formatter()
+ fields = [fname for _, fname, _, _ in formatter.parse(url) if fname]
+ missing = [f for f in fields if f not in params]
+
+ if missing or uid == '':
+ error = 401
+ logging.info(f"■■■589■■■パラメータエラー")
+ else:
+ url = url.format(sid=sid, ad_id=ad_id, client_id=client_id, media_id=media_id, media_uid=uid)
+ else:
+ url = url.format(sid=sid, ad_id=ad_id, client_id=client_id, media_id=media_id)
+ """
+ #master_ad_idという名前だと扱いずらい
+ params["ad_id"] = params.pop("master_ad_id")
+ # 置換処理(URLエンコードあり)
+ result = re.sub(
+ r"\{(\w+)\}",
+ lambda m: quote(str(params.get(m.group(1)))) if params.get(m.group(1)) is not None else "",
+ url
+ )
+ logging.info(f"■■■595■■■{result}")
+
+ if error == 0:
+ return response.Response(
+ ctx,
+ status_code=302,
+ headers={"Location": result}
+ )
+
+
+ # エラーページへ遷移
+ error_page = 'https://api.rocket-a.com/service/error?error=' + str(error)
+ if error == 2:
+ error_page = 'https://api.rocket-a.com/service/device_error'
+
+
+ return response.Response(
+ ctx,
+ status_code=302,
+ headers={"Location": error_page}
+ )
+
+'''
+ # testdic = click_data._asdict()
+ # logging.info(testdic)
+ return response.Response(
+ ctx,
+ response_data=f"Helloa{dv_ios_other}",
+ headers={"Content-Type": "application/json"}
+ )
+'''
+
+
+
diff --git a/oci_func_prod/click_test/func.yaml b/oci_func_prod/click_test/func.yaml
new file mode 100644
index 0000000..e0790f6
--- /dev/null
+++ b/oci_func_prod/click_test/func.yaml
@@ -0,0 +1,8 @@
+schema_version: 20180708
+name: click_test
+version: 0.0.1
+runtime: python
+build_image: fnproject/python:3.12-dev
+run_image: fnproject/python:3.12
+entrypoint: /python/bin/fdk /function/func.py handler
+memory: 256
\ No newline at end of file
diff --git a/oci_func_prod/click_test/requirements.txt b/oci_func_prod/click_test/requirements.txt
new file mode 100644
index 0000000..a12b610
--- /dev/null
+++ b/oci_func_prod/click_test/requirements.txt
@@ -0,0 +1,3 @@
+fdk>=0.1.103
+sqlalchemy
+pymysql
diff --git a/oci_func_prod/device_error/func.py b/oci_func_prod/device_error/func.py
new file mode 100644
index 0000000..19f7d4c
--- /dev/null
+++ b/oci_func_prod/device_error/func.py
@@ -0,0 +1,39 @@
+import io
+import json
+import logging
+import datetime
+
+from fdk import response
+
+dt_now = datetime.datetime.now() + datetime.timedelta(hours = 9)
+
+html = '<!doctype html>'
+html += '<html lang="ja">'
+html += '<head>'
+html += '<meta charset="utf-8">'
+html += '<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">'
+html += '<title>Rocket A|無効のリンク</title>'
+html += '<style>*,*::before,*::after{box-sizing: border-box;}body{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";margin:0;min-height:100vh;font-size:0.88rem;font-weight:400;line-height:1.5;color:#555;text-align:left;background-color:#ffffff;}.container .logo{margin:8rem auto 1rem;padding:3rem .5rem;width:120px;height:120px;background-color:#343a40;border-radius:50%;}.container .logo img{display:inline-block;margin:0 auto;line-height:120px;}.container h5,.container p{margin:0;padding:0;text-align:center;color:#555;}.container h5{font-size:1.5rem;font-weight:bold;line-height:1.2;margin-bottom:.5rem;}.container p,.copyright{font-size:0.88rem;}.copyright{margin:auto auto 0;padding:.5rem 0;width:100%;text-align:center;color:#fff;background-color:#343a40;}</style>'
+html += '</head>'
+html += '<body>'
+html += '<div class="container">'
+html += '<div class="logo"><img src="https://client.rocket-a.com/front/rocketa_logo_mono.png" width="100%" title="" alt=""></div>'
+html += '<h5>無効のリンク</h5>'
+html += '<p>誠に申し訳ございませんが、<br>お使いのデバイスこの広告に対応しておりません。</p>'
+html += '</div>'
+html += '<div class="copyright">Copyright (C) 2025-'
+html += dt_now.strftime('%Y')
+html += ' aix inc. All Rights Reserved.</div>'
+html += '</body>'
+html += '</html>'
+
+
+def handler(ctx, data: io.BytesIO = None):
+
+ return response.Response (
+ ctx,
+ status_code=404,
+ headers={"Content-Type": "text/html"},
+ response_data= html
+ )
+
diff --git a/oci_func_prod/device_error/func.yaml b/oci_func_prod/device_error/func.yaml
new file mode 100644
index 0000000..defc596
--- /dev/null
+++ b/oci_func_prod/device_error/func.yaml
@@ -0,0 +1,8 @@
+schema_version: 20180708
+name: device_error
+version: 0.0.1
+runtime: python
+build_image: fnproject/python:3.12-dev
+run_image: fnproject/python:3.12
+entrypoint: /python/bin/fdk /function/func.py handler
+memory: 256
\ No newline at end of file
diff --git a/oci_func_prod/device_error/requirements.txt b/oci_func_prod/device_error/requirements.txt
new file mode 100644
index 0000000..58a638e
--- /dev/null
+++ b/oci_func_prod/device_error/requirements.txt
@@ -0,0 +1 @@
+fdk>=0.1.103
\ No newline at end of file
diff --git a/oci_func_prod/error/func.py b/oci_func_prod/error/func.py
new file mode 100644
index 0000000..df0e7d1
--- /dev/null
+++ b/oci_func_prod/error/func.py
@@ -0,0 +1,60 @@
+import io
+import json
+import logging
+import datetime
+import urllib.parse
+
+from fdk import response
+
+dt_now = datetime.datetime.now() + datetime.timedelta(hours = 9)
+
+#error
+#101 ad_id param error
+#102 client_id param error
+#103 media_id param error
+#301 not found ad data
+#302 not found rewward data
+#401 url error (遷移先パラメータと取得パラメータの不一致)
+
+
+logging.basicConfig(level=logging.INFO)
+
+def handler(ctx, data: io.BytesIO = None):
+
+ full_url = ctx.RequestURL()
+ query_string = urllib.parse.urlparse(full_url).query
+ query_params = urllib.parse.parse_qs(query_string)
+ error = ''
+ if query_params.get('error') is not None:
+ error = query_params.get('error', [None])[0]
+ return output(ctx, error)
+
+
+
+def output(ctx, error):
+ html = '<!doctype html>'
+ html += '<html lang="ja">'
+ html += '<head>'
+ html += '<meta charset="utf-8">'
+ html += '<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">'
+ html += '<title>Rocket A|無効のリンク</title>'
+ html += '<style>*,*::before,*::after{box-sizing: border-box;}body{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";margin:0;min-height:100vh;font-size:0.88rem;font-weight:400;line-height:1.5;color:#555;text-align:left;background-color:#ffffff;}.container .logo{margin:8rem auto 1rem;padding:3rem .5rem;width:120px;height:120px;background-color:#343a40;border-radius:50%;}.container .logo img{display:inline-block;margin:0 auto;line-height:120px;}.container h5,.container p{margin:0;padding:0;text-align:center;color:#555;}.container h5{font-size:1.5rem;font-weight:bold;line-height:1.2;margin-bottom:.5rem;}.container p,.copyright{font-size:0.88rem;}.copyright{margin:auto auto 0;padding:.5rem 0;width:100%;text-align:center;color:#fff;background-color:#343a40;}</style>'
+ html += '</head>'
+ html += '<body>'
+ html += '<div class="container">'
+ html += '<div class="logo"><img src="https://client.rocket-a.com/front/rocketa_logo_mono.png" width="100%" title="" alt=""></div>'
+ html += '<h5>無効のリンク<br />ERROR CODE:'+error+'</h5>'
+ html += '<p>誠に申し訳ございませんが、<br>この広告のリンクは現在無効となっております。</p>'
+ html += '</div>'
+ html += '<div class="copyright">Copyright (C) 2025-'
+ html += dt_now.strftime('%Y')
+ html += ' aix inc. All Rights Reserved.</div>'
+ html += '</body>'
+ html += '</html>'
+
+ return response.Response (
+ ctx,
+ status_code=404,
+ headers={"Content-Type": "text/html"},
+ response_data= html
+ )
\ No newline at end of file
diff --git a/oci_func_prod/error/func.yaml b/oci_func_prod/error/func.yaml
new file mode 100644
index 0000000..748cbdf
--- /dev/null
+++ b/oci_func_prod/error/func.yaml
@@ -0,0 +1,8 @@
+schema_version: 20180708
+name: error
+version: 0.0.1
+runtime: python
+build_image: fnproject/python:3.12-dev
+run_image: fnproject/python:3.12
+entrypoint: /python/bin/fdk /function/func.py handler
+memory: 256
\ No newline at end of file
diff --git a/oci_func_prod/error/requirements.txt b/oci_func_prod/error/requirements.txt
new file mode 100644
index 0000000..58a638e
--- /dev/null
+++ b/oci_func_prod/error/requirements.txt
@@ -0,0 +1 @@
+fdk>=0.1.103
\ No newline at end of file
diff --git a/oci_func_prod/log/func.py b/oci_func_prod/log/func.py
new file mode 100644
index 0000000..7cce0c1
--- /dev/null
+++ b/oci_func_prod/log/func.py
@@ -0,0 +1,63 @@
+import io
+import json
+import logging
+import json
+import datetime
+import urllib.parse
+from sqlalchemy import create_engine, text
+
+from fdk import response
+
+logging.basicConfig(level=logging.INFO)
+
+int_param_list = {
+ 'ad_id',
+ 'client_id',
+ 'amount',
+ 'sales_count',
+ 'stage',
+}
+
+
+def handler(ctx, data: io.BytesIO = None):
+ logging.info("handler started")
+ headers = ctx.Headers()
+
+ full_url = ctx.RequestURL()
+
+ DB_HOST = "api.rocket-a.com"
+ DB_USER = "root"
+ DB_PASSWORD = "buSDonry4%h6rm-0fy"
+ DB_NAME = "rocketa-api"
+
+ # 接続URLの作成: mysql+pymysql://user:password@host/dbname
+ DB_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
+ engine = create_engine(DB_URL)
+ connection = engine.connect()
+
+
+ # 成果データ保存
+ sql_query = 'INSERT INTO '
+ sql_query += ' logs '
+ sql_query += '( '
+ sql_query += ' log '
+ sql_query += ') VALUES ( '
+ sql_query += ' :log '
+ sql_query += ') '
+ params = {
+ "log": full_url if full_url else None,
+ }
+ result = connection.execute(text(sql_query), params)
+
+ # トランザクションをコミットして変更を永続化
+ connection.commit()
+
+ # 終了ステータス表示
+
+ return response.Response(
+ ctx,
+ status_code=200,
+ response_data=json.dumps('OK'),
+ headers={"Access-Control-Allow-Origin": "*","Access-Control-Allow-Headers": "Content-Type"}
+ )
+
diff --git a/oci_func_prod/log/func.yaml b/oci_func_prod/log/func.yaml
new file mode 100644
index 0000000..e9184c9
--- /dev/null
+++ b/oci_func_prod/log/func.yaml
@@ -0,0 +1,8 @@
+schema_version: 20180708
+name: log
+version: 0.0.1
+runtime: python
+build_image: fnproject/python:3.12-dev
+run_image: fnproject/python:3.12
+entrypoint: /python/bin/fdk /function/func.py handler
+memory: 256
\ No newline at end of file
diff --git a/oci_func_prod/log/requirements.txt b/oci_func_prod/log/requirements.txt
new file mode 100644
index 0000000..e3b54a1
--- /dev/null
+++ b/oci_func_prod/log/requirements.txt
@@ -0,0 +1,3 @@
+fdk>=0.1.103
+sqlalchemy
+pymysql
\ No newline at end of file
diff --git a/oci_func_prod/result/func.py b/oci_func_prod/result/func.py
new file mode 100644
index 0000000..6b19337
--- /dev/null
+++ b/oci_func_prod/result/func.py
@@ -0,0 +1,213 @@
+import io
+import json
+import logging
+import json
+import datetime
+import urllib.parse
+from sqlalchemy import create_engine, text
+
+from fdk import response
+
+logging.basicConfig(level=logging.INFO)
+
+int_param_list = {
+ 'ad_id',
+ 'client_id',
+ 'amount',
+ 'sales_count',
+ 'stage',
+}
+
+
+def handler(ctx, data: io.BytesIO = None):
+ logging.info("handler started")
+ headers = ctx.Headers()
+
+ full_url = ctx.RequestURL()
+ query_string = urllib.parse.urlparse(full_url).query
+ logging.info(f"■■■28■■■{query_string}")
+ query_params = urllib.parse.parse_qs(query_string)
+
+ DB_HOST = "api.rocket-a.com"
+ DB_USER = "root"
+ DB_PASSWORD = "buSDonry4%h6rm-0fy"
+ DB_NAME = "rocketa-api"
+
+ # 接続URLの作成: mysql+pymysql://user:password@host/dbname
+ DB_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
+ engine = create_engine(DB_URL)
+ connection = engine.connect()
+
+ send_param_list = {
+ 'sid' : '',
+ 'ad_id' : '',
+ 'client_id' : '',
+ 'uid' : '',
+ 'uid2' : '',
+ 'product_code' : '',
+ 'amount' : '',
+ 'sales_count' : '',
+ 'stage' : '',
+ 'domain' : '',
+ 'sender' : '',
+ }
+ dt_now = datetime.datetime.now() + datetime.timedelta(hours = 9)
+ format_date = dt_now.strftime('%Y-%m-%d %H:%M:%S')
+ created_ym = dt_now.strftime('%Y%m')
+ error = 0
+ parameters = query_string
+ ip = ''
+ param_event = ''
+
+ i = 0
+ for key, value_list in query_params.items():
+ logging.info(f"■■■{key}■■■")
+ # 1. キーが格納先のリストに存在するかチェック
+ if key in send_param_list:
+
+ # 2. 値のリストが空ではないかチェック (あれば処理を続行)
+ if value_list:
+
+ # 3. リストの最初の要素 [0] を取り出し、格納先の辞書に代入
+ # (通常、クエリパラメータで同じキーが複数回渡されることは稀なため)
+ send_param_list[key] = value_list[0]
+ i += 1
+ else:
+ pass
+
+ if i == 0:
+
+ return response.Response(
+ ctx,
+ response_data=json.dumps('Bad Request'),
+ headers={"Content-Type": "application/json"}
+ )
+
+
+
+ # ソケット判定 謎の値を送られてきても対応できるように
+ if send_param_list['sender'] != '2' and send_param_list['sender'] != '3':
+ send_param_list['sender'] = 1
+
+ # クライアントのip取得
+ ip = headers.get('x-real-ip')
+
+ # 必須パラメータチェック
+ if send_param_list['sid'] == '':
+ error = 1
+
+ #if send_param_list['ad_id'] == '':
+ # error = 1
+
+ #if send_param_list['client_id'] == '':
+ # error = 1
+
+ # 廃棄イベントのリストに有る場合は処理を止める
+ if query_params.get('event') is not None:
+ param_event = query_params.get('event')
+
+ sql_query = 'SELECT '
+ sql_query += ' id '
+ sql_query += 'FROM '
+ sql_query += ' dispose_events '
+ sql_query += 'WHERE '
+ sql_query += ' event = :event '
+ sql_query += 'LIMIT 1 '
+
+ params = {
+ "event": param_event,
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ dispose_event = result.fetchone()
+
+ if dispose_event:
+ return response.Response(
+ ctx,
+ response_data=json.dumps('Dispose Event'),
+ headers={"Content-Type": "application/json"}
+ )
+
+ # 成果データ保存
+ sql_query = 'INSERT INTO '
+ sql_query += ' result_records '
+ sql_query += '( '
+ sql_query += ' ip, '
+ sql_query += ' sid, '
+ sql_query += ' ad_id, '
+ sql_query += ' client_id, '
+ sql_query += ' uid, '
+ sql_query += ' uid2, '
+ sql_query += ' product_code, '
+ sql_query += ' amount, '
+ sql_query += ' sales_count, '
+ sql_query += ' stage, '
+ sql_query += ' domain, '
+ sql_query += ' sender, '
+ sql_query += ' parameters, '
+ sql_query += ' created_ym '
+ sql_query += ') VALUES ( '
+ sql_query += ' :ip, '
+ sql_query += ' :sid, '
+ sql_query += ' :ad_id, '
+ sql_query += ' :client_id, '
+ sql_query += ' :uid, '
+ sql_query += ' :uid2, '
+ sql_query += ' :product_code, '
+ sql_query += ' :amount, '
+ sql_query += ' :sales_count, '
+ sql_query += ' :stage, '
+ sql_query += ' :domain, '
+ sql_query += ' :sender, '
+ sql_query += ' :parameters, '
+ sql_query += ' :created_ym '
+ sql_query += ') '
+
+ params = {
+ "ip": ip if ip else None,
+ "sid": send_param_list['sid'] if send_param_list['sid'] else None,
+ "ad_id": int(send_param_list['ad_id']) if send_param_list['ad_id'] else None,
+ "client_id": int(send_param_list['client_id']) if send_param_list['client_id'] else None,
+ "uid": send_param_list['uid'] if send_param_list['uid'] else None,
+ "uid2": send_param_list['uid2'] if send_param_list['uid2'] else None,
+ "product_code": send_param_list['product_code'] if send_param_list['product_code'] else None,
+ "amount": int(send_param_list['amount']) if send_param_list['amount'] else None,
+ "sales_count": int(send_param_list['sales_count']) if send_param_list['sales_count'] else None,
+ "stage": int(send_param_list['stage']) if send_param_list['stage'] else None,
+ "domain": send_param_list['domain'] if send_param_list['domain'] else None,
+ "sender": int(send_param_list['sender']),
+ "parameters": parameters,
+ "created_ym": int(created_ym),
+ }
+ result = connection.execute(text(sql_query), params)
+
+ # トランザクションをコミットして変更を永続化
+ connection.commit()
+
+
+
+ # 終了ステータス表示
+ if error == 0:
+ return response.Response(
+ ctx,
+ status_code=200,
+ response_data=json.dumps('OK'),
+ headers={"Access-Control-Allow-Origin": "*","Access-Control-Allow-Headers": "Content-Type"}
+ )
+ else:
+ return response.Response(
+ ctx,
+ status_code=200,
+ response_data=json.dumps('Bad Request'),
+ headers={"Access-Control-Allow-Origin": "*","Access-Control-Allow-Headers": "Content-Type"}
+ )
+'''
+ # testdic = send_param_list._asdict()
+ logging.info(send_param_list)
+ return response.Response(
+ ctx,
+ response_data=f"Hello",
+ headers={"Content-Type": "application/json"}
+ )
+'''
\ No newline at end of file
diff --git a/oci_func_prod/result/func.yaml b/oci_func_prod/result/func.yaml
new file mode 100644
index 0000000..a9d4366
--- /dev/null
+++ b/oci_func_prod/result/func.yaml
@@ -0,0 +1,8 @@
+schema_version: 20180708
+name: result
+version: 0.0.1
+runtime: python
+build_image: fnproject/python:3.12-dev
+run_image: fnproject/python:3.12
+entrypoint: /python/bin/fdk /function/func.py handler
+memory: 256
\ No newline at end of file
diff --git a/oci_func_prod/result/requirements.txt b/oci_func_prod/result/requirements.txt
new file mode 100644
index 0000000..a12b610
--- /dev/null
+++ b/oci_func_prod/result/requirements.txt
@@ -0,0 +1,3 @@
+fdk>=0.1.103
+sqlalchemy
+pymysql
diff --git a/oci_func_prod/result_test/func.py b/oci_func_prod/result_test/func.py
new file mode 100644
index 0000000..410920d
--- /dev/null
+++ b/oci_func_prod/result_test/func.py
@@ -0,0 +1,214 @@
+import io
+import json
+import logging
+import json
+import datetime
+import urllib.parse
+from sqlalchemy import create_engine, text
+
+from fdk import response
+
+logging.basicConfig(level=logging.INFO)
+
+int_param_list = {
+ 'ad_id',
+ 'client_id',
+ 'amount',
+ 'sales_count',
+ 'stage',
+}
+
+
+def handler(ctx, data: io.BytesIO = None):
+ logging.info("handler started")
+ headers = ctx.Headers()
+
+ full_url = ctx.RequestURL()
+ query_string = urllib.parse.urlparse(full_url).query
+ logging.info(f"■■■28■■■{query_string}")
+ query_params = urllib.parse.parse_qs(query_string)
+
+ DB_HOST = "api.rocket-a.com"
+ DB_USER = "root"
+ DB_PASSWORD = "buSDonry4%h6rm-0fy"
+ DB_NAME = "rocketa-api"
+
+ # 接続URLの作成: mysql+pymysql://user:password@host/dbname
+ DB_URL = f"mysql+pymysql://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
+ engine = create_engine(DB_URL)
+ connection = engine.connect()
+
+ send_param_list = {
+ 'sid' : '',
+ 'ad_id' : '',
+ 'client_id' : '',
+ 'uid' : '',
+ 'uid2' : '',
+ 'product_code' : '',
+ 'amount' : '',
+ 'sales_count' : '',
+ 'stage' : '',
+ 'domain' : '',
+ 'sender' : '',
+ }
+ dt_now = datetime.datetime.now() + datetime.timedelta(hours = 9)
+ format_date = dt_now.strftime('%Y-%m-%d %H:%M:%S')
+ created_ym = dt_now.strftime('%Y%m')
+ error = 0
+ parameters = query_string
+ ip = ''
+ param_event = ''
+
+ i = 0
+ for key, value_list in query_params.items():
+ logging.info(f"■■■{key}■■■")
+ # 1. キーが格納先のリストに存在するかチェック
+ if key in send_param_list:
+
+ # 2. 値のリストが空ではないかチェック (あれば処理を続行)
+ if value_list:
+
+ # 3. リストの最初の要素 [0] を取り出し、格納先の辞書に代入
+ # (通常、クエリパラメータで同じキーが複数回渡されることは稀なため)
+ send_param_list[key] = value_list[0]
+ i += 1
+ else:
+ pass
+
+ if i == 0:
+
+ return response.Response(
+ ctx,
+ response_data=json.dumps('Bad Request'),
+ headers={"Content-Type": "application/json"}
+ )
+
+
+
+ # ソケット判定 謎の値を送られてきても対応できるように
+ if send_param_list['sender'] != '2' and send_param_list['sender'] != '3':
+ send_param_list['sender'] = 1
+
+ # クライアントのip取得
+ ip = headers.get('x-real-ip')
+
+ # 必須パラメータチェック
+ if send_param_list['sid'] == '':
+ error = 1
+
+ #if send_param_list['ad_id'] == '':
+ # error = 1
+
+ #if send_param_list['client_id'] == '':
+ # error = 1
+
+ # 廃棄イベントのリストに有る場合は処理を止める
+ if query_params.get('event') is not None:
+ param_event = query_params.get('event')
+
+ sql_query = 'SELECT '
+ sql_query += ' id '
+ sql_query += 'FROM '
+ sql_query += ' dispose_events '
+ sql_query += 'WHERE '
+ sql_query += ' event = :event '
+ sql_query += 'LIMIT 1 '
+
+ params = {
+ "event": param_event,
+ }
+ # SQLAlchemyが安全にクエリを組み立て、SQLインジェクションを防ぐ
+ result = connection.execute(text(sql_query), params)
+
+ dispose_event = result.fetchone()
+
+ if dispose_event:
+ return response.Response(
+ ctx,
+ response_data=json.dumps('Dispose Event'),
+ headers={"Content-Type": "application/json"}
+ )
+
+ # 成果データ保存
+ sql_query = 'INSERT INTO '
+ sql_query += ' result_records_test '
+ sql_query += '( '
+ sql_query += ' ip, '
+ sql_query += ' sid, '
+ sql_query += ' ad_id, '
+ sql_query += ' client_id, '
+ sql_query += ' uid, '
+ sql_query += ' uid2, '
+ sql_query += ' product_code, '
+ sql_query += ' amount, '
+ sql_query += ' sales_count, '
+ sql_query += ' stage, '
+ sql_query += ' domain, '
+ sql_query += ' sender, '
+ sql_query += ' parameters, '
+ sql_query += ' created_ym '
+ sql_query += ') VALUES ( '
+ sql_query += ' :ip, '
+ sql_query += ' :sid, '
+ sql_query += ' :ad_id, '
+ sql_query += ' :client_id, '
+ sql_query += ' :uid, '
+ sql_query += ' :uid2, '
+ sql_query += ' :product_code, '
+ sql_query += ' :amount, '
+ sql_query += ' :sales_count, '
+ sql_query += ' :stage, '
+ sql_query += ' :domain, '
+ sql_query += ' :sender, '
+ sql_query += ' :parameters, '
+ sql_query += ' :created_ym '
+ sql_query += ') '
+
+ params = {
+ "ip": ip if ip else None,
+ "sid": send_param_list['sid'] if send_param_list['sid'] else None,
+ "ad_id": int(send_param_list['ad_id']) if send_param_list['ad_id'] else None,
+ "client_id": int(send_param_list['client_id']) if send_param_list['client_id'] else None,
+ "uid": send_param_list['uid'] if send_param_list['uid'] else None,
+ "uid2": send_param_list['uid2'] if send_param_list['uid2'] else None,
+ "product_code": send_param_list['product_code'] if send_param_list['product_code'] else None,
+ "amount": int(send_param_list['amount']) if send_param_list['amount'] else None,
+ "sales_count": int(send_param_list['sales_count']) if send_param_list['sales_count'] else None,
+ "stage": int(send_param_list['stage']) if send_param_list['stage'] else None,
+ "domain": send_param_list['domain'] if send_param_list['domain'] else None,
+ "sender": int(send_param_list['sender']),
+ "parameters": parameters,
+ "created_ym": int(created_ym),
+ }
+ result = connection.execute(text(sql_query), params)
+
+ # トランザクションをコミットして変更を永続化
+ connection.commit()
+
+
+ inserted_id = result.lastrowid
+ logging.info(f"■■■{inserted_id}■■■")
+ # 終了ステータス表示
+ if error == 0:
+ return response.Response(
+ ctx,
+ status_code=200,
+ response_data=json.dumps({"result": "OK", "insert_id":inserted_id}),
+ headers={"Access-Control-Allow-Origin": "*","Access-Control-Allow-Headers": "Content-Type"}
+ )
+ else:
+ return response.Response(
+ ctx,
+ status_code=200,
+ response_data=json.dumps({"result": "Bad Request"}),
+ headers={"Access-Control-Allow-Origin": "*","Access-Control-Allow-Headers": "Content-Type"}
+ )
+'''
+ # testdic = send_param_list._asdict()
+ logging.info(send_param_list)
+ return response.Response(
+ ctx,
+ response_data=f"Hello",
+ headers={"Content-Type": "application/json"}
+ )
+'''
\ No newline at end of file
diff --git a/oci_func_prod/result_test/func.yaml b/oci_func_prod/result_test/func.yaml
new file mode 100644
index 0000000..ad633b7
--- /dev/null
+++ b/oci_func_prod/result_test/func.yaml
@@ -0,0 +1,8 @@
+schema_version: 20180708
+name: result_test
+version: 0.0.1
+runtime: python
+build_image: fnproject/python:3.12-dev
+run_image: fnproject/python:3.12
+entrypoint: /python/bin/fdk /function/func.py handler
+memory: 256
\ No newline at end of file
diff --git a/oci_func_prod/result_test/requirements.txt b/oci_func_prod/result_test/requirements.txt
new file mode 100644
index 0000000..a12b610
--- /dev/null
+++ b/oci_func_prod/result_test/requirements.txt
@@ -0,0 +1,3 @@
+fdk>=0.1.103
+sqlalchemy
+pymysql
diff --git a/oci_func_prod/test/func.py b/oci_func_prod/test/func.py
new file mode 100644
index 0000000..e1053b7
--- /dev/null
+++ b/oci_func_prod/test/func.py
@@ -0,0 +1,79 @@
+import io
+import urllib.parse
+import datetime
+import logging
+from fdk import response
+
+
+def handler(ctx, data: io.BytesIO = None):
+
+ # ===== 時刻(JST)=====
+ now_jst = datetime.datetime.utcnow() + datetime.timedelta(hours=9)
+
+ # ===== URL / Query =====
+ full_url = ctx.RequestURL()
+ parsed = urllib.parse.urlparse(full_url)
+ query_params = urllib.parse.parse_qs(parsed.query)
+
+ error = query_params.get('error', [''])[0]
+
+ # ===== Headers =====
+ headers = ctx.Headers()
+
+ x_real_ip = headers.get('x-real-ip', '')
+ user_agent = headers.get('user-agent', '')
+ referer = headers.get('referer', '')
+
+ return output(
+ ctx,
+ now_jst,
+ full_url,
+ error,
+ x_real_ip,
+ user_agent,
+ referer
+ )
+
+
+def output(ctx, now_jst, full_url, error, x_real_ip, user_agent, referer):
+
+ html = f"""<!doctype html>
+<html lang="ja">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+<title>TEST</title>
+<style>
+*,*::before,*::after{{box-sizing:border-box;}}
+body{{display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif;margin:0;min-height:100vh;font-size:.88rem;line-height:1.5;color:#555;background:#fff;}}
+.container{{max-width:800px;margin:3rem auto;padding:1rem;}}
+h1{{font-size:1.4rem;margin-bottom:1rem;}}
+table{{width:100%;border-collapse:collapse;}}
+th,td{{padding:.5rem;border:1px solid #ddd;text-align:left;word-break:break-all;}}
+th{{background:#f5f5f5;width:30%;}}
+footer{{margin-top:auto;padding:.5rem;text-align:center;color:#fff;background:#343a40;}}
+</style>
+</head>
+<body>
+<div class="container">
+<h1>Request Info</h1>
+<table>
+<tr><th>日時 (JST)</th><td>{now_jst.strftime('%Y-%m-%d %H:%M:%S')}</td></tr>
+<tr><th>Request URL</th><td>{full_url}</td></tr>
+<tr><th>Error Param</th><td>{error}</td></tr>
+<tr><th>X-Real-IP</th><td>{x_real_ip}</td></tr>
+<tr><th>User-Agent</th><td>{user_agent}</td></tr>
+<tr><th>Referer</th><td>{referer}</td></tr>
+</table>
+</div>
+<footer>TEST</footer>
+</body>
+</html>
+"""
+
+ return response.Response(
+ ctx,
+ status_code=404,
+ headers={"Content-Type": "text/html; charset=utf-8"},
+ response_data=html
+ )
diff --git a/oci_func_prod/test/func.yaml b/oci_func_prod/test/func.yaml
new file mode 100644
index 0000000..76e09d5
--- /dev/null
+++ b/oci_func_prod/test/func.yaml
@@ -0,0 +1,8 @@
+schema_version: 20180708
+name: test
+version: 0.0.1
+runtime: python
+build_image: fnproject/python:3.12-dev
+run_image: fnproject/python:3.12
+entrypoint: /python/bin/fdk /function/func.py handler
+memory: 256
\ No newline at end of file
diff --git a/oci_func_prod/test/requirements.txt b/oci_func_prod/test/requirements.txt
new file mode 100644
index 0000000..58a638e
--- /dev/null
+++ b/oci_func_prod/test/requirements.txt
@@ -0,0 +1 @@
+fdk>=0.1.103
\ No newline at end of file