@ -1,7 +1,7 @@
|
||||
FROM sismics/debian-java7-jetty9
|
||||
MAINTAINER benjamin.gam@gmail.com
|
||||
|
||||
RUN apt-get -y -q install tesseract-ocr tesseract-ocr-fra
|
||||
RUN apt-get -y -q install tesseract-ocr tesseract-ocr-fra tesseract-ocr-jpn
|
||||
|
||||
ENV TESSDATA_PREFIX /usr/share/tesseract-ocr
|
||||
ENV LC_NUMERIC C
|
||||
|
@ -20,7 +20,7 @@ public class MainApplication extends Application {
|
||||
JSONObject json = PreferenceUtil.getCachedJson(getApplicationContext(), PreferenceUtil.PREF_CACHED_USER_INFO_JSON);
|
||||
ApplicationContext.getInstance().setUserInfo(getApplicationContext(), json);
|
||||
|
||||
// TODO Fullscreen preview
|
||||
// TODO google docs app: right drawer with all actions, with acls, with deep metadatas
|
||||
// TODO Provide documents to intent action get content
|
||||
|
||||
super.onCreate();
|
||||
|
@ -12,7 +12,9 @@ import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
@ -21,11 +23,14 @@ import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
import com.sismics.docs.adapter.AclListAdapter;
|
||||
import com.sismics.docs.adapter.FilePagerAdapter;
|
||||
import com.sismics.docs.event.DocumentDeleteEvent;
|
||||
import com.sismics.docs.event.DocumentEditEvent;
|
||||
@ -184,6 +189,58 @@ public class DocumentViewActivity extends AppCompatActivity {
|
||||
ImageView sharedImageView = (ImageView) findViewById(R.id.sharedImageView);
|
||||
sharedImageView.setVisibility(shared ? View.VISIBLE : View.GONE);
|
||||
|
||||
// Action edit document
|
||||
Button button = (Button) findViewById(R.id.actionEditDocument);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(DocumentViewActivity.this, DocumentEditActivity.class);
|
||||
intent.putExtra("document", DocumentViewActivity.this.document.toString());
|
||||
startActivityForResult(intent, REQUEST_CODE_EDIT_DOCUMENT);
|
||||
}
|
||||
});
|
||||
|
||||
// Action upload file
|
||||
button = (Button) findViewById(R.id.actionUploadFile);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
|
||||
.setType("*/*")
|
||||
.putExtra("android.intent.extra.ALLOW_MULTIPLE", true)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
startActivityForResult(Intent.createChooser(intent, getText(R.string.upload_from)), REQUEST_CODE_ADD_FILE);
|
||||
}
|
||||
});
|
||||
|
||||
// Action download document
|
||||
button = (Button) findViewById(R.id.actionDownload);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
downloadZip();
|
||||
}
|
||||
});
|
||||
|
||||
// Action delete document
|
||||
button = (Button) findViewById(R.id.actionDelete);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
deleteDocument();
|
||||
}
|
||||
});
|
||||
|
||||
// Action share
|
||||
button = (Button) findViewById(R.id.actionSharing);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
DialogFragment dialog = DocShareFragment.newInstance(DocumentViewActivity.this.document.optString("id"));
|
||||
dialog.show(getSupportFragmentManager(), "DocShareFragment");
|
||||
}
|
||||
});
|
||||
|
||||
// Grab the attached files
|
||||
updateFiles();
|
||||
|
||||
@ -202,41 +259,23 @@ public class DocumentViewActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.info:
|
||||
DrawerLayout drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
if (drawerLayout.isDrawerVisible(GravityCompat.END)) {
|
||||
drawerLayout.closeDrawer(GravityCompat.END);
|
||||
} else {
|
||||
drawerLayout.openDrawer(GravityCompat.END);
|
||||
}
|
||||
return true;
|
||||
|
||||
case R.id.download_file:
|
||||
downloadCurrentFile();
|
||||
return true;
|
||||
|
||||
case R.id.download_document:
|
||||
downloadZip();
|
||||
return true;
|
||||
|
||||
case R.id.share:
|
||||
DialogFragment dialog = DocShareFragment.newInstance(document.optString("id"));
|
||||
dialog.show(getSupportFragmentManager(), "DocShareFragment");
|
||||
return true;
|
||||
|
||||
case R.id.upload_file:
|
||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT)
|
||||
.setType("*/*")
|
||||
.putExtra("android.intent.extra.ALLOW_MULTIPLE", true)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
startActivityForResult(Intent.createChooser(intent, getText(R.string.upload_from)), REQUEST_CODE_ADD_FILE);
|
||||
return true;
|
||||
|
||||
case R.id.edit:
|
||||
intent = new Intent(this, DocumentEditActivity.class);
|
||||
intent.putExtra("document", document.toString());
|
||||
startActivityForResult(intent, REQUEST_CODE_EDIT_DOCUMENT);
|
||||
return true;
|
||||
|
||||
case R.id.delete_file:
|
||||
deleteCurrentFile();
|
||||
return true;
|
||||
|
||||
case R.id.delete_document:
|
||||
deleteDocument();
|
||||
return true;
|
||||
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
@ -514,17 +553,21 @@ public class DocumentViewActivity extends AppCompatActivity {
|
||||
DocumentResource.get(this, document.optString("id"), new JsonHttpResponseHandler() {
|
||||
@Override
|
||||
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
|
||||
boolean writable = response.optBoolean("writable");
|
||||
document = response;
|
||||
boolean writable = document.optBoolean("writable");
|
||||
|
||||
if (menu != null) {
|
||||
menu.findItem(R.id.share).setVisible(writable);
|
||||
menu.findItem(R.id.upload_file).setVisible(writable);
|
||||
menu.findItem(R.id.edit).setVisible(writable);
|
||||
menu.findItem(R.id.delete_file).setVisible(writable);
|
||||
menu.findItem(R.id.delete_document).setVisible(writable);
|
||||
}
|
||||
|
||||
// TODO Show the ACLs in a sliding panel from the right
|
||||
findViewById(R.id.actionEditDocument).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
||||
findViewById(R.id.actionUploadFile).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
||||
findViewById(R.id.actionSharing).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
||||
findViewById(R.id.actionDelete).setVisibility(writable ? View.VISIBLE : View.INVISIBLE);
|
||||
|
||||
// ACLs
|
||||
ListView aclListView = (ListView) findViewById(R.id.aclListView);
|
||||
aclListView.setAdapter(new AclListAdapter(document.optJSONArray("acls")));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
package com.sismics.docs.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.sismics.docs.R;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ACL list adapter.
|
||||
*
|
||||
* @author bgamard.
|
||||
*/
|
||||
public class AclListAdapter extends BaseAdapter {
|
||||
/**
|
||||
* Shares.
|
||||
*/
|
||||
private List<JSONObject> acls;
|
||||
|
||||
/**
|
||||
* ACL list adapter.
|
||||
*
|
||||
* @param acls ACLs
|
||||
*/
|
||||
public AclListAdapter(JSONArray acls) {
|
||||
this.acls = new ArrayList<>();
|
||||
|
||||
// Extract only share ACLs
|
||||
for (int i = 0; i < acls.length(); i++) {
|
||||
JSONObject acl = acls.optJSONObject(i);
|
||||
this.acls.add(acl);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return acls.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JSONObject getItem(int position) {
|
||||
return acls.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return getItem(position).optString("id").hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, final ViewGroup parent) {
|
||||
if (view == null) {
|
||||
LayoutInflater vi = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = vi.inflate(R.layout.acl_list_item, parent, false);
|
||||
}
|
||||
|
||||
// Fill the view
|
||||
final JSONObject acl = getItem(position);
|
||||
TextView typeTextView = (TextView) view.findViewById(R.id.typeTextView);
|
||||
typeTextView.setText(acl.optString("type"));
|
||||
TextView nameTextView = (TextView) view.findViewById(R.id.nameTextView);
|
||||
nameTextView.setText(acl.optString("name"));
|
||||
TextView permTextView = (TextView) view.findViewById(R.id.permTextView);
|
||||
permTextView.setText(acl.optString("perm"));
|
||||
|
||||
return view;
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 379 B |
Before Width: | Height: | Size: 378 B |
After Width: | Height: | Size: 283 B |
After Width: | Height: | Size: 275 B |
After Width: | Height: | Size: 530 B |
After Width: | Height: | Size: 476 B |
After Width: | Height: | Size: 493 B |
Before Width: | Height: | Size: 490 B |
After Width: | Height: | Size: 353 B |
After Width: | Height: | Size: 339 B |
After Width: | Height: | Size: 736 B |
After Width: | Height: | Size: 623 B |
65
docs-android/app/src/main/res/layout/acl_list_item.xml
Normal file
@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="0dp"
|
||||
android:background="?android:attr/selectableItemBackground">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/peopleImageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_marginLeft="18dp"
|
||||
android:layout_marginStart="18dp"
|
||||
android:src="@drawable/ic_people_grey600_24dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/typeTextView"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toRightOf="@id/peopleImageView"
|
||||
android:layout_toEndOf="@id/peopleImageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textColor="@color/primary_text_disabled_material_light"
|
||||
android:text="USER"
|
||||
android:layout_marginTop="12dp"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/nameTextView"
|
||||
android:layout_below="@id/typeTextView"
|
||||
android:layout_toRightOf="@id/peopleImageView"
|
||||
android:layout_toEndOf="@id/peopleImageView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textColor="@color/primary_text_default_material_light"
|
||||
android:text="admin"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:ellipsize="end"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/permTextView"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif"
|
||||
android:textColor="@color/primary_text_default_material_light"
|
||||
android:text="WRITE"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:layout_marginRight="18dp"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
</RelativeLayout>
|
@ -1,85 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:ignore="InconsistentLayout"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/detailLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp"
|
||||
android:background="#eee"
|
||||
android:elevation="4dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/createdDateLabel"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="24dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="@string/created_date"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/createdDateTextView"
|
||||
android:layout_toRightOf="@id/createdDateLabel"
|
||||
android:layout_toEndOf="@id/createdDateLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_alignParentTop="true"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:text="01/12/2014"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tagTextView"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/createdDateLabel"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:maxLines="1"
|
||||
android:fontFamily="sans-serif-light"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/descriptionTextView"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_below="@id/tagTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-light"
|
||||
android:text="Lorem ipsum dolor sit amen lorem ipsum dolor sit amen lorem ipsum dolor sit amen lorem ipsum dolor sit amen lorem ipsum dolor sit amen."/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sharedImageView"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_folder_shared_grey600_24dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_toLeftOf="@+id/languageImageView"
|
||||
android:layout_toStartOf="@+id/languageImageView"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/languageImageView"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"/>
|
||||
|
||||
</RelativeLayout>
|
||||
<!-- Main content -->
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.sismics.docs.ui.view.FileViewPager
|
||||
android:id="@+id/fileViewPager"
|
||||
@ -106,4 +37,200 @@
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- Right drawer -->
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/right_drawer"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:orientation="vertical"
|
||||
android:clickable="true"
|
||||
android:background="#fff"
|
||||
android:elevation="5dp">
|
||||
|
||||
<!-- Actions -->
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
style="?android:buttonBarStyle">
|
||||
|
||||
<Button
|
||||
android:id="@+id/actionEditDocument"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableTop="@drawable/ic_create_grey600_24dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:text="@string/edit_document"
|
||||
android:textColor="@color/button_material_dark"
|
||||
android:textAllCaps="false"
|
||||
android:layout_margin="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/actionUploadFile"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableTop="@drawable/ic_file_upload_grey600_24dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:text="@string/upload_file"
|
||||
android:textColor="@color/button_material_dark"
|
||||
android:textAllCaps="false"
|
||||
android:layout_margin="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/actionDownload"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableTop="@drawable/ic_file_download_grey600_24dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:text="@string/download_document"
|
||||
android:textColor="@color/button_material_dark"
|
||||
android:textAllCaps="false"
|
||||
android:layout_margin="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
style="?android:buttonBarStyle">
|
||||
|
||||
<Button
|
||||
android:id="@+id/actionSharing"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableTop="@drawable/ic_share_grey600_24dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:text="@string/share"
|
||||
android:textColor="@color/button_material_dark"
|
||||
android:textAllCaps="false"
|
||||
android:layout_margin="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/actionDelete"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableTop="@drawable/ic_delete_grey600_24dp"
|
||||
style="?android:buttonBarButtonStyle"
|
||||
android:text="@string/delete_document"
|
||||
android:textColor="@color/button_material_dark"
|
||||
android:textAllCaps="false"
|
||||
android:layout_margin="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:background="#eee"/>
|
||||
|
||||
<!-- Document metadata -->
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/detailLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/createdDateLabel"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="24dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:fontFamily="sans-serif"
|
||||
android:text="@string/created_date"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/createdDateTextView"
|
||||
android:layout_toRightOf="@id/createdDateLabel"
|
||||
android:layout_toEndOf="@id/createdDateLabel"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_alignParentTop="true"
|
||||
android:fontFamily="sans-serif-light"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tagTextView"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/createdDateLabel"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:maxLines="1"
|
||||
android:fontFamily="sans-serif-light"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/descriptionTextView"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_below="@id/tagTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="sans-serif-light"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/sharedImageView"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_folder_shared_grey600_24dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_toLeftOf="@+id/languageImageView"
|
||||
android:layout_toStartOf="@+id/languageImageView"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/languageImageView"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentTop="true"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:background="#eee"/>
|
||||
|
||||
<!-- ACLs -->
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/primary_text_default_material_light"
|
||||
android:text="@string/who_can_access"
|
||||
android:layout_margin="12dp"/>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/aclListView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:choiceMode="singleChoice"
|
||||
android:divider="@android:color/transparent"
|
||||
android:dividerHeight="0dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v4.widget.DrawerLayout>
|
@ -3,23 +3,10 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/edit"
|
||||
android:id="@+id/info"
|
||||
app:showAsAction="ifRoom"
|
||||
android:icon="@drawable/ic_create_white_24dp"
|
||||
android:title="@string/edit_document">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/upload_file"
|
||||
app:showAsAction="ifRoom"
|
||||
android:icon="@drawable/ic_file_upload_white_24dp"
|
||||
android:title="@string/upload_file">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/share"
|
||||
app:showAsAction="collapseActionView"
|
||||
android:title="@string/share">
|
||||
android:icon="@drawable/ic_info_white_24dp"
|
||||
android:title="@string/toggle_informations">
|
||||
</item>
|
||||
|
||||
<item
|
||||
@ -28,22 +15,10 @@
|
||||
android:title="@string/download_file">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/download_document"
|
||||
app:showAsAction="collapseActionView"
|
||||
android:title="@string/download_document">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/delete_file"
|
||||
app:showAsAction="collapseActionView"
|
||||
android:title="@string/delete_file">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/delete_document"
|
||||
app:showAsAction="collapseActionView"
|
||||
android:title="@string/delete_document">
|
||||
</item>
|
||||
|
||||
</menu>
|
@ -29,7 +29,7 @@
|
||||
<string name="created_date">Created date</string>
|
||||
<string name="download_file">Download current file</string>
|
||||
<string name="downloading_file">Downloading file number %1s</string>
|
||||
<string name="download_document">Download all files</string>
|
||||
<string name="download_document">Download</string>
|
||||
<string name="downloading_document">Downloading document</string>
|
||||
<string name="action_search">Search documents</string>
|
||||
<string name="all_documents">All documents</string>
|
||||
@ -54,7 +54,7 @@
|
||||
<string name="share_default_name">Share link</string>
|
||||
<string name="error_deleting_share">Error deleting the share</string>
|
||||
<string name="send_share_to">Send share link to</string>
|
||||
<string name="upload_file">Upload a file</string>
|
||||
<string name="upload_file">Add file</string>
|
||||
<string name="upload_from">Upload a file from</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="logout">Sign Out</string>
|
||||
@ -75,11 +75,11 @@
|
||||
<string name="language_english">English</string>
|
||||
<string name="language_japanese">Japanese</string>
|
||||
<string name="save">Save</string>
|
||||
<string name="edit_document">Edit document</string>
|
||||
<string name="edit_document">Edit</string>
|
||||
<string name="error_editing_document">Network error, please try again</string>
|
||||
<string name="please_wait">Please wait</string>
|
||||
<string name="document_editing_message">Sending your data</string>
|
||||
<string name="delete_document">Delete document</string>
|
||||
<string name="delete_document">Delete</string>
|
||||
<string name="delete_document_title">Delete document</string>
|
||||
<string name="delete_document_message">Really delete this document and all associated files?</string>
|
||||
<string name="document_delete_failure">Network error while deleting this document</string>
|
||||
@ -105,6 +105,8 @@
|
||||
<string name="before_date">Before date</string>
|
||||
<string name="search_tags">Search tags</string>
|
||||
<string name="all_languages">All languages</string>
|
||||
<string name="toggle_informations">Toggle informations</string>
|
||||
<string name="who_can_access">Who can access</string>
|
||||
|
||||
|
||||
</resources>
|
||||
|
@ -0,0 +1,23 @@
|
||||
package com.sismics.docs.core.constant;
|
||||
|
||||
/**
|
||||
* Audit log types.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public enum AuditLogType {
|
||||
/**
|
||||
* Create.
|
||||
*/
|
||||
CREATE,
|
||||
|
||||
/**
|
||||
* Update.
|
||||
*/
|
||||
UPDATE,
|
||||
|
||||
/**
|
||||
* Delete.
|
||||
*/
|
||||
DELETE
|
||||
}
|
@ -9,9 +9,11 @@ import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import com.sismics.docs.core.constant.AclTargetType;
|
||||
import com.sismics.docs.core.constant.AuditLogType;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.jpa.dto.AclDto;
|
||||
import com.sismics.docs.core.model.jpa.Acl;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
/**
|
||||
@ -35,6 +37,9 @@ public class AclDao {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
em.persist(acl);
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(acl, AuditLogType.CREATE);
|
||||
|
||||
return acl.getId();
|
||||
}
|
||||
|
||||
@ -121,9 +126,22 @@ public class AclDao {
|
||||
* @param perm Permission
|
||||
* @param targetId Target ID
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void delete(String sourceId, PermType perm, String targetId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId");
|
||||
|
||||
// Create audit log
|
||||
Query q = em.createQuery("from Acl a where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId");
|
||||
q.setParameter("sourceId", sourceId);
|
||||
q.setParameter("perm", perm);
|
||||
q.setParameter("targetId", targetId);
|
||||
List<Acl> aclList = q.getResultList();
|
||||
for (Acl acl : aclList) {
|
||||
AuditLogUtil.create(acl, AuditLogType.DELETE);
|
||||
}
|
||||
|
||||
// Soft delete the ACLs
|
||||
q = em.createQuery("update Acl a set a.deleteDate = :dateNow where a.sourceId = :sourceId and a.perm = :perm and a.targetId = :targetId");
|
||||
q.setParameter("sourceId", sourceId);
|
||||
q.setParameter("perm", perm);
|
||||
q.setParameter("targetId", targetId);
|
||||
|
@ -0,0 +1,109 @@
|
||||
package com.sismics.docs.core.dao.jpa;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.sismics.docs.core.constant.AuditLogType;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.AuditLogCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.AuditLogDto;
|
||||
import com.sismics.docs.core.model.jpa.AuditLog;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.QueryParam;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
/**
|
||||
* Audit log DAO.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class AuditLogDao {
|
||||
/**
|
||||
* Creates a new audit log.
|
||||
*
|
||||
* @param auditLog Audit log
|
||||
* @return New ID
|
||||
* @throws Exception
|
||||
*/
|
||||
public String create(AuditLog auditLog) {
|
||||
// Create the UUID
|
||||
auditLog.setId(UUID.randomUUID().toString());
|
||||
|
||||
// Create the audit log
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
auditLog.setCreateDate(new Date());
|
||||
em.persist(auditLog);
|
||||
|
||||
return auditLog.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches audit logs by criteria.
|
||||
*
|
||||
* @param paginatedList List of audit logs (updated by side effects)
|
||||
* @param criteria Search criteria
|
||||
* @param sortCriteria Sort criteria
|
||||
* @return List of audit logs
|
||||
* @throws Exception
|
||||
*/
|
||||
public void findByCriteria(PaginatedList<AuditLogDto> paginatedList, AuditLogCriteria criteria, SortCriteria sortCriteria) throws Exception {
|
||||
Map<String, Object> parameterMap = new HashMap<String, Object>();
|
||||
List<String> criteriaList = new ArrayList<String>();
|
||||
|
||||
StringBuilder sb = new StringBuilder("select l.LOG_ID_C c0, l.LOG_CREATEDATE_D c1, l.LOG_IDENTITY_C c2, l.LOG_CLASSENTITY_C c3, l.LOG_TYPE_C c4, l.LOG_MESSAGE_C c5 ");
|
||||
sb.append(" from T_AUDIT_LOG l ");
|
||||
|
||||
// Adds search criteria
|
||||
if (criteria.getDocumentId() != null) {
|
||||
// ACL on document is not checked here, it's assumed
|
||||
StringBuilder sb0 = new StringBuilder(" (l.LOG_IDENTITY_C = :documentId and l.LOG_CLASSENTITY_C = 'Document' ");
|
||||
sb0.append(" or l.LOG_IDENTITY_C in (select f.FIL_ID_C from T_FILE f where f.FIL_IDDOC_C = :documentId) and l.LOG_CLASSENTITY_C = 'File' ");
|
||||
sb0.append(" or l.LOG_IDENTITY_C in (select a.ACL_ID_C from T_ACL a where a.ACL_SOURCEID_C = :documentId) and l.LOG_CLASSENTITY_C = 'Acl') ");
|
||||
criteriaList.add(sb0.toString());
|
||||
parameterMap.put("documentId", criteria.getDocumentId());
|
||||
}
|
||||
|
||||
if (criteria.getUserId() != null) {
|
||||
StringBuilder sb0 = new StringBuilder(" (l.LOG_IDENTITY_C = :userId and l.LOG_CLASSENTITY_C = 'User' ");
|
||||
sb0.append(" or l.LOG_IDENTITY_C in (select t.TAG_ID_C from T_TAG t where t.TAG_IDUSER_C = :userId) and l.LOG_CLASSENTITY_C = 'Tag' ");
|
||||
// Show only logs from owned documents, ACL are lost on delete
|
||||
sb0.append(" or l.LOG_IDENTITY_C in (select d.DOC_ID_C from T_DOCUMENT d where d.DOC_IDUSER_C = :userId) and l.LOG_CLASSENTITY_C = 'Document') ");
|
||||
criteriaList.add(sb0.toString());
|
||||
parameterMap.put("userId", criteria.getUserId());
|
||||
}
|
||||
|
||||
if (!criteriaList.isEmpty()) {
|
||||
sb.append(" where ");
|
||||
sb.append(Joiner.on(" and ").join(criteriaList));
|
||||
}
|
||||
|
||||
// Perform the search
|
||||
QueryParam queryParam = new QueryParam(sb.toString(), parameterMap);
|
||||
List<Object[]> l = PaginatedLists.executePaginatedQuery(paginatedList, queryParam, sortCriteria);
|
||||
|
||||
// Assemble results
|
||||
List<AuditLogDto> auditLogDtoList = new ArrayList<AuditLogDto>();
|
||||
for (Object[] o : l) {
|
||||
int i = 0;
|
||||
AuditLogDto auditLogDto = new AuditLogDto();
|
||||
auditLogDto.setId((String) o[i++]);
|
||||
auditLogDto.setCreateTimestamp(((Timestamp) o[i++]).getTime());
|
||||
auditLogDto.setEntityId((String) o[i++]);
|
||||
auditLogDto.setEntityClass((String) o[i++]);
|
||||
auditLogDto.setType(AuditLogType.valueOf((String) o[i++]));
|
||||
auditLogDto.setMessage((String) o[i++]);
|
||||
auditLogDtoList.add(auditLogDto);
|
||||
}
|
||||
|
||||
paginatedList.setResultList(auditLogDtoList);
|
||||
}
|
||||
}
|
@ -15,11 +15,13 @@ import javax.persistence.Query;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Strings;
|
||||
import com.sismics.docs.core.constant.AuditLogType;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
|
||||
import com.sismics.docs.core.dao.lucene.LuceneDao;
|
||||
import com.sismics.docs.core.model.jpa.Document;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.QueryParam;
|
||||
@ -47,6 +49,9 @@ public class DocumentDao {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
em.persist(document);
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(document, AuditLogType.CREATE);
|
||||
|
||||
return document.getId();
|
||||
}
|
||||
|
||||
@ -145,6 +150,9 @@ public class DocumentDao {
|
||||
q.setParameter("documentId", id);
|
||||
q.setParameter("dateNow", dateNow);
|
||||
q.executeUpdate();
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(documentDb, AuditLogType.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -167,6 +175,7 @@ public class DocumentDao {
|
||||
*
|
||||
* @param paginatedList List of documents (updated by side effects)
|
||||
* @param criteria Search criteria
|
||||
* @param sortCriteria Sort criteria
|
||||
* @return List of documents
|
||||
* @throws Exception
|
||||
*/
|
||||
@ -248,4 +257,30 @@ public class DocumentDao {
|
||||
|
||||
paginatedList.setResultList(documentDtoList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a document.
|
||||
*
|
||||
* @param document Document to update
|
||||
* @return Updated document
|
||||
*/
|
||||
public Document update(Document document) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
|
||||
// Get the document
|
||||
Query q = em.createQuery("select d from Document d where d.id = :id and d.deleteDate is null");
|
||||
q.setParameter("id", document.getId());
|
||||
Document documentFromDb = (Document) q.getSingleResult();
|
||||
|
||||
// Update the document
|
||||
documentFromDb.setTitle(document.getTitle());
|
||||
documentFromDb.setDescription(document.getDescription());
|
||||
documentFromDb.setCreateDate(document.getCreateDate());
|
||||
documentFromDb.setLanguage(document.getLanguage());
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(documentFromDb, AuditLogType.UPDATE);
|
||||
|
||||
return documentFromDb;
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,9 @@ import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.Query;
|
||||
|
||||
import com.sismics.docs.core.constant.AuditLogType;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
/**
|
||||
@ -33,6 +35,9 @@ public class FileDao {
|
||||
file.setCreateDate(new Date());
|
||||
em.persist(file);
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(file, AuditLogType.CREATE);
|
||||
|
||||
return file.getId();
|
||||
}
|
||||
|
||||
@ -92,6 +97,9 @@ public class FileDao {
|
||||
// Delete the file
|
||||
Date dateNow = new Date();
|
||||
fileDb.setDeleteDate(dateNow);
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(fileDb, AuditLogType.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -113,6 +121,9 @@ public class FileDao {
|
||||
fileFromDb.setContent(file.getContent());
|
||||
fileFromDb.setOrder(file.getOrder());
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(fileFromDb, AuditLogType.UPDATE);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,22 @@
|
||||
package com.sismics.docs.core.dao.jpa;
|
||||
|
||||
import com.sismics.docs.core.dao.jpa.dto.TagDto;
|
||||
import com.sismics.docs.core.dao.jpa.dto.TagStatDto;
|
||||
import com.sismics.docs.core.model.jpa.DocumentTag;
|
||||
import com.sismics.docs.core.model.jpa.Tag;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.Query;
|
||||
import java.util.*;
|
||||
|
||||
import com.sismics.docs.core.constant.AuditLogType;
|
||||
import com.sismics.docs.core.dao.jpa.dto.TagDto;
|
||||
import com.sismics.docs.core.dao.jpa.dto.TagStatDto;
|
||||
import com.sismics.docs.core.model.jpa.DocumentTag;
|
||||
import com.sismics.docs.core.model.jpa.Tag;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
/**
|
||||
* Tag DAO.
|
||||
@ -48,31 +55,50 @@ public class TagDao {
|
||||
/**
|
||||
* Update tags on a document.
|
||||
*
|
||||
* @param documentId
|
||||
* @param tagIdSet
|
||||
* @param documentId Document ID
|
||||
* @param tagIdSet Set of tag ID
|
||||
*/
|
||||
public void updateTagList(String documentId, Set<String> tagIdSet) {
|
||||
// Delete old tag links
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
Query q = em.createQuery("delete DocumentTag dt where dt.documentId = :documentId");
|
||||
q.setParameter("documentId", documentId);
|
||||
q.executeUpdate();
|
||||
|
||||
// Create new tag links
|
||||
// Get current tag links
|
||||
Query q = em.createQuery("select dt from DocumentTag dt where dt.documentId = :documentId and dt.deleteDate is null");
|
||||
q.setParameter("documentId", documentId);
|
||||
@SuppressWarnings("unchecked")
|
||||
List<DocumentTag> documentTagList = q.getResultList();
|
||||
|
||||
// Deleting tags no longer linked
|
||||
for (DocumentTag documentTag : documentTagList) {
|
||||
if (!tagIdSet.contains(documentTag.getTagId())) {
|
||||
documentTag.setDeleteDate(new Date());
|
||||
}
|
||||
}
|
||||
|
||||
// Adding new tag links
|
||||
for (String tagId : tagIdSet) {
|
||||
boolean found = false;
|
||||
for (DocumentTag documentTag : documentTagList) {
|
||||
if (documentTag.getTagId().equals(tagId)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
DocumentTag documentTag = new DocumentTag();
|
||||
documentTag.setId(UUID.randomUUID().toString());
|
||||
documentTag.setDocumentId(documentId);
|
||||
documentTag.setTagId(tagId);
|
||||
em.persist(documentTag);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns tag list on a document.
|
||||
* @param documentId
|
||||
* @return
|
||||
*
|
||||
* @param documentId Document ID
|
||||
* @return List of tags
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<TagDto> getByDocumentId(String documentId, String userId) {
|
||||
@ -80,7 +106,7 @@ public class TagDao {
|
||||
StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C from T_DOCUMENT_TAG dt ");
|
||||
sb.append(" join T_TAG t on t.TAG_ID_C = dt.DOT_IDTAG_C ");
|
||||
sb.append(" where dt.DOT_IDDOCUMENT_C = :documentId and t.TAG_DELETEDATE_D is null ");
|
||||
sb.append(" and t.TAG_IDUSER_C = :userId ");
|
||||
sb.append(" and t.TAG_IDUSER_C = :userId and dt.DOT_DELETEDATE_D is null ");
|
||||
sb.append(" order by t.TAG_NAME_C ");
|
||||
|
||||
// Perform the query
|
||||
@ -104,15 +130,16 @@ public class TagDao {
|
||||
|
||||
/**
|
||||
* Returns stats on tags.
|
||||
* @param documentId
|
||||
* @return
|
||||
*
|
||||
* @param documentId Document ID
|
||||
* @return Stats by tag
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<TagStatDto> getStats(String userId) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
StringBuilder sb = new StringBuilder("select t.TAG_ID_C, t.TAG_NAME_C, t.TAG_COLOR_C, count(d.DOC_ID_C) ");
|
||||
sb.append(" from T_TAG t ");
|
||||
sb.append(" left join T_DOCUMENT_TAG dt on t.TAG_ID_C = dt.DOT_IDTAG_C ");
|
||||
sb.append(" left join T_DOCUMENT_TAG dt on t.TAG_ID_C = dt.DOT_IDTAG_C and dt.DOT_DELETEDATE_D is null ");
|
||||
sb.append(" left join T_DOCUMENT d on d.DOC_ID_C = dt.DOT_IDDOCUMENT_C and d.DOC_DELETEDATE_D is null and d.DOC_IDUSER_C = :userId ");
|
||||
sb.append(" where t.TAG_IDUSER_C = :userId and t.TAG_DELETEDATE_D is null ");
|
||||
sb.append(" group by t.TAG_ID_C ");
|
||||
@ -153,11 +180,15 @@ public class TagDao {
|
||||
tag.setCreateDate(new Date());
|
||||
em.persist(tag);
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(tag, AuditLogType.CREATE);
|
||||
|
||||
return tag.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a tag by name.
|
||||
*
|
||||
* @param userId User ID
|
||||
* @param name Name
|
||||
* @return Tag
|
||||
@ -176,6 +207,7 @@ public class TagDao {
|
||||
|
||||
/**
|
||||
* Returns a tag by ID.
|
||||
*
|
||||
* @param userId User ID
|
||||
* @param tagId Tag ID
|
||||
* @return Tag
|
||||
@ -206,13 +238,15 @@ public class TagDao {
|
||||
Tag tagDb = (Tag) q.getSingleResult();
|
||||
|
||||
// Delete the tag
|
||||
Date dateNow = new Date();
|
||||
tagDb.setDeleteDate(dateNow);
|
||||
tagDb.setDeleteDate(new Date());
|
||||
|
||||
// Delete linked data
|
||||
q = em.createQuery("delete DocumentTag dt where dt.tagId = :tagId");
|
||||
q.setParameter("tagId", tagId);
|
||||
q.executeUpdate();
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(tagDb, AuditLogType.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -229,4 +263,28 @@ public class TagDao {
|
||||
q.setParameter("name", "%" + name + "%");
|
||||
return q.getResultList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a tag.
|
||||
*
|
||||
* @param tag Tag to update
|
||||
* @return Updated tag
|
||||
*/
|
||||
public Tag update(Tag tag) {
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
|
||||
// Get the tag
|
||||
Query q = em.createQuery("select t from Tag t where t.id = :id and t.deleteDate is null");
|
||||
q.setParameter("id", tag.getId());
|
||||
Tag tagFromDb = (Tag) q.getSingleResult();
|
||||
|
||||
// Update the tag
|
||||
tagFromDb.setName(tag.getName());
|
||||
tagFromDb.setColor(tag.getColor());
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(tagFromDb, AuditLogType.UPDATE);
|
||||
|
||||
return tagFromDb;
|
||||
}
|
||||
}
|
||||
|
@ -15,10 +15,12 @@ import javax.persistence.Query;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.sismics.docs.core.constant.AuditLogType;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.UserCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.UserDto;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.QueryParam;
|
||||
@ -73,11 +75,15 @@ public class UserDao {
|
||||
throw new Exception("AlreadyExistingUsername");
|
||||
}
|
||||
|
||||
// Create the user
|
||||
user.setCreateDate(new Date());
|
||||
user.setPassword(hashPassword(user.getPassword()));
|
||||
user.setTheme(Constants.DEFAULT_THEME_ID);
|
||||
em.persist(user);
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(user, AuditLogType.CREATE);
|
||||
|
||||
return user.getId();
|
||||
}
|
||||
|
||||
@ -101,6 +107,9 @@ public class UserDao {
|
||||
userFromDb.setTheme(user.getTheme());
|
||||
userFromDb.setFirstConnection(user.isFirstConnection());
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@ -121,6 +130,9 @@ public class UserDao {
|
||||
// Update the user
|
||||
userFromDb.setPassword(hashPassword(user.getPassword()));
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
@ -194,6 +206,9 @@ public class UserDao {
|
||||
q = em.createQuery("delete from AuthenticationToken at where at.userId = :userId");
|
||||
q.setParameter("userId", userFromDb.getId());
|
||||
q.executeUpdate();
|
||||
|
||||
// Create audit log
|
||||
AuditLogUtil.create(userFromDb, AuditLogType.DELETE);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,36 @@
|
||||
package com.sismics.docs.core.dao.jpa.criteria;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Audit log criteria.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class AuditLogCriteria {
|
||||
/**
|
||||
* Document ID.
|
||||
*/
|
||||
private String documentId;
|
||||
|
||||
/**
|
||||
* User ID.
|
||||
*/
|
||||
private String userId;
|
||||
|
||||
public String getDocumentId() {
|
||||
return documentId;
|
||||
}
|
||||
|
||||
public void setDocumentId(String documentId) {
|
||||
this.documentId = documentId;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
package com.sismics.docs.core.dao.jpa.dto;
|
||||
|
||||
import javax.persistence.Id;
|
||||
|
||||
import com.sismics.docs.core.constant.AuditLogType;
|
||||
|
||||
/**
|
||||
* Audit log DTO.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class AuditLogDto {
|
||||
/**
|
||||
* Audit log ID.
|
||||
*/
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Entity ID.
|
||||
*/
|
||||
private String entityId;
|
||||
|
||||
/**
|
||||
* Entity class.
|
||||
*/
|
||||
private String entityClass;
|
||||
|
||||
/**
|
||||
* Audit log type.
|
||||
*/
|
||||
private AuditLogType type;
|
||||
|
||||
/**
|
||||
* Audit log message.
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* Creation date.
|
||||
*/
|
||||
private Long createTimestamp;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
public void setEntityId(String entityId) {
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
public String getEntityClass() {
|
||||
return entityClass;
|
||||
}
|
||||
|
||||
public void setEntityClass(String entityClass) {
|
||||
this.entityClass = entityClass;
|
||||
}
|
||||
|
||||
public AuditLogType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(AuditLogType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Long getCreateTimestamp() {
|
||||
return createTimestamp;
|
||||
}
|
||||
|
||||
public void setCreateTimestamp(Long createTimestamp) {
|
||||
this.createTimestamp = createTimestamp;
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import java.util.Date;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
@ -11,6 +12,7 @@ import javax.persistence.Table;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
|
||||
/**
|
||||
* ACL entity.
|
||||
@ -18,8 +20,9 @@ import com.sismics.docs.core.constant.PermType;
|
||||
* @author bgamard
|
||||
*/
|
||||
@Entity
|
||||
@EntityListeners(AuditLogUtil.class)
|
||||
@Table(name = "T_ACL")
|
||||
public class Acl {
|
||||
public class Acl implements Loggable {
|
||||
/**
|
||||
* ACL ID.
|
||||
*/
|
||||
@ -84,6 +87,7 @@ public class Acl {
|
||||
this.targetId = targetId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Date getDeleteDate() {
|
||||
return deleteDate;
|
||||
}
|
||||
@ -101,4 +105,9 @@ public class Acl {
|
||||
.add("targetId", targetId)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMessage() {
|
||||
return perm.name();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,178 @@
|
||||
package com.sismics.docs.core.model.jpa;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.sismics.docs.core.constant.AuditLogType;
|
||||
|
||||
/**
|
||||
* Audit log.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
@Entity
|
||||
@Table(name = "T_AUDIT_LOG")
|
||||
public class AuditLog {
|
||||
/**
|
||||
* Audit log ID.
|
||||
*/
|
||||
@Id
|
||||
@Column(name = "LOG_ID_C", length = 36)
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Entity ID.
|
||||
*/
|
||||
@Column(name = "LOG_IDENTITY_C", nullable = false, length = 36)
|
||||
private String entityId;
|
||||
|
||||
/**
|
||||
* Entity class.
|
||||
*/
|
||||
@Column(name = "LOG_CLASSENTITY_C", nullable = false, length = 50)
|
||||
private String entityClass;
|
||||
|
||||
/**
|
||||
* Audit log type.
|
||||
*/
|
||||
@Column(name = "LOG_TYPE_C", nullable = false, length = 50)
|
||||
@Enumerated(EnumType.STRING)
|
||||
private AuditLogType type;
|
||||
|
||||
/**
|
||||
* Audit log message.
|
||||
*/
|
||||
@Column(name = "LOG_MESSAGE_C", length = 1000)
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* Creation date.
|
||||
*/
|
||||
@Column(name = "LOG_CREATEDATE_D", nullable = false)
|
||||
private Date createDate;
|
||||
|
||||
/**
|
||||
* Getter of id.
|
||||
*
|
||||
* @return id
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of id.
|
||||
*
|
||||
* @param id id
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of entityId.
|
||||
*
|
||||
* @return entityId
|
||||
*/
|
||||
public String getEntityId() {
|
||||
return entityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of entityId.
|
||||
*
|
||||
* @param entityId entityId
|
||||
*/
|
||||
public void setEntityId(String entityId) {
|
||||
this.entityId = entityId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of entityClass.
|
||||
*
|
||||
* @return entityClass
|
||||
*/
|
||||
public String getEntityClass() {
|
||||
return entityClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of entityClass.
|
||||
*
|
||||
* @param entityClass entityClass
|
||||
*/
|
||||
public void setEntityClass(String entityClass) {
|
||||
this.entityClass = entityClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of message.
|
||||
*
|
||||
* @return message
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of message.
|
||||
*
|
||||
* @param message message
|
||||
*/
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of type.
|
||||
*
|
||||
* @return type
|
||||
*/
|
||||
public AuditLogType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of type.
|
||||
*
|
||||
* @param type type
|
||||
*/
|
||||
public void setType(AuditLogType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of createDate.
|
||||
*
|
||||
* @return createDate
|
||||
*/
|
||||
public Date getCreateDate() {
|
||||
return createDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of createDate.
|
||||
*
|
||||
* @param createDate createDate
|
||||
*/
|
||||
public void setCreateDate(Date createDate) {
|
||||
this.createDate = createDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Objects.toStringHelper(this)
|
||||
.add("id", id)
|
||||
.add("entityId", entityId)
|
||||
.add("entityClass", entityClass)
|
||||
.add("type", type)
|
||||
.toString();
|
||||
}
|
||||
}
|
@ -29,6 +29,18 @@ public class AuthenticationToken {
|
||||
@Column(name = "AUT_IDUSER_C", nullable = false, length = 36)
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* Login IP.
|
||||
*/
|
||||
@Column(name = "AUT_IP_C", nullable = true, length = 45)
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* Login user agent.
|
||||
*/
|
||||
@Column(name = "AUT_UA_C", nullable = true, length = 1000)
|
||||
private String userAgent;
|
||||
|
||||
/**
|
||||
* Remember the user next time (long lasted session).
|
||||
*/
|
||||
@ -101,6 +113,38 @@ public class AuthenticationToken {
|
||||
this.longLasted = longLasted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of ip.
|
||||
* @return ip
|
||||
*/
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of ip.
|
||||
* @param ip ip
|
||||
*/
|
||||
public void setIp(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of userAgent.
|
||||
* @return userAgent
|
||||
*/
|
||||
public String getUserAgent() {
|
||||
return userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter of userAgent.
|
||||
* @param userAgent userAgent
|
||||
*/
|
||||
public void setUserAgent(String userAgent) {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter of creationDate.
|
||||
*
|
||||
@ -142,6 +186,8 @@ public class AuthenticationToken {
|
||||
return Objects.toStringHelper(this)
|
||||
.add("id", "**hidden**")
|
||||
.add("userId", userId)
|
||||
.add("ip", ip)
|
||||
.add("userAgent", userAgent)
|
||||
.add("longLasted", longLasted)
|
||||
.toString();
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package com.sismics.docs.core.model.jpa;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@ -14,8 +17,9 @@ import java.util.Date;
|
||||
* @author bgamard
|
||||
*/
|
||||
@Entity
|
||||
@EntityListeners(AuditLogUtil.class)
|
||||
@Table(name = "T_DOCUMENT")
|
||||
public class Document {
|
||||
public class Document implements Loggable {
|
||||
/**
|
||||
* Document ID.
|
||||
*/
|
||||
@ -172,6 +176,7 @@ public class Document {
|
||||
*
|
||||
* @return the deleteDate
|
||||
*/
|
||||
@Override
|
||||
public Date getDeleteDate() {
|
||||
return deleteDate;
|
||||
}
|
||||
@ -191,4 +196,9 @@ public class Document {
|
||||
.add("id", id)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMessage() {
|
||||
return title;
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Link between a document and a tag.
|
||||
@ -31,17 +33,21 @@ public class DocumentTag implements Serializable {
|
||||
/**
|
||||
* Document ID.
|
||||
*/
|
||||
@Id
|
||||
@Column(name = "DOT_IDDOCUMENT_C", length = 36)
|
||||
private String documentId;
|
||||
|
||||
/**
|
||||
* Tag ID.
|
||||
*/
|
||||
@Id
|
||||
@Column(name = "DOT_IDTAG_C", length = 36)
|
||||
private String tagId;
|
||||
|
||||
/**
|
||||
* Deletion date.
|
||||
*/
|
||||
@Column(name = "DOT_DELETEDATE_D")
|
||||
private Date deleteDate;
|
||||
|
||||
/**
|
||||
* Getter of id.
|
||||
*
|
||||
@ -96,42 +102,22 @@ public class DocumentTag implements Serializable {
|
||||
this.tagId = tagId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((documentId == null) ? 0 : documentId.hashCode());
|
||||
result = prime * result + ((tagId == null) ? 0 : tagId.hashCode());
|
||||
return result;
|
||||
/**
|
||||
* Getter of deleteDate.
|
||||
*
|
||||
* @return the deleteDate
|
||||
*/
|
||||
public Date getDeleteDate() {
|
||||
return deleteDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
DocumentTag other = (DocumentTag) obj;
|
||||
if (documentId == null) {
|
||||
if (other.documentId != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!documentId.equals(other.documentId)) {
|
||||
return false;
|
||||
}
|
||||
if (tagId == null) {
|
||||
if (other.tagId != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!tagId.equals(other.tagId)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
/**
|
||||
* Setter of deleteDate.
|
||||
*
|
||||
* @param deleteDate deleteDate
|
||||
*/
|
||||
public void setDeleteDate(Date deleteDate) {
|
||||
this.deleteDate = deleteDate;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,12 +1,15 @@
|
||||
package com.sismics.docs.core.model.jpa;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Lob;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@ -15,8 +18,9 @@ import java.util.Date;
|
||||
* @author bgamard
|
||||
*/
|
||||
@Entity
|
||||
@EntityListeners(AuditLogUtil.class)
|
||||
@Table(name = "T_FILE")
|
||||
public class File {
|
||||
public class File implements Loggable {
|
||||
/**
|
||||
* File ID.
|
||||
*/
|
||||
@ -144,6 +148,7 @@ public class File {
|
||||
*
|
||||
* @return the deleteDate
|
||||
*/
|
||||
@Override
|
||||
public Date getDeleteDate() {
|
||||
return deleteDate;
|
||||
}
|
||||
@ -217,4 +222,9 @@ public class File {
|
||||
.add("id", id)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMessage() {
|
||||
return documentId;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
package com.sismics.docs.core.model.jpa;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* An entity which can be logged.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public interface Loggable {
|
||||
/**
|
||||
* Get a string representation of this entity for logging purpose.
|
||||
* Avoid returning sensitive data like passwords.
|
||||
*
|
||||
* @return Entity message
|
||||
*/
|
||||
public String toMessage();
|
||||
|
||||
/**
|
||||
* Loggable are soft deletable.
|
||||
*
|
||||
* @return deleteDate
|
||||
*/
|
||||
public Date getDeleteDate();
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
package com.sismics.docs.core.model.jpa;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
@ -14,8 +17,9 @@ import java.util.Date;
|
||||
* @author bgamard
|
||||
*/
|
||||
@Entity
|
||||
@EntityListeners(AuditLogUtil.class)
|
||||
@Table(name = "T_TAG")
|
||||
public class Tag {
|
||||
public class Tag implements Loggable {
|
||||
/**
|
||||
* Tag ID.
|
||||
*/
|
||||
@ -148,6 +152,7 @@ public class Tag {
|
||||
*
|
||||
* @return deleteDate
|
||||
*/
|
||||
@Override
|
||||
public Date getDeleteDate() {
|
||||
return deleteDate;
|
||||
}
|
||||
@ -168,4 +173,9 @@ public class Tag {
|
||||
.add("name", name)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMessage() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,15 @@
|
||||
package com.sismics.docs.core.model.jpa;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EntityListeners;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Date;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.sismics.docs.core.util.AuditLogUtil;
|
||||
|
||||
/**
|
||||
* User entity.
|
||||
@ -14,8 +17,9 @@ import java.util.Date;
|
||||
* @author jtremeaux
|
||||
*/
|
||||
@Entity
|
||||
@EntityListeners(AuditLogUtil.class)
|
||||
@Table(name = "T_USER")
|
||||
public class User {
|
||||
public class User implements Loggable {
|
||||
/**
|
||||
* User ID.
|
||||
*/
|
||||
@ -250,6 +254,7 @@ public class User {
|
||||
*
|
||||
* @return deleteDate
|
||||
*/
|
||||
@Override
|
||||
public Date getDeleteDate() {
|
||||
return deleteDate;
|
||||
}
|
||||
@ -286,4 +291,9 @@ public class User {
|
||||
.add("username", username)
|
||||
.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toMessage() {
|
||||
return username;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
package com.sismics.docs.core.util;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import com.sismics.docs.core.constant.AuditLogType;
|
||||
import com.sismics.docs.core.dao.jpa.AuditLogDao;
|
||||
import com.sismics.docs.core.model.jpa.AuditLog;
|
||||
import com.sismics.docs.core.model.jpa.Loggable;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
|
||||
/**
|
||||
* Audit log utilities.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class AuditLogUtil {
|
||||
/**
|
||||
* Create an audit log.
|
||||
*
|
||||
* @param entity Entity
|
||||
* @param type Audit log type
|
||||
*/
|
||||
public static void create(Loggable loggable, AuditLogType type) {
|
||||
// Get the entity ID
|
||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||
String entityId = (String) em.getEntityManagerFactory().getPersistenceUnitUtil().getIdentifier(loggable);
|
||||
|
||||
// Create the audit log
|
||||
AuditLogDao auditLogDao = new AuditLogDao();
|
||||
AuditLog auditLog = new AuditLog();
|
||||
auditLog.setEntityId(entityId);
|
||||
auditLog.setEntityClass(loggable.getClass().getSimpleName());
|
||||
auditLog.setType(type);
|
||||
auditLog.setMessage(loggable.toMessage());
|
||||
auditLogDao.create(auditLog);
|
||||
}
|
||||
}
|
@ -17,5 +17,6 @@
|
||||
<class>com.sismics.docs.core.model.jpa.DocumentTag</class>
|
||||
<class>com.sismics.docs.core.model.jpa.Share</class>
|
||||
<class>com.sismics.docs.core.model.jpa.Acl</class>
|
||||
<class>com.sismics.docs.core.model.jpa.AuditLog</class>
|
||||
</persistence-unit>
|
||||
</persistence>
|
@ -1 +1 @@
|
||||
db.version=9
|
||||
db.version=10
|
@ -0,0 +1,7 @@
|
||||
alter table T_FILE alter column FIL_IDUSER_C set not null;
|
||||
alter table T_AUTHENTICATION_TOKEN add column AUT_IP_C varchar(45);
|
||||
alter table T_AUTHENTICATION_TOKEN add column AUT_UA_C varchar(1000);
|
||||
create cached table T_AUDIT_LOG ( LOG_ID_C varchar(36) not null, LOG_IDENTITY_C varchar(36) not null, LOG_CLASSENTITY_C varchar(50) not null, LOG_TYPE_C varchar(50) not null, LOG_MESSAGE_C varchar(1000), LOG_CREATEDATE_D datetime, primary key (LOG_ID_C) );
|
||||
create index IDX_LOG_COMPOSITE on T_AUDIT_LOG (LOG_IDENTITY_C, LOG_CLASSENTITY_C);
|
||||
alter table T_DOCUMENT_TAG add column DOT_DELETEDATE_D datetime;
|
||||
update T_CONFIG set CFG_VALUE_C='10' where CFG_ID_C='DB_VERSION';
|
@ -118,8 +118,6 @@ public class RequestContextFilter implements Filter {
|
||||
}
|
||||
}
|
||||
|
||||
ThreadLocalContext.cleanup();
|
||||
|
||||
// No error processing the request : commit / rollback the current transaction depending on the HTTP code
|
||||
if (em.isOpen()) {
|
||||
if (em.getTransaction() != null && em.getTransaction().isActive()) {
|
||||
@ -143,5 +141,7 @@ public class RequestContextFilter implements Filter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ThreadLocalContext.cleanup();
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=9
|
||||
db.version=10
|
@ -20,17 +20,13 @@ import org.apache.log4j.Logger;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
import com.sismics.docs.core.dao.jpa.DocumentDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.DocumentCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.DocumentDto;
|
||||
import com.sismics.docs.core.model.context.AppContext;
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
import com.sismics.docs.core.util.DirectoryUtil;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
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.exception.ServerException;
|
||||
@ -64,20 +60,6 @@ public class AppResource extends BaseResource {
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
|
||||
// Specific data
|
||||
DocumentDao documentDao = new DocumentDao();
|
||||
PaginatedList<DocumentDto> paginatedList = PaginatedLists.create(1, 0);
|
||||
SortCriteria sortCriteria = new SortCriteria(0, true);
|
||||
DocumentCriteria documentCriteria = new DocumentCriteria();
|
||||
documentCriteria.setUserId(principal.getId());
|
||||
try {
|
||||
documentDao.findByCriteria(paginatedList, documentCriteria, sortCriteria);
|
||||
} catch (Exception e) {
|
||||
throw new ServerException("SearchError", "Error searching in documents", e);
|
||||
}
|
||||
response.put("document_count", paginatedList.getResultCount());
|
||||
|
||||
// General data
|
||||
response.put("current_version", currentVersion.replace("-SNAPSHOT", ""));
|
||||
response.put("min_version", minVersion);
|
||||
response.put("total_memory", Runtime.getRuntime().totalMemory());
|
||||
|
@ -0,0 +1,91 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
import org.codehaus.jettison.json.JSONObject;
|
||||
|
||||
import com.sismics.docs.core.constant.PermType;
|
||||
import com.sismics.docs.core.dao.jpa.AclDao;
|
||||
import com.sismics.docs.core.dao.jpa.AuditLogDao;
|
||||
import com.sismics.docs.core.dao.jpa.criteria.AuditLogCriteria;
|
||||
import com.sismics.docs.core.dao.jpa.dto.AuditLogDto;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedList;
|
||||
import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.core.util.jpa.SortCriteria;
|
||||
import com.sismics.rest.exception.ForbiddenClientException;
|
||||
import com.sismics.rest.exception.ServerException;
|
||||
|
||||
/**
|
||||
* Audit log REST resources.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
@Path("/auditlog")
|
||||
public class AuditLogResource extends BaseResource {
|
||||
/**
|
||||
* Returns the list of all logs for a document or user.
|
||||
*
|
||||
* @return Response
|
||||
* @throws JSONException
|
||||
*/
|
||||
@GET
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Response list(@QueryParam("document") String documentId) throws JSONException {
|
||||
if (!authenticate()) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// On a document or a user?
|
||||
PaginatedList<AuditLogDto> paginatedList = PaginatedLists.create(20, 0);
|
||||
SortCriteria sortCriteria = new SortCriteria(1, false);
|
||||
AuditLogCriteria criteria = new AuditLogCriteria();
|
||||
if (documentId == null) {
|
||||
// Search logs for a user
|
||||
criteria.setUserId(principal.getId());
|
||||
} else {
|
||||
// Check ACL on the document
|
||||
AclDao aclDao = new AclDao();
|
||||
if (!aclDao.checkPermission(documentId, PermType.READ, principal.getId())) {
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
criteria.setDocumentId(documentId);
|
||||
}
|
||||
|
||||
// Search the logs
|
||||
try {
|
||||
AuditLogDao auditLogDao = new AuditLogDao();
|
||||
auditLogDao.findByCriteria(paginatedList, criteria, sortCriteria);
|
||||
} catch (Exception e) {
|
||||
throw new ServerException("SearchError", "Error searching in logs", e);
|
||||
}
|
||||
|
||||
// Assemble the results
|
||||
List<JSONObject> logs = new ArrayList<>();
|
||||
JSONObject response = new JSONObject();
|
||||
for (AuditLogDto auditLogDto : paginatedList.getResultList()) {
|
||||
JSONObject log = new JSONObject();
|
||||
log.put("id", auditLogDto.getId());
|
||||
log.put("target", auditLogDto.getEntityId());
|
||||
log.put("class", auditLogDto.getEntityClass());
|
||||
log.put("type", auditLogDto.getType().name());
|
||||
log.put("message", auditLogDto.getMessage());
|
||||
log.put("create_date", auditLogDto.getCreateTimestamp());
|
||||
logs.add(log);
|
||||
}
|
||||
|
||||
// Send the response
|
||||
response.put("logs", logs);
|
||||
response.put("total", paginatedList.getResultCount());
|
||||
return Response.ok().entity(response).build();
|
||||
}
|
||||
}
|
@ -20,6 +20,7 @@ import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.codehaus.jettison.json.JSONException;
|
||||
@ -92,7 +93,7 @@ public class DocumentResource extends BaseResource {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
JSONObject document = new JSONObject();
|
||||
@ -430,7 +431,7 @@ public class DocumentResource extends BaseResource {
|
||||
try {
|
||||
document = documentDao.getDocument(id, PermType.WRITE, principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
// Update the document
|
||||
@ -447,6 +448,8 @@ public class DocumentResource extends BaseResource {
|
||||
document.setLanguage(language);
|
||||
}
|
||||
|
||||
document = documentDao.update(document);
|
||||
|
||||
// Update tags
|
||||
updateTagList(id, tagList);
|
||||
|
||||
@ -512,7 +515,7 @@ public class DocumentResource extends BaseResource {
|
||||
document = documentDao.getDocument(id, PermType.WRITE, principal.getId());
|
||||
fileList = fileDao.getByDocumentId(principal.getId(), id);
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", id));
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
// Delete the document
|
||||
|
@ -57,6 +57,7 @@ import com.sismics.rest.exception.ServerException;
|
||||
import com.sismics.rest.util.ValidationUtil;
|
||||
import com.sismics.util.mime.MimeType;
|
||||
import com.sismics.util.mime.MimeTypeUtil;
|
||||
import com.sun.jersey.api.client.ClientResponse.Status;
|
||||
import com.sun.jersey.multipart.FormDataBodyPart;
|
||||
import com.sun.jersey.multipart.FormDataParam;
|
||||
|
||||
@ -101,7 +102,7 @@ public class FileResource extends BaseResource {
|
||||
try {
|
||||
document = documentDao.getDocument(documentId, PermType.WRITE, principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,7 +199,7 @@ public class FileResource extends BaseResource {
|
||||
file = fileDao.getFile(id, principal.getId());
|
||||
document = documentDao.getDocument(documentId, PermType.WRITE, principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
// Check that the file is orphan
|
||||
@ -258,7 +259,7 @@ public class FileResource extends BaseResource {
|
||||
try {
|
||||
documentDao.getDocument(documentId, PermType.WRITE, principal.getId());
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
// Reorder files
|
||||
@ -294,13 +295,9 @@ public class FileResource extends BaseResource {
|
||||
|
||||
// Check document visibility
|
||||
if (documentId != null) {
|
||||
try {
|
||||
AclDao aclDao = new AclDao();
|
||||
if (!aclDao.checkPermission(documentId, PermType.READ, shareId == null ? principal.getId() : shareId)) {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
} else if (!authenticated) {
|
||||
throw new ForbiddenClientException();
|
||||
@ -357,7 +354,7 @@ public class FileResource extends BaseResource {
|
||||
documentDao.getDocument(file.getDocumentId(), PermType.WRITE, principal.getId());
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", id));
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
// Delete the file
|
||||
@ -417,7 +414,7 @@ public class FileResource extends BaseResource {
|
||||
}
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("FileNotFound", MessageFormat.format("File not found: {0}", fileId));
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
|
||||
@ -461,7 +458,7 @@ public class FileResource extends BaseResource {
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
throw new ServerException("FileError", "Error while reading the file", e);
|
||||
return Response.status(Status.SERVICE_UNAVAILABLE).build();
|
||||
}
|
||||
|
||||
return Response.ok(stream)
|
||||
@ -497,7 +494,7 @@ public class FileResource extends BaseResource {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
} catch (NoResultException e) {
|
||||
throw new ClientException("DocumentNotFound", MessageFormat.format("Document not found: {0}", documentId));
|
||||
return Response.status(Status.NOT_FOUND).build();
|
||||
}
|
||||
|
||||
// Get files and user associated with this document
|
||||
|
@ -175,6 +175,8 @@ public class TagResource extends BaseResource {
|
||||
tag.setColor(color);
|
||||
}
|
||||
|
||||
tagDao.update(tag);
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("id", id);
|
||||
return Response.ok().entity(response).build();
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.sismics.docs.core.constant.Constants;
|
||||
import com.sismics.docs.core.dao.jpa.AuthenticationTokenDao;
|
||||
import com.sismics.docs.core.dao.jpa.RoleBaseFunctionDao;
|
||||
@ -289,11 +290,19 @@ public class UserResource extends BaseResource {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
|
||||
// Get the remote IP
|
||||
String ip = request.getHeader("x-forwarded-for");
|
||||
if (Strings.isNullOrEmpty(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
|
||||
// Create a new session token
|
||||
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
|
||||
AuthenticationToken authenticationToken = new AuthenticationToken();
|
||||
authenticationToken.setUserId(userId);
|
||||
authenticationToken.setLongLasted(longLasted);
|
||||
authenticationToken.setIp(ip);
|
||||
authenticationToken.setUserAgent(StringUtils.abbreviate(request.getHeader("user-agent"), 1000));
|
||||
String token = authenticationTokenDao.create(authenticationToken);
|
||||
|
||||
// Cleanup old session tokens
|
||||
@ -566,6 +575,8 @@ public class UserResource extends BaseResource {
|
||||
for (AuthenticationToken authenticationToken : authenticationTokenDao.getByUserId(principal.getId())) {
|
||||
JSONObject session = new JSONObject();
|
||||
session.put("create_date", authenticationToken.getCreationDate().getTime());
|
||||
session.put("ip", authenticationToken.getIp());
|
||||
session.put("user_agent", authenticationToken.getUserAgent());
|
||||
if (authenticationToken.getLastConnectionDate() != null) {
|
||||
session.put("last_connection_date", authenticationToken.getLastConnectionDate().getTime());
|
||||
}
|
||||
|
@ -9,6 +9,11 @@ angular.module('docs').controller('DocumentDefault', function($scope, $state, Re
|
||||
$scope.app = data;
|
||||
});
|
||||
|
||||
// Load user audit log
|
||||
Restangular.one('auditlog').get().then(function(data) {
|
||||
$scope.logs = data.logs;
|
||||
});
|
||||
|
||||
/**
|
||||
* Load unlinked files.
|
||||
*/
|
||||
|
@ -4,9 +4,18 @@
|
||||
* Document view controller.
|
||||
*/
|
||||
angular.module('docs').controller('DocumentView', function ($scope, $state, $stateParams, $location, $dialog, $modal, Restangular, $upload, $q) {
|
||||
// Load data from server
|
||||
// Load document data from server
|
||||
Restangular.one('document', $stateParams.id).get().then(function(data) {
|
||||
$scope.document = data;
|
||||
}, function(response) {
|
||||
$scope.error = response;
|
||||
});
|
||||
|
||||
// Load audit log data from server
|
||||
Restangular.one('auditlog').get({
|
||||
document: $stateParams.id
|
||||
}).then(function(data) {
|
||||
$scope.logs = data.logs;
|
||||
});
|
||||
|
||||
// Watch for ACLs change and group them for easy displaying
|
||||
|
15
docs-web/src/main/webapp/src/app/docs/directive/AuditLog.js
Normal file
@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Audit log directive.
|
||||
*/
|
||||
angular.module('docs').directive('auditLog', function() {
|
||||
return {
|
||||
restrict: 'E',
|
||||
templateUrl: 'partial/docs/directive.auditlog.html',
|
||||
replace: true,
|
||||
scope: {
|
||||
logs: '='
|
||||
}
|
||||
}
|
||||
});
|
16
docs-web/src/main/webapp/src/app/docs/directive/ImgError.js
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Image error event directive.
|
||||
*/
|
||||
angular.module('docs').directive('imgError', function() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
link: function(scope, element, attrs) {
|
||||
element.bind('error', function() {
|
||||
//call the function that was passed
|
||||
scope.$apply(attrs.imgError);
|
||||
});
|
||||
}
|
||||
};
|
||||
})
|
@ -62,7 +62,9 @@
|
||||
<script src="app/docs/filter/Shorten.js" type="text/javascript"></script>
|
||||
<script src="app/docs/directive/File.js" type="text/javascript"></script>
|
||||
<script src="app/docs/directive/SelectTag.js" type="text/javascript"></script>
|
||||
<script src="app/docs/directive/AuditLog.js" type="text/javascript"></script>
|
||||
<script src="app/docs/directive/InlineEdit.js" type="text/javascript"></script>
|
||||
<script src="app/docs/directive/ImgError.js" type="text/javascript"></script>
|
||||
<!-- endref -->
|
||||
</head>
|
||||
<body>
|
||||
|
@ -0,0 +1,32 @@
|
||||
<table class="table">
|
||||
<tr ng-repeat="log in logs">
|
||||
<td>{{ log.create_date | date: 'yyyy-MM-dd HH:mm' }}</td>
|
||||
<td>
|
||||
{{ log.class }}
|
||||
<span ng-switch="log.type">
|
||||
<span ng-switch-when="CREATE">created</span>
|
||||
<span ng-switch-when="UPDATE">updated</span>
|
||||
<span ng-switch-when="DELETE">deleted</span>
|
||||
</span>
|
||||
|
||||
<span ng-switch="log.class">
|
||||
:
|
||||
<span ng-switch-when="Document">
|
||||
<a ng-href="#/document/view/{{ log.target }}">{{ log.message }}</a>
|
||||
</span>
|
||||
<span ng-switch-when="File">
|
||||
<a ng-href="#/document/view/{{ log.message }}/file/{{ log.target }}">Open</a>
|
||||
</span>
|
||||
<span ng-switch-when="Acl">
|
||||
{{ log.message }}
|
||||
</span>
|
||||
<span ng-switch-when="Tag">
|
||||
<a href="#/tag">{{ log.message }}</a>
|
||||
</span>
|
||||
<span ng-switch-when="User">
|
||||
<a href="#/settings/account">{{ log.message }}</a>
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
@ -1,10 +1,8 @@
|
||||
<img src="img/loader.gif" ng-show="!app" />
|
||||
|
||||
<div ng-show="app">
|
||||
<h1>
|
||||
{{ app.document_count }} <small>document{{ app.document_count > 1 ? 's' : '' }} in the database</small>
|
||||
</h1>
|
||||
|
||||
<div class="well">
|
||||
<h3>Quick upload</h3>
|
||||
<div class="row upload-zone" ng-model="dropFiles" ng-file-drop drag-over-class="bg-success"
|
||||
ng-multiple="true" allow-dir="false" accept="image/*,application/pdf,application/zip" ng-file-change="fileDropped($files, $event, $rejectedFiles)">
|
||||
<div class="col-xs-6 col-sm-4 col-md-3 col-lg-2 text-center" ng-repeat="file in files">
|
||||
@ -42,9 +40,15 @@
|
||||
<div class="btn-group" ng-show="checkedFiles().length > 0">
|
||||
<button class="btn btn-primary" ng-click="addDocument()"><span class="glyphicon glyphicon-plus"></span> Add to new document</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ui-view="file"></div>
|
||||
|
||||
<div class="well">
|
||||
<h3>Latest activity</h3>
|
||||
<audit-log logs="logs" />
|
||||
</div>
|
||||
|
||||
<div class="text-muted text-right">
|
||||
<ul class="list-inline">
|
||||
<li><strong>Version:</strong> {{ app.current_version }}</li>
|
||||
|
@ -68,6 +68,6 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="well" ui-view="document"></div>
|
||||
<div ui-view="document"></div>
|
||||
</div>
|
||||
</div>
|
@ -1,4 +1,11 @@
|
||||
<img src="img/loader.gif" ng-show="!document" />
|
||||
<img src="img/loader.gif" ng-show="!document && !error" />
|
||||
|
||||
<div ng-show="error" class="well-lg">
|
||||
<p class="text-center" ng-show="error.status == 404">
|
||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
||||
Document not found
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div ng-show="document">
|
||||
<div class="text-right" ng-show="document.writable">
|
||||
@ -135,6 +142,14 @@
|
||||
</form>
|
||||
</div>
|
||||
</tab>
|
||||
|
||||
<tab>
|
||||
<tab-heading class="pointer">
|
||||
<span class="glyphicon glyphicon-tasks"></span> Activity
|
||||
</tab-heading>
|
||||
|
||||
<audit-log logs="logs" />
|
||||
</tab>
|
||||
</tabset>
|
||||
|
||||
<div ui-view="file"></div>
|
||||
|
@ -22,5 +22,12 @@
|
||||
|
||||
|
||||
<div class="text-center" ng-if="$stateParams.fileId">
|
||||
<img ng-src="../api/file/{{ $stateParams.fileId }}/data?size=web" />
|
||||
<img ng-src="../api/file/{{ $stateParams.fileId }}/data?size=web"
|
||||
ng-init="error = false"
|
||||
img-error="error = true"
|
||||
ng-show="!error" />
|
||||
<p class="well-lg" ng-show="error">
|
||||
<span class="glyphicon glyphicon-warning-sign"></span>
|
||||
File not found
|
||||
</p>
|
||||
</div>
|
||||
|
@ -18,6 +18,6 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="well" ui-view="settings"></div>
|
||||
<div ui-view="settings"></div>
|
||||
</div>
|
||||
</div>
|
@ -4,6 +4,7 @@
|
||||
<tr>
|
||||
<th>Created date</th>
|
||||
<th>Last connection date</th>
|
||||
<th>From</th>
|
||||
<th>Current</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -11,6 +12,7 @@
|
||||
<tr ng-repeat="session in sessions | orderBy: '-current'" ng-class="{ 'info': session.current, 'warning': !session.current }">
|
||||
<td>{{ session.create_date | date: 'yyyy-MM-dd HH:mm' }}</td>
|
||||
<td>{{ session.last_connection_date | date: 'yyyy-MM-dd HH:mm' }}</td>
|
||||
<td title="{{ session.user_agent }}">{{ session.ip }}</td>
|
||||
<td><span ng-show="session.current" class="glyphicon glyphicon-ok"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
@ -30,18 +30,14 @@
|
||||
</div>
|
||||
|
||||
<div class="col-md-8" ng-if="stats.length >= 0">
|
||||
<div class="well">
|
||||
<h1>{{ tags.length }} <small>tag{{ tags.length > 1 ? 's' : '' }}</small></h1>
|
||||
<dl class="dl-horizontal" ng-repeat="stat in stats | orderBy: '-count'">
|
||||
<dt>{{ stat.name }} <span class="badge badge-info" ng-style="{ 'background': stat.color }">{{ stat.count }}</span></dt>
|
||||
<dd><progressbar value="stat.count / getStatCount() * 100" class="progress-info"></progressbar></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8" ng-if="!stats">
|
||||
<div class="well">
|
||||
<img src="img/loader.gif" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,3 +1,3 @@
|
||||
api.current_version=${project.version}
|
||||
api.min_version=1.0
|
||||
db.version=9
|
||||
db.version=10
|
@ -41,7 +41,6 @@ public class TestAppResource extends BaseJerseyTest {
|
||||
Assert.assertTrue(freeMemory > 0);
|
||||
Long totalMemory = json.getLong("total_memory");
|
||||
Assert.assertTrue(totalMemory > 0 && totalMemory > freeMemory);
|
||||
Assert.assertEquals(0, json.getInt("document_count"));
|
||||
|
||||
// Rebuild Lucene index
|
||||
appResource = resource().path("/app/batch/reindex");
|
||||
|
@ -0,0 +1,98 @@
|
||||
package com.sismics.docs.rest;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Test the audit log resource.
|
||||
*
|
||||
* @author bgamard
|
||||
*/
|
||||
public class TestAuditLogResource extends BaseJerseyTest {
|
||||
/**
|
||||
* Test the audit log resource.
|
||||
*
|
||||
* @throws JSONException
|
||||
*/
|
||||
@Test
|
||||
public void testAuditLogResource() throws JSONException {
|
||||
// Login auditlog1
|
||||
clientUtil.createUser("auditlog1");
|
||||
String auditlog1Token = clientUtil.login("auditlog1");
|
||||
|
||||
// Create a tag
|
||||
WebResource tagResource = resource().path("/tag");
|
||||
tagResource.addFilter(new CookieAuthenticationFilter(auditlog1Token));
|
||||
MultivaluedMapImpl postParams = new MultivaluedMapImpl();
|
||||
postParams.add("name", "SuperTag");
|
||||
postParams.add("color", "#ffff00");
|
||||
ClientResponse response = tagResource.put(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
JSONObject json = response.getEntity(JSONObject.class);
|
||||
String tag1Id = json.optString("id");
|
||||
Assert.assertNotNull(tag1Id);
|
||||
|
||||
// Create a document
|
||||
WebResource documentResource = resource().path("/document");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(auditlog1Token));
|
||||
postParams = new MultivaluedMapImpl();
|
||||
postParams.add("title", "My super title document 1");
|
||||
postParams.add("description", "My super description for document 1");
|
||||
postParams.add("tags", tag1Id);
|
||||
postParams.add("language", "eng");
|
||||
long 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 document1Id = json.optString("id");
|
||||
Assert.assertNotNull(document1Id);
|
||||
|
||||
// Get all logs for the document
|
||||
WebResource auditLogResource = resource().path("/auditlog");
|
||||
auditLogResource.addFilter(new CookieAuthenticationFilter(auditlog1Token));
|
||||
response = auditLogResource.queryParam("document", document1Id).get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
JSONArray logs = json.getJSONArray("logs");
|
||||
Assert.assertTrue(logs.length() == 3);
|
||||
|
||||
// Get all logs for the current user
|
||||
auditLogResource = resource().path("/auditlog");
|
||||
auditLogResource.addFilter(new CookieAuthenticationFilter(auditlog1Token));
|
||||
response = auditLogResource.get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
logs = json.getJSONArray("logs");
|
||||
Assert.assertTrue(logs.length() == 3);
|
||||
|
||||
// Deletes a tag
|
||||
tagResource = resource().path("/tag/" + tag1Id);
|
||||
tagResource.addFilter(new CookieAuthenticationFilter(auditlog1Token));
|
||||
response = tagResource.delete(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals("ok", json.getString("status"));
|
||||
|
||||
// Get all logs for the current user
|
||||
auditLogResource = resource().path("/auditlog");
|
||||
auditLogResource.addFilter(new CookieAuthenticationFilter(auditlog1Token));
|
||||
response = auditLogResource.get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
logs = json.getJSONArray("logs");
|
||||
Assert.assertTrue(logs.length() == 4);
|
||||
}
|
||||
}
|
@ -282,8 +282,7 @@ public class TestDocumentResource extends BaseJerseyTest {
|
||||
documentResource = resource().path("/document/" + document1Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(document1Token));
|
||||
response = documentResource.get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.BAD_REQUEST, Status.fromStatusCode(response.getStatus()));
|
||||
Assert.assertEquals(Status.NOT_FOUND, Status.fromStatusCode(response.getStatus()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -178,6 +178,12 @@ public class TestFileResource extends BaseJerseyTest {
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals("ok", json.getString("status"));
|
||||
|
||||
// Get the file data (not found)
|
||||
fileResource = resource().path("/file/" + file1Id + "/data");
|
||||
fileResource.addFilter(new CookieAuthenticationFilter(file1AuthenticationToken));
|
||||
response = fileResource.get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.NOT_FOUND, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Check that files are deleted from FS
|
||||
storedFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id).toFile();
|
||||
java.io.File webFile = Paths.get(DirectoryUtil.getStorageDirectory().getPath(), file1Id + "_web").toFile();
|
||||
|
@ -75,12 +75,61 @@ public class TestTagResource extends BaseJerseyTest {
|
||||
documentResource = resource().path("/document");
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(tag1Token));
|
||||
postParams = new MultivaluedMapImpl();
|
||||
postParams.add("title", "My super document 1");
|
||||
postParams.add("title", "My super document 2");
|
||||
postParams.add("tags", tag4Id);
|
||||
postParams.add("language", "eng");
|
||||
response = documentResource.put(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
String document2Id = json.getString("id");
|
||||
|
||||
// Check tags on a document
|
||||
documentResource = resource().path("/document/" + document2Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(tag1Token));
|
||||
response = documentResource.get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
JSONArray tags = json.getJSONArray("tags");
|
||||
Assert.assertEquals(1, tags.length());
|
||||
Assert.assertEquals(tag4Id, tags.getJSONObject(0).getString("id"));
|
||||
|
||||
// Update tags on a document
|
||||
documentResource = resource().path("/document/" + document2Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(tag1Token));
|
||||
postParams = new MultivaluedMapImpl();
|
||||
postParams.add("tags", tag3Id);
|
||||
postParams.add("tags", tag4Id);
|
||||
response = documentResource.post(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Check tags on a document
|
||||
documentResource = resource().path("/document/" + document2Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(tag1Token));
|
||||
response = documentResource.get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
tags = json.getJSONArray("tags");
|
||||
Assert.assertEquals(2, tags.length());
|
||||
Assert.assertEquals(tag3Id, tags.getJSONObject(0).getString("id"));
|
||||
Assert.assertEquals(tag4Id, tags.getJSONObject(1).getString("id"));
|
||||
|
||||
// Update tags on a document
|
||||
documentResource = resource().path("/document/" + document2Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(tag1Token));
|
||||
postParams = new MultivaluedMapImpl();
|
||||
postParams.add("tags", tag4Id);
|
||||
response = documentResource.post(ClientResponse.class, postParams);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
|
||||
// Check tags on a document
|
||||
documentResource = resource().path("/document/" + document2Id);
|
||||
documentResource.addFilter(new CookieAuthenticationFilter(tag1Token));
|
||||
response = documentResource.get(ClientResponse.class);
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
tags = json.getJSONArray("tags");
|
||||
Assert.assertEquals(1, tags.length());
|
||||
Assert.assertEquals(tag4Id, tags.getJSONObject(0).getString("id"));
|
||||
|
||||
// Get tag stats
|
||||
tagResource = resource().path("/tag/stats");
|
||||
@ -99,7 +148,7 @@ public class TestTagResource extends BaseJerseyTest {
|
||||
response = tagResource.get(ClientResponse.class);
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
JSONArray tags = json.getJSONArray("tags");
|
||||
tags = json.getJSONArray("tags");
|
||||
Assert.assertTrue(tags.length() > 0);
|
||||
Assert.assertEquals("Tag4", tags.getJSONObject(1).getString("name"));
|
||||
Assert.assertEquals("#00ff00", tags.getJSONObject(1).getString("color"));
|
||||
|
@ -144,6 +144,9 @@ public class TestUserResource extends BaseJerseyTest {
|
||||
Assert.assertEquals(Status.OK, Status.fromStatusCode(response.getStatus()));
|
||||
json = response.getEntity(JSONObject.class);
|
||||
Assert.assertTrue(json.getJSONArray("sessions").length() > 0);
|
||||
JSONObject session = json.getJSONArray("sessions").getJSONObject(0);
|
||||
Assert.assertEquals("127.0.0.1", session.getString("ip"));
|
||||
Assert.assertTrue(session.getString("user_agent").startsWith("Java"));
|
||||
|
||||
// Delete all sessions
|
||||
userResource = resource().path("/user/session");
|
||||
|