mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +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,12 @@
|
||||
/api/app/logs/*
|
||||
/api/app/favicon.ico
|
||||
/data/
|
||||
/etc/
|
||||
/neo4j/data/*
|
||||
/neo4j/import/*
|
||||
/neo4j/logs/*
|
||||
/secrets/*
|
||||
/TODO.txt
|
||||
/venv/
|
||||
/.idea/
|
||||
/secrets/
|
||||
@@ -0,0 +1,661 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
+268
-20
@@ -1,27 +1,275 @@
|
||||
# Server-Test
|
||||
# Kontroll BCF API server - OpenCDE APIs
|
||||
|
||||
```
|
||||
$ pip install -r requirements.txt
|
||||
$ cd bcfserver/
|
||||
$ python
|
||||
>>> from run import db
|
||||
>>> db.create_all()
|
||||
$ export FLASK_APP=run.py
|
||||
$ flask run
|
||||
```
|
||||
## What is this?
|
||||
|
||||
Go to [http://localhost:5000](http://localhost:5000) to see the server
|
||||
OpenCDE APIs:
|
||||
Foundation API,
|
||||
BCF API,
|
||||
Documents API
|
||||
implemented in python,
|
||||
the FastAPI framework
|
||||
and the Neo4j graph database.
|
||||
|
||||
# Register the user
|
||||
This is server software. To run or test this code you need to set up your server to work with a client.
|
||||
This server was developed and tested using Solibri as a client. To use a client with this server you need to configure
|
||||
a client id and a client key.
|
||||
|
||||
1. Go to http://localhost:5000/register to register the user
|
||||
2. Create the client
|
||||
3. For grant type enter authorization_code
|
||||
4. For response_type enter code secret
|
||||
5. Enter the scope and create the client
|
||||
You also need to configure encrypted communication and authentication using HTTP and OAuth2.
|
||||
And then you need an SSL-certificate. To get an SSL-certificate you need your own domain name.
|
||||
Solibri can only connect to preconfigured domains.
|
||||
|
||||
### You will be redirected to the page with the details of your client id and secret
|
||||
This means that it is not so easy to test this software.
|
||||
In the future I hope an open source BCF/CDE-client can be developed to access this server.
|
||||
Maybe Blender BIM in the future could be extended with such BCF/CDE-client functionality.
|
||||
|
||||
# Foundation API
|
||||
## Background
|
||||
|
||||
- Set the Base URL will be `http://127.0.0.1:5000/`
|
||||
You can read some background information about the project here:
|
||||
|
||||
https://martin.wiss.se/data/bcf_ifc_graph.pdf
|
||||
|
||||
The name of this project is Kontroll BCF API server because it was originally
|
||||
developed as a POC prototype to test if BCF API could be used for digital inspection plans
|
||||
for building permits (translated as "kontroll-plan" in Swedish language).
|
||||
|
||||
## Code structure
|
||||
|
||||
/docker-compose.yml
|
||||
You bring up the project using this file.
|
||||
|
||||
/api/Dockerfile
|
||||
Contains instructions for the FastAPI container.
|
||||
Referenced by /docker-compose.yml
|
||||
|
||||
/api/app
|
||||
Contains the server application.
|
||||
|
||||
/api/app/main.py
|
||||
This is where the application starts.
|
||||
The FastAPI-container will run this script on uvicorn.
|
||||
main.py will load routes from scripts in the /api/app/api folder.
|
||||
|
||||
/api/app/api
|
||||
This is where the FastAPI routes are defined.
|
||||
|
||||
/api/app/repository
|
||||
Routes often runs methods that will read or write data to or from a database.
|
||||
This is where methods that read or write data from database are declared.
|
||||
|
||||
/api/app/database
|
||||
Some general methods to interact with database.
|
||||
|
||||
/api/app/db_config
|
||||
When the Neo4j container is launched it will initialize a database
|
||||
with some test data from init.cypher. This initial state does not represent
|
||||
all data. Model.cypher contains a more complete model.
|
||||
|
||||
/api/app/ifcgraph
|
||||
Script to import data from IFC STEP file to Neo4j.
|
||||
|
||||
/api/app/models
|
||||
Pydantic models used to define the JSON structures of requests and responses.
|
||||
Models are arranged in files depending on if it is request or response
|
||||
or both (common) or not at all (other).
|
||||
|
||||
/api/app/security
|
||||
Contains methods for OAuth2 and a method to get the secrets used.
|
||||
You need to create your own secrets and put them in the /secrets folder.
|
||||
Secrets are more secure than environment values.
|
||||
|
||||
/api/app/templets
|
||||
Jinja2 templates used to generate some webpages necessary for OAuth2 flow and Documents API flow.
|
||||
|
||||
/api/app/requirements.txt
|
||||
Generated using pipreqs not pip freeze.
|
||||
|
||||
## Deployment
|
||||
|
||||
To get this code running properly you need:
|
||||
|
||||
- your own linux server, on premise or in the cloud
|
||||
- a domain name and a TLS-certificate for your domain name
|
||||
- a proxy server, nginx was used
|
||||
- to set up your proxy server to use HTTPS
|
||||
- to use proxy as bridge between docker network and remote
|
||||
- proxy (outside container) and uvicorn (inside container) should access same TLS certificate
|
||||
- to edit your .env file with values specific for your server and API
|
||||
- to replace hard coded values in code with specific values for your server and API
|
||||
- to create three secrets and put them in the /secrets folder
|
||||
- to put apoc-5.7.0-core and apoc-5.7.0-extended jars in /neo4j/plugins folder
|
||||
- to put an ifcopenshell.zip in /api/app/ifcopenshell folder
|
||||
- Docker and Neo4j software
|
||||
- a favicon at /favicon.ico
|
||||
- to run "bash restart.sh" to start and restart containers
|
||||
- a BCF client, registered with client ID and secret
|
||||
|
||||
This code was developed and tested using Solibri Office as BCF client, however:
|
||||
Solibri Office can only connect to pre-registered servers. This means that you cannot
|
||||
connect to your server using a regular version of Solibri Office. If you want to use Solibri,
|
||||
then you will have to first kindly ask Solibri if they want to add your server
|
||||
to the list of registered servers. An alternative is to develop your own BCF client
|
||||
or to use this existing BCF API client module for python to build your own client script:
|
||||
|
||||
https://pypi.org/project/bcf-client/
|
||||
|
||||
I do not know of any working BCF API client plugins for Revit or Archicad that allows
|
||||
connecting to arbitrary BCF API servers. It seems like most plugins instead were developed to
|
||||
only connect to a specific BCF API server. I think that is contradictory because BCF is an open standard
|
||||
intended to facilitate communication between multiple clients and servers.
|
||||
A BCF API client that can connect to multiple arbitrary BCF API servers would be a good thing to develop.
|
||||
|
||||
## TODO
|
||||
|
||||
The original intention of the code was just to create a prototype for private testing.
|
||||
Improvements are needed to collaborate on this code or to deploy this system in production.
|
||||
|
||||
Suggested improvements:
|
||||
|
||||
- General code structure refactoring
|
||||
- Better commenting
|
||||
- Testing scripts, unit testing
|
||||
- Better error handling
|
||||
- More consistent logging
|
||||
- Easier deployment
|
||||
- User registration and admin system
|
||||
- BCF client registration functionality
|
||||
- OData to Cypher converter
|
||||
- Documents API and BCF API integration
|
||||
- More complete OpenAPI documentation of routes
|
||||
- More complete documentation of pydantic models
|
||||
- Improvements on IFC-graph
|
||||
- BCF event logging
|
||||
- Implement also the routes that Solibri Office does not support
|
||||
- Replace hard coded values with .env values
|
||||
- Improve security before any kind of deployment in production
|
||||
- Making full use of native IFC (for example allowing BCF API to edit IFC directly using BIM snippets)
|
||||
- Integrate BCF API BIM snippets editing with GIT commits
|
||||
|
||||
## Prototype
|
||||
|
||||
This code was developed and tested on a system using:
|
||||
|
||||
- Ubuntu 20
|
||||
- Nginx 1.18
|
||||
- Lets encrypt, certbot
|
||||
- Docker 20
|
||||
|
||||
## Suggested reading
|
||||
|
||||
### API
|
||||
|
||||
- FastAPI documentation
|
||||
https://fastapi.tiangolo.com/
|
||||
|
||||
- Building Python Microservices with FastAPI
|
||||
https://www.packtpub.com/product/building-python-microservices-with-fastapi/9781803245966
|
||||
|
||||
- Building Python Web APIs with FastAPI
|
||||
https://www.packtpub.com/product/building-python-web-apis-with-fastapi/9781801076630
|
||||
|
||||
- Building Data Science Applications with FastAPI - Second Edition
|
||||
https://www.packtpub.com/product/building-data-science-applications-with-fastapi-second-edition/9781837632749
|
||||
|
||||
- Pydantic docs
|
||||
https://docs.pydantic.dev/latest/
|
||||
|
||||
- OData v4
|
||||
https://www.odata.org/documentation/
|
||||
|
||||
### OAuth2
|
||||
|
||||
- Mastering OAuth 2.0
|
||||
https://www.packtpub.com/product/mastering-oauth-20/9781784395407
|
||||
|
||||
- OAuth 2.0 Cookbook
|
||||
https://www.packtpub.com/product/oauth-20-cookbook/9781788295963
|
||||
|
||||
- OAuth2 with Password (and hashing), Bearer with JWT tokens
|
||||
https://fastapi.tiangolo.com/ur/tutorial/security/oauth2-jwt/
|
||||
|
||||
- Authorization Code Flow
|
||||
https://auth0.com/docs/get-started/authentication-and-authorization-flow/authorization-code-flow
|
||||
|
||||
- What is the OAuth 2.0 Authorization Code Grant Type?
|
||||
https://developer.okta.com/blog/2018/04/10/oauth-authorization-code-grant-type
|
||||
|
||||
### Neo4j and Cypher
|
||||
|
||||
- Neo4j Cypher Manual
|
||||
https://neo4j.com/docs/cypher-manual/current/introduction/
|
||||
|
||||
- Using Neo4j from python
|
||||
https://neo4j.com/docs/getting-started/languages-guides/neo4j-python/
|
||||
|
||||
- Building Neo4j Applications with Python
|
||||
https://graphacademy.neo4j.com/courses/app-python/
|
||||
|
||||
- The Property Graph Model
|
||||
https://neo4j.com/developer/graph-database/#property-graph
|
||||
|
||||
### OpenCDE APIs
|
||||
|
||||
- BCF API
|
||||
https://github.com/buildingSMART/BCF-API
|
||||
|
||||
- Documents API
|
||||
https://github.com/buildingSMART/documents-API
|
||||
|
||||
- Foundation API
|
||||
https://github.com/buildingSMART/foundation-API
|
||||
|
||||
- Open CDE APIs OAuth2 Example
|
||||
https://github.com/buildingSMART/foundation-API/blob/v1.0/OAuth2Examples.md
|
||||
|
||||
### Server
|
||||
|
||||
- Docker documentation
|
||||
https://docs.docker.com/
|
||||
|
||||
- Docker Compose overview
|
||||
https://docs.docker.com/compose/
|
||||
|
||||
- How to use secrets in Docker Compose
|
||||
https://docs.docker.com/compose/use-secrets/
|
||||
|
||||
- NGINX Reverse Proxy documentation
|
||||
https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/
|
||||
|
||||
- Certbot documentation
|
||||
https://eff-certbot.readthedocs.io/en/stable/
|
||||
|
||||
### IFC
|
||||
|
||||
- Native IFC
|
||||
https://github.com/brunopostle/ifcmerge/blob/main/docs/whitepaper.rst
|
||||
|
||||
- IFC Specifications Database
|
||||
https://technical.buildingsmart.org/standards/ifc/ifc-schema-specifications/
|
||||
|
||||
- IFC-graph for facilitating building information access and query
|
||||
https://www.sciencedirect.com/science/article/pii/S0926580523000389
|
||||
|
||||
- IFCOpenShell documentation
|
||||
https://blenderbim.org/docs-python/
|
||||
|
||||
### Frontend
|
||||
|
||||
- Jinja2 Documentation
|
||||
https://svn.python.org/projects/external/Jinja-2.1.1/docs/_build/html/index.html
|
||||
|
||||
- Bootstrap
|
||||
https://getbootstrap.com/docs/4.1/getting-started/introduction/
|
||||
|
||||
- jQuery
|
||||
https://api.jquery.com/
|
||||
|
||||
## License
|
||||
|
||||
The GNU Affero General Public License is a free,
|
||||
copyleft license for software and other kinds of works,
|
||||
specifically designed to ensure cooperation with the community
|
||||
in the case of network server software.
|
||||
|
||||
https://www.gnu.org/licenses/agpl-3.0.en.html
|
||||
|
||||
See the /LICENSE file.
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
# docker-compose up --build --force-recreate --detach
|
||||
# docker-compose up = Create and start containers.
|
||||
# --build = Build images before starting containers.
|
||||
# --force-recreate = Recreate containers even if their configuration and image haven’t changed.
|
||||
# --detach = Detached mode: Run containers in the background.
|
||||
|
||||
version: '3.7'
|
||||
|
||||
networks:
|
||||
kontroll_network:
|
||||
driver: bridge
|
||||
|
||||
services:
|
||||
|
||||
# HTTP: 27474 on server matches 7474 in neo4j container.
|
||||
# HTTPS: 27473 on server matches 7473 in neo4j container.
|
||||
# Bolt: 27687 on server matches 7687 in neo4j container.
|
||||
|
||||
# Environment variables are read from the .env file.
|
||||
# The .env file should be placed at the root of the project directory next to your docker-compose.yml file.
|
||||
|
||||
# APOC core and extended must be loaded from /neo4j/plugins folder.
|
||||
|
||||
neo4j:
|
||||
container_name: kontroll_neo4j
|
||||
image: library/neo4j:5.7.0-community
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "27474:7474"
|
||||
- "27473:7473"
|
||||
- "27687:7687"
|
||||
networks:
|
||||
- kontroll_network
|
||||
volumes:
|
||||
- ./neo4j/data:/data
|
||||
- ./neo4j/logs:/logs
|
||||
- ./neo4j/conf:/conf
|
||||
- ./neo4j/import:/import
|
||||
- ./neo4j/plugins:/plugins
|
||||
environment:
|
||||
- NEO4J_AUTH=${NEO4J_USER}/${NEO4J_INITIAL_PASSWORD}
|
||||
- NEO4J_ACCEPT_LICENSE_AGREEMENT=yes
|
||||
- NEO4J_dbms_security_procedures_unrestricted=apoc.*
|
||||
- NEO4J_dbms_security_auth__minimum__password__length=4
|
||||
- NEO4J_server_http_enabled
|
||||
- NEO4J_server_http_listen__address=0.0.0.0:7474
|
||||
- NEO4J_server_https_enabled
|
||||
- NEO4J_server_https_listen__address=0.0.0.0:7473
|
||||
- NEO4J_server_bolt_enabled
|
||||
- NEO4J_server_bolt_listen__address=0.0.0.0:7687
|
||||
secrets:
|
||||
- neo4j_password
|
||||
|
||||
# depends_on: Dependency services must be started before starting a dependent service.
|
||||
# HTTPS: 24443 on server matches 8000 in kontroll_fastapi container.
|
||||
# ./etc/letsencrypt contains HTTPS keys and certificates.
|
||||
|
||||
fastapi:
|
||||
container_name: kontroll_fastapi
|
||||
build:
|
||||
context: ./api
|
||||
dockerfile: Dockerfile
|
||||
target: server
|
||||
depends_on:
|
||||
- neo4j
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "24443:8000"
|
||||
networks:
|
||||
- kontroll_network
|
||||
volumes:
|
||||
- ./etc/letsencrypt:/etc/letsencrypt
|
||||
- ./api/app/logs:/code/app/logs
|
||||
- ./api/app/data:/code/app/data
|
||||
environment:
|
||||
- NEO4J_USER=${NEO4J_USER}
|
||||
- NEO4J_INITIAL_PASSWORD=${NEO4J_INITIAL_PASSWORD}
|
||||
- NEO4J_URI=${NEO4J_URI}
|
||||
- KONTROLL_BASE_URL=${KONTROLL_BASE_URL}
|
||||
- KONTROLL_CLIENT_ID=${KONTROLL_CLIENT_ID}
|
||||
- KONTROLL_CLIENT_NAME=${KONTROLL_CLIENT_NAME}
|
||||
- SECURITY_AUTHORIZATION_CODE_EXPIRE_SECONDS=${SECURITY_AUTHORIZATION_CODE_EXPIRE_SECONDS}
|
||||
- SECURITY_ACCESS_TOKEN_EXPIRE_SECONDS=${SECURITY_ACCESS_TOKEN_EXPIRE_SECONDS}
|
||||
- SECURITY_REFRESH_TOKEN_EXPIRE_SECONDS=${SECURITY_REFRESH_TOKEN_EXPIRE_SECONDS}
|
||||
- SECURITY_ALGORITHM=${SECURITY_ALGORITHM}
|
||||
- SESSION_URL_VALIDITY_SECONDS=${SESSION_URL_VALIDITY_SECONDS}
|
||||
- SESSION_MAX_FILE_SIZE_BYTES=${SESSION_MAX_FILE_SIZE_BYTES}
|
||||
secrets:
|
||||
- neo4j_password
|
||||
- kontroll_client_secret
|
||||
- security_secret_key
|
||||
|
||||
secrets:
|
||||
neo4j_password:
|
||||
file: ./secrets/neo4j_password.txt
|
||||
kontroll_client_secret:
|
||||
file: ./secrets/kontroll_client_secret.txt
|
||||
security_secret_key:
|
||||
file: ./secrets/security_secret_key.txt
|
||||
@@ -0,0 +1,8 @@
|
||||
apoc.ttl.enabled=true
|
||||
apoc.ttl.schedule=5
|
||||
apoc.import.file.enabled=true
|
||||
apoc.export.file.enabled=true
|
||||
# you have to include
|
||||
# - apoc-5.7.0-core.jar
|
||||
# - apoc-5.7.0-extended.jar
|
||||
# in /neo4j/plugins folder
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,444 +0,0 @@
|
||||
{
|
||||
"Projects":[{
|
||||
"project_id": "F445F4F2-4D02-4B2A-B612-5E456BEF9137",
|
||||
"name": "Example project 1",
|
||||
"authorization": {
|
||||
"project_actions": [
|
||||
"createTopic",
|
||||
"createDocument"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"project_id": "A233FBB2-3A3B-EFF4-C123-DE22ABC8414",
|
||||
"name": "Example project 2",
|
||||
"authorization": {
|
||||
"project_actions": []
|
||||
}
|
||||
}],
|
||||
"Extensions":[{
|
||||
"topic_type": [
|
||||
"Information",
|
||||
"Error"
|
||||
],
|
||||
"topic_status": [
|
||||
"Open",
|
||||
"Closed",
|
||||
"ReOpened"
|
||||
],
|
||||
"topic_label": [
|
||||
"Architecture",
|
||||
"Structural",
|
||||
"MEP"
|
||||
],
|
||||
"snippet_type": [
|
||||
".ifc",
|
||||
".csv"
|
||||
],
|
||||
"priority": [
|
||||
"Low",
|
||||
"Medium",
|
||||
"High"
|
||||
],
|
||||
"users": [
|
||||
"Architect@example.com",
|
||||
"BIM-Manager@example.com",
|
||||
"bob_heater@example.com"
|
||||
],
|
||||
"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"
|
||||
]
|
||||
}],
|
||||
"Topics": [{
|
||||
"guid": "A245F4F2-2C01-B43B-B612-5E456BEF8116",
|
||||
"server_assigned_id": "ISSUE-00001",
|
||||
"creation_author": "Architect@example.com",
|
||||
"title": "Example topic 1",
|
||||
"labels": [
|
||||
"Architecture",
|
||||
"Structural"
|
||||
],
|
||||
"creation_date": "2013-10-21T17:34:22.409Z"
|
||||
}, {
|
||||
"guid": "A211FCC2-3A3B-EAA4-C321-DE22ABC8414",
|
||||
"server_assigned_id": "ISSUE-00078",
|
||||
"creation_author": "Architect@example.com",
|
||||
"title": "Example topic 2",
|
||||
"labels": [
|
||||
"Architecture",
|
||||
"Heating",
|
||||
"Electrical"
|
||||
],
|
||||
"creation_date": "2014-11-19T14:24:11.316Z"
|
||||
},
|
||||
{
|
||||
"guid": "B345F4F2-3A04-B43B-A713-5E456BEF8228",
|
||||
"server_assigned_id": "ISSUE-00549",
|
||||
"creation_author": "Architect@example.com",
|
||||
"creation_date": "2016-08-01T17:34:22.409Z",
|
||||
"topic_type": "Clash",
|
||||
"topic_status": "open",
|
||||
"title": "Example topic 3",
|
||||
"priority": "high",
|
||||
"labels": [
|
||||
"Architecture",
|
||||
"Heating"
|
||||
],
|
||||
"assigned_to": "harry.muster@example.com",
|
||||
"bim_snippet": {
|
||||
"snippet_type": "clash",
|
||||
"is_external": true,
|
||||
"reference": "https://example.com/bcf/1.0/ADFE23AA11BCFF444122BB",
|
||||
"reference_schema": "https://example.com/bcf/1.0/clash.xsd"
|
||||
},
|
||||
"authorization": {
|
||||
"topic_actions": [
|
||||
"createComment",
|
||||
"createViewpoint"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"Files":[{
|
||||
"display_information": [{
|
||||
"field_display_name": "Model Name",
|
||||
"field_value": "ARCH-Z100-051"
|
||||
}, {
|
||||
"field_display_name": "Revision Date",
|
||||
"field_value": "May 3 2020"
|
||||
}],
|
||||
"file": {
|
||||
"ifc_project": "0J$yPqHBD12v72y4qF6XcD",
|
||||
"file_name": "OfficeBuilding_Architecture_0001.ifc",
|
||||
"reference": "https://example.com/files/0J$yPqHBD12v72y4qF6XcD_0001.ifc"
|
||||
}
|
||||
}, {
|
||||
"display_information": [{
|
||||
"field_display_name": "Model Name",
|
||||
"field_value": "MEP-Z100-015"
|
||||
}, {
|
||||
"field_display_name": "Revision Date",
|
||||
"field_value": "Apr 30 2020"
|
||||
}],
|
||||
"file": {
|
||||
"ifc_project": "3hwBHP91jBRwPsmyf$3Hea",
|
||||
"file_name": "OfficeBuilding_Heating_0003.ifc",
|
||||
"reference": "cf37bae6-0900-46be-b37f-b34754fe0b4a"
|
||||
}
|
||||
}],
|
||||
"Comments":[
|
||||
{
|
||||
"guid": "C4215F4D-AC45-A43A-D615-AA456BEF832B",
|
||||
"date": "2016-08-01T12:34:22.409Z",
|
||||
"author": "max.muster@example.com",
|
||||
"comment": "Clash found",
|
||||
"topic_guid": "B345F4F2-3A04-B43B-A713-5E456BEF8228",
|
||||
"authorization": {
|
||||
"comment_actions": [
|
||||
"update"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"guid": "A333FCA8-1A31-CAAC-A321-BB33ABC8414",
|
||||
"date": "2016-08-01T14:24:11.316Z",
|
||||
"author": "bob.heater@example.com",
|
||||
"comment": "will rework the heating model",
|
||||
"topic_guid": "B345F4F2-3A04-B43B-A713-5E456BEF8228"
|
||||
}
|
||||
],
|
||||
"RelatedTopics":[{
|
||||
"related_topic_guid": "db49df2b-0e42-473b-a3ee-f7b785d783c4"
|
||||
}, {
|
||||
"related_topic_guid": "6963a846-54d1-4050-954d-607cd5e48aa3"
|
||||
}],
|
||||
"Viewpoints":[{
|
||||
"guid": "b24a82e9-f67b-43b8-bda0-4946abf39624",
|
||||
"perspective_camera": {
|
||||
"camera_view_point": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"camera_direction": {
|
||||
"x": 1.0,
|
||||
"y": 1.0,
|
||||
"z": 2.0
|
||||
},
|
||||
"camera_up_vector": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
},
|
||||
"field_of_view": 90.0,
|
||||
"aspect_ratio": 1.33
|
||||
},
|
||||
"lines": [{
|
||||
"start_point": {
|
||||
"x": 2.0,
|
||||
"y": 1.0,
|
||||
"z": 1.0
|
||||
},
|
||||
"end_point": {
|
||||
"x": 0.0,
|
||||
"y": 1.0,
|
||||
"z": 0.7
|
||||
}
|
||||
}],
|
||||
"clipping_planes": [{
|
||||
"location": {
|
||||
"x": 0.7,
|
||||
"y": 0.3,
|
||||
"z": -0.2
|
||||
},
|
||||
"direction": {
|
||||
"x": 1.0,
|
||||
"y": 0.4,
|
||||
"z": 0.1
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
"guid": "a11a82e7-e66c-34b4-ada1-5846abf39133",
|
||||
"perspective_camera": {
|
||||
"camera_view_point": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
},
|
||||
"camera_direction": {
|
||||
"x": 1.0,
|
||||
"y": 1.0,
|
||||
"z": 2.0
|
||||
},
|
||||
"camera_up_vector": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 1.0
|
||||
},
|
||||
"field_of_view": 90.0,
|
||||
"aspect_ratio": 1.33
|
||||
},
|
||||
"lines": [{
|
||||
"start_point": {
|
||||
"x": 1.0,
|
||||
"y": 1.0,
|
||||
"z": 1.0
|
||||
},
|
||||
"end_point": {
|
||||
"x": 0.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
}
|
||||
}],
|
||||
"clipping_planes": [{
|
||||
"location": {
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"z": 0.5
|
||||
},
|
||||
"direction": {
|
||||
"x": 1.0,
|
||||
"y": 0.0,
|
||||
"z": 0.0
|
||||
}
|
||||
}],
|
||||
"coloring": [
|
||||
{
|
||||
"color": "#ff0000",
|
||||
"components": [
|
||||
{
|
||||
"ifc_guid": "2MF28NhmDBiRVyFakgdbCT",
|
||||
"originating_system": "Example CAD Application",
|
||||
"authoring_tool_id": "EXCAD/v1.0"
|
||||
},
|
||||
{
|
||||
"ifc_guid": "3$cshxZO9AJBebsni$z9Yk"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"visibility": {
|
||||
"default_visibility": true,
|
||||
"exceptions": [
|
||||
{
|
||||
"ifc_guid": "2MF28NhmDBiRVyFakgdbCT",
|
||||
"originating_system": "Example CAD Application",
|
||||
"authoring_tool_id": "EXCAD/v1.0"
|
||||
},
|
||||
{
|
||||
"ifc_guid": "3$cshxZO9AJBebsni$z9Yk"
|
||||
}
|
||||
],
|
||||
"view_setup_hints": {
|
||||
"spaces_visible": true,
|
||||
"space_boundaries_visible": false,
|
||||
"openings_visible": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"guid": "a11a82e7-e66c-34b4-ada1-5846abf39133",
|
||||
"index": 10,
|
||||
"perspective_camera": {
|
||||
"camera_view_point": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"camera_direction": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 2
|
||||
},
|
||||
"camera_up_vector": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 1
|
||||
},
|
||||
"field_of_view": 90,
|
||||
"aspect_ratio": 1.33
|
||||
},
|
||||
"lines": [
|
||||
{
|
||||
"start_point": {
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"end_point": {
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"clipping_planes": [
|
||||
{
|
||||
"location": {
|
||||
"x": 0.5,
|
||||
"y": 0.5,
|
||||
"z": 0.5
|
||||
},
|
||||
"direction": {
|
||||
"x": 1,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
}
|
||||
}
|
||||
],
|
||||
"bitmaps": [
|
||||
{
|
||||
"guid": "20c1cb56-315f-4a0a-922d-ed7a4a8edf55",
|
||||
"bitmap_type": "jpg",
|
||||
"location": {
|
||||
"x": 10,
|
||||
"y": -10,
|
||||
"z": 7
|
||||
},
|
||||
"normal": {
|
||||
"x": -1,
|
||||
"y": 1.25,
|
||||
"z": 0
|
||||
},
|
||||
"up": {
|
||||
"x": -5.4,
|
||||
"y": -4.3,
|
||||
"z": 1
|
||||
},
|
||||
"height": 1666
|
||||
}
|
||||
],
|
||||
"snapshot": {
|
||||
"snapshot_type": "png"
|
||||
},
|
||||
"selection": [
|
||||
{
|
||||
"ifc_guid": "2MF28NhmDBiRVyFakgdbCT",
|
||||
"originating_system": "Example CAD Application",
|
||||
"authoring_tool_id": "EXCAD/v1.0"
|
||||
},
|
||||
{
|
||||
"ifc_guid": "3$cshxZO9AJBebsni$z9Yk"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"DocumentReferences":[{
|
||||
"guid": "212ab37a-6122-448e-86fc-86503183b520",
|
||||
"url": "http://example.com/files/LegalRequirements.pdf",
|
||||
"description": "The legal requirements for buildings."
|
||||
}, {
|
||||
"guid": "6cbfe31d-95c3-4f4d-92a6-420c23698721",
|
||||
"document_guid": "472ab37a-6122-448e-86fc-86503183b520",
|
||||
"description": "The building owners global design parameters for buildings."
|
||||
}],
|
||||
"Documents":[{
|
||||
"guid": "472ab37a-6122-448e-86fc-86503183b520",
|
||||
"filename": "LegalRequirements.pdf"
|
||||
}, {
|
||||
"guid": "6cbfe31d-95c3-4f4d-92a6-420c23698721",
|
||||
"filename": "DesignParameters.pdf"
|
||||
}],
|
||||
"Events": [{
|
||||
"topic_guid": "A211FCC2-3A3B-EAA4-C321-DE22ABC8414",
|
||||
"date": "2014-11-19T14:24:11.316Z",
|
||||
"author": "Architect@example.com",
|
||||
"events": [
|
||||
{
|
||||
"type": "status_updated",
|
||||
"value": "Closed"
|
||||
}
|
||||
]
|
||||
}, {
|
||||
"topic_guid": "A245F4F2-2C01-B43B-B612-5E456BEF8116",
|
||||
"date": "2013-10-21T17:34:22.409Z",
|
||||
"author": "Architect@example.com",
|
||||
"events": [
|
||||
{
|
||||
"type": "type_updated",
|
||||
"value": "Warning"
|
||||
}
|
||||
]
|
||||
}],
|
||||
"EventComments":[{
|
||||
"comment_guid": "C4215F4D-AC45-A43A-D615-AA456BEF832B",
|
||||
"topic_guid": "A211FCC2-3A3B-EAA4-C321-DE22ABC8414",
|
||||
"date": "2014-11-19T14:24:11.316Z",
|
||||
"author": "Architect@example.com",
|
||||
"events": [
|
||||
{
|
||||
"type": "comment_created",
|
||||
"value": null
|
||||
}
|
||||
]
|
||||
}, {
|
||||
"comment_guid": "C4215F4D-AC45-A43A-D615-AA456BEF832B",
|
||||
"topic_guid": "A245F4F2-2C01-B43B-B612-5E456BEF8116",
|
||||
"date": "2013-10-21T17:34:22.409Z",
|
||||
"author": "Architect@example.com",
|
||||
"events": [
|
||||
{
|
||||
"type": "viewpoint_updated",
|
||||
"value": "b24a82e9-f67b-43b8-bda0-4946abf39624"
|
||||
}
|
||||
]
|
||||
}]
|
||||
}
|
||||
@@ -1,650 +0,0 @@
|
||||
|
||||
# OpenCDE - OpenCDE Python implementation
|
||||
# Copyright (C) 2021 Prabhat Singh <singh01prabhat@gmail.com>
|
||||
#
|
||||
# This file is part of OpenCDE.
|
||||
#
|
||||
# OpenCDE is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# OpenCDE is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with OpenCDE. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from flask import jsonify, url_for, redirect, render_template, request, session, send_file
|
||||
from flask_login import login_user, logout_user, login_required, current_user
|
||||
from flask.blueprints import Blueprint
|
||||
from foundation.models import User, OAuth2AuthorizationCode, OAuth2Token, OAuth2Client
|
||||
from run import app, db
|
||||
import json
|
||||
import os
|
||||
from flask_expects_json import expects_json
|
||||
|
||||
bcf = Blueprint("bcf", __name__, template_folder="templates", url_prefix="/bcf/3.0")
|
||||
my_absolute_dirpath = os.path.abspath(os.path.dirname(__file__))
|
||||
data = open(
|
||||
f"{my_absolute_dirpath}/project.json",
|
||||
)
|
||||
jdata = json.load(data)
|
||||
schema_path = os.path.join(my_absolute_dirpath, "schemas")
|
||||
|
||||
|
||||
def validate_client(request):
|
||||
Headers = str.split(request.headers["Authorization"])
|
||||
token = Headers[1]
|
||||
access_token = OAuth2Token.query.filter_by(access_token=token).first()
|
||||
if access_token:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def invalid_user():
|
||||
response = jsonify({"message": "User not recognized"})
|
||||
response.status = 401
|
||||
return response
|
||||
|
||||
|
||||
def invalid_project():
|
||||
response = jsonify({"message": "Project not found"})
|
||||
response.status_code = 404
|
||||
return response
|
||||
|
||||
|
||||
@bcf.route("/projects")
|
||||
def projects():
|
||||
if validate_client(request):
|
||||
return jsonify(jdata["Projects"])
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/")
|
||||
@login_required
|
||||
def bcf_3():
|
||||
return "<h1>BCF HOMPAGE</h1>"
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>")
|
||||
def project_details(project_id):
|
||||
if validate_client(request):
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
return jsonify(project)
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
project_put = open(f"{schema_path}/Project/project_PUT.json")
|
||||
project_put = json.load(project_put)
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>", methods=["PUT"])
|
||||
@expects_json(project_put)
|
||||
def update_project(project_id):
|
||||
if validate_client(request) and request.method == "PUT":
|
||||
data = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
response = jsonify(data)
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/extensions")
|
||||
def extensions(project_id):
|
||||
if validate_client(request):
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
return jsonify(jdata["Extensions"])
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics")
|
||||
def topics(project_id):
|
||||
|
||||
if validate_client(request):
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
return jsonify(jdata["Topics"])
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
topic_post = open(f"{schema_path}/Collaboration/Topic/topic_POST.json")
|
||||
topic_post = json.load(topic_post)
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>", methods=["POST"])
|
||||
@expects_json(topic_post)
|
||||
def create_topic(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "POST":
|
||||
body = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
response = jsonify(body)
|
||||
response.status_code = 201
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>")
|
||||
def topic_details(project_id, topic_id):
|
||||
if validate_client(request):
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify(topic)
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
topic_put = open(f"{schema_path}/Collaboration/Topic/topic_PUT.json")
|
||||
topic_put = json.load(topic_put)
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>", methods=["PUT"])
|
||||
@expects_json(topic_put)
|
||||
def update_topic(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "PUT":
|
||||
data = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify(data)
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>", methods=["DELETE"])
|
||||
def delete_topic(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "DELETE":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify("OK")
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/snippet", methods=["GET"])
|
||||
def get_snippet(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return send_file(f"{my_absolute_dirpath}/success.txt", download_name="snippet.txt")
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/snippet", methods=["PUT"])
|
||||
def update_snippet(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "PUT":
|
||||
data = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify("PUT Request Successful")
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/files_information", methods=["GET"])
|
||||
def get_files_information(project_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
return jsonify(jdata["Files"])
|
||||
return invalid_project()
|
||||
else:
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/files", methods=["GET"])
|
||||
def get_files(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify("Get request for files successful")
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
file_put = open(f"{schema_path}/Collaboration/File/file_PUT.json")
|
||||
file_put = json.load(file_put)
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/files", methods=["PUT"])
|
||||
@expects_json(file_put)
|
||||
def update_files(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "PUT":
|
||||
data = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify(data)
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/comments", methods=["GET"])
|
||||
def get_comments(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify(jdata["Comments"])
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
comment_post = open(f"{schema_path}/Collaboration/Comment/comment_POST.json")
|
||||
comment_post = json.load(comment_post)
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/comments", methods=["POST"])
|
||||
@expects_json(comment_post)
|
||||
def create_comments(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "POST":
|
||||
data = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
response = jsonify(data)
|
||||
response.status_code = 201
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/comments/<comment_id>", methods=["GET"])
|
||||
def get_comment(project_id, topic_id, comment_id):
|
||||
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
for comment in jdata["Comments"]:
|
||||
if (comment["guid"]) == comment_id:
|
||||
return jsonify(comment)
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
comment_put = open(f"{schema_path}/Collaboration/Comment/comment_PUT.json")
|
||||
comment_put = json.load(comment_put)
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/comments/<comment_id>", methods=["PUT"])
|
||||
@expects_json(comment_put)
|
||||
def update_comment(project_id, topic_id, comment_id):
|
||||
if validate_client(request) and request.method == "PUT":
|
||||
data = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
for comment in jdata["Comments"]:
|
||||
if (comment["guid"]) == comment_id:
|
||||
return jsonify(data)
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/comments/<comment_id>", methods=["DELETE"])
|
||||
def delete_comment(project_id, topic_id, comment_id):
|
||||
if validate_client(request) and request.method == "DELETE":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
for comment in jdata["Comments"]:
|
||||
if (comment["guid"]) == comment_id:
|
||||
return jsonify("OK")
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/viewpoints", methods=["GET"])
|
||||
def get_viewpoints(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if topic["guid"] == topic_id:
|
||||
return jsonify(jdata["Viewpoints"])
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
viewpoint_post = open(f"{schema_path}/Collaboration/Viewpoint/viewpoint_POST.json")
|
||||
viewpoint_post = json.load(viewpoint_post)
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/viewpoints", methods=["POST"])
|
||||
@expects_json(viewpoint_post)
|
||||
def create_viewpoints(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "POST":
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if topic["guid"] == topic_id:
|
||||
data = request.get_json()
|
||||
response = jsonify(data)
|
||||
response.status_code = 201
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/viewpoints/<viewpoint_id>", methods=["GET"])
|
||||
def get_viewpoint(project_id, topic_id, viewpoint_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if topic["guid"] == topic_id:
|
||||
for viewpoint in jdata["Viewpoints"]:
|
||||
if viewpoint["guid"] == viewpoint_id:
|
||||
response = jsonify(viewpoint)
|
||||
response.status_code = 200
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/viewpoints/<viewpoint_id>", methods=["DELETE"])
|
||||
def delete_viewpoint(project_id, topic_id, viewpoint_id):
|
||||
if validate_client(request) and request.method == "DELETE":
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if topic["guid"] == topic_id:
|
||||
for viewpoint in jdata["Viewpoints"]:
|
||||
if viewpoint["guid"] == viewpoint_id:
|
||||
response = jsonify(viewpoint)
|
||||
response.status_code = 200
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/viewpoints/<viewpoint_id>/snapshot", methods=["GET"])
|
||||
def get_snapshot(project_id, topic_id, viewpoint_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if topic["guid"] == topic_id:
|
||||
for viewpoint in jdata["Viewpoints"]:
|
||||
if viewpoint["guid"] == viewpoint_id:
|
||||
response = jsonify(viewpoint["snapshot"])
|
||||
response.status_code = 200
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/viewpoints/<viewpoint_id>/bitmaps/<bitmap_id>", methods=["GET"])
|
||||
def get_bitmap(project_id, topic_id, viewpoint_id, bitmap_id):
|
||||
if validate_client(request):
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if topic["guid"] == topic_id:
|
||||
for viewpoint in jdata["Viewpoints"]:
|
||||
if viewpoint["guid"] == viewpoint_id:
|
||||
for bitmap in viewpoint["bitmaps"]:
|
||||
if bitmap["guid"] == bitmap_id:
|
||||
response = jsonify(bitmap)
|
||||
response.status_code = 200
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/viewpoints/<viewpoint_id>/selection", methods=["GET"])
|
||||
def get_selection(project_id, topic_id, viewpoint_id):
|
||||
if validate_client(request):
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if topic["guid"] == topic_id:
|
||||
for viewpoint in jdata["Viewpoints"]:
|
||||
if viewpoint["guid"] == viewpoint_id:
|
||||
response = jsonify(viewpoint["selection"])
|
||||
response.status_code = 200
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/viewpoints/<viewpoint_id>/coloring", methods=["GET"])
|
||||
def get_coloring(project_id, topic_id, viewpoint_id):
|
||||
if validate_client(request):
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if topic["guid"] == topic_id:
|
||||
for viewpoint in jdata["Viewpoints"]:
|
||||
if viewpoint["guid"] == viewpoint_id:
|
||||
response = jsonify(viewpoint["coloring"])
|
||||
response.status_code = 200
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/viewpoints/<viewpoint_id>/visibility", methods=["GET"])
|
||||
def get_visibility(project_id, topic_id, viewpoint_id):
|
||||
if validate_client(request):
|
||||
for project in jdata["Projects"]:
|
||||
if project["project_id"] == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if topic["guid"] == topic_id:
|
||||
for viewpoint in jdata["Viewpoints"]:
|
||||
if viewpoint["guid"] == viewpoint_id:
|
||||
response = jsonify(viewpoint["visibility"])
|
||||
response.status_code = 200
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/related_topics", methods=["GET"])
|
||||
def get_related_topics(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify(jdata["RelatedTopics"])
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
related_topics_put = open(f"{schema_path}/Collaboration/RelatedTopic/related_topic_PUT.json")
|
||||
related_topics_put = json.load(related_topics_put)
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/related_topics", methods=["PUT"])
|
||||
@expects_json(related_topics_put)
|
||||
def update_related_topics(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "PUT":
|
||||
data = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify(data)
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/document_references", methods=["GET"])
|
||||
def get_document_references(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify(jdata["DocumentReferences"])
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
document_reference_post = open(f"{schema_path}/Collaboration/DocumentReference/document_reference_POST.json")
|
||||
document_reference_post = json.load(document_reference_post)
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/document_references", methods=["POST"])
|
||||
@expects_json(document_reference_post)
|
||||
def create_document_references(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "POST":
|
||||
data = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
response = jsonify(data)
|
||||
response.status_code = 201
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
document_reference_put = open(f"{schema_path}/Collaboration/DocumentReference/document_reference_PUT.json")
|
||||
document_reference_put = json.load(document_reference_put)
|
||||
|
||||
|
||||
@bcf.route(
|
||||
"/projects/<project_id>/topics/<topic_id>/document_references/<document_reference_id>",
|
||||
methods=["PUT"],
|
||||
)
|
||||
@expects_json(document_reference_put)
|
||||
def update_document_references(project_id, topic_id, document_reference_id):
|
||||
if validate_client(request) and request.method == "PUT":
|
||||
data = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
for document in jdata["DocumentReferences"]:
|
||||
if (document["guid"]) == document_reference_id:
|
||||
return jsonify(data)
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/documents", methods=["GET"])
|
||||
def get_documents(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
return jsonify(jdata["Documents"])
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/documents", methods=["POST"])
|
||||
def create_documents(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "POST":
|
||||
data = request.get_json()
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
response = jsonify(data)
|
||||
response.status_code = 201
|
||||
return response
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/documents/<document_id>", methods=["GET"])
|
||||
def get_document(project_id, topic_id, document_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
for document in jdata["Documents"]:
|
||||
if (document["guid"]) == document_id:
|
||||
return send_file(f"{my_absolute_dirpath}/success.txt", download_name="document.txt")
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/events", methods=["GET"])
|
||||
def get_events(project_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
return jsonify(jdata["Events"])
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/<topic_id>/events", methods=["GET"])
|
||||
def get_topic_events(project_id, topic_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
for event in jdata["Events"]:
|
||||
if (event["topic_guid"]) == topic_id:
|
||||
return jsonify(event)
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route("/projects/<project_id>/topics/comments/events", methods=["GET"])
|
||||
def get_comments_events(project_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
return jsonify(jdata["EventComments"])
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
|
||||
|
||||
@bcf.route(
|
||||
"/projects/<project_id>/topics/<topic_id>/comments/<comment_id>/events",
|
||||
methods=["GET"],
|
||||
)
|
||||
def get_comment_events(project_id, topic_id, comment_id):
|
||||
if validate_client(request) and request.method == "GET":
|
||||
for project in jdata["Projects"]:
|
||||
if (project["project_id"]) == project_id:
|
||||
for topic in jdata["Topics"]:
|
||||
if (topic["guid"]) == topic_id:
|
||||
for comment in jdata["EventComments"]:
|
||||
if (comment["comment_guid"]) == comment_id:
|
||||
return jsonify(comment)
|
||||
return invalid_project()
|
||||
return invalid_user()
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"title": "comment_actions",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["update",
|
||||
"delete"]
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"title": "project_actions",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["update",
|
||||
"createTopic",
|
||||
"createDocument"]
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"title": "topic_actions",
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["update",
|
||||
"updateBimSnippet",
|
||||
"updateRelatedTopics",
|
||||
"updateDocumentReferences",
|
||||
"updateFiles",
|
||||
"createComment",
|
||||
"createViewpoint",
|
||||
"delete"]
|
||||
}
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"required": false,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"enum": ["delete"]
|
||||
}
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"title": "comment_GET",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"date": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"author": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"comment": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"topic_guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"viewpoint_guid": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"modified_date": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"modified_author": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"authorization": {
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"properties": {
|
||||
"comment_actions": {
|
||||
"$ref": "../Action/comment_actions.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"title": "comment_POST",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"guid": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"comment": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"viewpoint_guid": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"title": "comment_PUT",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"viewpoint_guid": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"title": "document_GET",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"filename": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
||||
"title": "document_reference_GET",
|
||||
"description": "Schema for a single document reference GET, BCF REST API.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"document_guid": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"url": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"title": "document_reference_POST",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"guid": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"document_guid": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"url": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"title": "document_reference_PUT",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"document_guid": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"url": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"title": "comment_event_GET",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"comment_guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"topic_guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"date": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"author": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "event_action.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"title": "event_action",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"type": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"title": "topic_event_GET",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"topic_guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"date": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"author": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"actions": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"$ref": "event_action.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
||||
"title": "file_GET",
|
||||
"description": "Schema for a single file GET, BCF REST API.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ifc_project": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"ifc_spatial_structure_element": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"filename": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"date": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"reference": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"title": "file_PUT",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ifc_project": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"ifc_spatial_structure_element": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"filename": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"date": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"reference": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
||||
"title": "project_file_display_information",
|
||||
"description": "Schema for the display information of a project file, BCF REST API.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"field_display_name": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"field_value": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"title": "project_file_information",
|
||||
"description": "Schema for a single project file information, BCF REST API.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"display_information": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "project_file_display_information.json" },
|
||||
"default": []
|
||||
},
|
||||
"file": { "$ref": "file_GET.json" }
|
||||
}
|
||||
}
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"description": "Schema for the listing the response of the GET project files information service",
|
||||
"title": "project_files_information_GET",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "project_file_information.json"
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
||||
"title": "related_topic_GET",
|
||||
"description": "Schema for single related topic GET, BCF REST API.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"related_topic_guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
||||
"title": "related_topic_PUT",
|
||||
"description": "Schema for related topic PUT, BCF REST API.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"related_topic_guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"title": "bim_snippet",
|
||||
"type": ["object",
|
||||
"null"],
|
||||
"properties": {
|
||||
"snippet_type": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"is_external": {
|
||||
"required": true,
|
||||
"type": "boolean"
|
||||
},
|
||||
"reference": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"reference_schema": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
{
|
||||
"title": "topic_GET",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"server_assigned_id": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"topic_type": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"topic_status": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"reference_links": {
|
||||
"type": ["array",
|
||||
"null"],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"priority": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"index": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"labels": {
|
||||
"type": ["array",
|
||||
"null"],
|
||||
"items": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
}
|
||||
},
|
||||
"creation_date": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"creation_author": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"modified_date": {
|
||||
"type": "string"
|
||||
},
|
||||
"modified_author": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"assigned_to": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"stage": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"description": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"bim_snippet": {
|
||||
"$ref": "bim_snippet.json"
|
||||
},
|
||||
"due_date": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"authorization": {
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"properties": {
|
||||
"topic_actions": {
|
||||
"$ref": "../Action/topic_actions.json"
|
||||
},
|
||||
"topic_status": {
|
||||
"type": ["array",
|
||||
"null"],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
||||
"title": "topic_POST",
|
||||
"description": "Schema for topic POST, BCF REST API.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"guid": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"topic_type": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"topic_status": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"reference_links": {
|
||||
"type": ["array",
|
||||
"null"],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"priority": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"index": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"labels": {
|
||||
"type": ["array",
|
||||
"null"],
|
||||
"items": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
}
|
||||
},
|
||||
"assigned_to": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"stage": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"description": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"bim_snippet": {
|
||||
"$ref": "bim_snippet.json"
|
||||
},
|
||||
"due_date": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
||||
"title": "topic_PUT",
|
||||
"description": "Schema for topic PUT, BCF REST API.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"topic_type": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"topic_status": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"reference_links": {
|
||||
"type": ["array",
|
||||
"null"],
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"priority": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"index": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"labels": {
|
||||
"type": ["array",
|
||||
"null"],
|
||||
"items": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
}
|
||||
},
|
||||
"assigned_to": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"stage": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"description": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"bim_snippet": {
|
||||
"$ref": "bim_snippet.json"
|
||||
},
|
||||
"due_date": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"guid": {
|
||||
"type": "string"
|
||||
},
|
||||
"bitmap_type": {
|
||||
"type": "string",
|
||||
"enum": ["jpg", "png"]
|
||||
},
|
||||
"location": {
|
||||
"$ref": "location.json"
|
||||
},
|
||||
"normal": {
|
||||
"$ref": "direction.json"
|
||||
},
|
||||
"up": {
|
||||
"$ref": "direction.json"
|
||||
},
|
||||
"height": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bitmap_type": {
|
||||
"type": "string",
|
||||
"enum": ["jpg", "png"]
|
||||
},
|
||||
"bitmap_data": {
|
||||
"type": "string",
|
||||
"format": "base64"
|
||||
},
|
||||
"location": {
|
||||
"$ref": "location.json"
|
||||
},
|
||||
"normal": {
|
||||
"$ref": "direction.json"
|
||||
},
|
||||
"up": {
|
||||
"$ref": "direction.json"
|
||||
},
|
||||
"height": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"title": "clipping_plane",
|
||||
"type": ["object",
|
||||
"null"],
|
||||
"properties": {
|
||||
"location": {
|
||||
"$ref": "location.json"
|
||||
},
|
||||
"direction": {
|
||||
"$ref": "direction.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"title": "coloring",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"color": {
|
||||
"type": "string"
|
||||
},
|
||||
"components": {
|
||||
"$ref": "component_list.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
||||
"title": "coloring_GET",
|
||||
"description": "Schema for coloring GET, BCF REST API.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"coloring": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "coloring.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"title": "component",
|
||||
"type": ["object"],
|
||||
"properties": {
|
||||
"ifc_guid": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"originating_system": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"authoring_tool_id": {
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"title": "component list",
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "component.json"
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"title": "components",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"selection": {
|
||||
"$ref": "component_list.json"
|
||||
},
|
||||
"coloring": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "coloring.json"
|
||||
}
|
||||
},
|
||||
"visibility": {
|
||||
"$ref": "visibility.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"title": "direction",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "number"
|
||||
},
|
||||
"y": {
|
||||
"type": "number"
|
||||
},
|
||||
"z": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"title": "line",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"start_point": {
|
||||
"$ref": "point.json"
|
||||
},
|
||||
"end_point": {
|
||||
"$ref": "point.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{
|
||||
"title": "location",
|
||||
"type": ["object",
|
||||
"null"],
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "number"
|
||||
},
|
||||
"y": {
|
||||
"type": "number"
|
||||
},
|
||||
"z": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"title": "orthogonal_camera",
|
||||
"type": ["object",
|
||||
"null"],
|
||||
"properties": {
|
||||
"camera_view_point": {
|
||||
"$ref": "point.json"
|
||||
},
|
||||
"camera_direction": {
|
||||
"$ref": "direction.json"
|
||||
},
|
||||
"camera_up_vector": {
|
||||
"$ref": "direction.json"
|
||||
},
|
||||
"view_to_world_scale": {
|
||||
"type": "number"
|
||||
},
|
||||
"aspect_ratio": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"title": "perspective_camera",
|
||||
"type": ["object",
|
||||
"null"],
|
||||
"properties": {
|
||||
"camera_view_point": {
|
||||
"$ref": "point.json"
|
||||
},
|
||||
"camera_direction": {
|
||||
"$ref": "direction.json"
|
||||
},
|
||||
"camera_up_vector": {
|
||||
"$ref": "direction.json"
|
||||
},
|
||||
"field_of_view": {
|
||||
"type": "number"
|
||||
},
|
||||
"aspect_ratio": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"title": "point",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {
|
||||
"type": "number"
|
||||
},
|
||||
"y": {
|
||||
"type": "number"
|
||||
},
|
||||
"z": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-03/schema#",
|
||||
"title": "selection_GET",
|
||||
"description": "Schema for selection GET, BCF REST API.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"selection": {
|
||||
"$ref": "component_list.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"title": "snapshot_GET",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"snapshot_type": {
|
||||
"type": ["string"],
|
||||
"enum": ["jpg", "png"]
|
||||
}
|
||||
}
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"title": "snapshot_POST",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"snapshot_type": {
|
||||
"type": ["string"],
|
||||
"enum": ["jpg", "png"]
|
||||
},
|
||||
"snapshot_data": {
|
||||
"type": ["string"],
|
||||
"format": "base64"
|
||||
}
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"title": "view_setup_hints",
|
||||
"type": ["object", "null"],
|
||||
"properties": {
|
||||
"spaces_visible": {
|
||||
"type": "boolean",
|
||||
"default": "false"
|
||||
},
|
||||
"space_boundaries_visible": {
|
||||
"type": "boolean",
|
||||
"default": "false"
|
||||
},
|
||||
"openings_visible": {
|
||||
"type": "boolean",
|
||||
"default": "false"
|
||||
}
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
{
|
||||
"title": "viewpoint_GET",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"index": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"guid": {
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
"orthogonal_camera": {
|
||||
"$ref": "orthogonal_camera.json"
|
||||
},
|
||||
"perspective_camera": {
|
||||
"$ref": "perspective_camera.json"
|
||||
},
|
||||
"lines": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "line.json"
|
||||
}
|
||||
},
|
||||
"clipping_planes": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "clipping_plane.json"
|
||||
}
|
||||
},
|
||||
"bitmaps": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "bitmap_GET.json"
|
||||
}
|
||||
},
|
||||
"snapshot": {
|
||||
"$ref": "snapshot_GET.json"
|
||||
},
|
||||
"authorization": {
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"properties": {
|
||||
"viewpoint_actions": {
|
||||
"$ref": "../Action/viewpoint_actions.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"title": "viewpoint_POST",
|
||||
"type": [ "object" ],
|
||||
"properties": {
|
||||
"guid": {
|
||||
"type": ["string",
|
||||
"null"]
|
||||
},
|
||||
"index": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"orthogonal_camera": {
|
||||
"$ref": "orthogonal_camera.json"
|
||||
},
|
||||
"perspective_camera": {
|
||||
"$ref": "perspective_camera.json"
|
||||
},
|
||||
"lines": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "line.json"
|
||||
}
|
||||
},
|
||||
"clipping_planes": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "clipping_plane.json"
|
||||
}
|
||||
},
|
||||
"bitmaps": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "bitmap_POST.json"
|
||||
}
|
||||
},
|
||||
"snapshot": {
|
||||
"$ref": "snapshot_POST.json"
|
||||
},
|
||||
"components": {
|
||||
"$ref": "components.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user