Skip to content

Commit c3023c8

Browse files
fix: UI redesign, LM Studio native support and user permission system
Major Improvements: UI, Security, Media Integration and AI Enhancements
2 parents e4b388a + 6747221 commit c3023c8

110 files changed

Lines changed: 9719 additions & 3078 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/pull_request_template.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## Summary
2+
3+
Describe the change and why it is needed.
4+
5+
## Checklist
6+
7+
- [ ] Tests added/updated when applicable
8+
- [ ] Documentation updated when behavior or setup changed
9+
- [ ] No hardcoded styles introduced

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
uses: actions/checkout@v6.0.2
4747

4848
- name: Set up Node.js
49-
uses: actions/setup-node@v6.2.0
49+
uses: actions/setup-node@v6.3.0
5050
with:
5151
node-version: '20'
5252

.github/workflows/docker_hub_build.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020
fetch-depth: 0
2121

2222
- name: Set up Node.js
23-
uses: actions/setup-node@v6.2.0
23+
uses: actions/setup-node@v6.3.0
2424
with:
2525
node-version: '20'
2626

@@ -100,10 +100,10 @@ jobs:
100100
run: git pull origin main
101101

102102
- name: Set up Docker Buildx
103-
uses: docker/setup-buildx-action@v3.12.0
103+
uses: docker/setup-buildx-action@v4.0.0
104104

105105
- name: Log in to Docker Hub
106-
uses: docker/login-action@v3.7.0
106+
uses: docker/login-action@v4.0.0
107107
with:
108108
username: ciuse99
109109
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
@@ -113,7 +113,7 @@ jobs:
113113
run: echo "platform_slug=$(echo '${{ matrix.platform }}' | tr '/' '-')" >> $GITHUB_OUTPUT
114114

115115
- name: Build and push platform image
116-
uses: docker/build-push-action@v6.19.2
116+
uses: docker/build-push-action@v7.0.0
117117
with:
118118
context: .
119119
file: docker/Dockerfile
@@ -136,13 +136,13 @@ jobs:
136136

137137
steps:
138138
- name: Log in to Docker Hub
139-
uses: docker/login-action@v3.7.0
139+
uses: docker/login-action@v4.0.0
140140
with:
141141
username: ciuse99
142142
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
143143

144144
- name: Set up Docker Buildx
145-
uses: docker/setup-buildx-action@v3.12.0
145+
uses: docker/setup-buildx-action@v4.0.0
146146

147147
- name: Create and push multi-platform manifest
148148
env:

.github/workflows/docker_hub_build_nightly.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ jobs:
2525
uses: actions/checkout@v6.0.2
2626

2727
- name: Set up Docker Buildx
28-
uses: docker/setup-buildx-action@v3.12.0
28+
uses: docker/setup-buildx-action@v4.0.0
2929

3030
- name: Log in to Docker Hub
31-
uses: docker/login-action@v3.7.0
31+
uses: docker/login-action@v4.0.0
3232
with:
3333
username: ciuse99
3434
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
@@ -38,7 +38,7 @@ jobs:
3838
run: echo "platform_slug=$(echo '${{ matrix.platform }}' | tr '/' '-')" >> $GITHUB_OUTPUT
3939

4040
- name: Build and push platform image
41-
uses: docker/build-push-action@v6.19.2
41+
uses: docker/build-push-action@v7.0.0
4242
with:
4343
context: .
4444
file: docker/Dockerfile
@@ -60,13 +60,13 @@ jobs:
6060

6161
steps:
6262
- name: Log in to Docker Hub
63-
uses: docker/login-action@v3.7.0
63+
uses: docker/login-action@v4.0.0
6464
with:
6565
username: ciuse99
6666
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
6767

6868
- name: Set up Docker Buildx
69-
uses: docker/setup-buildx-action@v3.12.0
69+
uses: docker/setup-buildx-action@v4.0.0
7070

7171
- name: Create and push multi-platform manifest
7272
run: |

CONTRIBUTING.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Contributing to Jellyseer Request Automation
1+
# Contributing to Suggestarr
22

33
Thank you for considering contributing to this project! Here's how you can get started:
44

@@ -9,11 +9,37 @@ Thank you for considering contributing to this project! Here's how you can get s
99
- **Improve Documentation**: You can contribute by improving this documentation or adding new sections to it.
1010

