Compare commits
4 Commits
d2e051b09d
...
344edf369c
Author | SHA1 | Date |
---|---|---|
Jill | 344edf369c | |
Jill | dc6d5767bb | |
Jill | 543a959829 | |
Jill | 7e2dbd2917 |
99
flake.lock
99
flake.lock
|
@ -3,16 +3,16 @@
|
|||
"ameba-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1668496515,
|
||||
"narHash": "sha256-SZ2sBQeZgtPOYioH9eK5MveFtWVGPvgKMrqsCfjoRGM=",
|
||||
"lastModified": 1679041484,
|
||||
"narHash": "sha256-pc9mtVR/PBhM5l1PnDkm+y+McxbrfAmQzxmLi761VF4=",
|
||||
"owner": "crystal-ameba",
|
||||
"repo": "ameba",
|
||||
"rev": "cc687d028180203cb53390484871ffb8669b88c8",
|
||||
"rev": "7c74d196d6d9a496a81a0c7b79ef44f39faf41b8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "crystal-ameba",
|
||||
"ref": "v1.3.1",
|
||||
"ref": "v1.4.3",
|
||||
"repo": "ameba",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -37,13 +37,13 @@
|
|||
"crystal-aarch64-darwin": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"narHash": "sha256-d5znjl8QaQ/dMNa4GtKS/K14PwjBgt8Md6AwSCKcyl4=",
|
||||
"narHash": "sha256-NqYaZHM3kHAgYbO0RDJtA8eHqp4vVe4MBpisTOGrRVw=",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-darwin-universal.tar.gz"
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-darwin-universal.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.6.1/crystal-1.6.1-1-darwin-universal.tar.gz"
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-darwin-universal.tar.gz"
|
||||
}
|
||||
},
|
||||
"crystal-flake": {
|
||||
|
@ -51,7 +51,6 @@
|
|||
"ameba-src": "ameba-src",
|
||||
"bdwgc-src": "bdwgc-src",
|
||||
"crystal-aarch64-darwin": "crystal-aarch64-darwin",
|
||||
"crystal-i686-linux": "crystal-i686-linux",
|
||||
"crystal-src": "crystal-src",
|
||||
"crystal-x86_64-darwin": "crystal-x86_64-darwin",
|
||||
"crystal-x86_64-linux": "crystal-x86_64-linux",
|
||||
|
@ -60,11 +59,11 @@
|
|||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1672548746,
|
||||
"narHash": "sha256-CUpInKhwsX/jQvqeyGPC7mHQV6VWjaCOEwE+fh1uPwI=",
|
||||
"lastModified": 1683429373,
|
||||
"narHash": "sha256-Mx5lwMyk2T40wFqOoYcJLs4srwO2UrsepTZhlHNuTrI=",
|
||||
"owner": "manveru",
|
||||
"repo": "crystal-flake",
|
||||
"rev": "322876b36ae530b7648eb2692b9fa2c0f4e068b7",
|
||||
"rev": "e7a443c20e2be6e5dd870586705dd27c91aa9c5c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -73,31 +72,19 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"crystal-i686-linux": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"narHash": "sha256-Hfs70OcJYh+HlGQEftthr1qj10yRCAjzlsqnlUrLdjg=",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-linux-x86_64.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-linux-x86_64.tar.gz"
|
||||
}
|
||||
},
|
||||
"crystal-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1667495929,
|
||||
"narHash": "sha256-WgU6Y8ww1IYyB0vd5tXwmWBEL5RiPjHA7YzPd21jlsY=",
|
||||
"lastModified": 1681995387,
|
||||
"narHash": "sha256-t+1vM1m62UftCvfa90Dg6nqt6Zseh/GP/Gc1VfOa4+c=",
|
||||
"owner": "crystal-lang",
|
||||
"repo": "crystal",
|
||||
"rev": "879691b2e3268ab290a2a0951bd1d6032f0d90f3",
|
||||
"rev": "a59a3dbd738269d5aad6051c3834fc70f482f469",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "crystal-lang",
|
||||
"ref": "1.6.2",
|
||||
"ref": "1.8.1",
|
||||
"repo": "crystal",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -105,40 +92,40 @@
|
|||
"crystal-x86_64-darwin": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"narHash": "sha256-Hfs70OcJYh+HlGQEftthr1qj10yRCAjzlsqnlUrLdjg=",
|
||||
"narHash": "sha256-NqYaZHM3kHAgYbO0RDJtA8eHqp4vVe4MBpisTOGrRVw=",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-linux-x86_64.tar.gz"
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-darwin-universal.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-linux-x86_64.tar.gz"
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-darwin-universal.tar.gz"
|
||||
}
|
||||
},
|
||||
"crystal-x86_64-linux": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"narHash": "sha256-Hfs70OcJYh+HlGQEftthr1qj10yRCAjzlsqnlUrLdjg=",
|
||||
"narHash": "sha256-/Jk3uiglM/hzjygxmMUgVTvz+tuFFjBv8+uUIL05rXo=",
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-linux-x86_64.tar.gz"
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-linux-x86_64.tar.gz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.6.2/crystal-1.6.2-1-linux-x86_64.tar.gz"
|
||||
"url": "https://github.com/crystal-lang/crystal/releases/download/1.8.1/crystal-1.8.1-1-linux-x86_64.tar.gz"
|
||||
}
|
||||
},
|
||||
"crystalline-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1665222943,
|
||||
"narHash": "sha256-j/rQf+qn5ZjZhq3wG4WEKzG3YwRcnUEb4y36qXpiW7Q=",
|
||||
"lastModified": 1681549124,
|
||||
"narHash": "sha256-kx3rdGqIbrOaHY7V3uXLqIFEYzzsMKzNwZ6Neq8zM3c=",
|
||||
"owner": "elbywan",
|
||||
"repo": "crystalline",
|
||||
"rev": "41665a798659459ecd6e2cb1169d7faf1783e746",
|
||||
"rev": "4ac0ae282c5f4172230fea1e93df51c2b380f475",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "elbywan",
|
||||
"ref": "v0.7.0",
|
||||
"ref": "v0.9.0",
|
||||
"repo": "crystalline",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -162,12 +149,15 @@
|
|||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -178,16 +168,16 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1658406394,
|
||||
"narHash": "sha256-hgibXbbmxucpVJy9eOXKn7HxQtVkpeZ8euSnWl6c9Mk=",
|
||||
"lastModified": 1677543769,
|
||||
"narHash": "sha256-LwbqS8vGisXl2WHpK9r5+kodr0zoIT8F2YB0R4y1TsA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c93e5ab157b45adbb6165bd85a9d8f67e49ff31d",
|
||||
"rev": "b26d52c9feb6476580016e78935cbf96eb3e2115",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-22.05",
|
||||
"ref": "nixos-22.11",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -212,11 +202,11 @@
|
|||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1672501226,
|
||||
"narHash": "sha256-jzR7tUl9VZU77RpXKjG8V7VT9PVoaK8FmJnb+eNTm3Q=",
|
||||
"lastModified": 1682600000,
|
||||
"narHash": "sha256-ha4BehR1dh8EnXSoE1m/wyyYVvHI9txjW4w5/oxsW5Y=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "5353b10bd9a05533a94cf2e5629cf18b0af089dc",
|
||||
"rev": "50fc86b75d2744e1ab3837ef74b53f103a9b55a0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -232,6 +222,21 @@
|
|||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.7 KiB |
|
@ -128,7 +128,7 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/accounts/notifications/" title="Notifiations" class="circle-button notifications <%= unread_notifications ? "notifications-unread" : "" %>">
|
||||
<a href="/accounts/notifications" title="Notifiations" class="circle-button notifications <%= unread_notifications ? "notifications-unread" : "" %>">
|
||||
<%= File.read("public/assets/icons/bell.svg") %>
|
||||
</a>
|
||||
</div>
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
<li>The <a href="https://git.oat.zone/oat/crystal-gauntlet">Git repository</a></li>
|
||||
<li><a href="/accounts">Account stuff</a></li>
|
||||
<li><a href="/tools/song_upload">Song reuploading</a> and <a href="/tools/song_search">searching</a></li>
|
||||
<li><a href="/tools/levels">Levels</a> and <a href="/tools/reupload">level reuploading</a></li>
|
||||
<li><a href="/levels">Levels</a> and <a href="/tools/reupload">level reuploading</a></li>
|
||||
<%- if config_get("sessions.allow").as(Bool | Nil) -%>
|
||||
<li>The <a href="/tools/create_session">session creation page</a> (for accessing features in 1.9)</li>
|
||||
<%- end -%>
|
||||
|
|
|
@ -0,0 +1,316 @@
|
|||
<!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><%= name %> by <%= username %></title>
|
||||
|
||||
<style>
|
||||
body {
|
||||
max-width: 800px;
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--background-color-2);
|
||||
|
||||
border-radius: 1.5em;
|
||||
padding: 1em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
.card.bright {
|
||||
background-color: var(--accent-color);
|
||||
color: #000;
|
||||
}
|
||||
.card.bright ::selection {
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
}
|
||||
.card.bright a {
|
||||
color: #000;
|
||||
}
|
||||
.card-l {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
}
|
||||
.level-img {
|
||||
display: block;
|
||||
height: 3rem;
|
||||
}
|
||||
.stars {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.stars img {
|
||||
width: auto;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.card-r {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.card-top {
|
||||
display: flex;
|
||||
}
|
||||
.label-left {
|
||||
flex: 1 1 auto;
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
.label-right {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.label-right img {
|
||||
height: 1em;
|
||||
width: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.card-header {
|
||||
font-weight: bold;
|
||||
color: var(--text-color-dark);
|
||||
text-transform: uppercase;
|
||||
font-size: 0.8em;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.favicon {
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.description {
|
||||
flex-direction: column;
|
||||
gap: 0.5em;
|
||||
}
|
||||
|
||||
.leaderboard {
|
||||
min-width: 100%;
|
||||
overflow: auto;
|
||||
font-size: 0.9em;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
/* honestly quite horrendous */
|
||||
.leaderboard tr:nth-child(2) td:first-child {
|
||||
border-top-left-radius: 0.5em;
|
||||
}
|
||||
.leaderboard tr:nth-child(2) td:last-child {
|
||||
border-top-right-radius: 0.5em;
|
||||
}
|
||||
.leaderboard tr:last-child td:first-child {
|
||||
border-bottom-left-radius: 0.5em;
|
||||
}
|
||||
.leaderboard tr:last-child td:last-child {
|
||||
border-bottom-right-radius: 0.5em;
|
||||
}
|
||||
.leaderboard tr:not(.leaderboard-header):nth-child(even) td {
|
||||
background-color: var(--background-color-2);
|
||||
}
|
||||
.leaderboard th, .leaderboard td {
|
||||
padding: 0.2em 0.5em;
|
||||
text-align: left;
|
||||
}
|
||||
.leaderboard th {
|
||||
font-size: 0.8em;
|
||||
user-select: none;
|
||||
color: var(--text-color-dark);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.leaderboard td.rank {
|
||||
width: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.leaderboard td.percent {
|
||||
width: 75px;
|
||||
}
|
||||
.leaderboard td.coins {
|
||||
width: 4em;
|
||||
}
|
||||
.leaderboard td.coins .coin-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.leaderboard td.coins .coin-container img {
|
||||
width: 1em;
|
||||
height: auto;
|
||||
}
|
||||
.leaderboard td.icon {
|
||||
width: 2.5em;
|
||||
text-align: center;
|
||||
}
|
||||
.leaderboard td.name {
|
||||
width: 180px
|
||||
}
|
||||
.leaderboard td.name a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.leaderboard .player-icon {
|
||||
height: 1.2em;
|
||||
width: auto;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.comment {
|
||||
flex-direction: column;
|
||||
}
|
||||
.comment-author {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1ex;
|
||||
align-items: center;
|
||||
}
|
||||
.comment-author .player-icon {
|
||||
height: 1.5em;
|
||||
width: auto;
|
||||
}
|
||||
.comment-date {
|
||||
color: var(--text-color-dark);
|
||||
}
|
||||
.comment-author-label {
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a href="/"><img src="/favicon.png" width="64" height="auto" class="spinny favicon"></a><br>
|
||||
|
||||
<div class="card bright">
|
||||
<div class="card-l">
|
||||
<%=
|
||||
difficulty = (difficulty_set || difficulty_community).try { |n| LevelDifficulty.new(n) }
|
||||
demon_difficulty = demon_difficulty_int.try { |n| DemonDifficulty.new(n) }
|
||||
"<img src='#{Templates.get_difficulty_icon(difficulty, featured, epic, demon_difficulty)}' class='level-img'>"
|
||||
%>
|
||||
<%- if stars -%>
|
||||
<div class="stars">
|
||||
<%= stars %> <img src="/assets/icons/gd/star.png">
|
||||
</div>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<div class="card-r">
|
||||
<div class="card-top">
|
||||
<div class="label-left">
|
||||
<%= name %>
|
||||
</div>
|
||||
<div class="label-right">
|
||||
<%= downloads %> <img src="/assets/icons/gd/download.png"> <%= likes %> <img src="/assets/icons/gd/like.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-bottom">
|
||||
by <a href="/user/<%= username %>"><%= username %></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card description">
|
||||
<div class="card-header">DESCRIPTION</div>
|
||||
<div>
|
||||
<%= description != "" ? HTML.escape(description) : "<i>No description provided</i>" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card description">
|
||||
<div class="card-header">SONG</div>
|
||||
<div>
|
||||
<%- if song_name -%>
|
||||
<!--
|
||||
song_name, song_author, song_url, song_author_url
|
||||
-->
|
||||
<div><a href="<%= song_url %>" target="_blank" rel="noopener"><%= song_name %></a></div>
|
||||
<%- if song_author && song_author != "" -%>
|
||||
<div>by <a href="<%= song_author_url %>" target="_blank" rel="noopener"><%= song_author %></a></div>
|
||||
<%- else -%>
|
||||
<div><i>unknown artist</i></div>
|
||||
<%- end -%>
|
||||
<%- else -%>
|
||||
idk
|
||||
<%- end -%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="leaderboard">
|
||||
<tr class="leaderboard-header">
|
||||
<th class="rank">RANK</td>
|
||||
<th class="percent">PERCENT</td>
|
||||
<th class="coins">COINS</td>
|
||||
<th class="icon"></td>
|
||||
<th class="name">PLAYER</td>
|
||||
<th class="date">TIME</td>
|
||||
</tr>
|
||||
|
||||
<%
|
||||
rank = 0
|
||||
scores.each do |percent, coins, username, icon_type, color1, color2, cube, ship, ball, ufo, wave, robot, spider, special, set_at|
|
||||
rank = rank + 1
|
||||
|
||||
icon_value = [cube, ship, ball, ufo, wave, robot, spider][icon_type]
|
||||
type_str = ["cube", "ship", "ball", "ufo", "wave", "robot", "spider"][icon_type]
|
||||
|
||||
set_at_date = Time.parse(set_at, Format::TIME_FORMAT, Time::Location::UTC)
|
||||
%>
|
||||
<tr>
|
||||
<td class="rank">#<%= rank %></td>
|
||||
<td class="percent"><%= percent %>%</td>
|
||||
<td class="coins">
|
||||
<div class="coin-container">
|
||||
<%- coins.times do |i| -%>
|
||||
<img src="/assets/icons/gd/<%= rated_coins ? "silvercoin" : "browncoin" %>.png">
|
||||
<%- end -%>
|
||||
</div>
|
||||
</td>
|
||||
<td class="icon">
|
||||
<img src="https://gdicon.oat.zone/icon.png?type=<%=type_str%>&value=<%=icon_value%>&color1=<%=color1%>&color2=<%=color2%><%=special ? "&glow=1" : ""%>" class="player-icon">
|
||||
</td>
|
||||
<td class="name">
|
||||
<a href="/user/<%= username %>"><%= username %></a>
|
||||
</td>
|
||||
<td class="date">
|
||||
<time datetime="<%= Time::Format::RFC_3339.format(set_at_date) %>" title="<%= Time::Format::RFC_2822.format(set_at_date) %>"><%= Format.fmt_timespan(Time.utc - set_at_date) %></time>
|
||||
</td>
|
||||
</tr>
|
||||
<%- end -%>
|
||||
<%- if scores.size == 0 -%>
|
||||
<tr>
|
||||
<td colspan="6" style="text-align: center">
|
||||
<i>No scores</i>
|
||||
</td>
|
||||
</tr>
|
||||
<%- end -%>
|
||||
</table>
|
||||
|
||||
<h3>Comments</h3>
|
||||
|
||||
<%- comments.each do |comment, created_at, username, icon_type, color1, color2, cube, ship, ball, ufo, wave, robot, spider, special|
|
||||
icon_value = [cube, ship, ball, ufo, wave, robot, spider][icon_type]
|
||||
type_str = ["cube", "ship", "ball", "ufo", "wave", "robot", "spider"][icon_type]
|
||||
|
||||
created_at_date = Time.parse(created_at, Format::TIME_FORMAT, Time::Location::UTC)
|
||||
%>
|
||||
<div class="card comment">
|
||||
<div class="comment-author">
|
||||
<img src="https://gdicon.oat.zone/icon.png?type=<%=type_str%>&value=<%=icon_value%>&color1=<%=color1%>&color2=<%=color2%><%=special ? "&glow=1" : ""%>" class="player-icon">
|
||||
<a class="comment-author-label" href="/user/<%= username %>"><%= username %></a>
|
||||
<time class="comment-date" datetime="<%= Time::Format::RFC_3339.format(created_at_date) %>" title="<%= Time::Format::RFC_2822.format(created_at_date) %>"><%= Format.fmt_timespan(Time.utc - created_at_date) %> ago</time>
|
||||
</div>
|
||||
<div>
|
||||
<%= HTML.escape(comment) %>
|
||||
</div>
|
||||
</div>
|
||||
<%- end -%>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -55,7 +55,10 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1em
|
||||
gap: 1em;
|
||||
}
|
||||
.level a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
@ -69,8 +72,8 @@
|
|||
"<img src='#{Templates.get_difficulty_icon(difficulty, featured, epic, demon_difficulty)}' class='level-img'>"
|
||||
%>
|
||||
<div class="level-right">
|
||||
<span class="line"><span class="name"><%= name %></span><span class="id dim">#<%= id %></span></span>
|
||||
<small>by <%= username %></small><br>
|
||||
<span class="line"><span class="name"><a href="/levels/<%= id %>"><%= name %></a></span><span class="id dim">#<%= id %></span></span>
|
||||
<small>by <a href="/user/<%= username %>"><%= username %></a></small><br>
|
||||
</div>
|
||||
</div>
|
||||
<%- end -%>
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
version: 2.0
|
||||
shards:
|
||||
athena-routing:
|
||||
git: https://github.com/athena-framework/routing.git
|
||||
version: 0.1.6
|
||||
|
||||
db:
|
||||
git: https://github.com/crystal-lang/crystal-db.git
|
||||
version: 0.11.0
|
||||
|
|
|
@ -23,6 +23,9 @@ dependencies:
|
|||
branch: master
|
||||
http-session:
|
||||
github: straight-shoota/http-session
|
||||
athena-routing:
|
||||
github: athena-framework/routing
|
||||
version: ~> 0.1.0
|
||||
|
||||
crystal: 1.6.2
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ require "colorize"
|
|||
require "option_parser"
|
||||
require "migrate"
|
||||
|
||||
require "./server"
|
||||
require "./enums"
|
||||
require "./lib/hash"
|
||||
require "./lib/format"
|
||||
|
@ -64,7 +65,10 @@ module CrystalGauntlet
|
|||
DATA_FOLDER = Path.new("data")
|
||||
|
||||
@@endpoints = Hash(String, (HTTP::Server::Context -> String)).new
|
||||
@@template_endpoints = Hash(String, (HTTP::Server::Context -> Nil)).new
|
||||
@@template_endpoints = Hash(
|
||||
NamedTuple(name: String, path: String, methods: Enumerable(String)),
|
||||
Proc(HTTP::Server::Context, Hash(String, String?), Nil)
|
||||
).new
|
||||
|
||||
@@up_at = nil
|
||||
|
||||
|
@ -132,90 +136,6 @@ module CrystalGauntlet
|
|||
end
|
||||
end
|
||||
|
||||
class GDHandler
|
||||
include HTTP::Handler
|
||||
|
||||
def call(context : HTTP::Server::Context)
|
||||
# expunge trailing slashes
|
||||
path = context.request.path.chomp("/")
|
||||
|
||||
path = path.sub(config_get("general.append_path").as(String | Nil) || "", "")
|
||||
|
||||
body = context.request.body
|
||||
|
||||
if CrystalGauntlet.endpoints.has_key?(path) && context.request.method == "POST" && body
|
||||
func = CrystalGauntlet.endpoints[path]
|
||||
begin
|
||||
value = func.call(context)
|
||||
rescue err
|
||||
LOG.error { "error while handling #{path.colorize(:white)}:" }
|
||||
LOG.error { err.to_s }
|
||||
is_relevant = true
|
||||
err.backtrace.each do |str|
|
||||
# this is a hack. Oh well
|
||||
if str.starts_with?("src/crystal-gauntlet.cr") || (!is_relevant)
|
||||
is_relevant = false
|
||||
else
|
||||
LOG.error {" #{str}"}
|
||||
end
|
||||
end
|
||||
context.response.content_type = "text/plain"
|
||||
context.response.respond_with_status(500, "-1")
|
||||
else
|
||||
max_size = 2048
|
||||
|
||||
value_displayed = value
|
||||
if value.size > max_size
|
||||
value_displayed = value[0..max_size] + ("…".colorize(:dark_gray).to_s)
|
||||
end
|
||||
LOG.debug { "-> ".colorize(:green).to_s + value_displayed }
|
||||
|
||||
context.response.content_type = "text/plain"
|
||||
# to let endpoints manually write to IO
|
||||
if value != ""
|
||||
context.response.print value
|
||||
end
|
||||
end
|
||||
else
|
||||
call_next(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TemplateHandler
|
||||
include HTTP::Handler
|
||||
|
||||
def call(context : HTTP::Server::Context)
|
||||
# expunge trailing slashes
|
||||
path = context.request.path.chomp("/")
|
||||
|
||||
body = context.request.body
|
||||
|
||||
if CrystalGauntlet.template_endpoints.has_key?(path)
|
||||
func = CrystalGauntlet.template_endpoints[path]
|
||||
begin
|
||||
func.call(context)
|
||||
rescue err
|
||||
LOG.error { "error while handling #{path.colorize(:white)}:" }
|
||||
LOG.error { err.to_s }
|
||||
is_relevant = true
|
||||
err.backtrace.each do |str|
|
||||
# this is a hack. Oh well
|
||||
if str.starts_with?("src/crystal-gauntlet.cr") || (!is_relevant)
|
||||
is_relevant = false
|
||||
else
|
||||
LOG.error {" #{str}"}
|
||||
end
|
||||
end
|
||||
context.response.content_type = "text/html"
|
||||
context.response.respond_with_status(500, "Internal server error occurred, sorry about that")
|
||||
end
|
||||
else
|
||||
call_next(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.run()
|
||||
Log.setup_from_env(backend: Log::IOBackend.new(formatter: CrystalGauntletFormat))
|
||||
|
||||
|
@ -294,31 +214,7 @@ module CrystalGauntlet
|
|||
Dir.mkdir_p(DATA_FOLDER / v)
|
||||
}
|
||||
|
||||
server = HTTP::Server.new([
|
||||
HTTP::LogHandler.new,
|
||||
HTTP::StaticFileHandler.new("public/", fallthrough: true, directory_listing: false),
|
||||
HTTP::StaticFileHandler.new((DATA_FOLDER / "songs").to_s, fallthrough: true, directory_listing: false),
|
||||
CrystalGauntlet::GDHandler.new,
|
||||
CrystalGauntlet::TemplateHandler.new
|
||||
])
|
||||
|
||||
listen_on = URI.parse(ENV["LISTEN_ON"]? || "http://localhost:8080").normalize
|
||||
|
||||
case listen_on.scheme
|
||||
when "http"
|
||||
server.bind_tcp(listen_on.hostname.not_nil!, listen_on.port.not_nil!)
|
||||
when "unix"
|
||||
server.bind_unix(listen_on.to_s.sub("unix://",""))
|
||||
end
|
||||
|
||||
check_server_length(false)
|
||||
|
||||
Reupload.init()
|
||||
Ranks.init()
|
||||
|
||||
@@up_at = Time.utc
|
||||
LOG.notice { "Listening on #{listen_on.to_s.colorize(:white)}" }
|
||||
server.listen
|
||||
Server.run()
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -64,18 +64,16 @@ CrystalGauntlet.endpoints["/uploadGJLevel21.php"] = ->(context : HTTP::Server::C
|
|||
LOG.debug { "allowed objects: #{allowed_objects.inspect}" }
|
||||
|
||||
if forbidden_obj = level_objects.find do |obj|
|
||||
if !obj.has_key?("1")
|
||||
false
|
||||
end
|
||||
|
||||
id = obj["1"].to_i
|
||||
if allowed_objects.size > 0
|
||||
if !allowed_objects.includes?(id)
|
||||
true
|
||||
end
|
||||
else
|
||||
if forbidden_objects.includes?(id)
|
||||
true
|
||||
if obj.has_key?("1")
|
||||
id = obj["1"].to_i
|
||||
if allowed_objects.size > 0
|
||||
if !allowed_objects.includes?(id)
|
||||
true
|
||||
end
|
||||
else
|
||||
if forbidden_objects.includes?(id)
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -83,12 +81,12 @@ CrystalGauntlet.endpoints["/uploadGJLevel21.php"] = ->(context : HTTP::Server::C
|
|||
return "-1"
|
||||
end
|
||||
|
||||
if exploit_obj = level_objects.find do |obj|
|
||||
if exploit_obj = level_objects.find { |obj|
|
||||
# target color ID
|
||||
(obj.has_key?("23") && obj["23"].to_i < 0 || obj["23"].to_i > 1100) ||
|
||||
(obj.has_key?("23") && (obj["23"].to_i < 0 || obj["23"].to_i > 1100)) ||
|
||||
# target group ID
|
||||
(obj.has_key?("51") && obj["51"].to_i < 0 || obj["51"].to_i > 1100)
|
||||
end
|
||||
(obj.has_key?("51") && (obj["51"].to_i < 0 || obj["51"].to_i > 1100))
|
||||
}
|
||||
LOG.info { "preventing upload of level attempting to exploit invalid color/group IDs" }
|
||||
return "-1"
|
||||
end
|
||||
|
@ -147,7 +145,7 @@ CrystalGauntlet.endpoints["/uploadGJLevel21.php"] = ->(context : HTTP::Server::C
|
|||
# create new level
|
||||
next_id = IDs.get_next_id("levels")
|
||||
|
||||
DATABASE.exec("insert into levels (id, name, user_id, description, original, game_version, binary_version, password, requested_stars, unlisted, version, extra_data, level_info, editor_time, editor_time_copies, song_id, length, objects, coins, has_ldm, two_player) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", next_id, Clean.clean_special(params["levelName"])[..20-1], user_id, description[..140-1], params["original"].to_i, params["gameVersion"].to_i, (params["binaryVersion"]? || "0").to_i, params["password"] == "0" ? nil : params["password"].to_i, requested_stars, (params["unlisted"]? || "0").to_i == 1, params["levelVersion"].to_i, Clean.clean_special(extraString), Clean.clean_b64(params["levelInfo"]? || ""), (params["wt"]? || "0").to_i, (params["wt2"]? || "0").to_i, song_id.to_i, level_length, objects, coins, (params["ldm"]? || "0").to_i == 1, two_player)
|
||||
DATABASE.exec("insert into levels (id, name, user_id, description, original, game_version, binary_version, password, requested_stars, unlisted, version, extra_data, level_info, editor_time, editor_time_copies, song_id, length, objects, coins, has_ldm, two_player) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", next_id, Clean.clean_basic(params["levelName"])[..20-1], user_id, description[..140-1], params["original"].to_i, params["gameVersion"].to_i, (params["binaryVersion"]? || "0").to_i, params["password"] == "0" ? nil : params["password"].to_i, requested_stars, (params["unlisted"]? || "0").to_i == 1, params["levelVersion"].to_i, Clean.clean_special(extraString), Clean.clean_b64(params["levelInfo"]? || ""), (params["wt"]? || "0").to_i, (params["wt2"]? || "0").to_i, song_id.to_i, level_length, objects, coins, (params["ldm"]? || "0").to_i == 1, two_player)
|
||||
|
||||
File.write(DATA_FOLDER / "levels" / "#{next_id.to_s}.lvl", Base64.decode(params["levelString"]))
|
||||
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
require "athena-routing"
|
||||
|
||||
module CrystalGauntlet::Server
|
||||
class GDHandler
|
||||
include HTTP::Handler
|
||||
|
||||
def call(context : HTTP::Server::Context)
|
||||
# expunge trailing slashes
|
||||
path = context.request.path.chomp("/")
|
||||
|
||||
path = path.sub(config_get("general.append_path").as(String | Nil) || "", "")
|
||||
|
||||
body = context.request.body
|
||||
|
||||
if CrystalGauntlet.endpoints.has_key?(path) && context.request.method == "POST" && body
|
||||
func = CrystalGauntlet.endpoints[path]
|
||||
begin
|
||||
value = func.call(context)
|
||||
rescue err
|
||||
LOG.error { "error while handling #{path.colorize(:white)}:" }
|
||||
LOG.error { err.to_s }
|
||||
is_relevant = true
|
||||
err.backtrace.each do |str|
|
||||
# this is a hack. Oh well
|
||||
if str.starts_with?("src/crystal-gauntlet.cr") || (!is_relevant)
|
||||
is_relevant = false
|
||||
else
|
||||
LOG.error {" #{str}"}
|
||||
end
|
||||
end
|
||||
context.response.content_type = "text/plain"
|
||||
context.response.respond_with_status(500, "-1")
|
||||
else
|
||||
max_size = 2048
|
||||
|
||||
value_displayed = value
|
||||
if value.size > max_size
|
||||
value_displayed = value[0..max_size] + ("…".colorize(:dark_gray).to_s)
|
||||
end
|
||||
LOG.debug { "-> ".colorize(:green).to_s + value_displayed }
|
||||
|
||||
context.response.content_type = "text/plain"
|
||||
# to let endpoints manually write to IO
|
||||
if value != ""
|
||||
context.response.print value
|
||||
end
|
||||
end
|
||||
else
|
||||
call_next(context)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.run()
|
||||
template_handler = ART::RoutingHandler.new
|
||||
|
||||
CrystalGauntlet.template_endpoints.each do |key, handler|
|
||||
template_handler.add(
|
||||
key[:name],
|
||||
ART::Route.new(
|
||||
key[:path],
|
||||
methods: key[:methods]
|
||||
)
|
||||
) { |ctx, params| handler.call(ctx, params) }
|
||||
end
|
||||
|
||||
server = HTTP::Server.new([
|
||||
HTTP::LogHandler.new,
|
||||
HTTP::StaticFileHandler.new("public/", fallthrough: true, directory_listing: false),
|
||||
HTTP::StaticFileHandler.new((DATA_FOLDER / "songs").to_s, fallthrough: true, directory_listing: false),
|
||||
GDHandler.new,
|
||||
template_handler.compile
|
||||
])
|
||||
|
||||
listen_on = URI.parse(ENV["LISTEN_ON"]? || "http://localhost:8080").normalize
|
||||
|
||||
case listen_on.scheme
|
||||
when "http"
|
||||
server.bind_tcp(listen_on.hostname.not_nil!, listen_on.port.not_nil!)
|
||||
when "unix"
|
||||
server.bind_unix(listen_on.to_s.sub("unix://",""))
|
||||
end
|
||||
|
||||
check_server_length(false)
|
||||
|
||||
Reupload.init()
|
||||
Ranks.init()
|
||||
|
||||
@@up_at = Time.utc
|
||||
LOG.notice { "Listening on #{listen_on.to_s.colorize(:white)}" }
|
||||
server.listen
|
||||
end
|
||||
end
|
|
@ -1,11 +1,19 @@
|
|||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/#{config_get("general.append_path").as(String | Nil) || ""}accounts/accountManagement.php"] = ->(context : HTTP::Server::Context) {
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "account_management_redirect",
|
||||
path: "/#{config_get("general.append_path").as(String | Nil) || ""}accounts/accountManagement.php",
|
||||
methods: ["get"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.headers.add("Location", "/accounts/")
|
||||
context.response.status = HTTP::Status::MOVED_PERMANENTLY
|
||||
}
|
||||
|
||||
CrystalGauntlet.template_endpoints["/accounts"] = ->(context : HTTP::Server::Context) {
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "account_management",
|
||||
path: "/accounts",
|
||||
methods: ["get"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.content_type = "text/html"
|
||||
|
||||
account_id = nil
|
||||
|
|
|
@ -2,7 +2,12 @@ require "uri"
|
|||
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/accounts/settings"] = ->(context : HTTP::Server::Context) {
|
||||
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "account_settings",
|
||||
path: "/accounts/settings",
|
||||
methods: ["get", "post"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.content_type = "text/html"
|
||||
|
||||
account_id = nil
|
||||
|
|
|
@ -3,7 +3,11 @@ require "compress/gzip"
|
|||
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/tools/create_session"] = ->(context : HTTP::Server::Context) {
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "create_session",
|
||||
path: "/tools/create_session",
|
||||
methods: ["get", "post"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
disabled = !config_get("sessions.allow").as(Bool | Nil)
|
||||
result = nil
|
||||
body = context.request.body
|
||||
|
|
|
@ -2,12 +2,21 @@ require "ecr"
|
|||
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/tools"] = ->(context : HTTP::Server::Context) {
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "tools_redirect",
|
||||
path: "/tools",
|
||||
methods: ["get"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.headers.add("Location", "/")
|
||||
context.response.status = HTTP::Status::TEMPORARY_REDIRECT
|
||||
}
|
||||
|
||||
CrystalGauntlet.template_endpoints[""] = ->(context : HTTP::Server::Context) {
|
||||
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "index",
|
||||
path: "/",
|
||||
methods: ["get"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.content_type = "text/html"
|
||||
ECR.embed("./public/template/index.ecr", context.response)
|
||||
}
|
||||
|
|
|
@ -4,10 +4,37 @@ include CrystalGauntlet
|
|||
|
||||
levels_per_page = 10
|
||||
|
||||
CrystalGauntlet.template_endpoints["/tools/levels"] = ->(context : HTTP::Server::Context) {
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "list_levels",
|
||||
path: "/levels",
|
||||
methods: ["get"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.content_type = "text/html"
|
||||
page = (context.request.query_params["page"]? || "0").to_i? || 0
|
||||
total_levels = DATABASE.scalar("select count(*) from levels").as(Int64)
|
||||
levels = DATABASE.query_all("select levels.id, name, users.username, levels.community_difficulty, levels.difficulty, levels.demon_difficulty, levels.featured, levels.epic from levels left join users on levels.user_id = users.id order by levels.id desc limit #{levels_per_page} offset #{page * levels_per_page}", as: {Int32, String, String, Int32?, Int32?, Int32?, Bool, Bool})
|
||||
ECR.embed("./public/template/levels.ecr", context.response)
|
||||
}
|
||||
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "level_page",
|
||||
path: "/levels/{id<\\d+>}",
|
||||
methods: ["get"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.content_type = "text/html"
|
||||
id = params["id"].as(String).to_i
|
||||
|
||||
begin
|
||||
name, username, difficulty_community, difficulty_set, demon_difficulty_int, featured, epic, rated_coins, downloads, likes, stars, description, song_id, song_name, song_author, song_url, song_author_url = DATABASE.query_one("select levels.name, users.username, community_difficulty, difficulty, demon_difficulty, featured, epic, rated_coins, downloads, likes, levels.stars, description, song_id, song_data.name, song_authors.name, songs.url, song_authors.source from levels left join users on levels.user_id = users.id left join songs on songs.id = levels.song_id left join song_data on song_data.id = levels.song_id left join song_authors on song_data.author_id = song_authors.id where levels.id = ?", id, as: {String, String, Int32?, Int32?, Int32?, Bool, Bool, Bool, Int32, Int32, Int32?, String, Int32, String?, String?, String?, String?})
|
||||
rescue err
|
||||
LOG.error {"whar.... #{err}"}
|
||||
context.response.status = HTTP::Status::NOT_FOUND
|
||||
return
|
||||
end
|
||||
|
||||
scores = DATABASE.query_all("select distinct percent, level_scores.coins, users.username, users.icon_type, users.color1, users.color2, users.cube, users.ship, users.ball, users.ufo, users.wave, users.robot, users.spider, users.special, set_at from level_scores join users on level_scores.account_id = users.account_id where level_id = ? order by percent desc, level_scores.coins desc, set_at limit 25", id, as: {Int32, Int32, String, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, String})
|
||||
|
||||
comments = DATABASE.query_all("select comment, comments.created_at, users.username, users.icon_type, users.color1, users.color2, users.cube, users.ship, users.ball, users.ufo, users.wave, users.robot, users.spider, users.special from comments left join users on comments.user_id = users.id where level_id = ? order by comments.created_at asc limit 20", id, as: {String, String, String, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32, Int32})
|
||||
|
||||
ECR.embed("./public/template/level.ecr", context.response)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,11 @@ require "http-session"
|
|||
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/login"] = ->(context : HTTP::Server::Context) {
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "login",
|
||||
path: "/login",
|
||||
methods: ["get", "post"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
if session = CrystalGauntlet.sessions.get(context)
|
||||
logged_in = true
|
||||
account_id = session.account_id
|
||||
|
|
|
@ -3,12 +3,11 @@ require "http-session"
|
|||
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/accounts/logout"] = ->(context : HTTP::Server::Context) {
|
||||
if context.request.method != "POST"
|
||||
context.response.respond_with_status 405
|
||||
return
|
||||
end
|
||||
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "logout",
|
||||
path: "/accounts/logout",
|
||||
methods: ["post"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
CrystalGauntlet.sessions.delete(context)
|
||||
|
||||
context.response.headers.add("Location", "/")
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/accounts/notifications"] = ->(context : HTTP::Server::Context) {
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "account_notifications",
|
||||
path: "/accounts/notifications",
|
||||
methods: ["get"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.content_type = "text/html"
|
||||
|
||||
account_id = nil
|
||||
|
|
|
@ -3,7 +3,11 @@ require "xml"
|
|||
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/tools/reupload"] = ->(context : HTTP::Server::Context) {
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "reupload",
|
||||
path: "/tools/reupload",
|
||||
methods: ["get", "post"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.content_type = "text/html"
|
||||
|
||||
disabled = !(config_get("reuploads.allowed").as?(Bool))
|
||||
|
|
|
@ -2,7 +2,11 @@ require "ecr"
|
|||
|
||||
include CrystalGauntlet
|
||||
|
||||
CrystalGauntlet.template_endpoints["/tools/song_search"] = ->(context : HTTP::Server::Context) {
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "song_search",
|
||||
path: "/tools/song_search",
|
||||
methods: ["get", "post"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.content_type = "text/html"
|
||||
|
||||
error = nil
|
||||
|
|
|
@ -17,7 +17,11 @@ def get_next_song_id() : Int32
|
|||
end
|
||||
end
|
||||
|
||||
CrystalGauntlet.template_endpoints["/tools/song_upload"] = ->(context : HTTP::Server::Context) {
|
||||
CrystalGauntlet.template_endpoints[{
|
||||
name: "song_upload",
|
||||
path: "/tools/song_upload",
|
||||
methods: ["get", "post"]
|
||||
}] = ->(context : HTTP::Server::Context, params : Hash(String, String?)) {
|
||||
context.response.content_type = "text/html"
|
||||
|
||||
disabled = !(config_get("songs.allow_custom_songs").as?(Bool))
|
||||
|
|
Loading…
Reference in New Issue