require 'sinatra'
require 'sinatra/json'
require 'sqlite3'
require 'bcrypt'
require 'erb'
require 'securerandom'
set :bind, '0.0.0.0'
set :port, 4567
# ✅ セッション設定
use Rack::Session::Cookie,
key: 'rack.session',
path: '/',
same_site: :lax,
secure: false,
secret: SecureRandom.hex(64)
# ✅ CORS(Cookie を許可)
before do
response.headers['Access-Control-Allow-Origin'] = 'http://127.0.0.1:4567'
response.headers['Access-Control-Allow-Credentials'] = 'true'
end
options '*' do
response.headers['Access-Control-Allow-Origin'] = 'http://127.0.0.1:4567'
response.headers['Access-Control-Allow-Credentials'] = 'true'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, OPTIONS'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type'
200
end
# ✅ DB
DB = SQLite3::Database.new "database.db"
DB.results_as_hash = true
DB.execute <<-SQL
CREATE TABLE IF NOT EXISTS requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL,
description TEXT,
latitude REAL NOT NULL,
longitude REAL NOT NULL,
start_time TEXT,
end_time TEXT,
info_level REAL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
SQL
DB.execute <<-SQL
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
role TEXT NOT NULL CHECK(role IN ('supporter', 'requester')),
trust_score REAL DEFAULT 0.5,
info_level REAL DEFAULT 0.5,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
SQL
# ✅ ログイン必須
def require_login
unless session[:user_id]
if request.path.start_with?("/api/")
halt 401, json({ error: "ログインが必要です" })
else
redirect '/login'
end
end
end
# ✅ signup
post '/api/signup' do
data = JSON.parse(request.body.read)
username = data["username"]
password = data["password"]
role = data["role"] || "requester"
info_level = data["info_level"] || 0.5
halt 400, json({ error: "username, password, role が必要です" }) if username.nil? || password.nil? || role.nil?
password_hash = BCrypt::Password.create(password)
begin
DB.execute("INSERT INTO users (username, password_hash, role, info_level) VALUES (?, ?, ?, ?)",
[username, password_hash, role, info_level])
rescue SQLite3::ConstraintException
halt 400, json({ error: "そのユーザー名は既に使われています" })
end
json({ status: "success", message: "ユーザー登録完了", role: role })
end
# ✅ login
post '/api/login' do
data = JSON.parse(request.body.read)
username = data["username"]
password = data["password"]
user = DB.execute("SELECT * FROM users WHERE username = ?", [username]).first
halt 401, json({ error: "ユーザーが存在しません" }) unless user
if BCrypt::Password.new(user["password_hash"]) == password
session[:user_id] = user["id"]
json({
status: "success",
message: "ログイン成功",
user: user["username"],
role: user["role"],
trust_score: user["trust_score"],
info_level: user["info_level"]
})
else
halt 401, json({ error: "パスワードが違います" })
end
end
# ✅ logout
post '/api/logout' do
session.clear
json({ status: "success", message: "ログアウトしました" })
end
# ✅ me
get '/api/me' do
require_login
user = DB.execute("SELECT id, username, role, trust_score, info_level FROM users WHERE id = ?", [session[:user_id]]).first
json user
end
# ✅ トップページ
get '/' do
require_login
@requests = DB.execute("SELECT * FROM requests")
erb :index
end
get '/signup' do
erb :signup
end
get '/login' do
erb :login
end
# ✅ 依頼一覧
get '/api/requests' do
require_login
rows = DB.execute("SELECT * FROM requests")
json rows
end
# ✅ 依頼登録
post '/api/requests' do
require_login
data = JSON.parse(request.body.read)
category = data["category"]
description = data["description"]
latitude = data["latitude"]
longitude = data["longitude"]
start_time = data["start_time"]
end_time = data["end_time"]
info_level = data["info_level"] || 0.5
if category.nil? || latitude.nil? || longitude.nil?
halt 400, json({ error: "必須項目が不足しています" })
end
DB.execute(
"INSERT INTO requests (category, description, latitude, longitude, start_time, end_time, info_level)
VALUES (?, ?, ?, ?, ?, ?, ?)",
[category, description, latitude, longitude, start_time, end_time, info_level]
)
json({ status: "success", message: "依頼を登録しました" })
end