Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6367a1fd15 | ||
|
|
2c5ff64d42 | ||
|
|
e614cb41d8 | ||
|
|
82737e2280 | ||
|
|
3b5c27096b | ||
|
|
8a85830bd3 | ||
|
|
19ac90688e | ||
|
|
5f4a6bc462 | ||
|
|
4c7f3166d4 | ||
|
|
4233f4dd88 | ||
|
|
bd09312418 | ||
|
|
11ab07b238 | ||
|
|
d2e2f089fb | ||
|
|
d619f98de7 | ||
|
|
89228a52dc | ||
|
|
90a49efa4a | ||
|
|
a7423caeb1 | ||
|
|
6f31a2c228 | ||
|
|
fc98b0882f | ||
|
|
dff05967ea | ||
|
|
ec836a2f9d | ||
|
|
737c85cf00 | ||
|
|
ff7b07f464 | ||
|
|
19422b5afa | ||
|
|
6b93e413b6 | ||
|
|
ab72736bcc | ||
|
|
38939e5d05 | ||
|
|
1a90a0e0ad | ||
|
|
7aa9fa4646 | ||
|
|
82d788c8d3 | ||
|
|
ab8176efcb | ||
|
|
b4c3e7a928 | ||
|
|
2db263fb68 | ||
|
|
5fd4d37972 | ||
|
|
9b1dbf351a | ||
|
|
4c7c058e0d | ||
|
|
f8dc08b02b | ||
|
|
0e6bc3ce54 | ||
|
|
fcb018406d | ||
|
|
40756a5e4b | ||
|
|
61b12bdebd | ||
|
|
8b1c41ae1e | ||
|
|
d654564f6b | ||
|
|
8bd22ebafa | ||
|
|
647e66d57b | ||
|
|
67c8ac1aa3 | ||
|
|
f336c7ae53 | ||
|
|
9ea1dad62d | ||
|
|
58bc374e64 | ||
|
|
cea0d4887d | ||
|
|
d5e73ecd8b | ||
|
|
2235a0498b | ||
|
|
3f9b92831c | ||
|
|
5680750c82 | ||
|
|
298e3efe49 | ||
|
|
7b2bd6f9eb | ||
|
|
d935e07990 | ||
|
|
868a74c184 | ||
|
|
a86af9736b | ||
|
|
8bd4d27d2f | ||
|
|
94951c59f3 | ||
|
|
e39c83a5a6 |
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [jendib]
|
||||
@@ -4,7 +4,7 @@ language: java
|
||||
before_install:
|
||||
- sudo add-apt-repository -y ppa:mc3man/trusty-media
|
||||
- sudo apt-get -qq update
|
||||
- sudo apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld
|
||||
- sudo apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur tesseract-ocr-heb tesseract-ocr-hun
|
||||
- sudo apt-get -y -q install haveged && sudo service haveged start
|
||||
after_success:
|
||||
- |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM sismics/ubuntu-jetty:9.4.12
|
||||
MAINTAINER b.gamard@sismics.com
|
||||
|
||||
RUN apt-get update && apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld && \
|
||||
RUN apt-get update && apt-get -y -q install ffmpeg mediainfo tesseract-ocr tesseract-ocr-fra tesseract-ocr-ita tesseract-ocr-kor tesseract-ocr-rus tesseract-ocr-ukr tesseract-ocr-spa tesseract-ocr-ara tesseract-ocr-hin tesseract-ocr-deu tesseract-ocr-pol tesseract-ocr-jpn tesseract-ocr-por tesseract-ocr-tha tesseract-ocr-jpn tesseract-ocr-chi-sim tesseract-ocr-chi-tra tesseract-ocr-nld tesseract-ocr-tur tesseract-ocr-heb tesseract-ocr-hun && \
|
||||
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Remove the embedded javax.mail jar from Jetty
|
||||
|
||||
49
README.md
@@ -1,27 +1,25 @@
|
||||
<h3 align="center">
|
||||
<img src="https://www.sismicsdocs.com/img/github-title.png" alt="Sismics Docs" width=500 />
|
||||
<img src="https://teedy.io/img/github-title.png" alt="Teedy" width=500 />
|
||||
</h3>
|
||||
|
||||
[](https://twitter.com/sismicsdocs)
|
||||
[](https://twitter.com/teedyio)
|
||||
[](https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html)
|
||||
[](http://travis-ci.org/sismics/docs)
|
||||
|
||||
Docs is an open source, lightweight document management system for individuals and businesses.
|
||||
|
||||
**Discuss it on [Product Hunt](https://www.producthunt.com/posts/sismics-docs) 🦄**
|
||||
Teedy is an open source, lightweight document management system for individuals and businesses.
|
||||
|
||||
<hr />
|
||||
<h2 align="center">
|
||||
✨ We just launched a Cloud version of Sismics Docs! Head to <a href="https://www.sismicsdocs.com/">sismicsdocs.com</a> for more informations ✨
|
||||
✨ <a href="https://github.com/users/jendib/sponsorship">Sponsor this project if you use and appreciate it!</a> ✨
|
||||
</h2>
|
||||
<hr />
|
||||
|
||||

|
||||

|
||||
|
||||
Demo
|
||||
----
|
||||
|
||||
A demo is available at [demo.sismicsdocs.com](https://demo.sismicsdocs.com)
|
||||
A demo is available at [demo.teedy.io](https://demo.teedy.io)
|
||||
- Guest login is enabled with read access on all documents
|
||||
- "admin" login with "admin" password
|
||||
- "demo" login with "password" password
|
||||
@@ -36,6 +34,7 @@ Features
|
||||
- Flexible search engine with suggestions and highlighting
|
||||
- Full text search in all supported files
|
||||
- All [Dublin Core](http://dublincore.org/) metadata
|
||||
- Custom user-defined metadata 
|
||||
- Workflow system 
|
||||
- 256-bit AES encryption of stored files
|
||||
- File versioning 
|
||||
@@ -58,14 +57,15 @@ Features
|
||||
Install with Docker
|
||||
-------------------
|
||||
|
||||
From a Docker host, run this command to download and install Sismics Docs. The server will run on <http://[your-docker-host-ip]:8100>.
|
||||
**The default admin password is "admin". Don't forget to change it before going to production.**
|
||||
A preconfigured Docker image is available, including OCR and media conversion tools, listening on port 8080. The database is an embedded H2 database but PostgreSQL is also supported for more performance.
|
||||
|
||||
docker run --rm --name sismics_docs_latest -d -e DOCS_BASE_URL='http://[your-docker-host-ip]:8100' -p 8100:8080 -v sismics_docs_latest:/data sismics/docs:latest
|
||||
<img src="http://www.newdesignfile.com/postpic/2011/01/green-info-icon_206509.png" width="16px" height="16px"> **Note:** You will need to change [your-docker-host-ip] with the IP address or FQDN of your docker host e.g.
|
||||
|
||||
FQDN: http://docs.sismics.com
|
||||
IP: http://192.168.100.10
|
||||
**The default admin password is "admin". Don't forget to change it before going to production.**
|
||||
- Master branch, can be unstable. Not recommended for production use: `sismics/docs:latest`
|
||||
- Latest stable version: `sismics/docs:v1.7`
|
||||
|
||||
The data directory is `/data`. Don't forget to mount a volume on it.
|
||||
|
||||
To build external URL, the server is expecting a `DOCS_BASE_URL` environment variable (for example https://teedy.mycompany.com)
|
||||
|
||||
Manual installation
|
||||
-------------------
|
||||
@@ -81,12 +81,12 @@ Manual installation
|
||||
The latest release is downloadable here: <https://github.com/sismics/docs/releases> in WAR format.
|
||||
**The default admin password is "admin". Don't forget to change it before going to production.**
|
||||
|
||||
How to build Docs from the sources
|
||||
How to build Teedy from the sources
|
||||
----------------------------------
|
||||
|
||||
Prerequisites: JDK 8 with JCE, Maven 3, Tesseract 3 or 4
|
||||
Prerequisites: JDK 8 with JCE, Maven 3, NPM, Grunt, Tesseract 3 or 4
|
||||
|
||||
Docs is organized in several Maven modules:
|
||||
Teedy is organized in several Maven modules:
|
||||
|
||||
- docs-core
|
||||
- docs-web
|
||||
@@ -122,19 +122,8 @@ All contributions are more than welcomed. Contributions may close an issue, fix
|
||||
|
||||
The `master` branch is the default and base branch for the project. It is used for development and all Pull Requests should go there.
|
||||
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
Get updates on Sismics Docs' development and chat with the project maintainers:
|
||||
|
||||
- Follow [@sismicsdocs on Twitter](https://twitter.com/sismicsdocs)
|
||||
- Read and subscribe to [The Official Sismics Docs Blog](https://blog.sismicsdocs.com/)
|
||||
- Check the [Official Website](https://www.sismicsdocs.com)
|
||||
- Join us [on Facebook](https://www.facebook.com/sismicsdocs)
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Docs is released under the terms of the GPL license. See `COPYING` for more
|
||||
Teedy is released under the terms of the GPL license. See `COPYING` for more
|
||||
information or see <http://opensource.org/licenses/GPL-2.0>.
|
||||
|
||||
@@ -4,7 +4,7 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.3.0'
|
||||
classpath 'com.android.tools.build:gradle:3.4.0'
|
||||
}
|
||||
}
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme" >
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".activity.LoginActivity"
|
||||
android:label="@string/app_name"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.sismics.docs;
|
||||
|
||||
import android.app.Application;
|
||||
import android.support.v7.app.AppCompatDelegate;
|
||||
|
||||
import com.sismics.docs.model.application.ApplicationContext;
|
||||
import com.sismics.docs.util.PreferenceUtil;
|
||||
@@ -22,5 +23,7 @@ public class MainApplication extends Application {
|
||||
// TODO Provide documents to intent action get content
|
||||
|
||||
super.onCreate();
|
||||
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,14 +63,13 @@ public class DocListFragment extends Fragment {
|
||||
recyclerView.setAdapter(adapter);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setLongClickable(true);
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(getResources().getDrawable(R.drawable.abc_list_divider_mtrl_alpha)));
|
||||
|
||||
// Configure the LayoutManager
|
||||
final LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
|
||||
// Configure the swipe refresh layout
|
||||
swipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipeRefreshLayout);
|
||||
swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout);
|
||||
swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_bright,
|
||||
android.R.color.holo_green_light,
|
||||
android.R.color.holo_orange_light,
|
||||
@@ -194,7 +193,7 @@ public class DocListFragment extends Fragment {
|
||||
private void loadDocuments(final View view, final boolean reset) {
|
||||
if (view == null) return;
|
||||
final View progressBar = view.findViewById(R.id.progressBar);
|
||||
final TextView documentsEmptyView = (TextView) view.findViewById(R.id.documentsEmptyView);
|
||||
final TextView documentsEmptyView = view.findViewById(R.id.documentsEmptyView);
|
||||
|
||||
if (reset) {
|
||||
loading = true;
|
||||
|
||||
@@ -156,7 +156,7 @@ public class OkHttpUtil {
|
||||
public static OkHttpClient buildClient(final Context context) {
|
||||
// One-time header computation
|
||||
if (userAgent == null) {
|
||||
userAgent = "Sismics Docs Android " + ApplicationUtil.getVersionName(context) + "/Android " + Build.VERSION.RELEASE + "/" + Build.MODEL;
|
||||
userAgent = "Teedy Android " + ApplicationUtil.getVersionName(context) + "/Android " + Build.VERSION.RELEASE + "/" + Build.MODEL;
|
||||
}
|
||||
|
||||
if (acceptLanguage == null) {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:textColor="#212121"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:text="Test"
|
||||
android:textSize="16sp"
|
||||
android:ellipsize="end"
|
||||
@@ -46,7 +46,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:textColor="#777777"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:text="test2"
|
||||
android:textSize="16sp"
|
||||
android:maxLines="1"
|
||||
@@ -69,7 +69,7 @@
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:textColor="#777777"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:fontFamily="sans-serif-light"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -9,23 +9,22 @@
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/overview_coordinator_layout"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||
app:layout_scrollFlags="enterAlways|scroll|snap" />
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:popupTheme="@style/AppTheme"
|
||||
app:layout_scrollFlags="enterAlways|scroll|snap" />
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/main_fragment"
|
||||
|
||||
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.7 KiB |
@@ -11,7 +11,7 @@
|
||||
<!-- App -->
|
||||
<string name="drawer_open">Navigationsleiste öffnen</string>
|
||||
<string name="drawer_close">Navigationsleiste schließen</string>
|
||||
<string name="login_explain"><![CDATA[Um zu beginnen, müssen Sie Sismics Docs Server herunterladen und installieren <a href="https://github.com/sismics/docs">github.com/sismics/docs</a>, sowie die Login-Daten unten eingeben]]></string>
|
||||
<string name="login_explain"><![CDATA[Um zu beginnen, müssen Sie Teedy Server herunterladen und installieren <a href="https://github.com/sismics/docs">github.com/sismics/docs</a>, sowie die Login-Daten unten eingeben]]></string>
|
||||
<string name="server">Server</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="password">Password</string>
|
||||
@@ -83,7 +83,7 @@
|
||||
<string name="file_delete_failure">Netzwerkfehler beim Löschen der Datei</string>
|
||||
<string name="file_deleting_message">Lösche Datei</string>
|
||||
<string name="error_reading_file">Fehler beim Lesen der Datei</string>
|
||||
<string name="upload_notification_title">Sismics Docs</string>
|
||||
<string name="upload_notification_title">Teedy</string>
|
||||
<string name="upload_notification_message">Neue Datei in das Dokument hochladen</string>
|
||||
<string name="upload_notification_error">Fehler beim Hochladen der neuen Datei</string>
|
||||
<string name="delete_file">Aktuelle Datei löschen</string>
|
||||
@@ -119,9 +119,9 @@
|
||||
<string name="export_comments">Kommentare exportieren</string>
|
||||
<string name="export_metadata">Metadaten exportieren</string>
|
||||
<string name="mm">mm</string>
|
||||
<string name="download_file_title">Sismics Docs Datei Export</string>
|
||||
<string name="download_document_title">Sismics Docs Dokumentenexport</string>
|
||||
<string name="download_pdf_title">Sismics Docs PDF Export</string>
|
||||
<string name="download_file_title">Teedy Datei Export</string>
|
||||
<string name="download_document_title">Teedy Dokumentenexport</string>
|
||||
<string name="download_pdf_title">Teedy PDF Export</string>
|
||||
<string name="latest_activity">Letzte Aktivität</string>
|
||||
<string name="activity">Aktivitäten</string>
|
||||
<string name="email">E-Mail</string>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<!-- App -->
|
||||
<string name="drawer_open">Ouvrir le menu de navigation</string>
|
||||
<string name="drawer_close">Fermer le menu de navigation</string>
|
||||
<string name="login_explain"><![CDATA[Pour commencer, vous devez télécharger et installer le serveur Sismics Docs sur <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> et entrer son URL ci-dessous]]></string>
|
||||
<string name="login_explain"><![CDATA[Pour commencer, vous devez télécharger et installer le serveur Teedy sur <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> et entrer son URL ci-dessous]]></string>
|
||||
<string name="server">Serveur</string>
|
||||
<string name="username">Nom d\'utilisateur</string>
|
||||
<string name="password">Mot de passe</string>
|
||||
@@ -83,7 +83,7 @@
|
||||
<string name="file_delete_failure">Erreur réseau lors de la suppression du fichier</string>
|
||||
<string name="file_deleting_message">Suppression du fichier</string>
|
||||
<string name="error_reading_file">Erreur lors de la lecture du fichier</string>
|
||||
<string name="upload_notification_title">Sismics Docs</string>
|
||||
<string name="upload_notification_title">Teedy</string>
|
||||
<string name="upload_notification_message">Envoi du nouveau fichier</string>
|
||||
<string name="upload_notification_error">Erreur lors de l\'envoi du nouveau fichier</string>
|
||||
<string name="delete_file">Supprimer ce fichier</string>
|
||||
@@ -119,9 +119,9 @@
|
||||
<string name="export_comments">Exporter les commentaires</string>
|
||||
<string name="export_metadata">Exporter les métadonnées</string>
|
||||
<string name="mm">mm</string>
|
||||
<string name="download_file_title">Export de fichier Sismics Docs</string>
|
||||
<string name="download_document_title">Export de document Sismics Docs</string>
|
||||
<string name="download_pdf_title">Export PDF Sismics Docs</string>
|
||||
<string name="download_file_title">Export de fichier Teedy</string>
|
||||
<string name="download_document_title">Export de document Teedy</string>
|
||||
<string name="download_pdf_title">Export PDF Teedy</string>
|
||||
<string name="latest_activity">Activité récente</string>
|
||||
<string name="activity">Activité</string>
|
||||
<string name="email">E-mail</string>
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
<string name="validate_error_alphanumeric">Only letters and numbers</string>
|
||||
|
||||
<!-- App -->
|
||||
<string name="app_name" translatable="false">Sismics Docs</string>
|
||||
<string name="app_name" translatable="false">Teedy</string>
|
||||
<string name="drawer_open">Open navigation drawer</string>
|
||||
<string name="drawer_close">Close navigation drawer</string>
|
||||
<string name="login_explain"><![CDATA[To start, you must download and install Sismics Docs Server on <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> and enter its below]]></string>
|
||||
<string name="login_explain"><![CDATA[To start, you must download and install Teedy Server on <a href="https://github.com/sismics/docs">github.com/sismics/docs</a> and enter its below]]></string>
|
||||
<string name="server">Server</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="password">Password</string>
|
||||
@@ -87,7 +87,7 @@
|
||||
<string name="file_delete_failure">Network error while deleting the current file</string>
|
||||
<string name="file_deleting_message">Deleting file</string>
|
||||
<string name="error_reading_file">Error while reading the file</string>
|
||||
<string name="upload_notification_title">Sismics Docs</string>
|
||||
<string name="upload_notification_title">Teedy</string>
|
||||
<string name="upload_notification_message">Uploading the new file to the document</string>
|
||||
<string name="upload_notification_error">Error uploading the new file</string>
|
||||
<string name="delete_file">Delete current file</string>
|
||||
@@ -123,9 +123,9 @@
|
||||
<string name="export_comments">Export comments</string>
|
||||
<string name="export_metadata">Export metadata</string>
|
||||
<string name="mm">mm</string>
|
||||
<string name="download_file_title">Sismics Docs file export</string>
|
||||
<string name="download_document_title">Sismics Docs document export</string>
|
||||
<string name="download_pdf_title">Sismics Docs PDF export</string>
|
||||
<string name="download_file_title">Teedy file export</string>
|
||||
<string name="download_document_title">Teedy document export</string>
|
||||
<string name="download_pdf_title">Teedy PDF export</string>
|
||||
<string name="latest_activity">Latest activity</string>
|
||||
<string name="activity">Activity</string>
|
||||
<string name="email">E-mail</string>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<style name="AppTheme" parent="Theme.AppCompat.DayNight">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
@@ -14,7 +14,7 @@
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppThemeDark" parent="Theme.AppCompat.NoActionBar">
|
||||
<style name="AppThemeDark" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#Wed Jan 30 16:31:31 CET 2019
|
||||
#Tue May 07 11:49:13 CEST 2019
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-parent</artifactId>
|
||||
<version>1.6-SNAPSHOT</version>
|
||||
<version>1.8</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -189,7 +189,26 @@
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- JDK 11 JAXB dependencies -->
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
<artifactId>jaxb-core</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
<artifactId>jaxb-impl</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
||||
@@ -38,7 +38,7 @@ public class Constants {
|
||||
/**
|
||||
* Supported document languages.
|
||||
*/
|
||||
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor", "nld");
|
||||
public static final List<String> SUPPORTED_LANGUAGES = Lists.newArrayList("eng", "fra", "ita", "deu", "spa", "por", "pol", "rus", "ukr", "ara", "hin", "chi_sim", "chi_tra", "jpn", "tha", "kor", "nld", "tur", "heb", "hun");
|
||||
|
||||
/**
|
||||
* Base URL environment variable.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.sismics.docs.core.constant;
|
||||
|
||||
/**
|
||||
* Metadata type.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public enum MetadataType {
|
||||
STRING,
|
||||
INTEGER,
|
||||
FLOAT,
|
||||
DATE,
|
||||
BOOLEAN
|
||||
}
|
||||
@@ -61,6 +61,7 @@ public class AuditLogDao {
|
||||
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select f.FIL_ID_C from T_FILE f where f.FIL_IDDOC_C = :documentId) ");
|
||||
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select c.COM_ID_C from T_COMMENT c where c.COM_IDDOC_C = :documentId) ");
|
||||
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select a.ACL_ID_C from T_ACL a where a.ACL_SOURCEID_C = :documentId) ");
|
||||
queries.add(baseQuery + " where l.LOG_IDENTITY_C in (select r.RTE_ID_C from T_ROUTE r where r.RTE_IDDOCUMENT_C = :documentId) ");
|
||||
parameterMap.put("documentId", criteria.getDocumentId());
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ public class CommentDao {
|
||||
* @param comment Comment
|
||||
* @param userId User ID
|
||||
* @return New ID
|
||||
* @throws Exception
|
||||
*/
|
||||
public String create(Comment comment, String userId) {
|
||||
// Create the UUID
|
||||
@@ -99,7 +98,7 @@ public class CommentDao {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object[]> l = q.getResultList();
|
||||
|
||||
List<CommentDto> commentDtoList = new ArrayList<CommentDto>();
|
||||
List<CommentDto> commentDtoList = new ArrayList<>();
|
||||
for (Object[] o : l) {
|
||||
int i = 0;
|
||||
CommentDto commentDto = new CommentDto();
|
||||
@@ -107,7 +106,7 @@ public class CommentDao {
|
||||
commentDto.setContent((String) o[i++]);
|
||||
commentDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
|
||||
commentDto.setCreatorName((String) o[i++]);
|
||||
commentDto.setCreatorEmail((String) o[i++]);
|
||||
commentDto.setCreatorEmail((String) o[i]);
|
||||
commentDtoList.add(commentDto);
|
||||
}
|
||||
return commentDtoList;
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ContributorDao {
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<ContributorDto> getByDocumentId(String documentId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
StringBuilder sb = new StringBuilder("select u.USE_USERNAME_C, u.USE_EMAIL_C from T_CONTRIBUTOR c ");
|
||||
StringBuilder sb = new StringBuilder("select distinct u.USE_USERNAME_C, u.USE_EMAIL_C from T_CONTRIBUTOR c ");
|
||||
sb.append(" join T_USER u on u.USE_ID_C = c.CTR_IDUSER_C ");
|
||||
sb.append(" where c.CTR_IDDOC_C = :documentId ");
|
||||
Query q = em.createNativeQuery(sb.toString());
|
||||
|
||||
@@ -196,21 +196,6 @@ public class DocumentDao {
|
||||
* @return Updated document
|
||||
*/
|
||||
public Document update(Document document, String userId) {
|
||||
Document documentDb = updateSilently(document);
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(documentDb, AuditLogType.UPDATE, userId);
|
||||
|
||||
return documentDb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a document without audit log.
|
||||
*
|
||||
* @param document Document to update
|
||||
* @return Updated document
|
||||
*/
|
||||
public Document updateSilently(Document document) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
|
||||
// Get the document
|
||||
@@ -233,10 +218,28 @@ public class DocumentDao {
|
||||
documentDb.setLanguage(document.getLanguage());
|
||||
documentDb.setFileId(document.getFileId());
|
||||
documentDb.setUpdateDate(new Date());
|
||||
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(documentDb, AuditLogType.UPDATE, userId);
|
||||
|
||||
return documentDb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the file ID on a document.
|
||||
*
|
||||
* @param document Document
|
||||
*/
|
||||
public void updateFileId(Document document) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query query = em.createNativeQuery("update T_DOCUMENT d set DOC_IDFILE_C = :fileId, DOC_UPDATEDATE_D = :updateDate where d.DOC_ID_C = :id");
|
||||
query.setParameter("updateDate", new Date());
|
||||
query.setParameter("fileId", document.getFileId());
|
||||
query.setParameter("id", document.getId());
|
||||
query.executeUpdate();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of documents.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.sismics.docs.core.dao;
|
||||
|
||||
import com.sismics.docs.core.constant.MetadataType;
|
||||
import com.sismics.docs.core.dao.dto.DocumentMetadataDto;
|
||||
import com.sismics.docs.core.model.jpa.DocumentMetadata;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Document metadata DAO.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class DocumentMetadataDao {
|
||||
/**
|
||||
* Creates a new document metadata.
|
||||
*
|
||||
* @param documentMetadata Document metadata
|
||||
* @return New ID
|
||||
*/
|
||||
public String create(DocumentMetadata documentMetadata) {
|
||||
// Create the UUID
|
||||
documentMetadata.setId(UUID.randomUUID().toString());
|
||||
|
||||
// Create the document metadata
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
em.persist(documentMetadata);
|
||||
|
||||
return documentMetadata.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a document metadata.
|
||||
*
|
||||
* @param documentMetadata Document metadata
|
||||
* @return Updated document metadata
|
||||
*/
|
||||
public DocumentMetadata update(DocumentMetadata documentMetadata) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
|
||||
// Get the document metadata
|
||||
Query q = em.createQuery("select u from DocumentMetadata u where u.id = :id");
|
||||
q.setParameter("id", documentMetadata.getId());
|
||||
DocumentMetadata documentMetadataDb = (DocumentMetadata) q.getSingleResult();
|
||||
|
||||
// Update the document metadata
|
||||
documentMetadataDb.setValue(documentMetadata.getValue());
|
||||
|
||||
return documentMetadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all metadata values on a document.
|
||||
*
|
||||
* @param documentId Document ID
|
||||
* @return List of metadata
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<DocumentMetadataDto> getByDocumentId(String documentId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
StringBuilder sb = new StringBuilder("select dm.DME_ID_C, dm.DME_IDDOCUMENT_C, dm.DME_IDMETADATA_C, dm.DME_VALUE_C, m.MET_TYPE_C");
|
||||
sb.append(" from T_DOCUMENT_METADATA dm, T_METADATA m ");
|
||||
sb.append(" where dm.DME_IDMETADATA_C = m.MET_ID_C and dm.DME_IDDOCUMENT_C = :documentId and m.MET_DELETEDATE_D is null");
|
||||
|
||||
// Perform the search
|
||||
Query q = em.createNativeQuery(sb.toString());
|
||||
q.setParameter("documentId", documentId);
|
||||
List<Object[]> l = q.getResultList();
|
||||
|
||||
// Assemble results
|
||||
List<DocumentMetadataDto> dtoList = new ArrayList<>();
|
||||
for (Object[] o : l) {
|
||||
int i = 0;
|
||||
DocumentMetadataDto dto = new DocumentMetadataDto();
|
||||
dto.setId((String) o[i++]);
|
||||
dto.setDocumentId((String) o[i++]);
|
||||
dto.setMetadataId((String) o[i++]);
|
||||
dto.setValue((String) o[i++]);
|
||||
dto.setType(MetadataType.valueOf((String) o[i]));
|
||||
dtoList.add(dto);
|
||||
}
|
||||
return dtoList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.sismics.docs.core.dao;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.sismics.docs.core.constant.AuditLogType;
|
||||
import com.sismics.docs.core.constant.MetadataType;
|
||||
import com.sismics.docs.core.dao.criteria.MetadataCriteria;
|
||||
import com.sismics.docs.core.dao.dto.MetadataDto;
|
||||
import com.sismics.docs.core.model.jpa.Metadata;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
import com.sismics.docs.core.util.jpa.QueryParam;
|
||||
import com.sismics.docs.core.util.jpa.QueryUtil;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.Query;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Metadata DAO.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class MetadataDao {
|
||||
/**
|
||||
* Creates a new metdata.
|
||||
*
|
||||
* @param metadata Metadata
|
||||
* @param userId User ID
|
||||
* @return New ID
|
||||
*/
|
||||
public String create(Metadata metadata, String userId) {
|
||||
// Create the UUID
|
||||
metadata.setId(UUID.randomUUID().toString());
|
||||
|
||||
// Create the metadata
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
em.persist(metadata);
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(metadata, AuditLogType.CREATE, userId);
|
||||
|
||||
return metadata.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a metadata.
|
||||
*
|
||||
* @param metadata Metadata to update
|
||||
* @param userId User ID
|
||||
* @return Updated metadata
|
||||
*/
|
||||
public Metadata update(Metadata metadata, String userId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
|
||||
// Get the metadata
|
||||
Query q = em.createQuery("select r from Metadata r where r.id = :id and r.deleteDate is null");
|
||||
q.setParameter("id", metadata.getId());
|
||||
Metadata metadataDb = (Metadata) q.getSingleResult();
|
||||
|
||||
// Update the metadata
|
||||
metadataDb.setName(metadata.getName());
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(metadataDb, AuditLogType.UPDATE, userId);
|
||||
|
||||
return metadataDb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an active metadata by its ID.
|
||||
*
|
||||
* @param id Metadata ID
|
||||
* @return Metadata
|
||||
*/
|
||||
public Metadata getActiveById(String id) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
try {
|
||||
Query q = em.createQuery("select r from Metadata r where r.id = :id and r.deleteDate is null");
|
||||
q.setParameter("id", id);
|
||||
return (Metadata) q.getSingleResult();
|
||||
} catch (NoResultException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a metadata.
|
||||
*
|
||||
* @param id Metadata ID
|
||||
* @param userId User ID
|
||||
*/
|
||||
public void delete(String id, String userId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
|
||||
// Get the metadata
|
||||
Query q = em.createQuery("select r from Metadata r where r.id = :id and r.deleteDate is null");
|
||||
q.setParameter("id", id);
|
||||
Metadata metadataDb = (Metadata) q.getSingleResult();
|
||||
|
||||
// Delete the metadata
|
||||
Date dateNow = new Date();
|
||||
metadataDb.setDeleteDate(dateNow);
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(metadataDb, AuditLogType.DELETE, userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all metadata.
|
||||
*
|
||||
* @param criteria Search criteria
|
||||
* @param sortCriteria Sort criteria
|
||||
* @return List of metadata
|
||||
*/
|
||||
public List<MetadataDto> findByCriteria(MetadataCriteria criteria, SortCriteria sortCriteria) {
|
||||
Map<String, Object> parameterMap = new HashMap<>();
|
||||
List<String> criteriaList = new ArrayList<>();
|
||||
|
||||
StringBuilder sb = new StringBuilder("select m.MET_ID_C c0, m.MET_NAME_C c1, m.MET_TYPE_C c2");
|
||||
sb.append(" from T_METADATA m ");
|
||||
|
||||
criteriaList.add("m.MET_DELETEDATE_D is null");
|
||||
|
||||
if (!criteriaList.isEmpty()) {
|
||||
sb.append(" where ");
|
||||
sb.append(Joiner.on(" and ").join(criteriaList));
|
||||
}
|
||||
|
||||
// Perform the search
|
||||
QueryParam queryParam = QueryUtil.getSortedQueryParam(new QueryParam(sb.toString(), parameterMap), sortCriteria);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Object[]> l = QueryUtil.getNativeQuery(queryParam).getResultList();
|
||||
|
||||
// Assemble results
|
||||
List<MetadataDto> dtoList = new ArrayList<>();
|
||||
for (Object[] o : l) {
|
||||
int i = 0;
|
||||
MetadataDto dto = new MetadataDto();
|
||||
dto.setId((String) o[i++]);
|
||||
dto.setName((String) o[i++]);
|
||||
dto.setType(MetadataType.valueOf((String) o[i]));
|
||||
dtoList.add(dto);
|
||||
}
|
||||
return dtoList;
|
||||
}
|
||||
}
|
||||
@@ -36,13 +36,13 @@ public class RelationDao {
|
||||
List<Object[]> l = q.getResultList();
|
||||
|
||||
// Assemble results
|
||||
List<RelationDto> relationDtoList = new ArrayList<RelationDto>();
|
||||
List<RelationDto> relationDtoList = new ArrayList<>();
|
||||
for (Object[] o : l) {
|
||||
int i = 0;
|
||||
RelationDto relationDto = new RelationDto();
|
||||
relationDto.setId((String) o[i++]);
|
||||
relationDto.setTitle((String) o[i++]);
|
||||
String fromDocId = (String) o[i++];
|
||||
String fromDocId = (String) o[i];
|
||||
relationDto.setSource(documentId.equals(fromDocId));
|
||||
relationDtoList.add(relationDto);
|
||||
}
|
||||
|
||||
@@ -91,10 +91,15 @@ public class RouteDao {
|
||||
* Deletes a route and the associated steps.
|
||||
*
|
||||
* @param routeId Route ID
|
||||
* @param userId User ID
|
||||
*/
|
||||
public void deleteRoute(String routeId) {
|
||||
public void deleteRoute(String routeId, String userId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
|
||||
// Create audit log
|
||||
Route route = em.find(Route.class, routeId);
|
||||
AuditLogUtil.create(route, AuditLogType.DELETE, userId);
|
||||
|
||||
em.createNativeQuery("update T_ROUTE_STEP rs set RTP_DELETEDATE_D = :dateNow where rs.RTP_IDROUTE_C = :routeId and rs.RTP_DELETEDATE_D is null")
|
||||
.setParameter("routeId", routeId)
|
||||
.setParameter("dateNow", new Date())
|
||||
|
||||
@@ -61,7 +61,7 @@ public class RouteModelDao {
|
||||
q.setParameter("id", routeModel.getId());
|
||||
RouteModel routeModelDb = (RouteModel) q.getSingleResult();
|
||||
|
||||
// Update the group
|
||||
// Update the route model
|
||||
routeModelDb.setName(routeModel.getName());
|
||||
routeModelDb.setSteps(routeModel.getSteps());
|
||||
|
||||
@@ -88,6 +88,18 @@ public class RouteModelDao {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all route models.
|
||||
*
|
||||
* @return List of route models
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<RouteModel> findAll() {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query q = em.createQuery("select r from RouteModel r where r.deleteDate is null");
|
||||
return q.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a route model.
|
||||
*
|
||||
|
||||
@@ -171,6 +171,26 @@ public class UserDao {
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the onboarding status.
|
||||
*
|
||||
* @param user User to update
|
||||
* @return Updated user
|
||||
*/
|
||||
public User updateOnboarding(User user) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
|
||||
// Get the user
|
||||
Query q = em.createQuery("select u from User u where u.id = :id and u.deleteDate is null");
|
||||
q.setParameter("id", user.getId());
|
||||
User userDb = (User) q.getSingleResult();
|
||||
|
||||
// Update the user
|
||||
userDb.setOnboarding(user.isOnboarding());
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a user by its ID.
|
||||
*
|
||||
|
||||
@@ -51,6 +51,12 @@ public class DocumentCriteria {
|
||||
*/
|
||||
private List<List<String>> tagIdList;
|
||||
|
||||
/**
|
||||
* Tag IDs to excluded.
|
||||
* The first and second level list will be excluded.
|
||||
*/
|
||||
private List<List<String>> excludedTagIdList;
|
||||
|
||||
/**
|
||||
* Shared status.
|
||||
*/
|
||||
@@ -70,6 +76,11 @@ public class DocumentCriteria {
|
||||
* A route is active.
|
||||
*/
|
||||
private Boolean activeRoute;
|
||||
|
||||
/**
|
||||
* MIME type of a file.
|
||||
*/
|
||||
private String mimeType;
|
||||
|
||||
public List<String> getTargetIdList() {
|
||||
return targetIdList;
|
||||
@@ -119,6 +130,15 @@ public class DocumentCriteria {
|
||||
this.tagIdList = tagIdList;
|
||||
}
|
||||
|
||||
public List<List<String>> getExcludedTagIdList() {
|
||||
return excludedTagIdList;
|
||||
}
|
||||
|
||||
public DocumentCriteria setExcludedTagIdList(List<List<String>> excludedTagIdList) {
|
||||
this.excludedTagIdList = excludedTagIdList;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Boolean getShared() {
|
||||
return shared;
|
||||
}
|
||||
@@ -166,4 +186,12 @@ public class DocumentCriteria {
|
||||
public void setActiveRoute(Boolean activeRoute) {
|
||||
this.activeRoute = activeRoute;
|
||||
}
|
||||
|
||||
public String getMimeType() {
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
public void setMimeType(String mimeType) {
|
||||
this.mimeType = mimeType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.sismics.docs.core.dao.criteria;
|
||||
|
||||
/**
|
||||
* Metadata criteria.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class MetadataCriteria {
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.sismics.docs.core.dao.dto;
|
||||
|
||||
import com.sismics.docs.core.constant.MetadataType;
|
||||
|
||||
/**
|
||||
* Document metadata DTO.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class DocumentMetadataDto {
|
||||
/**
|
||||
* Document metadata ID.
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Document ID.
|
||||
*/
|
||||
private String documentId;
|
||||
|
||||
/**
|
||||
* Metadata ID.
|
||||
*/
|
||||
private String metadataId;
|
||||
|
||||
/**
|
||||
* Name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Value.
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* Type.
|
||||
*/
|
||||
private MetadataType type;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public DocumentMetadataDto setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public DocumentMetadataDto setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MetadataType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public DocumentMetadataDto setType(MetadataType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getDocumentId() {
|
||||
return documentId;
|
||||
}
|
||||
|
||||
public DocumentMetadataDto setDocumentId(String documentId) {
|
||||
this.documentId = documentId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getMetadataId() {
|
||||
return metadataId;
|
||||
}
|
||||
|
||||
public DocumentMetadataDto setMetadataId(String metadataId) {
|
||||
this.metadataId = metadataId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public DocumentMetadataDto setValue(String value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.sismics.docs.core.dao.dto;
|
||||
|
||||
import com.sismics.docs.core.constant.MetadataType;
|
||||
|
||||
/**
|
||||
* Metadata DTO.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class MetadataDto {
|
||||
/**
|
||||
* Metadata ID.
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Name.
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Type.
|
||||
*/
|
||||
private MetadataType type;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public MetadataDto setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public MetadataDto setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MetadataType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public MetadataDto setType(MetadataType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public class DocumentUpdatedAsyncListener {
|
||||
}
|
||||
|
||||
// Update database and index
|
||||
documentDao.updateSilently(document);
|
||||
documentDao.updateFileId(document);
|
||||
AppContext.getInstance().getIndexingHandler().updateDocument(document);
|
||||
|
||||
// Update contributors list
|
||||
|
||||
@@ -96,14 +96,14 @@ public class FileProcessingAsyncListener {
|
||||
final File file = event.getFile();
|
||||
FormatHandler formatHandler = FormatHandlerUtil.find(file.getMimeType());
|
||||
if (formatHandler == null) {
|
||||
log.error("Format unhandled: " + file.getMimeType());
|
||||
log.info("Format unhandled: " + file.getMimeType());
|
||||
FileUtil.endProcessingFile(file.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the user from the database
|
||||
// Get the creating user from the database for its private key
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getById(event.getUserId());
|
||||
User user = userDao.getById(file.getUserId());
|
||||
if (user == null) {
|
||||
// The user has been deleted meanwhile
|
||||
FileUtil.endProcessingFile(file.getId());
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.sismics.docs.core.model.jpa;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Link between a document and a metadata, holding the value.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "T_DOCUMENT_METADATA")
|
||||
public class DocumentMetadata implements Serializable {
|
||||
/**
|
||||
* Serial version UID.
|
||||
*/
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Document metadata ID.
|
||||
*/
|
||||
@Id
|
||||
@Column(name = "DME_ID_C", length = 36)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Document ID.
|
||||
*/
|
||||
@Column(name = "DME_IDDOCUMENT_C", nullable = false, length = 36)
|
||||
private String documentId;
|
||||
|
||||
/**
|
||||
* Metadata ID.
|
||||
*/
|
||||
@Column(name = "DME_IDMETADATA_C", nullable = false, length = 36)
|
||||
private String metadataId;
|
||||
|
||||
/**
|
||||
* Value.
|
||||
*/
|
||||
@Column(name = "DME_VALUE_C", length = 4000)
|
||||
private String value;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getDocumentId() {
|
||||
return documentId;
|
||||
}
|
||||
|
||||
public void setDocumentId(String documentId) {
|
||||
this.documentId = documentId;
|
||||
}
|
||||
|
||||
public String getMetadataId() {
|
||||
return metadataId;
|
||||
}
|
||||
|
||||
public DocumentMetadata setMetadataId(String metadataId) {
|
||||
this.metadataId = metadataId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public DocumentMetadata setValue(String value) {
|
||||
this.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("id", id)
|
||||
.add("documentId", documentId)
|
||||
.add("metadataId", metadataId)
|
||||
.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.sismics.docs.core.model.jpa;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.sismics.docs.core.constant.MetadataType;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Metadata entity.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "T_METADATA")
|
||||
public class Metadata implements Loggable {
|
||||
/**
|
||||
* Metadata ID.
|
||||
*/
|
||||
@Id
|
||||
@Column(name = "MET_ID_C", length = 36)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Name.
|
||||
*/
|
||||
@Column(name = "MET_NAME_C", length = 50, nullable = false)
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* Type.
|
||||
*/
|
||||
@Column(name = "MET_TYPE_C", length = 20, nullable = false)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private MetadataType type;
|
||||
|
||||
/**
|
||||
* Deletion date.
|
||||
*/
|
||||
@Column(name = "MET_DELETEDATE_D")
|
||||
private Date deleteDate;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Metadata setId(String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Metadata setName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MetadataType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Metadata setType(MetadataType type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getDeleteDate() {
|
||||
return deleteDate;
|
||||
}
|
||||
|
||||
public void setDeleteDate(Date deleteDate) {
|
||||
this.deleteDate = deleteDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("id", id)
|
||||
.add("name", name)
|
||||
.add("type", type)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMessage() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,13 @@ public class User implements Loggable {
|
||||
*/
|
||||
@Column(name = "USE_PRIVATEKEY_C", nullable = false, length = 100)
|
||||
private String privateKey;
|
||||
|
||||
|
||||
/**
|
||||
* False when the user passed the onboarding.
|
||||
*/
|
||||
@Column(name = "USE_ONBOARDING_B", nullable = false)
|
||||
private boolean onboarding;
|
||||
|
||||
/**
|
||||
* TOTP secret key.
|
||||
*/
|
||||
@@ -198,6 +204,15 @@ public class User implements Loggable {
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isOnboarding() {
|
||||
return onboarding;
|
||||
}
|
||||
|
||||
public User setOnboarding(boolean onboarding) {
|
||||
this.onboarding = onboarding;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
|
||||
@@ -85,7 +85,7 @@ public class FileService extends AbstractScheduledService {
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
class TemporaryPathReference extends PhantomReference<Path> {
|
||||
static class TemporaryPathReference extends PhantomReference<Path> {
|
||||
String path;
|
||||
TemporaryPathReference(Path referent, ReferenceQueue<? super Path> q) {
|
||||
super(referent, q);
|
||||
|
||||
@@ -22,9 +22,9 @@ public class DirectoryUtil {
|
||||
*/
|
||||
public static Path getBaseDataDirectory() {
|
||||
Path baseDataDir = null;
|
||||
if (StringUtils.isNotBlank(EnvironmentUtil.getDocsHome())) {
|
||||
if (StringUtils.isNotBlank(EnvironmentUtil.getTeedyHome())) {
|
||||
// If the docs.home property is set then use it
|
||||
baseDataDir = Paths.get(EnvironmentUtil.getDocsHome());
|
||||
baseDataDir = Paths.get(EnvironmentUtil.getTeedyHome());
|
||||
} else if (EnvironmentUtil.isUnitTest()) {
|
||||
// For unit testing, use a temporary directory
|
||||
baseDataDir = Paths.get(System.getProperty("java.io.tmpdir"));
|
||||
|
||||
@@ -32,6 +32,7 @@ public class EncryptionUtil {
|
||||
static {
|
||||
// Initialize Bouncy Castle provider
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 1);
|
||||
Security.removeProvider("SunRsaSign");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.sismics.docs.core.util;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.sismics.docs.core.constant.MetadataType;
|
||||
import com.sismics.docs.core.dao.DocumentMetadataDao;
|
||||
import com.sismics.docs.core.dao.MetadataDao;
|
||||
import com.sismics.docs.core.dao.criteria.MetadataCriteria;
|
||||
import com.sismics.docs.core.dao.dto.DocumentMetadataDto;
|
||||
import com.sismics.docs.core.dao.dto.MetadataDto;
|
||||
import com.sismics.docs.core.model.jpa.DocumentMetadata;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonArrayBuilder;
|
||||
import javax.json.JsonObjectBuilder;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Metadata utilities.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class MetadataUtil {
|
||||
/**
|
||||
* Update custom metadata on a document.
|
||||
*
|
||||
* @param documentId Document ID
|
||||
* @param metadataIdList Metadata ID list
|
||||
* @param metadataValueList Metadata value list
|
||||
*/
|
||||
public static void updateMetadata(String documentId, List<String> metadataIdList, List<String> metadataValueList) throws Exception {
|
||||
if (metadataIdList == null || metadataValueList == null || metadataIdList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (metadataIdList.size() != metadataValueList.size()) {
|
||||
throw new Exception("metadata_id and metadata_value must have the same length");
|
||||
}
|
||||
|
||||
Map<String, String> newValues = Maps.newHashMap();
|
||||
for (int i = 0; i < metadataIdList.size(); i++) {
|
||||
newValues.put(metadataIdList.get(i), metadataValueList.get(i));
|
||||
}
|
||||
|
||||
MetadataDao metadataDao = new MetadataDao();
|
||||
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
|
||||
List<MetadataDto> metadataDtoList = metadataDao.findByCriteria(new MetadataCriteria(), null);
|
||||
List<DocumentMetadataDto> documentMetadataDtoList = documentMetadataDao.getByDocumentId(documentId);
|
||||
|
||||
// Update existing values
|
||||
for (DocumentMetadataDto documentMetadataDto : documentMetadataDtoList) {
|
||||
if (newValues.containsKey(documentMetadataDto.getMetadataId())) {
|
||||
// Update the value
|
||||
String value = newValues.get(documentMetadataDto.getMetadataId());
|
||||
validateValue(documentMetadataDto.getType(), value);
|
||||
updateValue(documentMetadataDto.getId(), value);
|
||||
newValues.remove(documentMetadataDto.getMetadataId());
|
||||
} else {
|
||||
// Remove the value
|
||||
updateValue(documentMetadataDto.getId(), null);
|
||||
}
|
||||
}
|
||||
|
||||
// Create new values
|
||||
for (Map.Entry<String, String> entry : newValues.entrySet()) {
|
||||
// Search the metadata definition
|
||||
MetadataDto metadata = null;
|
||||
for (MetadataDto metadataDto : metadataDtoList) {
|
||||
if (metadataDto.getId().equals(entry.getKey())) {
|
||||
metadata = metadataDto;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (metadata == null) {
|
||||
throw new Exception(MessageFormat.format("Metadata not found: {0}", entry.getKey()));
|
||||
}
|
||||
|
||||
// Add the value
|
||||
validateValue(metadata.getType(), entry.getValue());
|
||||
createValue(documentId, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a custom metadata value.
|
||||
*
|
||||
* @param type Metadata type
|
||||
* @param value Value
|
||||
* @throws Exception In case of validation error
|
||||
*/
|
||||
private static void validateValue(MetadataType type, String value) throws Exception {
|
||||
switch (type) {
|
||||
case STRING:
|
||||
case BOOLEAN:
|
||||
return;
|
||||
case DATE:
|
||||
try {
|
||||
Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new Exception("Date value not parsable as timestamp");
|
||||
}
|
||||
break;
|
||||
case FLOAT:
|
||||
try {
|
||||
Double.parseDouble(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new Exception("Float value not parsable");
|
||||
}
|
||||
break;
|
||||
case INTEGER:
|
||||
try {
|
||||
Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new Exception("Integer value not parsable");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a custom metadata value on a document.
|
||||
*
|
||||
* @param documentId Document ID
|
||||
* @param metadataId Metadata ID
|
||||
* @param value Value
|
||||
*/
|
||||
private static void createValue(String documentId, String metadataId, String value) {
|
||||
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
|
||||
DocumentMetadata documentMetadata = new DocumentMetadata();
|
||||
documentMetadata.setDocumentId(documentId);
|
||||
documentMetadata.setMetadataId(metadataId);
|
||||
documentMetadata.setValue(value);
|
||||
documentMetadataDao.create(documentMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a custom metadata value.
|
||||
*
|
||||
* @param documentMetadataId Document metadata ID
|
||||
* @param value Value
|
||||
*/
|
||||
private static void updateValue(String documentMetadataId, String value) {
|
||||
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
|
||||
DocumentMetadata documentMetadata = new DocumentMetadata();
|
||||
documentMetadata.setId(documentMetadataId);
|
||||
documentMetadata.setValue(value);
|
||||
documentMetadataDao.update(documentMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom metadata to a JSON response.
|
||||
*
|
||||
* @param json JSON
|
||||
* @param documentId Document ID
|
||||
*/
|
||||
public static void addMetadata(JsonObjectBuilder json, String documentId) {
|
||||
DocumentMetadataDao documentMetadataDao = new DocumentMetadataDao();
|
||||
MetadataDao metadataDao = new MetadataDao();
|
||||
List<MetadataDto> metadataDtoList = metadataDao.findByCriteria(new MetadataCriteria(), new SortCriteria(1, true));
|
||||
List<DocumentMetadataDto> documentMetadataDtoList = documentMetadataDao.getByDocumentId(documentId);
|
||||
JsonArrayBuilder metadata = Json.createArrayBuilder();
|
||||
for (MetadataDto metadataDto : metadataDtoList) {
|
||||
JsonObjectBuilder meta = Json.createObjectBuilder()
|
||||
.add("id", metadataDto.getId())
|
||||
.add("name", metadataDto.getName())
|
||||
.add("type", metadataDto.getType().name());
|
||||
for (DocumentMetadataDto documentMetadataDto : documentMetadataDtoList) {
|
||||
if (documentMetadataDto.getMetadataId().equals(metadataDto.getId())) {
|
||||
if (documentMetadataDto.getValue() != null) {
|
||||
switch (metadataDto.getType()) {
|
||||
case STRING:
|
||||
meta.add("value", documentMetadataDto.getValue());
|
||||
break;
|
||||
case BOOLEAN:
|
||||
meta.add("value", Boolean.parseBoolean(documentMetadataDto.getValue()));
|
||||
break;
|
||||
case DATE:
|
||||
meta.add("value", Long.parseLong(documentMetadataDto.getValue()));
|
||||
break;
|
||||
case FLOAT:
|
||||
meta.add("value", Double.parseDouble(documentMetadataDto.getValue()));
|
||||
break;
|
||||
case INTEGER:
|
||||
meta.add("value", Integer.parseInt(documentMetadataDto.getValue()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
metadata.add(meta);
|
||||
}
|
||||
json.add("metadata", metadata);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import com.sismics.docs.core.constant.AclType;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.AclDao;
|
||||
import com.sismics.docs.core.dao.DocumentDao;
|
||||
import com.sismics.docs.core.dao.RouteModelDao;
|
||||
import com.sismics.docs.core.dao.UserDao;
|
||||
import com.sismics.docs.core.dao.criteria.UserCriteria;
|
||||
import com.sismics.docs.core.dao.dto.RouteStepDto;
|
||||
@@ -15,8 +16,14 @@ import com.sismics.docs.core.event.RouteStepValidateEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.Acl;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.model.jpa.RouteModel;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonArray;
|
||||
import javax.json.JsonObject;
|
||||
import javax.json.JsonReader;
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -87,4 +94,31 @@ public class RoutingUtil {
|
||||
AppContext.getInstance().getMailEventBus().post(routeStepValidateEvent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first route model name matching a target type and name.
|
||||
*
|
||||
* @param targetType Target type
|
||||
* @param targetName Target name
|
||||
* @return Route model name or null if none is matching
|
||||
*/
|
||||
public static String findRouteModelNameByTargetName(AclTargetType targetType, String targetName) {
|
||||
RouteModelDao routeModelDao = new RouteModelDao();
|
||||
List<RouteModel> routeModelList = routeModelDao.findAll();
|
||||
for (RouteModel routeModel : routeModelList) {
|
||||
try (JsonReader reader = Json.createReader(new StringReader(routeModel.getSteps()))) {
|
||||
JsonArray stepsJson = reader.readArray();
|
||||
for (int order = 0; order < stepsJson.size(); order++) {
|
||||
JsonObject step = stepsJson.getJsonObject(order);
|
||||
JsonObject target = step.getJsonObject("target");
|
||||
AclTargetType routeTargetType = AclTargetType.valueOf(target.getString("type"));
|
||||
String routeTargetName = target.getString("name");
|
||||
if (targetType == routeTargetType && targetName.equals(routeTargetName)) {
|
||||
return routeModel.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.sismics.util.mime.MimeType;
|
||||
import org.apache.pdfbox.io.MemoryUsageSetting;
|
||||
import org.apache.pdfbox.multipdf.PDFMergerUtility;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.rendering.ImageType;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
import org.apache.pdfbox.text.PDFTextStripper;
|
||||
import org.slf4j.Logger;
|
||||
@@ -60,7 +61,7 @@ public class PdfFormatHandler implements FormatHandler {
|
||||
for (int pageIndex = 0; pageIndex < pdfDocument.getNumberOfPages(); pageIndex++) {
|
||||
log.info("OCR page " + (pageIndex + 1) + "/" + pdfDocument.getNumberOfPages() + " of PDF file containing only images");
|
||||
sb.append(" ");
|
||||
sb.append(FileUtil.ocrFile(language, renderer.renderImage(pageIndex)));
|
||||
sb.append(FileUtil.ocrFile(language, renderer.renderImageWithDPI(pageIndex, 300, ImageType.GRAY)));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
|
||||
@@ -46,7 +46,6 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.*;
|
||||
|
||||
@@ -252,7 +251,7 @@ public class LuceneIndexingHandler implements IndexingHandler {
|
||||
" s.SHA_DELETEDATE_D IS NULL group by ac.ACL_SOURCEID_C) s on s.ACL_SOURCEID_C = d.DOC_ID_C " +
|
||||
" left join (SELECT count(f.FIL_ID_C) count, f.FIL_IDDOC_C " +
|
||||
" FROM T_FILE f " +
|
||||
" WHERE f.FIL_DELETEDATE_D IS NULL group by f.FIL_IDDOC_C) f on f.FIL_IDDOC_C = d.DOC_ID_C ");
|
||||
" WHERE f.FIL_DELETEDATE_D is null group by f.FIL_IDDOC_C) f on f.FIL_IDDOC_C = d.DOC_ID_C ");
|
||||
sb.append(" left join (select rs.*, rs3.idDocument " +
|
||||
"from T_ROUTE_STEP rs " +
|
||||
"join (select r.RTE_IDDOCUMENT_C idDocument, rs.RTP_IDROUTE_C idRoute, min(rs.RTP_ORDER_N) minOrder from T_ROUTE_STEP rs join T_ROUTE r on r.RTE_ID_C = rs.RTP_IDROUTE_C and r.RTE_DELETEDATE_D is null where rs.RTP_DELETEDATE_D is null and rs.RTP_ENDDATE_D is null group by rs.RTP_IDROUTE_C, r.RTE_IDDOCUMENT_C) rs3 on rs.RTP_IDROUTE_C = rs3.idRoute and rs.RTP_ORDER_N = rs3.minOrder " +
|
||||
@@ -309,9 +308,27 @@ public class LuceneIndexingHandler implements IndexingHandler {
|
||||
criteriaList.add("(" + Joiner.on(" OR ").join(tagCriteriaList) + ")");
|
||||
}
|
||||
}
|
||||
if (criteria.getExcludedTagIdList() != null && !criteria.getExcludedTagIdList().isEmpty()) {
|
||||
int index = 0;
|
||||
for (List<String> tagIdList : criteria.getExcludedTagIdList()) {
|
||||
List<String> tagCriteriaList = Lists.newArrayList();
|
||||
for (String tagId : tagIdList) {
|
||||
sb.append(String.format("left join T_DOCUMENT_TAG dtex%d on dtex%d.DOT_IDDOCUMENT_C = d.DOC_ID_C and dtex%d.DOT_IDTAG_C = :tagIdEx%d and dtex%d.DOT_DELETEDATE_D is null ", index, index, index, index, index));
|
||||
parameterMap.put("tagIdEx" + index, tagId);
|
||||
tagCriteriaList.add(String.format("dtex%d.DOT_ID_C is null", index));
|
||||
index++;
|
||||
}
|
||||
criteriaList.add("(" + Joiner.on(" AND ").join(tagCriteriaList) + ")");
|
||||
}
|
||||
}
|
||||
if (criteria.getShared() != null && criteria.getShared()) {
|
||||
criteriaList.add("s.count > 0");
|
||||
}
|
||||
if (criteria.getMimeType() != null) {
|
||||
sb.append("left join T_FILE f0 on f0.FIL_IDDOC_C = d.DOC_ID_C and f0.FIL_MIMETYPE_C = :mimeType and f0.FIL_DELETEDATE_D is null");
|
||||
parameterMap.put("mimeType", criteria.getMimeType());
|
||||
criteriaList.add("f0.FIL_ID_C is not null");
|
||||
}
|
||||
if (criteria.getLanguage() != null) {
|
||||
criteriaList.add("d.DOC_LANGUAGE_C = :language");
|
||||
parameterMap.put("language", criteria.getLanguage());
|
||||
@@ -377,7 +394,7 @@ public class LuceneIndexingHandler implements IndexingHandler {
|
||||
LuceneDictionary dictionary = new LuceneDictionary(directoryReader, "title");
|
||||
suggester.build(dictionary);
|
||||
int lastIndex = search.lastIndexOf(' ');
|
||||
String suggestQuery = search.substring(lastIndex < 0 ? 0 : lastIndex);
|
||||
String suggestQuery = search.substring(Math.max(lastIndex, 0));
|
||||
List<Lookup.LookupResult> lookupResultList = suggester.lookup(suggestQuery, false, 10);
|
||||
for (Lookup.LookupResult lookupResult : lookupResultList) {
|
||||
suggestionList.add(lookupResult.key.toString());
|
||||
|
||||
@@ -124,11 +124,11 @@ public class EmailUtil {
|
||||
|
||||
// Application name
|
||||
Config themeConfig = configDao.getById(ConfigType.THEME);
|
||||
String appName = "Sismics Docs";
|
||||
String appName = "Teedy";
|
||||
if (themeConfig != null) {
|
||||
try (JsonReader reader = Json.createReader(new StringReader(themeConfig.getValue()))) {
|
||||
JsonObject themeJson = reader.readObject();
|
||||
appName = themeJson.getString("name", "Sismics Docs");
|
||||
appName = themeJson.getString("name", "Teedy");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ public class EnvironmentUtil {
|
||||
|
||||
private static String MAC_OS_USER_HOME = System.getProperty("user.home");
|
||||
|
||||
private static String DOCS_HOME = System.getProperty("docs.home");
|
||||
private static String TEEDY_HOME = System.getProperty("docs.home");
|
||||
|
||||
/**
|
||||
* In a web application context.
|
||||
@@ -90,8 +90,8 @@ public class EnvironmentUtil {
|
||||
*
|
||||
* @return Home directory
|
||||
*/
|
||||
public static String getDocsHome() {
|
||||
return DOCS_HOME;
|
||||
public static String getTeedyHome() {
|
||||
return TEEDY_HOME;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -48,8 +48,11 @@ public class DialectUtil {
|
||||
sql = sql.replaceAll("(cached|memory) table", "table");
|
||||
sql = sql.replaceAll("datetime", "timestamp");
|
||||
sql = sql.replaceAll("longvarchar", "text");
|
||||
sql = sql.replaceAll("bit not null", "bool not null");
|
||||
sql = sql.replaceAll("bit default 1", "bool default true");
|
||||
sql = sql.replaceAll("bit default 0", "bool default false");
|
||||
sql = sql.replaceAll("bit not null default 1", "bool not null default true");
|
||||
sql = sql.replaceAll("bit not null default 0", "bool not null default false");
|
||||
sql = sql.replaceAll("bit not null", "bool not null");
|
||||
return sql;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public final class EMF {
|
||||
if (databaseUrl == null) {
|
||||
props.put("hibernate.connection.driver_class", "org.h2.Driver");
|
||||
props.put("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
|
||||
props.put("hibernate.connection.url", "jdbc:h2:file:" + dbFile + ";CACHE_SIZE=65536");
|
||||
props.put("hibernate.connection.url", "jdbc:h2:file:" + dbFile + ";CACHE_SIZE=65536;LOCK_TIMEOUT=10000");
|
||||
props.put("hibernate.connection.username", "sa");
|
||||
} else {
|
||||
props.put("hibernate.connection.driver_class", "org.postgresql.Driver");
|
||||
|
||||
@@ -1 +1 @@
|
||||
db.version=22
|
||||
db.version=24
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table T_USER add column USE_ONBOARDING_B bit not null default 1;
|
||||
update T_CONFIG set CFG_VALUE_C = '23' where CFG_ID_C = 'DB_VERSION';
|
||||
@@ -0,0 +1,5 @@
|
||||
create cached table T_METADATA ( MET_ID_C varchar(36) not null, MET_NAME_C varchar(50) not null, MET_TYPE_C varchar(20) not null, MET_DELETEDATE_D datetime, primary key (MET_ID_C) );
|
||||
create cached table T_DOCUMENT_METADATA ( DME_ID_C varchar(36) not null, DME_IDDOCUMENT_C varchar(36) not null, DME_IDMETADATA_C varchar(36) not null, DME_VALUE_C varchar(4000) null, primary key (DME_ID_C) );
|
||||
alter table T_DOCUMENT_METADATA add constraint FK_DME_IDDOCUMENT_C foreign key (DME_IDDOCUMENT_C) references T_DOCUMENT (DOC_ID_C) on delete restrict on update restrict;
|
||||
alter table T_DOCUMENT_METADATA add constraint FK_DME_IDMETADATA_C foreign key (DME_IDMETADATA_C) references T_METADATA (MET_ID_C) on delete restrict on update restrict;
|
||||
update T_CONFIG set CFG_VALUE_C = '24' where CFG_ID_C = 'DB_VERSION';
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.sismics.util.format;
|
||||
|
||||
import com.sismics.docs.core.util.format.PdfFormatHandler;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Test of {@link PdfFormatHandler}
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TestPdfFormatHandler {
|
||||
/**
|
||||
* Test related to https://github.com/sismics/docs/issues/373.
|
||||
*/
|
||||
@Test
|
||||
public void testIssue373() throws Exception {
|
||||
PdfFormatHandler formatHandler = new PdfFormatHandler();
|
||||
String content = formatHandler.extractContent("deu", Paths.get(ClassLoader.getSystemResource("file/issue373.pdf").toURI()));
|
||||
Assert.assertTrue(content.contains("Aufrechterhaltung"));
|
||||
Assert.assertTrue(content.contains("Außentemperatur"));
|
||||
Assert.assertTrue(content.contains("Grundumsatzmessungen"));
|
||||
Assert.assertTrue(content.contains("ermitteln"));
|
||||
}
|
||||
}
|
||||
BIN
docs-core/src/test/resources/file/issue373.pdf
Normal file
@@ -22,9 +22,9 @@ const prefs = new preferences('com.sismics.docs.importer',{
|
||||
});
|
||||
|
||||
// Welcome message
|
||||
console.log('Sismics Docs Importer 1.0.0, https://www.sismicsdocs.com' +
|
||||
console.log('Teedy Importer 1.0.0, https://teedy.io' +
|
||||
'\n\n' +
|
||||
'This program let you import files from your system to Sismics Docs' +
|
||||
'This program let you import files from your system to Teedy' +
|
||||
'\n');
|
||||
|
||||
// Ask for the base URL
|
||||
@@ -33,7 +33,7 @@ const askBaseUrl = () => {
|
||||
{
|
||||
type: 'input',
|
||||
name: 'baseUrl',
|
||||
message: 'What is the base URL of your Docs? (eg. https://docs.mycompany.com)',
|
||||
message: 'What is the base URL of your Teedy? (eg. https://teedy.mycompany.com)',
|
||||
default: prefs.importer.baseUrl
|
||||
}
|
||||
]).then(answers => {
|
||||
@@ -42,12 +42,12 @@ const askBaseUrl = () => {
|
||||
|
||||
// Test base URL
|
||||
const spinner = ora({
|
||||
text: 'Checking connection to Docs',
|
||||
text: 'Checking connection to Teedy',
|
||||
spinner: 'flips'
|
||||
}).start();
|
||||
request(answers.baseUrl + '/api/app', function (error, response) {
|
||||
if (!response || response.statusCode !== 200) {
|
||||
spinner.fail('Connection to Docs failed: ' + error);
|
||||
spinner.fail('Connection to Teedy failed: ' + error);
|
||||
askBaseUrl();
|
||||
return;
|
||||
}
|
||||
@@ -82,7 +82,7 @@ const askCredentials = () => {
|
||||
|
||||
// Test credentials
|
||||
const spinner = ora({
|
||||
text: 'Checking connection to Docs',
|
||||
text: 'Checking connection to Teedy',
|
||||
spinner: 'flips'
|
||||
}).start();
|
||||
request.post({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "docs-importer",
|
||||
"name": "teedy-importer",
|
||||
"version": "1.5.1",
|
||||
"description": "Import files to Sismics Docs",
|
||||
"description": "Import files to Teedy",
|
||||
"bin": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-parent</artifactId>
|
||||
<version>1.6-SNAPSHOT</version>
|
||||
<version>1.8</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* Stress app for Sismics Docs.
|
||||
* Stress app for Teedy.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-parent</artifactId>
|
||||
<version>1.6-SNAPSHOT</version>
|
||||
<version>1.8</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -68,6 +68,11 @@
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jul-to-slf4j</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test dependencies -->
|
||||
<dependency>
|
||||
|
||||
@@ -111,7 +111,18 @@ public class ValidationUtil {
|
||||
public static void validateHexColor(String s, String name, boolean nullable) throws ClientException {
|
||||
ValidationUtil.validateLength(s, name, 7, 7, nullable);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validate a tag name.
|
||||
*
|
||||
* @param name Name of the tag
|
||||
*/
|
||||
public static void validateTagName(String name) throws ClientException {
|
||||
if (name.contains(" ") || name.contains(":")) {
|
||||
throw new ClientException("IllegalTagName", "Spaces and colons are not allowed in tag name");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the provided string matches an URL with HTTP or HTTPS scheme.
|
||||
*
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.apache.log4j.PatternLayout;
|
||||
import org.apache.log4j.RollingFileAppender;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.bridge.SLF4JBridgeHandler;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityTransaction;
|
||||
@@ -57,6 +58,8 @@ public class RequestContextFilter implements Filter {
|
||||
fileAppender.setMaxBackupIndex(5);
|
||||
fileAppender.activateOptions();
|
||||
org.apache.log4j.Logger.getRootLogger().addAppender(fileAppender);
|
||||
SLF4JBridgeHandler.removeHandlersForRootLogger();
|
||||
SLF4JBridgeHandler.install();
|
||||
|
||||
// Initialize the application context
|
||||
TransactionUtil.handle(AppContext::getInstance);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.sismics.docs</groupId>
|
||||
<artifactId>docs-parent</artifactId>
|
||||
<version>1.6-SNAPSHOT</version>
|
||||
<version>1.8</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -26,25 +26,6 @@
|
||||
<artifactId>docs-web-common</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JDK 11 JAXB dependencies -->
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
<artifactId>jaxb-core</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.sun.xml.bind</groupId>
|
||||
<artifactId>jaxb-impl</artifactId>
|
||||
<version>2.3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Dependencies to Jersey -->
|
||||
<dependency>
|
||||
<groupId>org.glassfish.jersey.containers</groupId>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=22
|
||||
db.version=24
|
||||
@@ -6,3 +6,5 @@ log4j.appender.MEMORY=com.sismics.util.log4j.MemoryAppender
|
||||
log4j.appender.MEMORY.size=1000
|
||||
|
||||
log4j.logger.com.sismics=DEBUG
|
||||
log4j.logger.org.apache.pdfbox=ERROR
|
||||
log4j.logger.org.glassfish.jersey.servlet.WebComponent=ERROR
|
||||
@@ -228,8 +228,11 @@ public class AclResource extends BaseResource {
|
||||
SortCriteria sortCriteria = new SortCriteria(1, true);
|
||||
List<UserDto> userDtoList = userDao.findByCriteria(new UserCriteria().setSearch(search), sortCriteria);
|
||||
for (UserDto userDto : userDtoList) {
|
||||
users.add(Json.createObjectBuilder()
|
||||
.add("name", userDto.getUsername()));
|
||||
// No need to add users who will skip ACL check anyways
|
||||
if (!SecurityUtil.skipAclCheck(Lists.newArrayList(userDto.getId()))) {
|
||||
users.add(Json.createObjectBuilder()
|
||||
.add("name", userDto.getUsername()));
|
||||
}
|
||||
}
|
||||
|
||||
// Search groups
|
||||
@@ -237,8 +240,11 @@ public class AclResource extends BaseResource {
|
||||
JsonArrayBuilder groups = Json.createArrayBuilder();
|
||||
List<GroupDto> groupDtoList = groupDao.findByCriteria(new GroupCriteria().setSearch(search), sortCriteria);
|
||||
for (GroupDto groupDto : groupDtoList) {
|
||||
groups.add(Json.createObjectBuilder()
|
||||
.add("name", groupDto.getName()));
|
||||
// No need to add users who will skip ACL check anyways
|
||||
if (!SecurityUtil.skipAclCheck(Lists.newArrayList(groupDto.getId()))) {
|
||||
groups.add(Json.createObjectBuilder()
|
||||
.add("name", groupDto.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
|
||||
@@ -234,7 +234,10 @@ public class DocumentResource extends BaseResource {
|
||||
step.add("transitionable", getTargetIdList(null).contains(routeStepDto.getTargetId()));
|
||||
document.add("route_step", step);
|
||||
}
|
||||
|
||||
|
||||
// Add custom metadata
|
||||
MetadataUtil.addMetadata(document, documentId);
|
||||
|
||||
return Response.ok().entity(document.build()).build();
|
||||
}
|
||||
|
||||
@@ -459,11 +462,15 @@ public class DocumentResource extends BaseResource {
|
||||
|
||||
switch (params[0]) {
|
||||
case "tag":
|
||||
case "!tag":
|
||||
// New tag criteria
|
||||
List<TagDto> tagDtoList = TagUtil.findByName(params[1], allTagDtoList);
|
||||
if (documentCriteria.getTagIdList() == null) {
|
||||
documentCriteria.setTagIdList(new ArrayList<>());
|
||||
}
|
||||
if (documentCriteria.getExcludedTagIdList() == null) {
|
||||
documentCriteria.setExcludedTagIdList(new ArrayList<>());
|
||||
}
|
||||
if (tagDtoList.isEmpty()) {
|
||||
// No tag found, the request must returns nothing
|
||||
documentCriteria.getTagIdList().add(Lists.newArrayList(UUID.randomUUID().toString()));
|
||||
@@ -476,7 +483,11 @@ public class DocumentResource extends BaseResource {
|
||||
tagIdList.add(childrenTagDto.getId());
|
||||
}
|
||||
}
|
||||
documentCriteria.getTagIdList().add(tagIdList);
|
||||
if (params[0].startsWith("!")) {
|
||||
documentCriteria.getExcludedTagIdList().add(tagIdList);
|
||||
} else {
|
||||
documentCriteria.getTagIdList().add(tagIdList);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "after":
|
||||
@@ -558,6 +569,10 @@ public class DocumentResource extends BaseResource {
|
||||
documentCriteria.setLanguage(UUID.randomUUID().toString());
|
||||
}
|
||||
break;
|
||||
case "mime":
|
||||
// New mime type criteria
|
||||
documentCriteria.setMimeType(params[1]);
|
||||
break;
|
||||
case "by":
|
||||
// New creator criteria
|
||||
User user = userDao.getActiveByUsername(params[1]);
|
||||
@@ -606,6 +621,8 @@ public class DocumentResource extends BaseResource {
|
||||
* @apiParam {String} [rights] Rights
|
||||
* @apiParam {String[]} [tags] List of tags ID
|
||||
* @apiParam {String[]} [relations] List of related documents ID
|
||||
* @apiParam {String[]} [metadata_id] List of metadata ID
|
||||
* @apiParam {String[]} [metadata_value] List of metadata values
|
||||
* @apiParam {String} language Language
|
||||
* @apiParam {Number} [create_date] Create date (timestamp)
|
||||
* @apiSuccess {String} id Document ID
|
||||
@@ -626,6 +643,8 @@ public class DocumentResource extends BaseResource {
|
||||
* @param rights Rights
|
||||
* @param tagList Tags
|
||||
* @param relationList Relations
|
||||
* @param metadataIdList Metadata ID list
|
||||
* @param metadataValueList Metadata value list
|
||||
* @param language Language
|
||||
* @param createDateStr Creation date
|
||||
* @return Response
|
||||
@@ -644,6 +663,8 @@ public class DocumentResource extends BaseResource {
|
||||
@FormParam("rights") String rights,
|
||||
@FormParam("tags") List<String> tagList,
|
||||
@FormParam("relations") List<String> relationList,
|
||||
@FormParam("metadata_id") List<String> metadataIdList,
|
||||
@FormParam("metadata_value") List<String> metadataValueList,
|
||||
@FormParam("language") String language,
|
||||
@FormParam("create_date") String createDateStr) {
|
||||
if (!authenticate()) {
|
||||
@@ -666,7 +687,7 @@ public class DocumentResource extends BaseResource {
|
||||
if (!Constants.SUPPORTED_LANGUAGES.contains(language)) {
|
||||
throw new ClientException("ValidationError", MessageFormat.format("{0} is not a supported language", language));
|
||||
}
|
||||
|
||||
|
||||
// Create the document
|
||||
Document document = new Document();
|
||||
document.setUserId(principal.getId());
|
||||
@@ -696,6 +717,13 @@ public class DocumentResource extends BaseResource {
|
||||
// Update relations
|
||||
updateRelationList(document.getId(), relationList);
|
||||
|
||||
// Update custom metadata
|
||||
try {
|
||||
MetadataUtil.updateMetadata(document.getId(), metadataIdList, metadataValueList);
|
||||
} catch (Exception e) {
|
||||
throw new ClientException("ValidationError", e.getMessage());
|
||||
}
|
||||
|
||||
// Raise a document created event
|
||||
DocumentCreatedAsyncEvent documentCreatedAsyncEvent = new DocumentCreatedAsyncEvent();
|
||||
documentCreatedAsyncEvent.setUserId(principal.getId());
|
||||
@@ -726,6 +754,8 @@ public class DocumentResource extends BaseResource {
|
||||
* @apiParam {String} [rights] Rights
|
||||
* @apiParam {String[]} [tags] List of tags ID
|
||||
* @apiParam {String[]} [relations] List of related documents ID
|
||||
* @apiParam {String[]} [metadata_id] List of metadata ID
|
||||
* @apiParam {String[]} [metadata_value] List of metadata values
|
||||
* @apiParam {String} language Language
|
||||
* @apiParam {Number} [create_date] Create date (timestamp)
|
||||
* @apiSuccess {String} id Document ID
|
||||
@@ -755,6 +785,8 @@ public class DocumentResource extends BaseResource {
|
||||
@FormParam("rights") String rights,
|
||||
@FormParam("tags") List<String> tagList,
|
||||
@FormParam("relations") List<String> relationList,
|
||||
@FormParam("metadata_id") List<String> metadataIdList,
|
||||
@FormParam("metadata_value") List<String> metadataValueList,
|
||||
@FormParam("language") String language,
|
||||
@FormParam("create_date") String createDateStr) {
|
||||
if (!authenticate()) {
|
||||
@@ -816,7 +848,14 @@ public class DocumentResource extends BaseResource {
|
||||
|
||||
// Update relations
|
||||
updateRelationList(id, relationList);
|
||||
|
||||
|
||||
// Update custom metadata
|
||||
try {
|
||||
MetadataUtil.updateMetadata(document.getId(), metadataIdList, metadataValueList);
|
||||
} catch (Exception e) {
|
||||
throw new ClientException("ValidationError", e.getMessage());
|
||||
}
|
||||
|
||||
// Raise a document updated event
|
||||
DocumentUpdatedAsyncEvent documentUpdatedAsyncEvent = new DocumentUpdatedAsyncEvent();
|
||||
documentUpdatedAsyncEvent.setUserId(principal.getId());
|
||||
@@ -960,15 +999,30 @@ public class DocumentResource extends BaseResource {
|
||||
|
||||
// Delete the document
|
||||
documentDao.delete(id, principal.getId());
|
||||
|
||||
// Raise file deleted events (don't bother sending document updated event)
|
||||
|
||||
long totalSize = 0L;
|
||||
for (File file : fileList) {
|
||||
// Store the file size to update the quota
|
||||
java.nio.file.Path storedFile = DirectoryUtil.getStorageDirectory().resolve(file.getId());
|
||||
try {
|
||||
totalSize += Files.size(storedFile);
|
||||
} catch (IOException e) {
|
||||
// The file doesn't exists on disk, which is weird, but not fatal
|
||||
}
|
||||
|
||||
// Raise file deleted event
|
||||
FileDeletedAsyncEvent fileDeletedAsyncEvent = new FileDeletedAsyncEvent();
|
||||
fileDeletedAsyncEvent.setUserId(principal.getId());
|
||||
fileDeletedAsyncEvent.setFile(file);
|
||||
ThreadLocalContext.get().addAsyncEvent(fileDeletedAsyncEvent);
|
||||
}
|
||||
|
||||
|
||||
// Update the user quota
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getById(principal.getId());
|
||||
user.setStorageCurrent(user.getStorageCurrent() - totalSize);
|
||||
userDao.updateQuota(user);
|
||||
|
||||
// Raise a document deleted event
|
||||
DocumentDeletedAsyncEvent documentDeletedAsyncEvent = new DocumentDeletedAsyncEvent();
|
||||
documentDeletedAsyncEvent.setUserId(principal.getId());
|
||||
|
||||
@@ -608,7 +608,7 @@ public class FileResource extends BaseResource {
|
||||
if (size != null) {
|
||||
if (size.equals("content")) {
|
||||
return Response.ok(Strings.nullToEmpty(file.getContent()))
|
||||
.header(HttpHeaders.CONTENT_TYPE, "text/plain")
|
||||
.header(HttpHeaders.CONTENT_TYPE, "text/plain; charset=utf-8")
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.sismics.docs.rest.resource;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.sismics.docs.core.constant.AclTargetType;
|
||||
import com.sismics.docs.core.dao.GroupDao;
|
||||
import com.sismics.docs.core.dao.RoleBaseFunctionDao;
|
||||
import com.sismics.docs.core.dao.UserDao;
|
||||
@@ -12,6 +13,7 @@ import com.sismics.docs.core.dao.dto.UserDto;
|
||||
import com.sismics.docs.core.model.jpa.Group;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.model.jpa.UserGroup;
|
||||
import com.sismics.docs.core.util.RoutingUtil;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
import com.sismics.docs.rest.constant.BaseFunction;
|
||||
import com.sismics.rest.exception.ClientException;
|
||||
@@ -148,6 +150,14 @@ public class GroupResource extends BaseResource {
|
||||
}
|
||||
parentId = parentGroup.getId();
|
||||
}
|
||||
|
||||
// Check that this group is not used in any workflow in case of renaming
|
||||
if (!name.equals(groupName)) {
|
||||
String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.GROUP, groupName);
|
||||
if (routeModelName != null) {
|
||||
throw new ClientException("GroupUsedInRouteModel", routeModelName);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the group
|
||||
groupDao.update(group.setName(name)
|
||||
@@ -169,6 +179,7 @@ public class GroupResource extends BaseResource {
|
||||
* @apiSuccess {String} status Status OK
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (client) NotFound Group not found
|
||||
* @apiError (client) GroupUsedInRouteModel The group is used in a route model
|
||||
* @apiPermission admin
|
||||
* @apiVersion 1.5.0
|
||||
*
|
||||
@@ -197,7 +208,13 @@ public class GroupResource extends BaseResource {
|
||||
throw new ClientException("ForbiddenError", "The administrators group cannot be deleted");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check that this group is not used in any workflow
|
||||
String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.GROUP, groupName);
|
||||
if (routeModelName != null) {
|
||||
throw new ClientException("GroupUsedInRouteModel", routeModelName);
|
||||
}
|
||||
|
||||
// Delete the group
|
||||
groupDao.delete(group.getId(), principal.getId());
|
||||
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import com.sismics.docs.core.constant.MetadataType;
|
||||
import com.sismics.docs.core.dao.MetadataDao;
|
||||
import com.sismics.docs.core.dao.criteria.MetadataCriteria;
|
||||
import com.sismics.docs.core.dao.dto.MetadataDto;
|
||||
import com.sismics.docs.core.model.jpa.Metadata;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
import com.sismics.docs.rest.constant.BaseFunction;
|
||||
import com.sismics.rest.exception.ForbiddenClientException;
|
||||
import com.sismics.rest.util.ValidationUtil;
|
||||
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonArrayBuilder;
|
||||
import javax.json.JsonObjectBuilder;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Metadata REST resources.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
@Path("/metadata")
|
||||
public class MetadataResource extends BaseResource {
|
||||
/**
|
||||
* Returns the list of all configured metadata.
|
||||
*
|
||||
* @api {get} /metadata Get configured metadata
|
||||
* @apiName GetMetadata
|
||||
* @apiGroup Metadata
|
||||
* @apiParam {Number} sort_column Column index to sort on
|
||||
* @apiParam {Boolean} asc If true, sort in ascending order
|
||||
* @apiSuccess {Object[]} metadata List of metadata
|
||||
* @apiSuccess {String} metadata.id ID
|
||||
* @apiSuccess {String} metadata.name Name
|
||||
* @apiSuccess {String="STRING","INTEGER","FLOAT","DATE","BOOLEAN"} metadata.type Type
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiPermission user
|
||||
* @apiVersion 1.7.0
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
@GET
|
||||
public Response list(
|
||||
@QueryParam("sort_column") Integer sortColumn,
|
||||
@QueryParam("asc") Boolean asc) {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
JsonArrayBuilder metadata = Json.createArrayBuilder();
|
||||
SortCriteria sortCriteria = new SortCriteria(sortColumn, asc);
|
||||
|
||||
MetadataDao metadataDao = new MetadataDao();
|
||||
List<MetadataDto> metadataDtoList = metadataDao.findByCriteria(new MetadataCriteria(), sortCriteria);
|
||||
for (MetadataDto metadataDto : metadataDtoList) {
|
||||
metadata.add(Json.createObjectBuilder()
|
||||
.add("id", metadataDto.getId())
|
||||
.add("name", metadataDto.getName())
|
||||
.add("type", metadataDto.getType().name()));
|
||||
}
|
||||
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("metadata", metadata);
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a metadata.
|
||||
*
|
||||
* @api {put} /metadata Add a custom metadata
|
||||
* @apiName PutMetadata
|
||||
* @apiGroup Metadata
|
||||
* @apiParam {String{1..50}} name Name
|
||||
* @apiParam {String="STRING","INTEGER","FLOAT","DATE","BOOLEAN"} type Type
|
||||
* @apiSuccess {String} id ID
|
||||
* @apiSuccess {String} name Name
|
||||
* @apiSuccess {String="STRING","INTEGER","FLOAT","DATE","BOOLEAN"} type Type
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (client) ValidationError Validation error
|
||||
* @apiPermission admin
|
||||
* @apiVersion 1.7.0
|
||||
*
|
||||
* @param name Name
|
||||
* @param typeStr Type
|
||||
* @return Response
|
||||
*/
|
||||
@PUT
|
||||
public Response add(@FormParam("name") String name,
|
||||
@FormParam("type") String typeStr) {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Validate input data
|
||||
name = ValidationUtil.validateLength(name, "name", 1, 50, false);
|
||||
MetadataType type = MetadataType.valueOf(ValidationUtil.validateLength(typeStr, "type", 1, 20, false));
|
||||
|
||||
// Create the metadata
|
||||
MetadataDao metadataDao = new MetadataDao();
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setName(name);
|
||||
metadata.setType(type);
|
||||
metadataDao.create(metadata, principal.getId());
|
||||
|
||||
// Returns the metadata
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("id", metadata.getId())
|
||||
.add("name", metadata.getName())
|
||||
.add("type", metadata.getType().name());
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a metadata.
|
||||
*
|
||||
* @api {post} /metadata/:id Update a custom metadata
|
||||
* @apiName PostMetadataId
|
||||
* @apiGroup Metadata
|
||||
* @apiParam {String} id Metadata ID
|
||||
* @apiParam {String{1..50}} name Name
|
||||
* @apiSuccess {String} id ID
|
||||
* @apiSuccess {String} name Name
|
||||
* @apiSuccess {String="STRING","INTEGER","FLOAT","DATE","BOOLEAN"} type Type
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (client) ValidationError Validation error
|
||||
* @apiError (client) NotFound Metadata not found
|
||||
* @apiPermission admin
|
||||
* @apiVersion 1.7.0
|
||||
*
|
||||
* @param id ID
|
||||
* @param name Name
|
||||
* @return Response
|
||||
*/
|
||||
@POST
|
||||
@Path("{id: [a-z0-9\\-]+}")
|
||||
public Response update(@PathParam("id") String id,
|
||||
@FormParam("name") String name) {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Validate input data
|
||||
name = ValidationUtil.validateLength(name, "name", 1, 50, false);
|
||||
|
||||
// Get the metadata
|
||||
MetadataDao metadataDao = new MetadataDao();
|
||||
Metadata metadata = metadataDao.getActiveById(id);
|
||||
if (metadata == null) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// Update the metadata
|
||||
metadata.setName(name);
|
||||
metadataDao.update(metadata, principal.getId());
|
||||
|
||||
// Returns the metadata
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("id", metadata.getId())
|
||||
.add("name", metadata.getName())
|
||||
.add("type", metadata.getType().name());
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a metadata.
|
||||
*
|
||||
* @api {delete} /metadata/:id Delete a custom metadata
|
||||
* @apiName DeleteMetadataId
|
||||
* @apiGroup Metadata
|
||||
* @apiParam {String} id Metadata ID
|
||||
* @apiSuccess {String} status Status OK
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (client) NotFound Metadata not found
|
||||
* @apiPermission admin
|
||||
* @apiVersion 1.7.0
|
||||
*
|
||||
* @param id ID
|
||||
* @return Response
|
||||
*/
|
||||
@DELETE
|
||||
@Path("{id: [a-z0-9\\-]+}")
|
||||
public Response delete(@PathParam("id") String id) {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
|
||||
// Get the metadata
|
||||
MetadataDao metadataDao = new MetadataDao();
|
||||
Metadata metadata = metadataDao.getActiveById(id);
|
||||
if (metadata == null) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
// Delete the metadata
|
||||
metadataDao.delete(id, principal.getId());
|
||||
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("status", "ok");
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
}
|
||||
@@ -326,7 +326,7 @@ public class RouteResource extends BaseResource {
|
||||
|
||||
// Delete the route and the steps
|
||||
RouteDao routeDao = new RouteDao();
|
||||
routeDao.deleteRoute(routeStepDto.getRouteId());
|
||||
routeDao.deleteRoute(routeStepDto.getRouteId(), principal.getId());
|
||||
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
|
||||
@@ -155,7 +155,7 @@ public class TagResource extends BaseResource {
|
||||
* @apiSuccess {String} id Tag ID
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (client) ValidationError Validation error
|
||||
* @apiError (client) SpacesNotAllowed Spaces are not allowed in tag name
|
||||
* @apiError (client) IllegalTagName Spaces and colons are not allowed in tag name
|
||||
* @apiError (client) ParentNotFound Parent not found
|
||||
* @apiPermission user
|
||||
* @apiVersion 1.5.0
|
||||
@@ -177,12 +177,8 @@ public class TagResource extends BaseResource {
|
||||
// Validate input data
|
||||
name = ValidationUtil.validateLength(name, "name", 1, 36, false);
|
||||
ValidationUtil.validateHexColor(color, "color", true);
|
||||
|
||||
// Don't allow spaces
|
||||
if (name.contains(" ")) {
|
||||
throw new ClientException("SpacesNotAllowed", "Spaces are not allowed in tag name");
|
||||
}
|
||||
|
||||
ValidationUtil.validateTagName(name);
|
||||
|
||||
// Check the parent
|
||||
if (StringUtils.isEmpty(parentId)) {
|
||||
parentId = null;
|
||||
@@ -237,7 +233,7 @@ public class TagResource extends BaseResource {
|
||||
* @apiSuccess {String} id Tag ID
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (client) ValidationError Validation error
|
||||
* @apiError (client) SpacesNotAllowed Spaces are not allowed in tag name
|
||||
* @apiError (client) IllegalTagName Spaces and colons are not allowed in tag name
|
||||
* @apiError (client) ParentNotFound Parent not found
|
||||
* @apiError (client) CircularReference Circular reference in parent tag
|
||||
* @apiError (client) NotFound Tag not found
|
||||
@@ -263,12 +259,8 @@ public class TagResource extends BaseResource {
|
||||
// Validate input data
|
||||
name = ValidationUtil.validateLength(name, "name", 1, 36, true);
|
||||
ValidationUtil.validateHexColor(color, "color", true);
|
||||
|
||||
// Don't allow spaces
|
||||
if (name.contains(" ")) {
|
||||
throw new ClientException("SpacesNotAllowed", "Spaces are not allowed in tag name");
|
||||
}
|
||||
|
||||
ValidationUtil.validateTagName(name);
|
||||
|
||||
// Check permission
|
||||
AclDao aclDao = new AclDao();
|
||||
if (!aclDao.checkPermission(id, PermType.WRITE, getTargetIdList(null))) {
|
||||
|
||||
@@ -82,7 +82,7 @@ public class ThemeResource extends BaseResource {
|
||||
public Response get() {
|
||||
JsonObject themeConfig = getThemeConfig();
|
||||
JsonObjectBuilder json = Json.createObjectBuilder();
|
||||
json.add("name", themeConfig.getString("name", "Sismics Docs"));
|
||||
json.add("name", themeConfig.getString("name", "Teedy"));
|
||||
json.add("color", themeConfig.getString("color", "#ffffff"));
|
||||
json.add("css", themeConfig.getString("css", ""));
|
||||
return Response.ok().entity(json.build()).build();
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.sismics.docs.rest.resource;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.sismics.docs.core.constant.AclTargetType;
|
||||
import com.sismics.docs.core.constant.ConfigType;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.dao.*;
|
||||
@@ -15,6 +16,7 @@ import com.sismics.docs.core.event.PasswordLostEvent;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.*;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
import com.sismics.docs.core.util.RoutingUtil;
|
||||
import com.sismics.docs.core.util.authentication.AuthenticationUtil;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
import com.sismics.docs.rest.constant.BaseFunction;
|
||||
@@ -99,6 +101,7 @@ public class UserResource extends BaseResource {
|
||||
user.setPassword(password);
|
||||
user.setEmail(email);
|
||||
user.setStorageQuota(storageQuota);
|
||||
user.setOnboarding(true);
|
||||
|
||||
// Create the user
|
||||
UserDao userDao = new UserDao();
|
||||
@@ -434,6 +437,7 @@ public class UserResource extends BaseResource {
|
||||
* @apiGroup User
|
||||
* @apiSuccess {String} status Status OK
|
||||
* @apiError (client) ForbiddenError Access denied or the user cannot be deleted
|
||||
* @apiError (client) UserUsedInRouteModel The user is used in a route model
|
||||
* @apiPermission user
|
||||
* @apiVersion 1.5.0
|
||||
*
|
||||
@@ -449,6 +453,12 @@ public class UserResource extends BaseResource {
|
||||
if (hasBaseFunction(BaseFunction.ADMIN) || principal.isGuest()) {
|
||||
throw new ClientException("ForbiddenError", "This user cannot be deleted");
|
||||
}
|
||||
|
||||
// Check that this user is not used in any workflow
|
||||
String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.USER, principal.getName());
|
||||
if (routeModelName != null) {
|
||||
throw new ClientException("UserUsedInRouteModel", routeModelName);
|
||||
}
|
||||
|
||||
// Find linked data
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
@@ -493,6 +503,7 @@ public class UserResource extends BaseResource {
|
||||
* @apiSuccess {String} status Status OK
|
||||
* @apiError (client) ForbiddenError Access denied or the user cannot be deleted
|
||||
* @apiError (client) UserNotFound The user does not exist
|
||||
* @apiError (client) UserUsedInRouteModel The user is used in a route model
|
||||
* @apiPermission admin
|
||||
* @apiVersion 1.5.0
|
||||
*
|
||||
@@ -512,7 +523,7 @@ public class UserResource extends BaseResource {
|
||||
throw new ClientException("ForbiddenError", "The guest user cannot be deleted");
|
||||
}
|
||||
|
||||
// Check if the user exists
|
||||
// Check that the user exists
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getActiveByUsername(username);
|
||||
if (user == null) {
|
||||
@@ -525,6 +536,12 @@ public class UserResource extends BaseResource {
|
||||
if (baseFunctionSet.contains(BaseFunction.ADMIN.name())) {
|
||||
throw new ClientException("ForbiddenError", "The admin user cannot be deleted");
|
||||
}
|
||||
|
||||
// Check that this user is not used in any workflow
|
||||
String routeModelName = RoutingUtil.findRouteModelNameByTargetName(AclTargetType.USER, username);
|
||||
if (routeModelName != null) {
|
||||
throw new ClientException("UserUsedInRouteModel", routeModelName);
|
||||
}
|
||||
|
||||
// Find linked data
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
@@ -606,6 +623,7 @@ public class UserResource extends BaseResource {
|
||||
* @apiGroup User
|
||||
* @apiSuccess {Boolean} anonymous True if no user is connected
|
||||
* @apiSuccess {Boolean} is_default_password True if the admin has the default password
|
||||
* @apiSuccess {Boolean} onboarding True if the UI needs to display the onboarding
|
||||
* @apiSuccess {String} username Username
|
||||
* @apiSuccess {String} email E-mail
|
||||
* @apiSuccess {Number} storage_quota Storage quota (in bytes)
|
||||
@@ -649,8 +667,9 @@ public class UserResource extends BaseResource {
|
||||
.add("email", user.getEmail())
|
||||
.add("storage_quota", user.getStorageQuota())
|
||||
.add("storage_current", user.getStorageCurrent())
|
||||
.add("totp_enabled", user.getTotpKey() != null);
|
||||
|
||||
.add("totp_enabled", user.getTotpKey() != null)
|
||||
.add("onboarding", user.isOnboarding());
|
||||
|
||||
// Base functions
|
||||
JsonArrayBuilder baseFunctions = Json.createArrayBuilder();
|
||||
for (String baseFunction : ((UserPrincipal) principal).getBaseFunctionSet()) {
|
||||
@@ -882,6 +901,39 @@ public class UserResource extends BaseResource {
|
||||
.add("status", "ok");
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the onboarding experience as passed.
|
||||
*
|
||||
* @api {post} /user/onboarded Mark the onboarding experience as passed
|
||||
* @apiDescription Once the onboarding experience has been passed by the user, this resource prevent it from being displayed again.
|
||||
* @apiName PostUserOnboarded
|
||||
* @apiGroup User
|
||||
* @apiSuccess {String} status Status OK
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiPermission user
|
||||
* @apiVersion 1.7.0
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
@POST
|
||||
@Path("onboarded")
|
||||
public Response onboarded() {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// Save it
|
||||
UserDao userDao = new UserDao();
|
||||
User user = userDao.getActiveByUsername(principal.getName());
|
||||
user.setOnboarding(false);
|
||||
userDao.updateOnboarding(user);
|
||||
|
||||
// Always return OK
|
||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||
.add("status", "ok");
|
||||
return Response.ok().entity(response.build()).build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable time-based one-time password.
|
||||
|
||||
@@ -198,7 +198,7 @@ public class VocabularyResource extends BaseResource {
|
||||
/**
|
||||
* Delete a vocabulary entry.
|
||||
*
|
||||
* @api {delete} /vocabulary/:id Delete vocabulary entry
|
||||
* @api {delete} /vocabulary/:id Delete a vocabulary entry
|
||||
* @apiName DeleteVocabularyId
|
||||
* @apiGroup Vocabulary
|
||||
* @apiParam {String} id Entry ID
|
||||
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
@@ -5,7 +5,7 @@
|
||||
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
|
||||
version="3.0"
|
||||
metadata-complete="true">
|
||||
<display-name>Docs</display-name>
|
||||
<display-name>Teedy</display-name>
|
||||
|
||||
<!-- Proper loader/unloader of ImageIO plugins -->
|
||||
<listener>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
The web client and Android application for **Sismics Docs** are only examples
|
||||
The web client and Android application for **Teedy** are only examples
|
||||
of what is possible with the provided REST API. Everything you see in those apps are
|
||||
accessible using the API.
|
||||
|
||||
@@ -6,8 +6,8 @@ This documentation is divided in two parts. The first will get you started on es
|
||||
steps like authentication and the second part is a full reference of every endpoints.
|
||||
|
||||
## API URL
|
||||
The base URL depends on your server. If your instance of Docs is accessible through
|
||||
`https://docs.mycompany.com`, then the base API URL is `https://docs.mycompany.com/api`.
|
||||
The base URL depends on your server. If your instance of Teedy is accessible through
|
||||
`https://teedy.mycompany.com`, then the base API URL is `https://teedy.mycompany.com/api`.
|
||||
|
||||
## Verbs and status codes
|
||||
The API uses restful verbs.
|
||||
|
||||
735
docs-web/src/main/webapp/package-lock.json
generated
@@ -9,8 +9,8 @@
|
||||
"url": "git://github.com/sismics/docs.git"
|
||||
},
|
||||
"apidoc": {
|
||||
"name": "Sismics Docs API",
|
||||
"title": "Sismics Docs API",
|
||||
"name": "Teedy API",
|
||||
"title": "Teedy API",
|
||||
"url": "/api",
|
||||
"template": {
|
||||
"withCompare": false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Sismics Docs application.
|
||||
* Teedy application.
|
||||
*/
|
||||
angular.module('docs',
|
||||
// Dependencies
|
||||
@@ -145,6 +145,15 @@ angular.module('docs',
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('settings.metadata', {
|
||||
url: '/metadata',
|
||||
views: {
|
||||
'settings': {
|
||||
templateUrl: 'partial/docs/settings.metadata.html',
|
||||
controller: 'SettingsMetadata'
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('settings.user', {
|
||||
url: '/user',
|
||||
views: {
|
||||
@@ -411,9 +420,10 @@ angular.module('docs',
|
||||
prefix: 'locale/',
|
||||
suffix: '.json?@build.date@'
|
||||
})
|
||||
.registerAvailableLanguageKeys(['en', 'fr', 'de', 'ru', 'zh_CN', 'zh_TW'], {
|
||||
.registerAvailableLanguageKeys(['en', 'es', 'fr', 'de', 'ru', 'zh_CN', 'zh_TW'], {
|
||||
'ru_*': 'ru',
|
||||
'en_*': 'en',
|
||||
'es_*': 'es',
|
||||
'fr_*': 'fr',
|
||||
'de_*': 'de',
|
||||
'*': 'en'
|
||||
@@ -426,6 +436,9 @@ angular.module('docs',
|
||||
} else {
|
||||
// Or else determine the language based on the user's browser
|
||||
$translateProvider.determinePreferredLanguage();
|
||||
if (!$translateProvider.use()) {
|
||||
$translateProvider.use('en');
|
||||
}
|
||||
}
|
||||
|
||||
// Configuring Timago
|
||||
@@ -508,7 +521,10 @@ angular.module('docs',
|
||||
{ key: 'jpn', label: '日本語' },
|
||||
{ key: 'tha', label: 'ภาษาไทย' },
|
||||
{ key: 'kor', label: '한국어' },
|
||||
{ key: 'nld', label: 'Nederlands' }
|
||||
{ key: 'nld', label: 'Nederlands' },
|
||||
{ key: 'tur', label: 'Türkçe' },
|
||||
{ key: 'heb', label: 'עברית' },
|
||||
{ key: 'hun', label: 'Magyar' }
|
||||
];
|
||||
})
|
||||
/**
|
||||
|
||||
@@ -319,9 +319,9 @@ angular.module('docs').controller('Document', function ($scope, $rootScope, $tim
|
||||
*/
|
||||
$scope.extractNavigatedTag = function () {
|
||||
// Find the current tag in the search query
|
||||
var tagFound = /tag:([^ ]*)/.exec($scope.search);
|
||||
var tagFound = /(^| )tag:([^ ]*)/.exec($scope.search);
|
||||
if (tagFound) {
|
||||
tagFound = tagFound[1];
|
||||
tagFound = tagFound[2];
|
||||
// We search only for exact match
|
||||
$scope.navigatedTag = _.findWhere($scope.tags, { name: tagFound });
|
||||
} else {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
/**
|
||||
* Document default controller.
|
||||
*/
|
||||
angular.module('docs').controller('DocumentDefault', function ($scope, $rootScope, $state, Restangular, Upload, $translate, $uibModal, $dialog) {
|
||||
angular.module('docs').controller('DocumentDefault', function ($scope, $rootScope, $state, Restangular, Upload, $translate, $uibModal, $dialog, User) {
|
||||
// Load user audit log
|
||||
Restangular.one('auditlog').get().then(function (data) {
|
||||
$scope.logs = data.logs;
|
||||
@@ -13,7 +13,6 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
|
||||
$scope.loadFiles = function () {
|
||||
Restangular.one('file/list').get().then(function (data) {
|
||||
$scope.files = data.files;
|
||||
// TODO Keep currently uploading files
|
||||
});
|
||||
};
|
||||
$scope.loadFiles();
|
||||
@@ -121,7 +120,7 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
|
||||
}
|
||||
|
||||
Restangular.withConfig(function (RestangularConfigurer) {
|
||||
RestangularConfigurer.setBaseUrl('https://api.sismicsdocs.com');
|
||||
RestangularConfigurer.setBaseUrl('https://api.teedy.io');
|
||||
}).one('api').post('feedback', {
|
||||
content: content
|
||||
}).then(function () {
|
||||
@@ -145,48 +144,51 @@ angular.module('docs').controller('DocumentDefault', function ($scope, $rootScop
|
||||
|
||||
// Onboarding
|
||||
$translate('onboarding.step1.title').then(function () {
|
||||
if (localStorage.onboardingDisplayed || $(window).width() < 1000) {
|
||||
return;
|
||||
}
|
||||
localStorage.onboardingDisplayed = true;
|
||||
|
||||
$rootScope.onboardingEnabled = true;
|
||||
|
||||
$rootScope.onboardingSteps = [
|
||||
{
|
||||
title: $translate.instant('onboarding.step1.title'),
|
||||
description: $translate.instant('onboarding.step1.description'),
|
||||
position: 'centered',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: $translate.instant('onboarding.step2.title'),
|
||||
description: $translate.instant('onboarding.step2.description'),
|
||||
attachTo: '#document-add-btn',
|
||||
position: 'right',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: $translate.instant('onboarding.step3.title'),
|
||||
description: $translate.instant('onboarding.step3.description'),
|
||||
attachTo: '#quick-upload-zone',
|
||||
position: 'left',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: $translate.instant('onboarding.step4.title'),
|
||||
description: $translate.instant('onboarding.step4.description'),
|
||||
attachTo: '#search-box',
|
||||
position: 'right',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: $translate.instant('onboarding.step5.title'),
|
||||
description: $translate.instant('onboarding.step5.description'),
|
||||
attachTo: '#navigation-tag',
|
||||
position: "right",
|
||||
width: 300
|
||||
User.userInfo().then(function(userData) {
|
||||
if (!userData.onboarding || $(window).width() < 1000) {
|
||||
return;
|
||||
}
|
||||
];
|
||||
Restangular.one('user').post('onboarded');
|
||||
$rootScope.userInfo.onboarding = false;
|
||||
|
||||
$rootScope.onboardingEnabled = true;
|
||||
|
||||
$rootScope.onboardingSteps = [
|
||||
{
|
||||
title: $translate.instant('onboarding.step1.title'),
|
||||
description: $translate.instant('onboarding.step1.description'),
|
||||
position: 'centered',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: $translate.instant('onboarding.step2.title'),
|
||||
description: $translate.instant('onboarding.step2.description'),
|
||||
attachTo: '#document-add-btn',
|
||||
position: 'right',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: $translate.instant('onboarding.step3.title'),
|
||||
description: $translate.instant('onboarding.step3.description'),
|
||||
attachTo: '#quick-upload-zone',
|
||||
position: 'left',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: $translate.instant('onboarding.step4.title'),
|
||||
description: $translate.instant('onboarding.step4.description'),
|
||||
attachTo: '#search-box',
|
||||
position: 'right',
|
||||
width: 300
|
||||
},
|
||||
{
|
||||
title: $translate.instant('onboarding.step5.title'),
|
||||
description: $translate.instant('onboarding.step5.description'),
|
||||
attachTo: '#navigation-tag',
|
||||
position: "right",
|
||||
width: 300
|
||||
}
|
||||
];
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -59,9 +59,18 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
|
||||
$scope.document = {
|
||||
tags: [],
|
||||
relations: [],
|
||||
language: language
|
||||
language: language,
|
||||
metadata: []
|
||||
};
|
||||
|
||||
// Get custom metadata list
|
||||
Restangular.one('metadata').get({
|
||||
sort_column: 1,
|
||||
asc: true
|
||||
}).then(function(data) {
|
||||
$scope.document.metadata = data.metadata;
|
||||
});
|
||||
|
||||
if ($scope.navigatedTag) {
|
||||
$scope.document.tags.push($scope.navigatedTag);
|
||||
}
|
||||
@@ -92,7 +101,21 @@ angular.module('docs').controller('DocumentEdit', function($rootScope, $scope, $
|
||||
|
||||
// Extract ids from relations (only when our document is the source)
|
||||
document.relations = _.pluck(_.where(document.relations, { source: true }), 'id');
|
||||
|
||||
|
||||
// Extract custom metadata values
|
||||
var metadata = _.reject(document.metadata, function (meta) {
|
||||
return _.isUndefined(meta.value) || meta.value === '' || meta.value == null;
|
||||
});
|
||||
document.metadata_id = _.pluck(metadata, 'id');
|
||||
document.metadata_value = _.pluck(metadata, 'value');
|
||||
document.metadata_value = _.map(document.metadata_value, function (val) {
|
||||
if (val instanceof Date) {
|
||||
return val.getTime();
|
||||
}
|
||||
return val;
|
||||
});
|
||||
|
||||
// Send to server
|
||||
if ($scope.isEdit()) {
|
||||
promise = Restangular.one('document', $stateParams.id).post('', document);
|
||||
} else {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
angular.module('docs').controller('DocumentViewContent', function ($scope, $rootScope, $stateParams, Restangular, $dialog, $state, Upload, $translate, $uibModal) {
|
||||
$scope.displayMode = _.isUndefined(localStorage.fileDisplayMode) ? 'grid' : localStorage.fileDisplayMode;
|
||||
$scope.openedFile = undefined;
|
||||
|
||||
/**
|
||||
* Watch for display mode change.
|
||||
@@ -20,7 +21,15 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
|
||||
forceHelperSize: true,
|
||||
forcePlaceholderSize: true,
|
||||
tolerance: 'pointer',
|
||||
start: function() {
|
||||
$(this).addClass('currently-dragging');
|
||||
},
|
||||
stop: function () {
|
||||
var _this = this;
|
||||
setTimeout(function(){
|
||||
$(_this).removeClass('currently-dragging');
|
||||
}, 300);
|
||||
|
||||
// Send new positions to server
|
||||
$scope.$apply(function () {
|
||||
Restangular.one('file').post('reorder', {
|
||||
@@ -37,7 +46,6 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
|
||||
$scope.loadFiles = function () {
|
||||
Restangular.one('file/list').get({ id: $stateParams.id }).then(function (data) {
|
||||
$scope.files = data.files;
|
||||
// TODO Keep currently uploading files
|
||||
});
|
||||
};
|
||||
$scope.loadFiles();
|
||||
@@ -45,8 +53,11 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
|
||||
/**
|
||||
* Navigate to the selected file.
|
||||
*/
|
||||
$scope.openFile = function (file) {
|
||||
$state.go('document.view.content.file', { id: $stateParams.id, fileId: file.id })
|
||||
$scope.openFile = function (file, $event) {
|
||||
if ($($event.target).parents('.currently-dragging').length === 0) {
|
||||
$scope.openedFile = file;
|
||||
$state.go('document.view.content.file', { id: $stateParams.id, fileId: file.id });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -101,6 +112,9 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
|
||||
}
|
||||
|
||||
if (files && files.length) {
|
||||
// Sort by filename
|
||||
files = _.sortBy(files, 'name');
|
||||
|
||||
// Adding files to the UI
|
||||
var newfiles = [];
|
||||
_.each(files, function (file) {
|
||||
@@ -195,10 +209,14 @@ angular.module('docs').controller('DocumentViewContent', function ($scope, $root
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Open versions history.
|
||||
*/
|
||||
$scope.openVersions = function (file) {
|
||||
$uibModal.open({
|
||||
templateUrl: 'partial/docs/file.versions.html',
|
||||
controller: 'ModalFileVersions',
|
||||
size: 'lg',
|
||||
resolve: {
|
||||
file: function () {
|
||||
return file;
|
||||
|
||||
@@ -29,7 +29,7 @@ angular.module('docs').controller('SettingsConfig', function($scope, $rootScope,
|
||||
|
||||
// Update the theme
|
||||
$scope.update = function () {
|
||||
$scope.theme.name = $scope.theme.name.length === 0 ? 'Sismics Docs' : $scope.theme.name;
|
||||
$scope.theme.name = $scope.theme.name.length === 0 ? 'Teedy' : $scope.theme.name;
|
||||
Restangular.one('theme').post('', $scope.theme).then(function () {
|
||||
var stylesheet = $('#theme-stylesheet')[0];
|
||||
stylesheet.href = stylesheet.href.replace(/\?.*|$/, '?' + new Date().getTime());
|
||||
|
||||
@@ -46,12 +46,17 @@ angular.module('docs').controller('SettingsGroupEdit', function($scope, $dialog,
|
||||
$state.go('settings.group.edit', { name: group.name });
|
||||
}
|
||||
}, function (e) {
|
||||
if (e.data.type === 'GroupAlreadyExists') {
|
||||
var title = $translate.instant('settings.group.edit.edit_group_failed_title');
|
||||
var msg = $translate.instant('settings.group.edit.edit_group_failed_message');
|
||||
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
|
||||
$dialog.messageBox(title, msg, btns);
|
||||
}
|
||||
if (e.data.type === 'GroupAlreadyExists') {
|
||||
var title = $translate.instant('settings.group.edit.edit_group_failed_title');
|
||||
var msg = $translate.instant('settings.group.edit.edit_group_failed_message');
|
||||
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
|
||||
$dialog.messageBox(title, msg, btns);
|
||||
} else if (e.data.type === 'GroupUsedInRouteModel') {
|
||||
var title = $translate.instant('settings.group.edit.group_used_title');
|
||||
var msg = $translate.instant('settings.group.edit.group_used_message', { name: e.data.message });
|
||||
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
|
||||
$dialog.messageBox(title, msg, btns);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -71,8 +76,13 @@ angular.module('docs').controller('SettingsGroupEdit', function($scope, $dialog,
|
||||
Restangular.one('group', $stateParams.name).remove().then(function() {
|
||||
$scope.loadGroups();
|
||||
$state.go('settings.group');
|
||||
}, function() {
|
||||
$state.go('settings.group');
|
||||
}, function(e) {
|
||||
if (e.data.type === 'GroupUsedInRouteModel') {
|
||||
var title = $translate.instant('settings.group.edit.group_used_title');
|
||||
var msg = $translate.instant('settings.group.edit.group_used_message', { name: e.data.message });
|
||||
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
|
||||
$dialog.messageBox(title, msg, btns);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Settings metadata page controller.
|
||||
*/
|
||||
angular.module('docs').controller('SettingsMetadata', function($scope, Restangular) {
|
||||
// Load metadata
|
||||
Restangular.one('metadata').get({
|
||||
sort_column: 1,
|
||||
asc: true
|
||||
}).then(function(data) {
|
||||
$scope.metadata = data.metadata;
|
||||
});
|
||||
|
||||
// Add a metadata
|
||||
$scope.addMetadata = function() {
|
||||
Restangular.one('metadata').put($scope.newmetadata).then(function(data) {
|
||||
$scope.metadata.push(data);
|
||||
$scope.newmetadata = {};
|
||||
});
|
||||
};
|
||||
|
||||
// Delete a metadata
|
||||
$scope.deleteMetadata = function(meta) {
|
||||
Restangular.one('metadata', meta.id).remove().then(function() {
|
||||
$scope.metadata.splice($scope.metadata.indexOf(meta), 1);
|
||||
});
|
||||
};
|
||||
|
||||
// Update a metadata
|
||||
$scope.updateMetadata = function(meta) {
|
||||
Restangular.one('metadata', meta.id).post('', meta);
|
||||
};
|
||||
});
|
||||
@@ -70,8 +70,13 @@ angular.module('docs').controller('SettingsUserEdit', function($scope, $dialog,
|
||||
Restangular.one('user', $stateParams.username).remove().then(function () {
|
||||
$scope.loadUsers();
|
||||
$state.go('settings.user');
|
||||
}, function() {
|
||||
$state.go('settings.user');
|
||||
}, function(e) {
|
||||
if (e.data.type === 'UserUsedInRouteModel') {
|
||||
var title = $translate.instant('settings.user.edit.user_used_title');
|
||||
var msg = $translate.instant('settings.user.edit.user_used_message', { name: e.data.message });
|
||||
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
|
||||
$dialog.messageBox(title, msg, btns);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Add space between element directive.
|
||||
*/
|
||||
angular.module('docs').directive('addSpaceBetween', function () {
|
||||
return function (scope, element) {
|
||||
if(!scope.$last) {
|
||||
element.after(' ');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Invert text color for more legibility directive.
|
||||
*/
|
||||
angular.module('docs').directive('invertTextColor', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
attrs.$observe('invertTextColor', function(hex) {
|
||||
if (!hex || hex.length !== 7) {
|
||||
return;
|
||||
}
|
||||
|
||||
hex = hex.slice(1);
|
||||
var r = parseInt(hex.slice(0, 2), 16),
|
||||
g = parseInt(hex.slice(2, 4), 16),
|
||||
b = parseInt(hex.slice(4, 6), 16);
|
||||
element.css('color', (r * 0.299 + g * 0.587 + b * 0.114) > 186
|
||||
? '#000000'
|
||||
: '#FFFFFF');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -9,6 +9,7 @@ angular.module('docs').directive('selectRelation', function() {
|
||||
templateUrl: 'partial/docs/directive.selectrelation.html',
|
||||
replace: true,
|
||||
scope: {
|
||||
id: '=',
|
||||
relations: '=',
|
||||
ref: '@',
|
||||
ngDisabled: '='
|
||||
@@ -18,21 +19,12 @@ angular.module('docs').directive('selectRelation', function() {
|
||||
* Add a relation.
|
||||
*/
|
||||
$scope.addRelation = function($item) {
|
||||
// Does the new relation is already in the model
|
||||
var duplicate = _.find($scope.relations, function(relation) {
|
||||
if ($item.id === relation.id) {
|
||||
return relation;
|
||||
}
|
||||
});
|
||||
|
||||
// Add the new relation
|
||||
if (!duplicate) {
|
||||
$scope.relations.push({
|
||||
id: $item.id,
|
||||
title: $item.title,
|
||||
source: true
|
||||
});
|
||||
}
|
||||
$scope.relations.push({
|
||||
id: $item.id,
|
||||
title: $item.title,
|
||||
source: true
|
||||
});
|
||||
$scope.input = '';
|
||||
};
|
||||
|
||||
@@ -42,11 +34,11 @@ angular.module('docs').directive('selectRelation', function() {
|
||||
$scope.deleteRelation = function(deleteRelation) {
|
||||
$scope.relations = _.reject($scope.relations, function(relation) {
|
||||
return relation.id === deleteRelation.id;
|
||||
})
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a promise for typeahead title.
|
||||
* Returns a promise for typeahead document.
|
||||
*/
|
||||
$scope.getDocumentTypeahead = function($viewValue) {
|
||||
var deferred = $q.defer();
|
||||
@@ -57,8 +49,16 @@ angular.module('docs').directive('selectRelation', function() {
|
||||
asc: true,
|
||||
search: $viewValue
|
||||
}).then(function(data) {
|
||||
deferred.resolve(data.documents);
|
||||
});
|
||||
deferred.resolve(_.reject(data.documents, function(document) {
|
||||
var duplicate = _.find($scope.relations, function(relation) {
|
||||
if (document.id === relation.id) {
|
||||
return relation;
|
||||
}
|
||||
});
|
||||
|
||||
return document.id === $scope.id || duplicate;
|
||||
}));
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
},
|
||||
|
||||
@@ -61,9 +61,10 @@ angular.module('share',
|
||||
prefix: 'locale/',
|
||||
suffix: '.json?@build.date@'
|
||||
})
|
||||
.registerAvailableLanguageKeys(['en', 'fr', 'de', 'ru', 'zh_CN', 'zh_TW'], {
|
||||
.registerAvailableLanguageKeys(['en', 'es', 'fr', 'de', 'ru', 'zh_CN', 'zh_TW'], {
|
||||
'ru_*': 'ru',
|
||||
'en_*': 'en',
|
||||
'es_*': 'es',
|
||||
'fr_*': 'fr',
|
||||
'de_*': 'de',
|
||||
'*': 'en'
|
||||
|
||||
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 26 KiB |