Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e0cd411df | ||
|
|
bd3d592bdf | ||
|
|
189c6ae1c7 | ||
|
|
b4fa3aa04d | ||
|
|
08624052aa | ||
|
|
1a3a2aef86 | ||
|
|
cbd6ae9aa1 | ||
|
|
9c5edd1e44 | ||
|
|
ec11b04247 | ||
|
|
2cb255b538 | ||
|
|
230eb35cf2 | ||
|
|
c43b6f2e54 | ||
|
|
a6f61a3c5b | ||
|
|
49347e463a | ||
|
|
512729398b | ||
|
|
3f7b6d10f9 | ||
|
|
c404582c03 | ||
|
|
e1c3f6e7c5 | ||
|
|
4fcd168a85 | ||
|
|
f6c6d96c02 | ||
|
|
0733965540 | ||
|
|
028e8dd9f3 | ||
|
|
97f891132b | ||
|
|
61af47d651 | ||
|
|
c3b63a61cc | ||
|
|
b6a1d4aeaf | ||
|
|
6c512c78aa | ||
|
|
0aee5ca021 | ||
|
|
e863a8a088 | ||
|
|
58632e29cb | ||
|
|
e560809c25 | ||
|
|
a0344903ec | ||
|
|
9ecdc1b040 | ||
|
|
4a480a312e | ||
|
|
f00bff5f64 | ||
|
|
25f528c86d | ||
|
|
21e30b0fb2 | ||
|
|
07d8e15925 | ||
|
|
7609159f60 | ||
|
|
c2884ac9ce | ||
|
|
40010c9860 | ||
|
|
d97a665a9c | ||
|
|
39da976ef3 | ||
|
|
aecc0731bc | ||
|
|
751af50e8a | ||
|
|
005ac8634f | ||
|
|
055dd3b43b | ||
|
|
4a4eda09d1 | ||
|
|
e647bc7811 | ||
|
|
bf7c969629 | ||
|
|
6706abfc97 | ||
|
|
ed10e7bf80 | ||
|
|
73935be1ba | ||
|
|
c3494e6169 | ||
|
|
df9c5ba4c0 | ||
|
|
d3e22eefcb | ||
|
|
29b3ba9192 | ||
|
|
32eda86f4f | ||
|
|
b05c8570a1 |
@@ -2,3 +2,6 @@
|
|||||||
*.duckdb
|
*.duckdb
|
||||||
.env
|
.env
|
||||||
__pycache__
|
__pycache__
|
||||||
|
|
||||||
|
|
||||||
|
src/data/
|
||||||
@@ -13,3 +13,10 @@ wheels/
|
|||||||
*.sqlite
|
*.sqlite
|
||||||
*.duckdb
|
*.duckdb
|
||||||
*.db
|
*.db
|
||||||
|
*.log
|
||||||
|
*.gz
|
||||||
|
|
||||||
|
data
|
||||||
|
|
||||||
|
.vscode/*
|
||||||
|
*.tar
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
# Eurovision 25 Backend Changelog
|
||||||
|
|
||||||
|
## 1.0 (2025-05-18)
|
||||||
|
|
||||||
|
This marks the first stable release of the Eurovision 25 Homereview Backend, moving out of release candidates.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Fix results not showing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0rc8 (2025-05-17)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Add more result types (b4fa3aa)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Fix regression in db seeding (0862405)
|
||||||
|
|
||||||
|
### Chores
|
||||||
|
- Add docker exports to git ignore (1a3a2ae)
|
||||||
|
|
||||||
|
|
||||||
|
## 1.0rc7 (2025-05-16)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Added artist image URL to song endpoint
|
||||||
|
|
||||||
|
## 1.0rc6 (2025-05-16)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Added ability for users to change their password and avatar image
|
||||||
|
|
||||||
|
## 1.0rc5 (2025-05-16)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Updated dependencies to latest versions
|
||||||
|
- Changed API documentation for security reasons
|
||||||
|
|
||||||
|
## 1.0rc4 (2025-05-16)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Added user profile update functionality allowing users to edit their personal information
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Changed song seed values to use official and verified data
|
||||||
|
|
||||||
|
## 1.0rc3 (2025-05-16)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Added contest table and routes
|
||||||
|
- Added result endpoints with artist information
|
||||||
|
- Created result views in database schema
|
||||||
|
- Added avatar images for users
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
- Fixed data directory copied from dev environment causing build errors
|
||||||
|
- Fixed error when no reviews exist
|
||||||
|
- Fixed container not starting if data directory did not exist
|
||||||
|
- Fixed review PUT failing when no previous review exists
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Updated application version to 1.0rc3
|
||||||
|
- Merged changelog files for better version tracking
|
||||||
|
- Added more comprehensive logging
|
||||||
|
- Added gitignore entries
|
||||||
|
|
||||||
|
## 1.0rc2 (2025-05-10)
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
- Updated application version to 1.0rc2
|
||||||
|
- Added more comprehensive logging throughout the application
|
||||||
|
|
||||||
|
## 1.0rc1 (2025-05-01)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Initial release candidate
|
||||||
|
- Eurovision 25 Homereview API implementation
|
||||||
|
- Authentication and authorization system
|
||||||
|
- User management features
|
||||||
|
- Song management and reviews
|
||||||
|
- Review system
|
||||||
|
- Results calculation and display
|
||||||
|
- Admin functionality
|
||||||
|
- Static file serving for avatars and country flags
|
||||||
|
|
||||||
|
The application is feature complete, but may still contain bugs that will be addressed before the final 1.0.0 release.
|
||||||
|
|
||||||
|
## Planned for 1.0.0 (Final Release)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Additional security improvements and bug fixes
|
||||||
@@ -6,10 +6,13 @@ WORKDIR /app
|
|||||||
COPY pyproject.toml uv.lock /app/
|
COPY pyproject.toml uv.lock /app/
|
||||||
RUN uv sync --frozen --no-install-project --no-dev
|
RUN uv sync --frozen --no-install-project --no-dev
|
||||||
COPY src /app
|
COPY src /app
|
||||||
|
# Ensure data directory exists but don't copy from host
|
||||||
RUN uv sync --frozen --no-dev
|
RUN uv sync --frozen --no-dev
|
||||||
|
|
||||||
FROM base
|
FROM base
|
||||||
COPY --from=builder /app /app
|
COPY --from=builder /app /app
|
||||||
ENV PATH="/app/.venv/bin:$PATH"
|
ENV PATH="/app/.venv/bin:$PATH"
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
# Create data directory
|
||||||
|
RUN mkdir -p /app/data && chmod 755 /app/data
|
||||||
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
# Eurovision 25 Homereview Backend
|
# Eurovision 25 Homereview Backend
|
||||||
|
|
||||||
|
**Version: 1.0**
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
This project provides the backend API for the "Eurovision 25 Homereview" application. It allows a small, known group of users, organized into households/groups, to collaboratively review and score Eurovision Song Contest entries during the live show. Users submit numerical scores (1-100) for stage show, wardrobe, and song quality, along with optional text comments. Users can update their scores until the contest is marked as finished by an admin. Afterward, users can view aggregated results within their group or across all participants, including basic statistics.
|
This project provides the backend API for the "Eurovision 25 Homereview" application. It allows a small, known group of users, organized into households/groups, to collaboratively review and score Eurovision Song Contest entries during the live show. Users submit numerical scores (1-100) for stage show, wardrobe, and song quality, along with optional text comments. Users can update their scores until the contest is marked as finished by an admin. Afterward, users can view aggregated results within their group or across all participants, including basic statistics.
|
||||||
@@ -217,6 +219,15 @@ Automated tests are not implemented at this time. Manual testing via the API doc
|
|||||||
* **Static Files:** The production deployment **must** include configuring the web server (like Nginx running as a reverse proxy or alongside) to efficiently serve the static files (avatars) from their directory (e.g., `/var/www/html/static/avatars` or similar) under the expected URL path (e.g., `/static/avatars/`). This is more performant than serving via Python in production.
|
* **Static Files:** The production deployment **must** include configuring the web server (like Nginx running as a reverse proxy or alongside) to efficiently serve the static files (avatars) from their directory (e.g., `/var/www/html/static/avatars` or similar) under the expected URL path (e.g., `/static/avatars/`). This is more performant than serving via Python in production.
|
||||||
* **Source Code:** The container build process needs to ensure the `src` directory contents are correctly copied into the container image.
|
* **Source Code:** The container build process needs to ensure the `src` directory contents are correctly copied into the container image.
|
||||||
|
|
||||||
|
## TODOs
|
||||||
|
|
||||||
|
* [ ] Replace password hashing with passlib
|
||||||
|
* [ ] Switch to PostgreSQL from DuckDB
|
||||||
|
* [x] Implement more comprehensive logging
|
||||||
|
* [ ] Implement user disabling functionality
|
||||||
|
* [x] Implement user profile update functionality (name, email, password, avatar, etc.)
|
||||||
|
* [x] Fix avatar upload size validation
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
This is primarily a personal project. Contributions are generally not expected. If you find a bug, feel free to open an issue in the repository.
|
This is primarily a personal project. Contributions are generally not expected. If you find a bug, feel free to open an issue in the repository.
|
||||||
@@ -224,3 +235,4 @@ This is primarily a personal project. Contributions are generally not expected.
|
|||||||
## License
|
## License
|
||||||
|
|
||||||
This project is licensed under the **MIT License**. See the `LICENSE` file for details.
|
This project is licensed under the **MIT License**. See the `LICENSE` file for details.
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
|
# This compose file can be used with either Docker or Podman
|
||||||
|
# For Podman: podman-compose up -d
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
image: eurovision-25-backend:1.0
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- ADMIN_USERNAME=admin
|
||||||
|
- ADMIN_PASSWORD=password
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Version update
|
||||||
|
|
||||||
|
Here is the workflow for updating the version of the project.
|
||||||
|
|
||||||
|
1. Update version in `pyproject.toml`
|
||||||
|
2. Update version in `uv.lock`
|
||||||
|
3. Update version in `src/app.py`
|
||||||
|
4. Update version in `docker-compose.yml`
|
||||||
|
5. Update `CHANGES.md`. Take info from git log
|
||||||
|
6. Update `README.md`
|
||||||
|
7. Commit changes
|
||||||
|
8. Tag release
|
||||||
|
9. Push changes
|
||||||
|
10. Export docker image to tar file. File name should be `eurovision-25-backend-{version}.tar`
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "Eurovision-25-backend"
|
name = "Eurovision-25-backend"
|
||||||
version = "0.1.0"
|
version = "1.0"
|
||||||
description = "Backend for Eurovision 25"
|
description = "Backend for Eurovision 25"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
@@ -8,6 +8,7 @@ dependencies = [
|
|||||||
"aiofiles>=24.1.0",
|
"aiofiles>=24.1.0",
|
||||||
"duckdb>=1.2.2",
|
"duckdb>=1.2.2",
|
||||||
"fastapi>=0.115.12",
|
"fastapi>=0.115.12",
|
||||||
|
"loguru>=0.7.3",
|
||||||
"pandas>=2.2.3",
|
"pandas>=2.2.3",
|
||||||
"passlib[bcrypt]>=1.7.4",
|
"passlib[bcrypt]>=1.7.4",
|
||||||
"python-dotenv>=1.1.0",
|
"python-dotenv>=1.1.0",
|
||||||
|
|||||||
@@ -3,17 +3,30 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
# Import routers
|
# Import routers
|
||||||
from routes import auth, admin, users, songs, reviews, results
|
from routes import auth, admin, users, songs, reviews, results, contest
|
||||||
|
|
||||||
|
from lib.logger import logger
|
||||||
from lib.db import init_db
|
from lib.db import init_db
|
||||||
|
|
||||||
# Initialize FastAPI app
|
# Initialize FastAPI app
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Eurovision 25 Homereview API",
|
title="Eurovision 25 Homereview API",
|
||||||
description="Backend API for Eurovision 25 Homereview application",
|
description="Backend API for Eurovision 25 Homereview application",
|
||||||
version="1.0.0",
|
version="1.0",
|
||||||
|
openapi_url="/sec_schema.json",
|
||||||
|
docs_url="/docut",
|
||||||
|
redoc_url=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.middleware("http")
|
||||||
|
async def log_requests(request, call_next):
|
||||||
|
body = await request.body()
|
||||||
|
logger.debug(f"Request body: {body.decode('utf-8')}")
|
||||||
|
response = await call_next(request)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
# Add CORS middleware
|
# Add CORS middleware
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
@@ -32,12 +45,14 @@ app.mount("/static/flags", StaticFiles(directory="static/flags"), name="flags")
|
|||||||
# Startup event to create database tables
|
# Startup event to create database tables
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
def on_startup():
|
def on_startup():
|
||||||
|
logger.info("Starting up...")
|
||||||
init_db()
|
init_db()
|
||||||
|
|
||||||
|
|
||||||
# Health check endpoint
|
# Health check endpoint
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
|
logger.debug("Root endpoint accessed")
|
||||||
return {"status": "ok", "message": "Eurovision 25 Homereview API"}
|
return {"status": "ok", "message": "Eurovision 25 Homereview API"}
|
||||||
|
|
||||||
|
|
||||||
@@ -48,3 +63,4 @@ app.include_router(users.router)
|
|||||||
app.include_router(songs.router)
|
app.include_router(songs.router)
|
||||||
app.include_router(reviews.router)
|
app.include_router(reviews.router)
|
||||||
app.include_router(results.router)
|
app.include_router(results.router)
|
||||||
|
app.include_router(contest.router)
|
||||||
|
|||||||
@@ -181,7 +181,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name_en": "United Kingdom",
|
"name_en": "United Kingdom",
|
||||||
"name_fi": "Yhdistynyt kuningaskunta",
|
"name_fi": "Englanti",
|
||||||
"code": "GBR"
|
"code": "GBR"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -8,13 +8,19 @@
|
|||||||
DROP TABLE IF EXISTS Review;
|
DROP TABLE IF EXISTS Review;
|
||||||
DROP TABLE IF EXISTS Song;
|
DROP TABLE IF EXISTS Song;
|
||||||
DROP TABLE IF EXISTS "User"; -- Quoted because USER is a reserved keyword
|
DROP TABLE IF EXISTS "User"; -- Quoted because USER is a reserved keyword
|
||||||
DROP TABLE IF EXISTS "Group"; -- Quoted because GROUP is a reserved keyword
|
DROP TABLE IF EXISTS Team;
|
||||||
|
DROP TABLE IF EXISTS Contest;
|
||||||
DROP TABLE IF EXISTS CountryCodes;
|
DROP TABLE IF EXISTS CountryCodes;
|
||||||
|
DROP VIEW IF EXISTS ReviewSummaryGlobal;
|
||||||
|
DROP VIEW IF EXISTS ReviewSummaryByTeam;
|
||||||
|
DROP VIEW IF EXISTS ReviewSummaryByUser;
|
||||||
|
DROP VIEW IF EXISTS ReviewAllSongs;
|
||||||
|
|
||||||
DROP SEQUENCE IF EXISTS group_id_seq;
|
DROP SEQUENCE IF EXISTS group_id_seq;
|
||||||
DROP SEQUENCE IF EXISTS user_id_seq;
|
DROP SEQUENCE IF EXISTS user_id_seq;
|
||||||
DROP SEQUENCE IF EXISTS song_id_seq;
|
DROP SEQUENCE IF EXISTS song_id_seq;
|
||||||
DROP SEQUENCE IF EXISTS review_id_seq;
|
DROP SEQUENCE IF EXISTS review_id_seq;
|
||||||
|
DROP SEQUENCE IF EXISTS contest_id_seq;
|
||||||
|
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
-- Sequences for Primary Keys
|
-- Sequences for Primary Keys
|
||||||
@@ -23,12 +29,29 @@ CREATE SEQUENCE group_id_seq START 1;
|
|||||||
CREATE SEQUENCE user_id_seq START 1;
|
CREATE SEQUENCE user_id_seq START 1;
|
||||||
CREATE SEQUENCE song_id_seq START 1;
|
CREATE SEQUENCE song_id_seq START 1;
|
||||||
CREATE SEQUENCE review_id_seq START 1;
|
CREATE SEQUENCE review_id_seq START 1;
|
||||||
|
CREATE SEQUENCE contest_id_seq START 1;
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Table: Contest
|
||||||
|
-- Stores information about Eurovision contests by year.
|
||||||
|
-- =============================================================================
|
||||||
|
CREATE TABLE Contest (
|
||||||
|
id INTEGER PRIMARY KEY DEFAULT nextval('contest_id_seq'), -- Use sequence for auto-increment
|
||||||
|
year INTEGER DEFAULT EXTRACT(YEAR FROM CURRENT_DATE) NOT NULL, -- The year of the Eurovision contest
|
||||||
|
is_active BOOLEAN DEFAULT TRUE NOT NULL, -- Flag indicating if this is the active contest
|
||||||
|
finals_date DATE NOT NULL, -- Date of the finals
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the contest was created
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Timestamp when the contest was last updated
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Add index for faster lookups by year
|
||||||
|
CREATE INDEX idx_contest_year ON Contest (year);
|
||||||
|
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
-- Table: Group
|
-- Table: Group
|
||||||
-- Stores information about the different households or groups participating.
|
-- Stores information about the different households or groups participating.
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
CREATE TABLE "Group" (
|
CREATE TABLE "Team" (
|
||||||
id INTEGER PRIMARY KEY DEFAULT nextval('group_id_seq'), -- Use sequence for auto-increment
|
id INTEGER PRIMARY KEY DEFAULT nextval('group_id_seq'), -- Use sequence for auto-increment
|
||||||
name VARCHAR UNIQUE NOT NULL, -- The name of the household/group
|
name VARCHAR UNIQUE NOT NULL, -- The name of the household/group
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the group was created
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the group was created
|
||||||
@@ -42,16 +65,19 @@ CREATE TABLE "Group" (
|
|||||||
CREATE TABLE "User" (
|
CREATE TABLE "User" (
|
||||||
id INTEGER PRIMARY KEY DEFAULT nextval('user_id_seq'), -- Use sequence for auto-increment
|
id INTEGER PRIMARY KEY DEFAULT nextval('user_id_seq'), -- Use sequence for auto-increment
|
||||||
username VARCHAR UNIQUE NOT NULL, -- The user's login name
|
username VARCHAR UNIQUE NOT NULL, -- The user's login name
|
||||||
|
first_name VARCHAR DEFAULT '',
|
||||||
|
last_name VARCHAR DEFAULT '',
|
||||||
hashed_password VARCHAR NOT NULL, -- The securely hashed password
|
hashed_password VARCHAR NOT NULL, -- The securely hashed password
|
||||||
email VARCHAR UNIQUE, -- User's email address (nullable)
|
email VARCHAR UNIQUE, -- User's email address (nullable)
|
||||||
group_id INTEGER NOT NULL, -- Foreign Key -> Group.id
|
team_id INTEGER NOT NULL, -- Foreign Key -> Group.id
|
||||||
avatar_id INTEGER DEFAULT 0 NOT NULL, -- Identifier for the user's avatar
|
avatar_id INTEGER DEFAULT 0 NOT NULL, -- Identifier for the user's avatar
|
||||||
is_active BOOLEAN DEFAULT TRUE NOT NULL, -- Flag indicating if the user account is active
|
is_active BOOLEAN DEFAULT TRUE NOT NULL, -- Flag indicating if the user account is active
|
||||||
is_admin BOOLEAN DEFAULT FALSE NOT NULL, -- Flag indicating if the user has admin privileges
|
is_admin BOOLEAN DEFAULT FALSE NOT NULL, -- Flag indicating if the user has admin privileges
|
||||||
|
last_login TIMESTAMP DEFAULT '1970-01-01 00:00:00', -- Timestamp when the user was last logged in. Epoch (1970-01-01) means "never logged in"
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the user was created
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the user was created
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the user was last updated
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the user was last updated
|
||||||
|
|
||||||
FOREIGN KEY (group_id) REFERENCES "Group"(id) -- Link to the Group table
|
FOREIGN KEY (team_id) REFERENCES "Team"(id) -- Link to the Team table
|
||||||
);
|
);
|
||||||
|
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
@@ -65,6 +91,7 @@ CREATE TABLE Song (
|
|||||||
artist VARCHAR NOT NULL, -- The name of the performing artist(s)
|
artist VARCHAR NOT NULL, -- The name of the performing artist(s)
|
||||||
title VARCHAR NOT NULL, -- The title of the song
|
title VARCHAR NOT NULL, -- The title of the song
|
||||||
running_order INTEGER NOT NULL, -- Official order in the show (nullable)
|
running_order INTEGER NOT NULL, -- Official order in the show (nullable)
|
||||||
|
img_url VARCHAR, -- URL to the artist image (nullable)
|
||||||
lyrics_url VARCHAR, -- URL to the original lyrics (nullable)
|
lyrics_url VARCHAR, -- URL to the original lyrics (nullable)
|
||||||
artist_url VARCHAR, -- URL to the artist's page (nullable)
|
artist_url VARCHAR, -- URL to the artist's page (nullable)
|
||||||
flag_url VARCHAR, -- URL to the country flag (nullable)
|
flag_url VARCHAR, -- URL to the country flag (nullable)
|
||||||
@@ -90,7 +117,7 @@ CREATE TABLE Review (
|
|||||||
song_id INTEGER NOT NULL, -- Foreign Key -> Song.id
|
song_id INTEGER NOT NULL, -- Foreign Key -> Song.id
|
||||||
score_song INTEGER NOT NULL, -- Score (1-100) for song quality
|
score_song INTEGER NOT NULL, -- Score (1-100) for song quality
|
||||||
score_show INTEGER NOT NULL, -- Score (1-100) for the stage show
|
score_show INTEGER NOT NULL, -- Score (1-100) for the stage show
|
||||||
score_wardrobe INTEGER NOT NULL, -- Score (1-100) for wardrobe/costumes
|
score_costume INTEGER NOT NULL, -- Score (1-100) for wardrobe/costumes
|
||||||
text_review VARCHAR, -- Optional textual comments (nullable)
|
text_review VARCHAR, -- Optional textual comments (nullable)
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the review was created
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the review was created
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the review was last modified
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the review was last modified
|
||||||
@@ -101,7 +128,7 @@ CREATE TABLE Review (
|
|||||||
-- Ensure scores are within the valid range (1-100)
|
-- Ensure scores are within the valid range (1-100)
|
||||||
CHECK (score_song >= 1 AND score_song <= 100),
|
CHECK (score_song >= 1 AND score_song <= 100),
|
||||||
CHECK (score_show >= 1 AND score_show <= 100),
|
CHECK (score_show >= 1 AND score_show <= 100),
|
||||||
CHECK (score_wardrobe >= 1 AND score_wardrobe <= 100),
|
CHECK (score_costume >= 1 AND score_costume <= 100),
|
||||||
|
|
||||||
-- Ensure each user can only submit one review per song
|
-- Ensure each user can only submit one review per song
|
||||||
UNIQUE (user_id, song_id)
|
UNIQUE (user_id, song_id)
|
||||||
@@ -110,8 +137,182 @@ CREATE TABLE Review (
|
|||||||
CREATE TABLE CountryCodes (
|
CREATE TABLE CountryCodes (
|
||||||
code VARCHAR PRIMARY KEY,
|
code VARCHAR PRIMARY KEY,
|
||||||
name_en VARCHAR NOT NULL,
|
name_en VARCHAR NOT NULL,
|
||||||
name_fi VARCHAR NOT NULL
|
name_fi VARCHAR NOT NULL,
|
||||||
|
name_sv VARCHAR NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE VIEW ReviewSummaryGlobal AS
|
||||||
|
SELECT
|
||||||
|
song_id,
|
||||||
|
cc.name_fi AS country_fi,
|
||||||
|
cc.name_sv AS country_sv,
|
||||||
|
s.artist,
|
||||||
|
s.title,
|
||||||
|
COUNT(*) AS total_reviews,
|
||||||
|
AVG(score_song) AS avg_score_song,
|
||||||
|
AVG(score_show) AS avg_score_show,
|
||||||
|
AVG(score_costume) AS avg_score_costume,
|
||||||
|
AVG((score_song + score_show + score_costume)/3) AS avg_total_score
|
||||||
|
FROM Review r
|
||||||
|
JOIN Song s ON r.song_id = s.id
|
||||||
|
JOIN CountryCodes cc ON s.country = cc.code
|
||||||
|
GROUP BY song_id, cc.name_fi, cc.name_sv, s.artist, s.title
|
||||||
|
ORDER BY avg_total_score DESC;
|
||||||
|
|
||||||
|
CREATE VIEW ReviewSummaryByTeam AS
|
||||||
|
SELECT
|
||||||
|
r.song_id,
|
||||||
|
cc.name_fi AS country_fi,
|
||||||
|
cc.name_sv AS country_sv,
|
||||||
|
s.artist,
|
||||||
|
s.title,
|
||||||
|
u.team_id,
|
||||||
|
COUNT(*) AS total_reviews,
|
||||||
|
AVG(r.score_song) AS avg_score_song,
|
||||||
|
AVG(r.score_show) AS avg_score_show,
|
||||||
|
AVG(r.score_costume) AS avg_score_costume,
|
||||||
|
AVG((r.score_song + r.score_show + r.score_costume)/3) AS avg_total_score
|
||||||
|
FROM Review r
|
||||||
|
JOIN "User" u ON r.user_id = u.id
|
||||||
|
JOIN Song s ON r.song_id = s.id
|
||||||
|
JOIN CountryCodes cc ON s.country = cc.code
|
||||||
|
GROUP BY r.song_id, u.team_id, cc.name_fi, cc.name_sv, s.artist, s.title
|
||||||
|
ORDER BY avg_total_score DESC;
|
||||||
|
|
||||||
|
CREATE VIEW ReviewSummaryByUser AS
|
||||||
|
SELECT
|
||||||
|
r.song_id,
|
||||||
|
cc.name_fi AS country_fi,
|
||||||
|
cc.name_sv AS country_sv,
|
||||||
|
s.artist,
|
||||||
|
s.title,
|
||||||
|
u.id AS user_id,
|
||||||
|
COUNT(*) AS total_reviews,
|
||||||
|
AVG(r.score_song) AS avg_score_song,
|
||||||
|
AVG(r.score_show) AS avg_score_show,
|
||||||
|
AVG(r.score_costume) AS avg_score_costume,
|
||||||
|
AVG((r.score_song + r.score_show + r.score_costume)/3) AS avg_total_score
|
||||||
|
FROM Review r
|
||||||
|
JOIN "User" u ON r.user_id = u.id
|
||||||
|
JOIN Song s ON r.song_id = s.id
|
||||||
|
JOIN CountryCodes cc ON s.country = cc.code
|
||||||
|
GROUP BY r.song_id, u.id, cc.name_fi, cc.name_sv, s.artist, s.title
|
||||||
|
ORDER BY avg_total_score DESC;
|
||||||
|
|
||||||
|
CREATE VIEW ReviewAllSongs AS
|
||||||
|
SELECT
|
||||||
|
r.song_id,
|
||||||
|
cc.name_fi AS country_fi,
|
||||||
|
cc.name_sv AS country_sv,
|
||||||
|
s.artist,
|
||||||
|
s.title,
|
||||||
|
COUNT(*) AS total_reviews,
|
||||||
|
AVG(r.score_song) AS avg_score_song,
|
||||||
|
AVG(r.score_show) AS avg_score_show,
|
||||||
|
AVG(r.score_costume) AS avg_score_costume,
|
||||||
|
AVG((r.score_song + r.score_show + r.score_costume)/3) AS avg_total_score
|
||||||
|
FROM Review r
|
||||||
|
JOIN Song s ON r.song_id = s.id
|
||||||
|
JOIN CountryCodes cc ON s.country = cc.code
|
||||||
|
GROUP BY r.song_id, cc.name_fi, cc.name_sv, s.artist, s.title
|
||||||
|
ORDER BY avg_total_score DESC;
|
||||||
|
|
||||||
|
-- Insert country codes data
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ALB', 'Albania', 'Albania', 'Albanien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ARM', 'Armenia', 'Armenia', 'Armenien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('AUS', 'Australia', 'Australia', 'Australien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('AUT', 'Austria', 'Itävalta', 'Österrike');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('AZE', 'Azerbaijan', 'Azerbaidžan', 'Azerbajdzjan');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('BEL', 'Belgium', 'Belgia', 'Belgien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('HRV', 'Croatia', 'Kroatia', 'Kroatien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('CYP', 'Cyprus', 'Kypros', 'Cypern');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('CZE', 'Czechia', 'Tšekki', 'Tjeckien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('DNK', 'Denmark', 'Tanska', 'Danmark');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('EST', 'Estonia', 'Viro', 'Estland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('FIN', 'Finland', 'Suomi', 'Finland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('FRA', 'France', 'Ranska', 'Frankrike');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('GEO', 'Georgia', 'Georgia', 'Georgien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('DEU', 'Germany', 'Saksa', 'Tyskland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('GRC', 'Greece', 'Kreikka', 'Grekland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ISL', 'Iceland', 'Islanti', 'Island');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('IRL', 'Ireland', 'Irlanti', 'Irland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ISR', 'Israel', 'Israel', 'Israel');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ITA', 'Italy', 'Italia', 'Italien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('LVA', 'Latvia', 'Latvia', 'Lettland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('LTU', 'Lithuania', 'Liettua', 'Litauen');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('LUX', 'Luxembourg', 'Luxemburg', 'Luxemburg');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('MLT', 'Malta', 'Malta', 'Malta');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('MNE', 'Montenegro', 'Montenegro', 'Montenegro');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('NLD', 'Netherlands', 'Alankomaat', 'Nederländerna');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('NOR', 'Norway', 'Norja', 'Norge');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('POL', 'Poland', 'Puola', 'Polen');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('PRT', 'Portugal', 'Portugali', 'Portugal');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SMR', 'San Marino', 'San Marino', 'San Marino');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SRB', 'Serbia', 'Serbia', 'Serbien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SVN', 'Slovenia', 'Slovenia', 'Slovenien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ESP', 'Spain', 'Espanja', 'Spanien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SWE', 'Sweden', 'Ruotsi', 'Sverige');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('CHE', 'Switzerland', 'Sveitsi', 'Schweiz');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('UKR', 'Ukraine', 'Ukraina', 'Ukraina');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('GBR', 'United Kingdom', 'Englanti', 'Storbritannien');
|
||||||
|
|
||||||
|
|
||||||
|
-- TODO: Clean this next year. Its now here just to get the show running
|
||||||
|
INSERT INTO Contest (year, finals_date) VALUES (2025, '2025-05-16');
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- DEBUG DATA
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
-- Insert Sample Teams
|
||||||
|
-- INSERT INTO "Team" (name) VALUES ('The Eurovisionaries');
|
||||||
|
-- INSERT INTO "Team" (name) VALUES ('douze_points_posse');
|
||||||
|
|
||||||
|
-- -- Insert Sample Users (Passwords are 'password' hashed with bcrypt - for debug only!)
|
||||||
|
-- -- For Team 1 (The Eurovisionaries)
|
||||||
|
-- INSERT INTO "User" (username, first_name, last_name, hashed_password, email, team_id, avatar_id, is_admin)
|
||||||
|
-- VALUES ('debuguser1', 'Debug', 'UserOne', '$2b$12$7.xP5Y6.K7kZ5yL0HjEPIOjBqzsRcg8yU/gQcNcmauHAFsdn7Y2tu', '[email protected]', (SELECT id FROM "Team" WHERE name = 'The Eurovisionaries'), 1, FALSE);
|
||||||
|
-- INSERT INTO "User" (username, first_name, last_name, hashed_password, email, team_id, avatar_id, is_admin)
|
||||||
|
-- VALUES ('debugadmin', 'Debug', 'Admin', '$2b$12$7.xP5Y6.K7kZ5yL0HjEPIOjBqzsRcg8yU/gQcNcmauHAFsdn7Y2tu', '[email protected]', (SELECT id FROM "Team" WHERE name = 'The Eurovisionaries'), 2, TRUE);
|
||||||
|
|
||||||
|
-- -- For Team 2 (douze_points_posse)
|
||||||
|
-- INSERT INTO "User" (username, first_name, last_name, hashed_password, email, team_id, avatar_id, is_admin)
|
||||||
|
-- VALUES ('debuguser2', 'Another', 'Debugger', '$2b$12$7.xP5Y6.K7kZ5yL0HjEPIOjBqzsRcg8yU/gQcNcmauHAFsdn7Y2tu', '[email protected]', (SELECT id FROM "Team" WHERE name = 'douze_points_posse'), 3, FALSE);
|
||||||
|
|
||||||
|
-- -- Insert Sample Songs for Contest 2025
|
||||||
|
-- -- Assuming a contest with id=1 (or use (SELECT id FROM Contest WHERE year = 2025) if only one entry)
|
||||||
|
-- INSERT INTO Song (year, country, artist, title, running_order, img_url, language, confidence)
|
||||||
|
-- VALUES
|
||||||
|
-- (2025, 'SWE', 'Swedish Singers', 'Dancing in Stockholm', 1, 'https://example.com/swe.jpg', 'English', 0.95),
|
||||||
|
-- (2025, 'FIN', 'Finnish Rockers', 'Sauna Anthem', 2, 'https://example.com/fin.jpg', 'Finnish', 0.90),
|
||||||
|
-- (2025, 'NOR', 'Nordic Balladeers', 'Fjord Dreams', 3, 'https://example.com/nor.jpg', 'Norwegian', 0.85),
|
||||||
|
-- (2025, 'ITA', 'Italian Pop Sensations', 'Roman Holiday', 4, 'https://example.com/ita.jpg', 'Italian', 0.92),
|
||||||
|
-- (2025, 'GER', 'German Electro Duo', 'Berlin Beats', 5, 'https://example.com/ger.jpg', 'German', 0.88);
|
||||||
|
|
||||||
|
-- -- Insert Sample Reviews
|
||||||
|
-- -- User 1 reviews Song 1 and Song 2
|
||||||
|
-- INSERT INTO Review (user_id, song_id, score_song, score_show, score_costume, text_review)
|
||||||
|
-- VALUES
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debuguser1'), (SELECT id FROM Song WHERE title = 'Dancing in Stockholm'), 80, 75, 70, 'Great song!'),
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debuguser1'), (SELECT id FROM Song WHERE title = 'Sauna Anthem'), 90, 85, 80, 'Loved the energy!');
|
||||||
|
|
||||||
|
-- -- User 2 reviews Song 2 and Song 3
|
||||||
|
-- INSERT INTO Review (user_id, song_id, score_song, score_show, score_costume, text_review)
|
||||||
|
-- VALUES
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debuguser2'), (SELECT id FROM Song WHERE title = 'Sauna Anthem'), 88, 82, 78, 'Rock on Finland!'),
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debuguser2'), (SELECT id FROM Song WHERE title = 'Fjord Dreams'), 70, 65, 60, 'A bit slow for me.');
|
||||||
|
|
||||||
|
-- -- Admin reviews Song 1
|
||||||
|
-- INSERT INTO Review (user_id, song_id, score_song, score_show, score_costume, text_review)
|
||||||
|
-- VALUES
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debugadmin'), (SELECT id FROM Song WHERE title = 'Dancing in Stockholm'), 99, 92, 1, 'Absolutely stellar! A clear frontrunner!'),
|
||||||
|
-- -- More reviews with varied scores
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debuguser1'), (SELECT id FROM Song WHERE title = 'Roman Holiday'), 42, 23, 23, 'The song was okay, staging average, costumes needed work.'),
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debuguser1'), (SELECT id FROM Song WHERE title = 'Berlin Beats'), 96, 91, 87, 'Incredible energy and visuals! Loved it!'),
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debuguser2'), (SELECT id FROM Song WHERE title = 'Dancing in Stockholm'), 68, 77, 53, 'Decent entry, but not outstanding. Costumes were a bit plain.'),
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debuguser2'), (SELECT id FROM Song WHERE title = 'Roman Holiday'), 12, 33, 7, 'Really disliked this one. Song was boring, show forgettable, and costumes awful.'),
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debugadmin'), (SELECT id FROM Song WHERE title = 'Sauna Anthem'), 100, 11, 99, 'Perfection! Finland has outdone themselves!'),
|
||||||
|
-- ((SELECT id FROM "User" WHERE username = 'debugadmin'), (SELECT id FROM Song WHERE title = 'Berlin Beats'), 2, 94, 78, 'Strong contender. Great production, though costumes could be more imaginative.');
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
-- Views (Optional - Not Created Here Based on Documentation Decision)
|
-- Views (Optional - Not Created Here Based on Documentation Decision)
|
||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
@@ -120,3 +321,4 @@ CREATE TABLE CountryCodes (
|
|||||||
-- =============================================================================
|
-- =============================================================================
|
||||||
|
|
||||||
-- End of Schema Definition
|
-- End of Schema Definition
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
-- PostgreSQL Schema Definition for Eurovision 25 Homereview Backend
|
||||||
|
-- Converted from DuckDB schema
|
||||||
|
-- Target Database: PostgreSQL
|
||||||
|
-- Note: Timestamps default to creation time; application logic should handle 'updated_at' updates.
|
||||||
|
|
||||||
|
-- Drop objects in reverse order of dependency and existence check
|
||||||
|
DROP TABLE IF EXISTS Review;
|
||||||
|
DROP TABLE IF EXISTS Song;
|
||||||
|
DROP TABLE IF EXISTS "User"; -- Quoted because USER is a reserved keyword
|
||||||
|
DROP TABLE IF EXISTS Team;
|
||||||
|
DROP TABLE IF EXISTS CountryCodes;
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Table: Group
|
||||||
|
-- Stores information about the different households or groups participating.
|
||||||
|
-- =============================================================================
|
||||||
|
CREATE TABLE "Team" (
|
||||||
|
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- Use IDENTITY for auto-increment
|
||||||
|
name VARCHAR(255) UNIQUE NOT NULL, -- The name of the household/group
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the group was created
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Timestamp when the group was last updated
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Table: User
|
||||||
|
-- Stores information about individual users (players and admins).
|
||||||
|
-- =============================================================================
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- Use IDENTITY for auto-increment
|
||||||
|
username VARCHAR(255) UNIQUE NOT NULL, -- The user's login name
|
||||||
|
hashed_password VARCHAR(255) NOT NULL, -- The securely hashed password
|
||||||
|
email VARCHAR(255) UNIQUE, -- User's email address (nullable)
|
||||||
|
team_id INTEGER NOT NULL, -- Foreign Key -> Group.id
|
||||||
|
avatar_id INTEGER DEFAULT 0 NOT NULL, -- Identifier for the user's avatar
|
||||||
|
is_active BOOLEAN DEFAULT TRUE NOT NULL, -- Flag indicating if the user account is active
|
||||||
|
is_admin BOOLEAN DEFAULT FALSE NOT NULL, -- Flag indicating if the user has admin privileges
|
||||||
|
last_login TIMESTAMP DEFAULT '1970-01-01 00:00:00', -- Timestamp when the user was last logged in. Epoch (1970-01-01) means "never logged in"
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the user was created
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the user was last updated
|
||||||
|
|
||||||
|
FOREIGN KEY (team_id) REFERENCES "Team"(id) -- Link to the Team table
|
||||||
|
);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Table: Song
|
||||||
|
-- Stores information about each participating song in the contest.
|
||||||
|
-- =============================================================================
|
||||||
|
CREATE TABLE Song (
|
||||||
|
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- Use IDENTITY for auto-increment
|
||||||
|
year INTEGER NOT NULL, -- The year of the Eurovision contest
|
||||||
|
country VARCHAR(255) NOT NULL, -- The participating country name
|
||||||
|
artist VARCHAR(255) NOT NULL, -- The name of the performing artist(s)
|
||||||
|
title VARCHAR(255) NOT NULL, -- The title of the song
|
||||||
|
running_order INTEGER NOT NULL, -- Official order in the show (nullable)
|
||||||
|
lyrics_url VARCHAR(255), -- URL to the original lyrics (nullable)
|
||||||
|
artist_url VARCHAR(255), -- URL to the artist's page (nullable)
|
||||||
|
flag_url VARCHAR(255), -- URL to the country flag (nullable)
|
||||||
|
lyrics_original TEXT, -- Original lyrics (nullable)
|
||||||
|
lyrics_translation_fi TEXT, -- Finnish translation (nullable)
|
||||||
|
tags TEXT[], -- Array of tags (PostgreSQL array type)
|
||||||
|
confidence FLOAT NOT NULL DEFAULT 0.0, -- Confidence level of the translation (nullable)
|
||||||
|
language VARCHAR(50) NOT NULL, -- Language of the song (nullable)
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the song entry was created
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -- Timestamp when the song entry was last updated
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Add index for faster lookups by year as specified in documentation
|
||||||
|
CREATE INDEX idx_song_year ON Song (year);
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Table: Review
|
||||||
|
-- Stores the scores and comments submitted by a user for a specific song.
|
||||||
|
-- =============================================================================
|
||||||
|
CREATE TABLE Review (
|
||||||
|
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- Use IDENTITY for auto-increment
|
||||||
|
user_id INTEGER NOT NULL, -- Foreign Key -> User.id
|
||||||
|
song_id INTEGER NOT NULL, -- Foreign Key -> Song.id
|
||||||
|
score_song INTEGER NOT NULL, -- Score (1-100) for song quality
|
||||||
|
score_show INTEGER NOT NULL, -- Score (1-100) for the stage show
|
||||||
|
score_costume INTEGER NOT NULL, -- Score (1-100) for wardrobe/costumes
|
||||||
|
text_review TEXT, -- Optional textual comments (nullable)
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the review was created
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, -- Timestamp when the review was last modified
|
||||||
|
|
||||||
|
FOREIGN KEY (user_id) REFERENCES "User"(id), -- Link to the User table
|
||||||
|
FOREIGN KEY (song_id) REFERENCES Song(id), -- Link to the Song table
|
||||||
|
|
||||||
|
-- Ensure scores are within the valid range (1-100)
|
||||||
|
CHECK (score_song >= 1 AND score_song <= 100),
|
||||||
|
CHECK (score_show >= 1 AND score_show <= 100),
|
||||||
|
CHECK (score_costume >= 1 AND score_costume <= 100),
|
||||||
|
|
||||||
|
-- Ensure each user can only submit one review per song
|
||||||
|
UNIQUE (user_id, song_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE CountryCodes (
|
||||||
|
code VARCHAR(10) PRIMARY KEY,
|
||||||
|
name_en VARCHAR(255) NOT NULL,
|
||||||
|
name_fi VARCHAR(255) NOT NULL,
|
||||||
|
name_sv VARCHAR(255) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Insert country codes data
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ALB', 'Albania', 'Albania', 'Albanien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ARM', 'Armenia', 'Armenia', 'Armenien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('AUS', 'Australia', 'Australia', 'Australien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('AUT', 'Austria', 'Itävalta', 'Österrike');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('AZE', 'Azerbaijan', 'Azerbaidžan', 'Azerbajdzjan');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('BEL', 'Belgium', 'Belgia', 'Belgien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('HRV', 'Croatia', 'Kroatia', 'Kroatien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('CYP', 'Cyprus', 'Kypros', 'Cypern');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('CZE', 'Czechia', 'Tšekki', 'Tjeckien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('DNK', 'Denmark', 'Tanska', 'Danmark');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('EST', 'Estonia', 'Viro', 'Estland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('FIN', 'Finland', 'Suomi', 'Finland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('FRA', 'France', 'Ranska', 'Frankrike');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('GEO', 'Georgia', 'Georgia', 'Georgien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('DEU', 'Germany', 'Saksa', 'Tyskland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('GRC', 'Greece', 'Kreikka', 'Grekland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ISL', 'Iceland', 'Islanti', 'Island');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('IRL', 'Ireland', 'Irlanti', 'Irland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ISR', 'Israel', 'Israel', 'Israel');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ITA', 'Italy', 'Italia', 'Italien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('LVA', 'Latvia', 'Latvia', 'Lettland');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('LTU', 'Lithuania', 'Liettua', 'Litauen');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('LUX', 'Luxembourg', 'Luxemburg', 'Luxemburg');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('MLT', 'Malta', 'Malta', 'Malta');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('MNE', 'Montenegro', 'Montenegro', 'Montenegro');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('NLD', 'Netherlands', 'Alankomaat', 'Nederländerna');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('NOR', 'Norway', 'Norja', 'Norge');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('POL', 'Poland', 'Puola', 'Polen');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('PRT', 'Portugal', 'Portugali', 'Portugal');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SMR', 'San Marino', 'San Marino', 'San Marino');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SRB', 'Serbia', 'Serbia', 'Serbien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SVN', 'Slovenia', 'Slovenia', 'Slovenien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('ESP', 'Spain', 'Espanja', 'Spanien');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('SWE', 'Sweden', 'Ruotsi', 'Sverige');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('CHE', 'Switzerland', 'Sveitsi', 'Schweiz');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('UKR', 'Ukraine', 'Ukraina', 'Ukraina');
|
||||||
|
INSERT INTO CountryCodes (code, name_en, name_fi, name_sv) VALUES ('GBR', 'United Kingdom', 'Englanti', 'Storbritannien');
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
|
-- Views (Optional - Not Created Here Based on Documentation Decision)
|
||||||
|
-- =============================================================================
|
||||||
|
-- The documentation suggests performing result aggregations in the application
|
||||||
|
-- layer. No CREATE VIEW statements are included.
|
||||||
|
-- =============================================================================
|
||||||
|
|
||||||
|
-- End of Schema Definition
|
||||||
@@ -2,11 +2,17 @@ from duckdb import connect
|
|||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import json
|
import json
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
|
from lib.helpers import hash_password
|
||||||
|
from lib.logger import logger
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
DB_PATH = Path("./data/data.duckdb").absolute()
|
DB_PATH = Path("./data/data.duckdb").absolute()
|
||||||
SQL_PATH = Path("./lib/database.sql").absolute()
|
SQL_PATH = Path("./lib/database.sql").absolute()
|
||||||
SEED_JSON_PATH = Path("./lib/seed_data.json").absolute()
|
SEED_JSON_PATH = Path("./lib/seed_data.json").absolute()
|
||||||
COUNTRYCODES_JSON_PATH = Path("./lib/countrycodes.json").absolute()
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
@@ -19,6 +25,7 @@ def get_connection():
|
|||||||
|
|
||||||
|
|
||||||
def seed_db() -> None:
|
def seed_db() -> None:
|
||||||
|
# Seed Songs
|
||||||
with open(SEED_JSON_PATH, "r") as f:
|
with open(SEED_JSON_PATH, "r") as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
|
|
||||||
@@ -26,15 +33,16 @@ def seed_db() -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
for idx, item in enumerate(data):
|
for idx, item in enumerate(data):
|
||||||
item["running_order"] = idx + 1
|
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
|
logger.debug(f"Inserting song {idx + 1} of {len(data)}", item)
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO Song (year, artist, country, title, lyrics_url, artist_url, flag_url, running_order, lyrics_original, lyrics_translation_fi, tags, confidence, language) VALUES (2025, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
"INSERT INTO Song (year, artist, country, title, lyrics_url, img_url, artist_url, flag_url, running_order, lyrics_original, lyrics_translation_fi, tags, confidence, language) VALUES (2025, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
(
|
(
|
||||||
item["artist"],
|
item["artist"],
|
||||||
item["country"],
|
item["country"],
|
||||||
item["title"],
|
item["title"],
|
||||||
item["lyrics_url"],
|
item["lyrics_url"],
|
||||||
|
item["img_url"],
|
||||||
item["artist_url"],
|
item["artist_url"],
|
||||||
item["flag_url"],
|
item["flag_url"],
|
||||||
item["running_order"],
|
item["running_order"],
|
||||||
@@ -46,31 +54,40 @@ def seed_db() -> None:
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
with open(COUNTRYCODES_JSON_PATH, "r") as f:
|
# CountryCodes are now directly inserted in the SQL file
|
||||||
data = json.load(f)
|
|
||||||
|
|
||||||
if not data:
|
# Seed Teams
|
||||||
return
|
with get_connection() as conn:
|
||||||
|
conn.execute("INSERT INTO Team (name) VALUES (?)", ("Pontus",))
|
||||||
|
|
||||||
for item in data:
|
# Seed Admin users
|
||||||
|
admin_username = os.getenv("ADMIN_USERNAME")
|
||||||
|
hashed_admin_password = hash_password(os.getenv("ADMIN_PASSWORD", "password"))
|
||||||
|
logger.debug(
|
||||||
|
f"Admin user: {admin_username}, hashed password: {hashed_admin_password}"
|
||||||
|
)
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT INTO CountryCodes (code, name_en, name_fi) VALUES (?, ?, ?)",
|
'INSERT INTO "User" (username, hashed_password, team_id, is_active, is_admin) VALUES (?, ?, ?, ?, ?)',
|
||||||
(
|
(
|
||||||
item["code"],
|
admin_username,
|
||||||
item["name_en"],
|
hashed_admin_password,
|
||||||
item["name_fi"],
|
1,
|
||||||
|
True,
|
||||||
|
True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def init_db():
|
def init_db():
|
||||||
|
logger.info("Initializing database...")
|
||||||
|
if Path(DB_PATH).exists():
|
||||||
|
return
|
||||||
|
logger.info("Database does not exist, creating...")
|
||||||
|
if not Path(DB_PATH).parent.exists():
|
||||||
|
Path(DB_PATH).parent.mkdir(parents=True, exist_ok=True)
|
||||||
with open(SQL_PATH, "r") as f:
|
with open(SQL_PATH, "r") as f:
|
||||||
sql = f.read()
|
sql = f.read()
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
conn.execute(sql)
|
conn.execute(sql)
|
||||||
seed_db()
|
seed_db()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
init_db()
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from hashlib import md5
|
||||||
|
|
||||||
|
|
||||||
|
def hash_password(password: str) -> str:
|
||||||
|
return md5(password.encode()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def verify_password(password: str, hashed_password: str) -> bool:
|
||||||
|
return hash_password(password) == hashed_password
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
from loguru import logger
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
LOG_PATH = Path(os.getenv("LOG_PATH", "./data/logs/logs.log")).absolute()
|
||||||
|
ERROR_PATH = Path(os.getenv("ERROR_PATH", "./data/logs/errors.log")).absolute()
|
||||||
|
|
||||||
|
logger.remove()
|
||||||
|
logger.add(
|
||||||
|
LOG_PATH, rotation="10 MB", compression="gz", level=os.getenv("LOG_LEVEL", "INFO")
|
||||||
|
)
|
||||||
|
logger.add(sys.stdout, level=os.getenv("LOG_LEVEL", "INFO"))
|
||||||
|
|
||||||
|
logger.add(
|
||||||
|
ERROR_PATH,
|
||||||
|
rotation="10 MB",
|
||||||
|
compression="gz",
|
||||||
|
level=os.getenv("LOG_LEVEL_ERROR", "ERROR"),
|
||||||
|
)
|
||||||
|
logger.add(sys.stderr, level=os.getenv("LOG_LEVEL_ERROR", "ERROR"))
|
||||||
|
|
||||||
|
logger = logger
|
||||||
@@ -1,27 +1,10 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"artist": "Shkodra Elektronike",
|
|
||||||
"country": "ALB",
|
|
||||||
"title": "Zjerm",
|
|
||||||
"lyrics_url": "https://genius.com/Shkodra-elektronike-zjerm-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/shkodra_elektronike-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ALBANIA-WHITE%402000px.png",
|
|
||||||
"lyrics_original": "[Teksti i \"Zjerm\"]\n\n[Strofa 1: Beatriçe Gjergji]\nN'këtë minutë, n'këtë çast, no paranoja (Ah)\nPas shiut, ylber të duket bota (Ua)\nNëpër rrugë, asnjë ambulancë, kurrkush s'flet me arrogancë\nEdhe sot na thanë që s'prishet koha\nPërfundova nën ujë, kurrë s'm'u tha goja (No)\nImagjino një minutë pa ushtarë, pa jetima\nAsnjë shishe n'oqean, naftës i vjen era jargavan\nLirinë e fjalës ta mëson shkolla (Hopa)\n\n[Pararefreni: Beatriçe Gjergji]\nKrijo në mua një zemër t'pastër\nNë natën time, të dërgoj dritën\nAman, miserere\nAman, miserere\n\n[Refreni: Beatriçe Gjergji]\nNë zemrën time, n'zemrën teme\nKy minutë do të vazhdojë\nNjerëz't e mirë e njerëz't pa emër\nKërcejnë valle n'shpirt\nJarnane ti toka ime\nKu kam lindë, s'do të harroj\nJarnane ti bota ime\nVazhdo me shndritë, shndritë, shndritë, shndritë\n\n[Pasrefreni: Beatriçe Gjergji]\n(Shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë)\n(Shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n\n[Strofa 2: Kolë Laca, Kolë Laca & Beatriçe Gjergji]\nZjerm bjer mbi vallet tona tribale\nQë ushtrojnë sa orteku kur bjen n'male\nZjerm, njerz't pa emën e njerz't e dëlirë\nNjësoj këputen e bijnë si shtatë thika që t'ngulen n'shpirt\nZjerm, këtu flen deti, rana e hana\nE yjet s'i shofim se yjet na i shkel kamba kur ecim n'jerm\nZjerm, jena t'untë për flakë e dritë\nE t'kërkojmë n'kyt terr që s'pran' tuej shndritë\n\n[Pararefreni: Beatriçe Gjergji]\nKrijo në mua një zemër t'pastër\nNë natën time, të dërgoj dritën\nAman, miserere\nAman, miserere\n\n[Refreni: Beatriçe Gjergji]\nNë zemrën time, n'zemrën teme\nKy minutë do të vazhdojë\nNjerëz't e mirë e njerëz't pa emër\nKërcejnë valle n'shpirt\nJarnane ti toka ime\nKu kam lindë, s'do të harroj\nJarnane ti bota ime\nVazhdo me shndritë, shndritë, shndritë, shndritë\n\n[Mbyllja: Beatriçe Gjergji]\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)",
|
|
||||||
"lyrics_translation_fi": "[Säkeistö 1: Beatriçe Gjergji] Tässä hetkessä, tässä hetkessä, ei vainoharjoja (Ah) Sateen jälkeen maailma näyttää sateenkaarelta (Ua) Kadulla ei ole ambulansseja, kukaan ei puhu ylimielisesti Jopa tänään he sanoivat meille, että sää ei ole pilalla Päädyin veden alle, suuni ei koskaan kuivunut (Ei) Kuvittele hetki ilman sotilaita, ilman orpoja Ei pulloa valtameressä, öljy tuoksuu syreeniltä Sananvapauden opettaa koulu (Hopa) [Esikertosäe: Beatriçe Gjergji] Luo minussa puhdas sydän Lähetän valon yössäni Ole hyvä, miserere Ole hyvä, miserere [Kertosäe: Beatriçe Gjergji] Sydämessäni, sydämessäni Tämä hetki jatkuu Hyvät ihmiset ja nimettömät Ihmiset tanssivat tansseja sielussa Jarnane sinä minun maani Missä synnyin, en unohda Jarnane sinä minun maailmani Jatka loistamista, loistamista, loistamista, loistamista [Jälkikertosäe: Beatriçe Gjergji] (Loista, loista, loista) (Loista, loista, loista, loista) (Loista, loista, loista) (Loista, loista) (Loista, loista, loista, loista) (Loista, loista, loista, loista) [Säkeistö 2: Kolë Laca, Kolë Laca & Beatriçe Gjergji] Zjerm putoaa heimotanssiemme päälle Jotka harjoittelevat kuin lumivyöry putoaa vuorille Zjerm, nimettömät ihmiset ja puhtaat Ihmiset katkeavat ja putoavat kuin seitsemän veistä, jotka on juurtunut sieluun Zjerm, tässä meri, hiekka ja kuu nukkuvat Emme näe tähtiä, koska jalat polkevat niitä, kun kävelemme aavikolla Zjerm, me olemme nälkäisiä liekeille ja valolle Ja etsimme siitä pimeydestä, joka ei salli sinun loistaa [Esikertosäe: Beatriçe Gjergji] Luo minussa puhdas sydän Lähetän valon yössäni Ole hyvä, miserere Ole hyvä, miserere [Kertosäe: Beatriçe Gjergji] Sydämessäni, sydämessäni Tämä hetki jatkuu Hyvät ihmiset ja nimettömät Ihmiset tanssivat tansseja sielussa Jarnane sinä minun maani Missä synnyin, en unohda Jarnane sinä minun maailmani Jatka loistamista, loistamista, loistamista, loistamista [Loppu: Beatriçe Gjergji] (Loista, loista, loista, loista) (Loista, loista, loista, loista) (Loista, loista, loista, loista) (Loista, loista, loista, loista) (Loista, loista, loista, loista) (Loista, loista, loista, loista) (Loista, loista, loista, loista) (Loista, loista, loista, loista)",
|
|
||||||
"tags": [
|
|
||||||
"toivo",
|
|
||||||
"rauha",
|
|
||||||
"sisu"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Albania",
|
|
||||||
"running_order": 1
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"artist": "PARG",
|
"artist": "PARG",
|
||||||
"country": "ARM",
|
"country": "ARM",
|
||||||
"title": "SURVIVOR",
|
"title": "SURVIVOR",
|
||||||
"lyrics_url": "https://genius.com/Parg-survivor-lyrics",
|
"lyrics_url": "https://genius.com/Parg-survivor-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250508_Corinne-Cumming_EBU_00871.jpg?h=a7aa5b77&itok=xmVAnwjY",
|
||||||
"artist_url": "https://eurovision.tv/participant/parg-2025",
|
"artist_url": "https://eurovision.tv/participant/parg-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ARMENIA-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ARMENIA-WHITE.png",
|
||||||
"lyrics_original": "[Intro]\n Survivor\n\n [Verse 1]\n I got my bad shades on (Oh)\nJet black, I'm in my zone (Ah)\nI'm sick of the news, I'm sick of the views\nI'm sick of the lies they've told (Yes)\nCome on and shine the light (Okay)\nCome on and shine on me (Woah)\nGuess you can see, I don't believe\nAnythin' they told me\n\n[Pre-Chorus]\nLettin' me down, liftin' me up (Up)\nKeepin' me slow, I don't give up (Up)\nIt's tearin' me apart, I'm alive\nTry to look up, atop of a rock (Yeah)\nClimbin' these heights\nMan, I can't stop at all\nI'm standin' tall\n\n[Chorus]\nI'm a survivor, stay-aliver\nDo or die, in my prime, I'm a fighter\nI'm a survivor, won't be tied up\nBreak from the pain, take your place\nI'll remind ya\n\n[Post-Chorus]\nI'm a survivor (La, la-la-la, la-la-la, survivor)\n(La, la-la-la-la-la-la-la, survivor)\n(La, la-la-la, la-la-la, survivor)\nBreak from the pain, take your place\nI'll remind ya\n\n[Verse 2]\nKickin' me, breakin' me, but I won't stop\nPushin' me, pulling me, I will get up\nHittin' me, mockin' me, stabbin' me, crushin' me\nTearin' me, burnin' me, I'll never stop\nI could have lost my way\nI could have made mistakes\nI bled a lot, more than you thought\nBut I didn't fade away (Oh)\n\n[Pre-Chorus]\nSay you'll break me down (Down)\nAnd bring me to my knees (Knees)\nYou can do it, can you prove it?\nBut you ain't got shit on me\nHa-ha-ha-ha-ha-ha",
|
"lyrics_original": "[Intro]\n Survivor\n\n [Verse 1]\n I got my bad shades on (Oh)\nJet black, I'm in my zone (Ah)\nI'm sick of the news, I'm sick of the views\nI'm sick of the lies they've told (Yes)\nCome on and shine the light (Okay)\nCome on and shine on me (Woah)\nGuess you can see, I don't believe\nAnythin' they told me\n\n[Pre-Chorus]\nLettin' me down, liftin' me up (Up)\nKeepin' me slow, I don't give up (Up)\nIt's tearin' me apart, I'm alive\nTry to look up, atop of a rock (Yeah)\nClimbin' these heights\nMan, I can't stop at all\nI'm standin' tall\n\n[Chorus]\nI'm a survivor, stay-aliver\nDo or die, in my prime, I'm a fighter\nI'm a survivor, won't be tied up\nBreak from the pain, take your place\nI'll remind ya\n\n[Post-Chorus]\nI'm a survivor (La, la-la-la, la-la-la, survivor)\n(La, la-la-la-la-la-la-la, survivor)\n(La, la-la-la, la-la-la, survivor)\nBreak from the pain, take your place\nI'll remind ya\n\n[Verse 2]\nKickin' me, breakin' me, but I won't stop\nPushin' me, pulling me, I will get up\nHittin' me, mockin' me, stabbin' me, crushin' me\nTearin' me, burnin' me, I'll never stop\nI could have lost my way\nI could have made mistakes\nI bled a lot, more than you thought\nBut I didn't fade away (Oh)\n\n[Pre-Chorus]\nSay you'll break me down (Down)\nAnd bring me to my knees (Knees)\nYou can do it, can you prove it?\nBut you ain't got shit on me\nHa-ha-ha-ha-ha-ha",
|
||||||
@@ -34,32 +17,54 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Englanti",
|
"language": "Englanti",
|
||||||
"running_order": 2
|
"running_order": 18
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Go-Jo",
|
"year": 2025,
|
||||||
"country": "AUS",
|
"country": "ALB",
|
||||||
"title": "Milkshake Man",
|
"artist": "Shkodra Elektronike",
|
||||||
"lyrics_url": "https://genius.com/Go-jo-aus-milkshake-man-lyrics",
|
"title": "Zjerm",
|
||||||
"artist_url": "https://eurovision.tv/participant/go-jo-2025",
|
"running_order": 26,
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-AUSTRALIA-WHITE.png",
|
"lyrics_url": "https://genius.com/Shkodra-elektronike-zjerm-lyrics",
|
||||||
"lyrics_original": "[Verse 1]\nCome and take a sip from my special cup\nI heard that you could use a little pick-me-up\nWhat can get you high when you're oh-so sad?\nIt's the milk from the milkshake man\n\n[Pre-Chorus]\n(Ah) Well, I got chocolate, vanilla and lactose-free\n(Ah) And a caramel banana that you have got to see\n\n[Chorus]\nYou can call me the milkshake man\nI wanna shake and make the whole world dance\nThey drink my milk all across the land\nI can tell you want a taste of the milkshake man\n\n[Post-Chorus]\nSh-sh-shake me good, sh-sh-shake me good\nI can tell you want a taste of the milkshake man\nSh-sh-shake me good, sh-sh-shakе me good\nI can tell you want a taste of thе milkshake man\n\n[Verse 2]\nThe shake is not a drink, it's a state of mind\nIt's a secret superpower you were born to find\nDrink it every day and, before you know\nYou'll be bigger and stronger with harder bones (Woo)\n\n[Pre-Chorus]\n(Ah) Well, I got mango, mojito, a fruit ballet\n(Ah) And did you see the naughty special on my crême brûlée?\n\n[Chorus]\nYou can call me the milkshake man\nI wanna shake up all the milk I can\nThey know my name all across the land\nI can tell you want a taste of the milkshake man\n\n[Post-Chorus]\nSh-sh-shake me good, sh-sh-shake me good\nI can tell you want a taste of the milkshake man\nSh-sh-shake me good, sh-sh-shake me good\nI can tell you want a taste of the milkshake man\n\n[Bridge]\nExcusez-moi, monsieur, avez-vous du lait pour moi?\nWhen I say, \"Sweet, sweet\", you say, \"Yum, yum\"\nSweet, sweet (Yum, yum)\nSweet, sweet (Yum, yum)\nWhen I say, \"Sweet, sweet\", you say, \"Yum, yum\"\nSweet, sweet (Yum, yum)\nSweet, sweet (Yum, yum)\n\n[Outro]\nSh-sh-shake me good, sh-sh-shake me good\nI can tell you want a taste of the milkshake man\nSh-sh-shake me good, sh-sh-shake me good\nI can tell you want a taste of the milkshake man\nSh-sh-shake me good, sh-sh-shake me good\nI can tell you want a taste of the milkshake man\nSh-sh-shake me good, sh-sh-shake me good\nI can tell you want a taste of the milkshake man",
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250507_Corinne-Cumming_EBU_00462_0.jpg?h=868502f4&itok=JDYPbVwT",
|
||||||
"lyrics_translation_fi": "[Verse 1]\nTule ja ota siemaus erikoiskupistani\nKuulin, että voisit tarvita pientä piristystä\nMikä voi saada sinut hyvälle tuulelle, kun olet niin surullinen?\nSe on pirtelömiehen maitoa\n\n[Pre-Chorus]\n(Ah) No, minulla on suklaata, vaniljaa ja laktoositonta\n(Ah) Ja karamellibanaanin, joka sinun on nähtävä\n\n[Chorus]\nVoit kutsua minua pirtelömieheksi\nHaluan ravistaa ja saada koko maailman tanssimaan\nHe juovat maitoni ympäri maan\nNäen, että haluat maistaa pirtelömiestä\n\n[Post-Chorus]\nP-p-ravista minua hyvin, p-p-ravista minua hyvin\nNäen, että haluat maistaa pirtelömiestä\nP-p-ravista minua hyvin, p-p-ravista minua hyvin\nNäen, että haluat maistaa pirtelömiestä\n\n[Verse 2]\nPirtelö ei ole juoma, se on mielentila\nSe on salainen supervoima, joka sinun oli tarkoitus löytää\nJuo sitä joka päivä ja ennen kuin huomaatkaan\nSinusta tulee isompi ja vahvempi kovemmilla luilla (Woo)\n\n[Pre-Chorus]\n(Ah) No, minulla on mango, mojito, hedelmäbaletti\n(Ah) Ja näitkö tuhman erikoisen crême brûléessani?\n\n[Chorus]\nVoit kutsua minua pirtelömieheksi\nHaluan ravistaa kaiken maidon mitä voin\nHe tuntevat nimeni ympäri maan\nNäen, että haluat maistaa pirtelömiestä\n\n[Post-Chorus]\nP-p-ravista minua hyvin, p-p-ravista minua hyvin\nNäen, että haluat maistaa pirtelömiestä\nP-p-ravista minua hyvin, p-p-ravista minua hyvin\nNäen, että haluat maistaa pirtelömiestä\n\n[Bridge]\nExcusez-moi, monsieur, avez-vous du lait pour moi?\nKun sanon: \"Sweet, sweet\", sanot: \"Yum, yum\"\nSweet, sweet (Yum, yum)\nSweet, sweet (Yum, yum)\nKun sanon: \"Sweet, sweet\", sanot: \"Yum, yum\"\nSweet, sweet (Yum, yum)\nSweet, sweet (Yum, yum)\n\n[Outro]\nP-p-ravista minua hyvin, p-p-ravista minua hyvin\nNäen, että haluat maistaa pirtelömiestä\nP-p-ravista minua hyvin, p-p-ravista minua hyvin\nNäen, että haluat maistaa pirtelömiestä\nP-p-ravista minua hyvin, p-p-ravista minua hyvin\nNäen, että haluat maistaa pirtelömiestä\nP-p-ravista minua hyvin, p-p-ravista minua hyvin\nNäen, että haluat maistaa pirtelömiestä",
|
"artist_url": "https://eurovision.tv/participant/shkodra_elektronike-2025",
|
||||||
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ALBANIA-WHITE%402000px.png",
|
||||||
|
"lyrics_original": "[Teksti i \"Zjerm\"]\n\n[Strofa 1: Beatriçe Gjergji]\nN'këtë minutë, n'këtë çast, no paranoja (Ah)\nPas shiut, ylber të duket bota (Ua)\nNëpër rrugë, asnjë ambulancë, kurrkush s'flet me arrogancë\nEdhe sot na thanë që s'prishet koha\nPërfundova nën ujë, kurrë s'm'u tha goja (No)\nImagjino një minutë pa ushtarë, pa jetima\nAsnjë shishe n'oqean, naftës i vjen era jargavan\nLirinë e fjalës ta mëson shkolla (Hopa)\n\n[Pararefreni: Beatriçe Gjergji]\nKrijo në mua një zemër t'pastër\nNë natën time, të dërgoj dritën\nAman, miserere\nAman, miserere\n\n[Refreni: Beatriçe Gjergji]\nNë zemrën time, n'zemrën teme\nKy minutë do të vazhdojë\nNjerëz't e mirë e njerëz't pa emër\nKërcejnë valle n'shpirt\nJarnane ti toka ime\nKu kam lindë, s'do të harroj\nJarnane ti bota ime\nVazhdo me shndritë, shndritë, shndritë, shndritë\n\n[Pasrefreni: Beatriçe Gjergji]\n(Shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë)\n(Shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n\n[Strofa 2: Kolë Laca, Kolë Laca & Beatriçe Gjergji]\nZjerm bjer mbi vallet tona tribale\nQë ushtrojnë sa orteku kur bjen n'male\nZjerm, njerz't pa emën e njerz't e dëlirë\nNjësoj këputen e bijnë si shtatë thika që t'ngulen n'shpirt\nZjerm, këtu flen deti, rana e hana\nE yjet s'i shofim se yjet na i shkel kamba kur ecim n'jerm\nZjerm, jena t'untë për flakë e dritë\nE t'kërkojmë n'kyt terr që s'pran' tuej shndritë\n\n[Pararefreni: Beatriçe Gjergji]\nKrijo në mua një zemër t'pastër\nNë natën time, të dërgoj dritën\nAman, miserere\nAman, miserere\n\n[Refreni: Beatriçe Gjergji]\nNë zemrën time, n'zemrën teme\nKy minutë do të vazhdojë\nNjerëz't e mirë e njerëz't pa emër\nKërcejnë valle n'shpirt\nJarnane ti toka ime\nKu kam lindë, s'do të harroj\nJarnane ti bota ime\nVazhdo me shndritë, shndritë, shndritë, shndritë\n\n[Mbyllja: Beatriçe Gjergji]\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)\n(Shndritë, shndritë, shndritë, shndritë)",
|
||||||
|
"lyrics_translation_fi": "[Säkeistö 1: Beatriçe Gjergji]\nTässä hetkessä, tässä hetkessä, ei vainoharjoja (Ah)\nSateen jälkeen maailma näyttää sateenkaarelta (Ua)\nKadulla ei ole ambulansseja, kukaan ei puhu ylimielisesti\nJopa tänään he sanoivat meille, että sää ei ole pilalla\nPäädyin veden alle, suuni ei koskaan kuivunut (Ei)\nKuvittele hetki ilman sotilaita, ilman orpoja\nEi pulloa valtameressä, öljy tuoksuu syreeniltä\nSananvapauden opettaa koulu (Hopa)\n\n[Esikertosäe: Beatriçe Gjergji]\nLuo minussa puhdas sydän\nLähetän valon yössäni\nOle hyvä, miserere\nOle hyvä, miserere\n\n[Kertosäe: Beatriçe Gjergji]\nSydämessäni, sydämessäni\nTämä hetki jatkuu\nHyvät ihmiset ja nimettömät\nIhmiset tanssivat tansseja sielussa\nJarnane sinä minun maani\nMissä synnyin, en unohda\nJarnane sinä minun maailmani\nJatka loistamista, loistamista, loistamista, loistamista\n\n[Jälkikertosäe: Beatriçe Gjergji]\n(Loista, loista, loista)\n(Loista, loista, loista, loista)\n(Loista, loista, loista)\n(Loista, loista)\n(Loista, loista, loista, loista)\n(Loista, loista, loista, loista)\n\n[Säkeistö 2: Kolë Laca, Kolë Laca & Beatriçe Gjergji]\nZjerm putoaa heimotanssiemme päälle\nJotka harjoittelevat kuin lumivyöry putoaa vuorille\nZjerm, nimettömät ihmiset ja puhtaat\nIhmiset katkeavat ja putoavat kuin seitsemän veistä, jotka on juurtunut sieluun\nZjerm, tässä meri, hiekka ja kuu nukkuvat\nEmme näe tähtiä, koska jalat polkevat niitä, kun kävelemme aavikolla\nZjerm, me olemme nälkäisiä liekeille ja valolle\nJa etsimme siitä pimeydestä, joka ei salli sinun loistaa\n\n[Esikertosäe: Beatriçe Gjergji]\nLuo minussa puhdas sydän\nLähetän valon yössäni\nOle hyvä, miserere\nOle hyvä, miserere\n\n[Kertosäe: Beatriçe Gjergji]\nSydämessäni, sydämessäni\nTämä hetki jatkuu\nHyvät ihmiset ja nimettömät\nIhmiset tanssivat tansseja sielussa\nJarnane sinä minun maani\nMissä synnyin, en unohda\nJarnane sinä minun maailmani\nJatka loistamista, loistamista, loistamista, loistamista\n\n[Loppu: Beatriçe Gjergji]\n(Loista, loista, loista, loista)\n(Loista, loista, loista, loista)\n(Loista, loista, loista, loista)\n(Loista, loista, loista, loista)\n(Loista, loista, loista, loista)\n(Loista, loista, loista, loista)\n(Loista, loista, loista, loista)\n(Loista, loista, loista, loista)",
|
||||||
"tags": [
|
"tags": [
|
||||||
"juoma",
|
"toivo",
|
||||||
"energia",
|
"rauha",
|
||||||
"hauska",
|
"sisu"
|
||||||
"makea"
|
|
||||||
],
|
],
|
||||||
"confidence": 0.9,
|
"confidence": 0.949999988079071,
|
||||||
"language": "Englanti",
|
"language": "Albania"
|
||||||
"running_order": 3
|
},
|
||||||
|
{
|
||||||
|
"artist": "VÆB",
|
||||||
|
"country": "ISL",
|
||||||
|
"title": "RÓA",
|
||||||
|
"lyrics_url": "https://genius.com/Vb-isl-roa-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250507_Corinne-Cumming_EBU_00032.jpg?h=28c964fa&itok=fNyy_NJb",
|
||||||
|
"artist_url": "https://eurovision.tv/participant/V%C3%86B-2025",
|
||||||
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ICELAND-WHITE-v2.png",
|
||||||
|
"lyrics_original": "[Söngtextar fyrir „RÓA“]\n\n[Byrjun: Matthías]\nLet's go!\n\n[Viðlag: Hálfdán]\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af, ooh-ooh\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af\n\n[Vísa 1: Matthías, Hálfdán, VÆB]\nÉg set spítu ofan á spítu og kalla það bát\nEf ég sekk í dag er það ekkert mál\nMeð árar úr stáli sem duga í ár\nStefni á Færeyjar, já, eg er klár\nÉg er með vesti fyrir belti og vatnshelda skó\nÞví að veðrið það er erfitt ég er kominn með nóg\n\n[Fyrir-Viðlag: Matthías]\nEr sjórinn opnast koma öldurnar\nÉg er einn á bát að leita af betri stað\nÉg er ekki ennþá búin að missa allt\nEn við setjum seglin upp og höldum aftur af stað\n\n[Viðlag: Hálfdán]\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkеrt stoppað mig af, ooh-ooh\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkеrt stoppað mig af, ooh-ooh\n\n[Vísa 2: Matthías, Hálfdán]\nÉg er enþá á bát, sjáðu þetta, vá\nStoppa í Grænlandi? Já, ég er down! God damn!\nStýri á sjó ég er kapteinn (Ey!)\nKallaðu mig Gísli Marteinn\nMargir mánuðir síðan ég sá síðast sól\nVil eiða restinni af lífinu hér út á sjó\n\n[Fyrir-Viðlag: Matthías]\nEr sjórinn opnast koma öldurnar\nÉg er en á bát að leita af betri stað\nÉg er ekki ennþá búin að missa allt\nEn við setjum seglin upp og höldum aftur af stað\n\n[Viðlag: Hálfdán, Matthías]\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af, ooh-ooh\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af, ooh-ooh\n\n[Brú]\n(Hey, hey, hey, hey)\n(Hey, hey)\n\n[Viðlag: Hálfdán, Matthías]\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af, ooh-ooh\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af, ooh-ooh (Það getur ekkert stoppað mig af)\n\n[Endir: VÆB]\nÞað getur ekkert stoppað mig af\nÞað getur ekkert stoppað mig af, ooh-ooh",
|
||||||
|
"lyrics_translation_fi": "[Sanoitukset kappaleelle \"RÓA\"]\n\n[Alku: Matthías]\nLet's go!\n\n[Kertosäe: Hálfdán]\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua\n\n[Säkeistö 1: Matthías, Hálfdán, VÆB]\nLaitan riman riman päälle ja kutsun sitä veneeksi\nJos uppoan tänään, ei se ole ongelma\nMeloilla teräksestä, jotka kestävät vuoden\nSuuntaan Färsaarille, kyllä, olen valmis\nMinulla on liivi vyötäröllä ja vedenpitävät kengät\nKoska sää on vaikea, olen saanut tarpeekseni\n\n[Esikertosäe: Matthías]\nKun meri avautuu, aallot tulevat\nOlen yksin veneessä etsimässä parempaa paikkaa\nEn ole vielä menettänyt kaikkea\nMutta nostamme purjeet ja lähdemme taas\n\n[Kertosäe: Hálfdán]\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\n\n[Säkeistö 2: Matthías, Hálfdán]\nOlen yhä veneessä, katso tätä, vau\nPysähdytäänkö Grönlantiin? Kyllä, olen mukana! God damn!\nOhjaan merellä, olen kapteeni (Ey!)\nKutsu minua Gísli Marteinniksi\nMeni monta kuukautta siitä kun näin viimeksi aurinkoa\nHaluan viettää loppuelämäni täällä merellä\n\n[Esikertosäe: Matthías]\nKun meri avautuu, aallot tulevat\nOlen yhä veneessä etsimässä parempaa paikkaa\nEn ole vielä menettänyt kaikkea\nMutta nostamme purjeet ja lähdemme taas\n\n[Kertosäe: Hálfdán, Matthías]\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\n\n[Silta]\n(Hey, hey, hey, hey)\n(Hey, hey)\n\n[Kertosäe: Hálfdán, Matthías]\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh (Mikään ei voi pysäyttää minua)\n\n[Loppu: VÆB]\nMikään ei voi pysäyttää minua\nMikään ei voi pysäyttää minua, ooh-ooh",
|
||||||
|
"tags": [
|
||||||
|
"veneily",
|
||||||
|
"matka",
|
||||||
|
"äärimmäisyys",
|
||||||
|
"hauska"
|
||||||
|
],
|
||||||
|
"confidence": 0.95,
|
||||||
|
"language": "Islanti",
|
||||||
|
"running_order": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "JJ",
|
"artist": "JJ",
|
||||||
"country": "AUT",
|
"country": "AUT",
|
||||||
"title": "Wasted Love",
|
"title": "Wasted Love",
|
||||||
"lyrics_url": "https://genius.com/Jj-aut-wasted-love-lyrics",
|
"lyrics_url": "https://genius.com/Jj-aut-wasted-love-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250508_Corinne-Cumming_EBU_00932.jpg?h=25371bde&itok=usBLdKWr",
|
||||||
"artist_url": "https://eurovision.tv/participant/jj-2025",
|
"artist_url": "https://eurovision.tv/participant/jj-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-AUSTRIA-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-AUSTRIA-WHITE.png",
|
||||||
"lyrics_original": "[Verse 1]\nI'm an ocean of love\nAnd you're scared of water\nYou don't want to go under\nSo you let me go under\nI reach out my hand\nBut you watch me grow distant\nDrift out to the sea and\nFar away in an instant\n\n[Pre-Chorus]\nYou left me in the deep end\nI'm drownin' in my feelings\nHow do you not see that?\n\n[Chorus]\nNow that you're gone\nAll I have\nIs wasted love\nThis wasted love\nNow that you're gone\nCan't fill my heart\nWith wasted love\nThis wasted love\n\n[Verse 2]\nWhen you let me go\nI barely stayed afloat\nI'm floatin' all alone\nStill, I'm holdin' on to hope\n\n[Chorus]\nNow that you're gone\nAll I have\nIs wasted lovе\nThis wasted love\nNow that you're gonе\nCan't fill my heart\nWith wasted love\nThis wasted love\n\n[Bridge]\nWasted love\nThis wasted\n\n[Breakdown]\nWasted, wasted, wasted, wasted\nWasted, wasted, wasted, wasted\nLove (Wasted, wasted, wasted, wasted)\nLove",
|
"lyrics_original": "[Verse 1]\nI'm an ocean of love\nAnd you're scared of water\nYou don't want to go under\nSo you let me go under\nI reach out my hand\nBut you watch me grow distant\nDrift out to the sea and\nFar away in an instant\n\n[Pre-Chorus]\nYou left me in the deep end\nI'm drownin' in my feelings\nHow do you not see that?\n\n[Chorus]\nNow that you're gone\nAll I have\nIs wasted love\nThis wasted love\nNow that you're gone\nCan't fill my heart\nWith wasted love\nThis wasted love\n\n[Verse 2]\nWhen you let me go\nI barely stayed afloat\nI'm floatin' all alone\nStill, I'm holdin' on to hope\n\n[Chorus]\nNow that you're gone\nAll I have\nIs wasted lovе\nThis wasted love\nNow that you're gonе\nCan't fill my heart\nWith wasted love\nThis wasted love\n\n[Bridge]\nWasted love\nThis wasted\n\n[Breakdown]\nWasted, wasted, wasted, wasted\nWasted, wasted, wasted, wasted\nLove (Wasted, wasted, wasted, wasted)\nLove",
|
||||||
@@ -73,102 +78,6 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Englanti",
|
"language": "Englanti",
|
||||||
"running_order": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"artist": "Mamagama",
|
|
||||||
"country": "AZE",
|
|
||||||
"title": "Run With U",
|
|
||||||
"lyrics_url": "https://genius.com/Mamagama-run-with-u-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/mamagama-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-AZERBAIJAN-WHITE-v2.png",
|
|
||||||
"lyrics_original": "[Verse 1]\nRhythm pulls me out, don't know where to start\nYou electrify, never felt so high\nEvery glance you’re giving, every move I'm feeling\nI cannot resist, pure in every twist\n\n[Refrain]\nShall we dim the lights, leave our fears behind\nPlay wrong 'til it’s right, give it all tonight?\nHeartbeat takes control, moves my body and soul\nJust one look at me and I can't stand the heat\n\n[Pre-Chorus]\nShadows in the neon light, the night is young, the beat is tight\nSomethin' in the air that makes me fall for you\nThе way I do, uh-huh, the way I do, uh-huh\nThe way I do, uh-huh, the way I do, uh-huh\n\n[Chorus]\nNo mattеr what you do tonight\nI wanna run with you, I wanna run with you\nNo matter what you do tonight\nI wanna run with you, I wanna run with you\n\n[Interlude]\n(Running with me)\n(Running, let's go)\n\n[Verse 2]\nShooting to the stars, can't get enough, so far\nYou make me feel brand-new, never felt this true\nEvery laugh you're giving, every touch I'm living\nAnd I can't pull away, caught up in the play\n\n[Refrain]\nShall we dim the lights, leave our fears behind\nPlay wrong ’til it’s right, give it all?\n\n[Pre-Chorus]\nThe way I do, uh-huh, the way I do, uh-huh\nThe way I do, uh-huh, the way I do, uh-huh\n\n[Chorus]\nNo matter what you do tonight\nI wanna run with you, I wanna run with you\nNo matter what you do tonight\nI wanna run with you, I wanna run with you",
|
|
||||||
"lyrics_translation_fi": "[Verse 1]\nRytmi vetää minua ulos, en tiedä mistä aloittaa\nSinä sähköistät, en ole koskaan tuntenut oloani näin korkeaksi\nJokainen katse jonka annat, jokainen liike jonka tunnen\nEn voi vastustaa, puhdasta jokaisessa käänteessä\n\n[Refrain]\nLasketaanko valoja, jätetäänkö pelkomme taakse\nPelataan väärin, kunnes se on oikein, annetaan kaikki tänä iltana?\nSydämenlyönti ottaa vallan, liikuttaa kehoani ja sieluani\nYksi silmäys minuun ja en kestä kuumuutta\n\n[Pre-Chorus]\nVarjoja neonvalossa, yö on nuori, rytmi on tiukka\nJotain ilmassa saa minut lankeamaan sinuun\nNiin kuin teen, uh-huh, niin kuin teen, uh-huh\nNiin kuin teen, uh-huh, niin kuin teen, uh-huh\n\n[Chorus]\nEi väliä mitä teet tänä iltana\nHaluan juosta kanssasi, haluan juosta kanssasi\nEi väliä mitä teet tänä iltana\nHaluan juosta kanssasi, haluan juosta kanssasi\n\n[Interlude]\n(Juokse kanssani)\n(Juokse, mennään)\n\n[Verse 2]\nAmmu tähtien joukkoon, en saa tarpeekseni, niin kaukana\nSaat minut tuntemaan upouuden, en ole koskaan tuntenut tätä näin todeksi\nJokainen nauru jonka annat, jokainen kosketus jonka elän\nJa en pääse pois, olen jäänyt leikkiin\n\n[Refrain]\nLasketaanko valoja, jätetäänkö pelkomme taakse\nPelataan väärin, kunnes se on oikein, annetaanko kaikki?\n\n[Pre-Chorus]\nNiin kuin teen, uh-huh, niin kuin teen, uh-huh\nNiin kuin teen, uh-huh, niin kuin teen, uh-huh\n\n[Chorus]\nEi väliä mitä teet tänä iltana\nHaluan juosta kanssasi, haluan juosta kanssasi\nEi väliä mitä teet tänä iltana\nHaluan juosta kanssasi, haluan juosta kanssasi",
|
|
||||||
"tags": [
|
|
||||||
"tanssi",
|
|
||||||
"rakkaus",
|
|
||||||
"vapaa",
|
|
||||||
"intohimo"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Englanti",
|
|
||||||
"running_order": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"artist": "Red Sebastian",
|
|
||||||
"country": "BEL",
|
|
||||||
"title": "Strobe Lights",
|
|
||||||
"lyrics_url": "https://genius.com/Red-sebastian-strobe-lights-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/red-sebastian-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-BELGIUM-WHITE.png",
|
|
||||||
"lyrics_original": "[Intro]\nStrobe lights\nGettin' lost in your eyes\nCotton Candy haze\nFloatin' up in space\n\n[Verse 1]\nFallin' where up is down\nScreamin', but won't make a sound\nAlice, show me your hand\nTake it to Wonderland\n\n[Pre-Chorus]\nStep into the mirror to bright new dimensions\nWhere no words are needed to feel the connection\nWhere clocks never tick and where love is the ending\nCome down through the looking glass\n\n[Chorus]\nStrobe lights\nGettin' lost in your eyes\nCotton Candy haze\nWe're floatin' 'round in spacе\nSkintight\nNever felt so alivе\nCaught up in this maze\nIt's where I wanna stay\n\n[Verse 2]\nFlyin' deep underground\nSilence never felt so loud\n\n[Pre-Chorus]\nStep into the mirror to bright new dimensions\nWhere no words are needed to feel the connection\nWhere clocks never tick and where love is the ending\nCome down through the looking glass\n\n[Chorus]\nStrobe lights\nGettin' lost in your eyes\nCotton Candy haze\nWe're floatin' 'round in space\nSkintight\nI feel so alive\nCaught up in this maze\nIt's where I wanna stay\n\n[Outro]\nThat's where I wanna stay\nThat's where I will stay",
|
|
||||||
"lyrics_translation_fi": "[Intro]\nHuumaavat valot\nEksyn silmiisi\nHattarapilvi\nLeijumme avaruudessa\n\n[Verse 1]\nPutoon sinne missä ylös on alas\nHuudan, mutta ääntä ei kuulu\nAlice, näytä minulle kätesi\nViemme sen Ihmemaahan\n\n[Pre-Chorus]\nAstun peiliin kirkkaisiin uusiin ulottuvuuksiin\nMissä sanoja ei tarvita tuntemaan yhteyttä\nMissä kellot eivät koskaan tikitä ja missä rakkaus on loppu\nTule alas peilin läpi\n\n[Chorus]\nHuumaavat valot\nEksyn silmiisi\nHattarapilvi\nLeijumme avaruudessa\nIhonmyötäinen\nEn ole koskaan tuntenut olevani niin elossa\nJumissa tässä sokkelossa\nTäällä haluan olla\n\n[Verse 2]\nLennän syvälle maan alle\nHiljaisuus ei ole koskaan tuntunut niin kovalta\n\n[Pre-Chorus]\nAstun peiliin kirkkaisiin uusiin ulottuvuuksiin\nMissä sanoja ei tarvita tuntemaan yhteyttä\nMissä kellot eivät koskaan tikitä ja missä rakkaus on loppu\nTule alas peilin läpi\n\n[Chorus]\nHuumaavat valot\nEksyn silmiisi\nHattarapilvi\nLeijumme avaruudessa\nIhonmyötäinen\nTuntuu niin elossa\nJumissa tässä sokkelossa\nTäällä haluan olla\n\n[Outro]\nTäällä haluan olla\nTäällä aion pysyä",
|
|
||||||
"tags": [
|
|
||||||
"rakkaus",
|
|
||||||
"huuma",
|
|
||||||
"pakomatka",
|
|
||||||
"ihmeellinen"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Englanti",
|
|
||||||
"running_order": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"artist": "Marko Bošnjak",
|
|
||||||
"country": "HRV",
|
|
||||||
"title": "Poison Cake",
|
|
||||||
"lyrics_url": "https://genius.com/Marko-bosnjak-poison-cake-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/marko-bosnjak-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-CROATIA-WHITE.png",
|
|
||||||
"lyrics_original": "[Verse 1]\nIt's cool now in the kitchen\nThat happens when you're bitten\nI'm stirring and stirring, deliciously pouring\nAnd I know you're gonna love this\nA better kind of justice\nI don't want it burning\n\n[Break]\nOne last thing\nA sprinkle of my sweet revenge\nOne last thing\nA sprinkle of my sweet revenge\n\n[Pre-Chorus]\nMade you something that I know you'll like\nChocolate covered, sugar and spice\nTasty, tasty, yum-yum, tasty\nCandy colours, oh, so nice\n\n[Chorus]\nTake a bite of my poison cake (Yum)\nPoison cake\nPoison cake\nPoison cake\nPoison cake\n\n[Verse 2]\nTake a bite of my poison cake\nBeen cooking in my cauldron late\nYou're nеver ever gonna stay awakе\nYou're going down, down, down\nYou thought I was a servant slave\nBut I'm just a serpent snake\nYou better genuflect and pray\n(You better genuflect and pray)\n\n[Bridge]\nTingles of poison are hitting your veins\n\"What have you done?\" your words are slurring\nAs you fall down\n\n[Break]\nOne last thing\nA sprinkle of my sweet revenge\nOne last thing\nA sprinkle of my sweet revenge\n\n[Pre-Chorus]\nMade you something that I know you'll like\nChocolate covered, sugar and spice\nTasty, tasty, yum-yum, tasty\nCandy colors, oh, so nice\n\n[Chorus]\nTake a bite of my—\nPoison cake\nPoison cake\nPoison cake\nPoison cake\n\n[Post-Chorus]\nIt's not my fault I got carried away\nI lost control when I started to bake\nA dash of death is better to swallow\nDon't be scared, you'll be buried tomorrow\n\n[Outro]\n(Poison)\nPoison cake\nPoison cake\nPoison cake\nPoison cake\nPoison cake\nPoison cake\nPoison cake\nPoison cake",
|
|
||||||
"lyrics_translation_fi": "[Verse 1]\nKeittiössä on nyt viileää\nNiin käy, kun tulee purruksi\nSekoitan ja sekoitan, herkullisesti kaadan\nJa tiedän, että tulet tästä pitämään\nParempi oikeuden muoto\nEn halua sen palavan\n\n[Break]\nYksi asia vielä\nRipaus suloista kostoani\nYksi asia vielä\nRipaus suloista kostoani\n\n[Pre-Chorus]\nTein sinulle jotain, mistä tiedän sinun pitävän\nSuklaapäällysteinen, sokeria ja mausteita\nHerkullista, herkullista, nam-nam, herkullista\nKarkkivärit, oi, niin ihanat\n\n[Chorus]\nOta puraisu myrkkypiirakastani (Nam)\nMyrkkypiirakka\nMyrkkypiirakka\nMyrkkypiirakka\nMyrkkypiirakka\n\n[Verse 2]\nOta puraisu myrkkypiirakastani\nOlen keittänyt padassa myöhään\nEt tule koskaan enää pysymään hereillä\nOlet menossa alas, alas, alas\nHuittelet, että olin palvelijaorja\nMutta olenkin vain käärmekäärme\nParempi tehdä polvityö ja rukoilla\n(Parempi tehdä polvityö ja rukoilla)\n\n[Bridge]\nMyrkyn kihelmöinnit osuvat suoniisi\n\"Mitä olet tehnyt?\" sanasi puuroutuvat\nKun kaadut alas\n\n[Break]\nYksi asia vielä\nRipaus suloista kostoani\nYksi asia vielä\nRipaus suloista kostoani\n\n[Pre-Chorus]\nTein sinulle jotain, mistä tiedän sinun pitävän\nSuklaapäällysteinen, sokeria ja mausteita\nHerkullista, herkullista, nam-nam, herkullista\nKarkkivärit, oi, niin ihanat\n\n[Chorus]\nOta puraisu minun—\nMyrkkypiirakka\nMyrkkypiirakka\nMyrkkypiirakka\nMyrkkypiirakka\n\n[Post-Chorus]\nEi ole minun vikani, että innostuin liikaa\nMenetin kontrollin, kun aloin leipoa\nHipaus kuolemaa on parempi niellä\nÄlä pelkää, sinut haudataan huomenna\n\n[Outro]\n(Myrkky)\nMyrkkypiirakka\nMyrkkypiirakka\nMyrkkypiirakka\nMyrkkypiirakka\nMyrkkypiirakka\nMyrkkypiirakka\nMyrkkypiirakka\nMyrkkypiirakka",
|
|
||||||
"tags": [
|
|
||||||
"myrkky",
|
|
||||||
"kosto",
|
|
||||||
"makea",
|
|
||||||
"petos"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Englanti",
|
|
||||||
"running_order": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"artist": "Theo Evan",
|
|
||||||
"country": "CYP",
|
|
||||||
"title": "Shh",
|
|
||||||
"lyrics_url": "https://genius.com/Theo-evan-shh-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/theo-evan-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-CYPRUS-WHITE.png",
|
|
||||||
"lyrics_original": "[Verse 1]\nI've got golden locks and eyes so captivating\nI am famous for my beauty, who am I? (Shh)\nI've been told I am a little overconfident\nAnd this will be my downfall, who am I? (Shh)\n\n[Pre-Chorus]\nI fell in deep, I lost my heartbeat\nStuck in between, heaven and hell in me\nI fell in deep, I lost my heartbeat\nFollow the bloodstream\n\n[Chorus]\nHush, hush\nIf you know my name, then please don't repeat it\nHush, hush\nIf you know my name, then keep it a secret\nIn the ground, in the sky, in the afterlife\nIn the dark, in the light, in the sweet divine\nHush, hush\nHush, hush, hush\n\n[Verse 2]\nMy dеsire and my love got me in troublе\nI'm decay and I'm revival, who am I? (Shh)\nRobbed by jealousy and cheated by my enemy\nFrom tears and blood to flowers, who am I? (Shh)\n\n[Pre-Chorus]\nI fell in deep, I lost my heartbeat\nStuck in between, heaven and hell in me\nI fell in deep, I lost my heartbeat\nFollow the bloodstream\n\n[Chorus]\nHush, hush\nIf you know my name, then please don't repeat it\nHush, hush\nIf you know my name, then keep it a secret\nIn the ground, in the sky, in the afterlife\nIn the dark, in the light, in the sweet divine\nHush, hush\nHush, hush, hush\n\n[Bridge]\n(If you know my name, if you know my name, if you know my name)\nIf you know my name, if you know my name, if you know my name\nShh\nAh (If you know my name, if you know my name)\n\n[Chorus]\nIn the ground, in the sky, in the afterlife\nIn the dark, in the light, in the sweet divine\nHush, hush\nHush, hush, hush\n\n[Outro]\nShh",
|
|
||||||
"lyrics_translation_fi": "[Verse 1]\nMinulla on kultaiset kiharat ja silmät niin vangitsevat\nOlen kuuluisa kauneudestani, kuka minä olen? (Shh)\nMinulle on sanottu, että olen hieman liian itsevarma\nJa tämä on minun lankeemukseni, kuka minä olen? (Shh)\n\n[Pre-Chorus]\nPutoin syvälle, menetin sydämeni sykkeen\nJuuttunut väliin, taivas ja helvetti minussa\nPutoin syvälle, menetin sydämeni sykkeen\nSeuraa verenkiertoa\n\n[Chorus]\nHys, hys\nJos tiedät nimeni, älä toista sitä\nHys, hys\nJos tiedät nimeni, pidä se salassa\nMaassa, taivaalla, tuonpuoleisessa elämässä\nPimeässä, valossa, suloisessa jumalallisessa\nHys, hys\nHys, hys, hys\n\n[Verse 2]\nMinun haluni ja rakkauteni saivat minut vaikeuksiin\nOlen rappeutuminen ja olen elpyminen, kuka minä olen? (Shh)\nRyöstetty mustasukkaisuudella ja petetty viholliseni toimesta\nKyynelistä ja verestä kukkiin, kuka minä olen? (Shh)\n\n[Pre-Chorus]\nPutoin syvälle, menetin sydämeni sykkeen\nJuuttunut väliin, taivas ja helvetti minussa\nPutoin syvälle, menetin sydämeni sykkeen\nSeuraa verenkiertoa\n\n[Chorus]\nHys, hys\nJos tiedät nimeni, älä toista sitä\nHys, hys\nJos tiedät nimeni, pidä se salassa\nMaassa, taivaalla, tuonpuoleisessa elämässä\nPimeässä, valossa, suloisessa jumalallisessa\nHys, hys\nHys, hys, hys\n\n[Bridge]\n(Jos tiedät nimeni, jos tiedät nimeni, jos tiedät nimeni)\nJos tiedät nimeni, jos tiedät nimeni, jos tiedät nimeni\nShh\nAh (Jos tiedät nimeni, jos tiedät nimeni)\n\n[Chorus]\nMaassa, taivaalla, tuonpuoleisessa elämässä\nPimeässä, valossa, suloisessa jumalallisessa\nHys, hys\nHys, hys, hys\n\n[Outro]\nShh",
|
|
||||||
"tags": [
|
|
||||||
"kauneus",
|
|
||||||
"salaisuus",
|
|
||||||
"identiteetti",
|
|
||||||
"varjo",
|
|
||||||
"kuolema"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Englanti",
|
|
||||||
"running_order": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"artist": "ADONXS",
|
|
||||||
"country": "CZE",
|
|
||||||
"title": "Kiss Kiss Goodbye",
|
|
||||||
"lyrics_url": "https://genius.com/Adonxs-kiss-kiss-goodbye-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/adonxs-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-CZECHIA-WHITE.png",
|
|
||||||
"lyrics_original": "[Verse 1]\nBlow me a kiss goodbye, no more tears to dry\nOne kiss of love, one kiss of desperation\nIn a moment two of us collide\nThere's no other place to hide\nOnly one survives, when the night arrives\nWill you be my ride or die?\n\n[Pre-Chorus]\nOh, you seemed so holy, it almost fooled me\nTruly, when you said you love me, it almost moved me\n\n[Chorus]\nKiss, kiss goodbye (Kiss, kiss goodbye)\nKiss, kiss goodbye (Kiss, kiss goodbye)\nNow keep your half apologies, drunken apathy\nOver and over again\nKiss, kiss\n\n[Verse 2]\nCaught in a motion, you’re wearing mе out\nTainted devotion, heart on thе ground\nOnly one survives, when the night arrives\nWill you be my ride or die?\n\n[Pre-Chorus]\nTell me why is there you–\nYou seemed so holy, it almost fooled me\nTruly, when you said you love me, it almost moved me\n\n[Chorus]\nKiss, kiss goodbye (Kiss, kiss goodbye)\nKiss, kiss goodbye (Kiss, kiss goodbye)\nNow keep your half apologies, drunken apathy\nOver and over again\nKiss, kiss\n\n[Instrumental Break]\n\n[Pre-Chorus]\nBlow me a kiss goodbye, see how my tears run dry\nOne kiss of love won't feel the same tonight\n\n[Chorus]\n(Kiss, kiss goodbye)\n(Kiss, kiss goodbye)\n(Kiss, kiss goodbye)\n(Kiss, kiss goodbye)\n(Kiss, kiss goodbye)\nDon't give me your half apologies (Kiss, kiss goodbye), drunken apathy\nOver and over again\nKiss, kiss",
|
|
||||||
"lyrics_translation_fi": "[Verse 1]\nLähetä minulle hyvästelysuudelma, ei enää kyyneleitä kuivattavaksi\nYksi rakkauden suudelma, yksi epätoivon suudelma\nYhdessä hetkessä me kaksi törmäämme\nEi ole muuta paikkaa piiloutua\nVain yksi selviää, kun yö saapuu\nOletko valmis kulkemaan kanssani loppuun asti?\n\n[Pre-Chorus]\nVoi, vaikutit niin pyhältä, että melkein huijasit minua\nOikeasti, kun sanoit rakastavasi minua, se melkein liikutti minua\n\n[Chorus]\nSuudelma, hyvästi (Suudelma, hyvästi)\nSuudelma, hyvästi (Suudelma, hyvästi)\nNyt pidä puolinaiset anteeksipyyntösi, humalainen apatia\nYhä uudestaan\nSuudelma, suudelma\n\n[Verse 2]\nLiikkeessä kiinni, kulutat minua loppuun\nTurmeltunut antaumus, sydän maassa\nVain yksi selviää, kun yö saapuu\nOletko valmis kulkemaan kanssani loppuun asti?\n\n[Pre-Chorus]\nKerro minulle, miksi sinä olet olemassa–\nVaikutit niin pyhältä, että melkein huijasit minua\nOikeasti, kun sanoit rakastavasi minua, se melkein liikutti minua\n\n[Chorus]\nSuudelma, hyvästi (Suudelma, hyvästi)\nSuudelma, hyvästi (Suudelma, hyvästi)\nNyt pidä puolinaiset anteeksipyyntösi, humalainen apatia\nYhä uudestaan\nSuudelma, suudelma\n\n[Instrumental Break]\n\n[Pre-Chorus]\nLähetä minulle hyvästelysuudelma, katso kuinka kyyneleeni kuivuvat\nYksi rakkauden suudelma ei tunnu samalta tänä yönä\n\n[Chorus]\n(Suudelma, hyvästi)\n(Suudelma, hyvästi)\n(Suudelma, hyvästi)\n(Suudelma, hyvästi)\n(Suudelma, hyvästi)\nÄlä anna minulle puolinaisia anteeksipyyntöjäsi (Suudelma, hyvästi), humalaista apatiaa\nYhä uudestaan\nSuudelma, suudelma",
|
|
||||||
"tags": [
|
|
||||||
"suhde",
|
|
||||||
"pettymys",
|
|
||||||
"jäähyväiset",
|
|
||||||
"katumus"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Englanti",
|
|
||||||
"running_order": 9
|
"running_order": 9
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -176,6 +85,7 @@
|
|||||||
"country": "DNK",
|
"country": "DNK",
|
||||||
"title": "Hallucination",
|
"title": "Hallucination",
|
||||||
"lyrics_url": "https://genius.com/Sissal-hallucination-lyrics",
|
"lyrics_url": "https://genius.com/Sissal-hallucination-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250509_Corinne-Cumming_EBU_00158.jpg?h=44220c28&itok=YKaETyH4",
|
||||||
"artist_url": "https://eurovision.tv/participant/denmark-2025",
|
"artist_url": "https://eurovision.tv/participant/denmark-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-DENMARK-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-DENMARK-WHITE.png",
|
||||||
"lyrics_original": "[Verse 1]\nYou show me more\nMore than meets the eye\nYou open all the doors inside my mind\nI see colours I never saw before\nA thousand shades of light come to life\n\n[Pre-Chorus]\nMaking me question all that I thought true\nAll that I'm seeing through you\nMy vision's blurry but I can see clearly\nMaybe I'm losing control\n\n[Chorus]\nYou're my hallu-u-u\nHallucination\nHallu-u-u\nHallucination\n\n[Verse 2]\nI'm paranoid\nSlipping from reality\nYou're leading me into another fantasy\n\n[Pre-Chorus]\nOh, making me question all that I thought true\nAll that I'm seeing through you\nMy vision's blurry but I can see clearly\nMaybe I'm losing control\n\n[Chorus]\nYou're my hallu-u-u\nHallucination\nHallu-u-u\nHallucination\nA new-ew-ew\nRevelation\nHallu-u-u\nHallucination\n\n[Bridge]\nWorlds are changing\nDarkness fading\nI follow you to the end and now I'm changing (Now I'm changing)\nIt's a nеw sensation\n(Worlds are changing)\n(Darkness fading)\n(My hallucination)\nHallucination\n(Worlds arе changing)\n(Darknesѕ fading)\n(My hallucination)\n\n[Chorus]\nYou're my hallu-u-u\nHallucination\nHallu-u-u\nHallucination\n\n[Post-Chorus]\nHey yeah\nHey yeah\nHallucination\nYou're my hallu-u-u\nHallucination\nHallu-u-u\nHallucinаtion",
|
"lyrics_original": "[Verse 1]\nYou show me more\nMore than meets the eye\nYou open all the doors inside my mind\nI see colours I never saw before\nA thousand shades of light come to life\n\n[Pre-Chorus]\nMaking me question all that I thought true\nAll that I'm seeing through you\nMy vision's blurry but I can see clearly\nMaybe I'm losing control\n\n[Chorus]\nYou're my hallu-u-u\nHallucination\nHallu-u-u\nHallucination\n\n[Verse 2]\nI'm paranoid\nSlipping from reality\nYou're leading me into another fantasy\n\n[Pre-Chorus]\nOh, making me question all that I thought true\nAll that I'm seeing through you\nMy vision's blurry but I can see clearly\nMaybe I'm losing control\n\n[Chorus]\nYou're my hallu-u-u\nHallucination\nHallu-u-u\nHallucination\nA new-ew-ew\nRevelation\nHallu-u-u\nHallucination\n\n[Bridge]\nWorlds are changing\nDarkness fading\nI follow you to the end and now I'm changing (Now I'm changing)\nIt's a nеw sensation\n(Worlds are changing)\n(Darkness fading)\n(My hallucination)\nHallucination\n(Worlds arе changing)\n(Darknesѕ fading)\n(My hallucination)\n\n[Chorus]\nYou're my hallu-u-u\nHallucination\nHallu-u-u\nHallucination\n\n[Post-Chorus]\nHey yeah\nHey yeah\nHallucination\nYou're my hallu-u-u\nHallucination\nHallu-u-u\nHallucinаtion",
|
||||||
@@ -188,13 +98,14 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Englanti",
|
"language": "Englanti",
|
||||||
"running_order": 10
|
"running_order": 22
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Tommy Cash",
|
"artist": "Tommy Cash",
|
||||||
"country": "EST",
|
"country": "EST",
|
||||||
"title": "Espresso Macchiato",
|
"title": "Espresso Macchiato",
|
||||||
"lyrics_url": "https://genius.com/Tommy-cash-espresso-macchiato-lyrics",
|
"lyrics_url": "https://genius.com/Tommy-cash-espresso-macchiato-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250507_Corinne-Cumming_EBU_00226.jpg?h=444e4ea8&itok=TJ59RWTM",
|
||||||
"artist_url": "https://eurovision.tv/participant/tommy-cash-2025",
|
"artist_url": "https://eurovision.tv/participant/tommy-cash-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ESTONIA-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ESTONIA-WHITE.png",
|
||||||
"lyrics_original": "[Intro]\nMi amore, mi amore\nEspresso macchiato, macchiato, macchiato\nPor favore, por favore\nEspresso macchiato corneo\n\n[Chorus]\nMi amore, mi amore\nEspresso macchiato, macchiato, macchiato\nPor favore, por favore\nEspresso macchiato\nEspresso macchiato\n\n[Post-Chorus]\n(Uh-huh, uh-huh, uh-huh)\n\n[Verse 1]\nCiao bella, I'm Tommaso, addicted to tobacco\nMi like mi coffè very importante\nNo time to talk, scusi, my days are very busy\nAnd I just own this little ristorante\n\n[Pre-Chorus]\nLife may give you lemons when dancing with the demons\nNo stresso, no stresso, no need to be depresso\n\n[Chorus]\nMi amore, mi amore\nEspresso macchiato, macchiato, macchiato\nPor favore, por favore\nEspresso macchiato corneo\nMi amorе, mi amore\nEspresso macchiato, macchiato, macchiato\nPor favore, por favorе\nEspresso macchiato\nEspresso macchiato\n\n[Verse 2]\nMi like to fly privati with twenty-four carati\nAlso mi casa very grandioso\nMi money numeroso, I work around the clocko\nThat's why I'm sweating like a mafioso\n\n[Pre-Chorus]\nLife is like spaghetti, it's hard until you make it\nNo stresso, no stresso, it's gonna be espresso\n\n[Chorus]\nMi amore, mi amore\nEspresso macchiato, macchiato, macchiato\nPor favore, por favore\nEspresso macchiato\nEspresso macchiato\n\n[Outro]\nLa-la-la-la, la-la-la-la\nLa-la-la-la, la-la-la-la, la-la-la-la, la-la-la\nLa-la-la-la, la-la-la-la\nEspresso macchiato\nEspresso macchiato",
|
"lyrics_original": "[Intro]\nMi amore, mi amore\nEspresso macchiato, macchiato, macchiato\nPor favore, por favore\nEspresso macchiato corneo\n\n[Chorus]\nMi amore, mi amore\nEspresso macchiato, macchiato, macchiato\nPor favore, por favore\nEspresso macchiato\nEspresso macchiato\n\n[Post-Chorus]\n(Uh-huh, uh-huh, uh-huh)\n\n[Verse 1]\nCiao bella, I'm Tommaso, addicted to tobacco\nMi like mi coffè very importante\nNo time to talk, scusi, my days are very busy\nAnd I just own this little ristorante\n\n[Pre-Chorus]\nLife may give you lemons when dancing with the demons\nNo stresso, no stresso, no need to be depresso\n\n[Chorus]\nMi amore, mi amore\nEspresso macchiato, macchiato, macchiato\nPor favore, por favore\nEspresso macchiato corneo\nMi amorе, mi amore\nEspresso macchiato, macchiato, macchiato\nPor favore, por favorе\nEspresso macchiato\nEspresso macchiato\n\n[Verse 2]\nMi like to fly privati with twenty-four carati\nAlso mi casa very grandioso\nMi money numeroso, I work around the clocko\nThat's why I'm sweating like a mafioso\n\n[Pre-Chorus]\nLife is like spaghetti, it's hard until you make it\nNo stresso, no stresso, it's gonna be espresso\n\n[Chorus]\nMi amore, mi amore\nEspresso macchiato, macchiato, macchiato\nPor favore, por favore\nEspresso macchiato\nEspresso macchiato\n\n[Outro]\nLa-la-la-la, la-la-la-la\nLa-la-la-la, la-la-la-la, la-la-la-la, la-la-la\nLa-la-la-la, la-la-la-la\nEspresso macchiato\nEspresso macchiato",
|
||||||
@@ -207,13 +118,14 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Italia Ja Englanti",
|
"language": "Italia Ja Englanti",
|
||||||
"running_order": 11
|
"running_order": 3
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Erika Vikman",
|
"artist": "Erika Vikman",
|
||||||
"country": "FIN",
|
"country": "FIN",
|
||||||
"title": "ICH KOMME",
|
"title": "ICH KOMME",
|
||||||
"lyrics_url": "https://genius.com/Genius-english-translations-erika-vikman-ich-komme-english-translation-lyrics",
|
"lyrics_url": "https://genius.com/Genius-english-translations-erika-vikman-ich-komme-english-translation-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250509_Corinne-Cumming_EBU_00700.jpg?h=f4a86482&itok=0jvy2KHI",
|
||||||
"artist_url": "https://eurovision.tv/participant/erika-vikman-2025",
|
"artist_url": "https://eurovision.tv/participant/erika-vikman-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-FINLAND-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-FINLAND-WHITE.png",
|
||||||
"lyrics_original": "[Intro]\n(I'm coming)\n\n[Verse 1]\nNight falls, heart beats, they fall in love\nMoon rises, earth arches, my gates are open (Hey)\n\n[Pre-Chorus]\nI am Erika, welcome\nYou are like gorgeous, trance god\nJust make yourself at home, do what you want\nAnd when you come, I'll come with you\n\n[Chorus]\n(I'm coming, I'm coming)\nAnd before you come, I hear you screaming\n(I'm coming, I'm coming)\nAnd to that, I scream out loud, \"I'm coming\"\n(I'm coming, I'm coming)\nAnd together we come and be like\n(I'm coming, I'm coming)\nIt's like that when you fall in love\n(Wonderful)\n\n[Verse 2]\nI am Erika, nice to meet you\nI'll dance with you evеn a wedding waltz, but naked\n\n[Pre-Chorus]\nI am Erika, you're full of stamina\nHit mе once again, grab my ass\nAnd when you want more love, just shout, \"Encore\"\nAnd, baby, I'm coming\n\n[Chorus]\n(I'm coming, I'm coming)\nAnd again when you come, I hear you screaming\n(I'm coming, I'm coming)\nAnd all I can do is to cry, \"I'm coming\"\n(I'm coming, I'm coming)\nAnd together we come and be like\n(I'm coming, I'm coming)\nIt's like that when you fall in love\n\n[Bridge]\nLet go and let it happen\nBaby, surrender and come with me\nThe stars in your eyes and me on top of you\nBaby, you deserve only good\nLet go and let it happen\nBaby, you can still fall in love with yourself\nStars in your eyes and me on top of you\nBaby, you deserve only good\n\n[Outro]\n(I'm coming, I'm coming) I'm coming\n(I'm coming, I'm coming) Fall in love\n(I'm coming, I'm coming) Hey, baby\n(I'm coming, I'm coming) It's like that when you fall in love",
|
"lyrics_original": "[Intro]\n(I'm coming)\n\n[Verse 1]\nNight falls, heart beats, they fall in love\nMoon rises, earth arches, my gates are open (Hey)\n\n[Pre-Chorus]\nI am Erika, welcome\nYou are like gorgeous, trance god\nJust make yourself at home, do what you want\nAnd when you come, I'll come with you\n\n[Chorus]\n(I'm coming, I'm coming)\nAnd before you come, I hear you screaming\n(I'm coming, I'm coming)\nAnd to that, I scream out loud, \"I'm coming\"\n(I'm coming, I'm coming)\nAnd together we come and be like\n(I'm coming, I'm coming)\nIt's like that when you fall in love\n(Wonderful)\n\n[Verse 2]\nI am Erika, nice to meet you\nI'll dance with you evеn a wedding waltz, but naked\n\n[Pre-Chorus]\nI am Erika, you're full of stamina\nHit mе once again, grab my ass\nAnd when you want more love, just shout, \"Encore\"\nAnd, baby, I'm coming\n\n[Chorus]\n(I'm coming, I'm coming)\nAnd again when you come, I hear you screaming\n(I'm coming, I'm coming)\nAnd all I can do is to cry, \"I'm coming\"\n(I'm coming, I'm coming)\nAnd together we come and be like\n(I'm coming, I'm coming)\nIt's like that when you fall in love\n\n[Bridge]\nLet go and let it happen\nBaby, surrender and come with me\nThe stars in your eyes and me on top of you\nBaby, you deserve only good\nLet go and let it happen\nBaby, you can still fall in love with yourself\nStars in your eyes and me on top of you\nBaby, you deserve only good\n\n[Outro]\n(I'm coming, I'm coming) I'm coming\n(I'm coming, I'm coming) Fall in love\n(I'm coming, I'm coming) Hey, baby\n(I'm coming, I'm coming) It's like that when you fall in love",
|
||||||
@@ -226,13 +138,14 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Englanti",
|
"language": "Englanti",
|
||||||
"running_order": 12
|
"running_order": 13
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Louane",
|
"artist": "Louane",
|
||||||
"country": "FRA",
|
"country": "FRA",
|
||||||
"title": "maman",
|
"title": "maman",
|
||||||
"lyrics_url": "https://genius.com/Genius-english-translations-louane-maman-english-translation-lyrics",
|
"lyrics_url": "https://genius.com/Genius-english-translations-louane-maman-english-translation-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250510_Corinne-Cumming_EBU_00058.jpg?h=90275b85&itok=5WHUtsHd",
|
||||||
"artist_url": "https://eurovision.tv/participant/louane-2025",
|
"artist_url": "https://eurovision.tv/participant/louane-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-FRANCE-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-FRANCE-WHITE.png",
|
||||||
"lyrics_original": "[Verse 1]\nNo more lovers\nNo more beds\nIn the end, you see\nI built my life\nAnd the emptiness is vast\nSo are the questions\nHow are you doing?\nDo you see everything here?\nAnd I've changed a lot\nI've grown up\nFrom you, I've kept\nEverything that makes me who I am\n\n[Chorus]\nI'm better now, I know where I'm going\nI've stopped counting the years\nAnd if I once wanted to stop time\nNow, I'm the one she calls \"mom\"\n\n[Post-Chorus]\nMom, mom, mom\n\n[Verse 2]\nI've found love\nIndеlible\nYou know, the real kind \"forеver\"\nEven when time flies\nWhen he holds my hand\nI'm not afraid of anything anymore\nAnd it feels just like before\nWhen you used to hold my hand\n\n[Chorus]\nI'm better now, I know where I'm going\nI've stopped counting the years\nAnd if I once wanted to stop time\nNow, I'm the one she calls \"mom\"\n\n[Post-Chorus]\nMom, mom, mom\nMom, mom, mom\n\n[Chorus]\nI'm better now, I know where I'm going\nI've stopped counting the years\nAnd if I once wanted to stop time\nNow, I'm the one she calls\n\n[Post-Chorus]\nMom, mom, mom\nMom, mom\n\n[Outro]\nIf I once wanted to stop time\nNow, I'm the one she calls\nMom",
|
"lyrics_original": "[Verse 1]\nNo more lovers\nNo more beds\nIn the end, you see\nI built my life\nAnd the emptiness is vast\nSo are the questions\nHow are you doing?\nDo you see everything here?\nAnd I've changed a lot\nI've grown up\nFrom you, I've kept\nEverything that makes me who I am\n\n[Chorus]\nI'm better now, I know where I'm going\nI've stopped counting the years\nAnd if I once wanted to stop time\nNow, I'm the one she calls \"mom\"\n\n[Post-Chorus]\nMom, mom, mom\n\n[Verse 2]\nI've found love\nIndеlible\nYou know, the real kind \"forеver\"\nEven when time flies\nWhen he holds my hand\nI'm not afraid of anything anymore\nAnd it feels just like before\nWhen you used to hold my hand\n\n[Chorus]\nI'm better now, I know where I'm going\nI've stopped counting the years\nAnd if I once wanted to stop time\nNow, I'm the one she calls \"mom\"\n\n[Post-Chorus]\nMom, mom, mom\nMom, mom, mom\n\n[Chorus]\nI'm better now, I know where I'm going\nI've stopped counting the years\nAnd if I once wanted to stop time\nNow, I'm the one she calls\n\n[Post-Chorus]\nMom, mom, mom\nMom, mom\n\n[Outro]\nIf I once wanted to stop time\nNow, I'm the one she calls\nMom",
|
||||||
@@ -246,33 +159,14 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Englanti",
|
"language": "Englanti",
|
||||||
"running_order": 13
|
"running_order": 24
|
||||||
},
|
|
||||||
{
|
|
||||||
"artist": "Mariam Shengelia",
|
|
||||||
"country": "GEO",
|
|
||||||
"title": "Freedom",
|
|
||||||
"lyrics_url": "https://genius.com/Genius-english-translations-mariam-shengelia-freedom-english-translation-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/mariam-shengelia-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-GEORGIA-WHITE.png",
|
|
||||||
"lyrics_original": "[Verse]\nFreedom\n(A and B, G and D\nYears pass, times pass\nE and V, Z and T\nLet us never give up this sky and mountain)\nLike the scent of grass\nThe dew of the spring, Ankara\nThe thirst for freedom\nThe homeland carried with its heart\n\n[Pre-Chorus]\nAnd as this heart in my chest\nFreedom is in me\nAnd as our life\nThe homeland is the only one\n\n[Chorus]\nThis Sun warms and illuminates the roads\nThe mountains and seas of the homeland\nWe will follow the roads with hope\nAnd, blue and cloudless sky\nA calm and turbulent sea\nI do not want other treasures\n\n[Bridge]\nFreedom is a heavеn's gift we share\nFreedom is a human wealth to carе\nFreedom is a mountain spring for share\nPeople let it be here and there everywhere\n\n[Chorus]\nI'm living my life with my smile\nKindness resists of my style\nFreedom that makes life so mild\nMy home, my sweet apparel a song\nMeadows and mountains to roam\nThis is my wealth and my throne\n\n[Outro]\nFreedom, freedom, A and B",
|
|
||||||
"lyrics_translation_fi": "[Säkeistö]\nVapaus\n(A ja B, G ja D\nVuodet kuluvat, ajat kuluvat\nE ja V, Z ja T\nÄlkäämme koskaan luopuko tästä taivaasta ja vuoresta)\nKuin ruohon tuoksu\nKevään kaste, Ankara\nVapauden jano\nKotimaa sydämessään kannettuna\n\n[Esikertosäe]\nJa kun tämä sydän rinnassani\nVapaus on minussa\nJa kun elämämme\nKotimaa on ainoa\n\n[Kertosäe]\nTämä aurinko lämmittää ja valaisee tiet\nKotimaan vuoret ja meret\nSeuraamme teitä toivolla\nJa, sininen ja pilvetön taivas\nTyyni ja myrskyisä meri\nEn halua muita aarteita\n\n[Silta]\nVapaus on taivaan lahja, jonka jaamme\nVapaus on ihmisen rikkaus, josta välitämme\nVapaus on vuoristolähde jaettavaksi\nIhmiset, antakaa sen olla täällä ja kaikkialla\n\n[Kertosäe]\nElän elämääni hymyillen\nYstävällisyys vastustaa tyyliäni\nVapaus, joka tekee elämästä niin lempeää\nKotini, suloinen asuni, laulu\nNiityt ja vuoret vaeltaa\nTämä on rikkauteni ja valtaistuimeni\n\n[Outro]\nVapaus, vapaus, A ja B",
|
|
||||||
"tags": [
|
|
||||||
"vapaus",
|
|
||||||
"kotimaa",
|
|
||||||
"toivo",
|
|
||||||
"luonto",
|
|
||||||
"rauha"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Turkki",
|
|
||||||
"running_order": 14
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Abor & Tynna",
|
"artist": "Abor & Tynna",
|
||||||
"country": "DEU",
|
"country": "DEU",
|
||||||
"title": "Baller",
|
"title": "Baller",
|
||||||
"lyrics_url": "https://genius.com/Abor-and-tynna-baller-lyrics",
|
"lyrics_url": "https://genius.com/Abor-and-tynna-baller-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250510_Corinne-Cumming_EBU_00020.jpg?h=2df284cc&itok=vOLBSCZU",
|
||||||
"artist_url": "https://eurovision.tv/participant/abor-tynna-2025",
|
"artist_url": "https://eurovision.tv/participant/abor-tynna-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-GERMANY-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-GERMANY-WHITE.png",
|
||||||
"lyrics_original": "[Songtext zu „Baller“]\n\n[Refrain: Tynna]\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\n\n[Strophe 1: Tynna]\nKreidesilhouetten auf dem Trottoir\nZwischen uns ein Tatort wie bеi CSI\nHast „Baby, tut mir leid“ gesagt zum erstеn Mal\nHätt wissen soll'n, dass das das Ende von uns war\n\n[Pre-Refrain: Tynna]\nDu setzt 'n Punkt nach dem Satz, als hättst du mich nie gekannt\nAlso wechsel' ich Parfums und kauf' mir neues Gewand\nIch krieg' wieder diesen Drang, ich will den Weltuntergang\nHa, ich glaub', das war's, I shoot for the stars\n\n[Refrain: Tynna]\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\n\n[Strophe 2: Tynna]\nIch seh' die Sternensplitter auf meiner Haut wie Glitzer\nHab' gelernt, was mich nicht killt, macht mich nur schicker\nWürdest du für mich immer noch 'ne Kugel fang'n?\nWeil deine Waffe ist jetzt in meiner Hand\n\n[Pre-Refrain: Tynna]\nIch setz' 'n Punkt nach dem Satz, als hätt ich dich nie gekannt\nUnd dann wechsel' ich Parfums und kauf' mir neues Gewand\nIch krieg' wieder diesen Drang, ich will den Weltuntergang\nHa, ich glaub', das war's, I shoot for the stars\n\n[Refrain: Tynna]\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\n\n[Outro: Tynna]\nIch baller', la-la\nIch baller', la\nLa-la-la-la-la-l—",
|
"lyrics_original": "[Songtext zu „Baller“]\n\n[Refrain: Tynna]\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\n\n[Strophe 1: Tynna]\nKreidesilhouetten auf dem Trottoir\nZwischen uns ein Tatort wie bеi CSI\nHast „Baby, tut mir leid“ gesagt zum erstеn Mal\nHätt wissen soll'n, dass das das Ende von uns war\n\n[Pre-Refrain: Tynna]\nDu setzt 'n Punkt nach dem Satz, als hättst du mich nie gekannt\nAlso wechsel' ich Parfums und kauf' mir neues Gewand\nIch krieg' wieder diesen Drang, ich will den Weltuntergang\nHa, ich glaub', das war's, I shoot for the stars\n\n[Refrain: Tynna]\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\n\n[Strophe 2: Tynna]\nIch seh' die Sternensplitter auf meiner Haut wie Glitzer\nHab' gelernt, was mich nicht killt, macht mich nur schicker\nWürdest du für mich immer noch 'ne Kugel fang'n?\nWeil deine Waffe ist jetzt in meiner Hand\n\n[Pre-Refrain: Tynna]\nIch setz' 'n Punkt nach dem Satz, als hätt ich dich nie gekannt\nUnd dann wechsel' ich Parfums und kauf' mir neues Gewand\nIch krieg' wieder diesen Drang, ich will den Weltuntergang\nHa, ich glaub', das war's, I shoot for the stars\n\n[Refrain: Tynna]\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\nIch baller' Löcher in die Nacht\nSterne fall'n und knall'n auf mein Dach\nEs tut noch bisschen weh, wenn ich dich wiederseh'\nAber ich komm' nie wieder, egal was du mir sagst\n\n[Outro: Tynna]\nIch baller', la-la\nIch baller', la\nLa-la-la-la-la-l—",
|
||||||
@@ -285,13 +179,14 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Saksa",
|
"language": "Saksa",
|
||||||
"running_order": 15
|
"running_order": 16
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Klavdia",
|
"artist": "Klavdia",
|
||||||
"country": "GRC",
|
"country": "GRC",
|
||||||
"title": "Asteromáta",
|
"title": "Asteromáta",
|
||||||
"lyrics_url": "https://genius.com/Klavdia-asteromata-lyrics",
|
"lyrics_url": "https://genius.com/Klavdia-asteromata-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250508_Corinne-Cumming_EBU_00970.jpg?h=9b742a13&itok=twZoSdkc",
|
||||||
"artist_url": "https://eurovision.tv/participant/klavdia-2025",
|
"artist_url": "https://eurovision.tv/participant/klavdia-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-GREECE-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-GREECE-WHITE.png",
|
||||||
"lyrics_original": "[Εισαγωγή]\nΑστέρι μου\nΑστέρι μου\n\n[Στροφή 1]\nΓλυκιά μου μάνα μην μου κλαις\nΜαύρα και αν σου φορούνε\nΤο ξέθωρο το σώμα μου\nΦλόγες δεν το νικούνε\n\n[Στροφή 2]\nΤα χελιδόνια της φωτιάς\nΘάλασσες και αν περνούνε\nΤου ριζωμού τα χώματα\nΠοτέ δεν λησμονούνε\n\n[Ρεφραίν]\nΑστερομάτα μου μικρή\nΓύρε να σε φιλήσω\nΣτα άγια σου τα δάκρυα\nΤα χείλη μου να σβήσω\nΑστερομάτα μου μικρή\nΓύρε μου να σε πιάσω\nΤα ξεχασμένα μου φτερά\nΣτερνά να ξαποστάσω\n\n[Μετά-Ρεφραίν]\nΑχ αστέρι μου, τζιβαέρι μου\n\n[Στροφή 3]\nΓλυκιά μου μάνα μην μου κλαις\nΚαράβι είναι η ζωή μου\nΠου ψάχνει για τον γυρισμό\nΑγέρα το πανί μου\n\n[Ρεφραίν]\nΑστερομάτα μου μικρή\nΓύρε μου να σε πιάσω\nΤα ξεχασμένα μου φτερά\nΣτερνά να ξαποστάσω\n\n[Μετά-Ρεφραίν]\nΑχ αστέρι μου, τζιβαέρι μου\nΑχ αστέρι μου, τζιβαέρι μου\n\n[Έξοδος]\nΑστέρι μου",
|
"lyrics_original": "[Εισαγωγή]\nΑστέρι μου\nΑστέρι μου\n\n[Στροφή 1]\nΓλυκιά μου μάνα μην μου κλαις\nΜαύρα και αν σου φορούνε\nΤο ξέθωρο το σώμα μου\nΦλόγες δεν το νικούνε\n\n[Στροφή 2]\nΤα χελιδόνια της φωτιάς\nΘάλασσες και αν περνούνε\nΤου ριζωμού τα χώματα\nΠοτέ δεν λησμονούνε\n\n[Ρεφραίν]\nΑστερομάτα μου μικρή\nΓύρε να σε φιλήσω\nΣτα άγια σου τα δάκρυα\nΤα χείλη μου να σβήσω\nΑστερομάτα μου μικρή\nΓύρε μου να σε πιάσω\nΤα ξεχασμένα μου φτερά\nΣτερνά να ξαποστάσω\n\n[Μετά-Ρεφραίν]\nΑχ αστέρι μου, τζιβαέρι μου\n\n[Στροφή 3]\nΓλυκιά μου μάνα μην μου κλαις\nΚαράβι είναι η ζωή μου\nΠου ψάχνει για τον γυρισμό\nΑγέρα το πανί μου\n\n[Ρεφραίν]\nΑστερομάτα μου μικρή\nΓύρε μου να σε πιάσω\nΤα ξεχασμένα μου φτερά\nΣτερνά να ξαποστάσω\n\n[Μετά-Ρεφραίν]\nΑχ αστέρι μου, τζιβαέρι μου\nΑχ αστέρι μου, τζιβαέρι μου\n\n[Έξοδος]\nΑστέρι μου",
|
||||||
@@ -305,52 +200,14 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Kreikka",
|
"language": "Kreikka",
|
||||||
"running_order": 16
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"artist": "VÆB",
|
|
||||||
"country": "ISL",
|
|
||||||
"title": "RÓA",
|
|
||||||
"lyrics_url": "https://genius.com/Vb-isl-roa-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/V%C3%86B-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ICELAND-WHITE-v2.png",
|
|
||||||
"lyrics_original": "[Söngtextar fyrir „RÓA“]\n\n[Byrjun: Matthías]\nLet's go!\n\n[Viðlag: Hálfdán]\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af, ooh-ooh\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af\n\n[Vísa 1: Matthías, Hálfdán, VÆB]\nÉg set spítu ofan á spítu og kalla það bát\nEf ég sekk í dag er það ekkert mál\nMeð árar úr stáli sem duga í ár\nStefni á Færeyjar, já, eg er klár\nÉg er með vesti fyrir belti og vatnshelda skó\nÞví að veðrið það er erfitt ég er kominn með nóg\n\n[Fyrir-Viðlag: Matthías]\nEr sjórinn opnast koma öldurnar\nÉg er einn á bát að leita af betri stað\nÉg er ekki ennþá búin að missa allt\nEn við setjum seglin upp og höldum aftur af stað\n\n[Viðlag: Hálfdán]\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkеrt stoppað mig af, ooh-ooh\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkеrt stoppað mig af, ooh-ooh\n\n[Vísa 2: Matthías, Hálfdán]\nÉg er enþá á bát, sjáðu þetta, vá\nStoppa í Grænlandi? Já, ég er down! God damn!\nStýri á sjó ég er kapteinn (Ey!)\nKallaðu mig Gísli Marteinn\nMargir mánuðir síðan ég sá síðast sól\nVil eiða restinni af lífinu hér út á sjó\n\n[Fyrir-Viðlag: Matthías]\nEr sjórinn opnast koma öldurnar\nÉg er en á bát að leita af betri stað\nÉg er ekki ennþá búin að missa allt\nEn við setjum seglin upp og höldum aftur af stað\n\n[Viðlag: Hálfdán, Matthías]\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af, ooh-ooh\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af, ooh-ooh\n\n[Brú]\n(Hey, hey, hey, hey)\n(Hey, hey)\n\n[Viðlag: Hálfdán, Matthías]\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af, ooh-ooh\nRóandi hér, róandi þar\nRóa í gegnum öldurnar\nÞað getur ekkert stoppað mig af, ooh-ooh (Það getur ekkert stoppað mig af)\n\n[Endir: VÆB]\nÞað getur ekkert stoppað mig af\nÞað getur ekkert stoppað mig af, ooh-ooh",
|
|
||||||
"lyrics_translation_fi": "[Sanoitukset kappaleelle \"RÓA\"]\n\n[Alku: Matthías]\nLet's go!\n\n[Kertosäe: Hálfdán]\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua\n\n[Säkeistö 1: Matthías, Hálfdán, VÆB]\nLaitan riman riman päälle ja kutsun sitä veneeksi\nJos uppoan tänään, ei se ole ongelma\nMeloilla teräksestä, jotka kestävät vuoden\nSuuntaan Färsaarille, kyllä, olen valmis\nMinulla on liivi vyötäröllä ja vedenpitävät kengät\nKoska sää on vaikea, olen saanut tarpeekseni\n\n[Esikertosäe: Matthías]\nKun meri avautuu, aallot tulevat\nOlen yksin veneessä etsimässä parempaa paikkaa\nEn ole vielä menettänyt kaikkea\nMutta nostamme purjeet ja lähdemme taas\n\n[Kertosäe: Hálfdán]\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\n\n[Säkeistö 2: Matthías, Hálfdán]\nOlen yhä veneessä, katso tätä, vau\nPysähdytäänkö Grönlantiin? Kyllä, olen mukana! God damn!\nOhjaan merellä, olen kapteeni (Ey!)\nKutsu minua Gísli Marteinniksi\nMeni monta kuukautta siitä kun näin viimeksi aurinkoa\nHaluan viettää loppuelämäni täällä merellä\n\n[Esikertosäe: Matthías]\nKun meri avautuu, aallot tulevat\nOlen yhä veneessä etsimässä parempaa paikkaa\nEn ole vielä menettänyt kaikkea\nMutta nostamme purjeet ja lähdemme taas\n\n[Kertosäe: Hálfdán, Matthías]\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\n\n[Silta]\n(Hey, hey, hey, hey)\n(Hey, hey)\n\n[Kertosäe: Hálfdán, Matthías]\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh\nMelomista täällä, melomista tuolla\nMelomista aaltojen läpi\nMikään ei voi pysäyttää minua, ooh-ooh (Mikään ei voi pysäyttää minua)\n\n[Loppu: VÆB]\nMikään ei voi pysäyttää minua\nMikään ei voi pysäyttää minua, ooh-ooh",
|
|
||||||
"tags": [
|
|
||||||
"veneily",
|
|
||||||
"matka",
|
|
||||||
"äärimmäisyys",
|
|
||||||
"hauska"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Islanti",
|
|
||||||
"running_order": 17
|
"running_order": 17
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"artist": "EMMY",
|
|
||||||
"country": "IRL",
|
|
||||||
"title": "Laika Party",
|
|
||||||
"lyrics_url": "https://genius.com/Emmy-laika-party-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/emmy-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-IRELAND-WHITE.png",
|
|
||||||
"lyrics_original": "[Intro]\n(Ready for takeoff)\n\n[Verse 1]\nYou have probably heard 'bout this brave little girl\nAll we know is that she saved the world\nShe got sent away, but she wasn't afraid\nThough she was alone in the dark big space\n\n[Pre-Chorus]\nLeft the cheering crowd, went above the clouds\nRocket goin' high, then they said goodbye\nShe still wonders why, but she loves to fly\n\n[Chorus]\nI hope Laika never died and that she spins around us still\nAnd that she has a party in the air and always will\nI hope that she is dancin' every night among the stars\nI hopе Laika is alive\n\n[Build]\nLaika party in the sky\nLaika party in the sky\n\n[Drop]\nBam-bam, ba-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\n\n[Verse 2]\nThrough thе comets and the stones\nShe is howling for her bones\nSingin' alone to the stars and sun\nAnd if she didn't fly, nor would you and I\nA forever hero for humankind\n\n[Pre-Chorus]\nLeft the cheering crowd, went above the clouds\nRocket goin' high, then they said goodbye\nShe still wonders why, but she loves to fly\n\n[Chorus]\nI hope Laika never died and that she spins around us still\nAnd that she has a party in the air and always will\nI hope that she is dancin' every night among the stars\nI hope Laika is alive\n\n[Build]\nLaika party in the sky\nLaika party in the sky\n\n[Drop]\nBam-bam, ba-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\n\n[Outro]\nLaika party in the sky, oh\nLaika party in the sky, oh\nBam-bam, ba-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nLaika party in the sky (Ba-da-dam-bam-bam-bam—)",
|
|
||||||
"lyrics_translation_fi": "[Intro]\n(Valmiina lentoonlähtöön)\n\n[Verse 1]\nOlet luultavasti kuullut tästä urheasta pienestä tytöstä\nTiedämme vain, että hän pelasti maailman\nHänet lähetettiin pois, mutta hän ei pelännyt\nVaikka hän oli yksin pimeässä suuressa avaruudessa\n\n[Pre-Chorus]\nJätti hurraavan yleisön, meni pilvien yläpuolelle\nRaketti menee korkealle, sitten he sanoivat hyvästit\nHän miettii yhä miksi, mutta hän rakastaa lentämistä\n\n[Chorus]\nToivon, että Laika ei koskaan kuollut ja että hän pyörii yhä ympärillämme\nJa että hänellä on juhlat ilmassa ja tulee aina olemaan\nToivon, että hän tanssii joka ilta tähtien joukossa\nToivon, että Laika on elossa\n\n[Build]\nLaikan juhlat taivaalla\nLaikan juhlat taivaalla\n\n[Drop]\nBam-bam, ba-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\n\n[Verse 2]\nKomeettojen ja kivien läpi\nHän ulvoo luidensa perään\nLaulaa yksin tähdille ja auringolle\nJa jos hän ei olisi lentänyt, emme myöskään sinä ja minä\nIkuinen sankari ihmiskunnalle\n\n[Pre-Chorus]\nJätti hurraavan yleisön, meni pilvien yläpuolelle\nRaketti menee korkealle, sitten he sanoivat hyvästit\nHän miettii yhä miksi, mutta hän rakastaa lentämistä\n\n[Chorus]\nToivon, että Laika ei koskaan kuollut ja että hän pyörii yhä ympärillämme\nJa että hänellä on juhlat ilmassa ja tulee aina olemaan\nToivon, että hän tanssii joka ilta tähtien joukossa\nToivon, että Laika on elossa\n\n[Build]\nLaikan juhlat taivaalla\nLaikan juhlat taivaalla\n\n[Drop]\nBam-bam, ba-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\n\n[Outro]\nLaikan juhlat taivaalla, oh\nLaikan juhlat taivaalla, oh\nBam-bam, ba-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nBa-da-dam-bam-bam-bam-bam-bam\nLaikan juhlat taivaalla (Ba-da-dam-bam-bam-bam—)",
|
|
||||||
"tags": [
|
|
||||||
"Laika",
|
|
||||||
"avaruus",
|
|
||||||
"uhraus",
|
|
||||||
"toivo",
|
|
||||||
"tanssi"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Englanti",
|
|
||||||
"running_order": 18
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"artist": "Yuval Raphael",
|
"artist": "Yuval Raphael",
|
||||||
"country": "ISR",
|
"country": "ISR",
|
||||||
"title": "New Day Will Rise",
|
"title": "New Day Will Rise",
|
||||||
"lyrics_url": "https://genius.com/Yuval-raphael-new-day-will-rise-lyrics",
|
"lyrics_url": "https://genius.com/Yuval-raphael-new-day-will-rise-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250509_Corinne-Cumming_EBU_00398.jpg?h=60b43f30&itok=8iMI4yNp",
|
||||||
"artist_url": "https://eurovision.tv/participant/yuval-raphael-2025",
|
"artist_url": "https://eurovision.tv/participant/yuval-raphael-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ISRAEL-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ISRAEL-WHITE.png",
|
||||||
"lyrics_original": "[Verse 1]\nAnd even if you say goodbye, you'll never go away\nYou are the rainbow in my sky, my colors in the grey\nMy only wish upon a star, sunshine in the day\nThe only song that my piano ever plays\nAnd even if you say goodbye, you'll always be around\nTo lift me up and take me high, keep my feet close to the ground\nAre you proud of me tonight? Dreams are comin' true\nI choose the light, nothin' to lose if I lose you\n\n[Chorus]\nNew day will rise, life will go on\nEveryone cries, don't cry alone\nDarkness will fade, all the pain will go by\nBut wе will stay, even if you say goodbye\n\n[Verse 2]\nEt mêmе si tu dis adieu, tu ne partiras jamais\nT'es l'arc-en-ciel de mon ciel bleu, mes couleurs dans le gris\nEt mon seul souhait sous un ciel d'art, un rayon dans ma journée\nLa seule chanson que mon piano peut jouer\n\n[Chorus]\nNew day will rise, life will go on\nEveryone cries, don't cry alone\nDarkness will fade, all the pain will go by\nBut we will stay, even if you say\nNew day will rise, life will go on\nEveryone cries, don't cry alone\nDarkness will fade, all the pain will go by\nBut you will stay, love of my life\n\n[Bridge]\nמים רבים לא יכבו\nאת האהבה ונהרות, לא ישטפוה\n\n[Chorus]\nNew day will rise\nEveryone cries, don't cry alone\nDarkness will fade, all the pain will go by\nBut we will stay\n\n[Outro]\nEven if you say goodbye\nA new day will rise\nNew day will rise",
|
"lyrics_original": "[Verse 1]\nAnd even if you say goodbye, you'll never go away\nYou are the rainbow in my sky, my colors in the grey\nMy only wish upon a star, sunshine in the day\nThe only song that my piano ever plays\nAnd even if you say goodbye, you'll always be around\nTo lift me up and take me high, keep my feet close to the ground\nAre you proud of me tonight? Dreams are comin' true\nI choose the light, nothin' to lose if I lose you\n\n[Chorus]\nNew day will rise, life will go on\nEveryone cries, don't cry alone\nDarkness will fade, all the pain will go by\nBut wе will stay, even if you say goodbye\n\n[Verse 2]\nEt mêmе si tu dis adieu, tu ne partiras jamais\nT'es l'arc-en-ciel de mon ciel bleu, mes couleurs dans le gris\nEt mon seul souhait sous un ciel d'art, un rayon dans ma journée\nLa seule chanson que mon piano peut jouer\n\n[Chorus]\nNew day will rise, life will go on\nEveryone cries, don't cry alone\nDarkness will fade, all the pain will go by\nBut we will stay, even if you say\nNew day will rise, life will go on\nEveryone cries, don't cry alone\nDarkness will fade, all the pain will go by\nBut you will stay, love of my life\n\n[Bridge]\nמים רבים לא יכבו\nאת האהבה ונהרות, לא ישטפוה\n\n[Chorus]\nNew day will rise\nEveryone cries, don't cry alone\nDarkness will fade, all the pain will go by\nBut we will stay\n\n[Outro]\nEven if you say goodbye\nA new day will rise\nNew day will rise",
|
||||||
@@ -363,13 +220,14 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Englanti, Ranska, Heprea",
|
"language": "Englanti, Ranska, Heprea",
|
||||||
"running_order": 19
|
"running_order": 4
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Lucio Corsi",
|
"artist": "Lucio Corsi",
|
||||||
"country": "ITA",
|
"country": "ITA",
|
||||||
"title": "Volevo Essere Un Duro",
|
"title": "Volevo Essere Un Duro",
|
||||||
"lyrics_url": "https://genius.com/Lucio-corsi-volevo-essere-un-duro-lyrics",
|
"lyrics_url": "https://genius.com/Lucio-corsi-volevo-essere-un-duro-lyrics",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250510_Corinne-Cumming_EBU_00305_0.jpg?h=91b82584&itok=UcQF8ccv",
|
||||||
"artist_url": "https://eurovision.tv/participant/lucio-corsi-2025",
|
"artist_url": "https://eurovision.tv/participant/lucio-corsi-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ITALY-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-ITALY-WHITE.png",
|
||||||
"lyrics_original": "[Testo di \"Volevo essere un duro\"]\n\n[Strofa 1]\nVolevo essere un duro\nChe non gli importa del futuro\nUn robot, un lottatore di sumo\nUno spaccino in fuga da un cane lupo\nAlla stazione di Bolo\nUna gallina dalle uova d'oro\nPerò non sono nessuno\nNon sono nato con la faccia da duro\nHo anche paura del buio\nSe faccio a botte, le prendo\nCosì mi truccano gli occhi di nero\nMa non ho mai perso tempo\nÈ lui che mi ha lasciato indietro\n\n[Ritornello]\n\"Vivere la vita è un gioco da ragazzi\"\nMe lo diceva mamma ed io cadevo giù dagli alberi\nQuanto è duro il mondo per quelli normali\nChe hanno poco amore intorno o troppo sole negli occhiali\n\n[Strofa 2]\nVolevo essere un duro\nChe non gli importa del futuro, no\nUn robot, mеdaglia d'oro di sputo\nLo scippatore che t'aspetta nеl buio\nIl Re di Porta Portese\nLa gazza ladra che ti ruba la fede\n\n[Ritornello]\n\"Vivere la vita è un gioco da ragazzi\"\nMe lo diceva mamma ed io cadevo giù dagli alberi\nQuanto è duro il mondo per quelli normali\nChe hanno poco amore intorno o troppo sole negli occhiali\nVolevo essere un duro\nPerò non sono nessuno\nCintura bianca di judo\nInvece che una stella, uno starnuto\n\n[Bridge]\nI girasoli con gli occhiali mi hanno detto: \"Stai attento alla luce\"\nE che le lune senza buche sono fregature\nPerché, in fondo, è inutile fuggire dalle tue paure\n\n[Ritornello]\nVivere la vita è un gioco da ragazzi\nIo, io volevo essere un duro\nPerò non sono nessuno\n\n[Outro]\nNon sono altro che Lucio\nNon sono altro che Lucio",
|
"lyrics_original": "[Testo di \"Volevo essere un duro\"]\n\n[Strofa 1]\nVolevo essere un duro\nChe non gli importa del futuro\nUn robot, un lottatore di sumo\nUno spaccino in fuga da un cane lupo\nAlla stazione di Bolo\nUna gallina dalle uova d'oro\nPerò non sono nessuno\nNon sono nato con la faccia da duro\nHo anche paura del buio\nSe faccio a botte, le prendo\nCosì mi truccano gli occhi di nero\nMa non ho mai perso tempo\nÈ lui che mi ha lasciato indietro\n\n[Ritornello]\n\"Vivere la vita è un gioco da ragazzi\"\nMe lo diceva mamma ed io cadevo giù dagli alberi\nQuanto è duro il mondo per quelli normali\nChe hanno poco amore intorno o troppo sole negli occhiali\n\n[Strofa 2]\nVolevo essere un duro\nChe non gli importa del futuro, no\nUn robot, mеdaglia d'oro di sputo\nLo scippatore che t'aspetta nеl buio\nIl Re di Porta Portese\nLa gazza ladra che ti ruba la fede\n\n[Ritornello]\n\"Vivere la vita è un gioco da ragazzi\"\nMe lo diceva mamma ed io cadevo giù dagli alberi\nQuanto è duro il mondo per quelli normali\nChe hanno poco amore intorno o troppo sole negli occhiali\nVolevo essere un duro\nPerò non sono nessuno\nCintura bianca di judo\nInvece che una stella, uno starnuto\n\n[Bridge]\nI girasoli con gli occhiali mi hanno detto: \"Stai attento alla luce\"\nE che le lune senza buche sono fregature\nPerché, in fondo, è inutile fuggire dalle tue paure\n\n[Ritornello]\nVivere la vita è un gioco da ragazzi\nIo, io volevo essere un duro\nPerò non sono nessuno\n\n[Outro]\nNon sono altro che Lucio\nNon sono altro che Lucio",
|
||||||
@@ -383,7 +241,7 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Italia",
|
"language": "Italia",
|
||||||
"running_order": 20
|
"running_order": 14
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Tautumeitas",
|
"artist": "Tautumeitas",
|
||||||
@@ -403,7 +261,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Latvia",
|
"language": "Latvia",
|
||||||
"running_order": 21
|
"running_order": 11,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250508_Corinne-Cumming_EBU_00846.jpg?h=bde95cfb&itok=57KGEN-9"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Katarsis",
|
"artist": "Katarsis",
|
||||||
@@ -422,7 +281,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Liettua",
|
"language": "Liettua",
|
||||||
"running_order": 22
|
"running_order": 5,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250508_Corinne-Cumming_EBU_00985.jpg?h=dd7d7650&itok=RaMQzqro"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Laura Thorn",
|
"artist": "Laura Thorn",
|
||||||
@@ -441,7 +301,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Ranska",
|
"language": "Ranska",
|
||||||
"running_order": 23
|
"running_order": 2,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250509_Corinne-Cumming_EBU_00257.jpg?h=10965bfd&itok=3uXVqeyQ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Miriana Conte",
|
"artist": "Miriana Conte",
|
||||||
@@ -460,26 +321,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Englanti",
|
"language": "Englanti",
|
||||||
"running_order": 24
|
"running_order": 20,
|
||||||
},
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250508_Corinne-Cumming_EBU_01022.jpg?h=2e3391ea&itok=5zXK7fI0"
|
||||||
{
|
|
||||||
"artist": "Nina Žižić",
|
|
||||||
"country": "MNE",
|
|
||||||
"title": "Dobrodošli",
|
|
||||||
"lyrics_url": "https://genius.com/Genius-english-translations-nina-zizic-dobrodosli-english-translation-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/nina-zizic-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-MONTENEGRO-WHITE.png",
|
|
||||||
"lyrics_original": "[Verse 1]\nThe night which is to blame, wakes up\nShamelessly rubs its tired eyes\nWill it come or will it sleep?\nRed dawn comes to heal the wounds\n\n[Pre-Chorus]\nWhere is everyone now? Behind the border\nKnitting a wreath of flowers\n\n[Chorus]\nSmile, endure everything, it will pass\nEndure everything, it will pass, smile\nIt will pass, smile, endure everything\nIt will disappear, it will stop, but when will it?\nI bid you welcome\nTo the play about lovе\n\n[Verse 2]\nHow are you? Are you okay?\nWherе are now all those pure before God?\nAs if they are mute, they stand speechless\nThose who are there in good times and bad\n\n[Pre-Chorus]\nTheir faces are red, patched with sadness\nAnd who's to blame? Woah\n\n[Chorus]\nSmile, endure everything, it will pass\nEndure everything, it will pass, smile\nIt will pass, smile, endure everything\nIt will disappear, it will stop, but when will it?\nI bid you welcome\nTo the hotel of my weaknesses\nI bid you all welcome\nTo the play about love\n\n[Bridge: Vocalization]\n\n[Chorus]\nEndure everything, it will pass, smile\nIt will pass, smile, endure everything\nIt will disappear, it will stop, but when will it?\n\n[Outro]\n(It will stop, but when will it?)",
|
|
||||||
"lyrics_translation_fi": "[Verse 1]\nYö, joka on syypää, herää\nHäpeilemättömästi hieroo väsyneitä silmiään\nTuleeko se vai nukkuuko se?\nPunainen aamunkoitto tulee parantamaan haavat\n\n[Pre-Chorus]\nMissä kaikki ovat nyt? Rajan takana\nKudotaan kukkaseppelettä\n\n[Chorus]\nHymyile, kestä kaikki, se menee ohi\nKestä kaikki, se menee ohi, hymyile\nSe menee ohi, hymyile, kestä kaikki\nSe katoaa, se loppuu, mutta milloin?\nToivotan sinut tervetulleeksi\nNäytelmään rakkaudesta\n\n[Verse 2]\nMitä kuuluu? Oletko kunnossa?\nMissä ovat nyt kaikki ne puhtaat ennen Jumalaa?\nIkuin he ovat mykkiä, he seisovat sanattomina\nNe, jotka ovat siellä hyvinä ja huonoina aikoina\n\n[Pre-Chorus]\nHeidän kasvonsa ovat punaiset, surun paikkaamat\nJa kuka on syypää? Vau\n\n[Chorus]\nHymyile, kestä kaikki, se menee ohi\nKestä kaikki, se menee ohi, hymyile\nSe menee ohi, hymyile, kestä kaikki\nSe katoaa, se loppuu, mutta milloin?\nToivotan sinut tervetulleeksi\nHeikkouksieni hotelliin\nToivotan teidät kaikki tervetulleiksi\nNäytelmään rakkaudesta\n\n[Bridge: Vocalization]\n\n[Chorus]\nKestä kaikki, se menee ohi, hymyile\nSe menee ohi, hymyile, kestä kaikki\nSe katoaa, se loppuu, mutta milloin?\n\n[Outro]\n(Se loppuu, mutta milloin?)",
|
|
||||||
"tags": [
|
|
||||||
"kestävyys",
|
|
||||||
"rakkaus",
|
|
||||||
"vaikeudet",
|
|
||||||
"toivo"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Venäjä",
|
|
||||||
"running_order": 25
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Claude",
|
"artist": "Claude",
|
||||||
@@ -499,7 +342,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Ranska",
|
"language": "Ranska",
|
||||||
"running_order": 26
|
"running_order": 12,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250507_Corinne-Cumming_EBU_00520.jpg?h=bcf70b1f&itok=98AxxmNe"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Kyle Alessandro",
|
"artist": "Kyle Alessandro",
|
||||||
@@ -508,6 +352,7 @@
|
|||||||
"lyrics_url": "https://genius.com/Kyle-alessandro-lighter-lyrics",
|
"lyrics_url": "https://genius.com/Kyle-alessandro-lighter-lyrics",
|
||||||
"artist_url": "https://eurovision.tv/participant/kyle-alessandro-2025",
|
"artist_url": "https://eurovision.tv/participant/kyle-alessandro-2025",
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-NORWAY-WHITE.png",
|
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-NORWAY-WHITE.png",
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250507_Corinne-Cumming_EBU_00360.jpg?h=5b303bf5&itok=y67gzyIT",
|
||||||
"lyrics_original": "[Verse 1]\nGolden girl dressed in ice, a heart as dark as night\nYou got me to dim my light, no more\nI really think I bought your lies\nDid anything to keep you mine\nYou kept me hooked on your line, no more\n\n[Pre-Chorus]\nSomewhere along the way I lost my might\nI had to walk a hundred thousand miles\nI'm not afraid to set it all on fire\nI won't fall again, I'll be my own lighter\n\n[Chorus]\nNothing can burn me now\nI'll be my own lighter\nI feel a spark inside me, I don't need savin'\nNo way, no way\n'Cause I'm my own, I'm my own lighter\n\n[Verse 2]\nI'm tired of a million tries to fight, the signs\nAnd when everybody tried to tell me\nI should've known that it was timе to break free\nYour reigns that kept mе at your mercy\nI'll burn them to the ground\nNo more, no more, ignite the fire\n\n[Pre-Chorus]\nSomewhere along the way I lost my might\nI had to walk a hundred thousand miles\nI'm not afraid to set it all on fire\nI won't fall again, I'll be my own lighter\n\n[Chorus]\nNothing can burn me now\nI'll be my own lighter\nI feel a spark inside me, I don't need savin'\nNo way, no way\n'Cause I'm my own, I'm my own lighter\n\n[Bridge]\nSilence fills the room\nAnd I've taken off my jewels\nI wish none of this was true\nBut there's a fire growin' too, yeah\n\n[Chorus]\nNothing can burn me now\nI'll be my own lighter\nI feel a spark inside me, I don't need savin'\nNo way, no way\n'Cause I'm my own, I'm my own lighter\n\n[Outro]\nNothing can burn me down\nI'm my own, I'm my own lighter",
|
"lyrics_original": "[Verse 1]\nGolden girl dressed in ice, a heart as dark as night\nYou got me to dim my light, no more\nI really think I bought your lies\nDid anything to keep you mine\nYou kept me hooked on your line, no more\n\n[Pre-Chorus]\nSomewhere along the way I lost my might\nI had to walk a hundred thousand miles\nI'm not afraid to set it all on fire\nI won't fall again, I'll be my own lighter\n\n[Chorus]\nNothing can burn me now\nI'll be my own lighter\nI feel a spark inside me, I don't need savin'\nNo way, no way\n'Cause I'm my own, I'm my own lighter\n\n[Verse 2]\nI'm tired of a million tries to fight, the signs\nAnd when everybody tried to tell me\nI should've known that it was timе to break free\nYour reigns that kept mе at your mercy\nI'll burn them to the ground\nNo more, no more, ignite the fire\n\n[Pre-Chorus]\nSomewhere along the way I lost my might\nI had to walk a hundred thousand miles\nI'm not afraid to set it all on fire\nI won't fall again, I'll be my own lighter\n\n[Chorus]\nNothing can burn me now\nI'll be my own lighter\nI feel a spark inside me, I don't need savin'\nNo way, no way\n'Cause I'm my own, I'm my own lighter\n\n[Bridge]\nSilence fills the room\nAnd I've taken off my jewels\nI wish none of this was true\nBut there's a fire growin' too, yeah\n\n[Chorus]\nNothing can burn me now\nI'll be my own lighter\nI feel a spark inside me, I don't need savin'\nNo way, no way\n'Cause I'm my own, I'm my own lighter\n\n[Outro]\nNothing can burn me down\nI'm my own, I'm my own lighter",
|
||||||
"lyrics_translation_fi": "[Verse 1]\nKultainen tyttö pukeutunut jäähän, sydän yhtä synkkä kuin yö\nSait minut himmentämään valoani, ei enää\nLuulen todella, että ostin valheesi\nTein mitä tahansa pitääkseni sinut omana\nPidit minut koukussa siimassasi, ei enää\n\n[Pre-Chorus]\nJossain matkan varrella menetin voimani\nMinun piti kävellä satatuhatta mailia\nEn pelkää sytyttää kaikkea tuleen\nEn lankea enää, olen oma sytyttäjäni\n\n[Chorus]\nMikään ei voi polttaa minua nyt\nOlen oma sytyttäjäni\nTunnen kipinän sisälläni, en tarvitse pelastusta\nEi mitenkään, ei mitenkään\nKoska olen omani, olen oma sytyttäjäni\n\n[Verse 2]\nOlen kyllästynyt miljooniin yrityksiin taistella, merkkeihin\nJa kun kaikki yrittivät kertoa minulle\nMinun olisi pitänyt tietää, että oli aika vapautua\nValtasi, jotka pitivät minut armoillasi\nPoltan ne maan tasalle\nEi enää, ei enää, sytytä tulta\n\n[Pre-Chorus]\nJossain matkan varrella menetin voimani\nMinun piti kävellä satatuhatta mailia\nEn pelkää sytyttää kaikkea tuleen\nEn lankea enää, olen oma sytyttäjäni\n\n[Chorus]\nMikään ei voi polttaa minua nyt\nOlen oma sytyttäjäni\nTunnen kipinän sisälläni, en tarvitse pelastusta\nEi mitenkään, ei mitenkään\nKoska olen omani, olen oma sytyttäjäni\n\n[Bridge]\nHiljaisuus täyttää huoneen\nJa olen riisunut koruni\nToivon, ettei mikään tästä olisi totta\nMutta siellä on myös tuli kasvamassa, yeah\n\n[Chorus]\nMikään ei voi polttaa minua nyt\nOlen oma sytyttäjäni\nTunnen kipinän sisälläni, en tarvitse pelastusta\nEi mitenkään, ei mitenkään\nKoska olen omani, olen oma sytyttäjäni\n\n[Outro]\nMikään ei voi polttaa minua\nOlen omani, olen oma sytyttäjäni",
|
"lyrics_translation_fi": "[Verse 1]\nKultainen tyttö pukeutunut jäähän, sydän yhtä synkkä kuin yö\nSait minut himmentämään valoani, ei enää\nLuulen todella, että ostin valheesi\nTein mitä tahansa pitääkseni sinut omana\nPidit minut koukussa siimassasi, ei enää\n\n[Pre-Chorus]\nJossain matkan varrella menetin voimani\nMinun piti kävellä satatuhatta mailia\nEn pelkää sytyttää kaikkea tuleen\nEn lankea enää, olen oma sytyttäjäni\n\n[Chorus]\nMikään ei voi polttaa minua nyt\nOlen oma sytyttäjäni\nTunnen kipinän sisälläni, en tarvitse pelastusta\nEi mitenkään, ei mitenkään\nKoska olen omani, olen oma sytyttäjäni\n\n[Verse 2]\nOlen kyllästynyt miljooniin yrityksiin taistella, merkkeihin\nJa kun kaikki yrittivät kertoa minulle\nMinun olisi pitänyt tietää, että oli aika vapautua\nValtasi, jotka pitivät minut armoillasi\nPoltan ne maan tasalle\nEi enää, ei enää, sytytä tulta\n\n[Pre-Chorus]\nJossain matkan varrella menetin voimani\nMinun piti kävellä satatuhatta mailia\nEn pelkää sytyttää kaikkea tuleen\nEn lankea enää, olen oma sytyttäjäni\n\n[Chorus]\nMikään ei voi polttaa minua nyt\nOlen oma sytyttäjäni\nTunnen kipinän sisälläni, en tarvitse pelastusta\nEi mitenkään, ei mitenkään\nKoska olen omani, olen oma sytyttäjäni\n\n[Bridge]\nHiljaisuus täyttää huoneen\nJa olen riisunut koruni\nToivon, ettei mikään tästä olisi totta\nMutta siellä on myös tuli kasvamassa, yeah\n\n[Chorus]\nMikään ei voi polttaa minua nyt\nOlen oma sytyttäjäni\nTunnen kipinän sisälläni, en tarvitse pelastusta\nEi mitenkään, ei mitenkään\nKoska olen omani, olen oma sytyttäjäni\n\n[Outro]\nMikään ei voi polttaa minua\nOlen omani, olen oma sytyttäjäni",
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -519,7 +364,7 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Englanti",
|
"language": "Englanti",
|
||||||
"running_order": 27
|
"running_order": 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Justyna Steczkowska",
|
"artist": "Justyna Steczkowska",
|
||||||
@@ -539,7 +384,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Puola",
|
"language": "Puola",
|
||||||
"running_order": 28
|
"running_order": 15,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250507_Corinne-Cumming_EBU_00140.jpg?h=4606ab50&itok=l5j5YBzJ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "NAPA",
|
"artist": "NAPA",
|
||||||
@@ -559,7 +405,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Portugali",
|
"language": "Portugali",
|
||||||
"running_order": 29
|
"running_order": 21,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250507_Corinne-Cumming_EBU_00320.jpg?h=970e90fb&itok=iU7sPcym"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Gabry Ponte",
|
"artist": "Gabry Ponte",
|
||||||
@@ -579,47 +426,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Italia",
|
"language": "Italia",
|
||||||
"running_order": 30
|
"running_order": 25,
|
||||||
},
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250507_Corinne-Cumming_EBU_00443.jpg?h=f1bb2bb4&itok=i1ABEyK2"
|
||||||
{
|
|
||||||
"artist": "Princ",
|
|
||||||
"country": "SRB",
|
|
||||||
"title": "Mila",
|
|
||||||
"lyrics_url": "https://genius.com/Princ-mila-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/princ-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-SERBIA-WHITE.png",
|
|
||||||
"lyrics_original": "[Tekst pesme \"Mila\"]\n\n[Strofa 1]\nNeka bude mi to zadnja večera\nNek oko Kerbera u noć me potera\nNek te probudi taj miris sećanja\nI moja muzika od tvoga imena\n\n[Refren]\nMila, ti si platila što volim te ja\nMila, život vratila sa ruba\nIma mnogo razloga da ipak voliš drugoga\nAl' tako mi je stalo da te čuvam\n\n[Strofa 2]\nNek te poljubi ko bolje slaže te\nA nek te dobije ko rizikuje sve\n\n[Refren]\nMila, ti si platila što volim te ja\nMila, život vratila sa ruba\nIma mnogo razloga da ipak voliš drugoga\nAl' tako mi je stalo da te čuvam\n\n[Instrumentalna pauza]\n\n[Refren]\nMila, ti si platila što volim te ja\nMila, život vratila sa ruba\nIma mnogo razloga da ipak voliš drugoga\nAl' tako mi je stalo da te čuvam\n(Mila, ti si platila što volim te ja)\nTi si platila mila\n(Mila, život vratila sa ruba)\nIma mnogo razloga da ipak voliš drugoga\nAl' tako mi je stalo da te čuvam",
|
|
||||||
"lyrics_translation_fi": "[Säkeistö 1]\nOlkoon se minulle viimeinen ehtoollinen\nKerberoksen ympäröimänä yö minut ajaa takaa\nHerättäköön sinut muistojen tuoksu\nJa minun musiikkini sinun nimestäsi\n\n[Kertosäe]\nMila, sinä olet maksanut siitä, että minä rakastan sinua\nMila, palautit elämän reunalta\nOn monia syitä, miksi rakastaisit jotakuta toista\nMutta minulle on niin tärkeää suojella sinua\n\n[Säkeistö 2]\nSuudelkoon sinua se, joka paremmin sopii sinulle\nJa saakoon sinut se, joka riskeeraa kaiken\n\n[Kertosäe]\nMila, sinä olet maksanut siitä, että minä rakastan sinua\nMila, palautit elämän reunalta\nOn monia syitä, miksi rakastaisit jotakuta toista\nMutta minulle on niin tärkeää suojella sinua\n\n[Instrumentaalinen välisoitto]\n\n[Kertosäe]\nMila, sinä olet maksanut siitä, että minä rakastan sinua\nMila, palautit elämän reunalta\nOn monia syitä, miksi rakastaisit jotakuta toista\nMutta minulle on niin tärkeää suojella sinua\n(Mila, sinä olet maksanut siitä, että minä rakastan sinua)\nSinä olet maksanut, Mila\n(Mila, palautit elämän reunalta)\nOn monia syitä, miksi rakastaisit jotakuta toista\nMutta minulle on niin tärkeää suojella sinua",
|
|
||||||
"tags": [
|
|
||||||
"rakkaus",
|
|
||||||
"uhrautuminen",
|
|
||||||
" kaipaus",
|
|
||||||
"suojelu"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Serbia",
|
|
||||||
"running_order": 31
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"artist": "Klemen",
|
|
||||||
"country": "SVN",
|
|
||||||
"title": "How Much Time Do We Have Left",
|
|
||||||
"lyrics_url": "https://genius.com/Klemen-how-much-time-do-we-have-left-lyrics",
|
|
||||||
"artist_url": "https://eurovision.tv/participant/klemen-2025",
|
|
||||||
"flag_url": "https://eurovision.tv/sites/default/files/media/image/2023-08/ESC-HEART-SLOVENIA-WHITE.png",
|
|
||||||
"lyrics_original": "[Verse 1]\nOur baby boy was crawlin' in the livin' room\nYou read a diagnosis, it said that you'd be dyin' soon\nIt was a sunny day\nThrough the window, there were people goin' on their ways\nOur little child had the biggest smile\nWhile we couldn't do nothin' but cry\n\n[Chorus]\nHow much time do we have left together?\nWe never know\nAll I know is I don't wanna be here\nWithout your love\nHow much time do we have left together?\nWe never know, we never know\nWish wе could take it slow\n\n[Verse 2]\nA man in white\nWith a room full of diplomas, tryin' to get it right\nCould put you undеr hypnosis\nBy sayin' to give up the fight\nBut you were always determined to try\n\n[Chorus]\nHow much time do we have left together?\nWe never know\nAll I know is I don't wanna be here\nWithout your love\nHow much time do we have left together?\nWe never know, we never know\nBut we keep standin' strong\n\n[Bridge]\nI admire your power, I admire your will\nYou made me question the world\nMade me question what's real\nI admire your power, I admire your will\nYour ability to heal\nOh, I admire your power, I admire your will\nThe way you never gave up until you grew wings\nAnd you learned how to fly, made a loop in the sky\nYou landed right into my arms\n\n[Chorus]\nHow much time do we have left together?\nWe never know\nAll I know is I don't wanna be here\nWithout your love\nHow much time do we have left together?\nWe never know, we never know\nWish we could take it slow",
|
|
||||||
"lyrics_translation_fi": "[Verse 1]\nMeidän poikavauvamme ryömi olohuoneessa\nLuit diagnoosin, jossa sanottiin, että kuolisit pian\nOli aurinkoinen päivä\nIkkunan läpi ihmiset menivät omilla teillään\nPienellä lapsellamme oli suurin hymy\nVaikka emme voineet muuta kuin itkeä\n\n[Chorus]\nKuinka paljon aikaa meillä on jäljellä yhdessä?\nEmme koskaan tiedä\nTiedän vain, että en halua olla täällä\nIlman rakkauttasi\nKuinka paljon aikaa meillä on jäljellä yhdessä?\nEmme koskaan tiedä, emme koskaan tiedä\nToivon, että voisimme ottaa sen hitaasti\n\n[Verse 2]\nMies valkoisissa\nHuone täynnä tutkintoja, yrittää saada sen oikein\nVoi saada sinut hypnoosiin\nSanomalla, että luovutat taistelun\nMutta olit aina päättänyt yrittää\n\n[Chorus]\nKuinka paljon aikaa meillä on jäljellä yhdessä?\nEmme koskaan tiedä\nTiedän vain, että en halua olla täällä\nIlman rakkauttasi\nKuinka paljon aikaa meillä on jäljellä yhdessä?\nEmme koskaan tiedä, emme koskaan tiedä\nMutta pysymme vahvoina\n\n[Bridge]\nI admire your power, I admire your will\nYou made me question the world\nMade me question what's real\nI admire your power, I admire your will\nYour ability to heal\nOh, I admire your power, I admire your will\nThe way you never gave up until you grew wings\nAnd you learned how to fly, made a loop in the sky\nYou landed right into my arms\n\n[Chorus]\nKuinka paljon aikaa meillä on jäljellä yhdessä?\nEmme koskaan tiedä\nTiedän vain, että en halua olla täällä\nIlman rakkauttasi\nKuinka paljon aikaa meillä on jäljellä yhdessä?\nEmme koskaan tiedä, emme koskaan tiedä\nToivon, että voisimme ottaa sen hitaasti",
|
|
||||||
"tags": [
|
|
||||||
"Suru",
|
|
||||||
"Menetys",
|
|
||||||
"Rakkaus",
|
|
||||||
"Toivo",
|
|
||||||
"Resilienssi",
|
|
||||||
"Perhe"
|
|
||||||
],
|
|
||||||
"confidence": 0.95,
|
|
||||||
"language": "Englanti",
|
|
||||||
"running_order": 32
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Melody",
|
"artist": "Melody",
|
||||||
@@ -639,7 +447,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Espanja",
|
"language": "Espanja",
|
||||||
"running_order": 33
|
"running_order": 6,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250510_Corinne-Cumming_EBU_00316.jpg?h=8c795ec1&itok=KlHomwLj"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "KAJ",
|
"artist": "KAJ",
|
||||||
@@ -658,7 +467,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Ruotsi",
|
"language": "Ruotsi",
|
||||||
"running_order": 34
|
"running_order": 23,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250507_Corinne-Cumming_EBU_00286.jpg?h=f33f36c7&itok=JRGgvwU_"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Zoë Më",
|
"artist": "Zoë Më",
|
||||||
@@ -677,7 +487,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Ranska",
|
"language": "Ranska",
|
||||||
"running_order": 35
|
"running_order": 19,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250510_Corinne-Cumming_EBU_00250.jpg?h=0178f7ef&itok=EguF960o"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Ziferblat",
|
"artist": "Ziferblat",
|
||||||
@@ -696,7 +507,8 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Ukraina",
|
"language": "Ukraina",
|
||||||
"running_order": 36
|
"running_order": 7,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250507_Corinne-Cumming_EBU_00253.jpg?h=373d2acb&itok=Ap_BDOTU"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"artist": "Remember Monday",
|
"artist": "Remember Monday",
|
||||||
@@ -716,6 +528,7 @@
|
|||||||
],
|
],
|
||||||
"confidence": 0.95,
|
"confidence": 0.95,
|
||||||
"language": "Englanti",
|
"language": "Englanti",
|
||||||
"running_order": 37
|
"running_order": 8,
|
||||||
|
"img_url": "https://eurovision.tv/sites/default/files/styles/teaser/public/media/image/2025-05/250510_Corinne-Cumming_EBU_00181.jpg?h=00f31e3b&itok=fGY7Vs-D"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from datetime import date, datetime
|
||||||
|
|
||||||
|
|
||||||
|
class Contest(BaseModel):
|
||||||
|
id: int
|
||||||
|
year: int = Field(
|
||||||
|
default_factory=lambda: datetime.now().year,
|
||||||
|
description="The year of the contest",
|
||||||
|
examples=[2025],
|
||||||
|
)
|
||||||
|
is_active: bool = Field(
|
||||||
|
default=True, description="Is this the active contest?", examples=[True, False]
|
||||||
|
)
|
||||||
|
finals_date: date = Field(
|
||||||
|
description="The date of the finals", examples=["2025-05-15"]
|
||||||
|
)
|
||||||
|
created_at: datetime = Field(
|
||||||
|
description="The date and time when the contest was created",
|
||||||
|
examples=["2025-05-15T17:40:42.000Z"],
|
||||||
|
)
|
||||||
|
updated_at: datetime = Field(
|
||||||
|
description="The date and time when the contest was last updated",
|
||||||
|
examples=["2025-05-15T17:40:42.000Z"],
|
||||||
|
)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class Message(BaseModel):
|
||||||
|
type: str | None = None
|
||||||
|
message: str
|
||||||
|
timestamp: datetime = Field(default_factory=datetime.now)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
|
class Result(BaseModel):
|
||||||
|
song_id: int = Field(description="ID of the song", examples=[1])
|
||||||
|
country_fi: str = Field(
|
||||||
|
description="Country of the song in Finnish", examples=["Suomi"]
|
||||||
|
)
|
||||||
|
country_sv: str = Field(
|
||||||
|
description="Country of the song in Swedish", examples=["Sverige"]
|
||||||
|
)
|
||||||
|
artist: str = Field(description="Artist of the song", examples=["Käärijä"])
|
||||||
|
title: str = Field(description="Title of the song", examples=["Cha cha cha"])
|
||||||
|
total_reviews: int = Field(0, description="Total number of reviews", examples=[10])
|
||||||
|
avg_score_song: float = Field(
|
||||||
|
description="Average score for song quality", examples=[85.5]
|
||||||
|
)
|
||||||
|
avg_score_show: float = Field(
|
||||||
|
description="Average score for stage show", examples=[82.3]
|
||||||
|
)
|
||||||
|
avg_score_costume: float = Field(
|
||||||
|
description="Average score for wardrobe/costumes", examples=[88.1]
|
||||||
|
)
|
||||||
|
avg_total_score: float = Field(description="Average total score", examples=[85.5])
|
||||||
|
|
||||||
|
|
||||||
|
class TeamResult(Result):
|
||||||
|
team_id: int = Field(description="ID of the team", examples=[1])
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewBase(BaseModel):
|
||||||
|
song_id: int = Field(description="ID of the song", examples=[1])
|
||||||
|
user_id: int = Field(description="ID of the user", examples=[1])
|
||||||
|
score_song: int = Field(
|
||||||
|
gt=0, le=100, description="Score given by the user", examples=[1]
|
||||||
|
)
|
||||||
|
score_show: int = Field(
|
||||||
|
gt=0, le=100, description="Score given by the user", examples=[1]
|
||||||
|
)
|
||||||
|
score_costume: int = Field(
|
||||||
|
gt=0, le=100, description="Score given by the user", examples=[1]
|
||||||
|
)
|
||||||
|
text_review: str | None = Field(
|
||||||
|
default=None, description="Comment given by the user", examples=["comment"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewIn(ReviewBase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewSearch(BaseModel):
|
||||||
|
song_id: int = Field(description="ID of the song", examples=[1])
|
||||||
|
user_id: int = Field(description="ID of the user", examples=[1])
|
||||||
|
|
||||||
|
|
||||||
|
class ReviewOut(ReviewBase):
|
||||||
|
created_at: datetime = Field(
|
||||||
|
default_factory=datetime.now,
|
||||||
|
description="Creation timestamp",
|
||||||
|
examples=["2025-01-01T00:00:00.000Z"],
|
||||||
|
)
|
||||||
|
updated_at: datetime = Field(
|
||||||
|
default_factory=datetime.now,
|
||||||
|
description="Last update timestamp",
|
||||||
|
examples=["2025-01-01T00:00:00.000Z"],
|
||||||
|
)
|
||||||
@@ -15,6 +15,10 @@ class Song(BaseModel):
|
|||||||
running_order: int | None = Field(
|
running_order: int | None = Field(
|
||||||
default=None, description="Running order of the song.", example=13
|
default=None, description="Running order of the song.", example=13
|
||||||
)
|
)
|
||||||
|
img_url: str = Field(
|
||||||
|
description="URL to the artist image.",
|
||||||
|
example="https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
||||||
|
)
|
||||||
lyrics_url: str | None = Field(
|
lyrics_url: str | None = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="URL to the original lyrics.",
|
description="URL to the original lyrics.",
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class TeamBase(BaseModel):
|
||||||
|
name: str
|
||||||
|
|
||||||
|
|
||||||
|
class Team(TeamBase):
|
||||||
|
id: int
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class UserBase(BaseModel):
|
||||||
|
username: str = Field(description="Username of the user", examples=["Kalle"])
|
||||||
|
|
||||||
|
|
||||||
|
class User(UserBase):
|
||||||
|
id: int = Field(description="ID of the user", examples=[1])
|
||||||
|
team_id: int = Field(description="ID of the team", examples=[1])
|
||||||
|
is_active: bool = Field(
|
||||||
|
default=True, description="Is the user active?", examples=[True]
|
||||||
|
)
|
||||||
|
avatar_id: int = Field(default=0, description="ID of the avatar", examples=[0])
|
||||||
|
is_admin: bool = Field(
|
||||||
|
default=False, description="Is the user an admin?", examples=[False]
|
||||||
|
)
|
||||||
|
last_login: datetime = Field(
|
||||||
|
description="Last login timestamp",
|
||||||
|
examples=["2025-01-01T00:00:00.000Z"],
|
||||||
|
)
|
||||||
|
created_at: datetime = Field(
|
||||||
|
description="Creation timestamp", examples=["2025-01-01T00:00:00.000Z"]
|
||||||
|
)
|
||||||
|
updated_at: datetime = Field(
|
||||||
|
description="Last update timestamp", examples=["2025-01-01T00:00:00.000Z"]
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def has_logged_in(self) -> bool:
|
||||||
|
"""Returns True if the user has ever logged in, False if last_login is the epoch date."""
|
||||||
|
epoch = datetime(1970, 1, 1, 0, 0, 0)
|
||||||
|
return self.last_login and self.last_login > epoch
|
||||||
|
|
||||||
|
|
||||||
|
class CreateUser(UserBase):
|
||||||
|
password: str = Field(description="Password of the user", examples=["sn1rbul4"])
|
||||||
|
team_id: int = Field(description="ID of the team", examples=[1])
|
||||||
|
is_admin: bool = Field(
|
||||||
|
default=False, description="Is the user an admin?", examples=[False]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UserLogin(UserBase):
|
||||||
|
password: str = Field(description="Password of the user", examples=["sn1rbul4"])
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateUser(BaseModel):
|
||||||
|
id: int = Field(description="ID of the user", examples=[1])
|
||||||
|
avatar_id: int | None = Field(None, description="ID of the avatar", examples=[1])
|
||||||
|
email: str | None = Field(
|
||||||
|
None, description="Email of the user", examples=["[email protected]"]
|
||||||
|
)
|
||||||
|
password: str | None = Field(
|
||||||
|
None, description="Password of the user", examples=["p1p4l1"]
|
||||||
|
)
|
||||||
|
updated_at: datetime = Field(default=datetime.now(), description="Updated at")
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, Body, HTTPException, Request
|
||||||
# from sqlmodel import Session, select
|
|
||||||
|
|
||||||
# from models.user import User
|
from models.user import CreateUser
|
||||||
# from models.group import Group
|
from models.team import TeamBase, Team
|
||||||
|
from lib.db import get_connection
|
||||||
# from ..app import get_session
|
from lib.helpers import hash_password
|
||||||
|
from lib.logger import logger
|
||||||
|
|
||||||
router = APIRouter(prefix="/admin", tags=["Admin"])
|
router = APIRouter(prefix="/admin", tags=["Admin"])
|
||||||
|
|
||||||
@@ -18,8 +18,78 @@ router = APIRouter(prefix="/admin", tags=["Admin"])
|
|||||||
# # Implementation will be added with authentication logic
|
# # Implementation will be added with authentication logic
|
||||||
# return {"is_active": is_active}
|
# return {"is_active": is_active}
|
||||||
|
|
||||||
# @router.post("/users")
|
|
||||||
# async def create_user(username: str, password: str, group_id: int, session: Session = Depends(get_session)):
|
@router.post("/teams")
|
||||||
|
async def create_team(team: TeamBase, request: Request):
|
||||||
|
logger.info(
|
||||||
|
f"Admin action: Creating new team '{team.name}' from IP: {request.client.host}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with get_connection() as conn:
|
||||||
|
conn.execute("INSERT INTO Team (name) VALUES (?)", (team.name,))
|
||||||
|
logger.info(f"Team '{team.name}' created successfully")
|
||||||
|
return {"message": "Team created successfully"}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to create team '{team.name}': {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to create team")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/teams", response_model=list[Team])
|
||||||
|
async def list_teams(request: Request):
|
||||||
|
logger.debug(f"Admin action: Listing all teams from IP: {request.client.host}")
|
||||||
|
with get_connection() as conn:
|
||||||
|
teams = conn.execute("SELECT * FROM Team").fetchdf()
|
||||||
|
return teams.to_dict(orient="records")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/users")
|
||||||
|
async def create_user(user: CreateUser, request: Request):
|
||||||
|
logger.info(
|
||||||
|
f"Admin action: Creating new user '{user.username}' from IP: {request.client.host}"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with get_connection() as conn:
|
||||||
|
# Check if username already exists
|
||||||
|
existing = conn.execute(
|
||||||
|
"SELECT COUNT(*) as count FROM User WHERE username = ?",
|
||||||
|
(user.username,),
|
||||||
|
).fetchdf()
|
||||||
|
if existing["count"][0] > 0:
|
||||||
|
logger.warning(
|
||||||
|
f"Failed to create user: Username '{user.username}' already exists"
|
||||||
|
)
|
||||||
|
raise HTTPException(status_code=400, detail="Username already exists")
|
||||||
|
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO User (username, hashed_password, team_id, is_admin) VALUES (?, ?, ?, ?)",
|
||||||
|
(
|
||||||
|
user.username,
|
||||||
|
hash_password(user.password),
|
||||||
|
user.team_id,
|
||||||
|
user.is_admin,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
logger.info(
|
||||||
|
f"User '{user.username}' created successfully with team_id: {user.team_id}, admin status: {user.is_admin}"
|
||||||
|
)
|
||||||
|
return {"message": "User created successfully"}
|
||||||
|
except HTTPException:
|
||||||
|
# Re-raise HTTP exceptions
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to create user '{user.username}': {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail="Failed to create user")
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/users/disable")
|
||||||
|
async def disable_user(user_id: int = Body(..., embed=True), request: Request = None):
|
||||||
|
logger.info(
|
||||||
|
f"Admin action: Attempting to disable user with ID: {user_id} from IP: {request.client.host}"
|
||||||
|
)
|
||||||
|
logger.warning(f"Disable user functionality not implemented for user_id: {user_id}")
|
||||||
|
raise HTTPException(status_code=405, detail="Method not yet implemented")
|
||||||
|
|
||||||
|
|
||||||
# """
|
# """
|
||||||
# Add a new user
|
# Add a new user
|
||||||
# Only accessible to admin users
|
# Only accessible to admin users
|
||||||
|
|||||||
@@ -1,7 +1,52 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, HTTPException, Request
|
||||||
|
|
||||||
|
from lib.db import get_connection
|
||||||
|
from models.user import User, UserLogin
|
||||||
|
from lib.helpers import verify_password
|
||||||
|
from lib.logger import logger
|
||||||
|
|
||||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||||
|
|
||||||
@router.post("/token")
|
|
||||||
async def login():
|
@router.post("/token", response_model=User)
|
||||||
return {"message": "Login successful"}
|
async def login(user_login: UserLogin, request: Request):
|
||||||
|
logger.debug(
|
||||||
|
f"Login attempt for user: {user_login.username} from IP: {request.client.host}"
|
||||||
|
)
|
||||||
|
with get_connection() as conn:
|
||||||
|
user_df = conn.execute(
|
||||||
|
'SELECT * FROM "User" WHERE username = ?', (user_login.username,)
|
||||||
|
).fetchdf()
|
||||||
|
|
||||||
|
if user_df.empty:
|
||||||
|
logger.warning(
|
||||||
|
f"Failed login: Username {user_login.username} not found - IP: {request.client.host}"
|
||||||
|
)
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid username or password")
|
||||||
|
|
||||||
|
user_data = user_df.to_dict(orient="records")[0]
|
||||||
|
if not verify_password(user_login.password, user_data["hashed_password"]):
|
||||||
|
logger.warning(
|
||||||
|
f"Failed login: Incorrect password for user {user_login.username} - IP: {request.client.host}"
|
||||||
|
)
|
||||||
|
raise HTTPException(status_code=401, detail="Invalid username or password")
|
||||||
|
|
||||||
|
# Update the login timestamp in the database
|
||||||
|
with get_connection() as conn:
|
||||||
|
conn.execute(
|
||||||
|
'UPDATE "User" SET last_login = CURRENT_TIMESTAMP WHERE id = ?',
|
||||||
|
(user_data["id"],),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fetch the updated user data with the new last_login timestamp
|
||||||
|
updated_user = (
|
||||||
|
conn.execute('SELECT * FROM "User" WHERE id = ?', (user_data["id"],))
|
||||||
|
.fetchdf()
|
||||||
|
.to_dict(orient="records")[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Successful login: User {user_login.username} (ID: {user_data['id']}) logged in from {request.client.host}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return User(**updated_user)
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
|
from lib.db import get_connection
|
||||||
|
from lib.logger import logger
|
||||||
|
from models.contest import Contest
|
||||||
|
from models.msg import Message
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/contest", tags=["contest"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=list[Contest])
|
||||||
|
async def list_contests():
|
||||||
|
with get_connection() as conn:
|
||||||
|
logger.debug("Listing all contests")
|
||||||
|
df = conn.execute("SELECT * FROM Contest").fetchdf()
|
||||||
|
logger.debug(f"Contests: {df}")
|
||||||
|
contests = [Contest(**row) for row in df.to_dict(orient="records")]
|
||||||
|
return contests
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{year}", response_model=Contest)
|
||||||
|
async def get_contest(year: int):
|
||||||
|
with get_connection() as conn:
|
||||||
|
df = conn.execute("SELECT * FROM Contest WHERE year = ?", (year,)).fetchdf()
|
||||||
|
if df.empty:
|
||||||
|
raise HTTPException(status_code=404, detail="Contest not found")
|
||||||
|
contest = Contest(**df.to_dict(orient="records")[0])
|
||||||
|
return contest
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/", response_model=Message)
|
||||||
|
async def update_contest(year: int, is_active: bool):
|
||||||
|
with get_connection() as conn:
|
||||||
|
logger.debug(f"Updating contest {year} to active: {is_active}")
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE Contest SET is_active = ? WHERE year = ?",
|
||||||
|
(is_active, year),
|
||||||
|
)
|
||||||
|
return Message(message="Contest updated successfully")
|
||||||
@@ -1,3 +1,137 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
from enum import Enum
|
||||||
|
import duckdb # Added for specific exception handling
|
||||||
|
|
||||||
|
from models.result import Result, TeamResult
|
||||||
|
from models.msg import Message
|
||||||
|
from lib.db import get_connection
|
||||||
|
from lib.logger import logger
|
||||||
|
|
||||||
router = APIRouter(prefix="/results", tags=["results"])
|
router = APIRouter(prefix="/results", tags=["results"])
|
||||||
|
|
||||||
|
|
||||||
|
# It's good practice for FastAPI to have Enum derive from str as well
|
||||||
|
class ResultType(str, Enum):
|
||||||
|
DEVIATION = "deviation"
|
||||||
|
COSTUME = "costume"
|
||||||
|
SHOW = "show"
|
||||||
|
SONG = "song"
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/{result_type}", response_model=list[Result] | Message)
|
||||||
|
async def list_results(result_type: ResultType):
|
||||||
|
query = ""
|
||||||
|
try:
|
||||||
|
with get_connection() as conn:
|
||||||
|
if result_type == ResultType.DEVIATION:
|
||||||
|
# Selects the song with the highest standard deviation in its total review scores
|
||||||
|
query = """
|
||||||
|
WITH SongReviewDeviations AS (
|
||||||
|
SELECT
|
||||||
|
song_id,
|
||||||
|
COALESCE(STDDEV_SAMP((score_song + score_show + score_costume) / 3.0), 0) AS deviation_value
|
||||||
|
FROM Review
|
||||||
|
GROUP BY song_id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
ras.song_id,
|
||||||
|
ras.country_fi,
|
||||||
|
ras.country_sv,
|
||||||
|
ras.artist,
|
||||||
|
ras.title,
|
||||||
|
ras.avg_score_song,
|
||||||
|
ras.avg_score_show,
|
||||||
|
ras.avg_score_costume,
|
||||||
|
ras.avg_total_score
|
||||||
|
FROM ReviewAllSongs ras
|
||||||
|
JOIN SongReviewDeviations srd ON ras.song_id = srd.song_id
|
||||||
|
ORDER BY srd.deviation_value DESC
|
||||||
|
LIMIT 1;
|
||||||
|
"""
|
||||||
|
df = conn.execute(query).fetchdf()
|
||||||
|
logger.debug(f"Query for DEVIATION: {query}")
|
||||||
|
logger.debug(f"Result DataFrame for DEVIATION: {df}")
|
||||||
|
elif result_type == ResultType.COSTUME:
|
||||||
|
# Selects the song with the highest average costume score
|
||||||
|
query = """SELECT * FROM ReviewAllSongs ORDER BY avg_score_costume DESC LIMIT 1 """
|
||||||
|
df = conn.execute(query).fetchdf()
|
||||||
|
elif result_type == ResultType.SHOW:
|
||||||
|
# Selects the song with the highest average show score
|
||||||
|
query = """SELECT * FROM ReviewAllSongs ORDER BY avg_score_show DESC LIMIT 1 """
|
||||||
|
df = conn.execute(query).fetchdf()
|
||||||
|
elif result_type == ResultType.SONG:
|
||||||
|
# Selects the song with the highest average song score
|
||||||
|
query = """SELECT * FROM ReviewAllSongs ORDER BY avg_score_song DESC LIMIT 1 """
|
||||||
|
df = conn.execute(query).fetchdf()
|
||||||
|
else:
|
||||||
|
# This case should ideally not be reached if using Enums properly with FastAPI
|
||||||
|
return Message(type="error", message="Invalid result type")
|
||||||
|
|
||||||
|
if df.empty:
|
||||||
|
# If the dataframe is empty, return an empty list.
|
||||||
|
# This applies if a view exists but has no data, or LIMIT 1 returns no row.
|
||||||
|
return []
|
||||||
|
|
||||||
|
# The existing code implies Result model can handle various structures.
|
||||||
|
# For DEVIATION, row will be {'deviation_value': X}
|
||||||
|
# For COSTUME/SHOW/SONG (LIMIT 1), it will be a single row from ReviewAllSongs.
|
||||||
|
# For others, it will be multiple rows from their respective views.
|
||||||
|
# All these are converted to a list of Result objects.
|
||||||
|
results = [Result(**row) for row in df.to_dict(orient="records")]
|
||||||
|
return results
|
||||||
|
|
||||||
|
except duckdb.CatalogException as e:
|
||||||
|
# Handles errors like "View not found" (e.g., ReviewAllSongs doesn't exist)
|
||||||
|
# You might want to log the error e
|
||||||
|
print(f"Database Catalog Error: {e}")
|
||||||
|
return Message(
|
||||||
|
type="error",
|
||||||
|
message=f"Data source for '{result_type.value}' not found or query error. Details: {str(e)}",
|
||||||
|
)
|
||||||
|
except duckdb.Error as e: # Catch other DuckDB specific errors
|
||||||
|
print(f"DuckDB Error: {e}")
|
||||||
|
return Message(
|
||||||
|
type="error",
|
||||||
|
message=f"Database query error for '{result_type.value}'. Details: {str(e)}",
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
# Catch any other unexpected errors
|
||||||
|
print(f"Unexpected Error: {e}")
|
||||||
|
return Message(
|
||||||
|
type="error",
|
||||||
|
message=f"An unexpected error occurred while fetching results for '{result_type.value}'.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=list[Result])
|
||||||
|
async def list_global_results():
|
||||||
|
with get_connection() as conn:
|
||||||
|
df = conn.execute("SELECT * FROM ReviewSummaryGlobal").fetchdf()
|
||||||
|
if df.empty:
|
||||||
|
return []
|
||||||
|
results = [Result(**row) for row in df.to_dict(orient="records")]
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/user/{user_id}", response_model=list[Result])
|
||||||
|
async def list_user_results(user_id: int):
|
||||||
|
with get_connection() as conn:
|
||||||
|
df = conn.execute(
|
||||||
|
"SELECT * FROM ReviewSummaryByUser WHERE user_id = ?", (user_id,)
|
||||||
|
).fetchdf()
|
||||||
|
if df.empty:
|
||||||
|
return []
|
||||||
|
results = [Result(**row) for row in df.to_dict(orient="records")]
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/team/{team_id}", response_model=list[TeamResult])
|
||||||
|
async def list_team_results(team_id: int):
|
||||||
|
with get_connection() as conn:
|
||||||
|
df = conn.execute(
|
||||||
|
"SELECT * FROM ReviewSummaryByTeam WHERE team_id = ?", (team_id,)
|
||||||
|
).fetchdf()
|
||||||
|
if df.empty:
|
||||||
|
return []
|
||||||
|
results = [TeamResult(**row) for row in df.to_dict(orient="records")]
|
||||||
|
return results
|
||||||
|
|||||||
@@ -1,3 +1,151 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
router = APIRouter(prefix="/songs", tags=["songs"])
|
from models.review import ReviewOut, ReviewIn
|
||||||
|
from models.msg import Message
|
||||||
|
from lib.db import get_connection
|
||||||
|
from lib.logger import logger
|
||||||
|
|
||||||
|
router = APIRouter(prefix="/reviews", tags=["reviews"])
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/all", response_model=list[ReviewOut])
|
||||||
|
async def list_reviews():
|
||||||
|
with get_connection() as conn:
|
||||||
|
reviews = conn.execute("SELECT * FROM Review").fetchdf()
|
||||||
|
if reviews.empty:
|
||||||
|
return []
|
||||||
|
return reviews.to_dict(orient="records")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=ReviewOut)
|
||||||
|
async def get_review(song_id: int, user_id: int):
|
||||||
|
with get_connection() as conn:
|
||||||
|
review = conn.execute(
|
||||||
|
"SELECT * FROM Review WHERE song_id = ? AND user_id = ?",
|
||||||
|
(song_id, user_id),
|
||||||
|
).fetchdf()
|
||||||
|
if review.empty:
|
||||||
|
return []
|
||||||
|
return review.to_dict(orient="records")[0]
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/", response_model=Message)
|
||||||
|
async def create_review(review: ReviewIn):
|
||||||
|
logger.debug(f"Received create review request with data: {review.dict()}")
|
||||||
|
try:
|
||||||
|
with get_connection() as conn:
|
||||||
|
# Log the SQL query and parameters
|
||||||
|
logger.debug(
|
||||||
|
f"Executing INSERT INTO Review (user_id, song_id, score_song, score_show, score_costume, text_review) "
|
||||||
|
f"VALUES ({review.user_id}, {review.song_id}, {review.score_song}, "
|
||||||
|
f"{review.score_show}, {review.score_costume}, '{review.text_review}')"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Insert the review
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO Review (user_id, song_id, score_song, score_show, score_costume, text_review) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
|
(
|
||||||
|
review.user_id,
|
||||||
|
review.song_id,
|
||||||
|
review.score_song,
|
||||||
|
review.score_show,
|
||||||
|
review.score_costume,
|
||||||
|
review.text_review,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fetch the newly created review
|
||||||
|
logger.debug(
|
||||||
|
f"Checking if review was created for song_id={review.song_id}, user_id={review.user_id}"
|
||||||
|
)
|
||||||
|
result = conn.execute(
|
||||||
|
"SELECT * FROM Review WHERE song_id = ? AND user_id = ?",
|
||||||
|
(review.song_id, review.user_id),
|
||||||
|
).fetchdf()
|
||||||
|
logger.debug(f"Database result for newly created review: {result}")
|
||||||
|
|
||||||
|
if result.empty:
|
||||||
|
logger.warning(
|
||||||
|
f"Review not found after insert attempt: song_id={review.song_id}, user_id={review.user_id}"
|
||||||
|
)
|
||||||
|
raise HTTPException(status_code=404, detail="Review not found")
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Review created successfully for song_id={review.song_id}, user_id={review.user_id}"
|
||||||
|
)
|
||||||
|
return Message(type="success", message="Review created successfully")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error creating review: {str(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/", response_model=Message)
|
||||||
|
async def update_review(review: ReviewIn):
|
||||||
|
print("update_review")
|
||||||
|
logger.debug(f"Received update review request with data: {review.dict()}")
|
||||||
|
try:
|
||||||
|
with get_connection() as conn:
|
||||||
|
# Log the SQL query and parameters
|
||||||
|
logger.debug(
|
||||||
|
f"Executing UPDATE Review SET score_song = {review.score_song}, "
|
||||||
|
f"score_show = {review.score_show}, score_costume = {review.score_costume}, "
|
||||||
|
f"text_review = '{review.text_review}' WHERE song_id = {review.song_id} "
|
||||||
|
f"AND user_id = {review.user_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if the review exists
|
||||||
|
review_df = conn.execute(
|
||||||
|
"SELECT * FROM Review WHERE song_id = ? AND user_id = ?",
|
||||||
|
(review.song_id, review.user_id),
|
||||||
|
).fetchdf()
|
||||||
|
|
||||||
|
if not review_df.empty:
|
||||||
|
# Update the review
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE Review SET score_song = ?, score_show = ?, score_costume = ?, text_review = ? WHERE song_id = ? AND user_id = ?",
|
||||||
|
(
|
||||||
|
review.score_song,
|
||||||
|
review.score_show,
|
||||||
|
review.score_costume,
|
||||||
|
review.text_review,
|
||||||
|
review.song_id,
|
||||||
|
review.user_id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# Insert the review
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO Review (user_id, song_id, score_song, score_show, score_costume, text_review) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
|
(
|
||||||
|
review.user_id,
|
||||||
|
review.song_id,
|
||||||
|
review.score_song,
|
||||||
|
review.score_show,
|
||||||
|
review.score_costume,
|
||||||
|
review.text_review,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Fetch the updated review
|
||||||
|
logger.debug(
|
||||||
|
f"Checking if review was updated for song_id={review.song_id}, user_id={review.user_id}"
|
||||||
|
)
|
||||||
|
result = conn.execute(
|
||||||
|
"SELECT * FROM Review WHERE song_id = ? AND user_id = ?",
|
||||||
|
(review.song_id, review.user_id),
|
||||||
|
).fetchdf()
|
||||||
|
logger.debug(f"Database result: {result}")
|
||||||
|
|
||||||
|
if result.empty:
|
||||||
|
logger.warning(
|
||||||
|
f"Review not found after update attempt: song_id={review.song_id}, user_id={review.user_id}"
|
||||||
|
)
|
||||||
|
raise HTTPException(status_code=404, detail="Review not found")
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
f"Review updated successfully for song_id={review.song_id}, user_id={review.user_id}"
|
||||||
|
)
|
||||||
|
return Message(type="success", message="Review updated successfully")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error updating review: {str(e)}")
|
||||||
|
raise
|
||||||
|
|||||||
@@ -1,27 +1,79 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter, HTTPException, Request
|
||||||
|
|
||||||
|
from models.user import User, UpdateUser
|
||||||
|
from models.msg import Message
|
||||||
|
from lib.db import get_connection
|
||||||
|
from lib.logger import logger
|
||||||
|
from lib.helpers import hash_password
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
router = APIRouter(prefix="/users", tags=["users"])
|
router = APIRouter(prefix="/users", tags=["users"])
|
||||||
|
|
||||||
|
|
||||||
@router.get("/")
|
@router.get("/", response_model=list[User])
|
||||||
async def list_users():
|
async def list_users(request: Request):
|
||||||
return {"users": []}
|
logger.info(f"User list requested from {request.client.host}")
|
||||||
|
with get_connection() as conn:
|
||||||
|
users = conn.execute('SELECT * FROM "User"').fetchdf()
|
||||||
|
|
||||||
|
# Convert DataFrame to dict
|
||||||
|
users_dict = users.to_dict(orient="records")
|
||||||
|
return users_dict
|
||||||
|
|
||||||
|
|
||||||
@router.get("/{user_id}")
|
@router.get("/{user_id}", response_model=User)
|
||||||
async def get_user(user_id: int):
|
async def get_user(user_id: int, request: Request):
|
||||||
return {"user_id": user_id}
|
logger.debug(
|
||||||
|
f"User details requested for user_id: {user_id} from {request.client.host}"
|
||||||
|
)
|
||||||
|
with get_connection() as conn:
|
||||||
|
user_df = conn.execute(
|
||||||
|
'SELECT * FROM "User" WHERE id = ?', (user_id,)
|
||||||
|
).fetchdf()
|
||||||
|
|
||||||
|
if user_df.empty:
|
||||||
|
logger.warning(f"Failed user lookup: user_id {user_id} not found")
|
||||||
|
raise HTTPException(status_code=404, detail="User not found")
|
||||||
|
|
||||||
|
user_data = user_df.to_dict(orient="records")[0]
|
||||||
|
logger.debug(f"User details retrieved: {user_data}")
|
||||||
|
return User(**user_data)
|
||||||
|
|
||||||
|
|
||||||
# @router.post("/")
|
@router.patch("/", response_model=Message)
|
||||||
# async def create_user(user: User):
|
async def update_user(user: UpdateUser, request: Request):
|
||||||
# return {"user": user}
|
logger.info(f"Update attempt for user_id: {user.id} from {request.client.host}")
|
||||||
|
user_id = user.id
|
||||||
|
avatar_id = user.avatar_id if user.avatar_id else None
|
||||||
|
email = user.email if user.email else None
|
||||||
|
password = hash_password(user.password) if user.password else None
|
||||||
|
|
||||||
# @router.patch("/{user_id}")
|
with get_connection() as conn:
|
||||||
# async def update_user(user_id: int, user: User):
|
# Build query dynamically based on non-None values
|
||||||
# return {"user_id": user_id, "user": user}
|
update_fields = []
|
||||||
|
params = []
|
||||||
|
|
||||||
|
if avatar_id is not None:
|
||||||
|
update_fields.append("avatar_id = ?")
|
||||||
|
params.append(avatar_id)
|
||||||
|
|
||||||
@router.delete("/{user_id}")
|
if email is not None:
|
||||||
async def delete_user(user_id: int):
|
update_fields.append("email = ?")
|
||||||
return {"user_id": user_id}
|
params.append(email)
|
||||||
|
|
||||||
|
if password is not None:
|
||||||
|
update_fields.append("hashed_password = ?")
|
||||||
|
params.append(password)
|
||||||
|
|
||||||
|
# Only proceed if there are fields to update
|
||||||
|
if update_fields:
|
||||||
|
query = f'UPDATE "User" SET {", ".join(update_fields)}, updated_at = CURRENT_TIMESTAMP WHERE id = ?'
|
||||||
|
params.append(user_id)
|
||||||
|
logger.debug(f"Executing query: {query}")
|
||||||
|
logger.debug(f"Query parameters: {params}")
|
||||||
|
conn.execute(query, params)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"User updated successfully: {user_id}, avatar_id: {avatar_id}, email: {email}, password: {password}"
|
||||||
|
)
|
||||||
|
return Message(type="success", message="User updated successfully")
|
||||||
|
|||||||
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 5.7 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
@@ -86,14 +86,14 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.1.8"
|
version = "8.2.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" }
|
sdist = { url = "https://files.pythonhosted.org/packages/cd/0f/62ca20172d4f87d93cf89665fbaedcd560ac48b465bd1d92bfc7ea6b0a41/click-8.2.0.tar.gz", hash = "sha256:f5452aeddd9988eefa20f90f05ab66f17fce1ee2a36907fd30b05bbb5953814d", size = 235857, upload-time = "2025-05-10T22:21:03.111Z" }
|
||||||
wheels = [
|
wheels = [
|
||||||
{ url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" },
|
{ url = "https://files.pythonhosted.org/packages/a2/58/1f37bf81e3c689cc74ffa42102fa8915b59085f54a6e4a80bc6265c0f6bf/click-8.2.0-py3-none-any.whl", hash = "sha256:6b303f0b2aa85f1cb4e5303078fadcbcd4e476f114fab9b5007005711839325c", size = 102156, upload-time = "2025-05-10T22:21:01.352Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -131,12 +131,13 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "eurovision-25-backend"
|
name = "eurovision-25-backend"
|
||||||
version = "0.1.0"
|
version = "1.0"
|
||||||
source = { virtual = "." }
|
source = { virtual = "." }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "aiofiles" },
|
{ name = "aiofiles" },
|
||||||
{ name = "duckdb" },
|
{ name = "duckdb" },
|
||||||
{ name = "fastapi" },
|
{ name = "fastapi" },
|
||||||
|
{ name = "loguru" },
|
||||||
{ name = "pandas" },
|
{ name = "pandas" },
|
||||||
{ name = "passlib", extra = ["bcrypt"] },
|
{ name = "passlib", extra = ["bcrypt"] },
|
||||||
{ name = "python-dotenv" },
|
{ name = "python-dotenv" },
|
||||||
@@ -154,6 +155,7 @@ requires-dist = [
|
|||||||
{ name = "aiofiles", specifier = ">=24.1.0" },
|
{ name = "aiofiles", specifier = ">=24.1.0" },
|
||||||
{ name = "duckdb", specifier = ">=1.2.2" },
|
{ name = "duckdb", specifier = ">=1.2.2" },
|
||||||
{ name = "fastapi", specifier = ">=0.115.12" },
|
{ name = "fastapi", specifier = ">=0.115.12" },
|
||||||
|
{ name = "loguru", specifier = ">=0.7.3" },
|
||||||
{ name = "pandas", specifier = ">=2.2.3" },
|
{ name = "pandas", specifier = ">=2.2.3" },
|
||||||
{ name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" },
|
{ name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" },
|
||||||
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
{ name = "python-dotenv", specifier = ">=1.1.0" },
|
||||||
@@ -218,6 +220,19 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loguru"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||||
|
{ name = "win32-setctime", marker = "sys_platform == 'win32'" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "markdown-it-py"
|
name = "markdown-it-py"
|
||||||
version = "3.0.0"
|
version = "3.0.0"
|
||||||
@@ -639,3 +654,12 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" },
|
{ url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" },
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "win32-setctime"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" },
|
||||||
|
]
|
||||||
|
|||||||