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

Use Schema to make frontend generic (part 1) #154

Merged
merged 24 commits into from
Dec 13, 2024
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
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
// Turn on errors for missing imports.
"import/no-unresolved": "error",
"import/named": "off",
"no-useless-constructor": "off",
"no-console": [
"off"
]
Expand Down
17 changes: 5 additions & 12 deletions src/api/ApiClient.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { PHPResponse, PlaygroundClient } from '@wp-playground/client';
import { BlogPostsApi } from '@/api/BlogPosts';
import { PagesApi } from '@/api/Pages';
import { SettingsApi } from '@/api/Settings';
import { UsersApi } from '@/api/Users';
import { BlueprintsApi } from '@/api/Blueprints';
import { SubjectsApi } from '@/api/SubjectsApi';

export class ApiClient {
private readonly playgroundClient: PlaygroundClient;
private readonly _siteUrl: string;
private readonly _blogPosts: BlogPostsApi;
private readonly _pages: PagesApi;
private readonly _subjects: SubjectsApi;
private readonly _settings: SettingsApi;
private readonly _users: UsersApi;
private readonly _blueprints: BlueprintsApi;
Expand All @@ -18,8 +16,7 @@ export class ApiClient {
this.playgroundClient = playgroundClient;
this._siteUrl = siteUrl;
this._blueprints = new BlueprintsApi( this );
this._blogPosts = new BlogPostsApi( this );
this._pages = new PagesApi( this );
this._subjects = new SubjectsApi( this );
this._settings = new SettingsApi( this );
this._users = new UsersApi( this );
}
Expand All @@ -32,12 +29,8 @@ export class ApiClient {
return this._blueprints;
}

get blogPosts(): BlogPostsApi {
return this._blogPosts;
}

get pages(): PagesApi {
return this._pages;
get subjects(): SubjectsApi {
return this._subjects;
}

get settings(): SettingsApi {
Expand Down
12 changes: 6 additions & 6 deletions src/api/ApiTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ export type ApiPost = {
transformedId: number;
previewUrl: string;
sourceUrl: string;
rawDate: string;
parsedDate: string;
rawTitle: string;
parsedTitle: string;
rawContent: string;
parsedContent: string;
rawDate?: string;
parsedDate?: string;
rawTitle?: string;
parsedTitle?: string;
rawContent?: string;
parsedContent?: string;
};

export type ApiPage = ApiPost;
Expand Down
79 changes: 0 additions & 79 deletions src/api/BlogPosts.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/api/Blueprints.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ApiClient } from '@/api/ApiClient';
import { Blueprint } from '@/model/blueprint/Blueprint';
import { SubjectType } from '@/model/subject/Subject';
import { Blueprint } from '@/model/Blueprint';
import { SubjectType } from '@/model/Subject';

export class BlueprintsApi {
// eslint-disable-next-line no-useless-constructor
Expand Down
77 changes: 0 additions & 77 deletions src/api/Pages.ts

This file was deleted.

111 changes: 111 additions & 0 deletions src/api/SubjectsApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { ApiClient } from '@/api/ApiClient';
import { Subject, SubjectType } from '@/model/Subject';
import { ApiPost } from '@/api/ApiTypes';
import { newDateField } from '@/model/field/DateField';
import { newTextField } from '@/model/field/TextField';
import { newHtmlField } from '@/model/field/HtmlField';

const endpoints = new Map< string, string >( [
[ SubjectType.BlogPost, '/blog-posts' ],
[ SubjectType.Page, '/pages' ],
] );

export class SubjectsApi {
constructor( private readonly client: ApiClient ) {}

async create( type: SubjectType, sourceUrl: string ): Promise< Subject > {
const path = getEndpoint( type );
const response = ( await this.client.post( path, {
sourceUrl,
} ) ) as ApiPost;
return fromApiResponse( response );
}

async update( subject: Subject ): Promise< Subject > {
const path = `${ getEndpoint( subject.type ) }/${ subject.id }`;
const response = ( await this.client.post(
path,
toApiUpdateRequest( subject )
) ) as ApiPost;
return fromApiResponse( response );
}

async findById( type: SubjectType, id: number ): Promise< Subject | null > {
const path = `${ getEndpoint( type ) }/${ id }`;
const post = ( await this.client.get( path ) ) as ApiPost;
return post ? fromApiResponse( post ) : null;
}

async findBySourceUrl(
type: SubjectType,
sourceUrl: string
): Promise< Subject | null > {
const path = `${ getEndpoint( type ) }?sourceurl=${ sourceUrl }`;
const post = ( await this.client.get( path ) ) as ApiPost;
return post ? fromApiResponse( post ) : null;
}
}

function getEndpoint( type: SubjectType ): string {
const endpoint = endpoints.get( type );
if ( ! endpoint ) {
throw Error( `unknown endpoint: ${ type }` );
}
return endpoint;
}

function fromApiResponse( response: ApiPost ): Subject {
const date = newDateField( response.rawDate, response.parsedDate );
const title = newTextField( response.rawTitle, response.parsedTitle ?? '' );
const content = newHtmlField(
response.rawContent,
response.parsedContent ?? ''
);

return {
id: response.id,
type: SubjectType.BlogPost,
sourceUrl: response.sourceUrl,
transformedId: response.transformedId,
previewUrl: response.previewUrl,
fields: {
title,
date,
content,
},
};
}

type UpdateBody = Omit< ApiPost, 'sourceUrl' | 'transformedId' | 'previewUrl' >;

function toApiUpdateRequest( subject: Subject ): UpdateBody {
let request: UpdateBody = {
id: subject.id,
};

if ( subject.fields.date ) {
request = {
...request,
rawDate: subject.fields.date.rawValue,
parsedDate: subject.fields.date.parsedValue.toISOString(),
};
}

if ( subject.fields.title ) {
request = {
...request,
rawTitle: subject.fields.title.rawValue,
parsedTitle: subject.fields.title.parsedValue,
};
}

if ( subject.fields.content ) {
request = {
...request,
rawContent: subject.fields.content.rawValue,
parsedContent: subject.fields.content.parsedValue,
};
}

return request;
}
33 changes: 33 additions & 0 deletions src/model/Blueprint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { SubjectType } from '@/model/Subject';

export interface Blueprint {
type: SubjectType;
id: string; // TODO: Probably need to make this a number when we start storing Blueprints on the backend.
sourceUrl: string;
valid: boolean;
selectors: Record< string, string >;
}

export function newBlueprint(
type: SubjectType,
sourceUrl: string
): Blueprint {
return {
id: '',
type,
sourceUrl,
valid: false,
selectors: {},
};
}

export function validateBlueprint( blueprint: Blueprint ): boolean {
let isValid = true;
for ( const selector of Object.values( blueprint.selectors ) ) {
if ( selector === '' ) {
isValid = false;
break;
}
}
return isValid;
}
2 changes: 1 addition & 1 deletion src/model/subject/Schema.ts → src/model/Schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// eslint-disable-next-line import/no-unresolved
import Schema from '@schema/schema.json';
import { SubjectType } from '@/model/subject/Subject';
import { SubjectType } from '@/model/Subject';

export function getSchema( subjectType: SubjectType ) {
if ( ! Schema.hasOwnProperty( subjectType ) ) {
Expand Down
Loading
Loading