1111
## Guidelines:
12-
1. Fork the repository and create your branch from `main`.
12+
1. Fork the repository and create your branch from `nightly`.
1313
2. If you've added code that should be tested, add tests.
1414
3. Ensure the code is well-documented.
1515
4. Open a pull request and provide a clear explanation of the changes.
1616

17+
## Frontend Design System Rules (Required)
18+
19+
For all CSS and Vue `<style>` changes under `client/src`:
20+
21+
- Use design tokens from `client/src/assets/styles/variables.css`.
22+
- Do **not** introduce hardcoded color values (`#hex`, `rgb/rgba`, `hsl/hsla`) in style declarations.
23+
- Do **not** introduce hardcoded spacing values for `margin`, `padding`, or `gap`; use `var(--spacing-*)` (or `calc(...)` from tokens).
24+
- Do **not** introduce hardcoded `border-radius`; use `var(--radius-*)` / component radius tokens.
25+
- Do **not** introduce hardcoded `font-size`; use typography tokens (`var(--font-size-*)`, input/button font-size tokens).
26+
27+
### Advisory Linting (Warning-level)
28+
29+
The frontend includes warning-level style checks:
30+
31+
```bash
32+
cd client && npm run lint:styles
33+
```
34+
35+
This command is advisory-first (warning-level rules) to avoid breaking existing code while enforcing new contributions.
36+
37+
## Pull Request Checklist
38+
39+
- [ ] Tests added/updated when applicable
40+
- [ ] Documentation updated when behavior or setup changed
41+
- [ ] No hardcoded styles introduced
42+
1743
## Building the Docker Image
1844
To build the Docker image for development, run the following commands:
1945
```bash

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111
[![](https://dcbadge.limes.pink/api/server/https://discord.com/invite/JXwFd3PnXY?style=flat)](https://discord.com/invite/JXwFd3PnXY)
1212
</div>
1313

14-
SuggestArr is a project designed to automate media content recommendations and download requests based on user activity in media servers like **Jellyfin**, **Plex**, and now **Emby**. It retrieves recently watched content, searches for similar titles using the TMDb API, and sends automated download requests to **Jellyseer** or **Overseer**.
14+
SuggestArr is a project designed to automate media content recommendations and download requests based on user activity in media servers like **Jellyfin**, **Plex**, and now **Emby**. It retrieves recently watched content, searches for similar titles using the TMDb API, and sends automated download requests to **Seer**.
1515

1616
## Features
1717
- **Multi-Media Server Support**: Supports Jellyfin, Plex, and Emby for retrieving media content.
1818
- **TMDb Integration**: Searches for similar movies and TV shows on TMDb.
1919
- **AI-Powered Recommendations** *(beta)*: Uses any OpenAI-compatible LLM (OpenAI, Ollama, Gemini, LiteLLM…) to generate hyper-personalized suggestions based on watch history, complete with AI reasoning for each pick.
2020
- **AI Search** *(beta)*: Describe in natural language what you want to watch and let the AI find matching titles, personalised to your viewing history, with one-click request to Seer.
21-
- **Automated Requests**: Sends download requests for recommended content to Jellyseer or Seer.
21+
- **Automated Requests**: Sends download requests for recommended content to Seer.
2222
- **Web Interface**: A user-friendly interface for configuration and management.
2323
- **Real-Time Logs**: View and filter logs in real time (e.g., `INFO`, `ERROR`, `DEBUG`).
2424
- **User Selection**: Choose specific users to initiate requests, allowing management and approval of auto-requested content.
@@ -31,7 +31,7 @@ SuggestArr is a project designed to automate media content recommendations and d
3131
- **Python 3.x** or **Docker**
3232
- **[TMDb API Key](https://www.themoviedb.org/documentation/api)**
3333
- Configured **[Jellyfin](https://jellyfin.org/)**, **[Plex](https://www.plex.tv/)**, or **[Emby](https://emby.media/)**
34-
- Configured **[Jellyseer](https://github.com/Fallenbagel/jellyseerr)** or **[Overseer](https://github.com/sct/overseerr)**
34+
- Configured **[Seer](https://github.com/seerr-team/seerr)**
3535
- (Optional) External database (PostgreSQL or MySQL) for improved performance
3636

3737
## Docker Usage
@@ -68,15 +68,15 @@ Access the web interface at: http://localhost:5000 (or your custom port if confi
6868

6969
Make sure your environment is set up correctly and that the application is running to access the web interface.
7070

71-
### Using a Specific Jellyseer/Overseer User for Requests
72-
If you'd like to use a specific Jellyseer user to make media requests, follow these steps:
71+
### Using a Specific Seer User for Requests
72+
If you'd like to use a specific Seer user to make media requests, follow these steps:
7373

7474
1. In the web interface, enable the user selection option by checking the corresponding box.
7575
2. Select the desired user from the dropdown list.
7676
3. Enter the password for the selected user.
7777
4. The system will now use this user to make media requests, rather than using the admin or default profile.
7878

79-
Note: Currently, only local Jellyseer users are supported.
79+
Note: Currently, only local Seer users are supported.
8080

8181
## AI-Powered Recommendations (Beta)
8282

@@ -163,7 +163,7 @@ Type a natural-language description of what you feel like watching. The LLM inte
163163
- **Natural language queries** — describe mood, genre, decade, language, or specific themes
164164
- **Viewing-history personalisation** — the AI tailors results based on what you (or your users) have already watched
165165
- **Exclude already-watched titles** — hide content you've already seen
166-
- **One-click requesting** — send results directly to Jellyseer/Overseer without leaving the page
166+
- **One-click requesting** — send results directly to Seer without leaving the page
167167
- **Query interpretation badge** — see how the AI parsed your query (genres, year range, language, min rating)
168168

169169
### How to enable

TMDB_API_ENDPOINTS.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,11 @@ Tests the Jellyfin server connection with provided API token and URL.
9292
- `400 Bad Request`: Missing token or URL, or connection failed
9393
- `500 Internal Server Error`: Internal error
9494

95-
## Overseer/Jellyseer Endpoints
95+
## Seer Endpoints
9696

9797
### POST `/api/seer/test`
9898

99-
Tests the Overseer/Jellyseer API connection with provided API key and URL.
99+
Tests the Seer API connection with provided API key and URL.
100100

101101
**Request Body:**
102102
```json
@@ -111,7 +111,7 @@ Tests the Overseer/Jellyseer API connection with provided API key and URL.
111111
```json
112112
{
113113
"status": "success",
114-
"message": "Overseer/Jellyseer connection successful!",
114+
"message": "Seer connection successful!",
115115
"data": {
116116
"users_count": 4,
117117
"server_url": "http://localhost:5055",
@@ -208,7 +208,7 @@ All service test endpoints have been enhanced with robust validation:
208208
```json
209209
{
210210
"status": "success",
211-
"message": "Overseer/Jellyseer connection successful but no users found",
211+
"message": "Seer connection successful but no users found",
212212
"data": {
213213
"users_count": 0,
214214
"server_url": "http://localhost:5055"

api_service/app.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from api_service.blueprints.seer.routes import seer_bp
2323
from api_service.blueprints.plex.routes import plex_bp
2424
from api_service.blueprints.automation.routes import automation_bp
25+
from api_service.blueprints.integrations.routes import integrations_bp
2526
from api_service.blueprints.logs.routes import logs_bp
2627
from api_service.blueprints.config.routes import config_bp
2728
from api_service.blueprints.tmdb.routes import tmdb_bp
@@ -60,6 +61,11 @@ def __call__(self, environ, start_response):
6061
logger = LoggerManager.get_logger("APP")
6162
logger.debug(f"Current log level: {logging.getLevelName(logger.getEffectiveLevel())}")
6263

64+
DEFAULT_CORS_ORIGINS = [
65+
'http://localhost:8080',
66+
'http://127.0.0.1:8080',
67+
]
68+
6369
# App Factory Pattern for modularity and testability
6470
def create_app():
6571
"""
@@ -81,9 +87,10 @@ def create_app():
8187

8288
# ------------------------------------------------------------------
8389
# CORS
84-
# Default: allow all origins so existing LAN deployments keep working.
85-
# Operators who expose SuggestArr to the internet SHOULD restrict this
86-
# via the SUGGESTARR_ALLOWED_ORIGINS env var (comma-separated list).
90+
# Default: allow the local frontend dev servers used by the Vue app.
91+
# Credentialed requests cannot use wildcard origins, so operators who
92+
# expose SuggestArr elsewhere SHOULD set SUGGESTARR_ALLOWED_ORIGINS to
93+
# a comma-separated allowlist of exact origins.
8794
# Example: SUGGESTARR_ALLOWED_ORIGINS=https://suggestarr.home.example.com
8895
# ------------------------------------------------------------------
8996
allowed_origins_env = os.environ.get('SUGGESTARR_ALLOWED_ORIGINS', '').strip()
@@ -96,8 +103,12 @@ def create_app():
96103
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
97104
logger.info("CORS restricted to: %s", allowed_origins)
98105
else:
99-
# Wildcard — backward-compatible default for LAN deployments.
100-
CORS(application)
106+
CORS(application,
107+
origins=DEFAULT_CORS_ORIGINS,
108+
supports_credentials=True,
109+
allow_headers=["Authorization", "Content-Type"],
110+
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"])
111+
logger.info("CORS using default frontend origins: %s", DEFAULT_CORS_ORIGINS)
101112

102113
# ------------------------------------------------------------------
103114
# Rate limiter
@@ -150,6 +161,7 @@ def handle_500(exc):
150161
application.register_blueprint(seer_bp, url_prefix='/api/seer')
151162
application.register_blueprint(plex_bp, url_prefix='/api/plex')
152163
application.register_blueprint(automation_bp, url_prefix='/api/automation')
164+
application.register_blueprint(integrations_bp, url_prefix='/api/integrations')
153165
application.register_blueprint(logs_bp, url_prefix='/api')
154166
application.register_blueprint(config_bp, url_prefix='/api/config')
155167
application.register_blueprint(tmdb_bp, url_prefix='/api/tmdb')

api_service/auth/auth_service.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
from api_service.auth.secret_key import load_secret_key
2727
from api_service.config.logger_manager import LoggerManager
28+
from api_service.db.database_manager import DatabaseManager
2829

2930
logger = LoggerManager.get_logger("AuthService")
3031

@@ -95,12 +96,14 @@ def create_access_token(user_id: int, username: str, role: str) -> str:
9596
Issue a short-lived signed JWT access token.
9697
9798
Payload claims:
98-
sub — user ID (string)
99-
username — display name
100-
role — 'admin' or 'user'
101-
iat — issued-at timestamp
102-
exp — expiry timestamp (now + ACCESS_TOKEN_EXPIRE_MINUTES)
103-
jti — unique token ID (reserved for future revocation list)
99+
sub — user ID (string)
100+
username — display name
101+
role — 'admin' or 'user'
102+
iat — issued-at timestamp
103+
exp — expiry timestamp (now + ACCESS_TOKEN_EXPIRE_MINUTES)
104+
jti — unique token ID (reserved for future revocation list)
105+
can_manage_ai — boolean flag for per‑user AI management
106+
visible_tabs — comma‑separated list of UI tabs the user may access
104107
105108
Args:
106109
user_id: Internal DB primary key of the authenticated user.
@@ -111,13 +114,26 @@ def create_access_token(user_id: int, username: str, role: str) -> str:
111114
str: Signed JWT string.
112115
"""
113116
now = datetime.now(tz=timezone.utc)
117+
# Fetch permission fields from DB, fallback to defaults on error
118+
try:
119+
db = DatabaseManager()
120+
user_record = db.get_auth_user_by_id(user_id)
121+
can_manage_ai = bool(user_record.get('can_manage_ai', 0))
122+
visible_tabs = user_record.get('visible_tabs', 'requests,jobs,profile')
123+
except Exception as e:
124+
logger = LoggerManager.get_logger('AuthService')
125+
logger.warning("Failed to fetch permission fields for JWT payload: %s", e)
126+
can_manage_ai = False
127+
visible_tabs = 'requests,jobs,profile'
114128
payload = {
115129
"sub": str(user_id),
116130
"username": username,
117131
"role": role,
118132
"iat": now,
119133
"exp": now + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES),
120134
"jti": str(uuid.uuid4()),
135+
"can_manage_ai": can_manage_ai,
136+
"visible_tabs": visible_tabs,
121137
}
122138
return jwt.encode(payload, load_secret_key(), algorithm=_JWT_ALGORITHM)
123139

api_service/auth/middleware.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ def enforce_authentication() -> Optional[tuple]:
227227
"id": payload["sub"],
228228
"username": payload.get("username", ""),
229229
"role": payload.get("role", "user"),
230+
"can_manage_ai": payload.get("can_manage_ai", 0),
231+
"visible_tabs": payload.get("visible_tabs", "requests,jobs,profile"),
230232
}
231233

232234
return None # Allow the request to continue.

0 commit comments

Comments
 (0)