This commit is contained in:
Rikuoh Tsujitani 2024-08-18 22:59:25 +09:00
parent 091bb382c8
commit 081344beae
Signed by: riq0h
GPG key ID: 010F09DEA298C717
9 changed files with 103 additions and 93 deletions

View file

@ -1,9 +1,9 @@
## Usage ## Usage
`git clone https://github.com/riq0h/memoapp.git` ```
git clone https://github.com/riq0h/memoapp.git
`cd memoapp` cd memoapp
bundle install
`bundle install` psql -U postgres -f memos.sql
ruby app.rb
`ruby app.rb` ```

View file

@ -2,29 +2,46 @@
require 'sinatra' require 'sinatra'
require 'sinatra/reloader' require 'sinatra/reloader'
require 'json' require 'pg'
require 'securerandom'
require 'cgi' require 'cgi'
FILE_PATH = 'memos.json' configure do
set :conn, PG.connect(dbname: 'memo_app')
end
helpers do helpers do
def h(text) def h(text)
CGI.escapeHTML(text.to_s) CGI.escapeHTML(text.to_s)
end end
def db
settings.conn
end end
def load_memos def find_memo(id)
if !File.zero?(FILE_PATH) db.exec_params('SELECT * FROM memos WHERE id = $1', [id]).first
JSON.parse(File.read(FILE_PATH))
else
{}
end
end end
def save_memos(memos) def create_memo(title, content)
File.open(FILE_PATH, 'w') do |file| db.exec_params(
file.write(JSON.generate(memos)) 'INSERT INTO memos (title, content) VALUES ($1, $2) RETURNING id',
[title, content]
).first['id']
end
def update_memo(id, title, content)
db.exec_params(
'UPDATE memos SET title = $1, content = $2 WHERE id = $3',
[title, content, id]
)
end
def delete_memo(id)
db.exec_params('DELETE FROM memos WHERE id = $1', [id])
end
def all_memos
db.exec('SELECT * FROM memos ORDER BY created_at DESC').to_a
end end
end end
@ -33,7 +50,7 @@ get '/' do
end end
get '/memos' do get '/memos' do
@memos = load_memos @memos = all_memos
erb :index erb :index
end end
@ -42,37 +59,29 @@ get '/memos/new' do
end end
post '/memos' do post '/memos' do
memos = load_memos id = create_memo(params[:title], params[:content])
id = SecureRandom.uuid redirect "/memos/#{id}"
memos[id] = { 'title' => params[:title], 'content' => params[:content] }
save_memos(memos)
redirect '/memos'
end end
get '/memos/:id' do get '/memos/:id' do
@memo = load_memos[params[:id]] @memo = find_memo(params[:id])
halt 404, erb(:not_found) unless @memo halt 404, erb(:not_found) unless @memo
erb :show erb :show
end end
get '/memos/:id/edit' do get '/memos/:id/edit' do
@memo = load_memos[params[:id]] @memo = find_memo(params[:id])
halt 404, erb(:not_found) unless @memo halt 404, erb(:not_found) unless @memo
erb :edit erb :edit
end end
patch '/memos/:id' do patch '/memos/:id' do
memos = load_memos update_memo(params[:id], params[:title], params[:content])
halt 404, erb(:not_found) unless memos[params[:id]]
memos[params[:id]] = { 'title' => params[:title], 'content' => params[:content] }
save_memos(memos)
redirect "/memos/#{params[:id]}" redirect "/memos/#{params[:id]}"
end end
delete '/memos/:id' do delete '/memos/:id' do
memos = load_memos delete_memo(params[:id])
halt 404, erb(:not_found) unless memos.delete(params[:id])
save_memos(memos)
redirect '/memos' redirect '/memos'
end end

9
memoapp/memos.sql Normal file
View file

