├── .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 | DeveloFolio Logo 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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/bold.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/font-color.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/github-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/highlight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/icons/italic.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/linkedin-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/icons/pencil.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/playstore.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/icons/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/stack-overflow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /packages/client/public/icons/stackoverflow-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/icons/velog-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /packages/client/public/icons/verified.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/akka.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/appdynamics.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/appium.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/autoprefixer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/aws-glacier.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/client/public/logos/aws-quicksight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/aws-rds.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/aws-ses.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/bing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/buildkite-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/bulma.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/campaignmonitor-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/cockpit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/codebeat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/coderwall.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/client/public/logos/codesandbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/codrops.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/crateio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/crystal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/css-3_official.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/dropbox.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/client/public/logos/dropmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/client/public/logos/ello.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/elm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/client/public/logos/embedly.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/emmet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/ethereum.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/client/public/logos/evergreen-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/client/public/logos/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/figma.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/framework7-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/fsharp.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/gitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/client/public/logos/gleam.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/client/public/logos/google-ads.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/google-keep.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/gravatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/hack.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/client/public/logos/hashnode-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/haskell-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/haxl.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/hhvm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/client/public/logos/hibernate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/highcharts.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/client/public/logos/hosted-graphite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/houndci.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/infer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/jamstack-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/jhipster-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/kemal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/kibana.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/kickstarter-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/kirby-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/leveldb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/lighttpd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/logstash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/losant.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/mapbox-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/material-ui.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/microsoft-windows.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/mixmax.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/client/public/logos/modernizr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/modx-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/monero.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/mparticle-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/neat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/nodal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/nomad.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/client/public/logos/npm-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/npm.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/packer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/pagekit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/pagekite.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/passport.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/patreon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/precursor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/productboard-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/progress.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/puppet-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/rax.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/rubygems.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/sails.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/scaledrone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/client/public/logos/serverless.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/survicate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/teamgrid.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/terraform-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/trello.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/client/public/logos/tumblr-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/turret.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /packages/client/public/logos/twitch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/typo3-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/uikit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/vector-timber.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/vercel-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/vue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/vuetifyjs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/client/public/logos/web-fundamentals.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/webtask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/client/public/logos/whalar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/wicket-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/workboard.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/youtube-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /packages/client/public/logos/zendesk-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/zenhub-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /packages/client/public/logos/zorin-os.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/client/public/logos/zube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 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 {alt} 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 |
    25 | {children} 26 |
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 | {element.name} 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 | --------------------------------------------------------------------------------