first new commit

This commit is contained in:
philc 2023-01-22 10:53:09 +01:00
commit 869f192031
280 changed files with 101529 additions and 0 deletions

15
.eslintrc.js Executable file
View File

@ -0,0 +1,15 @@
module.exports = {
"extends": "eslint:recommended",
"rules": {
"@typescript-eslint/no-unused-vars": "off",
"arrow-body-style": "off"
},
"env": {
"browser": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaVersion": "latest"
}
};

5
.gitignore vendored Executable file
View File

@ -0,0 +1,5 @@
/node_modules
/tmp
/tribes
/nationchains/blocks
/yarn*

232
LICENSE Normal file
View File

@ -0,0 +1,232 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for software and other kinds of works.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is 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. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too.
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.
To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free.
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 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. Use with the GNU Affero General Public License.
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 Affero 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 special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU 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 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 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 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 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 General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”.
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 GPL, see <http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read <http://www.gnu.org/philosophy/why-not-lgpl.html>.

67
README.md Normal file
View File

@ -0,0 +1,67 @@
# apiXtribe a Decentralized Autonomous Organisation (DAO)
You are here on the tech side, to understand under the wood how it's work and how you can contribute to this tech journey.
See [apiXtribe web site](https://apixtribe.crabdance.com) how to create a new social world with apiXtribe.
## apiXtribe Architecture a quick view & keywords definition
- **a nation** is a topology network of nodes town that share the same fundamental rules (engrave into a common blockchain).
- **a town** is a physical installation node of a machine (linux server) plug into a LAN network (a telecom box NAT conf) accessible into internet a WAN network (domain name redirect to a town public IP).
- **a tribe** has a unique name and is a space to host data to apply specific algo rule register at the creation of a tribe by a druid that respect the town rules
- **a pagan** 'village farmer' is a unique identification create by anyone based on PGP (public/Private key) that allow a member to proof that he owns data. Can be anonymous but all his actions are store into a blockchain of reputation.
- **a druid** 'rules master' is a pagan that owned and defined rules of a tribe, he owns a domain name (DNS) to make it tribe's data accessible over the net. As pagan he can be anonymous, he is responsible of the tribe activities and can ban a pagan of his tribe. Any pagan become a druid when he decides to create a tribe by requesting a town mayor
- **a mayor** is a owner of a physical machine, he is in charge to make a town node working (IP availibily) and accept to be part of a nation (rules of the blockchain nation).
- **a contract** is an algorythme that is written into the blockchain and will be run if an action trig. Immutable contract are the one link to the blockchain. Any pagan/druid/major can defined a contract that apply to other pagans.
- **the Xtrib coin** is the token that drive the blockchain, it materialises an exchange value and is a conterpoint
- **a git apiXtribe** is a package ready to install a town by a mayor that will be able to join a nation (then it accepts nation rules). You can also create a nation and stay alone or not. Then mayor will be able to receive druid request to create a tribe. Then druid will invite pagan to join a tribe...
All actors will have the same target to respect contracts and are free to leave or to stay into a nation, a town or a tribe. If a contract is not fair, then a nation, tribe, will not create value.
## Network topology
As it is a decentralize organisation, a public address directory is replicated on each instance of a town into /nationchains .<br>
Each town (instance) is accessible with an anonymlous DNS https://apixtribe.crabdance.com where the IP adresse is update when elected town's IP to register a new block into the blockchain (each 10minutes).
```
/nationchains
/blocks #the blockchain each file is a block
/socialword
/contracts/ #sign contracts each js file is a rules register in the blockchain
/metaobject/ #sign meta data under blockchain control to validate object integrity (pagans, druid, contract,town, nation, ...)
/objects/name/ # uuid.json object description content
/searchindex/ # index files to apply CRUD in an easy way
/static/ any usefull static file
apixtribe.html # Describe the project as a end-user
namennation.html # ex: antsnation.html describe the rules for this nation and explain benefit for a futur mayor to join it
```
## apiXtribe Documentation
**Documentation for apiXtribe usage to start**, tuto HOW TO for user, dev ... is [here](https://apixtribe.org/doc)
- [Set up](http://gitlab.ndda.fr/philc/apixtribe/-/wikis/SETUP)
- [Manage users](http://gitlab.ndda.fr/philc/apixtribe/-/wikis/HOWTOuser)
- [Manage multilangue referentials](http://gitlab.ndda.fr/philc/apixtribe/-/wikis/HOWTOreferential)
- [How to manage trigger action](http://gitlab.ndda.fr/philc/apixtribe/-/wikis/HOWTOtriggeraction)
To quickly test we recommand you to "Set up for dev", you just need an linux desktop and it rocks...
Read more on the wiki's project [apixtribe wiki](http://gitlab.ndda.fr/philc/apixtribe/-/wikis/home)
**Documentation for apiXtribe dev** is done into the gitea wiki project.
You will find anything to understand how it works under the wood. Feel free to ask question, comment to improve the code. If you need to debug or you want to add a main feature setup your machine as local (no exchange with the blockchain)
- [Nations -> Towns -> Tribes -> Pagans Architecture](https://gitea.ndda.fr/apixtribe/apixtribe/wiki/Architecture) the social world
- [nationchains]() the blockchain and social contracts between mpayor, druids and pagans.
- [package.json and tribes/townconf.js]() setup and convention rules: eslint, unit test, multi-lang, header
- [middleware/route/Models]() restfullapi logical from routes to middleware to models to datamapper and result res.status(http code).json({data,info,moreinfo})
- [accessrights]() Access right definition on object (Object,...)
- [Odmdb.js]() convention for object descriptor manager to index, search, create, update, delete item of an Object collections (to simplify CRUD actions for Models)
- [Pagans.js]() decentralized and anonymous publicKey for a pagan based on PGP with accessright onto other objects. Authentification process and localStorage management of 24 hours JWT. Login/psw/email.
- [Tribes.js]() A Druid space to host a web space.
- [Tags.js]() Event trackers to log any usefull event that can be used into sodial contracts
- [Messages.js]() Notification (delete after read) and message between any interactions concerning pagans (1 to many or 1 to 1), trig by social contract or by a pagan
[Setup.js]() Technical infrastructure an
- [Blockchains and Contracts]() Contracts.js

119
apxtrib.js Executable file
View File

@ -0,0 +1,119 @@
const fs = require( 'fs-extra' );
const bodyParser = require( 'body-parser' );
const cors = require( 'cors' );
const express = require( 'express' );
/*******************************************
SEE http://gitlab.ndda.fr/philc/apixtribe/-/wikis/HOWTOoverview
To have a quick understanding before doing deeply in source code
*********************************************/
// check setup
if( !fs.existsSync( '/etc/nginx/nginx.conf' ) ) {
console.log( '\x1b[31m Check documentation, nginx have to be installed on this server first, no /etc/nginx/nginx.conf available, install then rerun yarn command.' );
process.exit();
}
if( !fs.existsSync( './tribes/townconf.js' ) ) {
console.log( `\x1b[42m####################################\nWellcome into apixtribe, you need to init your town by "yarn setup" the first time . \nCheck README's project to learn more. more.\n #####################################\x1b[0m` );
process.exit();
}
// config.js exist in any case from Setup.checkinit();
const config = require( './tribes/townconf.js' );
// Tribes allow to get local apixtribe instance context
// dataclient .tribeids [] .DOMs [] .routes (plugins {url:name route:path}) .appname {tribeid:[website]}
const dataclient = require( './models/Tribes' )
.init();
console.log( 'allowed DOMs to access to this apixtribe server: ', dataclient.DOMs )
const app = express();
app.set( 'trust proxy', true );
// To set depending of data form or get size to send
app.use( bodyParser.urlencoded( config.bodyparse.urlencoded ) );
// To set depending of post put json data size to send
app.use( express.json() )
app.use( bodyParser.json( config.bodyparse.json ) );
app.locals.tribeids = dataclient.tribeids;
console.log( 'app.locals.tribeids', app.locals.tribeids );
// User token authentification and user init user search
const datauser = require( './models/Pagans' )
.init( dataclient.tribeids );
app.locals.tokens = datauser.tokens;
console.log( 'app.locals.tokens key ', Object.keys( app.locals.tokens ) )
// Cors management
const corsOptions = {
origin: ( origin, callback ) => {
if( origin === undefined ) {
callback( null, true );
} else if( origin.indexOf( 'chrome-extension' ) > -1 ) {
callback( null, true );
} else {
//console.log( 'origin', origin )
//marchais avant modif eslint const rematch = ( /^https?\:\/\/(.*)\:.*/g ).exec( origin )
const rematch = ( /^https?:\/\/(.*):.*/g )
.exec( origin )
//console.log( rematch )
let tmp = origin.replace( /http.?:\/\//g, '' )
.split( '.' )
if( rematch && rematch.length > 1 ) tmp = rematch[ 1 ].split( '.' );
//console.log( 'tmp', tmp )
let dom = tmp[ tmp.length - 1 ];
if( tmp.length > 1 ) {
dom = `${tmp[tmp.length-2]}.${tmp[tmp.length-1]}`
}
console.log( `origin: ${origin}, dom:${dom}, CORS allowed? : ${dataclient.DOMs.includes( dom )}` );
if( dataclient.DOMs.includes( dom ) ) {
callback( null, true )
} else {
console.log( `Origin is not allowed by CORS` );
callback( new Error( 'Not allowed by CORS' ) );
}
}
},
exposedHeaders: Object.keys( config.exposedHeaders )
};
// CORS
app.use( cors( corsOptions ) );
// Static Routes
app.use( express.static( `${__dirname}/tribes/${config.mayorId}/www/cdn/public`, {
dotfiles: 'allow'
} ) );
//Allow to public access a space dev delivered by apixtribe
// this is just a static open route for dev purpose,
// for production, we'll use a nginx static set to /www/app/appname
/*console.log( `${config.dnsapixtribe}/space/tribeid/website`, dataclient.appname );
Object.keys( dataclient.appname )
.forEach( cid => {
dataclient.appname[ cid ].forEach( website => {
app.use( `/space/${cid}/${website}`, express.static( `${config.tribes}/${cid}/spacedev/${website}` ) );
} )
} );
*/
// Routers add any routes from /routes and /plugins
console.log( 'Routes available on this apixtribe instance' );
console.log( dataclient.routes );
// prefix only use for dev purpose in production a proxy nginx redirect /app/ to node apixtribe
dataclient.routes.forEach( r => {
try {
app.use( r.url, require( r.route ) );
} catch ( err ) {
console.log( `\x1b[31m!!! WARNING issue with route ${r.route} from ${r.url} check err if route is key then solve err, if not just be aware that this route won't work on your server. If you are not the maintainer and no turn around please contact the email maintainer.\x1b[0m` )
console.log( 'raise err-:', err );
}
} )
// Listen web server from config profil (dev prod, other)
app.listen( config.porthttp, () => {
console.log( `check in your browser that api works http://${config.dnsapixtribe}:${config.porthttp}` );
} );
/*httpServer.setTimeout( config.settimeout );
if( config.withssl == "YES" ) {
const httpsServer = https.createServer( config.SSLCredentials, app );
httpsServer.listen( config.port.https, () => {
console.log( `check in your browser that api works https://${config.dnsapixtribe}:${config.port.https}` );
} );
httpsServer.setTimeout( config.settimeout );
};*/
console.log( "\x1b[42m\x1b[37m", "Made with love for people's freedom, enjoy !!!", "\x1b[0m" );

View File

@ -0,0 +1,95 @@
const jwt = require( 'jwt-simple' );
const jsonfile = require( 'jsonfile' );
const fs = require( 'fs-extra' );
const moment = require( 'moment' );
const glob = require( 'glob' );
const path = require( 'path' );
// A REMPLACER PAR hasAccessrighton.js
/*
qui permet de passer en parametre des tests d'actions autoriser sur une objet
*/
// Check if package is installed or not to pickup the right config file
const src = ( __dirname.indexOf( '/node_modules/' ) > -1 ) ? '../../..' : '..';
const config = require( path.normalize( `${__dirname}/${src}/config.js` ) );
const haveAccessrighttoanobject = ( req, res, next ) => {
/*
from isAuthenticated req.session.header.accessrights={app:{'tribeid:projet':profile},
data:{ "sitewebsrc": "RWCDO",
"contacts": "RWCDO"}}
from the last successfull authentification.
profile is a keyword menu available into clientconf.json of tribeid
data, list of object accessright Read Write Create Delete Owner
a xuuid can read any objet if R
if O wner means that it can only read write its object create by himself
This middleware check that we apply RESTFull CRUD concept depending of access right of a xuuid trying to act onto a xworkon tribeid
Action get = Read put = Update post = Create delete = Delete
object = req.Urlpath.split(/)[0]
*/
console.log( 'haveAccessrighttoanobject()?' );
// req.originalUrl contain /object/action/id object id to run action
// req.route.methods ={ put:true, delete:true post:true, get:true }
const objet = req.baseUrl.slice( 1 ); //contain /object
const model = objet.charAt( 0 )
.toUpperCase() + objet.slice( 1 ); // model u object with first letter in uppercase
let droit = "";
let ownby = [];
/*
Check if object exist and get the OWNBY array, not relevant for referentials object that is only manage by CRUD no Owner logic
*/
if( objet != "referentials" ) {
if( !fs.existsSync( `${config.tribes}/${req.session.header.xworkon}/${objet}/${req.params.id}.json` ) ) {
res.status( 404 )
.send( {
payload: {
info: [ 'idNotfound' ],
model,
moreinfo: `${config.tribes}/${req.session.header.xworkon}/${objet}/${req.params.id}.json does not exist `
}
} );
} else {
ownby = jsonfile.readFileSync( `${config.tribes}/${req.session.header.xworkon}/${objet}/${req.params.id}.json` )
.OWNBY;
}
}
//console.log( req.session.header )
if( req.session.header.xpaganid == config.devnoauthxuuid ) {
console.log( 'haveAccessrighttoanobject yes cause dev test user' );
} else {
// accessrights was load from isAuthenticated.js middleware to make it available in req.session.header to be used into route for specific access if needed mainly to filter data in the get request depending of profil and data accessright.
if( Object.keys( req.session.header.accessrights.data )
.includes( "Alltribeid" ) && req.session.header.accessrights.data[ "Alltribeid" ][ objet ] ) {
droit = req.session.header.accessrights.data[ "Alltribeid" ][ objet ];
}
// erase rights if tribeid is specified in addition of Alltribeid
if( ( req.session.header.accessrights.data[ req.session.header.xworkon ] ) &&
req.session.header.accessrights.data[ req.session.header.xworkon ][ objet ] ) {
droit = req.session.header.accessrights.data[ req.session.header.xworkon ][ objet ];
if( ( req.route.methods.get && droit.includes( 'R' ) ) ||
( req.route.methods.put && droit.includes( 'U' ) ) ||
( req.route.methods.delete && droit.includes( 'D' ) ) ||
ownby.includes( req.params.id ) ) {
console.log( 'haveAccessrighttoanobject yes' )
} else if( req.route.methods.post && droit.includes( 'C' ) ) {
console.log( 'haveAccessrighttoanobject yes create' );
} else {
console.log( 'haveAccessrighttoanobject no' )
res.status( 403 )
.send( {
payload: {
info: [ 'NoAccessrights' ],
model,
moreinfo: `User ${req.session.header.xpaganid} accessrights are not set to do this action`
}
} );
}
}
}
next();
};
module.exports = haveAccessrighttoanobject;

88
middlewares/checkHeaders.js Executable file
View File

@ -0,0 +1,88 @@
const path = require( 'path' );
// Check if package is installed or not to pickup the right config file
//const src = ( __dirname.indexOf( '/node_modules/' ) > -1 ) ? '../../..' : '..';
//const config = require( path.normalize( `${__dirname}/${src}/config.js` ) );
const config = require( '../config.js' );
/*
Check que le header contient des éléments necessaire pour les
routes utilisant tribeid / language / token / uuid
*/
const checkHeaders = ( req, res, next ) => {
//console.log( 'checkHeaders()' );
// These headers must be passed in the request
// X-Auth and X-Uuid could have any true value
// header is stored in req.app.locals.header to be pass to route
/* const header = {
xtribeid: req.header('x-client-id'),
xlang: req.header('x-language'),
xauth: req.header('x-auth'),
xuuid: req.header('x-uuid'),
xworkon: req.header('x-xorkon',
xapp:req.header('x-app'))
};
On recupere accessrights via is Authenticated
*/
req.session = {};
const header = {};
let missingheader = "";
//console.log( 'avant validation headers', req.headers );
//attention changement 7/11/2021 phil des exposedheader cf config.js
//If in httprequest url header are send then they are used inpriority
//Use case : send an email with a unique link that works without password and request to change password
for( const h of config.exposedHeaders ) {
//console.log( h, req.header( h ) )
if( req.params[ h ] ) {
header[ h ] = req.params[ h ]
} else if( req.header( h ) ) {
header[ h ] = req.header( h )
} else {
// Missing header
missingheader += " " + h
}
};
//console.log( 'header', header )
if( req.params.xauth && req.params.xuuid ) {
// If this exist => it is a timeout limited token
req.app.locals.tokens[ req.params.xpaganid ] = req.params.xauth;
}
req.session.header = header;
// Each header have to be declared
if( missingheader != "" ) {
return res.status( 403 )
.send( {
info: [ 'forbiddenAccess' ],
model: 'Pagans',
moreinfo: 'checkHeader headerIsMissing:' + missingheader
} );
};
//console.log( req.app.locals.tribeids )
if( !req.app.locals.tribeids.includes( header.xtribe ) ) {
return res.status( 404 )
.send( {
info: [ 'tribeiddoesnotexist' ],
model: 'Pagans',
moreinfo: `xtribe unknown: ${header.xtribe}`
} );
}
if( !req.app.locals.tribeids.includes( header.xworkon ) ) {
return res.status( 404 )
.send( {
info: [ 'tribeiddoesnotexist' ],
model: 'Pagans',
moreinfo: `xworkon unknown: ${header.xworkon}`
} );
}
if( !config.languagesAvailable.includes( header.xlang ) ) {
return res.status( 404 )
.send( {
info: [ 'langNotused' ],
model: 'Pagans',
moreinfo: `xlang unknown: ${header.xlang}`
} );
}
//console.log( 'After middleare checkHeaders.js req.session.header', req.session.header )
//console.log( 'checkheaders next' )
next();
};
module.exports = checkHeaders;

42
middlewares/hasAccessrighton.js Executable file
View File

@ -0,0 +1,42 @@
const fs = require( 'fs-extra' );
const glob = require( 'glob' );
const path = require( 'path' );
const config = require( '../config.js' );
const hasAccessrighton = ( object, action, ownby ) => {
/*
@action (mandatory) : CRUDO
@object (mandatory)= name of a folder object in /tribeid space can be a tree for example objects/items
@ownby (option) = list des uuid propriétaire
return next() if all action exist in req.app.local.tokens[UUID].ACCESSRIGHTS.data[object]
OR if last action ="O" and uuid exist in ownBy
Careffull if you have many action CRO let O at the end this will force req.right at true if the owner try an action on this object
*/
return ( req, res, next ) => {
//console.log( 'err.stack hasAccessrights', err.statck )
//console.log( `test accessright on object:${object} for ${req.session.header.xworkon}:`, req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS.data[ req.session.header.xworkon ] )
req.right = false;
if( req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS.data[ req.session.header.xworkon ] && req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS.data[ req.session.header.xworkon ][ object ] ) {
req.right = true;
[ ...action ].forEach( a => {
if( a == "O" && ownby && ownby.includes( req.session.header.xpaganid ) ) {
req.right = true;
} else {
req.right = req.right && req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS.data[ req.session.header.xworkon ][ object ].includes( a )
}
} )
}
//console.log( 'Access data autorise? ', req.right )
if( !req.right ) {
return res.status( 403 )
.send( {
info: [ 'forbiddenAccess' ],
model: 'middleware',
moreinfo: 'no auth to act on this object'
} )
}
next();
}
}
module.exports = hasAccessrighton;

113
middlewares/isAuthenticated.js Executable file
View File

@ -0,0 +1,113 @@
const jwt = require( 'jwt-simple' );
const jsonfile = require( 'jsonfile' );
const fs = require( 'fs-extra' );
const moment = require( 'moment' );
const glob = require( 'glob' );
//const path = require( 'path' );
// Check if package is installed or not to pickup the right config file
//const src = '..'; // ( __dirname.indexOf( '/node_modules/' ) > -1 ) ? '../../..' : '..';
//const config = require( path.normalize( `${__dirname}/${src}/config.js` ) );
const config = require( '../config.js' );
const isAuthenticated = ( req, res, next ) => {
/*
check if authenticated with valid token
if not => set req.session.header.xauth=1
if yes => set for xWorkon
req.session.header.accessrights={
app:{'tribeid:website':[liste of menu]},
data:{ "sitewebsrc": "RWCDO",
"contacts": "RWCDO"}}
Liste of menu is linked with the app tht have to be consistent with accessrights.data
data, list of object accessright Read Write Create Delete Owner
a xuuid can read any objet if R
if O wner means that it can only read write its object create by himself
*/
console.log( 'isAuthenticated()?' );
//console.log( 'req.app.locals.tokens', req.app.locals.tokens )
//console.log( 'req.session.header', req.session.header );
// Check if token exist or not
req.session.header.accessrights = { app: "", data: {} }
if( req.session.header.xpaganid == config.devnoauthxuuid && req.session.header.xauth == config.devnoauthxauth ) {
console.log( 'isAuthenticated yes: carrefull using a bypass password give you accessrights={}' );
} else if( req.session.header.xpaganid == "1" || !req.app.locals.tokens[ req.session.header.xpaganid ] ) {
console.log( `isAuthenticated no : uuid=1 (value=${req.session.header.xpaganid}) or locals.tokens[uuid] empty ` );
console.log( 'req.app.locals.tokens de xpaganid', req.app.locals.tokens[ req.session.header.xpaganid ] );
console.log( 'list key uuid de req.app.locals.tokens', Object.keys( req.app.locals.tokens ) )
req.session.header.xauth = "1"
} else if( req.app.locals.tokens[ req.session.header.xpaganid ].TOKEN !== req.session.header.xauth ) {
// console.log(req.session.header.xuuid);
// console.log(req.session.header.xauth);
// update tokens from file in case recently logged
try {
console.log( 'token not in list of token (req.app.locals.tokens) try to refresh from file' );
req.app.locals.tokens = jsonfile.readFileSync( `${config.tmp}/tokens.json` );
} catch ( err ) {
console.log( `check isAuthenticated issue in reading ${config.tmp}/tokens.json` );
}
if( req.app.locals.tokens[ req.session.header.xpaganid ].TOKEN !== req.session.header.xauth ) {
// if still does not exist then out
console.log( 'isAuthenticated no, token outdated' );
req.session.header.xauth = "1"
req.session.header.xpaganid = "1"
}
}
if( req.session.header.xauth == "1" ) {
//return res.status( 403 )
return res.status( 403 )
.send( {
info: [ 'forbiddenAccess' ],
model: 'Pagans',
moreinfo: 'isAuthenticated faill'
} )
} else {
console.log( 'isAuthenticated yes' );
if( req.app.locals.tokens[ req.session.header.xpaganid ] ) {
//console.log( `accessright pour ${req.session.header.xpaganid}`, req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS );
//set header.accessrights from tokens.json
req.session.header.accessrights = req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS
} else {
// case of bypass no accessright available
req.session.header.accessrights = {}
}
// Once per day, clean old token
const currentday = moment()
.date();
console.log( 'test si menagedone' + currentday, !fs.existsSync( `${config.tmp}/menagedone${currentday}` ) )
if( !fs.existsSync( `${config.tmp}/menagedone${currentday}` ) ) {
glob.sync( `${config.tmp}/menagedone*` )
.forEach( f => {
fs.remove( f, ( err ) => {
if( err ) {
console.log( 'err remove menagedone', err )
}
} )
} );
glob.sync( `${config.tmp}/mdcreator*.log` )
.forEach( f => {
fs.remove( f, ( err ) => {
if( err ) {
console.log( 'err remove mdcreator log', err )
}
} )
} );
const newtokens = {};
for( const k of Object.keys( req.app.locals.tokens ) ) {
try {
const decodedToken = jwt.decode( req.app.locals.tokens[ k ].TOKEN, config.jwtSecret );
//console.log( moment( decodedToken.expiration ), moment() )
//console.log( moment( decodedToken.expiration ) >= moment() )
if( moment( decodedToken.expiration ) >= moment() ) {
newtokens[ k ] = req.app.locals.tokens[ k ];
}
} catch ( err ) {
console.log( "Check isAuthenticated cleaning token ", err );
}
};
req.app.locals.tokens = newtokens;
jsonfile.writeFileSync( `${config.tmp}/tokens.json`, newtokens );
fs.writeFileSync( `${config.tmp}/menagedone${currentday}`, 'fichier semaphore to clean data each day can be deleted with no consequence', 'utf-8' );
}
next();
}
};
module.exports = isAuthenticated;

121
models/Contracts.js Executable file
View File

@ -0,0 +1,121 @@
const fs = require( 'fs-extra' );
const jsonfile = require( 'jsonfile' );
const glob = require( 'glob' );
const path = require( 'path' );
const moment = require( 'moment' );
const axios = require( 'axios' );
const scrapeit = require( 'scrape-it' );
const cheerio = require( 'cheerio' );
const Mustache = require( 'mustache' );
const qrcode = require( 'qrcode' );
// Check if package is installed or not to pickup the right config file
const config = require( '../tribes/townconf.js' );
/*
Model that will process actions plan for each client like sending email campain, or anything that
are plan in /tribes/tribeid/actions/todo
*/
const Cards = {}; //require('../../models/Cards');
const Contracts = {};
/*
Send if envoicampain a liste of email in param.msg.destperso with param.headers
if not envoicampain, it just return a test about what to send
@param = {headers, msg:{destperso}}
*/
Contracts.sendcampain = async ( param, envoicampain ) => {
if( envoicampain ) {
// Carefull w the action post outputs/msg just wait the feedback of the 1st message
const retcampain = await axios.post( 'https://mail.maildigit.fr/outputs/msg', param.msg, {
headers: param.headers
} );
if( retcampain.status !== 200 ) {
console.log( "err", retcampain.payload.moreinfo );
fs.appendFileSync( `${config.tribes}/log_erreurglobal.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - IMPOSSIBLE TO SEND CAMPAIN TODO for :' + param.tribeid + ' -- ' + retcampain.payload.moreinfo + '\n', 'utf-8' );
};
return retcampain;
} else {
// permet de tester ce qu'il y a à envoyer
let premieremail = "";
for( let i = 0; i < param.msg.destperso.length; i++ ) {
premieremail += param.msg.destperso[ 0 ].email + ",";
}
return {
status: 201,
payload: {
info: [ 'simplecomptage' ],
model: 'Contracts',
moreinfo: "#email: " + param.msg.destperso.length + " - 5 1st emails: " + premieremail
}
};
}
}
Contracts.initActiontodo = async ( envoie ) => {
const datedeb = moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' );
let todo, actiondone;
let log = {
nbaction: 0,
nbactionexec: 0,
nbactionerr: 0,
actionlist: ""
};
const listclient = jsonfile.readFileSync( `${config.tribes}/tribeids.json` );
for( let clid in listclient ) {
console.log( listclient[ clid ] );
let listaction = glob.sync( `${config.tribes}/${listclient[clid]}/actions/todo/*.json` );
for( let action in listaction ) {
console.log( listaction[ action ] )
log.nbaction++;
todo = jsonfile.readFileSync( listaction[ action ] );
let passdate = true;
// currentdate doit etre après la startDate si existe et avant valideuntilDate si existe
// console.log('test now est avant date start ', moment() < moment(todo.startDate, 'YYYYMMDD HH:mm:ss').toDate());
if( todo.startDate && ( moment() < moment( todo.startDate, 'YYYYMMDD HH:mm:ss' )
.toDate() ) ) {
passdate = false;
};
// currentdate ne doit pas depasser la date de validité de la tache
// console.log('test now est après la date de validite ', moment() > moment(todo.validuntilDate, 'YYYYMMDD HH:mm:ss').toDate());
if( todo.valideuntilDate && ( moment() > moment( todo.validuntilDate, 'YYYYMMDD HH:mm:ss' )
.toDate() ) ) {
passdate = false;
};
// currentdate
if( passdate && todo.action && todo.error == "" ) {
log.nbactionexec++;
const actiondone = await Contracts[ todo.action ]( todo, envoie );
todo.datesRun.push( moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) );
//console.log("actiondone", actio jsonfile.writeFileSyncndone);
log.actionlist += "STATUS:" + actiondone.status + " -- " + listaction[ action ] + "\n";
if( actiondone.status == 200 ) {
todo.error = "";
} else {
log.nbactionerr++;
todo.error += "status : " + actiondone.status + ' ' + actiondone.payload.moreinfo;
};
if( parseInt( todo.maxnumberoftime ) && todo.maxnumberoftime != "999" && ( todo.datesRun.length >= parseInt( todo.maxnumberoftime ) ) ) {
//archive en done this triggeraction
jsonfile.writeFileSync( listaction[ action ].replace( '/todo/', '/done/' ), todo, {
spaces: 2
} );
fs.unlinkSync( listaction[ action ] );
} else {
jsonfile.writeFileSync( listaction[ action ], todo, {
spaces: 2
} );
}
} else {
log.actionlist += "STATUS : not executed " + listaction[ action ] + "\n";
};
};
};
const trace = "###################### LOGS ####################\nSTART:" + datedeb + " END:" + moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + "\n nombre d'actions analysées : " + log.nbaction + " dont executées : " + log.nbactionexec + " dont en erreur: " + log.nbactionerr + "\n" + log.actionlist;
fs.appendFileSync( `${config.tribes}/log.txt`, trace, 'utf-8' );
return "done";
}
module.exports = Contracts;

400
models/Messages.js Executable file
View File

@ -0,0 +1,400 @@
const bcrypt = require( 'bcrypt' );
const fs = require( 'fs-extra' );
const path = require( 'path' );
const jsonfile = require( 'jsonfile' );
const glob = require( 'glob' );
const Mustache = require( 'mustache' );
const jwt = require( 'jwt-simple' );
const { DateTime } = require( 'luxon' );
const UUID = require( 'uuid' );
const Outputs = require( '../models/Outputs.js' );
const config = require( '../tribes/townconf.js' );
const checkdata = require( `${config.tribes}/${config.mayorId}/www/cdn/public/js/checkdata` );
/*
Message manager
* Manage apixtribe message at different level
* this means that an object (most often a user or a contact) want to send a question to an other user.
To know more http://gitlab.ndda.fr/philc/apixtribe/-/wikis/HOWTONotification
*/
const Messages = {};
Messages.init = () => {
console.group( 'init Message' );
Messages.aggregate();
}
Messages.byEmailwithmailjet = ( tribeid, msg ) => {
/* @tribeid requester
@msg =[{
To:[{Email,Name}],
Cc:[{Email,Name}],
Bcc:[{Email,Name}]}],
Subject:"Subject",
TextPart:"texte content",
HTMLPart:"html content"
}]
If tribeid has a mailjet account it use it if not then it use the apixtribe @mailjetconf = {apikeypub:, apikeypriv:, From:{Email:,Name:}}
This send a bunch of messages check the mailjet account used
FYI: Basic account is 200 email /days 6000 /month
Log is stored into
tribeidsender/messages/logs/sent/timestamp.json
@todo GUI to manage statistics and notification alert limit sender email
*/
console.log( 'Envoie mailjet' )
const confclient = fs.readJsonSync( `${config.tribes}/${tribeid}/clientconf.json` );
let tribeidsender = tribeid;
if( confclient.smtp && confclient.smtp.mailjet ) {
mailjetconf = confclient.smtp.mailjet;
} else {
const confapixtribe = fs.readJsonSync( `${config.tribes}/${config.mayorId}/clientconf.json` );
if( !( confapixtribe.smtp && confapixtribe.smtp.mailjet ) ) {
return { status: 403, data: { models: "Messages", info: [ "nosmtpmailjet" ], moreinfo: "missing smtpmailjet parameter in apixtribe contact your admin to activate an mailjet account on this server." } }
}
tribeidsender = "apixtribe";
mailjetconf = confapixtribe.smtp.mailjet;
}
//add from from setings account
const MSG = msg.map( m => { m.From = mailjetconf.From; return m; } );
const mailjet = require( 'node-mailjet' )
.connect( mailjetconf.apikeypub, mailjetconf.apikeypriv );
const request = mailjet.post( 'send', { version: 'v3.1' } )
.request( { "Messages": MSG } );
request
.then( result => {
//store into tribeidsender/messages/logs/sent/timestamp.json
const t = Date.now();
MSG.result = result.body;
fs.outputJson( `${config.tribes}/${tribeidsender}/messages/logs/sent/${t}.json`, MSG )
console.log( result.body )
} )
.catch( err => {
const t = Date.now();
MSG.result = err;
fs.outputJson( `${config.tribes}/${tribeidsender}/messages/logs/error/${t}.json`, MSG )
console.log( err.statusCode, err )
} )
};
Messages.buildemail = ( tribeid, tplmessage, data ) => {
/* @tribeid => client concerned by sending email get info from clientconf.json (customization, useradmin:{ EMAIL,LOGIN} , ...)
@tplmessage => folder where template is available (ex: apixtribe/referentials/dataManagement/template/order)
@data { destemail = email to
if destuuid => then it get user EMAIL if exiat
subject = mail subject
// any relevant information for template message
msgcontenthtml = html msg content
msgcontenttxt = text msg content
....}
@return msg
mail part ready to send by mailjet or other smtp
*/
if( !fs.existsSync( `${config.tribes}/${tribeid}/clientconf.json` ) ) {
return { status: 404, data: { model: "Messages", info: [ "tribeiddoesnotexist" ], moreinfo: `${tribeid} does not exist` } }
}
if( !fs.existsSync( `${config.tribes}/${tplmessage}` ) ) {
return { status: 404, data: { model: "Messages", info: [ "tplmessagedoesnotexist" ], moreinfo: `tpl does not exist ${tplmessage}` } }
}
const clientconf = fs.readJsonSync( `${config.tribes}/${tribeid}/clientconf.json` )
//add clientconf.customization into data for template
data.customization = clientconf.customization;
//manage receiver
const msg = { To: [ { Email: clientconf.useradmin.EMAIL, Name: clientconf.useradmin.LOGIN } ] };
if( data.destemail ) {
msg.To.push( { Email: data.destemail } )
}
if( data.destuuid && fs.exist( `${config.tribes}/${tribeid}/users/${data.destuuid}.json` ) ) {
const uuidconf = fs.readJsonSync( `${config.tribes}/${tribeid}/users/${data.destuuid}.json` );
if( uuidconf.EMAIL && uuidconf.EMAIL.length > 4 ) {
msg.To.push( { Email: uuidconf.EMAIL, Name: uuidconf.LOGIN } )
}
}
//console.log( data )
// email content
msg.Subject = `Message from ${tribeid}`;
if( data.subject ) msg.Subject = data.subject;
msg.TextPart = Mustache.render( fs.readFileSync( `${config.tribes}/${data.tplmessage}/contenttxt.mustache`, 'utf-8' ), data );
msg.HTMLPart = Mustache.render( fs.readFileSync( `${config.tribes}/${data.tplmessage}/contenthtml.mustache`, 'utf-8' ), data );
return {
status: 200,
data: { model: "Messages", info: [ "msgcustomsuccessfull" ], msg: msg }
}
};
Messages.postinfo = ( data ) => {
/*
Data must have:
at least one of desttribeid:,destuuid:,destemail:
if an email have to be sent tplmessage:"tribeid/referentials/dataManagement/template/tplname",
at least one of contactemail:,contactphone,contactlogin,contactuuid
any other usefull keys
Data is stored into contacts object .json
*/
let contact = "";
[ 'contactemail', 'contactphone', 'contactuuid', 'contactlogin' ].forEach( c => {
if( data[ c ] ) contact += c + "##" + data[ c ] + "###";
} )
console.log( contact )
if( contact == "" ) {
return { status: 404, data: { model: "Messages", info: [ "contactundefine" ], moreinfo: "no contact field found in this form" } }
}
if( !data.desttribeid ) {
return { status: 404, data: { model: "Messages", info: [ "tribeidundefine" ], moreinfo: "no desttribeid field found in this form" } }
}
// save new message in contacts
let messages = {};
if( fs.existsSync( `${config.tribes}/${data.desttribeid}/contacts/${contact}.json` ) ) {
messages = fs.readJsonSync( `${config.tribes}/${data.desttribeid}/contacts/${contact}.json`, 'utf-8' );
}
messages[ Date.now() ] = data;
fs.outputJsonSync( `${config.tribes}/${data.desttribeid}/contacts/${contact}.json`, messages );
// if templatemessage exist then we send alert email
// data content have to be consistent with tplmessage to generate a clean email
if( data.tplmessage && data.tplmessage != "" &&
fs.existsSync( `${config.tribes}/${data.tplmessage}` ) ) {
if( !( data.msgtplhtml && data.msgtpltxt ) ) {
data.msgcontenthtml = `<p>${data.contactname} - ${contact.replace(/###/g,' ').replace(/##/g,":")}</p><p>Message: ${data.contactmessage}</p>`
data.msgcontenttxt = `${data.contactname} - ${contact}/nMessage:${data.contactmessage}\n`;
} else {
data.msgcontenthtml = Mustache.render( data.msgtplhtml, data )
data.msgcontenttxt = Mustache.render( data.msgtpltxt, data )
}
const msg = Messages.buildemail( data.desttribeid, data.tplmessage, data )
if( msg.status == 200 ) {
Messages.byEmailwithmailjet( data.desttribeid, [ msg.data.msg ] );
}
// we get error message eventualy but email feedback sent is not in real time
return msg;
} else {
return { status: 404, data: { info: "missingtpl", model: "Messages", moreinfo: `missing template ${data.tplmessage}` } }
}
}
Messages.aggregate = () => {
// collect each json file and add them to a global.json notifat require level
const dest = {};
try {
glob.sync( `${ config.tribes }/**/notif_*.json` )
.forEach( f => {
//console.log( 'find ', f )
const repglob = `${path.dirname(f)}/global.json`;
if( !dest[ repglob ] ) { dest[ repglob ] = [] }
dest[ repglob ].push( fs.readJsonSync( f, 'utf-8' ) );
fs.removeSync( f );
} )
//console.log( dest )
Object.keys( dest )
.forEach( g => {
let notif = [];
if( fs.existsSync( g ) ) { notif = fs.readJsonSync( g ) };
fs.writeJsonSync( g, notif.concat( dest[ g ] ), 'utf-8' );
} )
} catch ( err ) {
Console.log( "ATTENTION, des Messages risquent de disparaitre et ne sont pas traitées." );
}
}
Messages.object = ( data, header ) => {
/*
Create or replace an object no check at all here this is only
a way to add / replace object
if deeper thing have to be checheck or done then a callback:{tribeidplugin,pluginname,functionname}
data.descttribeid tribeid to send at least to admin
data.tplmessage = folder of emailtemplate
*/
console.log( 'data', data )
console.log( `${config.tribes}/${header.xworkon}/${data.object}` )
if( !fs.existsSync( `${config.tribes}/${header.xworkon}/${data.object}` ) ) {
return {
status: 404,
data: {
model: "Messages",
info: [ 'UnknownObjectfortribeid' ],
moreinfo: `This object ${data.object} does not exist for this tribeid ${header.xworkon}`
}
};
}
if( data.uuid == 0 ) {
data.uuid = UUID.v4();
}
if( data.callback ) {
// check from plugin data and add relevant data
const Plug = require( `${config.tribes}/${data.callback.tribeid}/plugins/${data.callback.plugins}/Model.js` );
const check = Plug[ data.callback.function ]( header.xworkon, data );
if( check.status == 200 ) {
data = check.data.data;
} else {
return check;
}
};
fs.outputJsonSync( `${config.tribes}/${header.xworkon}/${data.object}/${data.uuid}.json`, data );
// if templatemessage exist then we try to send alert email
if( data.tplmessage && data.tplmessage != "" &&
fs.existsSync( `${config.tribes}/${data.tplmessage}` ) ) {
const msg = Messages.buildemail( data.desttribeid, data.tplmessage, data )
if( msg.status == 200 ) {
console.log( 'WARN EMAIL DESACTIVATED CHANGE TO ACTIVATE in Messages.js' )
//Messages.byEmailwithmailjet( data.desttribeid, [ msg.data.msg ] );
}
// we get error message eventualy but email feedback sent is not in real time see notification alert in case of email not sent.
};
//Sendback data with new information
return {
status: 200,
data: {
model: "Messages",
info: [ 'Successregisterobject' ],
msg: data
}
};
}
Messages.notification = ( data, header ) => {
//check if valid notification
if( !req.body.elapseddays ) { req.body.elapseddays = 3 }
let missingkey = "";
[ 'uuid', 'title', 'desc', 'icon', 'elapseddays', 'classicon' ].forEach( k => {
if( !data[ k ] ) missingkey += ` ${k},`
if( k == "classicon" && !( [ 'text-danger', 'text-primary', 'text-success', 'text-warning', 'text-info' ].includes( data[ k ] ) ) ) {
missingkey += ` classicon is not well set ${data[k]}`;
}
} )
if( missingkey != '' ) {
return {
status: 422,
data: {
model: "Messages",
info: [ 'InvalidNote' ],
moreinfo: `invalid notification sent cause missing key ${missingkey}`
}
};
}
if( !fs.existsSync( `${config.tribes}/${header.xworkon}/${data.object}` ) ) {
return {
status: 404,
data: {
model: "Messages",
info: [ 'UnknownObjectfortribeid' ],
moreinfo: `This object ${data.object} does not exist for this tribeid ${data.tribeid}`
}
};
}
//by default store in tmp/notification this is only for sysadmin
// user adminapixtribe
let notdest = `${config.tmp}`;
if( data.app ) {
const destapp = `${config.tribes}/${data.app.split(':')[0]}/spacedev/${data.app.split(':')[1]}`;
if( fs.existsSync( destapp ) ) {
notdest = destapp;
}
}
if( data.plugins ) {
const destplugin = `${config.tribes}/${data.plugins.split(':')[0]}/plugins/${data.plugins.split(':')[1]}`;
if( fs.existsSync( destplugin ) ) {
notdest = destplugin;
}
}
if( data.object ) {
const destobject = `${config.tribes}/${data.tribeid}/${data.object}/`;
if( fs.existsSync( destobject ) ) {
notdest = destobject;
}
}
if( !data.time ) {
data.time = Date.now();
}
fs.outputJsonSync( `${notdest}/Messages/notif_${data.time}.json`, data );
return {
status: 200,
data: {
model: "Messages",
info: [ 'Successregisternotification' ],
notif: data
}
};
}
Messages.request = ( tribeid, uuid, ACCESSRIGHTS, apprequest ) => {
// list notif for each app
// list notif for each tribeid / objects if Owned
// list notif for
// check uuid notification
// Collect all notification and agregate them at relevant level;
Messages.aggregate();
//for test purpose
//const notif = jsonfile.readFileSync( `${config.tribes}/ndda/spacedev/mesa/src/static/components/notification/data_notiflist_fr.json` );
let notif;
if( !fs.existsSync( `${config.tribes}/${apprequest.tribeid}/spacedev/${apprequest.website}/src/static/components/notification/data_notiflist_${apprequest.lang}.json` ) ) {
// by default we send back this but this highlght an issue
notif = {
"iconnotif": "bell",
"number": 1,
"notifheader": "Your last Messages",
"notiffooter": "Check all Messages",
"actionnotifmanager": "",
"href": "?action=notification.view",
"notifs": [ {
"urldetail": "#",
"classicon": "text-danger",
"icon": "alert",
"title": `File does not exist`,
"desc": ` ${config.tribes}/${apprequest.tribeid}/spacedev/${apprequest.website}/src/static/components/notification/data_notiflist_${apprequest.lang}.json`,
"elapse": "il y a 13mn"
} ]
};
} else {
notif = jsonfile.readFileSync( `${config.tribes}/${apprequest.tribeid}/spacedev/${apprequest.website}/src/static/components/notification/data_notiflist_${apprequest.lang}.json` );
//clean up example notif
notif.notifs = [];
}
//check notification for plugins of tribeid of the login
glob.sync( `${config.tribes}/${tribeid}/plugins/*/Messages/global.json` )
Object.keys( ACCESSRIGHTS.app )
.forEach( a => {
// check for each app if notification about app
const appnot = `${config.tribes}/${a.split(':')[0]}/spacedev/${a.split(':')[1]}/Messages/global.json`;
if( fs.existsSync( appnot ) ) {
notif.notifs = notif.notifs.concat( fs.readJsonSync( appnot ) );
}
} );
Object.keys( ACCESSRIGHTS.plugin )
.forEach( p => {
// each plugin
if( ACCESSRIGHTS.plugin[ p ].profil == "owner" ) {
const pluginnot = `${config.tribes}/${p.split(':')[0]}/plugins/${p.split(':')[1]}/Messages/global.json`;
if( fs.existsSync( pluginnot ) ) {
notif.notifs = notif.notifs.concat( fs.readJsonSync( pluginnot ) );
}
}
} );
Object.keys( ACCESSRIGHTS.data )
.forEach( c => {
// each tribeid
Object.keys( ACCESSRIGHTS.data[ c ] )
.forEach( o => {
const cliobjnot = `${config.tribes}/${c}/${o}/Messages/global.json`
//check for each tribeid / Object per accessright user
if( fs.existsSync( cliobjnot ) ) {
console.log( `droit sur client ${c} objet ${o} : ${ACCESSRIGHTS.data[ c ][ o ]}` );
//check if intersection between user accessrigth for this object and the notification accessright is not empty @Todo replace true by intersec
console.log( 'WARN no actif filter all notif are shared with any authenticated user' )
const newnotif = fs.readJsonSync( cliobjnot )
.filter( n => { return true } );
notif.notifs = notif.notifs.concat( newnotif );
}
} )
} )
return {
status: 200,
data: {
model: "Messages",
info: [ 'successgetnotification' ],
notif: notif
}
};
}
module.exports = Messages;

105
models/Nationchains.js Executable file
View File

@ -0,0 +1,105 @@
const bcrypt = require( 'bcrypt' );
const fs = require( 'fs-extra' );
const path = require( 'path' );
const jsonfile = require( 'jsonfile' );
const glob = require( 'glob' );
const jwt = require( 'jwt-simple' );
const moment = require( 'moment' );
const axios = require( 'axios' );
const UUID = require( 'uuid' );
const Outputs = require( './Outputs.js' );
const config = require( '../tribes/townconf.js' );
const checkdata = require( `${config.tribes}/${config.mayorId}/www/cdn/public/js/checkdata` );
/*
Blockchain manager
* Manage network directory
* read Blockchain and search,
* submit a transaction (now) or contract (futur) to store from userA.pubkey to userB.pubkey a number of AXESS
* mine to be able to register a block and create AXESS
* manage APIXP rules 20 M APIXP 1AXESS = 1 block validation
* manage contract = action if something appened validate by a proof of work
*/
const Nationchains = {};
Nationchains.init = () => {
console.group( 'init Nationchains' );
}
Nationchains.synchronize = () => {
/*
Run process to communicate with a list of apixtribe instance to update transaction and earn AXP
To creation of a new tribeid or a new login
*/
//update himself then send to other information
if( process.env.NODE_ENV != "prod" ) {
// Not concerned
return {}
}
const initcurrentinstance = {
"fixedIP": "",
"lastblocknumber": 0,
"firsttimeupdate": 0,
"lastimeupdate": 0,
"positifupdate": 0,
"negatifupdate": 0,
"pubkeyadmin": "",
"tribeids": [],
"logins": [],
"knowninstance": []
};
let currentinstance = initcurrentinstance;
try {
currentinstance = fs.readFileSync( `${config.tribes}/${config.mayorId}/nationchains/nodes/${config.rootURL}`, 'utf-8' )
} catch ( err ) {
console.log( 'first init' )
}
const loginsglob = fs.readJsonSync( `${config.tmp}/loginsglob.json`, 'utf-8' );
currentinstance.logins = Object.keys( loginsglob );
currentinstance.tribeids = [ ...new Set( Object.values( loginsglob ) ) ];
currentinstance.instanceknown = glob.Sync( `${config.tribes}/${config.mayorId}/nationchains/nodes/*` );
//Save it
fs.outputJsonSync( `${config.tribes}/${config.mayorId}/nationchains/nodes/${config.rootURL}`, currentinstance );
// proof of work
// try to find a key based on last block with difficulty
// if find then send to all for update and try to get token
// in any case rerun Nationchains.synchronize()
currentinstance.instanceknown.forEach( u => {
if( u != config.rootURL ) {
//send currentinstance info and get back state of
axios.post( `https://${u}/nationchains/push`, currentinstance )
.then( rep => {
newdata = rep.payload.moreinfo
//Available update info
fs.readJson( `${config.tribes}/${config.mayorId}/nationchains/nodes/${u}`, ( err, data ) => {
if( err ) {
data.negatifupdate += 1;
data.lasttimeupdate = Date.now();
} else {
data.positifupdate += 1;
data.lastimeupdate = Date.now();
data.tribeids = newdata.tribeids;
data.logins = newdata.logins;
data.lastblocknumber = newdata.lastblocknumber;
newdata.knowninstance.forEach( k => {
if( !data.knowninstance.includes( k ) ) {
data.knowninstance.push( k );
//init the domain for next update
initcurrentinstance.firsttimeupdate = Date.now();
fs.outputJson( `${config.tribes}/${config.mayorId}/nationchains/nodes/${k}`, initcurrentinstance, 'utf-8' )
}
} )
}
//save with info
fs.outputJson( `${config.tribes}/${config.mayorId}/nationchains/nodes/${u}`, data );
} )
} )
.catch( err => {
//Not available
data.negatifupdate += 1;
data.lasttimeupdate = Date.now();
fs.outputJson( `${config.tribes}/${config.mayorId}/nationchains/nodes/${u}`, data );
} );
}
} );
};
module.exports = Nationchains;

17
models/Odmdb.js Normal file
View File

@ -0,0 +1,17 @@
const glob = require( 'glob' );
const path = require( 'path' );
const fs = require( 'fs-extra' );
const config = require( '../tribes/townconf.js' );
const Odmdb = {};
/*
Input: metaobject => data mapper of Key: Value
objname + an object {} + action check => get a valid or not answer
objname + an object {} + action search => apply matching algo to find probalistic object id
objname + action index => update /searcindex of objects concern
*/
module.exports = Odmdb;

557
models/Outputs.js Executable file
View File

@ -0,0 +1,557 @@
const fs = require( 'fs-extra' );
const path = require( 'path' );
const formidable = require( 'formidable' );
const jsonfile = require( 'jsonfile' );
const mustache = require( 'mustache' );
const moment = require( 'moment' );
const UUID = require( 'uuid' );
const pdf = require( "pdf-creator-node" );
const nodemailer = require( 'nodemailer' );
const smtpTransport = require( 'nodemailer-smtp-transport' );
const axios = require( 'axios' );
const { GoogleSpreadsheet } = require( 'google-spreadsheet' );
const config = require( '../tribes/townconf.js' );
const checkdata = require( `${config.tribes}/${config.mayorId}/www/cdn/public/js/checkdata` );
const Outputs = {};
Outputs.ggsheet2json = async ( req, header ) => {
/*
req.ggsource = key name of the ggsheet into clientconf
req.sheets = [list of sheet names to import]
into /ggsheets/ ggsource.json = {resultfolder,productIdSpreadsheet,
googleDriveCredentials}
*/
if( !fs.existsSync( `${config.tribes}/${header.xworkon}/${req.ggsource}` ) ) {
// Misconfiguration
return {
status: 404,
payload: {
data: {},
info: [ 'Misconfiguration' ],
moreinfo: `${header.xworkon}/${req.ggsource} missing file check gitlabdocumentation`,
model: 'Outputs'
}
};
}
const confgg = fs.readJsonSync( `${config.tribes}/${header.xworkon}/${req.ggsource}`, 'utf-8' )
//const ggconnect = clientconf.ggsheet[ req.ggsource ]
//googleDriveCredentials;
//console.log( ggconnect )
doc = new GoogleSpreadsheet( confgg.productIdSpreadsheet );
await doc.useServiceAccountAuth( confgg.googleDriveCredentials );
await doc.loadInfo();
let result = [];
let invalidfor = "";
//console.log( "sheets", req.sheets );
for( const sheetName of req.sheets ) {
console.log( 'loading: ', sheetName );
if( !doc.sheetsByTitle[ sheetName ] ) {
invalidfor += " " + sheetName;
} else {
sheet = doc.sheetsByTitle[ sheetName ]
await sheet.loadHeaderRow();
const records = await sheet.getRows( { offset: 1 } )
records.forEach( record => {
let offer = {}
for( let index = 0; index < record._sheet.headerValues.length; index++ ) {
offer[ record._sheet.headerValues[ index ] ] = record[ record._sheet.headerValues[ index ] ];
}
result.push( offer );
} );
}
}
const idfile = UUID.v4();
fs.outputJsonSync( `${config.tribes}/${header.xworkon}/${confgg.resultfolder}/${idfile}.json`, result, 'utf8' );
if( invalidfor == "" ) {
return {
status: 200,
payload: {
data: `${confgg.resultfolder}/${idfile}.json`,
info: [ 'Successfull' ],
model: 'Outputs'
}
}
} else {
return {
status: 207,
payload: {
data: `${confgg.resultfolder}/${idfile}.json`,
info: [ 'PartialySuccessfull' ],
moreinfo: `Following sheetNames does not exist :${invalidfor}`,
model: 'Outputs'
}
}
};
};
// REVOIR TOUT CE QU'IL Y A EN DESSOUS et ré-écrire avec la logique de ggsheet2json phil 25/10/2021
///////////////////////////////////////////////////
const sleep = ( milliseconds = 500 ) => new Promise( resolve => setTimeout( resolve, milliseconds ) );
/*
Process any data to a specific output:
emailer => generate a finale text file to send
csv => export json file to csv data
pdf => generate a customized document
*/
Outputs.envoiemail = async ( msg, nowait, nbfois ) => {
// console.log('{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}');
// console.log('msg to send', msg);
console.log( 'nbfois', nbfois );
let transporter = nodemailer.createTransport( msg.smtp );
if( !nowait ) {
console.log( 'attente 1er msg avant d envoyer les autres' );
const transport = await transporter.verify();
console.log( 'transport', transport );
if( transport.error ) {
console.log( 'Probleme de smtp', error );
return {
status: 500,
payload: {
info: ''
}
};
} else {
let rep = await transporter.sendMail( msg );
console.log( 'rep sendMail', rep );
if( rep.accepted && rep.accepted.length > 0 && rep.rejected.length == 0 ) {
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_success.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - Success after waiting 1st email to send ' + msg.to + '\n', 'utf-8' );
return {
status: '200',
payload: {
info: [ 'send1stemailok' ],
model: 'Outputs'
}
};
}
// status à tester si necessaire de detecter une erreur
// if (rep.rejectedErrors) {
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_error.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - err after waiting result\n ' + msg.to, 'utf-8' );
return {
status: '500',
payload: {
info: [ 'rejectedError' ],
model: 'Outputs'
}
};
}
} else {
transporter.sendMail( msg, async ( err, info ) => {
if( err ) {
if( nbfois < 4 ) {
console.log( 'nouvelle tentative ', nbfois );
await sleep( 600000 ); // attends 60sec pour faire une niéme tentative
Outputs.envoiemail( msg, true, nbfois + 1 );
} else {
// logerror in file
console.log( 'err', err )
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_error.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - err after 4 tries to ' + info.rejected.join( ',' ) + '\n', 'utf-8' );
// console.log('msg.to not well sent', msg.to);
}
} else {
console.log( 'info', info )
// console.log('msg.to well sent', msg.to);
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_success.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - Success after ' + nbfois + ' tries to ' + info.accepted.join( ',' ) + '\n', 'utf-8' );
}
} );
}
// return something to not wait the rest of email
return {
status: '200',
payload: {
info: [ 'send1stemailok' ],
model: 'Outputs'
}
};
};
Outputs.generemsg = async ( msg, header ) => {
/*
wait msg sent and return result sent
*/
// Recupere les parametre smtp du domainName à utiliser
console.log( 'pass Outputs.generemsg' )
try {
const confclientexpediteur = jsonfile.readFileSync( `${config.tribes}/${msg.tribeidperso.tribeidexpediteur}/clientconf.json` );
//console.log('expediteur', confclientexpediteur);
msg.smtp = confclientexpediteur.smtp;
/* const confclient = jsonfile.readFileSync(
`${config.tribes}/${msg.tribeidperso.tribeid}/clientconf.json`
);*/
} catch ( err ) {
console.log( 'la conf smtp du client n\'est pas definie' );
return {
status: 404,
payload: {
info: [ 'smtpnotdefined' ],
model: 'Outputs'
}
};
}
console.log( msg );
if( !msg.template.sendascontent && msg.template.htmlfile ) {
try {
msg.template.html = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache', 'utf-8' );
msg.template.text = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contenttxt.mustache', 'utf-8' );
} catch ( err ) {
console.log( 'WARNING, html file template missing ' + config.sharedData + '/' + msg.template.htmlfile );
console.log( err );
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles',
moreinfo: 'Template unavailable, check ' + config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache and contenttxt.mustache'
}
};
}
}
if( msg.template.html.length == 0 ) {
console.log( 'template.html est vide' )
return {
status: 404,
payload: {
info: [ 'ERRnotemplate' ],
model: 'Outputs',
moreinfo: 'No template email check '
}
};
}
// mustache any data into
// console.log(msg);
const msg2send = {};
msg2send.smtp = msg.smtp;
msg2send.from = msg.tribeidperso.from;
if( msg.tribeidperso.cc ) msg2send.cc = msg.tribeidperso.cc;
if( msg.tribeidperso.bcc ) msg2send.bcc = msg.tribeidperso.bcc;
if( msg.tribeidperso.replyTo ) msg2send.replyTo = msg.tribeidperso.replyTo;
msg2send.headers = {
'x-campaign-id': msg.tribeidperso.messageId,
'x-client-nd-id': msg.tribeidperso.tribeid,
'x-template-nd-id': msg.tribeidperso.templateId
};
// we get in datacust= {tribeidperso: with clientconf,destperso: personnalise data to send for email}
// console.log(msg);
console.log( 'nb de message to send:', msg.destperso.length );
//console.log(msg.destperso);
//msg.destperso.forEach(async (perso, pos) => {
let pos;
let pass1ermsg = false;
for( pos = 0; pos < msg.destperso.length; pos++ ) {
const datacust = {
tribeidperso: msg.tribeidperso,
destperso: msg.destperso[ pos ]
};
// Evaluation of each field if mustache exist
const datacusteval = {};
Object.keys( datacust.tribeidperso )
.forEach( k => {
if( typeof datacust.tribeidperso[ k ] === 'string' || datacust.tribeidperso[ k ] instanceof String ) {
datacusteval[ k ] = mustache.render( datacust.tribeidperso[ k ], datacust )
} else {
datacusteval[ k ] = datacust.tribeidperso[ k ];
}
} )
Object.keys( datacust.destperso )
.forEach( k => {
if( typeof datacust.destperso[ k ] === 'string' || datacust.destperso[ k ] instanceof String ) {
datacusteval[ k ] = mustache.render( datacust.destperso[ k ], datacust )
} else {
datacusteval[ k ] = datacust.destperso[ k ];
}
} )
msg2send.to = msg.destperso[ pos ].email;
console.log( 'msg2send.to ' + msg2send.to + ' pos:' + pos );
// console.log('avec datacusteval ', datacusteval)
msg2send.subject = mustache.render( msg.template.subject, datacusteval );
msg2send.text = mustache.render( msg.template.text, datacusteval );
msg2send.html = mustache.render( msg.template.html, datacusteval );
let nowait = true;
if( config.emailerurl == 'http://devapia.maildigit.fr:3015' ) {
fs.writeFileSync( 'devdata/tmp/test.html', msg2send.html, 'utf-8' );
console.log( 'lancement email sur dev, pour controler le mail générer voir ds ./config.js il faut changer config.emailerurl avec https://mail.maildigit.fr pour envoyer le mail ' )
return {
status: 200,
payload: {
info: [ 'msgsentok' ],
model: 'Outputs',
moreinfo: "parametrer sur emailer de dev et pas de production"
}
}
}
if( pos == 0 ) {
nowait = false;
/* we are waiting the first email was sent ok then we send all other
check NEEDDATA/OVH/workspace/emailerovh to send emailer with nodemailer and nodemailer-smtp-transport
*/
// console.log('envoie msg', msg);
//console.log(msg2send);
const ret = await Outputs.envoiemail( msg2send, nowait, 0 );
console.log( 'ret 1er msg', ret );
if( ret.status == 200 ) {
pass1ermsg = true;
};
} else if( pass1ermsg ) {
console.log( '###############################################' )
console.log( "envoie msg numero: " + pos + " email: " + msg2send.to )
//console.log(msg2send)
Outputs.envoiemail( msg2send, nowait, 0 );
/*Outputs.envoiemail(msg2send, nowait, 0).then(rep => {
console.log("envoie email" + pos)
}).catch(err => {
console.log(err);
});*/
};
};
if( pass1ermsg ) {
return {
status: 200,
payload: {
info: [ 'msgsentok' ],
model: 'Outputs'
}
};
} else {
return {
status: 500,
payload: {
info: [ 'msgsentko' ],
model: 'Ouputs',
moreinfo: "1er msg non envoyé car erreur"
}
}
}
};
Outputs.sendMailcampain = async ( msg, headersmsg ) => {
/*
Permet de lancer une campagne de mail personnalisé en mode web service
avec axios config.emailerurl https://mail qui peut être sur un autre serveur que celui en cours
Attention headermsg doit être retraduit avec les champs envoyé par un navigateur
Si le serveur en cours appelle cette fonction les champs du header doivent changer
voir config.js node_env .exposedHeaders
Pour un exemple de msg voir u exemple type de message envoyé dans un tribeid/domain/clientconf.json
avec l'envoi d'email
*/
//console.log(msg)
// On ajoute le contenu du template directement dans la demande
if( msg.template.sendascontent && msg.template.htmlfile ) {
try {
console.log( 'test', msg.template.sendascontent )
msg.template.html = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache', 'utf-8' );
msg.template.text = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contenttxt.mustache', 'utf-8' );
} catch ( err ) {
console.log( 'WARNING, html file template missing ' + config.sharedData + '/' + msg.template.htmlfile );
//console.log(err);
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles',
moreinfo: 'Template unavailable, check ' + config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache and contenttxt.mustache'
}
};
}
delete msg.template.htmlfile;
if( msg.template.html.length == 0 ) {
return {
status: 404,
payload: {
info: [ 'ERRnotemplate' ],
model: 'Outputs',
moreinfo: 'No template email check '
}
};
}
}
console.log( 'envoie sur', `${config.emailerurl}/outputs/msg` )
//console.log(msg)
// on check si les key de headermsg sont des key traduite via exposedHeaders
// (cas ou c'est l'application qui envoie un email)
if( headersmsg.xtribeid ) {
Object.keys( config.exposedHeaders )
.forEach( h => {
headersmsg[ h ] = headersmsg[ config.exposedHeaders[ h ] ];
delete headersmsg[ config.exposedHeaders[ h ] ];
} );
}
// on ajoute le code pour la signature
headersmsg.hashbody = msg.code;
console.log( 'header after traduction: ', headersmsg )
try {
const resCamp = await axios.post( `${config.emailerurl}/outputs/msg`, msg, {
headers: headersmsg
} );
//console.log('Etat:', resCamp);
console.log( 'Tried to send 1st email of the campain ' + msg.destperso[ 0 ].email );
// it just check the 1st email in destperso to return an answer if 1st is ok then all other are send in queue
if( resCamp ) {
return resCamp;
} else {
return { status: 200, payload: { info: [ 'CampainSent' ], model: 'Outputs' } };
}
} catch ( err ) {
// aios error handler
return { status: 401, payload: { info: [ 'erreuraxios' ], model: 'Outputs', moreinfo: err.message } }
}
};
Outputs.get = function ( filename, header ) {
// check file exist
const file = `${config.tribes}/${header.xworkon}/${filename}`;
// console.log('fichier demande ', file);
if( !fs.existsSync( file ) ) {
// console.log('le fichier demande n existe pas ', file);
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles'
}
};
} else {
console.log( 'envoie le fichier ', file );
return {
status: 200,
payload: {
info: [ 'fileknown' ],
model: 'UploadFiles',
file: file
}
};
}
};
Outputs.addjson = function ( data, header ) {
/*
Le header = {X-WorkOn:"",destinationfile:"", filename:""}
Le body = {jsonp:{},callback:function to launch after download,'code':'mot cle pour verifier que le fichier est à garder'}
*/
// console.log(req.body.jsonp);
try {
jsonfile.writeFileSync( header.destinationfile + '/' + header.filename, data.jsonp );
if( data.callback ) {
const execCB = require( `${config.mainDir}/models/tribeid/${header.xworkon
}` );
execCB[ data.callback ]();
}
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: header.destinationfile,
filename: header.filename
}
}
};
} catch ( err ) {
console.log( 'Impossible de sauvegarder le fichier, A COMPRENDRE', err );
return {
status: 503,
payload: {
info: [ 'savingError' ],
model: 'UploadFiles'
}
};
}
};
Outputs.add = function ( req, header ) {
const form = new formidable.IncomingForm();
console.log( 'req.headers', req.headers );
console.log( 'req.params', req.params );
console.log( 'req.query', req.query );
console.log( 'req.body', req.body );
let destinationfile = `${config.tribes}/${header.xworkon}/${header.destinationfile
}`;
form.parse( req, function ( err, fields, files ) {
console.log( 'files', files.file.path );
console.log( 'fields', fields );
const oldpath = files.file.path;
destinationfile += '/' + files.file.name;
console.log( 'oldpath', oldpath );
console.log( 'destinationfile', destinationfile );
fs.copyFile( oldpath, destinationfile, function ( err ) {
if( err ) {
console.log( err );
return {
status: 500,
payload: {
info: [ 'savingError' ],
model: 'UploadFiles'
}
};
} else {
console.log( 'passe' );
fs.unlink( oldpath );
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: destinationfile
}
}
};
}
} );
} );
};
Outputs.generepdf = ( req, header ) => {
return new Promise( ( resolve, reject ) => {
let options = {
format: "A4",
orientation: "portrait",
border: "10mm",
footer: {
height: "20mm",
contents: {
default: '<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', // html pagination if edit needed
}
}
};
let document = {
html: req.html,
data: {
data: req.data,
},
path: `${config.tribes}/${header.xtribe}/outputs/${UUID.v4()}.pdf`,
type: "",
};
pdf // generate pdf
.create( document, options )
.then( ( res ) => {
resolve( {
status: 200,
payload: {
info: [ 'wellPdfGenerated' ],
model: 'Outputs',
data: {
path: document.path,
filename: req.data.filename
},
render: {
filename: req.data.filename
}
}
} );
} )
.catch( ( err ) => {
reject( {
status: 500,
payload: {
info: [ 'pdfGenerationError' ],
model: 'Outputs',
error: err
}
} );
} );
} );
};
module.exports = Outputs;

492
models/OutputsDev.js Executable file
View File

@ -0,0 +1,492 @@
const fs = require( 'fs-extra' );
const path = require( 'path' );
const formidable = require( 'formidable' );
const jsonfile = require( 'jsonfile' );
const mustache = require( 'mustache' );
const moment = require( 'moment' );
const UUID = require( 'uuid' );
const pdf = require( "pdf-creator-node" );
const nodemailer = require( 'nodemailer' );
const smtpTransport = require( 'nodemailer-smtp-transport' );
const axios = require( 'axios' );
const { GoogleSpreadsheet } = require( 'google-spreadsheet' );
const async = require( 'async' );
const config = require( '../tribes/townconf.js' );
const checkdata = require( `${config.tribes}/${config.mayorId}/www/cdn/public/js/checkdata` );
const Outputs = {};
const sleep = ( milliseconds = 500 ) => new Promise( resolve => setTimeout( resolve, milliseconds ) );
/*
Process any data to a specific output:
emailer => generate a finale text file to send
csv => export json file to csv data
pdf => generate a customized document
*/
Outputs.envoiemail = ( msg, next ) => {
let transporter = nodemailer.createTransport( msg.smtp );
transporter.sendMail( msg, async ( err, info ) => {
if( err ) {
next( err );
} else {
console.log( 'info', info )
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_success.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - Success after ' + '0' + ' tries to ' + info.accepted.join( ',' ) + '\n', 'utf-8' );
next( null );
}
} );
};
Outputs.setupmail = ( msg, msg2send, index ) => {
const datacust = {
tribeidperso: msg.tribeidperso,
destperso: index
};
// Evaluation of each field if mustache exist
const datacusteval = {};
Object.keys( datacust.tribeidperso )
.forEach( k => {
if( typeof datacust.tribeidperso[ k ] === 'string' || datacust.tribeidperso[ k ] instanceof String ) {
datacusteval[ k ] = mustache.render( datacust.tribeidperso[ k ], datacust )
} else {
datacusteval[ k ] = datacust.tribeidperso[ k ];
}
} )
Object.keys( datacust.destperso )
.forEach( k => {
if( typeof datacust.destperso[ k ] === 'string' || datacust.destperso[ k ] instanceof String ) {
datacusteval[ k ] = mustache.render( datacust.destperso[ k ], datacust )
} else {
datacusteval[ k ] = datacust.destperso[ k ];
}
} )
msg2send.to = index.email;
console.log( 'msg2send.to ' + msg2send.to );
msg2send.subject = mustache.render( msg.template.subject, datacusteval );
msg2send.text = mustache.render( msg.template.text, datacusteval );
msg2send.html = mustache.render( msg.template.html, datacusteval );
// TODO need to move that in generemsg
// if (config.emailerurl == 'http://devapia.maildigit.fr:3015') {
// fs.writeFileSync('devdata/tmp/test.html', msg2send.html, 'utf-8');
// console.log('lancement email sur dev, pour controler le mail générer voir ds ./config.js il faut changer config.emailerurl avec https://mail.maildigit.fr pour envoyer le mail ')
// return {
// status: 200,
// payload: {
// info: ['msgsentok'],
// model: 'Outputs',
// moreinfo: "parametrer sur emailer de dev et pas de production"
// }
// }
// }
return msg2send;
}
Outputs.envoiefirstmail = async ( msg ) => {
console.log( '###############################################' )
console.log( "envoie first msg email: " + msg.to )
let transporter = nodemailer.createTransport( msg.smtp );
console.log( 'attente 1er msg avant d envoyer les autres' );
const transport = await transporter.verify();
console.log( 'transport', transport );
if( transport.error ) {
console.log( 'Probleme de smtp', error );
return {
status: 500,
payload: {
info: ''
}
};
} else {
let rep = await transporter.sendMail( msg );
console.log( 'rep sendMail', rep );
if( rep.accepted && rep.accepted.length > 0 && rep.rejected.length == 0 ) {
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_success.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - Success after waiting 1st email to send ' + msg.to + '\n', 'utf-8' );
return {
status: '200',
payload: {
info: [ 'send1stemailok' ],
model: 'Outputs'
}
};
}
// status à tester si necessaire de detecter une erreur
// if (rep.rejectedErrors) {
fs.appendFileSync( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_error.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - err after waiting result\n ' + msg.to, 'utf-8' );
return {
status: '500',
payload: {
info: [ 'rejectedError' ],
model: 'Outputs'
}
};
}
}
Outputs.envoiemails = ( msg, msg2send, targets, iteration, resolve, reject ) => {
let newtargets = [];
async.each( targets, ( index, callback ) => { // iterate asynchronously in msg.destperso (targets)
let finalmsg = Outputs.setupmail( msg, msg2send, index );
console.log( '###############################################' )
console.log( "envoie msg email: " + finalmsg.to )
Outputs.envoiemail( finalmsg, ( err ) => {
if( err ) { // intentionally don't pass this error in callback, we dont want to break loop
newtargets.push( index ); // stock all errored mails for next try
}
} );
callback();
}, function ( err ) { // part executed only once all iterations are done
if( newtargets.length > 0 && iteration < 4 ) {
setTimeout( () => {
Outputs.envoiemails( msg, msg2send, newtargets, iteration + 1, resolve, reject );
}, 600000 );
} else resolve( newtargets ); // return not resolved errors after 4 trys for log
} );
}
Outputs.generemsg = async ( msg, header ) => {
/*
wait msg sent and return result sent
*/
// Recupere les parametre smtp du domainName à utiliser
console.log( 'pass Outputs.generemsg' )
try {
const confclientexpediteur = jsonfile.readFileSync( `${config.tribes}/${msg.tribeidperso.tribeidexpediteur}/clientconf.json` );
msg.smtp = confclientexpediteur.smtp;
} catch ( err ) {
console.log( 'la conf smtp du client n\'est pas definie' );
return {
status: 404,
payload: {
info: [ 'smtpnotdefined' ],
model: 'Outputs'
}
};
}
console.log( msg );
if( !msg.template.sendascontent && msg.template.htmlfile ) {
try {
msg.template.html = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache', 'utf-8' );
msg.template.text = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contenttxt.mustache', 'utf-8' );
} catch ( err ) {
console.log( 'WARNING, html file template missing ' + config.sharedData + '/' + msg.template.htmlfile );
console.log( err );
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles',
moreinfo: 'Template unavailable, check ' + config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache and contenttxt.mustache'
}
};
}
}
if( msg.template.html.length == 0 ) {
console.log( 'template.html est vide' )
return {
status: 404,
payload: {
info: [ 'ERRnotemplate' ],
model: 'Outputs',
moreinfo: 'No template email check '
}
};
}
// mustache any data into
const msg2send = {};
msg2send.smtp = msg.smtp;
msg2send.from = msg.tribeidperso.from;
if( msg.tribeidperso.cc ) msg2send.cc = msg.tribeidperso.cc;
if( msg.tribeidperso.bcc ) msg2send.bcc = msg.tribeidperso.bcc;
if( msg.tribeidperso.replyTo ) msg2send.replyTo = msg.tribeidperso.replyTo;
msg2send.headers = {
'x-campaign-id': msg.tribeidperso.messageId,
'x-client-nd-id': msg.tribeidperso.tribeid,
'x-template-nd-id': msg.tribeidperso.templateId
};
console.log( 'nb de message to send:', msg.destperso.length );
// send first mail
const ret = await Outputs.envoiefirstmail( Outputs.setupmail( msg, msg2send, msg.destperso[ 0 ] ) );
console.log( 'ret 1er msg', ret );
if( ret.status == 200 ) {
pass1ermsg = true;
msg.destperso.shift();
};
console.log( 'attente 1er msg avant d envoyer les autres' );
// send other mails
new Promise( ( resolve, reject ) => { // useless promise used for recursive calls in Outputs.envoiemails
Outputs.envoiemails( msg, msg2send, msg.destperso, 0, resolve, reject );
} )
.then( ( result ) => {
async.eachSeries( result, ( index, callback ) => { // variante of each but runs only a single operation at a time because of fs.appendFile
fs.appendFile( `${config.tribes}/${msg.headers['x-client-nd-id']}/logs/${msg.headers['x-campaign-id']}_error.txt`, moment( new Date() )
.format( 'YYYYMMDD HH:mm:ss' ) + ' - err after 4 tries to ' + info.rejected.join( ',' ) + '\n', 'utf-8', ( err ) => {
callback( err );
}, ( err ) => {
if( err ) console.log( err );
} );
console.log( 'msg.to not well sent', msg.to );
} );
} )
if( pass1ermsg ) {
return {
status: 200,
payload: {
info: [ 'msgsentok' ],
model: 'Outputs'
}
};
} else {
return {
status: 500,
payload: {
info: [ 'msgsentko' ],
model: 'Ouputs',
moreinfo: "1er msg non envoyé car erreur"
}
}
}
};
Outputs.sendMailcampain = async ( msg, headersmsg ) => {
/*
Permet de lancer une campagne de mail personnalisé en mode web service
avec axios config.emailerurl https://mail qui peut être sur un autre serveur que celui en cours
Attention headermsg doit être retraduit avec les champs envoyé par un navigateur
Si le serveur en cours appelle cette fonction les champs du header doivent changer
voir config.js node_env .exposedHeaders
Pour un exemple de msg voir u exemple type de message envoyé dans un tribeid/domain/clientconf.json
avec l'envoi d'email
*/
//console.log(msg)
// On ajoute le contenu du template directement dans la demande
if( msg.template.sendascontent && msg.template.htmlfile ) {
try {
console.log( 'test', msg.template.sendascontent )
msg.template.html = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache', 'utf-8' );
msg.template.text = fs.readFileSync( config.sharedData + '/' + msg.template.htmlfile + '/contenttxt.mustache', 'utf-8' );
} catch ( err ) {
console.log( 'WARNING, html file template missing ' + config.sharedData + '/' + msg.template.htmlfile );
//console.log(err);
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles',
moreinfo: 'Template unavailable, check ' + config.sharedData + '/' + msg.template.htmlfile + '/contentinline.mustache and contenttxt.mustache'
}
};
}
delete msg.template.htmlfile;
if( msg.template.html.length == 0 ) {
return {
status: 404,
payload: {
info: [ 'ERRnotemplate' ],
model: 'Outputs',
moreinfo: 'No template email check '
}
};
}
}
console.log( 'envoie sur', `${config.emailerurl}/outputs/msg` )
//console.log(msg)
// on check si les key de headermsg sont des key traduite via exposedHeaders
// (cas ou c'est l'application qui envoie un email)
if( headersmsg.xtribeid ) {
Object.keys( config.exposedHeaders )
.forEach( h => {
headersmsg[ h ] = headersmsg[ config.exposedHeaders[ h ] ];
delete headersmsg[ config.exposedHeaders[ h ] ];
} );
}
// on ajoute le code pour la signature
headersmsg.hashbody = msg.code;
console.log( 'header after traduction: ', headersmsg )
try {
const resCamp = await axios.post( `${config.emailerurl}/outputs/msg`, msg, {
headers: headersmsg
} );
//console.log('Etat:', resCamp);
console.log( 'Tried to send 1st email of the campain ' + msg.destperso[ 0 ].email );
// it just check the 1st email in destperso to return an answer if 1st is ok then all other are send in queue
if( resCamp ) {
return resCamp;
} else {
return { status: 200, payload: { info: [ 'CampainSent' ], model: 'Outputs' } };
}
} catch ( err ) {
// aios error handler
return { status: 401, payload: { info: [ 'erreuraxios' ], model: 'Outputs', moreinfo: err.message } }
}
};
Outputs.get = function ( filename, header ) {
// check file exist
const file = `${config.tribes}/${header.xworkon}/${filename}`;
// console.log('fichier demande ', file);
if( !fs.existsSync( file ) ) {
// console.log('le fichier demande n existe pas ', file);
return {
status: 404,
payload: {
info: [ 'fileUnknown' ],
model: 'UploadFiles'
}
};
} else {
console.log( 'envoie le fichier ', file );
return {
status: 200,
payload: {
info: [ 'fileknown' ],
model: 'UploadFiles',
file: file
}
};
}
};
Outputs.addjson = function ( data, header ) {
/*
Le header = {X-WorkOn:"",destinationfile:"", filename:""}
Le body = {jsonp:{},callback:function to launch after download,'code':'mot cle pour verifier que le fichier est à garder'}
*/
// console.log(req.body.jsonp);
try {
jsonfile.writeFileSync( header.destinationfile + '/' + header.filename, data.jsonp );
if( data.callback ) {
const execCB = require( `${config.mainDir}/models/tribeid/${header.xworkon
}` );
execCB[ data.callback ]();
}
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: header.destinationfile,
filename: header.filename
}
}
};
} catch ( err ) {
console.log( 'Impossible de sauvegarder le fichier, A COMPRENDRE', err );
return {
status: 503,
payload: {
info: [ 'savingError' ],
model: 'UploadFiles'
}
};
}
};
Outputs.add = function ( req, header ) {
const form = new formidable.IncomingForm();
console.log( 'req.headers', req.headers );
console.log( 'req.params', req.params );
console.log( 'req.query', req.query );
console.log( 'req.body', req.body );
let destinationfile = `${config.tribes}/${header.xworkon}/${header.destinationfile
}`;
form.parse( req, function ( err, fields, files ) {
console.log( 'files', files.file.path );
console.log( 'fields', fields );
const oldpath = files.file.path;
destinationfile += '/' + files.file.name;
console.log( 'oldpath', oldpath );
console.log( 'destinationfile', destinationfile );
fs.copyFile( oldpath, destinationfile, function ( err ) {
if( err ) {
console.log( err );
return {
status: 500,
payload: {
info: [ 'savingError' ],
model: 'UploadFiles'
}
};
} else {
console.log( 'passe' );
fs.unlink( oldpath );
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: destinationfile
}
}
};
}
} );
} );
};
Outputs.sheettojson = async ( req, header ) => {
doc = new GoogleSpreadsheet( req.productIdSpreadsheet );
await doc.useServiceAccountAuth( req.googleDriveCredentials );
await doc.loadInfo();
let result = [];
for( const sheetName of req.sheets ) {
console.log( 'loading: ', sheetName );
sheet = doc.sheetsByTitle[ sheetName ]
await sheet.loadHeaderRow();
const records = await sheet.getRows( { offset: 1 } )
records.forEach( record => {
let offer = {}
for( let index = 0; index < record._sheet.headerValues.length; index++ ) {
offer[ record._sheet.headerValues[ index ] ] = record[ record._sheet.headerValues[ index ] ];
}
result.push( offer );
} );
}
return result;
};
Outputs.generepdf = ( req, header ) => {
return new Promise( ( resolve, reject ) => {
let options = {
format: "A4",
orientation: "portrait",
border: "10mm",
footer: {
height: "20mm",
contents: {
default: '<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', // html pagination if edit needed
}
}
};
let document = {
html: req.html,
data: {
data: req.data,
},
path: `${config.tribes}/${header.xtribeid}/outputs/${UUID.v4()}.pdf`,
type: "",
};
pdf // generate pdf
.create( document, options )
.then( ( res ) => {
resolve( {
status: 200,
payload: {
info: [ 'wellPdfGenerated' ],
model: 'Outputs',
data: {
path: document.path,
filename: req.data.filename
},
render: {
filename: req.data.filename
}
}
} );
} )
.catch( ( err ) => {
reject( {
status: 500,
payload: {
info: [ 'pdfGenerationError' ],
model: 'Outputs',
error: err
}
} );
} );
} );
};
module.exports = Outputs;

773
models/Pagans.js Executable file
View File

@ -0,0 +1,773 @@
const bcrypt = require( 'bcrypt' );
const fs = require( 'fs-extra' );
const jsonfile = require( 'jsonfile' );
const glob = require( 'glob' );
const moment = require( 'moment' );
const jwt = require( 'jwt-simple' );
const UUID = require( 'uuid' );
const Outputs = require( './Outputs.js' );
const config = require( '../tribes/townconf.js' );
const checkdata = require( `${config.tribes}/${config.mayorId}/www/cdn/public/js/checkdata` );
/*
Gestion des utilisateurs connecte
/tribes/tribeid/users/
UUID.json
/searchindex/indexTOKEN = {TOKEN:UUID}
to check authentification
indexLOGIN = {LOGIN:UUID}
to check if LOGIN exist
Used for referntial:
To update
Global: datashared/referentials/dataManagement/object/users.json
Local: data/tribe/*${header.workOn}/referentials/dataManagement/object/users.json
To use after a server rerun
data/tribe/*${header.workOn}/referentials/${header.langue}/object/users.json
Faire comme contact charger les index au lancement
et changer la logique de user/id/profil o
On initialise les objets au lancement du serveur
this.uids[u.UUID] = [u.LOGIN, u.EMAIL, u.password, u.profil, u.TOKEN];
this.logins[u.LOGIN] = u.UUID;
this.tokens[u.UUID] = u.TOKEN;
on stocke /domain/tribeid/users/logins.json et /uids.json
on stocke /domain/tribeids.json et tokkens.json
On retourne l'objet {tribeids:[list tribeid], tokens:{UUID:TOKEN}}
*/
const Pagans = {};
Pagans.init = tribeids => {
console.group( 'init Pagans logins, tokens ...' );
// store globaly tokens
const tokens = {};
const emailsglob = {};
const loginsglob = {};
// For each tribeid create series of indexes
//console.log(tribeids);
tribeids.forEach( tribeid => {
// Reset for each domain
const uids = {};
const logins = {};
const emails = {};
//check folder exist
/*if( !fs.existsSync( `${config.tribes}/${tribeid}/users` ) ) {
fs.mkdirSync( `${config.tribes}/${tribeid}/users` )
}
if( !fs.existsSync( `${config.tribes}/${tribeid}/users/searchindex` ) ) {
fs.mkdirSync( `${config.tribes}/${tribeid}/users/searchindex` )
}*/
glob.sync( `${config.tribes}/${tribeid}/users/*.json` )
.forEach( file => {
//console.log( file );
const u = fs.readJsonSync( file, 'utf-8' );
if( !u.TOKEN ) {
u.TOKEN = '';
}
//console.log( u )
uids[ u.UUID ] = [ u.LOGIN, u.EMAIL, u.PASSWORD, u.ACCESSRIGHTS, u.TOKEN ];
logins[ u.LOGIN ] = u.UUID;
loginsglob[ u.LOGIN ] = tribeid;
// On ne charge que les TOKEN valide
let decodedTOKEN = {};
if( u.TOKEN != '' ) {
try {
decodedTOKEN = jwt.decode( u.TOKEN, config.jwtSecret );
//console.log( 'decodeTOKEN', decodedTOKEN )
if( moment( decodedTOKEN.expiration ) > moment() ) {
tokens[ u.UUID ] = { TOKEN: u.TOKEN, ACCESSRIGHTS: u.ACCESSRIGHTS };
//console.log( `add token valid for ${u.UUID}:`, tokens[ u.UUID ] )
}
} catch ( err ) {
console.log( 'pb de TOKEN impossible a decoder' + u.TOKEN, err );
}
}
if( u.EMAIL ) {
emails[ u.EMAIL ] = u.UUID;
emailsglob[ u.EMAIL ] = tribeid;
}
} );
// UIDS INDEX BY DOMAIN ={UUID:[LOGIN,EMAIL,psw,accessRight,TOKEN]}
fs.outputJson( `${config.tribes}/${tribeid}/users/searchindex/uids.json`, uids, {
spaces: 2
}, err => {
if( err ) throw err;
} );
// LOGINS INDEX BY DOMAIN ={LOGIN:UUID}
fs.outputJson( `${config.tribes}/${tribeid}/users/searchindex/logins.json`, logins, {
spaces: 2
}, err => {
if( err ) throw err;
} );
// EMAILS INDEX BY DOMAIN ={EMAIL:UUID}
fs.outputJson( `${config.tribes}/${tribeid}/users/searchindex/emails.json`, emails, {
spaces: 2
}, err => {
if( err ) throw err;
} );
} );
// EMAILS et appartenance domain
fs.outputJson( `${config.tmp}/emailsglob.json`, emailsglob, {
spaces: 2
}, err => {
if( err ) throw err;
} );
// logins et appartenance domain (permet de retrouver tribeid pour un LOGIN)
fs.outputJson( `${config.tmp}/loginsglob.json`, loginsglob, {
spaces: 2
}, err => {
if( err ) throw err;
} );
// TOKENS GLOBAL INDEX Centralise tous les TOKEN pour plus de rapidité
// {UUID:TOKEN}
fs.outputJson( `${config.tmp}/tokens.json`, tokens, {
spaces: 2
}, err => {
if( err ) throw err;
} );
console.groupEnd();
return { tokens };
};
/**
* Update indexes: logins, uids and tokens
* @param {object} user User object
* @param {string} tribeid - The client identifier
* @rm {boolean} false => update, true => remove
*/
Pagans.updateDatabase = ( user, tribeid, rm = false ) => {
console.group( `Pagans.updateDatabase for ${tribeid} with user ${user.LOGIN}` )
console.assert( config.loglevel == "quiet", 'user', user );
const loginsIndex = `${config.tribes}/${tribeid}/users/searchindex/logins.json`;
jsonfile.readFile( loginsIndex, function ( err, logins ) {
console.assert( config.loglevel == "quiet", 'logins', logins );
try {
if( rm ) {
delete logins[ user.LOGIN ];
} else {
logins[ user.LOGIN ] = user.UUID;
}
jsonfile.writeFile( loginsIndex, logins, {
spaces: 2
}, err => {
if( err ) console.log( err );
} );
} catch ( err ) {
console.log( 'Gros pb de mise à jour Pagans.updateDatabase conflit des logins' );
}
} );
const uidsIndex = `${config.tribes}/${tribeid}/users/searchindex/uids.json`;
jsonfile.readFile( uidsIndex, function ( err, uids ) {
try {
if( rm ) {
delete uids[ user.UUID ];
} else {
uids[ user.UUID ] = [
user.LOGIN,
user.EMAIL,
user.PASSWORD,
user.ACCESSRIGHTS,
user.TOKEN
];
}
jsonfile.writeFile( uidsIndex, uids, {
spaces: 2
}, err => {
if( err ) console.log( err );
} );
} catch ( err ) {
console.log( 'Gros pb de mise à jour Pagans.updateDatabase conflit des uids si ce reproduit passer en mode sync bloquant' );
}
} );
const emailsIndex = `${config.tribes}/${tribeid}/users/searchindex/emails.json`;
jsonfile.readFile( emailsIndex, function ( err, emails ) {
console.assert( config.loglevel == "quiet", 'emailss', emails );
try {
if( rm ) {
delete emails[ user.EMAIL ];
} else {
emails[ user.EMAIL ] = user.UUID;
}
jsonfile.writeFile( emailsIndex, emails, {
spaces: 2
}, err => {
if( err ) console.log( err );
} );
} catch ( err ) {
console.log( 'Gros pb de mise à jour Pagans.updateDatabase conflit des emails' );
}
} );
const tokensIndex = `${config.tmp}/tokens.json`;
let tokens = {};
try {
tokens = jsonfile.readFileSync( tokensIndex );
} catch ( err ) {
console.log( 'tokens.json not available' )
}
// tokens[user.UUID] = user.TOKEN;
tokens[ user.UUID ] = { TOKEN: user.TOKEN, ACCESSRIGHTS: user.ACCESSRIGHTS };
jsonfile.writeFileSync( tokensIndex, tokens, {
spaces: 2
} );
/*
jsonfile.readFile(tokensIndex, function(err, tokens) {
tokens[user.UUID] = user.TOKEN;
jsonfile.writeFile(tokensIndex, tokens, { spaces: 2 }, err => {
if (err) console.log(err);
});
});*/
console.groupEnd();
};
/**
* Read the profil.json of an user
* @param {[type]} UUID ID of the user
* @param {[type]} tribeid tribeid
* @return {Promise} - Return a promise resolved with user data or rejected with an error
*/
// A S U P P R I M E R utiliser getinfousers pour recuperer des indexs de users
// en créer d'autres si necessaire
Pagans.getUserlist = ( header, filter, field ) => {
console.group( `getUserlist filter with filter ${filter} to get ${field}` );
/* const getuser = Pagans.getUser(header.xuuid, header.xtribeid);
if (getuser.status != 200)
return { status: getuser.status, data: getuser.payload };
const user = getuser.payload.data;
// console.log(user);
// check if update accessright allowed
// choose the level depending of ownby xuuid
let accessright = user.objectRights[header.xtribeid].users[0];
if (user.ownby.includes(header.xtribeid)) {
accessright = user.objectRights[header.xtribeid].users[1];
}
// Check update is possible at least user itself ownby itself
console.log(accessright);
console.log(accessright & 4);
if ((accessright & 4) != 4) {
return {
status: 403,
data: { info: ['forbiddenAccess'], model: 'Pagans' }
};
}
*/
const Userlist = [];
glob.sync( `${config.tribes}/${header.xworkon}/users/*/profil.json` )
.forEach( f => {
const infouser = jsonfile.readFileSync( f );
// Ajouter filter et liste de field
if( filter != 'all' ) {
// decode filter et test
}
const info = {};
field.split( '______' )
.forEach( k => {
if( ![ 'password' ].includes( k ) ) info[ k ] = infouser[ k ];
} );
Userlist.push( info );
} );
// console.log('userlist', Userlist);
console.groupEnd();
return {
status: 200,
data: {
data: Userlist
}
};
};
Pagans.getinfoPagans = ( tribeid, accessrights, listindex ) => {
const info = {}
const object = 'users';
const indexs = listindex.split( '_' )
let access = true;
indexs.forEach( index => {
access = !( [ 'emails', 'logins', 'uids' ].includes( index ) && !( accessrights.data[ object ] && accessrights.data[ object ].includes( 'R' ) ) );
if( access && fs.existsSync( `${config.tribes}/${tribeid}/${object}/searchindex/${index}.json` ) ) {
info[ index ] = jsonfile.readFileSync( `${config.tribes}/${tribeid}/${object}/searchindex/${index}.json` )
}
} )
console.log( info )
return { status: 200, data: { info: info } }
}
Pagans.getUser = ( UUID, tribeid, accessrights ) => {
console.assert( config.loglevel == "quiet", `getUser on ${UUID} for ${tribeid} with ${JSON.stringify(accessrights)}` );
if( !fs.existsSync( `${config.tribes}/${tribeid}/users/${UUID}.json` ) ) {
return {
status: 404,
data: {
info: [ 'useridNotfound' ],
model: 'Pagans',
render: {
UUID,
tribeid
}
}
}
}
const user = jsonfile.readFileSync( `${config.tribes}/${tribeid}/users/${UUID}.json` );
let access = true;
//console.log("test accessrights.data['users'].includes('R')", accessrights.data['users'].includes('R'))
console.assert( config.loglevel == "quiet", 'accessrights', accessrights )
access = accessrights.users && ( accessrights.users.includes( 'R' ) || ( accessrights.users.includes( 'O' ) && user.OWNEDBY.includes( UUID ) ) );
if( access ) {
return {
status: 200,
data: {
user: user,
model: "User",
info: [ "Authorizedtogetuserinfo" ]
}
};
}
return {
status: 403,
data: {
info: [ 'Forbidden' ],
model: 'Pagans',
render: {
UUID,
tribeid
}
}
};
};
Pagans.getUserIdFromEMAIL = ( tribeid, EMAIL ) => {
if( !checkdata.test.EMAIL( EMAIL ) ) {
return {
status: 400,
data: {
info: [ 'ERREMAIL' ],
model: 'Pagans'
}
};
}
const emailsIndex = jsonfile.readFileSync( `${config.tribes}/${tribeid}/users/searchindex/emails.json` );
if( !emailsIndex.hasOwnProperty( EMAIL ) ) {
return {
status: 404,
data: {
info: [ 'userEMAILNotfound' ],
model: 'Pagans'
}
};
}
return emailsIndex[ EMAIL ];
};
Pagans.updateUserpassword = ( UUID, header, data ) => {
const getUser = Pagans.getUser( UUID, header.xtribeid, { users: 'W' } );
if( getUser.status == 200 ) {
const user = getUser.data.user;
// console.log('user exist', user);
const match = bcrypt.compareSync( data.password, user.PASSWORD );
if( !match ) {
return {
status: 401,
data: {
info: [ 'checkCredentials' ],
model: 'Pagans'
}
};
}
// console.log('Credentials are matching!');
if( checkdata.test.password( {}, data.pswnew ) ) {
user.PASSWORD = bcrypt.hashSync( data.pswnew, config.saltRounds );
jsonfile.writeFileSync( `${config.tribes}/${header.xworkon}/users/${UUID}.json`, user, {
spaces: 2
} );
Pagans.updateDatabase( user, header.xworkon, false );
return {
status: 200,
data: {
info: [ 'successfulUpdate' ],
model: 'Pagans'
}
};
} else {
return {
status: 401,
data: {
info: [ 'pswTooSimple' ],
model: 'Pagans'
}
};
}
}
};
Pagans.createUser = ( header, data ) => {
/*
@input data={PUBKEY,EMAIL,LOGIN,UUID} check and create for header xworkon a user with generic password
*/
console.log( 'createUser on header.xworkon:' + header.xworkon + ' by user:' + header.xpaganid );
console.assert( config.loglevel == "quiet", 'with data:', data );
const ref = jsonfile.readFileSync( `${config.tribes}/${header.xworkon}/referentials/${header.xlang}/object/users.json` );
const logins = jsonfile.readFileSync( `${config.tribes}/${header.xworkon}/users/searchindex/logins.json` );
const LOGIN = Object.keys( logins );
console.assert( config.loglevel == "quiet", 'LOGIN list', LOGIN );
const emails = jsonfile.readFileSync( `${config.tribes}/${header.xworkon}/users/searchindex/emails.json` );
console.assert( config.loglevel == "quiet", 'emails', emails );
const EMAIL = Object.keys( emails );
console.assert( config.loglevel == "quiet", 'EMAIL list', EMAIL );
// list.UUID est forcement unique car on est en update et pas en create
if( !data.UUID ) data.UUID = UUID.v4();
// pour la logique de checkdata il faut passer le parametre
const check = checkdata.evaluate( {
list: {
LOGIN,
EMAIL,
UUID: []
}
}, ref, data );
console.assert( config.loglevel == "quiet", 'check & clean data before update ', check );
if( check.invalidefor.length > 0 ) {
return {
status: 403,
data: {
model: 'Pagans',
info: check.invalidefor
}
};
}
const clientConfig = jsonfile.readFileSync( `${config.tribes}/${header.xworkon}/clientconf.json` );
const user = check.data;
user.DATE_CREATE = new Date()
.toISOString();
user.PASSWORD = bcrypt.hashSync( clientConfig.genericpsw, config.saltRounds );
user.OWNBY = [ data.UUID ];
user.OBJECT = 'users';
// set le minimum de droit sur l'objet users dont il est le Owner
if( data.ACCESSRIGHTS ) {
user.ACCESSRIGHTS = data.ACCESSRIGHTS
} else {
user.ACCESSRIGHTS = { app: {}, data: {} };
}
user.ACCESSRIGHTS.data[ header.xworkon ] = { users: "O" };
jsonfile.writeFileSync( `${config.tribes}/${header.xworkon}/users/${user.UUID}.json`, user, {
spaces: 2
} );
Pagans.updateDatabase( user, header.xworkon, false );
return {
status: 200,
data: {
uuid: user.UUID,
info: [ 'successfulCreate' ],
model: 'Pagans'
}
};
};
Pagans.updateUser = ( UUID, header, data ) => {
console.log( 'updateUser UUID:' + UUID + ' on header.xworkon:' + header.xworkon + ' by user' + header.xpaganid );
// console.log('header', header);
console.assert( config.loglevel == "quiet", 'with data', data );
const getuser = Pagans.getUser( UUID, header.xworkon, { users: 'R' } );
if( getuser.status != 200 ) return {
status: getuser.status,
data: getuser.data.user
};
const user = getuser.data.user;
/*
let userconnected = user;
if (UUID != header.xuuid) {
// mean connected user want to change other user
const getuserconnected = Pagans.getUser(header.xuuid, header.xtribeid);
userconnected = getuserconnected.payload.data;
}
console.log('user to update', user);
console.log('user connected that request update', userconnected);
// check if update accessright allowed
// choose the level depending of ownby xuuid
let accessright = userconnected.objectRights[header.xworkon].users[0];
if (user.ownby.includes(header.xuuid)) {
accessright = userconnected.objectRights[header.xworkon].users[1];
}
// Check update is possible at least user itself ownby itself
console.log(accessright);
console.log(accessright & 2);
if ((accessright & 2) != 2) {
return {
status: 403,
data: { info: ['forbiddenAccess'], model: 'Pagans' }
};
}
*/
const ref = jsonfile.readFileSync( `${config.tribes}/${header.xworkon}/referentials/object/users_${
header.xlang
}.json` );
const logins = jsonfile.readFileSync( `${config.tribes}/${header.xworkon}/users/searchindex/logins.json` );
const LOGIN = Object.keys( logins )
.filter( l => logins[ l ] != user.UUID );
// console.log( 'LOGIN list', LOGIN );
const emails = jsonfile.readFileSync( `${config.tribes}/${header.xworkon}/users/searchindex/emails.json` );
// console.log( 'emails', emails );
const EMAIL = Object.keys( emails )
.filter( e => emails[ e ] != user.UUID );
// console.log( 'EMAIL list', EMAIL );
// list.UUID est forcement unique car on est en update et pas en create
// pour la logique de checkdata il faut passer le parametre
const check = checkdata.evaluate( {
profil: user[ 'apps' + header.xworkon + 'profil' ],
list: {
LOGIN,
EMAIL,
UUID: []
}
}, ref, data );
if( check.invalidefor.length > 0 ) {
return {
status: 403,
data: {
model: 'Pagans',
info: check.invalidefor,
}
};
}
data = check.data;
let saveuser = false;
let updateDatabase = false;
Object.keys( data )
.forEach( k => {
//console.log( user[ k ] )
//console.log( data[ k ] )
//console.log( '---' )
if( user[ k ] != data[ k ] ) {
user[ k ] = data[ k ];
saveuser = true;
if( [ 'TOKEN', 'LOGIN', 'EMAIL' ].includes( k ) ) updateDatabase = true;
// attention si LOGIN ou EMAIL change il faut checker qu il n existe pas dejà risque de conflit
}
} );
if( saveuser ) {
//console.log( 'mise à jour user profile.json' );
if( data.TOKEN ) {
user.date_lastLOGIN = new Date()
.toISOString();
} else {
user.date_update = new Date()
.toISOString();
}
try {
jsonfile.writeFileSync( `${config.tribes}/${header.xworkon}/users/${UUID}.json`, user, {
spaces: 2
} );
//console.log( 'declenche updatabase', updateDatabase )
if( updateDatabase ) {
// mean index have to be updated
Pagans.updateDatabase( user, header.xworkon, false );
console.assert( config.loglevel == "quiet", 'MISE A JOUR DU TOKEN ou de l\'EMAIL ou du LOGIN' );
}
} catch ( err ) {
console.log( 'ERRRRR need to understand update impossible of user: ' + UUID + ' in domain:' + header.xworkon + ' from user ' + header.xpaganid + ' of domain:' + header.xtribe );
console.log( 'with data :', data );
return {
status: 400,
data: {
info: [ 'failtoWritefs' ],
model: 'Pagans'
}
};
}
}
return {
status: 200,
data: {
info: [ 'successfulUpdate' ],
model: 'Pagans'
}
};
};
Pagans.deleteUser = ( UUID, header ) => {
// Delete remove from users object UUID and update index
// Activity is not deleted => means some activity can concern an UUID that does not exist anymore.
// update index
const infouser = jsonfile.readFileSync( `${config.tribes}/${header.xworkon}/users/${UUID}.json` );
Pagans.updateDatabase( infouser, header.xworkon, true );
fs.removeSync( `${config.tribes}/${header.xworkon}/users/${UUID}.json` );
return {
status: 200,
data: {
info: [ 'successfulDelete' ],
modedl: 'Pagans'
}
};
};
/*
@header { xtribeid: client domain name data
A VERIFIER inutile ,xworkon: client domain name where user is store and where data are stored
,xuuid: 1 if unknown else if user id auhtneticated
,xlanguage: langue used fr en return referential in this lang
,xauth: TOKEN valid for 24h'}
workon=xtribeid in an app client usage
in case of xtribeid=mymaidigit,
if workon==mymaildigit => admin role on any xworkon
else adlin role only in xworkon specified
@body {LOGIN: password:}
return {status:200, data:{data:{TOKEN,UUID}}}
return {status:401,data:{info:[code list], model: referential}}
*/
Pagans.loginUser = ( header, body, checkpsw ) => {
// On recupere tribeid du LOGIN
// ATTENTION xworkon peut être different du user xtribeid
//(cas d'un user qui a des droits sur un autre domain et qui travail sur cet autre domain)
// les function Pagans utilise le domain de travail xWorkon
// il faut donc modifier le header au moment du LOGIN
// pour que l'update du user au moment du LOGIN concerne bien le bon domain
header.xworkon = header.xtribe
const LOGINdom = jsonfile.readFileSync( `${config.tmp}/loginsglob.json` );
console.assert( config.loglevel == "quiet", LOGINdom )
console.assert( config.loglevel == "quiet", body )
if( !LOGINdom[ body.LOGIN ] ) {
return {
status: 401,
data: { info: [ 'LoginDoesNotExist' ], model: 'Pagans' }
};
}
const logins = jsonfile.readFileSync( `${config.tribes}/${LOGINdom[body.LOGIN]}/users/searchindex/logins.json` );
if( !Object.keys( logins )
.includes( body.LOGIN ) ) {
return {
status: 401,
data: {
info: [ 'LOGINDoesNotExist' ],
model: 'Pagans',
moreinfo: `Le login ${body.LOGIN} does not exist for tribeid ${LOGINdom[body.LOGIN]}`
}
};
}
// Load user
const uid = logins[ body.LOGIN ];
const getUser = Pagans.getUser( uid, LOGINdom[ body.LOGIN ], { users: 'R' } );
console.log( 'getPagans', getUser )
if( getUser.status != 200 ) {
return { status: 200, data: { model: 'Pagans', user: getUser.data.user } };
}
const user = getUser.data.user;
console.log( 'user', user )
if( checkpsw ) {
const match = bcrypt.compareSync( body.PASSWORD, user.PASSWORD );
if( !match ) {
return {
status: 401,
data: {
info: [ 'checkCredentials' ],
model: 'Pagans'
}
};
}
}
// Mise à jour user pour app LOGIN
user.tribeid = LOGINdom[ body.LOGIN ]
user.TOKEN = jwt.encode( {
expiration: moment()
.add( 1, 'day' ),
UUID: user.UUID
}, config.jwtSecret );
// on met à jour le header qui authentifie le user connecte
header.xtribe = LOGINdom[ body.LOGIN ]
header.xpaganid = user.UUID;
header.xauth = user.TOKEN;
// On modifie xworkon car au LOGIN on est forcemenet identifiable sur tribeid
// xworkon est utiliser pour travailler sur un environnement client different de celui
// de l'appartenace du user
header.xworkon = LOGINdom[ body.LOGIN ];
const majuser = Pagans.updateUser( user.UUID, header, {
UUID: user.UUID,
TOKEN: user.TOKEN
} );
// Enleve info confidentiel
delete user.PASSWORD;
if( user.ACCESSRIGHTS.data[ "Alltribeid" ] ) {
//cas admin on transforme les droits sur tous les tribeid existant
const newaccessrightsdata = {}
jsonfile.readFileSync( `${config.tribes}/tribeids.json` )
.forEach( cid => {
newaccessrightsdata[ cid ] = user.ACCESSRIGHTS.data[ "Alltribeid" ]
} )
user.ACCESSRIGHTS.data = newaccessrightsdata;
}
// on recupere le menu de l app qui demande le LOGIN si existe dans user.ACCESSRIGHTS.app[]
// header['X-app'] = tribeid:Projet pour récuperer le menu correspondant
console.assert( config.loglevel == "quiet", "header.xapp", header.xapp )
console.assert( config.loglevel == "quiet", "user.ACCESSRIGHTS.app[header.xapp]", user.ACCESSRIGHTS.app[ header.xapp ] );
return {
status: 200,
data: {
model: "Pagans",
info: [ 'loginSuccess' ],
user: user
}
};
};
Pagans.getlinkwithoutpsw = async ( EMAIL, header ) => {
// check le domain d'appartenance de l'eamail dans /tribes/emailsglob.json={email:cleintId}
// on remplace le header.xtribeid
const domforemail = jsonfile.readFileSync( `${config.tribes}/emailsglob.json`, 'utf-8' );
if( domforemail[ EMAIL ] ) {
header.xtribe = domforemail[ EMAIL ]
} else {
return { status: 404, info: { model: 'Pagans', info: [ 'emailnotfound' ], moreinfo: "email does not exist in /emailsglob.json" } };
}
// recupere le uuid du user dans /tribes/tribeid/users/searchindex/emails.json
// puis l'ensemble des info des user du domain /uuids.json
// infoforuuid[uuidforemail[EMAIL]] permet de récupérer toutes info du user, droit, etc...
const uuidforemail = jsonfile.readFileSync( `${config.tribes}/${header.xtribe}/users/searchindex/emails.json`, 'utf8' );
const infoforuuid = jsonfile.readFileSync( `${config.tribes}/${header.xtribe}/users/searchindex/uids.json`, 'utf8' );
// On recupere le modele d'email appemailinfo qui doit être présent dans clientconf.json
let confdom = jsonfile.readFileSync( `${config.tribes}/${header.xtribe}/clientconf.json`, 'utf8' );
let checkinfomail = "";
if( !confdom.appemailinfo ) {
checkinfomail += ' Erreur de clientconfig il manque un objet appemailinfo pour poursuivre';
}
if( checkinfomail != "" ) {
console.log( `Pb de config pour ${header.xtribe} ${checkinfomail} ` )
return {
status: 500,
info: {
model: 'Pagans',
info: [ 'objectmissingclientconf' ],
moreinfo: checkinfomail
}
};
}
let simulelogin;
try {
simulelogin = await Pagans.loginUser( header, {
LOGIN: infoforuuid[ uuidforemail[ EMAIL ] ][ 0 ],
PASSWORD: ""
}, false );
console.log( 'info simulelogin', simulelogin )
} catch ( err ) {
return {
status: 501,
info: {
model: 'Pagans',
info: [ 'loginImpossible' ],
moreinfo: "Impossible de se loger avec " + infoforuuid[ uuidforemail[ EMAIL ] ][ 0 ]
}
};
}
const url = `${config.rootURL}?xauth=${simulelogin.data.TOKEN}&xuuid=${simulelogin.data.UUID}&xtribeid=${simulelogin.data.tribeid}&xworkOn=${header.xworkon}&xlang=${header.xlang}`
//console.log('envoi email avec' + url)
confdom.appemailinfo.msg.destperso = [ {} ];
confdom.appemailinfo.msg.destperso[ 0 ].email = EMAIL;
confdom.appemailinfo.msg.destperso[ 0 ].subject = "Lien de réinitialisation valable 1h"
confdom.appemailinfo.msg.destperso[ 0 ].titre = "Vous avez oublier votre mot de passe"
confdom.appemailinfo.msg.destperso[ 0 ].texte = `
<p style='color: #999999; font-size: 16px; line-height: 24px; margin: 0;text-align:justify;'>
Bonjour,<br> Vous nous avez signalé que vous avez oublié votre mot de passe pour cette email.
Avec le lien suivant vous allez pouvoir utiliser votre interface 1h maximum.
<a href="${url}">Clicker ICI</a>.<br>
Nous vous conseillons de changer votre mot de passe.</p>
`
//console.log('envoi header :', header);
Outputs.sendMailcampain( confdom.appemailinfo.msg, header );
console.log( confdom.appemailinfo );
return {
status: 200,
info: {
model: 'Pagans',
info: [ 'emailsentforReinit' ],
moreinfo: 'EMAIL sent with unique link ' + url
}
};
}
module.exports = Pagans;

416
models/Referentials.js Executable file
View File

@ -0,0 +1,416 @@
const glob = require( 'glob' );
const path = require( 'path' );
const fs = require( 'fs-extra' );
const config = require( '../tribes/townconf.js' );
const Referentials = {};
/*
Manage Referential object data
each object is compose of a list of fields
each fields have it owns structur and check
those common rules allow to be used to manage:
- forms (creation to collect data)
- data check
- data access rights
common referential data stored in /data/shared/referential/
*/
Referentials.clientconf = ( xworkOn, listkey ) => {
/*
Retourne les info d'un clientconf.json sur une liste de [cle]
*/
let conf = {};
let dataconf = {};
//console.log( `${config.tribes}/${xworkOn}/clientconf.json` )
try {
conf = fs.readJsonSync( `${config.tribes}/${xworkOn}/clientconf.json` );
// remove information notrelevant for
[ 'emailFrom', 'emailClient', 'emailCc', 'commentkey', 'clezoomprivate', 'stripekeyprivate', 'genericpsw' ].forEach( c => {
delete conf[ c ];
} );
listkey.forEach( k => dataconf[ k ] = conf[ k ] )
//console.log( 'dataconf', dataconf )
} catch ( err ) {
console.log( 'Attention demande sur clienId inconnu ' + xworkOn );
}
return {
status: 200,
payload: {
data: dataconf
}
};
};
Referentials.clientconfglob = () => ( {
status: 200,
payload: {
data: fs.readJsonSync( `${config.tmp}/clientconfglob.json` )
}
} );
Referentials.getref = ( origin, source, ref, xworkOn, xlang ) => {
// If request origin then send back referential with all language in it
// if not origin or json source return by language
let referent = {};
let src;
if( origin && [ 'object', 'data' ].includes( source ) ) {
src = `${config.tribes}/${xworkOn}/referentials/${source}/${ref}.json`
} else {
src = `${config.tribes}/${xworkOn}/referentials/${source}/${ref}_${xlang}.json`;
}
//console.log( src )
try {
referent = fs.readJsonSync( src );
} catch ( err ) {
console.log( `Request ${src} does not exist ` );
}
return {
status: 200,
payload: {
data: referent
}
};
};
Referentials.putref = ( source, name, xworkOn, data ) => {
/*
We get a referential, we have 3 kinds of sources:
* data = [{uuid,DESC:{fr:,en,},DESCLONG:{fr:,en:}, other field}]
only DESC and DESCLONG have a translation
* json = are full complex object in language name_lg.json
* object = [{uuid,DESC:{fr,en},DESCLONG:{fr,en}},tpl,}]
Difference between data and object is that object defines rule to manage an object, and how to create a forms to get data each data is saved in one folder object/uuid.json and have to respect the corresponding object referentials definition.
For data and object it is possible only to put a file with all language available.
When store this script erase with the new file per _lg
name for source=json must end by _lg
*/
//console.log( data )
const pat = /.*_..\.json$/;
const file = `${config.tribes}/${xworkOn}/referentials/${source}/${name}.json`
if( [ 'object', 'data' ].includes( source ) ) {
if( pat.test( name ) ) {
return { status: 404, payload: { model: 'Referentials', info: [ 'nameunconsistent' ], moreinfo: "can not be update with one lang need a full file with language for object or data" } }
} else {
fs.outputJsonSync( file, data );
return Refernetials.update( xworkOn, source, name )
}
} else {
if( !pat.test( name ) ) {
return { status: 404, payload: { model: 'Referentials', info: [ 'nameunconsistent' ], moreinfo: "can not be update without a lang _lg.json a referential json" } }
} else {
fs.outputJsonSync( file, data );
return { status: 200, payload: { model: 'Referentials', info: [ 'successfull' ], moreinfo: "ref json updated " } }
}
};
};
Referentials.updatefull = ( tribeid ) => {
let err = "";
let nbrefupdate = 0;
const pat = /.*_..\.json$/;
[ 'object', 'data' ].forEach( o => {
glob.sync( `${config.tribes}/${tribeid}/referentials/${o}/*.json` )
.forEach( f => {
if( !pat.test( f ) ) {
const res = Referentials.update( tribeid, o, path.basename( f, '.json' ) );
if( res.status != 200 ) {
err += `Error on ${o}/${path.basename(f)}`
} else {
nbrefupdate += 1;
}
}
} )
} );
if( err != "" ) {
return { status: 500, payload: { info: [ 'Errupdateref' ], model: "Referentials", moreinfo: err } }
}
return { status: 200, payload: { info: [ 'Success' ], model: "Referentials", moreinfo: `Number of object and data ref updated: ${nbrefupdate}` } }
};
Referentials.inittribeid = () => {
console.log( "Clientconf list for this server", `${config.tribes}/**/clientconf.json` );
const TribesGlobalConfig = glob.sync( `${config.tribes}/**/clientconf.json` )
.map( f => fs.readJsonSync( f ) );
// store global conf for sharing to other api
fs.outputJsonSync( `${config.tmp}/clientconfglob.json`, TribesGlobalConfig, {
spaces: 2
} );
return { status: 200, payload: { moreinfo: TribesGlobalConfig } }
}
Referentials.generetribeids = () => {
const tribeids = [];
fs.readJsonSync( `${config.tmp}/clientconfglob.json` )
.forEach( c => {
if( !tribeids.includes( c.tribeid ) ) tribeids.push( c.tribeid );
} );
fs.outputJsonSync( `${config.tmp}/tribeids.json`, tribeids );
console.log( `update ${config.tribes}/tribeids` );
return tribeids;
}
Referentials.genereallowedDOM = () => {
const confglob = fs.readJsonSync( `${config.tmp}/clientconfglob.json` );
let allDom = [];
confglob.forEach( c => {
c.allowedDOMs.forEach( d => {
if( !allDom.includes( d ) ) allDom = allDom.concat( d );
} )
} );
return allDom;
};
/* A voir si encore necessaire pour générer un environnement identique
sur un autre server
// Génére les domaines s'ils n'existe pas dans le repertoire
function genereEnvClient() {
const confglob = fs.readJsonSync(
`${config.sharedData}/clientconfglob.json`
);
confglob.forEach(function(c) {
config.tribeidsConf[c.tribeid] = c;
if (c.allowedURLs) {
c.allowedURLs.forEach(u => {
if (!config.allowedURLs.includes(u)) {
config.allowedURLs.push(u);
}
});
} else {
console.log('erreur de fichier config d\'un site pour ', c);
}
// GLOBAL Tribes IDS INDEX
maketribeidsIndex();
if (!fs.existsSync(`${config.tribes}/${c.tribeid}`)) {
const execSync = require('child_process').execSync;
execSync(`cp -r ${config.tribes}/modele ${config.tribes}/${c.tribeid}`);
}
});
}
*/
Referentials.update = ( tribeid, source, name ) => {
/*
Replace for each language the referential name for a tribeid
After each update the version number is incremented by 1 in clientconf.json
*/
if( !fs.existsSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}.json` ) ) {
return { status: 500, payload: { info: [ "unknownRef" ], model: "Referentials", moreinfo: `file does not exist ${config.tribes}/${tribeid}/referentials/${source}/${name}.json` } }
};
const clientconf = fs.readJsonSync( `${config.tribes}/${tribeid}/clientconf.json` );
if( !clientconf.langueReferential ) {
return { status: 500, payload: { info: [ "missingConf" ], model: "Referentials", moreinfo: ` ${config.tribes}/${tribeid}/clientconf.json does not contain langueReferential array` } }
}
const ref = fs.readJsonSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}.json` );
clientconf.langueReferential.forEach( lg => {
//manage translation
let refnew = [];
refnew = [];
ref.forEach( d => {
if( d.DESC ) d.DESC = d.DESC[ lg ];
if( d.DESCLONG ) d.DESCLONG = d.DESCLONG[ lg ];
if( d.INFO ) d.INFO = d.INFO[ lg ];
if( d.desc ) d.desc = d.desc[ lg ];
if( d.desclong ) d.desclong = d.desclong[ lg ];
if( d.info ) d.info = d.info[ lg ];
if( d.placeholder ) d.placeholder = d.placeholder[ lg ];
refnew.push( d )
} )
//save new ref in language
//console.log( "New ref", refnew )
console.log( `Update referentials per lg ${config.tribes}/${tribeid}/referentials/${source}/${name}_${lg}.json` )
fs.outputJsonSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}_${lg}.json`, refnew, {
spaces: 2
} );
} );
//upgrade version number
if( !clientconf.referentials ) clientconf.referentials = {};
if( !clientconf.referentials[ source ] ) clientconf.referentials[ source ] = {};
if( !clientconf.referentials[ source ][ name ] ) clientconf.referentials[ source ][ name ] = { version: 0 };
clientconf.referentials[ source ][ name ].version += 1;
fs.outputJsonSync( `${config.tribes}/${tribeid}/clientconf.json`, clientconf, 'utf8' );
return {
status: 200,
payload: {
info: [ 'successUpdate' ],
model: "Referentials",
moreinfo: `${name} updated`
}
}
};
//console.log( Referentials.update( 'apixtribe', "object", "user" ) )
Referentials.genereobjet = ( tribeid, destination, tplmustache, objet, filtre ) => {
/* @TODO
Genere des objets d'agregat
@tribeid = data/tribee/ identifiant client
@destinations = [] of destination
@tplmustache = fichier mustache de mise en page
@objet = nom d'objet contact, companies, items, users
@filtre = fonction a executer
*/
}
//////// EN DESSOUS DE CETTE LIGNE A SUPPRIMER
/*
Le principe consistait à partager des referentiels dans shareddataLa gestion est trop compliqué => le principe chaque client duplique un referentiel pour que les app qui s'appuie sur des composants communs puissent fonctionner
*/
/*Referentials.genereClientObjectASUPP = () => {
const confglob = fs.readJsonSync( `${config.tmp}/clientconfglob.json` );
// Check and update folder and data per lang
// c as tribeid
confglob.forEach( ( c, postribeid ) => {
//check folder are well create
const lstfolder = [ 'actions', 'actions/done', 'actions/todo', 'cards', 'logs', 'orders', 'orders/reservation', 'orders/purchase', 'orders/contact', 'public', 'public/reservation', 'tags', 'tags/stats', 'tags/archives', 'tags/hits', 'tags/imgtg', 'users' ];
lstfolder.forEach( fol => {
if( !fs.existsSync( `${config.tribes}/${c.tribeid}/${fol}` ) ) {
fs.mkdirSync( `${config.tribes}/${c.tribeid}/${fol}` );
}
} )
if( c.referentials && !c.langue ) { console.log( `ERREUR referentials mais pas de langue:[] pour ${c.tribeid}/clientconf.json` ) }
if( c.referentials && c.langue ) {
let majclientconf = false;
// Create and check Object structure
Object.keys( c.referentials.object )
.forEach( o => {
// if object exist in shared then it merge sharedObject and domain referential object
let objfull = [];
const objshared = `${config.sharedData}/referentials/dataManagement/object/${o}.json`;
if( fs.existsSync( objshared ) ) {
objfull = objfull.concat( fs.readJsonSync( objshared ) );
}
const objdomain = `${config.tribes}/${c.tribeid}/referentials/dataManagement/object/${o}.json`;
if( fs.existsSync( objdomain ) ) {
objfull = objfull.concat( fs.readJsonSync( objdomain ) );
}
c.langue.forEach( lg => {
const objfulllg = objfull.map( field => {
if( field.DESC ) field.DESC = field.DESC[ lg ];
if( field.DESCLONG ) field.DESCLONG = field.DESCLONG[ lg ];
return field;
} );
const objectdomlg = `${config.tribes}/${c.tribeid}/referentials/${lg}/object/${o}.json`;
let savedObject = {};
if( fs.existsSync( objectdomlg ) ) {
savedObject = fs.readJsonSync( objectdomlg );
}
// TODO Always true change later to update only if needded
if( !fs.existsSync( objectdomlg ) || objfulllg.length !== savedObject.length || 1 == 1 ) {
fs.outputJsonSync( objectdomlg, objfulllg, {
spaces: 2
} );
confglob[ postribeid ].referentials.object[ o ].version += 1;
majclientconf = true;
}
} );
} );
// datafile
Object.keys( c.referentials.data )
.forEach( d => {
// if object exist in shared then it merge sharedObject and domain referential object
// console.log(c.tribeid + '--' + d);
let datafull = [];
const datashared = `${
config.sharedData
}/referentials/dataManagement/data/${d}.json`;
if( fs.existsSync( datashared ) ) {
datafull = datafull.concat( fs.readJsonSync( datashared ) );
}
const datadomain = `${config.tribes}/${
c.tribeid
}/referentials/dataManagement/data/${d}.json`;
if( fs.existsSync( datadomain ) ) {
datafull = datafull.concat( fs.readJsonSync( datadomain ) );
}
/* const defdata = `${config.tribes}/${
c.tribeid
}/referentials/dataManagement/data/${d}.json`;
*/
// for each Langues => generate fr.obj and compare it with existing file
// if diff then => upgrade version number in clientconf
// console.log(datafull);
// this could be improved by usind d.meta wich is the object that DESCribe this data
/* c.langue.forEach( lg => {
let meta;
if( c.referentials.data[ d ].meta ) {
meta = fs.readJsonSync( `${config.tribes}/${c.tribeid}/referentials/${lg}/object/${
c.referentials.data[d].meta
}.json` );
}
let datalg;
const datafulllg = datafull.map( tup => {
datalg = {};
meta.forEach( ch => {
if( tup[ ch.idfield ] ) {
if( ch.multilangue ) {
datalg[ ch.idfield ] = tup[ ch.idfield ][ lg ];
} else {
datalg[ ch.idfield ] = tup[ ch.idfield ];
}
}
} );
return datalg;
} );
// lit le fichier correspondant et le compare si différent le sauvegarde
// stocke l'information d'upgrade d ela version
const datadomlg = `${config.tribes}/${
c.tribeid
}/referentials/${lg}/data/${d}.json`;
let saveddata = {};
if( fs.existsSync( datadomlg ) ) {
saveddata = fs.readJsonSync( datadomlg );
}
// Condition to improve
// TODO always change file to improvelater by detecting real change.
if( !fs.existsSync( datadomlg ) || datafulllg.length != saveddata.length || 1 == 1 ) {
fs.outputJsonSync( datadomlg, datafulllg, {
spaces: 2
} );
confglob[ postribeid ].referentials.data[ d ].version += 1;
majclientconf = true;
}
} );
} );
// json file that have to start with lg {lg:'':{json }}
Object.keys( c.referentials.json )
.forEach( j => {
// if object exist in shared then it merge sharedObject and domain referential object
// console.log(c.tribeid + '--' + d);
let jsonfull = [];
const jsondomain = `${config.tribes}/${c.tribeid}/referentials/dataManagement/json/${j}.json`;
if( fs.existsSync( jsondomain ) ) {
jsonfull = fs.readJsonSync( jsondomain );
}
c.langue.forEach( lg => {
const jsondomlg = `${config.tribes}/${
c.tribeid
}/referentials/${lg}/json/${j}.json`;
// console.log('jsondomlg', jsondomlg);
let datalg = jsonfull;
if( jsonfull[ lg ] ) {
datalg = jsonfull[ lg ];
}
fs.outputJsonSync( jsondomlg, datalg, {
spaces: 2
} );
} );
} );
// update clientconf domain with updated version
if( majclientconf ) {
fs.outputJsonSync( `${config.tribes}/${c.tribeid}/clientconf.json`, c, {
spaces: 2
} );
}
}
} );
// update global conf
fs.outputJsonSync( `${config.tmp}/clientconfglob.json`, confglob, {
spaces: 2
} );
};*/
module.exports = Referentials;

220
models/Referentialssave.js Executable file
View File

@ -0,0 +1,220 @@
const glob = require( 'glob' );
const path = require( 'path' );
const fs = require( 'fs-extra' );
// Check if package is installed or not to pickup the right config file
const config = require( '../tribes/townconf.js' );
const Referentials = {};
/*
Manage Referential object data
each object is compose of a list of fields
each fields have it owns structur and check
those common rules allow to be used to manage:
- forms (creation to collect data)
- data check
- data access rights
common referential data stored in /data/shared/referential/
*/
Referentials.clientconf = ( xworkOn, listkey ) => {
/*
Retourne les info d'un clientconf.json sur une liste de [cle]
*/
let conf = {};
let dataconf = {};
console.log( `${config.tribes}/${xworkOn}/clientconf.json` )
try {
conf = fs.readJsonSync( `${config.tribes}/${xworkOn}/clientconf.json` );
// remove information notrelevant for
[ 'emailFrom', 'emailClient', 'emailCc', 'commentkey', 'clezoomprivate', 'stripekeyprivate', 'genericpsw' ].forEach( c => {
delete conf[ c ];
} );
listkey.forEach( k => dataconf[ k ] = conf[ k ] )
console.log( 'dataconf', dataconf )
} catch ( err ) {
console.log( 'Attention demande sur clienId inconnu ' + xworkOn );
}
return {
status: 200,
payload: {
data: dataconf
}
};
};
Referentials.clientconfglob = () => ( {
status: 200,
payload: {
data: fs.readJsonSync( `${config.tmp}/clientconfglob.json` )
}
} );
Referentials.inittribeid = () => {
console.log( "Clientconf list for this server", `${config.tribes}/**/clientconf.json` );
const TribesGlobalConfig = glob.sync( `${config.tribes}/**/clientconf.json` )
.map( f => fs.readJsonSync( f ) );
// store global conf for sharing to other api
fs.outputJsonSync( `${config.tmp}/clientconfglob.json`, TribesGlobalConfig, {
spaces: 2
} );
return { status: 200, payload: { moreinfo: TribesGlobalConfig } }
}
Referentials.generetribeids = () => {
const tribeids = [];
fs.readJsonSync( `${config.tmp}/clientconfglob.json` )
.forEach( c => {
if( !tribeids.includes( c.tribeid ) ) tribeids.push( c.tribeid );
} );
fs.outputJsonSync( `${config.tmp}/tribeids.json`, tribeids );
console.log( `update ${config.tribes}/tribeids` );
return tribeids;
}
Referentials.genereallowedDOM = () => {
const confglob = fs.readJsonSync( `${config.tmp}/clientconfglob.json` );
let allDom = [];
confglob.forEach( c => {
c.allowedDOMs.forEach( d => {
if( !allDom.includes( d ) ) allDom = allDom.concat( d );
} )
} );
return allDom;
};
Referentials.getref = ( source, ref, xworkOn, xlang, singlelang = true ) => {
let referent = {};
let src = `${config.tribes}/${xworkOn}/referentials/${xlang}/${source}/${ref}.json`;
if( !singlelang ) {
//request full referential to manage
src = `${config.tribes}/${xworkOn}/referentials/dataManagement/${source}/${ref}.json`
}
console.log( src )
try {
referent = fs.readJsonSync( src );
} catch ( err ) {
console.log( `Attention demande de referentiel inexistant pour ${src} ` );
}
return {
status: 200,
payload: {
data: referent
}
};
};
Referentials.putref = ( source, name, xworkOn, data ) => {
/*
We get a referential, we have 3 kinds of sources:
* data = [{uuid,desc:{fr:,en,},desclong:{fr:,en:}, other field}]
only desc and desclong have a translation
* json = {"fr":{},"en":{}, ...} are full complex object in language
* object = [{uuid,desc:{fr,en},desclong:{fr,en}},tpl,}]
Difference between data and object is that object defines rule to manage an object, and how to create a forms to get data each data is saved in one folder object/uuid.json and have to respect the corresponding object referentials definition.
*/
console.log( data )
// Create a backup of the day hour if exist
const file = `${config.tribes}/${xworkOn}/referentials/dataManagement/${source}/${name}.json`
if( fs.existsSync( file ) ) {
//backup change done per hour
const origin = fs.readJsonSync( file, 'utf-8' )
fs.outputJsonSync( `${config.tribes}/${xworkOn}/referentials/dataManagementBackup/${source}/${name}${moment().format('YYYYMMDDHHmm')}.json`, origin, { spaces: 2 } )
} else {
console.log( `Referential ${name}.json does not exist this created it` )
}
console.log( 'ref backup before update', name );
fs.outputJsonSync( file, data, { spaces: 2 } );
// update/create new referential and new version
return Referentials.update( xworkOn, source, name );
};
Referentials.updatefull = ( tribeid ) => {
// source json are only per lg so no full update for json
let err = "";
[ 'object', 'data' ].forEach( o => {
glob.sync( `${config.tribes}/${tribeid}/referentials/dataManagement/${o}/*.json` )
.forEach( f => {
if( finipaspar_lg ) {
const res = Referentials.update( tribeid, o, path.basename( f, '.json' ) );
if( res.status != 200 ) {
err += `Error on ${o}/${path.basename(f)}`
}
}
} )
} );
if( err != "" ) {
return { status: 500, payload: { info: [ 'Errupdateref' ], model: "Referentials", moreinfo: err } }
}
return { status: 200, payload: { info: [ 'Success' ], model: "Referentials" } }
};
Referentials.update = ( tribeid, source, name ) => {
/*
Replace for each language the referential name for a tribeid
After each update the version number is incremented by 1 in clientconf.json
*/
if( !fs.existsSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}.json` ) ) {
return { status: 500, payload: { info: [ "unknownRef" ], model: "Referentials", moreinfo: `file does not exist ${config.tribes}/${tribeid}/referentials/${source}/${name}.json` } }
};
const clientconf = fs.readJsonSync( `${config.tribes}/${tribeid}/clientconf.json` );
if( !clientconf.langueReferential ) {
return { status: 500, payload: { info: [ "missingConf" ], model: "Referentials", moreinfo: ` ${config.tribes}/${tribeid}/clientconf.json does not contain langueReferential array` } }
}
const ref = fs.readJsonSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}.json` );
clientconf.langueReferential.forEach( lg => {
/*if( !fs.existsSync( `${config.tribes}/${tribeid}/referentials/${lg}` ) ) {
[ '', '/data', '/json', '/object' ].forEach( p => {
fs.mkdirSync( `${config.tribes}/${tribeid}/referentials/${lg}${p}` );
} )
}
*/
//manage translation
let refnew = [];
if( source == 'json' ) {
refnew = ref[ lg ]
} else {
refnew = [];
ref.forEach( d => {
if( d.desc ) d.desc = d.desc[ lg ];
if( d.desclong ) d.desclong = d.desclong[ lg ];
if( d.info ) d.info = d.info[ lg ];
if( d.placeholder ) d.placeholder = d.placeholder[ lg ];
refnew.push( d )
} )
}
//save new ref in language
console.log( "testtttt", refnew )
console.log( `${config.tribes}/${tribeid}/referentials/${source}/${name}_${lg}.json` )
fs.outputJsonSync( `${config.tribes}/${tribeid}/referentials/${source}/${name}_${lg}.json`, refnew, {
spaces: 2
} );
} );
//upgrade version number
if( !clientconf.referentials[ source ][ name ] ) clientconf.referentials[ source ][ name ] = { version: 0 };
clientconf.referentials[ source ][ name ].version += 1;
return {
status: 200,
payload: {
info: [ 'successUpdate' ],
model: "Referentials",
moreinfo: `${name} updated`
}
}
};
//console.log( Referentials.update( 'apixtribe', "object", "user" ) )
Referentials.genereobjet = ( tribeid, destination, tplmustache, objet, filtre ) => {
/* @TODO
Genere des objets d'agregat
@tribeid = data/tribee/ identifiant client
@destinations = [] of destination
@tplmustache = fichier mustache de mise en page
@objet = nom d'objet contact, companies, items, users
@filtre = fonction a executer
*/
}
module.exports = Referentials;

149
models/Setup.js Executable file
View File

@ -0,0 +1,149 @@
const fs = require( 'fs-extra' );
const path = require( 'path' );
const dnsSync = require( 'dns-sync' );
const Mustache = require( 'mustache' );
const Setup = {};
if( !fs.existsSync( '/etc/nginx/nginx.conf' ) ) {
console.log( '\x1b[31m Check documentation, nginx have to be installed on this server first, no /etc/nginx/nginx.conf available' );
process.exit();
}
if( !fs.existsSync( '../config.js' ) ) {
console.log( `\x1b[42m####################################\nWellcome into apixtribe, this is a first install.\nWe need to make this server accessible from internet subdomain.domain to current IP. This setup will create your unique tribeid, with an admin login user to let you connect to the parameter interface.\nCheck README's project to learn more. more.\n#####################################\x1b[0m` );
const confdata = fs.readJsonSync( path.normalize( `${__dirname}/../setup/configsetup.json` ) );
console.log( 'Current setup conf from :\n' + path.normalize( `${__dirname}/../setup/configsetup.json` + '\nChange with relevant setup data and rerun yarn setup' ) );
console.log( confdata )
const readline = require( 'readline' );
const rl = readline.createInterface( {
input: process.stdin,
output: process.stdout
} );
rl.question( 'This is the data from setup/configsetup.json used, is it correct to use as first install (Yes/no)?', function ( rep1 ) {
let quest = `This is a production install, please check that ${confdata.subdomain}.${confdata.domain} IP is well redirect to tour server`;
if( rep1 !== "Yes" ) process.exit( 0 );
if( confdata.domain == 'local.fr' ) {
quest = `This is a development installation, please add in your /etc/hosts "127.0.0.1 ${confdata.subdomain}.${confdata.domain} " `;
}
rl.question( quest + '\nAre you sure to set this? (Yes/no)', function ( rep2 ) {
if( rep2 == "Yes" ) {
const check = Setup.checkdata( confdata );
if( check == "" ) {
Setup.config( confdata );
} else {
console.log( check );
}
} else {
console.log( 'Nothing done please, check setup/configsetup.json and answer twice Yes' )
}
rl.close();
} );
} );
rl.on( 'close', function () {
console.log( '\n Setup process ended' );
process.exit( 0 );
} );
} else {
console.log( 'Carefull you have already a config.js that is running. If you want to change remove config.js file and run again yarn setup' );
}
Setup.checkdata = conf => {
let rep = "";
if( ![ 'dev', 'prod' ].includes( conf.mode ) ) {
rep += "mode have to be 'dev' or 'prod' currently it's " + conf.mode + "\n";
}
conf.language.forEach( l => {
if( ![ "fr", "en", "it", "de", "sp" ].includes( l ) ) {
rep += l + " Only fr,en,it,de,sp are available \n";
}
} );
if( !fs.existsSync( `/home/${conf.linuxuser}` ) ) {
rep += `/home/${conf.linuxuser} does not exist, user has to be create with a /home on this server\n`;
}
// avant eslint if( !( /^[a-z\s\-]+$/.test( conf.druidid ) ) ) {
if( !( /^[a-z\s-]+$/.test( conf.druidid ) ) ) {
rep += `${conf.druidid} have to contain only a-z characters `;
}
// add a check later have to be unique if not in dev
if( !( /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/.test( conf.genericpsw ) ) ) {
rep += "Give a generic pasword for all your tribeid's user,they will change it min 8 char special char upper lower case and number"
}
if( conf.jwtsecret.length < 32 ) {
rep += "Your jwtsecretkey must have at least 32 characters"
}
if( conf.mode != 'dev' && !dnsSync.resolve( `${conf.subdomain}.${conf.domain}` ) ) {
rep += `\nresolving ${conf.subdomain}.${conf.domain} will not responding valid IP, please setup domain redirection IP before runing this script`
}
return rep
};
Setup.config = ( confdata ) => {
// Init this instance with a .config.js
Setup.configjs( confdata );
// Create tribeid space + a user admin + webspace withe apixtribe webapp install
Setup.druidid( confdata );
};
Setup.configjs = ( confdata ) => {
// Set /config.js
let confapixtribe = fs.readFileSync( './setup/config.mustache', 'utf-8' );
fs.writeFileSync( './config.js', Mustache.render( confapixtribe, confdata ), 'utf-8' );
if( fs.existsSync( './config.js' ) ) {
console.log( 'config.js successfully created.' );
} else {
console.log( "config.js not created, check what's wrong in tpl:", confapixtribe );
console.log( "for data :", confdata );
process.exit();
}
};
Setup.druidid = ( confdata ) => {
// create a tribeid with a user that will admin this instance into /tribes/tribeid /users
const config = require( '../config.js' );
// Need to do it on setup this is also done again in models/Tribes.js
console.log( `${config.tribes}/${confdata.druidid}` )
fs.ensureDirSync( `${config.tribes}/${confdata.druidid}` );
[ 'users', 'www', 'referentials', 'nationchains' ].forEach( r => {
fs.copySync( `${config.mainDir}/setup/tribes/apixtribe/${r}`, `${config.tribes}/${confdata.druidid}/${r}` );
} )
/* const confcli = JSON.parse( Mustache.render( fs.readFileSync( `${config.mainDir}/setup/tribes/apixtribe/clientconf.mustache`, 'utf8' ), confdata ) );
fs.outputJsonSync( `${config.tribes}/${confdata.druidid}/clientconf.json`, confcli );
// Create a new tribeid + admin user for this tribeid
// with access to {druidid}:webapp as admin
*/
const Tribes = require( '../models/Tribes.js' );
const access = { app: {}, data: {} }
access.app[ `${confdata.druidid}:webapp` ] = "admin";
access.data[ confdata.druidid ] = { "users": "CRUDO", "referentials": "CRUDO", "www": "CRUDO" };
const createclient = Tribes.create( {
tribeid: confdata.druidid,
genericpsw: confdata.genericpsw,
lanquageReferential: confdata.language,
useradmin: {
LOGIN: confdata.login,
xlang: confdata.language[ 0 ],
ACCESSRIGHTS: access
}
} );
if( createclient.status == 200 ) {
console.log( `Your tribeid domain was created with login : ${confdata.login} and password: ${confdata.genericpsw}, change it after the 1st login on https://${confdata.subdomain}.${confdata.domain}` );
// Create nginx conf for a first install
const confnginx = fs.readFileSync( './setup/nginx/nginx.conf.mustache', 'utf8' );
fs.outputFileSync( '/etc/nginx/nginx.conf', Mustache.render( confnginx, confdata ), 'utf-8' );
// Create a spacedev for webapp of apixtribe
// that will be accesible in prod from https://subdomain.domain/ and in dev http://webapp.local.fr
const addspaceweb = Tribes.addspaceweb( {
setup: true,
dnsname: [ `${confdata.subdomain}.${confdata.domain}` ],
mode: confdata.mode,
tribeid: confdata.druidid,
website: 'webapp',
pageindex: "app_index_fr.html"
} );
if( addspaceweb.status == 200 ) {
console.log( `WELL DONE run yarn dev to test then yarn startpm2 ` )
}
} else {
console.log( 'Issue ', createclient )
}
}
module.exports = Setup;

229
models/Tags.js Executable file
View File

@ -0,0 +1,229 @@
const fs = require( 'fs' );
const formidable = require( 'formidable' );
const jsonfile = require( 'jsonfile' );
const path = require( 'path' );
const glob = require( 'glob' );
const mustache = require( 'mustache' );
const moment = require( 'moment' );
// Check if package is installed or not to pickup the right config file
const config = require( '../tribes/townconf.js' );
const Tags = {};
/*
Keyword definition:
id: b64 encoded field that can be : email , uuid
tribeid: apiamaildigit client Id, (a folder /tribes/tribeid have to exist)
email: identifiant of a person
uuid: identifiant o
contact database
operationId: code name of an operation
messageId: code name of an templatesource have to exist in /datashared/templatesource/generic/messageId
*/
/*
Manage tag data collection
Popup survey manager
*/
Tags.info = ( data, req ) => {
//console.log('headers:', req.headers)
/*console.log('hostname', req.hostname)
console.log('req.ip', req.ip)
console.log('req.ips', req.ips)
console.log('req key', Object.keys(req))
*/
//console.log('req.rawHeaders', req.body)
data.useragent = `${req.headers['user-agent']}__${req.headers['accept-language']}__${req.headers['accept-encoding']}__${req.headers['connection']}`;
data.ips = req.ips;
data.ip = req.ip;
data.proxyip = req.connection.remoteAddress;
data.cookie = ""
Object.keys( req.headers )
.forEach( k => {
if( ![ 'user-agent', 'accept-language', 'accept-encoding', 'connection' ].includes( k ) ) {
data.cookie += `${k}__${req.headers['cookie']}|`
}
} )
//data.cookie = `${req.headers['cookie']}__${req.headers['upgrade-insecure-requests']}__${req.headers['if-modified-since']}__${req.headers['if-no-match']}__${req.headers['cache-control']}`;
return data
}
Tags.getfile = ( filename, req ) => {
const infotg = filename.split( '__' );
if( infotg.length < 3 && !fs.existsSync( `${config.tribes}/${infotg[1]}` ) ) {
return {
status: 400,
payload: { info: [ 'fileUnknown' ], model: 'UploadFiles' }
}
}
if( infotg[ 0 ] == "imgtg" ) {
jsonfile.writeFile( `${config.tribes}/${infotg[1]}/tags/imgtg/${Date.now()}.json`, Tags.info( { filename: filename, messageId: infotg[ 2 ], operationId: infotg[ 3 ], identifiant: infotg[ 4 ] }, req ), function ( err ) {
if( err ) {
console.log( `Erreur de sauvegarde de tag:${filename}` )
}
} );
return {
status: 200,
payload: { moreinfo: "Declenche tag", filename: `${config.mainDir}/public/imgtg.png` }
}
}
return { status: 404, payload: {} }
}
Tags.savehits = ( req ) => {
if( !fs.existsSync( `${config.tribes}/${req.params.tribeid}` ) ) {
console.log( `Erreur d'envoi de tag sur ${req.params.tribeid} pour ${req.params.r}` );
return false;
} else {
const info = JSON.parse( JSON.stringify( req.body ) );
jsonfile.writeFile( `${config.tribes}/${req.params.tribeid}/tags/hits/${Date.now()}.json`, Tags.info( info, req ), function ( err ) {
if( err ) {
console.log( `Erreur de sauvegarde de tag pour ${req.params.tribeid} check si /tags/hits et /tags/imgtg exist bien ` )
}
} );
}
return true;
}
Tags.dataloadstat = ( tribeid ) => {
/*
@TODO à appeler via une route pour agregation de tag
@TODO ajouter la suppression des fichiers hits traités qd le prog aura fait ses preuves en prod
@TODO corriger la prod pour que l'ip passe
Si on recharge plusieurs fois un hits (on ne le comptabilise pas si même r et même timestamps)
Manage tag info to agregate by user and by statistique
stats/data.json = {r:{
info:{useragent:"",ip:[]},
data:[ [timestamps, tit, cookie] ]
}
}
stats/graph.json = {"graphYears": {AAAA:#visites,"years":[AAAA,AAAA]},
"graphMonths": {AAAA:{"Jan":#visites,..},"monthsLabels":["Jan",..],"years":[AAAA,]},
"graphDaysOfWeek":{"labels":["Sun","Mon",...],"Sun":#visites},
"graphHoursOfDay":{"labels":["OOh","01h",...],"00h":#visites}
}
Pour tester des evolutions ou un client en dev (recuperer son repertoire de prod /tags )
ajouter Tags.dataloadstat('yes');
NODE_ENV=dev node ./models/Tags.js
*/
const agrege = {
data: {},
graph: {
visites: {
graphYears: { years: [] },
graphMonths: {
years: [],
monthsLabels: []
},
graphMonths: {
years: [],
monthsLabels: []
},
graphDayOfWeek: { labels: [] },
graphHourOfDay: { labels: [] }
},
visitors: {
graphMonths: {
years: [],
monthsLabels: []
}
}
}
};
try {
agrege.data = jsonfile.readfileSync( `${config.tribes}/${tribeid}/tags/stats/data.json`, "utf-8" );
agrege.graph = jsonfile.readfileSync( `${config.tribes}/${tribeid}/tags/stats/graph.json`, "utf-8" );
} catch ( err ) {
console.log( "ATTENTION tag reinitialisé en data.json et graph.json, s'il s'agit de 1ere connexion pas de pb. Le risque est de perdre les tag historiques" )
//return { status: 503, payload: { info: ['Errconfig'], model: 'Tags', moreinfo: `Il manque un ${config.tribes}/${tribeid}/tags/stats/data.json ou stats/graph.json` } }
}
glob.sync( `${config.tribes}/${tribeid}/tags/hits/*` )
.forEach( f => {
const hit = jsonfile.readFileSync( f );
const ts = parseInt( path.basename( f )
.split( ".json" )[ 0 ] );
//console.log(moment(ts).format('DD-MM-YYYY h:mm:ss'));
const tsm = moment( ts )
const year = tsm.format( 'YYYY' );
const month = tsm.format( 'MMM' );
const dayow = tsm.format( 'ddd' );
const hourod = tsm.format( 'HH' ) + "h";
let newvisitor = false;
let alreadydone = false;
//console.log(hit.r, ts)
// Agrege data pour # visiteur vs # de visiteur
if( agrege.data[ hit.r ] ) {
if( !agrege.data[ hit.r ].data.some( el => el[ 0 ] == ts ) ) {
//evite de charger plusieurs fois le même
agrege.data[ hit.r ].data.push( [ ts, hit.tit, hit.cookie ] )
} else {
alreadydone = true;
}
} else {
newvisitor = true;
agrege.data[ hit.r ] = {
info: { useragent: hit.useragent, ip: [ hit.ip ] },
data: [
[ ts, hit.tit, hit.cookie ]
]
}
}
if( !alreadydone ) {
if( newvisitor ) {
//traite Month
if( !agrege.graph.visitors.graphMonths[ year ] ) {
agrege.graph.visitors.graphMonths[ year ] = {}
agrege.graph.visitors.graphMonths.years.push( year )
}
if( agrege.graph.visitors.graphMonths[ year ][ month ] ) {
agrege.graph.visitors.graphMonths[ year ][ month ] += 1
} else {
agrege.graph.visitors.graphMonths[ year ][ month ] = 1
agrege.graph.visitors.graphMonths.monthsLabels.push( month )
}
}
//traite graphe Year #visite
if( agrege.graph.visites.graphYears[ year ] ) {
agrege.graph.visites.graphYears[ year ] += 1
} else {
agrege.graph.visites.graphYears[ year ] = 1
agrege.graph.visites.graphYears.years.push( year )
agrege.graph.visites.graphMonths[ year ] = {}
agrege.graph.visites.graphMonths.years.push( year )
}
//traite graphe Month
if( agrege.graph.visites.graphMonths[ year ][ month ] ) {
agrege.graph.visites.graphMonths[ year ][ month ] += 1
} else {
agrege.graph.visites.graphMonths[ year ][ month ] = 1
agrege.graph.visites.graphMonths.monthsLabels.push( month )
}
//traite graphe Days of week
if( agrege.graph.visites.graphDayOfWeek[ dayow ] ) {
agrege.graph.visites.graphDayOfWeek[ dayow ] += 1
} else {
agrege.graph.visites.graphDayOfWeek[ dayow ] = 1
agrege.graph.visites.graphDayOfWeek.labels.push( dayow )
}
//traite graphe Hour of day
if( agrege.graph.visites.graphHourOfDay[ hourod ] ) {
agrege.graph.visites.graphHourOfDay[ hourod ] += 1
} else {
agrege.graph.visites.graphHourOfDay[ hourod ] = 1
agrege.graph.visites.graphHourOfDay.labels.push( hourod )
}
}
} )
jsonfile.writeFileSync( `${config.tribes}/${tribeid}/tags/stats/data.json`, agrege.data, 'utf-8' );
jsonfile.writeFileSync( `${config.tribes}/${tribeid}/tags/stats/graph.json`, agrege.graph, 'utf-8' );
return { status: 200, payload: { info: [ 'Statsupdated' ], model: 'Tags' } }
}
//console.log(Tags.dataloadstat('yes'));
/*const ar = [
[1, 1],
[1, 2]
]
console.log(ar.some(el => el[0] == 1 && el[1] == 1))
console.log(ar.some(el => el == [1, 3]))
*/
module.exports = Tags;

353
models/Tribes.js Executable file
View File

@ -0,0 +1,353 @@
const bcrypt = require( 'bcrypt' );
const fs = require( 'fs-extra' );
const path = require( 'path' );
const glob = require( 'glob' );
const Mustache = require( 'mustache' );
const execSync = require( 'child_process' )
.execSync;
const dnsSync = require( 'dns-sync' );
const jwt = require( 'jwt-simple' );
const moment = require( 'moment' );
const UUID = require( 'uuid' );
const Outputs = require( './Outputs.js' );
const Pagans = require( './Pagans.js' );
const config = require( '../tribes/townconf.js' );
const checkdata = require( `${config.tribes}/${config.mayorId}/www/cdn/public/js/checkdata` );
/*
tribeid manager
/tribes/tribeid
Manage a tribeid space
* create
* update by managing option and contract
* delete a tribeid
* check accountability and
*/
const Tribes = {};
Tribes.init = () => {
console.group( 'init Tribes' );
let tribeids = [];
let routes = glob.sync( './routes/*.js' )
.map( f => {
return { url: `/${path.basename(f,'.js')}`, route: f }
} );
let DOMs = [];
let appname = {};
TribesGlobalConfig = glob.sync( `${config.tribes}/**/clientconf.json` )
.map( f => {
const conf = fs.readJSONSync( f );
// check if plugins exist and add it in .plugins of each tribeid conf
conf.plugins = glob.sync( `${config.tribes}/${conf.tribeid}/plugins/**/package.json` )
.map( p => {
const pack = fs.readJsonSync( p, 'utf8' );
routes.push( { url: `/${pack.name}`, route: `${config.tribes}/${conf.tribeid}/plugins/${pack.name}/route.js` } );
return pack;
} );
//Add here any other info to get a global view and init
//...
tribeids.push( conf.tribeid );
DOMs = [ ...new Set( [ ...DOMs, ...conf.allowedDOMs ] ) ];
if( conf.website ) appname[ conf.tribeid ] = Object.keys( conf.website )
return conf;
} );
// store global conf fofs.existsSync( `${config.tmp}/clientconfglob.json` )r sharing to other api
fs.outputJsonSync( `${config.tmp}/clientconfglob.json`, TribesGlobalConfig, {
spaces: 2
} );
return { tribeids, routes, DOMs, appname }
}
Tribes.create = ( data ) => {
/* data = clientconf.json
{
"tribeid": "apixtribe",
"genericpsw": "Trze3aze!",
"website": {
"presentation":"https://www.apixtribe.org",
"webapp": "https://webapp.apixtribe.org"
},
"allowedDOMs": ["local.fr", "localhost:9002", "ndda.fr", "apixtribe.org"],
"clientname": "apixtribe",
"clientlogo": "",
"geoloc": [],
"useradmin": {PUBKEY:"",EMAIL:"",LOGIN:"adminapixtribe",UUID:"adminapixtribe"},
"smtp": {
"emailFrom": "support@apixtribe.org",
"emailcc": [],
"service": "gmail",
"auth": {
"user": "antonin.ha@gmail.com",
"pass": "Ha06110"
}
},
"accepted-language": "fr,en",
"langueReferential": ["fr"]
}
What about:
"tribeid": same than the folder where all the client's file are stored
"genericpsw": a generic password for new user need upper lowercase number ans special char
"dnsname": a domain name belonging to the client
"subdns": "www", a sub domain subdns.dnsname give a public web access to
"website": { keywebsite:url}, give access to config.tribes/tribeid/www/keywebsite/index.html,
"allowedDOMs": ["local.fr", "localhost:9002", "nnda.fr"], //for CORS, @TODO generate from prévious URL this allow this apixtribe instance to be accessible
"clientname": Name of the organisation if any,
"clientlogo": logo of the organisation if any,
"geoloc": [], if any
"useradmin": { this is the 1st user create automaticaly to make gui available for the 1st user
"PUBKEY":public key to be authentify without an email,
"EMAIL":user email, we need at least one of authentification set up after the user can use both or only one
"LOGIN": login to use for access admintribeid,
"UUID": unique id normaly UUID but a uuid admintribeid is the same person in any apixtribe instance so we use it by convention.
"xlang": lang used by this user
},
"smtp": { smtp used to send email by nodemailer lib basic example with a google account
"emailFrom": "support@xx.fr",
"emailcc": [],
"service": "gmail",
"auth": {
"user": "antonin.ha@gmail.com",
"pass": "Ha06110"
}
},
"accepted-language": "fr,en", list of accepted-language in terme of http request.
"langueReferential": ["fr"], list of the text that have to be translate in referentials
}
*/
//update tmp/confglog.json
const dataclient = Tribes.init();
//return in prod all instance apxinfo={tribeids:[],logins:[]}
// in dev return only local
//check tribeid name is unique
console.log( 'liste des tribeid', dataclient.tribeids )
if( dataclient.tribeids.includes( data.tribeid ) ) {
return { status: 403, payload: { model: "client", info: [ 'tribeidalreadyexist' ] } }
}
//loginsglob = {login:tribeid}
let loginsglob = {};
if( fs.existsSync( `${config.tmp}/loginsglob.json`, 'utf-8' ) ) {
loginsglob = fs.readJsonSync( `${config.tmp}/loginsglob.json`, 'utf-8' );
}
const logins = Object.keys( loginsglob );
if( logins.includes( data.useradmin.login ) ) {
return { status: 403, payload: { model: "client", info: [ 'loginalreadyexist' ] } }
}
fs.ensureDirSync( `${config.tribes}/${data.tribeid}` );
[ 'users', 'www', 'referentials', 'nationchains' ].forEach( r => {
fs.copySync( `${config.mainDir}/setup/tribes/apixtribe/${r}`, `${config.tribes}/${data.tribeid}/${r}` );
} )
fs.outputJsonSync( `${config.tribes}/${data.tribeid}/clientconf.json`, data );
const confcli = JSON.parse( Mustache.render( fs.readFileSync( `${config.mainDir}/setup/tribes/apixtribe/clientconf.mustache`, 'utf8' ), data ) );
fs.outputJsonSync( `${config.tribes}/${data.tribeid}/clientconf.json`, confcli );
return Pagans.createUser( {
xpaganid: "setup",
xworkon: data.tribeid,
xlang: data.useradmin.xlang
}, data.useradmin );
};
Tribes.archive = ( tribeid ) => {
//A faire zip un repertoire tribeid dans
// remove tribeid de data ou devdata
try {
fs.moveSync( `${config.tribes}/${tribeid}`, `${config.archivefolder}/${tribeid}` );
//update apixtribeenv
Tribes.init();
return { status: 200, payload: { info: [ 'deletetribeidsuccessfull' ], models: 'Tribes', moreinfo: "TODO see in Tribes.archive" } }
} catch ( err ) {
console.log( "Erreur d'archivage", err )
return { status: 403, payload: { info: [ 'archiveerror' ], models: 'Tribes', moreinfo: err } }
}
}
////////////// Manage file for Tribes
Tribes.checkaccessfolder = ( folder, typeaccessrequested, useraccessrights, useruuid ) => {
// check folder right
}
Tribes.checkaccessfiles = ( listfile, typeaccessrequested, useraccessrights, useruuid ) => {
// @listfile to check accessright on file or folder
// @typeaccessrequested on files R read or download, U for pdate, D for delete , O for owned a Owner has all rights RUD on its files
// @useraccessright from its account /userd/uuid.json
// @useruuid public uuid user
// return {'ok':[file auhtorized],'ko':[files not authorized]}
const checkauthlistfile = { 'ok': [], 'ko': [] }
let structf = []
let inforep = { file: {}, dir: {} }
let done;
for( const f of listfile ) {
done = false;
if( !fs.existsSync( `${config.tribes}/${f}` ) ) {
done = true;
checkauthlistfile.ko.push( f )
console.log( `${f} file does not exist` )
} else {
structf = f.split( '/' );
}
//on ckeck tribeid existe / tribeid/object/
if( !done &&
useraccessrights.data[ structf[ 0 ] ] &&
useraccessrights.data[ structf[ 0 ] ][ structf[ 1 ] ] &&
useraccessrights.data[ structf[ 0 ] ][ structf[ 1 ] ].includes( typeaccessrequested ) ) {
done = true;
checkauthlistfile.ok.push( f );
} else {
// check if in folder we have a.info.json .file[f].shared{useruuid:'CRUDO'}
console.log( 'structf', structf )
if( fs.existsSync( `${config.tribes}/${structf.slice(0,-1).join('/')}/.info.json` ) ) {
inforep = fs.readJsonSync( `${config.tribes}/${structf.slice(0,-1).join('/')}/.info.json`, 'utf8' )
}
console.log( `no accessrights for ${f} for ${useruuid} ` )
}
if( !done && inforep.file[ f ] && inforep.file[ f ] && inforep.file[ f ].shared && inforep.file[ f ].shared[ useruuid ] && inforep.file[ f ].shared[ useruuid ].includes( typeaccessrequested ) ) {
done = true;
checkauthlistfile.ok.push( f )
}
// If no authorization then ko
if( !done ) {
checkauthlistfile.ko.push( f )
}
} // end loop for
//console.log( 'checkauthlistfile', checkauthlistfile )
return checkauthlistfile;
}
Tribes.dirls = ( tribeid, dir ) => {
/*
Return list of file into tribeid/dir
*/
let comment = { src: `${tribeid}/${dir}`, file: {}, dir: {} };
if( fs.existsSync( `${config.tribes}/${tribeid}/${dir}/.info.json` ) ) {
comment = fs.readJsonSync( `${config.tribes}/${tribeid}/${dir}/.info.json`, 'utf-8' );
}
const listfile = []
const listdir = []
glob.sync( `${config.tribes}/${tribeid}/${dir}/*` )
.forEach( f => {
//console.log( f )
const stats = fs.statSync( f );
// console.log( stats )
if( stats.isFile() ) {
listfile.push( path.basename( f ) )
if( !comment.file[ path.basename( f ) ] ) {
comment.file[ path.basename( f ) ] = { tags: [], info: "", thumbb64: "" };
}
comment.file[ path.basename( f ) ].mtime = stats.mtime;
comment.file[ path.basename( f ) ].ctime = stats.ctime;
comment.file[ path.basename( f ) ].size = stats.size;
}
if( stats.isDirectory() ) {
listdir.push( path.basename( f ) )
if( !comment.dir[ path.basename( f ) ] ) {
comment.dir[ path.basename( f ) ] = { tags: [], info: "", thumbb64: "" }
}
comment.dir[ path.basename( f ) ].nbfile = glob.sync( `${f}/*.*` )
.length;
comment.dir[ path.basename( f ) ].mtime = stats.mtime;
comment.dir[ path.basename( f ) ].ctime = stats.mtime;
console.log( 'comment.dir', comment.dir )
}
} );
// on remove les file or dir that was deleted
Object.keys( comment.file )
.forEach( f => {
if( !listfile.includes( f ) ) delete comment.file[ f ]
} )
Object.keys( comment.dir )
.forEach( d => {
if( !listdir.includes( d ) ) delete comment.dir[ d ]
} )
//console.log( comment )
fs.outputJson( `${config.tribes}/${tribeid}/${dir}/.info.json`, comment, 'utf-8' );
return { status: 200, payload: { info: [ 'succestogetls' ], models: 'Tribes', moreinfo: comment } }
};
Tribes.addspaceweb = ( data ) => {
/*
To create a public spaceweb accessible from https://dnsname/pageindex
input:
{dnsname:["archilinea.fr","www.archilinea.fr"], 1st is tha main dns other are just servername redirection
tribeid:"archilinea", from req.session.header.xworkon
website:"presentation",
pageindex:"app_index_fr.html"
mode:dev(local no ssl) | prod(IP + ssl)
}
output:
nginx conf and ssl to serve each https://dnsname to /{tribeid}/www/app/{website}
Carefull this action is executed with root and restart nginx + apixtribe to work
*/
data.configdomain = config.tribes;
data.porthttp = config.porthttp;
console.assert( config.loglevel == "quiet", 'data to create spaceweb:', data );
// create spaceweb app for tribeid/www/app/website/pageindexname.html
if( !fs.existsSync( `${config.tribes}/${data.tribeid}/www/app/${data.website}` ) ) {
fs.outputFileSync( `${config.tribes}/${data.tribeid}/www/app/${data.website}/${data.pageindex}`, `<h1>Hello ${data.tribeid} ${data.website} onto ${data.dnsname.join(',')}`, 'utf-8' )
}
//create logs folder
fs.ensureDirSync( `${config.tribes}/${data.tribeid}/logs/nginx` );
// add nginx http config
const confnginx = fs.readFileSync( 'setup/nginx/modelwebsite.conf.mustache', 'utf-8' );
fs.outputFileSync( `/etc/nginx/conf.d/${data.dnsname[0]}.conf`, Mustache.render( confnginx, data ), 'utf-8' );
if( data.mode == "dev" ) {
//add in /etc/hosts
let hosts = fs.readFileSync( '/etc/hosts', 'utf8' );
let chg = false;
data.dnsname.forEach( d => {
if( !hosts.includes( `127.0.0.1 ${d}` ) ) {
hosts += `\n127.0.0.1 ${d}`;
chg = true;
}
if( chg ) {
fs.outputFileSync( '/etc/hosts', hosts, 'utf8' )
}
} );
};
//Ckeck dns respond
data.dnsname.forEach( d => {
if( !dnsSync.resolve( `${d}` ) ) {
rep += `\nresolving ${d} will not responding valid IP, please setup domain redirection IP before runing this script`
}
} )
//update clienconf.json
const clientconf = fs.readJsonSync( `${config.tribes}/${data.tribeid}/clientconf.json` );
clientconf.website[ data.website ] = data.dnsname[ 0 ];
//merge allowedDOMs in unique concat
clientconf.allowedDOMs = [ ...new Set( ...clientconf.allowedDOMs, ...data.dnsname ) ];
fs.outputJsonSync( `${config.tribes}/${data.tribeid}/clientconf.json`, clientconf, 'utf-8' );
if( !data.setup ) {
// in setup apixtribe is not running and it will be start manually at the 1st run
// in other case need to plan a restart for CORS
setTimeout( Tribes.restartapixtribe, 300000, data.clienId );
}
const nginxrestart = execSync( `sudo systemctl restart nginx` )
.toString();
console.log( 'Restart nginx', nginxrestart )
if( data.mode == "prod" ) {
// get ssl certificate ATTENTION il faut ajouter -d devant chaque domain qui redirige vers l'espace web.
const certbot = execSync( `sudo certbot --nginx -d ${data.dnsname.join(' -d ')}` )
.toString();
console.log( 'certbot is running A CHECKER POUR UNE VRAIE PROD ????', certbot )
}
//sh execution to update change requested
return {
status: 200,
payload: {
model: "Tribes",
info: [ 'webspacecreated' ],
moreinfo: "Space web well created"
}
};
}
Tribes.restartapixtribe = ( tribeid ) => {
console.log( 'A restarting was requested 5mn ago from a new spacedev for ' + tribeid )
execSync( 'yarn restartpm2' );
}
module.exports = Tribes;

152
models/UploadFiles.js Executable file
View File

@ -0,0 +1,152 @@
const fs = require( 'fs-extra' );
const path = require( 'path' );
const formidable = require( 'formidable' );
const jsonfile = require( 'jsonfile' );
const mustache = require( 'mustache' );
const config = require( '../tribes/townconf.js' );
/*
model A SUPPRIMER !!!!!!!!!!!!!!!!!!!!!!
les functions d'upload de file et de droits d'accès doivent être gérer dans Tribes
*/
const UploadFiles = {};
UploadFiles.get = function ( filename, header ) {
// check file exist
const file = `${config.tribes}/${header.xworkon}/${filename}`;
// console.log('fichier demande ', file);
if( !fs.existsSync( file ) ) {
// console.log('le fichier demande n existe pas ', file);
return {
status: 404,
payload: { info: [ 'fileUnknown' ], model: 'UploadFiles' }
};
} else {
console.log( 'envoie le fichier ', file );
return {
status: 200,
payload: { info: [ 'fileknown' ], model: 'UploadFiles', file: file }
};
}
};
UploadFiles.addjson = function ( data, header ) {
/*
Le header = {X-WorkOn:"",destinationfile:"", filename:""}
Le body = {jsonp:{},callback:function to launch after download,'code':'mot cle pour verifier que le fichier est à garder'}
*/
// console.log(req.body.jsonp);
try {
jsonfile.writeFileSync( header.destinationfile + '/' + header.filename, data.jsonp );
if( data.callback ) {
const execCB = require( `${config.mainDir}/models/tribeid/${
header.xworkon
}` );
execCB[ data.callback ]();
}
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: header.destinationfile,
filename: header.filename
}
}
};
} catch ( err ) {
console.log( 'Impossible de sauvegarder le fichier, A COMPRENDRE', err );
return {
status: 503,
payload: { info: [ 'savingError' ], model: 'UploadFiles' }
};
}
};
UploadFiles.add = function ( req, header ) {
const form = new formidable.IncomingForm();
console.log( 'req.headers', req.headers );
console.log( 'req.params', req.params );
console.log( 'req.query', req.query );
console.log( 'req.body', req.body );
let destinationfile = `${config.tribes}/${header.xworkon}/${
header.destinationfile
}`;
form.parse( req, function ( err, fields, files ) {
console.log( 'files', files.file.path );
console.log( 'fields', fields );
const oldpath = files.file.path;
destinationfile += '/' + files.file.name;
console.log( 'oldpath', oldpath );
console.log( 'destinationfile', destinationfile );
fs.copyFile( oldpath, destinationfile, function ( err ) {
if( err ) {
console.log( err );
return {
status: 500,
payload: { info: [ 'savingError' ], model: 'UploadFiles' }
};
} else {
console.log( 'passe' );
fs.unlink( oldpath );
return {
status: 200,
payload: {
info: [ 'wellUpload' ],
model: 'UploadFiles',
render: {
destination: destinationfile
}
}
};
}
} );
} );
};
UploadFiles.updateEvent = function ( domainId, eventId, event ) {
// checkAndCreateNeededDirectories(domainId);
const eventsFile = `${config.tribes}/${domainId}/actions/events/events.json`;
if( !fs.existsSync( eventsFile ) ) {
jsonfile.writeFileSync( eventsFile, {} );
return { status: 404, payload: 'You have not any events.' };
}
const events = jsonfile.readFileSync( eventsFile );
if( !events.hasOwnProperty( eventId ) ) {
return {
status: 404,
payload: 'The event you are trying to update does not exist.'
};
}
events[ eventId ] = {
eventName: event.eventName,
eventDate: event.eventDate,
eventDescription: event.eventDescription
};
jsonfile.writeFileSync( eventsFile, events, { spaces: 2 } );
return {
status: 200,
payload: events
};
};
UploadFiles.deleteEvent = function ( domainId, eventId ) {
// checkAndCreateNeededDirectories(domainId);
const eventsFile = `${config.tribes}/${domainId}/actions/events/events.json`;
if( !fs.existsSync( eventsFile ) ) {
jsonfile.writeFileSync( eventsFile, {} );
return { status: 404, payload: 'You have not any events.' };
}
const events = jsonfile.readFileSync( eventsFile );
if( events.hasOwnProperty( eventId ) ) {
delete events[ eventId ];
jsonfile.writeFileSync( eventsFile, events, { spaces: 2 } );
return {
status: 200,
payload: events
};
} else {
return {
status: 404,
payload: 'The event you are trying to delete does not exist.'
};
}
};
module.exports = UploadFiles;

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>ANTS Nation</title>
<link rel="stylesheet" href="static/css/style.css">
<script src="static/js/script.js"></script>
</head>
<body>
<p> Info sur la nation des ANTS leur ville pour les rejoindre</p>
<p> </p>
</body>
</html>

View File

@ -0,0 +1,72 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>apiXtribe pourquoi comment</title>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<p> Info sur les nations (avec leur contrat accessible) et sur la town ainsi que les statistiques des tribes et de la town</p>
<p>
**An answer to our crazy world:**
Few peoples want to control value create by mass people, by using technology (gafam, gov, ...).
This DAO aim to serve any tribe that wants to free themself by creating their own organisation/rules into blockchain (cipher to keep data privacy, crypto to dev trust behind anonymity, web and open source to decentralize and build an anti-fragile system until electricity is their).
Our occidental society (globalization) wants us more and more money to control us (they say to protect us from ourself). Fine for, cozy chicken that accept to be eat when their master decides it. But not, **for free human that wants to apply consensual rules between them without any man in the middle**.
To make us under control, our masters print money and force us to breath it. We have no choice; they control institutions, they can stole our properties or our freedom, they drive the single thought. Forget strke and demonstration, to rebalance and create counter-powers, an alternative is to earn the minimum of their money to stay under their radar and keep our ressources (time, energy) to learn, create and share value for our tribes of friends, family, or people that share common view...
A DAO, by design (the blockchain) cannot be control by a central power, because it is a common good, each actor has a benefit and is free to apply fair rules.<br>
apiXtribes aims to allow any group of people to define rules to apply for their exchange of values.
With our master's money to create value you have to respect their rules (a register company, list of standards, list of taxes, ...) in exchange you benefit of institutions like protections (police with the legitimate violence), justice (law), school, medical, ... If you are a good boy then master give you back cash grant.
If you agree with the master's world, and happy to let institution decide for you, this is fine (apiXtribe is not for you). But if you consider that diversity is more resilient than standard and **you want to have choices then this project is for you.** Who knows the futur ;-) apiXtribe could be use to run the democratie (demos kratos: by the people for the people).
## Why money is the sinews of war
Our masters killed social ties by implementing the KPI (Key Performance Indicator) to quantify the value of each of us base on their rules. Their money is magic, they use as KPI of an individual value as well as the tools to drive our life. In occident, who can survive today without money in his pocket or in master's bank?
By **disconnecting value of money with gold or other "physical limit"** the money just value what they decide.<br>
apiXtribe use a crypto (that looks like a money but is not a money) to value each rule application to create a proof of work and at least a proof of stake that any human involve has an interest to protect it.
Then rules (**contracts**) can be set by anyone based on **an algo like if/then** between 2 humans. When the condition is trig then something is register in the blockchain.
```
# Example of rule between A & B to exchange an object description O that B own :
# B create a rule: send Object O against X with process (A is free to act or not depending of B reputation into the community where the rule is set):
if ("A pay/send X to B") then
"B send O to A"
if "A do not receive O after n days" or "O does not fit the object description" then
"A send a bad feedback on B that is written in blockchain"
if "B get a bad feedback" then
"B have to pay back 50% of X to the commuinity master"
if ("A do not receive O after n days" and "B does not give back X to A" and "B does not pay after n days the 50% of X") or "B have an average of bad feedback with at least 5 transactions" then
"B is baned of the community"
```
**apiXtribe blockchain will register this standard "contracts" and "reputation" of those who respects or not contracts. This cannot be change by any one and any one can read it.**<br>
A and B are just unique identificator (anonymous or not), to prove a human own A or B, apiXtribe use cryptograpĥic PGP based on public/private key. apiXtribe needs a network topology to be strong enough to avoid central control. This is why apiXtribe recommand rewarding rules to actor that provide electricity, machine, network access. Virtual world exist because physical one exist.
Until you stay **in crypto world, you just exchange data so no tax** (no master's rules). You can sale a service against money until you respect master/country's rule of this money (company that collect value tax, register turnover, ...). Then you can deliver crypto to your customer to consume your's or other's services when they want. If you exchange token against gov money then you have to respect your gov's laws (some country's law ask you to pay tax onto added value). So be aware of what you are doing some gov can kill you or put you in jail to exchange their money or data. This Xtrib coin is not for speculation is for social link without man in the middle that stole you.
</p>
<p>
**As a non tech,** you can invest and request to get your own nation or town (become a mayor) or tribe (become a druid). As non tech you'll need the help of a dev that will help you to modelise and setup Contracts. Any way you can also simply join any tribe as pagan and sales data/product and exchange it again Xtrib coin of a nation.
For non tech, if you have a project please request it to [support@apixtribe.org](mailto:support@apixtribe.org)
**As tech**:
**- As sysadmin** you can install a town, for yourself or for a "non tech" and earn Xtrib in exchange<br>
**- As back-end and front-end dev** you can dev web service (node.js express static web based on pwa) that you can sale to a druid or a mayor, or become yourself a druid and host a town to sale usage against Xtribe.<br>
**- As a former** you can create tuto or training to explain how to install an apiXtribe node, how to dev quickly an app with a setup. This is agnostic about the front-end pwa framework, react, vue, angular, .. . For the back-end is suitable for node.js dev but you can use any linux command to produce something to deliver accross apiXtribe.<br>
**- As a newbie** you can join this open source project to learn how to dev as a part of the team and to increase your portfolio of node.js/linux project.
We strongly believe in the cypher punk philosophy french :[cypher punk manifesto](https://dev.ndda.fr/cypherpunkmanifestofrench.html) english: [cypher punk manifesto](https://dev.ndda.fr/cypherpunkmanifestoenglish.html).
As tech, if this sound to you please join us by register in [sign-in gitea](https://gitea.ndda.fr/user/sign_up) and we'll come back to you.
To exchange with the community on discord: [https://discord.gg/SQ8YAqJgZV](https://discord.gg/SQ8YAqJgZV)
</p>
</body>
</html>

View File

@ -0,0 +1,185 @@
/*
This module have to be independant of any external package
it is shared between back and front and is usefull
to apply common check in front before sending it in back
can be include in project with
<script src="https://apiback.maildigit.fr/js/checkdata.js"></script>
or with const checkdata = require('../public/js/checkdata.js')
*/
// --##
const checkdata = {};
// each checkdata.test. return true or false
checkdata.test = {};
checkdata.test.emailadress = ( ctx, email ) => {
const regExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regExp.test( email );
};
/*
* @emaillist = "email1,email2, email3"
* it check if each eamil separate by , are correct
*/
checkdata.test.emailadresslist = ( ctx, emaillist ) => {
//console.log(emaillist.split(','))
if( emaillist.length > 0 ) {
const emails = emaillist.split( ',' );
for( var i in emails ) {
//console.log(emails[i])
if( !checkdata.test.emailadress( "", emails[ i ].trim() ) ) {
return false
}
}
};
return true;
};
checkdata.test.password = ( ctx, pwd ) => {
const regExp = new RegExp(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/
);
return regExp.test( pwd );
};
checkdata.test.required = ( ctx, val ) =>
( val != null && val != 'undefined' && val.length > 0 ) || ( !!val && val.constructor === Array && val.length > 0 ) || ( !!val && val.constructor === Object && Object.keys( val )
.length > 0 );
checkdata.test.isNumber = ( ctx, n ) => typeof n === 'number';
checkdata.test.isInt = ( ctx, n ) => n != '' && !isNaN( n ) && Math.round( n ) == n;
checkdata.test.isFloat = ( ctx, n ) => n != '' && !isNaN( n ) && Math.round( n ) != n;
checkdata.test.unique = ( ctx, val ) => {
if( ctx.list[ ctx.currentfield ] ) {
return !ctx.list[ ctx.currentfield ].includes( val );
} else {
console.log( 'ERR no list for field:' + ctx.currentfield );
return false;
}
};
checkdata.test.isDateDay = ( ctx, dateDay ) => true;
/* checkdata.test.filterInvalidInArray = (array, validate) =>
array ? array.filter(el => !validate(el)) : true;
// return true when every elements is valid
*/
checkdata.test.postalCode = ( ctx, postalCode ) => {
if( postalCode.length == 0 ) return true;
const regExp = new RegExp( /(^\d{5}$)|(^\d{5}-\d{4}$)/ );
return regExp.test( postalCode );
};
/**
* PHONE
*/
checkdata.test.phoneNumber = ( ctx, phoneNumber ) => {
if( phoneNumber.length == 0 ) return true;
phoneNumber = phoneNumber.trim()
.replace( /[- .]/g, '' )
//french number
const regExpfr = new RegExp( /^0[1-9][0-9]{9}$/ );
const regExpInternational = new RegExp( /^\+*(\d{3})*[0-9,\-]{8,}/ );
return regExpfr.test( phoneNumber ) || regExpInternational.test( phoneNumber );
};
/*
* @phonelist = "phone1,phone2,phone3"
* it check if each phone separate by , are correct
*/
checkdata.test.phoneNumberlist = ( ctx, phonelist ) => {
//console.log(emaillist.split(','))
if( phonelist.length > 0 ) {
const phones = phonelist.split( ',' );
for( var i in phones ) {
//console.log(emails[i])
if( !checkdata.test.phoneNumber( "", phones[ i ].trim() ) ) {
return false
}
}
};
return true;
};
// checkdata.normalize take a correct data then reformat it to harmonise it
checkdata.normalize = {};
checkdata.normalize.phoneNumber = ( ctx, phone ) => {
phone = phone.trim()
.replace( /[- .]/g, '' );
if( checkdata.test.phoneNumber( '', phone ) && phone.length == 10 && phone[ 0 ] == "0" ) {
phone = '+33 ' + phone.substring( 1 );
}
return phone;
}
checkdata.normalize.upperCase = ( ctx, txt ) => txt.toUpperCase();
checkdata.normalize.lowerCase = ( ctx, txt ) => txt.toLowerCase();
// fixe 10 position et complete par des 0 devant
checkdata.normalize.zfill10 = ( ctx, num ) => {
let s = num + '';
while( s.length < 10 ) s = '0' + s;
return s;
};
/*let tt = "+33 1 02.03 04 05";
console.log(checkdata.test.phoneNumber('', tt))
console.log(checkdata.normalize.phoneNumber('', tt))
*/
checkdata.evaluate = ( contexte, referential, data ) => {
/*
* contexte object {} with full info for evaluation
* file referential path to get object to apply
* data related to object
- return {validefor =[keyword of error] if empty no error,
clean data eventually reformated
updateDatabase}
*/
console.log( 'contexte', contexte );
console.log( 'referentiel', referential );
console.log( 'data', data );
const invalidefor = [];
const objectdef = {};
const listfield = referential.map( ch => {
objectdef[ ch.idfield ] = ch;
return ch.idfield;
} );
Object.keys( data )
.forEach( field => {
if( !listfield.includes( field ) ) {
// some data can be inside an object with no control at all
// they are used for process only
// i leave it in case it will become a non sens
// invalidefor.push('ERRFIELD unknown of referentials ' + field);
} else {
if( objectdef[ field ].check ) {
// check data with rule list in check
objectdef[ field ].check.forEach( ctrl => {
console.log( 'ctrl', ctrl );
contexte.currentfield = field;
if( !checkdata.test[ ctrl ] ) {
invalidefor.push( 'ERR check function does not exist :' + ctrl + '___' + field )
} else {
if( !checkdata.test[ ctrl ]( contexte, data[ field ] ) )
invalidefor.push( 'ERR' + ctrl + '___' + field );
}
} );
}
if( objectdef[ field ].nouserupdate ) {
// check if user can modify this information
console.log(
'evaluation :' + field + ' -- ' + objectdef[ field ].nouserupdate,
eval( objectdef[ field ].nouserupdate )
);
const evalright = eval( objectdef[ field ].nouserupdate );
objectdef[ field ].nouserupdate = evalright;
}
}
} );
console.log( {
invalidefor,
data
} );
return {
invalidefor,
data
};
};
if( typeof module !== 'undefined' ) module.exports = checkdata;

View File

@ -0,0 +1,604 @@
/* eslint-disable no-useless-escape */
const fs = require( 'fs' );
const path = require( 'path' );
const bcrypt = require( 'bcrypt' );
const moment = require( 'moment' );
const config = require( '../config' );
const utils = {};
console.log( "Check in /utils/index.js to find usefull function for your dev.\n Feel free to send suggestion, code to maintainer of apixtribe project (see /package.json to get email).\n We'll add to the roadmap to add it." );
/**
* EMAIL
*/
/* const validateEmail = email => {
const regExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return regExp.test(email);
};
const validatePassword = pwd => {
const regExp = new RegExp(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&.])[A-Za-z\d$@$!%*?&.{}:|\s]{8,}/
);
return regExp.test(pwd);
};
const filterInvalidInArray = (array, validate) =>
array ? array.filter(el => !validate(el)) : undefined; // return undefined when every elements is valid
/**
* POSTAL CODE
*/
/*
const validatePostalCode = postalCode =>
/(^\d{5}$)|(^\d{5}-\d{4}$)/.test(postalCode);
/**
* PHONE
*/
/* const validatePhoneNumber = phoneNumber =>
/((^0[1-9]|\+[0-9]{3})([-. ]?[0-9]{2}){4}$)/.test(phoneNumber);
const correctPhoneNumber = phone =>
phone[0] === '0' ? '+33' + phone.substr(1) : phone;
const checkData = (appProfil, referential, data) => {
// @TODO get a referentiel per object then check data validity and allowed access
// need to add referentiel manager
const invalidefor = [];
let updateDatabase = false;
Object.keys(data).forEach(field => {
switch (field) {
case 'token':
updateDatabase = true;
break;
case 'email':
if (!validateEmail(data.email)) {
invalidefor.push('ERREMAIL:' + field);
} else {
updateDatabase = true;
}
break;
case 'password':
if (!validatePassword(data.password)) {
invalidefor.push('ERRPWD:' + field);
} else {
data.password = bcrypt.hash(data.password, config.saltRounds);
updateDatabase = true;
}
break;
}
});
return { invalidefor, data, updateDatabase };
};
*/
//Permet d'attendre en milliseconde
// s'utilise avec async ()=>{
// await sleep(2000)
//}
utils.sleep = ( ms ) => {
return new Promise( resolve => setTimeout( resolve, ms ) );
}
utils.generemdp = ( nbpos ) => {
const chaine = "ABCDEFGHIJKLMNPQRSTUVWZY123456789";
let mdp = "";
for( var i = 0; i < nbpos; i++ ) {
var pos = Math.floor( Math.random() * chaine.length );
mdp += chaine.substring( pos, pos + 1 );
}
return mdp;
}
utils.generecompteur = ( filecpt, typeincrement ) => {
let file = `${filecpt}/${typeincrement}.json`;
let prefix = "";
if( typeincrement = "ANNEESEMAINE" ) {
file = `${filecpt}/${typeincrement}${moment().format('YYYY')}${moment().format('WW')}.json`
prefix = `${moment().format('YYYY')}${moment().format('WW')}`
}
let num = 1;
try {
num = parseInt( fs.readFileSync( file, 'utf8' ) ) + 1;
} catch ( err ) {
console.log( "Nouveau compteur incrementale ", file )
}
fs.writeFileSync( file, num, 'utf8' );
return prefix + num
}
/**
* CSV
*/
utils.json2csv = ( jsondata, options, callback ) => {
// uniquement json = [{niv1:val,niv1:[liste of val]}]
// console.log('_________________________');
// console.log(jsondata)
// console.log('_________________________');
if( jsondata.length == 0 ) {
return callback( "Empty json", null );
}
if( !options.retln ) options.retln = '\n';
if( !options.sep ) options.sep = ';';
if( !options.arraysplitsep ) options.arraysplitsep = ",";
if( !options.replacespecialcarJson2Csv ) {
options.replacespecialcarJson2Csv = []
} else {
if( typeof options.replacespecialcarJson2Csv == "string" ) {
//permet de passer des regex en string
options.replacespecialcarJson2Csv = eval( options.replacespecialcarJson2Csv );
}
};
let etat = "";
let csv = '';
let entete = '';
let prem = true;
for( const j in jsondata ) {
// console.log(jsondata[j])
for( const c in options.champs ) {
if( prem ) {
entete += options.champs[ c ] + options.sep;
}
if( jsondata[ j ][ options.champs[ c ] ] ) {
if( options.array.indexOf( options.champs[ c ] ) > -1 ) {
csv += jsondata[ j ][ options.champs[ c ] ].join( options.arraysplitsep ) + options.sep;
} else {
let currentValue = "";
if( jsondata[ j ][ options.champs[ c ] ] ) currentValue += jsondata[ j ][ options.champs[ c ] ];
options.replacespecialcarJson2Csv.forEach( re => {
//console.log(currentValue)
currentValue = currentValue.replace( re[ 1 ], re[ 0 ] )
} )
csv += currentValue + options.sep;
}
} else {
csv += options.sep;
}
}
csv = csv.substring( 0, csv.length - 1 ) + options.retln;
if( prem ) {
prem = false;
entete = entete.substring( 0, entete.length - 1 ) + options.retln;
// console.log(entete)
}
}
// return entete + csv;
if( etat == "" ) {
return callback( null, entete + csv );
} else {
return callback( etat, null );
}
};
/**
* Get headers from first line of CSV
* @param {array} lines array of string which contains each csv lines
* @return {array} string array of headers
*/
utils.getHeaders = ( lines, sep ) => lines[ 0 ].split( sep )
.map( i => i.replace( /"/g, '' ) );
/**
* [csv2json description]
* @param {object} csv object of csv file that has been read
* @param {object} options object containing csv options, headers, ...
{retln:'code de retour de ligne \n ou \n\r',
sep:'code to split cells',
champs:[ch1,ch2,...] catch only those field,
array:[ch1, ] can have more than one field champs with same name then data are push into an array }
* @param {Function} callback callback function
* @return {callback} - return an error if error, else return json
it convert a csv file into a json = [{field:value}]
Usage example:
fiche.csv2article = (err, fiche) => {
if (!err) {
console.log(fiche)
}
}
utils.csv2json(fs.readFileSync('./devdata/tribee/aubergenville/infoexterne/localbusiness.csv', 'utf-8'), {
retln: "\n",
sep: ";",
champs: ["NOM", "OBJET", "ADRESSE_PRO", "CP_PRO", "VILLE_PRO", "ZONE", "PHONE_PRO", "HORAIRESDESC", "HORAIREDATA", "URL", "FACEBOOK", "INSTA", "EMAIL_PRO", "IMG", "TAG"],
array: ["TAG", "PHONE_PRO", "EMAIL_PRO"]
}, fiche.csv2article)
*/
utils.replacecarbtweendblquote = ( csv, car, carremplacant ) => {
/*
return csv text with any car betwenn 2 " by CARSEPARATOR
*/
let newcsv = "";
let txtencours = "";
let flagouvert = false
const sepreg = new RegExp( `${car}`, 'gmi' )
for( let j = 0; j < csv.length; j++ ) {
//if((csv[j] == "\"" && csv[j + 1] && csv[j + 1] != "\"") || (csv[j] == "\"" && csv[j - 1] && csv[j - 1] != "\"") || (csv[j] == "\"" && csv[j - 1] && csv[j - 2] && csv[j - 1] != "\"" && csv[j - 2] != "\"")) {
if( csv[ j ] == "\"" ) {
if( flagouvert ) {
// on cherche à ferme une chaine de texte
if( csv[ j + 1 ] == "\"" ) {
//on a "" consecutif qu'on remplace par "" et on fait j+1
txtencours += "\"\""
j++
} else {
// on a bien une fermeture
flagouvert = false
newcsv += txtencours.replace( sepreg, carremplacant )
txtencours = "\""
}
} else {
// on ouvre une chaine
flagouvert = true
//on met le contenu précédent ds newcsv
newcsv += txtencours
txtencours = "\""
}
//} else if((csv[j] !== "\n") && (csv[j + 1] && csv[j] + csv[j + 1] !== "\n\r")) {
} else if( csv[ j ] !== "\n" ) {
txtencours += csv[ j ]
// } else if((csv[j] == "\n") || (csv[j + 1] && csv[j] + csv[j + 1] == "\n\r")) {
} else if( csv[ j ] == "\n" ) {
if( !flagouvert ) txtencours += "\n"
}
}
return newcsv + txtencours
}
utils.analysestring = ( string ) => {
let buftxt = ""
let bufcode = ""
let i = 0
let avecRL = false
for( let p = 0; p < string.length; p++ ) {
if( string[ p ].charCodeAt() == 10 ) {
buftxt += "[RL]"
avecRL = true
} else {
buftxt += string[ p ]
}
bufcode += "-" + string[ p ].charCodeAt();
if( i == 20 ) {
if( avecRL ) {
console.log( `${buftxt} - ${bufcode}` )
} else {
console.log( `${buftxt} ---- ${bufcode}` )
}
i = 0;
buftxt = ""
bufcode = ""
avecRL = false
}
i++;
}
}
const txtstring = `32932,BK_F2F_B_COM_10x1H-09,"My Communication Workshop ""Session N°9 - 1H""","<p>&nbsp;</p>
<table>
<tbody>
<tr>
<td>
<p>Learner who needs to develop their ability to communicate effectively at work, both in writing and speaking</p>
</td>
</tr>
</tbody>
</table>",,english,2,0,,2,0,classroom,"0000-00-00 00:00:00","0000-00-00 00:00:00",0000-00-00,0000-00-00,https://www.yesnyoulearning.com/lms/index.php?r=player&course_id=32932,1101,,"BUSINESS KEYS",0,
32933,BK_F2F_B_COM_10x1H-10,"My Communication Workshop Session N°10 - 1H","<p>&nbsp;</p>
<table>
<tbody>
<tr>
<td>
<p>Learner who needs to develop their ability to communicate effectively at work, both in writing and speaking</p>
</td>
</tr>
</tbody>
</table>",,english,2,0,,2,0,classroom,"0000-00-00 00:00:00","0000-00-00 00:00:00",0000-00-00,0000-00-00,https://www.yesnyoulearning.com/lms/index.php?r=player&course_id=32933,1101,,"BUSINESS KEYS",0,
32934,BK_F2F_B_JOB_10x1H-01,"My Job Search Workshop Session N°1 - 1H","<p>PACK JOB SEARCH</p>",,english,2,0,,2,0,classroom,,,0000-00-00,0000-00-00,https://www.yesnyoulearning.com/lms/index.php?r=player&course_id=32934,1108,,,0,
32935,BK_F2F_B_JOB_10x1H-02,"My Job Search Workshop Session N°2 - 1H","<p>PACK JOB SEARCH</p>",,english,2,0,,2,0,classroom,,,0000-00-00,0000-00-00,https://www.yesnyoulearning.com/lms/index.php?r=player&course_id=32935,1108,,,0,`
//utils.analysestring(txtstring)
//console.log(utils.replacecarbtweendblquote(txtstring, ",", 'CARSEPARATOR')
// .split("\n")[0].split(","))
utils.csv2json = ( csv, options, callback ) => {
// EN CAS DE PB AVEC UN FICHIER EXCEL RECALCITRANT
// l'ouvrir dans calc linux et sauvegarder csv utf8, ; , " enregistrer le contenu de la cellule comme affiché
console.log( '\n--------------- CSV2JSON ---------------\n' );
// Default CSV options
if( !options.retln ) options.retln = '\n';
if( csv.indexOf( '\n\r' ) > -1 ) options.retln = '\n\r';
if( !options.sep ) options.sep = ';';
//gestion d un separateur dans une chaine de texte
//const regseptext = new RegExp(`${options.sep}(?!(?:[^"]*"[^"]*")*[^"]*$)`, 'gm');
//csv = csv.replace(regseptext, "CARACSEPAR");
// csv = utils.replacecarbtweendblquote(csv, options.retln, "RETLIGNE")
csv = utils.replacecarbtweendblquote( csv, options.sep, "CARSEPARATOR" )
if( !options.replacespecialcarCsv2Json ) {
options.replacespecialcarCsv2Json = []
} else {
if( typeof options.replacespecialcarCsv2Json == "string" ) {
//permet de passer des regex en string
options.replacespecialcarCsv2Json = eval( options.replacespecialcarCsv2Json );
}
};
const result = [];
const lines = csv.split( options.retln );
const headers = utils.getHeaders( lines, options.sep );
let unknownHeaders = '';
//console.log('headers', headers)
//console.log('options.champs', options.champs)
headers.forEach( header => {
// Si un header n'est pas présent dans la liste des champs prédéfinis
// on l'ajoute aux champs inconnus
if( options.champs.indexOf( header ) === -1 ) {
unknownHeaders += `${header}, `;
}
} );
if( unknownHeaders !== '' ) {
const errorMsg = `CSV2JSON() - Champs inconnus : ${unknownHeaders}`;
return callback( errorMsg, null );
}
lines.forEach( ( line, index ) => {
// Skip headers line or empty lines
if( index === 0 || line.replace( /\s/g, '' )
.length === 0 ) {
return;
}
// pour debuguer on met origincsv pour voir la ligne d'origine
const currentLineData = { 'origincsv': line, 'linenumber': index };
const currentLine = line.split( options.sep ); // Current string in the line
for( let j = 0; j < headers.length; j++ ) {
// Si la ligne n'est pas vide
if( currentLine[ j ] ) {
// On clean le champs
// ajout eventuel de modification de caracter reservé ; dans les libelléetc...
let currentValue = currentLine[ j ].trim()
//on transforme le caractere separateur modifié entre double quote
currentValue = currentValue.replace( 'CARSEPARATOR', options.sep );
options.replacespecialcarCsv2Json.forEach( re => {
currentValue = currentValue.replace( re[ 0 ], re[ 1 ] )
} )
// Si le header est un email
if( headers[ j ].includes( 'EMAIL' ) ) {
// Supprimer tous les espaces
currentValue = currentLine[ j ].replace( /\s/g, '' );
}
// on check si le chamos doit être numerique
if( options.numericfield.includes( headers[ j ] ) ) {
currentValue = currentLine[ j ].replace( /\,/g, '.' );
try {
const test = parseFloat( currentValue );
} catch ( er ) {
return callback( `${headers[j]} contiens la valeur -${currentValue}- et devrait être numerique`, null );
}
}
if( currentValue ) {
// Si le header actuel est de type array
// Cela signifie que le header apparaît plusieurs fois dans le CSV
// et que les valeurs correspondantes à ce header
// doivent être mis dans un array
if( options.array && options.array.indexOf( headers[ j ] ) > -1 ) {
// Si le tableau pour ce header n'existe pas on le crée
if( !currentLineData[ headers[ j ] ] ) {
currentLineData[ headers[ j ] ] = [];
}
if( options.arraysplitsep ) {
currentValue.split( options.arraysplitsep )
.forEach( v => {
currentLineData[ headers[ j ] ].push( v );
} )
} else {
currentLineData[ headers[ j ] ].push( currentValue );
}
} else {
// Si un header est déjà présent pour la ligne
// alors que il n'est pas spécifié comme étant un array
// on retourne une erreur
if( currentLineData[ headers[ j ] ] ) {
const errorMsg = `Le champ ${
headers[j]
} est présent plusieurs fois alors qu'il n'est pas spécifié comme étant un array !`;
return callback( errorMsg, null );
}
currentLineData[ headers[ j ] ] = currentValue;
}
}
}
}
result.push( currentLineData );
} );
return callback( null, result );
};
/**
* [csvparam2json description]
* @param {object} csv object of csv file that has been read
* @param {object} options object containing csv options, headers, ...
{retln:'code de retour de ligne \n ou \n\r',
sep:'code to split cells',
champs:[ch1,ch2,...] catch only those field,
array:[ch1, ] can have more than one field champs with same name then data are push into an array }
* @param {Function} callback callback function
* @return {callback} - return an error if error, else return json
it converts a csv with 3 column col1;col2;col3 in a json in a tree
if in col1 we have __ => then it splits a leaf
col1 = xxxx__yyyy ; col2 = value ; col3 = comment that is ignored
return data = {xxxx:{yyyy:value}}
col1 = xxxx; col2 = value; col3 = comment ignored
return data = {xxxx:value}
Usage example:
fiche.csvparam2article = (err, fiche) => {
if (!err) {
console.log(fiche)
}
}
utils.csvparam2json(fs.readFileSync('./devdata/tribee/aubergenville/infoexterne/localbusiness.csv', 'utf-8'), {
retln: "\n",
sep: ";",
champs: ["NOM", "OBJET", "ADRESSE_PRO", "CP_PRO", "VILLE_PRO", "ZONE", "PHONE_PRO", "HORAIRESDESC", "HORAIREDATA", "URL", "FACEBOOK", "INSTA", "EMAIL_PRO", "IMG", "TAG"],
array: ["TAG", "PHONE_PRO", "EMAIL_PRO"]
}, fiche.csv2article)
*/
utils.csvparam2json = ( csv, options, callback ) => {
console.log( '\n--------------- CSVPARAM2JSON ---------------\n' );
let etat = "";
const param = {};
if( !options.retln ) {
options.retln = '\n';
}
if( csv.indexOf( '\n\r' ) > -1 ) {
options.retln = '\n\r';
}
if( !options.sep ) {
options.sep = ';';
}
if( !options.seplevel ) {
options.seplevel = "__";
}
if( !options.replacespecialcarCsv2Json ) {
options.replacespecialcarCsv2Json = []
} else {
if( typeof options.replacespecialcarCsv2Json == "string" ) {
//permet de passer des regex en string
options.replacespecialcarCsv2Json = eval( options.replacespecialcarCsv2Json );
}
};
const lines = csv.split( options.retln );
for( let i = 0; i < lines.length; i++ ) {
const infol = lines[ i ].split( options.sep )
//console.log(infol)
if( infol[ 0 ].length > 4 && infol.length < 2 ) {
// si le 1er element à plus de 4 caractere et s'il y a moins de 3 colonnes c'est qu'il y a un pb
etat += `Erreur sur ${lines[i]} moins de 3 column separé par ${options.sep}`;
continue;
}
// On ajoute ici la gestion de tous les caracteres spéciaux
// reservées pour le csv ; ' etc..'
if( infol[ 1 ] && infol[ 1 ] + "" == infol[ 1 ] ) {
options.replacespecialcarCsv2Json.forEach( re => {
//console.log("gggggggggggggggggggg", infol[1])
infol[ 1 ] = infol[ 1 ].replace( re[ 0 ], re[ 1 ] );
} )
// console.log(infol[1])
infol[ 1 ] = infol[ 1 ].replace( /'|/g, "\"" );
//console.log(infol[1])
if( infol[ 1 ].toLowerCase() === 'true' ) {
infol[ 1 ] = true;
} else if( infol[ 1 ].toLowerCase() === 'false' ) {
infol[ 1 ] = false;
}
}
console.log( infol[ 1 ] )
//supprime des lignes vides
if( infol[ 0 ] == '' ) continue;
if( infol[ 0 ].indexOf( options.seplevel ) == -1 ) {
param[ infol[ 0 ] ] = infol[ 1 ]
continue;
} else {
const arbre = infol[ 0 ].split( options.seplevel )
switch ( arbre.length ) {
case 1:
param[ arbre[ 0 ] ] = infol[ 1 ];
break;
case 2:
if( arbre[ 1 ] != "ARRAY" ) {
if( !param[ arbre[ 0 ] ] ) param[ arbre[ 0 ] ] = {};
param[ arbre[ 0 ] ][ arbre[ 1 ] ] = infol[ 1 ];
} else {
if( !param[ arbre[ 0 ] ] ) param[ arbre[ 0 ] ] = [];
//console.log('aff', infol[1].substring(1, infol[1].length - 1).replace(/""/g, '"'))
eval( "result=" + infol[ 1 ] )
//.substring(1, infol[1].length - 1).replace(/""/g, '"'))
param[ arbre[ 0 ] ].push( result )
}
break;
case 3:
if( arbre[ 2 ] != "ARRAY" ) {
if( !param[ arbre[ 0 ] ] ) param[ arbre[ 0 ] ] = {};
if( !param[ arbre[ 0 ] ][ arbre[ 1 ] ] ) param[ arbre[ 0 ] ][ arbre[ 1 ] ] = {};
param[ arbre[ 0 ] ][ arbre[ 1 ] ][ arbre[ 2 ] ] = infol[ 1 ];
} else {
if( !param[ arbre[ 0 ] ] ) param[ arbre[ 0 ] ] = {};
if( !param[ arbre[ 0 ] ][ arbre[ 1 ] ] ) param[ arbre[ 0 ] ][ arbre[ 1 ] ] = [];
//eval("result = \"test\"");
//console.log(result);
eval( "result=" + infol[ 1 ] );
//.substring(1, infol[1].length - 1).replace(/""/g, '"'))
param[ arbre[ 0 ] ][ arbre[ 1 ] ].push( result )
}
break;
case 4:
if( arbre[ 3 ] != "ARRAY" ) {
if( !param[ arbre[ 0 ] ] ) param[ arbre[ 0 ] ] = {};
if( !param[ arbre[ 0 ] ][ arbre[ 1 ] ] ) param[ arbre[ 0 ] ][ arbre[ 1 ] ] = {};
if( !param[ arbre[ 0 ] ][ arbre[ 1 ] ][ arbre[ 2 ] ] ) param[ arbre[ 0 ] ][ arbre[ 1 ] ][ arbre[ 2 ] ] = {};
param[ arbre[ 0 ] ][ arbre[ 1 ] ][ arbre[ 2 ] ][ arbre[ 3 ] ] = infol[ 1 ];
} else {
if( !param[ arbre[ 0 ] ] ) param[ arbre[ 0 ] ] = {};
if( !param[ arbre[ 0 ] ][ arbre[ 1 ] ] ) param[ arbre[ 0 ] ][ arbre[ 1 ] ] = {};
if( !param[ arbre[ 0 ] ][ arbre[ 1 ] ][ arbre[ 2 ] ] ) param[ arbre[ 0 ] ][ arbre[ 1 ] ][ arbre[ 2 ] ] = [];
eval( "result=" + infol[ 1 ] )
//.substring(1, infol[1].length - 1).replace(/""/g, '"'))
param[ arbre[ 0 ] ][ arbre[ 1 ] ][ arbre[ 2 ] ].push( result )
break;
}
default:
break;
}
}
}
// JSON.parse(JSON.stringify(param))
console.log( 'kkkkkkkkkkkkkkkkkk', param[ 'catalogue' ][ 'filtrecatalog' ][ 'searchengine' ] )
if( etat == "" ) {
return callback( null, JSON.parse( JSON.stringify( param ) ) );
} else {
return callback( etat, null );
}
}
utils.levenshtein = ( a, b ) => {
if( a.length === 0 ) return b.length;
if( b.length === 0 ) return a.length;
let tmp, i, j, prev, val, row;
// swap to save some memory O(min(a,b)) instead of O(a)
if( a.length > b.length ) {
tmp = a;
a = b;
b = tmp;
}
row = Array( a.length + 1 );
// init the row
for( i = 0; i <= a.length; i++ ) {
row[ i ] = i;
}
// fill in the rest
for( i = 1; i <= b.length; i++ ) {
prev = i;
for( j = 1; j <= a.length; j++ ) {
if( b[ i - 1 ] === a[ j - 1 ] ) {
val = row[ j - 1 ]; // match
} else {
val = Math.min( row[ j - 1 ] + 1, // substitution
Math.min( prev + 1, // insertion
row[ j ] + 1 ) ); // deletion
}
row[ j - 1 ] = prev;
prev = val;
}
row[ a.length ] = prev;
}
return row[ a.length ];
};
utils.testinarray = ( array, arrayreferent ) => {
// au moins un element de array existe dans arryreferent
let exist = false;
if( arrayreferent ) {
//console.log('arrrrrrrrrrrrrrr', arrayreferent)
array.forEach( e => {
//console.log(e)
if( arrayreferent.includes( e ) ) exist = true
} )
}
return exist
};
/*
DIRECTORY
*/
const isDirectory = source => fs.lstatSync( source )
.isDirectory();
const getDirectories = source => fs.readdirSync( source )
.map( name => path.join( source, name ) )
.filter( isDirectory );
module.exports = utils;

View File

@ -0,0 +1,36 @@
[
{
"idfield": "name",
"nouserupdate": true,
"nouservisible": true,
"desc": {
"fr": "Nom unique d'une nation dans un monde social",
"en": "A unique nation name in a social world"
},
"desclong": {
"fr": "Nom unique identifiant une nation dans le monde social créer par un maire d'une nouvelle ville avec un contrat sociale specifique",
"en": "Unique nation name in the social world created by a mayor of a town."
},
"info": {
"fr": "<p> Une nation posséde un nom unique, un contrat sociale (contracts/name.js) signé et validé par tous les maires des villes associées à cette nation </p>",
"en": "<p> A nation is defined by this unique name and a contratcs/nationname.js signed by each town's major define the common nation rules</p>"
},
"check": ["required", "unique"],
"type": "text",
"questioncollecte":"questioninput",
"searchindex":["all"]
},
{
"idfield": "nationtype",
"searchindex": ["name"],
"check": ["required"],
"desc": {
"fr": "Type de nation"
},
"default": "ACTIVE",
"type": "data",
"questioncollect": "questionselect",
"data": "state.json"
},
]

View File

@ -0,0 +1,265 @@
[{
"idfield": "UUID",
"nouserupdate": true,
"nouservisible": true,
"desc": {
"fr": "identifiant utilisateur",
"en": "user id"
},
"desclong": {
"fr": "Identifiant unique généré via UUID v4",
"en": "unique Id from a UUID v4"
},
"info": {
"fr": "<p> L'usage d'UUID v4 permet de générer un code unique sans centralisation, car il est basé sur un timestamp et une clé crypto ce qui donne un code du type 7d8291c0-e137-11e8-9f7b-1dc8e57bed33 </p>",
"en": "<p> UUID v4 allow a client to generate a unique code without centralisation, base on a timestamp and a salt it looks like 7d8291c0-e137-11e8-9f7b-1dc8e57bed33</p>"
},
"check": ["required", "unique"],
"type": "text",
"tpl": "input"
},
{
"idfield": "LOGIN",
"nouserupdate": true,
"check": ["required", "unique"],
"desc": {
"fr": "login",
"en": "login"
},
"type": "text",
"tpl": "input",
"info": {
"fr": "<p>Le login doit être unique sur une instance d'apixtribe.</p><p> Pour échanger en dehors d'une instance apixtribe on utilise la clé public du user ou pour un humain login@apixtribe.domain.xx avec le nom du domaine qui heberge l'instance</p><p> Ou encore login@domain.xx tout domain.xx utilisé pour heberger un espace web client /tribeid/www/</p>",
"en": "<p>Login have to be unique into an apixtribe instance</p><p> To exchange outside of an apixtribe instance, we use PublicKey or login@apixtribe.domain.xx or login@domainclient.xx where domain.xx is a apixtribe name server on internet and domain.xx is a tribeid name where a /tribeid/www is available on the net.</p>"
}
},
{
"idfield": "BIOGRAPHY",
"desc": {
"fr": "Vous en quelques mots",
"en": "Few words"
},
"placeholder": {
"fr": "",
"en": ""
},
"rows": 2,
"tpl": "textarea"
},
{
"nouserupdate": true,
"idfield": "PUBLICKEY",
"desc": {
"fr": "Votre clé public pour ce compte",
"en": "Your public key for this uuid"
},
"info": {
"fr": "<p>Cette clé est générée par votre navigateur, garder précisuesement votre clé privée que seule vous connaissez. En cas de perte de cette clé tous vos actifs seront perdus.</p><p>Cette méthode nous permet de vous garantir un contrôle total décentralisé.</p>",
"en": "<p>This key was generated by your browser, keep the private key related to this public key.</p><p>We garanty your total control by this way</p>."
},
"tpl": "textarea"
},
{
"idfield": "IMGAVATAR",
"tpl": "inputimg",
"altimg": "image avatar",
"classimg": "rounded-circle img-responsive mt-2",
"width": 128,
"height:"
128,
"classdivupload": "mt-2",
"classbtn": "btn-primary",
"desc": {
"fr": "changer votre avatar",
"en": "upload an avatar"
},
"info": {
"fr": "Pour un meilleur rendu, une mage carré de 128pc en foat jpg",
"en": "For best results, use an image at least 128px by 128px in .jpg format"
},
},
{
"idfield": "EMAIL",
"desc": {
"fr": "email",
"en": "email"
},
"tpl": "input",
"type": "email",
"check": ["emailadress", "unique"],
"placeholder": {
"fr": "#@",
"en": "@"
}
},
{
"idfield": "PHONE",
"desc": {
"fr": "Tel",
"en": "Phone"
},
"check": ["phone"]
"tpl": "input",
"type": "text"
},
{
"idfield": "NAME",
"desc": {
"fr": "Nom",
"en": "Name"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "FIRSTNAME",
"desc": {
"fr": "Prénom",
"en": "First Name"
},
"tpl": "input",
"type": "text"
},
{
"idfield": "NICKNAME",
"desc": {
"fr": "Pseudo",
"en": "Nickname"
},
"tpl": "input",
"type": "text"
},
"info": {
"fr": "<p>Nom avec lequel vous souhaitez qu'on vous reconnaisse sur l'instance de l'apixtribe </p><p>Attention ce nom n'est unique que sur une instance d'apixtribe. Un même speudo peut-être utilisé sur un autre serveur pour garantir l'identité vérifié pseudo@ domaine de rattachement.</p>",
"en": "<p>Carrefull a pseudo is unique into an instance of apixtribe to be sure to contact the right person check pseudo@ domain</p>.<p> Pseudo can be changed that is not the case of login.</p>"
},
"tpl": "input",
"type": "text"
}, {
"idfield": "COMPANYNAME",
"desc": {
"fr": "Nom de Société",
"en": "Compagnie name"
},
"tpl": "input",
"type": "text"
}, {
"idfield": "BILLINGADD",
"desc": {
"fr": "Adresse complete de facturation par defaut",
"en": "Full billing adress default"
},
"tpl": "input",
"type": "text",
"placeholder": {
"fr": "1 chemin du paradis - 91430 IGNY France",
"en": "123 Main St- 123MZ -Oxford UK"
}
}, {
"idfield": "DELIVERYADD",
"desc": {
"fr": "Adresse complete de livraison par defaut",
"en": "Full delivery adress default"
},
"tpl": "input",
"type": "text",
"placeholder": {
"fr": "1 chemin du paradis - 91430 IGNY France",
"en": "123 Main St- 123MZ -Oxford UK"
}
}, {
"idfield": "ADDRESS1",
"desc": {
"fr": "Adresse",
"en": "Address"
},
"tpl": "input",
"type": "text",
"placeholder": {
"fr": "1 chemin du paradis",
"en": "123 Main St"
}
}, {
"idfield": "ADDRESS2",
"desc": {
"fr": "Adresse 2",
"en": "Address 2"
},
"tpl": "input",
"type": "text",
"placeholder": {
"fr": "Appartement B",
"en": "Apt B"
}
}, {
"idfield": "CITY",
"desc": {
"fr": "Ville ",
"en": "CITY"
},
"tpl": "input",
"type": "text"
}, {
"idfield": "ZIP",
"desc": {
"fr": "Code Postal",
"en": "ZIP"
},
"tpl": "input",
"type": "text"
}, {
"idfield": "COUNTRY",
"desc": {
"fr": "Pays",
"en": "Country"
},
"tpl": "input",
"type": "text"
}, {
"nouserupdate": true,
"idfield": "DATE_CREATE",
"desc": {
"fr": "Date de création",
"en": "Create Date"
},
"tpl": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')"
}, {
"nouserupdate": true,o
"idfield": "DATE_UPDATE",
"desc": {
"fr": "Date mise à jour",
"en": "Update date"
},
"tpl": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')"
}, {
"nouserupdate": true,
"idfield": "DATE_LASTLOGIN",
"desc": {
"fr": "Date de derniére connexion",
"en": "Last date login"
},
"tpl": "date",
"format": "YYYY-MM-DD",
"default": "moment(new Date()).format('YYYY-MM-DD')"
}, {
"idfield": "ACCESSRIGHTS",
"nouserupdate": true,
"desc": {
"fr": "Vos droits d'accès",
"en": "Your access rights"
},
"default": {
"app": {},
"data": {
"tribeidname": {
"users": "O"
}
}
},
"tpl": "jsoneditor"
}]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,7 @@
{"UUID":"ants",
"publickey":"123",
"status":"open",
"domain":["antsnation.mooo.com"],
"svg":"",
"towns":["hill"]
}

View File

@ -0,0 +1,5 @@
{"UUID":"escape",
"publickey":"123",
"status":"open",
"towns":["game"]
}

View File

@ -0,0 +1 @@
["escapenation"]

View File

@ -0,0 +1 @@
{"archilinea":["escapenation","game"]}

View File

@ -0,0 +1,7 @@
{
"url":"https://escape.game.apixtribe.org",
"IP":"213.32.65.213",
"mayorid":"123",
"status":""
"tribes":[]
}

View File

@ -0,0 +1 @@
<p>404 - Not found on town {{config.town}} for the {{config.nation}} nation mettre un js qui /app qui retourne le serveur concerne </p>

View File

@ -0,0 +1 @@
<p>50x - Not found on town {{config.town}} for the {{config.nation}} nation mettre un js qui /app qui retourne le serveur concerne </p>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,93 @@
Copyright (c) 2009-2010, Understanding Limited (dave@understandinglimited.com)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,622 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
(function($){$.fn.easyTabs=function(option){var param=jQuery.extend({fadeSpeed:"fast",defaultContent:1,activeClass:'active'},option);$(this).each(function(){var thisId="#"+this.id;if(param.defaultContent==''){param.defaultContent=1;}
if(typeof param.defaultContent=="number")
{var defaultTab=$(thisId+" .tabs li:eq("+(param.defaultContent-1)+") a").attr('href').substr(1);}else{var defaultTab=param.defaultContent;}
$(thisId+" .tabs li a").each(function(){var tabToHide=$(this).attr('href').substr(1);$("#"+tabToHide).addClass('easytabs-tab-content');});hideAll();changeContent(defaultTab);function hideAll(){$(thisId+" .easytabs-tab-content").hide();}
function changeContent(tabId){hideAll();$(thisId+" .tabs li").removeClass(param.activeClass);$(thisId+" .tabs li a[href=#"+tabId+"]").closest('li').addClass(param.activeClass);if(param.fadeSpeed!="none")
{$(thisId+" #"+tabId).fadeIn(param.fadeSpeed);}else{$(thisId+" #"+tabId).show();}}
$(thisId+" .tabs li").click(function(){var tabId=$(this).find('a').attr('href').substr(1);changeContent(tabId);return false;});});}})(jQuery);
</script>
<link rel="stylesheet" href="specimen_files/specimen_stylesheet.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" />
<style type="text/css">
body{
font-family: 'cantarellbold';
}
</style>
<title>Cantarell Bold Specimen</title>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$('#container').easyTabs({defaultContent:1});
});
</script>
</head>
<body>
<div id="container">
<div id="header">
Cantarell Bold </div>
<ul class="tabs">
<li><a href="#specimen">Specimen</a></li>
<li><a href="#layout">Sample Layout</a></li>
<li><a href="#glyphs">Glyphs &amp; Languages</a></li>
<li><a href="#installing">Installing Webfonts</a></li>
</ul>
<div id="main_content">
<div id="specimen">
<div class="section">
<div class="grid12 firstcol">
<div class="huge">AaBb</div>
</div>
</div>
<div class="section">
<div class="glyph_range">A&#x200B;B&#x200b;C&#x200b;D&#x200b;E&#x200b;F&#x200b;G&#x200b;H&#x200b;I&#x200b;J&#x200b;K&#x200b;L&#x200b;M&#x200b;N&#x200b;O&#x200b;P&#x200b;Q&#x200b;R&#x200b;S&#x200b;T&#x200b;U&#x200b;V&#x200b;W&#x200b;X&#x200b;Y&#x200b;Z&#x200b;a&#x200b;b&#x200b;c&#x200b;d&#x200b;e&#x200b;f&#x200b;g&#x200b;h&#x200b;i&#x200b;j&#x200b;k&#x200b;l&#x200b;m&#x200b;n&#x200b;o&#x200b;p&#x200b;q&#x200b;r&#x200b;s&#x200b;t&#x200b;u&#x200b;v&#x200b;w&#x200b;x&#x200b;y&#x200b;z&#x200b;1&#x200b;2&#x200b;3&#x200b;4&#x200b;5&#x200b;6&#x200b;7&#x200b;8&#x200b;9&#x200b;0&#x200b;&amp;&#x200b;.&#x200b;,&#x200b;?&#x200b;!&#x200b;&#64;&#x200b;(&#x200b;)&#x200b;#&#x200b;$&#x200b;%&#x200b;*&#x200b;+&#x200b;-&#x200b;=&#x200b;:&#x200b;;</div>
</div>
<div class="section">
<div class="grid12 firstcol">
<table class="sample_table">
<tr><td>10</td><td class="size10">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>11</td><td class="size11">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>12</td><td class="size12">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>13</td><td class="size13">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>14</td><td class="size14">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>16</td><td class="size16">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>18</td><td class="size18">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>20</td><td class="size20">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>24</td><td class="size24">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>30</td><td class="size30">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>36</td><td class="size36">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>48</td><td class="size48">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>60</td><td class="size60">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>72</td><td class="size72">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>90</td><td class="size90">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
</table>
</div>
</div>
<div class="section" id="bodycomparison">
<div id="xheight">
<div class="fontbody">&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;body</div><div class="arialbody">body</div><div class="verdanabody">body</div><div class="georgiabody">body</div></div>
<div class="fontbody" style="z-index:1">
body<span>Cantarell Bold</span>
</div>
<div class="arialbody" style="z-index:1">
body<span>Arial</span>
</div>
<div class="verdanabody" style="z-index:1">
body<span>Verdana</span>
</div>
<div class="georgiabody" style="z-index:1">
body<span>Georgia</span>
</div>
</div>
<div class="section psample psample_row1" id="">
<div class="grid2 firstcol">
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row2" id="">
<div class="grid3 firstcol">
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid5">
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row3" id="">
<div class="grid5 firstcol">
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid7">
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row4" id="">
<div class="grid12 firstcol">
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row1 fullreverse">
<div class="grid2 firstcol">
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample psample_row2 fullreverse">
<div class="grid3 firstcol">
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid5">
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample fullreverse psample_row3" id="">
<div class="grid5 firstcol">
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid7">
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample fullreverse psample_row4" id="" style="border-bottom: 20px #000 solid;">
<div class="grid12 firstcol">
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
</div>
<div id="layout">
<div class="section">
<div class="grid12 firstcol">
<h1>Lorem Ipsum Dolor</h1>
<h2>Etiam porta sem malesuada magna mollis euismod</h2>
<p class="byline">By <a href="#link">Aenean Lacinia</a></p>
</div>
</div>
<div class="section">
<div class="grid8 firstcol">
<p class="large">Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
<h3>Pellentesque ornare sem</h3>
<p>Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit. </p>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. </p>
<p>Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur. </p>
<p>Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla. </p>
<h3>Cras mattis consectetur</h3>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum. </p>
<p>Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.</p>
</div>
<div class="grid4 sidebar">
<div class="box reverse">
<p class="last">Nullam quis risus eget urna mollis ornare vel eu leo. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
</div>
<p class="caption">Maecenas sed diam eget risus varius.</p>
<p>Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
<p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo. </p>
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec ullamcorper nulla non metus auctor fringilla. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
</div>
</div>
</div>
<div id="glyphs">
<div class="section">
<div class="grid12 firstcol">
<h1>Language Support</h1>
<p>The subset of Cantarell Bold in this kit supports the following languages:<br />
Albanian, Basque, Breton, Chamorro, Danish, Dutch, English, Faroese, Finnish, French, Frisian, Galician, German, Icelandic, Italian, Malagasy, Norwegian, Portuguese, Spanish, Alsatian, Aragonese, Arapaho, Arrernte, Asturian, Aymara, Bislama, Cebuano, Corsican, Fijian, French_creole, Genoese, Gilbertese, Greenlandic, Haitian_creole, Hiligaynon, Hmong, Hopi, Ibanag, Iloko_ilokano, Indonesian, Interglossa_glosa, Interlingua, Irish_gaelic, Jerriais, Lojban, Lombard, Luxembourgeois, Manx, Mohawk, Norfolk_pitcairnese, Occitan, Oromo, Pangasinan, Papiamento, Piedmontese, Potawatomi, Rhaeto-romance, Romansh, Rotokas, Sami_lule, Samoan, Sardinian, Scots_gaelic, Seychelles_creole, Shona, Sicilian, Somali, Southern_ndebele, Swahili, Swati_swazi, Tagalog_filipino_pilipino, Tetum, Tok_pisin, Uyghur_latinized, Volapuk, Walloon, Warlpiri, Xhosa, Yapese, Zulu, Latinbasic, Ubasic, Demo </p>
<h1>Glyph Chart</h1>
<p>The subset of Cantarell Bold in this kit includes all the glyphs listed below. Unicode entities are included above each glyph to help you insert individual characters into your layout.</p>
<div id="glyph_chart">
<div><p>&amp;#13;</p>&#13;</div>
<div><p>&amp;#32;</p>&#32;</div>
<div><p>&amp;#33;</p>&#33;</div>
<div><p>&amp;#34;</p>&#34;</div>
<div><p>&amp;#35;</p>&#35;</div>
<div><p>&amp;#36;</p>&#36;</div>
<div><p>&amp;#37;</p>&#37;</div>
<div><p>&amp;#38;</p>&#38;</div>
<div><p>&amp;#39;</p>&#39;</div>
<div><p>&amp;#40;</p>&#40;</div>
<div><p>&amp;#41;</p>&#41;</div>
<div><p>&amp;#42;</p>&#42;</div>
<div><p>&amp;#43;</p>&#43;</div>
<div><p>&amp;#44;</p>&#44;</div>
<div><p>&amp;#45;</p>&#45;</div>
<div><p>&amp;#46;</p>&#46;</div>
<div><p>&amp;#47;</p>&#47;</div>
<div><p>&amp;#48;</p>&#48;</div>
<div><p>&amp;#49;</p>&#49;</div>
<div><p>&amp;#50;</p>&#50;</div>
<div><p>&amp;#51;</p>&#51;</div>
<div><p>&amp;#52;</p>&#52;</div>
<div><p>&amp;#53;</p>&#53;</div>
<div><p>&amp;#54;</p>&#54;</div>
<div><p>&amp;#55;</p>&#55;</div>
<div><p>&amp;#56;</p>&#56;</div>
<div><p>&amp;#57;</p>&#57;</div>
<div><p>&amp;#58;</p>&#58;</div>
<div><p>&amp;#59;</p>&#59;</div>
<div><p>&amp;#60;</p>&#60;</div>
<div><p>&amp;#61;</p>&#61;</div>
<div><p>&amp;#62;</p>&#62;</div>
<div><p>&amp;#63;</p>&#63;</div>
<div><p>&amp;#64;</p>&#64;</div>
<div><p>&amp;#65;</p>&#65;</div>
<div><p>&amp;#66;</p>&#66;</div>
<div><p>&amp;#67;</p>&#67;</div>
<div><p>&amp;#68;</p>&#68;</div>
<div><p>&amp;#69;</p>&#69;</div>
<div><p>&amp;#70;</p>&#70;</div>
<div><p>&amp;#71;</p>&#71;</div>
<div><p>&amp;#72;</p>&#72;</div>
<div><p>&amp;#73;</p>&#73;</div>
<div><p>&amp;#74;</p>&#74;</div>
<div><p>&amp;#75;</p>&#75;</div>
<div><p>&amp;#76;</p>&#76;</div>
<div><p>&amp;#77;</p>&#77;</div>
<div><p>&amp;#78;</p>&#78;</div>
<div><p>&amp;#79;</p>&#79;</div>
<div><p>&amp;#80;</p>&#80;</div>
<div><p>&amp;#81;</p>&#81;</div>
<div><p>&amp;#82;</p>&#82;</div>
<div><p>&amp;#83;</p>&#83;</div>
<div><p>&amp;#84;</p>&#84;</div>
<div><p>&amp;#85;</p>&#85;</div>
<div><p>&amp;#86;</p>&#86;</div>
<div><p>&amp;#87;</p>&#87;</div>
<div><p>&amp;#88;</p>&#88;</div>
<div><p>&amp;#89;</p>&#89;</div>
<div><p>&amp;#90;</p>&#90;</div>
<div><p>&amp;#91;</p>&#91;</div>
<div><p>&amp;#92;</p>&#92;</div>
<div><p>&amp;#93;</p>&#93;</div>
<div><p>&amp;#94;</p>&#94;</div>
<div><p>&amp;#95;</p>&#95;</div>
<div><p>&amp;#96;</p>&#96;</div>
<div><p>&amp;#97;</p>&#97;</div>
<div><p>&amp;#98;</p>&#98;</div>
<div><p>&amp;#99;</p>&#99;</div>
<div><p>&amp;#100;</p>&#100;</div>
<div><p>&amp;#101;</p>&#101;</div>
<div><p>&amp;#102;</p>&#102;</div>
<div><p>&amp;#103;</p>&#103;</div>
<div><p>&amp;#104;</p>&#104;</div>
<div><p>&amp;#105;</p>&#105;</div>
<div><p>&amp;#106;</p>&#106;</div>
<div><p>&amp;#107;</p>&#107;</div>
<div><p>&amp;#108;</p>&#108;</div>
<div><p>&amp;#109;</p>&#109;</div>
<div><p>&amp;#110;</p>&#110;</div>
<div><p>&amp;#111;</p>&#111;</div>
<div><p>&amp;#112;</p>&#112;</div>
<div><p>&amp;#113;</p>&#113;</div>
<div><p>&amp;#114;</p>&#114;</div>
<div><p>&amp;#115;</p>&#115;</div>
<div><p>&amp;#116;</p>&#116;</div>
<div><p>&amp;#117;</p>&#117;</div>
<div><p>&amp;#118;</p>&#118;</div>
<div><p>&amp;#119;</p>&#119;</div>
<div><p>&amp;#120;</p>&#120;</div>
<div><p>&amp;#121;</p>&#121;</div>
<div><p>&amp;#122;</p>&#122;</div>
<div><p>&amp;#123;</p>&#123;</div>
<div><p>&amp;#124;</p>&#124;</div>
<div><p>&amp;#125;</p>&#125;</div>
<div><p>&amp;#126;</p>&#126;</div>
<div><p>&amp;#160;</p>&#160;</div>
<div><p>&amp;#161;</p>&#161;</div>
<div><p>&amp;#162;</p>&#162;</div>
<div><p>&amp;#163;</p>&#163;</div>
<div><p>&amp;#164;</p>&#164;</div>
<div><p>&amp;#165;</p>&#165;</div>
<div><p>&amp;#166;</p>&#166;</div>
<div><p>&amp;#167;</p>&#167;</div>
<div><p>&amp;#168;</p>&#168;</div>
<div><p>&amp;#169;</p>&#169;</div>
<div><p>&amp;#170;</p>&#170;</div>
<div><p>&amp;#171;</p>&#171;</div>
<div><p>&amp;#172;</p>&#172;</div>
<div><p>&amp;#173;</p>&#173;</div>
<div><p>&amp;#174;</p>&#174;</div>
<div><p>&amp;#175;</p>&#175;</div>
<div><p>&amp;#176;</p>&#176;</div>
<div><p>&amp;#177;</p>&#177;</div>
<div><p>&amp;#178;</p>&#178;</div>
<div><p>&amp;#179;</p>&#179;</div>
<div><p>&amp;#180;</p>&#180;</div>
<div><p>&amp;#181;</p>&#181;</div>
<div><p>&amp;#182;</p>&#182;</div>
<div><p>&amp;#183;</p>&#183;</div>
<div><p>&amp;#184;</p>&#184;</div>
<div><p>&amp;#185;</p>&#185;</div>
<div><p>&amp;#186;</p>&#186;</div>
<div><p>&amp;#187;</p>&#187;</div>
<div><p>&amp;#188;</p>&#188;</div>
<div><p>&amp;#189;</p>&#189;</div>
<div><p>&amp;#190;</p>&#190;</div>
<div><p>&amp;#191;</p>&#191;</div>
<div><p>&amp;#192;</p>&#192;</div>
<div><p>&amp;#193;</p>&#193;</div>
<div><p>&amp;#194;</p>&#194;</div>
<div><p>&amp;#195;</p>&#195;</div>
<div><p>&amp;#196;</p>&#196;</div>
<div><p>&amp;#197;</p>&#197;</div>
<div><p>&amp;#198;</p>&#198;</div>
<div><p>&amp;#199;</p>&#199;</div>
<div><p>&amp;#200;</p>&#200;</div>
<div><p>&amp;#201;</p>&#201;</div>
<div><p>&amp;#202;</p>&#202;</div>
<div><p>&amp;#203;</p>&#203;</div>
<div><p>&amp;#204;</p>&#204;</div>
<div><p>&amp;#205;</p>&#205;</div>
<div><p>&amp;#206;</p>&#206;</div>
<div><p>&amp;#207;</p>&#207;</div>
<div><p>&amp;#208;</p>&#208;</div>
<div><p>&amp;#209;</p>&#209;</div>
<div><p>&amp;#210;</p>&#210;</div>
<div><p>&amp;#211;</p>&#211;</div>
<div><p>&amp;#212;</p>&#212;</div>
<div><p>&amp;#213;</p>&#213;</div>
<div><p>&amp;#214;</p>&#214;</div>
<div><p>&amp;#215;</p>&#215;</div>
<div><p>&amp;#216;</p>&#216;</div>
<div><p>&amp;#217;</p>&#217;</div>
<div><p>&amp;#218;</p>&#218;</div>
<div><p>&amp;#219;</p>&#219;</div>
<div><p>&amp;#220;</p>&#220;</div>
<div><p>&amp;#221;</p>&#221;</div>
<div><p>&amp;#222;</p>&#222;</div>
<div><p>&amp;#223;</p>&#223;</div>
<div><p>&amp;#224;</p>&#224;</div>
<div><p>&amp;#225;</p>&#225;</div>
<div><p>&amp;#226;</p>&#226;</div>
<div><p>&amp;#227;</p>&#227;</div>
<div><p>&amp;#228;</p>&#228;</div>
<div><p>&amp;#229;</p>&#229;</div>
<div><p>&amp;#230;</p>&#230;</div>
<div><p>&amp;#231;</p>&#231;</div>
<div><p>&amp;#232;</p>&#232;</div>
<div><p>&amp;#233;</p>&#233;</div>
<div><p>&amp;#234;</p>&#234;</div>
<div><p>&amp;#235;</p>&#235;</div>
<div><p>&amp;#236;</p>&#236;</div>
<div><p>&amp;#237;</p>&#237;</div>
<div><p>&amp;#238;</p>&#238;</div>
<div><p>&amp;#239;</p>&#239;</div>
<div><p>&amp;#240;</p>&#240;</div>
<div><p>&amp;#241;</p>&#241;</div>
<div><p>&amp;#242;</p>&#242;</div>
<div><p>&amp;#243;</p>&#243;</div>
<div><p>&amp;#244;</p>&#244;</div>
<div><p>&amp;#245;</p>&#245;</div>
<div><p>&amp;#246;</p>&#246;</div>
<div><p>&amp;#247;</p>&#247;</div>
<div><p>&amp;#248;</p>&#248;</div>
<div><p>&amp;#249;</p>&#249;</div>
<div><p>&amp;#250;</p>&#250;</div>
<div><p>&amp;#251;</p>&#251;</div>
<div><p>&amp;#252;</p>&#252;</div>
<div><p>&amp;#253;</p>&#253;</div>
<div><p>&amp;#254;</p>&#254;</div>
<div><p>&amp;#255;</p>&#255;</div>
<div><p>&amp;#338;</p>&#338;</div>
<div><p>&amp;#339;</p>&#339;</div>
<div><p>&amp;#376;</p>&#376;</div>
<div><p>&amp;#710;</p>&#710;</div>
<div><p>&amp;#732;</p>&#732;</div>
<div><p>&amp;#8192;</p>&#8192;</div>
<div><p>&amp;#8193;</p>&#8193;</div>
<div><p>&amp;#8194;</p>&#8194;</div>
<div><p>&amp;#8195;</p>&#8195;</div>
<div><p>&amp;#8196;</p>&#8196;</div>
<div><p>&amp;#8197;</p>&#8197;</div>
<div><p>&amp;#8198;</p>&#8198;</div>
<div><p>&amp;#8199;</p>&#8199;</div>
<div><p>&amp;#8200;</p>&#8200;</div>
<div><p>&amp;#8201;</p>&#8201;</div>
<div><p>&amp;#8202;</p>&#8202;</div>
<div><p>&amp;#8208;</p>&#8208;</div>
<div><p>&amp;#8209;</p>&#8209;</div>
<div><p>&amp;#8210;</p>&#8210;</div>
<div><p>&amp;#8211;</p>&#8211;</div>
<div><p>&amp;#8212;</p>&#8212;</div>
<div><p>&amp;#8216;</p>&#8216;</div>
<div><p>&amp;#8217;</p>&#8217;</div>
<div><p>&amp;#8218;</p>&#8218;</div>
<div><p>&amp;#8220;</p>&#8220;</div>
<div><p>&amp;#8221;</p>&#8221;</div>
<div><p>&amp;#8222;</p>&#8222;</div>
<div><p>&amp;#8226;</p>&#8226;</div>
<div><p>&amp;#8230;</p>&#8230;</div>
<div><p>&amp;#8239;</p>&#8239;</div>
<div><p>&amp;#8249;</p>&#8249;</div>
<div><p>&amp;#8250;</p>&#8250;</div>
<div><p>&amp;#8287;</p>&#8287;</div>
<div><p>&amp;#8364;</p>&#8364;</div>
<div><p>&amp;#8482;</p>&#8482;</div>
<div><p>&amp;#9724;</p>&#9724;</div>
<div><p>&amp;#64257;</p>&#64257;</div>
<div><p>&amp;#64258;</p>&#64258;</div>
<div><p>&amp;#64259;</p>&#64259;</div>
<div><p>&amp;#64260;</p>&#64260;</div>
</div>
</div>
</div>
</div>
<div id="specs">
</div>
<div id="installing">
<div class="section">
<div class="grid7 firstcol">
<h1>Installing Webfonts</h1>
<p>Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.</p>
<h2>1. Upload your webfonts</h2>
<p>You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.</p>
<h2>2. Include the webfont stylesheet</h2>
<p>A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the <a href="https://www.fontspring.com/blog/further-hardening-of-the-bulletproof-syntax">Fontspring blog post</a> about it. The code for it is as follows:</p>
<code>
@font-face{
font-family: 'MyWebFont';
src: url('WebFont.eot');
src: url('WebFont.eot?#iefix') format('embedded-opentype'),
url('WebFont.woff') format('woff'),
url('WebFont.ttf') format('truetype'),
url('WebFont.svg#webfont') format('svg');
}
</code>
<p>We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:</p>
<code>&lt;link rel=&quot;stylesheet&quot; href=&quot;stylesheet.css&quot; type=&quot;text/css&quot; charset=&quot;utf-8&quot; /&gt;</code>
<h2>3. Modify your own stylesheet</h2>
<p>To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:</p>
<code>p { font-family: 'WebFont', Arial, sans-serif; }</code>
<h2>4. Test</h2>
<p>Getting webfonts to work cross-browser <em>can</em> be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.</p>
</div>
<div class="grid5 sidebar">
<div class="box">
<h2>Troubleshooting<br />Font-Face Problems</h2>
<p>Having trouble getting your webfonts to load in your new website? Here are some tips to sort out what might be the problem.</p>
<h3>Fonts not showing in any browser</h3>
<p>This sounds like you need to work on the plumbing. You either did not upload the fonts to the correct directory, or you did not link the fonts properly in the CSS. If you've confirmed that all this is correct and you still have a problem, take a look at your .htaccess file and see if requests are getting intercepted.</p>
<h3>Fonts not loading in iPhone or iPad</h3>
<p>The most common problem here is that you are serving the fonts from an IIS server. IIS refuses to serve files that have unknown MIME types. If that is the case, you must set the MIME type for SVG to "image/svg+xml" in the server settings. Follow these instructions from Microsoft if you need help.</p>
<h3>Fonts not loading in Firefox</h3>
<p>The primary reason for this failure? You are still using a version Firefox older than 3.5. So upgrade already! If that isn't it, then you are very likely serving fonts from a different domain. Firefox requires that all font assets be served from the same domain. Lastly it is possible that you need to add WOFF to your list of MIME types (if you are serving via IIS.)</p>
<h3>Fonts not loading in IE</h3>
<p>Are you looking at Internet Explorer on an actual Windows machine or are you cheating by using a service like Adobe BrowserLab? Many of these screenshot services do not render @font-face for IE. Best to test it on a real machine.</p>
<h3>Fonts not loading in IE9</h3>
<p>IE9, like Firefox, requires that fonts be served from the same domain as the website. Make sure that is the case.</p>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<p>&copy;2010-2017 Font Squirrel. All rights reserved.</p>
</div>
</div>
</body>
</html>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,622 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
(function($){$.fn.easyTabs=function(option){var param=jQuery.extend({fadeSpeed:"fast",defaultContent:1,activeClass:'active'},option);$(this).each(function(){var thisId="#"+this.id;if(param.defaultContent==''){param.defaultContent=1;}
if(typeof param.defaultContent=="number")
{var defaultTab=$(thisId+" .tabs li:eq("+(param.defaultContent-1)+") a").attr('href').substr(1);}else{var defaultTab=param.defaultContent;}
$(thisId+" .tabs li a").each(function(){var tabToHide=$(this).attr('href').substr(1);$("#"+tabToHide).addClass('easytabs-tab-content');});hideAll();changeContent(defaultTab);function hideAll(){$(thisId+" .easytabs-tab-content").hide();}
function changeContent(tabId){hideAll();$(thisId+" .tabs li").removeClass(param.activeClass);$(thisId+" .tabs li a[href=#"+tabId+"]").closest('li').addClass(param.activeClass);if(param.fadeSpeed!="none")
{$(thisId+" #"+tabId).fadeIn(param.fadeSpeed);}else{$(thisId+" #"+tabId).show();}}
$(thisId+" .tabs li").click(function(){var tabId=$(this).find('a').attr('href').substr(1);changeContent(tabId);return false;});});}})(jQuery);
</script>
<link rel="stylesheet" href="specimen_files/specimen_stylesheet.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" />
<style type="text/css">
body{
font-family: 'cantarellboldoblique';
}
</style>
<title>Cantarell BoldOblique Specimen</title>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$('#container').easyTabs({defaultContent:1});
});
</script>
</head>
<body>
<div id="container">
<div id="header">
Cantarell BoldOblique </div>
<ul class="tabs">
<li><a href="#specimen">Specimen</a></li>
<li><a href="#layout">Sample Layout</a></li>
<li><a href="#glyphs">Glyphs &amp; Languages</a></li>
<li><a href="#installing">Installing Webfonts</a></li>
</ul>
<div id="main_content">
<div id="specimen">
<div class="section">
<div class="grid12 firstcol">
<div class="huge">AaBb</div>
</div>
</div>
<div class="section">
<div class="glyph_range">A&#x200B;B&#x200b;C&#x200b;D&#x200b;E&#x200b;F&#x200b;G&#x200b;H&#x200b;I&#x200b;J&#x200b;K&#x200b;L&#x200b;M&#x200b;N&#x200b;O&#x200b;P&#x200b;Q&#x200b;R&#x200b;S&#x200b;T&#x200b;U&#x200b;V&#x200b;W&#x200b;X&#x200b;Y&#x200b;Z&#x200b;a&#x200b;b&#x200b;c&#x200b;d&#x200b;e&#x200b;f&#x200b;g&#x200b;h&#x200b;i&#x200b;j&#x200b;k&#x200b;l&#x200b;m&#x200b;n&#x200b;o&#x200b;p&#x200b;q&#x200b;r&#x200b;s&#x200b;t&#x200b;u&#x200b;v&#x200b;w&#x200b;x&#x200b;y&#x200b;z&#x200b;1&#x200b;2&#x200b;3&#x200b;4&#x200b;5&#x200b;6&#x200b;7&#x200b;8&#x200b;9&#x200b;0&#x200b;&amp;&#x200b;.&#x200b;,&#x200b;?&#x200b;!&#x200b;&#64;&#x200b;(&#x200b;)&#x200b;#&#x200b;$&#x200b;%&#x200b;*&#x200b;+&#x200b;-&#x200b;=&#x200b;:&#x200b;;</div>
</div>
<div class="section">
<div class="grid12 firstcol">
<table class="sample_table">
<tr><td>10</td><td class="size10">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>11</td><td class="size11">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>12</td><td class="size12">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>13</td><td class="size13">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>14</td><td class="size14">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>16</td><td class="size16">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>18</td><td class="size18">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>20</td><td class="size20">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>24</td><td class="size24">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>30</td><td class="size30">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>36</td><td class="size36">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>48</td><td class="size48">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>60</td><td class="size60">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>72</td><td class="size72">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>90</td><td class="size90">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
</table>
</div>
</div>
<div class="section" id="bodycomparison">
<div id="xheight">
<div class="fontbody">&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;body</div><div class="arialbody">body</div><div class="verdanabody">body</div><div class="georgiabody">body</div></div>
<div class="fontbody" style="z-index:1">
body<span>Cantarell BoldOblique</span>
</div>
<div class="arialbody" style="z-index:1">
body<span>Arial</span>
</div>
<div class="verdanabody" style="z-index:1">
body<span>Verdana</span>
</div>
<div class="georgiabody" style="z-index:1">
body<span>Georgia</span>
</div>
</div>
<div class="section psample psample_row1" id="">
<div class="grid2 firstcol">
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row2" id="">
<div class="grid3 firstcol">
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid5">
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row3" id="">
<div class="grid5 firstcol">
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid7">
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row4" id="">
<div class="grid12 firstcol">
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row1 fullreverse">
<div class="grid2 firstcol">
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample psample_row2 fullreverse">
<div class="grid3 firstcol">
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid5">
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample fullreverse psample_row3" id="">
<div class="grid5 firstcol">
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid7">
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample fullreverse psample_row4" id="" style="border-bottom: 20px #000 solid;">
<div class="grid12 firstcol">
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
</div>
<div id="layout">
<div class="section">
<div class="grid12 firstcol">
<h1>Lorem Ipsum Dolor</h1>
<h2>Etiam porta sem malesuada magna mollis euismod</h2>
<p class="byline">By <a href="#link">Aenean Lacinia</a></p>
</div>
</div>
<div class="section">
<div class="grid8 firstcol">
<p class="large">Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
<h3>Pellentesque ornare sem</h3>
<p>Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit. </p>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. </p>
<p>Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur. </p>
<p>Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla. </p>
<h3>Cras mattis consectetur</h3>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum. </p>
<p>Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.</p>
</div>
<div class="grid4 sidebar">
<div class="box reverse">
<p class="last">Nullam quis risus eget urna mollis ornare vel eu leo. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
</div>
<p class="caption">Maecenas sed diam eget risus varius.</p>
<p>Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
<p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo. </p>
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec ullamcorper nulla non metus auctor fringilla. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
</div>
</div>
</div>
<div id="glyphs">
<div class="section">
<div class="grid12 firstcol">
<h1>Language Support</h1>
<p>The subset of Cantarell BoldOblique in this kit supports the following languages:<br />
Albanian, Basque, Breton, Chamorro, Danish, Dutch, English, Faroese, Finnish, French, Frisian, Galician, German, Icelandic, Italian, Malagasy, Norwegian, Portuguese, Spanish, Alsatian, Aragonese, Arapaho, Arrernte, Asturian, Aymara, Bislama, Cebuano, Corsican, Fijian, French_creole, Genoese, Gilbertese, Greenlandic, Haitian_creole, Hiligaynon, Hmong, Hopi, Ibanag, Iloko_ilokano, Indonesian, Interglossa_glosa, Interlingua, Irish_gaelic, Jerriais, Lojban, Lombard, Luxembourgeois, Manx, Mohawk, Norfolk_pitcairnese, Occitan, Oromo, Pangasinan, Papiamento, Piedmontese, Potawatomi, Rhaeto-romance, Romansh, Rotokas, Sami_lule, Samoan, Sardinian, Scots_gaelic, Seychelles_creole, Shona, Sicilian, Somali, Southern_ndebele, Swahili, Swati_swazi, Tagalog_filipino_pilipino, Tetum, Tok_pisin, Uyghur_latinized, Volapuk, Walloon, Warlpiri, Xhosa, Yapese, Zulu, Latinbasic, Ubasic, Demo </p>
<h1>Glyph Chart</h1>
<p>The subset of Cantarell BoldOblique in this kit includes all the glyphs listed below. Unicode entities are included above each glyph to help you insert individual characters into your layout.</p>
<div id="glyph_chart">
<div><p>&amp;#13;</p>&#13;</div>
<div><p>&amp;#32;</p>&#32;</div>
<div><p>&amp;#33;</p>&#33;</div>
<div><p>&amp;#34;</p>&#34;</div>
<div><p>&amp;#35;</p>&#35;</div>
<div><p>&amp;#36;</p>&#36;</div>
<div><p>&amp;#37;</p>&#37;</div>
<div><p>&amp;#38;</p>&#38;</div>
<div><p>&amp;#39;</p>&#39;</div>
<div><p>&amp;#40;</p>&#40;</div>
<div><p>&amp;#41;</p>&#41;</div>
<div><p>&amp;#42;</p>&#42;</div>
<div><p>&amp;#43;</p>&#43;</div>
<div><p>&amp;#44;</p>&#44;</div>
<div><p>&amp;#45;</p>&#45;</div>
<div><p>&amp;#46;</p>&#46;</div>
<div><p>&amp;#47;</p>&#47;</div>
<div><p>&amp;#48;</p>&#48;</div>
<div><p>&amp;#49;</p>&#49;</div>
<div><p>&amp;#50;</p>&#50;</div>
<div><p>&amp;#51;</p>&#51;</div>
<div><p>&amp;#52;</p>&#52;</div>
<div><p>&amp;#53;</p>&#53;</div>
<div><p>&amp;#54;</p>&#54;</div>
<div><p>&amp;#55;</p>&#55;</div>
<div><p>&amp;#56;</p>&#56;</div>
<div><p>&amp;#57;</p>&#57;</div>
<div><p>&amp;#58;</p>&#58;</div>
<div><p>&amp;#59;</p>&#59;</div>
<div><p>&amp;#60;</p>&#60;</div>
<div><p>&amp;#61;</p>&#61;</div>
<div><p>&amp;#62;</p>&#62;</div>
<div><p>&amp;#63;</p>&#63;</div>
<div><p>&amp;#64;</p>&#64;</div>
<div><p>&amp;#65;</p>&#65;</div>
<div><p>&amp;#66;</p>&#66;</div>
<div><p>&amp;#67;</p>&#67;</div>
<div><p>&amp;#68;</p>&#68;</div>
<div><p>&amp;#69;</p>&#69;</div>
<div><p>&amp;#70;</p>&#70;</div>
<div><p>&amp;#71;</p>&#71;</div>
<div><p>&amp;#72;</p>&#72;</div>
<div><p>&amp;#73;</p>&#73;</div>
<div><p>&amp;#74;</p>&#74;</div>
<div><p>&amp;#75;</p>&#75;</div>
<div><p>&amp;#76;</p>&#76;</div>
<div><p>&amp;#77;</p>&#77;</div>
<div><p>&amp;#78;</p>&#78;</div>
<div><p>&amp;#79;</p>&#79;</div>
<div><p>&amp;#80;</p>&#80;</div>
<div><p>&amp;#81;</p>&#81;</div>
<div><p>&amp;#82;</p>&#82;</div>
<div><p>&amp;#83;</p>&#83;</div>
<div><p>&amp;#84;</p>&#84;</div>
<div><p>&amp;#85;</p>&#85;</div>
<div><p>&amp;#86;</p>&#86;</div>
<div><p>&amp;#87;</p>&#87;</div>
<div><p>&amp;#88;</p>&#88;</div>
<div><p>&amp;#89;</p>&#89;</div>
<div><p>&amp;#90;</p>&#90;</div>
<div><p>&amp;#91;</p>&#91;</div>
<div><p>&amp;#92;</p>&#92;</div>
<div><p>&amp;#93;</p>&#93;</div>
<div><p>&amp;#94;</p>&#94;</div>
<div><p>&amp;#95;</p>&#95;</div>
<div><p>&amp;#96;</p>&#96;</div>
<div><p>&amp;#97;</p>&#97;</div>
<div><p>&amp;#98;</p>&#98;</div>
<div><p>&amp;#99;</p>&#99;</div>
<div><p>&amp;#100;</p>&#100;</div>
<div><p>&amp;#101;</p>&#101;</div>
<div><p>&amp;#102;</p>&#102;</div>
<div><p>&amp;#103;</p>&#103;</div>
<div><p>&amp;#104;</p>&#104;</div>
<div><p>&amp;#105;</p>&#105;</div>
<div><p>&amp;#106;</p>&#106;</div>
<div><p>&amp;#107;</p>&#107;</div>
<div><p>&amp;#108;</p>&#108;</div>
<div><p>&amp;#109;</p>&#109;</div>
<div><p>&amp;#110;</p>&#110;</div>
<div><p>&amp;#111;</p>&#111;</div>
<div><p>&amp;#112;</p>&#112;</div>
<div><p>&amp;#113;</p>&#113;</div>
<div><p>&amp;#114;</p>&#114;</div>
<div><p>&amp;#115;</p>&#115;</div>
<div><p>&amp;#116;</p>&#116;</div>
<div><p>&amp;#117;</p>&#117;</div>
<div><p>&amp;#118;</p>&#118;</div>
<div><p>&amp;#119;</p>&#119;</div>
<div><p>&amp;#120;</p>&#120;</div>
<div><p>&amp;#121;</p>&#121;</div>
<div><p>&amp;#122;</p>&#122;</div>
<div><p>&amp;#123;</p>&#123;</div>
<div><p>&amp;#124;</p>&#124;</div>
<div><p>&amp;#125;</p>&#125;</div>
<div><p>&amp;#126;</p>&#126;</div>
<div><p>&amp;#160;</p>&#160;</div>
<div><p>&amp;#161;</p>&#161;</div>
<div><p>&amp;#162;</p>&#162;</div>
<div><p>&amp;#163;</p>&#163;</div>
<div><p>&amp;#164;</p>&#164;</div>
<div><p>&amp;#165;</p>&#165;</div>
<div><p>&amp;#166;</p>&#166;</div>
<div><p>&amp;#167;</p>&#167;</div>
<div><p>&amp;#168;</p>&#168;</div>
<div><p>&amp;#169;</p>&#169;</div>
<div><p>&amp;#170;</p>&#170;</div>
<div><p>&amp;#171;</p>&#171;</div>
<div><p>&amp;#172;</p>&#172;</div>
<div><p>&amp;#173;</p>&#173;</div>
<div><p>&amp;#174;</p>&#174;</div>
<div><p>&amp;#175;</p>&#175;</div>
<div><p>&amp;#176;</p>&#176;</div>
<div><p>&amp;#177;</p>&#177;</div>
<div><p>&amp;#178;</p>&#178;</div>
<div><p>&amp;#179;</p>&#179;</div>
<div><p>&amp;#180;</p>&#180;</div>
<div><p>&amp;#181;</p>&#181;</div>
<div><p>&amp;#182;</p>&#182;</div>
<div><p>&amp;#183;</p>&#183;</div>
<div><p>&amp;#184;</p>&#184;</div>
<div><p>&amp;#185;</p>&#185;</div>
<div><p>&amp;#186;</p>&#186;</div>
<div><p>&amp;#187;</p>&#187;</div>
<div><p>&amp;#188;</p>&#188;</div>
<div><p>&amp;#189;</p>&#189;</div>
<div><p>&amp;#190;</p>&#190;</div>
<div><p>&amp;#191;</p>&#191;</div>
<div><p>&amp;#192;</p>&#192;</div>
<div><p>&amp;#193;</p>&#193;</div>
<div><p>&amp;#194;</p>&#194;</div>
<div><p>&amp;#195;</p>&#195;</div>
<div><p>&amp;#196;</p>&#196;</div>
<div><p>&amp;#197;</p>&#197;</div>
<div><p>&amp;#198;</p>&#198;</div>
<div><p>&amp;#199;</p>&#199;</div>
<div><p>&amp;#200;</p>&#200;</div>
<div><p>&amp;#201;</p>&#201;</div>
<div><p>&amp;#202;</p>&#202;</div>
<div><p>&amp;#203;</p>&#203;</div>
<div><p>&amp;#204;</p>&#204;</div>
<div><p>&amp;#205;</p>&#205;</div>
<div><p>&amp;#206;</p>&#206;</div>
<div><p>&amp;#207;</p>&#207;</div>
<div><p>&amp;#208;</p>&#208;</div>
<div><p>&amp;#209;</p>&#209;</div>
<div><p>&amp;#210;</p>&#210;</div>
<div><p>&amp;#211;</p>&#211;</div>
<div><p>&amp;#212;</p>&#212;</div>
<div><p>&amp;#213;</p>&#213;</div>
<div><p>&amp;#214;</p>&#214;</div>
<div><p>&amp;#215;</p>&#215;</div>
<div><p>&amp;#216;</p>&#216;</div>
<div><p>&amp;#217;</p>&#217;</div>
<div><p>&amp;#218;</p>&#218;</div>
<div><p>&amp;#219;</p>&#219;</div>
<div><p>&amp;#220;</p>&#220;</div>
<div><p>&amp;#221;</p>&#221;</div>
<div><p>&amp;#222;</p>&#222;</div>
<div><p>&amp;#223;</p>&#223;</div>
<div><p>&amp;#224;</p>&#224;</div>
<div><p>&amp;#225;</p>&#225;</div>
<div><p>&amp;#226;</p>&#226;</div>
<div><p>&amp;#227;</p>&#227;</div>
<div><p>&amp;#228;</p>&#228;</div>
<div><p>&amp;#229;</p>&#229;</div>
<div><p>&amp;#230;</p>&#230;</div>
<div><p>&amp;#231;</p>&#231;</div>
<div><p>&amp;#232;</p>&#232;</div>
<div><p>&amp;#233;</p>&#233;</div>
<div><p>&amp;#234;</p>&#234;</div>
<div><p>&amp;#235;</p>&#235;</div>
<div><p>&amp;#236;</p>&#236;</div>
<div><p>&amp;#237;</p>&#237;</div>
<div><p>&amp;#238;</p>&#238;</div>
<div><p>&amp;#239;</p>&#239;</div>
<div><p>&amp;#240;</p>&#240;</div>
<div><p>&amp;#241;</p>&#241;</div>
<div><p>&amp;#242;</p>&#242;</div>
<div><p>&amp;#243;</p>&#243;</div>
<div><p>&amp;#244;</p>&#244;</div>
<div><p>&amp;#245;</p>&#245;</div>
<div><p>&amp;#246;</p>&#246;</div>
<div><p>&amp;#247;</p>&#247;</div>
<div><p>&amp;#248;</p>&#248;</div>
<div><p>&amp;#249;</p>&#249;</div>
<div><p>&amp;#250;</p>&#250;</div>
<div><p>&amp;#251;</p>&#251;</div>
<div><p>&amp;#252;</p>&#252;</div>
<div><p>&amp;#253;</p>&#253;</div>
<div><p>&amp;#254;</p>&#254;</div>
<div><p>&amp;#255;</p>&#255;</div>
<div><p>&amp;#338;</p>&#338;</div>
<div><p>&amp;#339;</p>&#339;</div>
<div><p>&amp;#376;</p>&#376;</div>
<div><p>&amp;#710;</p>&#710;</div>
<div><p>&amp;#732;</p>&#732;</div>
<div><p>&amp;#8192;</p>&#8192;</div>
<div><p>&amp;#8193;</p>&#8193;</div>
<div><p>&amp;#8194;</p>&#8194;</div>
<div><p>&amp;#8195;</p>&#8195;</div>
<div><p>&amp;#8196;</p>&#8196;</div>
<div><p>&amp;#8197;</p>&#8197;</div>
<div><p>&amp;#8198;</p>&#8198;</div>
<div><p>&amp;#8199;</p>&#8199;</div>
<div><p>&amp;#8200;</p>&#8200;</div>
<div><p>&amp;#8201;</p>&#8201;</div>
<div><p>&amp;#8202;</p>&#8202;</div>
<div><p>&amp;#8208;</p>&#8208;</div>
<div><p>&amp;#8209;</p>&#8209;</div>
<div><p>&amp;#8210;</p>&#8210;</div>
<div><p>&amp;#8211;</p>&#8211;</div>
<div><p>&amp;#8212;</p>&#8212;</div>
<div><p>&amp;#8216;</p>&#8216;</div>
<div><p>&amp;#8217;</p>&#8217;</div>
<div><p>&amp;#8218;</p>&#8218;</div>
<div><p>&amp;#8220;</p>&#8220;</div>
<div><p>&amp;#8221;</p>&#8221;</div>
<div><p>&amp;#8222;</p>&#8222;</div>
<div><p>&amp;#8226;</p>&#8226;</div>
<div><p>&amp;#8230;</p>&#8230;</div>
<div><p>&amp;#8239;</p>&#8239;</div>
<div><p>&amp;#8249;</p>&#8249;</div>
<div><p>&amp;#8250;</p>&#8250;</div>
<div><p>&amp;#8287;</p>&#8287;</div>
<div><p>&amp;#8364;</p>&#8364;</div>
<div><p>&amp;#8482;</p>&#8482;</div>
<div><p>&amp;#9724;</p>&#9724;</div>
<div><p>&amp;#64257;</p>&#64257;</div>
<div><p>&amp;#64258;</p>&#64258;</div>
<div><p>&amp;#64259;</p>&#64259;</div>
<div><p>&amp;#64260;</p>&#64260;</div>
</div>
</div>
</div>
</div>
<div id="specs">
</div>
<div id="installing">
<div class="section">
<div class="grid7 firstcol">
<h1>Installing Webfonts</h1>
<p>Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.</p>
<h2>1. Upload your webfonts</h2>
<p>You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.</p>
<h2>2. Include the webfont stylesheet</h2>
<p>A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the <a href="https://www.fontspring.com/blog/further-hardening-of-the-bulletproof-syntax">Fontspring blog post</a> about it. The code for it is as follows:</p>
<code>
@font-face{
font-family: 'MyWebFont';
src: url('WebFont.eot');
src: url('WebFont.eot?#iefix') format('embedded-opentype'),
url('WebFont.woff') format('woff'),
url('WebFont.ttf') format('truetype'),
url('WebFont.svg#webfont') format('svg');
}
</code>
<p>We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:</p>
<code>&lt;link rel=&quot;stylesheet&quot; href=&quot;stylesheet.css&quot; type=&quot;text/css&quot; charset=&quot;utf-8&quot; /&gt;</code>
<h2>3. Modify your own stylesheet</h2>
<p>To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:</p>
<code>p { font-family: 'WebFont', Arial, sans-serif; }</code>
<h2>4. Test</h2>
<p>Getting webfonts to work cross-browser <em>can</em> be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.</p>
</div>
<div class="grid5 sidebar">
<div class="box">
<h2>Troubleshooting<br />Font-Face Problems</h2>
<p>Having trouble getting your webfonts to load in your new website? Here are some tips to sort out what might be the problem.</p>
<h3>Fonts not showing in any browser</h3>
<p>This sounds like you need to work on the plumbing. You either did not upload the fonts to the correct directory, or you did not link the fonts properly in the CSS. If you've confirmed that all this is correct and you still have a problem, take a look at your .htaccess file and see if requests are getting intercepted.</p>
<h3>Fonts not loading in iPhone or iPad</h3>
<p>The most common problem here is that you are serving the fonts from an IIS server. IIS refuses to serve files that have unknown MIME types. If that is the case, you must set the MIME type for SVG to "image/svg+xml" in the server settings. Follow these instructions from Microsoft if you need help.</p>
<h3>Fonts not loading in Firefox</h3>
<p>The primary reason for this failure? You are still using a version Firefox older than 3.5. So upgrade already! If that isn't it, then you are very likely serving fonts from a different domain. Firefox requires that all font assets be served from the same domain. Lastly it is possible that you need to add WOFF to your list of MIME types (if you are serving via IIS.)</p>
<h3>Fonts not loading in IE</h3>
<p>Are you looking at Internet Explorer on an actual Windows machine or are you cheating by using a service like Adobe BrowserLab? Many of these screenshot services do not render @font-face for IE. Best to test it on a real machine.</p>
<h3>Fonts not loading in IE9</h3>
<p>IE9, like Firefox, requires that fonts be served from the same domain as the website. Make sure that is the case.</p>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<p>&copy;2010-2017 Font Squirrel. All rights reserved.</p>
</div>
</div>
</body>
</html>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,622 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
(function($){$.fn.easyTabs=function(option){var param=jQuery.extend({fadeSpeed:"fast",defaultContent:1,activeClass:'active'},option);$(this).each(function(){var thisId="#"+this.id;if(param.defaultContent==''){param.defaultContent=1;}
if(typeof param.defaultContent=="number")
{var defaultTab=$(thisId+" .tabs li:eq("+(param.defaultContent-1)+") a").attr('href').substr(1);}else{var defaultTab=param.defaultContent;}
$(thisId+" .tabs li a").each(function(){var tabToHide=$(this).attr('href').substr(1);$("#"+tabToHide).addClass('easytabs-tab-content');});hideAll();changeContent(defaultTab);function hideAll(){$(thisId+" .easytabs-tab-content").hide();}
function changeContent(tabId){hideAll();$(thisId+" .tabs li").removeClass(param.activeClass);$(thisId+" .tabs li a[href=#"+tabId+"]").closest('li').addClass(param.activeClass);if(param.fadeSpeed!="none")
{$(thisId+" #"+tabId).fadeIn(param.fadeSpeed);}else{$(thisId+" #"+tabId).show();}}
$(thisId+" .tabs li").click(function(){var tabId=$(this).find('a').attr('href').substr(1);changeContent(tabId);return false;});});}})(jQuery);
</script>
<link rel="stylesheet" href="specimen_files/specimen_stylesheet.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" />
<style type="text/css">
body{
font-family: 'cantarelloblique';
}
</style>
<title>Cantarell Oblique Specimen</title>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$('#container').easyTabs({defaultContent:1});
});
</script>
</head>
<body>
<div id="container">
<div id="header">
Cantarell Oblique </div>
<ul class="tabs">
<li><a href="#specimen">Specimen</a></li>
<li><a href="#layout">Sample Layout</a></li>
<li><a href="#glyphs">Glyphs &amp; Languages</a></li>
<li><a href="#installing">Installing Webfonts</a></li>
</ul>
<div id="main_content">
<div id="specimen">
<div class="section">
<div class="grid12 firstcol">
<div class="huge">AaBb</div>
</div>
</div>
<div class="section">
<div class="glyph_range">A&#x200B;B&#x200b;C&#x200b;D&#x200b;E&#x200b;F&#x200b;G&#x200b;H&#x200b;I&#x200b;J&#x200b;K&#x200b;L&#x200b;M&#x200b;N&#x200b;O&#x200b;P&#x200b;Q&#x200b;R&#x200b;S&#x200b;T&#x200b;U&#x200b;V&#x200b;W&#x200b;X&#x200b;Y&#x200b;Z&#x200b;a&#x200b;b&#x200b;c&#x200b;d&#x200b;e&#x200b;f&#x200b;g&#x200b;h&#x200b;i&#x200b;j&#x200b;k&#x200b;l&#x200b;m&#x200b;n&#x200b;o&#x200b;p&#x200b;q&#x200b;r&#x200b;s&#x200b;t&#x200b;u&#x200b;v&#x200b;w&#x200b;x&#x200b;y&#x200b;z&#x200b;1&#x200b;2&#x200b;3&#x200b;4&#x200b;5&#x200b;6&#x200b;7&#x200b;8&#x200b;9&#x200b;0&#x200b;&amp;&#x200b;.&#x200b;,&#x200b;?&#x200b;!&#x200b;&#64;&#x200b;(&#x200b;)&#x200b;#&#x200b;$&#x200b;%&#x200b;*&#x200b;+&#x200b;-&#x200b;=&#x200b;:&#x200b;;</div>
</div>
<div class="section">
<div class="grid12 firstcol">
<table class="sample_table">
<tr><td>10</td><td class="size10">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>11</td><td class="size11">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>12</td><td class="size12">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>13</td><td class="size13">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>14</td><td class="size14">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>16</td><td class="size16">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>18</td><td class="size18">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>20</td><td class="size20">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>24</td><td class="size24">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>30</td><td class="size30">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>36</td><td class="size36">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>48</td><td class="size48">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>60</td><td class="size60">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>72</td><td class="size72">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>90</td><td class="size90">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
</table>
</div>
</div>
<div class="section" id="bodycomparison">
<div id="xheight">
<div class="fontbody">&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;body</div><div class="arialbody">body</div><div class="verdanabody">body</div><div class="georgiabody">body</div></div>
<div class="fontbody" style="z-index:1">
body<span>Cantarell Oblique</span>
</div>
<div class="arialbody" style="z-index:1">
body<span>Arial</span>
</div>
<div class="verdanabody" style="z-index:1">
body<span>Verdana</span>
</div>
<div class="georgiabody" style="z-index:1">
body<span>Georgia</span>
</div>
</div>
<div class="section psample psample_row1" id="">
<div class="grid2 firstcol">
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row2" id="">
<div class="grid3 firstcol">
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid5">
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row3" id="">
<div class="grid5 firstcol">
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid7">
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row4" id="">
<div class="grid12 firstcol">
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row1 fullreverse">
<div class="grid2 firstcol">
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample psample_row2 fullreverse">
<div class="grid3 firstcol">
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid5">
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample fullreverse psample_row3" id="">
<div class="grid5 firstcol">
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid7">
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample fullreverse psample_row4" id="" style="border-bottom: 20px #000 solid;">
<div class="grid12 firstcol">
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
</div>
<div id="layout">
<div class="section">
<div class="grid12 firstcol">
<h1>Lorem Ipsum Dolor</h1>
<h2>Etiam porta sem malesuada magna mollis euismod</h2>
<p class="byline">By <a href="#link">Aenean Lacinia</a></p>
</div>
</div>
<div class="section">
<div class="grid8 firstcol">
<p class="large">Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
<h3>Pellentesque ornare sem</h3>
<p>Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit. </p>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. </p>
<p>Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur. </p>
<p>Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla. </p>
<h3>Cras mattis consectetur</h3>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum. </p>
<p>Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.</p>
</div>
<div class="grid4 sidebar">
<div class="box reverse">
<p class="last">Nullam quis risus eget urna mollis ornare vel eu leo. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
</div>
<p class="caption">Maecenas sed diam eget risus varius.</p>
<p>Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
<p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo. </p>
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec ullamcorper nulla non metus auctor fringilla. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
</div>
</div>
</div>
<div id="glyphs">
<div class="section">
<div class="grid12 firstcol">
<h1>Language Support</h1>
<p>The subset of Cantarell Oblique in this kit supports the following languages:<br />
Albanian, Basque, Breton, Chamorro, Danish, Dutch, English, Faroese, Finnish, French, Frisian, Galician, German, Icelandic, Italian, Malagasy, Norwegian, Portuguese, Spanish, Alsatian, Aragonese, Arapaho, Arrernte, Asturian, Aymara, Bislama, Cebuano, Corsican, Fijian, French_creole, Genoese, Gilbertese, Greenlandic, Haitian_creole, Hiligaynon, Hmong, Hopi, Ibanag, Iloko_ilokano, Indonesian, Interglossa_glosa, Interlingua, Irish_gaelic, Jerriais, Lojban, Lombard, Luxembourgeois, Manx, Mohawk, Norfolk_pitcairnese, Occitan, Oromo, Pangasinan, Papiamento, Piedmontese, Potawatomi, Rhaeto-romance, Romansh, Rotokas, Sami_lule, Samoan, Sardinian, Scots_gaelic, Seychelles_creole, Shona, Sicilian, Somali, Southern_ndebele, Swahili, Swati_swazi, Tagalog_filipino_pilipino, Tetum, Tok_pisin, Uyghur_latinized, Volapuk, Walloon, Warlpiri, Xhosa, Yapese, Zulu, Latinbasic, Ubasic, Demo </p>
<h1>Glyph Chart</h1>
<p>The subset of Cantarell Oblique in this kit includes all the glyphs listed below. Unicode entities are included above each glyph to help you insert individual characters into your layout.</p>
<div id="glyph_chart">
<div><p>&amp;#13;</p>&#13;</div>
<div><p>&amp;#32;</p>&#32;</div>
<div><p>&amp;#33;</p>&#33;</div>
<div><p>&amp;#34;</p>&#34;</div>
<div><p>&amp;#35;</p>&#35;</div>
<div><p>&amp;#36;</p>&#36;</div>
<div><p>&amp;#37;</p>&#37;</div>
<div><p>&amp;#38;</p>&#38;</div>
<div><p>&amp;#39;</p>&#39;</div>
<div><p>&amp;#40;</p>&#40;</div>
<div><p>&amp;#41;</p>&#41;</div>
<div><p>&amp;#42;</p>&#42;</div>
<div><p>&amp;#43;</p>&#43;</div>
<div><p>&amp;#44;</p>&#44;</div>
<div><p>&amp;#45;</p>&#45;</div>
<div><p>&amp;#46;</p>&#46;</div>
<div><p>&amp;#47;</p>&#47;</div>
<div><p>&amp;#48;</p>&#48;</div>
<div><p>&amp;#49;</p>&#49;</div>
<div><p>&amp;#50;</p>&#50;</div>
<div><p>&amp;#51;</p>&#51;</div>
<div><p>&amp;#52;</p>&#52;</div>
<div><p>&amp;#53;</p>&#53;</div>
<div><p>&amp;#54;</p>&#54;</div>
<div><p>&amp;#55;</p>&#55;</div>
<div><p>&amp;#56;</p>&#56;</div>
<div><p>&amp;#57;</p>&#57;</div>
<div><p>&amp;#58;</p>&#58;</div>
<div><p>&amp;#59;</p>&#59;</div>
<div><p>&amp;#60;</p>&#60;</div>
<div><p>&amp;#61;</p>&#61;</div>
<div><p>&amp;#62;</p>&#62;</div>
<div><p>&amp;#63;</p>&#63;</div>
<div><p>&amp;#64;</p>&#64;</div>
<div><p>&amp;#65;</p>&#65;</div>
<div><p>&amp;#66;</p>&#66;</div>
<div><p>&amp;#67;</p>&#67;</div>
<div><p>&amp;#68;</p>&#68;</div>
<div><p>&amp;#69;</p>&#69;</div>
<div><p>&amp;#70;</p>&#70;</div>
<div><p>&amp;#71;</p>&#71;</div>
<div><p>&amp;#72;</p>&#72;</div>
<div><p>&amp;#73;</p>&#73;</div>
<div><p>&amp;#74;</p>&#74;</div>
<div><p>&amp;#75;</p>&#75;</div>
<div><p>&amp;#76;</p>&#76;</div>
<div><p>&amp;#77;</p>&#77;</div>
<div><p>&amp;#78;</p>&#78;</div>
<div><p>&amp;#79;</p>&#79;</div>
<div><p>&amp;#80;</p>&#80;</div>
<div><p>&amp;#81;</p>&#81;</div>
<div><p>&amp;#82;</p>&#82;</div>
<div><p>&amp;#83;</p>&#83;</div>
<div><p>&amp;#84;</p>&#84;</div>
<div><p>&amp;#85;</p>&#85;</div>
<div><p>&amp;#86;</p>&#86;</div>
<div><p>&amp;#87;</p>&#87;</div>
<div><p>&amp;#88;</p>&#88;</div>
<div><p>&amp;#89;</p>&#89;</div>
<div><p>&amp;#90;</p>&#90;</div>
<div><p>&amp;#91;</p>&#91;</div>
<div><p>&amp;#92;</p>&#92;</div>
<div><p>&amp;#93;</p>&#93;</div>
<div><p>&amp;#94;</p>&#94;</div>
<div><p>&amp;#95;</p>&#95;</div>
<div><p>&amp;#96;</p>&#96;</div>
<div><p>&amp;#97;</p>&#97;</div>
<div><p>&amp;#98;</p>&#98;</div>
<div><p>&amp;#99;</p>&#99;</div>
<div><p>&amp;#100;</p>&#100;</div>
<div><p>&amp;#101;</p>&#101;</div>
<div><p>&amp;#102;</p>&#102;</div>
<div><p>&amp;#103;</p>&#103;</div>
<div><p>&amp;#104;</p>&#104;</div>
<div><p>&amp;#105;</p>&#105;</div>
<div><p>&amp;#106;</p>&#106;</div>
<div><p>&amp;#107;</p>&#107;</div>
<div><p>&amp;#108;</p>&#108;</div>
<div><p>&amp;#109;</p>&#109;</div>
<div><p>&amp;#110;</p>&#110;</div>
<div><p>&amp;#111;</p>&#111;</div>
<div><p>&amp;#112;</p>&#112;</div>
<div><p>&amp;#113;</p>&#113;</div>
<div><p>&amp;#114;</p>&#114;</div>
<div><p>&amp;#115;</p>&#115;</div>
<div><p>&amp;#116;</p>&#116;</div>
<div><p>&amp;#117;</p>&#117;</div>
<div><p>&amp;#118;</p>&#118;</div>
<div><p>&amp;#119;</p>&#119;</div>
<div><p>&amp;#120;</p>&#120;</div>
<div><p>&amp;#121;</p>&#121;</div>
<div><p>&amp;#122;</p>&#122;</div>
<div><p>&amp;#123;</p>&#123;</div>
<div><p>&amp;#124;</p>&#124;</div>
<div><p>&amp;#125;</p>&#125;</div>
<div><p>&amp;#126;</p>&#126;</div>
<div><p>&amp;#160;</p>&#160;</div>
<div><p>&amp;#161;</p>&#161;</div>
<div><p>&amp;#162;</p>&#162;</div>
<div><p>&amp;#163;</p>&#163;</div>
<div><p>&amp;#164;</p>&#164;</div>
<div><p>&amp;#165;</p>&#165;</div>
<div><p>&amp;#166;</p>&#166;</div>
<div><p>&amp;#167;</p>&#167;</div>
<div><p>&amp;#168;</p>&#168;</div>
<div><p>&amp;#169;</p>&#169;</div>
<div><p>&amp;#170;</p>&#170;</div>
<div><p>&amp;#171;</p>&#171;</div>
<div><p>&amp;#172;</p>&#172;</div>
<div><p>&amp;#173;</p>&#173;</div>
<div><p>&amp;#174;</p>&#174;</div>
<div><p>&amp;#175;</p>&#175;</div>
<div><p>&amp;#176;</p>&#176;</div>
<div><p>&amp;#177;</p>&#177;</div>
<div><p>&amp;#178;</p>&#178;</div>
<div><p>&amp;#179;</p>&#179;</div>
<div><p>&amp;#180;</p>&#180;</div>
<div><p>&amp;#181;</p>&#181;</div>
<div><p>&amp;#182;</p>&#182;</div>
<div><p>&amp;#183;</p>&#183;</div>
<div><p>&amp;#184;</p>&#184;</div>
<div><p>&amp;#185;</p>&#185;</div>
<div><p>&amp;#186;</p>&#186;</div>
<div><p>&amp;#187;</p>&#187;</div>
<div><p>&amp;#188;</p>&#188;</div>
<div><p>&amp;#189;</p>&#189;</div>
<div><p>&amp;#190;</p>&#190;</div>
<div><p>&amp;#191;</p>&#191;</div>
<div><p>&amp;#192;</p>&#192;</div>
<div><p>&amp;#193;</p>&#193;</div>
<div><p>&amp;#194;</p>&#194;</div>
<div><p>&amp;#195;</p>&#195;</div>
<div><p>&amp;#196;</p>&#196;</div>
<div><p>&amp;#197;</p>&#197;</div>
<div><p>&amp;#198;</p>&#198;</div>
<div><p>&amp;#199;</p>&#199;</div>
<div><p>&amp;#200;</p>&#200;</div>
<div><p>&amp;#201;</p>&#201;</div>
<div><p>&amp;#202;</p>&#202;</div>
<div><p>&amp;#203;</p>&#203;</div>
<div><p>&amp;#204;</p>&#204;</div>
<div><p>&amp;#205;</p>&#205;</div>
<div><p>&amp;#206;</p>&#206;</div>
<div><p>&amp;#207;</p>&#207;</div>
<div><p>&amp;#208;</p>&#208;</div>
<div><p>&amp;#209;</p>&#209;</div>
<div><p>&amp;#210;</p>&#210;</div>
<div><p>&amp;#211;</p>&#211;</div>
<div><p>&amp;#212;</p>&#212;</div>
<div><p>&amp;#213;</p>&#213;</div>
<div><p>&amp;#214;</p>&#214;</div>
<div><p>&amp;#215;</p>&#215;</div>
<div><p>&amp;#216;</p>&#216;</div>
<div><p>&amp;#217;</p>&#217;</div>
<div><p>&amp;#218;</p>&#218;</div>
<div><p>&amp;#219;</p>&#219;</div>
<div><p>&amp;#220;</p>&#220;</div>
<div><p>&amp;#221;</p>&#221;</div>
<div><p>&amp;#222;</p>&#222;</div>
<div><p>&amp;#223;</p>&#223;</div>
<div><p>&amp;#224;</p>&#224;</div>
<div><p>&amp;#225;</p>&#225;</div>
<div><p>&amp;#226;</p>&#226;</div>
<div><p>&amp;#227;</p>&#227;</div>
<div><p>&amp;#228;</p>&#228;</div>
<div><p>&amp;#229;</p>&#229;</div>
<div><p>&amp;#230;</p>&#230;</div>
<div><p>&amp;#231;</p>&#231;</div>
<div><p>&amp;#232;</p>&#232;</div>
<div><p>&amp;#233;</p>&#233;</div>
<div><p>&amp;#234;</p>&#234;</div>
<div><p>&amp;#235;</p>&#235;</div>
<div><p>&amp;#236;</p>&#236;</div>
<div><p>&amp;#237;</p>&#237;</div>
<div><p>&amp;#238;</p>&#238;</div>
<div><p>&amp;#239;</p>&#239;</div>
<div><p>&amp;#240;</p>&#240;</div>
<div><p>&amp;#241;</p>&#241;</div>
<div><p>&amp;#242;</p>&#242;</div>
<div><p>&amp;#243;</p>&#243;</div>
<div><p>&amp;#244;</p>&#244;</div>
<div><p>&amp;#245;</p>&#245;</div>
<div><p>&amp;#246;</p>&#246;</div>
<div><p>&amp;#247;</p>&#247;</div>
<div><p>&amp;#248;</p>&#248;</div>
<div><p>&amp;#249;</p>&#249;</div>
<div><p>&amp;#250;</p>&#250;</div>
<div><p>&amp;#251;</p>&#251;</div>
<div><p>&amp;#252;</p>&#252;</div>
<div><p>&amp;#253;</p>&#253;</div>
<div><p>&amp;#254;</p>&#254;</div>
<div><p>&amp;#255;</p>&#255;</div>
<div><p>&amp;#338;</p>&#338;</div>
<div><p>&amp;#339;</p>&#339;</div>
<div><p>&amp;#376;</p>&#376;</div>
<div><p>&amp;#710;</p>&#710;</div>
<div><p>&amp;#732;</p>&#732;</div>
<div><p>&amp;#8192;</p>&#8192;</div>
<div><p>&amp;#8193;</p>&#8193;</div>
<div><p>&amp;#8194;</p>&#8194;</div>
<div><p>&amp;#8195;</p>&#8195;</div>
<div><p>&amp;#8196;</p>&#8196;</div>
<div><p>&amp;#8197;</p>&#8197;</div>
<div><p>&amp;#8198;</p>&#8198;</div>
<div><p>&amp;#8199;</p>&#8199;</div>
<div><p>&amp;#8200;</p>&#8200;</div>
<div><p>&amp;#8201;</p>&#8201;</div>
<div><p>&amp;#8202;</p>&#8202;</div>
<div><p>&amp;#8208;</p>&#8208;</div>
<div><p>&amp;#8209;</p>&#8209;</div>
<div><p>&amp;#8210;</p>&#8210;</div>
<div><p>&amp;#8211;</p>&#8211;</div>
<div><p>&amp;#8212;</p>&#8212;</div>
<div><p>&amp;#8216;</p>&#8216;</div>
<div><p>&amp;#8217;</p>&#8217;</div>
<div><p>&amp;#8218;</p>&#8218;</div>
<div><p>&amp;#8220;</p>&#8220;</div>
<div><p>&amp;#8221;</p>&#8221;</div>
<div><p>&amp;#8222;</p>&#8222;</div>
<div><p>&amp;#8226;</p>&#8226;</div>
<div><p>&amp;#8230;</p>&#8230;</div>
<div><p>&amp;#8239;</p>&#8239;</div>
<div><p>&amp;#8249;</p>&#8249;</div>
<div><p>&amp;#8250;</p>&#8250;</div>
<div><p>&amp;#8287;</p>&#8287;</div>
<div><p>&amp;#8364;</p>&#8364;</div>
<div><p>&amp;#8482;</p>&#8482;</div>
<div><p>&amp;#9724;</p>&#9724;</div>
<div><p>&amp;#64257;</p>&#64257;</div>
<div><p>&amp;#64258;</p>&#64258;</div>
<div><p>&amp;#64259;</p>&#64259;</div>
<div><p>&amp;#64260;</p>&#64260;</div>
</div>
</div>
</div>
</div>
<div id="specs">
</div>
<div id="installing">
<div class="section">
<div class="grid7 firstcol">
<h1>Installing Webfonts</h1>
<p>Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.</p>
<h2>1. Upload your webfonts</h2>
<p>You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.</p>
<h2>2. Include the webfont stylesheet</h2>
<p>A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the <a href="https://www.fontspring.com/blog/further-hardening-of-the-bulletproof-syntax">Fontspring blog post</a> about it. The code for it is as follows:</p>
<code>
@font-face{
font-family: 'MyWebFont';
src: url('WebFont.eot');
src: url('WebFont.eot?#iefix') format('embedded-opentype'),
url('WebFont.woff') format('woff'),
url('WebFont.ttf') format('truetype'),
url('WebFont.svg#webfont') format('svg');
}
</code>
<p>We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:</p>
<code>&lt;link rel=&quot;stylesheet&quot; href=&quot;stylesheet.css&quot; type=&quot;text/css&quot; charset=&quot;utf-8&quot; /&gt;</code>
<h2>3. Modify your own stylesheet</h2>
<p>To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:</p>
<code>p { font-family: 'WebFont', Arial, sans-serif; }</code>
<h2>4. Test</h2>
<p>Getting webfonts to work cross-browser <em>can</em> be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.</p>
</div>
<div class="grid5 sidebar">
<div class="box">
<h2>Troubleshooting<br />Font-Face Problems</h2>
<p>Having trouble getting your webfonts to load in your new website? Here are some tips to sort out what might be the problem.</p>
<h3>Fonts not showing in any browser</h3>
<p>This sounds like you need to work on the plumbing. You either did not upload the fonts to the correct directory, or you did not link the fonts properly in the CSS. If you've confirmed that all this is correct and you still have a problem, take a look at your .htaccess file and see if requests are getting intercepted.</p>
<h3>Fonts not loading in iPhone or iPad</h3>
<p>The most common problem here is that you are serving the fonts from an IIS server. IIS refuses to serve files that have unknown MIME types. If that is the case, you must set the MIME type for SVG to "image/svg+xml" in the server settings. Follow these instructions from Microsoft if you need help.</p>
<h3>Fonts not loading in Firefox</h3>
<p>The primary reason for this failure? You are still using a version Firefox older than 3.5. So upgrade already! If that isn't it, then you are very likely serving fonts from a different domain. Firefox requires that all font assets be served from the same domain. Lastly it is possible that you need to add WOFF to your list of MIME types (if you are serving via IIS.)</p>
<h3>Fonts not loading in IE</h3>
<p>Are you looking at Internet Explorer on an actual Windows machine or are you cheating by using a service like Adobe BrowserLab? Many of these screenshot services do not render @font-face for IE. Best to test it on a real machine.</p>
<h3>Fonts not loading in IE9</h3>
<p>IE9, like Firefox, requires that fonts be served from the same domain as the website. Make sure that is the case.</p>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<p>&copy;2010-2017 Font Squirrel. All rights reserved.</p>
</div>
</div>
</body>
</html>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,622 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script type="text/javascript">
(function($){$.fn.easyTabs=function(option){var param=jQuery.extend({fadeSpeed:"fast",defaultContent:1,activeClass:'active'},option);$(this).each(function(){var thisId="#"+this.id;if(param.defaultContent==''){param.defaultContent=1;}
if(typeof param.defaultContent=="number")
{var defaultTab=$(thisId+" .tabs li:eq("+(param.defaultContent-1)+") a").attr('href').substr(1);}else{var defaultTab=param.defaultContent;}
$(thisId+" .tabs li a").each(function(){var tabToHide=$(this).attr('href').substr(1);$("#"+tabToHide).addClass('easytabs-tab-content');});hideAll();changeContent(defaultTab);function hideAll(){$(thisId+" .easytabs-tab-content").hide();}
function changeContent(tabId){hideAll();$(thisId+" .tabs li").removeClass(param.activeClass);$(thisId+" .tabs li a[href=#"+tabId+"]").closest('li').addClass(param.activeClass);if(param.fadeSpeed!="none")
{$(thisId+" #"+tabId).fadeIn(param.fadeSpeed);}else{$(thisId+" #"+tabId).show();}}
$(thisId+" .tabs li").click(function(){var tabId=$(this).find('a').attr('href').substr(1);changeContent(tabId);return false;});});}})(jQuery);
</script>
<link rel="stylesheet" href="specimen_files/specimen_stylesheet.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="stylesheet.css" type="text/css" charset="utf-8" />
<style type="text/css">
body{
font-family: 'cantarellregular';
}
</style>
<title>Cantarell Regular Specimen</title>
<script type="text/javascript" charset="utf-8">
$(document).ready(function() {
$('#container').easyTabs({defaultContent:1});
});
</script>
</head>
<body>
<div id="container">
<div id="header">
Cantarell Regular </div>
<ul class="tabs">
<li><a href="#specimen">Specimen</a></li>
<li><a href="#layout">Sample Layout</a></li>
<li><a href="#glyphs">Glyphs &amp; Languages</a></li>
<li><a href="#installing">Installing Webfonts</a></li>
</ul>
<div id="main_content">
<div id="specimen">
<div class="section">
<div class="grid12 firstcol">
<div class="huge">AaBb</div>
</div>
</div>
<div class="section">
<div class="glyph_range">A&#x200B;B&#x200b;C&#x200b;D&#x200b;E&#x200b;F&#x200b;G&#x200b;H&#x200b;I&#x200b;J&#x200b;K&#x200b;L&#x200b;M&#x200b;N&#x200b;O&#x200b;P&#x200b;Q&#x200b;R&#x200b;S&#x200b;T&#x200b;U&#x200b;V&#x200b;W&#x200b;X&#x200b;Y&#x200b;Z&#x200b;a&#x200b;b&#x200b;c&#x200b;d&#x200b;e&#x200b;f&#x200b;g&#x200b;h&#x200b;i&#x200b;j&#x200b;k&#x200b;l&#x200b;m&#x200b;n&#x200b;o&#x200b;p&#x200b;q&#x200b;r&#x200b;s&#x200b;t&#x200b;u&#x200b;v&#x200b;w&#x200b;x&#x200b;y&#x200b;z&#x200b;1&#x200b;2&#x200b;3&#x200b;4&#x200b;5&#x200b;6&#x200b;7&#x200b;8&#x200b;9&#x200b;0&#x200b;&amp;&#x200b;.&#x200b;,&#x200b;?&#x200b;!&#x200b;&#64;&#x200b;(&#x200b;)&#x200b;#&#x200b;$&#x200b;%&#x200b;*&#x200b;+&#x200b;-&#x200b;=&#x200b;:&#x200b;;</div>
</div>
<div class="section">
<div class="grid12 firstcol">
<table class="sample_table">
<tr><td>10</td><td class="size10">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>11</td><td class="size11">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>12</td><td class="size12">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>13</td><td class="size13">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>14</td><td class="size14">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>16</td><td class="size16">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>18</td><td class="size18">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>20</td><td class="size20">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>24</td><td class="size24">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>30</td><td class="size30">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>36</td><td class="size36">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>48</td><td class="size48">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>60</td><td class="size60">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>72</td><td class="size72">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
<tr><td>90</td><td class="size90">abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ</td></tr>
</table>
</div>
</div>
<div class="section" id="bodycomparison">
<div id="xheight">
<div class="fontbody">&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;body</div><div class="arialbody">body</div><div class="verdanabody">body</div><div class="georgiabody">body</div></div>
<div class="fontbody" style="z-index:1">
body<span>Cantarell Regular</span>
</div>
<div class="arialbody" style="z-index:1">
body<span>Arial</span>
</div>
<div class="verdanabody" style="z-index:1">
body<span>Verdana</span>
</div>
<div class="georgiabody" style="z-index:1">
body<span>Georgia</span>
</div>
</div>
<div class="section psample psample_row1" id="">
<div class="grid2 firstcol">
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row2" id="">
<div class="grid3 firstcol">
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid5">
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row3" id="">
<div class="grid5 firstcol">
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid7">
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row4" id="">
<div class="grid12 firstcol">
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="white_blend"></div>
</div>
<div class="section psample psample_row1 fullreverse">
<div class="grid2 firstcol">
<p class="size10"><span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size11"><span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid3">
<p class="size12"><span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size13"><span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample psample_row2 fullreverse">
<div class="grid3 firstcol">
<p class="size14"><span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid4">
<p class="size16"><span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid5">
<p class="size18"><span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample fullreverse psample_row3" id="">
<div class="grid5 firstcol">
<p class="size20"><span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="grid7">
<p class="size24"><span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
<div class="section psample fullreverse psample_row4" id="" style="border-bottom: 20px #000 solid;">
<div class="grid12 firstcol">
<p class="size30"><span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit libero, a pharetra augue.</p>
</div>
<div class="black_blend"></div>
</div>
</div>
<div id="layout">
<div class="section">
<div class="grid12 firstcol">
<h1>Lorem Ipsum Dolor</h1>
<h2>Etiam porta sem malesuada magna mollis euismod</h2>
<p class="byline">By <a href="#link">Aenean Lacinia</a></p>
</div>
</div>
<div class="section">
<div class="grid8 firstcol">
<p class="large">Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
<h3>Pellentesque ornare sem</h3>
<p>Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut id elit. </p>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. </p>
<p>Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur. </p>
<p>Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non magna. Donec ullamcorper nulla non metus auctor fringilla. </p>
<h3>Cras mattis consectetur</h3>
<p>Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum. </p>
<p>Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.</p>
</div>
<div class="grid4 sidebar">
<div class="box reverse">
<p class="last">Nullam quis risus eget urna mollis ornare vel eu leo. Donec ullamcorper nulla non metus auctor fringilla. Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. </p>
</div>
<p class="caption">Maecenas sed diam eget risus varius.</p>
<p>Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Vestibulum id ligula porta felis euismod semper. Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius blandit sit amet non magna. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
<p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla sed consectetur. Nullam quis risus eget urna mollis ornare vel eu leo. </p>
<p>Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec ullamcorper nulla non metus auctor fringilla. Maecenas faucibus mollis interdum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. </p>
</div>
</div>
</div>
<div id="glyphs">
<div class="section">
<div class="grid12 firstcol">
<h1>Language Support</h1>
<p>The subset of Cantarell Regular in this kit supports the following languages:<br />
Albanian, Basque, Breton, Chamorro, Danish, Dutch, English, Faroese, Finnish, French, Frisian, Galician, German, Icelandic, Italian, Malagasy, Norwegian, Portuguese, Spanish, Alsatian, Aragonese, Arapaho, Arrernte, Asturian, Aymara, Bislama, Cebuano, Corsican, Fijian, French_creole, Genoese, Gilbertese, Greenlandic, Haitian_creole, Hiligaynon, Hmong, Hopi, Ibanag, Iloko_ilokano, Indonesian, Interglossa_glosa, Interlingua, Irish_gaelic, Jerriais, Lojban, Lombard, Luxembourgeois, Manx, Mohawk, Norfolk_pitcairnese, Occitan, Oromo, Pangasinan, Papiamento, Piedmontese, Potawatomi, Rhaeto-romance, Romansh, Rotokas, Sami_lule, Samoan, Sardinian, Scots_gaelic, Seychelles_creole, Shona, Sicilian, Somali, Southern_ndebele, Swahili, Swati_swazi, Tagalog_filipino_pilipino, Tetum, Tok_pisin, Uyghur_latinized, Volapuk, Walloon, Warlpiri, Xhosa, Yapese, Zulu, Latinbasic, Ubasic, Demo </p>
<h1>Glyph Chart</h1>
<p>The subset of Cantarell Regular in this kit includes all the glyphs listed below. Unicode entities are included above each glyph to help you insert individual characters into your layout.</p>
<div id="glyph_chart">
<div><p>&amp;#13;</p>&#13;</div>
<div><p>&amp;#32;</p>&#32;</div>
<div><p>&amp;#33;</p>&#33;</div>
<div><p>&amp;#34;</p>&#34;</div>
<div><p>&amp;#35;</p>&#35;</div>
<div><p>&amp;#36;</p>&#36;</div>
<div><p>&amp;#37;</p>&#37;</div>
<div><p>&amp;#38;</p>&#38;</div>
<div><p>&amp;#39;</p>&#39;</div>
<div><p>&amp;#40;</p>&#40;</div>
<div><p>&amp;#41;</p>&#41;</div>
<div><p>&amp;#42;</p>&#42;</div>
<div><p>&amp;#43;</p>&#43;</div>
<div><p>&amp;#44;</p>&#44;</div>
<div><p>&amp;#45;</p>&#45;</div>
<div><p>&amp;#46;</p>&#46;</div>
<div><p>&amp;#47;</p>&#47;</div>
<div><p>&amp;#48;</p>&#48;</div>
<div><p>&amp;#49;</p>&#49;</div>
<div><p>&amp;#50;</p>&#50;</div>
<div><p>&amp;#51;</p>&#51;</div>
<div><p>&amp;#52;</p>&#52;</div>
<div><p>&amp;#53;</p>&#53;</div>
<div><p>&amp;#54;</p>&#54;</div>
<div><p>&amp;#55;</p>&#55;</div>
<div><p>&amp;#56;</p>&#56;</div>
<div><p>&amp;#57;</p>&#57;</div>
<div><p>&amp;#58;</p>&#58;</div>
<div><p>&amp;#59;</p>&#59;</div>
<div><p>&amp;#60;</p>&#60;</div>
<div><p>&amp;#61;</p>&#61;</div>
<div><p>&amp;#62;</p>&#62;</div>
<div><p>&amp;#63;</p>&#63;</div>
<div><p>&amp;#64;</p>&#64;</div>
<div><p>&amp;#65;</p>&#65;</div>
<div><p>&amp;#66;</p>&#66;</div>
<div><p>&amp;#67;</p>&#67;</div>
<div><p>&amp;#68;</p>&#68;</div>
<div><p>&amp;#69;</p>&#69;</div>
<div><p>&amp;#70;</p>&#70;</div>
<div><p>&amp;#71;</p>&#71;</div>
<div><p>&amp;#72;</p>&#72;</div>
<div><p>&amp;#73;</p>&#73;</div>
<div><p>&amp;#74;</p>&#74;</div>
<div><p>&amp;#75;</p>&#75;</div>
<div><p>&amp;#76;</p>&#76;</div>
<div><p>&amp;#77;</p>&#77;</div>
<div><p>&amp;#78;</p>&#78;</div>
<div><p>&amp;#79;</p>&#79;</div>
<div><p>&amp;#80;</p>&#80;</div>
<div><p>&amp;#81;</p>&#81;</div>
<div><p>&amp;#82;</p>&#82;</div>
<div><p>&amp;#83;</p>&#83;</div>
<div><p>&amp;#84;</p>&#84;</div>
<div><p>&amp;#85;</p>&#85;</div>
<div><p>&amp;#86;</p>&#86;</div>
<div><p>&amp;#87;</p>&#87;</div>
<div><p>&amp;#88;</p>&#88;</div>
<div><p>&amp;#89;</p>&#89;</div>
<div><p>&amp;#90;</p>&#90;</div>
<div><p>&amp;#91;</p>&#91;</div>
<div><p>&amp;#92;</p>&#92;</div>
<div><p>&amp;#93;</p>&#93;</div>
<div><p>&amp;#94;</p>&#94;</div>
<div><p>&amp;#95;</p>&#95;</div>
<div><p>&amp;#96;</p>&#96;</div>
<div><p>&amp;#97;</p>&#97;</div>
<div><p>&amp;#98;</p>&#98;</div>
<div><p>&amp;#99;</p>&#99;</div>
<div><p>&amp;#100;</p>&#100;</div>
<div><p>&amp;#101;</p>&#101;</div>
<div><p>&amp;#102;</p>&#102;</div>
<div><p>&amp;#103;</p>&#103;</div>
<div><p>&amp;#104;</p>&#104;</div>
<div><p>&amp;#105;</p>&#105;</div>
<div><p>&amp;#106;</p>&#106;</div>
<div><p>&amp;#107;</p>&#107;</div>
<div><p>&amp;#108;</p>&#108;</div>
<div><p>&amp;#109;</p>&#109;</div>
<div><p>&amp;#110;</p>&#110;</div>
<div><p>&amp;#111;</p>&#111;</div>
<div><p>&amp;#112;</p>&#112;</div>
<div><p>&amp;#113;</p>&#113;</div>
<div><p>&amp;#114;</p>&#114;</div>
<div><p>&amp;#115;</p>&#115;</div>
<div><p>&amp;#116;</p>&#116;</div>
<div><p>&amp;#117;</p>&#117;</div>
<div><p>&amp;#118;</p>&#118;</div>
<div><p>&amp;#119;</p>&#119;</div>
<div><p>&amp;#120;</p>&#120;</div>
<div><p>&amp;#121;</p>&#121;</div>
<div><p>&amp;#122;</p>&#122;</div>
<div><p>&amp;#123;</p>&#123;</div>
<div><p>&amp;#124;</p>&#124;</div>
<div><p>&amp;#125;</p>&#125;</div>
<div><p>&amp;#126;</p>&#126;</div>
<div><p>&amp;#160;</p>&#160;</div>
<div><p>&amp;#161;</p>&#161;</div>
<div><p>&amp;#162;</p>&#162;</div>
<div><p>&amp;#163;</p>&#163;</div>
<div><p>&amp;#164;</p>&#164;</div>
<div><p>&amp;#165;</p>&#165;</div>
<div><p>&amp;#166;</p>&#166;</div>
<div><p>&amp;#167;</p>&#167;</div>
<div><p>&amp;#168;</p>&#168;</div>
<div><p>&amp;#169;</p>&#169;</div>
<div><p>&amp;#170;</p>&#170;</div>
<div><p>&amp;#171;</p>&#171;</div>
<div><p>&amp;#172;</p>&#172;</div>
<div><p>&amp;#173;</p>&#173;</div>
<div><p>&amp;#174;</p>&#174;</div>
<div><p>&amp;#175;</p>&#175;</div>
<div><p>&amp;#176;</p>&#176;</div>
<div><p>&amp;#177;</p>&#177;</div>
<div><p>&amp;#178;</p>&#178;</div>
<div><p>&amp;#179;</p>&#179;</div>
<div><p>&amp;#180;</p>&#180;</div>
<div><p>&amp;#181;</p>&#181;</div>
<div><p>&amp;#182;</p>&#182;</div>
<div><p>&amp;#183;</p>&#183;</div>
<div><p>&amp;#184;</p>&#184;</div>
<div><p>&amp;#185;</p>&#185;</div>
<div><p>&amp;#186;</p>&#186;</div>
<div><p>&amp;#187;</p>&#187;</div>
<div><p>&amp;#188;</p>&#188;</div>
<div><p>&amp;#189;</p>&#189;</div>
<div><p>&amp;#190;</p>&#190;</div>
<div><p>&amp;#191;</p>&#191;</div>
<div><p>&amp;#192;</p>&#192;</div>
<div><p>&amp;#193;</p>&#193;</div>
<div><p>&amp;#194;</p>&#194;</div>
<div><p>&amp;#195;</p>&#195;</div>
<div><p>&amp;#196;</p>&#196;</div>
<div><p>&amp;#197;</p>&#197;</div>
<div><p>&amp;#198;</p>&#198;</div>
<div><p>&amp;#199;</p>&#199;</div>
<div><p>&amp;#200;</p>&#200;</div>
<div><p>&amp;#201;</p>&#201;</div>
<div><p>&amp;#202;</p>&#202;</div>
<div><p>&amp;#203;</p>&#203;</div>
<div><p>&amp;#204;</p>&#204;</div>
<div><p>&amp;#205;</p>&#205;</div>
<div><p>&amp;#206;</p>&#206;</div>
<div><p>&amp;#207;</p>&#207;</div>
<div><p>&amp;#208;</p>&#208;</div>
<div><p>&amp;#209;</p>&#209;</div>
<div><p>&amp;#210;</p>&#210;</div>
<div><p>&amp;#211;</p>&#211;</div>
<div><p>&amp;#212;</p>&#212;</div>
<div><p>&amp;#213;</p>&#213;</div>
<div><p>&amp;#214;</p>&#214;</div>
<div><p>&amp;#215;</p>&#215;</div>
<div><p>&amp;#216;</p>&#216;</div>
<div><p>&amp;#217;</p>&#217;</div>
<div><p>&amp;#218;</p>&#218;</div>
<div><p>&amp;#219;</p>&#219;</div>
<div><p>&amp;#220;</p>&#220;</div>
<div><p>&amp;#221;</p>&#221;</div>
<div><p>&amp;#222;</p>&#222;</div>
<div><p>&amp;#223;</p>&#223;</div>
<div><p>&amp;#224;</p>&#224;</div>
<div><p>&amp;#225;</p>&#225;</div>
<div><p>&amp;#226;</p>&#226;</div>
<div><p>&amp;#227;</p>&#227;</div>
<div><p>&amp;#228;</p>&#228;</div>
<div><p>&amp;#229;</p>&#229;</div>
<div><p>&amp;#230;</p>&#230;</div>
<div><p>&amp;#231;</p>&#231;</div>
<div><p>&amp;#232;</p>&#232;</div>
<div><p>&amp;#233;</p>&#233;</div>
<div><p>&amp;#234;</p>&#234;</div>
<div><p>&amp;#235;</p>&#235;</div>
<div><p>&amp;#236;</p>&#236;</div>
<div><p>&amp;#237;</p>&#237;</div>
<div><p>&amp;#238;</p>&#238;</div>
<div><p>&amp;#239;</p>&#239;</div>
<div><p>&amp;#240;</p>&#240;</div>
<div><p>&amp;#241;</p>&#241;</div>
<div><p>&amp;#242;</p>&#242;</div>
<div><p>&amp;#243;</p>&#243;</div>
<div><p>&amp;#244;</p>&#244;</div>
<div><p>&amp;#245;</p>&#245;</div>
<div><p>&amp;#246;</p>&#246;</div>
<div><p>&amp;#247;</p>&#247;</div>
<div><p>&amp;#248;</p>&#248;</div>
<div><p>&amp;#249;</p>&#249;</div>
<div><p>&amp;#250;</p>&#250;</div>
<div><p>&amp;#251;</p>&#251;</div>
<div><p>&amp;#252;</p>&#252;</div>
<div><p>&amp;#253;</p>&#253;</div>
<div><p>&amp;#254;</p>&#254;</div>
<div><p>&amp;#255;</p>&#255;</div>
<div><p>&amp;#338;</p>&#338;</div>
<div><p>&amp;#339;</p>&#339;</div>
<div><p>&amp;#376;</p>&#376;</div>
<div><p>&amp;#710;</p>&#710;</div>
<div><p>&amp;#732;</p>&#732;</div>
<div><p>&amp;#8192;</p>&#8192;</div>
<div><p>&amp;#8193;</p>&#8193;</div>
<div><p>&amp;#8194;</p>&#8194;</div>
<div><p>&amp;#8195;</p>&#8195;</div>
<div><p>&amp;#8196;</p>&#8196;</div>
<div><p>&amp;#8197;</p>&#8197;</div>
<div><p>&amp;#8198;</p>&#8198;</div>
<div><p>&amp;#8199;</p>&#8199;</div>
<div><p>&amp;#8200;</p>&#8200;</div>
<div><p>&amp;#8201;</p>&#8201;</div>
<div><p>&amp;#8202;</p>&#8202;</div>
<div><p>&amp;#8208;</p>&#8208;</div>
<div><p>&amp;#8209;</p>&#8209;</div>
<div><p>&amp;#8210;</p>&#8210;</div>
<div><p>&amp;#8211;</p>&#8211;</div>
<div><p>&amp;#8212;</p>&#8212;</div>
<div><p>&amp;#8216;</p>&#8216;</div>
<div><p>&amp;#8217;</p>&#8217;</div>
<div><p>&amp;#8218;</p>&#8218;</div>
<div><p>&amp;#8220;</p>&#8220;</div>
<div><p>&amp;#8221;</p>&#8221;</div>
<div><p>&amp;#8222;</p>&#8222;</div>
<div><p>&amp;#8226;</p>&#8226;</div>
<div><p>&amp;#8230;</p>&#8230;</div>
<div><p>&amp;#8239;</p>&#8239;</div>
<div><p>&amp;#8249;</p>&#8249;</div>
<div><p>&amp;#8250;</p>&#8250;</div>
<div><p>&amp;#8287;</p>&#8287;</div>
<div><p>&amp;#8364;</p>&#8364;</div>
<div><p>&amp;#8482;</p>&#8482;</div>
<div><p>&amp;#9724;</p>&#9724;</div>
<div><p>&amp;#64257;</p>&#64257;</div>
<div><p>&amp;#64258;</p>&#64258;</div>
<div><p>&amp;#64259;</p>&#64259;</div>
<div><p>&amp;#64260;</p>&#64260;</div>
</div>
</div>
</div>
</div>
<div id="specs">
</div>
<div id="installing">
<div class="section">
<div class="grid7 firstcol">
<h1>Installing Webfonts</h1>
<p>Webfonts are supported by all major browser platforms but not all in the same way. There are currently four different font formats that must be included in order to target all browsers. This includes TTF, WOFF, EOT and SVG.</p>
<h2>1. Upload your webfonts</h2>
<p>You must upload your webfont kit to your website. They should be in or near the same directory as your CSS files.</p>
<h2>2. Include the webfont stylesheet</h2>
<p>A special CSS @font-face declaration helps the various browsers select the appropriate font it needs without causing you a bunch of headaches. Learn more about this syntax by reading the <a href="https://www.fontspring.com/blog/further-hardening-of-the-bulletproof-syntax">Fontspring blog post</a> about it. The code for it is as follows:</p>
<code>
@font-face{
font-family: 'MyWebFont';
src: url('WebFont.eot');
src: url('WebFont.eot?#iefix') format('embedded-opentype'),
url('WebFont.woff') format('woff'),
url('WebFont.ttf') format('truetype'),
url('WebFont.svg#webfont') format('svg');
}
</code>
<p>We've already gone ahead and generated the code for you. All you have to do is link to the stylesheet in your HTML, like this:</p>
<code>&lt;link rel=&quot;stylesheet&quot; href=&quot;stylesheet.css&quot; type=&quot;text/css&quot; charset=&quot;utf-8&quot; /&gt;</code>
<h2>3. Modify your own stylesheet</h2>
<p>To take advantage of your new fonts, you must tell your stylesheet to use them. Look at the original @font-face declaration above and find the property called "font-family." The name linked there will be what you use to reference the font. Prepend that webfont name to the font stack in the "font-family" property, inside the selector you want to change. For example:</p>
<code>p { font-family: 'WebFont', Arial, sans-serif; }</code>
<h2>4. Test</h2>
<p>Getting webfonts to work cross-browser <em>can</em> be tricky. Use the information in the sidebar to help you if you find that fonts aren't loading in a particular browser.</p>
</div>
<div class="grid5 sidebar">
<div class="box">
<h2>Troubleshooting<br />Font-Face Problems</h2>
<p>Having trouble getting your webfonts to load in your new website? Here are some tips to sort out what might be the problem.</p>
<h3>Fonts not showing in any browser</h3>
<p>This sounds like you need to work on the plumbing. You either did not upload the fonts to the correct directory, or you did not link the fonts properly in the CSS. If you've confirmed that all this is correct and you still have a problem, take a look at your .htaccess file and see if requests are getting intercepted.</p>
<h3>Fonts not loading in iPhone or iPad</h3>
<p>The most common problem here is that you are serving the fonts from an IIS server. IIS refuses to serve files that have unknown MIME types. If that is the case, you must set the MIME type for SVG to "image/svg+xml" in the server settings. Follow these instructions from Microsoft if you need help.</p>
<h3>Fonts not loading in Firefox</h3>
<p>The primary reason for this failure? You are still using a version Firefox older than 3.5. So upgrade already! If that isn't it, then you are very likely serving fonts from a different domain. Firefox requires that all font assets be served from the same domain. Lastly it is possible that you need to add WOFF to your list of MIME types (if you are serving via IIS.)</p>
<h3>Fonts not loading in IE</h3>
<p>Are you looking at Internet Explorer on an actual Windows machine or are you cheating by using a service like Adobe BrowserLab? Many of these screenshot services do not render @font-face for IE. Best to test it on a real machine.</p>
<h3>Fonts not loading in IE9</h3>
<p>IE9, like Firefox, requires that fonts be served from the same domain as the website. Make sure that is the case.</p>
</div>
</div>
</div>
</div>
</div>
<div id="footer">
<p>&copy;2010-2017 Font Squirrel. All rights reserved.</p>
</div>
</div>
</body>
</html>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,5 @@
# Font Squirrel Font-face Generator Configuration File
# Upload this file to the generator to recreate the settings
# you used to create these fonts.
{"mode":"optimal","formats":["woff","woff2"],"tt_instructor":"default","fix_gasp":"xy","fix_vertical_metrics":"Y","metrics_ascent":"","metrics_descent":"","metrics_linegap":"","add_spaces":"Y","add_hyphens":"Y","fallback":"none","fallback_custom":"100","options_subset":"basic","subset_custom":"","subset_custom_range":"","subset_ot_features_list":"","css_stylesheet":"stylesheet.css","filename_suffix":"-webfont","emsquare":"2048","spacing_adjustment":"0"}

View File

@ -0,0 +1,129 @@
/*Notes about grid:
Columns: 12
Grid Width: 825px
Column Width: 55px
Gutter Width: 15px
-------------------------------*/
.section {margin-bottom: 18px;
}
.section:after {content: ".";display: block;height: 0;clear: both;visibility: hidden;}
.section {*zoom: 1;}
.section .firstcolumn,
.section .firstcol {margin-left: 0;}
/* Border on left hand side of a column. */
.border {
padding-left: 7px;
margin-left: 7px;
border-left: 1px solid #eee;
}
/* Border with more whitespace, spans one column. */
.colborder {
padding-left: 42px;
margin-left: 42px;
border-left: 1px solid #eee;
}
/* The Grid Classes */
.grid1, .grid1_2cols, .grid1_3cols, .grid1_4cols, .grid2, .grid2_3cols, .grid2_4cols, .grid3, .grid3_2cols, .grid3_4cols, .grid4, .grid4_3cols, .grid5, .grid5_2cols, .grid5_3cols, .grid5_4cols, .grid6, .grid6_4cols, .grid7, .grid7_2cols, .grid7_3cols, .grid7_4cols, .grid8, .grid8_3cols, .grid9, .grid9_2cols, .grid9_4cols, .grid10, .grid10_3cols, .grid10_4cols, .grid11, .grid11_2cols, .grid11_3cols, .grid11_4cols, .grid12
{margin-left: 15px;float: left;display: inline; overflow: hidden;}
.width1, .grid1, .span-1 {width: 55px;}
.width1_2cols,.grid1_2cols {width: 20px;}
.width1_3cols,.grid1_3cols {width: 8px;}
.width1_4cols,.grid1_4cols {width: 2px;}
.input_width1 {width: 49px;}
.width2, .grid2, .span-2 {width: 125px;}
.width2_3cols,.grid2_3cols {width: 31px;}
.width2_4cols,.grid2_4cols {width: 20px;}
.input_width2 {width: 119px;}
.width3, .grid3, .span-3 {width: 195px;}
.width3_2cols,.grid3_2cols {width: 90px;}
.width3_4cols,.grid3_4cols {width: 37px;}
.input_width3 {width: 189px;}
.width4, .grid4, .span-4 {width: 265px;}
.width4_3cols,.grid4_3cols {width: 78px;}
.input_width4 {width: 259px;}
.width5, .grid5, .span-5 {width: 335px;}
.width5_2cols,.grid5_2cols {width: 160px;}
.width5_3cols,.grid5_3cols {width: 101px;}
.width5_4cols,.grid5_4cols {width: 72px;}
.input_width5 {width: 329px;}
.width6, .grid6, .span-6 {width: 405px;}
.width6_4cols,.grid6_4cols {width: 90px;}
.input_width6 {width: 399px;}
.width7, .grid7, .span-7 {width: 475px;}
.width7_2cols,.grid7_2cols {width: 230px;}
.width7_3cols,.grid7_3cols {width: 148px;}
.width7_4cols,.grid7_4cols {width: 107px;}
.input_width7 {width: 469px;}
.width8, .grid8, .span-8 {width: 545px;}
.width8_3cols,.grid8_3cols {width: 171px;}
.input_width8 {width: 539px;}
.width9, .grid9, .span-9 {width: 615px;}
.width9_2cols,.grid9_2cols {width: 300px;}
.width9_4cols,.grid9_4cols {width: 142px;}
.input_width9 {width: 609px;}
.width10, .grid10, .span-10 {width: 685px;}
.width10_3cols,.grid10_3cols {width: 218px;}
.width10_4cols,.grid10_4cols {width: 160px;}
.input_width10 {width: 679px;}
.width11, .grid11, .span-11 {width: 755px;}
.width11_2cols,.grid11_2cols {width: 370px;}
.width11_3cols,.grid11_3cols {width: 241px;}
.width11_4cols,.grid11_4cols {width: 177px;}
.input_width11 {width: 749px;}
.width12, .grid12, .span-12 {width: 825px;}
.input_width12 {width: 819px;}
/* Subdivided grid spaces */
.emptycols_left1, .prepend-1 {padding-left: 70px;}
.emptycols_right1, .append-1 {padding-right: 70px;}
.emptycols_left2, .prepend-2 {padding-left: 140px;}
.emptycols_right2, .append-2 {padding-right: 140px;}
.emptycols_left3, .prepend-3 {padding-left: 210px;}
.emptycols_right3, .append-3 {padding-right: 210px;}
.emptycols_left4, .prepend-4 {padding-left: 280px;}
.emptycols_right4, .append-4 {padding-right: 280px;}
.emptycols_left5, .prepend-5 {padding-left: 350px;}
.emptycols_right5, .append-5 {padding-right: 350px;}
.emptycols_left6, .prepend-6 {padding-left: 420px;}
.emptycols_right6, .append-6 {padding-right: 420px;}
.emptycols_left7, .prepend-7 {padding-left: 490px;}
.emptycols_right7, .append-7 {padding-right: 490px;}
.emptycols_left8, .prepend-8 {padding-left: 560px;}
.emptycols_right8, .append-8 {padding-right: 560px;}
.emptycols_left9, .prepend-9 {padding-left: 630px;}
.emptycols_right9, .append-9 {padding-right: 630px;}
.emptycols_left10, .prepend-10 {padding-left: 700px;}
.emptycols_right10, .append-10 {padding-right: 700px;}
.emptycols_left11, .prepend-11 {padding-left: 770px;}
.emptycols_right11, .append-11 {padding-right: 770px;}
.pull-1 {margin-left: -70px;}
.push-1 {margin-right: -70px;margin-left: 18px;float: right;}
.pull-2 {margin-left: -140px;}
.push-2 {margin-right: -140px;margin-left: 18px;float: right;}
.pull-3 {margin-left: -210px;}
.push-3 {margin-right: -210px;margin-left: 18px;float: right;}
.pull-4 {margin-left: -280px;}
.push-4 {margin-right: -280px;margin-left: 18px;float: right;}

View File

@ -0,0 +1,396 @@
@import url('grid_12-825-55-15.css');
/*
CSS Reset by Eric Meyer - Released under Public Domain
http://meyerweb.com/eric/tools/css/reset/
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, font, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center, dl, dt, dd, ol, ul, li,
fieldset, form, label, legend, table,
caption, tbody, tfoot, thead, tr, th, td
{margin: 0;padding: 0;border: 0;outline: 0;
font-size: 100%;vertical-align: baseline;
background: transparent;}
body {line-height: 1;}
ol, ul {list-style: none;}
blockquote, q {quotes: none;}
blockquote:before, blockquote:after,
q:before, q:after {content: ''; content: none;}
:focus {outline: 0;}
ins {text-decoration: none;}
del {text-decoration: line-through;}
table {border-collapse: collapse;border-spacing: 0;}
body {
color: #000;
background-color: #dcdcdc;
}
a {
text-decoration: none;
color: #1883ba;
}
h1{
font-size: 32px;
font-weight: normal;
font-style: normal;
margin-bottom: 18px;
}
h2{
font-size: 18px;
}
#container {
width: 865px;
margin: 0px auto;
}
#header {
padding: 20px;
font-size: 36px;
background-color: #000;
color: #fff;
}
#header span {
color: #666;
}
#main_content {
background-color: #fff;
padding: 60px 20px 20px;
}
#footer p {
margin: 0;
padding-top: 10px;
padding-bottom: 50px;
color: #333;
font: 10px Arial, sans-serif;
}
.tabs {
width: 100%;
height: 31px;
background-color: #444;
}
.tabs li {
float: left;
margin: 0;
overflow: hidden;
background-color: #444;
}
.tabs li a {
display: block;
color: #fff;
text-decoration: none;
font: bold 11px/11px 'Arial';
text-transform: uppercase;
padding: 10px 15px;
border-right: 1px solid #fff;
}
.tabs li a:hover {
background-color: #00b3ff;
}
.tabs li.active a {
color: #000;
background-color: #fff;
}
div.huge {
font-size: 300px;
line-height: 1em;
padding: 0;
letter-spacing: -.02em;
overflow: hidden;
}
div.glyph_range {
font-size: 72px;
line-height: 1.1em;
}
.size10{ font-size: 10px; }
.size11{ font-size: 11px; }
.size12{ font-size: 12px; }
.size13{ font-size: 13px; }
.size14{ font-size: 14px; }
.size16{ font-size: 16px; }
.size18{ font-size: 18px; }
.size20{ font-size: 20px; }
.size24{ font-size: 24px; }
.size30{ font-size: 30px; }
.size36{ font-size: 36px; }
.size48{ font-size: 48px; }
.size60{ font-size: 60px; }
.size72{ font-size: 72px; }
.size90{ font-size: 90px; }
.psample_row1 { height: 120px;}
.psample_row1 { height: 120px;}
.psample_row2 { height: 160px;}
.psample_row3 { height: 160px;}
.psample_row4 { height: 160px;}
.psample {
overflow: hidden;
position: relative;
}
.psample p {
line-height: 1.3em;
display: block;
overflow: hidden;
margin: 0;
}
.psample span {
margin-right: .5em;
}
.white_blend {
width: 100%;
height: 61px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAO1JREFUeNrs3TsKgFAMRUE/eer+NxztxMYuEWQG3ECKwwUF58ycAKixOAGAyAKILAAiCyCyACILgMgCiCyAyAIgsgAiCyCyAIgsgMgCiCwAIgsgsgAiC4DIAogsACIL0CWuZ3UGgLrIhjMA1EV2OAOAJQtgyQLwjOzmDAAiCyCyAIgsQFtkd2cAEFkAkQVAZAHaIns4A4AlC2DJAiCyACILILIAiCzAV5H1dQGAJQsgsgCILIDIAvwisl58AViyAJYsACILILIAIgvAe2T9EhxAZAFEFgCRBeiL7HAGgLrIhjMAWLIAliwAt1OAAQDwygTBulLIlQAAAABJRU5ErkJggg==);
position: absolute;
bottom: 0;
}
.black_blend {
width: 100%;
height: 61px;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPJJREFUeNrs3TEKhTAQRVGjibr/9QoxhY2N3Ywo50A28IrLwP9g6b1PAMSYTQAgsgAiC4DIAogsgMgCILIAIgsgsgCILIDIAogsACILILIAIguAyAKILIDIAiCyACILgMgCZCnjLWYAiFGvB0BQZJsZAFyyAC5ZAO6RXc0AILIAIguAyAKkRXYzA4DIAogsACILkBbZ3QwALlkAlywAIgsgsgAiC4DIArwVWf8uAHDJAogsACILILIAv4isH74AXLIALlkARBZAZAFEFoDnyPokOIDIAogsACILkBfZZgaAuMhWMwC4ZAE+p4x3mAEgxinAAJ+XBbPWGkwAAAAAAElFTkSuQmCC);
position: absolute;
bottom: 0;
}
.fullreverse {
background: #000 !important;
color: #fff !important;
margin-left: -20px;
padding-left: 20px;
margin-right: -20px;
padding-right: 20px;
padding: 20px;
margin-bottom:0;
}
.sample_table td {
padding-top: 3px;
padding-bottom:5px;
padding-left: 5px;
vertical-align: middle;
line-height: 1.2em;
}
.sample_table td:first-child {
background-color: #eee;
text-align: right;
padding-right: 5px;
padding-left: 0;
padding: 5px;
font: 11px/12px "Courier New", Courier, mono;
}
code {
white-space: pre;
background-color: #eee;
display: block;
padding: 10px;
margin-bottom: 18px;
overflow: auto;
}
.bottom,.last {margin-bottom:0 !important; padding-bottom:0 !important;}
.box {
padding: 18px;
margin-bottom: 18px;
background: #eee;
}
.reverse,.reversed { background: #000 !important;color: #fff !important; border: none !important;}
#bodycomparison {
position: relative;
overflow: hidden;
font-size: 72px;
height: 90px;
white-space: nowrap;
}
#bodycomparison div{
font-size: 72px;
line-height: 90px;
display: inline;
margin: 0 15px 0 0;
padding: 0;
}
#bodycomparison div span{
font: 10px Arial;
position: absolute;
left: 0;
}
#xheight {
float: none;
position: absolute;
color: #d9f3ff;
font-size: 72px;
line-height: 90px;
}
.fontbody {
position: relative;
}
.arialbody{
font-family: Arial;
position: relative;
}
.verdanabody{
font-family: Verdana;
position: relative;
}
.georgiabody{
font-family: Georgia;
position: relative;
}
/* @group Layout page
*/
#layout h1 {
font-size: 36px;
line-height: 42px;
font-weight: normal;
font-style: normal;
}
#layout h2 {
font-size: 24px;
line-height: 23px;
font-weight: normal;
font-style: normal;
}
#layout h3 {
font-size: 22px;
line-height: 1.4em;
margin-top: 1em;
font-weight: normal;
font-style: normal;
}
#layout p.byline {
font-size: 12px;
margin-top: 18px;
line-height: 12px;
margin-bottom: 0;
}
#layout p {
font-size: 14px;
line-height: 21px;
margin-bottom: .5em;
}
#layout p.large{
font-size: 18px;
line-height: 26px;
}
#layout .sidebar p{
font-size: 12px;
line-height: 1.4em;
}
#layout p.caption {
font-size: 10px;
margin-top: -16px;
margin-bottom: 18px;
}
/* @end */
/* @group Glyphs */
#glyph_chart div{
background-color: #d9f3ff;
color: black;
float: left;
font-size: 36px;
height: 1.2em;
line-height: 1.2em;
margin-bottom: 1px;
margin-right: 1px;
text-align: center;
width: 1.2em;
position: relative;
padding: .6em .2em .2em;
}
#glyph_chart div p {
position: absolute;
left: 0;
top: 0;
display: block;
text-align: center;
font: bold 9px Arial, sans-serif;
background-color: #3a768f;
width: 100%;
color: #fff;
padding: 2px 0;
}
#glyphs h1 {
font-family: Arial, sans-serif;
}
/* @end */
/* @group Installing */
#installing {
font: 13px Arial, sans-serif;
}
#installing p,
#glyphs p{
line-height: 1.2em;
margin-bottom: 18px;
font: 13px Arial, sans-serif;
}
#installing h3{
font-size: 15px;
margin-top: 18px;
}
/* @end */
#rendering h1 {
font-family: Arial, sans-serif;
}
.render_table td {
font: 11px "Courier New", Courier, mono;
vertical-align: middle;
}

View File

@ -0,0 +1,12 @@
/*! Generated by Font Squirrel (https://www.fontsquirrel.com) on March 24, 2022 */
@font-face {
font-family: 'cantarelloblique';
src: url('cantarell-italic-webfont.woff2') format('woff2'),
url('cantarell-italic-webfont.woff') format('woff');
font-weight: normal;
font-style: normal;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,317 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="210mm"
height="297mm"
viewBox="0 0 210 297"
version="1.1"
id="svg8"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
sodipodi:docname="planchelogo.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient96685">
<stop
style="stop-color:#5288bc;stop-opacity:1;"
offset="0"
id="stop96681" />
<stop
style="stop-color:#5288bc;stop-opacity:0;"
offset="1"
id="stop96683" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4639">
<stop
style="stop-color:#ffc300;stop-opacity:1;"
offset="0"
id="stop4635" />
<stop
style="stop-color:#ffc300;stop-opacity:0;"
offset="1"
id="stop4637" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4639"
id="linearGradient4641"
x1="36.533747"
y1="131.6138"
x2="47.703334"
y2="131.6138"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient96685"
id="linearGradient96687"
x1="41.668779"
y1="214.36779"
x2="64.094759"
y2="214.36779"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.4"
inkscape:cx="213.57143"
inkscape:cy="666.07143"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="2237"
inkscape:window-height="1142"
inkscape:window-x="1740"
inkscape:window-y="504"
inkscape:window-maximized="0"
inkscape:pagecheckerboard="0" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="opacity:1;fill:#2f0000;fill-opacity:0;stroke:none;stroke-width:2.42463255;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4643-2-2"
width="54.897697"
height="19.509676"
x="13.750558"
y="203.4762"
inkscape:export-xdpi="224"
inkscape:export-ydpi="224" />
<rect
style="opacity:1;fill:#063665;fill-opacity:1;stroke:none;stroke-width:4.00071526;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4643"
width="54"
height="54"
x="18.331844"
y="113.92113"
inkscape:export-xdpi="224"
inkscape:export-ydpi="224"
inkscape:export-filename="/media/phil/usbfarm/apixtribe/tribes/apixtribe/www/cdn/chartegraphique/logocarre_bgplein.png" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#5288bc;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="23.71944"
y="142.92403"
id="text4520"><tspan
sodipodi:role="line"
id="tspan4518"
x="23.71944"
y="142.92403"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#5288bc;fill-opacity:1;stroke-width:0.264583">ap</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.3176px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;opacity:1;fill:#ffc300;fill-opacity:1;stroke:none;stroke-width:0.18294"
x="37.888023"
y="140.59386"
id="text4520-9"
transform="scale(0.95865062,1.0431329)"><tspan
sodipodi:role="line"
id="tspan4518-1"
x="37.888023"
y="140.59386"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.5622px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#ffc300;fill-opacity:1;stroke-width:0.18294">X</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#5288bc;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="47.392799"
y="143.50308"
id="text4520-7"><tspan
sodipodi:role="line"
id="tspan4518-5"
x="47.392799"
y="143.50308"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#5288bc;fill-opacity:1;stroke-width:0.264583">trib</tspan></text>
<rect
style="fill:#063665;fill-opacity:1;stroke:none;stroke-width:4.00072;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4643-0"
width="54"
height="54"
x="81.535225"
y="114.08669"
inkscape:export-xdpi="224"
inkscape:export-ydpi="224"
inkscape:export-filename="/media/phil/usbfarm/apixtribe/tribes/apixtribe/www/cdn/chartegraphique/logocarre_bgplein.png" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#5288bc;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="102.29675"
y="130.17517"
id="text4520-93"><tspan
sodipodi:role="line"
id="tspan4518-60"
x="102.29675"
y="130.17517"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#5288bc;fill-opacity:1;stroke-width:0.264583">ap</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.3176px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#ffc300;fill-opacity:1;stroke:none;stroke-width:0.18294"
x="107.40491"
y="140.76895"
id="text4520-9-6"
transform="scale(0.95865062,1.0431329)"><tspan
sodipodi:role="line"
id="tspan4518-1-2"
x="107.40491"
y="140.76895"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.5622px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#ffc300;fill-opacity:1;stroke-width:0.18294">X</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#5288bc;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="99.799774"
y="156.49184"
id="text4520-7-6"><tspan
sodipodi:role="line"
id="tspan4518-5-1"
x="99.799774"
y="156.49184"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#5288bc;fill-opacity:1;stroke-width:0.264583">trib</tspan></text>
<rect
style="opacity:1;fill:#063665;fill-opacity:1;stroke:none;stroke-width:4.10685205;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4663"
width="14.363096"
height="17.528641"
x="17.008928"
y="177.98474"
inkscape:export-xdpi="224"
inkscape:export-ydpi="224" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.3176px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;opacity:1;fill:#ffc300;fill-opacity:1;stroke:none;stroke-width:0.18294"
x="18.656412"
y="185.1286"
id="text4520-9-8"
transform="scale(0.95865062,1.0431329)"
inkscape:export-xdpi="224"
inkscape:export-ydpi="224"><tspan
sodipodi:role="line"
id="tspan4518-1-0"
x="18.656412"
y="185.1286"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.5622px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#ffc300;fill-opacity:1;stroke-width:0.18294">X</tspan></text>
<rect
style="opacity:1;fill:#350669;fill-opacity:1;stroke:none;stroke-width:4.10685205;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4663-2"
width="14.363096"
height="17.528643"
x="36.332958"
y="178.00836" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.3176px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;opacity:1;fill:#ffc300;fill-opacity:1;stroke:none;stroke-width:0.18294"
x="38.812359"
y="185.16508"
id="text4520-9-8-9"
transform="scale(0.95865062,1.0431329)"><tspan
sodipodi:role="line"
id="tspan4518-1-0-6"
x="38.812359"
y="185.16508"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.5622px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#ffc300;fill-opacity:1;stroke-width:0.18294">X</tspan></text>
<rect
style="opacity:1;fill:#063665;fill-opacity:1;stroke:none;stroke-width:2.42463255;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4643-2"
width="54.897697"
height="19.509676"
x="60.170753"
y="175.41145"
inkscape:export-xdpi="224"
inkscape:export-ydpi="224"
inkscape:export-filename="/media/phil/usbfarm/apixtribe/tribes/apixtribe/www/cdn/chartegraphique/logo_bgplein.png" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#5288bc;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="65.594994"
y="187.72525"
id="text4520-3"><tspan
sodipodi:role="line"
id="tspan4518-6"
x="65.594994"
y="187.72525"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#5288bc;fill-opacity:1;stroke-width:0.264583">ap</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.3176px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#ffc300;fill-opacity:1;stroke:none;stroke-width:0.18294"
x="81.569794"
y="183.54257"
id="text4520-9-7"
transform="scale(0.95865062,1.0431329)"><tspan
sodipodi:role="line"
id="tspan4518-1-5"
x="81.569794"
y="183.54257"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.5622px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#ffc300;fill-opacity:1;stroke-width:0.18294">X</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#5288bc;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="89.268356"
y="188.30431"
id="text4520-7-3"><tspan
sodipodi:role="line"
id="tspan4518-5-5"
x="89.268356"
y="188.30431"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#5288bc;fill-opacity:1;stroke-width:0.264583">trib</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#5288bc;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="21.507853"
y="218.6658"
id="text4520-6"><tspan
sodipodi:role="line"
id="tspan4518-2"
x="21.507853"
y="218.6658"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#063665;fill-opacity:1;stroke-width:0.264583">ap</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:7.3176px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#ffc300;fill-opacity:1;stroke:none;stroke-width:0.18294"
x="35.581047"
y="213.20374"
id="text4520-9-9"
transform="scale(0.95865062,1.0431329)"><tspan
sodipodi:role="line"
id="tspan4518-1-1"
x="35.581047"
y="213.20374"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:17.5622px;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#ffc300;fill-opacity:1;stroke-width:0.18294">X</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10.5833px;line-height:1.25;font-family:Ubuntu;-inkscape-font-specification:Ubuntu;letter-spacing:0px;word-spacing:0px;fill:#5288bc;fill-opacity:1;stroke:none;stroke-width:0.264583"
x="45.181217"
y="219.24486"
id="text4520-7-2"><tspan
sodipodi:role="line"
id="tspan4518-5-7"
x="45.181217"
y="219.24486"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:Cantarell;-inkscape-font-specification:'Cantarell Bold';fill:#063665;fill-opacity:1;stroke-width:0.264583">trib</tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 16 KiB

BIN
nationchains/static/img/imgtg.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 B

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="ant"
viewBox="0 0 128 128"
version="1.0"
sodipodi:docname="antsNation.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview18"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="1.6933594"
inkscape:cx="64.073818"
inkscape:cy="87.10496"
inkscape:window-width="1080"
inkscape:window-height="476"
inkscape:window-x="83"
inkscape:window-y="522"
inkscape:window-maximized="0"
inkscape:current-layer="ant" />
<defs
id="defs9">
<linearGradient
id="WhiteTransparent">
<stop
stop-color="#fff"
offset="0"
id="stop2" />
<stop
stop-color="#fff"
stop-opacity="0"
offset="1"
id="stop4" />
</linearGradient>
<linearGradient
id="linearGradient3153"
x1="682.04"
xlink:href="#WhiteTransparent"
gradientUnits="userSpaceOnUse"
y1="471.38"
gradientTransform="matrix(-2.0856038,1.3622785,1.2041058,2.3595711,906.18619,-1980.3141)"
x2="680.06"
y2="472.52" />
<linearGradient
id="linearGradient3156"
x1="681.72"
xlink:href="#WhiteTransparent"
gradientUnits="userSpaceOnUse"
y1="471.57"
gradientTransform="matrix(2.0856038,1.3622785,-1.2041058,2.3595711,-782.49521,-1981.4312)"
x2="676.89"
y2="474.35" />
</defs>
<path
id="ant-shape"
d="m 108.57904,25.388698 c 1.00838,0.936288 -7.10741,12.557231 -7.60812,13.233874 -6.933534,9.386448 -13.797528,9.709037 -14.645967,11.109529 -0.855391,1.392624 -4.944577,6.050445 -12.218881,7.419466 -0.02781,2.140077 -0.132134,4.162139 -0.326857,6.081916 4.47168,-1.392625 9.506667,-2.588551 12.323199,-2.580683 2.969529,-0.0079 13.025595,3.170778 14.826776,4.122799 0.63286,0.338322 12.12152,0.0079 12.12153,1.424095 0,1.431964 -11.43999,1.959118 -11.9129,1.235268 -4.151767,0.747456 -15.91166,-1.683738 -17.163454,-1.431964 -1.251791,0.26751 -10.883636,3.438288 -10.953179,3.454026 -0.723259,3.760872 -1.773374,5.814406 -3.018211,8.324277 4.784627,-0.157359 13.373312,5.200708 18.164894,10.621717 3.977918,4.500461 10.605459,11.778302 11.606889,13.383362 3.741471,5.97964 2.065461,24.68172 -0.0069,24.68172 -1.627319,0 2.267131,-16.74298 -1.474325,-22.7226 -1.077937,-1.72308 -13.129909,-11.030851 -14.5347,-12.856213 -1.404788,-1.825361 -8.873813,-4.437519 -12.003295,-7.081145 3.150344,2.305306 5.306208,5.908822 5.306209,10.865623 0,18.410975 -8.491323,25.830435 -14.868509,25.917005 0,0 -0.007,0.0235 -0.02086,0.0235 h -0.04173 -0.04173 -0.04173 c -6.370229,-0.0944 -14.819826,-7.52962 -14.819828,-25.909129 -0.007,-4.618479 1.877687,-8.088242 4.673357,-10.401414 -3.400794,2.462668 -10.070064,4.878126 -11.3914,6.593336 -1.411743,1.817493 -13.408087,11.125257 -14.492967,12.856217 -3.748434,5.97176 0.139079,22.73833 -1.502157,22.73833 -2.058507,0 -3.741474,-18.70995 0,-24.68959 1.008383,-1.59719 7.628974,-8.867166 11.606891,-13.367626 4.798535,-5.428876 13.373311,-10.786942 18.164893,-10.621716 -1.244837,-2.509873 -2.287998,-4.61848 -3.01821,-8.387222 -0.06259,-0.0079 -9.694433,-3.131439 -10.953181,-3.391081 -1.258747,-0.259642 -13.011687,2.179417 -17.17041,1.424097 -0.465953,0.731719 -11.912889,0.196699 -11.905936,-1.227398 -0.0069,-1.424097 11.516489,-1.117249 12.149333,-1.45557 1.794239,-0.959889 11.80162,-4.130666 14.778103,-4.130666 2.816533,0.0079 7.893245,1.203793 12.364924,2.580684 C 50.335878,61.321501 50.189837,59.299441 50.162019,57.159363 42.887715,55.774606 38.791573,51.124653 37.950091,49.73203 37.094699,48.339403 30.230707,48.016819 23.297172,38.614634 22.803409,37.945858 14.680672,26.31704 15.696009,25.388631 c 1.189201,-1.093645 8.630411,9.748377 8.929451,10.700396 2.016777,1.132983 11.599941,8.355746 12.754371,8.87503 1.161383,0.511417 7.00308,2.871798 13.21336,7.081146 0.952753,-4.114932 3.352022,-6.908051 6.286779,-8.387224 -4.054416,-0.0079 -7.003081,-0.904812 -7.003082,-5.956028 10e-7,-5.523294 0.938846,-10.613846 2.517493,-14.697313 -4.31868,-2.541339 -7.44816,-5.263647 -8.387007,-5.649173 -0.938841,-0.401272 -1.300468,-1.864708 0.479857,-5.562638 C 47.421985,5.7187832 50.600152,0.45512609 51.288638,0.74624078 51.977124,1.0530877 48.764187,12.886472 46.010242,15.43569 c 3.261613,0.65304 5.403569,1.848963 8.12274,3.981175 2.141955,-3.666463 4.895896,-5.869486 7.934975,-5.924564 0.007,-0.0078 0.0139,-0.03149 0.02087,-0.0236 0.02781,0 0.06255,-0.0078 0.09037,-0.0078 3.046028,0.04718 5.855605,2.281701 7.990608,5.956019 2.72612,-2.124324 4.854166,-3.351725 8.108824,-4.012632 -2.75394,-2.533473 -5.987732,-14.359001 -5.299246,-14.65011579 0.688485,-0.29111379 3.887502,4.93320949 6.822262,11.00726479 1.78728,3.705796 1.397839,5.20069 0.458994,5.586216 -0.945803,0.393416 -4.075283,3.115722 -8.393958,5.65705 1.585601,4.07559 2.524442,9.18189 2.524443,14.705183 -1e-6,5.035482 -2.948667,5.948162 -6.996127,5.948163 2.927802,1.487039 5.327071,4.280159 6.279823,8.379353 6.21028,-4.209346 12.079795,-6.538255 13.234223,-7.057539 1.161386,-0.511415 10.737588,-7.789255 12.754364,-8.922239 0.29208,-0.944151 7.719393,-11.77043 8.915543,-10.668917 z"
fill="#222222"
style="stroke-width:1.04611" />
<path
id="ant-specularity"
opacity="0.4"
d="m 74.996236,98.127788 c 0.007,-8.237731 -1.411743,-13.462043 -10.111699,-13.454174 -1.126613,0 -1.856824,1.439833 -1.856824,3.406818 -0.007,4.256555 -0.007,13.587928 -0.007,17.852348 -0.007,17.28587 11.99634,7.13623 11.975478,-7.804992 z M 72.923824,35.561944 c -0.452035,-6.08192 -1.307427,-9.299903 -3.219887,-14.233102 -1.766415,-4.547672 -3.046026,-6.074054 -5.452248,-6.089788 -2.232362,0.0078 -1.29352,5.601973 -1.286565,15.381829 -0.01391,4.453249 -0.764985,9.315634 2.976483,9.299898 3.727558,0 7.281257,-0.165227 6.982217,-4.358837 z m -0.146042,22.982247 c 0.250359,-4.1228 -0.312948,-8.709808 -2.684398,-11.392776 -1.425653,-1.628662 -2.816532,-3.170778 -4.722038,-3.186513 -3.240751,0.01573 -2.308861,8.340014 -2.301906,12.753927 0.01391,4.531933 -0.305994,16.160746 0.396399,19.551829 0.70935,3.38321 5.667837,0.810397 6.975264,-2.793119 1.3422,-3.721536 2.350589,-9.771981 2.336679,-14.933348 z"
fill="url(#linearGradient3156)"
style="fill:url(#linearGradient3156);stroke-width:1.04611" />
<path
id="ant-gloss"
opacity="0.4"
d="m 49.376224,100.01609 c 0.007,-8.237728 1.432607,-13.454173 10.132563,-13.462039 4.179593,3.910366 1.870733,15.444759 1.856824,21.298509 0.007,17.2544 -11.968523,7.10475 -11.989387,-7.83647 z m 2.100229,-62.550107 c 0.424218,-6.081914 1.300473,-9.29204 3.20598,-14.233098 1.794235,-4.547671 3.046028,-6.089788 5.459202,-6.081921 2.468813,3.800208 3.908372,22.942907 -1.689919,24.665986 -3.734513,-0.0079 -7.26735,-0.165226 -6.975265,-4.350969 z m 0.146044,22.966513 c -0.257314,-4.099198 0.305993,-8.717676 2.67049,-11.392774 1.43956,-1.612928 2.830441,-3.170782 4.708128,-3.170782 5.000214,6.160596 2.816533,24.052288 1.933322,32.305756 -0.716303,3.35961 -5.681744,0.778926 -6.968309,-2.816723 -1.342199,-3.705799 -2.364495,-9.756242 -2.343631,-14.925477 z"
fill="url(#linearGradient3153)"
style="fill:url(#linearGradient3153);stroke-width:1.04611" />
<metadata
id="metadata15">
<rdf:RDF>
<cc:Work>
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://creativecommons.org/licenses/publicdomain/" />
<dc:publisher>
<cc:Agent
rdf:about="http://openclipart.org/">
<dc:title>Openclipart</dc:title>
</cc:Agent>
</dc:publisher>
<dc:date>2006-08-29T16:35:08</dc:date>
<dc:description>Balanced ant design with specularity</dc:description>
<dc:source>http://openclipart.org/detail/66/ant-by-andy</dc:source>
<dc:creator>
<cc:Agent>
<dc:title>Andy</dc:title>
</cc:Agent>
</dc:creator>
<dc:subject>
<rdf:Bag>
<rdf:li>animal</rdf:li>
<rdf:li>ant</rdf:li>
<rdf:li>black</rdf:li>
<rdf:li>black &amp; white</rdf:li>
<rdf:li>bug</rdf:li>
<rdf:li>clip art</rdf:li>
<rdf:li>clipart</rdf:li>
<rdf:li>creature</rdf:li>
<rdf:li>creepy</rdf:li>
<rdf:li>glossy</rdf:li>
<rdf:li>icon</rdf:li>
<rdf:li>insect</rdf:li>
<rdf:li>logo</rdf:li>
<rdf:li>shiny</rdf:li>
<rdf:li>spooky</rdf:li>
</rdf:Bag>
</dc:subject>
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/publicdomain/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
</svg>

After

Width:  |  Height:  |  Size: 9.5 KiB

1
nationchains/testnetwork.html Executable file
View File

@ -0,0 +1 @@
ok

130
package.json Executable file
View File

@ -0,0 +1,130 @@
{
"name": "apxtrib",
"version": "0.0.1",
"logo": {
"file": "./nationchains/static/img/chartegraphique/logocarre_bgplein.png"
},
"description": "A standard conf api based on express.js with basic and usefull component to manage api project",
"main": "apxtrib.js",
"repository": {
"url": "https://gitea.ndda.fr/apxtrib/apxtrib",
"type": "git"
},
"scripts": {
"apidoc": "apidoc -i routes/ -o apidoc/",
"stoppm2": "pm2 stop apxtrib.js",
"startpm2": "pm2 start apxtrib.js --log-date-format 'DD-MM HH:mm:ss.SSS'",
"restartpm2": "pm2 restart apxtrib.js --log-date-format 'DD-MM HH:mm:ss.SSS'",
"startblockchain": "pm2 start ./models/Blockchains.js --log-date-format 'DD-MM HH:mm:ss:SSS'",
"logpm2": "pm2 logs apxtrib.js --lines 200",
"setup": "node models/Setup.js",
"dev": "node apxtrib.js",
"dev-watch": "nodemon apxtrib.js"
},
"apidoc": {
"url": "https://apxtrib.crabdance.com/apidoc"
},
"maintainers": [
{
"name": "Philippe Colzy",
"email": "support@need-data.com",
"url": "https://need-data.com"
}
],
"contributors": [
{
"name": "Antonin Hattabe",
"email": "",
"url": ""
},
{
"name": "Maxime Serrato",
"email": "",
"url": ""
},
{
"name": "Charles Filuzeau",
"email": "",
"url": ""
}
],
"author": {
"name": "patma",
"email": "patma@mail.be",
"url": ""
},
"license": "MIT",
"dependencies": {
"@editorjs/editorjs": "^2.22.2",
"@fortawesome/fontawesome-free": "^5.15.4",
"@popperjs/core": "^2.11.2",
"@web-component/code-flask": "^0.0.1",
"add-to-calendar-button": "^1.6.1",
"aos": "^3.0.0-beta.6",
"async": "^3.2.0",
"axios": "^0.18.0",
"baunsu": "^0.2.3",
"bcrypt": "^5.0.0",
"bootstrap": "^5.1.3",
"cheerio": "^1.0.0-rc.3",
"cors": "^2.8.4",
"crypto-js": "^4.1.1",
"dns-sync": "^0.2.1",
"express": "^4.16.3",
"feather-icons": "^4.28.0",
"filepond": "^4.30.3",
"filepond-plugin-file-metadata": "^1.0.8",
"filepond-plugin-image-crop": "^2.0.6",
"filepond-plugin-image-edit": "^1.6.3",
"filepond-plugin-image-exif-orientation": "^1.0.11",
"filepond-plugin-image-filter": "^1.0.1",
"filepond-plugin-image-preview": "^4.6.11",
"filepond-plugin-image-validate-size": "^1.2.7",
"flatpickr": "^4.6.9",
"formidable": "^1.2.1",
"fs-extra": "^6.0.1",
"glob": "^7.1.2",
"google-spreadsheet": "^3.1.15",
"html-to-text": "^5.1.1",
"iconv": "^3.0.1",
"jquery": "^3.6.0",
"js-beautify": "^1.14.0",
"jsdom": "^11.11.0",
"jsonfile": "^5.0.0",
"jszip": "^3.7.1",
"juice": "^5.2.0",
"jwt-simple": "^0.5.1",
"libxmljs": "^0.19.10",
"lodash": "^4.17.10",
"luxon": "^2.1.1",
"mailparser": "^2.3.2",
"minifyjs": "^0.2.0",
"moment": "^2.22.1",
"multer": "^1.3.0",
"mustache": "^2.3.0",
"natural": "^0.6.3",
"node-mailjet": "^3.3.4",
"node-mbox": "^1.0.0",
"node-sass": "^6.0.1",
"nodemailer": "^6.1.1",
"nodemailer-smtp-transport": "^2.7.4",
"parse-filepath": "^1.0.2",
"pdf-creator-node": "^2.2.2",
"pm2": "^5.1.2",
"prompt": "^1.2.0",
"qrcode": "^1.3.3",
"request-promise": "^4.2.4",
"rimraf": "^2.6.2",
"sass": "^1.54.5",
"scrape-it": "^5.1.4",
"serialize-javascript": "^5.0.1",
"simplebar": "^5.3.6",
"stripe": "^7.4.0",
"swiper": "^8.0.6",
"uuid": "^3.2.1"
},
"devDependencies": {
"nodemon": "^1.17.3",
"request": "^2.85.0"
}
}

63
routes/messages.js Executable file
View File

@ -0,0 +1,63 @@
const express = require( 'express' );
const path = require( 'path' );
// Classes
const Messages = require( '../models/Messages.js' );
// Middlewares ( prefix, object ) => {
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
router.post( '/', checkHeaders, ( req, res ) => {
/*
add message to (no authentification and accessright needs) :
a tribeid or uuid => create a contact based on email.json or phone.json or email_phone.json
if req.body.orderuuid exist then it store the req.body in /orders/orderuuid.json an order with state = order
*/
// check if a receiver is well identify if not then it send message to all user tribeid to inform **
if( !req.body.desttribeid ) req.body.desttribeid = req.session.header.xworkon;
if( !req.body.lang ) req.body.lang = req.session.header.xlang;
console.log( '/messages t send for ', req.session.header.xworkon );
//console.log(' Content: ',req.body);
const result = Messages.postinfo( req.body );
res.status( result.status )
.send( result.data )
} );
router.put( '/:objectname/:uuid', checkHeaders, isAuthenticated, ( req, res ) => {
// message that will create an object and sendback an email.
// if objectnane/uuid_lg.json exist ans accessright is ste to U for the user then it replace object data with req.body.key value
// if does not exist and accessright C then it create it with uuid
// then if req.body.tplmessage => render email with data
// No data management are done here, if need you can add plugin to create a workflow based object
// if need specific data check => req.body.callback={tribeidpugin,pluginname,function} will run pluginname.function(data) add data run specific stuf before saved the message object in /objectname/data.uuid_lg/json
let result;
console.log( "object", req.params.objectname )
if( req.params.objectname == 'notifications' ) {
//uuid is a timestamp
req.body.time = req.params.uuid;
result = Messages.notification( req.body, req.session.header );
} else {
req.body.uuid = req.params.uuid;
req.body.object = req.params.objectname;
result = Messages.object( req.body, req.session.header );
}
//console.log( 'result', result );
res.status( result.status )
.json( result.data )
} );
router.get( '/user', checkHeaders, isAuthenticated, ( req, res ) => {
// run agregate for tribeid concerned
//
console.log( "request notifiation for user", req.session.header.xpaganid );
const app = {
tribeid: req.session.header.xapp.split( ':' )[ 0 ],
website: req.session.header.xapp.split( ':' )[ 1 ],
lang: req.session.header.xlang
};
res.send( Messages.request( req.session.header.xtribe, req.session.header.xpaganid,
req.app.locals.tokens[ req.session.header.xpaganid ].ACCESSRIGHTS, app ) );
} );
module.exports = router;

37
routes/nationchains.js Executable file
View File

@ -0,0 +1,37 @@
const express = require( 'express' );
const config = require( '../config.js' );
// Classes
const Nationchains = require( '../models/Nationchains.js' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
/*
Manage the social world
@Todo
Manage a new nation
A major create a nation with at least a town => nation:{name, towns:[]} contracts/nationname.js + contracts/townsname.js
Manage a new towns in a nation => update nation:[nationname:towns:[]} contracts/townname.js
*/
router.post( '/push', checkHeaders, ( req, res ) => {
// Get information from other apixtribe instance in req.body
// check req.body.hashnext => means this is a candidate to validate next block
//
// return it's own information back with the last call to Nationchains.synchronize()
res.send( { status: 200, payload: { moreinfo: fs.readFileSync( `${config.tribes}/${config.mayorId}/nationchains/nodes/${config.rootURL}`, 'utf-8' ) } } )
} )
module.exports = router;

19
routes/odmdb.js Normal file
View File

@ -0,0 +1,19 @@
const express = require( 'express' );
const glob = require( 'glob' );
const path = require( 'path' );
// Classes
const Odmdb = require( '../models/Odmdb.js' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
router.get('/searchauth/:objectname/:question',checkHeaders,isAuthenticated,( req, res ) => {
console.log( 'route referentials get all language' + req.params.objectname + '-' + req.params.question );
const getref = Referentials.getref( true, req.params.source, req.params.idref, req.session.header.xworkon, req.session.header.xlang );
// Return any status the data if any erreur return empty object
res.jsonp( getref.payload.data );
} );
module.exports = router;

65
routes/outputs.js Executable file
View File

@ -0,0 +1,65 @@
// Upload de file
const express = require( 'express' );
const fs = require( 'fs-extra' );
// Classes
const UploadFile = require( '../models/UploadFiles' );
const Outputs = require( '../models/Outputs' );
//const Outputstest = require('../models/Outputstest');
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const router = express.Router();
router.post( '/ggsheet2json', checkHeaders, async ( req, res ) => {
console.log( 'route outputs sheet to json' );
let result = await Outputs.ggsheet2json( req.body, req.session.header );
res.send( result );
} );
// checkHeaders, isuploadFileValid
router.post( '/msg', checkHeaders, async ( req, res ) => {
console.log( 'route outputs msg post ' );
const envoi = await Outputs.generemsg( req.body, req.session.header );
res.status( envoi.status )
.send( {
payload: envoi.payload
} );
} );
/*test functionnalité
router.post('/msgtest', checkHeaders, isemailValid, async (req, res) => {
console.log('route outputs msg post en test');
const envoi = await Outputstest.generemsg(req.body, req.session.header);
res.status(envoi.status).send({
payload: envoi.payload
});
});
*/
router.post( '/template', checkHeaders, ( req, res ) => {
console.log( 'route outputs post de fichier template ' );
// a callback can be pass to req.body to run a specific process after upload
const saveFile = UploadFile.addjson( req.body, req.session.header );
console.log( saveFile );
res.send( saveFile );
// res.send({ status: 200, payload: { info: 'fine' } });
} );
router.post( '/pdf', checkHeaders, ( req, res ) => {
console.log( 'route outputs pdf post' );
Outputs.generepdf( req.body, req.session.header )
.then( ( doc ) => {
res.status( doc.status )
.download( doc.payload.data.path, doc.payload.data.name );
} )
.catch( ( err ) => {
console.log( err );
res.status( err.status )
.send( { payload: err.payload } );
} );
} );
module.exports = router;

211
routes/pagans.js Executable file
View File

@ -0,0 +1,211 @@
const express = require( 'express' );
const path = require( 'path' );
// Classes
const Pagans = require( '../models/Pagans.js' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
/*
models/Pagans.js
Managed:
/data/tribee/client-Id/users/uuid.json
/searchindex/emails.json {email:uuid}
/login.json {login:uuid}
/uids.json {uuid;[[
login,
email,
encrypted psw,
accessrights]}
ACCESSRIGHTS = {
app:{"tribeid:appname":"profil"},
data:{"tribeid":{object:"CRUDO"}}
}
ACCESSRIGHTS is store into the token and is load into req.session.header.accessrights by hasAccessrighton() middleware
appname is a website space object /sitewebsrc/appname
website live is strored into /dist source in /src
This can be managed by maildigitcreator or not.
apixtribe/sitewebs/webapp is the webinterface of apixtribe
profil: admin / manager / user are key word to give specific access to data into model. Any kind of other profil can exist. It is usefull to manage specific menu in an app.
It is also possible to authorize update a field's object depending of rule into dataManagement/object/
{ field:X
nouserupdate: "!(['admin','manager'].includes(contexte.profil))",
}
data allow a user to access tribeid with Create Read Update Delete Own (CRUDO) on each object of a tribeid independantly of any app.
Create allow to create a new object respecting rules defined into /referentials/dataManagement/object/name.json
Update idem
Delete idem
Owner means it can be Write/Delete if field OWNER contain the UUID that try to act on this object. Usefull to allow someone to fully manage its objects.
*/
router.get( '/isauth', checkHeaders, isAuthenticated, ( req, res ) => {
if( req.session.header.xpaganid == "1" ) {
return res.status( 401 )
.send( { info: "not authenticate" } );
} else return res.status( 200 )
.send( { info: "well authenticated" } )
} )
router.post( '/login', checkHeaders, async ( req, res ) => {
// console.log('POST /users/login with: ', req.app.locals.header);
/*
Check un mot de passe pour un login pour obtenir un token d'authentification
valable 1 hour, 1 day
@header
@body.LOGIN
@body.PASSWORD
@checkpsw = true check si les 2 mot de passe cryptés correspondent
false bypass le contrôle et permet de générer un token
utile le temps de reinitialisé son mot de passe.
@return
*/
console.log( 'login for ', req.body, "in", req.session.header )
const log = await Pagans.loginUser( req.session.header, req.body, true );
console.log( "log user login", log );
if( log.status == 200 ) {
// update req.app.locals.tokens for this uuid just after login success then next isAuth will be valid
req.app.locals.tokens[ log.data.user.UUID ] = { TOKEN: log.data.user.TOKEN, ACCESSRIGHTS: log.data.user.ACCESSRIGHTS }
console.log( req.app.locals )
}
return res.status( log.status )
.send( log.data );
} );
router.get( '/getlinkwithoutpsw/:email', checkHeaders, async ( req, res ) => {
/*
Permet pour un email existant de renvoyer un email avec un lien valable 1h
@email est le compte pour lequel on demande un accès
Réponse:
Si email n'existe pas on n'envoie pas d'email
Si email existe on envoie un email avec un lien dont le token est valable 1h
@return
{status:200 ou erreur ,
payload:{
info:[list de key to appear in correct requester langue],
model:'Pagans',
moreinfo: 'texte pour log '
}
}
*/
console.log( `GET /users/getlinkwithoutpsw for email: ${req.params.email} tribeid :${req.header('X-Client-Id')}` );
if( !req.params.email ) {
return res.status( 404 )
.send( {
info: [ 'emailmissing' ],
model: 'Pagans'
} );
} else {
try {
const getlink = await Pagans.getlinkwithoutpsw( req.params.email, req.session.header );
console.log( 'getlink', getlink )
//met à jour le token créer pour le uuid
req.app.locals.tokens[ getlink.data.info.xuuid ] = getlink.data.info.token;
// attention si on relance le serveur le token temporaire est perdu
return res.status( getlink.status )
.send( getlink.data );
} catch ( err ) {
console.log( err )
return res.status( 500 )
.send( { info: [ 'errServer' ], model: 'Pagans' } );
}
}
} );
router.post( '/register', checkHeaders, async ( req, res ) => {
console.log( `POST /users for ${req.session.header.xtribe}` );
if( req.session.header.xauth == '123123' ) {
// Creation d'un utilisateur avec information de base aucun droit
// On modifie le contenu du form pour n egarder que login/email et psw
// pour le client_id permet de traiter un user en attente de validation
console.log( 'req du post', req );
}
} );
router.get( '/info/:listindex', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'R' ), async ( req, res ) => {
console.log( `get users info on tribeid ${req.session.header.xworkon} for ${req.params.listindex} with accessright`, req.session.header.accessrights.data );
const result = await Pagans.getinfoPagans( req.session.header.xpresworkon, req.session.header.accessrights, req.params.listindex );
res.status( result.status )
.send( result.data );
} );
router.get( '/list/:filter/:field', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'R' ), async ( req, res ) => {
console.log( 'GET /users/list/filtre/champs list for ' + req.session.header.xworkon );
if(
[ 'admin', 'manager' ].includes( req.session.header.decodetoken[ 'apps' + req.session.header.xworkon + 'profil' ] ) ) {
try {
const userslist = await Pagans.getUserlist( req.session.header, req.params.filter, req.params.field );
console.log( 'userslist', userslist );
if( userslist.status == 200 ) {
return res.status( userslist.status )
.send( userslist.data );
}
} catch ( err ) {
console.log( err );
return res.status( 400 )
.send( { info: 'erreur' } );
}
} else {
res.status( 403 )
.send( {
info: [ 'forbiddenAccess' ],
model: 'Pagans'
} );
}
} );
router.get( '/uuid/:id', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'R' ), async ( req, res ) => {
console.log( `GET /users/uuid/${req.params.id}` );
//console.log('req.app.locals: ', req.app.locals);
//console.log('req.session', req.session);
const result = await Pagans.getUser( req.params.id, req.session.header.xworkon, req.session.header.accessrights );
res.status( result.status )
.send( result.data );
} );
router.put( '/chgpsw/:id', checkHeaders, isAuthenticated, async ( req, res ) => {
console.log( `PUT update /users/chgpsw/${req.params.id}` );
try {
const majpsw = await Pagans.updateUserpassword( req.params.id, req.session.header, req.body );
res.status( majpsw.status )
.send( majpsw.data );
} catch ( {
status,
data
} ) {
res.status( status )
.send( data );
}
} );
router.post( '/uuid', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'C' ), async ( req, res ) => {
console.log( 'POST /users create for ' + req.session.header.xworkon, req.body );
const usercreate = await Pagans.createUser( req.session.header, req.body );
return res.status( usercreate.status )
.send( usercreate.data );
} );
router.put( '/uuid/:id', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'U' ), async ( req, res ) => {
console.log( `PUT update /users/${req.params.id}` );
// console.log('req.app.locals: ', req.app.locals);
// console.log('req.session', req.session);
try {
const majUser = await Pagans.updateUser( req.params.id, req.session.header, req.body );
res.status( majUser.status )
.send( majUser.data );
} catch ( {
status,
data
} ) {
res.status( status )
.send( data );
}
} );
router.delete( '/uuid/:id', checkHeaders, isAuthenticated, hasAccessrighton( 'users', 'D' ), ( req, res ) => {
console.log( `DELETE /users/uuid/${req.params.id}` );
const result = Pagans.deleteUser( req.params.id, req.session.header );
res.status( result.status )
.send( result.data );
} );
module.exports = router;

94
routes/referentials.js Executable file
View File

@ -0,0 +1,94 @@
// Upload de file
const express = require( 'express' );
const glob = require( 'glob' );
const path = require( 'path' );
// Classes
const Referentials = require( '../models/Referentials' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
/*
* keylist = list of key at 1st level in clientconf.json separated by _
* we use header.xworkon
* To manage AccesRight obkect referentials does not follow the same logic than other object this is why
*/
router.get( '/clientconf/:keylist', checkHeaders, isAuthenticated, ( req, res ) => {
// retourne liste info (non sensible) du tribeid inside headers.xworkon sur keylist ="key1_key2"
/*
if (req.session.header.accessrights.data[ "Alltribeid" ] && req.session.header.accessrights.data[ "Alltribeid" ].referentials.includes('R') ;
*/
console.log( `get clientconf for ${req.session.header.xworkon} on ${req.params.keylist}` )
let dataref = {}
if( req.params.keylist.split( '_' )
.length > 0 ) {
const ref = Referentials.clientconf( req.session.header.xworkon, req.params.keylist.split( '_' ) )
if( ref.status == 200 ) {
dataref = ref.payload.data;
} else {
console.log( "erreur ", ref )
}
}
console.log( 'envoie en jsonp: dataref' )
res.jsonp( dataref )
} );
router.get( '/clientconfglob', checkHeaders, isAuthenticated, ( req, res ) => {
res.jsonp( Referentials.clientconfglob()
.payload.data );
} );
router.get( '/contentlist/:source', checkHeaders, isAuthenticated,
( req, res ) => {
const payload = [];
console.log( req.params.source, `${config.tribes}/${req.session.header.xworkon}/referentials/dataManagement/${req.params.source}/*.json` )
glob.sync( `${config.tribes}/${req.session.header.xworkon}/referentials/dataManagement/${req.params.source}/*.json` )
.forEach( f => {
payload.push( path.basename( f, '.json' ) );
} )
res.json( payload );
} );
router.get( '/contentfull/:source/:idref', checkHeaders, isAuthenticated,
( req, res ) => {
//only for data and object
console.log( 'route referentials get all language' + req.params.source + '-' + req.params.idref );
const getref = Referentials.getref( true, req.params.source, req.params.idref, req.session.header.xworkon, req.session.header.xlang );
// Return any status the data if any erreur return empty object
res.jsonp( getref.payload.data );
} );
router.get( '/content/:source/:idref', checkHeaders, isAuthenticated,
( req, res ) => {
console.log( 'route referentials get ' + req.params.source + '-' + req.params.idref );
const getref = Referentials.getref( false, req.params.source, req.params.idref, req.session.header.xworkon, req.session.header.xlang );
res.jsonp( getref.payload.data );
} );
// get with no authentification
router.get( '/contentnoauth/:source/:idref', checkHeaders,
( req, res ) => {
console.log( 'route referentials get ' + req.params.source + '-' + req.params.idref );
// @TODO check access right in clientconf before sending back json file
const getref = Referentials.getref( false, req.params.source, req.params.idref, req.session.header.xworkon, req.session.header.xlang );
res.jsonp( getref.payload.data );
} );
router.get( '/lg', ( req, res ) => {
console.log( req.headers[ "accept-language" ] )
let lg = '??';
if( req.headers[ "accept-language" ] && req.headers[ "accept-language" ].split( ',' )
.length > 0 ) {
lg = req.headers[ "accept-language" ].split( ',' )[ 0 ];
}
res.json( { lg } )
} );
router.put( '/content/:source/:idref', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( `route put content for ${req.params.idref} that is a ${req.params.source}` );
const putref = Referentials.putref( req.params.source, req.params.idref, req.session.header.xworkon, req.body )
return res.status( putref.status )
.send( { payload: putref.payload } )
} );
//hasAccessrighton( 'referentials', 'U' )
router.get( '/updatefull', checkHeaders, isAuthenticated, hasAccessrighton( 'referentials', 'U' ), ( req, res ) => {
console.log( `route get to force update content updatefull is accessrighton` );
const updtref = Referentials.updatefull( req.session.header.xworkon )
return res.status( updtref.status )
.send( { payload: updtref.payload } )
} );
module.exports = router;

29
routes/tags.js Executable file
View File

@ -0,0 +1,29 @@
//Installation d'un tag
/*
*/
// Upload de file
const express = require('express');
// Classes
const Tags = require('../models/Tags');
// Middlewares
const router = express.Router();
router.get('/:filename', (req, res) => {
//console.log('route tags get ', req.params.filename);
const savetag = Tags.getfile(req.params.filename, req);
if(savetag.status == 200) {
res.sendFile(savetag.payload.filename);
} else {
res.status(savetag.status)
.send({ payload: savetag.payload })
}
})
router.post('/:tribeid', (req, res) => {
//console.log('route tags post ', req.params.tribeid);
const savetag = Tags.savehits(req);
res.status(200)
.send('');
})
module.exports = router;

374
routes/tribes.js Executable file
View File

@ -0,0 +1,374 @@
const express = require( 'express' );
const fs = require( 'fs-extra' );
const path = require( 'path' );
const config = require( '../tribes/townconf.js' );
// Classes
const Tribes = require( '../models/Tribes.js' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const hasAccessrighton = require( '../middlewares/hasAccessrighton' );
const router = express.Router();
router.get( '/clientconf/:tribeid', checkHeaders, isAuthenticated, ( req, res ) => {
/*
get a clientconf.json for a tribeid depending of user accessright
if tribeid == all and user is admin of apixtribe => get /tmp/clientconfglob.json
req.session.header.accessrights, req.session.header.apixpaganid
*/
console.log( `Tribes/clientconf for tribeid:${req.params.tribeid}` )
if( req.params.tribeid == "all" && req.session.header.accessrights.data.apixtribe && req.session.header.accessrights.data.apixtribe.tribeid && req.session.header.accessrights.data.apixtribe.tribeid.includes( 'R' ) ) {
res.status( 200 )
.send( { moreinfo: fs.readJsonSync( `${config.tmp}/clientconfglob.json`, 'utf-8' ) } );
return;
}
if( req.session.header.accessrights.data[ req.params.tribeid ] &&
req.session.header.accessrights.data[ req.params.tribeid ].tribeid &&
req.session.header.accessrights.data[ req.params.tribeid ].tribeid.includes( 'R' ) &&
fs.existsSync( `${config.tribes}/${req.params.tribeid}/clientconf.json` ) ) {
// const conftribeid = { moreinfo: {} }
// conftribeid.moreinfo[ req.params.tribeid ] = fs.readJsonSync( `${config.tribes}/${req.params.tribeid}/clientconf.json`, 'utf-8' );
res.status( 200 )
.send( { moreinfo: [ fs.readJsonSync( `${config.tribes}/${req.params.tribeid}/clientconf.json`, 'utf-8' ) ] } );
return;
}
// if not authorized or dos not exist return empty
// no specific message is send for security reason (check only log)
res.status( 403 )
.send( { info: [ 'forbidenAccess' ], models: 'Tribes' } )
.end();
} )
router.put( '/', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( 'Create a new tribeid, with a useradmin' )
console.log( ' send data = clientconf.json with all parameter.' )
// !!!!! check for security any ; \n or so because data can be used into shell
const add = Tribes.create( req.body );
res.status( add.status )
.send( add.payload )
} )
router.delete( '/archivetribeid/:tribeid', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( "request archive tribeid" )
const archive = Tribes.archive( req.params.tribeid );
res.status( archive.status )
.send( archive.payload )
} );
router.post( '/spaceweb', checkHeaders, isAuthenticated, ( req, res ) => {
// !!!!! check for security any ; \n or so because data can be used into shell
console.log( 'Create a new webapp for xworkon ' )
req.body.tribeid = req.session.header.xworkon;
const add = Tribes.addspaceweb( req.body )
res.status( add.status )
.send( add.payload )
} )
router.get( '/spaceweb/components/:tribeid/:website/:key', checkHeaders, ( req, res ) => {
// check if key is valid before continue
// exemple: get Tribes/spaceweb/components/ndda/mesa/123?rep=appmesatable/appsimpletable.mustache
const file = `${config.tribes}/${req.params.tribeid}/spacedev/${req.params.website}/src/ctatic/components/${req.query.path}`
console.log( `Request components file from ${file}` )
if( fs.existsSync( file ) ) {
res.sendFile( file );
} else {
res.send( `console.error("Missing components file in ${req.params.tribeid}/spacedev/${req.params.website}/src/ctatic/components/${req.query.path}");` );
}
} )
router.get( '/plugins/:tribeid/:pluginname/:key/:filename', ( req, res ) => {
// No accessright possible cause it is load on the fly
// @todo Check key to authorize access to the plugin (key comme from user ACCESSRIGHTS[tribeid plugin owner:pluginname]).key
// return a file into /:tribeid owner of plugin/plugins/:pluginname/components/:filename
// if not exist or invalid key then return console.error
const file = `${config.tribes}/${req.params.tribeid}/plugins/${req.params.pluginname}/components/${req.params.filename}`
console.log( 'Tribes/plugins/ ', file )
if( fs.existsSync( file ) ) {
res.sendFile( file );
} else {
res.send( `console.error("Missing plugin file in ${req.params.tribeid}/plugins/${req.params.pluginname}/components/${req.params.filename}");` );
}
} );
router.get( '/dirls', checkHeaders, isAuthenticated, ( req, res ) => {
// url /Tribes/dirls?rep=referentials/dataManagement
// request information about a req.query.rep from header xworkon/
// return
// {file:[{}],dir:[{}]}
// @todo check if isAuthorized and exist
console.log( 'request dirls', `${config.tribes}/${req.session.header.xworkon}/${req.query.rep}` );
if( !fs.existsSync( `${config.tribes}/${req.session.header.xworkon}/${req.query.rep}` ) ) {
res.status( 404 )
.send( { 'info': [ 'dirnotexist' ], model: 'Tribes' } );
}
const info = Tribes.dirls( req.session.header.xworkon, req.query.rep );
console.log( info )
res.status( info.status )
.send( info.payload );
} )
router.delete( '/ls', checkHeaders, isAuthenticated, ( req, res ) => {
// check Accessright with D or O on each
// url /Tribes/ls
// req.body.files=[listfiles file to delete ]
const authfiles = Tribes.checkaccessfiles( req.body, 'D', req.session.header.accessrights, req.session.header.apixpaganid );
authfiles.ok.forEach( f => { fs.remove( `${config.tribes}/${f}` ); } )
res.status( 200 )
.send( { 'info': [ 'fileauthdeleted' ], models: 'Tribes', moreinfo: authfiles } )
} );
router.put( '/sendjson', checkHeaders, isAuthenticated, ( req, res ) => {
//req.body = {object:spacedev, path:website/src/data/tpldataname_lg.json, data:{...}}
//console.log( req.body )
const dest = `${config.tribes}/${req.session.header.xworkon}/${req.body.object}/${req.body.path}`;
console.log( `Send json to saved to ${dest}` );
if( !( req.body.object && fs.existsSync( `${config.tribes}/${req.session.header.xworkon}/${req.body.object}` ) ) ) {
res.status( '404' )
.send( { info: [ 'objectmissiong' ], models: 'Tribes', moreinfo: `object: ${req.body.object} does not exist req.body must {object, data, path} into data ${req.session.header.xworkon}/${req.body.object}` } )
} else {
if( fs.existsSync( `${config.tribes}/${req.session.header.xworkon}/${req.body.object}/${req.body.path}` ) ) {
// exist so can be update check accessright update on this
hasAccessrighton( req.body.object, "U" );
} else {
hasAccessrighton( req.body.object, "C" );
}
fs.outputJsonSync( dest, req.body.data );
res.status( 200 )
.send( { info: [ 'filesaved' ], models: 'Tribes' } )
}
} );
router.post( '/downloadls', checkHeaders, isAuthenticated, ( req, res ) => {
// midlleware hasAccessrighton.js is not apply here only to access/update/create information inside an object
// to get file a user need accessrights to data: object: R or to Own it
// or if exist a .info.json into folder get shared as R in uuid
//req.body contain list of path file or folder if only 1 file then download it, otherwise zip list and send zip file
const authfiles = Tribes.checkaccessfiles( req.body.files, 'R', req.session.header.accessrights, req.session.header.xpaganid );
if( authfiles.ok.length == 1 ) {
// bidouille en attendnat de faire un .zip binaire propre
if( !authfiles.ok[ 0 ].includes( '.xml' ) ) {
res.status( 200 )
.download( `${config.tribes}/${authfiles.ok[0]}`, authfiles.ok[ 0 ] );
} else {
fs.copySync( `${config.tribes}/${authfiles.ok[0]}`, `${config.tribes}/${config.mayorId}/www/app/webapp/static/tmp/${authfiles.ok[ 0 ]}` )
}
} else if( authfiles.ok.length > 1 ) {
// on zip et on envoie
//res.status( 200 )
// .download( `${config.tribes}/${authfiles.ok[0]}`, authfiles.ok[ 0 ])
res.status( 200 )
.attachment( `${config.tribes}/${authfiles.ok[0]}` );
} else {
req.body.filepon
res.status( 403 )
.send( 'Forbidden access' )
}
} );
router.post( '/upfilepond', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( 'post /Tribes/uploadfilepond' );
// Store file and return a unique id to save button
// that provide folder where to store it
const formidable = require( 'formidable' );
const form = formidable( { multiples: false } );
form.parse( req, ( err, fields, files ) => {
if( err ) { next( err ); return; }
//console.log( 'fields',fields);
// fileMetadaObject send
let context = JSON.parse( fields.filepond );
let idfile = files.filepond.path;
let name = files.filepond.name;
let subfolder = context.subfolder;
name = name.replace( /[ ,'"]/g, "_" );
//console.log( 'files.filepond:', files.filepond );
console.log( idfile, `${config.tribes}/${req.session.header.xworkon}/www/${subfolder}/${name}` )
// On le supprime s'il existe deja
fs.removeSync( `${config.tribes}/${req.session.header.xworkon}/www/${subfolder}/${name}` );
// mv tmp
fs.moveSync( idfile, `${config.tribes}/${req.session.header.xworkon}/www/${subfolder}/${name}` );
//res.status(200).send({models:"Tribes",info:["Savedsuccess"],moreinfo:{id:file.filepond.path}})
//return for filepond
res.writeHead( 200, { 'Content-Type': 'text/plain' } );
res.end( idfile );
} )
} );
router.delete( '/file', checkHeaders, isAuthenticated, ( req, res ) => {
//src = objectfolder with accessright/...
//hasAccessrighton( "www", "D" ),
if( !req.query.src ) {
res.status( 404 )
.send( { info: [ 'deleteerror' ], models: "Tribes", moreinfo: "your del req need a src" } )
return;
};
hasAccessrighton( req.query.src.split( '/' )[ 0 ], "D" );
console.log( 'Remove file', `${config.tribes}/${req.session.header.xworkon}/${req.query.src}` )
console.log( req.body )
fs.removeSync( `${config.tribes}/${req.session.header.xworkon}/${req.query.src}` );
res.status( 200 )
.send( { info: [ 'Successfullremove' ], models: "Tribes" } )
} );
router.post( '/uploadfile', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( 'upload a file ' )
/* Authentification is needed to get a TOKEN
curl -X POST -H "xtribe: apixtribe" -H "xworkon: pvmsaveurs" -H "xlang: fr" -H "xpaganid: 1" -H "xauth: 1" -H "xapp: pvmsaveurs:pvmsaveurs" -H "Content-Type: application/json" -d '{"LOGIN":"adminapixtribe","PASSWORD":"Trze3aze!"}' http://pvmsaveurs.pvmsaveurs.fr/app/users/login
if exist replace xpaganidTOKEN with payload.TOKEN value
curl -H "xtribe: pvmsaveurs" -H "xworkon: pvmsaveurs" -H "xlang: fr" -H "xpaganid: adminapixtribe" -H "xauth: xpressuuisToken" -H "xapp: pvmsaveurs:pvmsaveurs" -F 'data=@filename.xx' http://pvmsaveurs.pvmsaveurs.fr/app/Tribes/uploadfile
*/
const formidable = require( 'formidable' );
const form = formidable( { multiples: false } );
form.parse( req, function ( err, fields, files ) {
//console.log( files.data )
var oldPath = files.data.path;
var newPath = `${config.tribes}/${req.session.header.xworkon}/${clientconf.uploadzip[files.data.name].dest}`;
console.log( 'oldPath', oldPath )
console.log( 'newPath', newPath )
var rawData = fs.readFileSync( oldPath )
fs.outputFile( newPath, rawData, function ( err ) {
if( err ) {
console.log( err );
return res.status( 405 )
.send( { info: [ 'savederror' ], models: "Tribes", moreinfo: "your file was not able to be saved into the server" } )
} else {
return res.status( 200 )
.send( {
info: [ "successfullsent" ],
models: "Tribes"
} );
}
} )
} );
} );
router.post( '/uploadzip', checkHeaders, ( req, res ) => {
console.log( 'uploadzip a file ' )
/* no authentification to upload a zip filename into /tribes/${xworkon}/${clientconf.uploadzip[filename].dest}
unzip it using the password ${clientconf.uploadzip[filename].psw
if no error then run the callback ${clientconf.uploadzip[filename].callback
but a password to unzip
in clientconf.json need to be set
"uploadzip": {
"articlesTribespvm.zip": {
"comment": "unzip with overwrite if same name",
"psw": "azPI1209qtrse",
"dest": "importexport/tmp",
"unzipoption": "-aoa",
"callback": "importexport/integrationitem.js"
}
},
Example:
cd where zip file is stored
curl -H "xtribe: pvmsaveurs" -H "xworkon: pvmsaveurs" -H "xlang: fr" -H "xpaganid: adminapixtribe" -H "xauth: 1" -H "xapp: pvmsaveurs:pvmsaveurs" -F 'data=@articlesTribespvm.zip' http://pvmsaveurs.pvmsaveurs.fr/app/Tribes/uploadzip
*/
const clientconf = fs.readJSONSync( `${config.tribes}/${req.session.header.xworkon}/clientconf.json` )
if( !clientconf.uploadzip ) {
return res.status( '404' )
.send( { info: [ "missconf" ], models: "Tribes", moreinfo: `no uploadzip in clientconf for ${req.session.header.xworkon} please contact apixtribe admin ` } );
};
const uploadzip = clientconf.uploadzip;
const formidable = require( 'formidable' );
const form = formidable( { multiples: false } );
form.parse( req, function ( err, fields, files ) {
//console.log( files.data )
var oldPath = files.data.path;
if( !Object.keys( clientconf.uploadzip )
.includes( files.data.name ) ) {
return res.status( 403 )
.send( { info: [ "notAllowed" ], models: "Tribes", moreinfo: `file ${files.data.name} not allowed to be upload` } )
} else {
console.log( "context:", clientconf.uploadzip[ files.data.name ] )
var newPath = `${config.tribes}/${req.session.header.xworkon}/${clientconf.uploadzip[files.data.name].dest}`;
//console.log( 'oldPath', oldPath )
//console.log( 'newPath', `${newPath}/${files.data.name}` )
fs.moveSync( oldPath, `${newPath}/${files.data.name}`, { overwrite: true } );
const cp = require( 'child_process' );
//console.log( `7z e -p${clientconf.uploadzip[ files.data.name ].psw} ${newPath}/${files.data.name}` );
console.log( '7z', [ 'e', `-p${clientconf.uploadzip[ files.data.name ].psw}`, `${newPath}/${files.data.name}`, `-o${config.tribes}/${req.session.header.xworkon}/${clientconf.uploadzip[ files.data.name ].dest}`, clientconf.uploadzip[ files.data.name ].unzipoption ] );
var newFiles = cp.spawnSync( '7z', [ 'e', `-p${clientconf.uploadzip[ files.data.name ].psw}`, `${newPath}/${files.data.name}`, `-o${config.tribes}/${req.session.header.xworkon}/${clientconf.uploadzip[ files.data.name ].dest}`, clientconf.uploadzip[ files.data.name ].unzipoption ] );
console.log( newFiles.output.toString() )
if( newFiles.output.toString()
.includes( 'Everything is Ok' ) ) {
if( clientconf.uploadzip[ files.data.name ].callback ) {
const integ = require( `${config.tribes}/${req.session.header.xworkon}/${clientconf.uploadzip[files.data.name].callback}` )
.run();
console.log( 'integration', integ )
return res.status( integ.status )
.send( integ.payload );
} else {
return res.status( 200 )
.send( {
info: [ "successfullsent" ],
models: "Tribes"
} );
}
} else {
return res.status( 400 )
.send( {
info: [ "zipfileerror" ],
models: "Tribes",
moreinfo: newFiles.output.toString()
} )
}
}
} )
} );
router.post( '/upload', checkHeaders, isAuthenticated, ( req, res ) => {
1 // ACHANGER VIA usage sendjson
// url /Tribes/upload?save=tmp&rep=referentials/dataManagement
// if save=tmp then store in a tmp file
// if save=ok then mv the tmp file to the folder
// midlleware hasAccessrighton.js is not apply here only to access/update/create information inside an object
// to upload a file a user need accessrights to data: object: C or to Own it
// or if dir.file exist a .info.json into folder get shared as C in uuid accessright
/*
to add in front
<form action="/upload" method="POST" enctype="multipart/form-data">
<input type="file" name="file"/>
<input type="submit" value="upload"/>
</form>
*/
console.log( 'Envoie image' )
console.log( 'body', req.body );
console.log( 'params', req.params );
//const authfolder = Tribes.checkaccessfiles( req.params.rep, 'C', req.session.header.accessrights, req.session.header.xpaganid );
// cheack autorisation to create or replace a file for this accessrights user
const authfolder = { ok: "tt" }
if( authfolder.ok ) {
if( req.params.save == 'file' ) {
if( fs.existsSync( req.body.filepond ) ) {
fs.mv( req.body.filepond, req.params.rep );
}
};
// voir si c'est toujours pertinent car upload est géré par filepond pour les image
if( req.params.save == 'upload' ) {
const form = formidable( { multiples: false } );
form.parse( req, ( err, fields, files ) => {
if( err ) { next( err ); return; }
let thefile = files.filebond.path;
fs.outputFileSync()
console.log( 'thefile:' + thefile );
res.writeHead( 200, { 'Content-Type': 'text/plain' } );
res.end( theFile );
} )
}
} else {
res.status( 403 )
.send( 'forbiden access' );
}
} );
/*
Manage tribeid into /data/tribee/tribeid
client space dedicated
@Todo
clientconfglob copy cut from Referentials.clientconfglob
clientconf.json copy cut from Referentials.clientconf
list of tribeid copy cut from Referentials.
Add a tribeid
update clientconf
*/
module.exports = router;

49
routes/uploadFiles.js Executable file
View File

@ -0,0 +1,49 @@
// Upload de file
const express = require( 'express' );
const path = require( 'path' );
const jsonfile = require( 'jsonfile' );
const fs = require( 'fs' );
// Classes
const UploadFile = require( '../models/UploadFiles' );
// Middlewares
const checkHeaders = require( '../middlewares/checkHeaders' );
const isAuthenticated = require( '../middlewares/isAuthenticated' );
const router = express.Router();
router.post( '/', checkHeaders, ( req, res ) => {
console.log( 'route uploadFile post ' );
const saveFile = UploadFile.add( req, req.session.header );
res.send( saveFile );
// res.send({ status: 200, payload: { info: 'fine' } });
} );
router.post( '/json', checkHeaders, ( req, res ) => {
console.log( 'route uploadFile post de fichier json ' );
// a callback can be pass to req.body to run a specific process after upload
const saveFile = UploadFile.addjson( req.body, req.session.header );
console.log( saveFile );
res.send( saveFile );
// res.send({ status: 200, payload: { info: 'fine' } });
} );
router.get( '/:filename', checkHeaders, isAuthenticated, ( req, res ) => {
console.log( 'route uploadFile get ', req.params.filename );
const pushFile = UploadFile.get(
req.params.filename.replace( /______/g, '/' ),
req.session.header
);
if( pushFile.status == 200 ) {
if( path.extname( pushFile.payload.file ) === '.json' ) {
jsonfile.readFile( pushFile.payload.file, ( err, p ) => {
if( err ) console.error( err );
res.jsonp( p );
} );
} else {
res.download( pushFile.payload.file, path.basename( pushFile.payload.file ) );
}
} else {
res.send( pushFile );
}
} );
module.exports = router;

111
setup/ASUPconfig.jsold Executable file
View File

@ -0,0 +1,111 @@
'use strict';
const path = require( 'path' );
const fs = require( 'fs' );
const config = {};
if( !process.env.NODE_ENV ) process.env.NODE_ENV = "dev"
console.log( 'apixtribe process.env.NODE_ENV: ', process.env.NODE_ENV );
// VOIR l'ancien fichier de cnfig au cas ou il manque des chemins dans config
// voir la doc http://gitlab.ndda.fr/philc/apiamaildigitfr/wikis/InstallConf
config.prod = {
mainDir: __dirname,
tmp: path.join( __dirname, '/tmp' ),
public: path.join( __dirname, '/public' ),
archivefolder: path.join( __dirname, '/archive' ),
rootURL: '{{setupdns}}',
domain: path.join( __dirname, '/data/tribee' ),
withssl: "{{withssl}}",
SSLCredentials: {
key: path.join( __dirname, '/data/certs/{{setupdns}}.key' ),
cert: path.join( __dirname, '/data/certs/{{setupdns}}.crt' ),
ca: path.join( __dirname, '/data/certs/{{setupdns}}.csr' )
},
port: {
http: "{{httpport}}",
https: "{{httpsport}}"
},
jwtSecret: '{{jwtsecretkey}}',
saltRounds: 10,
languagesAvailable: [ 'fr', 'en', 'it', 'de', 'ru' ],
lg: {},
exposedHeaders: {
'x-auth': 'xauth',
'x-uuid': 'xuuid',
'x-language': 'xlang',
'x-client-id': 'xtribeid',
'x-workOn': 'xworkOn',
'x-app': 'xapp'
},
bodyparse: {
urlencoded: {
limit: '50mb',
extended: true
},
json: { limit: '500mb' }
}
};
// Development and test config
// apixtribe.local.fr
config.dev = {
mainDir: __dirname,
tmp: path.join( __dirname, '/tmp' ),
public: path.join( __dirname, '/public' ),
//public allow to serve on /public file into folder /public with or without login
archivefolder: path.join( __dirname, '/archive' ),
rootURL: 'apixtribe.local.fr',
domain: path.join( __dirname, '/data/tribee' ),
withssl: "YES",
SSLCredentials: {
key: path.join( __dirname, '/setup/data/certs/apixtribe.local.fr.key' ),
cert: path.join( __dirname, '/setup/data/certs/apixtribe.local.fr.crt' ),
ca: path.join( __dirname, '/setup/data/certs/apixtribe.local.fr.csr' )
},
port: {
http: "{{httpport}}",
https: "{{httpsport}}"
},
jwtSecret: 'dnsqd515+npsc^dsqdsqd^d$qdd$$$dŝqdze154615ae.Dsd:sqd!',
// Avoid authentification for uuid2 and this auth token
// Always remove this from production
devnoauthxuuid: "2",
devnoauthxauth: "autoriserparlapatrouille",
saltRounds: 10,
languagesAvailable: [ 'fr', 'en', 'it', 'de', 'ru' ],
lg: {},
exposedHeaders: {
'x-auth': 'xauth',
'x-uuid': 'xuuid',
'x-language': 'xlang',
'x-client-id': 'xtribeid',
'x-workOn': 'xworkOn',
'x-app': 'xapp'
},
bodyparse: {
urlencoded: {
limit: '50mb',
extended: true
},
json: { limit: '500mb' }
}
};
if( !config[ process.env.NODE_ENV ] ) {
console.log( 'config.js -> Exit setup due to node_ENV have to be set as prod or dev instead of ', process.env.NODE_ENV )
process.exit();
}
const confuse = config[ process.env.NODE_ENV ];
if( confuse.withssl == "YES" ) {
if( !fs.existsSync( confuse.SSLCredentials.key ) ) {
const prompt = require( 'prompt-sync' )( { sigint: true } );
//const repdata = ( process.NODE_ENV == "dev" ) ? 'devdata' : 'data';
const reinit = prompt( `Missing file to ssl ${confuse.SSLCredentials.key}, please run the process letsencrypt to get a ssl certificat or reinit project (type reinit will erase ) and answer no to question: Do you want to manage ssl : ` );
if( reinit == 'reinit' ) {
fs.removeSync( `${__dirname}/config.js` );
fs.removeSync( `${__dirname}/data` )
}
process.exit();
}
confuse.SSLCredentials.key = fs.readFileSync( confuse.SSLCredentials.key, 'utf8' );
confuse.SSLCredentials.cert = fs.readFileSync( confuse.SSLCredentials.cert, 'utf8' );
confuse.SSLCredentials.ca = fs.readFileSync( confuse.SSLCredentials.ca, 'utf8' );
}
module.exports = confuse;

View File

@ -0,0 +1,37 @@
server {
server_name {{config.apixtribeDNS}};
add_header X-Request-ID $request_id; # Return to client
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Max-Age 3600;
add_header Access-Control-Expose-Headers Content-Length;
add_header Access-Control-Allow-Headers Range;
access_log /media/phil/usbfarm/apixtribe/tmp/nginx/apixtribe.crabdance.access.log main;
#location = /app {
# return 302 /app/;
#}
location /app/ {
rewrite /app/(.*$) /$1 break;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 90;
proxy_read_timeout 90;
proxy_buffers 32 4k;
proxy_set_header X-NginX-Proxy true;
proxy_pass http://localhost:3017;
proxy_redirect off;
}
location / {
root /media/phil/usbfarm/apixtribe/nationchains/;
index apixtribe.html;
}
error_page 404 /media/phil/usbfarm/apixtribe/nationchains/error/404.html;
error_page 500 502 503 504 /media/phil/usbfarm/apixtribe/nationchains/error/50x.html;
}

26
setup/config.mustache Executable file
View File

@ -0,0 +1,26 @@
const path = require( 'path' );
const config = {
loglevel:"{{consoleloglevel}}",
linuxuser:"{{linuxuser}}",
druidid:"{{druidid}}",
dnsapixtribe:"{{subdomain}}.{{domain}}",
mainDir: __dirname,
tmp: path.join( __dirname, '/tmp' ),
public: path.join( __dirname, 'data/tribe/{{druidid}}/www/cdn' ),
//(@TODO ASUP mettre /cdn de apixtribe) public allow to serve on /public file into folder /public with or without login
archivefolder: path.join( __dirname, '/archive' ),
domain: path.join( __dirname, '/data/tribe' ),
porthttp:{{porthttp}} ,
jwtSecret: '{{jwtsecret}}',
saltRounds: 10,
languagesAvailable: [ {{#language}}'{{.}}',{{/language}} ],
exposedHeaders:[ 'xauth', 'xpaganid', 'xlang', 'xtribe', 'xworkon', 'xapp' ],
bodyparse: {
urlencoded: {
limit: '50mb',
extended: true
},
json: { limit: '500mb' }
}
};
module.exports = config;

13
setup/configsetup.json Executable file
View File

@ -0,0 +1,13 @@
{
"linuxuser": "phil",
"mode": "dev",
"domain": "local.fr",
"subdomain": "dev",
"consoleloglevel": "quiet",
"porthttp": 3018,
"language": ["fr", "en"],
"jwtsecret": "longsentenceusedtoencryptionChangethisforproduction",
"druidid": "test",
"login": "testadmin",
"genericpsw": "@12ABab@"
}

View File

@ -0,0 +1,9 @@
{
"hash": "",
"previousblockhash": "",
"nextblockhash": "",
"info": "txt:freedomandthefuturofinternet##",
"time": 1643763742,
"size": "",
"difficulty": ""
}

View File

@ -0,0 +1,10 @@
{
"fixedIP":"",
"firsttimeping":0,
"lastimeping":0,
"positifping":0,
"negatifping":0,
"pubkeyadmin":"",
"tribeids":[],
"logins":[]
}

View File

@ -0,0 +1,28 @@
{
"tribeid": "{{tribeid}}",
"genericpsw": "{{genericpsw}}",
"saltRounds": "tomakeitharderbetterstronger",
"website": {
"webapp": "{{subdomain}}.{{domain}}"
},
"allowedDOMs": ["{{domain}}"],
"customization": {
"claim": "Be a Producer, not a product.",
"name": "apixtribe",
"logo": "https://{{subdomain}}.{{domain}}/cdn/{{druidid}}/img/logo/apixtribe.png",
"favicon": "https://{{subdomain}}.{{domain}}/cdn/{{druidid}}/img/iconX74x74.png",
"colors": {
"primary": "#01717B",
"secondary": "#CA5F00",
"success": "",
"info": "",
"warning": "",
"danger": "",
"light": "#fff",
"dark": "#222"
}
},
"smtp": {},
"accepted-language": "fr,en",
"langueReferential": ["fr"]
}

View File

@ -0,0 +1,106 @@
[{
"uuid": "ERRcritical",
"desc": {
"fr": "Erreur critique",
"en": "Critical Error"
}
},
{
"uuid": "failtoWritefs",
"desc": {
"fr": "Impossible d'enregistrer cette information",
"en": "Fail to write on system"
}
},
{
"uuid": "successfulCreate",
"desc": {
"fr": "Création réussie",
"en": "Creation is successful"
}
},
{
"uuid": "successfulUpdate",
"desc": {
"fr": "Mise à jour réussie",
"en": "Succesfull update"
}
},
{
"uuid": "successfulDelete",
"desc": {
"fr": "Suppression effectuée",
"en": "The user has been deleted"
}
},
{
"uuid": "forbiddenAccess",
"desc": {
"fr": "Accès non autorisé",
"en": "Forbidden access"
}
},
{
"uuid": "userNotAllowtoCreate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à créer un nouvel utilisateur",
"en": "Pagans is not allowed to create a new user account"
}
},
{
"uuid": "userNotAllowtoUpdate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à mettre à jour cet utilisateur",
"en": "Pagans is not allowed to update this user account"
}
},
{
"uuid": "userNotAllowtoDelete",
"desc": {
"fr": "L'utilisateur n'est pas authrisé à supprimer utilisateur",
"en": "Pagans is not allowed to delete a user account"
}
},
{
"uuid": "invalidData",
"desc": {
"fr": "Vérifiez vos données",
"en": "Check your data"
}
},
{
"uuid": "ERRemail",
"desc": {
"fr": "Vérifiez votre email",
"en": "Check your email"
}
},
{
"uuid": "Reservationplandoesnotexist",
"desc": {
"fr": "Il n'y a pas de Planning de réservation",
"en": "No reservation plan at this adress"
}
},
{
"uuid": "Reservationitemwelldone",
"desc": {
"fr": "Votre reservation a bien été enregistrée",
"en": "Registration well done."
}
},
{
"uuid": "Nomoreavailability",
"desc": {
"fr": "Désolé, la place n'est plus disponible",
"en": "Unavailable."
}
},
{
"uuid": "CatalogdoesnotExist",
"desc": {
"fr": "Désolé, ce catagoue n'existe pas",
"en": "This catalog does not exist."
}
}
]

View File

@ -0,0 +1,170 @@
[
{
"uuid": "ERRcritical",
"desc": {
"fr": "Erreur critique",
"en": "Critical Error"
}
},
{
"uuid": "msgsentok",
"desc": {
"fr": "L'email a bien été envoyé",
"en": "email sent"
}
},
{
"uuid": "emailAlreadyExist",
"desc": {
"fr": "Cet email a déjà un compte",
"en": "Email already exists"
}
},
{
"uuid": "failtoWritefs",
"desc": {
"fr": "Impossible d'enregistrer cette information",
"en": "Fail to write on system"
}
},
{
"uuid": "successfulCreate",
"desc": {
"fr": "Création réussie",
"en": "Creation is successful"
}
},
{
"uuid": "successfulUpdate",
"desc": {
"fr": "Mise à jour réussie",
"en": "Succesfull update"
}
},
{
"uuid": "successfulDelete",
"desc": {
"fr": "Suppression effectuée",
"en": "The user has been deleted"
}
},
{
"uuid": "serverNeedAuthentification",
"desc": {
"fr": "Ce serveur nécessite une authentification",
"en": "This server needs authentification"
}
},
{
"uuid": "forbiddenAccess",
"desc": {
"fr": "Accès non autorisé",
"en": "Forbidden access"
}
},
{
"uuid": "userNotAllowtoCreate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à créer un nouvel utilisateur",
"en": "Pagans is not allowed to create a new user account"
}
},
{
"uuid": "userNotAllowtoUpdate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à mettre à jour cet utilisateur",
"en": "Pagans is not allowed to update this user account"
}
},
{
"uuid": "userNotAllowtoDelete",
"desc": {
"fr": "L'utilisateur n'est pas authrisé à supprimer utilisateur",
"en": "Pagans is not allowed to delete a user account"
}
},
{
"uuid": "useridNotfound",
"desc": {
"fr": "L'utilisateur {{uuid}} n'existe pas sur {{tribeid}}",
"en": "Pagans {{uuid}} not found for {{tribeid}}"
}
},
{
"uuid": "useremailNotfound",
"desc": {
"fr": "L'email n'existe pas",
"en": "Email not found"
}
},
{
"uuid": "loginDoesNotExist",
"desc": {
"fr": "Ce login n'existe pas",
"en": "This login doesn't exist"
}
},
{
"uuid": "checkCredentials",
"desc": {
"fr": "Vérifiez vos identifiants",
"en": "Check your credentials"
}
},
{
"uuid": "wrongPassword",
"desc": {
"fr": "Vérifiez votre mot de passe",
"en": "Check your password"
}
},
{
"uuid": "invalidData",
"desc": {
"fr": "Vérifiez vos données",
"en": "Check your data"
}
},
{
"uuid": "pswTooSimple",
"desc": {
"fr": "Le mot de passe doit faire au moins 8 caractéres comporter au moins un chiffre, une lettre minuscule, une lettre majuscule et un caractere spécial type @ !...",
"en": "Password too simple, need to contain at least 8 caracters lower and uppercase, number and @! ..."
}
},
{
"uuid": "ERRemail",
"desc": {
"fr": "Vérifiez votre email",
"en": "Check your email"
}
},
{
"uuid": "ERRnewnewbisdiff",
"desc": {
"fr": "Le mot de passe de confirmation ne correspond pas",
"en": "Check your confirmation password"
}
},
{
"uuid": "ERRnotemplate",
"desc": {
"fr": "il n'y a pas de template d'email dans la demande template.html est vide et pas de htmlfile présent.",
"en": "Check your email template conf tribeid no msg.template.htmlfile and msg.html==''"
}
},
{
"uuid": "wellPdfGenerated",
"desc": {
"fr": "Le pdf: {{filename}} a ete genere correctement.",
"en": "Pdf well generated"
}
},
{
"uuid": "pdfGenerationError",
"desc": {
"fr": "Erreur dans la generation de pdf, verifiez le json",
"en": "Error in pdf generation, check the json"
}
}
]

View File

@ -0,0 +1,23 @@
[
{
"uuid": "fileUnknown",
"desc": {
"fr": "Fichier inconnu",
"en": "File unknown"
}
},
{
"uuid": "wellUpload",
"desc": {
"fr": "Fichier bien récupéré dans {{destination}}/{{filename}}",
"en": "File well uploaded in {{destination}}/{{filename}}"
}
},
{
"uuid": "savingError",
"desc": {
"fr": "Impossible de sauvegarder",
"en": "Saving file is impossible"
}
}
]

View File

@ -0,0 +1,149 @@
[
{
"uuid": "ERRcritical",
"desc": {
"fr": "Erreur critique",
"en": "Critical Error"
}
},
{
"uuid": "loginAlreadyExist",
"desc": {
"fr": "Ce login est déjà attribué",
"en": "Login already exists"
}
},
{
"uuid": "emailAlreadyExist",
"desc": {
"fr": "Cet email a déjà un compte",
"en": "Email already exists"
}
},
{
"uuid": "failtoWritefs",
"desc": {
"fr": "Impossible d'enregistrer cette information",
"en": "Fail to write on system"
}
},
{
"uuid": "successfulCreate",
"desc": {
"fr": "Création réussie",
"en": "Creation is successful"
}
},
{
"uuid": "successfulUpdate",
"desc": {
"fr": "Mise à jour réussie",
"en": "Succesfull update"
}
},
{
"uuid": "successfulDelete",
"desc": {
"fr": "Suppression effectuée",
"en": "The user has been deleted"
}
},
{
"uuid": "serverNeedAuthentification",
"desc": {
"fr": "Ce serveur nécessite une authentification",
"en": "This server needs authentification"
}
},
{
"uuid": "forbiddenAccess",
"desc": {
"fr": "Accès non autorisé",
"en": "Forbidden access"
}
},
{
"uuid": "userNotAllowtoCreate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à créer un nouvel utilisateur",
"en": "Pagans is not allowed to create a new user account"
}
},
{
"uuid": "userNotAllowtoUpdate",
"desc": {
"fr": "L'utilisateur n'est pas authorisé à mettre à jour cet utilisateur",
"en": "Pagans is not allowed to update this user account"
}
},
{
"uuid": "userNotAllowtoDelete",
"desc": {
"fr": "L'utilisateur n'est pas authrisé à supprimer utilisateur",
"en": "Pagans is not allowed to delete a user account"
}
},
{
"uuid": "useridNotfound",
"desc": {
"fr": "L'utilisateur {{uuid}} n'existe pas sur {{tribeid}}",
"en": "Pagans {{uuid}} not found for {{tribeid}}"
}
},
{
"uuid": "useremailNotfound",
"desc": {
"fr": "L'email n'existe pas",
"en": "Email not found"
}
},
{
"uuid": "loginDoesNotExist",
"desc": {
"fr": "Ce login n'existe pas",
"en": "This login doesn't exist"
}
},
{
"uuid": "checkCredentials",
"desc": {
"fr": "Vérifiez vos identifiants",
"en": "Check your credentials"
}
},
{
"uuid": "wrongPassword",
"desc": {
"fr": "Vérifiez votre mot de passe",
"en": "Check your password"
}
},
{
"uuid": "invalidData",
"desc": {
"fr": "Vérifiez vos données",
"en": "Check your data"
}
},
{
"uuid": "pswTooSimple",
"desc": {
"fr": "Le mot de passe doit faire au moins 8 caractéres comporter au moins un chiffre, une lettre minuscule, une lettre majuscule et un caractere spécial type @ !...",
"en": "Password too simple, need to contain at least 8 caracters lower and uppercase, number and @! ..."
}
},
{
"uuid": "ERRemail",
"desc": {
"fr": "Vérifiez votre email",
"en": "Check your email"
}
},
{
"uuid": "ERRnewnewbisdiff",
"desc": {
"fr": "Le mot de passe de confirmation ne correspond pas",
"en": "Check your confirmation password"
}
}
]

View File

@ -0,0 +1,12 @@
[
{
"uuid": 0,
"desclong": { "fr": "Monsieur", "en": "Mister" },
"desc": { "fr": "M.", "en": "M." }
},
{
"uuid": 1,
"desclong": { "fr": "Madame", "en": "Miss" },
"desc": { "fr": "Mme", "en": "Miss" }
}
]

View File

@ -0,0 +1,314 @@
[
{
"uuid": "AMDTA",
"desc": { "fr": "Amiante (DTA)" },
"desclong": {
"fr": "correspond au champ diag_01 dans le modèle de rapport Ordre de mission"
},
"color": "#449245"
},
{
"uuid": "AMVENT",
"desc": { "fr": "Amiante (Vente)" },
"desclong": {
"fr": "correspond au champ diag_02 dans le modèle de rapport Ordre de mission"
},
"color": "#136202"
},
{
"uuid": "AMTRAV",
"desc": { "fr": "Amiante (Travaux)" },
"desclong": {
"fr": "correspond au champ diag_03 dans le modèle de rapport Ordre de mission"
},
"color": "#865694"
},
{
"uuid": "AMDEMOL",
"desc": { "fr": "Amiante (Démol)" },
"desclong": {
"fr": "correspond au champ diag_04 dans le modèle de rapport Ordre de mission"
},
"color": "#086446"
},
{
"uuid": "DIAGTERM",
"desc": { "fr": "Diag.Termites" },
"desclong": {
"fr": "correspond au champ diag_05 dans le modèle de rapport Ordre de mission"
},
"color": "#036034"
},
{
"uuid": "DIAGPARA",
"desc": { "fr": "Diag.Parasites" },
"desclong": {
"fr": "correspond au champ diag_06 dans le modèle de rapport Ordre de mission"
},
"color": "#116801"
},
{
"uuid": "CARREZ",
"desc": { "fr": "Mesurage (Carrez)" },
"desclong": {
"fr": "correspond au champ diag_07 dans le modèle de rapport Ordre de mission"
},
"color": "#341770"
},
{
"uuid": "CREP",
"desc": { "fr": "Constat des risques d'exposition au plomb (CREP)" },
"desclong": {
"fr": "correspond au champ diag_08 dans le modèle de rapport Ordre de mission"
},
"color": "#303924"
},
{
"uuid": "ASSAINISS",
"desc": { "fr": "Assainissement" },
"desclong": {
"fr": "correspond au champ diag_09 dans le modèle de rapport Ordre de mission"
},
"color": "#279573"
},
{
"uuid": "PISCINE",
"desc": { "fr": "Piscine" },
"desclong": {
"fr": "correspond au champ diag_10 dans le modèle de rapport Ordre de mission"
},
"color": "#450176"
},
{
"uuid": "GAZ",
"desc": { "fr": "Gaz" },
"desclong": {
"fr": "correspond au champ diag_11 dans le modèle de rapport Ordre de mission"
},
"color": "#763822"
},
{
"uuid": "ELEC",
"desc": { "fr": "Electricité" },
"desclong": {
"fr": "correspond au champ diag_12 dans le modèle de rapport Ordre de mission"
},
"color": "#873048"
},
{
"uuid": "SRU",
"desc": { "fr": "D.Technique SRU" },
"desclong": {
"fr": "correspond au champ diag_13 dans le modèle de rapport Ordre de mission"
},
"color": "#279870"
},
{
"uuid": "DPE",
"desc": { "fr": "DPE" },
"desclong": {
"fr": "correspond au champ diag_14 dans le modèle de rapport Ordre de mission"
},
"color": "#862302"
},
{
"uuid": "TX0",
"desc": { "fr": "Prêt à taux zéro" },
"desclong": {
"fr": "correspond au champ diag_15 dans le modèle de rapport Ordre de mission"
},
"color": "#279050"
},
{
"uuid": "ERNT",
"desc": { "fr": "ERNT" },
"desclong": {
"fr": "correspond au champ diag_16 dans le modèle de rapport Ordre de mission"
},
"color": "#870447"
},
{
"uuid": "ROBIEN",
"desc": { "fr": "Robien" },
"desclong": {
"fr": "correspond au champ diag_17 dans le modèle de rapport Ordre de mission"
},
"color": "#238577"
},
{
"uuid": "ETATLIEU",
"desc": { "fr": "Etat des lieux" },
"desclong": {
"fr": "correspond au champ diag_18 dans le modèle de rapport Ordre de mission"
},
"color": "#159488"
},
{
"uuid": "DIAGPBEAU",
"desc": { "fr": "Diag. plomb dans l'eau" },
"desclong": {
"fr": "correspond au champ diag_19 dans le modèle de rapport Ordre de mission"
},
"color": "#210836"
},
{
"uuid": "ASCENSE",
"desc": { "fr": "Ascenseur" },
"desclong": {
"fr": "correspond au champ diag_20 dans le modèle de rapport Ordre de mission"
},
"color": "#702852"
},
{
"uuid": "RADON",
"desc": { "fr": "Radon" },
"desclong": {
"fr": "correspond au champ diag_21 dans le modèle de rapport Ordre de mission"
},
"color": "#851497"
},
{
"uuid": "INCENDIE",
"desc": { "fr": "Incendie" },
"desclong": {
"fr": "correspond au champ diag_22 dans le modèle de rapport Ordre de mission"
},
"color": "#329688"
},
{
"uuid": "HANDICAP",
"desc": { "fr": "Handicapé" },
"desclong": {
"fr": "correspond au champ diag_23 dans le modèle de rapport Ordre de mission"
},
"color": "#752339"
},
{
"uuid": "BOUTIN",
"desc": { "fr": "Mesurage (Boutin)" },
"desclong": {
"fr": "correspond au champ diag_24 dans le modèle de rapport Ordre de mission"
},
"color": "#645556"
},
{
"uuid": "AMDAPP",
"desc": { "fr": "Amiante DAPP" },
"desclong": {
"fr": "correspond au champ diag_25 dans le modèle de rapport Ordre de mission"
},
"color": "#594067"
},
{
"uuid": "DRIPP",
"desc": { "fr": "DRIPP" },
"desclong": {
"fr": "correspond au champ diag_26 dans le modèle de rapport Ordre de mission"
},
"color": "#135691"
},
{
"uuid": "DPN",
"desc": { "fr": "DPN Performance numérique" },
"desclong": {
"fr": "correspond au champ diag_27 dans le modèle de rapport Ordre de mission"
},
"color": "#659536"
},
{
"uuid": "INFILTRO",
"desc": { "fr": "Infiltrométrie" },
"desclong": {
"fr": "correspond au champ diag_28 dans le modèle de rapport Ordre de mission"
},
"color": "#931708"
},
{
"uuid": "AMAPTVX",
"desc": { "fr": "Amiante Examun Visuel APTVX" },
"desclong": {
"fr": "correspond au champ diag_29 dans le modèle de rapport Ordre de mission"
},
"color": "#165812"
},
{
"uuid": "DECHET",
"desc": { "fr": "Dechet" },
"desclong": {
"fr": "correspond au champ diag_30 dans le modèle de rapport Ordre de mission"
},
"color": "#682853"
},
{
"uuid": "PBAPTVX",
"desc": { "fr": "Plomb APTVX" },
"desclong": {
"fr": "correspond au champ diag_31 dans le modèle de rapport Ordre de mission"
},
"color": "#051199"
},
{
"uuid": "AMCTRLPERIO",
"desc": { "fr": "Amiante Contrôl périodique" },
"desclong": {
"fr": "correspond au champ diag_32 dans le modèle de rapport Ordre de mission"
},
"color": "#726364"
},
{
"uuid": "AMEMPOUSS",
"desc": { "fr": "Amiante Empoussièrement" },
"desclong": {
"fr": "correspond au champ diag_33 dans le modèle de rapport Ordre de mission"
},
"color": "#053460"
},
{
"uuid": "DEVINTERNE",
"desc": { "fr": "Module developpement Interne" },
"desclong": {
"fr": "correspond au champ diag_34 dans le modèle de rapport Ordre de mission"
},
"color": "#687134"
},
{
"uuid": "HOMEINSPECT",
"desc": { "fr": "Home Inspection" },
"desclong": {
"fr": "correspond au champ diag_35 dans le modèle de rapport Ordre de mission"
},
"color": "#574776"
},
{
"uuid": "4PTINSPECTION",
"desc": { "fr": "4PT Inspection" },
"desclong": {
"fr": "correspond au champ diag_36 dans le modèle de rapport Ordre de mission"
},
"color": "#729909"
},
{
"uuid": "WINDMITIG",
"desc": { "fr": "Wind Mitigation Inspection" },
"desclong": {
"fr": "correspond au champ diag_37 dans le modèle de rapport Ordre de mission"
},
"color": "#646189"
},
{
"uuid": "PBAVTVX",
"desc": { "fr": "Plomb Av Tvx" },
"desclong": {
"fr": "correspond au champ diag_37 DIAG_38 dans le modèle de rapport Ordre de mission"
},
"color": "#971402"
},
{
"uuid": "HAP",
"desc": { "fr": "HAP" },
"desclong": {
"fr": "correspond au champ DIAG_39 dans le modèle de rapport Ordre de mission"
},
"color": "#577844"
}
]

View File

@ -0,0 +1,46 @@
[
{
"uuid": "USER",
"desc": {
"fr": "USER - Utilisateur interne",
"en": "USER - User profil"
},
"desclong": {
"fr": "Utilisateur interne à l'organisation",
"en": "User profil"
}
},
{
"uuid": "CLIENT",
"desc": {
"fr": "CLIENT - Utilisateur externe",
"en": "CLIENT - User profil"
},
"desclong": {
"fr": "Utilisateur externe type client",
"en": "User profil"
}
},
{
"uuid": "MANAGER",
"desc": {
"fr": "MANAGER - Gestionnaire de données",
"en": "MANAGER - Data Manager"
},
"desclong": {
"fr": "Gestionnaire de données avec des accès sensible",
"en": "Data Manager with sensible data access"
}
},
{
"uuid": "ADMIN",
"desc": {
"fr": "ADMIN - Admininstrateur",
"en": "ADMIN - Admin profil"
},
"desclong": {
"fr": "Admininstrateur avec accès complet aux comptes",
"en": "Admin profil with full access"
}
}
]

View File

@ -0,0 +1,17 @@
[
{
"uuid": "OTHER",
"desclong": { "fr": "Autre", "en": "Other" },
"desc": { "fr": "Aut.", "en": "Oth." }
},
{
"uuid": "DIAG",
"desclong": { "fr": "Diagnostiqueur", "en": "Diag" },
"desc": { "fr": "Diag", "en": "DIAG" }
},
{
"uuid": "GEST",
"desclong": { "fr": "Gestion", "en": "Management" },
"desc": { "fr": "Gest", "en": "Manag" }
}
]

View File

@ -0,0 +1,129 @@
{
"commentaire": "Parametrage permettant de mettre à jour le cataloguecatBoutiquefixe.json",
"nomcatalogue": "",
"objet": "companies",
"optioncatalogcsv": {
"retln": "\n",
"sep": ";",
"seplevel": "__",
"replacespecialcarCsv2Json": "[[/Semicolon/g,';']]",
"replacespecialcarJson2Csv": "[[/;/g, 'Semicolon']]"
},
"removeforPublic": "",
"templates": {
"templateidintro": "",
"menu": "",
"header": "",
"section": "",
"filtre": "bdcfiltercateg",
"card": "bdccard"
},
"vendeur": {
"nom": "Commune dIgny",
"siret": "333 951 978 00050",
"responsable": "Marie Faujas (Directrice de la Communication, Culture et Evènementiel)",
"adressesociale": "23, avenue de la division Leclerc",
"CP": "91430",
"ville": "IGNY",
"logo": "logo.png",
"email": "mairie@igny.fr",
"tel": "01 69 33 11 19",
"telinter": "33169331119",
"cartegmap": "",
"reseauxociaux": [],
"horaire": "",
"horairedata": "",
"IBAN": "",
"tribunalRCS": "",
"ScanKbis": "",
"RIBSociete": "",
"ScanIdcardResp1": "",
"JustifDomicileResp1": "",
"ScanIdcardResp2": "",
"JustifDomicileResp2": ""
},
"hebergeur": {
"nom": "SAS Need-Data",
"adresse": "6 Avenue des Andes",
"CP": "91940",
"Ville": "Les Ulis",
"site": "https://need-data.com"
},
"page": {
"objettype": "page",
"nom": "",
"urlsite": "https://shop.igny.fr",
"couleurprimaire": "",
"couleursecondaire": "",
"description": "",
"metaauthor": "",
"keywords": "",
"ogtitle": "",
"ogvignette": "",
"title": "",
"ordre": []
},
"header": {
"objettype": "html",
"photopleinepage": "",
"TITREpage": "",
"SSTITREpage": "",
"inscriptionemailbouton": "S'inscrire",
"TEXTpage": ""
},
"edito": {
"objettype": "html",
"tmpl": "sectionmaildigit",
"contenthtml": "static/data/staticContentwebsite/edito.html"
},
"referencer": {
"objettype": "html",
"tmpl": "sectionmaildigit",
"contenthtml": "static/data/staticContentwebsite/referencer.html"
},
"footer": {
"objettype": "html",
"tmpl": "sectionmaildigit",
"classsection": "footer text-center bg-primary text-white",
"contenthtml": "static/data/staticContentwebsite/footer.html"
},
"catalogue": {
"objettype": "catalogueCards",
"tmpl": "sectionmaildigit",
"tpl": "",
"titre": "",
"sstitre": "",
"text": "",
"youtube": "",
"youtubesmallscreen": "",
"youtubebtn": "",
"filtrecatalog": {
"tpl": "bdcfiltercateg",
"btnlist": true,
"seleclist": false,
"classbtn": "btn-outline-primary",
"modalbutton": "video pour passer commande",
"modalcontent": "",
"modalcontentsmallscreen": "",
"classcolfiltre": "text-left",
"titrefiltre": "",
"message": " <p><b>La livraison est offerte à partir de <s>6 cartons</s> 2 cartons (pendant la période de confinement)</b></p>",
"categorie": "Nos Gammes",
"btncategorie": "btn-outline-primary",
"item": []
},
"cards": {
"tpl": "bdccard",
"devise": "€",
"buttonajoutpanier": true,
"showAvailibility": false,
"masqueitemifnomoreavailable": true,
"buttonBookwithemail": false,
"buttonBookaddForm": true,
"cardClass": "col-12",
"btndesc": "Ajouter cette réservation",
"cardbtn": "btn-primary",
"objet": {}
}
}
}

View File

@ -0,0 +1,18 @@
{
"commentaire": "Spécification permettant d'importer et d'exporter en csv des companies, pour documentation voir gitlab",
"object": "companies",
"cardscsv": "compagniesIGNY.csv",
"ASUPPcatalogPerso": "catalogueCommerceIGNY.csv",
"optioncardscsv": {
"retln": "\n",
"sep": ";",
"champs": ["UUID", "TYPE", "STATUT", "CATEGORIE", "TITRE", "SSTITRE", "KEYWORD", "MOTHTMLDUMAGASIN", "MOTTXTDUMAGASIN", "ADRESSE_PRO", "CP_PRO", "VILLE_PRO", "PHONE_PRO", "HORAIRESDESC", "HORAIREDATA", "URL", "FACEBOOK", "INSTA", "YOUTUBE", "LINKEDIN", "EMAIL_PRO", "IMG_VIGNETTE", "DATEOUVERTURE", "DATEFERMETURE", "HTML", "COMMENTPOSTTRT", "DATE_CREATE", "DATE_UPDATE"],
"array": ["CATEGORIE", "PHONE_PRO", "EMAIL_PRO"],
"arraysplitsep": ",",
"numericfield": [],
"search": "(fiche.UUID && fiche.UUID == data.UUID) || (fiche.EMAIL_PRO && utils.testinarray(fiche.EMAIL_PRO,data.EMAIL_PRO)) || (fiche.PHONE_PRO && utils.testinarray(fiche.PHONE_PRO,data.PHONE_PRO))",
"merge": [],
"replacespecialcarCsv2Json": "[[/Semicolon/g,';']]",
"replacespecialcarJson2Csv": "[[/;/g, 'Semicolon']]"
}
}

View File

@ -0,0 +1,134 @@
{
"fr": {
"menuleft": {
"sbbrandlink": "app.html",
"sbtitle": "YES",
"sbgroupmenu": [{
"groupheader": "Suivi",
"sbssgroupmenu": [{
"name": "Reporting",
"icon": "sliders",
"actionclick": "pwa.reporting.init()"
}, {
"name": "Test sous niveau",
"icon": "sliders",
"actionclick": "pwa.reporting.init()",
"iditemmenus": "suivitest",
"itemmenus": [{
"name": "sousmenu",
"actionclick": "pwa.reporting.init()"
}]
}]
}, {
"groupheader": "Admin",
"sbssgroupmenu": [{
"name": "Pagans",
"icon": "sliders",
"actionclick": "pwa.users.init()"
}, {
"name": "Referentiels",
"icon": "sliders",
"iditemmenus": "adminreferentiel",
"itemmenus": [{
"name": "Offre",
"actionclick": "pwa.referential.setting('offre')"
}, {
"name": "return action",
"actionclick": "pwa.referential.setting('returnaction')"
}, {
"name": "data",
"actionclick": "pwa.referential.setting('data')"
}, {
"name": "json",
"actionclick": "pwa.referential.setting('json')"
}, {
"name": "Objects",
"actionclick": "pwa.referential.setting('object')"
}]
}]
}, {
"groupheader": "Teacher",
"sbssgroupmenu": [{
"name": "Evaluation",
"icon": "sliders",
"actionclick": "pwa.evaluation.init()"
}, {
"name": "ScheduleOnce",
"icon": "sliders",
"actionclick": "pwa.scheduleonce.init()"
}]
}, {
"groupheader": "Operation",
"sbssgroupmenu": [{
"name": "Action Learner",
"icon": "sliders",
"actionclick": "pwa.actionlearner.init()"
}, {
"name": "Action teacher",
"icon": "sliders",
"actionclick": "pwa.actionteacher.init()"
}]
}, {
"groupheader": "Marketing",
"sbssgroupmenu": [{
"name": "Gestion l'offre",
"icon": "sliders",
"actionclick": "pwa.offers.init()"
}, {
"name": "Action teacher",
"icon": "sliders",
"actionclick": "pwa.actionteacher.init()"
}]
}, {
"groupheader": "Learner",
"sbssgroupmenu": [{
"name": "Mes evaluations",
"icon": "sliders",
"actionclick": "pwa.learner.init()"
},
{
"name": "Actions learner",
"icon": "sliders",
"actionclick": "pwa.learner.action()"
}
]
}]
},
"menutop": {
"withsearch": true,
"searchtxt": "Recherche...",
"withnotification": true,
"notificationheader": "Vos notifications",
"notificationfooter": "Voir toutes les notifications",
"href": "?action=notification.view",
"withmessage": false,
"messageheader": "Vos messages non lus",
"messagefooter": "Voir tous les messages",
"avatarimg": "",
"name": "",
"menuprofil": [{
"icon": "user",
"desc": "Profile",
"href": "?action=user.settings",
"menubreaker": false
},
{
"icon": "pie-chart",
"desc": "Activity",
"href": "?action=user.activity",
"menubreaker": true
},
{
"icon": "settings",
"desc": "Log out",
"href": "?action=userauth.logout",
"menubreaker": false
}
]
}
},
"en": {
"menuleft": {},
"menutop": {}
}
}

View File

@ -0,0 +1,134 @@
{
"fr": {
"menuleft": {
"sbbrandlink": "app.html",
"sbtitle": "YES",
"sbgroupmenu": [{
"groupheader": "Suivi",
"sbssgroupmenu": [{
"name": "Reporting",
"icon": "sliders",
"actionclick": "pwa.reporting.init()"
}, {
"name": "Test sous niveau",
"icon": "sliders",
"actionclick": "pwa.reporting.init()",
"iditemmenus": "suivitest",
"itemmenus": [{
"name": "sousmenu",
"actionclick": "pwa.reporting.init()"
}]
}]
}, {
"groupheader": "Admin",
"sbssgroupmenu": [{
"name": "Pagans",
"icon": "sliders",
"actionclick": "pwa.users.init()"
}, {
"name": "Referentiels",
"icon": "sliders",
"iditemmenus": "adminreferentiel",
"itemmenus": [{
"name": "Offre",
"actionclick": "pwa.referential.setting('offre')"
}, {
"name": "return action",
"actionclick": "pwa.referential.setting('returnaction')"
}, {
"name": "data",
"actionclick": "pwa.referential.setting('data')"
}, {
"name": "json",
"actionclick": "pwa.referential.setting('json')"
}, {
"name": "Objects",
"actionclick": "pwa.referential.setting('object')"
}]
}]
}, {
"groupheader": "Teacher",
"sbssgroupmenu": [{
"name": "Evaluation",
"icon": "sliders",
"actionclick": "pwa.evaluation.init()"
}, {
"name": "ScheduleOnce",
"icon": "sliders",
"actionclick": "pwa.scheduleonce.init()"
}]
}, {
"groupheader": "Operation",
"sbssgroupmenu": [{
"name": "Action Learner",
"icon": "sliders",
"actionclick": "pwa.actionlearner.init()"
}, {
"name": "Action teacher",
"icon": "sliders",
"actionclick": "pwa.actionteacher.init()"
}]
}, {
"groupheader": "Marketing",
"sbssgroupmenu": [{
"name": "Gestion l'offre",
"icon": "sliders",
"actionclick": "pwa.offers.init()"
}, {
"name": "Action teacher",
"icon": "sliders",
"actionclick": "pwa.actionteacher.init()"
}]
}, {
"groupheader": "Learner",
"sbssgroupmenu": [{
"name": "Mes evaluations",
"icon": "sliders",
"actionclick": "pwa.learner.init()"
},
{
"name": "Actions learner",
"icon": "sliders",
"actionclick": "pwa.learner.action()"
}
]
}]
},
"menutop": {
"withsearch": true,
"searchtxt": "Recherche...",
"withnotification": true,
"notificationheader": "Vos notifications",
"notificationfooter": "Voir toutes les notifications",
"href": "?action=notification.view",
"withmessage": false,
"messageheader": "Vos messages non lus",
"messagefooter": "Voir tous les messages",
"avatarimg": "",
"name": "",
"menuprofil": [{
"icon": "user",
"desc": "Profile",
"href": "?action=user.settings",
"menubreaker": false
},
{
"icon": "pie-chart",
"desc": "Activity",
"href": "?action=user.activity",
"menubreaker": true
},
{
"icon": "settings",
"desc": "Log out",
"href": "?action=userauth.logout",
"menubreaker": false
}
]
}
},
"en": {
"menuleft": {},
"menutop": {}
}
}

Some files were not shown because too many files have changed in this diff Show More