Merge branch 'develop/0.2.0' into 'feature/32-use-bootstrap-for-header-navigation'
# Conflicts: # kontor-vue/src/App.vue # kontor-vue/src/assets/main.css
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from src.apis.version1 import comic, media, tysc, admin
|
from src.apis.version1 import comic, mediaactor, mediafile, tysc, admin
|
||||||
|
|
||||||
api_router = APIRouter(prefix="/api")
|
api_router = APIRouter(prefix="/api")
|
||||||
api_router.include_router(comic.router, prefix="/comics", tags=["comics"])
|
api_router.include_router(comic.router, prefix="/comics", tags=["comics"])
|
||||||
api_router.include_router(media.router, prefix="/media", tags=["media"])
|
api_router.include_router(mediafile.router, prefix="/media", tags=["media"])
|
||||||
|
api_router.include_router(mediaactor.router, prefix="/media", tags=["media"])
|
||||||
api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"])
|
api_router.include_router(tysc.router, prefix="/tysc", tags=["tysc"])
|
||||||
api_router.include_router(admin.router, prefix="/login", tags=["login"])
|
api_router.include_router(admin.router, prefix="/login", tags=["login"])
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
from typing import List, AnyStr
|
||||||
|
|
||||||
|
from fastapi import APIRouter, status, HTTPException, Depends
|
||||||
|
from sqlalchemy import select, Sequence
|
||||||
|
from src.core.log_conf import logger
|
||||||
|
from src.apis.utils import SessionDep
|
||||||
|
from src.db.repository.media import create_new_mediafile
|
||||||
|
from src.schema.media.actor import MediaActorResponse
|
||||||
|
from src.db.models.media import MediaActor
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
@router.get("/actors", response_model=List[MediaActorResponse])
|
||||||
|
#def get_all_files(db: SessionDep, review: bool = False, download: bool = False, current_user: Profile = Depends(get_current_user_from_token)) -> List[MediaFileResponse]:
|
||||||
|
def get_all_files(db: SessionDep, review: bool = False, download: bool = False) -> List[MediaActorResponse]:
|
||||||
|
results: List[MediaActorResponse] = []
|
||||||
|
actors = db.scalars(select(MediaActor)).all()
|
||||||
|
for mediaactor in actors:
|
||||||
|
response = MediaActorResponse(id=mediaactor.id, name=str(mediaactor.name), url=str(mediaactor.url))
|
||||||
|
results.append(response)
|
||||||
|
return results
|
||||||
@@ -4,7 +4,9 @@ from fastapi import APIRouter, status, HTTPException, Depends
|
|||||||
from sqlalchemy import select, Sequence
|
from sqlalchemy import select, Sequence
|
||||||
from src.core.log_conf import logger
|
from src.core.log_conf import logger
|
||||||
from src.apis.utils import SessionDep
|
from src.apis.utils import SessionDep
|
||||||
from src.db.repository.media import create_new_mediafile
|
from src.db.repository.media import create_new_mediaactorfile, create_new_mediafile
|
||||||
|
from src.schema.media.actor import MediaActorResponse
|
||||||
|
from src.schema.media.actorfile import MediaActorFileResponse
|
||||||
from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file
|
from src.schema.media.file import MediaFileResponse, Link, get_file_details, set_file
|
||||||
from src.db.models.media import MediaFile
|
from src.db.models.media import MediaFile
|
||||||
|
|
||||||
@@ -47,6 +49,43 @@ def get_file(file_id: AnyStr, db: SessionDep) -> MediaFileResponse:
|
|||||||
response = get_file_details(mediafile)
|
response = get_file_details(mediafile)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@router.get("/files/{file_id}/actors", response_model=List[MediaActorResponse])
|
||||||
|
def get_file_actors(file_id: AnyStr, db: SessionDep) -> List[MediaActorResponse]:
|
||||||
|
mediafile = db.get(MediaFile, file_id)
|
||||||
|
if not mediafile:
|
||||||
|
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||||
|
actor_files = mediafile.media_actor_files
|
||||||
|
logger.info(f"already known actors: {actor_files}")
|
||||||
|
results: List[MediaActorResponse] = []
|
||||||
|
for actor_file in actor_files:
|
||||||
|
response = MediaActorResponse(id=actor_file.media_actor.id, name=actor_file.media_actor.name, url=actor_file.media_actor.url)
|
||||||
|
results.append(response)
|
||||||
|
return results
|
||||||
|
|
||||||
|
@router.put("/files/{file_id}/actors", response_model=List[MediaActorFileResponse])
|
||||||
|
def update_file_actors(file_id: AnyStr, db: SessionDep, actors: List[MediaActorResponse]) -> List[MediaActorFileResponse]:
|
||||||
|
mediafile = db.get(MediaFile, file_id)
|
||||||
|
if not mediafile:
|
||||||
|
raise HTTPException(status_code=404, detail="MediaFile could not be found")
|
||||||
|
actor_files = mediafile.media_actor_files
|
||||||
|
logger.info(f"already known actors: {actor_files}")
|
||||||
|
for actor in actors:
|
||||||
|
already_associated = False
|
||||||
|
for actor_file in actor_files:
|
||||||
|
if actor.id == actor_file.media_actor_id:
|
||||||
|
logger.info("alreay associated - do nothing")
|
||||||
|
already_associated = True
|
||||||
|
break
|
||||||
|
if not already_associated:
|
||||||
|
create_new_mediaactorfile(db, actor.id, mediafile.id)
|
||||||
|
db.refresh(mediafile)
|
||||||
|
actor_files = mediafile.media_actor_files
|
||||||
|
results: List[MediaActorFileResponse] = []
|
||||||
|
for actor_file in actor_files:
|
||||||
|
response = MediaActorFileResponse(id=actor_file.id, actor_id=actor_file.media_actor_id, file_id=actor_file.media_file_id)
|
||||||
|
results.append(response)
|
||||||
|
return results
|
||||||
|
|
||||||
@router.put("/files/{file_id}", response_model=MediaFileResponse)
|
@router.put("/files/{file_id}", response_model=MediaFileResponse)
|
||||||
def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
|
def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> MediaFileResponse:
|
||||||
mediaFile = db.get(MediaFile, file_id)
|
mediaFile = db.get(MediaFile, file_id)
|
||||||
@@ -55,7 +94,11 @@ def update_file(file_id: AnyStr, db: SessionDep, info: MediaFileResponse) -> Med
|
|||||||
set_file(info, mediaFile)
|
set_file(info, mediaFile)
|
||||||
db.add(mediaFile)
|
db.add(mediaFile)
|
||||||
db.commit()
|
db.commit()
|
||||||
return info
|
mediafile = db.get(MediaFile, file_id)
|
||||||
|
if not mediafile:
|
||||||
|
raise HTTPException(status_code=404, detail="MediaFile could not be updated")
|
||||||
|
response = get_file_details(mediafile)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
@router.post("/files", status_code=status.HTTP_201_CREATED)
|
@router.post("/files", status_code=status.HTTP_201_CREATED)
|
||||||
@@ -21,10 +21,10 @@ class BaseMixin:
|
|||||||
|
|
||||||
|
|
||||||
class BaseVideoMixin:
|
class BaseVideoMixin:
|
||||||
cloud_link = Column(String)
|
cloud_link = Column(String, nullable=True)
|
||||||
file_name = Column(String)
|
file_name = Column(String, nullable=True)
|
||||||
path = Column(String)
|
path = Column(String)
|
||||||
review = Column(Boolean)
|
review = Column(Boolean)
|
||||||
title = Column(String)
|
title = Column(String)
|
||||||
url = Column(String, unique=True)
|
url = Column(String, nullable=True)
|
||||||
should_download = Column(Boolean)
|
should_download = Column(Boolean)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class MediaFile(Base, BaseMixin, BaseVideoMixin):
|
|||||||
class MediaActor(Base, BaseMixin):
|
class MediaActor(Base, BaseMixin):
|
||||||
__tablename__ = 'media_actor'
|
__tablename__ = 'media_actor'
|
||||||
name = Column(String)
|
name = Column(String)
|
||||||
url = Column(String, unique=True)
|
url = Column(String, unique=True, nullable=True)
|
||||||
media_actor_files = relationship("MediaActorFile")
|
media_actor_files = relationship("MediaActorFile")
|
||||||
|
|
||||||
|
|
||||||
@@ -82,6 +82,11 @@ class MediaActorFile(Base, BaseMixin):
|
|||||||
media_file_id = Column(String, ForeignKey("media_file.id"), nullable=True)
|
media_file_id = Column(String, ForeignKey("media_file.id"), nullable=True)
|
||||||
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
media_file = relationship("MediaFile", back_populates="media_actor_files")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f'MediaActorFile({self.id} {self.media_actor_id} {self.media_file_id})'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'{self.id} {self.media_actor_id} {self.media_file_id}'
|
||||||
|
|
||||||
class MediaArticle(Base, BaseMixin):
|
class MediaArticle(Base, BaseMixin):
|
||||||
__tablename__ = 'media_article'
|
__tablename__ = 'media_article'
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import AnyStr
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from src.core.log_conf import logger
|
from src.core.log_conf import logger
|
||||||
from src.db.models.media import MediaFile, MediaVideo
|
from src.db.models.media import MediaActorFile, MediaFile, MediaVideo
|
||||||
from src.webapps.media.forms import AddLinkForm
|
from src.webapps.media.forms import AddLinkForm
|
||||||
|
|
||||||
|
|
||||||
@@ -38,3 +38,16 @@ def create_new_mediafile(link: AnyStr, db: Session) -> MediaFile:
|
|||||||
logger.info(f"created {media_file}")
|
logger.info(f"created {media_file}")
|
||||||
return media_file
|
return media_file
|
||||||
|
|
||||||
|
def create_new_mediaactorfile(db: Session, actor_id: AnyStr, file_id: AnyStr) -> MediaActorFile:
|
||||||
|
logger.info(f"create MediaActorFile with actor {actor_id} and file {file_id}")
|
||||||
|
media_actor_file: MediaActorFile = MediaActorFile()
|
||||||
|
media_actor_file.id = str(uuid.uuid4())
|
||||||
|
media_actor_file.created_date = datetime.now()
|
||||||
|
media_actor_file.last_modified_date = datetime.now()
|
||||||
|
media_actor_file.version = 0
|
||||||
|
media_actor_file.media_actor_id = actor_id
|
||||||
|
media_actor_file.media_file_id = file_id
|
||||||
|
db.add(media_actor_file)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(media_actor_file)
|
||||||
|
return media_actor_file
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from src.db.models.media import MediaActor
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class MediaActorResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
name: str
|
||||||
|
url: str
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from src.db.models.media import MediaFile
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class MediaActorFileResponse(BaseModel):
|
||||||
|
id: str
|
||||||
|
file_id: str
|
||||||
|
actor_id: str
|
||||||
@@ -9,14 +9,14 @@ class MediaFileResponse(BaseModel):
|
|||||||
title: str | None = None
|
title: str | None = None
|
||||||
file_name: str | None = None
|
file_name: str | None = None
|
||||||
cloud_link: str | None = None
|
cloud_link: str | None = None
|
||||||
url: str
|
url: str | None = None
|
||||||
review: bool = False
|
review: bool = False
|
||||||
should_download: bool = False
|
should_download: bool = False
|
||||||
|
|
||||||
class Link(BaseModel):
|
class Link(BaseModel):
|
||||||
url: str
|
url: str
|
||||||
|
|
||||||
def get_file_details(mediafile: MediaFile) -> MediaFileResponse | None:
|
def get_file_details(mediafile: MediaFile) -> MediaFileResponse:
|
||||||
response = MediaFileResponse(id=mediafile.id,
|
response = MediaFileResponse(id=mediafile.id,
|
||||||
title=mediafile.title,
|
title=mediafile.title,
|
||||||
file_name=mediafile.file_name,
|
file_name=mediafile.file_name,
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial;
|
||||||
|
padding: 10px;
|
||||||
|
background: lightgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header/Blog Title */
|
||||||
|
.header {
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the top navigation bar */
|
||||||
|
.topnav {
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: darkgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the topnav links */
|
||||||
|
.topnav a {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
color: #f2f2f2;
|
||||||
|
text-align: center;
|
||||||
|
padding: 14px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change color on hover */
|
||||||
|
.topnav a:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnav {
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnav a {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
color: #f2f2f2;
|
||||||
|
text-align: center;
|
||||||
|
padding: 14px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inline {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inline label {
|
||||||
|
margin: 5px 10px 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inline input {
|
||||||
|
padding-right: 50px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inline button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: dodgerblue;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inline button:hover {
|
||||||
|
background-color: royalblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill-nav a {
|
||||||
|
display: inline-block;
|
||||||
|
color: white;
|
||||||
|
background-color: dodgerblue;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change the color of links on mouse-over */
|
||||||
|
.pill-nav a:hover {
|
||||||
|
background-color: royalblue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a color to the active/current link */
|
||||||
|
.pill-nav a.active {
|
||||||
|
background-color: royalblue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maincolumn {
|
||||||
|
float:left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
/* Create two unequal columns that floats next to each other */
|
||||||
|
/* Left column */
|
||||||
|
.leftcolumn {
|
||||||
|
float: left;
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right column */
|
||||||
|
.rightcolumn {
|
||||||
|
float: left;
|
||||||
|
width: 25%;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fake image */
|
||||||
|
.fakeimg {
|
||||||
|
background-color: #aaa;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a card effect for articles */
|
||||||
|
.card {
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear floats after the columns */
|
||||||
|
.row::after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
background: #ddd;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive layout - when the screen is less than 800px wide, make the two columns stack on top of each other instead of next to each other */
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.leftcolumn, .rightcolumn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive layout - when the screen is less than 400px wide, make the navigation links stack on top of each other instead of next to each other */
|
||||||
|
@media screen and (max-width: 400px) {
|
||||||
|
.topnav a {
|
||||||
|
float: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,23 +4,39 @@
|
|||||||
<title>Comic List</title>
|
<title>Comic List</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>Comics..</Comics></h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subnav %}
|
||||||
|
<div class="subnav">
|
||||||
|
<a href="/comic/artists/">Artists</a>
|
||||||
|
<a href="/comic/publishers/">Publishers</a>
|
||||||
|
<a href="/comic/worktypes">WorkTypes</a>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% with msg=msg %}
|
{% with msg=msg %}
|
||||||
{% include "components/alerts.html" %}
|
{% include "components/alerts.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<form class="d-flex" action="/comic/comics/">
|
<form class="form-inline" action="/comic/comics">
|
||||||
<input class="form-control me-2" name="query" id="autocomplete" type="search" placeholder="Search" aria-label="Search">
|
<input type="text" placeholder="Search" name="query">
|
||||||
Completed<input type="checkbox" name="completed" {% if request.query_params.get("completed")=="on" %}checked{% endif %} aria-label="Completed">
|
<label>
|
||||||
Order<input type="checkbox" name="order" {% if request.query_params.get("order")=="on" %}checked{% endif %} aria-label="Order">
|
<input type="checkbox" name="completed" {% if request.query_params.get("completed")=="on" %}checked{% endif %}>Completed
|
||||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="order" {% if request.query_params.get("order")=="on" %}checked{% endif %}>Order
|
||||||
|
</label>
|
||||||
|
<button type="submit">Search</button>
|
||||||
|
<div class="pill-nav">
|
||||||
|
<a href="/comic/comic/add">Add Comic</a>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<h1 class="display-5">Comics..</h1>
|
<div class="maincolumn">
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th scope="col">Title</th>
|
<th scope="col">Title</th>
|
||||||
@@ -40,9 +56,6 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div>
|
|
||||||
<a href="/comic/comic/add" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Add Comic</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<h2>Footer</h2>
|
||||||
@@ -1,4 +1,12 @@
|
|||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
|
<div class="topnav">
|
||||||
|
<a href="/">Kontor</a>
|
||||||
|
<a href="/comic/comics">Comics</a>
|
||||||
|
<a href="/media/files">Media</a>
|
||||||
|
<a style="float:right" href="/login/">Login</a></li>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- <nav class="navbar navbar-expand-lg navbar-light bg-light px-5">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<a class="navbar-brand" href="#">Kontor</a>
|
<a class="navbar-brand" href="#">Kontor</a>
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
@@ -63,4 +71,4 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav> -->
|
||||||
|
|||||||
@@ -5,6 +5,9 @@
|
|||||||
<title>Kontor</title>
|
<title>Kontor</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>Kontor</h1>
|
||||||
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% with msg=msg %}
|
{% with msg=msg %}
|
||||||
{% include "components/alerts.html" %}
|
{% include "components/alerts.html" %}
|
||||||
|
|||||||
@@ -4,25 +4,41 @@
|
|||||||
<title>MediaFiles List</title>
|
<title>MediaFiles List</title>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<h1>MediaFiles..</h1>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block subnav %}
|
||||||
|
{% include "media/media_nav.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% with msg=msg %}
|
{% with msg=msg %}
|
||||||
{% include "components/alerts.html" %}
|
{% include "components/alerts.html" %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
<div class="container">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<form class="d-flex" action="/media/files/">
|
<form class="form-inline" action="/media/files">
|
||||||
<input class="form-control me-2" name="query" id="autocomplete" type="search" placeholder="Search" aria-label="Search">
|
<input type="text" placeholder="Search" name="query">
|
||||||
Review<input type="checkbox" name="review" aria-label="Review">
|
<label>
|
||||||
Download<input type="checkbox" name="download" aria-label="Download">
|
<input type="checkbox" name="review" {% if request.query_params.get("review")=="on" %}checked{% endif %}>Review
|
||||||
<button class="btn btn-outline-success" type="submit">Search</button>
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="download" {% if request.query_params.get("download")=="on" %}checked{% endif %}>Download
|
||||||
|
</label>
|
||||||
|
<button type="submit">Search</button>
|
||||||
|
<div class="pill-nav">
|
||||||
|
<a href="/media/files/add">Add MediaFile</a>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<div class="maincolumn">
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th scope="col">Titel</th>
|
<th scope="col">Titel</th>
|
||||||
<th scope="col">Review</th>
|
<th scope="col">Review</th>
|
||||||
<th scope="col">Download</th>
|
<th scope="col">Download</th>
|
||||||
|
<th colspan="2">Actions</th>
|
||||||
</tr></thead>
|
</tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{% for mediafile in mediafiles %}
|
{% for mediafile in mediafiles %}
|
||||||
@@ -30,6 +46,8 @@
|
|||||||
<th scope="row"><a href="/media/files/{{mediafile.id}}">{{mediafile.title}}</a></th>
|
<th scope="row"><a href="/media/files/{{mediafile.id}}">{{mediafile.title}}</a></th>
|
||||||
<td>{% with check=mediafile.review %}{% include "components/check.html" %}{% endwith %}</td>
|
<td>{% with check=mediafile.review %}{% include "components/check.html" %}{% endwith %}</td>
|
||||||
<td>{% with check=mediafile.should_download %}{% include "components/check.html" %}{% endwith %}</td>
|
<td>{% with check=mediafile.should_download %}{% include "components/check.html" %}{% endwith %}</td>
|
||||||
|
<td><a href="/media/files/edit/{{mediafile.id}}" class="btn btn-outline-primary btn-sm active" role="button" aria-pressed="true">Edit</a></td>
|
||||||
|
<td><a href="/media/files/delete/{{mediafile.id}}" class="btn btn-outline-danger btn-sm active" role="button" aria-pressed="true">Delete</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="subnav">
|
||||||
|
<a href="/media/files/">MediaFiles</a>
|
||||||
|
<a href="/media/actors/">MediaActors</a>
|
||||||
|
<a href="/media/videos/">MediaVideos</a>
|
||||||
|
</div>
|
||||||
@@ -4,22 +4,72 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-BmbxuPwQa2lc/FVzBcNJ7UAyJxM6wuqIj61tLrc4wSX0szH/Ev+nYRRuWlolflfl" crossorigin="anonymous">
|
||||||
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
|
<link rel="stylesheet" href="{{ url_for('static', path='style.css') }}">
|
||||||
{% block title %}
|
{% block title %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
{% block header %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
{% include "components/navbar.html" %}
|
{% include "components/navbar.html" %}
|
||||||
|
{% block subnav %}
|
||||||
|
{% endblock %}
|
||||||
|
<!-- <div class="topnav">
|
||||||
|
<a href="#">Link</a>
|
||||||
|
<a href="#">Link</a>
|
||||||
|
<a href="#">Link</a>
|
||||||
|
<a href="#" style="float:right">Link</a>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta2/dist/js/bootstrap.bundle.min.js" integrity="sha384-b5kHyXgcpbZJO/tY9Ul7kGkf1S0CWuKcCD38l8YkeH8z8QjE0GmW1gYU5S9FOnJ0" crossorigin="anonymous"></script>
|
||||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
|
||||||
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
|
|
||||||
<script src="{{ url_for('static', path='js/autocomplete.js') }}"></script>
|
<script src="{{ url_for('static', path='js/autocomplete.js') }}"></script>
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
<!-- <div class="row">
|
||||||
|
<div class="leftcolumn">
|
||||||
|
<div class="card">
|
||||||
|
<h2>TITLE HEADING</h2>
|
||||||
|
<h5>Title description, Dec 7, 2017</h5>
|
||||||
|
<div class="fakeimg" style="height:200px;">Image</div>
|
||||||
|
<p>Some text..</p>
|
||||||
|
<p>Sunt in culpa qui officia deserunt mollit anim id est laborum consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2>TITLE HEADING</h2>
|
||||||
|
<h5>Title description, Sep 2, 2017</h5>
|
||||||
|
<div class="fakeimg" style="height:200px;">Image</div>
|
||||||
|
<p>Some text..</p>
|
||||||
|
<p>Sunt in culpa qui officia deserunt mollit anim id est laborum consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="rightcolumn">
|
||||||
|
<div class="card">
|
||||||
|
<h2>About Me</h2>
|
||||||
|
<div class="fakeimg" style="height:100px;">Image</div>
|
||||||
|
<p>Some text about me in culpa qui officia deserunt mollit anim..</p>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>Popular Post</h3>
|
||||||
|
<div class="fakeimg"><p>Image</p></div>
|
||||||
|
<div class="fakeimg"><p>Image</p></div>
|
||||||
|
<div class="fakeimg"><p>Image</p></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>Follow Me</h3>
|
||||||
|
<p>Some text..</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
{% include "components/footer.html" %}
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ def add_worktype(request: Request, db: SessionDep):
|
|||||||
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request})
|
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request})
|
||||||
|
|
||||||
@router.post("/worktype/add")
|
@router.post("/worktype/add")
|
||||||
async def add_worktype(db: SessionDep, request: Request):
|
async def add_worktype_post(db: SessionDep, request: Request):
|
||||||
form = AddWorktypeForm(request)
|
form = AddWorktypeForm(request)
|
||||||
await form.load_data()
|
await form.load_data()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
@@ -47,11 +47,11 @@ async def add_worktype(db: SessionDep, request: Request):
|
|||||||
|
|
||||||
@router.get("/worktype/edit/{worktype_id}")
|
@router.get("/worktype/edit/{worktype_id}")
|
||||||
def edit_worktype(db: SessionDep, request: Request, worktype_id: str):
|
def edit_worktype(db: SessionDep, request: Request, worktype_id: str):
|
||||||
worktype = db.get(WorkType, worktype_id)
|
worktype: WorkType | None = db.get(WorkType, worktype_id)
|
||||||
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request, "worktype": worktype.name})
|
return templates.TemplateResponse("comic/worktype_edit.html", {"request": request, "worktype": worktype.name})
|
||||||
|
|
||||||
@router.post("/worktype/edit/{worktype_id}")
|
@router.post("/worktype/edit/{worktype_id}")
|
||||||
async def edit_worktype(request: Request, db: SessionDep, worktype_id: str):
|
async def edit_worktype_post(request: Request, db: SessionDep, worktype_id: str):
|
||||||
form = AddWorktypeForm(request)
|
form = AddWorktypeForm(request)
|
||||||
await form.load_data()
|
await form.load_data()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
from typing import AnyStr
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from sqlalchemy import or_
|
||||||
|
|
||||||
|
from src.apis.utils import SessionDep
|
||||||
|
from src.db.models.media import MediaActor
|
||||||
|
from src.core.log_conf import logger
|
||||||
|
|
||||||
|
templates = Jinja2Templates(directory="src/templates")
|
||||||
|
router = APIRouter(include_in_schema=False, prefix="/media")
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/actors")
|
||||||
|
def get_actors(db: SessionDep, request: Request, msg: str | None = None):
|
||||||
|
actors = db.query(MediaActor).all()
|
||||||
|
return templates.TemplateResponse("media/actors.html", {"request": request, "msg": msg, "actors": actors})
|
||||||
|
|
||||||
|
@router.get("/actors/{actor_id}")
|
||||||
|
def artist_detail(actor_id: AnyStr, request: Request, db: SessionDep):
|
||||||
|
actor = db.get(MediaActor, actor_id)
|
||||||
|
return templates.TemplateResponse("media/actor_detail.html", {"request": request, "actor": actor})
|
||||||
|
|
||||||
@@ -9,12 +9,13 @@ from src.apis.utils import SessionDep
|
|||||||
from src.apis.version1.admin import get_current_user_from_token
|
from src.apis.version1.admin import get_current_user_from_token
|
||||||
from src.db.models.admin import Profile
|
from src.db.models.admin import Profile
|
||||||
from src.db.models.media import MediaFile, MediaActor
|
from src.db.models.media import MediaFile, MediaActor
|
||||||
|
from src.core.log_conf import logger
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="src/templates")
|
templates = Jinja2Templates(directory="src/templates")
|
||||||
router = APIRouter(include_in_schema=False, prefix="/media")
|
router = APIRouter(include_in_schema=False, prefix="/media")
|
||||||
|
|
||||||
@router.get("/files")
|
@router.get("/files")
|
||||||
def get_mediafiles(db: SessionDep, request: Request, msg: str = None):
|
def get_mediafiles(db: SessionDep, request: Request, msg: str | None = None):
|
||||||
params = request.query_params
|
params = request.query_params
|
||||||
query = params.get("query")
|
query = params.get("query")
|
||||||
filter = {}
|
filter = {}
|
||||||
@@ -40,6 +41,7 @@ def get_mediafiles(db: SessionDep, request: Request, msg: str = None):
|
|||||||
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": mediafiles})
|
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": mediafiles})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
logger.info("User is not authorized, no data shown")
|
||||||
msg = "Nicht berechtigt!!"
|
msg = "Nicht berechtigt!!"
|
||||||
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": []})
|
return templates.TemplateResponse("media/files.html", {"request": request, "msg": msg, "mediafiles": []})
|
||||||
|
|
||||||
@@ -48,13 +50,8 @@ def file_details(file_id: AnyStr, request: Request, db: SessionDep):
|
|||||||
mediafile = db.get(MediaFile, file_id)
|
mediafile = db.get(MediaFile, file_id)
|
||||||
return templates.TemplateResponse("media/file_detail.html", {"request": request, "mediafile":mediafile})
|
return templates.TemplateResponse("media/file_detail.html", {"request": request, "mediafile":mediafile})
|
||||||
|
|
||||||
@router.get("/actors")
|
@router.get("files/edit/{file_id}")
|
||||||
def get_actors(db: SessionDep, request: Request, msg: str = None):
|
def edit_file(db: SessionDep, request: Request, file_id: str):
|
||||||
actors = db.query(MediaActor).all()
|
media_file = db.get(MediaFile, file_id)
|
||||||
return templates.TemplateResponse("media/actors.html", {"request": request, "msg": msg, "actors": actors})
|
return templates.TemplateResponse("media/file_detail.html", {"request": request, "mediafile":media_file})
|
||||||
|
|
||||||
@router.get("/actors/{actor_id}")
|
|
||||||
def artist_detail(actor_id: AnyStr, request: Request, db: SessionDep):
|
|
||||||
actor = db.get(MediaActor, actor_id)
|
|
||||||
return templates.TemplateResponse("media/actor_detail.html", {"request": request, "actor": actor})
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ class BaseMixin:
|
|||||||
|
|
||||||
|
|
||||||
class BaseVideoMixin:
|
class BaseVideoMixin:
|
||||||
cloud_link = Column(String)
|
cloud_link = Column(String, nullable=True)
|
||||||
file_name = Column(String)
|
file_name = Column(String, nullable=True)
|
||||||
path = Column(String)
|
path = Column(String)
|
||||||
review = Column(Boolean)
|
review = Column(Boolean)
|
||||||
title = Column(String)
|
title = Column(String)
|
||||||
url = Column(String, unique=True)
|
url = Column(String, nullable=True)
|
||||||
should_download = Column(Boolean)
|
should_download = Column(Boolean)
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ def __parse_output__(lines_list: list[str]) -> str | None:
|
|||||||
|
|
||||||
def is_file_downloaded(media_file: dict, dir: Path) -> FileStatus:
|
def is_file_downloaded(media_file: dict, dir: Path) -> FileStatus:
|
||||||
file_name_as_title = f"{media_file['file_name']}"
|
file_name_as_title = f"{media_file['file_name']}"
|
||||||
|
if not file_name_as_title:
|
||||||
|
log.info("title has not been set - start download")
|
||||||
|
return FileStatus.UNKNOWN
|
||||||
file_title = Path(dir, f"{file_name_as_title}.mp4")
|
file_title = Path(dir, f"{file_name_as_title}.mp4")
|
||||||
if file_title.exists():
|
if file_title.exists():
|
||||||
log.info(f"{file_name_as_title} has been downloaded")
|
log.info(f"{file_name_as_title} has been downloaded")
|
||||||
|
|||||||
@@ -3,25 +3,46 @@ download files with URLs from DB
|
|||||||
"""
|
"""
|
||||||
import logging.config
|
import logging.config
|
||||||
import requests
|
import requests
|
||||||
import yaml
|
import re
|
||||||
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from platformdirs import PlatformDirs
|
|
||||||
|
|
||||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument('--verbose', '-v', action='count', default=0)
|
parser.add_argument('--verbose', '-v', action='count', default=0)
|
||||||
parser.add_argument('--config', '-c', default='kontor-docker')
|
parser.add_argument('--all', '-a', action='store_true')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
def get_logger(level: int, config: str):
|
def get_logger(level: int) -> logging.Logger:
|
||||||
dirs = PlatformDirs(config)
|
logging.config.dictConfig({
|
||||||
logging_config = Path(dirs.user_config_dir, 'logging-config.yaml')
|
'version': 1,
|
||||||
with open(logging_config, 'rt') as f:
|
'disable_existing_loggers': False,
|
||||||
configDict = yaml.safe_load(f.read())
|
'formatters': {
|
||||||
logging.config.dictConfig(configDict)
|
'simple': {
|
||||||
logger = logging.getLogger('development')
|
'format': '[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s',
|
||||||
|
'datefmt': '%Y-%m-%d %H:%M:%S',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'handlers': {
|
||||||
|
'console': {
|
||||||
|
'class': logging.StreamHandler,
|
||||||
|
'level': logging.DEBUG,
|
||||||
|
'formatter': 'simple',
|
||||||
|
'stream': 'ext://sys.stdout'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'loggers': {
|
||||||
|
'urllib3.connectionpool': {
|
||||||
|
'level': 'WARNING',
|
||||||
|
'propagate': False,
|
||||||
|
},
|
||||||
|
'root': {
|
||||||
|
'level': 'DEBUG',
|
||||||
|
'handlers': ['console'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
logger = logging.getLogger(__file__)
|
||||||
if level is not None:
|
if level is not None:
|
||||||
match level:
|
match level:
|
||||||
case 0:
|
case 0:
|
||||||
@@ -32,35 +53,78 @@ def get_logger(level: int, config: str):
|
|||||||
logger.setLevel(logging.CRITICAL)
|
logger.setLevel(logging.CRITICAL)
|
||||||
return logger
|
return logger
|
||||||
|
|
||||||
|
def update_file(log: logging.Logger, media_file):
|
||||||
|
update = requests.put(f"http://127.0.0.1:8800/api/media/files/{media_file['id']}", json=media_file)
|
||||||
|
log.info(f"update status: {update.status_code}")
|
||||||
|
log.info(f"update result: {update.json()}")
|
||||||
|
|
||||||
|
def get_actor_links(log: logging.Logger, media_file_url: str) -> list:
|
||||||
|
try:
|
||||||
|
r = requests.get(media_file_url)
|
||||||
|
soup = BeautifulSoup(r.content, "html.parser")
|
||||||
|
error404 = soup.css.select_one('.error404-title')
|
||||||
|
if error404 and error404.get_text() == "Video nicht gefunden":
|
||||||
|
log.info(f"{error404.get_text()}")
|
||||||
|
item['url'] = None
|
||||||
|
item['review'] = False
|
||||||
|
update_file(log, item)
|
||||||
|
return []
|
||||||
|
anchors = soup.find_all('a', attrs={'href': re.compile("^https://.*pornstars/.*")})
|
||||||
|
actor_links = []
|
||||||
|
for anchor in anchors:
|
||||||
|
link_url = anchor.get('href')
|
||||||
|
if link_url.endswith('all/countries'):
|
||||||
|
continue
|
||||||
|
actor_links.append(link_url)
|
||||||
|
log.info(f"links({len(actor_links)}): {actor_links}")
|
||||||
|
return actor_links
|
||||||
|
except Exception as error:
|
||||||
|
log.info(f"something went wrong: {error}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
log = get_logger(args.verbose, args.config)
|
log = get_logger(args.verbose)
|
||||||
log.info('kontor.update_titles started')
|
log.info('kontor.find_links started')
|
||||||
response = requests.get("http://127.0.0.1:8800/api/media/files?review=true")
|
log.info('get all actors')
|
||||||
|
response = requests.get("http://127.0.0.1:8800/api/media/actors")
|
||||||
|
data = response.json()
|
||||||
|
actors = {}
|
||||||
|
for item in data:
|
||||||
|
actor = {}
|
||||||
|
actor['id'] = item['id']
|
||||||
|
actor['name'] = item['name']
|
||||||
|
actor['url'] = item['url']
|
||||||
|
actors[item['url']] = actor
|
||||||
|
log.debug(f'all actors: {actors}')
|
||||||
|
files_url = ""
|
||||||
|
if args.all:
|
||||||
|
files_url= "http://127.0.0.1:8800/api/media/files"
|
||||||
|
else:
|
||||||
|
files_url = "http://127.0.0.1:8800/api/media/files?review=true"
|
||||||
|
response = requests.get(files_url)
|
||||||
log.info(f"Status: {response.status_code}")
|
log.info(f"Status: {response.status_code}")
|
||||||
data = response.json()
|
data = response.json()
|
||||||
log.info(f"data: {len(data)}")
|
log.info(f"data: {len(data)}")
|
||||||
for item in data:
|
for item in data:
|
||||||
link = item['url']
|
link = item['url']
|
||||||
|
if not link:
|
||||||
|
continue
|
||||||
|
if str(link) == "None":
|
||||||
|
continue
|
||||||
log.info(f"{item['id']} - {str(link)}")
|
log.info(f"{item['id']} - {str(link)}")
|
||||||
try:
|
actor_links = get_actor_links(log, link)
|
||||||
r = requests.get(link)
|
actor_list = []
|
||||||
soup = BeautifulSoup(r.content, "html.parser")
|
for actor_link in actor_links:
|
||||||
title = soup.title.string
|
if actor_link in actors:
|
||||||
anchors = soup.find_all('a')
|
log.info(f"found actor with id: {actors[actor_link]['id']}")
|
||||||
for anchor in anchors:
|
actor_list.append(actors[actor_link])
|
||||||
if anchor.has_attr('href'):
|
actor_response = requests.put(f"http://127.0.0.1:8800/api/media/files/{item['id']}/actors", json=actor_list)
|
||||||
link_url = anchor['href']
|
actor_data = actor_response.json()
|
||||||
if link_url and link_url.__contains__('pornstars/'):
|
log.info(f"found {len(actor_data)} actors")
|
||||||
log.info(link_url)
|
log.info(f"found actors: {actor_data}")
|
||||||
item['title'] = title
|
|
||||||
item['review'] = False
|
item['review'] = False
|
||||||
except Exception as error:
|
update = requests.put(f"http://127.0.0.1:8800/api/media/files/{item['id']}", json=item)
|
||||||
log.info(f"something went wrong: {error} {anchor}")
|
log.info(f"update status: {update.status_code}")
|
||||||
item['title'] = None
|
log.info(f"update result: {update.json()}")
|
||||||
item['review'] = True
|
log.info('kontor.find_links finished')
|
||||||
#update = requests.put(f"http://127.0.0.1:8800/api/media/files/{item['id']}", json=item)
|
|
||||||
#log.info(f"update status: {update.status_code}")
|
|
||||||
#log.info(f"update result: {update.json()}")
|
|
||||||
log.info('kontor.update_titles finished')
|
|
||||||
|
|
||||||
|
|||||||
@@ -43,9 +43,14 @@ if __name__ == '__main__':
|
|||||||
for item in data:
|
for item in data:
|
||||||
link = item['url']
|
link = item['url']
|
||||||
log.info(f"{item['id']} - {str(link)}")
|
log.info(f"{item['id']} - {str(link)}")
|
||||||
|
if not link:
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
r = requests.get(link)
|
r = requests.get(link)
|
||||||
soup = BeautifulSoup(r.content, "html.parser")
|
soup = BeautifulSoup(r.content, "html.parser")
|
||||||
|
title_tag = soup.find('title')
|
||||||
|
if title_tag:
|
||||||
|
title= title_tag.get_text()
|
||||||
title = soup.title.string
|
title = soup.title.string
|
||||||
item['title'] = title
|
item['title'] = title
|
||||||
item['review'] = False
|
item['review'] = False
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import java.util.List;
|
|||||||
@Setter
|
@Setter
|
||||||
@EqualsAndHashCode(callSuper = false)
|
@EqualsAndHashCode(callSuper = false)
|
||||||
@Entity
|
@Entity
|
||||||
@Table(uniqueConstraints = { @UniqueConstraint(columnNames = { "url" }) })
|
|
||||||
public class MediaFile extends AbstractEntity {
|
public class MediaFile extends AbstractEntity {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|||||||
+32
-29
@@ -1,40 +1,43 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, inject } from 'vue'
|
|
||||||
import { RouterView } from 'vue-router'
|
import { RouterView } from 'vue-router'
|
||||||
import HelloWorld from './components/HelloWorld.vue'
|
import MainHeader from '@/components/MainHeader.vue'
|
||||||
import BSTooltip from '@/components/BSTooltip.vue'
|
import MainNavbar from '@/components/MainNavbar.vue'
|
||||||
import BSNavbar from '@/components/BSNavbar.vue'
|
|
||||||
|
|
||||||
const bootstrap = inject('bootstrap')
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
try {
|
|
||||||
// Expanding the navbar.
|
|
||||||
const collapse = new bootstrap.Collapse('#navbarSupportedContent')
|
|
||||||
collapse.show()
|
|
||||||
// Tooltips are opt-in and must be initialized manually!
|
|
||||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
|
|
||||||
const tooltipList = [...tooltipTriggerList].map(
|
|
||||||
(tooltipTriggerEl) => new bootstrap.Tooltip(tooltipTriggerEl),
|
|
||||||
)
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Bootstrap error: ', e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="wrapper">
|
<header>
|
||||||
<BSNavbar />
|
<MainHeader headerTitle="Kontor"/>
|
||||||
<BSTooltip />
|
<MainNavbar />
|
||||||
</div>
|
</header>
|
||||||
<HelloWorld msg="It works" />
|
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
#wrapper {
|
@media (min-width: 1024px) {
|
||||||
max-width: 600px;
|
header {
|
||||||
margin: 0 auto;
|
display: inline;
|
||||||
|
place-items: center;
|
||||||
|
padding-right: calc(var(--section-gap) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
margin: 0 2rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
header .wrapper {
|
||||||
|
display: flex;
|
||||||
|
place-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
text-align: left;
|
||||||
|
margin-left: -1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
padding: 1rem 0;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* color palette from <https://github.com/vuejs/theme> */
|
/* color palette from <https://github.com/vuejs/theme> */
|
||||||
:root {
|
/* :root {
|
||||||
--vt-c-white: #ffffff;
|
--vt-c-white: #ffffff;
|
||||||
--vt-c-white-soft: #f8f8f8;
|
--vt-c-white-soft: #f8f8f8;
|
||||||
--vt-c-white-mute: #f2f2f2;
|
--vt-c-white-mute: #f2f2f2;
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||||
--vt-c-text-dark-1: var(--vt-c-white);
|
--vt-c-text-dark-1: var(--vt-c-white);
|
||||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||||
}
|
} */
|
||||||
|
|
||||||
/* semantic color variables for this project */
|
/* semantic color variables for this project */
|
||||||
:root {
|
:root {
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
--section-gap: 160px;
|
--section-gap: 160px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
/* @media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root {
|
||||||
--color-background: var(--vt-c-black);
|
--color-background: var(--vt-c-black);
|
||||||
--color-background-soft: var(--vt-c-black-soft);
|
--color-background-soft: var(--vt-c-black-soft);
|
||||||
@@ -48,39 +48,4 @@
|
|||||||
--color-heading: var(--vt-c-text-dark-1);
|
--color-heading: var(--vt-c-text-dark-1);
|
||||||
--color-text: var(--vt-c-text-dark-2);
|
--color-text: var(--vt-c-text-dark-2);
|
||||||
}
|
}
|
||||||
}
|
} */
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
color: var(--color-text);
|
|
||||||
background: var(--color-background);
|
|
||||||
transition:
|
|
||||||
color 0.5s,
|
|
||||||
background-color 0.5s;
|
|
||||||
line-height: 1.6;
|
|
||||||
font-family:
|
|
||||||
Inter,
|
|
||||||
-apple-system,
|
|
||||||
BlinkMacSystemFont,
|
|
||||||
'Segoe UI',
|
|
||||||
Roboto,
|
|
||||||
Oxygen,
|
|
||||||
Ubuntu,
|
|
||||||
Cantarell,
|
|
||||||
'Fira Sans',
|
|
||||||
'Droid Sans',
|
|
||||||
'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
font-size: 15px;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,209 @@
|
|||||||
@import 'bootstrap/dist/css/bootstrap.css';
|
@import 'bootstrap/dist/css/bootstrap.css';
|
||||||
|
|
||||||
:root {
|
* {
|
||||||
--bs-tertiary-bg-rgb: 101, 127, 220;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: Arial;
|
||||||
|
padding: 10px;
|
||||||
|
background: lightgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header/Blog Title */
|
||||||
|
.header {
|
||||||
|
padding: 30px;
|
||||||
|
text-align: center;
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
font-size: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the top navigation bar */
|
||||||
|
.topnav {
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: darkgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Style the topnav links */
|
||||||
|
.topnav a {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
color: #f2f2f2;
|
||||||
|
text-align: center;
|
||||||
|
padding: 14px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change color on hover */
|
||||||
|
.topnav a:hover {
|
||||||
|
background-color: #ddd;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnav {
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subnav a {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
color: #f2f2f2;
|
||||||
|
text-align: center;
|
||||||
|
padding: 14px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inline {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inline label {
|
||||||
|
margin: 5px 10px 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inline input {
|
||||||
|
padding-right: 50px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inline button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: dodgerblue;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-inline button:hover {
|
||||||
|
background-color: royalblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill-nav a {
|
||||||
|
display: inline-block;
|
||||||
|
color: white;
|
||||||
|
background-color: dodgerblue;
|
||||||
|
text-align: center;
|
||||||
|
padding: 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Change the color of links on mouse-over */
|
||||||
|
.pill-nav a:hover {
|
||||||
|
background-color: royalblue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a color to the active/current link */
|
||||||
|
.pill-nav a.active {
|
||||||
|
background-color: royalblue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.maincolumn {
|
||||||
|
float:left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
/* Create two unequal columns that floats next to each other */
|
||||||
|
/* Left column */
|
||||||
|
.leftcolumn {
|
||||||
|
float: left;
|
||||||
|
width: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right column */
|
||||||
|
.rightcolumn {
|
||||||
|
float: left;
|
||||||
|
width: 25%;
|
||||||
|
background-color: #f1f1f1;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fake image */
|
||||||
|
.fakeimg {
|
||||||
|
background-color: #aaa;
|
||||||
|
width: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Add a card effect for articles */
|
||||||
|
.card {
|
||||||
|
background-color: white;
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clear floats after the columns */
|
||||||
|
.row::after {
|
||||||
|
content: "";
|
||||||
|
display: table;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Footer */
|
||||||
|
.footer {
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
background: #ddd;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive layout - when the screen is less than 800px wide, make the two columns stack on top of each other instead of next to each other */
|
||||||
|
@media screen and (max-width: 800px) {
|
||||||
|
.leftcolumn, .rightcolumn {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive layout - when the screen is less than 400px wide, make the navigation links stack on top of each other instead of next to each other */
|
||||||
|
@media screen and (max-width: 400px) {
|
||||||
|
.topnav a {
|
||||||
|
float: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #app {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2rem;
|
||||||
|
font-weight: normal;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* a,
|
||||||
|
.green {
|
||||||
|
text-decoration: none;
|
||||||
|
color: hsla(160, 100%, 37%, 1);
|
||||||
|
transition: 0.4s;
|
||||||
|
padding: 3px;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* @media (hover: hover) {
|
||||||
|
a:hover {
|
||||||
|
background-color: hsla(160, 100%, 37%, 0.2);
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* @media (min-width: 1024px) {
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
padding: 0 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
} */
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
defineProps<{
|
|
||||||
msg: string
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="greetings">
|
|
||||||
<h1 class="green">{{ msg }}</h1>
|
|
||||||
<h3>
|
|
||||||
You’ve successfully created a project with
|
|
||||||
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
h1 {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 2.6rem;
|
|
||||||
position: relative;
|
|
||||||
top: -10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.greetings h1,
|
|
||||||
.greetings h3 {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
defineProps<{
|
||||||
|
headerTitle: string
|
||||||
|
}>()
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="header">
|
||||||
|
<h2>{{ headerTitle }}</h2>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterLink } from 'vue-router'
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="topnav">
|
||||||
|
<RouterLink to="/">Kontor</RouterLink>
|
||||||
|
<RouterLink to="/comics">Comics</RouterLink>
|
||||||
|
<RouterLink to="/tysc">TYSC</RouterLink>
|
||||||
|
<RouterLink to="/media">Media</RouterLink>
|
||||||
|
<RouterLink style="float: right;" to="/login">Login</RouterLink>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import WelcomeItem from './WelcomeItem.vue'
|
|
||||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
|
||||||
import ToolingIcon from './icons/IconTooling.vue'
|
|
||||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
|
||||||
import CommunityIcon from './icons/IconCommunity.vue'
|
|
||||||
import SupportIcon from './icons/IconSupport.vue'
|
|
||||||
|
|
||||||
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<DocumentationIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Documentation</template>
|
|
||||||
|
|
||||||
Vue’s
|
|
||||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
|
||||||
provides you with all information you need to get started.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<ToolingIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Tooling</template>
|
|
||||||
|
|
||||||
This project is served and bundled with
|
|
||||||
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
|
||||||
recommended IDE setup is
|
|
||||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
|
|
||||||
+
|
|
||||||
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>. If
|
|
||||||
you need to test your components and web pages, check out
|
|
||||||
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
|
|
||||||
and
|
|
||||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
|
||||||
/
|
|
||||||
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
|
|
||||||
|
|
||||||
<br />
|
|
||||||
|
|
||||||
More instructions are available in
|
|
||||||
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
|
|
||||||
>.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<EcosystemIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Ecosystem</template>
|
|
||||||
|
|
||||||
Get official tools and libraries for your project:
|
|
||||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
|
||||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
|
||||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
|
||||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
|
||||||
you need more resources, we suggest paying
|
|
||||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
|
||||||
a visit.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<CommunityIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Community</template>
|
|
||||||
|
|
||||||
Got stuck? Ask your question on
|
|
||||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
|
|
||||||
(our official Discord server), or
|
|
||||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
|
||||||
>StackOverflow</a
|
|
||||||
>. You should also follow the official
|
|
||||||
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
|
||||||
Bluesky account or the
|
|
||||||
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
|
||||||
X account for latest news in the Vue world.
|
|
||||||
</WelcomeItem>
|
|
||||||
|
|
||||||
<WelcomeItem>
|
|
||||||
<template #icon>
|
|
||||||
<SupportIcon />
|
|
||||||
</template>
|
|
||||||
<template #heading>Support Vue</template>
|
|
||||||
|
|
||||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
|
||||||
us by
|
|
||||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
|
||||||
</WelcomeItem>
|
|
||||||
</template>
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="item">
|
|
||||||
<i>
|
|
||||||
<slot name="icon"></slot>
|
|
||||||
</i>
|
|
||||||
<div class="details">
|
|
||||||
<h3>
|
|
||||||
<slot name="heading"></slot>
|
|
||||||
</h3>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.item {
|
|
||||||
margin-top: 2rem;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details {
|
|
||||||
flex: 1;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
display: flex;
|
|
||||||
place-items: center;
|
|
||||||
place-content: center;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
|
|
||||||
color: var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 500;
|
|
||||||
margin-bottom: 0.4rem;
|
|
||||||
color: var(--color-heading);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.item {
|
|
||||||
margin-top: 0;
|
|
||||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
i {
|
|
||||||
top: calc(50% - 25px);
|
|
||||||
left: -26px;
|
|
||||||
position: absolute;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
background: var(--color-background);
|
|
||||||
border-radius: 8px;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:before {
|
|
||||||
content: ' ';
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
bottom: calc(50% + 25px);
|
|
||||||
height: calc(50% - 25px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:after {
|
|
||||||
content: ' ';
|
|
||||||
border-left: 1px solid var(--color-border);
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: calc(50% + 25px);
|
|
||||||
height: calc(50% - 25px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:first-of-type:before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item:last-of-type:after {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest'
|
|
||||||
|
|
||||||
import { mount } from '@vue/test-utils'
|
|
||||||
import HelloWorld from '../HelloWorld.vue'
|
|
||||||
|
|
||||||
describe('HelloWorld', () => {
|
|
||||||
it('renders properly', () => {
|
|
||||||
const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } })
|
|
||||||
expect(wrapper.text()).toContain('Hello Vitest')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
|
|
||||||
|
import { mount } from '@vue/test-utils'
|
||||||
|
import MainHeader from '../MainHeader.vue'
|
||||||
|
|
||||||
|
describe('MainHeader', () => {
|
||||||
|
it('renders properly', () => {
|
||||||
|
const wrapper = mount(MainHeader, { props: { headerTitle: 'Kontor' } })
|
||||||
|
expect(wrapper.text()).toContain('Kontor')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M11.447 8.894a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm0 1.789a1 1 0 1 0 .894-1.789l-.894 1.789zM7.447 7.106a1 1 0 1 0-.894 1.789l.894-1.789zM10 9a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0H8zm9.447-5.606a1 1 0 1 0-.894-1.789l.894 1.789zm-2.894-.789a1 1 0 1 0 .894 1.789l-.894-1.789zm2 .789a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zM18 5a1 1 0 1 0-2 0h2zm-2 2.5a1 1 0 1 0 2 0h-2zm-5.447-4.606a1 1 0 1 0 .894-1.789l-.894 1.789zM9 1l.447-.894a1 1 0 0 0-.894 0L9 1zm-2.447.106a1 1 0 1 0 .894 1.789l-.894-1.789zm-6 3a1 1 0 1 0 .894 1.789L.553 4.106zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zm-2-.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 2.789a1 1 0 1 0 .894-1.789l-.894 1.789zM2 5a1 1 0 1 0-2 0h2zM0 7.5a1 1 0 1 0 2 0H0zm8.553 12.394a1 1 0 1 0 .894-1.789l-.894 1.789zm-1.106-2.789a1 1 0 1 0-.894 1.789l.894-1.789zm1.106 1a1 1 0 1 0 .894 1.789l-.894-1.789zm2.894.789a1 1 0 1 0-.894-1.789l.894 1.789zM8 19a1 1 0 1 0 2 0H8zm2-2.5a1 1 0 1 0-2 0h2zm-7.447.394a1 1 0 1 0 .894-1.789l-.894 1.789zM1 15H0a1 1 0 0 0 .553.894L1 15zm1-2.5a1 1 0 1 0-2 0h2zm12.553 2.606a1 1 0 1 0 .894 1.789l-.894-1.789zM17 15l.447.894A1 1 0 0 0 18 15h-1zm1-2.5a1 1 0 1 0-2 0h2zm-7.447-5.394l-2 1 .894 1.789 2-1-.894-1.789zm-1.106 1l-2-1-.894 1.789 2 1 .894-1.789zM8 9v2.5h2V9H8zm8.553-4.894l-2 1 .894 1.789 2-1-.894-1.789zm.894 0l-2-1-.894 1.789 2 1 .894-1.789zM16 5v2.5h2V5h-2zm-4.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zm-2.894-1l-2 1 .894 1.789 2-1L8.553.106zM1.447 5.894l2-1-.894-1.789-2 1 .894 1.789zm-.894 0l2 1 .894-1.789-2-1-.894 1.789zM0 5v2.5h2V5H0zm9.447 13.106l-2-1-.894 1.789 2 1 .894-1.789zm0 1.789l2-1-.894-1.789-2 1 .894 1.789zM10 19v-2.5H8V19h2zm-6.553-3.894l-2-1-.894 1.789 2 1 .894-1.789zM2 15v-2.5H0V15h2zm13.447 1.894l2-1-.894-1.789-2 1 .894 1.789zM18 15v-2.5h-2V15h2z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
<template>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
|
||||||
<path
|
|
||||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
|
||||||
<template>
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
aria-hidden="true"
|
|
||||||
role="img"
|
|
||||||
class="iconify iconify--mdi"
|
|
||||||
width="24"
|
|
||||||
height="24"
|
|
||||||
preserveAspectRatio="xMidYMid meet"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
|
||||||
fill="currentColor"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import HomeView from '../views/HomeView.vue'
|
import HomeView from '../views/HomeView.vue'
|
||||||
|
import ComicsView from '@/views/ComicsView.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
@@ -10,12 +11,14 @@ const router = createRouter({
|
|||||||
component: HomeView,
|
component: HomeView,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: '/comics',
|
||||||
name: 'about',
|
name: 'comics',
|
||||||
// route level code-splitting
|
component: ComicsView,
|
||||||
// this generates a separate chunk (About.[hash].js) for this route
|
},
|
||||||
// which is lazy-loaded when the route is visited.
|
{
|
||||||
component: () => import('../views/AboutView.vue'),
|
path: '/comic/artists',
|
||||||
|
name: 'artists',
|
||||||
|
component: () => import('@/views/ComicsView.vue')
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="about">
|
|
||||||
<h1>This is an about page</h1>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.about {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="subnav">
|
||||||
|
<a href="/comic/artists/">Artists</a>
|
||||||
|
<a href="/comic/publishers/">Publishers</a>
|
||||||
|
<a href="/comic/worktypes">WorkTypes</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div> list of comics</div>
|
||||||
|
</template>
|
||||||
|
<style lang="css" scoped>
|
||||||
|
</style>
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import TheWelcome from '../components/TheWelcome.vue'
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<TheWelcome />
|
<p>Kontor Application</p>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user