Skip to content

Commit

Permalink
Persorgs CRUD (#474)
Browse files Browse the repository at this point in the history
* Implement deleting Persorgs
* Fix editing Persorgs
* Improve Persorg and Source creation info display
* Set now-time for tests that snapshot relative time

---------

Signed-off-by: Carl Gieringer <78054+carlgieringer@users.noreply.github.com>
  • Loading branch information
carlgieringer authored Jul 17, 2023
1 parent d064a55 commit 81eed28
Show file tree
Hide file tree
Showing 41 changed files with 531 additions and 222 deletions.
6 changes: 3 additions & 3 deletions howdju-common/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
CreateJustificationTarget,
CreateJustifiedSentenceInput,
CreateMediaExcerptInput,
CreatePersorg,
CreatePicRegionInput,
CreatePropositionCompoundAtomInput,
CreatePropositionCompoundInput,
Expand All @@ -59,7 +60,6 @@ import {
JustificationRootTarget,
JustificationRootTargetType,
JustificationVotePolarity,
Persorg,
PicRegion,
PropositionTagVote,
SourceExcerpt,
Expand Down Expand Up @@ -204,7 +204,7 @@ export const makeCreateUrlLocatorInput = (
props?: Partial<CreateUrlLocatorInput>
): CreateUrlLocatorInput => merge({ url: makeCreateUrl() }, props);

export const makePersorg = (): Persorg => ({
export const makeCreatePersorg = (): CreatePersorg => ({
isOrganization: false,
name: "",
knownFor: "",
Expand Down Expand Up @@ -281,7 +281,7 @@ export const makeCreateStatementInput = (
): CreateStatementInput =>
merge(
{
speaker: makePersorg(),
speaker: makeCreatePersorg(),
sentenceType: "PROPOSITION",
sentence: makeCreatePropositionInput(),
},
Expand Down
48 changes: 48 additions & 0 deletions howdju-common/lib/zodError.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,52 @@ describe("makeModelErrors", () => {
)
);
});
test("Returns correct error for multiple errors", () => {
type User = { name: string; title: string };
const error = makeModelErrors<User>(
(r) => r.name("Name is already in use."),
(r) => r.title("Title is already in use.")
);

expect(error).toEqual(
formatZodError(
new z.ZodError([
{
code: z.ZodIssueCode.custom,
path: ["name"],
message: "Name is already in use.",
},
{
code: z.ZodIssueCode.custom,
path: ["title"],
message: "Title is already in use.",
},
])
)
);
});
test("Returns correct error for multiple errors on same field", () => {
type User = { username: string };
const error = makeModelErrors<User>(
(r) => r.username("Username must be a palindrome."),
(r) => r.username("Username must be lowercase.")
);

expect(error).toEqual(
formatZodError(
new z.ZodError([
{
code: z.ZodIssueCode.custom,
path: ["username"],
message: "Username must be a palindrome.",
},
{
code: z.ZodIssueCode.custom,
path: ["username"],
message: "Username must be lowercase.",
},
])
)
);
});
});
5 changes: 3 additions & 2 deletions howdju-common/lib/zodError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,9 @@ export function formatZodError<T>(error: z.ZodError<T>): ModelErrors<T> {
export function makeZodCustomIssuesError<T extends object>(
issueDescriptors: IssueDescriptor<T>[]
): z.ZodError<T> {
const proxy = makeCallableProxy<T>();
const issues = issueDescriptors.map((d) => d(proxy)) as z.ZodIssue[];
const issues = issueDescriptors.map((d) =>
d(makeCallableProxy<T>())
) as z.ZodIssue[];
return new z.ZodError<T>(issues);
}

Expand Down
23 changes: 17 additions & 6 deletions howdju-common/lib/zodSchemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,19 +188,30 @@ export const Persorg = Entity.extend({
twitterUrl: urlString({ domain: /twitter.com$/ }).optional(),
/** The persorg's Wikipedia URL. */
wikipediaUrl: urlString({ domain: /wikipedia.org$/ }).optional(),
created: momentObject,
creatorUserId: z.string(),
});
export type Persorg = z.infer<typeof Persorg>;

export const CreatePersorg = Persorg;
export const CreatePersorg = Persorg.omit({
id: true,
created: true,
creatorUserId: true,
creator: true,
});
export type CreatePersorg = z.infer<typeof CreatePersorg>;

export const CreatePersorgInput = Persorg;
export const CreatePersorgInput = CreatePersorg;
export type CreatePersorgInput = z.infer<typeof CreatePersorgInput>;

export const UpdatePersorg = Persorg.merge(PersistedEntity);
export const UpdatePersorg = Persorg.merge(PersistedEntity).omit({
created: true,
creatorUserId: true,
creator: true,
});
export type UpdatePersorg = z.infer<typeof UpdatePersorg>;

export const UpdatePersorgInput = Persorg;
export const UpdatePersorgInput = UpdatePersorg;
export type UpdatePersorgInput = z.infer<typeof UpdatePersorgInput>;

/** Represents an utterance of a proposition by a persorg. */
Expand Down Expand Up @@ -241,7 +252,7 @@ export type SentenceType = Statement["sentenceType"];
export const SentenceTypes = sentenceTypes.Enum;

export type CreateStatementInput = Entity & {
speaker: Persorg;
speaker: CreatePersorgInput;
} & (
| {
sentenceType: "PROPOSITION";
Expand Down Expand Up @@ -269,7 +280,7 @@ export const CreateStatementInput: z.ZodType<CreateStatementInput> = z.lazy(
);

export type CreateStatement = Entity & {
speaker: Persorg;
speaker: CreatePersorg;
} & (
| {
sentenceType: "PROPOSITION";
Expand Down
23 changes: 18 additions & 5 deletions howdju-service-common/lib/daos/MediaExcerptsDao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,20 @@ export class MediaExcerptsDao {
);
}

async deleteMediaExcerptSpeakersForPersorgId(
persorgId: EntityId,
deletedAt: Moment
) {
return await this.database.query(
"deleteMediaExcerptSpeakersForSourceId",
`update media_excerpt_speakers
set deleted = $2
where speaker_persorg_id = $1 and deleted is null
`,
[persorgId, deletedAt]
);
}

async readMediaExcerpts(
filters: MediaExcerptSearchFilter | undefined,
sorts: SortDescription[],
Expand Down Expand Up @@ -904,9 +918,8 @@ export class MediaExcerptsDao {
${whereSql}
and (
${continuationWhereSql}
) and (
${filterWhereSql}
)
${filterWhereSql ? `and (${filterWhereSql})` : ""}
${orderBySql}
${countSql}
`;
Expand Down Expand Up @@ -1007,7 +1020,7 @@ function makeFilterSubselects(filters: MediaExcerptSearchFilter | undefined) {
const sql = `
select media_excerpt_id
from media_excerpts
where creator_user_id = $1
where creator_user_id = $1 and deleted is null
`;
const args = [value];
filterSubselects.push({ sql, args });
Expand All @@ -1016,8 +1029,8 @@ function makeFilterSubselects(filters: MediaExcerptSearchFilter | undefined) {
case "speakerPersorgId": {
const sql = `
select media_excerpt_id
from media_excerpts join media_excerpt_speakers using (media_excerpt_id)
where speaker_persorg_id = $1
from media_excerpts join media_excerpt_speakers mes using (media_excerpt_id)
where speaker_persorg_id = $1 and mes.deleted is null
`;
const args = [value];
filterSubselects.push({ sql, args });
Expand Down
43 changes: 28 additions & 15 deletions howdju-service-common/lib/daos/PersorgsDao.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ exports.PersorgsDao = class PersorgsDao extends BaseDao {

async createPersorg(persorg, creatorUserId, now) {
const sql = `
insert into persorgs (is_organization, name, normal_name, known_for, normal_known_for, website_url, twitter_url, wikipedia_url, creator_user_id, created)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
insert into persorgs
(is_organization, name, normal_name, known_for, normal_known_for, website_url, twitter_url, wikipedia_url, creator_user_id, created)
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
returning *
`;
const args = [
Expand All @@ -34,9 +35,9 @@ exports.PersorgsDao = class PersorgsDao extends BaseDao {
return await this.queryOne(
"readEquivalentPersorg",
`
select *
from persorgs
where
select *
from persorgs
where
is_organization = $1
and normal_name = $2
and normal_known_for = $3
Expand Down Expand Up @@ -70,8 +71,8 @@ exports.PersorgsDao = class PersorgsDao extends BaseDao {
return await this.queryMany(
"readPersorgs",
`
select *
from persorgs
select *
from persorgs
where deleted is null
order by created
`
Expand All @@ -82,10 +83,10 @@ exports.PersorgsDao = class PersorgsDao extends BaseDao {
return await this.queryMany(
"readPersorgsLikeName",
`
select *
from persorgs
where normal_name ilike '%' || $1 || '%'
and deleted is null
select *
from persorgs
where normal_name ilike '%' || $1 || '%'
and deleted is null
order by length(name), name
`,
[normalizeText(persorgName)]
Expand All @@ -96,9 +97,9 @@ exports.PersorgsDao = class PersorgsDao extends BaseDao {
const equivalentPersorgs = await this.queryMany(
"hasEquivalentPersorgs",
`
select * from persorgs
where
normal_name = $1
select * from persorgs
where
normal_name = $1
and normal_known_for = $2
and persorg_id <> $3
and deleted is null`,
Expand All @@ -111,7 +112,7 @@ exports.PersorgsDao = class PersorgsDao extends BaseDao {
return this.queryOne(
"updatePersorg",
`
update persorgs set
update persorgs set
is_organization = $2,
name = $3,
normal_name = $4,
Expand All @@ -138,4 +139,16 @@ exports.PersorgsDao = class PersorgsDao extends BaseDao {
]
);
}

async deletePersorgForId(persorgId, deletedAt) {
return await this.queryOne(
"deletePersorgForId",
`
update persorgs set
deleted = $2
where persorg_id = $1 and deleted is null
`,
[persorgId, deletedAt]
);
}
};
6 changes: 5 additions & 1 deletion howdju-service-common/lib/daos/SourcesDao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,11 @@ export class SourcesDao {
`,
[updateSource.description, normalDescription, updateSource.id]
);
return this.readSourceForId(updateSource.id);
const source = await this.readSourceForId(updateSource.id);
if (!source) {
throw Error(`Source ${updateSource.id} not found after update.`);
}
return source;
}

async deleteSourceForId(sourceId: string, deletedAt: Moment) {
Expand Down
6 changes: 5 additions & 1 deletion howdju-service-common/lib/daos/orm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -915,7 +915,11 @@ export const toPersorg = wrapMapper(function toPersorgMapper(
})
: undefined;

return { ...persorg, creator };
return {
...persorg,
creator,
creatorUserId: toIdString(row.creator_user_id),
};
});

export const toRegistrationRequest = wrapMapper(
Expand Down
5 changes: 3 additions & 2 deletions howdju-service-common/lib/initializers/servicesInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ export function servicesInitializer(provider: AwsProvider) {
);

const persorgsService = new PersorgsService(
provider.logger,
provider.appConfig,
authService,
permissionsService,
provider.persorgsDao
provider.persorgsDao,
provider.mediaExcerptsDao
);

const statementsService = new StatementsService(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ exports.AccountSettingsService = class AccountSettingsService extends (
createSchema: CreateAccountSettings,
updateSchema: UpdateAccountSettings,
},
logger,
authService
);
this.logger = logger;
Expand Down
Loading

0 comments on commit 81eed28

Please sign in to comment.