Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ dist
.trae
*uploads*
data/*
pg_data/
backups/
.env
.claude/
.claude/
81 changes: 81 additions & 0 deletions docker-compose.local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
services:
postgres:
image: pgvector/pgvector:pg18-trixie
container_name: trailsnap_postgres
restart: unless-stopped
environment:
TZ: Asia/Shanghai
POSTGRES_DB: trailsnap
POSTGRES_USER: trailsnap
POSTGRES_PASSWORD: trailsnap
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C"
PGDATA: /var/lib/postgresql/data/pgdata
networks:
- app-network
ports:
- "5532:5432"
volumes:
- trailsnap_pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U trailsnap -d trailsnap -p 5432"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s

server:
image: siyuan044/trailsnap-server:latest
restart: unless-stopped
expose:
- "8000"
ports:
- "8800:8000"
networks:
- app-network
volumes:
- ./data:/app/data
- ./package/server/app:/app/app:ro
- /Volumes/photo:/app/Photos/:ro
environment:
- TZ=Asia/Shanghai
- DB_URL=postgresql://trailsnap:trailsnap@postgres:5432/trailsnap
- RAILWAY_DB_URL=postgresql://trailsnap:trailsnap@postgres:5432/railway
- AI_API_URL=http://ai:8001
depends_on:
postgres:
condition: service_healthy
restart: true

ai:
image: siyuan044/trailsnap-ai:latest
restart: unless-stopped
expose:
- "8001"
ports:
- "8801:8001"
networks:
- app-network
volumes:
- ./data:/app/data
environment:
- TZ=Asia/Shanghai

frontend:
image: siyuan044/trailsnap-frontend:latest
restart: unless-stopped
ports:
- "8082:80"
depends_on:
- server
networks:
- app-network
environment:
- TZ=Asia/Shanghai

networks:
app-network:
driver: bridge

volumes:
trailsnap_pg_data:
external: true
85 changes: 85 additions & 0 deletions docker-compose.nas.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
services:
postgres:
image: pgvector/pgvector:pg18-trixie
container_name: trailsnap_postgres
restart: unless-stopped
environment:
TZ: Asia/Shanghai
POSTGRES_DB: trailsnap
POSTGRES_USER: trailsnap
POSTGRES_PASSWORD: trailsnap
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --lc-collate=C --lc-ctype=C"
PGDATA: /var/lib/postgresql/data/pgdata
networks:
- app-network
ports:
- "5532:5432"
volumes:
- trailsnap_pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U trailsnap -d trailsnap -p 5432"]
interval: 5s
timeout: 5s
retries: 5
start_period: 10s

server:
image: siyuan044/trailsnap-server:latest
restart: unless-stopped
expose:
- "8000"
ports:
- "8800:8000"
networks:
- app-network
volumes:
- ./data:/app/data
- ./package/server/main.py:/app/main.py:ro
- ./package/server/app:/app/app:ro
- /volume1/photo:/app/Photos/
- /volume1/homes/rowankid/Photos:/app/SynologyPhotos/rowankid:ro
- /volume1/homes/wenjing/Photos:/app/SynologyPhotos/wenjing:ro
environment:
- TZ=Asia/Shanghai
- DB_URL=postgresql://trailsnap:trailsnap@postgres:5432/trailsnap
- RAILWAY_DB_URL=postgresql://trailsnap:trailsnap@postgres:5432/railway
- AI_API_URL=http://192.168.10.12:8801
depends_on:
postgres:
condition: service_healthy
restart: true

ai:
image: siyuan044/trailsnap-ai:latest
restart: unless-stopped
expose:
- "8001"
ports:
- "8801:8001"
networks:
- app-network
volumes:
- ./data:/app/data
environment:
- TZ=Asia/Shanghai

frontend:
image: siyuan044/trailsnap-frontend:latest
restart: unless-stopped
ports:
- "8082:80"
volumes:
- ./package/website/dist:/usr/share/nginx/html:ro
depends_on:
- server
networks:
- app-network
environment:
- TZ=Asia/Shanghai

networks:
app-network:
driver: bridge

volumes:
trailsnap_pg_data:
54 changes: 54 additions & 0 deletions package/server/app/api/classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
class RemovePhotosRequest(BaseModel):
photo_ids: List[UUID]

class RenameTagRequest(BaseModel):
new_name: str

class MergeTagsRequest(BaseModel):
target_name: str
source_names: List[str]

@router.get("", response_model=BaseResponse[List[schemas.TagStats]], summary="获取智能分类标签列表")
def get_tags(
skip: int = 0,
Expand All @@ -29,6 +36,53 @@ def get_tags(
data = crud.get_tags_with_stats(db, current_user.id, skip, limit)
return BaseResponse(data=data)

@router.post("/merge", summary="合并分类标签")
def merge_tags(
payload: MergeTagsRequest = Body(..., description="合并标签请求"),
db: Session = Depends(get_db),
current_user: User = Depends(deps.get_current_user),
):
success, count, message = crud.merge_tags(
db,
current_user.id,
payload.target_name,
payload.source_names,
)
if not success:
raise HTTPException(status_code=400, detail=message)

from app.crud.album import trigger_conditional_albums_update
trigger_conditional_albums_update(db, current_user.id, None)
return BaseResponse(data={"status": "success", "count": count})

@router.put("/{path:path}", summary="重命名分类标签")
def rename_tag(
payload: RenameTagRequest = Body(..., description="标签重命名请求"),
path: str = Path(..., description="标签名称(支持多级/包含/)", path=True),
db: Session = Depends(get_db),
current_user: User = Depends(deps.get_current_user),
):
success, message = crud.rename_tag(db, current_user.id, path, payload.new_name)
if not success:
raise HTTPException(status_code=400, detail=message)

from app.crud.album import trigger_conditional_albums_update
trigger_conditional_albums_update(db, current_user.id, None)
return BaseResponse(data={"status": "success"})

@router.delete("/{path:path}", summary="删除分类标签")
def delete_tag(
path: str = Path(..., description="标签名称(支持多级/包含/)", path=True),
db: Session = Depends(get_db),
current_user: User = Depends(deps.get_current_user),
):
if not crud.delete_tag(db, current_user.id, path):
raise HTTPException(status_code=404, detail="Tag not found")

from app.crud.album import trigger_conditional_albums_update
trigger_conditional_albums_update(db, current_user.id, None)
return BaseResponse(data={"status": "success"})

@router.get("/{path:path}/photos", response_model=BaseResponse[List[photo_schemas.Photo]], summary="获取分类照片列表")
def get_tag_photos(
# path=True 声明:匹配剩余的全部路径(支持包含/)
Expand Down
11 changes: 8 additions & 3 deletions package/server/app/api/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,15 @@ async def get_media_file(
raise HTTPException(status_code=404, detail="File not found")

file_path = photo.file_path
# Determine media type
# Determine media type. Browsers cannot render HEIC/HEIF reliably, so serve
# the generated medium preview for the "file" view used by the lightbox.
ext = os.path.splitext(file_path)[1].lower()
if ext == '.heic':
file_path = _get_thumbnail_path, photo.owner_id, photo_id, db, 'medium'
if ext in ('.heic', '.heif'):
file_path = await run_in_threadpool(_get_thumbnail_path, photo.owner_id, photo_id, db, 'medium')
preview_exists = await run_in_threadpool(os.path.exists, file_path)
if not preview_exists:
raise HTTPException(status_code=404, detail="Preview not found")
ext = os.path.splitext(file_path)[1].lower()
file_size = await run_in_threadpool(os.path.getsize, file_path)

media_type = "application/octet-stream"
Expand Down
Loading