Compare commits
2 Commits
server-v2.
...
server_del
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
058bd3c2f6 | ||
|
|
c101555773 |
4
Assets/WebsiteAssets/css/font-awesome.min.css
vendored
Normal file
12
Assets/WebsiteAssets/css/fork-awesome.min.css
vendored
Normal file
BIN
Assets/WebsiteAssets/fonts/forkawesome-webfont.eot
Normal file
2849
Assets/WebsiteAssets/fonts/forkawesome-webfont.svg
Normal file
|
After Width: | Height: | Size: 470 KiB |
BIN
Assets/WebsiteAssets/fonts/forkawesome-webfont.ttf
Normal file
BIN
Assets/WebsiteAssets/fonts/forkawesome-webfont.woff
Normal file
BIN
Assets/WebsiteAssets/fonts/forkawesome-webfont.woff2
Normal file
@@ -2,12 +2,11 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-12 social-links">
|
||||
<a href="https://twitter.com/joplinapp" title="Joplin Twitter feed"><i class="fab fa-twitter"></i></a>
|
||||
<a href="https://mastodon.social/@joplinapp" title="Joplin Mastodon feed"><i class="fab fa-mastodon"></i></a>
|
||||
<a href="https://www.patreon.com/joplin" title="Joplin Patreon"><i class="fab fa-patreon"></i></a>
|
||||
<a href="https://discordapp.com/invite/d2HMPwE" title="Joplin Discord chat"><i class="fab fa-discord"></i></a>
|
||||
<a href="https://www.reddit.com/r/joplinapp/" title="Joplin Subreddit"><i class="fab fa-reddit"></i></a>
|
||||
<a href="https://github.com/laurent22/joplin/" title="Joplin GitHub repository"><i class="fab fa-github"></i></a>
|
||||
<a href="https://twitter.com/joplinapp" title="Twitter feed"><i class="fab fa-twitter"></i></a>
|
||||
<a href="https://github.com/laurent22/joplin/" title="GitHub repository"><i class="fab fa-github"></i></a>
|
||||
<a href="https://www.patreon.com/joplin" title="Patreon blog"><i class="fab fa-patreon"></i></a>
|
||||
<a href="https://discordapp.com/invite/d2HMPwE" title="Discord chat"><i class="fab fa-discord"></i></a>
|
||||
<a href="https://www.reddit.com/r/joplinapp/" title="Subreddit"><i class="fab fa-reddit"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 730 KiB After Width: | Height: | Size: 498 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 898 KiB After Width: | Height: | Size: 410 KiB |
@@ -58,18 +58,18 @@ COPY --chown=$user:$user packages/fork-htmlparser2 ./packages/fork-htmlparser2
|
||||
RUN npm run bootstrap
|
||||
|
||||
# We have a separate step for the server files because they are more likely to
|
||||
# change. And we need all the server files because there is a postinstall
|
||||
# script.
|
||||
# change.
|
||||
|
||||
COPY --chown=$user:$user packages/server ./packages/server
|
||||
COPY --chown=$user:$user packages/server/package*.json ./packages/server/
|
||||
RUN npm run bootstrapServerOnly
|
||||
|
||||
# Now copy the source files. Put lib last as it's more likely to change.
|
||||
# Now copy the source files. Put lib and server last as they are more likely to change.
|
||||
|
||||
COPY --chown=$user:$user packages/fork-sax ./packages/fork-sax
|
||||
COPY --chown=$user:$user packages/renderer ./packages/renderer
|
||||
COPY --chown=$user:$user packages/tools ./packages/tools
|
||||
COPY --chown=$user:$user packages/lib ./packages/lib
|
||||
COPY --chown=$user:$user packages/server ./packages/server
|
||||
|
||||
# Finally build everything, in particular the TypeScript files.
|
||||
|
||||
|
||||
@@ -476,11 +476,11 @@ Please see the [donation page](https://github.com/laurent22/joplin/blob/dev/read
|
||||
Name | Description
|
||||
--- | ---
|
||||
[Support Forum](https://discourse.joplinapp.org/) | This is the main place for general discussion about Joplin, user support, software development questions, and to discuss new features. Also where the latest beta versions are released and discussed.
|
||||
[Twitter feed](https://twitter.com/joplinapp) | Follow us on Twitter
|
||||
[Mastodon feed](https://mastodon.social/@joplinapp) | Follow us on Mastodon
|
||||
[Patreon page](https://www.patreon.com/joplin) |The latest news are often posted there
|
||||
[Discord server](https://discordapp.com/invite/d2HMPwE) | Our chat server
|
||||
[Sub-reddit](https://www.reddit.com/r/joplinapp/) | Also a good place to get help
|
||||
[Discord server](https://discordapp.com/invite/d2HMPwE) | Our chat server
|
||||
[Patreon page](https://www.patreon.com/joplin) |The latest news are often posted there
|
||||
[Mastodon feed](https://mastodon.social/@joplinapp) | Follow us on Mastodon
|
||||
[Twitter feed](https://twitter.com/joplinapp) | Follow us on Twitter
|
||||
|
||||
# Contributing
|
||||
|
||||
|
||||
4
packages/server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "2.5.4",
|
||||
"version": "2.5.2",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/server",
|
||||
"version": "2.5.4",
|
||||
"version": "2.5.2",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||
"@joplin/lib": "~2.4",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "2.5.4",
|
||||
"version": "2.5.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start-dev": "nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",
|
||||
|
||||
@@ -21,7 +21,6 @@ import apiVersionHandler from './middleware/apiVersionHandler';
|
||||
import clickJackingHandler from './middleware/clickJackingHandler';
|
||||
import deleteOldChanges from './commands/deleteOldChanges';
|
||||
import newModelFactory from './models/factory';
|
||||
import deleteOldChanges90 from './commands/deleteOldChanges90';
|
||||
|
||||
interface Argv {
|
||||
env?: Env;
|
||||
@@ -35,7 +34,6 @@ interface Argv {
|
||||
createDb?: boolean;
|
||||
envFile?: string;
|
||||
deleteOldChanges?: boolean;
|
||||
deleteOldChanges90?: boolean;
|
||||
}
|
||||
|
||||
const argv: Argv = yargsArgv as any;
|
||||
@@ -245,20 +243,13 @@ async function main() {
|
||||
await disconnectDb(db);
|
||||
} else if (argv.createDb) {
|
||||
await createDb(config().database);
|
||||
} else if (argv.deleteOldChanges || argv.deleteOldChanges90) {
|
||||
} else if (argv.deleteOldChanges) {
|
||||
// Eventually all commands should be started in a more generic way. All
|
||||
// should go under /commands, and they will receive a context object
|
||||
// with an intialized models property.
|
||||
//
|
||||
// Also should use yargs command system.
|
||||
const connectionCheck = await waitForConnection(config().database);
|
||||
const models = newModelFactory(connectionCheck.connection, config());
|
||||
|
||||
if (argv.deleteOldChanges90) {
|
||||
await deleteOldChanges90({ models });
|
||||
} else {
|
||||
await deleteOldChanges({ models });
|
||||
}
|
||||
await deleteOldChanges({ models });
|
||||
} else {
|
||||
runCommandAndExitApp = false;
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import { Day } from '../utils/time';
|
||||
import { CommandContext } from '../utils/types';
|
||||
|
||||
export default async function(ctx: CommandContext) {
|
||||
await ctx.models.change().deleteOldChanges(90 * Day);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createUserAndSession, beforeAllDb, afterAllTests, beforeEachDb, models, expectThrow, createFolder, createItemTree3, expectNotThrow, createNote, updateNote } from '../utils/testing/testUtils';
|
||||
import { ChangeType } from '../services/database/types';
|
||||
import { Day, msleep } from '../utils/time';
|
||||
import { msleep } from '../utils/time';
|
||||
import { ChangePagination } from './ChangeModel';
|
||||
import { SqliteMaxVariableNum } from '../db';
|
||||
|
||||
@@ -192,24 +192,19 @@ describe('ChangeModel', function() {
|
||||
//
|
||||
// https://www.timeanddate.com/date/dateadd.html
|
||||
|
||||
const changeTtl = (180 + 1) * Day;
|
||||
|
||||
const { session: session1 } = await createUserAndSession(1);
|
||||
const { session: session2 } = await createUserAndSession(2);
|
||||
|
||||
jest.useFakeTimers('modern');
|
||||
|
||||
const t1 = new Date('2020-01-01').getTime();
|
||||
jest.setSystemTime(t1);
|
||||
jest.setSystemTime(new Date('2020-01-01').getTime());
|
||||
const note1 = await createNote(session1.id, {});
|
||||
|
||||
const t2 = new Date('2020-01-10').getTime();
|
||||
jest.setSystemTime(t2);
|
||||
jest.setSystemTime(new Date('2020-01-10').getTime());
|
||||
const note2 = await createNote(session2.id, {});
|
||||
await updateNote(session1.id, { id: note1.jop_id });
|
||||
|
||||
const t3 = new Date('2020-01-20').getTime();
|
||||
jest.setSystemTime(t3);
|
||||
jest.setSystemTime(new Date('2020-01-20').getTime());
|
||||
await updateNote(session1.id, { id: note1.jop_id });
|
||||
|
||||
const t4 = new Date('2020-01-30').getTime();
|
||||
@@ -230,9 +225,9 @@ describe('ChangeModel', function() {
|
||||
await models().change().deleteOldChanges();
|
||||
expect(await models().change().count()).toBe(7);
|
||||
|
||||
// 180 days after T4, it should delete all U1 updates events except for
|
||||
// the last one
|
||||
jest.setSystemTime(new Date(t4 + changeTtl).getTime());
|
||||
// 90 days later, it should delete all U1 updates events except for the
|
||||
// last one
|
||||
jest.setSystemTime(new Date('2020-05-01').getTime());
|
||||
await models().change().deleteOldChanges();
|
||||
expect(await models().change().count()).toBe(5);
|
||||
{
|
||||
@@ -246,13 +241,13 @@ describe('ChangeModel', function() {
|
||||
// Between T5 and T6, 90 days later - nothing should happen because
|
||||
// there's only one note 2 change that is older than 90 days at this
|
||||
// point.
|
||||
jest.setSystemTime(new Date(t5 + changeTtl).getTime());
|
||||
jest.setSystemTime(new Date('2020-05-15').getTime());
|
||||
await models().change().deleteOldChanges();
|
||||
expect(await models().change().count()).toBe(5);
|
||||
|
||||
// After T6, more than 90 days later - now the change at T5 should be
|
||||
// deleted, keeping only the change at T6.
|
||||
jest.setSystemTime(new Date(t6 + changeTtl).getTime());
|
||||
jest.setSystemTime(new Date('2020-05-25').getTime());
|
||||
await models().change().deleteOldChanges();
|
||||
expect(await models().change().count()).toBe(4);
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ import { PaginatedResults, Pagination, PaginationOrderDir } from './utils/pagina
|
||||
|
||||
const logger = Logger.create('ChangeModel');
|
||||
|
||||
export const defaultChangeTtl = 180 * Day;
|
||||
export const changeTtl = 90 * Day;
|
||||
|
||||
export interface DeltaChange extends Change {
|
||||
jop_updated_time?: number;
|
||||
@@ -309,9 +309,8 @@ export default class ChangeModel extends BaseModel<Change> {
|
||||
return output;
|
||||
}
|
||||
|
||||
public async deleteOldChanges(ttl: number = null) {
|
||||
ttl = ttl === null ? defaultChangeTtl : ttl;
|
||||
const cutOffDate = Date.now() - ttl;
|
||||
public async deleteOldChanges() {
|
||||
const cutOffDate = Date.now() - changeTtl;
|
||||
const limit = 1000;
|
||||
const doneItemIds: Uuid[] = [];
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
# Joplin Server Changelog
|
||||
|
||||
## [server-v2.5.4](https://github.com/laurent22/joplin/releases/tag/server-v2.5.4) - 2021-10-23T19:09:45Z
|
||||
|
||||
- New: Added tool to delete old changes (169b585)
|
||||
- Fixed: Fixed issue when a notebook is shared, then unshared, then shared again (47fc51e)
|
||||
|
||||
## [server-v2.5.2](https://github.com/laurent22/joplin/releases/tag/server-v2.5.2) - 2021-10-07T13:36:27Z
|
||||
|
||||
- New: Add support for promotion codes (5b58811)
|
||||
|
||||
@@ -49,32 +49,6 @@ When syncing from the start, there will be many "create" events for files that a
|
||||
|
||||
## Regarding the deletion of old change events
|
||||
|
||||
**2021-10-22**
|
||||
|
||||
### Handling UPDATE events
|
||||
|
||||
**Update events** older than x days (currently 180 days) will be automatically compressed, by deleting all events except the latest one. For example, if a note has been modified on July 2, July 7 and July 15, only the July 15 event will be kept.
|
||||
|
||||
It means that a client that has not synced for more than 180 days is likely to get a "resyncRequired" error code if the sync cursor they had correspond to a change that has been deleted. When that happens a full sync will start from the beginning.
|
||||
|
||||
This side effect is considered acceptable because:
|
||||
|
||||
- It is unlikely that a client won't be synced for more than 180 days.
|
||||
- No data loss will occur.
|
||||
- The need to do a full sync again, while annoying, is not a major issue in most cases.
|
||||
|
||||
### Handling CREATE and DELETE events
|
||||
|
||||
Currently **Create** and **Delete** events are not automatically deleted. This is because clients build their data based on the Create/Update/Delete events so if we delete the CREATE events, certain notes will no longer be created on new clients.
|
||||
|
||||
A possible solution would be to have this kind of logic client side: When a sync cursor is invalid, do a full sync, which will not rely on /delta but on the basicDelta() function as used for file system or webdav sync. It will simply compare what's on the server with what's on the client and do the sync like this. Once it's done, it can start using /delta again. Advantage if that it can be done using basicDelta(). Disadvantage is that it's not possible accurately enumerate the server content that way, since new items can be created or deleted during that basicDelta process.
|
||||
|
||||
A possibly more reliable technique would be to delete all Create/Delete event pairs. Doing so won't affect new clients - which simply won't get any CREATE event, since the item has been deleted anyway. It will affect clients that did not sync for a long time because they won't be notified that an item has been deleted - but that's probably an acceptable side effect. The main trouble will be the shared notes and notebooks - we'd need to make sure that when we delete something from a user it doesn't incorrectly delete it from another user (I don't think it would, but that will need to be considered).
|
||||
|
||||
**2021-01-01**
|
||||
|
||||
**Obsolete**
|
||||
|
||||
Keeping all change events permanently would represent a lot of data, however it might be necessary. Without it, it won't be possible for a client to know what file has been deleted and thus a client that has not synced for a long time will keep its files permanently.
|
||||
|
||||
So most likely we'll always keep the change events. However, we could compress the old ones to just "create" and "delete" events. All "update" events are not needed. And for a file that has been deleted, we don't need to keep the "create" event.
|
||||
|
||||