Compare commits
2 Commits
095c3123ab
...
12ae7504a0
Author | SHA1 | Date |
---|---|---|
|
12ae7504a0 | 2 months ago |
|
af557ffc6e | 2 months ago |
@ -0,0 +1,15 @@
|
||||
-- +migrate up
|
||||
CREATE TABLE notifications (
|
||||
id SERIAL PRIMARY KEY,
|
||||
|
||||
account_id INTEGER NOT NULL references accounts(id),
|
||||
type TEXT NOT NULL,
|
||||
target INTEGER, -- represents whatever is affected; for SQL querying assistance. INTEGER because it's always an ID, might be tweaked in the future
|
||||
details TEXT NOT NULL, -- a JSON of various things relevant to displaying the notification
|
||||
|
||||
created_at TEXT NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'now')),
|
||||
read_at TEXT
|
||||
);
|
||||
|
||||
-- +migrate down
|
||||
DROP TABLE notifications;
|
After Width: | Height: | Size: 321 B |
@ -0,0 +1,48 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<title>Account Management</title>
|
||||
<style>
|
||||
body {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.header {
|
||||
height: 96px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: x-large;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5em;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<a href="/accounts/" title="<%=username%>" class="circle-button">
|
||||
<img src="https://gdicon.oat.zone/icon.png?type=<%=type_str%>&value=<%=icon_value%>&color1=<%=color1%>&color2=<%=color2%><%=glow ? "&glow=1" : ""%>" class="icon">
|
||||
</a>
|
||||
<span class="title">
|
||||
<a href="/"><img src="/favicon.png" width="64" height="auto" class="spinny favicon"></a>
|
||||
Notifications
|
||||
</span>
|
||||
<a href="/accounts/notifications/" title="Notifiations" class="circle-button notifications <%= unread_notifications ? "notifications-unread" : "" %>">
|
||||
<%= # todo: unhardcode public dir location
|
||||
File.read("public/assets/icons/bell.svg") %>
|
||||
</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,38 @@
|
||||
require "json"
|
||||
|
||||
include CrystalGauntlet
|
||||
|
||||
module CrystalGauntlet::Notifications
|
||||
extend self
|
||||
|
||||
alias NotificationDetails = Hash(String, String | Int64 | Bool | Float64 | Nil)
|
||||
|
||||
def clear_previous_notifications(account_id : Int32, type : String, target : Int32)
|
||||
DATABASE.exec("delete from notifications where account_id = ? and type = ? and target = ?", account_id, type, target)
|
||||
end
|
||||
|
||||
def send_notification(account_id : Int32, type : String, target : Int32?, details : NotificationDetails? = nil)
|
||||
DATABASE.exec("insert into notifications (id, account_id, type, target, details) values (?, ?, ?, ?, ?)", IDs.get_next_id("notifications"), account_id, type, target, details.try &.to_json || "{}")
|
||||
end
|
||||
|
||||
NOTIFICATION_STRINGS = {
|
||||
"authored_level_featured" => %(Your level <b>%{level_name}</b> has been featured!),
|
||||
"authored_level_rated" => %(Your level <b>%{level_name}</b> has been rated!)
|
||||
}
|
||||
|
||||
def format_notification(type : String, target : Int32, details : NotificationDetails? = nil, html_safe : Bool = false)
|
||||
details = details || {} of String => String | Int64 | Bool | Float64 | Nil
|
||||
string = NOTIFICATION_STRINGS[type]
|
||||
|
||||
#case type
|
||||
#when "authored_level_featured", "authored_level_rated"
|
||||
# details["action"] = (type == "authored_level_featured") ? "featured" : "rated"
|
||||
#end
|
||||
|
||||
if html_safe
|
||||
string % details.transform_values ->HTML.escape
|
||||
else
|
||||
string % details
|
||||
end
|
||||
end
|
||||
end
|
@ -0,0 +1,20 @@
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/accounts/notifications"] = ->(context : HTTP::Server::Context) {
|
||||
context.response.content_type = "text/html"
|
||||
|
||||
account_id = nil
|
||||
user_id = nil
|
||||
username = nil
|
||||
Templates.auth()
|
||||
|
||||
icon_type, color1, color2, cube, ship, ball, ufo, wave, robot, spider, glow = DATABASE.query_one("select icon_type, color1, color2, cube, ship, ball, ufo, wave, robot, spider, glow from users where id = ?", user_id, as: {Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32})
|
||||
|
||||
icon_value = [cube, ship, ball, ufo, wave, robot, spider][icon_type]
|
||||
type_str = ["cube", "ship", "ball", "ufo", "wave", "robot", "spider"][icon_type]
|
||||
|
||||
notification_count = DATABASE.scalar("select count(*) from notifications where account_id = ? and read_at is null", account_id).as(Int64)
|
||||
unread_notifications = notification_count > 0
|
||||
|
||||
ECR.embed("./public/template/notifications.ecr", context.response)
|
||||
}
|
Loading…
Reference in new issue