diff --git a/packages/lib/JoplinDatabase.ts b/packages/lib/JoplinDatabase.ts index 7c34dada29..485f890b59 100644 --- a/packages/lib/JoplinDatabase.ts +++ b/packages/lib/JoplinDatabase.ts @@ -351,7 +351,7 @@ export default class JoplinDatabase extends Database { // must be set in the synchronizer too. // Note: v16 and v17 don't do anything. They were used to debug an issue. - const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41]; + const existingDatabaseVersions = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42]; let currentVersionIndex = existingDatabaseVersions.indexOf(fromVersion); @@ -910,6 +910,10 @@ export default class JoplinDatabase extends Database { queries.push('ALTER TABLE `folders` ADD COLUMN icon TEXT NOT NULL DEFAULT ""'); } + if (targetVersion == 42) { + queries.push('ALTER TABLE `notes` ADD COLUMN is_shared_recursive INT NOT NULL DEFAULT 0'); + } + const updateVersionQuery = { sql: 'UPDATE version SET version = ?', params: [targetVersion] }; queries.push(updateVersionQuery); diff --git a/packages/lib/models/Folder.ts b/packages/lib/models/Folder.ts index 2a84316d74..0063b9d306 100644 --- a/packages/lib/models/Folder.ts +++ b/packages/lib/models/Folder.ts @@ -348,24 +348,55 @@ export default class Folder extends BaseItem { } public static async updateNoteShareIds() { - // Find all the notes where the share_id is not the same as the - // parent share_id because we only need to update those. - const rows = await this.db().selectAll(` - SELECT notes.id, folders.share_id, notes.parent_id - FROM notes - LEFT JOIN folders ON notes.parent_id = folders.id - WHERE notes.share_id != folders.share_id - `); + { + // Find all the notes where the share_id is not the same as the + // parent share_id because we only need to update those. + const rows = await this.db().selectAll(` + SELECT notes.id, folders.share_id, notes.parent_id + FROM notes + LEFT JOIN folders ON notes.parent_id = folders.id + WHERE notes.share_id != folders.share_id + `); - logger.debug('updateNoteShareIds: notes to update:', rows.length); + logger.debug('updateNoteShareIds: notes to update:', rows.length); - for (const row of rows) { - await Note.save({ - id: row.id, - share_id: row.share_id || '', - parent_id: row.parent_id, - updated_time: Date.now(), - }, { autoTimestamp: false }); + for (const row of rows) { + await Note.save({ + id: row.id, + share_id: row.share_id || '', + parent_id: row.parent_id, + updated_time: Date.now(), + }, { autoTimestamp: false }); + } + } + + // Also applies recursively published notes + { + // Find all the notes where the share_id is not the same as the + // parent share_id because we only need to update those. + const rows = await this.db().selectAll(` + SELECT notes.id, folders.share_id, notes.parent_id, notes.body + FROM notes + WHERE notes.is_shared = 1 AND notes.is_shared_recursive = 1 + `); + + logger.debug('updateNoteShareIds: notes recursively published:', rows.length); + + // for (const row of rows) { + + // // await Note.save({ + // // id: row.id, + // // share_id: row.share_id || '', + // // parent_id: row.parent_id, + // // updated_time: Date.now(), + // // }, { autoTimestamp: false }); + // } + + // TODO: should start from root notes - don't set is_recursive on children? + // TODO: when a child note is moved out of parent, how to know that is_shared should be cleared? - need share_root prop? + // or based on Share API objects? (passed to this function?) + // TODO: also unshare notes that have been unlinked (is_shared != parent.is_shared) + // TODO: avoid infinite loops } } @@ -625,7 +656,7 @@ export default class Folder extends BaseItem { rootFolders.push(folder); } else { if (!idToFolders[folder.parent_id]) { - // It means the notebook is refering a folder that doesn't exist. In theory it shouldn't happen + // It means the notebook is referring a folder that doesn't exist. In theory it shouldn't happen // but sometimes does - https://github.com/laurent22/joplin/issues/1068#issuecomment-450594708 rootFolders.push(folder); } else { diff --git a/packages/lib/services/database/types.ts b/packages/lib/services/database/types.ts index fd46f01458..ab1ea22378 100644 --- a/packages/lib/services/database/types.ts +++ b/packages/lib/services/database/types.ts @@ -52,6 +52,10 @@ export const defaultFolderIcon = () => { + + + + // AUTO-GENERATED BY packages/tools/generate-database-types.js /* @@ -59,225 +63,226 @@ export const defaultFolderIcon = () => { * Rerun sql-ts to regenerate this file. */ export interface AlarmEntity { - "id"?: number | null - "note_id"?: string - "trigger_time"?: number - "type_"?: number + 'id'?: number | null; + 'note_id'?: string; + 'trigger_time'?: number; + 'type_'?: number; } export interface DeletedItemEntity { - "id"?: number | null - "item_type"?: number - "item_id"?: string - "deleted_time"?: number - "sync_target"?: number - "type_"?: number + 'id'?: number | null; + 'item_type'?: number; + 'item_id'?: string; + 'deleted_time'?: number; + 'sync_target'?: number; + 'type_'?: number; } export interface FolderEntity { - "id"?: string | null - "title"?: string - "created_time"?: number - "updated_time"?: number - "user_created_time"?: number - "user_updated_time"?: number - "encryption_cipher_text"?: string - "encryption_applied"?: number - "parent_id"?: string - "is_shared"?: number - "share_id"?: string - "master_key_id"?: string - "icon"?: string - "type_"?: number + 'id'?: string | null; + 'title'?: string; + 'created_time'?: number; + 'updated_time'?: number; + 'user_created_time'?: number; + 'user_updated_time'?: number; + 'encryption_cipher_text'?: string; + 'encryption_applied'?: number; + 'parent_id'?: string; + 'is_shared'?: number; + 'share_id'?: string; + 'master_key_id'?: string; + 'icon'?: string; + 'type_'?: number; } export interface ItemChangeEntity { - "id"?: number | null - "item_type"?: number - "item_id"?: string - "type"?: number - "created_time"?: number - "source"?: number - "before_change_item"?: string - "type_"?: number + 'id'?: number | null; + 'item_type'?: number; + 'item_id'?: string; + 'type'?: number; + 'created_time'?: number; + 'source'?: number; + 'before_change_item'?: string; + 'type_'?: number; } export interface KeyValueEntity { - "id"?: number | null - "key"?: string - "value"?: string - "type"?: number - "updated_time"?: number - "type_"?: number + 'id'?: number | null; + 'key'?: string; + 'value'?: string; + 'type'?: number; + 'updated_time'?: number; + 'type_'?: number; } export interface MigrationEntity { - "id"?: number | null - "number"?: number - "updated_time"?: number - "created_time"?: number - "type_"?: number + 'id'?: number | null; + 'number'?: number; + 'updated_time'?: number; + 'created_time'?: number; + 'type_'?: number; } export interface NoteResourceEntity { - "id"?: number | null - "note_id"?: string - "resource_id"?: string - "is_associated"?: number - "last_seen_time"?: number - "type_"?: number + 'id'?: number | null; + 'note_id'?: string; + 'resource_id'?: string; + 'is_associated'?: number; + 'last_seen_time'?: number; + 'type_'?: number; } export interface NoteTagEntity { - "id"?: string | null - "note_id"?: string - "tag_id"?: string - "created_time"?: number - "updated_time"?: number - "user_created_time"?: number - "user_updated_time"?: number - "encryption_cipher_text"?: string - "encryption_applied"?: number - "is_shared"?: number - "type_"?: number + 'id'?: string | null; + 'note_id'?: string; + 'tag_id'?: string; + 'created_time'?: number; + 'updated_time'?: number; + 'user_created_time'?: number; + 'user_updated_time'?: number; + 'encryption_cipher_text'?: string; + 'encryption_applied'?: number; + 'is_shared'?: number; + 'type_'?: number; } export interface NoteEntity { - "id"?: string | null - "parent_id"?: string - "title"?: string - "body"?: string - "created_time"?: number - "updated_time"?: number - "is_conflict"?: number - "latitude"?: number - "longitude"?: number - "altitude"?: number - "author"?: string - "source_url"?: string - "is_todo"?: number - "todo_due"?: number - "todo_completed"?: number - "source"?: string - "source_application"?: string - "application_data"?: string - "order"?: number - "user_created_time"?: number - "user_updated_time"?: number - "encryption_cipher_text"?: string - "encryption_applied"?: number - "markup_language"?: number - "is_shared"?: number - "share_id"?: string - "conflict_original_id"?: string - "master_key_id"?: string - "type_"?: number + 'id'?: string | null; + 'parent_id'?: string; + 'title'?: string; + 'body'?: string; + 'created_time'?: number; + 'updated_time'?: number; + 'is_conflict'?: number; + 'latitude'?: number; + 'longitude'?: number; + 'altitude'?: number; + 'author'?: string; + 'source_url'?: string; + 'is_todo'?: number; + 'todo_due'?: number; + 'todo_completed'?: number; + 'source'?: string; + 'source_application'?: string; + 'application_data'?: string; + 'order'?: number; + 'user_created_time'?: number; + 'user_updated_time'?: number; + 'encryption_cipher_text'?: string; + 'encryption_applied'?: number; + 'markup_language'?: number; + 'is_shared'?: number; + 'share_id'?: string; + 'conflict_original_id'?: string; + 'master_key_id'?: string; + 'is_shared_recursive'?: number; + 'type_'?: number; } export interface NotesNormalizedEntity { - "id"?: string - "title"?: string - "body"?: string - "user_created_time"?: number - "user_updated_time"?: number - "is_todo"?: number - "todo_completed"?: number - "parent_id"?: string - "latitude"?: number - "longitude"?: number - "altitude"?: number - "source_url"?: string - "todo_due"?: number - "type_"?: number + 'id'?: string; + 'title'?: string; + 'body'?: string; + 'user_created_time'?: number; + 'user_updated_time'?: number; + 'is_todo'?: number; + 'todo_completed'?: number; + 'parent_id'?: string; + 'latitude'?: number; + 'longitude'?: number; + 'altitude'?: number; + 'source_url'?: string; + 'todo_due'?: number; + 'type_'?: number; } export interface ResourceLocalStateEntity { - "id"?: number | null - "resource_id"?: string - "fetch_status"?: number - "fetch_error"?: string - "type_"?: number + 'id'?: number | null; + 'resource_id'?: string; + 'fetch_status'?: number; + 'fetch_error'?: string; + 'type_'?: number; } export interface ResourceEntity { - "id"?: string | null - "title"?: string - "mime"?: string - "filename"?: string - "created_time"?: number - "updated_time"?: number - "user_created_time"?: number - "user_updated_time"?: number - "file_extension"?: string - "encryption_cipher_text"?: string - "encryption_applied"?: number - "encryption_blob_encrypted"?: number - "size"?: number - "is_shared"?: number - "share_id"?: string - "master_key_id"?: string - "type_"?: number + 'id'?: string | null; + 'title'?: string; + 'mime'?: string; + 'filename'?: string; + 'created_time'?: number; + 'updated_time'?: number; + 'user_created_time'?: number; + 'user_updated_time'?: number; + 'file_extension'?: string; + 'encryption_cipher_text'?: string; + 'encryption_applied'?: number; + 'encryption_blob_encrypted'?: number; + 'size'?: number; + 'is_shared'?: number; + 'share_id'?: string; + 'master_key_id'?: string; + 'type_'?: number; } export interface ResourcesToDownloadEntity { - "id"?: number | null - "resource_id"?: string - "updated_time"?: number - "created_time"?: number - "type_"?: number + 'id'?: number | null; + 'resource_id'?: string; + 'updated_time'?: number; + 'created_time'?: number; + 'type_'?: number; } export interface RevisionEntity { - "id"?: string | null - "parent_id"?: string - "item_type"?: number - "item_id"?: string - "item_updated_time"?: number - "title_diff"?: string - "body_diff"?: string - "metadata_diff"?: string - "encryption_cipher_text"?: string - "encryption_applied"?: number - "updated_time"?: number - "created_time"?: number - "type_"?: number + 'id'?: string | null; + 'parent_id'?: string; + 'item_type'?: number; + 'item_id'?: string; + 'item_updated_time'?: number; + 'title_diff'?: string; + 'body_diff'?: string; + 'metadata_diff'?: string; + 'encryption_cipher_text'?: string; + 'encryption_applied'?: number; + 'updated_time'?: number; + 'created_time'?: number; + 'type_'?: number; } export interface SettingEntity { - "key"?: string | null - "value"?: string | null - "type_"?: number + 'key'?: string | null; + 'value'?: string | null; + 'type_'?: number; } export interface SyncItemEntity { - "id"?: number | null - "sync_target"?: number - "sync_time"?: number - "item_type"?: number - "item_id"?: string - "sync_disabled"?: number - "sync_disabled_reason"?: string - "force_sync"?: number - "item_location"?: number - "type_"?: number + 'id'?: number | null; + 'sync_target'?: number; + 'sync_time'?: number; + 'item_type'?: number; + 'item_id'?: string; + 'sync_disabled'?: number; + 'sync_disabled_reason'?: string; + 'force_sync'?: number; + 'item_location'?: number; + 'type_'?: number; } export interface TableFieldEntity { - "id"?: number | null - "table_name"?: string - "field_name"?: string - "field_type"?: number - "field_default"?: string | null - "type_"?: number + 'id'?: number | null; + 'table_name'?: string; + 'field_name'?: string; + 'field_type'?: number; + 'field_default'?: string | null; + 'type_'?: number; } export interface TagEntity { - "id"?: string | null - "title"?: string - "created_time"?: number - "updated_time"?: number - "user_created_time"?: number - "user_updated_time"?: number - "encryption_cipher_text"?: string - "encryption_applied"?: number - "is_shared"?: number - "parent_id"?: string - "type_"?: number + 'id'?: string | null; + 'title'?: string; + 'created_time'?: number; + 'updated_time'?: number; + 'user_created_time'?: number; + 'user_updated_time'?: number; + 'encryption_cipher_text'?: string; + 'encryption_applied'?: number; + 'is_shared'?: number; + 'parent_id'?: string; + 'type_'?: number; } export interface TagsWithNoteCountEntity { - "id"?: string | null - "title"?: string | null - "created_time"?: number | null - "updated_time"?: number | null - "note_count"?: any | null - "todo_completed_count"?: any | null - "type_"?: number + 'id'?: string | null; + 'title'?: string | null; + 'created_time'?: number | null; + 'updated_time'?: number | null; + 'note_count'?: any | null; + 'todo_completed_count'?: any | null; + 'type_'?: number; } export interface VersionEntity { - "version"?: number - "table_fields_version"?: number - "type_"?: number + 'version'?: number; + 'table_fields_version'?: number; + 'type_'?: number; } diff --git a/packages/lib/services/share/ShareService.ts b/packages/lib/services/share/ShareService.ts index 02025c8b58..e2624078f7 100644 --- a/packages/lib/services/share/ShareService.ts +++ b/packages/lib/services/share/ShareService.ts @@ -241,6 +241,7 @@ export default class ShareService { id: note.id, parent_id: note.parent_id, is_shared: 1, + is_shared_recursive: recursive ? 1 : 0, updated_time: Date.now(), }, { autoTimestamp: false, diff --git a/packages/lib/services/share/reducer.ts b/packages/lib/services/share/reducer.ts index e840d1f2f3..d43150f3d7 100644 --- a/packages/lib/services/share/reducer.ts +++ b/packages/lib/services/share/reducer.ts @@ -27,6 +27,7 @@ export interface StateShare { folder_id: string; note_id: string; master_key_id: string; + recursive: number; user?: StateShareUserUser; } diff --git a/packages/server/src/models/ShareModel.ts b/packages/server/src/models/ShareModel.ts index 28d53a6f50..91721ead04 100644 --- a/packages/server/src/models/ShareModel.ts +++ b/packages/server/src/models/ShareModel.ts @@ -64,6 +64,7 @@ export default class ShareModel extends BaseModel { if (object.owner_id) output.owner_id = object.owner_id; if (object.note_id) output.note_id = object.note_id; if (object.master_key_id) output.master_key_id = object.master_key_id; + if ('recursive' in object) output.recursive = object.recursive; return output; }