diff --git a/README.md b/README.md index ff520de6..c9970c9b 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,10 @@ Features - Full text search in image and PDF - SHA-256 encryption - Tag system -- Multi-users -- Document sharing +- Multi-users ACL system +- Document sharing by URL - RESTful Web API +- Modern Android client Download -------- diff --git a/docs-android/app/app.iml b/docs-android/app/app.iml index 999cb6ee..d5ce36f5 100644 --- a/docs-android/app/app.iml +++ b/docs-android/app/app.iml @@ -75,6 +75,12 @@ + + + + + + @@ -100,16 +106,16 @@ + - - + + - \ No newline at end of file diff --git a/docs-android/app/build.gradle b/docs-android/app/build.gradle index e1f591d4..c9195ab8 100644 --- a/docs-android/app/build.gradle +++ b/docs-android/app/build.gradle @@ -3,7 +3,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:1.1.0' + classpath 'com.android.tools.build:gradle:1.2.3' } } apply plugin: 'com.android.application' @@ -28,6 +28,21 @@ android { targetCompatibility JavaVersion.VERSION_1_7 } + signingConfigs { + release { + storeFile file(System.getenv("TRACKINO_STORE_PATH")) + storePassword System.getenv("TRACKINO_STORE_PASS") + keyAlias System.getenv("TRACKINO_STORE_ALIAS") + keyPassword System.getenv("TRACKINO_STORE_KEYPASS") + } + } + + buildTypes { + release { + signingConfig signingConfigs.release + } + } + lintOptions { abortOnError false } @@ -35,7 +50,7 @@ android { dependencies { compile fileTree(dir: 'libs', include: '*.jar') - compile 'com.android.support:appcompat-v7:22.1.0' + compile 'com.android.support:appcompat-v7:22.1.1' compile 'com.android.support:recyclerview-v7:22.0.0' compile 'com.loopj.android:android-async-http:1.4.6' compile 'it.sephiroth.android.library.imagezoom:imagezoom:1.0.5' diff --git a/docs-android/app/src/main/AndroidManifest.xml b/docs-android/app/src/main/AndroidManifest.xml index b38e3f46..ff234698 100644 --- a/docs-android/app/src/main/AndroidManifest.xml +++ b/docs-android/app/src/main/AndroidManifest.xml @@ -16,7 +16,8 @@ android:theme="@style/AppTheme" > + android:label="@string/app_name" + android:theme="@style/AppThemeDark"> @@ -25,7 +26,8 @@ + android:launchMode="singleTop" + android:windowSoftInputMode="adjustNothing"> diff --git a/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentEditActivity.java b/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentEditActivity.java index 56cde065..cea2d4f4 100644 --- a/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentEditActivity.java +++ b/docs-android/app/src/main/java/com/sismics/docs/activity/DocumentEditActivity.java @@ -84,8 +84,10 @@ public class DocumentEditActivity extends AppCompatActivity { // Setup the activity setContentView(R.layout.document_edit_activity); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setHomeButtonEnabled(true); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeButtonEnabled(true); + } languageSpinner = (Spinner) findViewById(R.id.languageSpinner); tagsEditText = (TagsCompleteTextView) findViewById(R.id.tagsEditText); datePickerView = (DatePickerView) findViewById(R.id.dateEditText); @@ -93,7 +95,7 @@ public class DocumentEditActivity extends AppCompatActivity { descriptionEditText = (EditText) findViewById(R.id.descriptionEditText); // Language spinner - LanguageAdapter languageAdapter = new LanguageAdapter(this); + LanguageAdapter languageAdapter = new LanguageAdapter(this, false); languageSpinner.setAdapter(languageAdapter); // Tags auto-complete diff --git a/docs-android/app/src/main/java/com/sismics/docs/activity/MainActivity.java b/docs-android/app/src/main/java/com/sismics/docs/activity/MainActivity.java index b013f29b..3d0e49c7 100644 --- a/docs-android/app/src/main/java/com/sismics/docs/activity/MainActivity.java +++ b/docs-android/app/src/main/java/com/sismics/docs/activity/MainActivity.java @@ -9,19 +9,21 @@ import android.provider.SearchRecentSuggestions; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.SearchView; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; +import android.widget.SearchView; import android.widget.TextView; import com.androidquery.util.AQUtility; import com.sismics.docs.R; import com.sismics.docs.adapter.TagListAdapter; +import com.sismics.docs.event.AdvancedSearchEvent; import com.sismics.docs.event.SearchEvent; +import com.sismics.docs.fragment.SearchFragment; import com.sismics.docs.listener.JsonHttpResponseHandler; import com.sismics.docs.model.application.ApplicationContext; import com.sismics.docs.provider.RecentSuggestionsProvider; @@ -137,6 +139,8 @@ public class MainActivity extends AppCompatActivity { }); handleIntent(getIntent()); + + EventBus.getDefault().register(this); } @Override @@ -154,6 +158,11 @@ public class MainActivity extends AppCompatActivity { }); return true; + case R.id.advanced_search: + SearchFragment dialog = SearchFragment.newInstance(); + dialog.show(getSupportFragmentManager(), "SearchFragment"); + return true; + case R.id.settings: startActivity(new Intent(MainActivity.this, SettingsActivity.class)); return true; @@ -253,8 +262,18 @@ public class MainActivity extends AppCompatActivity { drawerLayout.closeDrawers(); } + /** + * An advanced search event has been fired. + * + * @param event Advanced search event + */ + public void onEventMainThread(AdvancedSearchEvent event) { + searchQuery(event.getQuery()); + } + @Override protected void onDestroy() { + EventBus.getDefault().unregister(this); if(isTaskRoot()) { int cacheSizeMb = PreferenceUtil.getIntegerPreference(this, PreferenceUtil.PREF_CACHE_SIZE, 10); AQUtility.cleanCacheAsync(this, cacheSizeMb * 1000000, cacheSizeMb * 1000000); diff --git a/docs-android/app/src/main/java/com/sismics/docs/adapter/LanguageAdapter.java b/docs-android/app/src/main/java/com/sismics/docs/adapter/LanguageAdapter.java index f2d481d3..f5b74788 100644 --- a/docs-android/app/src/main/java/com/sismics/docs/adapter/LanguageAdapter.java +++ b/docs-android/app/src/main/java/com/sismics/docs/adapter/LanguageAdapter.java @@ -25,9 +25,12 @@ public class LanguageAdapter extends BaseAdapter { private List languageList; - public LanguageAdapter(Context context) { + public LanguageAdapter(Context context, boolean noValue) { this.context = context; this.languageList = new ArrayList<>(); + if (noValue) { + languageList.add(new Language("", R.string.all_languages, 0)); + } languageList.add(new Language("fra", R.string.language_french, R.drawable.fra)); languageList.add(new Language("eng", R.string.language_english, R.drawable.eng)); languageList.add(new Language("jpn", R.string.language_japanese, R.drawable.jpn)); diff --git a/docs-android/app/src/main/java/com/sismics/docs/event/AdvancedSearchEvent.java b/docs-android/app/src/main/java/com/sismics/docs/event/AdvancedSearchEvent.java new file mode 100644 index 00000000..c463f28e --- /dev/null +++ b/docs-android/app/src/main/java/com/sismics/docs/event/AdvancedSearchEvent.java @@ -0,0 +1,31 @@ +package com.sismics.docs.event; + +/** + * Advanced search event. + * + * @author bgamard. + */ +public class AdvancedSearchEvent { + /** + * Search query. + */ + private String query; + + /** + * Create an advanced search event. + * + * @param query Query + */ + public AdvancedSearchEvent(String query) { + this.query = query; + } + + /** + * Getter of query. + * + * @return query + */ + public String getQuery() { + return query; + } +} diff --git a/docs-android/app/src/main/java/com/sismics/docs/fragment/SearchFragment.java b/docs-android/app/src/main/java/com/sismics/docs/fragment/SearchFragment.java new file mode 100644 index 00000000..93ead327 --- /dev/null +++ b/docs-android/app/src/main/java/com/sismics/docs/fragment/SearchFragment.java @@ -0,0 +1,128 @@ +package com.sismics.docs.fragment; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Spinner; + +import com.sismics.docs.R; +import com.sismics.docs.adapter.LanguageAdapter; +import com.sismics.docs.adapter.TagAutoCompleteAdapter; +import com.sismics.docs.event.AdvancedSearchEvent; +import com.sismics.docs.ui.view.DatePickerView; +import com.sismics.docs.ui.view.TagsCompleteTextView; +import com.sismics.docs.util.PreferenceUtil; +import com.sismics.docs.util.SearchQueryBuilder; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +import de.greenrobot.event.EventBus; + +/** + * Advanced search fragment. + * + * @author bgamard. + */ +public class SearchFragment extends DialogFragment { + /** + * Document sharing dialog fragment + */ + public static SearchFragment newInstance() { + SearchFragment fragment = new SearchFragment(); + Bundle args = new Bundle(); + fragment.setArguments(args); + return fragment; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + // Setup the view + LayoutInflater inflater = getActivity().getLayoutInflater(); + View view = inflater.inflate(R.layout.search_dialog, null); + final EditText searchEditText = (EditText) view.findViewById(R.id.searchEditText); + final EditText fulltextEditText = (EditText) view.findViewById(R.id.fulltextEditText); + final CheckBox sharedCheckbox = (CheckBox) view.findViewById(R.id.sharedCheckbox); + final Spinner languageSpinner = (Spinner) view.findViewById(R.id.languageSpinner); + final DatePickerView beforeDatePicker = (DatePickerView) view.findViewById(R.id.beforeDatePicker); + final DatePickerView afterDatePicker = (DatePickerView) view.findViewById(R.id.afterDatePicker); + final TagsCompleteTextView tagsEditText = (TagsCompleteTextView) view.findViewById(R.id.tagsEditText); + + // Language spinner + LanguageAdapter languageAdapter = new LanguageAdapter(getActivity(), true); + languageSpinner.setAdapter(languageAdapter); + + // Tags auto-complete + JSONObject tags = PreferenceUtil.getCachedJson(getActivity(), PreferenceUtil.PREF_CACHED_TAGS_JSON); + if (tags == null) { + Dialog dialog = builder.create(); + dialog.cancel(); + return dialog; + } + JSONArray tagArray = tags.optJSONArray("stats"); + + List tagList = new ArrayList<>(); + for (int i = 0; i < tagArray.length(); i++) { + tagList.add(tagArray.optJSONObject(i)); + } + + tagsEditText.allowDuplicates(false); + tagsEditText.setAdapter(new TagAutoCompleteAdapter(getActivity(), 0, tagList)); + + // Build the dialog + builder.setView(view) + .setPositiveButton(R.string.search, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // Build the simple criterias + SearchQueryBuilder queryBuilder = new SearchQueryBuilder() + .simpleSearch(searchEditText.getText().toString()) + .shared(sharedCheckbox.isChecked()) + .language(((LanguageAdapter.Language) languageSpinner.getSelectedItem()).getId()) + .before(beforeDatePicker.getDate()) + .after(afterDatePicker.getDate()); + + // Fulltext criteria + String fulltextCriteria = fulltextEditText.getText().toString(); + if (!fulltextCriteria.trim().isEmpty()) { + String[] criterias = fulltextCriteria.split(" "); + for (String criteria : criterias) { + queryBuilder.fulltextSearch(criteria); + } + } + + // Tags criteria + for (Object object : tagsEditText.getObjects()) { + JSONObject tag = (JSONObject) object; + queryBuilder.tag(tag.optString("name")); + } + + // Send the advanced search event + EventBus.getDefault().post(new AdvancedSearchEvent(queryBuilder.build())); + + getDialog().cancel(); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + getDialog().cancel(); + } + }); + Dialog dialog = builder.create(); + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + return dialog; + } +} diff --git a/docs-android/app/src/main/java/com/sismics/docs/util/SearchQueryBuilder.java b/docs-android/app/src/main/java/com/sismics/docs/util/SearchQueryBuilder.java new file mode 100644 index 00000000..54ab1f94 --- /dev/null +++ b/docs-android/app/src/main/java/com/sismics/docs/util/SearchQueryBuilder.java @@ -0,0 +1,151 @@ +package com.sismics.docs.util; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Search query builder. + * + * @author bgamard. + */ +public class SearchQueryBuilder { + /** + * The query. + */ + private StringBuilder query; + + /** + * Search separator. + */ + private static String SEARCH_SEPARATOR = " "; + + /** + * Search date format. + */ + private SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); + + /** + * Build a query. + */ + public SearchQueryBuilder() { + query = new StringBuilder(); + } + + /** + * Add a simple search criteria. + * + * @param simpleSearch Simple search criteria + * @return The builder + */ + public SearchQueryBuilder simpleSearch(String simpleSearch) { + if (isValid(simpleSearch)) { + query.append(SEARCH_SEPARATOR).append(simpleSearch); + } + return this; + } + + /** + * Add a fulltext search criteria. + * + * @param fulltextSearch Fulltext search criteria + * @return The builder + */ + public SearchQueryBuilder fulltextSearch(String fulltextSearch) { + if (isValid(fulltextSearch)) { + query.append(SEARCH_SEPARATOR) + .append("full:") + .append(fulltextSearch); + } + return this; + } + + /** + * Add a language criteria. + * + * @param language Language criteria + * @return The builder + */ + public SearchQueryBuilder language(String language) { + if (isValid(language)) { + query.append(SEARCH_SEPARATOR) + .append("lang:") + .append(language); + } + return this; + } + + /** + * Add a shared criteria. + * + * @param shared Shared criteria + * @return The builder + */ + public SearchQueryBuilder shared(boolean shared) { + if (shared) { + query.append(SEARCH_SEPARATOR).append("shared:yes"); + } + return this; + } + + /** + * Add a tag criteria. + * + * @param tag Tag criteria + * @return The builder + */ + public SearchQueryBuilder tag(String tag) { + query.append(SEARCH_SEPARATOR) + .append("tag:") + .append(tag); + return this; + } + + /** + * Add a before date criteria. + * + * @param before Before date criteria + * @return The builder + */ + public SearchQueryBuilder before(Date before) { + if (before != null) { + query.append(SEARCH_SEPARATOR) + .append("before:") + .append(DATE_FORMAT.format(before)); + } + return this; + } + + /** + * Add an after date criteria. + * + * @param after After date criteria + * @return The builder + */ + public SearchQueryBuilder after(Date after) { + if (after != null) { + query.append(SEARCH_SEPARATOR) + .append("after:") + .append(DATE_FORMAT.format(after)); + } + return this; + } + + /** + * Build the query. + * + * @return The query + */ + public String build() { + return query.toString(); + } + + /** + * Return true if the search criteria is valid. + * + * @param criteria Search criteria + * @return True if the search criteria is valid + */ + private boolean isValid(String criteria) { + return criteria != null && !criteria.trim().isEmpty(); + } +} diff --git a/docs-android/app/src/main/res/layout/document_edit_activity.xml b/docs-android/app/src/main/res/layout/document_edit_activity.xml index f9b775be..8ee3c76e 100644 --- a/docs-android/app/src/main/res/layout/document_edit_activity.xml +++ b/docs-android/app/src/main/res/layout/document_edit_activity.xml @@ -10,8 +10,8 @@ android:layout_height="wrap_content" android:layout_margin="8dp" android:padding="16dp" - android:textSize="24dp" - android:hint="Title"/> + android:textSize="24sp" + android:hint="@string/title"/> @@ -30,7 +30,7 @@ android:orientation="horizontal"> @@ -63,6 +62,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" - android:hint="Add tags" + android:hint="@string/add_tags" android:layout_margin="8dp"/> \ No newline at end of file diff --git a/docs-android/app/src/main/res/layout/login_activity.xml b/docs-android/app/src/main/res/layout/login_activity.xml index 6c04d955..eae776b3 100644 --- a/docs-android/app/src/main/res/layout/login_activity.xml +++ b/docs-android/app/src/main/res/layout/login_activity.xml @@ -2,110 +2,80 @@ + android:orientation="vertical" + android:background="@color/colorPrimaryDark"> - + android:visibility="visible" + android:padding="40dp" + android:orientation="vertical"> - + + + + + + + + + + + + + + + Edit @@ -10,17 +10,17 @@ - -

