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