@ -0,0 +1,9 @@
CREATE DATABASE memo_app;
DROP TABLE IF EXISTS memos;
\c memo_app
CREATE TABLE memos (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

View file

@ -1,3 +1,10 @@
:root {
--save-color: #007bff;
--edit-color: #28a745;
--delete-color: #dc3545;
--back-color: #6c757d;
}
body { body {
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
background-color: #f0f0f0; background-color: #f0f0f0;
@ -35,7 +42,7 @@ nav {
nav a { nav a {
margin: 0 10px; margin: 0 10px;
text-decoration: none; text-decoration: none;
color: #007bff; color: var(--save-color);
} }
nav a:hover { nav a:hover {
@ -59,44 +66,42 @@ form textarea {
border-radius: 4px; border-radius: 4px;
} }
form button { .button {
padding: 10px; padding: 10px;
color: #fff; color: #fff;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer; cursor: pointer;
text-decoration: none;
text-align: center;
display: inline-block;
width: 100%;
} }
form button.save { .button:hover {
background-color: #007bff; opacity: 0.9;
} }
form button.save:hover { .button.save {
background-color: #0056b3; background-color: var(--save-color);
} }
form button.delete { .button.delete {
background-color: #dc3545; background-color: var(--delete-color);
padding: 12px;
font-size: 90%;
} }
form button.delete:hover { .button.edit {
background-color: #c82333; background-color: var(--edit-color);
} }
form button.edit { .button.back {
background-color: #28a745; background-color: var(--back-color);
} margin-top: 10px;
display: block;
form button.edit:hover { margin: 10px auto 0;
background-color: #218838; box-sizing: border-box;
}
form button.back {
background-color: #6c757d;
}
form button.back:hover {
background-color: #5a6268;
} }
.memo-list { .memo-list {
@ -110,7 +115,7 @@ form button.back:hover {
.memo-list a { .memo-list a {
text-decoration: none; text-decoration: none;
color: #007bff; color: var(--save-color);
} }
.memo-list a:hover { .memo-list a:hover {
@ -123,27 +128,18 @@ form button.back:hover {
margin-bottom: 10px; margin-bottom: 10px;
} }
.memo-actions form { .memo-actions form,
display: inline; .memo-actions a {
flex: 1;
margin: 0 5px;
} }
.memo-actions button { .memo-actions form:first-child,
padding: 10px; .memo-actions a:first-child {
color: #fff; margin-left: 0;
border: none;
border-radius: 4px;
cursor: pointer;
} }
.memo-actions .edit { .memo-actions form:last-child,
background-color: #28a745; .memo-actions a:last-child {
margin-right: 0;
} }
.memo-actions .edit:hover {
background-color: #218838;
}
.memo-actions .back {
background-color: #6c757d;
}

View file

@ -1,8 +1,8 @@
<form action="/memos/<%= params[:id] %>" method="post"> <form action="/memos/<%= @memo['id'] %>" method="post">
<input type="hidden" name="_method" value="patch"> <input type="hidden" name="_method" value="patch">
<label for="title">タイトル</label> <label for="title">タイトル</label>
<input type="text" name="title" id="title" value="<%= h(@memo['title']) %>"> <input type="text" name="title" id="title" value="<%= h(@memo['title']) %>">
<label for="content">内容</label> <label for="content">内容</label>
<textarea name="content" id="content"><%= h(@memo['content']) %></textarea> <textarea name="content" id="content"><%= h(@memo['content']) %></textarea>
<button type="submit" class="edit">変更</button> <button type="submit" class="button save">保存</button>
</form> </form>

View file

@ -1,5 +1,5 @@
<ul class="memo-list"> <ul class="memo-list">
<% @memos.each do |id, memo| %> <% @memos.each do |memo| %>
<li><a href="/memos/<%= id %>"><%= h(memo["title"]) %></a></li> <li><a href="/memos/<%= memo['id'] %>"><%= h(memo['title']) %></a></li>
<% end %> <% end %>
</ul> </ul>

View file

@ -3,5 +3,5 @@
<input type="text" name="title" id="title"> <input type="text" name="title" id="title">
<label for="content">内容</label> <label for="content">内容</label>
<textarea name="content" id="content"></textarea> <textarea name="content" id="content"></textarea>
<button type="submit" class="save">保存</button> <button type="submit" class="button save">保存</button>
</form> </form>

View file

@ -1,3 +1,3 @@
<h1>404 Not Found</h1> <h2>404 Not Found</h2>
<p>お探しのページは見つかりませんでした。</p> <p>お探しのページは見つかりませんでした。</p>
<a href="/">トップページに戻る</a> <a href="/">トップページに戻る</a>

View file

@ -1,14 +1,10 @@
<h2><%= h(@memo['title']) %></h2> <h2><%= h(@memo['title']) %></h2>
<p><%= h(@memo['content']) %></p> <p><%= h(@memo['content']) %></p>
<div class="memo-actions"> <div class="memo-actions">
<form action="/memos/<%= params[:id] %>/edit" method="get"> <a href="/memos/<%= @memo['id'] %>/edit" class="button edit">編集</a>
<button type="submit" class="edit">変更</button> <form action="/memos/<%= @memo['id'] %>" method="post">
</form>
<form action="/memos/<%= params[:id] %>" method="post">
<input type="hidden" name="_method" value="delete"> <input type="hidden" name="_method" value="delete">
<button type="submit" class="delete">削除</button> <button type="submit" class="button delete">削除</button>
</form> </form>
</div> </div>
<form action="/memos" method="get"> <a href="/memos" class="button back">戻る</a>
<button type="submit" class="back">戻る</button>
</form>