-
-
-
-
- - - -
-
-
+ + + + Content + + +

+ +
+
+
+
+ + + +
+
+
+
+
+ +
+
+
-
- + +
+

+ {{ file.status }} +

+
+ +
-
-
-
-

- {{ file.status }} +

+ + Drag & drop files here to upload

-
- -
+ + + + + Permissions + + + + + + + + + + + + +
ForPermission
{{ acl[0].type == 'SHARE' ? 'Shared' : 'User' }} {{ acl[0].name }} + + {{ a.perm }} + + +
+ +
+

Add a permission

+ +
+
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+
+ +
+
+
+
+
+ -

- - Drag & drop files here to upload -

-
-
-
\ No newline at end of file diff --git a/docs-web/src/main/webapp/src/style/main.less b/docs-web/src/main/webapp/src/style/main.less index 39551d4f..1ba0630a 100644 --- a/docs-web/src/main/webapp/src/style/main.less +++ b/docs-web/src/main/webapp/src/style/main.less @@ -179,4 +179,8 @@ input[readonly].share-link { .pointer { cursor: pointer; +} + +.tab-pane { + margin-top: 20px; } \ No newline at end of file diff --git a/docs-web/src/prod/resources/config.properties b/docs-web/src/prod/resources/config.properties index 1bfb8fb0..44ddb414 100644 --- a/docs-web/src/prod/resources/config.properties +++ b/docs-web/src/prod/resources/config.properties @@ -1,3 +1,3 @@ api.current_version=${project.version} api.min_version=1.0 -db.version=7 \ No newline at end of file +db.version=8 \ No newline at end of file diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java new file mode 100644 index 00000000..38618790 --- /dev/null +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestAclResource.java @@ -0,0 +1,177 @@ +package com.sismics.docs.rest; + +import java.util.Date; + +import junit.framework.Assert; + +import org.codehaus.jettison.json.JSONArray; +import org.codehaus.jettison.json.JSONException; +import org.codehaus.jettison.json.JSONObject; +import org.junit.Test; + +import com.sismics.docs.rest.filter.CookieAuthenticationFilter; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.ClientResponse.Status; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.core.util.MultivaluedMapImpl; + +/** + * Test the ACL resource. + * + * @author bgamard + */ +public class TestAclResource extends BaseJerseyTest { + /** + * Test the ACL resource. + * + * @throws JSONException + */ + @Test + public void testAclResource() throws JSONException { + // Login acl1 + clientUtil.createUser("acl1"); + String acl1Token = clientUtil.login("acl1"); + + // Login acl2 + clientUtil.createUser("acl2"); + String acl2Token = clientUtil.login("acl2"); + + // Create a document + WebResource documentResource = resource().path("/document"); + documentResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + MultivaluedMapImpl postParams = new MultivaluedMapImpl(); + postParams.add("title", "My super title document 1"); + postParams.add("language", "eng"); + postParams.add("create_date", new Date().getTime()); + ClientResponse response = documentResource.put(ClientResponse.class, postParams); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + JSONObject json = response.getEntity(JSONObject.class); + String document1Id = json.optString("id"); + + // Get the document as acl1 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + Assert.assertEquals(document1Id, json.getString("id")); + JSONArray acls = json.getJSONArray("acls"); + Assert.assertEquals(2, acls.length()); + + // Get the document as acl2 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl2Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus())); + + // Add an ACL READ for acl2 with acl1 + WebResource aclResource = resource().path("/acl"); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + postParams = new MultivaluedMapImpl(); + postParams.add("source", document1Id); + postParams.add("perm", "READ"); + postParams.add("username", "acl2"); + response = aclResource.put(ClientResponse.class, postParams); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + json = response.getEntity(JSONObject.class); + String acl2Id = json.getString("id"); + + // Add an ACL WRITE for acl2 with acl1 + aclResource = resource().path("/acl"); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + postParams = new MultivaluedMapImpl(); + postParams.add("source", document1Id); + postParams.add("perm", "WRITE"); + postParams.add("username", "acl2"); + response = aclResource.put(ClientResponse.class, postParams); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + + // Add an ACL WRITE for acl2 with acl1 (again) + aclResource = resource().path("/acl"); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + postParams = new MultivaluedMapImpl(); + postParams.add("source", document1Id); + postParams.add("perm", "WRITE"); + postParams.add("username", "acl2"); + response = aclResource.put(ClientResponse.class, postParams); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + + // Get the document as acl1 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + Assert.assertEquals(document1Id, json.getString("id")); + acls = json.getJSONArray("acls"); + Assert.assertEquals(4, acls.length()); + + // Get the document as acl2 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl2Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + Assert.assertEquals(document1Id, json.getString("id")); + acls = json.getJSONArray("acls"); + Assert.assertEquals(4, acls.length()); + + // Delete the ACL WRITE for acl2 with acl2 + aclResource = resource().path("/acl/" + document1Id + "/WRITE/" + acl2Id); + aclResource.addFilter(new CookieAuthenticationFilter(acl2Token)); + response = aclResource.delete(ClientResponse.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + + // Delete the ACL READ for acl2 with acl2 + aclResource = resource().path("/acl/" + document1Id + "/READ/" + acl2Id); + aclResource.addFilter(new CookieAuthenticationFilter(acl2Token)); + response = aclResource.delete(ClientResponse.class); + Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus())); + + // Delete the ACL READ for acl2 with acl1 + aclResource = resource().path("/acl/" + document1Id + "/READ/" + acl2Id); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = aclResource.delete(ClientResponse.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + + // Get the document as acl1 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + Assert.assertEquals(document1Id, json.getString("id")); + acls = json.getJSONArray("acls"); + Assert.assertEquals(2, acls.length()); + String acl1Id = acls.getJSONObject(0).getString("id"); + + // Get the document as acl2 + documentResource = resource().path("/document/" + document1Id); + documentResource.addFilter(new CookieAuthenticationFilter(acl2Token)); + response = documentResource.get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.FORBIDDEN, Status.fromStatusCode(response.getStatus())); + + // Delete the ACL READ for acl1 with acl1 + aclResource = resource().path("/acl/" + document1Id + "/READ/" + acl1Id); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = aclResource.delete(ClientResponse.class); + Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus())); + + // Delete the ACL WRITE for acl1 with acl1 + aclResource = resource().path("/acl/" + document1Id + "/WRITE/" + acl1Id); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = aclResource.delete(ClientResponse.class); + Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus())); + + // Search target list + aclResource = resource().path("/acl/target/search"); + aclResource.addFilter(new CookieAuthenticationFilter(acl1Token)); + response = aclResource.queryParam("search", "acl").get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + JSONArray users = json.getJSONArray("users"); + Assert.assertEquals(2, users.length()); + } +} \ No newline at end of file diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java index 78ba8535..24168ec9 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestDocumentResource.java @@ -43,6 +43,10 @@ public class TestDocumentResource extends BaseJerseyTest { clientUtil.createUser("document1"); String document1Token = clientUtil.login("document1"); + // Login document3 + clientUtil.createUser("document3"); + String document3Token = clientUtil.login("document3"); + // Create a tag WebResource tagResource = resource().path("/tag"); tagResource.addFilter(new CookieAuthenticationFilter(document1Token)); @@ -115,6 +119,61 @@ public class TestDocumentResource extends BaseJerseyTest { Assert.assertEquals("SuperTag", tags.getJSONObject(0).getString("name")); Assert.assertEquals("#ffff00", tags.getJSONObject(0).getString("color")); + // List all documents from document3 + documentResource = resource().path("/document/list"); + documentResource.addFilter(new CookieAuthenticationFilter(document3Token)); + getParams = new MultivaluedMapImpl(); + getParams.putSingle("sort_column", 3); + getParams.putSingle("asc", false); + response = documentResource.queryParams(getParams).get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + documents = json.getJSONArray("documents"); + Assert.assertTrue(documents.length() == 0); + + // Create a document with document3 + documentResource = resource().path("/document"); + documentResource.addFilter(new CookieAuthenticationFilter(document3Token)); + postParams = new MultivaluedMapImpl(); + postParams.add("title", "My super title document 1"); + postParams.add("description", "My super description for document 1"); + postParams.add("language", "eng"); + create1Date = new Date().getTime(); + postParams.add("create_date", create1Date); + response = documentResource.put(ClientResponse.class, postParams); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + json = response.getEntity(JSONObject.class); + String document3Id = json.optString("id"); + Assert.assertNotNull(document3Id); + + // Add a file + fileResource = resource().path("/file"); + fileResource.addFilter(new CookieAuthenticationFilter(document1Token)); + form = new FormDataMultiPart(); + file = this.getClass().getResourceAsStream("/file/Einstein-Roosevelt-letter.png"); + fdp = new FormDataBodyPart("file", + new BufferedInputStream(file), + MediaType.APPLICATION_OCTET_STREAM_TYPE); + form.bodyPart(fdp); + form.field("id", document1Id); + response = fileResource.type(MediaType.MULTIPART_FORM_DATA).put(ClientResponse.class, form); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + json = response.getEntity(JSONObject.class); + String file3Id = json.getString("id"); + Assert.assertNotNull(file3Id); + + // List all documents from document3 + documentResource = resource().path("/document/list"); + documentResource.addFilter(new CookieAuthenticationFilter(document3Token)); + getParams = new MultivaluedMapImpl(); + getParams.putSingle("sort_column", 3); + getParams.putSingle("asc", false); + response = documentResource.queryParams(getParams).get(ClientResponse.class); + json = response.getEntity(JSONObject.class); + Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); + documents = json.getJSONArray("documents"); + Assert.assertTrue(documents.length() == 1); + // Search documents Assert.assertEquals(1, searchDocuments("full:uranium full:einstein", document1Token)); Assert.assertEquals(1, searchDocuments("full:title", document1Token)); diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestLocaleResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestLocaleResource.java index 6b2923d9..cbc24a5b 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestLocaleResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestLocaleResource.java @@ -24,7 +24,6 @@ public class TestLocaleResource extends BaseJerseyTest { public void testLocaleResource() throws JSONException { WebResource localeResource = resource().path("/locale"); ClientResponse response = localeResource.get(ClientResponse.class); - response = localeResource.get(ClientResponse.class); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); JSONObject json = response.getEntity(JSONObject.class); JSONArray locale = json.getJSONArray("locales"); diff --git a/docs-web/src/test/java/com/sismics/docs/rest/TestShareResource.java b/docs-web/src/test/java/com/sismics/docs/rest/TestShareResource.java index e69ca0f6..2333d4e1 100644 --- a/docs-web/src/test/java/com/sismics/docs/rest/TestShareResource.java +++ b/docs-web/src/test/java/com/sismics/docs/rest/TestShareResource.java @@ -83,9 +83,7 @@ public class TestShareResource extends BaseJerseyTest { json = response.getEntity(JSONObject.class); Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus())); Assert.assertEquals(document1Id, json.getString("id")); - Assert.assertEquals(1, json.getJSONArray("shares").length()); - Assert.assertEquals(share1Id, json.getJSONArray("shares").getJSONObject(0).getString("id")); - Assert.assertEquals("4 All", json.getJSONArray("shares").getJSONObject(0).getString("name")); + Assert.assertEquals(3, json.getJSONArray("acls").length()); // 2 for the creator, 1 for the share // Get all files from this document anonymously fileResource = resource().path("/file/list");