diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 08c17a9..6fe773e 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,4 +1,5 @@ import { env } from '$env/dynamic/private'; +import { logger } from '$lib/server/logger'; import { Strapi } from '$lib/server/strapi'; import type { Handle, HandleServerError } from '@sveltejs/kit'; import { randomUUID } from 'crypto'; @@ -6,7 +7,7 @@ import { randomUUID } from 'crypto'; export const handleError: HandleServerError = async ({ error, event, message, status }) => { const errorId = randomUUID(); - console.error(error, { errorId, event, message, status }); + logger.error({ error, errorId, event, message, status }, 'Blyat'); return { message: 'An Internal Error Occurred', diff --git a/src/lib/server/strapi/index.ts b/src/lib/server/strapi/index.ts index a42303c..b461388 100644 --- a/src/lib/server/strapi/index.ts +++ b/src/lib/server/strapi/index.ts @@ -15,6 +15,22 @@ export class Strapi { }); } + async SiteBackground() { + const background = this.client.single('site-background'); + + return await background.find({ + populate: ['Background'] + }); + } + + async About() { + const about = this.client.single('about'); + + return await about.find({ + populate: ['Titles', 'Photo', 'Activities'] + }); + } + async Blogs() { const blogs = this.client.collection('blogs'); @@ -30,4 +46,18 @@ export class Strapi { populate: ['Header', 'Topics'] }); } + + async Experiences() { + const experiences = this.client.collection('experiences'); + + return await experiences.find({ + populate: ['Logo'] + }); + } + + async Education() { + const education = this.client.collection('educations'); + + return await education.find({}); + } } diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts new file mode 100644 index 0000000..5055cd2 --- /dev/null +++ b/src/routes/+page.server.ts @@ -0,0 +1,44 @@ +import { logger } from '$lib/server/logger'; +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; + +interface Data { + about: { + name: string; + bio: string; + titles: string[]; + photo: { + url: string; + }; + activities: { title: string; description: string }[]; + }; +} + +export const load: PageServerLoad = async ({ locals }): Promise => { + const { strapi } = locals; + + try { + const { data: about } = await strapi.About(); + + return { + about: { + name: about.Name, + bio: about.Bio, + titles: about.Titles.map((title: { value: string }) => title.value), + photo: { + url: about.Photo.url + }, + activities: about.Activities.map((item: { Title: string; Description: string }) => { + return { title: item.Title, description: item.Description }; + }) + } + } satisfies Data; + } catch (err) { + if (err instanceof Error) { + logger.error('Uh-oh!', err); + if (err instanceof Error) { + error(500, err); + } + } + } +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 529e7fc..768c21c 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,69 +1,37 @@ +{#snippet Activity(item: ActivityItem)} +
+ +
+

{item.title}

+

+ {item.description} +

+
+
+{/snippet} +
- +

What I Do

-
- -
-

Web Development

-

- Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae - pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. -

-
-
-
- -
-

Video Game Development

-

- Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae - pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. -

-
-
-
- -
-

Linux Ricing

-

- Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae - pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. -

-
-
-
- -
-

Open-Source Development

-

- Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae - pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. -

-
-
+ {#each about.activities as activity (activity.title)} + {@render Activity(activity)} + {/each}
diff --git a/src/routes/blog/+page.server.ts b/src/routes/blog/+page.server.ts index fe5d3ae..5aabede 100644 --- a/src/routes/blog/+page.server.ts +++ b/src/routes/blog/+page.server.ts @@ -1,3 +1,4 @@ +import { logger } from '$lib/server/logger'; import { error } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; @@ -25,7 +26,9 @@ export const load: PageServerLoad = async ({ locals }) => { meta }; } catch (err) { - console.log('Uh-oh!'); - error(500, err.message); + logger.error('Uh-oh!', err); + if (err instanceof Error) { + error(500, err); + } } }; diff --git a/src/routes/contact/+page.server.ts b/src/routes/contact/+page.server.ts index 6ee228a..b3f396a 100644 --- a/src/routes/contact/+page.server.ts +++ b/src/routes/contact/+page.server.ts @@ -2,6 +2,7 @@ import { env } from '$env/dynamic/private'; import { fail } from '@sveltejs/kit'; import { createTransport } from 'nodemailer'; import type { Actions } from './$types'; +import { logger } from '$lib/server/logger'; export const actions = { default: async ({ request }) => { @@ -74,7 +75,7 @@ export const actions = { } }; } catch (error) { - console.error(error); + logger.error(error); } } } satisfies Actions; diff --git a/src/routes/resume/+page.server.ts b/src/routes/resume/+page.server.ts new file mode 100644 index 0000000..0d81056 --- /dev/null +++ b/src/routes/resume/+page.server.ts @@ -0,0 +1,43 @@ +import { error } from '@sveltejs/kit'; +import type { PageServerLoad } from './$types'; +import { logger } from '$lib/server/logger'; + +export const load: PageServerLoad = async ({ locals }) => { + const { strapi } = locals; + + try { + const { data: experiences, meta: experiencesMeta } = await strapi.Experiences(); + const { data: education, meta: educationMeta } = await strapi.Education(); + + return { + experiences: experiences.map((item) => { + return { + title: item.Title, + company: item.Company, + description: item.Description, + startDate: item.StartDate, + endDate: item.EndDate, + logo: item.Logo + }; + }), + education: education.map((item) => { + return { + institute: item.Institute, + field: item.Field, + description: item.Description, + startDate: item.StartDate, + endDate: item.EndDate + }; + }), + meta: { + experiences: experiencesMeta, + education: educationMeta + } + }; + } catch (err) { + logger.error('Uh-oh!', err); + if (err instanceof Error) { + error(500, err); + } + } +}; diff --git a/src/routes/resume/+page.svelte b/src/routes/resume/+page.svelte index fa5b5fa..ba971e1 100644 --- a/src/routes/resume/+page.svelte +++ b/src/routes/resume/+page.svelte @@ -1,5 +1,98 @@ - - +{#snippet ExperienceEntry(item: ExperienceItem)} +
+
+
+ {dayjs(item.startDate).format('MMM YYYY')} - {item.endDate + ? dayjs(item.endDate).format('MMM YYYY') + : 'Current'} +
+

{item.company}

+
+
+

{item.title}

+

{item.description}

+
+
+{/snippet} + +{#snippet EducationEntry(item: EducationItem)} +
+
+
+ {dayjs(item.startDate).format('MMM YYYY')} - {item.endDate + ? dayjs(item.endDate).format('MMM YYYY') + : 'Current'} +
+

{item.institute}

+
+
+

{item.field}

+

{item.description}

+
+
+{/snippet} + +
+ +
+
+

Experience

+
+
+
+
+
+ + {#if !experiences} + Looks like I have no experience :( + {:else} + {#each experiences as item (item.company)} + {@render ExperienceEntry(item)} + {/each} + {/if} + + +
+
+

Education

+
+
+
+
+
+ + {#if !education} + Looks like I have no education :( + {:else} + {#each education as item (item.field)} + {@render EducationEntry(item)} + {/each} + {/if} +
diff --git a/strapi/src/api/about/content-types/about/schema.json b/strapi/src/api/about/content-types/about/schema.json new file mode 100644 index 0000000..e7f6b6a --- /dev/null +++ b/strapi/src/api/about/content-types/about/schema.json @@ -0,0 +1,41 @@ +{ + "kind": "singleType", + "collectionName": "abouts", + "info": { + "singularName": "about", + "pluralName": "abouts", + "displayName": "About", + "description": "" + }, + "options": { + "draftAndPublish": true + }, + "attributes": { + "Name": { + "type": "string" + }, + "Bio": { + "type": "text" + }, + "Photo": { + "type": "media", + "multiple": false, + "required": false, + "allowedTypes": ["images", "files", "videos", "audios"] + }, + "Activities": { + "displayName": "Thing I Do", + "type": "component", + "repeatable": true, + "component": "shared.thing-i-do", + "max": 4, + "min": 4 + }, + "Titles": { + "displayName": "Title", + "type": "component", + "repeatable": true, + "component": "shared.title" + } + } +} diff --git a/strapi/src/api/about/controllers/about.ts b/strapi/src/api/about/controllers/about.ts new file mode 100644 index 0000000..cddd029 --- /dev/null +++ b/strapi/src/api/about/controllers/about.ts @@ -0,0 +1,7 @@ +/** + * about controller + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreController('api::about.about'); diff --git a/strapi/src/api/about/routes/about.ts b/strapi/src/api/about/routes/about.ts new file mode 100644 index 0000000..8878140 --- /dev/null +++ b/strapi/src/api/about/routes/about.ts @@ -0,0 +1,7 @@ +/** + * about router + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::about.about'); diff --git a/strapi/src/api/about/services/about.ts b/strapi/src/api/about/services/about.ts new file mode 100644 index 0000000..3fabb98 --- /dev/null +++ b/strapi/src/api/about/services/about.ts @@ -0,0 +1,7 @@ +/** + * about service + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreService('api::about.about'); diff --git a/strapi/src/api/education/content-types/education/schema.json b/strapi/src/api/education/content-types/education/schema.json new file mode 100644 index 0000000..2fffd90 --- /dev/null +++ b/strapi/src/api/education/content-types/education/schema.json @@ -0,0 +1,29 @@ +{ + "kind": "collectionType", + "collectionName": "educations", + "info": { + "singularName": "education", + "pluralName": "educations", + "displayName": "Education" + }, + "options": { + "draftAndPublish": true + }, + "attributes": { + "Institute": { + "type": "string" + }, + "StartDate": { + "type": "date" + }, + "EndDate": { + "type": "date" + }, + "Field": { + "type": "string" + }, + "Description": { + "type": "text" + } + } +} diff --git a/strapi/src/api/education/controllers/education.ts b/strapi/src/api/education/controllers/education.ts new file mode 100644 index 0000000..bea3806 --- /dev/null +++ b/strapi/src/api/education/controllers/education.ts @@ -0,0 +1,7 @@ +/** + * education controller + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreController('api::education.education'); diff --git a/strapi/src/api/education/routes/education.ts b/strapi/src/api/education/routes/education.ts new file mode 100644 index 0000000..fd5371a --- /dev/null +++ b/strapi/src/api/education/routes/education.ts @@ -0,0 +1,7 @@ +/** + * education router + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::education.education'); diff --git a/strapi/src/api/education/services/education.ts b/strapi/src/api/education/services/education.ts new file mode 100644 index 0000000..02ab1bc --- /dev/null +++ b/strapi/src/api/education/services/education.ts @@ -0,0 +1,7 @@ +/** + * education service + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreService('api::education.education'); diff --git a/strapi/src/api/experience/content-types/experience/schema.json b/strapi/src/api/experience/content-types/experience/schema.json new file mode 100644 index 0000000..47d8b1b --- /dev/null +++ b/strapi/src/api/experience/content-types/experience/schema.json @@ -0,0 +1,41 @@ +{ + "kind": "collectionType", + "collectionName": "experiences", + "info": { + "singularName": "experience", + "pluralName": "experiences", + "displayName": "Experience", + "description": "" + }, + "options": { + "draftAndPublish": true + }, + "attributes": { + "Title": { + "type": "string", + "required": true + }, + "Company": { + "type": "string", + "required": true, + "unique": true + }, + "Description": { + "type": "text", + "required": true + }, + "StartDate": { + "type": "date", + "required": true + }, + "EndDate": { + "type": "date" + }, + "Logo": { + "type": "media", + "multiple": false, + "required": false, + "allowedTypes": ["images", "files", "videos", "audios"] + } + } +} diff --git a/strapi/src/api/experience/controllers/experience.ts b/strapi/src/api/experience/controllers/experience.ts new file mode 100644 index 0000000..84b83d7 --- /dev/null +++ b/strapi/src/api/experience/controllers/experience.ts @@ -0,0 +1,7 @@ +/** + * experience controller + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreController('api::experience.experience'); diff --git a/strapi/src/api/experience/routes/experience.ts b/strapi/src/api/experience/routes/experience.ts new file mode 100644 index 0000000..32a74f2 --- /dev/null +++ b/strapi/src/api/experience/routes/experience.ts @@ -0,0 +1,7 @@ +/** + * experience router + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreRouter('api::experience.experience'); diff --git a/strapi/src/api/experience/services/experience.ts b/strapi/src/api/experience/services/experience.ts new file mode 100644 index 0000000..ae50c88 --- /dev/null +++ b/strapi/src/api/experience/services/experience.ts @@ -0,0 +1,7 @@ +/** + * experience service + */ + +import { factories } from '@strapi/strapi'; + +export default factories.createCoreService('api::experience.experience'); diff --git a/strapi/src/components/shared/thing-i-do.json b/strapi/src/components/shared/thing-i-do.json new file mode 100644 index 0000000..dc4a17b --- /dev/null +++ b/strapi/src/components/shared/thing-i-do.json @@ -0,0 +1,17 @@ +{ + "collectionName": "components_shared_thing_i_dos", + "info": { + "displayName": "Thing I Do", + "icon": "walk", + "description": "" + }, + "options": {}, + "attributes": { + "Title": { + "type": "string" + }, + "Description": { + "type": "text" + } + } +} diff --git a/strapi/src/components/shared/title.json b/strapi/src/components/shared/title.json new file mode 100644 index 0000000..bd860f3 --- /dev/null +++ b/strapi/src/components/shared/title.json @@ -0,0 +1,14 @@ +{ + "collectionName": "components_shared_titles", + "info": { + "displayName": "Title", + "icon": "user", + "description": "" + }, + "options": {}, + "attributes": { + "value": { + "type": "string" + } + } +} diff --git a/strapi/types/generated/components.d.ts b/strapi/types/generated/components.d.ts index d9fdd5d..f99b38a 100644 --- a/strapi/types/generated/components.d.ts +++ b/strapi/types/generated/components.d.ts @@ -61,6 +61,31 @@ export interface SharedTag extends Struct.ComponentSchema { }; } +export interface SharedThingIDo extends Struct.ComponentSchema { + collectionName: 'components_shared_thing_i_dos'; + info: { + description: ''; + displayName: 'Thing I Do'; + icon: 'walk'; + }; + attributes: { + Description: Schema.Attribute.Text; + Title: Schema.Attribute.String; + }; +} + +export interface SharedTitle extends Struct.ComponentSchema { + collectionName: 'components_shared_titles'; + info: { + description: ''; + displayName: 'Title'; + icon: 'user'; + }; + attributes: { + value: Schema.Attribute.String; + }; +} + declare module '@strapi/strapi' { export module Public { export interface ComponentSchemas { @@ -69,6 +94,8 @@ declare module '@strapi/strapi' { 'shared.rich-text': SharedRichText; 'shared.seo': SharedSeo; 'shared.tag': SharedTag; + 'shared.thing-i-do': SharedThingIDo; + 'shared.title': SharedTitle; } } } diff --git a/strapi/types/generated/contentTypes.d.ts b/strapi/types/generated/contentTypes.d.ts index 3a8e0a7..a685609 100644 --- a/strapi/types/generated/contentTypes.d.ts +++ b/strapi/types/generated/contentTypes.d.ts @@ -336,6 +336,41 @@ export interface AdminUser extends Struct.CollectionTypeSchema { }; } +export interface ApiAboutAbout extends Struct.SingleTypeSchema { + collectionName: 'abouts'; + info: { + description: ''; + displayName: 'About'; + pluralName: 'abouts'; + singularName: 'about'; + }; + options: { + draftAndPublish: true; + }; + attributes: { + Activities: Schema.Attribute.Component<'shared.thing-i-do', true> & + Schema.Attribute.SetMinMax< + { + max: 4; + min: 4; + }, + number + >; + Bio: Schema.Attribute.Text; + createdAt: Schema.Attribute.DateTime; + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; + locale: Schema.Attribute.String & Schema.Attribute.Private; + localizations: Schema.Attribute.Relation<'oneToMany', 'api::about.about'> & + Schema.Attribute.Private; + Name: Schema.Attribute.String; + Photo: Schema.Attribute.Media<'images' | 'files' | 'videos' | 'audios'>; + publishedAt: Schema.Attribute.DateTime; + Titles: Schema.Attribute.Component<'shared.title', true>; + updatedAt: Schema.Attribute.DateTime; + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; + }; +} + export interface ApiBlogBlog extends Struct.CollectionTypeSchema { collectionName: 'blogs'; info: { @@ -363,6 +398,85 @@ export interface ApiBlogBlog extends Struct.CollectionTypeSchema { }; } +export interface ApiEducationEducation extends Struct.CollectionTypeSchema { + collectionName: 'educations'; + info: { + displayName: 'Education'; + pluralName: 'educations'; + singularName: 'education'; + }; + options: { + draftAndPublish: true; + }; + attributes: { + createdAt: Schema.Attribute.DateTime; + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; + Description: Schema.Attribute.Text; + EndDate: Schema.Attribute.Date; + Field: Schema.Attribute.String; + Institute: Schema.Attribute.String; + locale: Schema.Attribute.String & Schema.Attribute.Private; + localizations: Schema.Attribute.Relation<'oneToMany', 'api::education.education'> & + Schema.Attribute.Private; + publishedAt: Schema.Attribute.DateTime; + StartDate: Schema.Attribute.Date; + updatedAt: Schema.Attribute.DateTime; + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; + }; +} + +export interface ApiExperienceExperience extends Struct.CollectionTypeSchema { + collectionName: 'experiences'; + info: { + description: ''; + displayName: 'Experience'; + pluralName: 'experiences'; + singularName: 'experience'; + }; + options: { + draftAndPublish: true; + }; + attributes: { + Company: Schema.Attribute.String & Schema.Attribute.Required & Schema.Attribute.Unique; + createdAt: Schema.Attribute.DateTime; + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; + Description: Schema.Attribute.Text & Schema.Attribute.Required; + EndDate: Schema.Attribute.Date; + locale: Schema.Attribute.String & Schema.Attribute.Private; + localizations: Schema.Attribute.Relation<'oneToMany', 'api::experience.experience'> & + Schema.Attribute.Private; + Logo: Schema.Attribute.Media<'images' | 'files' | 'videos' | 'audios'>; + publishedAt: Schema.Attribute.DateTime; + StartDate: Schema.Attribute.Date & Schema.Attribute.Required; + Title: Schema.Attribute.String & Schema.Attribute.Required; + updatedAt: Schema.Attribute.DateTime; + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; + }; +} + +export interface ApiSiteBackgroundSiteBackground extends Struct.SingleTypeSchema { + collectionName: 'site_backgrounds'; + info: { + displayName: 'Site Background'; + pluralName: 'site-backgrounds'; + singularName: 'site-background'; + }; + options: { + draftAndPublish: true; + }; + attributes: { + Background: Schema.Attribute.Media<'images' | 'files' | 'videos' | 'audios'>; + createdAt: Schema.Attribute.DateTime; + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; + locale: Schema.Attribute.String & Schema.Attribute.Private; + localizations: Schema.Attribute.Relation<'oneToMany', 'api::site-background.site-background'> & + Schema.Attribute.Private; + publishedAt: Schema.Attribute.DateTime; + updatedAt: Schema.Attribute.DateTime; + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; + }; +} + export interface PluginContentReleasesRelease extends Struct.CollectionTypeSchema { collectionName: 'strapi_releases'; info: { @@ -790,7 +904,11 @@ declare module '@strapi/strapi' { 'admin::transfer-token': AdminTransferToken; 'admin::transfer-token-permission': AdminTransferTokenPermission; 'admin::user': AdminUser; + 'api::about.about': ApiAboutAbout; 'api::blog.blog': ApiBlogBlog; + 'api::education.education': ApiEducationEducation; + 'api::experience.experience': ApiExperienceExperience; + 'api::site-background.site-background': ApiSiteBackgroundSiteBackground; 'plugin::content-releases.release': PluginContentReleasesRelease; 'plugin::content-releases.release-action': PluginContentReleasesReleaseAction; 'plugin::i18n.locale': PluginI18NLocale;