Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement deleting Sources #468

Merged
merged 1 commit into from
Jul 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions howdju-service-common/lib/daos/MediaExcerptsDao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -979,6 +979,19 @@ export class MediaExcerptsDao {
);
return mediaExcerpts.filter(isDefined);
}

async deleteMediaExcerptCitationsForSourceId(
sourceId: EntityId,
deletedAt: Moment
) {
await this.database.query(
"deleteMediaExcerptCitationsForSourceId",
`update media_excerpt_citations
set deleted = $1
where source_id = $2`,
[deletedAt, sourceId]
);
}
}

function makeFilterSubselects(filters: MediaExcerptSearchFilter | undefined) {
Expand Down Expand Up @@ -1013,8 +1026,8 @@ function makeFilterSubselects(filters: MediaExcerptSearchFilter | undefined) {
case "sourceId": {
const sql = `
select media_excerpt_id
from media_excerpts join media_excerpt_citations using (media_excerpt_id)
where source_id = $1
from media_excerpts join media_excerpt_citations mec using (media_excerpt_id)
where mec.source_id = $1 and mec.deleted is null
`;
const args = [value];
filterSubselects.push({ sql, args });
Expand Down
12 changes: 11 additions & 1 deletion howdju-service-common/lib/daos/SourcesDao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export class SourcesDao {
rows: [row],
} = await this.db.query<SourceRow>(
"readSourceForId",
`SELECT * FROM sources WHERE source_id = $1`,
`SELECT * FROM sources WHERE source_id = $1 and deleted is null`,
[normalizeText(sourceId)]
);
if (!row) {
Expand Down Expand Up @@ -103,4 +103,14 @@ export class SourcesDao {
);
return this.readSourceForId(updateSource.id);
}

async deleteSourceForId(sourceId: string, deletedAt: Moment) {
await this.db.query(
"deleteSourceForId",
`
update sources set deleted = $2 where source_id = $1
`,
[sourceId, deletedAt]
);
}
}
3 changes: 2 additions & 1 deletion howdju-service-common/lib/initializers/servicesInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ export function servicesInitializer(provider: AwsProvider) {
provider.appConfig,
authService,
permissionsService,
provider.sourcesDao
provider.sourcesDao,
provider.mediaExcerptsDao
);

const mediaExcerptsService = new MediaExcerptsService(
Expand Down
27 changes: 24 additions & 3 deletions howdju-service-common/lib/services/SourcesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
utcNow,
} from "howdju-common";

import { SourcesDao } from "../daos";
import { MediaExcerptsDao, SourcesDao } from "../daos";
import { EntityWrapper } from "../types";
import { readWriteReread } from "./patterns";
import {
Expand All @@ -29,7 +29,8 @@ export class SourcesService {
private config: ApiConfig,
private authService: AuthService,
private permissionsService: PermissionsService,
private sourcesDao: SourcesDao
private sourcesDao: SourcesDao,
private mediaExcerptsDao: MediaExcerptsDao
) {}

async readSourceForId(sourceId: EntityId): Promise<SourceOut> {
Expand Down Expand Up @@ -83,7 +84,27 @@ export class SourcesService {
return this.sourcesDao.updateSource(updateSource);
}

async checkUpdateSourcePermission(userId: EntityId, source: SourceOut) {
async deleteSourceForId(userIdent: UserIdent, sourceId: EntityId) {
const userId = await this.authService.readUserIdForUserIdent(userIdent);
const source = await this.sourcesDao.readSourceForId(sourceId);
if (!source) {
throw new EntityNotFoundError("SOURCE", sourceId);
}

await this.checkUpdateSourcePermission(userId, source);

const deletedAt = utcNow();
await this.mediaExcerptsDao.deleteMediaExcerptCitationsForSourceId(
sourceId,
deletedAt
);
await this.sourcesDao.deleteSourceForId(sourceId, deletedAt);
}

private async checkUpdateSourcePermission(
userId: EntityId,
source: SourceOut
) {
const hasEditPermission = await this.permissionsService.userHasPermission(
userId,
"EDIT_ANY_ENTITY"
Expand Down
16 changes: 16 additions & 0 deletions howdju-service-routes/lib/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,22 @@ export const serviceRoutes = {
}
),
},
deleteSource: {
path: "sources/:sourceId",
method: httpMethods.DELETE,
request: handler(
Authed.merge(PathParams("sourceId")),
async (
appProvider: ServicesProvider,
{ authToken, pathParams: { sourceId } }
) => {
await prefixErrorPath(
appProvider.sourcesService.deleteSourceForId({ authToken }, sourceId),
"source"
);
}
),
},

/*
* Root target justifications
Expand Down
1 change: 1 addition & 0 deletions premiser-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@
"connected-react-router": "^6.9.1",
"core-js": "^3.11.0",
"dompurify": "^2.2.9",
"expiry-map": "^2.0.0",
"history": "^4.10.1",
"howdju-ajv-sourced": "workspace:howdju-ajv-sourced",
"howdju-client-common": "workspace:howdju-client-common",
Expand Down
22 changes: 16 additions & 6 deletions premiser-ui/src/JustificationRootTargetCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
} from "react-md";
import { Link } from "react-router-dom";
import { connect, ConnectedProps } from "react-redux";
import { push } from "connected-react-router";

import {
EntityId,
Expand Down Expand Up @@ -43,11 +44,12 @@ import Tagger from "./Tagger";
import { combineIds, combineSuggestionsKeys } from "./viewModels";
import {
api,
apiLike,
editors,
flows,
mapActionCreatorGroupToDispatchToProps,
ui,
} from "./actions";
import app from "./app/appSlice";
import { divideMenuItems } from "./util";
import { contentReportEditorId } from "./content-report/ReportContentDialog";
import { ComponentId, EditorId, MenuItems, SuggestionsKey } from "./types";
Expand Down Expand Up @@ -305,10 +307,18 @@ class JustificationRootTargetCard extends React.Component<Props> {
};

deleteRootTarget = () => {
this.props.apiLike.deleteJustificationRootTarget(
this.props.rootTargetType,
this.props.rootTarget.id
);
switch (this.props.rootTargetType) {
case "PROPOSITION":
this.props.flows.apiActionOnSuccess(
api.deleteProposition(this.props.rootTarget.id),
app.addToast("Deleted Proposition"),
push(paths.home())
);
break;
case "STATEMENT":
logger.error("deleting statements is unimplemented");
break;
}
};

onMouseOver = () => {
Expand All @@ -324,8 +334,8 @@ const connector = connect(
null,
mapActionCreatorGroupToDispatchToProps({
api,
apiLike,
editors,
flows,
ui,
})
);
Expand Down
12 changes: 12 additions & 0 deletions premiser-ui/src/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import {
EditorId,
} from "./types";
import { createAction, actionTypeDelim } from "./actionHelpers";
import { AnyAction } from "@reduxjs/toolkit";
import { AnyApiAction } from "./apiActions";

export { str } from "./actionHelpers";

Expand Down Expand Up @@ -405,6 +407,16 @@ export const flows = {
onSuccessAction,
})
),
apiActionOnSuccess: createAction(
"FLOWS/API_ACTION_ON_SUCCESS",
(
apiAction: AnyApiAction,
...onSuccessActions: [AnyAction, ...AnyAction[]]
) => ({
apiAction,
onSuccessActions,
})
),
};

export const autocompletes = {
Expand Down
6 changes: 5 additions & 1 deletion premiser-ui/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface RequestOptions {
endpoint: string;
method: HttpMethod;
body: string;
requestId?: string;
headers: Record<string, string>;
}

Expand All @@ -33,10 +34,13 @@ export function sendRequest({
method,
body,
headers,
requestId,
}: RequestOptions) {
const controller = new AbortController();

const requestId = newId();
if (!requestId) {
requestId = newId();
}
headers = { ...headers, [customHeaderKeys.REQUEST_ID]: requestId };

// https://github.com/mzabriskie/axios#request-config
Expand Down
40 changes: 15 additions & 25 deletions premiser-ui/src/apiActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { isEmpty, join, merge, toString } from "lodash";
import queryString from "query-string";
import { compile } from "path-to-regexp";
import { JsonObject, Schema } from "type-fest";
import { v4 as uuidv4 } from "uuid";

import {
ContextTrailItemInfo,
Expand Down Expand Up @@ -115,20 +116,6 @@ export type ApiResponseActionMeta<N, M> = {
requestMeta: M;
};

/**
* @typeparam N the type of the normalization schema
*/
interface ResourceApiConfig<B, N> {
endpoint: string;
fetchInit?: {
method: HttpMethod;
body?: B;
};
canSkipRehydrate?: boolean;
cancelKey?: string;
normalizationSchema?: N;
}

/** Properties that may be present on API responses */
export interface ApiResponseWrapper {
/**
Expand All @@ -143,16 +130,6 @@ export interface ApiResponseWrapper {
continuationToken?: string;
}

/**
* The meta of an ApiAction
*
* @typeparam P the payload type.
*/
export type ApiActionMeta<P> = {
apiConfig:
| ResourceApiConfig<any, any>
| ((p: P) => ResourceApiConfig<any, any>);
};
export type ApiAction<Route extends ServiceRoute> = PayloadAction<
ApiConfig<Route>
>;
Expand Down Expand Up @@ -218,6 +195,7 @@ type ApiConfig<Route extends ServiceRoute> = {
fetchInit: {
method: HttpMethod;
body: JsonObject;
requestId: string;
};
canSkipRehydrate: boolean;
cancelKey: string;
Expand Down Expand Up @@ -311,11 +289,12 @@ function apiActionCreator<
fetchInit: {
method: route.method,
body,
requestId: uuidv4(),
},
normalizationSchema,
canSkipRehydrate,
cancelKey,
} as ResourceApiConfig<InferRequestBody<Route>, NormalizationSchema>;
} as ApiConfig<Route>;
const baseMeta = {
queryStringParams,
pathParams,
Expand Down Expand Up @@ -1114,6 +1093,13 @@ export const api = {
normalizationSchema: { source: sourceSchema },
})
),
deleteSource: apiActionCreator(
"DELETE_SOURCE",
serviceRoutes.deleteSource,
(sourceId: EntityId) => ({
pathParams: { sourceId: sourceId },
})
),

fetchPropositionTextSuggestions: apiActionCreator(
"FETCH_PROPOSITION_TEXT_SUGGESTIONS",
Expand Down Expand Up @@ -1367,3 +1353,7 @@ export const apiActionCreatorsByActionType = reduce(
},
{}
);

export const allApiResponseActions = Object.values(api).map(
(actionCreator) => actionCreator.response
);
5 changes: 1 addition & 4 deletions premiser-ui/src/editors/withEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,7 @@ import { useAppDispatch } from "@/hooks";
import { ServiceRoute } from "howdju-service-routes";

export class CommitThenPutAction {
action: AnyAction;
constructor(action: AnyAction) {
this.action = action;
}
constructor(public action: AnyAction) {}
}

/** Editor fields can return one or more actions to dispatch. */
Expand Down
Loading