mirror of
https://github.com/JuLi0n21/pwa-player.git
synced 2026-04-19 23:40:05 +00:00
remove outdated backends
This commit is contained in:
7
backend/.gitignore
vendored
7
backend/.gitignore
vendored
@@ -1,7 +0,0 @@
|
||||
.vs/
|
||||
bin/
|
||||
obj/
|
||||
|
||||
*.db
|
||||
*.env
|
||||
cookies.json
|
||||
@@ -1,85 +0,0 @@
|
||||
using OsuParsers.Enums.Replays;
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace shitweb
|
||||
{
|
||||
public class ApiClient
|
||||
{
|
||||
private const string CookiesFilePath = "cookies.json";
|
||||
private HttpClient _client;
|
||||
private HttpClientHandler _handler;
|
||||
|
||||
public ApiClient()
|
||||
{
|
||||
_handler = new HttpClientHandler();
|
||||
_client = new HttpClient(_handler);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
LoadCookies();
|
||||
|
||||
}
|
||||
|
||||
public void SaveCookies(String cookie)
|
||||
{
|
||||
var cookies = _handler.CookieContainer.GetCookies(new Uri("https://proxy.illegalesachen.download"));
|
||||
var Cookie = new CookieInfo();
|
||||
|
||||
Cookie.Name = "session_cookie";
|
||||
Cookie.Value = cookie;
|
||||
var json = JsonSerializer.Serialize(Cookie, new JsonSerializerOptions { WriteIndented = true });
|
||||
File.WriteAllText(CookiesFilePath, json);
|
||||
LoadCookies();
|
||||
}
|
||||
|
||||
public Boolean LoadCookies()
|
||||
{
|
||||
if (File.Exists(CookiesFilePath))
|
||||
{
|
||||
var json = File.ReadAllText(CookiesFilePath);
|
||||
var cookieInfo = JsonSerializer.Deserialize<CookieInfo>(json);
|
||||
|
||||
var cookie = new Cookie(cookieInfo.Name, cookieInfo.Value);
|
||||
_handler.CookieContainer.Add(new Uri("https://proxy.illegalesachen.download"), cookie);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public async Task UpdateSettingsAsync(string endpoint)
|
||||
{
|
||||
var requestContent = new JsonContent(new { endpoint = endpoint });
|
||||
var response = await _client.PostAsync("https://proxy.illegalesachen.download/settings", requestContent);
|
||||
try
|
||||
{
|
||||
response.EnsureSuccessStatusCode();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
System.Console.WriteLine(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class CookieInfo
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public class JsonContent : StringContent
|
||||
{
|
||||
public JsonContent(object obj)
|
||||
: base(JsonSerializer.Serialize(obj), System.Text.Encoding.UTF8, "application/json")
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using shitweb;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Drawing.Text;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddMemoryCache();
|
||||
|
||||
builder.Services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy("AllowAll",
|
||||
policy =>
|
||||
{
|
||||
policy.AllowAnyOrigin()
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyMethod();
|
||||
});
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
app.UseCors("AllowAll");
|
||||
|
||||
var apiClient = new ApiClient();
|
||||
|
||||
|
||||
app.MapGet("/ping", () => "pong");
|
||||
|
||||
|
||||
app.MapGet("/login", () => {
|
||||
return Results.Redirect("https://proxy.illegalesachen.download/login");
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/songs/{hash}", (string hash) =>
|
||||
{
|
||||
return Results.Ok(new { hash });
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/songs/recent", (HttpContext httpContext, int? limit, int? offset) =>
|
||||
{
|
||||
var limitValue = limit ?? 100; // default to 10 if not provided
|
||||
var offsetValue = offset ?? 0; // default to 0 if not provided
|
||||
|
||||
return Results.Json(SqliteDB.GetByRecent(limitValue, offsetValue));
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/songs/favorite", (int? limit, int? offset) =>
|
||||
{
|
||||
var limitValue = limit ?? 100; // default to 10 if not provided
|
||||
var offsetValue = offset ?? 0; // default to 0 if not provided
|
||||
|
||||
return Results.Ok(new { Limit = limitValue, Offset = offsetValue, Message = "List of favorite songs" });
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/songs/{hash}", (string hash) =>
|
||||
{
|
||||
return Results.Ok($"Details for song with hash {hash}");
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/collections/", async (int? limit, int? offset, [FromServices] IMemoryCache cache) =>
|
||||
{
|
||||
|
||||
var limitValue = limit ?? 100; // default to 10 if not provided
|
||||
var offsetValue = offset ?? 0;
|
||||
|
||||
string cacheKey = $"collections_{offsetValue}_{limitValue}";
|
||||
|
||||
if (!cache.TryGetValue(cacheKey, out var collections))
|
||||
{
|
||||
|
||||
collections = Osudb.Instance.GetCollections(limit: limitValue, offset: offsetValue);
|
||||
|
||||
var cacheEntryOptions = new MemoryCacheEntryOptions()
|
||||
.SetSlidingExpiration(TimeSpan.FromDays(1))
|
||||
.SetAbsoluteExpiration(TimeSpan.FromDays(3));
|
||||
|
||||
cache.Set(cacheKey, collections, cacheEntryOptions);
|
||||
}
|
||||
|
||||
return Results.Json(collections);
|
||||
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/collection/{index}", (int index) =>
|
||||
{
|
||||
return Results.Json(Osudb.Instance.GetCollection(index));
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/audio/{*fileName}", async (string fileName, HttpContext context) =>
|
||||
{
|
||||
var decodedFileName = Uri.UnescapeDataString(fileName);
|
||||
var filePath = Path.Combine(Osudb.osufolder, "Songs", decodedFileName);
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
return Results.NotFound();
|
||||
}
|
||||
|
||||
var fileExtension = Path.GetExtension(filePath).ToLowerInvariant();
|
||||
var contentType = fileExtension switch
|
||||
{
|
||||
".mp3" => "audio/mpeg",
|
||||
".wav" => "audio/wav",
|
||||
".ogg" => "audio/ogg",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
|
||||
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
|
||||
return Results.Stream(fileStream, contentType, enableRangeProcessing: true);
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/search/active", async (string? q) =>
|
||||
{
|
||||
return Results.Ok(SqliteDB.activeSearch(q));
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/search/artist", async (string? q, int? limit, int? offset) =>
|
||||
{
|
||||
var limitv = limit ?? 100;
|
||||
var offsetv = offset ?? 0;
|
||||
|
||||
return Results.Ok(SqliteDB.GetArtistSearch(q, limitv, offsetv));
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/search/songs", async (string? q, int? limit, int? offset) =>
|
||||
{
|
||||
var limitv = limit ?? 100;
|
||||
var offsetv = offset ?? 0;
|
||||
return Results.Ok();
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/search/collections", async (string? q, int? limit, int? offset) =>
|
||||
{
|
||||
var limitv = limit ?? 100;
|
||||
var offsetv = offset ?? 0;
|
||||
return Results.Ok();
|
||||
});
|
||||
|
||||
app.MapGet("/api/v1/images/{*filename}", async (string filename, int? h, int? w) =>
|
||||
{
|
||||
var decodedFileName = Uri.UnescapeDataString(filename);
|
||||
var filePath = Path.Combine(Osudb.osufolder, "Songs", decodedFileName);
|
||||
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
filePath = "default-bg.png";
|
||||
}
|
||||
|
||||
var fileExtension = Path.GetExtension(filePath).ToLowerInvariant();
|
||||
var contentType = fileExtension switch
|
||||
{
|
||||
".jpg" or ".jpeg" => "image/jpeg",
|
||||
".png" => "image/png",
|
||||
".gif" => "image/gif",
|
||||
".bmp" => "image/bmp",
|
||||
".webp" => "image/webp",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
|
||||
if (w == null || h == null)
|
||||
{
|
||||
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, useAsync: true);
|
||||
return Results.Stream(fileStream, contentType, filename);
|
||||
}
|
||||
using var originalImage = new Bitmap(filePath);
|
||||
|
||||
|
||||
// If resizing is requested, resize the image
|
||||
Bitmap resizedImage;
|
||||
if (w.HasValue || h.HasValue)
|
||||
{
|
||||
resizedImage = ResizeImage(originalImage, w, h);
|
||||
}
|
||||
else
|
||||
{
|
||||
resizedImage = new Bitmap(originalImage); // Keep original size
|
||||
}
|
||||
|
||||
// Convert the resized image to a memory stream
|
||||
var memoryStream = new MemoryStream();
|
||||
resizedImage.Save(memoryStream, GetImageFormat(fileExtension));
|
||||
memoryStream.Position = 0; // Reset stream position
|
||||
|
||||
return Results.File(memoryStream, contentType);
|
||||
|
||||
});
|
||||
|
||||
static Bitmap ResizeImage(Image originalImage, int? width, int? height)
|
||||
{
|
||||
int newWidth = width ?? originalImage.Width;
|
||||
int newHeight = height ?? originalImage.Height;
|
||||
|
||||
if (width == null)
|
||||
{
|
||||
newWidth = originalImage.Width * newHeight / originalImage.Height;
|
||||
}
|
||||
else if (height == null)
|
||||
{
|
||||
newHeight = originalImage.Height * newWidth / originalImage.Width;
|
||||
}
|
||||
|
||||
var resizedImage = new Bitmap(newWidth, newHeight);
|
||||
using (var graphics = Graphics.FromImage(resizedImage))
|
||||
{
|
||||
graphics.CompositingQuality = CompositingQuality.HighQuality;
|
||||
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
|
||||
graphics.SmoothingMode = SmoothingMode.HighQuality;
|
||||
graphics.DrawImage(originalImage, 0, 0, newWidth, newHeight);
|
||||
}
|
||||
|
||||
return resizedImage;
|
||||
}
|
||||
|
||||
static ImageFormat GetImageFormat(string extension)
|
||||
{
|
||||
return extension switch
|
||||
{
|
||||
".jpg" or ".jpeg" => ImageFormat.Jpeg,
|
||||
".png" => ImageFormat.Png,
|
||||
".gif" => ImageFormat.Gif,
|
||||
".bmp" => ImageFormat.Bmp,
|
||||
".webp" => ImageFormat.Webp,
|
||||
_ => ImageFormat.Png,
|
||||
};
|
||||
}
|
||||
|
||||
Osudb.Instance.ToString();
|
||||
startCloudflared();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
Thread.Sleep(500);
|
||||
if (!apiClient.LoadCookies())
|
||||
{
|
||||
Console.WriteLine("Please visit this link and paste the Value back into here: ");
|
||||
|
||||
var cookie = Console.ReadLine();
|
||||
|
||||
apiClient.SaveCookies(cookie);
|
||||
}
|
||||
|
||||
Console.WriteLine("Ur Osu songs should now be available, please delete the cookies.json if it doesnt show up and try again.");
|
||||
});
|
||||
|
||||
await apiClient.InitializeAsync();
|
||||
app.Run();
|
||||
|
||||
async Task startCloudflared() {
|
||||
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "cloudflared",
|
||||
Arguments = "tunnel --url http://localhost:5153",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
}
|
||||
};
|
||||
|
||||
process.ErrorDataReceived += (sender, e) => {
|
||||
if (!string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
ParseForUrls(e.Data);
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
process.BeginErrorReadLine();
|
||||
|
||||
await Task.Run(() => process.WaitForExit());
|
||||
}
|
||||
|
||||
void ParseForUrls(string data)
|
||||
{
|
||||
var urlRegex = new Regex(@"https?://[^\s]*\.trycloudflare\.com");
|
||||
var matches = urlRegex.Matches(data);
|
||||
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
Console.WriteLine($"Login here if not done already: {match.Value}/login");
|
||||
apiClient.UpdateSettingsAsync(match.Value);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:45205",
|
||||
"sslPort": 44305
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"shitweb": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:5153",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,384 +0,0 @@
|
||||
using OsuParsers.Beatmaps;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Data.SQLite;
|
||||
using System.Reflection.PortableExecutable;
|
||||
|
||||
namespace shitweb
|
||||
{
|
||||
public class SqliteDB
|
||||
{
|
||||
private static SqliteDB instance;
|
||||
private const string filename = "Beatmaps.db";
|
||||
private const string dburl = $"Data Source={filename};Version=3;";
|
||||
|
||||
static SqliteDB() { }
|
||||
|
||||
public static SqliteDB Instance()
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
|
||||
instance = new SqliteDB();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public void setup(OsuParsers.Database.OsuDatabase osuDatabase)
|
||||
{
|
||||
|
||||
int count = 0;
|
||||
|
||||
using (var connection = new SQLiteConnection(dburl))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
string createBeatmaps = @"
|
||||
CREATE TABLE IF NOT EXISTS Beatmap (
|
||||
BeatmapId INTEGER DEFAULT 0,
|
||||
Artist TEXT DEFAULT '?????',
|
||||
ArtistUnicode TEXT DEFAULT '?????',
|
||||
Title TEXT DEFAULT '???????',
|
||||
TitleUnicode TEXT DEFAULT '???????',
|
||||
Creator TEXT DEFAULT '?????',
|
||||
Difficulty TEXT DEFAULT '1',
|
||||
AudioFileName TEXT DEFAULT 'unknown.mp3',
|
||||
MD5Hash TEXT DEFAULT '00000000000000000000000000000000',
|
||||
FileName TEXT DEFAULT 'unknown.osu',
|
||||
RankedStatus TEXT DEFAULT Unknown,
|
||||
LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00',
|
||||
TotalTime INTEGER DEFAULT 0,
|
||||
AudioPreviewTime INTEGER DEFAULT 0,
|
||||
BeatmapSetId INTEGER DEFAULT -1,
|
||||
Source TEXT DEFAULT '',
|
||||
Tags TEXT DEFAULT '',
|
||||
LastPlayed DATETIME DEFAULT '0001-01-01 00:00:00',
|
||||
FolderName TEXT DEFAULT 'Unknown Folder',
|
||||
UNIQUE (Artist, Title, MD5Hash)
|
||||
);";
|
||||
|
||||
using (var command = new SQLiteCommand(createBeatmaps, connection))
|
||||
{
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
string activeSearch = @"CREATE VIRTUAL TABLE IF NOT EXISTS BeatmapFTS USING fts4(
|
||||
Title,
|
||||
Artist,
|
||||
);";
|
||||
|
||||
using (var command = new SQLiteCommand(activeSearch, connection))
|
||||
{
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
string triggerSearchupdate = @"CREATE TRIGGER IF NOT EXISTS Beatmap_Insert_Trigger
|
||||
AFTER INSERT ON Beatmap
|
||||
BEGIN
|
||||
INSERT INTO BeatmapFTS (Title, Artist)
|
||||
VALUES (NEW.Title, NEW.Artist);
|
||||
END;";
|
||||
|
||||
using (var command = new SQLiteCommand(triggerSearchupdate, connection))
|
||||
{
|
||||
command.ExecuteNonQuery();
|
||||
}
|
||||
|
||||
string query = @"SELECT COUNT(rowid) as count FROM Beatmap";
|
||||
|
||||
using (var command = new SQLiteCommand(query, connection))
|
||||
{
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
count = reader.GetInt32(reader.GetOrdinal("count"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count < osuDatabase.BeatmapCount)
|
||||
{
|
||||
DateTime? LastMapInsert = null;
|
||||
|
||||
using (var connection = new SQLiteConnection(dburl))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
string query = @"SELECT MAX(LastModifiedTime) as Time FROM Beatmap"; ;
|
||||
using (var command = new SQLiteCommand(query, connection))
|
||||
{
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
try
|
||||
{
|
||||
LastMapInsert = reader.GetDateTime(reader.GetOrdinal("Time"));
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
LastMapInsert = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
int size = osuDatabase.BeatmapCount;
|
||||
Console.CursorVisible = false;
|
||||
foreach (var item in osuDatabase.Beatmaps)
|
||||
{
|
||||
if (LastMapInsert == null || item.LastModifiedTime > LastMapInsert)
|
||||
{
|
||||
insertBeatmap(item);
|
||||
i++;
|
||||
Console.CursorTop -= 1;
|
||||
Console.Write($"Inserted {i}/{size}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static List<Song> GetByRecent(int limit, int offset)
|
||||
{
|
||||
var songs = new List<Song>();
|
||||
|
||||
using (var connection = new SQLiteConnection(dburl))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
string query = @"
|
||||
SELECT
|
||||
MD5Hash,
|
||||
Title,
|
||||
Artist,
|
||||
TotalTime,
|
||||
Creator,
|
||||
FileName,
|
||||
FolderName,
|
||||
AudioFileName
|
||||
FROM
|
||||
Beatmap
|
||||
GROUP BY
|
||||
BeatmapSetId
|
||||
ORDER BY
|
||||
LastModifiedTime DESC
|
||||
LIMIT @Limit
|
||||
OFFSET @Offset
|
||||
";
|
||||
|
||||
using (var command = new SQLiteCommand(query, connection))
|
||||
{
|
||||
command.Parameters.AddWithValue("@Limit", limit);
|
||||
command.Parameters.AddWithValue("@Offset", offset);
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
songs.Add(new Song(reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return songs;
|
||||
}
|
||||
|
||||
public void insertBeatmap(OsuParsers.Database.Objects.DbBeatmap beatmap)
|
||||
{
|
||||
|
||||
using (var connection = new SQLiteConnection(dburl))
|
||||
{
|
||||
|
||||
connection.Open();
|
||||
|
||||
string insertBeatmap = @"
|
||||
INSERT INTO Beatmap (
|
||||
BeatmapId, Artist, ArtistUnicode, Title, TitleUnicode, Creator, Difficulty,
|
||||
AudioFileName, MD5Hash, FileName, RankedStatus, LastModifiedTime, TotalTime,
|
||||
AudioPreviewTime, BeatmapSetId, Source, Tags, LastPlayed, FolderName
|
||||
) VALUES (
|
||||
@BeatmapId, @Artist, @ArtistUnicode, @Title, @TitleUnicode, @Creator, @Difficulty,
|
||||
@AudioFileName, @MD5Hash, @FileName, @RankedStatus, @LastModifiedTime, @TotalTime,
|
||||
@AudioPreviewTime, @BeatmapSetId, @Source, @Tags, @LastPlayed, @FolderName
|
||||
);";
|
||||
using (var command = new SQLiteCommand(insertBeatmap, connection))
|
||||
{
|
||||
command.Parameters.AddWithValue("@BeatmapSetId", beatmap.BeatmapSetId);
|
||||
command.Parameters.AddWithValue("@BeatmapId", beatmap.BeatmapId);
|
||||
command.Parameters.AddWithValue("@Artist", beatmap.Artist);
|
||||
command.Parameters.AddWithValue("@ArtistUnicode", beatmap.ArtistUnicode);
|
||||
command.Parameters.AddWithValue("@Title", beatmap.Title);
|
||||
command.Parameters.AddWithValue("@TitleUnicode", beatmap.TitleUnicode);
|
||||
command.Parameters.AddWithValue("@Creator", beatmap.Creator);
|
||||
command.Parameters.AddWithValue("@Difficulty", beatmap.Difficulty);
|
||||
command.Parameters.AddWithValue("@AudioFileName", beatmap.AudioFileName);
|
||||
command.Parameters.AddWithValue("@MD5Hash", beatmap.MD5Hash);
|
||||
command.Parameters.AddWithValue("@FileName", beatmap.FileName);
|
||||
command.Parameters.AddWithValue("@RankedStatus", beatmap.RankedStatus);
|
||||
command.Parameters.AddWithValue("@LastModifiedTime", beatmap.LastModifiedTime);
|
||||
command.Parameters.AddWithValue("@TotalTime", beatmap.TotalTime);
|
||||
command.Parameters.AddWithValue("@AudioPreviewTime", beatmap.AudioPreviewTime);
|
||||
command.Parameters.AddWithValue("@Source", beatmap.Source);
|
||||
command.Parameters.AddWithValue("@Tags", beatmap.Tags);
|
||||
command.Parameters.AddWithValue("@LastPlayed", beatmap.LastPlayed);
|
||||
command.Parameters.AddWithValue("@FolderName", beatmap.FolderName);
|
||||
|
||||
int rows = command.ExecuteNonQuery();
|
||||
Console.WriteLine(rows);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Song GetSongByHash(string hash)
|
||||
{
|
||||
|
||||
using (var connection = new SQLiteConnection(dburl))
|
||||
{
|
||||
connection.Open();
|
||||
|
||||
string query = @"
|
||||
SELECT
|
||||
MD5Hash,
|
||||
Title,
|
||||
Artist,
|
||||
TotalTime,
|
||||
Creator,
|
||||
FileName,
|
||||
FolderName,
|
||||
AudioFileName
|
||||
FROM Beatmap
|
||||
WHERE MD5Hash = @Hash;
|
||||
";
|
||||
|
||||
using (var command = new SQLiteCommand(query, connection))
|
||||
{
|
||||
command.Parameters.AddWithValue("@Hash", hash);
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
while (reader.Read())
|
||||
{
|
||||
return new Song(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ActiveSearch activeSearch(string query) {
|
||||
ActiveSearch search = new ActiveSearch();
|
||||
|
||||
using (var connection = new SQLiteConnection(dburl))
|
||||
{
|
||||
string q = @"SELECT
|
||||
MD5Hash,
|
||||
Title,
|
||||
Artist,
|
||||
TotalTime,
|
||||
Creator,
|
||||
FileName,
|
||||
FolderName,
|
||||
AudioFileName
|
||||
FROM Beatmap
|
||||
WHERE Title LIKE @query
|
||||
OR Artist LIKE @query
|
||||
OR Tags LIKE @query
|
||||
Group By Title
|
||||
LIMIT 15";
|
||||
|
||||
connection.Open();
|
||||
|
||||
using (var command = new SQLiteCommand(q, connection))
|
||||
{
|
||||
command.Parameters.AddWithValue("@query", "%" + query + "%");
|
||||
|
||||
using (var reader = command.ExecuteReader()) {
|
||||
|
||||
while (reader.Read()) {
|
||||
|
||||
search.Songs.Add(new Song(reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string q2 = @"SELECT
|
||||
Artist
|
||||
FROM Beatmap
|
||||
WHERE Artist LIKE @query
|
||||
OR Title LIKE @query
|
||||
Group By Artist
|
||||
LIMIT 5";
|
||||
|
||||
using (var command = new SQLiteCommand(q2, connection))
|
||||
{
|
||||
command.Parameters.AddWithValue("@query", query + "%");
|
||||
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
|
||||
search.Artist.Add(reader.GetString(reader.GetOrdinal("Artist")));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return search;
|
||||
}
|
||||
|
||||
public static List<Song> GetArtistSearch(string query, int limit, int offset) {
|
||||
List<Song> songs = new List<Song>();
|
||||
|
||||
query = $"%{query}%";
|
||||
using (var connection = new SQLiteConnection(dburl))
|
||||
{
|
||||
string q = @"SELECT
|
||||
MD5Hash,
|
||||
Title,
|
||||
Artist,
|
||||
TotalTime,
|
||||
Creator,
|
||||
FileName,
|
||||
FolderName,
|
||||
AudioFileName
|
||||
FROM Beatmap
|
||||
WHERE Artist LIKE @query
|
||||
Group By Title
|
||||
LIMIT @Limit
|
||||
OFFSET @Offset";
|
||||
|
||||
connection.Open();
|
||||
|
||||
using (var command = new SQLiteCommand(q, connection))
|
||||
{
|
||||
command.Parameters.AddWithValue("@query", query);
|
||||
command.Parameters.AddWithValue("@Limit", limit);
|
||||
command.Parameters.AddWithValue("@Offset", offset);
|
||||
|
||||
using (var reader = command.ExecuteReader())
|
||||
{
|
||||
|
||||
while (reader.Read())
|
||||
{
|
||||
songs.Add(new Song(reader));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return songs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"Name": "cookie_name",
|
||||
"Value": "random cookie value here"
|
||||
}
|
||||
158
backend/osudb.cs
158
backend/osudb.cs
@@ -1,158 +0,0 @@
|
||||
using Microsoft.Win32;
|
||||
using OsuParsers.Database;
|
||||
using shitweb;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
public class Osudb
|
||||
{
|
||||
private static Osudb instance = null;
|
||||
private static readonly object padlock = new object();
|
||||
public static string osufolder { get; private set; }
|
||||
public static CollectionDatabase CollectionDb { get; private set; }
|
||||
|
||||
static Osudb()
|
||||
{
|
||||
var key = Registry.GetValue(
|
||||
@"HKEY_LOCAL_MACHINE\SOFTWARE\Classes\osu\shell\open\command",
|
||||
"",
|
||||
null
|
||||
);
|
||||
|
||||
if (key != null)
|
||||
{
|
||||
string[] keyparts = key.ToString().Split('"');
|
||||
|
||||
osufolder = Path.GetDirectoryName(keyparts[1]);
|
||||
|
||||
Parse(osufolder);
|
||||
}
|
||||
else throw new Exception("Osu not Installed... ");
|
||||
}
|
||||
|
||||
static void Parse(string filepath)
|
||||
{
|
||||
OsuDatabase osuDatabase = null;
|
||||
string file = "/osu!.db";
|
||||
if (File.Exists(filepath + file))
|
||||
{
|
||||
using (FileStream fileStream = new FileStream(filepath + file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize: 4096, useAsync: true))
|
||||
{
|
||||
Console.WriteLine($"Parsing {file}");
|
||||
osuDatabase = OsuParsers.Decoders.DatabaseDecoder.DecodeOsu($"{filepath}{file}");
|
||||
Console.WriteLine($"Parsed {file}");
|
||||
|
||||
fileStream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
file = "/collection.db";
|
||||
if (File.Exists(filepath + file))
|
||||
{
|
||||
using (FileStream fileStream = new FileStream(filepath + file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, bufferSize: 4096, useAsync: true))
|
||||
{
|
||||
Console.WriteLine($"Parsing {file}");
|
||||
CollectionDb = OsuParsers.Decoders.DatabaseDecoder.DecodeCollection($"{filepath}{file}");
|
||||
Console.WriteLine($"Parsed {file}");
|
||||
}
|
||||
}
|
||||
|
||||
SqliteDB.Instance().setup(osuDatabase);
|
||||
osuDatabase = null;
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
public static Osudb Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (padlock)
|
||||
{
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new Osudb();
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static OsuParsers.Database.Objects.Collection GetCollectionbyName(string name) {
|
||||
|
||||
return CollectionDb.Collections.FirstOrDefault(collection => collection.Name == name);
|
||||
}
|
||||
|
||||
public static OsuParsers.Database.Objects.Collection GetCollectionbyIndex(int index)
|
||||
{
|
||||
|
||||
return CollectionDb.Collections.ElementAtOrDefault(index);
|
||||
}
|
||||
|
||||
public Collection GetCollection(int index) {
|
||||
|
||||
var collection = GetCollectionbyIndex(index);
|
||||
if (collection == null) { return null; }
|
||||
|
||||
List<Song> songs = new List<Song>();
|
||||
var activeId = "";
|
||||
|
||||
collection.MD5Hashes.ForEach(hash =>
|
||||
{
|
||||
var beatmap = SqliteDB.GetSongByHash(hash);
|
||||
if (beatmap == null) { return; }
|
||||
|
||||
songs.Add(beatmap);
|
||||
|
||||
});
|
||||
|
||||
return new Collection(collection.Name, songs.Count, songs);
|
||||
}
|
||||
|
||||
public List<CollectionPreview> GetCollections(int limit, int offset)
|
||||
{
|
||||
|
||||
List<CollectionPreview> collections = new List<CollectionPreview>();
|
||||
|
||||
for (int i = offset; i < CollectionDb.Collections.Count - 1 && i < offset + limit; i++) {
|
||||
var collection = CollectionDb.Collections[i];
|
||||
|
||||
var beatmap = SqliteDB.GetSongByHash(collection.MD5Hashes.FirstOrDefault());
|
||||
|
||||
collections.Add(new CollectionPreview(index: i, name: collection.Name, previewimage: beatmap.previewimage, length: collection.Count));
|
||||
};
|
||||
|
||||
return collections;
|
||||
}
|
||||
|
||||
public static string getBG(string songfolder, string diff)
|
||||
{
|
||||
string folderpath = Path.Combine(songfolder, diff);
|
||||
string filepath = Path.Combine(osufolder, "Songs", folderpath);
|
||||
|
||||
if (File.Exists(filepath))
|
||||
{
|
||||
string fileContents = File.ReadAllText($@"{filepath}"); // Read the contents of the file
|
||||
|
||||
string pattern = @"\d+,\d+,""(?<image_filename>[^""]+\.[a-zA-Z]+)"",\d+,\d+";
|
||||
|
||||
Match match = Regex.Match(fileContents, pattern);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string background = Uri.EscapeDataString(match.Groups["image_filename"].Value);
|
||||
|
||||
return Path.Combine(Uri.EscapeDataString(songfolder), background);
|
||||
}
|
||||
|
||||
pattern = @"\d+,\d+,""(?<image_filename>[^""]+\.[a-zA-Z]+)""";
|
||||
|
||||
match = Regex.Match(fileContents, pattern);
|
||||
|
||||
if (match.Success)
|
||||
{
|
||||
string background = Uri.EscapeDataString(match.Groups["image_filename"].Value);
|
||||
return Path.Combine(Uri.EscapeDataString(songfolder), background);
|
||||
}
|
||||
}
|
||||
return "default-bg.png";
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OsuParsers" Version="1.7.1" />
|
||||
<PackageReference Include="System.Data.SQLite" Version="1.0.118" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="8.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="default-bg.png">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,25 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.8.34322.80
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "shitweb", "shitweb.csproj", "{A81ACB49-5C0C-42D5-88DA-3BC7E256F859}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A81ACB49-5C0C-42D5-88DA-3BC7E256F859}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A81ACB49-5C0C-42D5-88DA-3BC7E256F859}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A81ACB49-5C0C-42D5-88DA-3BC7E256F859}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A81ACB49-5C0C-42D5-88DA-3BC7E256F859}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A5EA63AB-B1DD-4171-922C-D01C758AD544}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
102
backend/types.cs
102
backend/types.cs
@@ -1,102 +0,0 @@
|
||||
using OsuParsers.Database.Objects;
|
||||
using OsuParsers.Enums.Database;
|
||||
using OsuParsers.Enums;
|
||||
using System.Data.SQLite;
|
||||
using System.CodeDom;
|
||||
|
||||
public class Song{
|
||||
public string hash {get; set;}
|
||||
public string name {get; set;}
|
||||
public string artist {get; set;}
|
||||
public int length {get; set;}
|
||||
public string url { get; set; }
|
||||
public string previewimage {get; set;}
|
||||
public string mapper {get; set;}
|
||||
|
||||
public Song(string hash, string name, string artist, int length, string url, string previewimage, string mapper) {
|
||||
this.hash = hash; this.name = name; this.artist = artist; this.length = length; this.url = url; this.previewimage = previewimage; this.mapper = mapper;
|
||||
}
|
||||
|
||||
public Song(SQLiteDataReader reader) {
|
||||
string folder = reader.GetString(reader.GetOrdinal("FolderName"));
|
||||
string file = reader.GetString(reader.GetOrdinal("FileName"));
|
||||
string audio = reader.GetString(reader.GetOrdinal("AudioFileName"));
|
||||
|
||||
this.hash = reader.GetString(reader.GetOrdinal("MD5Hash"));
|
||||
this.name = reader.GetString(reader.GetOrdinal("Title"));
|
||||
this.artist = reader.GetString(reader.GetOrdinal("Artist"));
|
||||
this.length = reader.GetInt32(reader.GetOrdinal("TotalTime"));
|
||||
this.url = Uri.EscapeDataString($"{folder}/{audio}");
|
||||
this.previewimage = Osudb.getBG(folder, file);
|
||||
this.mapper = reader.GetString(reader.GetOrdinal("Creator"));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class CollectionPreview{
|
||||
public int index { get; set;}
|
||||
public string? name {get; set;}
|
||||
public int length {get; set;}
|
||||
public string? previewimage {get; set;}
|
||||
|
||||
private CollectionPreview() { }
|
||||
|
||||
public CollectionPreview(int index, string name, string previewimage, int length) {
|
||||
this.index = index; this.name = name; this.previewimage = previewimage; this.length = length;
|
||||
}
|
||||
|
||||
}
|
||||
public class Collection{
|
||||
public string? name {get; set;}
|
||||
public int length {get; set;}
|
||||
public List<Song> songs { get; set;} = new List<Song>();
|
||||
|
||||
private Collection() { }
|
||||
|
||||
public Collection(string name, int length, List<Song> songs) {
|
||||
this.name = name; this.length = length; this.songs = songs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class ActiveSearch{
|
||||
public List<string> Artist { get; set; } = new List<string>();
|
||||
public List<Song> Songs { get; set; } = new List<Song>();
|
||||
}
|
||||
|
||||
public class Beatmap
|
||||
{
|
||||
public string? Artist { get; set; }
|
||||
public string? ArtistUnicode { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? TitleUnicode { get; set; }
|
||||
public string? Creator { get; set; }
|
||||
public string? Difficulty { get; set; }
|
||||
public string? AudioFileName { get; set; }
|
||||
public string? MD5Hash { get; set; }
|
||||
public string? FileName { get; set; }
|
||||
public RankedStatus RankedStatus { get; set; }
|
||||
public DateTime LastModifiedTime { get; set; }
|
||||
public int TotalTime { get; set; }
|
||||
public int AudioPreviewTime { get; set; }
|
||||
public int BeatmapId { get; set; }
|
||||
public int BeatmapSetId { get; set; }
|
||||
public string? Source { get; set; }
|
||||
public string? Tags { get; set; }
|
||||
public DateTime LastPlayed { get; set; }
|
||||
public string? FolderName { get; set; }
|
||||
}
|
||||
|
||||
public class BeatmapSet {
|
||||
public int BeatmapSetId { get; set; }
|
||||
public string? FolderName { get; set; }
|
||||
public string? Creator { get; set; }
|
||||
public DateTime LastModifiedTime { get; set; }
|
||||
public List<Beatmap> Beatmaps { get; private set; } = new List<Beatmap>();
|
||||
|
||||
public void AddBeatmap(Beatmap beatmap)
|
||||
{
|
||||
beatmap.BeatmapSetId = this.BeatmapSetId;
|
||||
Beatmaps.Add(beatmap);
|
||||
}
|
||||
}
|
||||
3
go-backend/.gitignore
vendored
3
go-backend/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
data/*
|
||||
.vscode/
|
||||
.env
|
||||
@@ -1,599 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/juli0n21/go-osu-parser/parser"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
var ErrBeatmapCountNotMatch = errors.New("beatmap count not matching")
|
||||
|
||||
var osuDB *parser.OsuDB
|
||||
var osuRoot string
|
||||
|
||||
func initDB(connectionString string, osuDb *parser.OsuDB, osuroot string) (*sql.DB, error) {
|
||||
|
||||
osuDB = osuDb
|
||||
osuRoot = osuroot
|
||||
|
||||
dir := filepath.Dir(connectionString)
|
||||
|
||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||
err = os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory %s: %v", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite", connectionString)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open SQLite database %s: %v", connectionString, err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("PRAGMA temp_store = MEMORY;")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err = createDB(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = checkhealth(db, osuDB); err != nil {
|
||||
if err = rebuildBeatmapDb(db, osuDB); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err = createCollectionDB(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
collectionDB, err := parser.ParseCollectionsDB(path.Join(osuRoot, "collection.db"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = checkCollectionHealth(db, collectionDB); err != nil {
|
||||
if err = rebuildCollectionDb(db, collectionDB); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func createDB(db *sql.DB) error {
|
||||
|
||||
_, err := db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS Beatmap (
|
||||
BeatmapId INTEGER DEFAULT 0,
|
||||
Artist TEXT DEFAULT '?????',
|
||||
ArtistUnicode TEXT DEFAULT '?????',
|
||||
Title TEXT DEFAULT '???????',
|
||||
TitleUnicode TEXT DEFAULT '???????',
|
||||
Creator TEXT DEFAULT '?????',
|
||||
Difficulty TEXT DEFAULT '1',
|
||||
Audio TEXT DEFAULT 'unknown.mp3',
|
||||
MD5Hash TEXT DEFAULT '00000000000000000000000000000000',
|
||||
File TEXT DEFAULT 'unknown.osu',
|
||||
RankedStatus TEXT DEFAULT Unknown,
|
||||
LastModifiedTime DATETIME DEFAULT '0001-01-01 00:00:00',
|
||||
TotalTime INTEGER DEFAULT 0,
|
||||
AudioPreviewTime INTEGER DEFAULT 0,
|
||||
BeatmapSetId INTEGER DEFAULT -1,
|
||||
Source TEXT DEFAULT '',
|
||||
Tags TEXT DEFAULT '',
|
||||
LastPlayed DATETIME DEFAULT '0001-01-01 00:00:00',
|
||||
Folder TEXT DEFAULT 'Unknown Folder',
|
||||
UNIQUE (Artist, Title, MD5Hash, Difficulty)
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_beatmap_md5hash ON Beatmap(MD5Hash);")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_beatmap_lastModifiedTime ON Beatmap(LastModifiedTime);")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_beatmap_title_artist ON Beatmap(Title, Artist);")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createCollectionDB(db *sql.DB) error {
|
||||
_, err := db.Exec(`CREATE TABLE IF NOT EXISTS Collection (
|
||||
Name TEXT DEFAULT '',
|
||||
MD5Hash TEXT DEFAULT '00000000000000000000000000000000'
|
||||
);
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_collection_name ON Collection(Name);")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec("CREATE INDEX IF NOT EXISTS idx_collection_md5hash ON Collection(MD5Hash);")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkhealth(db *sql.DB, osuDb *parser.OsuDB) error {
|
||||
|
||||
rows, err := db.Query(`SELECT COUNT(*) FROM Beatmap GROUP BY BeatmapSetId;`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var count int
|
||||
if err = rows.Scan(&count); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count != int(osuDb.FolderCount) {
|
||||
log.Println("Folder count missmatch rebuilding db...")
|
||||
return ErrBeatmapCountNotMatch
|
||||
}
|
||||
|
||||
rows, err = db.Query(`SELECT COUNT(*) FROM Beatmap;`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = rows.Scan(&count); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count != int(osuDb.NumberOfBeatmaps) {
|
||||
log.Println("Beatmap count missmatch rebuilding db...")
|
||||
return ErrBeatmapCountNotMatch
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rebuildBeatmapDb(db *sql.DB, osuDb *parser.OsuDB) error {
|
||||
|
||||
if _, err := db.Exec("DROP TABLE Beatmap"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createDB(db); err != nil {
|
||||
return err
|
||||
}
|
||||
stmt, err := db.Prepare(`
|
||||
INSERT INTO Beatmap (
|
||||
BeatmapId, Artist, ArtistUnicode, Title, TitleUnicode, Creator,
|
||||
Difficulty, Audio, MD5Hash, File, RankedStatus,
|
||||
LastModifiedTime, TotalTime, AudioPreviewTime, BeatmapSetId,
|
||||
Source, Tags, LastPlayed, Folder
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`)
|
||||
// ON CONFLICT (Artist, Title, MD5Hash) DO NOTHING
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stmt = tx.Stmt(stmt)
|
||||
|
||||
for i, beatmap := range osuDb.Beatmaps {
|
||||
//fmt.Println(i, beatmap.Artist, beatmap.SongTitle, beatmap.MD5Hash)
|
||||
_, err := stmt.Exec(
|
||||
beatmap.DifficultyID, beatmap.Artist, beatmap.ArtistUnicode,
|
||||
beatmap.SongTitle, beatmap.SongTitleUnicode, beatmap.Creator,
|
||||
beatmap.Difficulty, beatmap.AudioFileName, beatmap.MD5Hash,
|
||||
beatmap.FileName, beatmap.RankedStatus, beatmap.LastModificationTime,
|
||||
beatmap.TotalTime, beatmap.AudioPreviewStartTime, beatmap.BeatmapID,
|
||||
beatmap.SongSource, beatmap.SongTags, beatmap.LastPlayed, beatmap.FolderName,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(i, "hash: ", beatmap.MD5Hash, "artist:", beatmap.Artist, "title:", beatmap.SongTitle, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkCollectionHealth(db *sql.DB, collectionDB *parser.Collections) error {
|
||||
rows, err := db.Query(`SELECT COUNT(*) FROM Collection GROUP BY Name;`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var count int
|
||||
if err = rows.Scan(&count); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count != int(collectionDB.NumberOfCollections) {
|
||||
return errors.New("Collection Count Not Matching")
|
||||
}
|
||||
|
||||
rows, err = db.Query(`SELECT COUNT(*) FROM Collection;`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = rows.Scan(&count); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sum := 0
|
||||
for _, col := range collectionDB.Collections {
|
||||
sum += len(col.Beatmaps)
|
||||
}
|
||||
|
||||
if count != int(sum) {
|
||||
return errors.New("Beatmap count missmatch rebuilding collections")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rebuildCollectionDb(db *sql.DB, collectionDb *parser.Collections) error {
|
||||
if _, err := db.Exec("DROP TABLE Collection"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := createCollectionDB(db); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stmt, err := db.Prepare(`
|
||||
INSERT INTO Collection (
|
||||
Name,
|
||||
MD5Hash
|
||||
) VALUES (?, ?)
|
||||
`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stmt = tx.Stmt(stmt)
|
||||
|
||||
for _, col := range collectionDb.Collections {
|
||||
for _, hash := range col.Beatmaps {
|
||||
_, err := stmt.Exec(col.Name, hash)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func getBeatmapCount(db *sql.DB) int {
|
||||
rows, err := db.Query("SELECT COUNT(*) FROM Beatmap")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var count int
|
||||
if rows.Next() {
|
||||
err = rows.Scan(&count)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
func getRecent(db *sql.DB, limit, offset int) ([]Song, error) {
|
||||
rows, err := db.Query("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap ORDER BY LastModifiedTime DESC LIMIT ? OFFSET ?", limit, offset)
|
||||
if err != nil {
|
||||
return []Song{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return scanSongs(rows)
|
||||
}
|
||||
|
||||
func getSearch(db *sql.DB, q string, limit, offset int) (ActiveSearch, error) {
|
||||
rows, err := db.Query("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap WHERE MD5Hash FROM Songs WHERE Title LIKE ? OR Artist LIKE ? LIMIT ? OFFSET ?", "%"+q+"%", "%"+q+"%", limit, offset)
|
||||
if err != nil {
|
||||
return ActiveSearch{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
_, err = scanSongs(rows)
|
||||
if err != nil {
|
||||
return ActiveSearch{}, err
|
||||
}
|
||||
return ActiveSearch{}, nil
|
||||
}
|
||||
|
||||
func getArtists(db *sql.DB, q string, limit, offset int) ([]Artist, error) {
|
||||
rows, err := db.Query("SELECT Artist, COUNT(Artist) FROM Beatmap WHERE Artist LIKE ? OR Title LIKE ? GROUP BY Artist LIMIT ? OFFSET ?", "%"+q+"%", "%"+q+"%", limit, offset)
|
||||
if err != nil {
|
||||
return []Artist{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
artist := []Artist{}
|
||||
for rows.Next() {
|
||||
var a string
|
||||
var c int
|
||||
err := rows.Scan(&a, &c)
|
||||
if err != nil {
|
||||
return []Artist{}, err
|
||||
}
|
||||
artist = append(artist, Artist{Artist: a, Count: c})
|
||||
}
|
||||
|
||||
return artist, nil
|
||||
}
|
||||
|
||||
func getFavorites(db *sql.DB, q string, limit, offset int) ([]Song, error) {
|
||||
rows, err := db.Query("SELECT * FROM Songs WHERE IsFavorite = 1 LIMIT ? OFFSET ?", limit, offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanSongs(rows)
|
||||
}
|
||||
|
||||
func getCollection(db *sql.DB, limit, offset, index int) (Collection, error) {
|
||||
rows, err := db.Query(`
|
||||
WITH cols AS (
|
||||
SELECT
|
||||
c.Name,
|
||||
ROW_NUMBER() OVER (ORDER BY c.Name) AS RowNumber
|
||||
FROM Collection c
|
||||
GROUP BY c.Name
|
||||
)
|
||||
|
||||
SELECT
|
||||
c.Name, b.BeatmapId, b.MD5Hash, b.Title, b.Artist,
|
||||
b.Creator, b.Folder, b.File, b.Audio, b.TotalTime
|
||||
FROM Collection c
|
||||
Join Beatmap b ON c.MD5Hash = b.MD5Hash
|
||||
WHERE c.Name = (SELECT Name FROM cols WHERE RowNumber = ?)
|
||||
LIMIT ?
|
||||
OFFSET ?;`, index, limit, offset)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var c Collection
|
||||
for rows.Next() {
|
||||
s := Song{}
|
||||
if err := rows.Scan(&c.Name, &s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
s.Image = extractImageFromFile(osuRoot, s.Folder, s.File)
|
||||
|
||||
c.Songs = append(c.Songs, s)
|
||||
}
|
||||
|
||||
row := db.QueryRow(`SELECT COUNT(*) FROM Collection WHERE Name = ?`, c.Name)
|
||||
var count string
|
||||
row.Scan(&count)
|
||||
|
||||
if i, err := strconv.Atoi(count); err == nil {
|
||||
c.Items = i
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getCollectionByName(db *sql.DB, limit, offset int, name string) (Collection, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT
|
||||
c.Name, b.BeatmapId, b.MD5Hash, b.Title, b.Artist,
|
||||
b.Creator, b.Folder, b.File, b.Audio, b.TotalTime
|
||||
FROM Collection c
|
||||
Join Beatmap b ON c.MD5Hash = b.MD5Hash
|
||||
WHERE c.Name = ?
|
||||
LIMIT ?
|
||||
OFFSET ?;`, name, limit, offset)
|
||||
if err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var c Collection
|
||||
for rows.Next() {
|
||||
s := Song{}
|
||||
if err := rows.Scan(&c.Name, &s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
s.Image = extractImageFromFile(osuRoot, s.Folder, s.File)
|
||||
|
||||
c.Songs = append(c.Songs, s)
|
||||
}
|
||||
|
||||
row := db.QueryRow(`SELECT COUNT(*) FROM Collection WHERE Name = ?`, c.Name)
|
||||
var count string
|
||||
row.Scan(&count)
|
||||
|
||||
if i, err := strconv.Atoi(count); err == nil {
|
||||
c.Items = i
|
||||
} else {
|
||||
return Collection{}, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func getCollections(db *sql.DB, q string, limit, offset int) ([]CollectionPreview, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT
|
||||
c.Name, COUNT(b.MD5Hash), b.Folder, b.File
|
||||
FROM Collection c
|
||||
Join Beatmap b ON c.MD5Hash = b.MD5Hash
|
||||
WHERE c.Name LIKE ?
|
||||
GROUP BY c.NAME
|
||||
LIMIT ?
|
||||
OFFSET ?;`, "%"+q+"%", limit, offset)
|
||||
if err != nil {
|
||||
return []CollectionPreview{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var collections []CollectionPreview
|
||||
for rows.Next() {
|
||||
var c CollectionPreview
|
||||
var folder, file, count string
|
||||
if err := rows.Scan(&c.Name, &count, &folder, &file); err != nil {
|
||||
return []CollectionPreview{}, err
|
||||
}
|
||||
|
||||
if i, err := strconv.Atoi(count); err == nil {
|
||||
c.Items = i
|
||||
}
|
||||
c.Image = extractImageFromFile(osuRoot, folder, file)
|
||||
|
||||
collections = append(collections, c)
|
||||
}
|
||||
|
||||
return collections, nil
|
||||
}
|
||||
|
||||
func getSong(db *sql.DB, hash string) (Song, error) {
|
||||
|
||||
row := db.QueryRow("SELECT BeatmapId, MD5Hash, Title, Artist, Creator, Folder, File, Audio, TotalTime FROM Beatmap WHERE MD5Hash = ?", hash)
|
||||
s, err := scanSong(row)
|
||||
return s, err
|
||||
|
||||
}
|
||||
|
||||
func scanSongs(rows *sql.Rows) ([]Song, error) {
|
||||
songs := []Song{}
|
||||
for rows.Next() {
|
||||
var s Song
|
||||
if err := rows.Scan(&s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil {
|
||||
return []Song{}, err
|
||||
}
|
||||
|
||||
bm, err := parser.ParseOsuFile(fmt.Sprintf("%sSongs/%s/%s", osuRoot, s.Folder, s.File))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
s.Image = fmt.Sprintf("404.png")
|
||||
} else {
|
||||
if len(bm.Events) > 1 && len(bm.Events[0].EventParams) > 1 {
|
||||
s.Image = fmt.Sprintf("%s/%s", s.Folder, strings.Trim(bm.Events[0].EventParams[0], "\""))
|
||||
}
|
||||
}
|
||||
|
||||
songs = append(songs, s)
|
||||
}
|
||||
return songs, nil
|
||||
}
|
||||
|
||||
func scanSong(row *sql.Row) (Song, error) {
|
||||
|
||||
s := Song{}
|
||||
if err := row.Scan(&s.BeatmapID, &s.MD5Hash, &s.Title, &s.Artist, &s.Creator, &s.Folder, &s.File, &s.Audio, &s.TotalTime); err != nil {
|
||||
return Song{}, err
|
||||
}
|
||||
|
||||
s.Image = extractImageFromFile(osuRoot, s.Folder, s.File)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func extractImageFromFile(osuRoot, folder, file string) string {
|
||||
bm, err := parser.ParseOsuFile(filepath.Join(osuRoot, "Songs", folder, file))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return "404.png"
|
||||
}
|
||||
|
||||
if bm.Version > 3 {
|
||||
if len(bm.Events) > 0 && len(bm.Events[0].EventParams) > 0 {
|
||||
return fmt.Sprintf(`%s/%s`, folder, strings.Trim(bm.Events[0].EventParams[0], "\""))
|
||||
}
|
||||
} else {
|
||||
fmt.Println(bm.Events)
|
||||
}
|
||||
|
||||
return "404.png"
|
||||
}
|
||||
|
||||
func scanCollections(rows *sql.Rows) ([]Collection, error) {
|
||||
|
||||
var collection []Collection
|
||||
for rows.Next() {
|
||||
var c Collection
|
||||
if err := rows.Scan(&c); err != nil {
|
||||
return []Collection{}, err
|
||||
}
|
||||
collection = append(collection, c)
|
||||
}
|
||||
return collection, nil
|
||||
}
|
||||
|
||||
func scanCollectionPreviews(rows *sql.Rows) ([]CollectionPreview, error) {
|
||||
|
||||
var collection []CollectionPreview
|
||||
for rows.Next() {
|
||||
var c CollectionPreview
|
||||
if err := rows.Scan(&c); err != nil {
|
||||
return []CollectionPreview{}, err
|
||||
}
|
||||
collection = append(collection, c)
|
||||
}
|
||||
return collection, nil
|
||||
}
|
||||
|
||||
func scanCollectionPreview(row *sql.Row) (CollectionPreview, error) {
|
||||
|
||||
var c CollectionPreview
|
||||
if err := row.Scan(&c); err != nil {
|
||||
return CollectionPreview{}, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
@@ -1,685 +0,0 @@
|
||||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
|
||||
const docTemplate = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/audio/{filepath}": {
|
||||
"get": {
|
||||
"description": "Retrieves a song file from the server based on the provided encoded filepath",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"files"
|
||||
],
|
||||
"summary": "Retrieves a song file by its encoded path",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Base64 encoded file path",
|
||||
"name": "filepath",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The requested song file",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "File Not Found",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collection": {
|
||||
"get": {
|
||||
"description": "Retrieves a collection of songs using the provided index.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"songs"
|
||||
],
|
||||
"summary": "Get a collection of songs by index",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Index",
|
||||
"name": "index",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Index",
|
||||
"name": "name",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid parameter",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/image/{filepath}": {
|
||||
"get": {
|
||||
"description": "Retrieves an image file from the server based on the provided encoded filepath",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"files"
|
||||
],
|
||||
"summary": "Retrieves an image file by its encoded path",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Base64 encoded file path",
|
||||
"name": "filepath",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The requested image file",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "File Not Found",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/login": {
|
||||
"get": {
|
||||
"description": "Redirects users to an external authentication page",
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Redirect to login page",
|
||||
"responses": {
|
||||
"307": {
|
||||
"description": "Temporary Redirect",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ping": {
|
||||
"get": {
|
||||
"description": "Returns a pong response if the server is running",
|
||||
"tags": [
|
||||
"health"
|
||||
],
|
||||
"summary": "Check server health",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "pong",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/search/active": {
|
||||
"get": {
|
||||
"description": "Searches active records in the database based on the query parameter",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"search"
|
||||
],
|
||||
"summary": "Searches active records based on a query",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Limit the number of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset for pagination",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Active search result",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.ActiveSearch"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/search/artist": {
|
||||
"get": {
|
||||
"description": "Searches for artists in the database based on the query parameter",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"search"
|
||||
],
|
||||
"summary": "Searches for artists based on a query",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Limit the number of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset for pagination",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of artists",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.Artist"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/search/collections": {
|
||||
"get": {
|
||||
"description": "Searches collections in the database based on the query parameter",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"search"
|
||||
],
|
||||
"summary": "Searches collections based on a query",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Limit the number of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset for pagination",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of collections",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Collection"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/song/{hash}": {
|
||||
"get": {
|
||||
"description": "Retrieves a song using its unique hash identifier.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"songs"
|
||||
],
|
||||
"summary": "Get a song by its hash",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Song hash",
|
||||
"name": "hash",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid parameter",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Song not found",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/songs/artist": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"songs"
|
||||
],
|
||||
"summary": "Returns all the Songs of a specific Artist",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Artist Name",
|
||||
"name": "artist",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/songs/favorites": {
|
||||
"get": {
|
||||
"description": "Retrieves favorite songs filtered by a query with pagination support.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"songs"
|
||||
],
|
||||
"summary": "Get a list of favorite songs based on a query",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Limit",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid parameter",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/songs/recents": {
|
||||
"get": {
|
||||
"description": "Retrieves recent songs with pagination support.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"songs"
|
||||
],
|
||||
"summary": "Get a list of recent songs",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Limit",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"main.ActiveSearch": {
|
||||
"description": "ActiveSearch holds search results for a given artist",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"artist": {
|
||||
"type": "string",
|
||||
"example": "Ed Sheeran"
|
||||
},
|
||||
"songs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"main.Artist": {
|
||||
"description": "Artist holds search results for a given artist",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"artist": {
|
||||
"type": "string",
|
||||
"example": "Miku"
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"example": 21
|
||||
}
|
||||
}
|
||||
},
|
||||
"main.Collection": {
|
||||
"description": "Collection holds a list of songs",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"example": 15
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Best of 2023"
|
||||
},
|
||||
"songs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"main.Song": {
|
||||
"description": "Song represents a song with metadata",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"artist": {
|
||||
"type": "string",
|
||||
"example": "Ed Sheeran"
|
||||
},
|
||||
"audio": {
|
||||
"type": "string",
|
||||
"example": "audio.mp3"
|
||||
},
|
||||
"beatmap_id": {
|
||||
"type": "integer",
|
||||
"example": 123456
|
||||
},
|
||||
"creator": {
|
||||
"type": "string",
|
||||
"example": "JohnDoe"
|
||||
},
|
||||
"file": {
|
||||
"type": "string",
|
||||
"example": "beatmap.osu"
|
||||
},
|
||||
"folder": {
|
||||
"type": "string",
|
||||
"example": "osu/Songs/123456"
|
||||
},
|
||||
"image": {
|
||||
"type": "string",
|
||||
"example": "cover.jpg"
|
||||
},
|
||||
"md5_hash": {
|
||||
"type": "string",
|
||||
"example": "abcd1234efgh5678"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"example": "Shape of You"
|
||||
},
|
||||
"total_time": {
|
||||
"type": "integer",
|
||||
"example": 240
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "1.0",
|
||||
Host: "",
|
||||
BasePath: "api/v1/",
|
||||
Schemes: []string{},
|
||||
Title: "go-osu-music-hoster",
|
||||
Description: "Server Hosting ur own osu files over a simple Api",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
RightDelim: "}}",
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}
|
||||
@@ -1,661 +0,0 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "Server Hosting ur own osu files over a simple Api",
|
||||
"title": "go-osu-music-hoster",
|
||||
"contact": {},
|
||||
"version": "1.0"
|
||||
},
|
||||
"host": "/",
|
||||
"basePath": "/api/v1/",
|
||||
"paths": {
|
||||
"/audio/{filepath}": {
|
||||
"get": {
|
||||
"description": "Retrieves a song file from the server based on the provided encoded filepath",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"files"
|
||||
],
|
||||
"summary": "Retrieves a song file by its encoded path",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Base64 encoded file path",
|
||||
"name": "filepath",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The requested song file",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "File Not Found",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/collection": {
|
||||
"get": {
|
||||
"description": "Retrieves a collection of songs using the provided index.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"songs"
|
||||
],
|
||||
"summary": "Get a collection of songs by index",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Index",
|
||||
"name": "index",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Index",
|
||||
"name": "name",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid parameter",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/image/{filepath}": {
|
||||
"get": {
|
||||
"description": "Retrieves an image file from the server based on the provided encoded filepath",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"files"
|
||||
],
|
||||
"summary": "Retrieves an image file by its encoded path",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Base64 encoded file path",
|
||||
"name": "filepath",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The requested image file",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "File Not Found",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/login": {
|
||||
"get": {
|
||||
"description": "Redirects users to an external authentication page",
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Redirect to login page",
|
||||
"responses": {
|
||||
"307": {
|
||||
"description": "Temporary Redirect",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/ping": {
|
||||
"get": {
|
||||
"description": "Returns a pong response if the server is running",
|
||||
"tags": [
|
||||
"health"
|
||||
],
|
||||
"summary": "Check server health",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "pong",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/search/active": {
|
||||
"get": {
|
||||
"description": "Searches active records in the database based on the query parameter",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"search"
|
||||
],
|
||||
"summary": "Searches active records based on a query",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Limit the number of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset for pagination",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Active search result",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.ActiveSearch"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/search/artist": {
|
||||
"get": {
|
||||
"description": "Searches for artists in the database based on the query parameter",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"search"
|
||||
],
|
||||
"summary": "Searches for artists based on a query",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Limit the number of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset for pagination",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of artists",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.Artist"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/search/collections": {
|
||||
"get": {
|
||||
"description": "Searches collections in the database based on the query parameter",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"search"
|
||||
],
|
||||
"summary": "Searches collections based on a query",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Limit the number of results",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset for pagination",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of collections",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Collection"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/song/{hash}": {
|
||||
"get": {
|
||||
"description": "Retrieves a song using its unique hash identifier.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"songs"
|
||||
],
|
||||
"summary": "Get a song by its hash",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Song hash",
|
||||
"name": "hash",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid parameter",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Song not found",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/songs/artist": {
|
||||
"get": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"songs"
|
||||
],
|
||||
"summary": "Returns all the Songs of a specific Artist",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Artist Name",
|
||||
"name": "artist",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/songs/favorites": {
|
||||
"get": {
|
||||
"description": "Retrieves favorite songs filtered by a query with pagination support.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"songs"
|
||||
],
|
||||
"summary": "Get a list of favorite songs based on a query",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "query",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Limit",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid parameter",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/songs/recents": {
|
||||
"get": {
|
||||
"description": "Retrieves recent songs with pagination support.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"songs"
|
||||
],
|
||||
"summary": "Get a list of recent songs",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 10,
|
||||
"description": "Limit",
|
||||
"name": "limit",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"description": "Offset",
|
||||
"name": "offset",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"main.ActiveSearch": {
|
||||
"description": "ActiveSearch holds search results for a given artist",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"artist": {
|
||||
"type": "string",
|
||||
"example": "Ed Sheeran"
|
||||
},
|
||||
"songs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"main.Artist": {
|
||||
"description": "Artist holds search results for a given artist",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"artist": {
|
||||
"type": "string",
|
||||
"example": "Miku"
|
||||
},
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"example": 21
|
||||
}
|
||||
}
|
||||
},
|
||||
"main.Collection": {
|
||||
"description": "Collection holds a list of songs",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"example": 15
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"example": "Best of 2023"
|
||||
},
|
||||
"songs": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/main.Song"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"main.Song": {
|
||||
"description": "Song represents a song with metadata",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"artist": {
|
||||
"type": "string",
|
||||
"example": "Ed Sheeran"
|
||||
},
|
||||
"audio": {
|
||||
"type": "string",
|
||||
"example": "audio.mp3"
|
||||
},
|
||||
"beatmap_id": {
|
||||
"type": "integer",
|
||||
"example": 123456
|
||||
},
|
||||
"creator": {
|
||||
"type": "string",
|
||||
"example": "JohnDoe"
|
||||
},
|
||||
"file": {
|
||||
"type": "string",
|
||||
"example": "beatmap.osu"
|
||||
},
|
||||
"folder": {
|
||||
"type": "string",
|
||||
"example": "osu/Songs/123456"
|
||||
},
|
||||
"image": {
|
||||
"type": "string",
|
||||
"example": "cover.jpg"
|
||||
},
|
||||
"md5_hash": {
|
||||
"type": "string",
|
||||
"example": "abcd1234efgh5678"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"example": "Shape of You"
|
||||
},
|
||||
"total_time": {
|
||||
"type": "integer",
|
||||
"example": 240
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,446 +0,0 @@
|
||||
basePath: /api/v1/
|
||||
definitions:
|
||||
main.ActiveSearch:
|
||||
description: ActiveSearch holds search results for a given artist
|
||||
properties:
|
||||
artist:
|
||||
example: Ed Sheeran
|
||||
type: string
|
||||
songs:
|
||||
items:
|
||||
$ref: '#/definitions/main.Song'
|
||||
type: array
|
||||
type: object
|
||||
main.Artist:
|
||||
description: Artist holds search results for a given artist
|
||||
properties:
|
||||
artist:
|
||||
example: Miku
|
||||
type: string
|
||||
count:
|
||||
example: 21
|
||||
type: integer
|
||||
type: object
|
||||
main.Collection:
|
||||
description: Collection holds a list of songs
|
||||
properties:
|
||||
items:
|
||||
example: 15
|
||||
type: integer
|
||||
name:
|
||||
example: Best of 2023
|
||||
type: string
|
||||
songs:
|
||||
items:
|
||||
$ref: '#/definitions/main.Song'
|
||||
type: array
|
||||
type: object
|
||||
main.Song:
|
||||
description: Song represents a song with metadata
|
||||
properties:
|
||||
artist:
|
||||
example: Ed Sheeran
|
||||
type: string
|
||||
audio:
|
||||
example: audio.mp3
|
||||
type: string
|
||||
beatmap_id:
|
||||
example: 123456
|
||||
type: integer
|
||||
creator:
|
||||
example: JohnDoe
|
||||
type: string
|
||||
file:
|
||||
example: beatmap.osu
|
||||
type: string
|
||||
folder:
|
||||
example: osu/Songs/123456
|
||||
type: string
|
||||
image:
|
||||
example: cover.jpg
|
||||
type: string
|
||||
md5_hash:
|
||||
example: abcd1234efgh5678
|
||||
type: string
|
||||
title:
|
||||
example: Shape of You
|
||||
type: string
|
||||
total_time:
|
||||
example: 240
|
||||
type: integer
|
||||
type: object
|
||||
host: /
|
||||
info:
|
||||
contact: {}
|
||||
description: Server Hosting ur own osu files over a simple Api
|
||||
title: go-osu-music-hoster
|
||||
version: "1.0"
|
||||
paths:
|
||||
/audio/{filepath}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieves a song file from the server based on the provided encoded
|
||||
filepath
|
||||
parameters:
|
||||
- description: Base64 encoded file path
|
||||
in: path
|
||||
name: filepath
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The requested song file
|
||||
schema:
|
||||
type: file
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
description: File Not Found
|
||||
schema:
|
||||
type: string
|
||||
summary: Retrieves a song file by its encoded path
|
||||
tags:
|
||||
- files
|
||||
/collection:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieves a collection of songs using the provided index.
|
||||
parameters:
|
||||
- description: Index
|
||||
in: query
|
||||
name: index
|
||||
type: integer
|
||||
- description: Index
|
||||
in: query
|
||||
name: name
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/main.Song'
|
||||
type: array
|
||||
"400":
|
||||
description: Invalid parameter
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
type: string
|
||||
summary: Get a collection of songs by index
|
||||
tags:
|
||||
- songs
|
||||
/image/{filepath}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieves an image file from the server based on the provided encoded
|
||||
filepath
|
||||
parameters:
|
||||
- description: Base64 encoded file path
|
||||
in: path
|
||||
name: filepath
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The requested image file
|
||||
schema:
|
||||
type: file
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
description: File Not Found
|
||||
schema:
|
||||
type: string
|
||||
summary: Retrieves an image file by its encoded path
|
||||
tags:
|
||||
- files
|
||||
/login:
|
||||
get:
|
||||
description: Redirects users to an external authentication page
|
||||
responses:
|
||||
"307":
|
||||
description: Temporary Redirect
|
||||
schema:
|
||||
type: string
|
||||
summary: Redirect to login page
|
||||
tags:
|
||||
- auth
|
||||
/ping:
|
||||
get:
|
||||
description: Returns a pong response if the server is running
|
||||
responses:
|
||||
"200":
|
||||
description: pong
|
||||
schema:
|
||||
type: string
|
||||
summary: Check server health
|
||||
tags:
|
||||
- health
|
||||
/search/active:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Searches active records in the database based on the query parameter
|
||||
parameters:
|
||||
- description: Search query
|
||||
in: query
|
||||
name: query
|
||||
required: true
|
||||
type: string
|
||||
- default: 10
|
||||
description: Limit the number of results
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- default: 0
|
||||
description: Offset for pagination
|
||||
in: query
|
||||
name: offset
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Active search result
|
||||
schema:
|
||||
$ref: '#/definitions/main.ActiveSearch'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
type: string
|
||||
summary: Searches active records based on a query
|
||||
tags:
|
||||
- search
|
||||
/search/artist:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Searches for artists in the database based on the query parameter
|
||||
parameters:
|
||||
- description: Search query
|
||||
in: query
|
||||
name: query
|
||||
required: true
|
||||
type: string
|
||||
- default: 10
|
||||
description: Limit the number of results
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- default: 0
|
||||
description: Offset for pagination
|
||||
in: query
|
||||
name: offset
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: List of artists
|
||||
schema:
|
||||
$ref: '#/definitions/main.Artist'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
type: string
|
||||
summary: Searches for artists based on a query
|
||||
tags:
|
||||
- search
|
||||
/search/collections:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Searches collections in the database based on the query parameter
|
||||
parameters:
|
||||
- description: Search query
|
||||
in: query
|
||||
name: query
|
||||
required: true
|
||||
type: string
|
||||
- default: 10
|
||||
description: Limit the number of results
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- default: 0
|
||||
description: Offset for pagination
|
||||
in: query
|
||||
name: offset
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: List of collections
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/main.Collection'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
type: string
|
||||
summary: Searches collections based on a query
|
||||
tags:
|
||||
- search
|
||||
/song/{hash}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieves a song using its unique hash identifier.
|
||||
parameters:
|
||||
- description: Song hash
|
||||
in: path
|
||||
name: hash
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/main.Song'
|
||||
"400":
|
||||
description: Invalid parameter
|
||||
schema:
|
||||
type: string
|
||||
"404":
|
||||
description: Song not found
|
||||
schema:
|
||||
type: string
|
||||
summary: Get a song by its hash
|
||||
tags:
|
||||
- songs
|
||||
/songs/artist:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: Artist Name
|
||||
in: query
|
||||
name: artist
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/main.Song'
|
||||
type: array
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
type: string
|
||||
summary: Returns all the Songs of a specific Artist
|
||||
tags:
|
||||
- songs
|
||||
/songs/favorites:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieves favorite songs filtered by a query with pagination support.
|
||||
parameters:
|
||||
- description: Search query
|
||||
in: query
|
||||
name: query
|
||||
required: true
|
||||
type: string
|
||||
- default: 10
|
||||
description: Limit
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- default: 0
|
||||
description: Offset
|
||||
in: query
|
||||
name: offset
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/main.Song'
|
||||
type: array
|
||||
"400":
|
||||
description: Invalid parameter
|
||||
schema:
|
||||
type: string
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
type: string
|
||||
summary: Get a list of favorite songs based on a query
|
||||
tags:
|
||||
- songs
|
||||
/songs/recents:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Retrieves recent songs with pagination support.
|
||||
parameters:
|
||||
- default: 10
|
||||
description: Limit
|
||||
in: query
|
||||
name: limit
|
||||
type: integer
|
||||
- default: 0
|
||||
description: Offset
|
||||
in: query
|
||||
name: offset
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/main.Song'
|
||||
type: array
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
type: string
|
||||
summary: Get a list of recent songs
|
||||
tags:
|
||||
- songs
|
||||
swagger: "2.0"
|
||||
@@ -1,34 +0,0 @@
|
||||
module backend
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require (
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/juli0n21/go-osu-parser v0.0.8
|
||||
github.com/swaggo/http-swagger v1.3.4
|
||||
github.com/swaggo/swag v1.16.4
|
||||
modernc.org/sqlite v1.34.5
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/swaggo/files v1.0.1 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/tools v0.29.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
modernc.org/libc v1.55.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
)
|
||||
@@ -1,115 +0,0 @@
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/juli0n21/go-osu-parser v0.0.8 h1:aQtuhAniGvpUw446arhq/3aUOK9YvZEkL7aYUGlViAo=
|
||||
github.com/juli0n21/go-osu-parser v0.0.8/go.mod h1:oLLWnZReOMW4i5aNva/zvXsFqzdQigrbjyxOSs0cx+0=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww=
|
||||
github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ=
|
||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
|
||||
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
|
||||
golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
||||
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
||||
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U=
|
||||
modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g=
|
||||
modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
@@ -1,443 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
_ "backend/docs"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
httpSwagger "github.com/swaggo/http-swagger"
|
||||
|
||||
"github.com/juli0n21/go-osu-parser/parser"
|
||||
)
|
||||
|
||||
var ErrRequiredParameterNotPresent = errors.New("required parameter missing")
|
||||
var ErrFailedToParseEncoded = errors.New("invalid encoded value")
|
||||
var ErrFileNotFound = errors.New("file not found")
|
||||
|
||||
type Server struct {
|
||||
Port string
|
||||
OsuDir string
|
||||
Db *sql.DB
|
||||
OsuDb *parser.OsuDB
|
||||
Env map[string]string
|
||||
}
|
||||
|
||||
func (s *Server) registerRoutes() http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("/api/v1/ping", s.ping)
|
||||
mux.HandleFunc("/api/v1/login", s.login)
|
||||
|
||||
mux.HandleFunc("/api/v1/song/{hash}/", s.song)
|
||||
mux.HandleFunc("/api/v1/songs/recent", s.recents)
|
||||
mux.HandleFunc("/api/v1/songs/favorites", s.favorites)
|
||||
mux.HandleFunc("/api/v1/songs/artist", s.aristsSongs)
|
||||
|
||||
mux.HandleFunc("/api/v1/collection", s.collection)
|
||||
mux.HandleFunc("/api/v1/search/collections", s.collectionSearch)
|
||||
|
||||
mux.HandleFunc("/api/v1/search/active", s.activeSearch)
|
||||
mux.HandleFunc("/api/v1/search/artist", s.artistSearch)
|
||||
|
||||
mux.HandleFunc("/api/v1/audio/{filepath}", s.songFile)
|
||||
mux.HandleFunc("/api/v1/image/{filepath}", s.imageFile)
|
||||
|
||||
mux.HandleFunc("/api/v1/callback", s.callback)
|
||||
mux.Handle("/swagger/", httpSwagger.WrapHandler)
|
||||
|
||||
return corsMiddleware(logRequests(mux))
|
||||
}
|
||||
|
||||
func run(s *Server) {
|
||||
mux := s.registerRoutes()
|
||||
fmt.Println("starting server on http://localhost" + s.Port)
|
||||
log.Fatal(http.ListenAndServe(s.Port, mux))
|
||||
}
|
||||
|
||||
func logRequests(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s %s %s", r.RemoteAddr, r.Method, r.URL.Path)
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func corsMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// ping godoc
|
||||
//
|
||||
// @Summary Check server health
|
||||
// @Description Returns a pong response if the server is running
|
||||
// @Tags health
|
||||
// @Success 200 {string} string "pong"
|
||||
// @Router /ping [get]
|
||||
func (s *Server) ping(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "pong")
|
||||
}
|
||||
|
||||
// login godoc
|
||||
//
|
||||
// @Summary Redirect to login page
|
||||
// @Description Redirects users to an external authentication page
|
||||
// @Tags auth
|
||||
// @Success 307 {string} string "Temporary Redirect"
|
||||
// @Router /login [get]
|
||||
func (s *Server) login(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "https://proxy.illegalesachen.download/login", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
// song godoc
|
||||
//
|
||||
// @Summary Get a song by its hash
|
||||
// @Description Retrieves a song using its unique hash identifier.
|
||||
// @Tags songs
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param hash path string true "Song hash"
|
||||
// @Success 200 {object} Song
|
||||
// @Failure 400 {string} string "Invalid parameter"
|
||||
// @Failure 404 {string} string "Song not found"
|
||||
// @Router /song/{hash} [get]
|
||||
func (s *Server) song(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
hash := r.PathValue("hash")
|
||||
if hash == "" {
|
||||
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
song, err := getSong(s.Db, hash)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, "beatmap not found by hash", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, song, http.StatusOK)
|
||||
}
|
||||
|
||||
// recents godoc
|
||||
//
|
||||
// @Summary Get a list of recent songs
|
||||
// @Description Retrieves recent songs with pagination support.
|
||||
// @Tags songs
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param limit query int false "Limit" default(10)
|
||||
// @Param offset query int false "Offset" default(0)
|
||||
// @Success 200 {array} Song
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /songs/recents [get]
|
||||
func (s *Server) recents(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
limit, offset := pagination(r)
|
||||
recent, err := getRecent(s.Db, limit, offset)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, recent, http.StatusOK)
|
||||
}
|
||||
|
||||
// favorites godoc
|
||||
//
|
||||
// @Summary Get a list of favorite songs based on a query
|
||||
// @Description Retrieves favorite songs filtered by a query with pagination support.
|
||||
// @Tags songs
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param query query string true "Search query"
|
||||
// @Param limit query int false "Limit" default(10)
|
||||
// @Param offset query int false "Offset" default(0)
|
||||
// @Success 200 {array} Song
|
||||
// @Failure 400 {string} string "Invalid parameter"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /songs/favorites [get]
|
||||
func (s *Server) favorites(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query().Get("query")
|
||||
if query == "" {
|
||||
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
limit, offset := pagination(r)
|
||||
|
||||
favorites, err := getFavorites(s.Db, query, limit, offset)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, favorites, http.StatusOK)
|
||||
}
|
||||
|
||||
// collection godoc
|
||||
//
|
||||
// @Summary Get a collection of songs by index
|
||||
// @Description Retrieves a collection of songs using the provided index.
|
||||
// @Tags songs
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param index query int false "Index"
|
||||
// @Param name query string false "Index"
|
||||
// @Success 200 {array} Song
|
||||
// @Failure 400 {string} string "Invalid parameter"
|
||||
// @Failure 500 {string} string "Internal server error"
|
||||
// @Router /collection [get]
|
||||
func (s *Server) collection(w http.ResponseWriter, r *http.Request) {
|
||||
limit, offset := pagination(r)
|
||||
name := r.URL.Query().Get("name")
|
||||
if name != "" {
|
||||
c, err := getCollectionByName(s.Db, limit, offset, name)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, c, http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
index, err := strconv.Atoi(r.URL.Query().Get("index"))
|
||||
if err != nil {
|
||||
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
|
||||
return
|
||||
} else {
|
||||
c, err := getCollection(s.Db, limit, offset, index)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, c, http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// @Summary Searches collections based on a query
|
||||
// @Description Searches collections in the database based on the query parameter
|
||||
// @Tags search
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param query query string true "Search query"
|
||||
// @Param limit query int false "Limit the number of results" default(10)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Success 200 {array} Collection "List of collections"
|
||||
// @Failure 400 {object} string "Bad Request"
|
||||
// @Failure 500 {object} string "Internal Server Error"
|
||||
// @Router /search/collections [get]
|
||||
func (s *Server) collectionSearch(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query().Get("query")
|
||||
|
||||
limit, offset := pagination(r)
|
||||
|
||||
preview, err := getCollections(s.Db, q, limit, offset)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, preview, http.StatusOK)
|
||||
}
|
||||
|
||||
// @Summary Returns all the Songs of a specific Artist
|
||||
// @Tags songs
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param artist query string true "Artist Name"
|
||||
// @Success 200 {array} Song
|
||||
// @Failure 400 {object} string "Bad Request"
|
||||
// @Failure 500 {object} string "Internal Server Error"
|
||||
// @Router /songs/artist [get]
|
||||
func (s *Server) aristsSongs(w http.ResponseWriter, r *http.Request) {
|
||||
artist := r.URL.Query().Get("artist")
|
||||
if artist == "" {
|
||||
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, []Song{}, http.StatusOK)
|
||||
}
|
||||
|
||||
// @Summary Searches active records based on a query
|
||||
// @Description Searches active records in the database based on the query parameter
|
||||
// @Tags search
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param query query string true "Search query"
|
||||
// @Param limit query int false "Limit the number of results" default(10)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Success 200 {object} ActiveSearch "Active search result"
|
||||
// @Failure 400 {object} string "Bad Request"
|
||||
// @Failure 500 {object} string "Internal Server Error"
|
||||
// @Router /search/active [get]
|
||||
func (s *Server) activeSearch(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query().Get("query")
|
||||
if q == "" {
|
||||
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
//TODO
|
||||
limit, offset := pagination(r)
|
||||
|
||||
recent, err := getSearch(s.Db, q, limit, offset)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, recent, http.StatusOK)
|
||||
}
|
||||
|
||||
// @Summary Searches for artists based on a query
|
||||
// @Description Searches for artists in the database based on the query parameter
|
||||
// @Tags search
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param query query string true "Search query"
|
||||
// @Param limit query int false "Limit the number of results" default(10)
|
||||
// @Param offset query int false "Offset for pagination" default(0)
|
||||
// @Success 200 {object} Artist "List of artists"
|
||||
// @Failure 400 {object} string "Bad Request"
|
||||
// @Failure 500 {object} string "Internal Server Error"
|
||||
// @Router /search/artist [get]
|
||||
func (s *Server) artistSearch(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query().Get("query")
|
||||
if q == "" {
|
||||
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
//TODO
|
||||
limit, offset := pagination(r)
|
||||
|
||||
a, err := getArtists(s.Db, q, limit, offset)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
writeJSON(w, a, http.StatusOK)
|
||||
}
|
||||
|
||||
// @Summary Retrieves a song file by its encoded path
|
||||
// @Description Retrieves a song file from the server based on the provided encoded filepath
|
||||
// @Tags files
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param filepath path string true "Base64 encoded file path"
|
||||
// @Success 200 {file} File "The requested song file"
|
||||
// @Failure 400 {object} string "Bad Request"
|
||||
// @Failure 404 {object} string "File Not Found"
|
||||
// @Router /audio/{filepath} [get]
|
||||
func (s *Server) songFile(w http.ResponseWriter, r *http.Request) {
|
||||
f := r.PathValue("filepath")
|
||||
if f == "" {
|
||||
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
filename, err := base64.RawStdEncoding.DecodeString(f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(s.OsuDir + "Songs/" + string(filename))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, ErrFileNotFound.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, ErrFileNotFound.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeContent(w, r, stat.Name(), stat.ModTime(), file)
|
||||
}
|
||||
|
||||
// @Summary Retrieves an image file by its encoded path
|
||||
// @Description Retrieves an image file from the server based on the provided encoded filepath
|
||||
// @Tags files
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param filepath path string true "Base64 encoded file path"
|
||||
// @Success 200 {file} File "The requested image file"
|
||||
// @Failure 400 {object} string "Bad Request"
|
||||
// @Failure 404 {object} string "File Not Found"
|
||||
// @Router /image/{filepath} [get]
|
||||
func (s *Server) imageFile(w http.ResponseWriter, r *http.Request) {
|
||||
f := r.PathValue("filepath")
|
||||
if f == "" {
|
||||
http.Error(w, ErrRequiredParameterNotPresent.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
filename, err := base64.RawStdEncoding.DecodeString(f)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, ErrFailedToParseEncoded.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, s.OsuDir+"Songs/"+string(filename))
|
||||
}
|
||||
|
||||
func (s *Server) callback(w http.ResponseWriter, r *http.Request) {
|
||||
cookie := r.URL.Query().Get("COOKIE")
|
||||
|
||||
if cookie != "" {
|
||||
s.Env["COOKIE"] = cookie
|
||||
godotenv.Write(s.Env, ".env")
|
||||
}
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, v any, status int) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
if err := json.NewEncoder(w).Encode(v); err != nil {
|
||||
fmt.Println(err)
|
||||
http.Error(w, "Failed to encode JSON response", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
func pagination(r *http.Request) (int, int) {
|
||||
limit, err := strconv.Atoi(r.URL.Query().Get("limit"))
|
||||
if err != nil || limit <= 0 || limit > 100 {
|
||||
limit = 100
|
||||
}
|
||||
offset, err := strconv.Atoi(r.URL.Query().Get("offset"))
|
||||
if err != nil || offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
return limit, offset
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/juli0n21/go-osu-parser/parser"
|
||||
)
|
||||
|
||||
// @title go-osu-music-hoster
|
||||
// @version 1.0
|
||||
// @description Server Hosting ur own osu files over a simple Api
|
||||
|
||||
// @host /
|
||||
// @BasePath /api/v1/
|
||||
func main() {
|
||||
envMap, err := godotenv.Read(".env")
|
||||
if err != nil {
|
||||
fmt.Println("Error reading .env file")
|
||||
}
|
||||
|
||||
if envMap["OSU_PATH"] == "" {
|
||||
fmt.Println("Osu Path not found! Please paste the full path to your osu! folder.")
|
||||
fmt.Println("Osu Path not found pls paste the full Path to ur osu! folder \n it should start with 'C://' and can be opened from the Settings menu!)\n path: ")
|
||||
|
||||
fmt.Scanln(&osuRoot)
|
||||
osuRoot = strings.TrimSpace(osuRoot)
|
||||
|
||||
envMap["OSU_PATH"] = osuRoot
|
||||
godotenv.Write(envMap, ".env")
|
||||
}
|
||||
|
||||
osuRoot := envMap["OSU_PATH"]
|
||||
cookie := envMap["COOKIE"]
|
||||
port := GetEnv(envMap["PORT"], ":8080")
|
||||
filename := path.Join(osuRoot, "osu!.db")
|
||||
|
||||
osuDb, err := parser.ParseOsuDB(filename)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if cookie == "" {
|
||||
fmt.Println("No Authentication found please follow the link to log in!\n http://proxy.illegalesachen.download/login")
|
||||
}
|
||||
|
||||
url, err := StartCloudflared(port)
|
||||
if err != nil {
|
||||
log.Fatalf("Cloudflared service couldnt be started: %v", err)
|
||||
}
|
||||
|
||||
if err = sendUrl(url, cookie); err != nil {
|
||||
log.Fatalf("Couldnt Update Endpoint url with Proxy: %v", err)
|
||||
}
|
||||
|
||||
db, err := initDB("./data/music.db", osuDb, osuRoot)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
s := &Server{
|
||||
Port: port,
|
||||
Db: db,
|
||||
OsuDir: osuRoot,
|
||||
Env: envMap,
|
||||
}
|
||||
|
||||
run(s)
|
||||
}
|
||||
|
||||
func GetEnv(key, fallback string) string {
|
||||
if value, ok := os.LookupEnv(key); ok && value != "" {
|
||||
return value
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
func StartCloudflared(port string) (string, error) {
|
||||
cmd := exec.Command("cloudflared", "tunnel", "--url", fmt.Sprintf("http://localhost%s", port))
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error creating StderrPipe: %v", err)
|
||||
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return "", fmt.Errorf("Error starting command: %v", err)
|
||||
|
||||
}
|
||||
|
||||
stderrScanner := bufio.NewScanner(stderr)
|
||||
|
||||
urlRegex := regexp.MustCompile(`https?://[\w.-]+\.trycloudflare\.com`)
|
||||
|
||||
for stderrScanner.Scan() {
|
||||
line := stderrScanner.Text()
|
||||
if url := urlRegex.FindString(line); url != "" {
|
||||
fmt.Println("Found URL:", url)
|
||||
return url, nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
return "", fmt.Errorf("Error waiting for command: %v", err)
|
||||
}
|
||||
|
||||
if err := stderrScanner.Err(); err != nil {
|
||||
return "", fmt.Errorf("Error reading stderr: %v", err)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no url found")
|
||||
}
|
||||
|
||||
func sendUrl(endpoint, cookie string) error {
|
||||
url := GetEnv("PROXY_URL", "https://proxy.illegalesachen.download/settings")
|
||||
|
||||
payload := struct {
|
||||
Sharing *bool `json:"sharing"`
|
||||
Endpoint string `json:"endpoint"`
|
||||
}{
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
body, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error marshalling payload: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %v", err)
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "session_cookie",
|
||||
Value: cookie,
|
||||
})
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error sending request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Error in request: %s", resp.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package main
|
||||
|
||||
// Song represents a song entity
|
||||
// @Description Song represents a song with metadata
|
||||
type Song struct {
|
||||
BeatmapID int `json:"beatmap_id" example:"123456"`
|
||||
MD5Hash string `json:"md5_hash" example:"abcd1234efgh5678"`
|
||||
Title string `json:"title" example:"Shape of You"`
|
||||
Artist string `json:"artist" example:"Ed Sheeran"`
|
||||
Creator string `json:"creator" example:"JohnDoe"`
|
||||
Folder string `json:"folder" example:"osu/Songs/123456"`
|
||||
File string `json:"file" example:"beatmap.osu"`
|
||||
Audio string `json:"audio" example:"audio.mp3"`
|
||||
TotalTime int64 `json:"total_time" example:"240"`
|
||||
Image string `json:"image" example:"cover.jpg"`
|
||||
}
|
||||
|
||||
// CollectionPreview represents a preview of a song collection
|
||||
// @Description CollectionPreview contains summary data of a song collection
|
||||
type CollectionPreview struct {
|
||||
Name string `json:"name" example:"Collection Name"`
|
||||
Image string `json:"image" example:"cover.jpg"`
|
||||
Items int `json:"items" example:"10"`
|
||||
}
|
||||
|
||||
// Collection represents a full song collection
|
||||
// @Description Collection holds a list of songs
|
||||
type Collection struct {
|
||||
Name string `json:"name" example:"Best of 2023"`
|
||||
Items int `json:"items" example:"15"`
|
||||
Songs []Song `json:"songs"`
|
||||
}
|
||||
|
||||
// ActiveSearch represents an active song search query
|
||||
// @Description ActiveSearch holds search results for a given artist
|
||||
type ActiveSearch struct {
|
||||
Artist string `json:"artist" example:"Ed Sheeran"`
|
||||
Songs []Song `json:"songs"`
|
||||
}
|
||||
|
||||
// Artist represents an active song search query
|
||||
// @Description Artist holds search results for a given artist
|
||||
type Artist struct {
|
||||
Artist string `json:"artist" example:"Miku"`
|
||||
Count int `json:"count" example:"21"`
|
||||
}
|
||||
Reference in New Issue
Block a user