├── .github
├── release-drafter.yml
└── workflows
│ ├── deploy-image-resizer.yml
│ ├── deploy-og-image.yml
│ ├── deploy-server.yml
│ ├── merge-release.yml
│ └── release-drafter.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .vscode
└── settings.json
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── lerna.json
├── package.json
├── packages
├── client
│ ├── .babelrc
│ ├── .env.example
│ ├── .eslintrc
│ ├── .gitignore
│ ├── apollo.config.js
│ ├── codegen.yml
│ ├── env.d.ts
│ ├── next-env.d.ts
│ ├── next.config.js
│ ├── package.json
│ ├── patches
│ │ ├── baseui+9.116.3.patch
│ │ └── next+11.0.1.patch
│ ├── public
│ │ ├── favicon.ico
│ │ ├── favicons
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── android-chrome-256x256.png
│ │ │ ├── apple-touch-icon.png
│ │ │ ├── browserconfig.xml
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ ├── favicon.ico
│ │ │ ├── mstile-150x150.png
│ │ │ ├── safari-pinned-tab.svg
│ │ │ └── site.webmanifest
│ │ ├── fonts
│ │ │ ├── SpoqaHanSansNeo-Bold.woff2
│ │ │ ├── SpoqaHanSansNeo-Light.woff2
│ │ │ ├── SpoqaHanSansNeo-Medium.woff2
│ │ │ ├── SpoqaHanSansNeo-Regular.woff2
│ │ │ └── SpoqaHanSansNeo-Thin.woff2
│ │ ├── icons
│ │ │ ├── android.svg
│ │ │ ├── apple.svg
│ │ │ ├── appstore.svg
│ │ │ ├── bold.svg
│ │ │ ├── check.svg
│ │ │ ├── code.svg
│ │ │ ├── copy.svg
│ │ │ ├── drag-handle.svg
│ │ │ ├── facebook-circle.svg
│ │ │ ├── facebook.svg
│ │ │ ├── font-color.svg
│ │ │ ├── github-circle.svg
│ │ │ ├── github.svg
│ │ │ ├── github2.svg
│ │ │ ├── google.svg
│ │ │ ├── highlight.svg
│ │ │ ├── image.svg
│ │ │ ├── italic.svg
│ │ │ ├── link.svg
│ │ │ ├── linkedin-circle.svg
│ │ │ ├── pencil.svg
│ │ │ ├── playstore.svg
│ │ │ ├── plus.svg
│ │ │ ├── setting.svg
│ │ │ ├── stack-overflow.svg
│ │ │ ├── stackoverflow-circle.svg
│ │ │ ├── trash-line.svg
│ │ │ ├── twitter-circle.svg
│ │ │ ├── twitter.svg
│ │ │ ├── user-add-outlined.svg
│ │ │ ├── velog-circle.svg
│ │ │ ├── verified.svg
│ │ │ ├── web.svg
│ │ │ ├── x.svg
│ │ │ └── youtube-circle.svg
│ │ ├── images
│ │ │ ├── block-thumbnails
│ │ │ │ ├── career-list.png
│ │ │ │ ├── project-list.png
│ │ │ │ ├── school-list.png
│ │ │ │ └── skill-list.png
│ │ │ ├── chat.png
│ │ │ ├── example.png
│ │ │ ├── illust
│ │ │ │ └── images.svg
│ │ │ ├── logo-picker.gif
│ │ │ ├── logo.svg
│ │ │ └── url.png
│ │ ├── logos
│ │ │ ├── 100tb.svg
│ │ │ ├── 500px.svg
│ │ │ ├── adroll.svg
│ │ │ ├── adyen.svg
│ │ │ ├── aerospike-icon.svg
│ │ │ ├── aerospike.svg
│ │ │ ├── airbnb.svg
│ │ │ ├── airbrake.svg
│ │ │ ├── airflow.svg
│ │ │ ├── airtable.svg
│ │ │ ├── akamai.svg
│ │ │ ├── akka.svg
│ │ │ ├── alfresco.svg
│ │ │ ├── algolia.svg
│ │ │ ├── altair.svg
│ │ │ ├── amazon-chime.svg
│ │ │ ├── amazon-connect.svg
│ │ │ ├── amex.svg
│ │ │ ├── amp-icon.svg
│ │ │ ├── amp.svg
│ │ │ ├── ampersand.svg
│ │ │ ├── android-icon.svg
│ │ │ ├── android.svg
│ │ │ ├── angellist.svg
│ │ │ ├── angular-icon.svg
│ │ │ ├── angular.svg
│ │ │ ├── ansible.svg
│ │ │ ├── ant-design.svg
│ │ │ ├── apache-camel.svg
│ │ │ ├── apache.svg
│ │ │ ├── apache_cloudstack.svg
│ │ │ ├── apiary.svg
│ │ │ ├── apollostack.svg
│ │ │ ├── apostrophe.svg
│ │ │ ├── appbaseio-icon.svg
│ │ │ ├── appbaseio.svg
│ │ │ ├── appcircle-icon.svg
│ │ │ ├── appcircle.svg
│ │ │ ├── appcode.svg
│ │ │ ├── appdynamics.svg
│ │ │ ├── appium.svg
│ │ │ ├── apple-app-store.svg
│ │ │ ├── apple-pay.svg
│ │ │ ├── apple.svg
│ │ │ ├── appsignal-icon.svg
│ │ │ ├── appsignal.svg
│ │ │ ├── apptentive.svg
│ │ │ ├── appveyor.svg
│ │ │ ├── arangodb.svg
│ │ │ ├── archlinux.svg
│ │ │ ├── arduino.svg
│ │ │ ├── armory.svg
│ │ │ ├── asana.svg
│ │ │ ├── asciidoctor.svg
│ │ │ ├── astronomer.svg
│ │ │ ├── atlassian.svg
│ │ │ ├── atom-icon.svg
│ │ │ ├── atom.svg
│ │ │ ├── atomic-icon.svg
│ │ │ ├── atomic.svg
│ │ │ ├── aurelia.svg
│ │ │ ├── aurora.svg
│ │ │ ├── auth0.svg
│ │ │ ├── authy.svg
│ │ │ ├── autoit.svg
│ │ │ ├── autoprefixer.svg
│ │ │ ├── ava.svg
│ │ │ ├── awesome.svg
│ │ │ ├── aws-api-gateway.svg
│ │ │ ├── aws-cloudformation.svg
│ │ │ ├── aws-cloudfront.svg
│ │ │ ├── aws-cloudsearch.svg
│ │ │ ├── aws-cloudwatch.svg
│ │ │ ├── aws-codedeploy.svg
│ │ │ ├── aws-cognito.svg
│ │ │ ├── aws-dynamodb.svg
│ │ │ ├── aws-ec2.svg
│ │ │ ├── aws-elastic-cache.svg
│ │ │ ├── aws-glacier.svg
│ │ │ ├── aws-iam.svg
│ │ │ ├── aws-kinesis.svg
│ │ │ ├── aws-lambda.svg
│ │ │ ├── aws-opsworks.svg
│ │ │ ├── aws-quicksight.svg
│ │ │ ├── aws-rds.svg
│ │ │ ├── aws-route53.svg
│ │ │ ├── aws-s3.svg
│ │ │ ├── aws-ses.svg
│ │ │ ├── aws-sns.svg
│ │ │ ├── aws-sqs.svg
│ │ │ ├── aws-waf.svg
│ │ │ ├── aws.svg
│ │ │ ├── azure-icon.svg
│ │ │ ├── azure.svg
│ │ │ ├── babel.svg
│ │ │ ├── backbone-icon.svg
│ │ │ ├── backbone.svg
│ │ │ ├── backerkit.svg
│ │ │ ├── baker-street.svg
│ │ │ ├── balena.svg
│ │ │ ├── bamboo.svg
│ │ │ ├── basecamp.svg
│ │ │ ├── basekit.svg
│ │ │ ├── bash-icon.svg
│ │ │ ├── bash.svg
│ │ │ ├── batch.svg
│ │ │ ├── beats.svg
│ │ │ ├── behance.svg
│ │ │ ├── bem-2.svg
│ │ │ ├── bem.svg
│ │ │ ├── bigpanda.svg
│ │ │ ├── bing.svg
│ │ │ ├── bitbucket.svg
│ │ │ ├── bitcoin.svg
│ │ │ ├── bitnami.svg
│ │ │ ├── bitrise-icon.svg
│ │ │ ├── bitrise.svg
│ │ │ ├── blender.svg
│ │ │ ├── blocs.svg
│ │ │ ├── blogger.svg
│ │ │ ├── blossom.svg
│ │ │ ├── blueprint.svg
│ │ │ ├── bluetooth.svg
│ │ │ ├── booqable.svg
│ │ │ ├── bootstrap.svg
│ │ │ ├── bosun.svg
│ │ │ ├── botanalytics.svg
│ │ │ ├── bourbon.svg
│ │ │ ├── bower.svg
│ │ │ ├── box.svg
│ │ │ ├── brackets.svg
│ │ │ ├── branch.svg
│ │ │ ├── brandfolder-icon.svg
│ │ │ ├── brandfolder.svg
│ │ │ ├── brave.svg
│ │ │ ├── braze.svg
│ │ │ ├── broccoli.svg
│ │ │ ├── brotli.svg
│ │ │ ├── browserify-icon.svg
│ │ │ ├── browserify.svg
│ │ │ ├── browserling.svg
│ │ │ ├── browserslist.svg
│ │ │ ├── browserstack.svg
│ │ │ ├── browsersync.svg
│ │ │ ├── brunch.svg
│ │ │ ├── buck.svg
│ │ │ ├── buddy.svg
│ │ │ ├── buffer.svg
│ │ │ ├── bugherd.svg
│ │ │ ├── bugsee.svg
│ │ │ ├── bugsnag-icon.svg
│ │ │ ├── bugsnag.svg
│ │ │ ├── buildkite-icon.svg
│ │ │ ├── buildkite.svg
│ │ │ ├── bulma.svg
│ │ │ ├── c-plusplus.svg
│ │ │ ├── c-sharp.svg
│ │ │ ├── c.svg
│ │ │ ├── cachet.svg
│ │ │ ├── caffe2.svg
│ │ │ ├── cakephp-icon.svg
│ │ │ ├── cakephp.svg
│ │ │ ├── campaignmonitor-icon.svg
│ │ │ ├── campaignmonitor.svg
│ │ │ ├── canjs.svg
│ │ │ ├── capacitorjs-icon.svg
│ │ │ ├── capacitorjs.svg
│ │ │ ├── capistrano.svg
│ │ │ ├── carbide.svg
│ │ │ ├── cassandra.svg
│ │ │ ├── centos-icon.svg
│ │ │ ├── centos.svg
│ │ │ ├── certbot.svg
│ │ │ ├── ceylon.svg
│ │ │ ├── chai.svg
│ │ │ ├── chalk.svg
│ │ │ ├── chargebee-icon.svg
│ │ │ ├── chargebee.svg
│ │ │ ├── chef.svg
│ │ │ ├── chevereto.svg
│ │ │ ├── chromatic-icon.svg
│ │ │ ├── chromatic.svg
│ │ │ ├── chrome.svg
│ │ │ ├── circleci.svg
│ │ │ ├── cirrus-ci.svg
│ │ │ ├── cirrus.svg
│ │ │ ├── clion.svg
│ │ │ ├── cljs.svg
│ │ │ ├── clojure.svg
│ │ │ ├── close.svg
│ │ │ ├── cloud9.svg
│ │ │ ├── cloudacademy-icon.svg
│ │ │ ├── cloudacademy.svg
│ │ │ ├── cloudcraft.svg
│ │ │ ├── cloudflare.svg
│ │ │ ├── cloudinary.svg
│ │ │ ├── cloudlinux.svg
│ │ │ ├── cobalt.svg
│ │ │ ├── cockpit.svg
│ │ │ ├── cocoapods.svg
│ │ │ ├── codacy.svg
│ │ │ ├── codebase.svg
│ │ │ ├── codebeat.svg
│ │ │ ├── codecademy.svg
│ │ │ ├── codeception.svg
│ │ │ ├── codeclimate.svg
│ │ │ ├── codecov.svg
│ │ │ ├── codefactor-icon.svg
│ │ │ ├── codefactor.svg
│ │ │ ├── codeigniter.svg
│ │ │ ├── codepen-icon.svg
│ │ │ ├── codepen.svg
│ │ │ ├── codepush.svg
│ │ │ ├── codersrank.svg
│ │ │ ├── coderwall.svg
│ │ │ ├── codesandbox.svg
│ │ │ ├── codeship.svg
│ │ │ ├── codio.svg
│ │ │ ├── codrops.svg
│ │ │ ├── coffeescript.svg
│ │ │ ├── compass.svg
│ │ │ ├── componentkit.svg
│ │ │ ├── compose.svg
│ │ │ ├── composer.svg
│ │ │ ├── conan-io.svg
│ │ │ ├── concourse.svg
│ │ │ ├── concrete5.svg
│ │ │ ├── confluence.svg
│ │ │ ├── consul.svg
│ │ │ ├── contentful.svg
│ │ │ ├── convox.svg
│ │ │ ├── copyleft-pirate.svg
│ │ │ ├── copyleft.svg
│ │ │ ├── cordova.svg
│ │ │ ├── coreos-icon.svg
│ │ │ ├── coreos.svg
│ │ │ ├── couchbase.svg
│ │ │ ├── couchdb-icon.svg
│ │ │ ├── couchdb.svg
│ │ │ ├── coursera.svg
│ │ │ ├── coveralls.svg
│ │ │ ├── cpanel.svg
│ │ │ ├── craftcms.svg
│ │ │ ├── crashlytics.svg
│ │ │ ├── crateio.svg
│ │ │ ├── createjs.svg
│ │ │ ├── cross-browser-testing.svg
│ │ │ ├── crucible.svg
│ │ │ ├── crystal.svg
│ │ │ ├── css-3.svg
│ │ │ ├── css-3_official.svg
│ │ │ ├── cssnext.svg
│ │ │ ├── cucumber.svg
│ │ │ ├── customerio-icon.svg
│ │ │ ├── customerio.svg
│ │ │ ├── cyclejs.svg
│ │ │ ├── cypress.svg
│ │ │ ├── d3.svg
│ │ │ ├── dart.svg
│ │ │ ├── dashlane-icon.svg
│ │ │ ├── dashlane.svg
│ │ │ ├── database-labs.svg
│ │ │ ├── datadog.svg
│ │ │ ├── datocms-icon.svg
│ │ │ ├── datocms.svg
│ │ │ ├── dcos-icon.svg
│ │ │ ├── dcos.svg
│ │ │ ├── debian.svg
│ │ │ ├── delighted-icon.svg
│ │ │ ├── delighted.svg
│ │ │ ├── deno.svg
│ │ │ ├── deployhq.svg
│ │ │ ├── derby.svg
│ │ │ ├── designernews.svg
│ │ │ ├── deviantart.svg
│ │ │ ├── dialogflow.svg
│ │ │ ├── digital-ocean.svg
│ │ │ ├── dimer.svg
│ │ │ ├── dinersclub.svg
│ │ │ ├── discord.svg
│ │ │ ├── discover.svg
│ │ │ ├── disqus.svg
│ │ │ ├── django-icon.svg
│ │ │ ├── django.svg
│ │ │ ├── dockbit.svg
│ │ │ ├── docker-icon.svg
│ │ │ ├── docker.svg
│ │ │ ├── doctrine.svg
│ │ │ ├── docusaurus.svg
│ │ │ ├── dojo-icon.svg
│ │ │ ├── dojo-toolkit.svg
│ │ │ ├── dojo.svg
│ │ │ ├── dotnet.svg
│ │ │ ├── dreamhost.svg
│ │ │ ├── dribbble-icon.svg
│ │ │ ├── dribbble.svg
│ │ │ ├── drift.svg
│ │ │ ├── drip.svg
│ │ │ ├── drone-icon.svg
│ │ │ ├── drone.svg
│ │ │ ├── dropbox.svg
│ │ │ ├── dropmark.svg
│ │ │ ├── dropzone.svg
│ │ │ ├── drupal-icon.svg
│ │ │ ├── drupal.svg
│ │ │ ├── duckduckgo.svg
│ │ │ ├── dynatrace-icon.svg
│ │ │ ├── dynatrace.svg
│ │ │ ├── dyndns.svg
│ │ │ ├── ebanx.svg
│ │ │ ├── eclipse-icon.svg
│ │ │ ├── eclipse.svg
│ │ │ ├── editorconfig.svg
│ │ │ ├── egghead.svg
│ │ │ ├── elasticsearch.svg
│ │ │ ├── electron.svg
│ │ │ ├── element.svg
│ │ │ ├── elemental-ui.svg
│ │ │ ├── elementary.svg
│ │ │ ├── ello.svg
│ │ │ ├── elm.svg
│ │ │ ├── elo.svg
│ │ │ ├── emacs.svg
│ │ │ ├── embedly.svg
│ │ │ ├── ember-tomster.svg
│ │ │ ├── ember.svg
│ │ │ ├── emmet.svg
│ │ │ ├── engine-yard-icon.svg
│ │ │ ├── engine-yard.svg
│ │ │ ├── envato.svg
│ │ │ ├── envoyer.svg
│ │ │ ├── envoyproxy.svg
│ │ │ ├── enyo.svg
│ │ │ ├── erlang.svg
│ │ │ ├── es6.svg
│ │ │ ├── esdoc.svg
│ │ │ ├── eslint-old.svg
│ │ │ ├── eslint.svg
│ │ │ ├── eta-lang.svg
│ │ │ ├── etcd.svg
│ │ │ ├── ethereum.svg
│ │ │ ├── ethnio.svg
│ │ │ ├── eventbrite-icon.svg
│ │ │ ├── eventbrite.svg
│ │ │ ├── eventsentry.svg
│ │ │ ├── evergreen-icon.svg
│ │ │ ├── evergreen.svg
│ │ │ ├── expo-icon.svg
│ │ │ ├── expo.svg
│ │ │ ├── express.svg
│ │ │ ├── fabric.svg
│ │ │ ├── facebook.svg
│ │ │ ├── falcor.svg
│ │ │ ├── fastify-icon.svg
│ │ │ ├── fastify.svg
│ │ │ ├── fastlane.svg
│ │ │ ├── fastly.svg
│ │ │ ├── feathersjs.svg
│ │ │ ├── fedora.svg
│ │ │ ├── figma.svg
│ │ │ ├── firebase.svg
│ │ │ ├── firefox.svg
│ │ │ ├── flannel.svg
│ │ │ ├── flarum.svg
│ │ │ ├── flask.svg
│ │ │ ├── flat-ui.svg
│ │ │ ├── flattr-icon.svg
│ │ │ ├── flattr.svg
│ │ │ ├── fleep.svg
│ │ │ ├── flickr.svg
│ │ │ ├── flight.svg
│ │ │ ├── floodio.svg
│ │ │ ├── flow.svg
│ │ │ ├── flowxo.svg
│ │ │ ├── floydhub.svg
│ │ │ ├── flutter.svg
│ │ │ ├── flux.svg
│ │ │ ├── fluxxor.svg
│ │ │ ├── fly.svg
│ │ │ ├── fomo.svg
│ │ │ ├── font-awesome.svg
│ │ │ ├── forestadmin-icon.svg
│ │ │ ├── forestadmin.svg
│ │ │ ├── forever.svg
│ │ │ ├── formkeep.svg
│ │ │ ├── foundation.svg
│ │ │ ├── framework7-icon.svg
│ │ │ ├── framework7.svg
│ │ │ ├── freebsd.svg
│ │ │ ├── freedcamp-icon.svg
│ │ │ ├── freedcamp.svg
│ │ │ ├── freedomdefined.svg
│ │ │ ├── frontapp.svg
│ │ │ ├── fsharp.svg
│ │ │ ├── fuchsia.svg
│ │ │ ├── galliumos.svg
│ │ │ ├── game-analytics-icon.svg
│ │ │ ├── game-analytics.svg
│ │ │ ├── gatsby.svg
│ │ │ ├── geekbot.svg
│ │ │ ├── getyourguide.svg
│ │ │ ├── ghost.svg
│ │ │ ├── giantswarm.svg
│ │ │ ├── git-icon.svg
│ │ │ ├── git.svg
│ │ │ ├── gitboard.svg
│ │ │ ├── github-icon.svg
│ │ │ ├── github-octocat.svg
│ │ │ ├── github.svg
│ │ │ ├── gitkraken.svg
│ │ │ ├── gitlab.svg
│ │ │ ├── gitter.svg
│ │ │ ├── gitup.svg
│ │ │ ├── glamorous.svg
│ │ │ ├── gleam.svg
│ │ │ ├── glimmerjs.svg
│ │ │ ├── glint.svg
│ │ │ ├── glitch-icon.svg
│ │ │ ├── glitch.svg
│ │ │ ├── gnu.svg
│ │ │ ├── go.svg
│ │ │ ├── gocd.svg
│ │ │ ├── gohorse.svg
│ │ │ ├── google-360suite.svg
│ │ │ ├── google-admob.svg
│ │ │ ├── google-ads.svg
│ │ │ ├── google-adsense.svg
│ │ │ ├── google-analytics.svg
│ │ │ ├── google-calendar.svg
│ │ │ ├── google-cloud-functions.svg
│ │ │ ├── google-cloud-run.svg
│ │ │ ├── google-cloud.svg
│ │ │ ├── google-currents.svg
│ │ │ ├── google-data-studio.svg
│ │ │ ├── google-developers.svg
│ │ │ ├── google-drive.svg
│ │ │ ├── google-fit.svg
│ │ │ ├── google-gmail.svg
│ │ │ ├── google-gsuite.svg
│ │ │ ├── google-home.svg
│ │ │ ├── google-icon.svg
│ │ │ ├── google-keep.svg
│ │ │ ├── google-maps.svg
│ │ │ ├── google-marketing-platform.svg
│ │ │ ├── google-meet.svg
│ │ │ ├── google-one.svg
│ │ │ ├── google-optimize.svg
│ │ │ ├── google-pay-icon.svg
│ │ │ ├── google-pay.svg
│ │ │ ├── google-photos.svg
│ │ │ ├── google-play-icon.svg
│ │ │ ├── google-play.svg
│ │ │ ├── google-tag-manager.svg
│ │ │ ├── google.svg
│ │ │ ├── gopher.svg
│ │ │ ├── gradle.svg
│ │ │ ├── grafana.svg
│ │ │ ├── grails.svg
│ │ │ ├── graphene.svg
│ │ │ ├── graphql.svg
│ │ │ ├── grav.svg
│ │ │ ├── gravatar.svg
│ │ │ ├── graylog-icon.svg
│ │ │ ├── graylog.svg
│ │ │ ├── gridsome-icon.svg
│ │ │ ├── gridsome.svg
│ │ │ ├── grommet.svg
│ │ │ ├── groovehq.svg
│ │ │ ├── grove.svg
│ │ │ ├── grunt.svg
│ │ │ ├── gulp.svg
│ │ │ ├── gunicorn.svg
│ │ │ ├── gusto.svg
│ │ │ ├── gwt.svg
│ │ │ ├── hack.svg
│ │ │ ├── hacker-one.svg
│ │ │ ├── hadoop.svg
│ │ │ ├── haiku-icon.svg
│ │ │ ├── haiku.svg
│ │ │ ├── haml.svg
│ │ │ ├── hanami.svg
│ │ │ ├── handlebars.svg
│ │ │ ├── hapi.svg
│ │ │ ├── hashnode-icon.svg
│ │ │ ├── hashnode.svg
│ │ │ ├── haskell-icon.svg
│ │ │ ├── haskell.svg
│ │ │ ├── hasura.svg
│ │ │ ├── haxe.svg
│ │ │ ├── haxl.svg
│ │ │ ├── hbase.svg
│ │ │ ├── heap.svg
│ │ │ ├── helpscout-icon.svg
│ │ │ ├── helpscout.svg
│ │ │ ├── heroku-icon.svg
│ │ │ ├── heroku-redis.svg
│ │ │ ├── heroku.svg
│ │ │ ├── hexo.svg
│ │ │ ├── hhvm.svg
│ │ │ ├── hibernate.svg
│ │ │ ├── highcharts.svg
│ │ │ ├── hipercard.svg
│ │ │ ├── hoa.svg
│ │ │ ├── homebrew.svg
│ │ │ ├── hoodie.svg
│ │ │ ├── hosted-graphite.svg
│ │ │ ├── hostgator-icon.svg
│ │ │ ├── hostgator.svg
│ │ │ ├── hotjar.svg
│ │ │ ├── houndci.svg
│ │ │ ├── html-5.svg
│ │ │ ├── html5-boilerplate.svg
│ │ │ ├── hubspot.svg
│ │ │ ├── hugo.svg
│ │ │ ├── humongous.svg
│ │ │ ├── hyper.svg
│ │ │ ├── hyperapp.svg
│ │ │ ├── ibm.svg
│ │ │ ├── ieee.svg
│ │ │ ├── ifttt.svg
│ │ │ ├── imagemin.svg
│ │ │ ├── immutable.svg
│ │ │ ├── impala.svg
│ │ │ ├── importio.svg
│ │ │ ├── infer.svg
│ │ │ ├── inferno.svg
│ │ │ ├── influxdb.svg
│ │ │ ├── ink.svg
│ │ │ ├── insomnia.svg
│ │ │ ├── instagram-icon.svg
│ │ │ ├── instagram.svg
│ │ │ ├── intellij-idea.svg
│ │ │ ├── intercom-icon.svg
│ │ │ ├── intercom.svg
│ │ │ ├── internetexplorer.svg
│ │ │ ├── invision-icon.svg
│ │ │ ├── invision.svg
│ │ │ ├── ionic-icon.svg
│ │ │ ├── ionic.svg
│ │ │ ├── ios.svg
│ │ │ ├── iron-icon.svg
│ │ │ ├── iron.svg
│ │ │ ├── itsalive-icon.svg
│ │ │ ├── itsalive.svg
│ │ │ ├── jade.svg
│ │ │ ├── jamstack-icon.svg
│ │ │ ├── jamstack.svg
│ │ │ ├── jasmine.svg
│ │ │ ├── java.svg
│ │ │ ├── javascript.svg
│ │ │ ├── jcb.svg
│ │ │ ├── jekyll.svg
│ │ │ ├── jelastic-icon.svg
│ │ │ ├── jelastic.svg
│ │ │ ├── jenkins.svg
│ │ │ ├── jest.svg
│ │ │ ├── jetbrains.svg
│ │ │ ├── jfrog.svg
│ │ │ ├── jhipster-icon.svg
│ │ │ ├── jhipster.svg
│ │ │ ├── jira.svg
│ │ │ ├── joomla.svg
│ │ │ ├── jquery-mobile.svg
│ │ │ ├── jquery.svg
│ │ │ ├── jruby.svg
│ │ │ ├── jsbin.svg
│ │ │ ├── jsdelivr.svg
│ │ │ ├── jsdom.svg
│ │ │ ├── jsfiddle.svg
│ │ │ ├── json.svg
│ │ │ ├── jspm.svg
│ │ │ ├── jss.svg
│ │ │ ├── juju.svg
│ │ │ ├── julia.svg
│ │ │ ├── jupyter.svg
│ │ │ ├── kafka-icon.svg
│ │ │ ├── kafka.svg
│ │ │ ├── kaios.svg
│ │ │ ├── kallithea.svg
│ │ │ ├── karma.svg
│ │ │ ├── kde.svg
│ │ │ ├── keen.svg
│ │ │ ├── kemal.svg
│ │ │ ├── keycdn-icon.svg
│ │ │ ├── keycdn.svg
│ │ │ ├── keystonejs.svg
│ │ │ ├── khan_academy-icon.svg
│ │ │ ├── khan_academy.svg
│ │ │ ├── kibana.svg
│ │ │ ├── kickstarter-icon.svg
│ │ │ ├── kickstarter.svg
│ │ │ ├── kinto-icon.svg
│ │ │ ├── kinto.svg
│ │ │ ├── kirby-icon.svg
│ │ │ ├── kirby.svg
│ │ │ ├── kissmetrics.svg
│ │ │ ├── kitematic.svg
│ │ │ ├── kloudless.svg
│ │ │ ├── knex.svg
│ │ │ ├── knockout.svg
│ │ │ ├── koa.svg
│ │ │ ├── kong-icon.svg
│ │ │ ├── kong.svg
│ │ │ ├── kops.svg
│ │ │ ├── koreio.svg
│ │ │ ├── kotlin.svg
│ │ │ ├── kraken.svg
│ │ │ ├── krakenjs.svg
│ │ │ ├── kubernetes.svg
│ │ │ ├── kustomer.svg
│ │ │ ├── laravel.svg
│ │ │ ├── lastfm.svg
│ │ │ ├── lateral.svg
│ │ │ ├── launchrock.svg
│ │ │ ├── leaflet.svg
│ │ │ ├── leankit-icon.svg
│ │ │ ├── leankit.svg
│ │ │ ├── less.svg
│ │ │ ├── lets-cloud.svg
│ │ │ ├── letsencrypt.svg
│ │ │ ├── leveldb.svg
│ │ │ ├── liftweb.svg
│ │ │ ├── lighthouse.svg
│ │ │ ├── lighttpd.svg
│ │ │ ├── linkedin-icon.svg
│ │ │ ├── linkedin.svg
│ │ │ ├── linkerd.svg
│ │ │ ├── linode.svg
│ │ │ ├── linux-mint.svg
│ │ │ ├── linux-tux.svg
│ │ │ ├── litmus.svg
│ │ │ ├── loader.svg
│ │ │ ├── lodash.svg
│ │ │ ├── logentries.svg
│ │ │ ├── logstash.svg
│ │ │ ├── lookback.svg
│ │ │ ├── looker-icon.svg
│ │ │ ├── looker.svg
│ │ │ ├── loom.svg
│ │ │ ├── loopback-icon.svg
│ │ │ ├── loopback.svg
│ │ │ ├── losant.svg
│ │ │ ├── lua.svg
│ │ │ ├── lucene.net.svg
│ │ │ ├── lucene.svg
│ │ │ ├── lumen.svg
│ │ │ ├── macOS.svg
│ │ │ ├── madge.svg
│ │ │ ├── maestro.svg
│ │ │ ├── mageia.svg
│ │ │ ├── magento.svg
│ │ │ ├── mailchimp-freddie.svg
│ │ │ ├── mailchimp.svg
│ │ │ ├── maildeveloper.svg
│ │ │ ├── mailgun-icon.svg
│ │ │ ├── mailgun.svg
│ │ │ ├── mailjet.svg
│ │ │ ├── manuscript.svg
│ │ │ ├── mapbox-icon.svg
│ │ │ ├── mapbox.svg
│ │ │ ├── maps-me.svg
│ │ │ ├── mapzen-icon.svg
│ │ │ ├── mapzen.svg
│ │ │ ├── mariadb-icon.svg
│ │ │ ├── mariadb.svg
│ │ │ ├── marionette.svg
│ │ │ ├── markdown.svg
│ │ │ ├── marko.svg
│ │ │ ├── marvel.svg
│ │ │ ├── mastercard.svg
│ │ │ ├── mastodon-icon.svg
│ │ │ ├── mastodon.svg
│ │ │ ├── material-ui.svg
│ │ │ ├── materializecss.svg
│ │ │ ├── mattermost-icon.svg
│ │ │ ├── mattermost.svg
│ │ │ ├── maven.svg
│ │ │ ├── maxcdn.svg
│ │ │ ├── mdn.svg
│ │ │ ├── mdx.svg
│ │ │ ├── medium-icon.svg
│ │ │ ├── medium.svg
│ │ │ ├── memcached.svg
│ │ │ ├── memsql-icon.svg
│ │ │ ├── memsql.svg
│ │ │ ├── mention.svg
│ │ │ ├── mercurial.svg
│ │ │ ├── mesos.svg
│ │ │ ├── metabase.svg
│ │ │ ├── meteor-icon.svg
│ │ │ ├── meteor.svg
│ │ │ ├── microcosm.svg
│ │ │ ├── microsoft-edge.svg
│ │ │ ├── microsoft-windows.svg
│ │ │ ├── microsoft.svg
│ │ │ ├── middleman.svg
│ │ │ ├── milligram.svg
│ │ │ ├── mio.svg
│ │ │ ├── mist.svg
│ │ │ ├── mithril.svg
│ │ │ ├── mixmax.svg
│ │ │ ├── mixpanel.svg
│ │ │ ├── mlab.svg
│ │ │ ├── mobx.svg
│ │ │ ├── mocha.svg
│ │ │ ├── mockflow.svg
│ │ │ ├── modernizr.svg
│ │ │ ├── modx-icon.svg
│ │ │ ├── modx.svg
│ │ │ ├── moltin-icon.svg
│ │ │ ├── moltin.svg
│ │ │ ├── momentjs.svg
│ │ │ ├── monday-icon.svg
│ │ │ ├── monday.svg
│ │ │ ├── monero.svg
│ │ │ ├── mongodb.svg
│ │ │ ├── mono.svg
│ │ │ ├── moon.svg
│ │ │ ├── mootools.svg
│ │ │ ├── morpheus-icon.svg
│ │ │ ├── morpheus.svg
│ │ │ ├── mozilla.svg
│ │ │ ├── mparticle-icon.svg
│ │ │ ├── mparticle.svg
│ │ │ ├── mysql.svg
│ │ │ ├── namecheap.svg
│ │ │ ├── nanonets.svg
│ │ │ ├── nativescript.svg
│ │ │ ├── neat.svg
│ │ │ ├── neo4j.svg
│ │ │ ├── neovim.svg
│ │ │ ├── nestjs.svg
│ │ │ ├── netbeans.svg
│ │ │ ├── netflix-icon.svg
│ │ │ ├── netflix.svg
│ │ │ ├── netlify.svg
│ │ │ ├── new-relic.svg
│ │ │ ├── nextjs-icon.svg
│ │ │ ├── nextjs.svg
│ │ │ ├── nginx.svg
│ │ │ ├── nightwatch.svg
│ │ │ ├── nodal.svg
│ │ │ ├── node-sass.svg
│ │ │ ├── nodebots.svg
│ │ │ ├── nodejs-icon.svg
│ │ │ ├── nodejs.svg
│ │ │ ├── nodemon.svg
│ │ │ ├── nodeos.svg
│ │ │ ├── nodewebkit.svg
│ │ │ ├── nomad.svg
│ │ │ ├── noysi.svg
│ │ │ ├── npm-icon.svg
│ │ │ ├── npm.svg
│ │ │ ├── nuclide.svg
│ │ │ ├── numpy.svg
│ │ │ ├── nuxt-icon.svg
│ │ │ ├── nuxt.svg
│ │ │ ├── oauth.svg
│ │ │ ├── ocaml.svg
│ │ │ ├── octodns.svg
│ │ │ ├── octopus-deploy.svg
│ │ │ ├── olark.svg
│ │ │ ├── onesignal.svg
│ │ │ ├── open-graph.svg
│ │ │ ├── opencart.svg
│ │ │ ├── opencollective.svg
│ │ │ ├── opencv.svg
│ │ │ ├── opengl.svg
│ │ │ ├── openlayers.svg
│ │ │ ├── openshift.svg
│ │ │ ├── opensource.svg
│ │ │ ├── openstack-icon.svg
│ │ │ ├── openstack.svg
│ │ │ ├── opera.svg
│ │ │ ├── opsgenie.svg
│ │ │ ├── optimizely.svg
│ │ │ ├── oracle.svg
│ │ │ ├── oreilly.svg
│ │ │ ├── origami.svg
│ │ │ ├── origin.svg
│ │ │ ├── oshw.svg
│ │ │ ├── osquery.svg
│ │ │ ├── packer.svg
│ │ │ ├── pagekit.svg
│ │ │ ├── pagekite.svg
│ │ │ ├── panda.svg
│ │ │ ├── parse.svg
│ │ │ ├── parsehub.svg
│ │ │ ├── passbolt-icon.svg
│ │ │ ├── passbolt.svg
│ │ │ ├── passport.svg
│ │ │ ├── patreon.svg
│ │ │ ├── paypal.svg
│ │ │ ├── peer5.svg
│ │ │ ├── pepperoni.svg
│ │ │ ├── percona.svg
│ │ │ ├── percy-icon.svg
│ │ │ ├── percy.svg
│ │ │ ├── perf-rocks.svg
│ │ │ ├── perl.svg
│ │ │ ├── phalcon.svg
│ │ │ ├── phoenix.svg
│ │ │ ├── phonegap-bot.svg
│ │ │ ├── phonegap.svg
│ │ │ ├── php-alt.svg
│ │ │ ├── php.svg
│ │ │ ├── phpstorm.svg
│ │ │ ├── pinterest.svg
│ │ │ ├── pipedrive.svg
│ │ │ ├── pipefy.svg
│ │ │ ├── pivotal_tracker.svg
│ │ │ ├── pixijs.svg
│ │ │ ├── pkg.svg
│ │ │ ├── planless-icon.svg
│ │ │ ├── planless.svg
│ │ │ ├── plastic-scm.svg
│ │ │ ├── platformio.svg
│ │ │ ├── play.svg
│ │ │ ├── pm2.svg
│ │ │ ├── poeditor.svg
│ │ │ ├── polymer.svg
│ │ │ ├── postcss.svg
│ │ │ ├── postgraphile.svg
│ │ │ ├── postgresql.svg
│ │ │ ├── postman.svg
│ │ │ ├── pouchdb.svg
│ │ │ ├── preact.svg
│ │ │ ├── precursor.svg
│ │ │ ├── prestashop.svg
│ │ │ ├── presto.svg
│ │ │ ├── prettier.svg
│ │ │ ├── prisma.svg
│ │ │ ├── prismic-icon.svg
│ │ │ ├── prismic.svg
│ │ │ ├── processwire.svg
│ │ │ ├── productboard-icon.svg
│ │ │ ├── productboard.svg
│ │ │ ├── producthunt.svg
│ │ │ ├── progress.svg
│ │ │ ├── prometheus.svg
│ │ │ ├── promises.svg
│ │ │ ├── proofy.svg
│ │ │ ├── prospect.svg
│ │ │ ├── protactor.svg
│ │ │ ├── protoio.svg
│ │ │ ├── protonet.svg
│ │ │ ├── prott.svg
│ │ │ ├── pug.svg
│ │ │ ├── pumpkindb.svg
│ │ │ ├── puppet-icon.svg
│ │ │ ├── puppet.svg
│ │ │ ├── puppeteer.svg
│ │ │ ├── puppy-linux.svg
│ │ │ ├── purescript-icon.svg
│ │ │ ├── purescript.svg
│ │ │ ├── pushbullet.svg
│ │ │ ├── pusher-icon.svg
│ │ │ ├── pusher.svg
│ │ │ ├── pycharm.svg
│ │ │ ├── python.svg
│ │ │ ├── pytorch.svg
│ │ │ ├── pyup.svg
│ │ │ ├── q.svg
│ │ │ ├── qlik.svg
│ │ │ ├── qt.svg
│ │ │ ├── quarkus-icon.svg
│ │ │ ├── quarkus.svg
│ │ │ ├── quay.svg
│ │ │ ├── quobyte.svg
│ │ │ ├── quora.svg
│ │ │ ├── r-lang.svg
│ │ │ ├── rabbitmq-icon.svg
│ │ │ ├── rabbitmq.svg
│ │ │ ├── rackspace.svg
│ │ │ ├── rails.svg
│ │ │ ├── ramda.svg
│ │ │ ├── raml.svg
│ │ │ ├── rancher-icon.svg
│ │ │ ├── rancher.svg
│ │ │ ├── raphael.svg
│ │ │ ├── raspberry-pi.svg
│ │ │ ├── rax.svg
│ │ │ ├── react-router.svg
│ │ │ ├── react-spring.svg
│ │ │ ├── react-styleguidist.svg
│ │ │ ├── react.svg
│ │ │ ├── reactivex.svg
│ │ │ ├── realm.svg
│ │ │ ├── reapp.svg
│ │ │ ├── reasonml-icon.svg
│ │ │ ├── reasonml.svg
│ │ │ ├── reddit-icon.svg
│ │ │ ├── reddit.svg
│ │ │ ├── redhat-icon.svg
│ │ │ ├── redhat.svg
│ │ │ ├── redis.svg
│ │ │ ├── redsmin.svg
│ │ │ ├── redux-observable.svg
│ │ │ ├── redux-saga.svg
│ │ │ ├── redux.svg
│ │ │ ├── reindex.svg
│ │ │ ├── relay.svg
│ │ │ ├── require.svg
│ │ │ ├── rest-li.svg
│ │ │ ├── rethinkdb.svg
│ │ │ ├── riak.svg
│ │ │ ├── riot.svg
│ │ │ ├── rocket-chat-icon.svg
│ │ │ ├── rocket-chat.svg
│ │ │ ├── rocksdb.svg
│ │ │ ├── rollbar-icon.svg
│ │ │ ├── rollbar.svg
│ │ │ ├── rollupjs.svg
│ │ │ ├── rsa.svg
│ │ │ ├── rsmq.svg
│ │ │ ├── rubocop.svg
│ │ │ ├── ruby.svg
│ │ │ ├── rubygems.svg
│ │ │ ├── rubymine.svg
│ │ │ ├── rum.svg
│ │ │ ├── runscope.svg
│ │ │ ├── rust.svg
│ │ │ ├── rxdb.svg
│ │ │ ├── safari.svg
│ │ │ ├── sagui.svg
│ │ │ ├── sails.svg
│ │ │ ├── salesforce.svg
│ │ │ ├── sameroom.svg
│ │ │ ├── sanity.svg
│ │ │ ├── sass-doc.svg
│ │ │ ├── sass.svg
│ │ │ ├── saucelabs.svg
│ │ │ ├── scala.svg
│ │ │ ├── scaledrone.svg
│ │ │ ├── scribd-icon.svg
│ │ │ ├── scribd.svg
│ │ │ ├── section-icon.svg
│ │ │ ├── section.svg
│ │ │ ├── segment-icon.svg
│ │ │ ├── segment.svg
│ │ │ ├── selenium.svg
│ │ │ ├── semantic-ui.svg
│ │ │ ├── semantic-web.svg
│ │ │ ├── semaphoreci.svg
│ │ │ ├── sencha.svg
│ │ │ ├── sendgrid-icon.svg
│ │ │ ├── sendgrid.svg
│ │ │ ├── seneca.svg
│ │ │ ├── sensu-icon.svg
│ │ │ ├── sensu.svg
│ │ │ ├── sentry-icon.svg
│ │ │ ├── sentry.svg
│ │ │ ├── sequelize.svg
│ │ │ ├── serverless.svg
│ │ │ ├── sherlock-icon.svg
│ │ │ ├── sherlock.svg
│ │ │ ├── shields.svg
│ │ │ ├── shipit.svg
│ │ │ ├── shippable.svg
│ │ │ ├── shogun.svg
│ │ │ ├── shopify.svg
│ │ │ ├── sidekick.svg
│ │ │ ├── sidekiq-icon.svg
│ │ │ ├── sidekiq.svg
│ │ │ ├── signal.svg
│ │ │ ├── sinatra.svg
│ │ │ ├── sitepoint.svg
│ │ │ ├── skaffolder.svg
│ │ │ ├── sketch.svg
│ │ │ ├── skylight.svg
│ │ │ ├── skype.svg
│ │ │ ├── slack-icon.svg
│ │ │ ├── slack.svg
│ │ │ ├── slides.svg
│ │ │ ├── slim.svg
│ │ │ ├── smartling.svg
│ │ │ ├── smashingmagazine.svg
│ │ │ ├── snap-svg.svg
│ │ │ ├── snupps.svg
│ │ │ ├── snyk.svg
│ │ │ ├── socket.io.svg
│ │ │ ├── solarwinds.svg
│ │ │ ├── solid.svg
│ │ │ ├── solr.svg
│ │ │ ├── sonarqube.svg
│ │ │ ├── soundcloud.svg
│ │ │ ├── sourcegraph.svg
│ │ │ ├── sourcetrail.svg
│ │ │ ├── sourcetree.svg
│ │ │ ├── spark.svg
│ │ │ ├── sparkcentral.svg
│ │ │ ├── sparkpost.svg
│ │ │ ├── speakerdeck.svg
│ │ │ ├── speedcurve.svg
│ │ │ ├── spinnaker.svg
│ │ │ ├── splunk.svg
│ │ │ ├── spree.svg
│ │ │ ├── spring-icon.svg
│ │ │ ├── spring.svg
│ │ │ ├── sqlite.svg
│ │ │ ├── square.svg
│ │ │ ├── squarespace.svg
│ │ │ ├── stackbit-icon.svg
│ │ │ ├── stackbit.svg
│ │ │ ├── stackoverflow-icon.svg
│ │ │ ├── stackoverflow.svg
│ │ │ ├── stackshare.svg
│ │ │ ├── statuspage.svg
│ │ │ ├── steam.svg
│ │ │ ├── stetho.svg
│ │ │ ├── stickermule.svg
│ │ │ ├── stimulus.svg
│ │ │ ├── stitch.svg
│ │ │ ├── stoplight.svg
│ │ │ ├── storybook-icon.svg
│ │ │ ├── storybook.svg
│ │ │ ├── strapi-icon.svg
│ │ │ ├── strapi.svg
│ │ │ ├── strider.svg
│ │ │ ├── stripe.svg
│ │ │ ├── struts.svg
│ │ │ ├── styleci.svg
│ │ │ ├── stylefmt.svg
│ │ │ ├── stylelint.svg
│ │ │ ├── stylus.svg
│ │ │ ├── sublimetext-icon.svg
│ │ │ ├── sublimetext.svg
│ │ │ ├── subversion.svg
│ │ │ ├── sugarss.svg
│ │ │ ├── surge.svg
│ │ │ ├── survicate.svg
│ │ │ ├── suse.svg
│ │ │ ├── susy.svg
│ │ │ ├── svelte-icon.svg
│ │ │ ├── svelte.svg
│ │ │ ├── svg.svg
│ │ │ ├── svgator.svg
│ │ │ ├── swagger.svg
│ │ │ ├── swift.svg
│ │ │ ├── swiftype.svg
│ │ │ ├── symfony.svg
│ │ │ ├── sysdig-icon.svg
│ │ │ ├── sysdig.svg
│ │ │ ├── t3.svg
│ │ │ ├── tableau-icon.svg
│ │ │ ├── tableau.svg
│ │ │ ├── taiga.svg
│ │ │ ├── tailwindcss-icon.svg
│ │ │ ├── tailwindcss.svg
│ │ │ ├── tapcart-icon.svg
│ │ │ ├── tapcart.svg
│ │ │ ├── targetprocess.svg
│ │ │ ├── taskade-icon.svg
│ │ │ ├── taskade.svg
│ │ │ ├── tastejs.svg
│ │ │ ├── tealium.svg
│ │ │ ├── teamgrid.svg
│ │ │ ├── teamwork-icon.svg
│ │ │ ├── teamwork.svg
│ │ │ ├── telegram.svg
│ │ │ ├── tensorflow.svg
│ │ │ ├── terminal.svg
│ │ │ ├── terraform-icon.svg
│ │ │ ├── terraform.svg
│ │ │ ├── testlodge.svg
│ │ │ ├── tnw.svg
│ │ │ ├── todoist-icon.svg
│ │ │ ├── todoist.svg
│ │ │ ├── todomvc.svg
│ │ │ ├── tomcat.svg
│ │ │ ├── tor.svg
│ │ │ ├── torus.svg
│ │ │ ├── traackr.svg
│ │ │ ├── trac.svg
│ │ │ ├── travis-ci-monochrome.svg
│ │ │ ├── travis-ci.svg
│ │ │ ├── treasuredata-icon.svg
│ │ │ ├── treasuredata.svg
│ │ │ ├── treehouse.svg
│ │ │ ├── trello.svg
│ │ │ ├── tsu.svg
│ │ │ ├── tsuru.svg
│ │ │ ├── tumblr-icon.svg
│ │ │ ├── tumblr.svg
│ │ │ ├── tunein.svg
│ │ │ ├── turret.svg
│ │ │ ├── twilio-icon.svg
│ │ │ ├── twilio.svg
│ │ │ ├── twitch.svg
│ │ │ ├── twitter.svg
│ │ │ ├── typeform-icon.svg
│ │ │ ├── typeform.svg
│ │ │ ├── typescript-icon.svg
│ │ │ ├── typescript.svg
│ │ │ ├── typo3-icon.svg
│ │ │ ├── typo3.svg
│ │ │ ├── ubuntu.svg
│ │ │ ├── udacity.svg
│ │ │ ├── udemy-icon.svg
│ │ │ ├── udemy.svg
│ │ │ ├── uikit.svg
│ │ │ ├── umu.svg
│ │ │ ├── unbounce-icon.svg
│ │ │ ├── unbounce.svg
│ │ │ ├── undertow.svg
│ │ │ ├── unionpay.svg
│ │ │ ├── unitjs.svg
│ │ │ ├── unito-icon.svg
│ │ │ ├── unito.svg
│ │ │ ├── unity.svg
│ │ │ ├── upcase.svg
│ │ │ ├── upwork.svg
│ │ │ ├── user-testing-icon.svg
│ │ │ ├── user-testing.svg
│ │ │ ├── uservoice-icon.svg
│ │ │ ├── uservoice.svg
│ │ │ ├── uwsgi.svg
│ │ │ ├── v8-ignition.svg
│ │ │ ├── v8-turbofan.svg
│ │ │ ├── v8.svg
│ │ │ ├── vaadin.svg
│ │ │ ├── vaddy.svg
│ │ │ ├── vagrant-icon.svg
│ │ │ ├── vagrant.svg
│ │ │ ├── vault-icon.svg
│ │ │ ├── vault.svg
│ │ │ ├── vector-timber.svg
│ │ │ ├── vercel-icon.svg
│ │ │ ├── vercel.svg
│ │ │ ├── vernemq.svg
│ │ │ ├── vim.svg
│ │ │ ├── vimeo-icon.svg
│ │ │ ├── vimeo.svg
│ │ │ ├── visa.svg
│ │ │ ├── visaelectron.svg
│ │ │ ├── visual-studio-code.svg
│ │ │ ├── visual-studio.svg
│ │ │ ├── vivaldi-icon.svg
│ │ │ ├── vivaldi.svg
│ │ │ ├── vlang.svg
│ │ │ ├── void.svg
│ │ │ ├── vue.svg
│ │ │ ├── vuetifyjs.svg
│ │ │ ├── vulkan.svg
│ │ │ ├── vultr.svg
│ │ │ ├── vwo.svg
│ │ │ ├── w3c.svg
│ │ │ ├── waffle.svg
│ │ │ ├── wagtail.svg
│ │ │ ├── wakatime.svg
│ │ │ ├── watchman.svg
│ │ │ ├── wearos.svg
│ │ │ ├── weave.svg
│ │ │ ├── web-fundamentals.svg
│ │ │ ├── web.dev-icon.svg
│ │ │ ├── web.dev.svg
│ │ │ ├── webassembly.svg
│ │ │ ├── webcomponents.svg
│ │ │ ├── webflow.svg
│ │ │ ├── webhint-icon.svg
│ │ │ ├── webhint.svg
│ │ │ ├── webhooks.svg
│ │ │ ├── webix-icon.svg
│ │ │ ├── webix.svg
│ │ │ ├── webmin.svg
│ │ │ ├── webpack.svg
│ │ │ ├── webplatform.svg
│ │ │ ├── webrtc.svg
│ │ │ ├── websocket.svg
│ │ │ ├── webstorm.svg
│ │ │ ├── webtask.svg
│ │ │ ├── webtorrent.svg
│ │ │ ├── weebly.svg
│ │ │ ├── whalar.svg
│ │ │ ├── whatsapp.svg
│ │ │ ├── whatwg.svg
│ │ │ ├── wicket-icon.svg
│ │ │ ├── wicket.svg
│ │ │ ├── wifi.svg
│ │ │ ├── wildfly.svg
│ │ │ ├── wire.svg
│ │ │ ├── wix.svg
│ │ │ ├── woocommerce-icon.svg
│ │ │ ├── woocommerce.svg
│ │ │ ├── woopra.svg
│ │ │ ├── wordpress-icon.svg
│ │ │ ├── wordpress.svg
│ │ │ ├── workboard.svg
│ │ │ ├── wpengine.svg
│ │ │ ├── wufoo.svg
│ │ │ ├── xamarin.svg
│ │ │ ├── xampp.svg
│ │ │ ├── xcart.svg
│ │ │ ├── xero.svg
│ │ │ ├── xplenty.svg
│ │ │ ├── xstate.svg
│ │ │ ├── xtend.svg
│ │ │ ├── xwiki-icon.svg
│ │ │ ├── xwiki.svg
│ │ │ ├── yahoo.svg
│ │ │ ├── yammer.svg
│ │ │ ├── yandex-ru.svg
│ │ │ ├── yarn.svg
│ │ │ ├── ycombinator.svg
│ │ │ ├── yeoman.svg
│ │ │ ├── yii.svg
│ │ │ ├── youtrack.svg
│ │ │ ├── youtube-icon.svg
│ │ │ ├── youtube.svg
│ │ │ ├── zapier-icon.svg
│ │ │ ├── zapier.svg
│ │ │ ├── zend-framework.svg
│ │ │ ├── zendesk-icon.svg
│ │ │ ├── zendesk.svg
│ │ │ ├── zenhub-icon.svg
│ │ │ ├── zenhub.svg
│ │ │ ├── zeplin.svg
│ │ │ ├── zoho.svg
│ │ │ ├── zorin-os.svg
│ │ │ ├── zube.svg
│ │ │ ├── zulip-icon.svg
│ │ │ ├── zulip.svg
│ │ │ └── zwave.svg
│ │ ├── robots.txt
│ │ └── vercel.svg
│ ├── scripts
│ │ └── disable-preview.sh
│ ├── src
│ │ ├── apollo
│ │ │ ├── cache.ts
│ │ │ ├── client.ts
│ │ │ ├── constants.ts
│ │ │ ├── links
│ │ │ │ ├── auth.link.ts
│ │ │ │ ├── http.link.ts
│ │ │ │ └── refresh.link.ts
│ │ │ ├── type-policies.ts
│ │ │ ├── use-apollo.ts
│ │ │ └── utils
│ │ │ │ └── with-auth-ssr.ts
│ │ ├── components
│ │ │ ├── develofolio-image.tsx
│ │ │ ├── ga-input.tsx
│ │ │ ├── icon.tsx
│ │ │ ├── image-uploader.tsx
│ │ │ ├── language-combobox.tsx
│ │ │ ├── link-input.tsx
│ │ │ ├── pimary-button.tsx
│ │ │ ├── portal.tsx
│ │ │ ├── primary-input.tsx
│ │ │ ├── title-input.tsx
│ │ │ └── top-progress-bar.tsx
│ │ ├── graphql
│ │ │ └── schema
│ │ │ │ ├── auth.graphql
│ │ │ │ ├── file.graphql
│ │ │ │ ├── page.graphql
│ │ │ │ ├── school.graphql
│ │ │ │ └── user.graphql
│ │ ├── hooks
│ │ │ ├── use-constant.ts
│ │ │ ├── use-debounce-effect.ts
│ │ │ ├── use-debounce-state.ts
│ │ │ ├── use-file-load.ts
│ │ │ ├── use-hover.ts
│ │ │ ├── use-modal.ts
│ │ │ ├── use-overrides.ts
│ │ │ ├── use-router-loading.ts
│ │ │ └── use-scroll-trigger-page.ts
│ │ ├── layouts
│ │ │ ├── basic-layout.tsx
│ │ │ └── editor-layout.tsx
│ │ ├── modules
│ │ │ ├── editor
│ │ │ │ ├── banner
│ │ │ │ │ ├── banner-bio.tsx
│ │ │ │ │ ├── banner-name.tsx
│ │ │ │ │ ├── banner-tagline.tsx
│ │ │ │ │ ├── banner.tsx
│ │ │ │ │ └── with-banner.ts
│ │ │ │ ├── blocks
│ │ │ │ │ ├── block-picker.tsx
│ │ │ │ │ └── use-blocks.ts
│ │ │ │ ├── career-list
│ │ │ │ │ ├── career-list-item-description.tsx
│ │ │ │ │ ├── career-list-item-name.tsx
│ │ │ │ │ ├── career-list-item-period.tsx
│ │ │ │ │ ├── career-list-item-position.tsx
│ │ │ │ │ ├── career-list-item.tsx
│ │ │ │ │ ├── career-list.tsx
│ │ │ │ │ └── with-carrer-list.ts
│ │ │ │ ├── custom-element.tsx
│ │ │ │ ├── custom-types.ts
│ │ │ │ ├── dnd
│ │ │ │ │ ├── hooks
│ │ │ │ │ │ ├── use-dnd-block.ts
│ │ │ │ │ │ ├── use-drag-block.ts
│ │ │ │ │ │ └── use-drop-block-on-editor.ts
│ │ │ │ │ ├── root-draggable.tsx
│ │ │ │ │ ├── types.ts
│ │ │ │ │ └── utils
│ │ │ │ │ │ ├── get-hover-direction.ts
│ │ │ │ │ │ └── get-new-direction.ts
│ │ │ │ ├── editor-sidebar.tsx
│ │ │ │ ├── editor.atoms.ts
│ │ │ │ ├── elements
│ │ │ │ │ ├── blockquote.tsx
│ │ │ │ │ ├── bulleted-list.tsx
│ │ │ │ │ ├── heading.tsx
│ │ │ │ │ ├── list-item.tsx
│ │ │ │ │ └── paragraph.tsx
│ │ │ │ ├── leaf
│ │ │ │ │ ├── custom-leaf.tsx
│ │ │ │ │ ├── toolbar-buttons
│ │ │ │ │ │ ├── font-color-button.tsx
│ │ │ │ │ │ ├── form-button.tsx
│ │ │ │ │ │ ├── highlight-button.tsx
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ ├── link-button.tsx
│ │ │ │ │ │ └── toolbar-button.tsx
│ │ │ │ │ ├── toolbar.tsx
│ │ │ │ │ └── utils.ts
│ │ │ │ ├── logo
│ │ │ │ │ ├── inline-logo-picker.tsx
│ │ │ │ │ ├── insert-logo.tsx
│ │ │ │ │ ├── logo-index.ts
│ │ │ │ │ ├── logo-picker-results.tsx
│ │ │ │ │ ├── logo.tsx
│ │ │ │ │ ├── logos.json
│ │ │ │ │ ├── popover-logo-picker.tsx
│ │ │ │ │ ├── types.ts
│ │ │ │ │ ├── use-logo-picker.ts
│ │ │ │ │ └── with-logo.ts
│ │ │ │ ├── page-editor.tsx
│ │ │ │ ├── placeholder
│ │ │ │ │ └── placeholder.tsx
│ │ │ │ ├── project-list
│ │ │ │ │ ├── project-list-item-description.tsx
│ │ │ │ │ ├── project-list-item-name.tsx
│ │ │ │ │ ├── project-list-item.tsx
│ │ │ │ │ ├── project-list.tsx
│ │ │ │ │ └── with-project-list.ts
│ │ │ │ ├── school-list
│ │ │ │ │ ├── school-list-item-major.tsx
│ │ │ │ │ ├── school-list-item-name.tsx
│ │ │ │ │ ├── school-list-item-period.tsx
│ │ │ │ │ ├── school-list-item.tsx
│ │ │ │ │ ├── school-list.tsx
│ │ │ │ │ ├── school-picker.tsx
│ │ │ │ │ ├── use-school-picker.ts
│ │ │ │ │ └── with-school-list.ts
│ │ │ │ ├── serialize.tsx
│ │ │ │ ├── shortcuts
│ │ │ │ │ └── with-shortcuts.ts
│ │ │ │ ├── skill-list
│ │ │ │ │ ├── skill-list-item-description.tsx
│ │ │ │ │ ├── skill-list-item-logos.tsx
│ │ │ │ │ ├── skill-list-item-name.tsx
│ │ │ │ │ ├── skill-list-item.tsx
│ │ │ │ │ ├── skill-list.tsx
│ │ │ │ │ └── with-skill-list.ts
│ │ │ │ ├── social-link
│ │ │ │ │ └── edit-link-popover.tsx
│ │ │ │ ├── utils
│ │ │ │ │ ├── find-node.ts
│ │ │ │ │ ├── generate-initial-content.ts
│ │ │ │ │ ├── is-expanded.ts
│ │ │ │ │ ├── is-mod-key.ts
│ │ │ │ │ ├── match.ts
│ │ │ │ │ └── some-node.ts
│ │ │ │ └── with-editor.ts
│ │ │ └── user
│ │ │ │ ├── components
│ │ │ │ └── delete-account-modal.tsx
│ │ │ │ └── hooks
│ │ │ │ ├── use-logout.ts
│ │ │ │ └── use-user.ts
│ │ ├── pages
│ │ │ ├── [slug].tsx
│ │ │ ├── _app.tsx
│ │ │ ├── _document.tsx
│ │ │ ├── actual-index.tsx
│ │ │ ├── docs
│ │ │ │ ├── privacy.tsx
│ │ │ │ └── terms.tsx
│ │ │ ├── edit
│ │ │ │ ├── index.tsx
│ │ │ │ └── settings.tsx
│ │ │ ├── login
│ │ │ │ └── index.tsx
│ │ │ └── sitemap.xml.tsx
│ │ ├── routes.ts
│ │ ├── styles
│ │ │ ├── get-scrollbar-width.ts
│ │ │ ├── global-styles.css
│ │ │ ├── hex-to-rgb.ts
│ │ │ ├── responsive.ts
│ │ │ ├── styles.ts
│ │ │ ├── styletron.ts
│ │ │ ├── theme.ts
│ │ │ └── z-indexes.ts
│ │ └── utils
│ │ │ ├── access-token.ts
│ │ │ ├── constants.ts
│ │ │ ├── generate-path.ts
│ │ │ ├── is-server.ts
│ │ │ ├── match-error-code.ts
│ │ │ └── storage.ts
│ └── tsconfig.json
├── image-resizer
│ ├── package.json
│ ├── serverless.ts
│ ├── src
│ │ └── handler.ts
│ └── tsconfig.json
├── og-image
│ ├── .env.example
│ ├── .gitignore
│ ├── package.json
│ ├── serverless.ts
│ ├── src
│ │ ├── functions
│ │ │ ├── index.ts
│ │ │ └── main
│ │ │ │ ├── handler.ts
│ │ │ │ └── index.ts
│ │ └── libs
│ │ │ ├── chromium.ts
│ │ │ ├── deep-filter.ts
│ │ │ ├── handler-path.ts
│ │ │ ├── options.ts
│ │ │ ├── parser.ts
│ │ │ └── template.ts
│ ├── tsconfig.json
│ ├── tsconfig.paths.json
│ └── webpack.config.js
└── server
│ ├── .env.example
│ ├── .eslintrc.js
│ ├── .gitignore
│ ├── env.d.ts
│ ├── nest-cli.json
│ ├── package.json
│ ├── schema.graphql
│ ├── serverless.yml
│ ├── src
│ ├── app.module.ts
│ ├── common
│ │ ├── pagination
│ │ │ ├── cursor-paginate.ts
│ │ │ ├── offset-paginate.ts
│ │ │ ├── page-info.ts
│ │ │ ├── paginated.ts
│ │ │ └── pagination.args.ts
│ │ └── types
│ │ │ └── express.d.ts
│ ├── config
│ │ ├── aws.config.ts
│ │ ├── base.config.ts
│ │ ├── env.d.ts
│ │ ├── facebook.config.ts
│ │ ├── github.config.ts
│ │ ├── google.config.ts
│ │ ├── jwt.config.ts
│ │ └── ormconfig.ts
│ ├── main.ts
│ ├── migrations
│ │ ├── 1623476692069-CreateUsersTable.ts
│ │ ├── 1623480513569-RenameUserImageField.ts
│ │ ├── 1623480715807-AddUserNameField.ts
│ │ ├── 1623485545430-RemoveLegacyUserField.ts
│ │ ├── 1623502457042-CreatePagesTable.ts
│ │ ├── 1623557539235-FixPageSlugPrimaryToUnique.ts
│ │ ├── 1623559498055-CreateSocialLinkTable.ts
│ │ ├── 1625971802693-RemoveUserAccessTokenField.ts
│ │ ├── 1625973327384-UserEmialNullable.ts
│ │ ├── 1625980683676-UserPageOneToOne.ts
│ │ ├── 1626443016900-UserPageOnDeleteSetNull.ts
│ │ ├── 1626444510117-CreateFileTable.ts
│ │ ├── 1626512470665-RemoveSocialLinkTable.ts
│ │ ├── 1628605679514-ChangeStructure.ts
│ │ ├── 1629289882551-CreateSchoolsTable.ts
│ │ ├── 1629982011935-AddPageColumns.ts
│ │ ├── 1631883784435-AddFacebookToProvider.ts
│ │ ├── 1631945557973-AddGoogleToProvider.ts
│ │ └── 1636003916276-AddLanguageType.ts
│ ├── modules
│ │ ├── auth
│ │ │ ├── auth.module.ts
│ │ │ ├── auth.resolver.ts
│ │ │ ├── facebook
│ │ │ │ ├── facebook-oauth.controller.ts
│ │ │ │ ├── facebook-oauth.guard.ts
│ │ │ │ ├── facebook-oauth.module.ts
│ │ │ │ └── facebook-oauth.strategy.ts
│ │ │ ├── github
│ │ │ │ ├── github-oauth.controller.ts
│ │ │ │ ├── github-oauth.guard.ts
│ │ │ │ ├── github-oauth.module.ts
│ │ │ │ └── github-oauth.strategy.ts
│ │ │ ├── google
│ │ │ │ ├── google-oauth.controller.ts
│ │ │ │ ├── google-oauth.guard.ts
│ │ │ │ ├── google-oauth.module.ts
│ │ │ │ └── google-oauth.strategy.ts
│ │ │ ├── graphql
│ │ │ │ ├── gql-auth.decorator.ts
│ │ │ │ └── gql-auth.guard.ts
│ │ │ └── jwt
│ │ │ │ ├── jwt-access.guard.ts
│ │ │ │ ├── jwt-access.strategy.ts
│ │ │ │ ├── jwt-auth.controller.ts
│ │ │ │ ├── jwt-auth.module.ts
│ │ │ │ ├── jwt-auth.service.ts
│ │ │ │ ├── jwt-refresh.guard.ts
│ │ │ │ └── jwt-refresh.strategy.ts
│ │ ├── file
│ │ │ ├── dto
│ │ │ │ └── upload-url.dto.ts
│ │ │ ├── enum
│ │ │ │ └── upload-type.enum.ts
│ │ │ ├── file.entity.ts
│ │ │ ├── file.module.ts
│ │ │ ├── file.resolver.ts
│ │ │ └── file.service.ts
│ │ ├── health
│ │ │ ├── health.controller.ts
│ │ │ └── health.module.ts
│ │ ├── page
│ │ │ ├── enum
│ │ │ │ └── language-type.enum.ts
│ │ │ ├── input
│ │ │ │ └── update-page.input.ts
│ │ │ ├── page.entity.ts
│ │ │ ├── page.module.ts
│ │ │ ├── page.resolver.ts
│ │ │ └── page.service.ts
│ │ ├── school
│ │ │ ├── dto
│ │ │ │ └── cursor-paginated-school.dto.ts
│ │ │ ├── input
│ │ │ │ └── school-filter.input.ts
│ │ │ ├── school.entity.ts
│ │ │ ├── school.module.ts
│ │ │ ├── school.resolver.ts
│ │ │ └── school.service.ts
│ │ └── user
│ │ │ ├── decorator
│ │ │ └── current-user.decorator.ts
│ │ │ ├── enum
│ │ │ └── provider-type.enum.ts
│ │ │ ├── input
│ │ │ ├── create-user.input.ts
│ │ │ └── update-user.input.ts
│ │ │ ├── user.entity.ts
│ │ │ ├── user.module.ts
│ │ │ ├── user.resolver.ts
│ │ │ └── user.service.ts
│ ├── serverless.ts
│ └── types
│ │ └── express.d.ts
│ ├── test
│ ├── app.e2e-spec.ts
│ └── jest-e2e.json
│ ├── tsconfig.build.json
│ └── tsconfig.json
└── yarn.lock
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: 'v$RESOLVED_VERSION 🌈'
2 | tag-template: 'v$RESOLVED_VERSION'
3 | categories:
4 | - title: '🚀 Features'
5 | labels:
6 | - 'Feature'
7 | - 'Enhancement'
8 | - title: '🐛 Bug Fixes'
9 | labels:
10 | - 'Fix'
11 | - 'Bug'
12 | - title: '🧰 Maintenance'
13 | label: 'Chore'
14 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
15 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
16 | version-resolver:
17 | major:
18 | labels:
19 | - 'Version: Major'
20 | minor:
21 | labels:
22 | - 'Version: Minor'
23 | patch:
24 | labels:
25 | - 'Version: Patch'
26 | default: patch
27 | template: |
28 | ## What’s Changed
29 |
30 | $CHANGES
31 |
--------------------------------------------------------------------------------
/.github/workflows/merge-release.yml:
--------------------------------------------------------------------------------
1 | name: Merge main to release
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | environment: production
11 |
12 | steps:
13 | - name: Merge main -> release
14 | uses: devmasx/merge-branch@v1.3.1
15 | if: ${{ success() }}
16 | with:
17 | type: now
18 | from_branch: main
19 | target_branch: release
20 | github_token: ${{ secrets.PERSONLA_ACCESS_TOKEN }}
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | ormlogs.log
4 | yarn-error.log
5 | .serverless
6 | .DS_STORE
7 | dist
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .serverless
3 | .next
4 | .serverless_nextjs
5 | client/public
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": true,
6 | "useTabs": true,
7 | "endOfLine": "lf"
8 | }
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "eslint.workingDirectories": ["./packages/server", "./packages/client"]
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 개발자들을 위한 포트폴리오 에디터
6 |
7 | **디벨로폴리오**는 개발자 포트폴리오에 특화된 정적 페이지 에디터입니다.
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | # Usage
16 |
17 | https://velog.io/@chojonghoon/DeveloFolio-개발자들을-위한-포트폴리오-에디터
18 |
19 | # Contributing
20 |
21 | 디벨로폴리오에 기여하고싶으신가요? 아래 링크를 참고해주세요.
22 |
23 | [Contributing](CONTRIBUTING.md)
24 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "npmClient": "yarn",
3 | "useWorkspaces": true,
4 | "packages": ["packages/*"],
5 | "version": "0.0.0"
6 | }
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "develofolio",
3 | "private": true,
4 | "workspaces": {
5 | "packages": [
6 | "packages/*"
7 | ],
8 | "nohoist": [
9 | "**"
10 | ]
11 | },
12 | "devDependencies": {
13 | "lerna": "^4.0.0",
14 | "prettier": "^2.3.2"
15 | },
16 | "scripts": {
17 | "dev": "lerna run dev --parallel --stream --scope={client,server}",
18 | "lint": "lerna run lint --stream --scope={client,server}"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/client/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["next/babel"]
3 | }
4 |
--------------------------------------------------------------------------------
/packages/client/.env.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_CLIENT_HOST=http://localhost:3000
2 | NEXT_PUBLIC_SERVER_HOST=http://localhost:4000
3 | NEXT_PUBLIC_IMAGES_HOST=https://images-v3.develofolio.com
4 | NEXT_PUBLIC_OG_IMAGE_HOST=https://og-image-v2.develofolio.com
5 |
--------------------------------------------------------------------------------
/packages/client/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next", "next/core-web-vitals", "plugin:baseui/recommended"],
3 | "rules": {
4 | "react/display-name": "off",
5 | "baseui/deprecated-theme-api": "warn",
6 | "baseui/deprecated-component-api": "warn",
7 | "baseui/no-deep-imports": "warn"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
36 | .env
37 |
38 | # code-gen
39 | /src/graphql/*.generated.*
40 |
41 | .serverless_nextjs
--------------------------------------------------------------------------------
/packages/client/apollo.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | client: {
3 | service: {
4 | name: 'develofolio',
5 | localSchemaFile: './packages/server/schema.graphql',
6 | },
7 | excludes: ['**/*.generated.*'],
8 | },
9 | }
10 |
--------------------------------------------------------------------------------
/packages/client/codegen.yml:
--------------------------------------------------------------------------------
1 | overwrite: true
2 |
3 | schema: '../server/schema.graphql'
4 |
5 | documents: 'src/graphql/schema/*.graphql'
6 |
7 | generates:
8 | src/graphql/document.generated.ts:
9 | plugins:
10 | - typescript
11 | - typescript-operations
12 | - typed-document-node
13 | src/graphql/helpers.generated.ts:
14 | plugins:
15 | - typescript-apollo-client-helpers
16 | config:
17 | useTypeImports: true
18 | src/graphql/fragment-matcher.generated.json:
19 | plugins:
20 | - fragment-matcher
21 | config:
22 | useExplicitTyping: true
23 |
24 | hooks:
25 | afterAllFileWrite:
26 | - prettier --write "src/graphql/*.ts" "src/graphql/*.json"
27 |
--------------------------------------------------------------------------------
/packages/client/env.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace NodeJS {
2 | interface ProcessEnv {
3 | readonly NEXT_PUBLIC_CLIENT_HOST: string
4 | readonly NEXT_PUBLIC_SERVER_HOST: string
5 | readonly NEXT_PUBLIC_IMAGES_HOST: string
6 | readonly NEXT_PUBLIC_OG_IMAGE_HOST: string
7 | readonly NEXT_PUBLIC_GA_TRACKING_ID?: string
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/client/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 | ///
4 |
--------------------------------------------------------------------------------
/packages/client/patches/next+11.0.1.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/next/types/index.d.ts b/node_modules/next/types/index.d.ts
2 | index 476d491..2ac8968 100644
3 | --- a/node_modules/next/types/index.d.ts
4 | +++ b/node_modules/next/types/index.d.ts
5 | @@ -57,7 +57,9 @@ export type Redirect =
6 | /**
7 | * `Page` type, use it as a guide to create `pages`.
8 | */
9 | -export type NextPage = NextComponentType
10 | +export type NextPage = NextComponentType & {
11 | + getLayout?: (page: React.ReactNode) => React.ReactNode
12 | +}
13 |
14 | /**
15 | * `Config` type, use it for export const config
16 |
--------------------------------------------------------------------------------
/packages/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/favicon.ico
--------------------------------------------------------------------------------
/packages/client/public/favicons/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/favicons/android-chrome-192x192.png
--------------------------------------------------------------------------------
/packages/client/public/favicons/android-chrome-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/favicons/android-chrome-256x256.png
--------------------------------------------------------------------------------
/packages/client/public/favicons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/favicons/apple-touch-icon.png
--------------------------------------------------------------------------------
/packages/client/public/favicons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #ffffff
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/packages/client/public/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/packages/client/public/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/packages/client/public/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/favicons/favicon.ico
--------------------------------------------------------------------------------
/packages/client/public/favicons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/favicons/mstile-150x150.png
--------------------------------------------------------------------------------
/packages/client/public/favicons/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "DeveloFolio",
3 | "short_name": "DeveloFolio",
4 | "icons": [
5 | {
6 | "src": "/favicons/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/favicons/android-chrome-256x256.png",
12 | "sizes": "256x256",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/packages/client/public/fonts/SpoqaHanSansNeo-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/fonts/SpoqaHanSansNeo-Bold.woff2
--------------------------------------------------------------------------------
/packages/client/public/fonts/SpoqaHanSansNeo-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/fonts/SpoqaHanSansNeo-Light.woff2
--------------------------------------------------------------------------------
/packages/client/public/fonts/SpoqaHanSansNeo-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/fonts/SpoqaHanSansNeo-Medium.woff2
--------------------------------------------------------------------------------
/packages/client/public/fonts/SpoqaHanSansNeo-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/fonts/SpoqaHanSansNeo-Regular.woff2
--------------------------------------------------------------------------------
/packages/client/public/fonts/SpoqaHanSansNeo-Thin.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/fonts/SpoqaHanSansNeo-Thin.woff2
--------------------------------------------------------------------------------
/packages/client/public/icons/apple.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/bold.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/check.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/code.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/facebook.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/font-color.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/github-circle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/highlight.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/image.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/packages/client/public/icons/italic.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/linkedin-circle.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/packages/client/public/icons/pencil.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/playstore.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/icons/plus.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/stack-overflow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/icons/stackoverflow-circle.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/packages/client/public/icons/velog-circle.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/packages/client/public/icons/verified.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/packages/client/public/images/block-thumbnails/career-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/images/block-thumbnails/career-list.png
--------------------------------------------------------------------------------
/packages/client/public/images/block-thumbnails/project-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/images/block-thumbnails/project-list.png
--------------------------------------------------------------------------------
/packages/client/public/images/block-thumbnails/school-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/images/block-thumbnails/school-list.png
--------------------------------------------------------------------------------
/packages/client/public/images/block-thumbnails/skill-list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/images/block-thumbnails/skill-list.png
--------------------------------------------------------------------------------
/packages/client/public/images/chat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/images/chat.png
--------------------------------------------------------------------------------
/packages/client/public/images/example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/images/example.png
--------------------------------------------------------------------------------
/packages/client/public/images/logo-picker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/images/logo-picker.gif
--------------------------------------------------------------------------------
/packages/client/public/images/url.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChoJongHoon/develofolio/dc15df3f7c582ae9133e18ea63e9d833daf01373/packages/client/public/images/url.png
--------------------------------------------------------------------------------
/packages/client/public/logos/aerospike-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/akka.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/appdynamics.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/appium.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/autoprefixer.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/aws-glacier.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/aws-quicksight.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/aws-rds.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/aws-ses.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/bing.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/buildkite-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/packages/client/public/logos/bulma.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/campaignmonitor-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/cockpit.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/codebeat.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/coderwall.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/codesandbox.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/codrops.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/packages/client/public/logos/crateio.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/crystal.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/css-3_official.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/dropbox.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/packages/client/public/logos/dropmark.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/ello.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/elm.svg:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/packages/client/public/logos/embedly.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/emmet.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/packages/client/public/logos/ethereum.svg:
--------------------------------------------------------------------------------
1 |
2 |
12 |
--------------------------------------------------------------------------------
/packages/client/public/logos/evergreen-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/facebook.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/figma.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/framework7-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/fsharp.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/gitter.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/gleam.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/google-ads.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/packages/client/public/logos/google-keep.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/gravatar.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/hack.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/packages/client/public/logos/hashnode-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/haskell-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/packages/client/public/logos/haxl.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/hhvm.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/hibernate.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/packages/client/public/logos/highcharts.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/hosted-graphite.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/houndci.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/infer.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/jamstack-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/jhipster-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/kemal.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/packages/client/public/logos/kibana.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/packages/client/public/logos/kickstarter-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/kirby-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/leveldb.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/lighttpd.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/packages/client/public/logos/logstash.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/losant.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/mapbox-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/material-ui.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/packages/client/public/logos/microsoft-windows.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/mixmax.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/packages/client/public/logos/modernizr.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/modx-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/monero.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/mparticle-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/neat.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/packages/client/public/logos/nodal.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/packages/client/public/logos/nomad.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/npm-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/npm.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/packer.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/pagekit.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/pagekite.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/passport.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/packages/client/public/logos/patreon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/precursor.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/productboard-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/progress.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/puppet-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/rax.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/rubygems.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/sails.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/scaledrone.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/packages/client/public/logos/serverless.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/survicate.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/packages/client/public/logos/teamgrid.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/terraform-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/trello.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/tumblr-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/turret.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/twitch.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/typo3-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/uikit.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/vector-timber.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/vercel-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/vue.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/packages/client/public/logos/vuetifyjs.svg:
--------------------------------------------------------------------------------
1 |
2 |
10 |
--------------------------------------------------------------------------------
/packages/client/public/logos/web-fundamentals.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/webtask.svg:
--------------------------------------------------------------------------------
1 |
2 |
11 |
--------------------------------------------------------------------------------
/packages/client/public/logos/whalar.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/wicket-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/workboard.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/youtube-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/packages/client/public/logos/zendesk-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/zenhub-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/client/public/logos/zorin-os.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/logos/zube.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/packages/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Allow: /
3 | Sitemap: https://develofolio.com/sitemap.xml
4 |
--------------------------------------------------------------------------------
/packages/client/scripts/disable-preview.sh:
--------------------------------------------------------------------------------
1 | echo "VERCEL_GIT_COMMIT_REF: $VERCEL_GIT_COMMIT_REF"
2 |
3 | if [[ "$VERCEL_GIT_COMMIT_REF" == "release" ]] ; then
4 | # Proceed with the build
5 | echo "✅ - Build can proceed"
6 | exit 1;
7 |
8 | else
9 | # Don't build
10 | echo "🛑 - Build cancelled"
11 | exit 0;
12 | fi
--------------------------------------------------------------------------------
/packages/client/src/apollo/cache.ts:
--------------------------------------------------------------------------------
1 | import { InMemoryCache } from '@apollo/client'
2 | import { typePolicies } from './type-policies'
3 |
4 | export const cache = new InMemoryCache({
5 | typePolicies,
6 | })
7 |
--------------------------------------------------------------------------------
/packages/client/src/apollo/constants.ts:
--------------------------------------------------------------------------------
1 | export const INIT_STATE = '__APOLLO_INITIAL_STATE__'
2 | export const SERVER_ACCESS_TOKEN = '__APOLLO_SERVER_ACCESS_TOKEN__'
3 |
--------------------------------------------------------------------------------
/packages/client/src/apollo/links/auth.link.ts:
--------------------------------------------------------------------------------
1 | import { setContext } from '@apollo/client/link/context'
2 | import { getAccessToken } from '~/utils/access-token'
3 | import { isServer } from '~/utils/is-server'
4 |
5 | export const createAuthLink = (serverAccessToken?: string) =>
6 | setContext((_request, { headers }) => {
7 | const accessToken = isServer() ? serverAccessToken : getAccessToken()
8 |
9 | return {
10 | headers: {
11 | ...headers,
12 | authorization: accessToken ? `bearer ${accessToken}` : '',
13 | },
14 | }
15 | })
16 |
--------------------------------------------------------------------------------
/packages/client/src/apollo/links/http.link.ts:
--------------------------------------------------------------------------------
1 | import { HttpLink } from '@apollo/client'
2 |
3 | export const httpLink = new HttpLink({
4 | uri: `${process.env.NEXT_PUBLIC_SERVER_HOST}/graphql`,
5 | credentials: 'include',
6 | fetch,
7 | })
8 |
--------------------------------------------------------------------------------
/packages/client/src/apollo/type-policies.ts:
--------------------------------------------------------------------------------
1 | import { TypedTypePolicies } from 'src/graphql/helpers.generated'
2 |
3 | export const typePolicies: TypedTypePolicies = {}
4 |
--------------------------------------------------------------------------------
/packages/client/src/apollo/use-apollo.ts:
--------------------------------------------------------------------------------
1 | import { NormalizedCacheObject } from '@apollo/client'
2 | import { useMemo } from 'react'
3 | import { setAccessToken } from '~/utils/access-token'
4 | import { initApolloClient } from './client'
5 |
6 | export const useApollo = (
7 | initialState?: NormalizedCacheObject,
8 | accessToken?: string
9 | ) => {
10 | if (accessToken) {
11 | setAccessToken(accessToken)
12 | }
13 | const store = useMemo(
14 | () => initApolloClient(initialState, accessToken),
15 | [accessToken, initialState]
16 | )
17 | return store
18 | }
19 |
--------------------------------------------------------------------------------
/packages/client/src/components/develofolio-image.tsx:
--------------------------------------------------------------------------------
1 | import Image, { ImageLoader, ImageProps } from 'next/image'
2 |
3 | const loader: ImageLoader = ({ src, width, quality }) => {
4 | const url = new URL(`${process.env.NEXT_PUBLIC_IMAGES_HOST}/${src}`)
5 |
6 | url.searchParams.set('w', width.toString())
7 | if (quality) {
8 | url.searchParams.set('q', quality.toString())
9 | }
10 |
11 | return url.toString()
12 | }
13 |
14 | export const DevelofolioImage = ({ alt, ...props }: ImageProps) => {
15 | return
16 | }
17 |
--------------------------------------------------------------------------------
/packages/client/src/components/portal.tsx:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect, useState, FC } from 'react'
2 | import { createPortal } from 'react-dom'
3 |
4 | /**
5 | * Client only
6 | */
7 | export const Portal: FC = ({ children }) => {
8 | const ref = useRef(null)
9 | const [mounted, setMounted] = useState(false)
10 |
11 | useEffect(() => {
12 | ref.current = document.querySelector('#portal')
13 | setMounted(true)
14 | }, [])
15 |
16 | return mounted && ref.current ? createPortal(children, ref.current) : null
17 | }
18 |
--------------------------------------------------------------------------------
/packages/client/src/graphql/schema/auth.graphql:
--------------------------------------------------------------------------------
1 | mutation DeleteAccount {
2 | deleteAccount
3 | }
4 |
--------------------------------------------------------------------------------
/packages/client/src/graphql/schema/file.graphql:
--------------------------------------------------------------------------------
1 | fragment FileParts on File {
2 | id
3 | key
4 | }
5 |
6 | query GenerateUploadUrl($filename: String!, $type: UploadType!) {
7 | generateUploadPath(filename: $filename, type: $type) {
8 | key
9 | url
10 | }
11 | }
12 |
13 | mutation CreateFile($key: String!) {
14 | file: createFile(key: $key) {
15 | ...FileParts
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/client/src/graphql/schema/school.graphql:
--------------------------------------------------------------------------------
1 | query GetSchoolsByCursor(
2 | $filter: SchoolFilterInput
3 | $after: String
4 | $before: String
5 | $first: Int
6 | $last: Int
7 | ) {
8 | getSchoolsByCursor(
9 | filter: $filter
10 | after: $after
11 | before: $before
12 | first: $first
13 | last: $last
14 | ) {
15 | edges {
16 | node {
17 | id
18 | logo
19 | name
20 | }
21 | }
22 | pageInfo {
23 | endCursor
24 | hasNextPage
25 | countTotal
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/client/src/graphql/schema/user.graphql:
--------------------------------------------------------------------------------
1 | query Me {
2 | me {
3 | id
4 | name
5 | email
6 | avatar
7 | page {
8 | ...PageParts
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/client/src/hooks/use-constant.ts:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react'
2 |
3 | interface ResultBox {
4 | v: T
5 | }
6 |
7 | export function useConstant(fn: () => T): T {
8 | const ref = useRef>()
9 |
10 | if (!ref.current) {
11 | ref.current = { v: fn() }
12 | }
13 |
14 | return ref.current.v
15 | }
16 |
--------------------------------------------------------------------------------
/packages/client/src/hooks/use-debounce-effect.ts:
--------------------------------------------------------------------------------
1 | import { EffectCallback, DependencyList, useRef, useEffect } from 'react'
2 |
3 | export const useDebounceEffect = (
4 | effect: EffectCallback,
5 | delay: number,
6 | deps?: DependencyList
7 | ) => {
8 | const interval = useRef(null)
9 | useEffect(() => {
10 | if (interval.current !== null) {
11 | clearTimeout(interval.current)
12 | }
13 |
14 | interval.current = setTimeout(() => {
15 | effect()
16 | }, delay)
17 | // eslint-disable-next-line react-hooks/exhaustive-deps
18 | }, deps)
19 | }
20 |
--------------------------------------------------------------------------------
/packages/client/src/hooks/use-debounce-state.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react'
2 |
3 | export const useDebounceState = (value: any, ms: number) => {
4 | const [debouncedValue, setValue] = useState(value)
5 | const timeout = useRef>()
6 |
7 | useEffect(() => {
8 | if (timeout.current) {
9 | clearTimeout(timeout.current)
10 | }
11 | timeout.current = setTimeout(() => {
12 | setValue(value)
13 | }, ms)
14 |
15 | return () => {
16 | if (timeout.current) {
17 | clearTimeout(timeout.current)
18 | }
19 | }
20 | }, [ms, value])
21 |
22 | return debouncedValue
23 | }
24 |
--------------------------------------------------------------------------------
/packages/client/src/hooks/use-hover.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useRef, useState } from 'react'
2 |
3 | export const useHover = () => {
4 | const [value, setValue] = useState(false)
5 | const ref = useRef(null)
6 | const handleMouseOver = () => setValue(true)
7 | const handleMouseOut = () => setValue(false)
8 | useEffect(
9 | () => {
10 | const node = ref.current
11 | if (node) {
12 | node.addEventListener('mouseover', handleMouseOver)
13 | node.addEventListener('mouseout', handleMouseOut)
14 | return () => {
15 | node.removeEventListener('mouseover', handleMouseOver)
16 | node.removeEventListener('mouseout', handleMouseOut)
17 | }
18 | }
19 | },
20 | [ref.current] // Recall only if ref changes
21 | )
22 | return [ref, value] as const
23 | }
24 |
--------------------------------------------------------------------------------
/packages/client/src/hooks/use-modal.ts:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from 'react'
2 |
3 | export const useModal = () => {
4 | const [isOpen, setIsOpen] = useState(false)
5 |
6 | const onClose = useCallback(() => {
7 | setIsOpen(false)
8 | }, [])
9 |
10 | const onOpen = useCallback(() => {
11 | setIsOpen(true)
12 | }, [])
13 |
14 | return [isOpen, onOpen, onClose] as const
15 | }
16 |
--------------------------------------------------------------------------------
/packages/client/src/hooks/use-overrides.ts:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import { mergeOverrides } from 'baseui'
3 |
4 | export const useOverrides = (defaults: T, overrides?: T): T =>
5 | useMemo(
6 | () => mergeOverrides(defaults as any, (overrides ?? {}) as any) as any,
7 | [defaults, overrides]
8 | )
9 |
--------------------------------------------------------------------------------
/packages/client/src/hooks/use-router-loading.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import Router from 'next/router'
3 |
4 | export const useRouterLoading = () => {
5 | const [loading, setLoading] = useState(false)
6 |
7 | useEffect(() => {
8 | const start = () => {
9 | setLoading(true)
10 | }
11 |
12 | const end = () => {
13 | setLoading(false)
14 | }
15 |
16 | Router.events.on('routeChangeStart', start)
17 | Router.events.on('routeChangeComplete', end)
18 | Router.events.on('routeChangeError', end)
19 |
20 | return () => {
21 | Router.events.off('routeChangeStart', start)
22 | Router.events.off('routeChangeComplete', end)
23 | Router.events.off('routeChangeError', end)
24 | }
25 | }, [])
26 |
27 | return [loading] as const
28 | }
29 |
--------------------------------------------------------------------------------
/packages/client/src/hooks/use-scroll-trigger-page.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import { ScrollTrigger } from 'gsap/ScrollTrigger'
3 |
4 | export const useScrollTriggerPage = () => {
5 | useEffect(() => {
6 | return () => {
7 | ScrollTrigger.getAll().forEach((t) => t.kill())
8 | ScrollTrigger.clearMatchMedia()
9 | }
10 | }, [])
11 | }
12 |
--------------------------------------------------------------------------------
/packages/client/src/layouts/editor-layout.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useStyletron } from 'styletron-react'
3 | import { EditorSidebar } from '~/modules/editor/editor-sidebar'
4 |
5 | interface EditorLayoutProps {
6 | children?: React.ReactNode
7 | }
8 |
9 | export const EditorLayout = ({ children }: EditorLayoutProps) => {
10 | const [css] = useStyletron()
11 |
12 | return (
13 |
14 |
19 |
28 |
29 |
30 |
36 | {children}
37 |
38 |
39 |
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/banner/banner-bio.tsx:
--------------------------------------------------------------------------------
1 | import { BannerBioElement, CustomRenderElementProps } from '../custom-types'
2 | import OpenColor from 'open-color'
3 | import { Placeholder } from '../placeholder/placeholder'
4 | import { nanoid } from 'nanoid'
5 | import { ParagraphMedium } from 'baseui/typography'
6 |
7 | export const generateBannerBioElement = (): BannerBioElement => ({
8 | id: nanoid(),
9 | type: 'banner-bio',
10 | children: [{ text: '' }],
11 | })
12 |
13 | export const BannerBio = ({
14 | attributes,
15 | children,
16 | element,
17 | }: CustomRenderElementProps) => {
18 | return (
19 |
32 | 간단한 소개
33 | {children}
34 |
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/banner/banner-name.tsx:
--------------------------------------------------------------------------------
1 | import { BannerNameElement, CustomRenderElementProps } from '../custom-types'
2 | import OpenColor from 'open-color'
3 | import { Placeholder } from '../placeholder/placeholder'
4 | import { nanoid } from 'nanoid'
5 | import { HeadingXXLarge } from 'baseui/typography'
6 |
7 | export const generateBannerNameElement = (): BannerNameElement => ({
8 | id: nanoid(),
9 | type: 'banner-name',
10 | children: [{ text: '' }],
11 | })
12 |
13 | export const BannerName = ({
14 | attributes,
15 | children,
16 | element,
17 | }: CustomRenderElementProps) => {
18 | return (
19 |
31 | 이름
32 | {children}
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/banner/banner-tagline.tsx:
--------------------------------------------------------------------------------
1 | import { BannerTaglineElement, CustomRenderElementProps } from '../custom-types'
2 | import OpenColor from 'open-color'
3 | import { Placeholder } from '../placeholder/placeholder'
4 | import { nanoid } from 'nanoid'
5 | import { HeadingSmall } from 'baseui/typography'
6 |
7 | export const generateBannerTaglineElement = (): BannerTaglineElement => ({
8 | id: nanoid(),
9 | type: 'banner-tagline',
10 | children: [{ text: '' }],
11 | })
12 |
13 | export const BannerTagline = ({
14 | attributes,
15 | children,
16 | element,
17 | }: CustomRenderElementProps) => {
18 | return (
19 |
30 | 직책
31 | {children}
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/blocks/use-blocks.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react'
2 | import { useSetRecoilState } from 'recoil'
3 | import { blockPickerShowState } from '../editor.atoms'
4 |
5 | export const useBlocks = () => {
6 | const setShow = useSetRecoilState(blockPickerShowState)
7 |
8 | const onShowBlockPicker = useCallback(() => {
9 | setShow(true)
10 | }, [setShow])
11 |
12 | const onAddBlockButtonClick = useCallback<
13 | React.MouseEventHandler
14 | >(
15 | (event) => {
16 | event.preventDefault()
17 | onShowBlockPicker()
18 | },
19 | [onShowBlockPicker]
20 | )
21 |
22 | return { onAddBlockButtonClick }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/dnd/hooks/use-drag-block.ts:
--------------------------------------------------------------------------------
1 | import { useDrag } from 'react-dnd'
2 |
3 | export const useDragBlock = (id?: string) => {
4 | return useDrag(
5 | () => ({
6 | type: 'block',
7 | item() {
8 | document.body.classList.add('dragging')
9 | return { id }
10 | },
11 | collect: (monitor) => ({
12 | isDragging: monitor.isDragging(),
13 | }),
14 | end: () => {
15 | document.body.classList.remove('dragging')
16 | },
17 | }),
18 | []
19 | )
20 | }
21 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/dnd/types.ts:
--------------------------------------------------------------------------------
1 | export interface DragItemBlock {
2 | id: string
3 | type: string
4 | }
5 |
6 | export type DropDirection = 'top' | 'bottom' | null
7 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/dnd/utils/get-new-direction.ts:
--------------------------------------------------------------------------------
1 | import { DropDirection } from '../types'
2 |
3 | /**
4 | * Get new direction if updated
5 | */
6 | export const getNewDirection = (
7 | previousDir: DropDirection,
8 | dir?: DropDirection
9 | ): DropDirection | undefined => {
10 | if (!dir && previousDir) {
11 | return null
12 | }
13 |
14 | if (dir === 'top' && previousDir !== 'top') {
15 | return 'top'
16 | }
17 |
18 | if (dir === 'bottom' && previousDir !== 'bottom') {
19 | return 'bottom'
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/elements/bulleted-list.tsx:
--------------------------------------------------------------------------------
1 | import { Cell } from 'baseui/layout-grid'
2 | import { useStyletron } from 'styletron-react'
3 | import {
4 | BulletedListElement,
5 | CustomRenderElementProps,
6 | WithId,
7 | } from '../custom-types'
8 | import { RootDraggable } from '../dnd/root-draggable'
9 |
10 | export const BulletedList = ({
11 | attributes,
12 | children,
13 | element,
14 | }: CustomRenderElementProps>) => {
15 | const [css] = useStyletron()
16 | return (
17 |
18 |
19 |
27 | |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/elements/list-item.tsx:
--------------------------------------------------------------------------------
1 | import { useStyletron } from 'styletron-react'
2 | import { CustomRenderElementProps, ListItemElement } from '../custom-types'
3 |
4 | export const ListItem = ({
5 | attributes,
6 | children,
7 | }: CustomRenderElementProps) => {
8 | const [css] = useStyletron()
9 | return (
10 |
20 |
36 | •
37 |
38 | {children}
39 |
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/leaf/toolbar-buttons/form-button.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react'
2 | import { useSlate } from 'slate-react'
3 | import { IconType } from '~/components/icon'
4 | import { LeafFormat } from '../../custom-types'
5 | import { ToolbarButton } from './toolbar-button'
6 | import { isFormatActive, toggleFormat } from '../utils'
7 |
8 | interface FormatButtonProps {
9 | format: LeafFormat
10 | }
11 |
12 | export const FormatButton = ({ format }: FormatButtonProps) => {
13 | const editor = useSlate()
14 | const isActive = isFormatActive(editor, format)
15 | const iconType = useMemo(() => {
16 | switch (format) {
17 | case 'bold':
18 | return 'Bold'
19 | case 'italic':
20 | return 'Italic'
21 | case 'code':
22 | return 'Code'
23 | }
24 | }, [format])
25 | return (
26 | {
30 | event.preventDefault()
31 | toggleFormat(editor, format)
32 | }}
33 | />
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/leaf/toolbar-buttons/index.ts:
--------------------------------------------------------------------------------
1 | export { FontColorButton } from './font-color-button'
2 | export { FormatButton } from './form-button'
3 | export { HighlightButton } from './highlight-button'
4 | export { LinkButton } from './link-button'
5 | export { ToolbarButton } from './toolbar-button'
6 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/leaf/utils.ts:
--------------------------------------------------------------------------------
1 | import { Editor, Text, Transforms } from 'slate'
2 | import { CustomText, LeafFormat } from '~/modules/editor/custom-types'
3 |
4 | export const toggleFormat = (editor: Editor, format: LeafFormat) => {
5 | const isActive = isFormatActive(editor, format)
6 | Transforms.setNodes(
7 | editor,
8 | { [format]: isActive ? null : true },
9 | { match: Text.isText, split: true }
10 | )
11 | }
12 |
13 | export const isFormatActive = (editor: Editor, format: keyof CustomText) => {
14 | const [match] = Editor.nodes(editor, {
15 | match: (n) => Text.isText(n) && n[format] === true,
16 | mode: 'all',
17 | })
18 | return Boolean(match)
19 | }
20 |
21 | export const getSelectedText = (editor: Editor) => {
22 | const [match] = Editor.nodes(editor, {
23 | match: (n) => Text.isText(n),
24 | mode: 'all',
25 | })
26 |
27 | return match ? match[0] : undefined
28 | }
29 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/logo/insert-logo.tsx:
--------------------------------------------------------------------------------
1 | import { Editor, Transforms } from 'slate'
2 | import { LogoElement } from '../custom-types'
3 |
4 | type Logo = Omit
5 |
6 | export const insertLogo = (editor: Editor, logo: Logo) => {
7 | const iconElement: LogoElement = {
8 | type: 'logo',
9 | children: [{ text: '' }],
10 | ...logo,
11 | }
12 | Transforms.insertNodes(editor, iconElement)
13 | Transforms.move(editor)
14 | }
15 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/logo/logo-index.ts:
--------------------------------------------------------------------------------
1 | import FlexSearch from 'flexsearch'
2 | import logos from './logos.json'
3 |
4 | export const logoIndex = FlexSearch.create<{
5 | index: number
6 | name: string
7 | shortName: string
8 | }>({
9 | encode: 'advanced',
10 | tokenize: 'reverse',
11 | cache: true,
12 | async: true,
13 | doc: {
14 | id: 'index',
15 | field: ['name', 'shortName'],
16 | },
17 | })
18 |
19 | // TODO: indexing 과정 빌드시 처리하도록 (Server Component)
20 | logos.forEach((logo, i) => {
21 | logoIndex.add({ index: i, name: logo.name, shortName: logo.shortname })
22 | })
23 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/logo/logo.tsx:
--------------------------------------------------------------------------------
1 | import { CustomRenderElementProps, LogoElement } from '../custom-types'
2 | import { StyleObject, useStyletron } from 'styletron-react'
3 |
4 | export const Logo = ({
5 | attributes,
6 | children,
7 | element,
8 | }: CustomRenderElementProps) => {
9 | const [css] = useStyletron()
10 |
11 | return (
12 |
13 | {/* eslint-disable-next-line @next/next/no-img-element */}
14 |
19 | {children}
20 |
21 | )
22 | }
23 |
24 | const wrapper: StyleObject = {
25 | display: 'inline-block',
26 | alignItems: 'center',
27 | justifyContent: 'center',
28 | position: 'relative',
29 | }
30 |
31 | const imgStyles: StyleObject = {
32 | height: '1em',
33 | display: 'block',
34 | }
35 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/logo/types.ts:
--------------------------------------------------------------------------------
1 | import logos from './logos.json'
2 |
3 | export type ILogo = typeof logos[number]
4 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/logo/with-logo.ts:
--------------------------------------------------------------------------------
1 | import { Editor } from 'slate'
2 |
3 | export const withLogo = (editor: Editor) => {
4 | const { isInline, isVoid } = editor
5 |
6 | editor.isInline = (element) =>
7 | element.type === 'logo' ? true : isInline(element)
8 |
9 | editor.isVoid = (element) =>
10 | element.type === 'logo' ? true : isVoid(element)
11 |
12 | return editor
13 | }
14 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/placeholder/placeholder.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo } from 'react'
2 | import { Editor } from 'slate'
3 | import { useSlateStatic } from 'slate-react'
4 | import { useStyletron } from 'styletron-react'
5 | import { CustomElement } from '../custom-types'
6 |
7 | interface PlaceholderProps {
8 | children?: React.ReactNode
9 | element: CustomElement
10 | }
11 |
12 | export const Placeholder = ({ children, element }: PlaceholderProps) => {
13 | const [css] = useStyletron()
14 | const editor = useSlateStatic()
15 |
16 | const isEmpty = Editor.isEmpty(editor, element)
17 |
18 | if (!isEmpty) {
19 | return <>>
20 | }
21 |
22 | return (
23 |
36 | {children}
37 |
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/skill-list/skill-list-item-description.tsx:
--------------------------------------------------------------------------------
1 | import { ParagraphSmall } from 'baseui/typography'
2 | import { nanoid } from 'nanoid'
3 | import {
4 | CustomRenderElementProps,
5 | SkillListItemDescriptionElement,
6 | } from '../custom-types'
7 | import { Placeholder } from '../placeholder/placeholder'
8 |
9 | export const generateSkillListItemDescriptionElement =
10 | (): SkillListItemDescriptionElement => ({
11 | id: nanoid(),
12 | type: 'skill-list-item-description',
13 | children: [{ text: '' }],
14 | })
15 |
16 | export const SkillListItemDescription = ({
17 | attributes,
18 | children,
19 | element,
20 | }: CustomRenderElementProps) => {
21 | return (
22 |
23 | 스킬 설명
24 | {children}
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/skill-list/skill-list-item-name.tsx:
--------------------------------------------------------------------------------
1 | import { LabelMedium } from 'baseui/typography'
2 | import { nanoid } from 'nanoid'
3 | import {
4 | CustomRenderElementProps,
5 | SkillListItemNameElement,
6 | } from '../custom-types'
7 | import { Placeholder } from '../placeholder/placeholder'
8 |
9 | export const generateSkillListItemNameElement =
10 | (): SkillListItemNameElement => ({
11 | id: nanoid(),
12 | type: 'skill-list-item-name',
13 | children: [{ text: '' }],
14 | })
15 |
16 | export const SkillListItemName = ({
17 | attributes,
18 | children,
19 | element,
20 | }: CustomRenderElementProps) => {
21 | return (
22 |
33 | 스킬 이름
34 | {children}
35 |
36 | )
37 | }
38 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/social-link/edit-link-popover.tsx:
--------------------------------------------------------------------------------
1 | import { useStyletron } from 'styletron-react'
2 | import { padding } from 'polished'
3 | import OpenColor from 'open-color'
4 |
5 | interface EditLinkPopoverProps {
6 | onChange: (value: string) => void
7 | onClose?: () => void
8 | defaultValue?: string | null
9 | }
10 |
11 | export const EditLinkPopover = ({
12 | onChange,
13 | onClose,
14 | defaultValue,
15 | }: EditLinkPopoverProps) => {
16 | const [css] = useStyletron()
17 |
18 | return (
19 |
25 | {
31 | onChange(event.target.value)
32 | }}
33 | onKeyDown={(event) => {
34 | if (event.key === 'Enter') {
35 | onClose?.()
36 | event.preventDefault()
37 | }
38 | }}
39 | />
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/utils/generate-initial-content.ts:
--------------------------------------------------------------------------------
1 | import { Descendant } from 'slate'
2 | import { generateBannerElement } from '../banner/banner'
3 | import { generateParagraphElement } from '../elements/paragraph'
4 |
5 | export const generateInitialContent = (): Descendant[] => [
6 | generateBannerElement(),
7 | generateParagraphElement(),
8 | ]
9 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/utils/is-expanded.ts:
--------------------------------------------------------------------------------
1 | import { Range } from 'slate'
2 |
3 | /**
4 | * See {@link Range.isExpanded}.
5 | * Return false if `range` is not defined.
6 | */
7 | export const isExpanded = (range?: Range | null) =>
8 | !!range && Range.isExpanded(range)
9 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/utils/is-mod-key.ts:
--------------------------------------------------------------------------------
1 | import { IS_MAC } from '~/utils/constants'
2 |
3 | export const isModKey = (event: React.KeyboardEvent) =>
4 | IS_MAC ? event.metaKey : event.ctrlKey
5 |
--------------------------------------------------------------------------------
/packages/client/src/modules/editor/utils/some-node.ts:
--------------------------------------------------------------------------------
1 | import { Editor, Node } from 'slate'
2 | import { findNode, FindNodeOptions } from './find-node'
3 |
4 | /**
5 | * Iterate through all of the nodes in the editor and break early for the first truthy match. Otherwise
6 | * returns false.
7 | */
8 | export const someNode = (
9 | editor: Editor,
10 | options: FindNodeOptions
11 | ) => {
12 | return !!findNode(editor, options)
13 | }
14 |
--------------------------------------------------------------------------------
/packages/client/src/modules/user/hooks/use-logout.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios'
2 | import { useRouter } from 'next/dist/client/router'
3 | import { useCallback } from 'react'
4 | import { ROUTE_HOME } from '~/routes'
5 |
6 | export const useLogout = () => {
7 | const router = useRouter()
8 |
9 | const logout = useCallback(async () => {
10 | await axios.get('/jwt/logout', {
11 | baseURL: process.env.NEXT_PUBLIC_SERVER_HOST,
12 | withCredentials: true,
13 | })
14 | router.push(ROUTE_HOME)
15 | }, [router])
16 |
17 | return [logout] as const
18 | }
19 |
--------------------------------------------------------------------------------
/packages/client/src/modules/user/hooks/use-user.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from '@apollo/client'
2 | import { MeDocument } from '~/graphql/document.generated'
3 |
4 | export const useUser = () => {
5 | const { data } = useQuery(MeDocument)
6 | return data?.me
7 | }
8 |
--------------------------------------------------------------------------------
/packages/client/src/routes.ts:
--------------------------------------------------------------------------------
1 | export const ROUTE_HOME = '/'
2 |
3 | export const ROUTE_LOGIN = '/login'
4 |
5 | export const ROUTE_EDIT = '/edit'
6 | export const ROUTE_EDIT_SETTINGS = '/edit/settings'
7 |
8 | export const ROUTE_TERMS = '/docs/terms'
9 | export const ROUTE_PRIVACY = '/docs/privacy'
10 |
--------------------------------------------------------------------------------
/packages/client/src/styles/get-scrollbar-width.ts:
--------------------------------------------------------------------------------
1 | export const getScrollbarWidth = () => {
2 | if (typeof document === 'undefined') {
3 | return 15
4 | }
5 |
6 | const outer = document.createElement('div')
7 | outer.style.width = '100px'
8 | outer.style.overflow = 'scroll'
9 | outer.style.position = 'absolute'
10 |
11 | document.body.appendChild(outer)
12 |
13 | const widthNoScroll = outer.offsetWidth
14 | // force scrollbars
15 |
16 | // add innerdiv
17 | const inner = document.createElement('div')
18 | inner.style.width = '100%'
19 | outer.appendChild(inner)
20 |
21 | const widthWithScroll = inner.offsetWidth
22 |
23 | // remove divs
24 | outer.parentNode?.removeChild(outer)
25 |
26 | return widthNoScroll - widthWithScroll
27 | }
28 |
--------------------------------------------------------------------------------
/packages/client/src/styles/hex-to-rgb.ts:
--------------------------------------------------------------------------------
1 | export const hexToRGB = (hex: string, alpha?: number) => {
2 | var r = parseInt(hex.slice(1, 3), 16),
3 | g = parseInt(hex.slice(3, 5), 16),
4 | b = parseInt(hex.slice(5, 7), 16)
5 |
6 | if (alpha) {
7 | return 'rgba(' + r + ', ' + g + ', ' + b + ', ' + alpha + ')'
8 | } else {
9 | return 'rgb(' + r + ', ' + g + ', ' + b + ')'
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/client/src/styles/styles.ts:
--------------------------------------------------------------------------------
1 | import { StyleObject } from 'styletron-standard'
2 |
3 | export const linkStyles: StyleObject = {
4 | textDecoration: 'none',
5 | ':hover': {
6 | textDecoration: 'underline',
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/packages/client/src/styles/styletron.ts:
--------------------------------------------------------------------------------
1 | import { Client, Server } from 'styletron-engine-atomic'
2 |
3 | const styletron: Client | Server =
4 | typeof window === 'undefined'
5 | ? new Server()
6 | : new Client({
7 | hydrate: document.getElementsByClassName(
8 | '_styletron_hydrate_'
9 | ) as HTMLCollectionOf,
10 | })
11 |
12 | export { styletron }
13 |
--------------------------------------------------------------------------------
/packages/client/src/styles/theme.ts:
--------------------------------------------------------------------------------
1 | import { createTheme } from 'baseui'
2 | import { ThemePrimitives } from 'baseui/theme'
3 |
4 | const primitives: Partial = {
5 | primaryFontFamily: `Spoqa Han Sans Neo, -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', 'Malgun Gothic', '맑은 고딕', 나눔고딕, 'Nanum Gothic', 'Noto Sans KR', 'Noto Sans CJK KR', arial, 돋움, Dotum, Tahoma, Geneva, sans-serif;`,
6 | }
7 |
8 | export const theme = createTheme(primitives)
9 |
--------------------------------------------------------------------------------
/packages/client/src/styles/z-indexes.ts:
--------------------------------------------------------------------------------
1 | export const zIndexes = {
2 | header: 1000,
3 | backdrop: 2000,
4 | } as const
5 |
--------------------------------------------------------------------------------
/packages/client/src/utils/access-token.ts:
--------------------------------------------------------------------------------
1 | let accessToken = ''
2 |
3 | export const setAccessToken = (newAccessToken: string) => {
4 | accessToken = newAccessToken
5 | }
6 |
7 | export const getAccessToken = () => {
8 | return accessToken
9 | }
10 |
--------------------------------------------------------------------------------
/packages/client/src/utils/constants.ts:
--------------------------------------------------------------------------------
1 | export const IS_MAC =
2 | typeof window != 'undefined' &&
3 | /Mac|iPod|iPhone|iPad/.test(window.navigator.platform)
4 |
--------------------------------------------------------------------------------
/packages/client/src/utils/generate-path.ts:
--------------------------------------------------------------------------------
1 | import { compile, PathFunction } from 'path-to-regexp'
2 |
3 | const cache: {
4 | [key in string]: PathFunction>
5 | } = {}
6 | const cacheLimit = 10000
7 | let cacheCount = 0
8 |
9 | function compilePath(path: string) {
10 | if (cache[path]) return cache[path]
11 |
12 | const generator = compile(path)
13 |
14 | if (cacheCount < cacheLimit) {
15 | cache[path] = generator
16 | cacheCount++
17 | }
18 |
19 | return generator
20 | }
21 |
22 | /**
23 | * Public API for generating a URL pathname from a path and parameters.
24 | */
25 | export default function generatePath(path = '/', params = {}) {
26 | return path === '/' ? path : compilePath(path)(params)
27 | }
28 |
--------------------------------------------------------------------------------
/packages/client/src/utils/is-server.ts:
--------------------------------------------------------------------------------
1 | export const isServer = () => typeof window === 'undefined'
2 |
--------------------------------------------------------------------------------
/packages/client/src/utils/match-error-code.ts:
--------------------------------------------------------------------------------
1 | import { ApolloError } from '@apollo/client'
2 | import { GraphQLError } from 'graphql'
3 |
4 | export const matchErrorCode = (
5 | error: ApolloError | readonly GraphQLError[] | undefined,
6 | code: string
7 | ): boolean => {
8 | let hasError = false
9 | const graphQLErrors =
10 | error instanceof ApolloError ? error.graphQLErrors : error
11 |
12 | if (graphQLErrors && graphQLErrors.length > 0) {
13 | for (const err of graphQLErrors) {
14 | const errOptions = err.extensions
15 | const errCode = errOptions?.code
16 | if (errCode === code) {
17 | hasError = true
18 | }
19 | }
20 | }
21 |
22 | return hasError
23 | }
24 |
--------------------------------------------------------------------------------
/packages/client/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "~/*": ["src/*"],
6 | "~/api/*": ["src/pages/api/*"]
7 | },
8 | "target": "es5",
9 | "lib": ["dom", "dom.iterable", "esnext"],
10 | "allowJs": true,
11 | "skipLibCheck": true,
12 | "strict": true,
13 | "forceConsistentCasingInFileNames": true,
14 | "noEmit": true,
15 | "esModuleInterop": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "jsx": "preserve",
21 | "downlevelIteration": true
22 | },
23 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
24 | "exclude": ["node_modules"]
25 | }
26 |
--------------------------------------------------------------------------------
/packages/image-resizer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "image-resizer",
3 | "version": "0.1.0",
4 | "main": "serverless.ts",
5 | "license": "MIT",
6 | "scripts": {
7 | "deploy": "rm -rf node_modules && env npm_config_arch=x64 npm_config_platform=linux yarn install && serverless deploy -v"
8 | },
9 | "dependencies": {
10 | "image-size": "^1.0.0",
11 | "query-string": "^7.0.1",
12 | "sharp": "^0.30.3"
13 | },
14 | "devDependencies": {
15 | "@serverless/typescript": "^3.8.0",
16 | "@types/aws-lambda": "^8.10.81",
17 | "@types/aws-sdk": "^2.7.0",
18 | "@types/sharp": "^0.28.5",
19 | "serverless": "^2.53.0",
20 | "serverless-plugin-typescript": "^2.1.1",
21 | "ts-node": "^10.7.0",
22 | "tsconfig-paths": "^3.14.1",
23 | "typescript": "^4.6.3"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/image-resizer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "preserveConstEnums": true,
5 | "strictNullChecks": true,
6 | "sourceMap": false,
7 | "allowJs": true,
8 | "target": "es5",
9 | "outDir": "./dist",
10 | "moduleResolution": "node",
11 | "lib": ["es2015"],
12 | "rootDir": "./",
13 | "esModuleInterop": true
14 | },
15 | "ts-node": {
16 | "require": ["tsconfig-paths/register"]
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/og-image/.env.example:
--------------------------------------------------------------------------------
1 | CLIENT_HOST=https://develofolio.com
2 | IMAGES_HOST=https://images-v3.develofolio.com
3 |
--------------------------------------------------------------------------------
/packages/og-image/.gitignore:
--------------------------------------------------------------------------------
1 | lib
2 | .webpack
--------------------------------------------------------------------------------
/packages/og-image/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "og-image",
3 | "version": "0.1.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "deploy": "serverless deploy -v"
8 | },
9 | "devDependencies": {
10 | "@serverless/typescript": "^2.55.0",
11 | "@types/aws-lambda": "^8.10.83",
12 | "@types/node": "^16.9.1",
13 | "@types/puppeteer": "5.4.3",
14 | "@types/puppeteer-core": "5.4.0",
15 | "dotenv-webpack": "^7.0.3",
16 | "serverless": "^2.57.0",
17 | "serverless-apigw-binary": "^0.4.4",
18 | "serverless-offline": "^8.1.0",
19 | "serverless-webpack": "^5.5.4",
20 | "ts-loader": "^9.2.5",
21 | "ts-node": "^10.2.1",
22 | "tsconfig-paths": "^3.11.0",
23 | "tsconfig-paths-webpack-plugin": "^3.5.1",
24 | "typescript": "^4.4.3",
25 | "webpack": "^5.52.1",
26 | "webpack-node-externals": "^3.0.0"
27 | },
28 | "dependencies": {
29 | "axios": "^0.26.1",
30 | "chrome-aws-lambda": "7.0.0",
31 | "lodash": "^4.17.21",
32 | "open-color": "^1.9.1",
33 | "puppeteer-core": "7.0.0",
34 | "slate": "^0.76.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/og-image/serverless.ts:
--------------------------------------------------------------------------------
1 | import { AWS } from '@serverless/typescript'
2 | import main from '~/functions/main'
3 |
4 | const serverlessConfiguration: AWS = {
5 | service: 'develofolio-og-image',
6 | frameworkVersion: '2',
7 | useDotenv: true,
8 | provider: {
9 | name: 'aws',
10 | runtime: 'nodejs14.x',
11 | region: 'us-east-1',
12 | lambdaHashingVersion: '20201221',
13 | },
14 | plugins: [
15 | 'serverless-webpack',
16 | 'serverless-offline',
17 | 'serverless-apigw-binary',
18 | ],
19 | custom: {
20 | webpack: {
21 | webpackConfig: './webpack.config.js',
22 | includeModules: true,
23 | },
24 | apigwBinary: {
25 | types: ['*/*'],
26 | },
27 | },
28 | functions: {
29 | main,
30 | },
31 | }
32 |
33 | module.exports = serverlessConfiguration
34 |
--------------------------------------------------------------------------------
/packages/og-image/src/functions/index.ts:
--------------------------------------------------------------------------------
1 | export { default as main } from './main'
2 |
--------------------------------------------------------------------------------
/packages/og-image/src/functions/main/index.ts:
--------------------------------------------------------------------------------
1 | import { AWS } from '@serverless/typescript'
2 | import { handlerPath } from '~/libs/handler-path'
3 |
4 | export default {
5 | handler: `${handlerPath(__dirname)}/handler.main`,
6 | events: [
7 | {
8 | http: {
9 | method: 'GET',
10 | path: '/',
11 | },
12 | },
13 | ],
14 | timeout: 60,
15 | } as AWS['functions'][string]
16 |
--------------------------------------------------------------------------------
/packages/og-image/src/libs/chromium.ts:
--------------------------------------------------------------------------------
1 | import core from 'puppeteer-core'
2 | import { getOptions } from './options'
3 |
4 | let _page: core.Page | null
5 |
6 | async function getPage(isDev: boolean) {
7 | if (_page) {
8 | return _page
9 | }
10 | const options = await getOptions(isDev)
11 | const browser = await core.launch(options)
12 | _page = await browser.newPage()
13 | return _page
14 | }
15 |
16 | export async function getScreenshot(html: string, isDev: boolean) {
17 | const page = await getPage(isDev)
18 | await page.setViewport({ width: 1200, height: 630 })
19 | await page.setContent(html, { waitUntil: 'networkidle0' })
20 | const file = await page.screenshot({ type: 'png' })
21 | return file
22 | }
23 |
--------------------------------------------------------------------------------
/packages/og-image/src/libs/deep-filter.ts:
--------------------------------------------------------------------------------
1 | import { isArray } from 'lodash'
2 |
3 | export const deepFilter = (
4 | value: T | T[],
5 | childrenKey: keyof T,
6 | predicate: (value: T, index: number) => boolean
7 | ) => {
8 | const recursive = (result: T[], value: T | T[]) => {
9 | if (isArray(value)) {
10 | result.push(...value.filter(predicate))
11 | value.forEach((item) => {
12 | if (item[childrenKey]) {
13 | recursive(result, item[childrenKey] as any)
14 | }
15 | })
16 | }
17 |
18 | return
19 | }
20 | const result: T[] = []
21 |
22 | recursive(result, value)
23 |
24 | return result
25 | }
26 |
--------------------------------------------------------------------------------
/packages/og-image/src/libs/handler-path.ts:
--------------------------------------------------------------------------------
1 | export const handlerPath = (context: string) => {
2 | return `${context.split(process.cwd())[1].substring(1).replace(/\\/g, '/')}`
3 | }
4 |
--------------------------------------------------------------------------------
/packages/og-image/src/libs/options.ts:
--------------------------------------------------------------------------------
1 | import chrome from 'chrome-aws-lambda'
2 |
3 | const exePath =
4 | process.platform === 'win32'
5 | ? 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe'
6 | : process.platform === 'linux'
7 | ? '/usr/bin/google-chrome'
8 | : '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'
9 |
10 | interface Options {
11 | args: string[]
12 | executablePath: string
13 | headless: boolean
14 | }
15 |
16 | export async function getOptions(isDev: boolean) {
17 | let options: Options
18 | if (isDev) {
19 | options = {
20 | args: [],
21 | executablePath: exePath,
22 | headless: true,
23 | }
24 | } else {
25 | options = {
26 | args: chrome.args,
27 | executablePath: await chrome.executablePath,
28 | headless: chrome.headless,
29 | }
30 | }
31 | return options
32 | }
33 |
--------------------------------------------------------------------------------
/packages/og-image/src/libs/parser.ts:
--------------------------------------------------------------------------------
1 | import { APIGatewayProxyEventMultiValueQueryStringParameters } from 'aws-lambda'
2 |
3 | export interface ParsedRequest {
4 | slug: string
5 | }
6 |
7 | export function parseRequest(
8 | qs: APIGatewayProxyEventMultiValueQueryStringParameters
9 | ) {
10 | const { slug } = qs ?? {}
11 |
12 | const parsedRequest: ParsedRequest = {
13 | slug: decodeURIComponent(slug[0]),
14 | }
15 |
16 | return parsedRequest
17 | }
18 |
--------------------------------------------------------------------------------
/packages/og-image/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.paths.json",
3 | "compilerOptions": {
4 | "lib": ["ESNext"],
5 | "moduleResolution": "node",
6 | "noUnusedLocals": true,
7 | "noUnusedParameters": true,
8 | "removeComments": true,
9 | "sourceMap": true,
10 | "target": "ES2020",
11 | "outDir": "dist",
12 | "skipLibCheck": true,
13 | "allowSyntheticDefaultImports": true
14 | },
15 | "include": ["src/**/*.ts", "serverless.ts"],
16 | "exclude": [
17 | "node_modules/**/*",
18 | ".serverless/**/*",
19 | ".webpack/**/*",
20 | ".vscode/**/*"
21 | ],
22 | "ts-node": {
23 | "require": ["tsconfig-paths/register"]
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/og-image/tsconfig.paths.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "~/*": ["src/*"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/server/.env.example:
--------------------------------------------------------------------------------
1 | NODE_ENV=development
2 | CLIENT_HOST=http://localhost:3000
3 | HOST=http://localhost:4000
4 |
5 | # DATABASE 설정
6 | DB_HOST=localhost
7 | DB_PORT=5432
8 | DB_NAME=develofolio
9 | DB_USERNAME=test
10 | DB_PASSWORD=test
11 |
12 | # Dev App
13 | GITHUB_CLIENT_ID=da5e4dc778714b3040e4
14 | GITHUB_CLIENT_SECRET=d268ac6f666c7fd090fe8775afb6f3f282957ff0
15 |
16 | # Test App
17 | FACEBOOK_CLIENT_ID=2206147246192321
18 | FACEBOOK_CLIENT_SECRET=c41b27adfa9d46a8e228b3362a566e15
19 |
20 | GOOGLE_CLIENT_ID=SECRET_VALUE
21 | GOOGLE_CLIENT_SECRET=SECRET_VALUE
22 |
23 | JWT_ACCESS_TOKEN_SECRET=accesstokensecret
24 | JWT_ACCESS_TOKEN_EXPIRES_IN=10s
25 | JWT_REFRESH_TOKEN_SECRET=refreshtokensecret
26 | JWT_REFRESH_TOKEN_EXPIRES_IN=30d
27 |
28 | AWS_ACCESS_KEY_ID=SECRET_VALUE
29 | AWS_SECRET_ACCESS_KEY=SECRET_VALUE
30 | AWS_PUBLIC_BUCKET_NAME=develofolio-storage
31 |
--------------------------------------------------------------------------------
/packages/server/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: './tsconfig.json',
5 | sourceType: 'module',
6 | tsconfigRootDir: __dirname,
7 | },
8 | plugins: ['@typescript-eslint/eslint-plugin'],
9 | extends: [
10 | 'plugin:@typescript-eslint/recommended',
11 | 'plugin:prettier/recommended',
12 | ],
13 | root: true,
14 | env: {
15 | node: true,
16 | jest: true,
17 | },
18 | ignorePatterns: ['.eslintrc.js'],
19 | rules: {
20 | '@typescript-eslint/interface-name-prefix': 'off',
21 | '@typescript-eslint/explicit-function-return-type': 'off',
22 | '@typescript-eslint/explicit-module-boundary-types': 'off',
23 | '@typescript-eslint/no-explicit-any': 'off',
24 | 'prettier/prettier': 'off',
25 | },
26 | }
27 |
--------------------------------------------------------------------------------
/packages/server/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | lerna-debug.log*
12 |
13 | # OS
14 | .DS_Store
15 |
16 | # Tests
17 | /coverage
18 | /.nyc_output
19 |
20 | # IDEs and editors
21 | /.idea
22 | .project
23 | .classpath
24 | .c9/
25 | *.launch
26 | .settings/
27 | *.sublime-workspace
28 |
29 | # IDE - VSCode
30 | .vscode/*
31 | !.vscode/settings.json
32 | !.vscode/tasks.json
33 | !.vscode/launch.json
34 | !.vscode/extensions.json
35 |
36 | .env
37 |
38 | .build
--------------------------------------------------------------------------------
/packages/server/env.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace NodeJS {
2 | interface ProcessEnv {
3 | readonly SERVER: string
4 | readonly CLIENT: string
5 |
6 | readonly DB_HOST: string
7 | readonly DB_PORT: string
8 | readonly DB_NAME: string
9 | readonly DB_USERNAME: string
10 | readonly DB_PASSWORD: string
11 |
12 | readonly GITHUB_CLIENT_ID: string
13 | readonly GITHUB_CLIENT_SECRET: string
14 |
15 | readonly JWT_ACCESS_TOKEN_SECRET: string
16 | readonly JWT_ACCESS_TOKEN_EXPIRATION_TIME: string
17 | readonly JWT_REFRESH_TOKEN_SECRET: string
18 | readonly JWT_REFRESH_TOKEN_EXPIRATION_TIME: string
19 |
20 | readonly AWS_ACCESS_KEY_ID: string
21 | readonly AWS_SECRET_ACCESS_KEY: string
22 | readonly AWS_S3_BUCKET: string
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/server/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "collection": "@nestjs/schematics",
3 | "sourceRoot": "src"
4 | }
5 |
--------------------------------------------------------------------------------
/packages/server/serverless.yml:
--------------------------------------------------------------------------------
1 | service: develofolio-server
2 |
3 | provider:
4 | name: aws
5 | runtime: nodejs14.x
6 | region: ap-northeast-2
7 |
8 | plugins:
9 | - serverless-plugin-warmup
10 |
11 | custom:
12 | warmup:
13 | main:
14 | enabled: true
15 |
16 | package:
17 | individually: true
18 | exclude:
19 | - src/*.ts
20 | - node_modules/.bin/**
21 |
22 | functions:
23 | main:
24 | handler: dist/serverless.handler
25 | events:
26 | - http:
27 | method: ANY
28 | path: /
29 | - http:
30 | method: ANY
31 | path: '{proxy+}'
32 | timeout: 60
33 | warmup: true
34 |
--------------------------------------------------------------------------------
/packages/server/src/common/pagination/offset-paginate.ts:
--------------------------------------------------------------------------------
1 | import { SelectQueryBuilder } from 'typeorm'
2 | import { OffsetPageInfo } from './page-info'
3 | import { OffsetPaginationArgs } from './pagination.args'
4 |
5 | export async function offsetPaginate(
6 | query: SelectQueryBuilder,
7 | paginationArgs: OffsetPaginationArgs
8 | ) {
9 | const { limit, offset } = paginationArgs
10 | const countTotal = await query.getCount()
11 | const countAfter = countTotal - (offset + limit)
12 |
13 | query.limit(limit).offset(offset)
14 |
15 | const result = await query.getMany()
16 | const edges = result.map((value) => ({
17 | node: value,
18 | }))
19 |
20 | const pageInfo = new OffsetPageInfo()
21 | pageInfo.hasNextPage = countAfter > 0
22 | pageInfo.hasPreviousPage = offset > 0
23 | pageInfo.countCurrent = edges.length
24 | pageInfo.countBefore = offset
25 | pageInfo.countNext = countAfter > 0 ? countAfter : 0
26 | pageInfo.countTotal = countTotal
27 |
28 | return { edges, pageInfo }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/server/src/common/pagination/page-info.ts:
--------------------------------------------------------------------------------
1 | import { Field, Int, ObjectType } from '@nestjs/graphql'
2 |
3 | @ObjectType()
4 | export class OffsetPageInfo {
5 | @Field(() => Boolean)
6 | hasPreviousPage: boolean
7 |
8 | @Field(() => Boolean)
9 | hasNextPage: boolean
10 |
11 | @Field(() => Int)
12 | countCurrent: number
13 |
14 | @Field(() => Int)
15 | countTotal: number
16 |
17 | @Field(() => Int)
18 | countBefore: number
19 |
20 | @Field(() => Int)
21 | countNext: number
22 | }
23 |
24 | @ObjectType()
25 | export class CursorPageInfo extends OffsetPageInfo {
26 | @Field(() => String)
27 | startCursor: string
28 |
29 | @Field(() => String)
30 | endCursor: string
31 | }
32 |
--------------------------------------------------------------------------------
/packages/server/src/common/pagination/pagination.args.ts:
--------------------------------------------------------------------------------
1 | import { ArgsType, Int, Field } from '@nestjs/graphql'
2 |
3 | @ArgsType()
4 | export class CursorPaginationArgs {
5 | @Field(() => Int, { nullable: true })
6 | first?: number
7 |
8 | @Field(() => String, { nullable: true })
9 | after?: string
10 |
11 | @Field(() => Int, { nullable: true })
12 | last?: number
13 |
14 | @Field(() => String, { nullable: true })
15 | before?: string
16 | }
17 |
18 | @ArgsType()
19 | export class OffsetPaginationArgs {
20 | @Field(() => Int)
21 | limit: number
22 |
23 | @Field(() => Int)
24 | offset: number
25 | }
26 |
--------------------------------------------------------------------------------
/packages/server/src/common/types/express.d.ts:
--------------------------------------------------------------------------------
1 | import { User } from 'src/modules/user/user.entity'
2 |
3 | declare module 'express' {
4 | export interface Request {
5 | user?: User
6 | }
7 | export interface RequestWithAuth extends Request {
8 | user: User
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/server/src/config/aws.config.ts:
--------------------------------------------------------------------------------
1 | import { registerAs } from '@nestjs/config'
2 |
3 | export const awsConfig = registerAs('aws', () => ({
4 | accessKeyId: process.env.AWS_ACCESS_KEY_ID,
5 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
6 | bucket: process.env.AWS_PUBLIC_BUCKET_NAME,
7 | }))
8 |
--------------------------------------------------------------------------------
/packages/server/src/config/base.config.ts:
--------------------------------------------------------------------------------
1 | import { registerAs } from '@nestjs/config'
2 |
3 | export const baseConfig = registerAs('base', () => ({
4 | clientHost: process.env.CLIENT_HOST,
5 | host: process.env.HOST,
6 | }))
7 |
--------------------------------------------------------------------------------
/packages/server/src/config/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare namespace NodeJS {
4 | interface ProcessEnv {
5 | readonly NODE_ENV: 'development' | 'production' | 'test'
6 |
7 | readonly CLIENT_HOST: string
8 | readonly HOST: string
9 |
10 | readonly DB_HOST: string
11 | readonly DB_USER: string
12 | readonly DB_PORT: string
13 | readonly DB_USERNAME: string
14 | readonly DB_PASSWORD: string
15 |
16 | readonly GITHUB_CLIENT_ID: string
17 | readonly GITHUB_CLIENT_SECRET: string
18 |
19 | readonly FACEBOOK_CLIENT_ID: string
20 | readonly FACEBOOK_CLIENT_SECRET: string
21 |
22 | readonly GOOGLE_CLIENT_ID: string
23 | readonly GOOGLE_CLIENT_SECRET: string
24 |
25 | readonly JWT_ACCESS_TOKEN_SECRET: string
26 | readonly JWT_ACCESS_TOKEN_EXPIRES_IN: string
27 | readonly JWT_REFRESH_TOKEN_SECRET: string
28 | readonly JWT_REFRESH_TOKEN_EXPIRES_IN: string
29 |
30 | readonly AWS_ACCESS_KEY_ID: string
31 | readonly AWS_SECRET_ACCESS_KEY: string
32 | readonly AWS_PUBLIC_BUCKET_NAME: string
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/server/src/config/facebook.config.ts:
--------------------------------------------------------------------------------
1 | import { registerAs } from '@nestjs/config'
2 |
3 | export const facebookConfig = registerAs('facebook', () => ({
4 | clientId: process.env.FACEBOOK_CLIENT_ID,
5 | clientSecret: process.env.FACEBOOK_CLIENT_SECRET,
6 | }))
7 |
--------------------------------------------------------------------------------
/packages/server/src/config/github.config.ts:
--------------------------------------------------------------------------------
1 | import { registerAs } from '@nestjs/config'
2 |
3 | export const githubConfig = registerAs('github', () => ({
4 | clientId: process.env.GITHUB_CLIENT_ID,
5 | clientSecret: process.env.GITHUB_CLIENT_SECRET,
6 | }))
7 |
--------------------------------------------------------------------------------
/packages/server/src/config/google.config.ts:
--------------------------------------------------------------------------------
1 | import { registerAs } from '@nestjs/config'
2 |
3 | export const googleConfig = registerAs('google', () => ({
4 | clientId: process.env.GOOGLE_CLIENT_ID,
5 | clientSecret: process.env.GOOGLE_CLIENT_SECRET,
6 | }))
7 |
--------------------------------------------------------------------------------
/packages/server/src/config/jwt.config.ts:
--------------------------------------------------------------------------------
1 | import { registerAs } from '@nestjs/config'
2 |
3 | export const jwtConfig = registerAs('jwt', () => ({
4 | accessTokenSecret: process.env.JWT_ACCESS_TOKEN_SECRET,
5 | accessTokenExpiresIn: process.env.JWT_ACCESS_TOKEN_EXPIRES_IN,
6 | refreshTokenSecret: process.env.JWT_REFRESH_TOKEN_SECRET,
7 | refreshTokenExpiresIn: process.env.JWT_REFRESH_TOKEN_EXPIRES_IN,
8 | }))
9 |
--------------------------------------------------------------------------------
/packages/server/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory } from '@nestjs/core'
2 | import { AppModule } from './app.module'
3 | import cookieParser from 'cookie-parser'
4 | import { ConfigService } from '@nestjs/config'
5 | import { config } from 'aws-sdk'
6 |
7 | async function bootstrap() {
8 | const app = await NestFactory.create(AppModule)
9 |
10 | const configService = app.get(ConfigService)
11 |
12 | app.enableCors({
13 | credentials: true,
14 | origin: configService.get('CLIENT_HOST'),
15 | })
16 |
17 | app.use(cookieParser())
18 |
19 | config.update({
20 | region: 'ap-northeast-2',
21 | })
22 |
23 | await app.listen(4000)
24 | }
25 | bootstrap()
26 |
--------------------------------------------------------------------------------
/packages/server/src/migrations/1623480513569-RenameUserImageField.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from 'typeorm'
2 |
3 | export class RenameUserImageField1623480513569 implements MigrationInterface {
4 | name = 'RenameUserImageField1623480513569'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(
8 | `ALTER TABLE "users" RENAME COLUMN "image" TO "avatar"`
9 | )
10 | }
11 |
12 | public async down(queryRunner: QueryRunner): Promise {
13 | await queryRunner.query(
14 | `ALTER TABLE "users" RENAME COLUMN "avatar" TO "image"`
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/server/src/migrations/1623480715807-AddUserNameField.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from 'typeorm'
2 |
3 | export class AddUserNameField1623480715807 implements MigrationInterface {
4 | name = 'AddUserNameField1623480715807'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE "users" ADD "name" text NOT NULL`)
8 | }
9 |
10 | public async down(queryRunner: QueryRunner): Promise {
11 | await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "name"`)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/server/src/migrations/1623485545430-RemoveLegacyUserField.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from 'typeorm'
2 |
3 | export class RemoveLegacyUserField1623485545430 implements MigrationInterface {
4 | name = 'RemoveLegacyUserField1623485545430'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "email_verified"`)
8 | await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "provider_type"`)
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(
13 | `ALTER TABLE "users" ADD "provider_type" character varying(255) NOT NULL`
14 | )
15 | await queryRunner.query(
16 | `ALTER TABLE "users" ADD "email_verified" TIMESTAMP WITH TIME ZONE`
17 | )
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/server/src/migrations/1625973327384-UserEmialNullable.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from 'typeorm'
2 |
3 | export class UserEmialNullable1625973327384 implements MigrationInterface {
4 | name = 'UserEmialNullable1625973327384'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(
8 | `ALTER TABLE "users" ALTER COLUMN "email" DROP NOT NULL`
9 | )
10 | }
11 |
12 | public async down(queryRunner: QueryRunner): Promise {
13 | await queryRunner.query(
14 | `ALTER TABLE "users" ALTER COLUMN "email" SET NOT NULL`
15 | )
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/server/src/migrations/1626444510117-CreateFileTable.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from 'typeorm'
2 |
3 | export class CreateFileTable1626444510117 implements MigrationInterface {
4 | name = 'CreateFileTable1626444510117'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(
8 | `CREATE TABLE "files" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "owner_id" uuid NOT NULL, "key" character varying NOT NULL, "is_private" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_6c16b9093a142e0e7613b04a3d9" PRIMARY KEY ("id"))`
9 | )
10 | await queryRunner.query(
11 | `ALTER TABLE "files" ADD CONSTRAINT "FK_4bc1db1f4f34ec9415acd88afdb" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`
12 | )
13 | }
14 |
15 | public async down(queryRunner: QueryRunner): Promise {
16 | await queryRunner.query(
17 | `ALTER TABLE "files" DROP CONSTRAINT "FK_4bc1db1f4f34ec9415acd88afdb"`
18 | )
19 | await queryRunner.query(`DROP TABLE "files"`)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/server/src/migrations/1629289882551-CreateSchoolsTable.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from 'typeorm'
2 |
3 | export class CreateSchoolsTable1629289882551 implements MigrationInterface {
4 | name = 'CreateSchoolsTable1629289882551'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(
8 | `CREATE TABLE "schools" ("id" SERIAL NOT NULL, "name" character varying(255) NOT NULL, "logo" character varying(255) NOT NULL, CONSTRAINT "PK_95b932e47ac129dd8e23a0db548" PRIMARY KEY ("id"))`
9 | )
10 | }
11 |
12 | public async down(queryRunner: QueryRunner): Promise {
13 | await queryRunner.query(`DROP TABLE "schools"`)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/server/src/migrations/1629982011935-AddPageColumns.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from 'typeorm'
2 |
3 | export class AddPageColumns1629982011935 implements MigrationInterface {
4 | name = 'AddPageColumns1629982011935'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(
8 | `ALTER TABLE "pages" ADD "title" character varying(255)`
9 | )
10 | await queryRunner.query(
11 | `ALTER TABLE "pages" ADD "gtag" character varying(255)`
12 | )
13 | }
14 |
15 | public async down(queryRunner: QueryRunner): Promise {
16 | await queryRunner.query(`ALTER TABLE "pages" DROP COLUMN "gtag"`)
17 | await queryRunner.query(`ALTER TABLE "pages" DROP COLUMN "title"`)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/server/src/migrations/1636003916276-AddLanguageType.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from 'typeorm'
2 |
3 | export class AddLanguageType1636003916276 implements MigrationInterface {
4 | name = 'AddLanguageType1636003916276'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(
8 | `CREATE TYPE "pages_language_enum" AS ENUM('KO', 'EN', 'ZH', 'JA', 'DE', 'FE', 'ES', 'RU')`
9 | )
10 | await queryRunner.query(
11 | `ALTER TABLE "pages" ADD "language" "pages_language_enum" NOT NULL DEFAULT 'KO'`
12 | )
13 | }
14 |
15 | public async down(queryRunner: QueryRunner): Promise {
16 | await queryRunner.query(`ALTER TABLE "pages" DROP COLUMN "language"`)
17 | await queryRunner.query(`DROP TYPE "pages_language_enum"`)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { PassportModule } from '@nestjs/passport'
3 | import { UserModule } from '../user/user.module'
4 | import { PageModule } from '../page/page.module'
5 | import { FacebookOauthModule } from './facebook/facebook-oauth.module'
6 | import { GithubOauthModule } from './github/github-oauth.module'
7 | import { GoogleOauthModule } from './google/google-oauth.module'
8 | import { JwtAuthModule } from './jwt/jwt-auth.module'
9 | import { AuthResolver } from './auth.resolver'
10 | import { FileModule } from '../file/file.module'
11 |
12 | @Module({
13 | imports: [
14 | PassportModule,
15 | GithubOauthModule,
16 | FacebookOauthModule,
17 | GoogleOauthModule,
18 | JwtAuthModule,
19 | UserModule,
20 | PageModule,
21 | FileModule,
22 | ],
23 | providers: [AuthResolver],
24 | controllers: [],
25 | })
26 | export class AuthModule {}
27 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/auth.resolver.ts:
--------------------------------------------------------------------------------
1 | import { UseGuards } from '@nestjs/common'
2 | import { Mutation, Resolver } from '@nestjs/graphql'
3 | import { FileService } from '../file/file.service'
4 | import { PageService } from '../page/page.service'
5 | import { CurrentUser } from '../user/decorator/current-user.decorator'
6 | import { User } from '../user/user.entity'
7 | import { UserService } from '../user/user.service'
8 | import { GqlAuthGuard } from './graphql/gql-auth.guard'
9 |
10 | @Resolver()
11 | export class AuthResolver {
12 | constructor(
13 | private readonly pageService: PageService,
14 | private readonly userService: UserService,
15 | private readonly fileService: FileService
16 | ) {}
17 |
18 | @Mutation(() => Boolean)
19 | @UseGuards(GqlAuthGuard)
20 | async deleteAccount(@CurrentUser() user: User) {
21 | await this.fileService.deleteUserFiles(user.id)
22 | await this.userService.remove(user.id)
23 | await this.pageService.remove(user.pageId)
24 |
25 | return true
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/facebook/facebook-oauth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common'
2 | import { AuthGuard } from '@nestjs/passport'
3 |
4 | @Injectable()
5 | export class FacebookOauthGuard extends AuthGuard('facebook') {}
6 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/facebook/facebook-oauth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { PageModule } from '../../page/page.module'
3 | import { UserModule } from '../../user/user.module'
4 | import { JwtAuthModule } from '../jwt/jwt-auth.module'
5 | import { FacebookOauthController } from './facebook-oauth.controller'
6 | import { FacebookOauthStrategy } from './facebook-oauth.strategy'
7 |
8 | @Module({
9 | imports: [UserModule, JwtAuthModule, PageModule],
10 | controllers: [FacebookOauthController],
11 | providers: [FacebookOauthStrategy],
12 | })
13 | export class FacebookOauthModule {}
14 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/github/github-oauth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common'
2 | import { AuthGuard } from '@nestjs/passport'
3 |
4 | @Injectable()
5 | export class GithubOauthGuard extends AuthGuard('github') {}
6 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/github/github-oauth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { PageModule } from '../../page/page.module'
3 | import { UserModule } from '../../user/user.module'
4 | import { JwtAuthModule } from '../jwt/jwt-auth.module'
5 | import { GithubOauthController } from './github-oauth.controller'
6 | import { GithubOauthStrategy } from './github-oauth.strategy'
7 |
8 | @Module({
9 | imports: [UserModule, JwtAuthModule, PageModule],
10 | controllers: [GithubOauthController],
11 | providers: [GithubOauthStrategy],
12 | })
13 | export class GithubOauthModule {}
14 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/google/google-oauth.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common'
2 | import { AuthGuard } from '@nestjs/passport'
3 |
4 | @Injectable()
5 | export class GoogleOauthGuard extends AuthGuard('google') {}
6 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/google/google-oauth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { PageModule } from '../../page/page.module'
3 | import { UserModule } from '../../user/user.module'
4 | import { JwtAuthModule } from '../jwt/jwt-auth.module'
5 | import { GoogleOauthController } from './google-oauth.controller'
6 | import { GoogleOauthStrategy } from './google-oauth.strategy'
7 |
8 | @Module({
9 | imports: [UserModule, JwtAuthModule, PageModule],
10 | controllers: [GoogleOauthController],
11 | providers: [GoogleOauthStrategy],
12 | })
13 | export class GoogleOauthModule {}
14 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/graphql/gql-auth.decorator.ts:
--------------------------------------------------------------------------------
1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'
2 | import { GqlExecutionContext } from '@nestjs/graphql'
3 |
4 | export const CurrentUser = createParamDecorator(
5 | (_data: unknown, context: ExecutionContext) => {
6 | const ctx = GqlExecutionContext.create(context)
7 |
8 | return ctx.getContext().req.user
9 | }
10 | )
11 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/graphql/gql-auth.guard.ts:
--------------------------------------------------------------------------------
1 | import { ExecutionContext, Injectable } from '@nestjs/common'
2 | import { GqlExecutionContext } from '@nestjs/graphql'
3 | import { AuthGuard } from '@nestjs/passport'
4 |
5 | @Injectable()
6 | export class GqlAuthGuard extends AuthGuard('jwt-access') {
7 | getRequest(context: ExecutionContext) {
8 | const ctx = GqlExecutionContext.create(context)
9 | return ctx.getContext().req
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/jwt/jwt-access.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common'
2 | import { AuthGuard } from '@nestjs/passport'
3 |
4 | @Injectable()
5 | export class JwtAccessGuard extends AuthGuard('jwt-access') {}
6 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/jwt/jwt-auth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { JwtModule } from '@nestjs/jwt'
3 | import { JwtAuthController } from './jwt-auth.controller'
4 | import { JwtAuthService } from './jwt-auth.service'
5 | import { JwtAuthStrategy } from './jwt-access.strategy'
6 | import { JwtRefreshAuthStrategy } from './jwt-refresh.strategy'
7 | import { UserModule } from '../../user/user.module'
8 |
9 | @Module({
10 | imports: [JwtModule.register({}), UserModule],
11 | providers: [JwtAuthStrategy, JwtRefreshAuthStrategy, JwtAuthService],
12 | exports: [JwtModule, JwtAuthService],
13 | controllers: [JwtAuthController],
14 | })
15 | export class JwtAuthModule {}
16 |
--------------------------------------------------------------------------------
/packages/server/src/modules/auth/jwt/jwt-refresh.guard.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common'
2 | import { AuthGuard } from '@nestjs/passport'
3 |
4 | @Injectable()
5 | export class JwtRefreshAuthGuard extends AuthGuard('jwt-refresh') {}
6 |
--------------------------------------------------------------------------------
/packages/server/src/modules/file/dto/upload-url.dto.ts:
--------------------------------------------------------------------------------
1 | import { Field, ObjectType } from '@nestjs/graphql'
2 |
3 | @ObjectType()
4 | export class UploadUrl {
5 | @Field(() => String)
6 | url: string
7 |
8 | @Field(() => String)
9 | key: string
10 | }
11 |
--------------------------------------------------------------------------------
/packages/server/src/modules/file/enum/upload-type.enum.ts:
--------------------------------------------------------------------------------
1 | import { registerEnumType } from '@nestjs/graphql'
2 |
3 | export enum UploadType {
4 | PROFILE = 'PROFILE',
5 | PROJECT = 'PROJECT',
6 | SCHOOL = 'SCHOOL',
7 | CAREER = 'CAREER',
8 | }
9 |
10 | registerEnumType(UploadType, {
11 | name: 'UploadType',
12 | })
13 |
--------------------------------------------------------------------------------
/packages/server/src/modules/file/file.entity.ts:
--------------------------------------------------------------------------------
1 | import { Field, ObjectType } from '@nestjs/graphql'
2 | import {
3 | Column,
4 | Entity,
5 | JoinColumn,
6 | ManyToOne,
7 | PrimaryGeneratedColumn,
8 | } from 'typeorm'
9 | import { User } from '../user/user.entity'
10 |
11 | @ObjectType()
12 | @Entity({ name: 'files' })
13 | export class File {
14 | @Field(() => String)
15 | @PrimaryGeneratedColumn('uuid')
16 | id: string
17 |
18 | @Field(() => String, { nullable: true })
19 | @Column({ name: 'owner_id', type: 'uuid' })
20 | ownerId?: string
21 |
22 | @Field(() => User, { nullable: true })
23 | @ManyToOne(() => User, { nullable: true })
24 | @JoinColumn({ name: 'owner_id' })
25 | owner?: User
26 |
27 | @Field(() => String)
28 | @Column()
29 | key: string
30 |
31 | @Field(() => Boolean)
32 | @Column({ name: 'is_private', type: 'boolean', default: false })
33 | isPrivate: boolean
34 | }
35 |
--------------------------------------------------------------------------------
/packages/server/src/modules/file/file.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { TypeOrmModule } from '@nestjs/typeorm'
3 | import { File } from './file.entity'
4 | import { FileResolver } from './file.resolver'
5 | import { FileService } from './file.service'
6 |
7 | @Module({
8 | imports: [TypeOrmModule.forFeature([File])],
9 | providers: [FileService, FileResolver],
10 | exports: [FileService],
11 | })
12 | export class FileModule {}
13 |
--------------------------------------------------------------------------------
/packages/server/src/modules/health/health.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get } from '@nestjs/common'
2 | import {
3 | HealthCheckService,
4 | HealthCheck,
5 | TypeOrmHealthIndicator,
6 | } from '@nestjs/terminus'
7 |
8 | @Controller('health')
9 | export class HealthController {
10 | constructor(
11 | private health: HealthCheckService,
12 | private db: TypeOrmHealthIndicator
13 | ) {}
14 |
15 | @Get()
16 | @HealthCheck()
17 | check() {
18 | return this.health.check([async () => this.db.pingCheck('typeorm')])
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/server/src/modules/health/health.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { TerminusModule } from '@nestjs/terminus'
3 | import { HealthController } from './health.controller'
4 |
5 | @Module({
6 | imports: [TerminusModule],
7 | controllers: [HealthController],
8 | })
9 | export class HealthModule {}
10 |
--------------------------------------------------------------------------------
/packages/server/src/modules/page/enum/language-type.enum.ts:
--------------------------------------------------------------------------------
1 | import { registerEnumType } from '@nestjs/graphql'
2 |
3 | export enum LanguageType {
4 | KO = 'KO',
5 | EN = 'EN',
6 | ZH = 'ZH',
7 | JA = 'JA',
8 | DE = 'DE',
9 | FE = 'FE',
10 | ES = 'ES',
11 | RU = 'RU',
12 | }
13 |
14 | registerEnumType(LanguageType, {
15 | name: 'LanguageType',
16 | valuesMap: {
17 | KO: { description: '한국어' },
18 | EN: { description: '영어' },
19 | ZH: { description: '중국어' },
20 | JA: { description: '일본어' },
21 | DE: { description: '독일' },
22 | FE: { description: '프랑스' },
23 | ES: { description: '스페인' },
24 | RU: { description: '러시아' },
25 | },
26 | })
27 |
--------------------------------------------------------------------------------
/packages/server/src/modules/page/input/update-page.input.ts:
--------------------------------------------------------------------------------
1 | import { InputType, PickType, PartialType } from '@nestjs/graphql'
2 | import { Page } from '../page.entity'
3 |
4 | @InputType()
5 | export class UpdatePageInput extends PartialType(
6 | PickType(Page, ['content', 'slug', 'title', 'gtag', 'language'] as const),
7 | InputType
8 | ) {}
9 |
--------------------------------------------------------------------------------
/packages/server/src/modules/page/page.module.ts:
--------------------------------------------------------------------------------
1 | import { forwardRef, Module } from '@nestjs/common'
2 | import { TypeOrmModule } from '@nestjs/typeorm'
3 | import { UserModule } from '../user/user.module'
4 | import { Page } from './page.entity'
5 | import { PageResolver } from './page.resolver'
6 | import { PageService } from './page.service'
7 |
8 | @Module({
9 | imports: [TypeOrmModule.forFeature([Page]), forwardRef(() => UserModule)],
10 | providers: [PageResolver, PageService],
11 | exports: [PageService],
12 | })
13 | export class PageModule {}
14 |
--------------------------------------------------------------------------------
/packages/server/src/modules/school/dto/cursor-paginated-school.dto.ts:
--------------------------------------------------------------------------------
1 | import { ObjectType } from '@nestjs/graphql'
2 | import { Paginated } from '../../../common/pagination/paginated'
3 | import { School } from '../school.entity'
4 |
5 | @ObjectType()
6 | export class CursorPaginatedSchool extends Paginated(School, 'cursor') {}
7 |
--------------------------------------------------------------------------------
/packages/server/src/modules/school/input/school-filter.input.ts:
--------------------------------------------------------------------------------
1 | import { Field, InputType } from '@nestjs/graphql'
2 |
3 | @InputType()
4 | export class SchoolFilterInput {
5 | @Field(() => String)
6 | keyword?: string
7 | }
8 |
--------------------------------------------------------------------------------
/packages/server/src/modules/school/school.entity.ts:
--------------------------------------------------------------------------------
1 | import { Field, Int, ObjectType } from '@nestjs/graphql'
2 | import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
3 |
4 | @ObjectType()
5 | @Entity({
6 | name: 'schools',
7 | })
8 | export class School {
9 | @Field(() => Int)
10 | @PrimaryGeneratedColumn('increment')
11 | id: string
12 |
13 | @Field(() => String)
14 | @Column({ type: 'varchar', length: 255 })
15 | name: string
16 |
17 | @Field(() => String)
18 | @Column({ type: 'varchar', length: 255 })
19 | logo: string
20 | }
21 |
--------------------------------------------------------------------------------
/packages/server/src/modules/school/school.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common'
2 | import { TypeOrmModule } from '@nestjs/typeorm'
3 | import { School } from './school.entity'
4 | import { SchoolResolver } from './school.resolver'
5 | import { SchoolService } from './school.service'
6 |
7 | @Module({
8 | imports: [TypeOrmModule.forFeature([School])],
9 | providers: [SchoolResolver, SchoolService],
10 | })
11 | export class SchoolModule {}
12 |
--------------------------------------------------------------------------------
/packages/server/src/modules/school/school.resolver.ts:
--------------------------------------------------------------------------------
1 | import { Args, Query, Resolver } from '@nestjs/graphql'
2 | import { CursorPaginationArgs } from '../../common/pagination/pagination.args'
3 | import { CursorPaginatedSchool } from './dto/cursor-paginated-school.dto'
4 | import { SchoolFilterInput } from './input/school-filter.input'
5 | import { School } from './school.entity'
6 | import { SchoolService } from './school.service'
7 |
8 | @Resolver(() => School)
9 | export class SchoolResolver {
10 | constructor(private readonly schoolService: SchoolService) {}
11 |
12 | @Query(() => CursorPaginatedSchool)
13 | async getSchoolsByCursor(
14 | @Args() paginationArgs: CursorPaginationArgs,
15 | @Args('filter', { nullable: true }) filter?: SchoolFilterInput
16 | ) {
17 | return await this.schoolService.findByCursor(paginationArgs, filter)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/server/src/modules/school/school.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common'
2 | import { InjectRepository } from '@nestjs/typeorm'
3 | import { Repository } from 'typeorm'
4 | import { cursorPaginate } from '../../common/pagination/cursor-paginate'
5 | import { CursorPaginationArgs } from '../../common/pagination/pagination.args'
6 | import { SchoolFilterInput } from './input/school-filter.input'
7 | import { School } from './school.entity'
8 |
9 | @Injectable()
10 | export class SchoolService {
11 | constructor(
12 | @InjectRepository(School)
13 | private readonly schoolRepository: Repository
14 | ) {}
15 |
16 | async findByCursor(
17 | paginationArgs: CursorPaginationArgs,
18 | filter?: SchoolFilterInput
19 | ) {
20 | const qb = this.schoolRepository.createQueryBuilder('school')
21 |
22 | if (filter?.keyword) {
23 | qb.where(`school.name ILIKE :keyword`, {
24 | keyword: `%${filter.keyword}%`,
25 | })
26 | }
27 |
28 | return await cursorPaginate(School, qb, paginationArgs, ['name', 'id'])
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/server/src/modules/user/decorator/current-user.decorator.ts:
--------------------------------------------------------------------------------
1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common'
2 | import { GqlExecutionContext } from '@nestjs/graphql'
3 |
4 | export const CurrentUser = createParamDecorator(
5 | (data: unknown, context: ExecutionContext) => {
6 | const ctx = GqlExecutionContext.create(context)
7 |
8 | return ctx.getContext().req.user
9 | }
10 | )
11 |
--------------------------------------------------------------------------------
/packages/server/src/modules/user/enum/provider-type.enum.ts:
--------------------------------------------------------------------------------
1 | import { registerEnumType } from '@nestjs/graphql'
2 |
3 | export enum ProviderType {
4 | GITHUB = 'GITHUB',
5 | FACEBOOK = 'FACEBOOK',
6 | GOOGLE = 'GOOGLE',
7 | }
8 |
9 | registerEnumType(ProviderType, {
10 | name: 'ProviderType',
11 | })
12 |
--------------------------------------------------------------------------------
/packages/server/src/modules/user/input/create-user.input.ts:
--------------------------------------------------------------------------------
1 | import { InputType, PickType } from '@nestjs/graphql'
2 | import { User } from '../user.entity'
3 |
4 | @InputType()
5 | export class CreateUserInput extends PickType(
6 | User,
7 | ['avatar', 'email', 'name', 'providerId', 'provider', 'pageId'] as const,
8 | InputType
9 | ) {}
10 |
--------------------------------------------------------------------------------
/packages/server/src/modules/user/input/update-user.input.ts:
--------------------------------------------------------------------------------
1 | import { InputType, PartialType, PickType } from '@nestjs/graphql'
2 | import { User } from '../user.entity'
3 |
4 | @InputType()
5 | export class UpdateUserInput extends PartialType(
6 | PickType(User, ['avatar', 'email', 'name'] as const),
7 | InputType
8 | ) {}
9 |
--------------------------------------------------------------------------------
/packages/server/src/modules/user/user.module.ts:
--------------------------------------------------------------------------------
1 | import { forwardRef, Module } from '@nestjs/common'
2 | import { TypeOrmModule } from '@nestjs/typeorm'
3 | import { PageModule } from '../page/page.module'
4 | import { User } from './user.entity'
5 | import { UserResolver } from './user.resolver'
6 | import { UserService } from './user.service'
7 |
8 | @Module({
9 | imports: [TypeOrmModule.forFeature([User]), forwardRef(() => PageModule)],
10 | providers: [UserService, UserResolver],
11 | controllers: [],
12 | exports: [UserService],
13 | })
14 | export class UserModule {}
15 |
--------------------------------------------------------------------------------
/packages/server/src/modules/user/user.resolver.ts:
--------------------------------------------------------------------------------
1 | import { UseGuards } from '@nestjs/common'
2 | import { Parent, Query, ResolveField, Resolver } from '@nestjs/graphql'
3 | import { GqlAuthGuard } from '../auth/graphql/gql-auth.guard'
4 | import { Page } from '../page/page.entity'
5 | import { PageService } from '../page/page.service'
6 | import { CurrentUser } from './decorator/current-user.decorator'
7 | import { User } from './user.entity'
8 | import { UserService } from './user.service'
9 |
10 | @Resolver(() => User)
11 | export class UserResolver {
12 | constructor(
13 | private readonly userService: UserService,
14 | private readonly pageService: PageService
15 | ) {}
16 |
17 | @Query(() => User)
18 | @UseGuards(GqlAuthGuard)
19 | async me(@CurrentUser() user: User) {
20 | return await this.userService.findOne(user.id)
21 | }
22 |
23 | @ResolveField(() => Page)
24 | async page(@Parent() user: User) {
25 | return this.pageService.findOne(user.pageId)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/server/src/types/express.d.ts:
--------------------------------------------------------------------------------
1 | import { User as UserEntity } from 'src/modules/user/user.entity'
2 |
3 | declare global {
4 | namespace Express {
5 | // eslint-disable-next-line @typescript-eslint/no-empty-interface
6 | interface User extends UserEntity {}
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/server/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing'
2 | import { INestApplication } from '@nestjs/common'
3 | import request from 'supertest'
4 | import { AppModule } from './../src/app.module'
5 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AppModule],
12 | }).compile()
13 |
14 | app = moduleFixture.createNestApplication()
15 | await app.init()
16 | })
17 |
18 | it('/ (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!')
23 | })
24 | })
25 |
--------------------------------------------------------------------------------
/packages/server/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".e2e-spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/server/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "target": "es2017",
10 | "sourceMap": true,
11 | "outDir": "./dist",
12 | "strict": true,
13 | "strictPropertyInitialization": false,
14 | "esModuleInterop": true,
15 | "skipLibCheck": true
16 | }
17 | }
18 |
--------------------------------------------------------------------------------