mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 18:43:26 +00:00
Replace existing code in src/opencdeserver with code from the "Kontroll OpenCDE API:s server" POC project on https://github.com/marwiss/Kontroll
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
FROM python:3.10-bullseye AS base
|
||||
RUN apt-get update && apt-get install -y \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
COPY ./app/ifcopenshell/ifcopenshell.zip /ifcopenshell.zip
|
||||
CMD ["unzip", "/ifcopenshell.zip", "-d", "/usr/local/lib/python3.10/site-packages/"]
|
||||
WORKDIR /code/app
|
||||
COPY ./app/requirements.txt /code/requirements.txt
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
RUN pip install neo4j==5.9.0 uvicorn ifcopenshell Jinja2 python-multipart bcrypt --upgrade
|
||||
# pip install: Install packages.
|
||||
# --no-cache-dir: You can shrink the image size by disabling the cache.
|
||||
# --upgrade: Upgrade packages.
|
||||
# -r: use requirements file.
|
||||
COPY ./app /code/app
|
||||
|
||||
FROM base AS server
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers", \
|
||||
"--ssl-keyfile", "/etc/letsencrypt/live/kontroll.digital/privkey.pem", \
|
||||
"--ssl-certfile", "/etc/letsencrypt/live/kontroll.digital/fullchain.pem"]
|
||||
# If running behind a proxy like Nginx or Traefik add --proxy-headers.
|
||||
# --ssl-keyfile: Path to keyfile.
|
||||
# --ssl-certificat: Path to certificate.
|
||||
@@ -0,0 +1,566 @@
|
||||
from uuid import UUID
|
||||
from fastapi import APIRouter, Depends, UploadFile, HTTPException
|
||||
from fastapi.responses import FileResponse
|
||||
|
||||
from security.secure import get_current_active_user
|
||||
|
||||
from models.bcf_request import *
|
||||
from models.bcf_response import *
|
||||
from models.request import *
|
||||
from models.other import *
|
||||
|
||||
from repository.bcf import bcf_db
|
||||
|
||||
from api.logging import LoggingRoute
|
||||
|
||||
|
||||
router = APIRouter(route_class=LoggingRoute)
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# BCF API (endpoints below only applies to BCF API versions 2.1 and 1.0)
|
||||
|
||||
|
||||
# @router.get("/bcf/versions")
|
||||
# def versions_get():
|
||||
# return {"What": "Returns a list of all supported BCF API versions of the server."}
|
||||
#
|
||||
#
|
||||
# @router.get("/bcf/3.0/auth")
|
||||
# def auth_get(version: str):
|
||||
# return {"What": "Obtaining Authentication Information."}
|
||||
#
|
||||
#
|
||||
# @router.get("/bcf/3.0/current-user")
|
||||
# def current_user_get(version: str):
|
||||
# return {"What": "Get current user."}
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Projects
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects", tags=["projects_get"])
|
||||
def projects_get(current_user: User = Depends(get_current_active_user)) -> List[ProjectGET]:
|
||||
projects_response = bcf_db.get_projects(current_user)
|
||||
bcf_db.debug(endpoint='projects_get',
|
||||
request={},
|
||||
response={count: value.dict() for count, value in enumerate(projects_response)})
|
||||
return projects_response
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}", tags=["project_get"])
|
||||
def project_get(project_id: UUID, current_user: User = Depends(get_current_active_user)) -> ProjectGET:
|
||||
project_response = bcf_db.get_project(project_id, current_user)
|
||||
bcf_db.debug(endpoint='project_get',
|
||||
request={'project_id': project_id},
|
||||
response=project_response.dict())
|
||||
return project_response
|
||||
|
||||
|
||||
@router.put("/bcf/3.0/projects/{project_id}", tags=["project_put"], status_code=200)
|
||||
def project_put(project_id: UUID, project_request: ProjectPUT,
|
||||
current_user: User = Depends(get_current_active_user)) -> ProjectGET:
|
||||
project_response = bcf_db.put_project(project_id, project_request, current_user)
|
||||
bcf_db.debug(endpoint='project_put',
|
||||
request={'project_id': project_id, 'project_request': project_request},
|
||||
response=project_response.dict())
|
||||
return project_response
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/extensions", tags=["project_extensions_get"])
|
||||
def project_extensions_get(project_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> ExtensionsGET:
|
||||
extensions_response = bcf_db.get_project_extensions(project_id, current_user)
|
||||
bcf_db.debug(endpoint='project_extensions_get',
|
||||
request={'project_id': project_id},
|
||||
response=extensions_response.dict())
|
||||
return extensions_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Topics
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics", tags=["topics_get"])
|
||||
def topics_get(project_id: str,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[TopicGET]:
|
||||
topics_response = bcf_db.get_topics(project_id, current_user)
|
||||
bcf_db.debug(endpoint='topics_get',
|
||||
request={'project_id': project_id},
|
||||
response={count: value.dict() for count, value in enumerate(topics_response)})
|
||||
return topics_response
|
||||
|
||||
|
||||
@router.post("/bcf/3.0/projects/{project_id}/topics", tags=["topic_post"], status_code=201)
|
||||
def topic_post(project_id: UUID, topic_request: TopicPOST,
|
||||
current_user: User = Depends(get_current_active_user)) -> TopicGET:
|
||||
topic_response = bcf_db.post_topic(project_id, topic_request, current_user)
|
||||
if topic_response is None:
|
||||
raise HTTPException(status_code=400, detail="Could not create topic.")
|
||||
bcf_db.debug(endpoint='topic_post',
|
||||
request={'project_id': project_id, 'topic_request': topic_request.dict()},
|
||||
response=topic_response.dict())
|
||||
return topic_response
|
||||
|
||||
|
||||
# Implemented
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}", tags=["topic_get"])
|
||||
def topic_get(project_id: UUID, topic_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> TopicGET:
|
||||
topic_response = bcf_db.get_topic(project_id, topic_id, current_user)
|
||||
if topic_response is None:
|
||||
raise HTTPException(status_code=404, detail="Item not found.")
|
||||
bcf_db.debug(endpoint='topic_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id},
|
||||
response=topic_response.dict())
|
||||
return topic_response
|
||||
|
||||
|
||||
# Implemented
|
||||
@router.put("/bcf/3.0/projects/{project_id}/topics/{topic_id}", tags=["topic_put"], status_code=200)
|
||||
def topic_put(project_id: UUID, topic_id: UUID, topic_request: TopicPUT,
|
||||
current_user: User = Depends(get_current_active_user)) -> TopicGET:
|
||||
topic_response = bcf_db.put_topic(project_id, topic_id, topic_request, current_user)
|
||||
bcf_db.debug(endpoint='topic_put',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'topic_request': topic_request.dict()},
|
||||
response=topic_response.dict())
|
||||
return topic_response
|
||||
|
||||
|
||||
# Implemented
|
||||
@router.delete("/bcf/3.0/projects/{project_id}/topics/{topic_id}", tags=["topic_delete"], status_code=200)
|
||||
def topic_delete(project_id: UUID, topic_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> int:
|
||||
topic_response = bcf_db.delete_topic(project_id, topic_id, current_user)
|
||||
if topic_response is None:
|
||||
raise HTTPException(status_code=404, detail="Item not found.")
|
||||
bcf_db.debug(endpoint='topic_delete',
|
||||
request={'project_id': project_id, 'topic_id': topic_id},
|
||||
response={topic_response})
|
||||
return topic_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# BIM snippets (not used by any client)
|
||||
|
||||
|
||||
# Implemented
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/snippet", tags=["bim_snippet_get"])
|
||||
def bim_snippet_get(project_id: UUID, topic_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> BimSnippet:
|
||||
bim_snippet_response = bcf_db.get_bim_snippet(project_id, topic_id, current_user)
|
||||
if bim_snippet_response is None:
|
||||
raise HTTPException(status_code=404, detail="Item not found.")
|
||||
bcf_db.debug(endpoint='bim_snippet_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id},
|
||||
response=bim_snippet_response.dict())
|
||||
return bim_snippet_response
|
||||
|
||||
|
||||
# Implemented
|
||||
@router.put("/bcf/3.0/projects/{project_id}/topics/{topic_id}/snippet", tags=["bim_snippet_put"], status_code=200)
|
||||
def bim_snippet_put(project_id: UUID, topic_id: UUID, snippet: BimSnippet,
|
||||
current_user: User = Depends(get_current_active_user)) -> BimSnippet:
|
||||
bim_snippet_response = bcf_db.put_bim_snippet(project_id, topic_id, snippet, current_user)
|
||||
bcf_db.debug(endpoint='bim_snippet_put',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'snippet': snippet.dict()},
|
||||
response=bim_snippet_response.dict())
|
||||
return bim_snippet_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Files
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/files_information", tags=["files_information_get"])
|
||||
def files_information_get(project_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[ProjectFileInformation]:
|
||||
files_information_response = bcf_db.get_files_information(project_id, current_user)
|
||||
bcf_db.debug(endpoint='files_information_get',
|
||||
request={'project_id': project_id},
|
||||
response={count: value.dict() for count, value in enumerate(files_information_response)})
|
||||
return files_information_response
|
||||
|
||||
|
||||
# Implemented
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/files", tags=["files_get"])
|
||||
def files_get(project_id: UUID, topic_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[FileGET]:
|
||||
files_response = bcf_db.get_files(project_id, topic_id, current_user)
|
||||
bcf_db.debug(endpoint='files_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id},
|
||||
response={count: value.dict() for count, value in enumerate(files_response)})
|
||||
return files_response
|
||||
|
||||
|
||||
# request body file = FilePUT
|
||||
@router.put("/bcf/3.0/projects/{project_id}/topics/{topic_id}/files", tags=["files_put"], status_code=200)
|
||||
def files_put(project_id: UUID, topic_id: UUID, files: List[FilePUT],
|
||||
current_user: User = Depends(get_current_active_user)) -> List[FileGET]:
|
||||
files_response = bcf_db.put_files(project_id, topic_id, files, current_user)
|
||||
bcf_db.debug(endpoint='files_put',
|
||||
request={'project_id': project_id,
|
||||
'topic_id': topic_id,
|
||||
'files': {count: value.dict() for count, value in enumerate(files)}},
|
||||
response={count: value.dict() for count, value in enumerate(files_response)})
|
||||
return files_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Comments
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/comments", tags=["comments_get"])
|
||||
def comments_get(project_id: UUID, topic_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[CommentGET]:
|
||||
comments_response = bcf_db.get_comments(project_id, topic_id, current_user)
|
||||
bcf_db.debug(endpoint='comments_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id},
|
||||
response={count: value.dict() for count, value in enumerate(comments_response)})
|
||||
return comments_response
|
||||
|
||||
|
||||
# request body comment = CommentPOST
|
||||
@router.post("/bcf/3.0/projects/{project_id}/topics/{topic_id}/comments", tags=["comment_post"], status_code=201)
|
||||
def comment_post(project_id: UUID, topic_id: UUID, comment: CommentPOST,
|
||||
current_user: User = Depends(get_current_active_user)) -> CommentGET:
|
||||
comment_response = bcf_db.post_comment(project_id, topic_id, comment, current_user)
|
||||
bcf_db.debug(endpoint='comment_post',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'comment': comment},
|
||||
response=comment_response.dict())
|
||||
return comment_response
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/comments/{comment_id}", tags=["comment_get"])
|
||||
def comment_get(project_id: UUID, topic_id: UUID, comment_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> CommentGET:
|
||||
comment_response = bcf_db.get_comment(project_id, topic_id, comment_id, current_user)
|
||||
bcf_db.debug(endpoint='comment_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'comment_id': comment_id},
|
||||
response=comment_response.dict())
|
||||
return comment_response
|
||||
|
||||
|
||||
# request body comment = CommentPUT
|
||||
@router.put("/bcf/3.0/projects/{project_id}/topics/{topic_id}/comments/{comment_id}", tags=["comment_put"], status_code=200)
|
||||
def comment_put(project_id: UUID, topic_id: UUID, comment_id: UUID, comment: CommentPUT,
|
||||
current_user: User = Depends(get_current_active_user)) -> CommentGET:
|
||||
comment_response = bcf_db.put_comment(project_id, topic_id, comment_id, comment, current_user)
|
||||
bcf_db.debug(endpoint='comment_put',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'comment': comment},
|
||||
response=comment_response.dict())
|
||||
return comment_response
|
||||
|
||||
|
||||
# Implemented
|
||||
@router.delete("/bcf/3.0/projects/{project_id}/topics/{topic_id}/comments/{comment_id}",
|
||||
tags=["comment_delete"], status_code=200)
|
||||
def comment_delete(project_id: UUID, topic_id: UUID, comment_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> int:
|
||||
comment_response = bcf_db.delete_comment(project_id, topic_id, comment_id, current_user)
|
||||
if comment_response is None:
|
||||
raise HTTPException(status_code=404, detail="Item not found.")
|
||||
bcf_db.debug(endpoint='comment_delete',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'comment_id': comment_id},
|
||||
response={comment_response})
|
||||
return comment_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Viewpoints
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/viewpoints", tags=["viewpoints_get"])
|
||||
def viewpoints_get(project_id: UUID, topic_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[ViewpointGET]:
|
||||
viewpoints_response = bcf_db.get_viewpoints(project_id, topic_id, current_user)
|
||||
bcf_db.debug(endpoint='viewpoints_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id},
|
||||
response={count: value.dict() for count, value in enumerate(viewpoints_response)})
|
||||
return viewpoints_response
|
||||
|
||||
|
||||
# request body viewpoint = viewpointPOST
|
||||
@router.post("/bcf/3.0/projects/{project_id}/topics/{topic_id}/viewpoints", tags=["viewpoint_post"], status_code=201)
|
||||
def viewpoint_post(project_id: UUID, topic_id: UUID, viewpoint: ViewpointPOST,
|
||||
current_user: User = Depends(get_current_active_user)) -> ViewpointGET:
|
||||
viewpoint_response = bcf_db.post_viewpoint(project_id, topic_id, viewpoint, current_user)
|
||||
bcf_db.debug(endpoint='viewpoint_post',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'viewpoint': viewpoint.dict()},
|
||||
response=viewpoint_response.dict())
|
||||
return viewpoint_response
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}", tags=["viewpoint_get"])
|
||||
def viewpoint_get(project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> ViewpointGET:
|
||||
viewpoint_response = bcf_db.get_viewpoint(project_id, topic_id, viewpoint_id, current_user)
|
||||
bcf_db.debug(endpoint='viewpoint_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'viewpoint_id': viewpoint_id},
|
||||
response=viewpoint_response.dict())
|
||||
return viewpoint_response
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/snapshot",
|
||||
tags=["viewpoint_snapshot_get"])
|
||||
async def viewpoint_snapshot_get(project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> FileResponse:
|
||||
viewpoint_snapshot_response = bcf_db.get_viewpoint_snapshot(project_id, topic_id, viewpoint_id, current_user)
|
||||
bcf_db.debug(endpoint='viewpoint_snapshot_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'viewpoint_id': viewpoint_id},
|
||||
response=viewpoint_snapshot_response)
|
||||
snapshot_name = 'snapshot_' + str(viewpoint_id)
|
||||
file_ending = '.' + viewpoint_snapshot_response.split('/', 2)[1]
|
||||
snapshot_path = 'data/snapshots/' + snapshot_name + file_ending
|
||||
snapshot_type = viewpoint_snapshot_response
|
||||
return FileResponse(path=snapshot_path,
|
||||
media_type=snapshot_type)
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/bitmaps/{bitmap_id}",
|
||||
tags=["viewpoint_bitmap_get"])
|
||||
async def viewpoint_bitmap_get(project_id: UUID, topic_id: UUID, viewpoint_id: UUID, bitmap_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> FileResponse:
|
||||
viewpoint_bitmap_response = bcf_db.get_viewpoint_bitmap(project_id, topic_id, viewpoint_id, bitmap_id, current_user)
|
||||
bcf_db.debug(endpoint='viewpoint_bitmap_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id,
|
||||
'viewpoint_id': viewpoint_id, 'bitmap_id': bitmap_id},
|
||||
response=viewpoint_bitmap_response.dict())
|
||||
bitmap_name = 'bitmap_' + str(viewpoint_id)
|
||||
file_ending = '.' + viewpoint_bitmap_response['bitmap_type'].split('/', 2)[1]
|
||||
bitmap_path = 'data/bitmaps/' + bitmap_name + file_ending
|
||||
bitmap_type = viewpoint_bitmap_response['bitmap_type']
|
||||
return FileResponse(path=bitmap_path,
|
||||
media_type=bitmap_type)
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/coloring",
|
||||
tags=["viewpoint_colored_components_get"])
|
||||
def viewpoint_colored_components_get(project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> ColoringGET:
|
||||
viewpoint_colored_components_response = bcf_db.get_viewpoint_colored_components(project_id, topic_id, viewpoint_id, current_user)
|
||||
bcf_db.debug(endpoint='viewpoint_colored_components_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'viewpoint_id': viewpoint_id},
|
||||
response=viewpoint_colored_components_response.dict())
|
||||
return viewpoint_colored_components_response
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/selection",
|
||||
tags=["viewpoint_selected_components_get"])
|
||||
def viewpoint_selected_components_get(project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> SelectionGET:
|
||||
viewpoint_selected_components_response = bcf_db.get_viewpoint_selected_components(project_id, topic_id, viewpoint_id, current_user)
|
||||
bcf_db.debug(endpoint='viewpoint_selected_components_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'viewpoint_id': viewpoint_id},
|
||||
response=viewpoint_selected_components_response.dict())
|
||||
return viewpoint_selected_components_response
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}/visibility",
|
||||
tags=["viewpoint_components_visibility_get"])
|
||||
def viewpoint_components_visibility_get(project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> VisibilityGET:
|
||||
viewpoint_components_visibility_response = bcf_db.get_viewpoint_components_visibility(project_id, topic_id, viewpoint_id, current_user)
|
||||
bcf_db.debug(endpoint='viewpoint_components_visibility_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'viewpoint_id': viewpoint_id},
|
||||
response=viewpoint_components_visibility_response.dict())
|
||||
return viewpoint_components_visibility_response
|
||||
|
||||
|
||||
# Implemented
|
||||
@router.delete("/bcf/3.0/projects/{project_id}/topics/{topic_id}/viewpoints/{viewpoint_id}",
|
||||
tags=["viewpoint_delete"], status_code=200)
|
||||
def viewpoint_delete(project_id: UUID, topic_id: UUID, viewpoint_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> int:
|
||||
viewpoint_response = bcf_db.delete_viewpoint(project_id, topic_id, viewpoint_id, current_user)
|
||||
if viewpoint_response is None:
|
||||
raise HTTPException(status_code=404, detail="Item not found.")
|
||||
bcf_db.debug(endpoint='viewpoint_delete',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'viewpoint_id': viewpoint_id},
|
||||
response={viewpoint_response})
|
||||
return viewpoint_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Related topics
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/related_topics", tags=["related_topics_get"])
|
||||
def related_topics_get(project_id: UUID, topic_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[RelatedTopicGET]:
|
||||
related_topics_response = bcf_db.get_related_topics(project_id, topic_id, current_user)
|
||||
bcf_db.debug(endpoint='related_topics_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id},
|
||||
response={count: value.dict() for count, value in enumerate(related_topics_response)})
|
||||
return related_topics_response
|
||||
|
||||
|
||||
# request body related_topic = RelatedTopicPUT
|
||||
@router.put("/bcf/3.0/projects/{project_id}/topics/{topic_id}/related_topics", tags=["related_topics_put"], status_code=200)
|
||||
def related_topics_put(project_id: UUID, topic_id: UUID, related_topics: List[RelatedTopicPUT],
|
||||
current_user: User = Depends(get_current_active_user)) -> List[RelatedTopicGET]:
|
||||
related_topics_response = bcf_db.put_related_topics(project_id, topic_id, related_topics, current_user)
|
||||
bcf_db.debug(endpoint='related_topics_put',
|
||||
request={'project_id': project_id, 'topic_id': topic_id,
|
||||
'related_topics': {count: value.dict() for count, value in enumerate(related_topics)}},
|
||||
response={count: value.dict() for count, value in enumerate(related_topics_response)})
|
||||
return related_topics_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Document references <- from topic
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/document_references",
|
||||
tags=["topic_document_references_get"])
|
||||
def topic_document_references_get(project_id: UUID, topic_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[DocumentReferenceGET]:
|
||||
topic_document_references_response = bcf_db.get_topic_document_references(project_id, topic_id, current_user)
|
||||
bcf_db.debug(endpoint='topic_document_references_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id},
|
||||
response={count: value.dict() for count, value in enumerate(topic_document_references_response)})
|
||||
return topic_document_references_response
|
||||
|
||||
|
||||
# request body document_reference = DocumentReferencePOST
|
||||
@router.post("/bcf/3.0/projects/{project_id}/topics/{topic_id}/document_references",
|
||||
tags=["topic_document_references_post"],
|
||||
status_code=201)
|
||||
def topic_document_reference_post(project_id: UUID, topic_id: UUID, document_reference: DocumentReferencePOST,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentReferenceGET:
|
||||
topic_document_references_response = bcf_db.post_topic_document_references(project_id,
|
||||
topic_id,
|
||||
document_reference,
|
||||
current_user)
|
||||
bcf_db.debug(endpoint='topic_document_references_post',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'document_reference': document_reference},
|
||||
response={count: value.dict() for count, value in enumerate(topic_document_references_response)})
|
||||
return topic_document_references_response
|
||||
|
||||
|
||||
# request body document_reference = DocumentReferencePUT
|
||||
@router.put("/bcf/3.0/projects/{project_id}/topics/{topic_id}/document_references/{reference_id}",
|
||||
tags=["topic_document_references_put"],
|
||||
status_code=200)
|
||||
def topic_document_references_put(project_id: UUID, topic_id: UUID, reference_id: UUID,
|
||||
document_reference: DocumentReferencePUT,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentReferenceGET:
|
||||
topic_document_references_response = bcf_db.put_topic_document_references(project_id,
|
||||
topic_id,
|
||||
reference_id,
|
||||
document_reference,
|
||||
current_user)
|
||||
bcf_db.debug(endpoint='topic_document_references_put',
|
||||
request={'project_id': project_id,
|
||||
'topic_id': topic_id,
|
||||
'reference_id': reference_id,
|
||||
'document_reference': document_reference.dict()},
|
||||
response={count: value.dict() for count, value in enumerate(topic_document_references_response)})
|
||||
return topic_document_references_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Documents project
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/documents", tags=["documents_get"])
|
||||
def documents_get(project_id: UUID, current_user: User = Depends(get_current_active_user)) -> List[DocumentGET]:
|
||||
documents_response = bcf_db.get_documents(project_id, current_user)
|
||||
bcf_db.debug(endpoint='documents_get',
|
||||
request={'project_id': project_id},
|
||||
response={count: value.dict() for count, value in enumerate(documents_response)})
|
||||
return documents_response
|
||||
|
||||
|
||||
# request body file = UploadFile
|
||||
@router.post("/bcf/3.0/projects/{project_id}/documents", tags=["document_post"], status_code=201)
|
||||
async def document_post(project_id: UUID, file: UploadFile,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentGET:
|
||||
document_response = bcf_db.post_document(project_id, file, current_user)
|
||||
bcf_db.debug(endpoint='document_post',
|
||||
request={'project_id': project_id},
|
||||
response=document_response.dict())
|
||||
return document_response
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/documents/{document_id}", tags=["document_get"])
|
||||
def document_get(project_id: UUID, document_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentGET:
|
||||
document_response = bcf_db.get_document(project_id, document_id, current_user)
|
||||
bcf_db.debug(endpoint='document_get',
|
||||
request={'project_id': project_id, 'document_id': document_id},
|
||||
response=document_response.dict())
|
||||
return document_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Topics events
|
||||
|
||||
|
||||
# ...
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/events", tags=["topics_events_get"])
|
||||
def topics_events_get(project_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[TopicEventGET]:
|
||||
topic_events_response = bcf_db.get_topics_events(project_id, current_user)
|
||||
bcf_db.debug(endpoint='topics_events_get',
|
||||
request={'project_id': project_id},
|
||||
response={count: value.dict() for count, value in enumerate(topic_events_response)})
|
||||
return topic_events_response
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/events", tags=["topic_events_get"])
|
||||
def topic_events_get(project_id: UUID, topic_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[TopicEventGET]:
|
||||
topic_events_response = bcf_db.get_topic_events(project_id, topic_id, current_user)
|
||||
bcf_db.debug(endpoint='topic_events_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id},
|
||||
response={count: value.dict() for count, value in enumerate(topic_events_response)})
|
||||
return topic_events_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Comments events
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/comments/events", tags=["comments_events_get"])
|
||||
def comments_events_get(project_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[CommentEventGET]:
|
||||
comments_events_response = bcf_db.get_comments_events(project_id, current_user)
|
||||
bcf_db.debug(endpoint='comments_events_get',
|
||||
request={'project_id': project_id},
|
||||
response={count: value.dict() for count, value in enumerate(comments_events_response)})
|
||||
return comments_events_response
|
||||
|
||||
|
||||
@router.get("/bcf/3.0/projects/{project_id}/topics/{topic_id}/comments/{comment_id}/events",
|
||||
tags=["comment_events_get"])
|
||||
def comment_events_get(project_id: UUID, topic_id: UUID, comment_id: UUID,
|
||||
current_user: User = Depends(get_current_active_user)) -> List[CommentEventGET]:
|
||||
comment_events_response = bcf_db.get_comment_events(project_id, topic_id, comment_id, current_user)
|
||||
bcf_db.debug(endpoint='comment_events_get',
|
||||
request={'project_id': project_id, 'topic_id': topic_id, 'comment_id': comment_id},
|
||||
response={count: value.dict() for count, value in enumerate(comment_events_response)})
|
||||
return comment_events_response
|
||||
@@ -0,0 +1,762 @@
|
||||
|
||||
import collections
|
||||
import os
|
||||
import shutil
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
from fastapi import HTTPException, status, APIRouter, Request, Depends
|
||||
from fastapi import UploadFile, Form
|
||||
from fastapi.responses import FileResponse, HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from typing import Union
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from pydantic import ValidationError
|
||||
from security.secure import get_current_active_user
|
||||
|
||||
from repository.documents import doc_db
|
||||
|
||||
from models.documents_request import *
|
||||
from models.documents_response import *
|
||||
from models.documents_common import *
|
||||
from models.documents_other import *
|
||||
|
||||
from api.logging import LoggingRoute
|
||||
|
||||
router = APIRouter(route_class=LoggingRoute)
|
||||
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
################################################################
|
||||
# DOCUMENTS API UPLOAD FLOW
|
||||
################################################################
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Initiate the upload flow'
|
||||
#
|
||||
# description: 'The client will call this endpoint to initiate the flow of uploading documents to the CDE.'
|
||||
#
|
||||
# requestBody:
|
||||
# description: 'The body of this request contains a list of files to upload and a callback URL to the client,
|
||||
# which is used later in the flow to communicate from the CDE server back to the client via query parameters.'
|
||||
#
|
||||
# {
|
||||
# "server_context": "7188a2be-6c4e-4e3b-b7e7-cd27f0d4ad67",
|
||||
# "callback": {
|
||||
# "url": "http://localhost:8080/cde-callback-example",
|
||||
# "expires_in": 3600
|
||||
# },
|
||||
# "files": [
|
||||
# {
|
||||
# "file_name": "model.ifc",
|
||||
# "session_file_id": "76ec9d91-0731-4405-b3c4-9bf945f9955b"
|
||||
# }
|
||||
# ]
|
||||
# }
|
||||
#
|
||||
# The client is using the optional server_context in this example to provide a guid value
|
||||
# which was obtained, from the CDE, in a previous document exchange. The server_context
|
||||
# informs the CDE of the user's previous activity (e.g. a "project" or a directory on
|
||||
# the CDE). The CDE can use the server_context to resume the document selection session
|
||||
# from where the user has last left.
|
||||
#
|
||||
# description: 'The CDE returns a URL for the client to open in a local browser.
|
||||
#
|
||||
# {
|
||||
# "upload_ui_url": "https://cde.example.com/document-upload?upload_session=7c41c859-c0c1-4914-ac6c-8fbd50fb8247",
|
||||
# "expires_in": 60,
|
||||
# "max_size_in_bytes": 1073741824
|
||||
# }
|
||||
#
|
||||
# The user will then be presented with the CDE UI to enter document metadata'
|
||||
#
|
||||
# links /server-provided-path-upload-documents-url
|
||||
#
|
||||
# * Once the user has completed entering document metadata in the CDE UI,
|
||||
# the CDE will append `?upload_documents_url=/server-provided-path-upload-documents-url`
|
||||
# to the client's callback (see request) and provide it to the client via a browser redirect
|
||||
#
|
||||
# * If the user has cancelled the download the CDE server will append `?user_cancelled_selection=true`
|
||||
# to the client's callback (see request) and provide it to the client via a browser redirect.
|
||||
|
||||
# implemented
|
||||
|
||||
|
||||
@router.post("/documents/1.0/upload-documents", tags=[""])
|
||||
def upload_documents_post(upload_documents: UploadDocuments,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentUploadSessionInitialization:
|
||||
post_upload_documents_response = doc_db.post_upload_documents(upload_documents, current_user)
|
||||
doc_db.debug(endpoint='upload_documents_post',
|
||||
request={'upload_documents': upload_documents},
|
||||
response=post_upload_documents_response.dict())
|
||||
return post_upload_documents_response
|
||||
|
||||
|
||||
# This goes to a website UI where the user can enter document metadata.
|
||||
@router.get("/documents/1.0/document-upload", tags=[""], response_class=HTMLResponse)
|
||||
def upload_documents_get(request: Request, upload_session: UUID):
|
||||
|
||||
data_for_upload_documents = doc_db.get_data_for_upload_documents(upload_session)
|
||||
print('Data for site: ', data_for_upload_documents)
|
||||
|
||||
return templates.TemplateResponse(
|
||||
'upload_files.html',
|
||||
{'request': request,
|
||||
'upload_session': upload_session,
|
||||
'username': data_for_upload_documents.current_user.username,
|
||||
'email': data_for_upload_documents.current_user.email,
|
||||
'full_name': data_for_upload_documents.current_user.full_name,
|
||||
'server_context': data_for_upload_documents.server_context,
|
||||
'callback_url': data_for_upload_documents.callback.url,
|
||||
'callback_expires_in': data_for_upload_documents.callback.expires_in,
|
||||
'documents': data_for_upload_documents.documents,
|
||||
'projects': data_for_upload_documents.projects})
|
||||
|
||||
|
||||
@router.post("/documents/1.0/save-metadata-for-documents", tags=[""])
|
||||
async def save_metadata_for_documents_post(request: Request) -> list:
|
||||
|
||||
form_data = await request.form()
|
||||
form_data_json = jsonable_encoder(form_data)
|
||||
|
||||
print("Form values: ")
|
||||
print(form_data_json)
|
||||
|
||||
documents = collections.defaultdict(dict)
|
||||
names = ('session_file_id', 'document', 'title', 'version_number', 'filename')
|
||||
|
||||
for whole_form_key, value in form_data_json.items():
|
||||
if whole_form_key.startswith(names):
|
||||
start_form_key, document_id = whole_form_key.split("@", 1)
|
||||
print('New field: ', start_form_key, ' for document id: ', document_id)
|
||||
documents[document_id][start_form_key] = value
|
||||
|
||||
print("Documents: ")
|
||||
print(documents)
|
||||
|
||||
username = form_data_json['username']
|
||||
upload_session = form_data_json['upload_session']
|
||||
server_context = form_data_json['server_context']
|
||||
callback_url = form_data_json['callback_url']
|
||||
callback_expires_in = form_data_json['callback_expires_in']
|
||||
project = form_data_json['project']
|
||||
|
||||
documents_saved = list()
|
||||
|
||||
for key in documents:
|
||||
try:
|
||||
documents[key]['project'] = project
|
||||
document = DocumentMetadata(**documents[key])
|
||||
save_metadata_response = doc_db.save_metadata_for_documents(document, upload_session, username)
|
||||
documents_saved.append(save_metadata_response)
|
||||
except ValidationError as e:
|
||||
print(e)
|
||||
continue
|
||||
|
||||
doc_db.debug(endpoint='save_metadata_for_documents_post',
|
||||
request={'documents': documents},
|
||||
response={'response': documents_saved})
|
||||
|
||||
return documents_saved
|
||||
|
||||
|
||||
# http://localhost:8080/cde-callback-example?upload_documents_url=
|
||||
# https%3A%2F%2Fcde.example.com%2Fupload-instructions%3Fupload_session%3Dee56b8f3-8f93-4819-976e-46a45a5a996f
|
||||
|
||||
@router.post("/documents/1.0/upload-instructions", tags=[""])
|
||||
def upload_instructions(session_id: str, server_context: str, upload_files: UploadFileDetails,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentsToUpload:
|
||||
documents_to_upload_model = DocumentsToUpload()
|
||||
documents_to_upload_model.server_context = server_context
|
||||
documents_to_upload_model.documents_to_upload = list()
|
||||
for upload_file in upload_files.files:
|
||||
get_upload_instructions_response = doc_db.get_upload_instructions(session_id, server_context, upload_file, current_user)
|
||||
doc_db.debug(endpoint='upload_instructions',
|
||||
request={'session_id': session_id,
|
||||
'server_context': server_context,
|
||||
'document': upload_file},
|
||||
response=get_upload_instructions_response.dict())
|
||||
documents_to_upload_model.documents_to_upload.append(get_upload_instructions_response)
|
||||
return documents_to_upload_model
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Upload a single file part'
|
||||
#
|
||||
# description: This endpoint allows the client to upload the content of a single file part.
|
||||
# * The `/server-provided-path-upload-documents-url` operation specifies the list of part
|
||||
# upload requests for each file
|
||||
# * For each part, the URL, method (put or post), headers and request body are specified
|
||||
# * The client must upload all parts before proceeding to complete the upload
|
||||
# * Parts can be uploaded concurrently and in any order
|
||||
#
|
||||
# requestbody: 'The file content', application/octet-stream, type: string, format: binary
|
||||
#
|
||||
# responses: DocumentsToUpload
|
||||
#
|
||||
# links:
|
||||
# /server-provided-path-document-upload-completion
|
||||
# description: This operation should be called when all the parts have been successfully uploaded
|
||||
#
|
||||
# /server-provided-path-document-upload-cancellation
|
||||
# description: This operation should be called to cancel the upload
|
||||
|
||||
|
||||
@router.post("/documents/1.0/upload-part/{part_id}", tags=[""])
|
||||
async def upload_part(part_id: str, request: Request,
|
||||
current_user: User = Depends(get_current_active_user)):
|
||||
|
||||
# file_name = doc_db.safe_path(part_id)
|
||||
file_name = part_id
|
||||
|
||||
# see if the user really has the part-node in the database, before receiving upload of this part
|
||||
# and get the document_id of the part
|
||||
document = doc_db.user_has_part(part_id, current_user)
|
||||
|
||||
request_body = await request.body()
|
||||
|
||||
if document:
|
||||
|
||||
# try to receive the uploaded part
|
||||
try:
|
||||
print('File contents: ', request_body)
|
||||
|
||||
# use document_id instead as dir_name
|
||||
# dir_name = doc_db.safe_path(document.document_id)
|
||||
dir_name = document.document_id
|
||||
|
||||
path = './data/document_parts/' + dir_name + '/'
|
||||
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
with open(path + file_name, 'wb') as f:
|
||||
f.write(request_body)
|
||||
|
||||
except Exception:
|
||||
print('Error uploading file')
|
||||
print(traceback.format_exc())
|
||||
print('Error uploading file')
|
||||
print(sys.exc_info()[2])
|
||||
|
||||
finally:
|
||||
# We will write to the database, information about part successfully uploaded.
|
||||
doc_db.mark_part_as_uploaded(part_id, current_user)
|
||||
|
||||
doc_db.debug(endpoint='upload-part',
|
||||
request={'part_id': part_id},
|
||||
response={'uploaded': True})
|
||||
|
||||
return {"message": f"Successfully uploaded part {file_name}"}
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Complete a file upload after all the parts have been successfully uploaded'
|
||||
#
|
||||
# description: The `/server-provided-path-upload-documents-url` operation specifies
|
||||
# the upload completion URL for each file. The upload completion endpoint must be
|
||||
# called by the client when the file content upload has completed successfully.
|
||||
#
|
||||
# responses: DocumentVersion
|
||||
#
|
||||
# links:
|
||||
# /server-provided-path-document-upload-completion
|
||||
# description: This operation should be called when all the parts have been successfully uploaded
|
||||
#
|
||||
# /server-provided-path-document-upload-cancellation
|
||||
# description: This operation should be called to cancel the upload
|
||||
|
||||
@router.post("/documents/1.0/upload-completion", tags=[""])
|
||||
def upload_completion(upload_session: str,
|
||||
current_user: User = Depends(get_current_active_user)) -> Union[DocumentVersion, bool]:
|
||||
|
||||
# check if all parts really are marked as uploaded in database
|
||||
# retrieve document_id, file_type, file_ending, and parts_id (in order)
|
||||
|
||||
if not doc_db.all_parts_uploaded(upload_session, current_user):
|
||||
raise HTTPException(status_code=400, detail="All parts not uploaded.")
|
||||
|
||||
parts = doc_db.retrieve_uploaded_parts(upload_session, current_user)
|
||||
print('Number of parts: ' + str(len(parts)))
|
||||
document = doc_db.get_document_from_session(upload_session, current_user)
|
||||
|
||||
# check if all parts really are uploaded to document_id-dir
|
||||
document_name = doc_db.safe_path(document.document_id)
|
||||
|
||||
path = './data/document_parts/' + document_name + '/'
|
||||
|
||||
for part in parts:
|
||||
part = doc_db.safe_path(part)
|
||||
print('Checking for part ' + part + ' in dir ' + path)
|
||||
|
||||
if not os.path.isfile(path + part):
|
||||
print(part + ' is not in dir ' + path)
|
||||
raise HTTPException(status_code=400, detail="All parts not in dir.")
|
||||
else:
|
||||
print(part + ' is in dir ' + path)
|
||||
|
||||
# merge parts to a new temporary document
|
||||
temp_doc_path = path
|
||||
temp_doc_file_name = path + document_name
|
||||
if not os.path.exists(temp_doc_path):
|
||||
os.makedirs(temp_doc_path)
|
||||
|
||||
new_doc_path = './data/documents/'
|
||||
new_doc_path_name = new_doc_path + document.file_description.name
|
||||
|
||||
# Read parts and write to temp doc.
|
||||
with open(temp_doc_file_name, 'ab') as temp_doc:
|
||||
for part in parts:
|
||||
part = doc_db.safe_path(part)
|
||||
with open(temp_doc_path + part, 'rb') as part_doc:
|
||||
temp_doc.write(part_doc.read())
|
||||
|
||||
# move document to new location in documents dir
|
||||
os.rename(temp_doc_file_name, new_doc_path_name)
|
||||
|
||||
# remove temp parts
|
||||
for part in parts:
|
||||
os.remove(temp_doc_path + part)
|
||||
|
||||
# remove temp path
|
||||
os.rmdir(temp_doc_path)
|
||||
|
||||
# clean database and create new document node under correct project
|
||||
if doc_db.document_upload_finish(upload_session, current_user, document.project):
|
||||
# get DocumentVersion
|
||||
document = doc_db.get_document_version(document.document_id, document.version_index, current_user)
|
||||
|
||||
doc_db.debug(endpoint='upload_completion',
|
||||
request={'upload_session': upload_session},
|
||||
response=document.dict())
|
||||
return document
|
||||
else:
|
||||
return False
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Cancel the upload of a single file'
|
||||
#
|
||||
# description: The `/server-provided-path-upload-documents-url` operation specifies the
|
||||
# upload cancellation URL for each file. This endpoint must be called by the client when
|
||||
# the file content upload has been cancelled.
|
||||
#
|
||||
# responses: 204
|
||||
|
||||
|
||||
@router.post("/documents/1.0/upload-cancellation", tags=[""], status_code=status.HTTP_204_NO_CONTENT)
|
||||
def upload_cancellation(upload_session: str, current_user: User = Depends(get_current_active_user)):
|
||||
|
||||
try:
|
||||
# remove session and leaf nodes from database
|
||||
document = doc_db.get_upload_cancellation(upload_session, current_user)
|
||||
|
||||
# clean temp dir
|
||||
document_name = doc_db.safe_path(document.document_id)
|
||||
path = './data/document_parts/' + document_name + '/'
|
||||
shutil.rmtree(path)
|
||||
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
finally:
|
||||
print('Upload cancellation complete.')
|
||||
|
||||
return
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Query for the latest versions of multiple documents'
|
||||
#
|
||||
# description: This endpoint can be called when querying for the latest versions for multiple
|
||||
# documents. It's an aggregate endpoint that should make periodic polling of a collection
|
||||
# of documents easier.
|
||||
#
|
||||
# parameters:
|
||||
# in: header
|
||||
# name: If-None-Match
|
||||
# description: 'Clients may provide a previosuly received etag value to avoid receiving
|
||||
# a response object if there were no changes since the last query'
|
||||
# schema: type: string, format: etag_value
|
||||
#
|
||||
# responses
|
||||
# description: 'A list of DocumentVersion objects, where each DocumentVersion
|
||||
# represents the latest version for each given document on the server.'
|
||||
#
|
||||
# headers:
|
||||
# ETag:
|
||||
# schema: type: string, format: etag_value
|
||||
#
|
||||
# description: A unique identifier of the response that allows CDEs to save bandwidth and the
|
||||
# clients to implement caching and avoid computation when the document versions haven't changed
|
||||
# since the last query
|
||||
#
|
||||
# links
|
||||
# /server-provided-path-document-versions
|
||||
# These operations allows listing all the versions of a document
|
||||
# that has been flagged to have a new version in the response.
|
||||
#
|
||||
|
||||
@router.post("/documents/1.0/document-versions", tags=[""])
|
||||
def document_versions_post(document_ids: List[UUID],
|
||||
current_user: User = Depends(get_current_active_user)) -> List[DocumentVersion]:
|
||||
|
||||
document_versions = list()
|
||||
for document_id in document_ids:
|
||||
document_versions.append(doc_db.get_document_version(document_id, 1, current_user))
|
||||
|
||||
doc_db.debug(endpoint='document_versions_post',
|
||||
request={document_ids},
|
||||
response={document_versions})
|
||||
|
||||
return document_versions
|
||||
|
||||
|
||||
################################################################
|
||||
# DOWNLOAD FLOW
|
||||
################################################################
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Initiate document selection and download flow'
|
||||
#
|
||||
# description: The client will call this endpoint to initiate the flow of selecting and downloading documents.
|
||||
#
|
||||
# requestBody: 'The body of this request contains a callback URL to the client, which is used later in the
|
||||
# flow to communicate from the CDE server back to the client via query parameters.'
|
||||
#
|
||||
# {
|
||||
# "server_context": "711c0744-0a92-489f-8ca1-13813aa2dee7",
|
||||
# "callback": {
|
||||
# "url": "http://localhost:8080/cde-callback-example",
|
||||
# "expires_in": 3600
|
||||
# },
|
||||
# "supported_file_extensions": [
|
||||
# ".ifc",
|
||||
# ".ifczip"
|
||||
# ]
|
||||
# }
|
||||
#
|
||||
# responses: 'The CDE returns a URL for the client to open in a local browser.
|
||||
# The user will then be presented with the CDE UI to select and download documents'
|
||||
#
|
||||
# {
|
||||
# "select_documents_url": "https://cde.example.com/document-selection?selection_session=7c41c859-c0c1-4914-ac6c-8fbd50fb8247",
|
||||
# "expires_in": 60
|
||||
# }
|
||||
#
|
||||
# links /server-provided-path-selected-documents-url
|
||||
#
|
||||
# * Once the user has completed selecting documents in the CDE UI, the CDE will append
|
||||
# `?selected_documents_url=/server-provided-path-selected-documents-url` to the client's
|
||||
# callback (see request) and provide it to the client via a browser redirect.
|
||||
#
|
||||
# in this case we use a static url: selected-documents-url (but this is not safe)
|
||||
#
|
||||
# * IF the user has cancelled the download, the CDE server will append `?user_cancelled_selection=true`
|
||||
# to the client's callback (see request) and provide it to the client via a browser redirect.
|
||||
#
|
||||
|
||||
|
||||
@router.post("/documents/1.0/select-documents", tags=[""])
|
||||
def select_documents_post(select_documents: SelectDocuments,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentDiscoverySessionInitialization:
|
||||
post_select_documents_response = doc_db.post_select_documents(select_documents, current_user)
|
||||
doc_db.debug(endpoint='select_documents_post',
|
||||
request={'select_documents': select_documents},
|
||||
response=post_select_documents_response.dict())
|
||||
print("Returns ", post_select_documents_response)
|
||||
return post_select_documents_response
|
||||
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Retrieve the user''s selection from the CDE'
|
||||
#
|
||||
# description: This endpoint returns the document selection. The client retrieves its URL
|
||||
# via the callback URL from the CDE after the selection has finished from a query parameter
|
||||
# named `selected_documents_url`. In case when the user cancels the selection on the CDE UI,
|
||||
# this callback will still be called, but the `selected_documents_url` query parameter will
|
||||
# not be present, instead the server will provide a query parameter called
|
||||
# `user_cancelled_selection=true`.
|
||||
#
|
||||
# responses: SelectedDocuments
|
||||
#
|
||||
# links /server-provided-path-document-download
|
||||
# Use the `document_version_download` operation to download the file contents
|
||||
#
|
||||
# links /server-provided-path-document-metadata
|
||||
# Use the `document_version_metadata` URL operation to retrieve document metadata
|
||||
|
||||
# documents/1.0/document-selection?selection_session=7cf3dd70-c880-4fb1-9897-f60472959533
|
||||
|
||||
@router.get("/documents/1.0/document-selection", tags=[""], response_class=HTMLResponse)
|
||||
def selected_documents_get(request: Request,
|
||||
selection_session: UUID):
|
||||
data_for_document_selection = doc_db.get_data_for_document_selection(selection_session)
|
||||
return templates.TemplateResponse(
|
||||
'select_files.html',
|
||||
{'request': request,
|
||||
'selection_session': selection_session,
|
||||
'current_user': data_for_document_selection.current_user,
|
||||
'server_context': data_for_document_selection.server_context,
|
||||
'callback_url': data_for_document_selection.callback.url,
|
||||
'callback_expires_in': data_for_document_selection.callback.expires_in,
|
||||
'projects': data_for_document_selection.projects})
|
||||
|
||||
|
||||
@router.post("/documents/1.0/mark-documents-as-selected", tags=[""])
|
||||
async def mark_documents_as_selected_post(request: Request) -> DocumentsMarkedAsSelected:
|
||||
|
||||
form_data = await request.form()
|
||||
form_data_json = jsonable_encoder(form_data)
|
||||
|
||||
print("Form values: ")
|
||||
print(form_data_json)
|
||||
|
||||
documents = list()
|
||||
for key, value in form_data_json.items():
|
||||
if 'document_' in key:
|
||||
document_id = key.split("ocument_", 1)[1]
|
||||
documents.append(document_id)
|
||||
|
||||
print("Sends ", documents)
|
||||
|
||||
get_selected_response = doc_db.post_mark_documents_as_selected(documents, form_data_json['selection_session'])
|
||||
doc_db.debug(endpoint='mark_some_documents_as_selected_post',
|
||||
request={'documents': documents,
|
||||
'form_data_json[selection_session]': form_data_json['selection_session']},
|
||||
response=get_selected_response.dict())
|
||||
|
||||
return get_selected_response
|
||||
|
||||
|
||||
@router.get("/documents/1.0/download-instructions", tags=[""])
|
||||
def download_instructions(session_id: UUID, server_context: str,
|
||||
current_user: User = Depends(get_current_active_user)) -> SelectedDocuments:
|
||||
get_download_instructions_response = doc_db.get_download_instructions(session_id, server_context, current_user)
|
||||
doc_db.debug(endpoint='download_instructions',
|
||||
request={'session_id': session_id,
|
||||
'server_context': server_context},
|
||||
response=get_download_instructions_response.dict())
|
||||
return get_download_instructions_response
|
||||
|
||||
|
||||
# download links
|
||||
|
||||
@router.get("/documents/1.0/document/{document_id}/version/{version_index}", tags=[""])
|
||||
def document_version(document_id: str, version_index: int,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentVersion:
|
||||
# This endpoint returns the document version model itself.
|
||||
get_document_version = doc_db.get_document_version(document_id, version_index, current_user)
|
||||
doc_db.debug(endpoint='document_version',
|
||||
request={'document_id': document_id,
|
||||
'version_index': version_index},
|
||||
response=get_document_version.dict())
|
||||
return get_document_version
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Get document metadata for a single document'
|
||||
#
|
||||
# description: This endpoint returns the metadata for a single document. Its URL is retrieved
|
||||
# from the links object in the initial document selection response, with the `document_version_metadata`
|
||||
# property.
|
||||
#
|
||||
# responses: 'The file content', application/octet-stream, type: string, format: binary
|
||||
#
|
||||
#
|
||||
# links /server-provided-path-document-download
|
||||
# Use the `document_version_download` operation to download the file contents
|
||||
#
|
||||
# links /server-provided-path-document-metadata
|
||||
# Use the `document_version_metadata` URL operation to retrieve document metadata
|
||||
|
||||
|
||||
@router.get("/documents/1.0/document/{document_id}/version/{version_index}/metadata", tags=[""])
|
||||
def document_version_metadata(document_id: str, version_index: int,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentMetadataEntries:
|
||||
# The metadata for document versions is a list of key-value pairs
|
||||
get_document_version_metadata_result = doc_db.get_document_version_metadata(document_id, version_index, current_user)
|
||||
doc_db.debug(endpoint='document_version',
|
||||
request={'document_id': document_id,
|
||||
'version_index': version_index},
|
||||
response=get_document_version_metadata_result.dict())
|
||||
return get_document_version_metadata_result
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Download the document'
|
||||
#
|
||||
# description: Use this endpoint to download the document.
|
||||
# Its URL is retrieved from the links object in the initial document selection response,
|
||||
# with the `document_version_download` property.
|
||||
#
|
||||
# responses: 'The file content', application/octet-stream, type: string, format: binary
|
||||
#
|
||||
#
|
||||
# links /server-provided-path-document-download
|
||||
# Use the `document_version_download` operation to download the file contents
|
||||
#
|
||||
# links /server-provided-path-document-metadata
|
||||
# Use the `document_version_metadata` URL operation to retrieve document metadata
|
||||
|
||||
|
||||
@router.get("/documents/1.0/document/{document_id}/version/{version_index}/download", tags=[""])
|
||||
def document_version_download(document_id: str, version_index: int,
|
||||
current_user: User = Depends(get_current_active_user)) -> FileResponse:
|
||||
# The url to download the binary content of this document version.
|
||||
# May either directly return the result or redirect to a storage provider
|
||||
keep_characters = (' ', '.', '_', '-')
|
||||
document_id = "".join(c for c in document_id if c.isalnum() or c in keep_characters).rstrip()
|
||||
file_location = './data/documents/' + document_id + '.ifc'
|
||||
return FileResponse(file_location,
|
||||
media_type='application/x-step',
|
||||
filename='6dbd4d52-14db-11ee-be56-0242ac120002.ifc')
|
||||
|
||||
|
||||
@router.get("/documents/1.0/document/{document_id}/version/{version_index}/versions", tags=[""])
|
||||
def document_versions(document_id: str, version_index: int,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentVersions:
|
||||
# This url returns a list of all document versions for the parent document.
|
||||
# The client can use this URL to monitor for new document versions
|
||||
get_document_versions_result = doc_db.get_document_versions(document_id, current_user)
|
||||
doc_db.debug(endpoint='document_version',
|
||||
request={'document_id': document_id},
|
||||
response=get_document_versions_result.dict())
|
||||
return get_document_versions_result
|
||||
|
||||
|
||||
@router.get("/documents/1.0/document/{document_id}/version/{version_index}/details", tags=[""],
|
||||
response_class=HTMLResponse)
|
||||
def document_version_details(request: Request,
|
||||
document_id: str,
|
||||
version_index: int,
|
||||
current_user: User = Depends(get_current_active_user)):
|
||||
# This url returns a list of all document versions for the parent document.
|
||||
# The client can use this URL to monitor for new document versions
|
||||
details = doc_db.get_document_version(document_id, version_index, current_user)
|
||||
doc_db.debug(endpoint='document_version',
|
||||
request={'document_id': document_id,
|
||||
'version_index': version_index},
|
||||
response=details.dict())
|
||||
return templates.TemplateResponse(
|
||||
'document_details.html',
|
||||
{'request': request,
|
||||
'details': details})
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Get the versions of a single document'
|
||||
#
|
||||
# description: This endpoint returns the versions for a single document. Its URL is retrieved
|
||||
# from the links object in the initial document selection response, with the `document_versions` property.
|
||||
#
|
||||
# responses: DocumentVersions
|
||||
#
|
||||
# links: /document-versions
|
||||
# parameters:
|
||||
# document_id: '$response.body#..document_id'
|
||||
# description: > Use the `document_id` to query for updates for this document
|
||||
|
||||
# @router.get("/documents/1.0/document-versions", tags=[""])
|
||||
# def document_versions_get(current_user: User = Depends(get_current_active_user)) -> DocumentVersions:
|
||||
# xxx_response = doc_db.get_post_put_xxx(current_user)
|
||||
# doc_db.debug(endpoint='xxx_get_post_put',
|
||||
# request={},
|
||||
# response={count: value.dict() for count, value in enumerate(xxx_response)})
|
||||
# return xxx_response
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
# summary: 'Retrieve upload instructions after the user has entered document metadata on the CDE'
|
||||
#
|
||||
# description: This endpoint returns the document upload information. The client retrieves
|
||||
# this URL via the callback URL from the server after the user's completion of the upload
|
||||
# metadata entry from a query parameter named `upload_documents_url`. In case when the
|
||||
# user cancels the upload on the CDE UI, this callback will still be called, but the
|
||||
# `upload_documents_url` query parameter will not be present, instead the server will
|
||||
# provide a query parameter called `user_cancelled_upload=true`.
|
||||
#
|
||||
# requestbody: description: 'The client sends the list of files to be uploaded with this
|
||||
# request. The file sizes are then used by the server to break the file to multiple
|
||||
# upload parts, as required.'
|
||||
#
|
||||
# responses: DocumentsToUpload
|
||||
#
|
||||
# links:
|
||||
# /server-provided-path-document-upload-part
|
||||
# description: This operation should be called for each part specified in this reponse
|
||||
#
|
||||
# /server-provided-path-document-upload-completion
|
||||
# description: This operation should be called when all the parts have been successfully uploaded
|
||||
#
|
||||
# /server-provided-path-document-upload-cancellation
|
||||
# description: This operation should be called to cancel the upload
|
||||
|
||||
################################################################
|
||||
# CUSTOM UPLOAD FLOW, USING DOWNLOAD SESSION
|
||||
################################################################
|
||||
|
||||
# TODO: Fortsätt här
|
||||
# include selection session i result
|
||||
|
||||
|
||||
@router.post("/documents/1.0/upload_file_to_project", tags=[""])
|
||||
async def upload_documents_post(file: UploadFile, project: str = Form(...), selection_session: str = Form(...)) -> Document:
|
||||
|
||||
# Get the file size (in bytes)
|
||||
file.file.seek(0, 2)
|
||||
file_size = file.file.tell()
|
||||
|
||||
# Create document id for file
|
||||
document_id = str(uuid4())
|
||||
|
||||
# Move cursor back to the beginning of the file
|
||||
await file.seek(0)
|
||||
|
||||
# Find file name ending and create new storage file name
|
||||
if file.filename.lower().endswith(tuple(file_types)):
|
||||
file_ending = file.filename.split('.')[-1].lower()
|
||||
name = document_id + '.' + file_ending
|
||||
else:
|
||||
file_ending = ''
|
||||
name = document_id
|
||||
|
||||
# Get mime type and file type
|
||||
mime_type = ''
|
||||
file_type = ''
|
||||
if hasattr(file_types, file_ending):
|
||||
mime_type = file_types[file_ending]['mime_type']
|
||||
file_type = file_types[file_ending]['file_type']
|
||||
|
||||
# Create document data
|
||||
document_version_dict = {
|
||||
'document_id': document_id,
|
||||
'session_file_id': '',
|
||||
'version_index': 1,
|
||||
'version_number': '1',
|
||||
'creation_date': doc_db.timestamp(),
|
||||
'title': file.filename,
|
||||
'original_file_name': file.filename,
|
||||
'file_ending': file_ending,
|
||||
'mime_type': mime_type,
|
||||
'file_type': file_type,
|
||||
'project': project,
|
||||
'file_description': {
|
||||
'name': name,
|
||||
'size_in_bytes': file_size
|
||||
}
|
||||
}
|
||||
|
||||
document_version_model = Document(**document_version_dict)
|
||||
|
||||
# Save file to disc
|
||||
upload_directory = './data/documents/'
|
||||
destination_path = os.path.join(upload_directory, name)
|
||||
with open(destination_path, 'wb') as buffer:
|
||||
shutil.copyfileobj(file.file, buffer)
|
||||
|
||||
# create database record
|
||||
inserted_document = doc_db.create_node_for_uploaded_file(selection_session, project, document_version_model)
|
||||
print('Created node for document id: ' + str(inserted_document.document_id))
|
||||
doc_db.create_ifc_graph_for_document(inserted_document.document_id)
|
||||
|
||||
# return document version of database record
|
||||
return inserted_document
|
||||
@@ -0,0 +1,299 @@
|
||||
import os
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import APIRouter, Request, Form
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPBasicCredentials, HTTPBasic
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from models.request import *
|
||||
|
||||
from repository.foundation import foundation_db
|
||||
from security.secure import authenticate_user, get_current_active_user
|
||||
|
||||
from uuid import uuid4
|
||||
from api.logging import LoggingRoute
|
||||
from security.secure import get_secrets
|
||||
|
||||
secrets = get_secrets()
|
||||
|
||||
router = APIRouter(route_class=LoggingRoute)
|
||||
|
||||
http_basic = HTTPBasic()
|
||||
oauth2_state = None
|
||||
authorization_code = None
|
||||
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
clients = {
|
||||
os.environ['KONTROLL_CLIENT_ID']:
|
||||
{
|
||||
'name': os.environ['KONTROLL_CLIENT_NAME'],
|
||||
'secret': secrets['kontroll_client_secret']
|
||||
}
|
||||
}
|
||||
|
||||
# ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
|
||||
#
|
||||
#
|
||||
# Foundations API
|
||||
|
||||
# 2.1 Versions Service
|
||||
# GET /foundation/versions
|
||||
#
|
||||
# The Versions service is used to discover the available OpenCDE APIs
|
||||
# and where to find them. The api_base_url field specifies the base path for each API.
|
||||
#
|
||||
# To clarify, the versions service allows specifying api_base_url for the Foundation API. However, to ensure
|
||||
# discoverability, the Versions service is always served from the /foundation/versions base path regardless of the
|
||||
# api_base_url parameter value.
|
||||
#
|
||||
# PARAMETER TYPE DESCRIPTION REQUIRED
|
||||
# api_id string Identifier of the API true
|
||||
# version_id string Identifier of the version true
|
||||
# detailed_version string URL of the specification on GitHub false
|
||||
# api_base_url string Optional, fqURL, to allow servers to relocate the API false
|
||||
|
||||
|
||||
@router.get("/foundation/versions", tags=["api_versions_get"])
|
||||
def api_versions_get():
|
||||
return {
|
||||
"versions": [{
|
||||
"api_id": "foundation",
|
||||
"version_id": "1.0",
|
||||
"detailed_version": "https://github.com/BuildingSMART/foundation-API/tree/release_1_0"
|
||||
}, {
|
||||
"api_id": "bcf",
|
||||
"version_id": "3.0",
|
||||
"detailed_version": "https://github.com/buildingSMART/BCF-API/tree/release_3_0",
|
||||
"api_base_url": os.environ['KONTROLL_BASE_URL'] + "bcf/3.0"
|
||||
}, {
|
||||
"api_id": "documents",
|
||||
"version_id": "1.0",
|
||||
"detailed_version": "https://github.com/buildingSMART/documents-API/tree/release_1_0",
|
||||
"api_base_url": os.environ['KONTROLL_BASE_URL'] + "documents/1.0"
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
# 2.2.1 Obtaining authentication information
|
||||
# GET /foundation/1.0/auth
|
||||
#
|
||||
# PARAMETER TYPE DESCRIPTION Required
|
||||
# oauth2_auth_url string URL to authorization page false
|
||||
# oauth2_token_url string URL for token requests false
|
||||
# oauth2_dynamic_client_reg_url string URL for automated client registration false
|
||||
# http_basic_supported boolean Indicates if Http Basic Authentication is supported false
|
||||
# supported_oauth2_flows string[] array of supported OAuth2 flows true
|
||||
#
|
||||
# Requirement: oauth2_auth_url and oauth2_token_url must be present at the same time.
|
||||
#
|
||||
# "oauth2_dynamic_client_reg_url" is commented out since registration is not allowed.
|
||||
# If properties are not present in the response, clients should assume that the functionality
|
||||
# is not supported by the server.
|
||||
|
||||
|
||||
@router.get("/foundation/1.0/auth",
|
||||
tags=["foundation_auth_get"])
|
||||
def authentication_get():
|
||||
|
||||
return_variable = {
|
||||
"oauth2_auth_url": os.environ['KONTROLL_BASE_URL'] + "foundation/oauth2/auth",
|
||||
"oauth2_token_url": os.environ['KONTROLL_BASE_URL'] + "foundation/oauth2/token",
|
||||
# "oauth2_dynamic_client_reg_url": os.environ['KONTROLL_BASE_URL'] + "foundation/oauth2/reg",
|
||||
"http_basic_supported": True,
|
||||
"supported_oauth2_flows": [
|
||||
"authorization_code_grant"
|
||||
]
|
||||
}
|
||||
print(return_variable)
|
||||
return return_variable
|
||||
|
||||
|
||||
# Authentication url
|
||||
# GET /foundation/oauth2/auth
|
||||
#
|
||||
# To initiate the workflow, the client sends the user to the "oauth2_auth_url" with the following parameters added:
|
||||
#
|
||||
# PARAMETER VALUE
|
||||
# response_type code - as string literal
|
||||
# client_id your client id
|
||||
# state unique user defined value
|
||||
# scope defines the scope of access
|
||||
# redirect_uri The redirect_uri registered for your client. This parameter is optional for
|
||||
# OAUTH servers that don't support multiple redirect URLs
|
||||
#
|
||||
# EXAMPLE
|
||||
# GET https://api.kontroll.digital/foundation/oauth2/auth?
|
||||
# response_type=code
|
||||
# &client_id=<your_client_id>
|
||||
# &state=<user_defined_string>
|
||||
# &scope=<some_number>
|
||||
# &redirect_uri=http://localhost:10445/Callback
|
||||
|
||||
|
||||
@router.get("/foundation/oauth2/auth", response_class=HTMLResponse)
|
||||
def authorization(request: Request,
|
||||
response_type: str,
|
||||
client_id: str,
|
||||
state: str,
|
||||
scope: str,
|
||||
redirect_uri: str,
|
||||
):
|
||||
|
||||
client_name = clients[client_id]['name']
|
||||
|
||||
print(f"Response type: {response_type}, "
|
||||
f"Client_id: {client_id}, "
|
||||
f"Client_name: {client_name}, "
|
||||
f"State: {state}, "
|
||||
f"Scope: {scope},"
|
||||
f"Redirect_URI: {redirect_uri}."
|
||||
)
|
||||
|
||||
# 3. Solibri sends the user to oauth2_auth_url with the following parameters:
|
||||
# response_type=code, client_id=solibri_test_001, state=..., redirect_uri=uri, scope=...
|
||||
# 4. The API will ask the user to sign in with username and password.
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"login.html",
|
||||
{"request": request,
|
||||
"response_type": response_type,
|
||||
"client_id": client_id,
|
||||
"client_name": client_name,
|
||||
"state": state,
|
||||
"scope": scope,
|
||||
"redirect_uri": redirect_uri,
|
||||
})
|
||||
|
||||
|
||||
@router.get("/foundation/oauth2/code")
|
||||
def code(username: str,
|
||||
password: str,
|
||||
response_type: str,
|
||||
client_id,
|
||||
client_name,
|
||||
state: str,
|
||||
redirect_uri: str,
|
||||
scope: str = ''
|
||||
):
|
||||
|
||||
global oauth2_state
|
||||
oauth2_state = state
|
||||
|
||||
print('Username: ' + username + '. Password: ' + password)
|
||||
user = authenticate_user(username, password)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect username or password",
|
||||
headers={"WWW-Authenticate": "Bearer"}
|
||||
)
|
||||
|
||||
# The user is signed in, now the main purpose of this function is to generate the authorization code.
|
||||
# There are many ways to generate this code, in our app it is simple the word "test".
|
||||
# Conventionally auth_code needs to be encrypted because it can contain credentials, scope
|
||||
# and other request related details.
|
||||
|
||||
# This functions adds the authorization code to the user node in the graph.
|
||||
# The authorization code node self-destructs after time delay SECURITY_AUTHORIZATION_CODE_EXPIRE_SECONDS
|
||||
|
||||
global authorization_code
|
||||
new_authorization_code = str(uuid4())
|
||||
|
||||
if foundation_db.create_authorization_code(username, new_authorization_code, scope):
|
||||
authorization_code = new_authorization_code
|
||||
print(f"Authorization code: {authorization_code}")
|
||||
|
||||
# 5. When the access code is returned, the server redirects the user to redirect_uri.
|
||||
# http://localhost:10445/Callback&scope=test&code=xxx&state=xxx
|
||||
# 6. The code-parameter will be appended to the redirect_uri as a query parameter
|
||||
# this code-parameter is the access code
|
||||
# 7. The state-parameter should also be appended to the redirect_uri as a query parameter
|
||||
# 8. If the user denies client access redirect_uri will instead contain an error query parameter
|
||||
return authorization_code
|
||||
|
||||
|
||||
# /foundation/oauth2/reg is not in use, since registration is not allowed.
|
||||
|
||||
# Path to obtain token
|
||||
# With the obtained authorization code,
|
||||
# the client is able to request an access token from the server.
|
||||
# The "oauth2_token_url" from the authentication resource
|
||||
# is used to send token requests to, for example:
|
||||
#
|
||||
# Will return
|
||||
# access_token string The issued OAuth2 token
|
||||
# token_type string Always bearer
|
||||
# expires_in integer The lifetime of the access token in seconds
|
||||
# refresh_token string The issued OAuth2 refresh token, one-time-usable only
|
||||
#
|
||||
# The POST request should be done via HTTP Basic Authorization
|
||||
# with your application client_id as the username
|
||||
# and your client_secret as the password
|
||||
#
|
||||
# username: client_id
|
||||
# password: client_secret
|
||||
#
|
||||
# example
|
||||
# POST https://example.com/foundation/oauth2/token?grant_type=authorization_code&code=<your_authorization_code>
|
||||
|
||||
|
||||
@router.post("/foundation/oauth2/token",
|
||||
tags=["login_for_access_token_post"],
|
||||
status_code=201)
|
||||
def login_for_access_token(
|
||||
grant_type: Optional[str] = Form(None),
|
||||
refresh_token: Optional[str] = Form(None),
|
||||
code: Optional[str] = Form(None),
|
||||
credentials: HTTPBasicCredentials = Depends(http_basic)):
|
||||
|
||||
print('grant_type: ', grant_type)
|
||||
print('refresh_token: ', refresh_token)
|
||||
print('code: ', code)
|
||||
print('credentials: ', credentials)
|
||||
|
||||
# The API should check that the credentials (client_id and client_secret) are correct
|
||||
# credentials.username contains the client_id
|
||||
# credentials.password contains the client_secret
|
||||
if credentials.username not in clients or credentials.password != clients[credentials.username]['secret']:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Incorrect client_id or client_secret",
|
||||
)
|
||||
|
||||
if grant_type == 'authorization_code':
|
||||
# use authorization code,
|
||||
# create access token and refresh token,
|
||||
# delete authorization code
|
||||
user_info = foundation_db.use_authorization_code(code)
|
||||
|
||||
elif grant_type == 'refresh_token':
|
||||
# use refresh token to get access token
|
||||
# delete old access token and old refresh token
|
||||
# create new access token and a new refresh token
|
||||
# return old access token and new refresh token
|
||||
user_info = foundation_db.use_refresh_token(refresh_token)
|
||||
|
||||
user_info.token_type = "Bearer"
|
||||
user_info.expires_in = int(os.environ['SECURITY_ACCESS_TOKEN_EXPIRE_SECONDS'])
|
||||
print('user_info: ', user_info)
|
||||
return user_info
|
||||
|
||||
|
||||
# 3.1.1 Get current user
|
||||
# GET /foundation/1.0/current-user
|
||||
#
|
||||
# Response body
|
||||
# {
|
||||
# "id": "Architect@example.com",
|
||||
# "name": "John Doe"
|
||||
# }
|
||||
|
||||
|
||||
@router.get("/foundation/1.0/current-user", response_model=User, tags=["current_user_get"])
|
||||
async def current_user_get(current_user: User = Depends(get_current_active_user)):
|
||||
return current_user
|
||||
@@ -0,0 +1,42 @@
|
||||
from fastapi import FastAPI, APIRouter, Response, Request
|
||||
from starlette.background import BackgroundTask
|
||||
from starlette.responses import StreamingResponse
|
||||
from fastapi.routing import APIRoute
|
||||
from starlette.types import Message
|
||||
from typing import Callable, Dict, Any
|
||||
import logging
|
||||
import httpx
|
||||
|
||||
|
||||
def log_info(req_body, res_body, route_url):
|
||||
logging.info('request:' + route_url + ':' + str(req_body))
|
||||
logging.info('response:' + route_url + ':' + str(res_body))
|
||||
|
||||
|
||||
class LoggingRoute(APIRoute):
|
||||
def get_route_handler(self) -> Callable:
|
||||
original_route_handler = super().get_route_handler()
|
||||
|
||||
async def custom_route_handler(request: Request) -> Response:
|
||||
req_body = await request.body()
|
||||
response = await original_route_handler(request)
|
||||
route_url = str(request.url)
|
||||
if isinstance(response, StreamingResponse):
|
||||
res_body = b''
|
||||
async for item in response.body_iterator:
|
||||
res_body += item
|
||||
task = BackgroundTask(log_info, req_body, res_body, route_url)
|
||||
return Response(content=res_body, status_code=response.status_code,
|
||||
headers=dict(response.headers), media_type=response.media_type, background=task)
|
||||
else:
|
||||
if hasattr(response, 'body'):
|
||||
res_body = response.body
|
||||
else:
|
||||
res_body = {'no response': True}
|
||||
response.background = BackgroundTask(log_info, req_body, res_body, route_url)
|
||||
return response
|
||||
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
logging.basicConfig(filename='logs/info.log', level=logging.DEBUG)
|
||||
@@ -0,0 +1,178 @@
|
||||
|
||||
import collections
|
||||
import os
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
from fastapi import APIRouter, Request, Depends
|
||||
from fastapi.responses import FileResponse, HTMLResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import ValidationError
|
||||
from security.secure import get_current_active_user
|
||||
|
||||
from repository.documents import doc_db
|
||||
|
||||
from models.documents_request import *
|
||||
from models.documents_response import *
|
||||
from models.documents_other import *
|
||||
|
||||
from api.logging import LoggingRoute
|
||||
|
||||
router = APIRouter(route_class=LoggingRoute)
|
||||
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
################################################################
|
||||
# UPLOAD FLOW
|
||||
################################################################
|
||||
|
||||
@router.post("/user/1.0/upload-documents", tags=[""])
|
||||
def upload_documents_post(upload_documents: UploadDocuments,
|
||||
current_user: User = Depends(get_current_active_user)) -> DocumentUploadSessionInitialization:
|
||||
post_upload_documents_response = doc_db.post_upload_documents(upload_documents, current_user)
|
||||
doc_db.debug(endpoint='upload_documents_post',
|
||||
request={'upload_documents': upload_documents},
|
||||
response=post_upload_documents_response.dict())
|
||||
return post_upload_documents_response
|
||||
|
||||
|
||||
# This goes to a website UI where the user can enter document metadata.
|
||||
@router.get("/user/1.0/document-upload", tags=[""], response_class=HTMLResponse)
|
||||
def upload_documents_get(request: Request, upload_session: UUID):
|
||||
|
||||
data_for_upload_documents = doc_db.get_data_for_upload_documents(upload_session)
|
||||
print('Data for site: ', data_for_upload_documents)
|
||||
|
||||
return templates.TemplateResponse(
|
||||
'upload_files.html',
|
||||
{'request': request,
|
||||
'upload_session': upload_session,
|
||||
'username': data_for_upload_documents.current_user.username,
|
||||
'email': data_for_upload_documents.current_user.email,
|
||||
'full_name': data_for_upload_documents.current_user.full_name,
|
||||
'server_context': data_for_upload_documents.server_context,
|
||||
'callback_url': data_for_upload_documents.callback.url,
|
||||
'callback_expires_in': data_for_upload_documents.callback.expires_in,
|
||||
'documents': data_for_upload_documents.documents,
|
||||
'projects': data_for_upload_documents.projects})
|
||||
|
||||
|
||||
@router.post("/user/1.0/save-metadata-for-documents", tags=[""])
|
||||
async def save_metadata_for_documents_post(request: Request) -> list:
|
||||
|
||||
form_data = await request.form()
|
||||
form_data_json = jsonable_encoder(form_data)
|
||||
|
||||
print("Form values: ")
|
||||
print(form_data_json)
|
||||
|
||||
documents = collections.defaultdict(dict)
|
||||
names = ('session_file_id', 'document', 'title', 'version_number', 'filename')
|
||||
|
||||
for whole_form_key, value in form_data_json.items():
|
||||
if whole_form_key.startswith(names):
|
||||
start_form_key, document_id = whole_form_key.split("@", 1)
|
||||
print('New field: ', start_form_key, ' for document id: ', document_id)
|
||||
documents[document_id][start_form_key] = value
|
||||
|
||||
print("Documents: ")
|
||||
print(documents)
|
||||
|
||||
username = form_data_json['username']
|
||||
upload_session = form_data_json['upload_session']
|
||||
server_context = form_data_json['server_context']
|
||||
callback_url = form_data_json['callback_url']
|
||||
callback_expires_in = form_data_json['callback_expires_in']
|
||||
project = form_data_json['project']
|
||||
|
||||
documents_saved = list()
|
||||
|
||||
for key in documents:
|
||||
try:
|
||||
documents[key]['project'] = project
|
||||
document = DocumentMetadata(**documents[key])
|
||||
save_metadata_response = doc_db.save_metadata_for_documents(document, upload_session, username)
|
||||
documents_saved.append(save_metadata_response)
|
||||
except ValidationError as e:
|
||||
print(e)
|
||||
continue
|
||||
|
||||
doc_db.debug(endpoint='save_metadata_for_documents_post',
|
||||
request={'documents': documents},
|
||||
response={'response': documents_saved})
|
||||
|
||||
return documents_saved
|
||||
|
||||
|
||||
# http://localhost:8080/cde-callback-example?upload_documents_url=
|
||||
# https%3A%2F%2Fcde.example.com%2Fupload-instructions%3Fupload_session%3Dee56b8f3-8f93-4819-976e-46a45a5a996f
|
||||
|
||||
|
||||
@router.post("/user/1.0/upload-part/{part_id}", tags=[""])
|
||||
async def upload_part(part_id: str, request: Request,
|
||||
current_user: User = Depends(get_current_active_user)):
|
||||
|
||||
# file_name = doc_db.safe_path(part_id)
|
||||
file_name = part_id
|
||||
|
||||
# see if the user really has the part-node in the database, before receiving upload of this part
|
||||
# and get the document_id of the part
|
||||
document = doc_db.user_has_part(part_id, current_user)
|
||||
|
||||
request_body = await request.body()
|
||||
|
||||
if document:
|
||||
|
||||
# try to receive the uploaded part
|
||||
try:
|
||||
print('File contents: ', request_body)
|
||||
|
||||
# use document_id instead as dir_name
|
||||
# dir_name = doc_db.safe_path(document.document_id)
|
||||
dir_name = document.document_id
|
||||
|
||||
path = './data/document_parts/' + dir_name + '/'
|
||||
|
||||
if not os.path.exists(path):
|
||||
os.makedirs(path)
|
||||
|
||||
with open(path + file_name, 'wb') as f:
|
||||
f.write(request_body)
|
||||
|
||||
except Exception:
|
||||
print('Error uploading file')
|
||||
print(traceback.format_exc())
|
||||
print('Error uploading file')
|
||||
print(sys.exc_info()[2])
|
||||
|
||||
finally:
|
||||
# We will write to the database, information about part successfully uploaded.
|
||||
doc_db.mark_part_as_uploaded(part_id, current_user)
|
||||
|
||||
doc_db.debug(endpoint='upload-part',
|
||||
request={'part_id': part_id},
|
||||
response={'uploaded': True})
|
||||
|
||||
return {"message": f"Successfully uploaded part {file_name}"}
|
||||
|
||||
|
||||
################################################################
|
||||
# DOWNLOAD FLOW
|
||||
################################################################
|
||||
|
||||
|
||||
@router.get("/user/1.0/document/{document_id}/version/{version_index}/download", tags=[""])
|
||||
def document_version_download(document_id: str, version_index: int,
|
||||
current_user: User = Depends(get_current_active_user)) -> FileResponse:
|
||||
# The url to download the binary content of this document version.
|
||||
# May either directly return the result or redirect to a storage provider
|
||||
keep_characters = (' ', '.', '_', '-')
|
||||
document_id = "".join(c for c in document_id if c.isalnum() or c in keep_characters).rstrip()
|
||||
file_location = './data/documents/' + document_id + '.ifc'
|
||||
return FileResponse(file_location,
|
||||
media_type='application/x-step',
|
||||
filename='6dbd4d52-14db-11ee-be56-0242ac120002.ifc')
|
||||
@@ -0,0 +1,258 @@
|
||||
ISO-10303-21;
|
||||
HEADER;FILE_DESCRIPTION(('ViewDefinition [CoordinationView_V2.0, QuantityTakeOffAddOnView, SpaceBoundary2ndLevelAddOnView]','Option [Elements to export: Selected elements only]','Option [Partial Structure Display: Entire Model]','Option [IFC Domain: All]','Option [Structural Function: All Elements]','Option [Convert Grid elements: On]','Option [Convert IFC Annotations and ARCHICAD 2D elements: Off]','Option [Convert 2D symbols of Doors and Windows: On]','Option [Export geometries that Participates in Collision Detection only: Off]','Option [Split complex elements: Off]','Option [Material Preservation: Explode where necessary]','Option [Elements in Solid Element Operations: Extruded/revolved]','Option [Elements with junctions: Extruded/revolved without junctions]','Option [IFC Site Location: At Project Origin]','Option [Curtain Wall export mode: Container Element]','Option [Railing export mode: Single Element]','Option [Stair export mode: Single Element]','Option [Properties To Export: All properties]','Option [Space containment: Off]','Option [Bounding Box: Off]','Option [Geometry to type objects: Off]','Option [Element Properties: Off]','Option [Building Material Properties: Off]','Option [Element Parameters: Off]','Option [Component Parameters: Off]','Option [IFC Base Quantities: On]','Option [Door Window Parameters: Off]','Option [IFC Space boundaries: On]','Option [ARCHICAD Zone Categories as IFC Space classification data: On]','Option [Element Classifications: Off]'),'2;1');
|
||||
FILE_NAME('C:\\Users\\Yoga\\Desktop\\test.ifc','2023-09-04T21:58:54',('Arkitekten'),('Arkitektkontoret'),'The EXPRESS Data Manager Version 5.02.0100.09 : 26 Sep 2013','IFC file generated by GRAPHISOFT ARCHICAD 25.0.0 NOR FULL Windows version (IFC add-on version: 3002 NOR FULL).','Arkitekten');
|
||||
FILE_SCHEMA(('IFC2X3'));
|
||||
ENDSEC;
|
||||
|
||||
DATA;
|
||||
#1= IFCACTORROLE(.USERDEFINED.,'Ark:',$);
|
||||
#2= IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Arkitektveien 19'),$,'Arkitektbyen',$,'0000',$);
|
||||
#6= IFCTELECOMADDRESS(.USERDEFINED.,$,'Architect Telecom Address',('000 000 00'),$,$,('arkitekt@arkitektfirma.no'),'www.arkitektfirma.no');
|
||||
#9= IFCPERSON($,$,'Arkitekten',$,$,$,(#1),(#2,#6));
|
||||
#15= IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Arkitektveien 19'),$,'Arkitektbyen',$,'0000',$);
|
||||
#17= IFCTELECOMADDRESS(.USERDEFINED.,$,'Architect Telecom Address',('000 000 00'),$,$,('arkitekt@arkitektfirma.no'),'www.arkitektfirma.no');
|
||||
#20= IFCORGANIZATION($,'Arkitektkontoret',$,$,(#15,#17));
|
||||
#27= IFCPERSONANDORGANIZATION(#9,#20,$);
|
||||
#30= IFCORGANIZATION('GS','GRAPHISOFT','GRAPHISOFT',$,$);
|
||||
#31= IFCAPPLICATION(#30,'25.0.0','ARCHICAD','IFC add-on version: 3002 NOR FULL');
|
||||
#32= IFCOWNERHISTORY(#27,#31,$,.NOCHANGE.,$,$,$,1693857534);
|
||||
#33= IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
|
||||
#34= IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#35= IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#36= IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#37= IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.0174532925199),#36);
|
||||
#38= IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#39= IFCCONVERSIONBASEDUNIT(#38,.PLANEANGLEUNIT.,'DEGREE',#37);
|
||||
#40= IFCSIUNIT(*,.SOLIDANGLEUNIT.,$,.STERADIAN.);
|
||||
#41= IFCMEASUREWITHUNIT(IFCPOSITIVELENGTHMEASURE(0.000304617419787),#40);
|
||||
#42= IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#43= IFCCONVERSIONBASEDUNIT(#42,.SOLIDANGLEUNIT.,'SQUAREDEGREE',#41);
|
||||
#44= IFCMONETARYUNIT(.NOK.);
|
||||
#45= IFCSIUNIT(*,.TIMEUNIT.,$,.SECOND.);
|
||||
#46= IFCMEASUREWITHUNIT(IFCTIMEMEASURE(31556926.),#45);
|
||||
#47= IFCDIMENSIONALEXPONENTS(0,0,1,0,0,0,0);
|
||||
#48= IFCCONVERSIONBASEDUNIT(#47,.TIMEUNIT.,'Year',#46);
|
||||
#49= IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.);
|
||||
#50= IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.DEGREE_CELSIUS.);
|
||||
#51= IFCSIUNIT(*,.LUMINOUSINTENSITYUNIT.,$,.LUMEN.);
|
||||
#52= IFCSIUNIT(*,.ENERGYUNIT.,.MEGA.,.JOULE.);
|
||||
#53= IFCDERIVEDUNIT((#56,#58,#60),.THERMALCONDUCTANCEUNIT.,$);
|
||||
#55= IFCSIUNIT(*,.POWERUNIT.,$,.WATT.);
|
||||
#56= IFCDERIVEDUNITELEMENT(#55,1);
|
||||
#57= IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#58= IFCDERIVEDUNITELEMENT(#57,-1);
|
||||
#59= IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.KELVIN.);
|
||||
#60= IFCDERIVEDUNITELEMENT(#59,-1);
|
||||
#61= IFCDERIVEDUNIT((#64,#66,#68),.SPECIFICHEATCAPACITYUNIT.,$);
|
||||
#63= IFCSIUNIT(*,.ENERGYUNIT.,$,.JOULE.);
|
||||
#64= IFCDERIVEDUNITELEMENT(#63,1);
|
||||
#65= IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.);
|
||||
#66= IFCDERIVEDUNITELEMENT(#65,-1);
|
||||
#67= IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.KELVIN.);
|
||||
#68= IFCDERIVEDUNITELEMENT(#67,-1);
|
||||
#69= IFCDERIVEDUNIT((#72,#74),.MASSDENSITYUNIT.,$);
|
||||
#71= IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.);
|
||||
#72= IFCDERIVEDUNITELEMENT(#71,1);
|
||||
#73= IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#74= IFCDERIVEDUNITELEMENT(#73,-1);
|
||||
#75= IFCUNITASSIGNMENT((#33,#34,#35,#39,#43,#44,#48,#49,#50,#51,#52,#53,#61,#69));
|
||||
#77= IFCDIRECTION((1.,0.,0.));
|
||||
#81= IFCDIRECTION((0.,0.,1.));
|
||||
#83= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#85= IFCAXIS2PLACEMENT3D(#83,#81,#77);
|
||||
#86= IFCDIRECTION((0.,1.));
|
||||
#88= IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,0.01,#85,#86);
|
||||
#91= IFCPROJECT('1CGyzxdzHaJybf9L4wHy3R',#32,'Prosjektnavn','Prosjektbeskrivelse',$,$,'Fase',(#88),#75);
|
||||
#98= IFCPOSTALADDRESS($,$,$,$,('Adresse'),'#','Sted',$,'0000','Norge');
|
||||
#100= IFCDIRECTION((1.,0.,0.));
|
||||
#102= IFCDIRECTION((0.,0.,1.));
|
||||
#104= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#106= IFCAXIS2PLACEMENT3D(#104,#102,#100);
|
||||
#107= IFCLOCALPLACEMENT($,#106);
|
||||
#110= IFCSITE('01uPthe3Uixx0TRWar3Vt8',#32,'Eiendomsnavn',$,$,#107,$,$,.ELEMENT.,(59,55,52,428000),(10,42,27,972000),0.,'',#98);
|
||||
#116= IFCRELAGGREGATES('2F2UAtyl4uWjrKORQfJcEW',#32,$,$,#91,(#110));
|
||||
#122= IFCQUANTITYLENGTH('GrossPerimeter',$,$,0.);
|
||||
#124= IFCQUANTITYAREA('GrossArea',$,$,0.);
|
||||
#125= IFCELEMENTQUANTITY('17rUPIjEMxVrkRJ11moELl',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#122,#124));
|
||||
#130= IFCRELDEFINESBYPROPERTIES('0l65N8yZCPuwGi0lRf7Opq',#32,$,$,(#110),#125);
|
||||
#134= IFCPOSTALADDRESS($,$,$,$,('Adresse'),'#','Sted',$,'0000','Norge');
|
||||
#136= IFCDIRECTION((1.,0.,0.));
|
||||
#138= IFCDIRECTION((0.,0.,1.));
|
||||
#140= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#142= IFCAXIS2PLACEMENT3D(#140,#138,#136);
|
||||
#143= IFCLOCALPLACEMENT(#107,#142);
|
||||
#145= IFCBUILDING('3AMeTOOFsdVLZuNV0$27s5',#32,'Bygningens navn','Bygningstype',$,#143,$,'Byggnummer',.ELEMENT.,$,$,#134);
|
||||
#147= IFCRELAGGREGATES('0bntSkh7UDU_vAHXbaggwi',#32,$,$,#110,(#145));
|
||||
#151= IFCQUANTITYAREA('GrossFloorArea',$,$,0.);
|
||||
#152= IFCELEMENTQUANTITY('3GDAgT3z640iFgZCkp8utf',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#151));
|
||||
#154= IFCRELDEFINESBYPROPERTIES('3joCDTcjDP3x5zmHkLS3S0',#32,$,$,(#145),#152);
|
||||
#158= IFCDIRECTION((1.,0.,0.));
|
||||
#160= IFCDIRECTION((0.,0.,1.));
|
||||
#162= IFCCARTESIANPOINT((0.,0.,1000.));
|
||||
#164= IFCAXIS2PLACEMENT3D(#162,#160,#158);
|
||||
#165= IFCLOCALPLACEMENT(#143,#164);
|
||||
#167= IFCBUILDINGSTOREY('0St1hMhFUHZyrAECcby7I2',#32,'1. etasje',$,$,#165,$,$,.ELEMENT.,1000.);
|
||||
#169= IFCRELAGGREGATES('0TRv38eowcVEYjXmnFlLK1',#32,$,$,#145,(#167));
|
||||
#173= IFCQUANTITYLENGTH('NetHeight',$,$,2700.);
|
||||
#174= IFCQUANTITYLENGTH('GrossHeight',$,$,2700.);
|
||||
#175= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#176= IFCQUANTITYAREA('GrossFloorArea',$,$,0.);
|
||||
#177= IFCELEMENTQUANTITY('0t$Y2LddO4Hpr3Foz8Be1$',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#173,#174,#175,#176));
|
||||
#179= IFCRELDEFINESBYPROPERTIES('0J4XjFI$kRh2sJoL52j_PV',#32,$,$,(#167),#177);
|
||||
#183= IFCDIRECTION((1.,0.,0.));
|
||||
#185= IFCDIRECTION((0.,0.,1.));
|
||||
#187= IFCCARTESIANPOINT((-4423.22691561,10230.4382609,0.));
|
||||
#189= IFCAXIS2PLACEMENT3D(#187,#185,#183);
|
||||
#190= IFCLOCALPLACEMENT(#165,#189);
|
||||
#192= IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#88,$,.MODEL_VIEW.,$);
|
||||
#194= IFCCARTESIANPOINT((0.,-300.));
|
||||
#196= IFCCARTESIANPOINT((8599.33565217,-300.));
|
||||
#198= IFCCARTESIANPOINT((8599.33565217,0.));
|
||||
#200= IFCCARTESIANPOINT((0.,0.));
|
||||
#202= IFCPOLYLINE((#194,#196,#198,#200,#194));
|
||||
#204= IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,'',#202);
|
||||
#205= IFCDIRECTION((1.,0.,0.));
|
||||
#207= IFCDIRECTION((0.,0.,1.));
|
||||
#209= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#211= IFCAXIS2PLACEMENT3D(#209,#207,#205);
|
||||
#212= IFCDIRECTION((0.,0.,1.));
|
||||
#214= IFCEXTRUDEDAREASOLID(#204,#211,#212,2700.);
|
||||
#215= IFCSHAPEREPRESENTATION(#192,'Body','SweptSolid',(#214));
|
||||
#222= IFCPRESENTATIONLAYERASSIGNMENT('230- Yttervegger (som generisk objekt eller for gruppering)',$,(#215,#232,#357,#366,#443,#452),$);
|
||||
#225= IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Axis','Model',*,*,*,*,#88,$,.MODEL_VIEW.,$);
|
||||
#226= IFCCARTESIANPOINT((0.,0.));
|
||||
#228= IFCCARTESIANPOINT((8599.33565217,0.));
|
||||
#230= IFCPOLYLINE((#226,#228));
|
||||
#232= IFCSHAPEREPRESENTATION(#225,'Axis','Curve2D',(#230));
|
||||
#236= IFCPRODUCTDEFINITIONSHAPE($,$,(#215,#232));
|
||||
#242= IFCWALLSTANDARDCASE('1Xezbq735AdBO_4DerG66G',#32,'YVT-A','',$,#190,#236,'61A3D974-1C31-4A9C-B63E-10DA35406190');
|
||||
#257= IFCRELCONTAINEDINSPATIALSTRUCTURE('0PUNS2OuLwupG5o8M6iDor',#32,$,$,(#242,#373,#459),#167);
|
||||
#261= IFCMATERIAL('Yttervegg');
|
||||
#264= IFCCOLOURRGB($,1.,1.,1.);
|
||||
#265= IFCSURFACESTYLERENDERING(#264,0.,IFCNORMALISEDRATIOMEASURE(0.3),$,$,$,IFCNORMALISEDRATIOMEASURE(0.69),$,.NOTDEFINED.);
|
||||
#266= IFCSURFACESTYLE('Maling - 01 Blank',.BOTH.,(#265));
|
||||
#268= IFCPRESENTATIONSTYLEASSIGNMENT((#266));
|
||||
#270= IFCSTYLEDITEM($,(#268),$);
|
||||
#272= IFCSTYLEDREPRESENTATION(#192,$,$,(#270));
|
||||
#274= IFCMATERIALDEFINITIONREPRESENTATION($,$,(#272),#261);
|
||||
#278= IFCMATERIALLAYER(#261,300.,.U.);
|
||||
#280= IFCMATERIALLAYERSET((#278),'Yttervegg 300');
|
||||
#283= IFCMATERIALLAYERSETUSAGE(#280,.AXIS2.,.NEGATIVE.,0.);
|
||||
#284= IFCRELASSOCIATESMATERIAL('2msyZzaqHpLqjyOSMDJ6bC',#32,$,$,(#242),#283);
|
||||
#287= IFCPROPERTYSINGLEVALUE('GTIN',$,IFCLABEL('3'),$);
|
||||
#291= IFCPROPERTYSET('2axpxSTK16Nv0$I70apGdK',#32,'Reuse',$,(#287));
|
||||
#293= IFCRELDEFINESBYPROPERTIES('36a5T_GLs751QxsAvh1iEB',#32,$,$,(#242),#291);
|
||||
#297= IFCPROPERTYSINGLEVALUE('Renovation Status',$,IFCLABEL('New'),$);
|
||||
#298= IFCPROPERTYSET('04Pv5xI9$vG1lZdN7wMAEc',#32,'AC_Pset_RenovationAndPhasing',$,(#297));
|
||||
#300= IFCRELDEFINESBYPROPERTIES('2AvYgZFQ$eywkTT_yzugG4',#32,$,$,(#242),#298);
|
||||
#303= IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.T.),$);
|
||||
#304= IFCPROPERTYSET('3vuFSkrI50S$hRePMCpJTW',#32,'Pset_WallCommon',$,(#303));
|
||||
#306= IFCRELDEFINESBYPROPERTIES('3eVxLUBuO61JJXioN57x5f',#32,$,$,(#242),#304);
|
||||
#309= IFCQUANTITYLENGTH('Length',$,$,8599.33565217);
|
||||
#310= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#311= IFCQUANTITYLENGTH('Width',$,$,300.);
|
||||
#312= IFCQUANTITYAREA('GrossFootprintArea',$,$,2.57980069565);
|
||||
#313= IFCQUANTITYAREA('NetFootprintArea',$,$,2.57980069565);
|
||||
#314= IFCQUANTITYAREA('GrossSideArea',$,$,23.2182062609);
|
||||
#315= IFCQUANTITYAREA('NetSideArea',$,$,23.2182062609);
|
||||
#316= IFCQUANTITYVOLUME('GrossVolume',$,$,6.96546187826);
|
||||
#317= IFCQUANTITYVOLUME('NetVolume',$,$,6.96546187826);
|
||||
#318= IFCELEMENTQUANTITY('1uJa0ITKB1qrAAf8WtmaZ2',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#309,#310,#311,#312,#313,#314,#315,#316,#317));
|
||||
#320= IFCRELDEFINESBYPROPERTIES('3eMBYSfULCCgJUrbtWuHgF',#32,$,$,(#242),#318);
|
||||
#323= IFCWALLTYPE('2V6t5i6hK0w_M3VKaOTPG8',#32,'Yttervegg 300',$,$,$,$,'9F1B716C-1AB5-00EB-E583-7D4918759408',$,.NOTDEFINED.);
|
||||
#325= IFCRELDEFINESBYTYPE('0GIkSqbN66cNEifXw6t8R_',#32,$,$,(#242,#373,#459),#323);
|
||||
#328= IFCDIRECTION((1.,0.,0.));
|
||||
#330= IFCDIRECTION((0.,0.,1.));
|
||||
#332= IFCCARTESIANPOINT((-3058.25300257,12869.3878261,0.));
|
||||
#334= IFCAXIS2PLACEMENT3D(#332,#330,#328);
|
||||
#335= IFCLOCALPLACEMENT(#165,#334);
|
||||
#336= IFCCARTESIANPOINT((0.,-300.));
|
||||
#338= IFCCARTESIANPOINT((8508.3373913,-300.));
|
||||
#340= IFCCARTESIANPOINT((8508.3373913,0.));
|
||||
#342= IFCCARTESIANPOINT((0.,0.));
|
||||
#344= IFCPOLYLINE((#336,#338,#340,#342,#336));
|
||||
#346= IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,'',#344);
|
||||
#347= IFCDIRECTION((1.,0.,0.));
|
||||
#349= IFCDIRECTION((0.,0.,1.));
|
||||
#351= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#353= IFCAXIS2PLACEMENT3D(#351,#349,#347);
|
||||
#354= IFCDIRECTION((0.,0.,1.));
|
||||
#356= IFCEXTRUDEDAREASOLID(#346,#353,#354,2700.);
|
||||
#357= IFCSHAPEREPRESENTATION(#192,'Body','SweptSolid',(#356));
|
||||
#360= IFCCARTESIANPOINT((0.,0.));
|
||||
#362= IFCCARTESIANPOINT((8508.3373913,0.));
|
||||
#364= IFCPOLYLINE((#360,#362));
|
||||
#366= IFCSHAPEREPRESENTATION(#225,'Axis','Curve2D',(#364));
|
||||
#369= IFCPRODUCTDEFINITIONSHAPE($,$,(#357,#366));
|
||||
#373= IFCWALLSTANDARDCASE('3eT96F_cnDsOufCEsotwtp',#32,'YVT-B','',$,#335,#369,'E874918F-FA6C-4DD9-8E29-30EDB2DFADF3');
|
||||
#377= IFCMATERIALLAYERSETUSAGE(#280,.AXIS2.,.NEGATIVE.,0.);
|
||||
#378= IFCRELASSOCIATESMATERIAL('1iVlD94vOmLa5OpxnkCqlJ',#32,$,$,(#373),#377);
|
||||
#381= IFCPROPERTYSINGLEVALUE('GTIN',$,IFCLABEL('2'),$);
|
||||
#382= IFCPROPERTYSET('3lu_1RsD2PskLfQWcCOLU3',#32,'Reuse',$,(#381));
|
||||
#384= IFCRELDEFINESBYPROPERTIES('1PufL50Yxw16Mg0x9OekT1',#32,$,$,(#373),#382);
|
||||
#388= IFCPROPERTYSINGLEVALUE('Renovation Status',$,IFCLABEL('New'),$);
|
||||
#389= IFCPROPERTYSET('0221Oat$Xdl8OooXaoH0Gp',#32,'AC_Pset_RenovationAndPhasing',$,(#388));
|
||||
#391= IFCRELDEFINESBYPROPERTIES('3OX_1Z$WvLZFlLD1nCbhRr',#32,$,$,(#373),#389);
|
||||
#394= IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.T.),$);
|
||||
#395= IFCPROPERTYSET('1T4$VD7m_rVNvlN5DrtbUq',#32,'Pset_WallCommon',$,(#394));
|
||||
#397= IFCRELDEFINESBYPROPERTIES('1v2J9jM6ihU_rOwjxcKYIn',#32,$,$,(#373),#395);
|
||||
#400= IFCQUANTITYLENGTH('Length',$,$,8508.3373913);
|
||||
#401= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#402= IFCQUANTITYLENGTH('Width',$,$,300.);
|
||||
#403= IFCQUANTITYAREA('GrossFootprintArea',$,$,2.55250121739);
|
||||
#404= IFCQUANTITYAREA('NetFootprintArea',$,$,2.55250121739);
|
||||
#405= IFCQUANTITYAREA('GrossSideArea',$,$,22.9725109565);
|
||||
#406= IFCQUANTITYAREA('NetSideArea',$,$,22.9725109565);
|
||||
#407= IFCQUANTITYVOLUME('GrossVolume',$,$,6.89175328696);
|
||||
#408= IFCQUANTITYVOLUME('NetVolume',$,$,6.89175328696);
|
||||
#409= IFCELEMENTQUANTITY('09pcUknANnc6EQrUWIhXdt',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#400,#401,#402,#403,#404,#405,#406,#407,#408));
|
||||
#411= IFCRELDEFINESBYPROPERTIES('2zE052B_urXe2bvDBLgYv1',#32,$,$,(#373),#409);
|
||||
#414= IFCDIRECTION((1.,0.,0.));
|
||||
#416= IFCDIRECTION((0.,0.,1.));
|
||||
#418= IFCCARTESIANPOINT((35.6878669942,15007.8469565,0.));
|
||||
#420= IFCAXIS2PLACEMENT3D(#418,#416,#414);
|
||||
#421= IFCLOCALPLACEMENT(#165,#420);
|
||||
#422= IFCCARTESIANPOINT((0.,-300.));
|
||||
#424= IFCCARTESIANPOINT((7234.36173913,-300.));
|
||||
#426= IFCCARTESIANPOINT((7234.36173913,0.));
|
||||
#428= IFCCARTESIANPOINT((0.,0.));
|
||||
#430= IFCPOLYLINE((#422,#424,#426,#428,#422));
|
||||
#432= IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,'',#430);
|
||||
#433= IFCDIRECTION((1.,0.,0.));
|
||||
#435= IFCDIRECTION((0.,0.,1.));
|
||||
#437= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#439= IFCAXIS2PLACEMENT3D(#437,#435,#433);
|
||||
#440= IFCDIRECTION((0.,0.,1.));
|
||||
#442= IFCEXTRUDEDAREASOLID(#432,#439,#440,2700.);
|
||||
#443= IFCSHAPEREPRESENTATION(#192,'Body','SweptSolid',(#442));
|
||||
#446= IFCCARTESIANPOINT((0.,0.));
|
||||
#448= IFCCARTESIANPOINT((7234.36173913,0.));
|
||||
#450= IFCPOLYLINE((#446,#448));
|
||||
#452= IFCSHAPEREPRESENTATION(#225,'Axis','Curve2D',(#450));
|
||||
#455= IFCPRODUCTDEFINITIONSHAPE($,$,(#443,#452));
|
||||
#459= IFCWALLSTANDARDCASE('3k3LbXmTfDWODxdSuyCGYE',#32,'YVT-C','',$,#421,#455,'EE0D5961-C1DA-4D81-837B-9DCE3C31088E');
|
||||
#463= IFCMATERIALLAYERSETUSAGE(#280,.AXIS2.,.NEGATIVE.,0.);
|
||||
#464= IFCRELASSOCIATESMATERIAL('3_dZLENyJ5_qngksl2ie6r',#32,$,$,(#459),#463);
|
||||
#467= IFCPROPERTYSINGLEVALUE('GTIN',$,IFCLABEL('1'),$);
|
||||
#468= IFCPROPERTYSET('342IhYDnCDoqxc_Zm6g_fw',#32,'Reuse',$,(#467));
|
||||
#470= IFCRELDEFINESBYPROPERTIES('0KYu05w72mseS$C1XTnSzM',#32,$,$,(#459),#468);
|
||||
#474= IFCPROPERTYSINGLEVALUE('Renovation Status',$,IFCLABEL('New'),$);
|
||||
#475= IFCPROPERTYSET('2POedyNP84Nk7S4D$CmpS2',#32,'AC_Pset_RenovationAndPhasing',$,(#474));
|
||||
#477= IFCRELDEFINESBYPROPERTIES('2oUR0$VhKaEPV9FW3eb0G3',#32,$,$,(#459),#475);
|
||||
#480= IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.T.),$);
|
||||
#481= IFCPROPERTYSET('2IgxHtV7Xo2x_LGYkyiDI6',#32,'Pset_WallCommon',$,(#480));
|
||||
#483= IFCRELDEFINESBYPROPERTIES('2d$ext$iF9gxEs6l5zaPYh',#32,$,$,(#459),#481);
|
||||
#486= IFCQUANTITYLENGTH('Length',$,$,7234.36173913);
|
||||
#487= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#488= IFCQUANTITYLENGTH('Width',$,$,300.);
|
||||
#489= IFCQUANTITYAREA('GrossFootprintArea',$,$,2.17030852174);
|
||||
#490= IFCQUANTITYAREA('NetFootprintArea',$,$,2.17030852174);
|
||||
#491= IFCQUANTITYAREA('GrossSideArea',$,$,19.5327766957);
|
||||
#492= IFCQUANTITYAREA('NetSideArea',$,$,19.5327766957);
|
||||
#493= IFCQUANTITYVOLUME('GrossVolume',$,$,5.8598330087);
|
||||
#494= IFCQUANTITYVOLUME('NetVolume',$,$,5.8598330087);
|
||||
#495= IFCELEMENTQUANTITY('1LnynQ0O$3P0FeM4JPb_Ao',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#486,#487,#488,#489,#490,#491,#492,#493,#494));
|
||||
#497= IFCRELDEFINESBYPROPERTIES('14UKMwdhaOGUYk9crv2_wD',#32,$,$,(#459),#495);
|
||||
ENDSEC;
|
||||
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,258 @@
|
||||
ISO-10303-21;
|
||||
HEADER;FILE_DESCRIPTION(('ViewDefinition [CoordinationView_V2.0, QuantityTakeOffAddOnView, SpaceBoundary2ndLevelAddOnView]','Option [Elements to export: Selected elements only]','Option [Partial Structure Display: Entire Model]','Option [IFC Domain: All]','Option [Structural Function: All Elements]','Option [Convert Grid elements: On]','Option [Convert IFC Annotations and ARCHICAD 2D elements: Off]','Option [Convert 2D symbols of Doors and Windows: On]','Option [Export geometries that Participates in Collision Detection only: Off]','Option [Split complex elements: Off]','Option [Material Preservation: Explode where necessary]','Option [Elements in Solid Element Operations: Extruded/revolved]','Option [Elements with junctions: Extruded/revolved without junctions]','Option [IFC Site Location: At Project Origin]','Option [Curtain Wall export mode: Container Element]','Option [Railing export mode: Single Element]','Option [Stair export mode: Single Element]','Option [Properties To Export: All properties]','Option [Space containment: Off]','Option [Bounding Box: Off]','Option [Geometry to type objects: Off]','Option [Element Properties: Off]','Option [Building Material Properties: Off]','Option [Element Parameters: Off]','Option [Component Parameters: Off]','Option [IFC Base Quantities: On]','Option [Door Window Parameters: Off]','Option [IFC Space boundaries: On]','Option [ARCHICAD Zone Categories as IFC Space classification data: On]','Option [Element Classifications: Off]'),'2;1');
|
||||
FILE_NAME('C:\\Users\\Yoga\\Desktop\\test.ifc','2023-09-04T21:58:54',('Arkitekten'),('Arkitektkontoret'),'The EXPRESS Data Manager Version 5.02.0100.09 : 26 Sep 2013','IFC file generated by GRAPHISOFT ARCHICAD 25.0.0 NOR FULL Windows version (IFC add-on version: 3002 NOR FULL).','Arkitekten');
|
||||
FILE_SCHEMA(('IFC2X3'));
|
||||
ENDSEC;
|
||||
|
||||
DATA;
|
||||
#1= IFCACTORROLE(.USERDEFINED.,'Ark:',$);
|
||||
#2= IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Arkitektveien 19'),$,'Arkitektbyen',$,'0000',$);
|
||||
#6= IFCTELECOMADDRESS(.USERDEFINED.,$,'Architect Telecom Address',('000 000 00'),$,$,('arkitekt@arkitektfirma.no'),'www.arkitektfirma.no');
|
||||
#9= IFCPERSON($,$,'Arkitekten',$,$,$,(#1),(#2,#6));
|
||||
#15= IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Arkitektveien 19'),$,'Arkitektbyen',$,'0000',$);
|
||||
#17= IFCTELECOMADDRESS(.USERDEFINED.,$,'Architect Telecom Address',('000 000 00'),$,$,('arkitekt@arkitektfirma.no'),'www.arkitektfirma.no');
|
||||
#20= IFCORGANIZATION($,'Arkitektkontoret',$,$,(#15,#17));
|
||||
#27= IFCPERSONANDORGANIZATION(#9,#20,$);
|
||||
#30= IFCORGANIZATION('GS','GRAPHISOFT','GRAPHISOFT',$,$);
|
||||
#31= IFCAPPLICATION(#30,'25.0.0','ARCHICAD','IFC add-on version: 3002 NOR FULL');
|
||||
#32= IFCOWNERHISTORY(#27,#31,$,.NOCHANGE.,$,$,$,1693857534);
|
||||
#33= IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
|
||||
#34= IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#35= IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#36= IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#37= IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.0174532925199),#36);
|
||||
#38= IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#39= IFCCONVERSIONBASEDUNIT(#38,.PLANEANGLEUNIT.,'DEGREE',#37);
|
||||
#40= IFCSIUNIT(*,.SOLIDANGLEUNIT.,$,.STERADIAN.);
|
||||
#41= IFCMEASUREWITHUNIT(IFCPOSITIVELENGTHMEASURE(0.000304617419787),#40);
|
||||
#42= IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#43= IFCCONVERSIONBASEDUNIT(#42,.SOLIDANGLEUNIT.,'SQUAREDEGREE',#41);
|
||||
#44= IFCMONETARYUNIT(.NOK.);
|
||||
#45= IFCSIUNIT(*,.TIMEUNIT.,$,.SECOND.);
|
||||
#46= IFCMEASUREWITHUNIT(IFCTIMEMEASURE(31556926.),#45);
|
||||
#47= IFCDIMENSIONALEXPONENTS(0,0,1,0,0,0,0);
|
||||
#48= IFCCONVERSIONBASEDUNIT(#47,.TIMEUNIT.,'Year',#46);
|
||||
#49= IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.);
|
||||
#50= IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.DEGREE_CELSIUS.);
|
||||
#51= IFCSIUNIT(*,.LUMINOUSINTENSITYUNIT.,$,.LUMEN.);
|
||||
#52= IFCSIUNIT(*,.ENERGYUNIT.,.MEGA.,.JOULE.);
|
||||
#53= IFCDERIVEDUNIT((#56,#58,#60),.THERMALCONDUCTANCEUNIT.,$);
|
||||
#55= IFCSIUNIT(*,.POWERUNIT.,$,.WATT.);
|
||||
#56= IFCDERIVEDUNITELEMENT(#55,1);
|
||||
#57= IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#58= IFCDERIVEDUNITELEMENT(#57,-1);
|
||||
#59= IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.KELVIN.);
|
||||
#60= IFCDERIVEDUNITELEMENT(#59,-1);
|
||||
#61= IFCDERIVEDUNIT((#64,#66,#68),.SPECIFICHEATCAPACITYUNIT.,$);
|
||||
#63= IFCSIUNIT(*,.ENERGYUNIT.,$,.JOULE.);
|
||||
#64= IFCDERIVEDUNITELEMENT(#63,1);
|
||||
#65= IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.);
|
||||
#66= IFCDERIVEDUNITELEMENT(#65,-1);
|
||||
#67= IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.KELVIN.);
|
||||
#68= IFCDERIVEDUNITELEMENT(#67,-1);
|
||||
#69= IFCDERIVEDUNIT((#72,#74),.MASSDENSITYUNIT.,$);
|
||||
#71= IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.);
|
||||
#72= IFCDERIVEDUNITELEMENT(#71,1);
|
||||
#73= IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#74= IFCDERIVEDUNITELEMENT(#73,-1);
|
||||
#75= IFCUNITASSIGNMENT((#33,#34,#35,#39,#43,#44,#48,#49,#50,#51,#52,#53,#61,#69));
|
||||
#77= IFCDIRECTION((1.,0.,0.));
|
||||
#81= IFCDIRECTION((0.,0.,1.));
|
||||
#83= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#85= IFCAXIS2PLACEMENT3D(#83,#81,#77);
|
||||
#86= IFCDIRECTION((0.,1.));
|
||||
#88= IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,0.01,#85,#86);
|
||||
#91= IFCPROJECT('1CGyzxdzHaJybf9L4wHy3R',#32,'Prosjektnavn','Prosjektbeskrivelse',$,$,'Fase',(#88),#75);
|
||||
#98= IFCPOSTALADDRESS($,$,$,$,('Adresse'),'#','Sted',$,'0000','Norge');
|
||||
#100= IFCDIRECTION((1.,0.,0.));
|
||||
#102= IFCDIRECTION((0.,0.,1.));
|
||||
#104= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#106= IFCAXIS2PLACEMENT3D(#104,#102,#100);
|
||||
#107= IFCLOCALPLACEMENT($,#106);
|
||||
#110= IFCSITE('01uPthe3Uixx0TRWar3Vt8',#32,'Eiendomsnavn',$,$,#107,$,$,.ELEMENT.,(59,55,52,428000),(10,42,27,972000),0.,'',#98);
|
||||
#116= IFCRELAGGREGATES('2F2UAtyl4uWjrKORQfJcEW',#32,$,$,#91,(#110));
|
||||
#122= IFCQUANTITYLENGTH('GrossPerimeter',$,$,0.);
|
||||
#124= IFCQUANTITYAREA('GrossArea',$,$,0.);
|
||||
#125= IFCELEMENTQUANTITY('17rUPIjEMxVrkRJ11moELl',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#122,#124));
|
||||
#130= IFCRELDEFINESBYPROPERTIES('0l65N8yZCPuwGi0lRf7Opq',#32,$,$,(#110),#125);
|
||||
#134= IFCPOSTALADDRESS($,$,$,$,('Adresse'),'#','Sted',$,'0000','Norge');
|
||||
#136= IFCDIRECTION((1.,0.,0.));
|
||||
#138= IFCDIRECTION((0.,0.,1.));
|
||||
#140= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#142= IFCAXIS2PLACEMENT3D(#140,#138,#136);
|
||||
#143= IFCLOCALPLACEMENT(#107,#142);
|
||||
#145= IFCBUILDING('3AMeTOOFsdVLZuNV0$27s5',#32,'Bygningens navn','Bygningstype',$,#143,$,'Byggnummer',.ELEMENT.,$,$,#134);
|
||||
#147= IFCRELAGGREGATES('0bntSkh7UDU_vAHXbaggwi',#32,$,$,#110,(#145));
|
||||
#151= IFCQUANTITYAREA('GrossFloorArea',$,$,0.);
|
||||
#152= IFCELEMENTQUANTITY('3GDAgT3z640iFgZCkp8utf',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#151));
|
||||
#154= IFCRELDEFINESBYPROPERTIES('3joCDTcjDP3x5zmHkLS3S0',#32,$,$,(#145),#152);
|
||||
#158= IFCDIRECTION((1.,0.,0.));
|
||||
#160= IFCDIRECTION((0.,0.,1.));
|
||||
#162= IFCCARTESIANPOINT((0.,0.,1000.));
|
||||
#164= IFCAXIS2PLACEMENT3D(#162,#160,#158);
|
||||
#165= IFCLOCALPLACEMENT(#143,#164);
|
||||
#167= IFCBUILDINGSTOREY('0St1hMhFUHZyrAECcby7I2',#32,'1. etasje',$,$,#165,$,$,.ELEMENT.,1000.);
|
||||
#169= IFCRELAGGREGATES('0TRv38eowcVEYjXmnFlLK1',#32,$,$,#145,(#167));
|
||||
#173= IFCQUANTITYLENGTH('NetHeight',$,$,2700.);
|
||||
#174= IFCQUANTITYLENGTH('GrossHeight',$,$,2700.);
|
||||
#175= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#176= IFCQUANTITYAREA('GrossFloorArea',$,$,0.);
|
||||
#177= IFCELEMENTQUANTITY('0t$Y2LddO4Hpr3Foz8Be1$',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#173,#174,#175,#176));
|
||||
#179= IFCRELDEFINESBYPROPERTIES('0J4XjFI$kRh2sJoL52j_PV',#32,$,$,(#167),#177);
|
||||
#183= IFCDIRECTION((1.,0.,0.));
|
||||
#185= IFCDIRECTION((0.,0.,1.));
|
||||
#187= IFCCARTESIANPOINT((-4423.22691561,10230.4382609,0.));
|
||||
#189= IFCAXIS2PLACEMENT3D(#187,#185,#183);
|
||||
#190= IFCLOCALPLACEMENT(#165,#189);
|
||||
#192= IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#88,$,.MODEL_VIEW.,$);
|
||||
#194= IFCCARTESIANPOINT((0.,-300.));
|
||||
#196= IFCCARTESIANPOINT((8599.33565217,-300.));
|
||||
#198= IFCCARTESIANPOINT((8599.33565217,0.));
|
||||
#200= IFCCARTESIANPOINT((0.,0.));
|
||||
#202= IFCPOLYLINE((#194,#196,#198,#200,#194));
|
||||
#204= IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,'',#202);
|
||||
#205= IFCDIRECTION((1.,0.,0.));
|
||||
#207= IFCDIRECTION((0.,0.,1.));
|
||||
#209= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#211= IFCAXIS2PLACEMENT3D(#209,#207,#205);
|
||||
#212= IFCDIRECTION((0.,0.,1.));
|
||||
#214= IFCEXTRUDEDAREASOLID(#204,#211,#212,2700.);
|
||||
#215= IFCSHAPEREPRESENTATION(#192,'Body','SweptSolid',(#214));
|
||||
#222= IFCPRESENTATIONLAYERASSIGNMENT('230- Yttervegger (som generisk objekt eller for gruppering)',$,(#215,#232,#357,#366,#443,#452),$);
|
||||
#225= IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Axis','Model',*,*,*,*,#88,$,.MODEL_VIEW.,$);
|
||||
#226= IFCCARTESIANPOINT((0.,0.));
|
||||
#228= IFCCARTESIANPOINT((8599.33565217,0.));
|
||||
#230= IFCPOLYLINE((#226,#228));
|
||||
#232= IFCSHAPEREPRESENTATION(#225,'Axis','Curve2D',(#230));
|
||||
#236= IFCPRODUCTDEFINITIONSHAPE($,$,(#215,#232));
|
||||
#242= IFCWALLSTANDARDCASE('1Xezbq735AdBO_4DerG66G',#32,'YVT-A','',$,#190,#236,'61A3D974-1C31-4A9C-B63E-10DA35406190');
|
||||
#257= IFCRELCONTAINEDINSPATIALSTRUCTURE('0PUNS2OuLwupG5o8M6iDor',#32,$,$,(#242,#373,#459),#167);
|
||||
#261= IFCMATERIAL('Yttervegg');
|
||||
#264= IFCCOLOURRGB($,1.,1.,1.);
|
||||
#265= IFCSURFACESTYLERENDERING(#264,0.,IFCNORMALISEDRATIOMEASURE(0.3),$,$,$,IFCNORMALISEDRATIOMEASURE(0.69),$,.NOTDEFINED.);
|
||||
#266= IFCSURFACESTYLE('Maling - 01 Blank',.BOTH.,(#265));
|
||||
#268= IFCPRESENTATIONSTYLEASSIGNMENT((#266));
|
||||
#270= IFCSTYLEDITEM($,(#268),$);
|
||||
#272= IFCSTYLEDREPRESENTATION(#192,$,$,(#270));
|
||||
#274= IFCMATERIALDEFINITIONREPRESENTATION($,$,(#272),#261);
|
||||
#278= IFCMATERIALLAYER(#261,300.,.U.);
|
||||
#280= IFCMATERIALLAYERSET((#278),'Yttervegg 300');
|
||||
#283= IFCMATERIALLAYERSETUSAGE(#280,.AXIS2.,.NEGATIVE.,0.);
|
||||
#284= IFCRELASSOCIATESMATERIAL('2msyZzaqHpLqjyOSMDJ6bC',#32,$,$,(#242),#283);
|
||||
#287= IFCPROPERTYSINGLEVALUE('GTIN',$,IFCLABEL('3'),$);
|
||||
#291= IFCPROPERTYSET('2axpxSTK16Nv0$I70apGdK',#32,'Reuse',$,(#287));
|
||||
#293= IFCRELDEFINESBYPROPERTIES('36a5T_GLs751QxsAvh1iEB',#32,$,$,(#242),#291);
|
||||
#297= IFCPROPERTYSINGLEVALUE('Renovation Status',$,IFCLABEL('New'),$);
|
||||
#298= IFCPROPERTYSET('04Pv5xI9$vG1lZdN7wMAEc',#32,'AC_Pset_RenovationAndPhasing',$,(#297));
|
||||
#300= IFCRELDEFINESBYPROPERTIES('2AvYgZFQ$eywkTT_yzugG4',#32,$,$,(#242),#298);
|
||||
#303= IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.T.),$);
|
||||
#304= IFCPROPERTYSET('3vuFSkrI50S$hRePMCpJTW',#32,'Pset_WallCommon',$,(#303));
|
||||
#306= IFCRELDEFINESBYPROPERTIES('3eVxLUBuO61JJXioN57x5f',#32,$,$,(#242),#304);
|
||||
#309= IFCQUANTITYLENGTH('Length',$,$,8599.33565217);
|
||||
#310= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#311= IFCQUANTITYLENGTH('Width',$,$,300.);
|
||||
#312= IFCQUANTITYAREA('GrossFootprintArea',$,$,2.57980069565);
|
||||
#313= IFCQUANTITYAREA('NetFootprintArea',$,$,2.57980069565);
|
||||
#314= IFCQUANTITYAREA('GrossSideArea',$,$,23.2182062609);
|
||||
#315= IFCQUANTITYAREA('NetSideArea',$,$,23.2182062609);
|
||||
#316= IFCQUANTITYVOLUME('GrossVolume',$,$,6.96546187826);
|
||||
#317= IFCQUANTITYVOLUME('NetVolume',$,$,6.96546187826);
|
||||
#318= IFCELEMENTQUANTITY('1uJa0ITKB1qrAAf8WtmaZ2',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#309,#310,#311,#312,#313,#314,#315,#316,#317));
|
||||
#320= IFCRELDEFINESBYPROPERTIES('3eMBYSfULCCgJUrbtWuHgF',#32,$,$,(#242),#318);
|
||||
#323= IFCWALLTYPE('2V6t5i6hK0w_M3VKaOTPG8',#32,'Yttervegg 300',$,$,$,$,'9F1B716C-1AB5-00EB-E583-7D4918759408',$,.NOTDEFINED.);
|
||||
#325= IFCRELDEFINESBYTYPE('0GIkSqbN66cNEifXw6t8R_',#32,$,$,(#242,#373,#459),#323);
|
||||
#328= IFCDIRECTION((1.,0.,0.));
|
||||
#330= IFCDIRECTION((0.,0.,1.));
|
||||
#332= IFCCARTESIANPOINT((-3058.25300257,12869.3878261,0.));
|
||||
#334= IFCAXIS2PLACEMENT3D(#332,#330,#328);
|
||||
#335= IFCLOCALPLACEMENT(#165,#334);
|
||||
#336= IFCCARTESIANPOINT((0.,-300.));
|
||||
#338= IFCCARTESIANPOINT((8508.3373913,-300.));
|
||||
#340= IFCCARTESIANPOINT((8508.3373913,0.));
|
||||
#342= IFCCARTESIANPOINT((0.,0.));
|
||||
#344= IFCPOLYLINE((#336,#338,#340,#342,#336));
|
||||
#346= IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,'',#344);
|
||||
#347= IFCDIRECTION((1.,0.,0.));
|
||||
#349= IFCDIRECTION((0.,0.,1.));
|
||||
#351= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#353= IFCAXIS2PLACEMENT3D(#351,#349,#347);
|
||||
#354= IFCDIRECTION((0.,0.,1.));
|
||||
#356= IFCEXTRUDEDAREASOLID(#346,#353,#354,2700.);
|
||||
#357= IFCSHAPEREPRESENTATION(#192,'Body','SweptSolid',(#356));
|
||||
#360= IFCCARTESIANPOINT((0.,0.));
|
||||
#362= IFCCARTESIANPOINT((8508.3373913,0.));
|
||||
#364= IFCPOLYLINE((#360,#362));
|
||||
#366= IFCSHAPEREPRESENTATION(#225,'Axis','Curve2D',(#364));
|
||||
#369= IFCPRODUCTDEFINITIONSHAPE($,$,(#357,#366));
|
||||
#373= IFCWALLSTANDARDCASE('3eT96F_cnDsOufCEsotwtp',#32,'YVT-B','',$,#335,#369,'E874918F-FA6C-4DD9-8E29-30EDB2DFADF3');
|
||||
#377= IFCMATERIALLAYERSETUSAGE(#280,.AXIS2.,.NEGATIVE.,0.);
|
||||
#378= IFCRELASSOCIATESMATERIAL('1iVlD94vOmLa5OpxnkCqlJ',#32,$,$,(#373),#377);
|
||||
#381= IFCPROPERTYSINGLEVALUE('GTIN',$,IFCLABEL('2'),$);
|
||||
#382= IFCPROPERTYSET('3lu_1RsD2PskLfQWcCOLU3',#32,'Reuse',$,(#381));
|
||||
#384= IFCRELDEFINESBYPROPERTIES('1PufL50Yxw16Mg0x9OekT1',#32,$,$,(#373),#382);
|
||||
#388= IFCPROPERTYSINGLEVALUE('Renovation Status',$,IFCLABEL('New'),$);
|
||||
#389= IFCPROPERTYSET('0221Oat$Xdl8OooXaoH0Gp',#32,'AC_Pset_RenovationAndPhasing',$,(#388));
|
||||
#391= IFCRELDEFINESBYPROPERTIES('3OX_1Z$WvLZFlLD1nCbhRr',#32,$,$,(#373),#389);
|
||||
#394= IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.T.),$);
|
||||
#395= IFCPROPERTYSET('1T4$VD7m_rVNvlN5DrtbUq',#32,'Pset_WallCommon',$,(#394));
|
||||
#397= IFCRELDEFINESBYPROPERTIES('1v2J9jM6ihU_rOwjxcKYIn',#32,$,$,(#373),#395);
|
||||
#400= IFCQUANTITYLENGTH('Length',$,$,8508.3373913);
|
||||
#401= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#402= IFCQUANTITYLENGTH('Width',$,$,300.);
|
||||
#403= IFCQUANTITYAREA('GrossFootprintArea',$,$,2.55250121739);
|
||||
#404= IFCQUANTITYAREA('NetFootprintArea',$,$,2.55250121739);
|
||||
#405= IFCQUANTITYAREA('GrossSideArea',$,$,22.9725109565);
|
||||
#406= IFCQUANTITYAREA('NetSideArea',$,$,22.9725109565);
|
||||
#407= IFCQUANTITYVOLUME('GrossVolume',$,$,6.89175328696);
|
||||
#408= IFCQUANTITYVOLUME('NetVolume',$,$,6.89175328696);
|
||||
#409= IFCELEMENTQUANTITY('09pcUknANnc6EQrUWIhXdt',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#400,#401,#402,#403,#404,#405,#406,#407,#408));
|
||||
#411= IFCRELDEFINESBYPROPERTIES('2zE052B_urXe2bvDBLgYv1',#32,$,$,(#373),#409);
|
||||
#414= IFCDIRECTION((1.,0.,0.));
|
||||
#416= IFCDIRECTION((0.,0.,1.));
|
||||
#418= IFCCARTESIANPOINT((35.6878669942,15007.8469565,0.));
|
||||
#420= IFCAXIS2PLACEMENT3D(#418,#416,#414);
|
||||
#421= IFCLOCALPLACEMENT(#165,#420);
|
||||
#422= IFCCARTESIANPOINT((0.,-300.));
|
||||
#424= IFCCARTESIANPOINT((7234.36173913,-300.));
|
||||
#426= IFCCARTESIANPOINT((7234.36173913,0.));
|
||||
#428= IFCCARTESIANPOINT((0.,0.));
|
||||
#430= IFCPOLYLINE((#422,#424,#426,#428,#422));
|
||||
#432= IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,'',#430);
|
||||
#433= IFCDIRECTION((1.,0.,0.));
|
||||
#435= IFCDIRECTION((0.,0.,1.));
|
||||
#437= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#439= IFCAXIS2PLACEMENT3D(#437,#435,#433);
|
||||
#440= IFCDIRECTION((0.,0.,1.));
|
||||
#442= IFCEXTRUDEDAREASOLID(#432,#439,#440,2700.);
|
||||
#443= IFCSHAPEREPRESENTATION(#192,'Body','SweptSolid',(#442));
|
||||
#446= IFCCARTESIANPOINT((0.,0.));
|
||||
#448= IFCCARTESIANPOINT((7234.36173913,0.));
|
||||
#450= IFCPOLYLINE((#446,#448));
|
||||
#452= IFCSHAPEREPRESENTATION(#225,'Axis','Curve2D',(#450));
|
||||
#455= IFCPRODUCTDEFINITIONSHAPE($,$,(#443,#452));
|
||||
#459= IFCWALLSTANDARDCASE('3k3LbXmTfDWODxdSuyCGYE',#32,'YVT-C','',$,#421,#455,'EE0D5961-C1DA-4D81-837B-9DCE3C31088E');
|
||||
#463= IFCMATERIALLAYERSETUSAGE(#280,.AXIS2.,.NEGATIVE.,0.);
|
||||
#464= IFCRELASSOCIATESMATERIAL('3_dZLENyJ5_qngksl2ie6r',#32,$,$,(#459),#463);
|
||||
#467= IFCPROPERTYSINGLEVALUE('GTIN',$,IFCLABEL('1'),$);
|
||||
#468= IFCPROPERTYSET('342IhYDnCDoqxc_Zm6g_fw',#32,'Reuse',$,(#467));
|
||||
#470= IFCRELDEFINESBYPROPERTIES('0KYu05w72mseS$C1XTnSzM',#32,$,$,(#459),#468);
|
||||
#474= IFCPROPERTYSINGLEVALUE('Renovation Status',$,IFCLABEL('New'),$);
|
||||
#475= IFCPROPERTYSET('2POedyNP84Nk7S4D$CmpS2',#32,'AC_Pset_RenovationAndPhasing',$,(#474));
|
||||
#477= IFCRELDEFINESBYPROPERTIES('2oUR0$VhKaEPV9FW3eb0G3',#32,$,$,(#459),#475);
|
||||
#480= IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.T.),$);
|
||||
#481= IFCPROPERTYSET('2IgxHtV7Xo2x_LGYkyiDI6',#32,'Pset_WallCommon',$,(#480));
|
||||
#483= IFCRELDEFINESBYPROPERTIES('2d$ext$iF9gxEs6l5zaPYh',#32,$,$,(#459),#481);
|
||||
#486= IFCQUANTITYLENGTH('Length',$,$,7234.36173913);
|
||||
#487= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#488= IFCQUANTITYLENGTH('Width',$,$,300.);
|
||||
#489= IFCQUANTITYAREA('GrossFootprintArea',$,$,2.17030852174);
|
||||
#490= IFCQUANTITYAREA('NetFootprintArea',$,$,2.17030852174);
|
||||
#491= IFCQUANTITYAREA('GrossSideArea',$,$,19.5327766957);
|
||||
#492= IFCQUANTITYAREA('NetSideArea',$,$,19.5327766957);
|
||||
#493= IFCQUANTITYVOLUME('GrossVolume',$,$,5.8598330087);
|
||||
#494= IFCQUANTITYVOLUME('NetVolume',$,$,5.8598330087);
|
||||
#495= IFCELEMENTQUANTITY('1LnynQ0O$3P0FeM4JPb_Ao',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#486,#487,#488,#489,#490,#491,#492,#493,#494));
|
||||
#497= IFCRELDEFINESBYPROPERTIES('14UKMwdhaOGUYk9crv2_wD',#32,$,$,(#459),#495);
|
||||
ENDSEC;
|
||||
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,258 @@
|
||||
ISO-10303-21;
|
||||
HEADER;FILE_DESCRIPTION(('ViewDefinition [CoordinationView_V2.0, QuantityTakeOffAddOnView, SpaceBoundary2ndLevelAddOnView]','Option [Elements to export: Selected elements only]','Option [Partial Structure Display: Entire Model]','Option [IFC Domain: All]','Option [Structural Function: All Elements]','Option [Convert Grid elements: On]','Option [Convert IFC Annotations and ARCHICAD 2D elements: Off]','Option [Convert 2D symbols of Doors and Windows: On]','Option [Export geometries that Participates in Collision Detection only: Off]','Option [Split complex elements: Off]','Option [Material Preservation: Explode where necessary]','Option [Elements in Solid Element Operations: Extruded/revolved]','Option [Elements with junctions: Extruded/revolved without junctions]','Option [IFC Site Location: At Project Origin]','Option [Curtain Wall export mode: Container Element]','Option [Railing export mode: Single Element]','Option [Stair export mode: Single Element]','Option [Properties To Export: All properties]','Option [Space containment: Off]','Option [Bounding Box: Off]','Option [Geometry to type objects: Off]','Option [Element Properties: Off]','Option [Building Material Properties: Off]','Option [Element Parameters: Off]','Option [Component Parameters: Off]','Option [IFC Base Quantities: On]','Option [Door Window Parameters: Off]','Option [IFC Space boundaries: On]','Option [ARCHICAD Zone Categories as IFC Space classification data: On]','Option [Element Classifications: Off]'),'2;1');
|
||||
FILE_NAME('C:\\Users\\Yoga\\Desktop\\test.ifc','2023-09-04T21:58:54',('Arkitekten'),('Arkitektkontoret'),'The EXPRESS Data Manager Version 5.02.0100.09 : 26 Sep 2013','IFC file generated by GRAPHISOFT ARCHICAD 25.0.0 NOR FULL Windows version (IFC add-on version: 3002 NOR FULL).','Arkitekten');
|
||||
FILE_SCHEMA(('IFC2X3'));
|
||||
ENDSEC;
|
||||
|
||||
DATA;
|
||||
#1= IFCACTORROLE(.USERDEFINED.,'Ark:',$);
|
||||
#2= IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Arkitektveien 19'),$,'Arkitektbyen',$,'0000',$);
|
||||
#6= IFCTELECOMADDRESS(.USERDEFINED.,$,'Architect Telecom Address',('000 000 00'),$,$,('arkitekt@arkitektfirma.no'),'www.arkitektfirma.no');
|
||||
#9= IFCPERSON($,$,'Arkitekten',$,$,$,(#1),(#2,#6));
|
||||
#15= IFCPOSTALADDRESS(.USERDEFINED.,$,'Architect Postal Address',$,('Arkitektveien 19'),$,'Arkitektbyen',$,'0000',$);
|
||||
#17= IFCTELECOMADDRESS(.USERDEFINED.,$,'Architect Telecom Address',('000 000 00'),$,$,('arkitekt@arkitektfirma.no'),'www.arkitektfirma.no');
|
||||
#20= IFCORGANIZATION($,'Arkitektkontoret',$,$,(#15,#17));
|
||||
#27= IFCPERSONANDORGANIZATION(#9,#20,$);
|
||||
#30= IFCORGANIZATION('GS','GRAPHISOFT','GRAPHISOFT',$,$);
|
||||
#31= IFCAPPLICATION(#30,'25.0.0','ARCHICAD','IFC add-on version: 3002 NOR FULL');
|
||||
#32= IFCOWNERHISTORY(#27,#31,$,.NOCHANGE.,$,$,$,1693857534);
|
||||
#33= IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
|
||||
#34= IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
|
||||
#35= IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#36= IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
|
||||
#37= IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.0174532925199),#36);
|
||||
#38= IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#39= IFCCONVERSIONBASEDUNIT(#38,.PLANEANGLEUNIT.,'DEGREE',#37);
|
||||
#40= IFCSIUNIT(*,.SOLIDANGLEUNIT.,$,.STERADIAN.);
|
||||
#41= IFCMEASUREWITHUNIT(IFCPOSITIVELENGTHMEASURE(0.000304617419787),#40);
|
||||
#42= IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
|
||||
#43= IFCCONVERSIONBASEDUNIT(#42,.SOLIDANGLEUNIT.,'SQUAREDEGREE',#41);
|
||||
#44= IFCMONETARYUNIT(.NOK.);
|
||||
#45= IFCSIUNIT(*,.TIMEUNIT.,$,.SECOND.);
|
||||
#46= IFCMEASUREWITHUNIT(IFCTIMEMEASURE(31556926.),#45);
|
||||
#47= IFCDIMENSIONALEXPONENTS(0,0,1,0,0,0,0);
|
||||
#48= IFCCONVERSIONBASEDUNIT(#47,.TIMEUNIT.,'Year',#46);
|
||||
#49= IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.);
|
||||
#50= IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.DEGREE_CELSIUS.);
|
||||
#51= IFCSIUNIT(*,.LUMINOUSINTENSITYUNIT.,$,.LUMEN.);
|
||||
#52= IFCSIUNIT(*,.ENERGYUNIT.,.MEGA.,.JOULE.);
|
||||
#53= IFCDERIVEDUNIT((#56,#58,#60),.THERMALCONDUCTANCEUNIT.,$);
|
||||
#55= IFCSIUNIT(*,.POWERUNIT.,$,.WATT.);
|
||||
#56= IFCDERIVEDUNITELEMENT(#55,1);
|
||||
#57= IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
|
||||
#58= IFCDERIVEDUNITELEMENT(#57,-1);
|
||||
#59= IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.KELVIN.);
|
||||
#60= IFCDERIVEDUNITELEMENT(#59,-1);
|
||||
#61= IFCDERIVEDUNIT((#64,#66,#68),.SPECIFICHEATCAPACITYUNIT.,$);
|
||||
#63= IFCSIUNIT(*,.ENERGYUNIT.,$,.JOULE.);
|
||||
#64= IFCDERIVEDUNITELEMENT(#63,1);
|
||||
#65= IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.);
|
||||
#66= IFCDERIVEDUNITELEMENT(#65,-1);
|
||||
#67= IFCSIUNIT(*,.THERMODYNAMICTEMPERATUREUNIT.,$,.KELVIN.);
|
||||
#68= IFCDERIVEDUNITELEMENT(#67,-1);
|
||||
#69= IFCDERIVEDUNIT((#72,#74),.MASSDENSITYUNIT.,$);
|
||||
#71= IFCSIUNIT(*,.MASSUNIT.,.KILO.,.GRAM.);
|
||||
#72= IFCDERIVEDUNITELEMENT(#71,1);
|
||||
#73= IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
|
||||
#74= IFCDERIVEDUNITELEMENT(#73,-1);
|
||||
#75= IFCUNITASSIGNMENT((#33,#34,#35,#39,#43,#44,#48,#49,#50,#51,#52,#53,#61,#69));
|
||||
#77= IFCDIRECTION((1.,0.,0.));
|
||||
#81= IFCDIRECTION((0.,0.,1.));
|
||||
#83= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#85= IFCAXIS2PLACEMENT3D(#83,#81,#77);
|
||||
#86= IFCDIRECTION((0.,1.));
|
||||
#88= IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,0.01,#85,#86);
|
||||
#91= IFCPROJECT('1CGyzxdzHaJybf9L4wHy3R',#32,'Prosjektnavn','Prosjektbeskrivelse',$,$,'Fase',(#88),#75);
|
||||
#98= IFCPOSTALADDRESS($,$,$,$,('Adresse'),'#','Sted',$,'0000','Norge');
|
||||
#100= IFCDIRECTION((1.,0.,0.));
|
||||
#102= IFCDIRECTION((0.,0.,1.));
|
||||
#104= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#106= IFCAXIS2PLACEMENT3D(#104,#102,#100);
|
||||
#107= IFCLOCALPLACEMENT($,#106);
|
||||
#110= IFCSITE('01uPthe3Uixx0TRWar3Vt8',#32,'Eiendomsnavn',$,$,#107,$,$,.ELEMENT.,(59,55,52,428000),(10,42,27,972000),0.,'',#98);
|
||||
#116= IFCRELAGGREGATES('2F2UAtyl4uWjrKORQfJcEW',#32,$,$,#91,(#110));
|
||||
#122= IFCQUANTITYLENGTH('GrossPerimeter',$,$,0.);
|
||||
#124= IFCQUANTITYAREA('GrossArea',$,$,0.);
|
||||
#125= IFCELEMENTQUANTITY('17rUPIjEMxVrkRJ11moELl',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#122,#124));
|
||||
#130= IFCRELDEFINESBYPROPERTIES('0l65N8yZCPuwGi0lRf7Opq',#32,$,$,(#110),#125);
|
||||
#134= IFCPOSTALADDRESS($,$,$,$,('Adresse'),'#','Sted',$,'0000','Norge');
|
||||
#136= IFCDIRECTION((1.,0.,0.));
|
||||
#138= IFCDIRECTION((0.,0.,1.));
|
||||
#140= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#142= IFCAXIS2PLACEMENT3D(#140,#138,#136);
|
||||
#143= IFCLOCALPLACEMENT(#107,#142);
|
||||
#145= IFCBUILDING('3AMeTOOFsdVLZuNV0$27s5',#32,'Bygningens navn','Bygningstype',$,#143,$,'Byggnummer',.ELEMENT.,$,$,#134);
|
||||
#147= IFCRELAGGREGATES('0bntSkh7UDU_vAHXbaggwi',#32,$,$,#110,(#145));
|
||||
#151= IFCQUANTITYAREA('GrossFloorArea',$,$,0.);
|
||||
#152= IFCELEMENTQUANTITY('3GDAgT3z640iFgZCkp8utf',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#151));
|
||||
#154= IFCRELDEFINESBYPROPERTIES('3joCDTcjDP3x5zmHkLS3S0',#32,$,$,(#145),#152);
|
||||
#158= IFCDIRECTION((1.,0.,0.));
|
||||
#160= IFCDIRECTION((0.,0.,1.));
|
||||
#162= IFCCARTESIANPOINT((0.,0.,1000.));
|
||||
#164= IFCAXIS2PLACEMENT3D(#162,#160,#158);
|
||||
#165= IFCLOCALPLACEMENT(#143,#164);
|
||||
#167= IFCBUILDINGSTOREY('0St1hMhFUHZyrAECcby7I2',#32,'1. etasje',$,$,#165,$,$,.ELEMENT.,1000.);
|
||||
#169= IFCRELAGGREGATES('0TRv38eowcVEYjXmnFlLK1',#32,$,$,#145,(#167));
|
||||
#173= IFCQUANTITYLENGTH('NetHeight',$,$,2700.);
|
||||
#174= IFCQUANTITYLENGTH('GrossHeight',$,$,2700.);
|
||||
#175= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#176= IFCQUANTITYAREA('GrossFloorArea',$,$,0.);
|
||||
#177= IFCELEMENTQUANTITY('0t$Y2LddO4Hpr3Foz8Be1$',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#173,#174,#175,#176));
|
||||
#179= IFCRELDEFINESBYPROPERTIES('0J4XjFI$kRh2sJoL52j_PV',#32,$,$,(#167),#177);
|
||||
#183= IFCDIRECTION((1.,0.,0.));
|
||||
#185= IFCDIRECTION((0.,0.,1.));
|
||||
#187= IFCCARTESIANPOINT((-4423.22691561,10230.4382609,0.));
|
||||
#189= IFCAXIS2PLACEMENT3D(#187,#185,#183);
|
||||
#190= IFCLOCALPLACEMENT(#165,#189);
|
||||
#192= IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Body','Model',*,*,*,*,#88,$,.MODEL_VIEW.,$);
|
||||
#194= IFCCARTESIANPOINT((0.,-300.));
|
||||
#196= IFCCARTESIANPOINT((8599.33565217,-300.));
|
||||
#198= IFCCARTESIANPOINT((8599.33565217,0.));
|
||||
#200= IFCCARTESIANPOINT((0.,0.));
|
||||
#202= IFCPOLYLINE((#194,#196,#198,#200,#194));
|
||||
#204= IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,'',#202);
|
||||
#205= IFCDIRECTION((1.,0.,0.));
|
||||
#207= IFCDIRECTION((0.,0.,1.));
|
||||
#209= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#211= IFCAXIS2PLACEMENT3D(#209,#207,#205);
|
||||
#212= IFCDIRECTION((0.,0.,1.));
|
||||
#214= IFCEXTRUDEDAREASOLID(#204,#211,#212,2700.);
|
||||
#215= IFCSHAPEREPRESENTATION(#192,'Body','SweptSolid',(#214));
|
||||
#222= IFCPRESENTATIONLAYERASSIGNMENT('230- Yttervegger (som generisk objekt eller for gruppering)',$,(#215,#232,#357,#366,#443,#452),$);
|
||||
#225= IFCGEOMETRICREPRESENTATIONSUBCONTEXT('Axis','Model',*,*,*,*,#88,$,.MODEL_VIEW.,$);
|
||||
#226= IFCCARTESIANPOINT((0.,0.));
|
||||
#228= IFCCARTESIANPOINT((8599.33565217,0.));
|
||||
#230= IFCPOLYLINE((#226,#228));
|
||||
#232= IFCSHAPEREPRESENTATION(#225,'Axis','Curve2D',(#230));
|
||||
#236= IFCPRODUCTDEFINITIONSHAPE($,$,(#215,#232));
|
||||
#242= IFCWALLSTANDARDCASE('1Xezbq735AdBO_4DerG66G',#32,'YVT-A','',$,#190,#236,'61A3D974-1C31-4A9C-B63E-10DA35406190');
|
||||
#257= IFCRELCONTAINEDINSPATIALSTRUCTURE('0PUNS2OuLwupG5o8M6iDor',#32,$,$,(#242,#373,#459),#167);
|
||||
#261= IFCMATERIAL('Yttervegg');
|
||||
#264= IFCCOLOURRGB($,1.,1.,1.);
|
||||
#265= IFCSURFACESTYLERENDERING(#264,0.,IFCNORMALISEDRATIOMEASURE(0.3),$,$,$,IFCNORMALISEDRATIOMEASURE(0.69),$,.NOTDEFINED.);
|
||||
#266= IFCSURFACESTYLE('Maling - 01 Blank',.BOTH.,(#265));
|
||||
#268= IFCPRESENTATIONSTYLEASSIGNMENT((#266));
|
||||
#270= IFCSTYLEDITEM($,(#268),$);
|
||||
#272= IFCSTYLEDREPRESENTATION(#192,$,$,(#270));
|
||||
#274= IFCMATERIALDEFINITIONREPRESENTATION($,$,(#272),#261);
|
||||
#278= IFCMATERIALLAYER(#261,300.,.U.);
|
||||
#280= IFCMATERIALLAYERSET((#278),'Yttervegg 300');
|
||||
#283= IFCMATERIALLAYERSETUSAGE(#280,.AXIS2.,.NEGATIVE.,0.);
|
||||
#284= IFCRELASSOCIATESMATERIAL('2msyZzaqHpLqjyOSMDJ6bC',#32,$,$,(#242),#283);
|
||||
#287= IFCPROPERTYSINGLEVALUE('GTIN',$,IFCLABEL('3'),$);
|
||||
#291= IFCPROPERTYSET('2axpxSTK16Nv0$I70apGdK',#32,'Reuse',$,(#287));
|
||||
#293= IFCRELDEFINESBYPROPERTIES('36a5T_GLs751QxsAvh1iEB',#32,$,$,(#242),#291);
|
||||
#297= IFCPROPERTYSINGLEVALUE('Renovation Status',$,IFCLABEL('New'),$);
|
||||
#298= IFCPROPERTYSET('04Pv5xI9$vG1lZdN7wMAEc',#32,'AC_Pset_RenovationAndPhasing',$,(#297));
|
||||
#300= IFCRELDEFINESBYPROPERTIES('2AvYgZFQ$eywkTT_yzugG4',#32,$,$,(#242),#298);
|
||||
#303= IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.T.),$);
|
||||
#304= IFCPROPERTYSET('3vuFSkrI50S$hRePMCpJTW',#32,'Pset_WallCommon',$,(#303));
|
||||
#306= IFCRELDEFINESBYPROPERTIES('3eVxLUBuO61JJXioN57x5f',#32,$,$,(#242),#304);
|
||||
#309= IFCQUANTITYLENGTH('Length',$,$,8599.33565217);
|
||||
#310= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#311= IFCQUANTITYLENGTH('Width',$,$,300.);
|
||||
#312= IFCQUANTITYAREA('GrossFootprintArea',$,$,2.57980069565);
|
||||
#313= IFCQUANTITYAREA('NetFootprintArea',$,$,2.57980069565);
|
||||
#314= IFCQUANTITYAREA('GrossSideArea',$,$,23.2182062609);
|
||||
#315= IFCQUANTITYAREA('NetSideArea',$,$,23.2182062609);
|
||||
#316= IFCQUANTITYVOLUME('GrossVolume',$,$,6.96546187826);
|
||||
#317= IFCQUANTITYVOLUME('NetVolume',$,$,6.96546187826);
|
||||
#318= IFCELEMENTQUANTITY('1uJa0ITKB1qrAAf8WtmaZ2',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#309,#310,#311,#312,#313,#314,#315,#316,#317));
|
||||
#320= IFCRELDEFINESBYPROPERTIES('3eMBYSfULCCgJUrbtWuHgF',#32,$,$,(#242),#318);
|
||||
#323= IFCWALLTYPE('2V6t5i6hK0w_M3VKaOTPG8',#32,'Yttervegg 300',$,$,$,$,'9F1B716C-1AB5-00EB-E583-7D4918759408',$,.NOTDEFINED.);
|
||||
#325= IFCRELDEFINESBYTYPE('0GIkSqbN66cNEifXw6t8R_',#32,$,$,(#242,#373,#459),#323);
|
||||
#328= IFCDIRECTION((1.,0.,0.));
|
||||
#330= IFCDIRECTION((0.,0.,1.));
|
||||
#332= IFCCARTESIANPOINT((-3058.25300257,12869.3878261,0.));
|
||||
#334= IFCAXIS2PLACEMENT3D(#332,#330,#328);
|
||||
#335= IFCLOCALPLACEMENT(#165,#334);
|
||||
#336= IFCCARTESIANPOINT((0.,-300.));
|
||||
#338= IFCCARTESIANPOINT((8508.3373913,-300.));
|
||||
#340= IFCCARTESIANPOINT((8508.3373913,0.));
|
||||
#342= IFCCARTESIANPOINT((0.,0.));
|
||||
#344= IFCPOLYLINE((#336,#338,#340,#342,#336));
|
||||
#346= IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,'',#344);
|
||||
#347= IFCDIRECTION((1.,0.,0.));
|
||||
#349= IFCDIRECTION((0.,0.,1.));
|
||||
#351= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#353= IFCAXIS2PLACEMENT3D(#351,#349,#347);
|
||||
#354= IFCDIRECTION((0.,0.,1.));
|
||||
#356= IFCEXTRUDEDAREASOLID(#346,#353,#354,2700.);
|
||||
#357= IFCSHAPEREPRESENTATION(#192,'Body','SweptSolid',(#356));
|
||||
#360= IFCCARTESIANPOINT((0.,0.));
|
||||
#362= IFCCARTESIANPOINT((8508.3373913,0.));
|
||||
#364= IFCPOLYLINE((#360,#362));
|
||||
#366= IFCSHAPEREPRESENTATION(#225,'Axis','Curve2D',(#364));
|
||||
#369= IFCPRODUCTDEFINITIONSHAPE($,$,(#357,#366));
|
||||
#373= IFCWALLSTANDARDCASE('3eT96F_cnDsOufCEsotwtp',#32,'YVT-B','',$,#335,#369,'E874918F-FA6C-4DD9-8E29-30EDB2DFADF3');
|
||||
#377= IFCMATERIALLAYERSETUSAGE(#280,.AXIS2.,.NEGATIVE.,0.);
|
||||
#378= IFCRELASSOCIATESMATERIAL('1iVlD94vOmLa5OpxnkCqlJ',#32,$,$,(#373),#377);
|
||||
#381= IFCPROPERTYSINGLEVALUE('GTIN',$,IFCLABEL('2'),$);
|
||||
#382= IFCPROPERTYSET('3lu_1RsD2PskLfQWcCOLU3',#32,'Reuse',$,(#381));
|
||||
#384= IFCRELDEFINESBYPROPERTIES('1PufL50Yxw16Mg0x9OekT1',#32,$,$,(#373),#382);
|
||||
#388= IFCPROPERTYSINGLEVALUE('Renovation Status',$,IFCLABEL('New'),$);
|
||||
#389= IFCPROPERTYSET('0221Oat$Xdl8OooXaoH0Gp',#32,'AC_Pset_RenovationAndPhasing',$,(#388));
|
||||
#391= IFCRELDEFINESBYPROPERTIES('3OX_1Z$WvLZFlLD1nCbhRr',#32,$,$,(#373),#389);
|
||||
#394= IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.T.),$);
|
||||
#395= IFCPROPERTYSET('1T4$VD7m_rVNvlN5DrtbUq',#32,'Pset_WallCommon',$,(#394));
|
||||
#397= IFCRELDEFINESBYPROPERTIES('1v2J9jM6ihU_rOwjxcKYIn',#32,$,$,(#373),#395);
|
||||
#400= IFCQUANTITYLENGTH('Length',$,$,8508.3373913);
|
||||
#401= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#402= IFCQUANTITYLENGTH('Width',$,$,300.);
|
||||
#403= IFCQUANTITYAREA('GrossFootprintArea',$,$,2.55250121739);
|
||||
#404= IFCQUANTITYAREA('NetFootprintArea',$,$,2.55250121739);
|
||||
#405= IFCQUANTITYAREA('GrossSideArea',$,$,22.9725109565);
|
||||
#406= IFCQUANTITYAREA('NetSideArea',$,$,22.9725109565);
|
||||
#407= IFCQUANTITYVOLUME('GrossVolume',$,$,6.89175328696);
|
||||
#408= IFCQUANTITYVOLUME('NetVolume',$,$,6.89175328696);
|
||||
#409= IFCELEMENTQUANTITY('09pcUknANnc6EQrUWIhXdt',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#400,#401,#402,#403,#404,#405,#406,#407,#408));
|
||||
#411= IFCRELDEFINESBYPROPERTIES('2zE052B_urXe2bvDBLgYv1',#32,$,$,(#373),#409);
|
||||
#414= IFCDIRECTION((1.,0.,0.));
|
||||
#416= IFCDIRECTION((0.,0.,1.));
|
||||
#418= IFCCARTESIANPOINT((35.6878669942,15007.8469565,0.));
|
||||
#420= IFCAXIS2PLACEMENT3D(#418,#416,#414);
|
||||
#421= IFCLOCALPLACEMENT(#165,#420);
|
||||
#422= IFCCARTESIANPOINT((0.,-300.));
|
||||
#424= IFCCARTESIANPOINT((7234.36173913,-300.));
|
||||
#426= IFCCARTESIANPOINT((7234.36173913,0.));
|
||||
#428= IFCCARTESIANPOINT((0.,0.));
|
||||
#430= IFCPOLYLINE((#422,#424,#426,#428,#422));
|
||||
#432= IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,'',#430);
|
||||
#433= IFCDIRECTION((1.,0.,0.));
|
||||
#435= IFCDIRECTION((0.,0.,1.));
|
||||
#437= IFCCARTESIANPOINT((0.,0.,0.));
|
||||
#439= IFCAXIS2PLACEMENT3D(#437,#435,#433);
|
||||
#440= IFCDIRECTION((0.,0.,1.));
|
||||
#442= IFCEXTRUDEDAREASOLID(#432,#439,#440,2700.);
|
||||
#443= IFCSHAPEREPRESENTATION(#192,'Body','SweptSolid',(#442));
|
||||
#446= IFCCARTESIANPOINT((0.,0.));
|
||||
#448= IFCCARTESIANPOINT((7234.36173913,0.));
|
||||
#450= IFCPOLYLINE((#446,#448));
|
||||
#452= IFCSHAPEREPRESENTATION(#225,'Axis','Curve2D',(#450));
|
||||
#455= IFCPRODUCTDEFINITIONSHAPE($,$,(#443,#452));
|
||||
#459= IFCWALLSTANDARDCASE('3k3LbXmTfDWODxdSuyCGYE',#32,'YVT-C','',$,#421,#455,'EE0D5961-C1DA-4D81-837B-9DCE3C31088E');
|
||||
#463= IFCMATERIALLAYERSETUSAGE(#280,.AXIS2.,.NEGATIVE.,0.);
|
||||
#464= IFCRELASSOCIATESMATERIAL('3_dZLENyJ5_qngksl2ie6r',#32,$,$,(#459),#463);
|
||||
#467= IFCPROPERTYSINGLEVALUE('GTIN',$,IFCLABEL('1'),$);
|
||||
#468= IFCPROPERTYSET('342IhYDnCDoqxc_Zm6g_fw',#32,'Reuse',$,(#467));
|
||||
#470= IFCRELDEFINESBYPROPERTIES('0KYu05w72mseS$C1XTnSzM',#32,$,$,(#459),#468);
|
||||
#474= IFCPROPERTYSINGLEVALUE('Renovation Status',$,IFCLABEL('New'),$);
|
||||
#475= IFCPROPERTYSET('2POedyNP84Nk7S4D$CmpS2',#32,'AC_Pset_RenovationAndPhasing',$,(#474));
|
||||
#477= IFCRELDEFINESBYPROPERTIES('2oUR0$VhKaEPV9FW3eb0G3',#32,$,$,(#459),#475);
|
||||
#480= IFCPROPERTYSINGLEVALUE('IsExternal',$,IFCBOOLEAN(.T.),$);
|
||||
#481= IFCPROPERTYSET('2IgxHtV7Xo2x_LGYkyiDI6',#32,'Pset_WallCommon',$,(#480));
|
||||
#483= IFCRELDEFINESBYPROPERTIES('2d$ext$iF9gxEs6l5zaPYh',#32,$,$,(#459),#481);
|
||||
#486= IFCQUANTITYLENGTH('Length',$,$,7234.36173913);
|
||||
#487= IFCQUANTITYLENGTH('Height',$,$,2700.);
|
||||
#488= IFCQUANTITYLENGTH('Width',$,$,300.);
|
||||
#489= IFCQUANTITYAREA('GrossFootprintArea',$,$,2.17030852174);
|
||||
#490= IFCQUANTITYAREA('NetFootprintArea',$,$,2.17030852174);
|
||||
#491= IFCQUANTITYAREA('GrossSideArea',$,$,19.5327766957);
|
||||
#492= IFCQUANTITYAREA('NetSideArea',$,$,19.5327766957);
|
||||
#493= IFCQUANTITYVOLUME('GrossVolume',$,$,5.8598330087);
|
||||
#494= IFCQUANTITYVOLUME('NetVolume',$,$,5.8598330087);
|
||||
#495= IFCELEMENTQUANTITY('1LnynQ0O$3P0FeM4JPb_Ao',#32,'BaseQuantities',$,'ARCHICAD BIM Base Quantities',(#486,#487,#488,#489,#490,#491,#492,#493,#494));
|
||||
#497= IFCRELDEFINESBYPROPERTIES('14UKMwdhaOGUYk9crv2_wD',#32,$,$,(#459),#495);
|
||||
ENDSEC;
|
||||
|
||||
END-ISO-10303-21;
|
||||
@@ -0,0 +1,120 @@
|
||||
import jsonpickle
|
||||
import os
|
||||
import pytz
|
||||
|
||||
from dateutil import parser
|
||||
from uuid import uuid4, UUID
|
||||
|
||||
from neo4j import GraphDatabase
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from models.request import UserInDB
|
||||
from security.secrets import get_secrets
|
||||
|
||||
get_secrets()
|
||||
|
||||
# neo4j://127.0.0.1:27687 is used when there are no environment variables, i.e. when running from terminal.
|
||||
# otherwise the environment variable will have a value of neo4j://kontroll_neo4j:27687
|
||||
# kontroll_neo4j is the docker-compose network.
|
||||
|
||||
driver = GraphDatabase.driver(os.environ['NEO4J_URI'],
|
||||
auth=(os.environ['NEO4J_USER'],
|
||||
os.environ['NEO4J_INITIAL_PASSWORD']))
|
||||
|
||||
# initial password should be changed to secret password
|
||||
|
||||
driver.verify_connectivity()
|
||||
|
||||
cypher_file_path = "./db_config/init.cypher"
|
||||
|
||||
|
||||
class MyDB:
|
||||
|
||||
def __init__(self, object_driver):
|
||||
self.driver = object_driver
|
||||
self.database = 'neo4j'
|
||||
|
||||
@staticmethod
|
||||
def timestamp():
|
||||
return datetime.now(timezone.utc).isoformat(sep='T', timespec='milliseconds')
|
||||
|
||||
@staticmethod
|
||||
def bcf_time(any_datetime):
|
||||
if isinstance(any_datetime, type('str')):
|
||||
datetime_any = parser.parse(any_datetime).astimezone(pytz.utc)
|
||||
elif isinstance(any_datetime, type(datetime.now())):
|
||||
datetime_any = any_datetime.astimezone(pytz.utc)
|
||||
else:
|
||||
return False
|
||||
string_date = datetime_any.isoformat(sep='T', timespec='milliseconds')
|
||||
return str(string_date)
|
||||
|
||||
@staticmethod
|
||||
def safe_path(path_name):
|
||||
safe_path_name = ''.join(x for x in path_name if x.isalnum() or '-')
|
||||
return safe_path_name
|
||||
|
||||
@staticmethod
|
||||
def debug(endpoint: str, request, response):
|
||||
print("\n\n\nEndpoint: ", jsonpickle.dumps(endpoint),
|
||||
"\nRequest: ", jsonpickle.dumps(request),
|
||||
"\nResponse: ", jsonpickle.dumps(response))
|
||||
|
||||
@staticmethod
|
||||
def node_to_json(node):
|
||||
json = {}
|
||||
for fields in node.items():
|
||||
json[fields[0]] = fields[1]
|
||||
return json
|
||||
|
||||
@staticmethod
|
||||
def is_valid_uuid(uuid_to_test, version=4):
|
||||
try:
|
||||
uuid_obj = UUID(uuid_to_test, version=version)
|
||||
except ValueError:
|
||||
return False
|
||||
return str(uuid_obj) == uuid_to_test
|
||||
|
||||
@staticmethod
|
||||
def new_uuid():
|
||||
try:
|
||||
uuid_obj = uuid4()
|
||||
except ValueError:
|
||||
return False
|
||||
return str(uuid_obj)
|
||||
|
||||
def initialize_db(self):
|
||||
def initialize_db_work(tx):
|
||||
cypher_file = open(cypher_file_path, "r")
|
||||
cypher_data = cypher_file.read()
|
||||
cypher_file.close()
|
||||
cypher_statements = cypher_data.split(';')
|
||||
cypher_statements.pop()
|
||||
for cypher_statement in cypher_statements:
|
||||
tx.run(cypher_statement)
|
||||
return
|
||||
with self.driver.session() as session:
|
||||
return session.execute_write(initialize_db_work)
|
||||
|
||||
def get_user(self, username: str):
|
||||
def get_user_work(tx, username_work: str):
|
||||
cypher = """
|
||||
MATCH (u:User)
|
||||
WHERE u.username = $username
|
||||
RETURN u AS user
|
||||
LIMIT 1
|
||||
"""
|
||||
result = tx.run(cypher, username=username_work)
|
||||
first = result.single()
|
||||
if first is None:
|
||||
return None
|
||||
user_node = first.get("user")
|
||||
user_dict = self.node_to_json(user_node)
|
||||
user = UserInDB(**user_dict)
|
||||
return user
|
||||
with self.driver.session() as session:
|
||||
return session.execute_read(get_user_work, username_work=username)
|
||||
|
||||
db = MyDB(driver)
|
||||
db.initialize_db()
|
||||
@@ -0,0 +1,655 @@
|
||||
{
|
||||
"style": {
|
||||
"font-family": "sans-serif",
|
||||
"background-color": "#ffffff",
|
||||
"background-image": "",
|
||||
"background-size": "100%",
|
||||
"node-color": "#ffffff",
|
||||
"border-width": 4,
|
||||
"border-color": "#000000",
|
||||
"radius": 50,
|
||||
"node-padding": 5,
|
||||
"node-margin": 2,
|
||||
"outside-position": "auto",
|
||||
"node-icon-image": "",
|
||||
"node-background-image": "",
|
||||
"icon-position": "inside",
|
||||
"icon-size": 64,
|
||||
"caption-position": "inside",
|
||||
"caption-max-width": 200,
|
||||
"caption-color": "#000000",
|
||||
"caption-font-size": 50,
|
||||
"caption-font-weight": "normal",
|
||||
"label-position": "inside",
|
||||
"label-display": "pill",
|
||||
"label-color": "#000000",
|
||||
"label-background-color": "#ffffff",
|
||||
"label-border-color": "#000000",
|
||||
"label-border-width": 4,
|
||||
"label-font-size": 40,
|
||||
"label-padding": 5,
|
||||
"label-margin": 4,
|
||||
"directionality": "directed",
|
||||
"detail-position": "inline",
|
||||
"detail-orientation": "parallel",
|
||||
"arrow-width": 5,
|
||||
"arrow-color": "#000000",
|
||||
"margin-start": 5,
|
||||
"margin-end": 5,
|
||||
"margin-peer": 20,
|
||||
"attachment-start": "normal",
|
||||
"attachment-end": "normal",
|
||||
"relationship-icon-image": "",
|
||||
"type-color": "#000000",
|
||||
"type-background-color": "#ffffff",
|
||||
"type-border-color": "#000000",
|
||||
"type-border-width": 0,
|
||||
"type-font-size": 16,
|
||||
"type-padding": 5,
|
||||
"property-position": "outside",
|
||||
"property-alignment": "colon",
|
||||
"property-color": "#000000",
|
||||
"property-font-size": 16,
|
||||
"property-font-weight": "normal"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"id": "n0",
|
||||
"position": {
|
||||
"x": -289.1004788300022,
|
||||
"y": 479.89101705018163
|
||||
},
|
||||
"caption": "Topic",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Topic"
|
||||
],
|
||||
"properties": {
|
||||
"guid": "STRING",
|
||||
"creation_date": "",
|
||||
"modified_date": "",
|
||||
"creation_author": "",
|
||||
"modified_author": "",
|
||||
"topic_type": "",
|
||||
"topic_status": "",
|
||||
"title": "",
|
||||
"priority": "",
|
||||
"index": "",
|
||||
"assigned_to": "",
|
||||
"stage": "",
|
||||
"description": "",
|
||||
"due_date": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n1",
|
||||
"position": {
|
||||
"x": -333.1242108163427,
|
||||
"y": 63.328364869784195
|
||||
},
|
||||
"caption": "Project",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Project"
|
||||
],
|
||||
"properties": {
|
||||
"project_id": "STRING",
|
||||
"name": "STRING"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n2",
|
||||
"position": {
|
||||
"x": 16.812745060861175,
|
||||
"y": -286.6085910074197
|
||||
},
|
||||
"caption": "Extensions",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Extensions"
|
||||
],
|
||||
"properties": {
|
||||
"topic_type": "",
|
||||
"topic_status": "",
|
||||
"topic_label": "",
|
||||
"snippet_type": "",
|
||||
"priority": "",
|
||||
"users": "",
|
||||
"stage": "",
|
||||
"project_actions": "",
|
||||
"topic_actions": "",
|
||||
"comment_actions": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n3",
|
||||
"position": {
|
||||
"x": 226.07663507128208,
|
||||
"y": 479.89101705018163
|
||||
},
|
||||
"caption": "Viewpoint",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Viewpoint"
|
||||
],
|
||||
"properties": {
|
||||
"guid": "",
|
||||
"snapshot": "",
|
||||
"snapshot_type": "",
|
||||
"index": "",
|
||||
"camera_definition": "",
|
||||
"camera_view_point": "",
|
||||
"camera_direction": "",
|
||||
"camera_up_vector": "",
|
||||
"view_tws_or_fo_view": "",
|
||||
"aspect_ratio": "",
|
||||
"default_visibility": "",
|
||||
"spaces_visible": "",
|
||||
"space_boundaries_visible": "",
|
||||
"openings_visible": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n4",
|
||||
"position": {
|
||||
"x": -1063.7571554834985,
|
||||
"y": -259.5570287821265
|
||||
},
|
||||
"caption": "User",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"User"
|
||||
],
|
||||
"properties": {
|
||||
"UUID": "",
|
||||
"username": "STRING",
|
||||
"full_name": "",
|
||||
"email": "",
|
||||
"hashed_password": "",
|
||||
"disabled": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n5",
|
||||
"position": {
|
||||
"x": 529.1987482469625,
|
||||
"y": 63.328364869784195
|
||||
},
|
||||
"caption": "Component",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Component"
|
||||
],
|
||||
"properties": {
|
||||
"ifc_guid": "",
|
||||
"selected": "",
|
||||
"originating_system": "",
|
||||
"authoring_tool_id": "",
|
||||
"color": "",
|
||||
"visibility_exception": "",
|
||||
"GTIN": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n6",
|
||||
"position": {
|
||||
"x": 264.11970819463727,
|
||||
"y": -73.26061694118164
|
||||
},
|
||||
"caption": "Comment",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Comment"
|
||||
],
|
||||
"properties": {
|
||||
"guid": "",
|
||||
"reply_to_comment_guid": "",
|
||||
"date": "",
|
||||
"modified_date": "",
|
||||
"author": "",
|
||||
"modified_author": "",
|
||||
"comment": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n7",
|
||||
"position": {
|
||||
"x": -651.5332260698573,
|
||||
"y": -497.5546253954978
|
||||
},
|
||||
"caption": "Token:AcessToken",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Token:AcessToken"
|
||||
],
|
||||
"properties": {
|
||||
"code": "",
|
||||
"scope": "",
|
||||
"value": "",
|
||||
"hash": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n8",
|
||||
"position": {
|
||||
"x": -485.20568785423296,
|
||||
"y": -367.1506322968085
|
||||
},
|
||||
"caption": "Token:RefreshToken",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Token:RefreshToken"
|
||||
],
|
||||
"properties": {
|
||||
"code": "",
|
||||
"scope": "",
|
||||
"value": "",
|
||||
"hash": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n9",
|
||||
"position": {
|
||||
"x": -192.7032908251408,
|
||||
"y": -331.31640881713093
|
||||
},
|
||||
"caption": "Process",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Process"
|
||||
],
|
||||
"properties": {}
|
||||
},
|
||||
{
|
||||
"id": "n10",
|
||||
"position": {
|
||||
"x": -1507.6552365118007,
|
||||
"y": -73.26061694118164
|
||||
},
|
||||
"caption": "Session:Upload",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Session:Upload"
|
||||
],
|
||||
"properties": {
|
||||
"server_context": "",
|
||||
"upload_session": "",
|
||||
"callback": "",
|
||||
"session_url_timedelta": "",
|
||||
"session_callback_timedelta": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n11",
|
||||
"position": {
|
||||
"x": -1063.7571554834985,
|
||||
"y": 370.6374640871204
|
||||
},
|
||||
"caption": "Document:Model:Temporary",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Document:Model"
|
||||
],
|
||||
"properties": {
|
||||
"document_id": "",
|
||||
"version_index": "",
|
||||
"ifc_project": "",
|
||||
"name": "",
|
||||
"title": "",
|
||||
"guid": "",
|
||||
"url": "",
|
||||
"description": "",
|
||||
"session_file_id": "",
|
||||
"version_number": "",
|
||||
"creation_date": "",
|
||||
"original_file_name": "",
|
||||
"file_ending": "",
|
||||
"mime_type": "",
|
||||
"file_type": "",
|
||||
"size_in_bytes": "",
|
||||
"project": "",
|
||||
"reference": "",
|
||||
"date": "",
|
||||
"ifc_spatial_structure_element": "",
|
||||
"filename": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n12",
|
||||
"position": {
|
||||
"x": -1352.4885415635456,
|
||||
"y": 659.3688501671675
|
||||
},
|
||||
"caption": "Part",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Part"
|
||||
],
|
||||
"properties": {
|
||||
"uuid": "",
|
||||
"number": "",
|
||||
"content_range_start": "",
|
||||
"content_range_end": "",
|
||||
"content_length": "",
|
||||
"uploaded": "Boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n13",
|
||||
"position": {
|
||||
"x": -954.4308563764228,
|
||||
"y": 63.328364869784195
|
||||
},
|
||||
"caption": "Session:Select",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Session:Select"
|
||||
],
|
||||
"properties": {
|
||||
"server_context": "",
|
||||
"selection_session": "",
|
||||
"callback": "",
|
||||
"session_url_timedelta": "",
|
||||
"session_callback_timedelta": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n14",
|
||||
"position": {
|
||||
"x": -1217.4300471311708,
|
||||
"y": -21.114959701631534
|
||||
},
|
||||
"caption": "Link",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Link"
|
||||
],
|
||||
"properties": {}
|
||||
},
|
||||
{
|
||||
"id": "n15",
|
||||
"position": {
|
||||
"x": 570.3648741605024,
|
||||
"y": 533.5911545751243
|
||||
},
|
||||
"caption": "Line",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Line"
|
||||
],
|
||||
"properties": {
|
||||
"start_point": "",
|
||||
"end_point": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n16",
|
||||
"position": {
|
||||
"x": 645.5935574267153,
|
||||
"y": 299.3814975504374
|
||||
},
|
||||
"caption": "ClippingPlane",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"CilppingPlane"
|
||||
],
|
||||
"properties": {
|
||||
"location": "",
|
||||
"direction": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n17",
|
||||
"position": {
|
||||
"x": 425.3162762641219,
|
||||
"y": 765.7610254853134
|
||||
},
|
||||
"caption": "Bitmap",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"Bitmap"
|
||||
],
|
||||
"properties": {
|
||||
"type": "",
|
||||
"location": "",
|
||||
"normal": "",
|
||||
"up": "",
|
||||
"height": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n18",
|
||||
"position": {
|
||||
"x": 645.5935574267153,
|
||||
"y": -331.31640881713093
|
||||
},
|
||||
"caption": "IfcProduct",
|
||||
"style": {},
|
||||
"labels": [
|
||||
"IfcProduct"
|
||||
],
|
||||
"properties": {
|
||||
"GlobalId": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "n19",
|
||||
"position": {
|
||||
"x": -988.4895851499441,
|
||||
"y": -536.5949869348817
|
||||
},
|
||||
"caption": "AuthorizationCode",
|
||||
"style": {},
|
||||
"labels": [],
|
||||
"properties": {
|
||||
"code": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"id": "n0",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n1",
|
||||
"toId": "n0"
|
||||
},
|
||||
{
|
||||
"id": "n1",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n1",
|
||||
"toId": "n2"
|
||||
},
|
||||
{
|
||||
"id": "n2",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n0",
|
||||
"toId": "n3"
|
||||
},
|
||||
{
|
||||
"id": "n3",
|
||||
"type": "HAS_ACTIONS_ON",
|
||||
"style": {},
|
||||
"properties": {
|
||||
"read": "BOOLEAN",
|
||||
"update": "BOOLEAN",
|
||||
"createTopic": "BOOLEAN",
|
||||
"createDocument": "BOOLEAN",
|
||||
"createViewpoint": "BOOLEAN",
|
||||
"createComment": "BOOLEAN",
|
||||
"updateDocumentReferences": "BOOLEAN",
|
||||
"updateFiles": "BOOLEAN",
|
||||
"updateRelatedTopics": "BOOLEAN"
|
||||
},
|
||||
"fromId": "n4",
|
||||
"toId": "n1"
|
||||
},
|
||||
{
|
||||
"id": "n4",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n3",
|
||||
"toId": "n5"
|
||||
},
|
||||
{
|
||||
"id": "n5",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n0",
|
||||
"toId": "n6"
|
||||
},
|
||||
{
|
||||
"id": "n6",
|
||||
"type": "RELATED_TO",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n6",
|
||||
"toId": "n3"
|
||||
},
|
||||
{
|
||||
"id": "n7",
|
||||
"type": "CAN_USE",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n4",
|
||||
"toId": "n7"
|
||||
},
|
||||
{
|
||||
"id": "n8",
|
||||
"type": "CAN_USE",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n4",
|
||||
"toId": "n8"
|
||||
},
|
||||
{
|
||||
"id": "n9",
|
||||
"type": "CONTAINS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n9",
|
||||
"toId": "n1"
|
||||
},
|
||||
{
|
||||
"id": "n10",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n4",
|
||||
"toId": "n10"
|
||||
},
|
||||
{
|
||||
"id": "n11",
|
||||
"type": "CONTAINS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n10",
|
||||
"toId": "n11"
|
||||
},
|
||||
{
|
||||
"id": "n12",
|
||||
"type": "REFERS_TO",
|
||||
"style": {},
|
||||
"properties": {
|
||||
"guid": ""
|
||||
},
|
||||
"fromId": "n0",
|
||||
"toId": "n11"
|
||||
},
|
||||
{
|
||||
"id": "n13",
|
||||
"type": "CONTAINS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n1",
|
||||
"toId": "n11"
|
||||
},
|
||||
{
|
||||
"id": "n14",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n11",
|
||||
"toId": "n12"
|
||||
},
|
||||
{
|
||||
"id": "n15",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n4",
|
||||
"toId": "n13"
|
||||
},
|
||||
{
|
||||
"id": "n16",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n10",
|
||||
"toId": "n14"
|
||||
},
|
||||
{
|
||||
"id": "n17",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n13",
|
||||
"toId": "n14"
|
||||
},
|
||||
{
|
||||
"id": "n18",
|
||||
"type": "SELECTED",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n13",
|
||||
"toId": "n11"
|
||||
},
|
||||
{
|
||||
"id": "n19",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n3",
|
||||
"toId": "n15"
|
||||
},
|
||||
{
|
||||
"id": "n20",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n3",
|
||||
"toId": "n16"
|
||||
},
|
||||
{
|
||||
"id": "n21",
|
||||
"type": "HAS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n3",
|
||||
"toId": "n17"
|
||||
},
|
||||
{
|
||||
"id": "n22",
|
||||
"type": "SAME_AS",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n5",
|
||||
"toId": "n18"
|
||||
},
|
||||
{
|
||||
"id": "n23",
|
||||
"type": "CAN_USE",
|
||||
"style": {},
|
||||
"properties": {},
|
||||
"fromId": "n4",
|
||||
"toId": "n19"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
MATCH (n) DETACH DELETE n;
|
||||
CREATE
|
||||
(user1:User:Admin {UUID: 1, username: 'admin', full_name: "Admin Sliter", email: "admin@verket.se", hashed_password: "$2a$10$SyekXmrbf9rEd9KrfcXQd.gNWM8iRl228nPTrxqJq0wlDamlHzDly", disabled: false}),
|
||||
(user2:User:Officer {UUID: 2, username: 'kommun', full_name: "Kalle Nitisk", email: "kalle.nitisk@kommunen.se", hashed_password: "$2a$10$lX1BIZZuBGOIEiHCxXwrf.IoeHOAWgB3ri6vX10xtMVEmzVH/GbCu", disabled: false}),
|
||||
(user3:User:Developer {UUID: 3, username: 'byggherre', full_name: "Byggherre Stolt", email: "stolt@byggfirman.com", hashed_password: "$2a$10$i08DcyX2r9CeWBPaUykDeeynvI5/Aa9scRfi.MfbTgCGm/XqcpGcG", disabled: false}),
|
||||
(user4:User:Auditor {UUID: 4, username: 'kontrollansvarig', full_name: "Kontrollansvarig Tapper", email: "kontrollansvarig@ansvaret.se", hashed_password: "$2a$10$OlfSbVqpOmuNNf8KHxFXhONU70nxbrMSH5vL2uc9ydCDfJVAP/gl6", disabled: false}),
|
||||
(user5:User:Tester {UUID: 5, username: 'kontrollant', full_name: "Finn Failer", email: "finn.failer@byggfirman.com", hashed_password: "$2a$10$lX1BIZZuBGOIEiHCxXwrf.IoeHOAWgB3ri6vX10xtMVEmzVH/GbCu", disabled: false}),
|
||||
(municipality1:Municipality {UUID: 6, name: 'Karlskrona'}),
|
||||
(building1:Building {UUID: 7, status: "Under construction"}),
|
||||
(project1:Project {project_id: 'c8e9ccce-a4af-11ed-b9df-0242ac120003', name: 'Bygglov'}),
|
||||
(project2:Project {project_id: 'd1842bfe-a4af-11ed-b9df-0242ac120003', name: 'Rivningslov'}),
|
||||
(document1:Document {
|
||||
document_id: '78860d3c-14db-11ee-be56-0242ac120002',
|
||||
session_file_id: '',
|
||||
version_index: 1,
|
||||
version_number: 'Första',
|
||||
creation_date: '2023-01-01T01:01:01.111',
|
||||
title: 'Solibri building',
|
||||
original_file_name: 'Solibri_building.ifc',
|
||||
file_ending: '.ifc',
|
||||
mime_type: 'application/x-step',
|
||||
file_type: 'STEP Physical File (SPF)',
|
||||
name: '78860d3c-14db-11ee-be56-0242ac120002.ifc',
|
||||
size_in_bytes: 16962975,
|
||||
ifc_project: "0g8GxLEzP459ZWW6_RGsez"
|
||||
}),
|
||||
(document2:Document {
|
||||
document_id: '6dbd4d52-14db-11ee-be56-0242ac120002',
|
||||
session_file_id: '',
|
||||
version_index: 2,
|
||||
version_number: 'Andra',
|
||||
creation_date: '2023-01-01T01:01:01.111',
|
||||
title: 'Office building',
|
||||
original_file_name: 'Office_building.ifc',
|
||||
file_ending: '.ifc',
|
||||
mime_type: 'application/x-step',
|
||||
file_type: 'STEP Physical File (SPF)',
|
||||
name: '6dbd4d52-14db-11ee-be56-0242ac120002.ifc',
|
||||
size_in_bytes: 8332380,
|
||||
ifc_project: "2gszwez3bE580ABnUBdjoQ"
|
||||
}),
|
||||
(document3:Document {
|
||||
document_id: '78860e68-14db-11ee-be56-0242ac120002',
|
||||
session_file_id: '',
|
||||
version_index: 3,
|
||||
version_number: 'Tredje',
|
||||
creation_date: '2023-01-01T01:01:01:01.111',
|
||||
title: 'Solibri structural',
|
||||
original_file_name: 'Solibri_structural.ifc',
|
||||
file_ending: '.ifc',
|
||||
mime_type: 'application/x-step',
|
||||
file_type: 'STEP Physical File (SPF)',
|
||||
name: '78860e68-14db-11ee-be56-0242ac120002.ifc',
|
||||
size_in_bytes: 146606,
|
||||
ifc_project: "19sOQo3B98PAcnw01BM8Xo"
|
||||
}),
|
||||
(user2)-[r4:HAS_ACTIONS_ON {
|
||||
read: True,
|
||||
update: True,
|
||||
createTopic: True,
|
||||
createDocument: True,
|
||||
createViewpoint: True,
|
||||
createComment: True
|
||||
}]->(project1),
|
||||
(user2)-[r8:HAS_ACTIONS_ON {
|
||||
read: True,
|
||||
update: True,
|
||||
createTopic: True,
|
||||
createDocument: True,
|
||||
createViewpoint: True,
|
||||
createComment: True
|
||||
}]->(project2),
|
||||
(user3)-[r9:HAS_ACTIONS_ON {
|
||||
read: True,
|
||||
update: True,
|
||||
createTopic: True,
|
||||
createDocument: True,
|
||||
createViewpoint: True,
|
||||
createComment: True
|
||||
}]->(project1),
|
||||
(project1)-[r10:HAS]->(e1:Extensions {
|
||||
topic_type: ["Information", "Error"],
|
||||
custom_information: ["Custom value 1", "Custom value 2"],
|
||||
topic_status: ["Open", "Closed", "ReOpened", "Custom test status"],
|
||||
topic_label: ["Architecture", "Structural", "MEP"],
|
||||
snippet_type: [".ifc", ".csv"],
|
||||
priority: ["Low", "Medium", "High"],
|
||||
users: ["kommun", "byggherre", "kontrollansvarig", "kontrollant", "martin@wiss.se"],
|
||||
stage: ["Preliminary Planning End", "Construction Start", "Construction End"],
|
||||
project_actions: ["update", "createTopic", "createDocument"],
|
||||
topic_actions: ["update", "updateBimSnippet", "updateRelatedTopics", "updateDocumentReferences", "updateFiles", "createComment", "createViewpoint"],
|
||||
comment_actions: ["update"]}),
|
||||
(project1)-[r11:CONTAINS]->(document1),
|
||||
(project1)-[r12:CONTAINS]->(document2);
|
||||
@@ -0,0 +1,9 @@
|
||||
MATCH (n) DETACH DELETE n;
|
||||
CREATE (:Extensions {topic_type: "", topic_status: "", topic_label: "", snippet_type: "", priority: "", users: "", stage: "", project_actions: "", topic_actions: "", comment_actions: ""})<-[:HAS]-(Project:Project {project_id: "STRING", name: "STRING"})-[:HAS]->(Topic:Topic {guid: "STRING", creation_date: "", modified_date: "", creation_author: "", modified_author: "", topic_type: "", topic_status: "", title: "", priority: "", index: "", assigned_to: "", stage: "", description: "", due_date: ""})-[:HAS]->(Viewpoint:Viewpoint {guid: "", snapshot: "", snapshot_type: "", index: "", camera_definition: "", camera_view_point: "", camera_direction: "", camera_up_vector: "", view_tws_or_fo_view: "", aspect_ratio: "", default_visibility: "", spaces_visible: "", space_boundaries_visible: "", openings_visible: ""})-[:HAS]->(:Component {ifc_guid: "", selected: "", originating_system: "", authoring_tool_id: "", color: "", visibility_exception: "", GTIN: ""})-[:SAME_AS]->(:IfcProduct {GlobalId: ""}),
|
||||
(:`Token:AcessToken` {code: "", scope: "", value: "", hash: ""})<-[:CAN_USE]-(User:User {UUID: "", username: "STRING", full_name: "", email: "", hashed_password: "", disabled: ""})-[:HAS_ACTIONS_ON {read: "BOOLEAN", update: "BOOLEAN", createTopic: "BOOLEAN", createDocument: "BOOLEAN", createViewpoint: "BOOLEAN", createComment: "BOOLEAN", updateDocumentReferences: "BOOLEAN", updateFiles: "BOOLEAN", updateRelatedTopics: "BOOLEAN"}]->(Project)<-[:CONTAINS]-(:Process),
|
||||
(Project)-[:CONTAINS]->(`Document:Model:Temporary`:`Document:Model` {document_id: "", version_index: "", ifc_project: "", name: "", title: "", guid: "", url: "", description: "", session_file_id: "", version_number: "", creation_date: "", original_file_name: "", file_ending: "", mime_type: "", file_type: "", size_in_bytes: "", project: "", reference: "", date: "", ifc_spatial_structure_element: "", filename: ""})<-[:REFERS_TO {guid: ""}]-(Topic)-[:HAS]->(:Comment {guid: "", reply_to_comment_guid: "", date: "", modified_date: "", author: "", modified_author: "", comment: ""})-[:RELATED_TO]->(Viewpoint)-[:HAS]->(:Line {start_point: "", end_point: ""}),
|
||||
(:Part {uuid: "", number: "", content_range_start: "", content_range_end: "", content_length: "", uploaded: "Boolean"})<-[:HAS]-(`Document:Model:Temporary`)<-[:CONTAINS]-(`Session:Upload`:`Session:Upload` {server_context: "", upload_session: "", callback: "", session_url_timedelta: "", session_callback_timedelta: ""})<-[:HAS]-(User)-[:CAN_USE]->(:`Token:RefreshToken` {code: "", scope: "", value: "", hash: ""}),
|
||||
({code: ""})<-[:CAN_USE]-(User)-[:HAS]->(`Session:Select`:`Session:Select` {server_context: "", selection_session: "", callback: "", session_url_timedelta: "", session_callback_timedelta: ""})-[:HAS]->(Link:Link),
|
||||
(`Session:Upload`)-[:HAS]->(Link),
|
||||
(`Session:Select`)-[:SELECTED]->(`Document:Model:Temporary`),
|
||||
(:Bitmap {type: "", location: "", normal: "", up: "", height: ""})<-[:HAS]-(Viewpoint)-[:HAS]->(:CilppingPlane {location: "", direction: ""})
|
||||
@@ -0,0 +1,89 @@
|
||||
# The code in this file originally comes from the following article:
|
||||
#
|
||||
# IFC-graph for facilitating building information access and query
|
||||
#
|
||||
# Junxiang Zhu, Peng Wu *, Xiang Lei
|
||||
#
|
||||
# School of Design and the Built Environment, Curtin University,
|
||||
# Bentley 6102, Western Australia, Australia
|
||||
#
|
||||
# The article was made available online 13 February 2023 in the journal
|
||||
# Automation in Construction 148 (2023) 104778
|
||||
#
|
||||
# 0926-5805/© 2023 The Authors.
|
||||
# Published by Elsevier B.V.
|
||||
#
|
||||
# This is an open access article under the CC BY license (http://creativecommons.org/licenses/by/4.0/).
|
||||
#
|
||||
# Some modifications have been made to the code.
|
||||
#
|
||||
|
||||
from py2neo.data import Node, Relationship
|
||||
from uuid import uuid4
|
||||
import ifcopenshell
|
||||
from py2neo import Graph
|
||||
|
||||
|
||||
# Create the basic node with literal attributes and the class hierarchy
|
||||
def create_pure_node_from_ifc_entity(ifc_entity, ifc_file, hierarchy=True):
|
||||
node = Node()
|
||||
if ifc_entity.id() != 0:
|
||||
node['id'] = ifc_entity.id()
|
||||
else:
|
||||
node['id'] = str(uuid4())
|
||||
node['name'] = ifc_entity.is_a()
|
||||
if hierarchy:
|
||||
for label in ifc_file.wrapped_data.types_with_super():
|
||||
if ifc_entity.is_a(label):
|
||||
node.add_label(label)
|
||||
else:
|
||||
node.add_label(ifc_entity.is_a())
|
||||
attributes_type = ['ENTITY INSTANCE', 'AGGREGATE OF ENTITY INSTANCE', 'DERIVED']
|
||||
for i in range(ifc_entity.__len__()):
|
||||
if not ifc_entity.wrapped_data.get_argument_type(i) in attributes_type:
|
||||
name = ifc_entity.wrapped_data.get_argument_name(i)
|
||||
name_value = ifc_entity.wrapped_data.get_argument(i)
|
||||
node[name]= name_value
|
||||
node.__primarylabel__ = 'Root'
|
||||
node.__primarykey__ = 'id'
|
||||
return node
|
||||
|
||||
|
||||
# Process literal attributes, entity attributes, and relationship attributes
|
||||
def create_graph_from_ifc_entity_all(graph, ifc_entity, ifc_file):
|
||||
node = create_pure_node_from_ifc_entity(ifc_entity, ifc_file)
|
||||
graph.merge(node)
|
||||
for i in range(ifc_entity.__len__()):
|
||||
if ifc_entity[i]:
|
||||
if ifc_entity.wrapped_data.get_argument_type(i) == 'ENTITY INSTANCE':
|
||||
if ifc_entity[i].is_a() in ['IfcOwnerHistory'] and ifc_entity.is_a() != 'IfcProject':
|
||||
continue
|
||||
else:
|
||||
sub_node = create_pure_node_from_ifc_entity(ifc_entity[i], ifc_file)
|
||||
REL = Relationship(node, ifc_entity.wrapped_data.get_argument_name(i), sub_node)
|
||||
graph.merge(REL)
|
||||
elif ifc_entity.wrapped_data.get_argument_type(i) == 'AGGREGATE OF ENTITY INSTANCE':
|
||||
for sub_entity in ifc_entity[i]:
|
||||
sub_node = create_pure_node_from_ifc_entity(sub_entity, ifc_file)
|
||||
REL = Relationship(node, ifc_entity.wrapped_data.get_argument_name(i), sub_node)
|
||||
graph.merge(REL)
|
||||
for rel_name in ifc_entity.wrapped_data.get_inverse_attribute_names():
|
||||
if ifc_entity.wrapped_data.get_inverse(rel_name):
|
||||
inverse_relations = ifc_entity.wrapped_data.get_inverse(rel_name)
|
||||
for wrapped_rel_entity in inverse_relations:
|
||||
rel_entity = ifc_file.by_id(wrapped_rel_entity.id())
|
||||
sub_node = create_pure_node_from_ifc_entity(rel_entity, ifc_file)
|
||||
REL = Relationship(node, rel_name, sub_node)
|
||||
graph.merge(REL)
|
||||
return
|
||||
|
||||
|
||||
def create_full_graph(graph, ifc_file):
|
||||
idx = 1
|
||||
length = len(ifc_file.wrapped_data.entity_names())
|
||||
for entity_id in ifc_file.wrapped_data.entity_names():
|
||||
entity = ifc_file.by_id(entity_id)
|
||||
print(idx, '/', length, entity)
|
||||
create_graph_from_ifc_entity_all(graph, entity, ifc_file)
|
||||
idx += 1
|
||||
return
|
||||
Binary file not shown.
@@ -0,0 +1,120 @@
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import HTMLResponse, FileResponse
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from api import foundation
|
||||
from api import bcf
|
||||
from api import documents
|
||||
|
||||
endpoint_metadata = [
|
||||
{"name": "api_versions_get", "description": "/foundation/versions"},
|
||||
{"name": "foundation_auth_get", "description": "/foundation/1.0/auth"},
|
||||
{"name": "authentication_get", "description": "/authentication"},
|
||||
{"name": "login_for_access_token_post", "description": "/foundation/oauth2/token"},
|
||||
{"name": "current_user_get", "description": "/foundation/1.0/current-user"},
|
||||
{"name": "projects_get",
|
||||
"description": "Retrieve a collection of projects that the currently logged on user has access to."},
|
||||
{"name": "project_get",
|
||||
"description": "Retrieve a specific project. The top level data container is known as the BCF project, "
|
||||
"with a UUID and a project name attribute."},
|
||||
{"name": "project_put",
|
||||
"description": "Modify a specific project. This operation is only possible when the server returns the update "
|
||||
"flag in the Project authorization."},
|
||||
{"name": "project_extensions_get",
|
||||
"description": "Retrieve a specific projects extensions. Project extensions are used to define possible values "
|
||||
"that can be used in topics and comments, for example topic labels and priorities. They may "
|
||||
"change during the course of a project. The most recent extensions state which values are valid "
|
||||
"at a given moment for newly created topics and comments."},
|
||||
{"name": "topics_get",
|
||||
"description": "Retrieve a collection of topics related to a project (default sort order is creation_date)."},
|
||||
{"name": "topic_post",
|
||||
"description": "Add a new topic. The BCF project contains zero or more topics. Each topic represents a model "
|
||||
"issue. A topic will have a UUID, a title, description, priority, stage, labels (similar to "
|
||||
"tags), creation date / author, due date, and assigned to. If modified, it may contain the "
|
||||
"modification date and author."},
|
||||
{"name": "topic_get", "description": "Retrieve a specific topic."},
|
||||
{"name": "topic_put", "description": "Modify a specific topic, description similar to POST."},
|
||||
{"name": "bim_snippet_get",
|
||||
"description": "Retrieves a topics BIM-Snippet as binary file. BIM snippet has been in BCF specification since "
|
||||
"the very beginning, but is has never been used. Snippets have originally been added to provide "
|
||||
"for 'changes in IFC'. To get this really working in a CDE environment changes to the IFC format "
|
||||
"is necessary."},
|
||||
{"name": "bim_snippet_put",
|
||||
"description": "Puts a new BIM Snippet binary file to a topic. If this is used, the parent topics BIM Snippet "
|
||||
"property is_external must be set to false and the reference must be the file name with "
|
||||
"extension."},
|
||||
{"name": "files_get", "description": "Retrieve a collection of file references as topic header."},
|
||||
{"name": "files_put", "description": "Update a collection of file references on the topic header."},
|
||||
{"name": "comments_get",
|
||||
"description": "Retrieve a collection of all comments related to a topic (default ordering is date)."},
|
||||
{"name": "comment_post", "description": "Add a new comment to a topic."},
|
||||
{"name": "comment_put", "description": "Update a single comment, description similar to POST."},
|
||||
{"name": "comment_get", "description": "Get a single comment."},
|
||||
{"name": "viewpoints_get", "description": "Retrieve a collection of all viewpoints related to a topic."},
|
||||
{"name": "viewpoint_post",
|
||||
"description": "Add a new viewpoint. Viewpoints are immutable, meaning that they should never change. "
|
||||
"Requirements for different visualizations should be handled by creating new viewpoint elements."},
|
||||
{"name": "viewpoint_get", "description": "Retrieve a specific viewpoint."},
|
||||
{"name": "viewpoint_selected_components_get",
|
||||
"description": "Retrieve a collection of all selected components in a viewpoint."},
|
||||
{"name": "viewpoint_colored_components_get",
|
||||
"description": "Retrieve a collection of all colored components in a viewpoint."},
|
||||
{"name": "viewpoint_components_visibility_get", "description": "Retrieve visibility of components in a viewpoint."},
|
||||
{"name": "viewpoint_snapshot_get", "description": "Retrieve a specific viewpoints bitmap image file (png or jpg)."},
|
||||
{"name": "related_topics_get", "description": "Retrieve a collection of all related topics to a topic."},
|
||||
{"name": "related_topics_put", "description": "Add or update a collection of all related topics to a topic."},
|
||||
{"name": "topic_document_references_get",
|
||||
"description": "Retrieve a collection of all document references to a topic."},
|
||||
{"name": "topic_document_references_post", "description": "Add or update document references to a topic."},
|
||||
{"name": "topic_document_references_put", "description": "Add or update document references to a topic."},
|
||||
{"name": "documents_get", "description": "Retrieve a collection of all documents uploaded to a project."},
|
||||
{"name": "document_post", "description": "Upload a document (binary file) to a project."},
|
||||
{"name": "document_get", "description": "Retrieves a document as binary file."},
|
||||
{"name": "topics_events_get",
|
||||
"description": "Retrieve a collection of topic events related to a project (default sort order is date)."},
|
||||
{"name": "topic_events_get",
|
||||
"description": "Retrieve a collection of topic events related to a project (default sort order is date)."},
|
||||
{"name": "comments_events_get",
|
||||
"description": "Retrieve a collection of comment events related to a project (default sort order is date)."},
|
||||
{"name": "comment_events_get",
|
||||
"description": "Retrieve a collection of comment events related to a comment (default sort order is date)."}
|
||||
]
|
||||
|
||||
app = FastAPI(
|
||||
title="Kontroll API",
|
||||
description="Implementering av BCF API 3.0",
|
||||
version="0.0.1",
|
||||
openapi_tags=endpoint_metadata
|
||||
)
|
||||
|
||||
# Configure app to accept requests from anywhere
|
||||
origins = ["*"]
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
app.include_router(foundation.router, prefix='')
|
||||
app.include_router(bcf.router, prefix='')
|
||||
app.include_router(documents.router, prefix='')
|
||||
|
||||
templates = Jinja2Templates(directory="templates")
|
||||
|
||||
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
def index(request: Request):
|
||||
return templates.TemplateResponse(
|
||||
"index.html",
|
||||
{"request": request})
|
||||
|
||||
|
||||
favicon_path = 'favicon.ico'
|
||||
|
||||
|
||||
@app.get('/favicon.ico', include_in_schema=False)
|
||||
async def favicon():
|
||||
return FileResponse(favicon_path)
|
||||
@@ -0,0 +1,87 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class BimSnippet(BaseModel):
|
||||
snippet_type: str
|
||||
is_external: str
|
||||
reference: str
|
||||
reference_schema: str
|
||||
|
||||
|
||||
class Point(BaseModel):
|
||||
x: Optional[float] = None
|
||||
y: Optional[float] = None
|
||||
z: Optional[float] = None
|
||||
|
||||
|
||||
class Line(BaseModel):
|
||||
start_point: Optional[Point] = None
|
||||
end_point: Optional[Point] = None
|
||||
|
||||
|
||||
class Direction(BaseModel):
|
||||
x: Optional[float] = None
|
||||
y: Optional[float] = None
|
||||
z: Optional[float] = None
|
||||
|
||||
|
||||
class OrthogonalCamera(BaseModel):
|
||||
camera_view_point: Optional[Point] = None
|
||||
camera_direction: Optional[Direction] = None
|
||||
camera_up_vector: Optional[Direction] = None
|
||||
view_to_world_scale: Optional[float] = None
|
||||
aspect_ratio: Optional[float] = None
|
||||
|
||||
|
||||
class PerspectiveCamera(BaseModel):
|
||||
camera_view_point: Optional[Point] = None
|
||||
camera_direction: Optional[Direction] = None
|
||||
camera_up_vector: Optional[Direction] = None
|
||||
field_of_view: Optional[float] = None
|
||||
aspect_ratio: Optional[float] = None
|
||||
|
||||
|
||||
class Location(BaseModel):
|
||||
x: Optional[float] = None
|
||||
y: Optional[float] = None
|
||||
z: Optional[float] = None
|
||||
|
||||
|
||||
class ClippingPlane(BaseModel):
|
||||
location: Optional[Location] = None
|
||||
direction: Optional[Direction] = None
|
||||
|
||||
|
||||
class SnapshotType(Enum):
|
||||
jpg = 'jpg'
|
||||
png = 'png'
|
||||
|
||||
|
||||
class BitmapType(Enum):
|
||||
jpg = 'jpg'
|
||||
png = 'png'
|
||||
|
||||
|
||||
class Component(BaseModel):
|
||||
ifc_guid: Optional[str] = None
|
||||
originating_system: Optional[str] = None
|
||||
authoring_tool_id: Optional[str] = None
|
||||
|
||||
|
||||
class Coloring(BaseModel):
|
||||
color: Optional[str] = None
|
||||
components: Optional[List[Component]] = None
|
||||
|
||||
|
||||
class ViewSetupHints(BaseModel):
|
||||
spaces_visible: Optional[bool] = False
|
||||
space_boundaries_visible: Optional[bool] = False
|
||||
openings_visible: Optional[bool] = False
|
||||
|
||||
|
||||
class Visibility(BaseModel):
|
||||
default_visibility: Optional[bool] = False
|
||||
exceptions: Optional[List[Component]] = None
|
||||
view_setup_hints: Optional[ViewSetupHints] = None
|
||||
@@ -0,0 +1,108 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
from models.bcf_common import BimSnippet, BitmapType, Location, Direction, SnapshotType, Component
|
||||
from models.bcf_common import Coloring, OrthogonalCamera, Visibility, PerspectiveCamera, Line, ClippingPlane
|
||||
|
||||
|
||||
class ProjectPUT(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
class TopicPOST(BaseModel):
|
||||
guid: Optional[str] = None
|
||||
topic_type: Optional[str] = None
|
||||
topic_status: Optional[str] = None
|
||||
reference_links: Optional[List[str]] = None
|
||||
title: str
|
||||
priority: Optional[str] = None
|
||||
index: Optional[int] = None
|
||||
labels: Optional[List[str]] = None
|
||||
assigned_to: Optional[str] = None
|
||||
stage: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
bim_snippet: Optional[BimSnippet] = None
|
||||
due_date: Optional[str] = None
|
||||
|
||||
|
||||
class TopicPUT(BaseModel):
|
||||
topic_type: Optional[str] = None
|
||||
topic_status: Optional[str] = None
|
||||
reference_links: Optional[List[str]] = None
|
||||
title: str
|
||||
priority: Optional[str] = None
|
||||
index: Optional[int] = None
|
||||
labels: Optional[List[str]] = None
|
||||
assigned_to: Optional[str] = None
|
||||
stage: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
bim_snippet: Optional[BimSnippet] = None
|
||||
due_date: Optional[str] = None
|
||||
|
||||
|
||||
class FilePUT(BaseModel):
|
||||
ifc_project: Optional[str] = None
|
||||
ifc_spatial_structure_element: Optional[str] = None
|
||||
filename: Optional[str] = None
|
||||
date: Optional[str] = None
|
||||
reference: Optional[str] = None
|
||||
|
||||
|
||||
class CommentPOST(BaseModel):
|
||||
guid: Optional[str] = None # comment id
|
||||
comment: str
|
||||
viewpoint_guid: Optional[str] = None
|
||||
# reply_to_comment_guid: Optional[str] = None
|
||||
|
||||
|
||||
class CommentPUT(BaseModel):
|
||||
comment: str
|
||||
viewpoint_guid: Optional[str] = None
|
||||
|
||||
|
||||
class BitmapPOST(BaseModel):
|
||||
bitmap_type: Optional[BitmapType] = None
|
||||
bitmap_data: Optional[str] = None
|
||||
location: Optional[Location] = None
|
||||
normal: Optional[Direction] = None
|
||||
up: Optional[Direction] = None
|
||||
height: Optional[float] = None
|
||||
|
||||
|
||||
class SnapshotPOST(BaseModel):
|
||||
snapshot_type: Optional[SnapshotType] = None
|
||||
snapshot_data: Optional[str] = None
|
||||
|
||||
|
||||
class Components(BaseModel):
|
||||
selection: Optional[List[Component]] = None
|
||||
coloring: Optional[List[Coloring]] = None
|
||||
visibility: Optional[Visibility] = None
|
||||
|
||||
|
||||
class ViewpointPOST(BaseModel):
|
||||
guid: Optional[str] = None
|
||||
index: Optional[int] = None
|
||||
orthogonal_camera: Optional[OrthogonalCamera] = None
|
||||
perspective_camera: Optional[PerspectiveCamera] = None
|
||||
lines: Optional[List[Line]] = None
|
||||
clipping_planes: Optional[List[ClippingPlane]] = None
|
||||
bitmaps: Optional[List[BitmapPOST]] = None
|
||||
snapshot: Optional[SnapshotPOST] = None
|
||||
components: Optional[Components] = None
|
||||
|
||||
|
||||
class RelatedTopicPUT(BaseModel):
|
||||
related_topic_guid: str
|
||||
|
||||
|
||||
class DocumentReferencePOST(BaseModel):
|
||||
guid: Optional[str] = None
|
||||
document_guid: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class DocumentReferencePUT(BaseModel):
|
||||
document_guid: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
@@ -0,0 +1,197 @@
|
||||
from models.bcf_common import *
|
||||
|
||||
|
||||
class ProjectAction(Enum):
|
||||
update = 'update'
|
||||
createTopic = 'createTopic'
|
||||
createDocument = 'createDocument'
|
||||
|
||||
|
||||
class ProjectGETAuthorization(BaseModel):
|
||||
project_actions: Optional[List[ProjectAction]] = None
|
||||
|
||||
|
||||
class ProjectGET(BaseModel):
|
||||
project_id: str
|
||||
name: str
|
||||
authorization: Optional[ProjectGETAuthorization] = None
|
||||
|
||||
|
||||
class TopicAction(Enum):
|
||||
update = 'update'
|
||||
updateBimSnippet = 'updateBimSnippet'
|
||||
updateRelatedTopics = 'updateRelatedTopics'
|
||||
updateDocumentReferences = 'updateDocumentReferences'
|
||||
updateFiles = 'updateFiles'
|
||||
createComment = 'createComment'
|
||||
createViewpoint = 'createViewpoint'
|
||||
delete = 'delete'
|
||||
|
||||
|
||||
class CommentAction(Enum):
|
||||
update = 'update'
|
||||
delete = 'delete'
|
||||
|
||||
|
||||
class ExtensionsGET(BaseModel):
|
||||
topic_type: List[str]
|
||||
custom_information: List[str]
|
||||
topic_status: List[str]
|
||||
topic_label: List[str]
|
||||
snippet_type: List[str]
|
||||
priority: List[str]
|
||||
users: List[str]
|
||||
stage: List[str]
|
||||
project_actions: Optional[List[str]] = None
|
||||
topic_actions: Optional[List[str]] = None
|
||||
comment_actions: Optional[List[str]] = None
|
||||
|
||||
|
||||
class TopicGETAuthorization(BaseModel):
|
||||
topic_actions: Optional[List[TopicAction]] = None
|
||||
topic_status: Optional[List[str]] = None
|
||||
|
||||
|
||||
class TopicGET(BaseModel):
|
||||
guid: str
|
||||
server_assigned_id: str
|
||||
topic_type: Optional[str] = None
|
||||
topic_status: Optional[str] = None
|
||||
reference_links: Optional[List[str]] = None
|
||||
title: str
|
||||
priority: Optional[str] = None
|
||||
index: Optional[int] = None
|
||||
labels: Optional[List[str]] = None
|
||||
creation_date: str
|
||||
creation_author: str
|
||||
modified_date: Optional[str] = None
|
||||
modified_author: Optional[str] = None
|
||||
assigned_to: Optional[str] = None
|
||||
stage: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
bim_snippet: Optional[BimSnippet] = None
|
||||
due_date: Optional[str] = None
|
||||
authorization: Optional[TopicGETAuthorization] = None
|
||||
|
||||
|
||||
class ProjectFileDisplayInformation(BaseModel):
|
||||
field_display_name: str
|
||||
field_value: str
|
||||
|
||||
|
||||
class FileGET(BaseModel):
|
||||
ifc_project: Optional[str] = None
|
||||
ifc_spatial_structure_element: Optional[str] = None
|
||||
filename: Optional[str] = None
|
||||
date: Optional[str] = None
|
||||
reference: Optional[str] = None
|
||||
|
||||
|
||||
class ProjectFileInformation(BaseModel):
|
||||
display_information: Optional[List[ProjectFileDisplayInformation]] = None
|
||||
file: Optional[FileGET] = None
|
||||
|
||||
|
||||
class CommentGETAuthorization(BaseModel):
|
||||
comment_actions: Optional[List[CommentAction]] = None
|
||||
|
||||
|
||||
class CommentGET(BaseModel):
|
||||
guid: str
|
||||
date: str
|
||||
author: str
|
||||
comment: str
|
||||
topic_guid: str
|
||||
viewpoint_guid: Optional[str] = None
|
||||
reply_to_comment_guid: Optional[str] = None
|
||||
modified_date: Optional[str] = None
|
||||
modified_author: Optional[str] = None
|
||||
authorization: Optional[CommentGETAuthorization] = None
|
||||
|
||||
|
||||
class BitmapGET(BaseModel):
|
||||
guid: Optional[str] = None
|
||||
bitmap_type: Optional[BitmapType] = None
|
||||
location: Optional[Location] = None
|
||||
normal: Optional[Direction] = None
|
||||
up: Optional[Direction] = None
|
||||
height: Optional[float] = None
|
||||
|
||||
|
||||
class SnapshotGET(BaseModel):
|
||||
snapshot_type: Optional[SnapshotType] = None
|
||||
|
||||
|
||||
class ViewpointAction(Enum):
|
||||
delete = 'delete'
|
||||
|
||||
|
||||
class ViewpointGETAuthorization(BaseModel):
|
||||
viewpoint_actions: Optional[List[ViewpointAction]] = None
|
||||
|
||||
|
||||
class ViewpointGET(BaseModel):
|
||||
index: Optional[int] = None
|
||||
guid: str
|
||||
orthogonal_camera: Optional[OrthogonalCamera] = None
|
||||
perspective_camera: Optional[PerspectiveCamera] = None
|
||||
lines: Optional[List[Line]] = None
|
||||
clipping_planes: Optional[List[ClippingPlane]] = None
|
||||
bitmaps: Optional[List[BitmapGET]] = None
|
||||
snapshot: Optional[SnapshotGET] = None
|
||||
authorization: Optional[ViewpointGETAuthorization] = None
|
||||
|
||||
|
||||
class ColoringGET(BaseModel):
|
||||
coloring: Optional[List[Coloring]] = None
|
||||
|
||||
|
||||
class SelectionGET(BaseModel):
|
||||
selection: Optional[List[Component]] = None
|
||||
|
||||
|
||||
class VisibilityGET(BaseModel):
|
||||
visibility: Optional[Visibility] = None
|
||||
|
||||
|
||||
class RelatedTopicGET(BaseModel):
|
||||
related_topic_guid: str
|
||||
|
||||
|
||||
class DocumentReferenceGET(BaseModel):
|
||||
guid: str
|
||||
document_guid: Optional[str] = None
|
||||
url: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
|
||||
|
||||
class DocumentGET(BaseModel):
|
||||
guid: str
|
||||
filename: str
|
||||
|
||||
|
||||
class TopicEventGET(BaseModel):
|
||||
topic_guid: str
|
||||
date: str
|
||||
author: str
|
||||
# actions: Optional[List[EventAction]] = Field(None, min_items=1)
|
||||
|
||||
|
||||
class CommentEventGET(BaseModel):
|
||||
comment_guid: str
|
||||
topic_guid: str
|
||||
date: str
|
||||
author: str
|
||||
# actions: Optional[List[EventAction]] = Field(None, min_items=1)
|
||||
|
||||
|
||||
# ---- maybe not necessary now
|
||||
|
||||
|
||||
class EventAction(BaseModel):
|
||||
type: str
|
||||
value: Optional[str] = None
|
||||
|
||||
|
||||
class Error(BaseModel):
|
||||
message: str
|
||||
@@ -0,0 +1,99 @@
|
||||
from pydantic import BaseModel, Field, constr
|
||||
from typing import List, Optional
|
||||
|
||||
|
||||
# This file contains models used in both requests and responses during upload and download of documents.
|
||||
|
||||
|
||||
# ---- REQUEST MODELS ---- #
|
||||
|
||||
|
||||
class CallbackLink(BaseModel):
|
||||
url: constr(min_length=1) = Field(
|
||||
description='The server will web-browser-redirect to this URL once the user has completed selecting '
|
||||
'documents or entering document metadata on the CDE'
|
||||
)
|
||||
expires_in: int = Field(
|
||||
description='The expiry period for the URL, in seconds'
|
||||
)
|
||||
|
||||
|
||||
# ---- RESPONSE MODELS ---- #
|
||||
|
||||
|
||||
class LinkData(BaseModel):
|
||||
url: constr(min_length=1)
|
||||
|
||||
|
||||
class DocumentVersionLinks(BaseModel):
|
||||
document_version: LinkData
|
||||
document_version_metadata: LinkData
|
||||
document_version_download: LinkData
|
||||
document_versions: LinkData
|
||||
document_details: Optional[LinkData] = None
|
||||
|
||||
|
||||
class FileDescription(BaseModel):
|
||||
name: constr(min_length=1) = Field(
|
||||
description='The name of the document version file on the server. The files are named by '
|
||||
'document_id.file_ending',
|
||||
example='908e1cd4-2e09-11ee-be56-0242ac120002.ifc'
|
||||
)
|
||||
size_in_bytes: int = Field(
|
||||
description='The size of the file in bytes',
|
||||
example='124563'
|
||||
)
|
||||
|
||||
|
||||
class Document(BaseModel):
|
||||
document_id: constr(min_length=1) = Field(
|
||||
description='A machine readable identifier that can be used to uniquely identify this version in future calls '
|
||||
'UUID is used - see `Query` section',
|
||||
example='908e1cd4-2e09-11ee-be56-0242ac120002'
|
||||
)
|
||||
session_file_id: Optional[str] = Field(
|
||||
description='A machine readable identifier that can be used to uniquely the file '
|
||||
'during the upload session, UUID is used',
|
||||
example='908e1cd4-2e09-11ee-be56-0242ac120002'
|
||||
)
|
||||
version_index: int = Field(
|
||||
description='A machine readable sequence number of the version of the document. The sequence must be ordered, '
|
||||
'so that newer versions have higher values than previous ones. Each version index must be unique '
|
||||
'for that document, but there may be gaps in the sequence',
|
||||
example='12'
|
||||
)
|
||||
version_number: Optional[constr(min_length=1)] = Field(
|
||||
description='A human readable version number. This is not expected to be in any specific format across CDEs '
|
||||
'and may hold any value',
|
||||
example='V2.0-larger'
|
||||
)
|
||||
creation_date: str = Field(
|
||||
description='The creation date of the document revision',
|
||||
example='2016-04-28T16:31:12.270+02:00'
|
||||
)
|
||||
title: Optional[constr(min_length=1)] = Field(
|
||||
description='A human readable code or identifier. Metadata entered by user in CDE.',
|
||||
example='Large garage'
|
||||
)
|
||||
original_file_name: Optional[str] = Field(
|
||||
description='The full name of the file as sent to the API',
|
||||
example='First_floor_vent.ifc')
|
||||
file_ending: Optional[str] = Field(
|
||||
description='The ending of the file name, including the dot',
|
||||
example='.ifc')
|
||||
mime_type: Optional[str] = Field(
|
||||
description='The mime type identifier',
|
||||
example='application/x-step')
|
||||
file_type: Optional[str] = Field(
|
||||
description='The full name of the file type',
|
||||
example='STEP Physical File (SPF)')
|
||||
project: Optional[str] = Field(
|
||||
description='The project to which the document will belong once it has been uploaded,'
|
||||
'this information is added as metadata by the user in the CDE',
|
||||
example='908e1cd4-2e09-11ee-be56-0242ac120003')
|
||||
file_description: FileDescription
|
||||
parts: Optional[List[str]]
|
||||
|
||||
|
||||
class DocumentVersion(Document):
|
||||
links: DocumentVersionLinks
|
||||
@@ -0,0 +1,26 @@
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel
|
||||
from models.documents_common import Document, DocumentVersion
|
||||
|
||||
|
||||
# This file contains models that are not used at all, at least not yet.
|
||||
|
||||
|
||||
# ---- not used at all, at least not yet ----
|
||||
|
||||
|
||||
class DocumentQuery(BaseModel):
|
||||
document_ids: List[str]
|
||||
|
||||
|
||||
class DocumentUpload(Document):
|
||||
session_file_id: Optional[str]
|
||||
ifc_project: Optional[str]
|
||||
|
||||
|
||||
class MetadataForDocumentsSaved(BaseModel):
|
||||
documents: Optional[List[str]]
|
||||
|
||||
|
||||
class DocumentQueryResult(BaseModel):
|
||||
versions: List[DocumentVersion]
|
||||
@@ -0,0 +1,84 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional
|
||||
|
||||
from models.documents_common import CallbackLink, Document
|
||||
from models.documents_request import FileToUpload, DocumentMetadataEntry
|
||||
from models.request import User
|
||||
|
||||
|
||||
# This file contains models that are neither used in requests nor in responses,
|
||||
# and they can be used during both upload and download of documents.
|
||||
|
||||
|
||||
# ---- UPLOAD MODELS ----
|
||||
|
||||
|
||||
class ProjectOnly(BaseModel):
|
||||
project_id: str
|
||||
name: str
|
||||
|
||||
|
||||
class DataForUploadDocuments(BaseModel):
|
||||
server_context: Optional[str] = Field(
|
||||
description="A CDE controlled identifier recording the user's context on the CDE. For example which project "
|
||||
'and folder the user was on. If the client provides the `server_context` in subsequent calls then '
|
||||
'the CDE will attemp to load the UI at the same place.'
|
||||
)
|
||||
documents: List[FileToUpload]
|
||||
callback: Optional[CallbackLink]
|
||||
current_user: Optional[User]
|
||||
projects: List[ProjectOnly]
|
||||
|
||||
|
||||
# ---- DOWNLOAD MODELS ----
|
||||
|
||||
class DocumentMetadataEntries(BaseModel):
|
||||
metadata: List[DocumentMetadataEntry] = Field(
|
||||
description='An array of metadata entries'
|
||||
)
|
||||
|
||||
|
||||
class Project(BaseModel):
|
||||
project_id: str
|
||||
name: str
|
||||
documents: Optional[List[Document]] = Field(
|
||||
description='An array containing all the documents selected by the user'
|
||||
)
|
||||
|
||||
|
||||
class DataForDocumentSelection(BaseModel):
|
||||
server_context: Optional[str] = Field(
|
||||
description="A CDE controlled identifier recording the user's context on the CDE. For example which project "
|
||||
'and folder the user was on. If the client provides the `server_context` in subsequent calls then '
|
||||
'the CDE will attemp to load the UI at the same place.'
|
||||
)
|
||||
projects: List[Project]
|
||||
callback: Optional[CallbackLink]
|
||||
current_user: Optional[User]
|
||||
|
||||
|
||||
# ---- OTHER MODELS ----
|
||||
|
||||
|
||||
file_types = {
|
||||
'smc': {
|
||||
'file_type': 'Solibri Model Checker',
|
||||
'file_ending': '.smc',
|
||||
'mime_type': 'application/octet-stream'
|
||||
},
|
||||
'ifc': {
|
||||
'file_type': 'STEP Physical File',
|
||||
'file_ending': '.ifc',
|
||||
'mime_type': 'application/x-step'
|
||||
},
|
||||
'ifczip': {
|
||||
'file_type': 'ZIP of a STEP Physical File',
|
||||
'file_ending': '.ifcZIP',
|
||||
'mime_type': 'application/zip'
|
||||
},
|
||||
'pdf': {
|
||||
'file_type': 'Adobe Portable Document Format',
|
||||
'file_ending': '.pdf',
|
||||
'mime_type': 'application/pdf'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
from pydantic import BaseModel, Field, constr
|
||||
from typing import List, Optional
|
||||
from enum import Enum
|
||||
|
||||
from models.documents_common import CallbackLink
|
||||
|
||||
|
||||
# This file contains models used in requests during upload and download of documents.
|
||||
|
||||
|
||||
# ---- UPLOAD MODELS ----
|
||||
|
||||
|
||||
class FileToUpload(BaseModel):
|
||||
file_name: constr(min_length=1) = Field(
|
||||
description='The CDE UI will display this value to the User when entering document metadata. This is the '
|
||||
'original name of the file. This attribute is the same as the name attribute in a document model.'
|
||||
)
|
||||
session_file_id: constr(min_length=1) = Field(
|
||||
description='This is a client provided id to differentiate between multiple files that are being uploaded in '
|
||||
'the same session'
|
||||
)
|
||||
document_id: Optional[constr(min_length=1)] = Field(
|
||||
description='When present, indicates that this upload is a new version of an existing document'
|
||||
)
|
||||
|
||||
|
||||
class UploadDocuments(BaseModel):
|
||||
callback: CallbackLink
|
||||
server_context: Optional[str] = Field(
|
||||
description="A CDE controlled identifier recording the user's context on the CDE. For example which project "
|
||||
'and folder the user was on. If the client provides the `server_context` in subsequent calls then '
|
||||
'the CDE will attemp to load the UI at the same place.'
|
||||
)
|
||||
files: List[FileToUpload]
|
||||
|
||||
|
||||
class UploadFileDetail(BaseModel):
|
||||
size_in_bytes: int = Field(
|
||||
description='The uploaded file size'
|
||||
)
|
||||
session_file_id: constr(min_length=1) = Field(
|
||||
description='This is a client provided id to differentiate between multiple files that are being uploaded in '
|
||||
'the same session'
|
||||
)
|
||||
|
||||
|
||||
class UploadFileDetails(BaseModel):
|
||||
files: List[UploadFileDetail]
|
||||
|
||||
|
||||
# ---- DOWNLOAD MODELS ----
|
||||
|
||||
|
||||
class SelectDocuments(BaseModel):
|
||||
callback: CallbackLink
|
||||
server_context: Optional[str] = Field(
|
||||
description="A CDE controlled identifier recording the user's context on the CDE. For example which project "
|
||||
'and folder the user was on. If the client provides the `server_context` in subsequent calls then '
|
||||
'the CDE will attemp to load the UI at the same place.'
|
||||
)
|
||||
supported_file_extensions: Optional[List[str]] = Field(
|
||||
description='The client may optionally provide an array of accepted file extensions that should be opened '
|
||||
'during this flow. The CDE server UI should make an attempt to only show files matching these '
|
||||
'extensions to the user for the download selection or help the user in selecting the desired '
|
||||
'files. However, the server does not have to guarantee that only files matching the extensions '
|
||||
'will be selected. The extensions here must contain the dot separator.',
|
||||
example=['.ifc', '.ifczip']
|
||||
)
|
||||
|
||||
|
||||
class DataType(Enum):
|
||||
string = 'string'
|
||||
boolean = 'boolean'
|
||||
date_time = 'date-time'
|
||||
date = 'date'
|
||||
integer32 = 'integer32'
|
||||
integer64 = 'integer64'
|
||||
number = 'number'
|
||||
url = 'url'
|
||||
|
||||
|
||||
class DocumentMetadataEntry(BaseModel):
|
||||
name: constr(min_length=1) = Field(
|
||||
description='The name of the metadata property'
|
||||
)
|
||||
value: List[constr(min_length=1)] = Field(
|
||||
description='The value of the metadata property, can be a list'
|
||||
)
|
||||
data_type: DataType = Field(
|
||||
description='The data type of the items in the value array'
|
||||
)
|
||||
@@ -0,0 +1,139 @@
|
||||
from pydantic import BaseModel, Field, constr
|
||||
from typing import List, Optional
|
||||
from enum import Enum
|
||||
|
||||
from models.documents_common import DocumentVersion, LinkData
|
||||
|
||||
|
||||
# This file contains models used in responses during upload and download of documents.
|
||||
|
||||
|
||||
# ---- UPLOAD MODELS ----
|
||||
|
||||
|
||||
class DocumentUploadSessionInitialization(BaseModel):
|
||||
upload_ui_url: constr(min_length=1) = Field(
|
||||
description='A CDE UI URL for the client to open in a local browser. The user would enter document metadata '
|
||||
'directly in the CDE'
|
||||
)
|
||||
expires_in: int = Field(
|
||||
description='`upload_ui_url` expiry in seconds'
|
||||
)
|
||||
max_size_in_bytes: int = Field(
|
||||
description='The maximum file size supported by the CDE. Attempts to upload a larger file will fail'
|
||||
)
|
||||
|
||||
|
||||
class HttpMethod(Enum):
|
||||
POST = 'POST'
|
||||
PUT = 'PUT'
|
||||
|
||||
|
||||
class HeaderValue(BaseModel):
|
||||
name: constr(min_length=1)
|
||||
value: constr(min_length=1)
|
||||
|
||||
|
||||
class Headers(BaseModel):
|
||||
values: List[HeaderValue]
|
||||
|
||||
|
||||
class MultipartFormData(BaseModel):
|
||||
prefix: str = Field(
|
||||
description='This is a server provided value. Its value must be prefixed to the binary content body when '
|
||||
'uploading this part'
|
||||
)
|
||||
suffix: str = Field(
|
||||
description='This is a server provided value. Its value must be suffixed to the binary content body when '
|
||||
'uploading this part. Typically, this is the end boundary for a multipart/form-data request'
|
||||
)
|
||||
|
||||
|
||||
class UploadFilePartInstruction(BaseModel):
|
||||
url: constr(min_length=1)
|
||||
http_method: HttpMethod
|
||||
additional_headers: Optional[Headers] = None
|
||||
include_authorization: Optional[bool] = Field(
|
||||
description='Whether or not to include the authorization request header in the file upload request. '
|
||||
'Including the authorization header with some cloud storage providers might fail the request'
|
||||
)
|
||||
multipart_form_data: Optional[MultipartFormData] = None
|
||||
content_range_start: int = Field(
|
||||
description='The inclusive, zero index based start for this part'
|
||||
)
|
||||
content_range_end: int = Field(
|
||||
description='The inclusive, zero index based end for this part'
|
||||
)
|
||||
|
||||
|
||||
class DocumentToUpload(BaseModel):
|
||||
session_file_id: constr(min_length=1) = Field(
|
||||
description='A client-provided identifier that allows matching the specification with the correct file on the '
|
||||
"user's machine"
|
||||
)
|
||||
upload_file_parts: List[UploadFilePartInstruction] = Field(
|
||||
description='An array of request specifications detailing how to split the file to parts and upload each part '
|
||||
'to the CDE'
|
||||
# min_length=1,
|
||||
)
|
||||
upload_completion: LinkData
|
||||
upload_cancellation: LinkData
|
||||
|
||||
|
||||
class DocumentsToUpload(BaseModel):
|
||||
server_context: Optional[str] = Field(
|
||||
description="A CDE controlled identifier recording the user's context on the CDE. For example which project "
|
||||
'and folder the user was on. If the client provides the `server_context` in subsequent calls then '
|
||||
'the CDE will attemp to load the UI at the same place.'
|
||||
)
|
||||
documents_to_upload: Optional[List[DocumentToUpload]]
|
||||
|
||||
|
||||
# ---- DOWNLOAD MODELS ----
|
||||
|
||||
|
||||
class DocumentDiscoverySessionInitialization(BaseModel):
|
||||
select_documents_url: constr(min_length=1) = Field(
|
||||
description='A CDE UI URL for the client to open in a local browser. The user would search and select '
|
||||
'documents directly in the CDE'
|
||||
)
|
||||
expires_in: int = Field(
|
||||
description='`select_documents_url` expiry in seconds'
|
||||
)
|
||||
|
||||
|
||||
class DocumentsMarkedAsSelected(BaseModel):
|
||||
documents: Optional[List[str]]
|
||||
|
||||
|
||||
class SelectedDocuments(BaseModel):
|
||||
server_context: Optional[str] = Field(
|
||||
description="A CDE controlled identifier recording the user's context on the CDE. For example which project "
|
||||
'and folder the user was on. If the client provides the `server_context` in subsequent calls then '
|
||||
'the CDE will attemp to load the UI at the same place.'
|
||||
)
|
||||
documents: List[DocumentVersion] = Field(
|
||||
description='An array containing all the documents selected by the user'
|
||||
)
|
||||
|
||||
|
||||
class DocumentMetadata(BaseModel):
|
||||
session_file_id: constr(min_length=1) = Field(
|
||||
description='This is a client provided id to differentiate between multiple files that are being uploaded in '
|
||||
'the same session'
|
||||
)
|
||||
document_id: Optional[constr(min_length=1)] = Field(
|
||||
description='When present, indicates that this upload is a new version of an existing document'
|
||||
)
|
||||
version_number: constr(min_length=1) = Field(
|
||||
description='A human readable version number. This is not expected to be in any specific format across CDEs '
|
||||
'and may hold any value'
|
||||
)
|
||||
title: constr(min_length=1) = Field(
|
||||
description='A human readable code or identifier'
|
||||
)
|
||||
project: Optional[str]
|
||||
|
||||
|
||||
class DocumentVersions(BaseModel):
|
||||
documents: List[DocumentVersion]
|
||||
@@ -0,0 +1,17 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class TokenRequest(BaseModel):
|
||||
grant_type: str | None = None
|
||||
|
||||
|
||||
class UserInfo(BaseModel):
|
||||
username: str
|
||||
scope: str
|
||||
|
||||
|
||||
class TokenInfo(BaseModel):
|
||||
access_token: str | None = None
|
||||
refresh_token: str | None = None
|
||||
token_type: str | None = None
|
||||
expires_in: str | None = None
|
||||
@@ -0,0 +1,18 @@
|
||||
from pydantic import BaseModel
|
||||
from typing import List
|
||||
|
||||
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
username: str | None = None
|
||||
scopes: List[str] = []
|
||||
expires: str | None = None
|
||||
|
||||
|
||||
class LoginReq(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
@@ -0,0 +1,20 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class User(BaseModel):
|
||||
username: str
|
||||
email: str | None = None
|
||||
full_name: str | None = None
|
||||
disabled: bool | None = None
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
allow_population_by_field_name = True
|
||||
|
||||
|
||||
class UserInDB(User):
|
||||
hashed_password: str
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
allow_population_by_field_name = True
|
||||
@@ -0,0 +1,4 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,186 @@
|
||||
import os
|
||||
import hashlib
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
from database.neo4j import MyDB, driver
|
||||
from security.secure import create_access_token
|
||||
from jose import jwt
|
||||
|
||||
from security.secrets import get_secrets
|
||||
from security.secure import credentials_exception
|
||||
|
||||
from models.foundation_other import *
|
||||
from models.other import *
|
||||
|
||||
secrets = get_secrets()
|
||||
|
||||
|
||||
class FoundationDB(MyDB):
|
||||
|
||||
# implemented
|
||||
def create_authorization_code(self, username, authorization_code, scope) -> bool:
|
||||
def create_authorization_code_work(tx) -> bool:
|
||||
cypher = """
|
||||
MATCH (u:User)
|
||||
WHERE u.username = $username
|
||||
OPTIONAL MATCH (u)-[r1:CAN_USE]->(t:Token)
|
||||
WITH u, t
|
||||
DETACH DELETE t
|
||||
MERGE (u)-[r:CAN_USE]->(ac:AuthorizationCode)
|
||||
SET ac.code = $authorization_code
|
||||
SET ac.scope = $scope
|
||||
WITH ac
|
||||
CALL apoc.ttl.expireIn(ac, $time_delta, 's')
|
||||
RETURN ac AS authorization_code
|
||||
"""
|
||||
result = tx.run(cypher,
|
||||
username=username,
|
||||
authorization_code=authorization_code,
|
||||
scope=scope,
|
||||
time_delta=int(os.environ['SECURITY_AUTHORIZATION_CODE_EXPIRE_SECONDS']))
|
||||
summary = result.consume()
|
||||
if summary.counters.nodes_created < 1:
|
||||
raise HTTPException(status_code=400, detail="Authorization code was not created.")
|
||||
return summary.counters.nodes_created
|
||||
|
||||
with self.driver.session() as session:
|
||||
return session.execute_write(create_authorization_code_work)
|
||||
|
||||
def use_authorization_code(self, authorization_code) -> TokenInfo:
|
||||
def use_code_to_get_user_info_work(tx) -> TokenData:
|
||||
cypher = """
|
||||
MATCH (u:User)-[r1:CAN_USE]->(ac:AuthorizationCode)
|
||||
WHERE ac.code = $authorization_code
|
||||
WITH u.username AS username, ac.scope AS scope
|
||||
RETURN
|
||||
username, scope
|
||||
"""
|
||||
result = tx.run(cypher,
|
||||
authorization_code=authorization_code)
|
||||
first = result.single()
|
||||
if first is None:
|
||||
raise HTTPException(status_code=404, detail="Authorization code not found.")
|
||||
authorized_user = TokenData()
|
||||
authorized_user.username = first.get("username")
|
||||
authorized_user.scopes = first.get("scope").split(' ')
|
||||
return authorized_user
|
||||
|
||||
with self.driver.session() as session:
|
||||
user_info = session.execute_read(use_code_to_get_user_info_work)
|
||||
|
||||
token_info = TokenInfo()
|
||||
token_info.access_token = create_access_token(
|
||||
user_info.dict(),
|
||||
timedelta(seconds=int(os.environ['SECURITY_ACCESS_TOKEN_EXPIRE_SECONDS'])))
|
||||
token_info.refresh_token = create_access_token(
|
||||
user_info.dict(),
|
||||
timedelta(seconds=int(os.environ['SECURITY_REFRESH_TOKEN_EXPIRE_SECONDS'])))
|
||||
|
||||
def add_tokens_and_delete_code_work(tx) -> bool:
|
||||
cypher = """
|
||||
MATCH (u:User)-[r1:CAN_USE]->(ac1:AuthorizationCode)
|
||||
WHERE ac1.code = $authorization_code
|
||||
AND u.username = $username
|
||||
DETACH DELETE ac1
|
||||
MERGE (u)-[r4:CAN_USE]->(t2:Token:AccessToken)
|
||||
SET t2.value = $access_token
|
||||
SET t2.hash = $access_token_hash
|
||||
MERGE (u)-[r5:CAN_USE]->(t3:Token:RefreshToken)
|
||||
SET t3.value = $refresh_token
|
||||
SET t3.hash = $refresh_token_hash
|
||||
"""
|
||||
result = tx.run(cypher,
|
||||
authorization_code=authorization_code,
|
||||
username=user_info.username,
|
||||
access_token=token_info.access_token,
|
||||
access_token_hash=hashlib.md5(token_info.access_token.encode('utf-8')).hexdigest(),
|
||||
refresh_token=token_info.refresh_token,
|
||||
refresh_token_hash=hashlib.md5(token_info.refresh_token.encode('utf-8')).hexdigest())
|
||||
|
||||
summary = result.consume()
|
||||
if summary.counters.nodes_created < 1 or summary.counters.nodes_deleted < 1:
|
||||
raise HTTPException(status_code=400, detail="Tokens were not created.")
|
||||
return summary.counters.nodes_created
|
||||
|
||||
with self.driver.session() as session:
|
||||
session.execute_write(add_tokens_and_delete_code_work)
|
||||
|
||||
return token_info
|
||||
|
||||
def use_refresh_token(self, refresh_token) -> TokenInfo:
|
||||
def use_refresh_to_get_access_work(tx) -> (TokenData, TokenInfo):
|
||||
cypher = """
|
||||
MATCH (rt:RefreshToken)<-[r1:CAN_USE]-(u:User)-[r2:CAN_USE]->(at:AccessToken)
|
||||
WHERE u.username = $username
|
||||
AND rt.hash = $refresh_token_hash
|
||||
RETURN
|
||||
at.value AS access_token
|
||||
"""
|
||||
refresh_token_payload = jwt.decode(refresh_token,
|
||||
secrets['security_secret_key'],
|
||||
algorithms=[os.environ['SECURITY_ALGORITHM']])
|
||||
username_from_refresh_token: str = refresh_token_payload.get("username")
|
||||
print('refresh_token_username: ', username_from_refresh_token)
|
||||
result = tx.run(cypher,
|
||||
username=username_from_refresh_token,
|
||||
refresh_token_hash=hashlib.md5(refresh_token.encode('utf-8')).hexdigest()
|
||||
)
|
||||
first = result.single()
|
||||
if first is None:
|
||||
raise HTTPException(status_code=404, detail="Access token not found.")
|
||||
token_info = TokenInfo()
|
||||
token_info.access_token = first.get("access_token")
|
||||
token_info.refresh_token = refresh_token
|
||||
access_token_payload = jwt.decode(token_info.access_token,
|
||||
secrets['security_secret_key'],
|
||||
algorithms=[os.environ['SECURITY_ALGORITHM']])
|
||||
username_from_access_token: str = access_token_payload.get("username")
|
||||
if username_from_access_token is None:
|
||||
raise credentials_exception
|
||||
token_scopes = access_token_payload.get("scopes", [])
|
||||
token_data = TokenData(scopes=token_scopes, username=username_from_access_token)
|
||||
return token_data, token_info
|
||||
|
||||
with self.driver.session() as session:
|
||||
got_token_data, got_token_info = session.execute_read(use_refresh_to_get_access_work)
|
||||
new_token_info = TokenInfo()
|
||||
new_token_info.access_token = create_access_token(
|
||||
got_token_data.dict(),
|
||||
timedelta(seconds=int(os.environ['SECURITY_ACCESS_TOKEN_EXPIRE_SECONDS'])))
|
||||
new_token_info.refresh_token = create_access_token(
|
||||
got_token_data.dict(),
|
||||
timedelta(seconds=int(os.environ['SECURITY_REFRESH_TOKEN_EXPIRE_SECONDS'])))
|
||||
|
||||
def update_tokens_work(tx) -> bool:
|
||||
cypher = """
|
||||
MATCH (u:User)
|
||||
WHERE u.username = $username
|
||||
OPTIONAL MATCH (u)-[r1:CAN_USE]->(t:Token)
|
||||
WITH u, t
|
||||
DETACH DELETE t
|
||||
MERGE (rt:Token:RefreshToken)<-[r2:CAN_USE]-(u)-[r3:CAN_USE]->(at:Token:AccessToken)
|
||||
SET
|
||||
rt.value = $refresh_token,
|
||||
at.value = $access_token,
|
||||
rt.hash = $refresh_token_hash,
|
||||
at.hash = $access_token_hash
|
||||
"""
|
||||
result = tx.run(cypher,
|
||||
username=got_token_data.username,
|
||||
access_token=new_token_info.access_token,
|
||||
refresh_token=new_token_info.refresh_token,
|
||||
access_token_hash=hashlib.md5(new_token_info.access_token.encode('utf-8')).hexdigest(),
|
||||
refresh_token_hash=hashlib.md5(new_token_info.refresh_token.encode('utf-8')).hexdigest())
|
||||
summary = result.consume()
|
||||
if summary.counters.nodes_created < 2 or summary.counters.nodes_deleted < 2:
|
||||
raise HTTPException(status_code=400, detail="Tokens were not deleted and created.")
|
||||
return summary.counters.nodes_created
|
||||
|
||||
with self.driver.session() as session:
|
||||
session.execute_write(update_tokens_work)
|
||||
return new_token_info
|
||||
|
||||
foundation_db = FoundationDB(driver)
|
||||
@@ -0,0 +1,10 @@
|
||||
fastapi==0.101.0
|
||||
httpx==0.24.1
|
||||
jose==1.0.0
|
||||
jsonpickle==3.0.1
|
||||
passlib==1.7.4
|
||||
pydantic==1.10.7
|
||||
python_dateutil==2.8.2
|
||||
python_jose==3.3.0
|
||||
py2neo==2021.2.4
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
from glob import glob
|
||||
|
||||
|
||||
def get_secrets():
|
||||
secrets = dict()
|
||||
for var in glob('/run/secrets/*'):
|
||||
k = var.split('/')[-1]
|
||||
v = open(var).read().rstrip('\n')
|
||||
secrets[k] = v
|
||||
return secrets
|
||||
@@ -0,0 +1,117 @@
|
||||
from fastapi import Depends, HTTPException, status, Security
|
||||
from fastapi.security import OAuth2AuthorizationCodeBearer, SecurityScopes
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from passlib.context import CryptContext
|
||||
from jose import jwt, JWTError
|
||||
|
||||
from models.other import TokenData
|
||||
from models.request import User
|
||||
|
||||
from database.neo4j import db
|
||||
import os
|
||||
|
||||
from security.secrets import get_secrets
|
||||
|
||||
secrets = get_secrets()
|
||||
|
||||
# password context
|
||||
crypt_context = CryptContext(
|
||||
schemes=["bcrypt"],
|
||||
deprecated="auto")
|
||||
|
||||
oauth2_scheme = OAuth2AuthorizationCodeBearer(
|
||||
authorizationUrl='foundation/oauth2/auth',
|
||||
tokenUrl='foundation/oauth2/token',
|
||||
scopes={
|
||||
'test': 'Full access, but only test data.',
|
||||
'user': 'Normal user access.',
|
||||
'admin': 'Full access to all.'
|
||||
})
|
||||
|
||||
|
||||
credentials_exception = HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: timedelta | None = None):
|
||||
payload = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||
payload.update({"expires": str(expire)})
|
||||
encoded_jwt = jwt.encode(payload, secrets['security_secret_key'], algorithm=os.environ['SECURITY_ALGORITHM'])
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def verify_password(plain_password, hashed_password):
|
||||
return crypt_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
# see if given password matches password of username
|
||||
def authenticate_user(username_to_authenticate: str, plain_password: str) -> dict or False:
|
||||
# maybe put an exception around this code below
|
||||
user = db.get_user(username_to_authenticate)
|
||||
if not user:
|
||||
return False
|
||||
if verify_password(plain_password, user.hashed_password):
|
||||
return user
|
||||
return False
|
||||
|
||||
|
||||
def get_password_hash(plain_password):
|
||||
return crypt_context.hash(plain_password)
|
||||
|
||||
|
||||
# get current user from token
|
||||
async def get_current_user(security_scopes: SecurityScopes, token: str = Depends(oauth2_scheme)):
|
||||
|
||||
print("\n\n\nGets current user.")
|
||||
|
||||
if security_scopes.scopes:
|
||||
authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
|
||||
else:
|
||||
authenticate_value = f"Bearer"
|
||||
print(authenticate_value)
|
||||
|
||||
try:
|
||||
print('Token: ', token)
|
||||
payload = jwt.decode(token,
|
||||
secrets['security_secret_key'],
|
||||
algorithms=[os.environ['SECURITY_ALGORITHM']])
|
||||
username_from_token: str = payload.get("username")
|
||||
print('Token username: ', username_from_token)
|
||||
if username_from_token is None:
|
||||
raise credentials_exception
|
||||
token_scopes = payload.get("scopes", [])
|
||||
print('Token scopes: ', token_scopes)
|
||||
token_data = TokenData(scopes=token_scopes, username=username_from_token)
|
||||
|
||||
except JWTError:
|
||||
print('JWTError')
|
||||
raise credentials_exception
|
||||
|
||||
user = db.get_user(username=token_data.username)
|
||||
|
||||
if user is None:
|
||||
raise credentials_exception
|
||||
|
||||
for scope in security_scopes.scopes:
|
||||
if scope not in token_data.scopes:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Not enough permissions",
|
||||
headers={"WWW-Authenticate": authenticate_value},
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
async def get_current_active_user(current_user: User = Security(get_current_user, scopes=["test"])):
|
||||
if current_user.disabled:
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
return current_user
|
||||
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kontroll.digital</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#signin_button').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
get_code();
|
||||
});
|
||||
});
|
||||
function get_code () {
|
||||
let form_values = $('#signin_form').serialize();
|
||||
console.log(form_values);
|
||||
$.ajax ({
|
||||
type: 'GET',
|
||||
url: './code',
|
||||
data: form_values,
|
||||
dataType: 'json',
|
||||
success: function (msg) {
|
||||
if (msg) {
|
||||
console.log('Result: New authorization code: ' + msg);
|
||||
send_code(msg)
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
let errors = ['An unknown error occured.'];
|
||||
console.log(errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
function send_code (code) {
|
||||
let redirect_uri = $('#redirect_uri').val();
|
||||
let response_type = $('#response_type').val();
|
||||
let scope = $('#scope').val();
|
||||
let state = $('#state').val();
|
||||
|
||||
console.log('Redirect URI: ' + redirect_uri);
|
||||
console.log('Response type: ' + response_type);
|
||||
console.log('Scope: ' + scope);
|
||||
console.log('State: ' + state);
|
||||
console.log('Code: ' + code);
|
||||
|
||||
$.get ({
|
||||
url: redirect_uri,
|
||||
crossDomain: true,
|
||||
data: {
|
||||
response_type: response_type,
|
||||
scope: scope,
|
||||
state: state,
|
||||
code: code
|
||||
},
|
||||
dataType: 'html',
|
||||
success: function (msg) {
|
||||
if (msg) {
|
||||
console.log('Result text: ' + msg);
|
||||
$('#result_message').text('Received verification code. You may now close this window.');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
let errors = ['An unknown error occured.'];
|
||||
console.log(errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.page_layout {
|
||||
width: 100%;
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page_layout" class="page_layout">
|
||||
<form action="./code" id="signin_form" name="signin_form">
|
||||
<h1 class="h3 mb-3">kontroll.digital</h1>
|
||||
<div id="result_message">
|
||||
<label for="username" class="sr-only mb-1">Username</label>
|
||||
<input type="text" id="username" name="username" class="form-control mb-3" placeholder="Username" required autofocus>
|
||||
<label for="password" class="sr-only mb-1">Password</label>
|
||||
<input type="password" id="password" name="password" class="form-control mb-3" placeholder="Password" required>
|
||||
<div class="checkbox mb-3">
|
||||
<label>
|
||||
By signing in, I allow: {{client_name}}, to access data from Kontroll BCF server, on my behalf.
|
||||
</label>
|
||||
</div>
|
||||
<input type="hidden" id="response_type" name="response_type" value="{{response_type}}">
|
||||
<input type="hidden" id="client_id" name="client_id" value="{{client_id}}">
|
||||
<input type="hidden" id="client_name" name="client_name" value="{{client_name}}">
|
||||
<input type="hidden" id="state" name="state" value="{{state}}">
|
||||
<input type="hidden" id="redirect_uri" name="redirect_uri" value="{{redirect_uri}}">
|
||||
<input type="hidden" id="scope" name="scope" value="{{scope}}">
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit" id="signin_button">Sign in</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kontroll.digital</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#signin_button').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
get_code();
|
||||
});
|
||||
});
|
||||
function get_code () {
|
||||
let form_values = $('#signin_form').serialize();
|
||||
console.log(form_values);
|
||||
$.ajax ({
|
||||
type: 'GET',
|
||||
url: './code',
|
||||
data: form_values,
|
||||
dataType: 'json',
|
||||
success: function (msg) {
|
||||
if (msg) {
|
||||
console.log('Result code: ' + msg);
|
||||
send_code(msg)
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
let errors = ['An unknown error occured.'];
|
||||
console.log(errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
function send_code (code) {
|
||||
let redirect_uri = $('#redirect_uri').val();
|
||||
let response_type = $('#response_type').val();
|
||||
let scope = $('#scope').val();
|
||||
let state = $('#state').val();
|
||||
|
||||
console.log('Redirect URI: ' + redirect_uri);
|
||||
console.log('Response type: ' + response_type);
|
||||
console.log('Scope: ' + scope);
|
||||
console.log('State: ' + state);
|
||||
console.log('Code: ' + code);
|
||||
|
||||
$.get ({
|
||||
url: redirect_uri,
|
||||
crossDomain: true,
|
||||
data: {
|
||||
response_type: response_type,
|
||||
scope: scope,
|
||||
state: state,
|
||||
code: code
|
||||
},
|
||||
dataType: 'html',
|
||||
success: function (msg) {
|
||||
if (msg) {
|
||||
console.log('Result text: ' + msg);
|
||||
$('#result_message').text('Received verification code. You may now close this window.');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
let errors = ['An unknown error occured.'];
|
||||
console.log(errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.page_layout {
|
||||
width: 100%;
|
||||
max-width: 330px;
|
||||
padding: 15px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page_layout" class="page_layout">
|
||||
<form action="./code" id="signin_form" name="signin_form">
|
||||
<h1 class="h3 mb-3">kontroll.digital</h1>
|
||||
<div id="result_message">
|
||||
<label for="username" class="sr-only mb-1">Username</label>
|
||||
<input type="text" id="username" name="username" class="form-control mb-3" placeholder="Username" required autofocus>
|
||||
<label for="password" class="sr-only mb-1">Password</label>
|
||||
<input type="password" id="password" name="password" class="form-control mb-3" placeholder="Password" required>
|
||||
<div class="checkbox mb-3">
|
||||
<label>
|
||||
By signing in, I allow: {{client_name}}, to access data from Kontroll BCF server, on my behalf.
|
||||
</label>
|
||||
</div>
|
||||
<input type="hidden" id="response_type" name="response_type" value="{{response_type}}">
|
||||
<input type="hidden" id="client_id" name="client_id" value="{{client_id}}">
|
||||
<input type="hidden" id="client_name" name="client_name" value="{{client_name}}">
|
||||
<input type="hidden" id="state" name="state" value="{{state}}">
|
||||
<input type="hidden" id="scope" name="scope" value="{{scope}}">
|
||||
<input type="hidden" id="redirect_uri" name="redirect_uri" value="{{redirect_uri}}">
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit" id="signin_button">Sign in</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kontroll.digital</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
|
||||
// Text format functions
|
||||
|
||||
$('.size_in_bytes').each(function(i, obj) {
|
||||
$(this).text(formatBytes($(this).text()));
|
||||
console.log($(this).text());
|
||||
});
|
||||
|
||||
$('.date_in_iso').each(function(i, obj) {
|
||||
$(this).text(formatDate($(this).text()));
|
||||
console.log($(this).text());
|
||||
});
|
||||
|
||||
// Event functions
|
||||
|
||||
{% for project in projects %}
|
||||
|
||||
$('#button_{{project.project_id}}').on('click', function (event) {
|
||||
let form_data = new FormData();
|
||||
let files = $('#file_{{project.project_id}}')[0].files;
|
||||
if(files.length > 0) {
|
||||
form_data.append('file', files[0]);
|
||||
form_data.append('project', '{{project.project_id}}');
|
||||
form_data.append('selection_session', $('#selection_session').val());
|
||||
$.ajax({
|
||||
url: './upload_file_to_project',
|
||||
type: 'POST',
|
||||
data: form_data,
|
||||
dataType: 'json',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
success:function (response) {
|
||||
if (response.document_id) {
|
||||
$('#span_{{project.project_id}}').text(response.document_id);
|
||||
} else {
|
||||
$('#span_{{project.project_id}}').text('Not uploaded');
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
$('#span_{{project.project_id}}').text('Please select a file.')
|
||||
}
|
||||
event.preventDefault();
|
||||
});
|
||||
|
||||
{% endfor %}
|
||||
|
||||
$('#select_documents_button').on('click', function (event) {
|
||||
select_documents();
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
// Download functions
|
||||
|
||||
function select_documents () {
|
||||
event.preventDefault();
|
||||
let form_values = $('#select_documents_form').serialize();
|
||||
console.log('Form values: ' + form_values);
|
||||
$.ajax ({
|
||||
type: 'POST',
|
||||
url: './mark-documents-as-selected',
|
||||
data: form_values,
|
||||
dataType: 'json',
|
||||
success: function (msg) {
|
||||
if (msg) {
|
||||
console.log('Result of mark-some-documents-as-selected: ' + msg);
|
||||
callback(msg)
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
let errors = ['An unknown error occured.'];
|
||||
console.log(errors);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function callback (msg) {
|
||||
let callback_url = $('#callback_url').val();
|
||||
let session_id = $('#selection_session').val();
|
||||
let server_context = $('#server_context').val();
|
||||
let selected_documents_url = 'https://api.kontroll.digital/documents/1.0/download-instructions'
|
||||
+ '?session_id=' + session_id
|
||||
+ '&server_context=' + server_context
|
||||
|
||||
callback_url = callback_url + '?selected_documents_url=' + encodeURIComponent(selected_documents_url)
|
||||
|
||||
console.log('Selected documents url: ' + selected_documents_url);
|
||||
console.log('Callback url: ' + callback_url);
|
||||
|
||||
$.get ({
|
||||
url: callback_url,
|
||||
crossDomain: true,
|
||||
dataType: 'html',
|
||||
success: function (msg) {
|
||||
if (msg) {
|
||||
console.log('Result text: ' + msg);
|
||||
$('#result_message').text('You may now close this window.');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
let errors = ['An unknown error occured.'];
|
||||
console.log(errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
function formatBytes(bytes, decimals = 2) {
|
||||
bytes = parseInt(bytes)
|
||||
const k = 1024
|
||||
if (!+bytes) return '0 Bytes'
|
||||
const dm = decimals < 0 ? 0 : decimals
|
||||
const sizes = ['Bytes', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb', 'Eb', 'Zb', 'Yb']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`
|
||||
}
|
||||
|
||||
function formatDate(iso_date) {
|
||||
let d = new Date(iso_date)
|
||||
let curr_date = d.getDate();
|
||||
let curr_month = d.getMonth() + 1;
|
||||
let curr_year = d.getFullYear();
|
||||
return curr_year + "-" + curr_month + "-" + curr_date
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.page_layout {
|
||||
width: 100%;
|
||||
max-width: 780px;
|
||||
padding: 15px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page_layout" class="page_layout">
|
||||
<form action="" id="select_documents_form" name="select_documents_form">
|
||||
<h1 class="h3 mb-3">kontroll.digital</h1>
|
||||
<div id="select_documents_div">
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col">Version</th>
|
||||
<th scope="col">File type</th>
|
||||
<th scope="col">Size</th>
|
||||
<th scope="col">Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{% for project in projects %}
|
||||
|
||||
<tr>
|
||||
<td colspan="7" class="project_upload">
|
||||
|
||||
<h2>{{project.name}}</h2>
|
||||
|
||||
<span>
|
||||
<input type="file" id="file_{{project.project_id}}" name="file_{{project.project_id}}" />
|
||||
<input type="button" id="button_{{project.project_id}}" name="button_{{project.project_id}}" value="Upload">
|
||||
</span>
|
||||
|
||||
<span id="span_{{project.project_id}}"></span>
|
||||
|
||||
</td>
|
||||
<tr>
|
||||
|
||||
{% for document in project.documents %}
|
||||
|
||||
<tr>
|
||||
<td><input type="checkbox" id="document_{{document.document_id}}" name="document_{{document.document_id}}"></td>
|
||||
<td><span>{{document.document_id}}</span></td>
|
||||
<td><span>{{document.title}}</span></td>
|
||||
<td><span>{{document.version_number}}</span></td>
|
||||
<td><span>{{document.file_type}}</span></td>
|
||||
<td><span class="size_in_bytes">{{document.file_description.size_in_bytes}}</span></td>
|
||||
<td><span>{{document.creation_date}}</span></td>
|
||||
<tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<input type="hidden" id="username" name="username" value="{{current_user}}">
|
||||
<input type="hidden" id="selection_session" name="selection_session" value="{{selection_session}}">
|
||||
<input type="hidden" id="server_context" name="server_context" value="{{server_context}}">
|
||||
<input type="hidden" id="callback_url" name="callback_url" value="{{callback_url}}">
|
||||
<input type="hidden" id="callback_expires_in" name="callback_expires_in" value="{{callback_expires_in}}">
|
||||
|
||||
<button class="btn btn-lg btn-primary btn-block" id="select_documents_button" type="button">Select documents</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,140 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kontroll.digital</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.min.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#save_metadata_button').on('click', function (event) {
|
||||
event.preventDefault();
|
||||
save_metadata();
|
||||
});
|
||||
});
|
||||
function save_metadata () {
|
||||
let form_values = $('#save_metadata_form').serialize();
|
||||
console.log('Form values: ' + form_values);
|
||||
$.ajax ({
|
||||
type: 'POST',
|
||||
url: './save-metadata-for-documents',
|
||||
data: form_values,
|
||||
dataType: 'json',
|
||||
success: function (msg) {
|
||||
if (msg) {
|
||||
console.log('Result of save-metadata-for-documents: ' + msg);
|
||||
callback(msg)
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
let errors = ['An unknown error occured.'];
|
||||
console.log(errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
function callback (msg) {
|
||||
let callback_url = $('#callback_url').val();
|
||||
let session_id = $('#upload_session').val();
|
||||
let server_context = $('#server_context').val();
|
||||
let upload_documents_url = 'https://api.kontroll.digital/documents/1.0/upload-instructions'
|
||||
+ '?session_id=' + session_id
|
||||
+ '&server_context=' + server_context
|
||||
|
||||
callback_url = callback_url + '?upload_documents_url=' + encodeURIComponent(upload_documents_url)
|
||||
|
||||
console.log('Upload documents url: ' + upload_documents_url);
|
||||
console.log('Callback url: ' + callback_url);
|
||||
|
||||
$.get ({
|
||||
url: callback_url,
|
||||
crossDomain: true,
|
||||
dataType: 'html',
|
||||
success: function (msg) {
|
||||
if (msg) {
|
||||
console.log('Result text: ' + msg);
|
||||
$('#result_message').text('You may now close this window.');
|
||||
}
|
||||
},
|
||||
error: function () {
|
||||
let errors = ['An unknown error occured.'];
|
||||
console.log(errors);
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 40px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.page_layout {
|
||||
width: 100%;
|
||||
max-width: 780px;
|
||||
padding: 15px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page_layout" class="page_layout">
|
||||
<form action="" id="save_metadata_form" name="save_metadata_form">
|
||||
<h1 class="h3 mb-3">kontroll.digital</h1>
|
||||
|
||||
<div id="save_metadata_div">
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Id</th>
|
||||
<th scope="col">Title</th>
|
||||
<th scope="col">Version</th>
|
||||
<th scope="col">File name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
||||
{% for document in documents %}
|
||||
|
||||
<tr>
|
||||
<input type="hidden" id="session_file_id@{{document.session_file_id}}" name="session_file_id@{{document.session_file_id}}" value="{{document.session_file_id}}">
|
||||
<td>{{document.document_id}}<input type="hidden" id="document@{{document.session_file_id}}" name="document@{{document.session_file_id}}" value="{{document.document_id}}"></td>
|
||||
<td><input type="text" id="title@{{document.session_file_id}}" name="title@{{document.session_file_id}}" value="{{document.title}}"></td>
|
||||
<td><input type="text" id="version_number@{{document.session_file_id}}" name="version_number@{{document.session_file_id}}" value="{{document.version_number}}"></td>
|
||||
<td>{{document.file_name}}<input type="hidden" id="filename@{{document.session_file_id}}" name="filename@{{document.session_file_id}}" value="{{document.file_name}}"></td>
|
||||
<tr>
|
||||
|
||||
{% endfor %}
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<input type="hidden" id="username" name="username" value="{{username}}">
|
||||
<input type="hidden" id="email" name="email" value="{{email}}">
|
||||
<input type="hidden" id="full_name" name="full_name" value="{{full_name}}">
|
||||
<input type="hidden" id="upload_session" name="upload_session" value="{{upload_session}}">
|
||||
<input type="hidden" id="server_context" name="server_context" value="{{server_context}}">
|
||||
<input type="hidden" id="callback_url" name="callback_url" value="{{callback_url}}">
|
||||
<input type="hidden" id="callback_expires_in" name="callback_expires_in" value="{{callback_expires_in}}">
|
||||
|
||||
<div class="form-group mb-3">
|
||||
<label for="project">Associate document with this project:</label>
|
||||
<select class="form-control" id="project" name="project">
|
||||
{% for project in projects %}
|
||||
<option value="{{project.project_id}}">{{project.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-lg btn-primary btn-block" type="submit" id="save_metadata_button">Save metadata</button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user