├── tools ├── generators │ └── .gitkeep ├── tsconfig.tools.json └── ignore-vercel-build.sh ├── Procfile ├── packages ├── linx-next │ ├── public │ │ ├── .gitkeep │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── maskable_icon.png │ │ ├── apple-touch-icon.png │ │ ├── mstile-150x150.png │ │ ├── Lynx - icon large.png │ │ ├── maskable_icon_x128.png │ │ ├── maskable_icon_x192.png │ │ ├── maskable_icon_x512.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── images │ │ │ └── linkgroupDefaultBackground.png │ │ ├── fallback-Xdc_5gJKddz2G953z4CJc.js │ │ ├── browserconfig.xml │ │ ├── head.html │ │ ├── Lynx logo.svg │ │ └── site.webmanifest │ ├── containers │ │ ├── SignIn │ │ │ ├── index.ts │ │ │ ├── SignIn.styled.ts │ │ │ └── SignIn.tsx │ │ ├── SignUp │ │ │ ├── index.ts │ │ │ └── SignUp.styled.ts │ │ ├── MainFeed │ │ │ ├── index.ts │ │ │ └── MainFeed.styled.ts │ │ ├── LandingPage │ │ │ ├── index.ts │ │ │ ├── LandingPage.styled.ts │ │ │ └── LandingPage.tsx │ │ └── StatsPage │ │ │ ├── index.ts │ │ │ ├── StatsPage.tsx │ │ │ └── StatsPage.styled.ts │ ├── layouts │ │ ├── Footer │ │ │ ├── index.tsx │ │ │ ├── Footer.styled.ts │ │ │ └── Footer.tsx │ │ ├── Header │ │ │ ├── index.tsx │ │ │ ├── Header.styled.ts │ │ │ └── Header.tsx │ │ ├── UserNav │ │ │ ├── index.ts │ │ │ ├── UserNav.styled.ts │ │ │ └── UserNav.tsx │ │ ├── AuthLayout │ │ │ ├── index.tsx │ │ │ └── AuthLayout.tsx │ │ └── MainLayout │ │ │ ├── index.tsx │ │ │ └── MainLayout.tsx │ ├── components │ │ ├── Button │ │ │ ├── index.ts │ │ │ ├── Button.tsx │ │ │ ├── Button.test.tsx │ │ │ └── Button.styled.ts │ │ ├── TagList │ │ │ ├── index.ts │ │ │ ├── TagList.styled.ts │ │ │ ├── TagList.tsx │ │ │ └── TagList.test.tsx │ │ ├── ErrorPanel │ │ │ ├── index.ts │ │ │ ├── ErrorPanel.styled.ts │ │ │ └── ErrorPanel.tsx │ │ ├── ReviewForm │ │ │ ├── index.ts │ │ │ └── ReviewForm.styled.ts │ │ ├── SearchBar │ │ │ ├── index.ts │ │ │ ├── SearchBar.tsx │ │ │ └── SearchBar.styled.ts │ │ ├── StatPill │ │ │ ├── index.ts │ │ │ ├── StatPill.tsx │ │ │ └── StatPill.styled.ts │ │ ├── LinkGroupBody │ │ │ ├── index.ts │ │ │ ├── LinkGroupBody.styled.ts │ │ │ └── LinkGroupBody.tsx │ │ ├── LinkGroupForm │ │ │ ├── index.ts │ │ │ ├── LinkGroupForm.styled.ts │ │ │ └── LinkGroupForm.tsx │ │ ├── LogoAppName │ │ │ ├── index.ts │ │ │ ├── LogoAppName.styled.ts │ │ │ └── LogoAppName.tsx │ │ ├── LynxInfoPanel │ │ │ ├── index.ts │ │ │ ├── LynxInfoPanel.tsx │ │ │ └── LynxInfoPanel.styled.ts │ │ ├── ReviewStars │ │ │ ├── index.ts │ │ │ ├── ReviewStars.styled.ts │ │ │ ├── ReviewStars.tsx │ │ │ └── ReviewStars.test.tsx │ │ ├── SocialButton │ │ │ ├── index.ts │ │ │ ├── SocialButton.tsx │ │ │ └── SocialButton.styled.ts │ │ ├── UserDropdown │ │ │ ├── index.ts │ │ │ ├── UserDropdown.tsx │ │ │ └── UserDropdown.styled.ts │ │ ├── AuthLinkFlavor │ │ │ ├── index.ts │ │ │ ├── AuthLinkFlavor.tsx │ │ │ └── AuthLinkFlavor.styled.ts │ │ ├── CreateLinkGroup │ │ │ └── index.ts │ │ ├── ExpandingButton │ │ │ ├── index.ts │ │ │ ├── ExpandingButton.test.tsx │ │ │ └── ExpandingButton.tsx │ │ ├── LinkComponent │ │ │ ├── index.ts │ │ │ ├── LinkComponent.styled.ts │ │ │ ├── LinkComponent.test.tsx │ │ │ └── LinkComponent.tsx │ │ ├── LinkGroupDisplay │ │ │ ├── index.ts │ │ │ ├── LinkGroupDisplay.styled.ts │ │ │ └── LinkGroupDisplay.tsx │ │ ├── LinkGroupHeader │ │ │ ├── index.ts │ │ │ ├── LinkGroupHeader.test.tsx │ │ │ ├── LinkGroupHeader.styled.ts │ │ │ └── LinkGroupHeader.tsx │ │ ├── ReviewComponent │ │ │ ├── index.ts │ │ │ ├── ReviewComponent.styled.ts │ │ │ └── ReviewComponent.tsx │ │ ├── GithubLoginButton │ │ │ ├── index.ts │ │ │ └── GithubLoginButton.tsx │ │ ├── GoogleLoginButton │ │ │ ├── index.ts │ │ │ └── GoogleLoginButton.tsx │ │ ├── ServiceRouteLinks │ │ │ ├── index.ts │ │ │ ├── ServiceRouteLinks.tsx │ │ │ └── ServiceRouteLinks.styled.ts │ │ ├── SpecialBackground │ │ │ ├── index.tsx │ │ │ └── SpecialBackground.styled.tsx │ │ └── Text │ │ │ └── Text.styled.tsx │ ├── specs │ │ └── setupTests.ts │ ├── styles │ │ ├── breakpoints.scss │ │ └── global.scss │ ├── index.d.ts │ ├── pages │ │ ├── settings │ │ │ └── profile.tsx │ │ ├── api │ │ │ ├── logout.ts │ │ │ ├── auth.ts │ │ │ └── revalidate.ts │ │ ├── index.tsx │ │ ├── signup.tsx │ │ ├── signin.tsx │ │ ├── 404.tsx │ │ ├── 500.tsx │ │ ├── _offline.tsx │ │ ├── new.tsx │ │ ├── stats.tsx │ │ ├── explore.tsx │ │ ├── t │ │ │ ├── all.tsx │ │ │ └── [tag].tsx │ │ ├── _app.tsx │ │ ├── u │ │ │ ├── [user].tsx │ │ │ └── [user] │ │ │ │ └── [group].tsx │ │ └── _document.tsx │ ├── next-env.d.ts │ ├── styled.d.ts │ ├── api │ │ ├── revalidate.ts │ │ ├── tag.ts │ │ ├── review.ts │ │ ├── link.ts │ │ ├── linkgroup.ts │ │ └── user.ts │ ├── assets │ │ └── icons │ │ │ ├── Trash.tsx │ │ │ ├── OpenInNewTab.tsx │ │ │ ├── AddIcon.tsx │ │ │ ├── ChevronDown.tsx │ │ │ ├── Eye.tsx │ │ │ ├── Lock.tsx │ │ │ ├── CheckIcon.tsx │ │ │ ├── GithubOutlineIcon.tsx │ │ │ ├── index.ts │ │ │ ├── LinkIcon.tsx │ │ │ ├── StarEmpty.tsx │ │ │ ├── StarFull.tsx │ │ │ ├── LynxLogoDetailNoCircleSmallBox.tsx │ │ │ ├── LogoSmall.tsx │ │ │ ├── LynxLogoDetailNoCircle.tsx │ │ │ ├── LynxLogoDetail.tsx │ │ │ ├── GithubIcon.tsx │ │ │ ├── WatchersIcon.tsx │ │ │ ├── NoConnectionIcon.tsx │ │ │ └── LinkedAmountIcon.tsx │ ├── jest.config.ts │ ├── tsconfig.spec.json │ ├── hooks │ │ └── useOutside.tsx │ ├── .eslintrc.json │ ├── helpers │ │ └── fetcher.ts │ ├── tsconfig.json │ ├── auth │ │ └── AuthGate.tsx │ ├── next.config.js │ └── project.json ├── linx-next-e2e │ ├── src │ │ ├── support │ │ │ ├── app.po.ts │ │ │ ├── index.ts │ │ │ └── commands.ts │ │ ├── fixtures │ │ │ └── example.json │ │ └── integration │ │ │ └── app.spec.ts │ ├── tsconfig.json │ ├── .eslintrc.json │ ├── cypress.json │ └── project.json └── api │ ├── src │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── assets │ │ └── favicon.ico │ ├── app │ │ ├── lib │ │ │ ├── db.ts │ │ │ └── redis.ts │ │ ├── routes │ │ │ ├── usergroup │ │ │ │ └── index.ts │ │ │ ├── stats │ │ │ │ └── index.ts │ │ │ ├── auth │ │ │ │ ├── google.ts │ │ │ │ ├── github.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── review │ │ │ │ └── index.ts │ │ │ ├── user │ │ │ │ └── index.ts │ │ │ ├── tag │ │ │ │ └── index.ts │ │ │ ├── link │ │ │ │ └── index.ts │ │ │ └── linkgroup │ │ │ │ └── index.ts │ │ ├── controllers │ │ │ ├── stats │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── auth │ │ │ │ ├── logout.ts │ │ │ │ ├── me.ts │ │ │ │ ├── signin.ts │ │ │ │ ├── signup.ts │ │ │ │ └── google.ts │ │ │ ├── tag │ │ │ │ └── index.ts │ │ │ ├── user │ │ │ │ └── index.ts │ │ │ └── review │ │ │ │ └── index.ts │ │ ├── middlewares │ │ │ ├── measureRequest │ │ │ │ └── index.ts │ │ │ ├── auth │ │ │ │ ├── requireUser.ts │ │ │ │ └── deserializeUser.ts │ │ │ ├── cors │ │ │ │ └── index.ts │ │ │ ├── cache │ │ │ │ └── index.ts │ │ │ └── rateLimit │ │ │ │ └── index.ts │ │ ├── helpers │ │ │ ├── logger.ts │ │ │ ├── redis.ts │ │ │ ├── cookie.ts │ │ │ ├── jwt.ts │ │ │ ├── utilsJS.ts │ │ │ ├── pushDiscordWebhook.ts │ │ │ └── authorizeAndEnd.ts │ │ └── services │ │ │ ├── stats.ts │ │ │ ├── review.ts │ │ │ ├── user.types.ts │ │ │ ├── tag.ts │ │ │ ├── session.ts │ │ │ └── link.ts │ ├── interfaces │ │ └── index.ts │ └── main.ts │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tsconfig.app.json │ ├── .eslintrc.json │ ├── jest.config.ts │ └── project.json ├── .prettierrc ├── Banner.png ├── .prettierignore ├── jest.preset.ts ├── jest.preset.js ├── dist └── packages │ └── api │ └── assets │ └── favicon.ico ├── jest.config.ts ├── workspace.json ├── .vscode └── extensions.json ├── .env.example ├── .editorconfig ├── tsconfig.base.json ├── .gitignore ├── nx.json ├── .eslintrc.json ├── README.md └── lynx-logo.svg /tools/generators/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: npm run start 2 | -------------------------------------------------------------------------------- /packages/linx-next/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /Banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/Banner.png -------------------------------------------------------------------------------- /packages/linx-next/containers/SignIn/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './SignIn'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/containers/SignUp/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './SignUp'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/Footer/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Footer'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/Header/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './Header'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/Button/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Button'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/TagList/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './TagList'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/containers/MainFeed/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './MainFeed'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/UserNav/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './UserNav'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/ErrorPanel/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './ErrorPanel'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/ReviewForm/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ReviewForm'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/SearchBar/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './SearchBar'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/StatPill/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './StatPill'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/containers/LandingPage/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './LandingPage'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/containers/StatsPage/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './StatsPage'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/AuthLayout/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './AuthLayout'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/MainLayout/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './MainLayout'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/specs/setupTests.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/extend-expect'; 2 | -------------------------------------------------------------------------------- /packages/linx-next-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/LinkGroupBody/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './LinkGroupBody'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/LinkGroupForm/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './LinkGroupForm'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/LogoAppName/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './LogoAppName'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/LynxInfoPanel/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './LynxInfoPanel'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/ReviewStars/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ReviewStars'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/SocialButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './SocialButton'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/UserDropdown/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './UserDropdown'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/UserNav/UserNav.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/AuthLinkFlavor/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './AuthLinkFlavor'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/CreateLinkGroup/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './CreateLinkGroup'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/ExpandingButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ExpandingButton'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/LinkComponent/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './LinkComponent'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/LinkGroupDisplay/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './LinkGroupDisplay'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/LinkGroupHeader/index.ts: -------------------------------------------------------------------------------- 1 | export {default} from './LinkGroupHeader'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/ReviewComponent/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ReviewComponent'; 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /jest.preset.ts: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset'); 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /packages/linx-next/components/GithubLoginButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './GithubLoginButton'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/GoogleLoginButton/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './GoogleLoginButton'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/ServiceRouteLinks/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ServiceRouteLinks'; 2 | -------------------------------------------------------------------------------- /packages/linx-next/components/SpecialBackground/index.tsx: -------------------------------------------------------------------------------- 1 | export { default } from './SpecialBackground'; 2 | -------------------------------------------------------------------------------- /packages/api/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /packages/api/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | }; 4 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nrwl/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /dist/packages/api/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/dist/packages/api/assets/favicon.ico -------------------------------------------------------------------------------- /packages/api/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/api/src/assets/favicon.ico -------------------------------------------------------------------------------- /packages/linx-next/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/favicon.ico -------------------------------------------------------------------------------- /packages/linx-next/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/favicon-16x16.png -------------------------------------------------------------------------------- /packages/linx-next/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/favicon-32x32.png -------------------------------------------------------------------------------- /packages/linx-next/public/maskable_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/maskable_icon.png -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | const { getJestProjects } = require('@nrwl/jest'); 2 | 3 | module.exports = { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /packages/linx-next/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/apple-touch-icon.png -------------------------------------------------------------------------------- /packages/linx-next/public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/mstile-150x150.png -------------------------------------------------------------------------------- /packages/linx-next-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /packages/linx-next/public/Lynx - icon large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/Lynx - icon large.png -------------------------------------------------------------------------------- /packages/linx-next/public/maskable_icon_x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/maskable_icon_x128.png -------------------------------------------------------------------------------- /packages/linx-next/public/maskable_icon_x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/maskable_icon_x192.png -------------------------------------------------------------------------------- /packages/linx-next/public/maskable_icon_x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/maskable_icon_x512.png -------------------------------------------------------------------------------- /packages/api/src/app/lib/db.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client'; 2 | 3 | const db = new PrismaClient(); 4 | 5 | export default db; 6 | -------------------------------------------------------------------------------- /packages/linx-next/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /packages/linx-next/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /packages/api/src/app/routes/usergroup/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'hyper-express'; 2 | 3 | const userGroupRouter = new Router(); 4 | 5 | export default userGroupRouter; 6 | -------------------------------------------------------------------------------- /packages/linx-next/public/images/linkgroupDefaultBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/net-runner/lynx/HEAD/packages/linx-next/public/images/linkgroupDefaultBackground.png -------------------------------------------------------------------------------- /packages/linx-next/public/fallback-Xdc_5gJKddz2G953z4CJc.js: -------------------------------------------------------------------------------- 1 | (()=>{"use strict";self.fallback=async e=>"document"===e.destination?caches.match("/_offline",{ignoreSearch:!0}):Response.error()})(); -------------------------------------------------------------------------------- /packages/linx-next/styles/breakpoints.scss: -------------------------------------------------------------------------------- 1 | //Predefined Break-points 2 | $mediaMaxWidth: 1260px; 3 | $mediaBp1Width: 960px; 4 | $mediaMinWidth: 480px; 5 | 6 | @mixin breakpoint($size) {} 7 | -------------------------------------------------------------------------------- /workspace.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "projects": { 4 | "api": "packages/api", 5 | "linx-next": "packages/linx-next", 6 | "linx-next-e2e": "packages/linx-next-e2e" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "firsttris.vscode-jest-runner", 6 | "dbaeumer.vscode-eslint" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/linx-next/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module '*.svg' { 3 | const content: any; 4 | export const ReactComponent: any; 5 | export default content; 6 | } 7 | -------------------------------------------------------------------------------- /packages/linx-next/pages/settings/profile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | //Page : User settings for profile 4 | const Profile = () => { 5 | return
Profile
; 6 | }; 7 | Profile.requireAuth = true; 8 | export default Profile; 9 | -------------------------------------------------------------------------------- /packages/linx-next/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /packages/api/src/app/routes/stats/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'hyper-express'; 2 | import handleStats from '../../controllers/stats'; 3 | 4 | const statRouter = new Router(); 5 | 6 | statRouter.get('/', handleStats); 7 | 8 | export default statRouter; 9 | -------------------------------------------------------------------------------- /packages/linx-next/containers/MainFeed/MainFeed.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | flex-direction: column; 8 | `; 9 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | DATABASE_URL= 2 | API_URL= 3 | GITHUB_APP_SECRET= 4 | GITHUB_APP_ID= 5 | FRONTEND_URL= 6 | DISCORD_WEBHOOK_URL= 7 | GITHUB_HOOK_SECRET= 8 | GOOGLE_APP_ID= 9 | GOOGLE_APP_SECRET= 10 | AUTH_CORE_SECRET= 11 | COOKIE_NAME= 12 | REDIS_TLS_URL= 13 | REDIS_URL= 14 | -------------------------------------------------------------------------------- /packages/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.app.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/linx-next/styled.d.ts: -------------------------------------------------------------------------------- 1 | import 'styled-components'; 2 | import { CustomTheme } from './pages/_app'; 3 | 4 | declare module 'styled-components' { 5 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 6 | export interface DefaultTheme extends CustomTheme {} 7 | } 8 | -------------------------------------------------------------------------------- /packages/api/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["jest.config.ts", "**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/linx-next/api/revalidate.ts: -------------------------------------------------------------------------------- 1 | export async function revalidate(route: string) { 2 | const res = await fetch(`${process.env.FRONTEND_URL}api/revalidate`, { 3 | method: 'POST', 4 | body: JSON.stringify({ 5 | refresh_route: route, 6 | }), 7 | }); 8 | return res; 9 | } 10 | -------------------------------------------------------------------------------- /packages/linx-next-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/api/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["node"] 7 | }, 8 | "exclude": ["jest.config.ts", "**/*.spec.ts", "**/*.test.ts"], 9 | "include": ["**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/linx-next/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 00A8AD 7 | 8 | 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /packages/api/src/app/controllers/stats/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultRouteHandler } from '../../../interfaces'; 2 | import { getAllStats } from '../../services/stats'; 3 | 4 | const handleStats: defaultRouteHandler = async (req, res) => { 5 | const stats = await getAllStats(); 6 | return res.status(200).json(stats); 7 | }; 8 | 9 | export default handleStats; 10 | -------------------------------------------------------------------------------- /packages/linx-next/components/SearchBar/SearchBar.tsx: -------------------------------------------------------------------------------- 1 | import { useHotkeys } from 'react-hotkeys-hook'; 2 | import * as S from './SearchBar.styled'; 3 | 4 | const SearchBar = () => { 5 | //Capute pressing / or CTRL+K for focus 6 | useHotkeys('/, ctrl+k, command+k', () => { 7 | return; 8 | }); 9 | 10 | return <>Sb; 11 | }; 12 | export default SearchBar; 13 | -------------------------------------------------------------------------------- /packages/linx-next/components/LogoAppName/LogoAppName.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const LAN = styled.div` 4 | height: 3rem; 5 | display: flex; 6 | align-items: center; 7 | flex-direction: row; 8 | 9 | & > p { 10 | margin-left: 0.5rem; 11 | font-size: 2.8rem; 12 | font-family: 'Open Sans', sans-serif; 13 | } 14 | `; 15 | -------------------------------------------------------------------------------- /packages/linx-next/pages/api/logout.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next'; 2 | import Cookie from 'cookies'; 3 | export default function handler(req: NextApiRequest, res: NextApiResponse) { 4 | const cookies = new Cookie(req, res); 5 | 6 | cookies.set('access_token'); 7 | cookies.set('refresh_token'); 8 | 9 | res.status(200).end(); 10 | } 11 | -------------------------------------------------------------------------------- /packages/linx-next/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react'; 2 | import AuthLayout from '../layouts/AuthLayout/AuthLayout'; 3 | import LandingPage from '../containers/LandingPage'; 4 | 5 | const Index = () => ; 6 | 7 | Index.getLayout = (page: ReactElement) => { 8 | return {page}; 9 | }; 10 | 11 | export default Index; 12 | -------------------------------------------------------------------------------- /packages/api/src/app/middlewares/measureRequest/index.ts: -------------------------------------------------------------------------------- 1 | import log from '../../helpers/logger'; 2 | 3 | export const measureRequest = (req, res, next) => { 4 | const start = Date.now(); 5 | res.once('finish', () => { 6 | const duration = Date.now() - start; 7 | log.info('Req ' + req.originalUrl + ' processed in: ' + duration + 'ms'); 8 | }); 9 | next(); 10 | }; 11 | -------------------------------------------------------------------------------- /packages/api/src/app/helpers/logger.ts: -------------------------------------------------------------------------------- 1 | import logger from 'pino'; 2 | import * as dayjs from 'dayjs'; 3 | 4 | const log = logger({ 5 | transport: { 6 | target: 'pino-pretty', 7 | options: { 8 | colorize: true, 9 | }, 10 | }, 11 | base: { 12 | pid: false, 13 | }, 14 | timestamp: () => `,"time":"${dayjs().format()}"`, 15 | }); 16 | 17 | export default log; 18 | -------------------------------------------------------------------------------- /packages/api/src/app/lib/redis.ts: -------------------------------------------------------------------------------- 1 | import log from '../helpers/logger'; 2 | import { createClient } from '@redis/client'; 3 | 4 | const { REDIS_URL } = process.env; 5 | 6 | const redisClient = createClient({ url: REDIS_URL }); 7 | 8 | redisClient.on('ready', () => log.info('[REDIS] Connected')); 9 | redisClient.on('error', (e) => log.error('[REDIS] ' + e)); 10 | 11 | export default redisClient; 12 | -------------------------------------------------------------------------------- /packages/linx-next/pages/api/auth.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next'; 2 | export default function handler(req: NextApiRequest, res: NextApiResponse) { 3 | const access_token = req.cookies['access_token']; 4 | const refresh_token = req.cookies['refresh_token']; 5 | res 6 | .status(200) 7 | .json({ hasAuthCookies: access_token || refresh_token ? true : false }); 8 | } 9 | -------------------------------------------------------------------------------- /packages/linx-next/components/ReviewForm/ReviewForm.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const StarContainer = styled.div` 4 | background: ${({ theme }) => theme.backgroundTertiary}; 5 | height: 2.4rem; 6 | line-height: 2.4rem; 7 | padding: 0 1rem 0 2rem; 8 | border-radius: 1rem; 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | `; 13 | -------------------------------------------------------------------------------- /packages/linx-next/components/ServiceRouteLinks/ServiceRouteLinks.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import * as S from './ServiceRouteLinks.styled'; 3 | 4 | const ServiceRouteLinks = () => ( 5 | 6 | Offline 7 | 404 8 | 500 9 | 10 | ); 11 | export default ServiceRouteLinks; 12 | -------------------------------------------------------------------------------- /packages/api/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["*.ts", "*.tsx"], 11 | "rules": {} 12 | }, 13 | { 14 | "files": ["*.js", "*.jsx"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /packages/api/src/app/routes/auth/google.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'hyper-express'; 2 | import { GoogleAuthController } from '../../controllers'; 3 | 4 | const googleRouter = new Router(); 5 | const googleController = new GoogleAuthController(); 6 | 7 | googleRouter.get('/', googleController.oauthRedirect); 8 | googleRouter.get('/callback', googleController.oauthCallback); 9 | 10 | export default googleRouter; 11 | -------------------------------------------------------------------------------- /packages/api/src/app/routes/index.ts: -------------------------------------------------------------------------------- 1 | import authRouter from './auth'; 2 | import linkRouter from './link'; 3 | import linkGroupRouter from './linkgroup'; 4 | import tagRouter from './tag'; 5 | import userRouter from './user'; 6 | import userGroupRouter from './usergroup'; 7 | 8 | export { 9 | authRouter, 10 | linkRouter, 11 | linkGroupRouter, 12 | tagRouter, 13 | userRouter, 14 | userGroupRouter, 15 | }; 16 | -------------------------------------------------------------------------------- /packages/linx-next/components/GoogleLoginButton/GoogleLoginButton.tsx: -------------------------------------------------------------------------------- 1 | import { GoogleIcon } from '../../assets/icons'; 2 | import React from 'react'; 3 | import SocialButton from '../SocialButton'; 4 | 5 | const GoogleLoginButton = () => ( 6 | } 9 | linkToAuth={'api/auth/signin/google'} 10 | /> 11 | ); 12 | 13 | export default GoogleLoginButton; 14 | -------------------------------------------------------------------------------- /packages/linx-next/components/SearchBar/SearchBar.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div` 4 | display: flex; 5 | align-items: center; 6 | justify-content: flex-start; 7 | position: fixed; 8 | border-radius: 1rem; 9 | bottom: 3rem; 10 | left: 0.5rem; 11 | font-size: 1.5rem; 12 | & > a { 13 | margin-right: 0.5rem; 14 | opacity: 0.2; 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /packages/api/src/app/controllers/index.ts: -------------------------------------------------------------------------------- 1 | import LinkController from './link'; 2 | import GithubAuthController from './auth/github'; 3 | import GoogleAuthController from './auth/google'; 4 | import LinkGroupController from './linkgroup'; 5 | import * as UserController from './user'; 6 | 7 | export { 8 | LinkController, 9 | GithubAuthController, 10 | GoogleAuthController, 11 | LinkGroupController, 12 | UserController, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/linx-next/components/ReviewStars/ReviewStars.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div<{ isInput: boolean }>` 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | & > svg:not(:first-child) { 8 | margin-left: 0.2rem; 9 | } 10 | ${({ isInput }) => { 11 | return isInput === true ? 'cursor:pointer' : 'cursor:default'; 12 | }} 13 | `; 14 | -------------------------------------------------------------------------------- /packages/api/src/app/routes/review/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'hyper-express'; 2 | import { handleReviewAdd, handleReviewDelete } from '../../controllers/review'; 3 | import requireUser from '../../middlewares/auth/requireUser'; 4 | 5 | const reviewRouter = new Router(); 6 | 7 | reviewRouter.post('/add', requireUser, handleReviewAdd); 8 | reviewRouter.delete(':id', requireUser, handleReviewDelete); 9 | 10 | export default reviewRouter; 11 | -------------------------------------------------------------------------------- /packages/linx-next/components/GithubLoginButton/GithubLoginButton.tsx: -------------------------------------------------------------------------------- 1 | import { GithubOutlineIcon } from '../../assets/icons'; 2 | import React from 'react'; 3 | import SocialButton from '../SocialButton'; 4 | 5 | const GithubLoginButton = () => ( 6 | } 9 | linkToAuth={'api/auth/signin/github'} 10 | /> 11 | ); 12 | 13 | export default GithubLoginButton; 14 | -------------------------------------------------------------------------------- /packages/linx-next/components/ServiceRouteLinks/ServiceRouteLinks.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div` 4 | display: flex; 5 | align-items: center; 6 | justify-content: flex-start; 7 | position: fixed; 8 | border-radius: 1rem; 9 | bottom: 3rem; 10 | left: 0.5rem; 11 | font-size: 1.5rem; 12 | & > a { 13 | margin-right: 0.5rem; 14 | opacity: 0.2; 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /packages/api/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'api', 4 | preset: '../../jest.preset.js', 5 | globals: { 6 | 'ts-jest': { 7 | tsconfig: '/tsconfig.spec.json', 8 | }, 9 | }, 10 | testEnvironment: 'node', 11 | transform: { 12 | '^.+\\.[tj]s$': 'ts-jest', 13 | }, 14 | moduleFileExtensions: ['ts', 'js', 'html'], 15 | coverageDirectory: '../../coverage/packages/api', 16 | }; 17 | -------------------------------------------------------------------------------- /packages/linx-next/assets/icons/Trash.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SVGProps } from 'react'; 3 | 4 | const SvgComponent = (props: SVGProps) => ( 5 | 6 | 10 | 11 | ); 12 | 13 | export default SvgComponent; 14 | -------------------------------------------------------------------------------- /packages/linx-next/components/Text/Text.styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const SmallAuthText = styled.p` 4 | font-size: 1.2rem; 5 | font-family: 'Poppins', sans-serif; 6 | text-align: center; 7 | font-weight: 400; 8 | `; 9 | 10 | export const AuthImportantText = styled.p` 11 | color: ${({ theme }) => theme.primary}; 12 | font-size: 1.6rem; 13 | font-family: 'Poppins', sans-serif; 14 | text-align: center; 15 | `; 16 | -------------------------------------------------------------------------------- /packages/linx-next-e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 7 | "rules": {} 8 | }, 9 | { 10 | "files": ["src/plugins/index.js"], 11 | "rules": { 12 | "@typescript-eslint/no-var-requires": "off", 13 | "no-undef": "off" 14 | } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/linx-next/components/AuthLinkFlavor/AuthLinkFlavor.tsx: -------------------------------------------------------------------------------- 1 | import * as S from './AuthLinkFlavor.styled'; 2 | 3 | interface Props { 4 | type: 'up' | 'down' | 'relative'; 5 | } 6 | 7 | const AuthLinkFlavor: React.FC = ({ type }) => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | export default AuthLinkFlavor; 16 | -------------------------------------------------------------------------------- /packages/api/src/app/routes/auth/github.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'hyper-express'; 2 | import { GithubAuthController } from '../../controllers'; 3 | 4 | const githubRouter = new Router(); 5 | const githubController = new GithubAuthController(); 6 | 7 | githubRouter.get('/', githubController.oauthRedirect); 8 | githubRouter.get('/callback', githubController.oauthCallback); 9 | githubRouter.post('/hook', githubController.hookEvents); 10 | 11 | export default githubRouter; 12 | -------------------------------------------------------------------------------- /packages/linx-next/public/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /packages/api/src/app/middlewares/auth/requireUser.ts: -------------------------------------------------------------------------------- 1 | import { authorizedRouteHandler } from '../../../interfaces'; 2 | import log from '../../helpers/logger'; 3 | 4 | const requireUser: authorizedRouteHandler = (req, res, next) => { 5 | log.info('[AUTH] USER CHECK REQUESTED FOR ' + req.originalUrl); 6 | const id = res.locals.id; 7 | 8 | if (!id || !id.user || !id.session) return res.status(403).end(); 9 | 10 | return next(); 11 | }; 12 | 13 | export default requireUser; 14 | -------------------------------------------------------------------------------- /packages/linx-next/components/StatPill/StatPill.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as S from './StatPill.styled'; 3 | 4 | interface Props { 5 | ico: JSX.Element; 6 | stat: number | string; 7 | isReversed?: boolean; 8 | } 9 | 10 | const StatPill: React.FC = ({ ico, stat, isReversed }) => ( 11 | 12 |
{stat}
13 | {ico} 14 |
15 | ); 16 | export default StatPill; 17 | -------------------------------------------------------------------------------- /packages/linx-next-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "supportFile": "./src/support/index.ts", 7 | "pluginsFile": false, 8 | "video": true, 9 | "videosFolder": "../../dist/cypress/packages/linx-next-e2e/videos", 10 | "screenshotsFolder": "../../dist/cypress/packages/linx-next-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /packages/linx-next/assets/icons/OpenInNewTab.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SVGProps } from 'react'; 3 | 4 | const SvgComponent = (props: SVGProps) => ( 5 | 6 | 10 | 11 | ); 12 | 13 | export default SvgComponent; 14 | -------------------------------------------------------------------------------- /packages/linx-next/pages/signup.tsx: -------------------------------------------------------------------------------- 1 | import { NextSeo } from 'next-seo'; 2 | import React, { ReactElement } from 'react'; 3 | import AuthLayout from '../layouts/AuthLayout'; 4 | import SignUp from "../containers/SignUp"; 5 | 6 | const Signup = () => 7 | 8 | Signup.getLayout = (page: ReactElement) => ( 9 | 10 | 11 | 12 | {page} 13 | 14 | ); 15 | 16 | export default Signup; 17 | -------------------------------------------------------------------------------- /packages/api/src/app/helpers/redis.ts: -------------------------------------------------------------------------------- 1 | import redisClient from '../lib/redis'; 2 | 3 | const getFromCache = async (key: string) => 4 | JSON.parse(await redisClient.get(key)); 5 | 6 | const setExCache = async ( 7 | key: string, 8 | duration_seconds: number, 9 | value: string 10 | ) => await redisClient.setEx(key, duration_seconds, value); 11 | 12 | const deleteFromCache = async (key: string) => await redisClient.del(key); 13 | 14 | export { getFromCache, setExCache, deleteFromCache }; 15 | -------------------------------------------------------------------------------- /packages/api/src/app/routes/user/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'hyper-express'; 2 | import { UserController } from '../../controllers'; 3 | 4 | const userRouter = new Router(); 5 | 6 | userRouter.get('/all', UserController.handleAllUsers); 7 | userRouter.get('/all/groups', UserController.handleAllUsersWithGroups); 8 | userRouter.get('/:user', UserController.handleAllUsersGroups); 9 | userRouter.get('/:user/g/:group', UserController.handleUserGroupLinks); 10 | 11 | export default userRouter; 12 | -------------------------------------------------------------------------------- /packages/linx-next-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('linx-next', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome linx-next'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /packages/linx-next/components/LogoAppName/LogoAppName.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import React from 'react'; 3 | import LogoSmall from '../../assets/icons/LogoSmall'; 4 | import * as S from './LogoAppName.styled'; 5 | 6 | const LogoAppName = () => { 7 | return ( 8 | 9 | 10 | 11 | 12 |

LYNX

13 |
14 |
15 | 16 | ); 17 | }; 18 | 19 | export default LogoAppName; 20 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/Footer/Footer.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Footer = styled.footer` 4 | z-index: 10; 5 | display: flex; 6 | align-items: center; 7 | justify-content: center; 8 | height: 3rem; 9 | text-align: center; 10 | font-size: 1.2rem; 11 | font-family: Inter, serif; 12 | background-color: ${({ theme }) => theme.backgroundSecondary}; 13 | & > div > a { 14 | font-weight: 500; 15 | text-decoration: underline; 16 | } 17 | `; 18 | -------------------------------------------------------------------------------- /packages/linx-next/jest.config.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'jsdom', 3 | displayName: 'linx-next', 4 | preset: '../../jest.preset.ts', 5 | transform: { 6 | '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', 7 | '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], 8 | }, 9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], 10 | coverageDirectory: '../../coverage/packages/linx-next', 11 | setupFilesAfterEnv: ['./specs/setupTests.ts'], 12 | }; 13 | -------------------------------------------------------------------------------- /packages/linx-next/assets/icons/AddIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SVGProps } from 'react'; 3 | 4 | const SvgComponent = (props: SVGProps) => ( 5 | 6 | 13 | 14 | ); 15 | 16 | export default SvgComponent; 17 | -------------------------------------------------------------------------------- /packages/linx-next/components/ReviewComponent/ReviewComponent.styled.ts: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import styled from 'styled-components'; 3 | 4 | export const ReviewRow = styled.div` 5 | display: flex; 6 | width: 100%; 7 | flex: 1; 8 | flex-grow: 1; 9 | flex-direction: row; 10 | justify-content: space-between; 11 | align-items: space-between; 12 | & a { 13 | font-weight: bold; 14 | } 15 | `; 16 | 17 | export const UserLink = styled(Link)``; 18 | export const DescriptionBlock = styled.p``; 19 | -------------------------------------------------------------------------------- /packages/linx-next/pages/signin.tsx: -------------------------------------------------------------------------------- 1 | import { NextSeo } from 'next-seo'; 2 | import React, { ReactElement } from 'react'; 3 | import AuthLayout from '../layouts/AuthLayout'; 4 | import SignIn from '../containers/SignIn'; 5 | 6 | const SigninPage = () => ; 7 | 8 | SigninPage.getLayout = (page: ReactElement) => { 9 | return ( 10 | 11 | 12 | {page} 13 | 14 | ); 15 | }; 16 | 17 | export default SigninPage; 18 | -------------------------------------------------------------------------------- /packages/api/src/app/services/stats.ts: -------------------------------------------------------------------------------- 1 | import db from '../lib/db'; 2 | 3 | export const getAllStats = async () => { 4 | const allUsers = await db.user.count(); 5 | const allLinks = await db.link.count(); 6 | const allLinkGroups = await db.linkGroup.count(); 7 | const allTags = await db.tag.count(); 8 | const allReviews = await db.review.count(); 9 | 10 | return { 11 | users: allUsers, 12 | links: allLinks, 13 | linkGroups: allLinkGroups, 14 | tags: allTags, 15 | review: allReviews, 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/linx-next/components/LynxInfoPanel/LynxInfoPanel.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as S from './LynxInfoPanel.styled'; 3 | import AuthLinkFlavor from '../AuthLinkFlavor'; 4 | 5 | interface Props { 6 | text: string; 7 | } 8 | const LynxInfoPanel: React.FC = ({ text }) => ( 9 | 10 | 11 | 12 | {text} 13 | 14 | 15 | ); 16 | 17 | export default LynxInfoPanel; 18 | -------------------------------------------------------------------------------- /packages/linx-next/components/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React, { MouseEventHandler } from 'react'; 2 | import * as S from './Button.styled'; 3 | 4 | interface Props { 5 | children?: JSX.Element | string; 6 | onClick?: MouseEventHandler; 7 | type?: 'button' | 'submit' | 'reset'; 8 | isSecondary?: boolean; 9 | } 10 | 11 | const Button: React.FC = ({ children, onClick, type, isSecondary }) => ( 12 | 13 | {children} 14 | 15 | ); 16 | export default Button; 17 | -------------------------------------------------------------------------------- /packages/linx-next/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node", "@testing-library/jest-dom"], 7 | "jsx": "react" 8 | }, 9 | "include": [ 10 | "jest.config.ts", 11 | "**/*.test.ts", 12 | "**/*.spec.ts", 13 | "**/*.test.tsx", 14 | "**/*.spec.tsx", 15 | "**/*.test.js", 16 | "**/*.spec.js", 17 | "**/*.test.jsx", 18 | "**/*.spec.jsx", 19 | "**/*.d.ts" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/linx-next/api/tag.ts: -------------------------------------------------------------------------------- 1 | import { GroupTag, Tag } from '@prisma/client'; 2 | 3 | export async function getTags() { 4 | const tags = (await fetch(`${process.env.FRONTEND_URL}api/tag`).then((res) => 5 | res.json() 6 | )) as (Tag & { _count: { Groups: number } })[]; 7 | return tags; 8 | } 9 | export async function addMultipleGroupTags(data: Omit[]) { 10 | const res = await fetch(`${process.env.FRONTEND_URL}api/tag/add/group/many`, { 11 | method: 'POST', 12 | body: JSON.stringify(data), 13 | }); 14 | return res; 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2021", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": {} 18 | }, 19 | "exclude": ["node_modules", "tmp"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/api/src/app/controllers/auth/logout.ts: -------------------------------------------------------------------------------- 1 | import log from '../../helpers/logger'; 2 | import { authorizedRouteHandler } from '../../../interfaces'; 3 | import { removeSession } from '../../services/session'; 4 | 5 | const handleLogout: authorizedRouteHandler = async (req, res) => { 6 | log.info('[USER] Logout for sessionID : ' + res.locals.id.session); 7 | 8 | await removeSession(res.locals.id.session); 9 | 10 | res.clearCookie('access_token'); 11 | res.clearCookie('refresh_token'); 12 | 13 | res.status(200).end(); 14 | }; 15 | export default handleLogout; 16 | -------------------------------------------------------------------------------- /packages/api/src/app/helpers/cookie.ts: -------------------------------------------------------------------------------- 1 | import { CookieOptions } from 'hyper-express'; 2 | 3 | const { COOKIE_DOMAIN } = process.env; 4 | const env = process.env.NODE_ENV; 5 | 6 | const isProduction = env === 'production'; 7 | 8 | export const cookieOptions: CookieOptions = { 9 | maxAge: 365 * 24 * 60 * 60, 10 | httpOnly: true, 11 | domain: isProduction ? COOKIE_DOMAIN : 'localhost', 12 | path: '/', 13 | sameSite: 'lax', 14 | secure: isProduction, 15 | }; 16 | export const refreshCookieOptions: CookieOptions = { 17 | ...cookieOptions, 18 | maxAge: 365 * 24 * 60 * 60, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/linx-next/assets/icons/ChevronDown.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SVGProps } from 'react'; 3 | 4 | const SvgComponent = (props: SVGProps) => ( 5 | 13 | 20 | 21 | ); 22 | 23 | export default SvgComponent; 24 | -------------------------------------------------------------------------------- /packages/linx-next/components/LinkGroupDisplay/LinkGroupDisplay.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | position: relative; 5 | display: flex; 6 | align-items: flex-start; 7 | justify-content: space-between; 8 | flex-direction: column; 9 | width: 80rem; 10 | min-height: 22rem; 11 | margin: 1rem 0 3rem; 12 | border-radius: 2rem; 13 | background: ${({ theme }) => theme.backgroundSecondary}; 14 | border: 0.3rem solid ${({ theme }) => theme.backgroundSecondary}; 15 | & > * { 16 | z-index: 3; 17 | } 18 | `; 19 | -------------------------------------------------------------------------------- /packages/linx-next/components/SocialButton/SocialButton.tsx: -------------------------------------------------------------------------------- 1 | import router from 'next/router'; 2 | import * as S from './SocialButton.styled'; 3 | 4 | interface Props { 5 | icon: JSX.Element; 6 | text: string; 7 | linkToAuth: string; 8 | } 9 | 10 | const SocialButton: React.FC = ({ linkToAuth, text, icon }) => { 11 | const handleClick = (href: string) => { 12 | router.push(href); 13 | }; 14 | return ( 15 | handleClick(linkToAuth)}> 16 | {icon} 17 | {text} 18 | 19 | ); 20 | }; 21 | 22 | export default SocialButton; 23 | -------------------------------------------------------------------------------- /packages/linx-next/pages/404.tsx: -------------------------------------------------------------------------------- 1 | import AuthLayout from '../layouts/AuthLayout'; 2 | import { ReactElement } from 'react'; 3 | import { NextSeo } from 'next-seo'; 4 | import ErrorPanel from '../components/ErrorPanel'; 5 | 6 | const Custom404 = () => ; 7 | 8 | Custom404.getLayout = (page: ReactElement) => { 9 | return ( 10 | 11 | 15 | {page} 16 | 17 | ); 18 | }; 19 | 20 | export default Custom404; 21 | -------------------------------------------------------------------------------- /packages/linx-next/pages/500.tsx: -------------------------------------------------------------------------------- 1 | import AuthLayout from '../layouts/AuthLayout'; 2 | import { ReactElement } from 'react'; 3 | import { NextSeo } from 'next-seo'; 4 | import ErrorPanel from '../components/ErrorPanel'; 5 | 6 | const Custom500 = () => ; 7 | 8 | Custom500.getLayout = (page: ReactElement) => { 9 | return ( 10 | 11 | 15 | {page} 16 | 17 | ); 18 | }; 19 | 20 | export default Custom500; 21 | -------------------------------------------------------------------------------- /packages/api/src/app/controllers/auth/me.ts: -------------------------------------------------------------------------------- 1 | import { authorizedRouteHandler } from '../../../interfaces'; 2 | import log from '../../helpers/logger'; 3 | import { hideSelectedObjectKeys } from '../../helpers/utilsJS'; 4 | import { getUserById } from '../../services/user'; 5 | 6 | const handleMe: authorizedRouteHandler = async (req, res) => { 7 | const usrId = res.locals.id.user; 8 | 9 | log.info('[USER] Get profile: ' + usrId); 10 | const user = await getUserById(res.locals.id.user); 11 | const wuser = hideSelectedObjectKeys(user, ['id', 'password']); 12 | 13 | res.json(wuser); 14 | }; 15 | 16 | export default handleMe; 17 | -------------------------------------------------------------------------------- /packages/linx-next/components/ExpandingButton/ExpandingButton.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import ExpandingButton from './ExpandingButton'; 4 | 5 | describe('ExpandingButton', () => { 6 | const onClickHandler = jest.fn(); 7 | 8 | it('renders the button text', () => { 9 | const { getByText } = render( 10 | 17 | ); 18 | expect(getByText('Click me')).toBeInTheDocument(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/linx-next/assets/icons/Eye.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SVGProps } from 'react'; 3 | 4 | const SvgComponent = (props: SVGProps) => ( 5 | 11 | 15 | 16 | ); 17 | 18 | export default SvgComponent; 19 | -------------------------------------------------------------------------------- /packages/linx-next/assets/icons/Lock.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SVGProps } from 'react'; 3 | 4 | const SvgComponent = (props: SVGProps) => ( 5 | 11 | 15 | 16 | ); 17 | 18 | export default SvgComponent; 19 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/MainLayout/MainLayout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import Header from '../Header'; 4 | 5 | const Content = styled.div` 6 | display: flex; 7 | justify-content: center; 8 | align-items: center; 9 | min-height: calc(100vh - 11rem); 10 | `; 11 | 12 | const Wrapper = styled.div` 13 | display: flex; 14 | flex-direction: column; 15 | min-height: 100vh; 16 | overflow: hidden; 17 | `; 18 | 19 | const MainLayout = ({ children }: { children }) => ( 20 | 21 |
22 | {children} 23 | 24 | ); 25 | 26 | export default MainLayout; 27 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/Footer/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import * as S from './Footer.styled'; 3 | 4 | const Footer = () => { 5 | const [isShown, setIsShown] = useState(false); 6 | const emote = isShown ? '🦊' : '❤'; 7 | return ( 8 | setIsShown(true)} 10 | onMouseLeave={() => setIsShown(false)} 11 | > 12 |
13 | made with {emote} by{' '} 14 | @net-runner &{' '} 15 | @przemec 16 |
17 |
18 | ); 19 | }; 20 | 21 | export default Footer; 22 | -------------------------------------------------------------------------------- /packages/linx-next/components/SocialButton/SocialButton.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Button = styled.button` 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | background: transparent; 8 | border-color: rgba(249, 249, 249, 0.25); 9 | border-width: 0.1rem; 10 | height: 4rem; 11 | width: 30rem; 12 | padding: 0.5rem 3rem; 13 | border-radius: 1.5rem; 14 | color: #f9f9f9; 15 | font-weight: bold; 16 | font-size: 1.4rem; 17 | letter-spacing: 0.1rem; 18 | font-family: 'Poppins', sans-serif; 19 | white-space: nowrap; 20 | margin-top: 4rem; 21 | & > svg { 22 | margin-right: 2rem; 23 | } 24 | `; 25 | -------------------------------------------------------------------------------- /packages/linx-next/components/AuthLinkFlavor/AuthLinkFlavor.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { LinkIcon } from '../../assets/icons'; 3 | 4 | export const Container = styled.div` 5 | display: flex; 6 | align-items: center; 7 | justify-content: flex-start; 8 | position: absolute; 9 | border-radius: 1rem; 10 | &.up { 11 | left: 3rem; 12 | top: 0.4rem; 13 | } 14 | &.down { 15 | right: 1rem; 16 | bottom: 0; 17 | } 18 | &.relative { 19 | position: relative; 20 | } 21 | `; 22 | export const SpecialLinkIcon = styled(LinkIcon)` 23 | width: 5.5rem; 24 | height: 5.5rem; 25 | transform: rotate(-45deg); 26 | margin-right: 1rem; 27 | `; 28 | -------------------------------------------------------------------------------- /packages/linx-next-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /packages/linx-next/hooks/useOutside.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | function useOutside(ref, callback) { 4 | useEffect(() => { 5 | /** 6 | * Alert if clicked on outside of element 7 | */ 8 | function handleClickOutside(event) { 9 | if (ref.current && !ref.current.contains(event.target)) { 10 | callback(); 11 | } 12 | } 13 | // Bind the event listener 14 | document.addEventListener('mousedown', handleClickOutside); 15 | return () => { 16 | // Unbind the event listener on clean up 17 | document.removeEventListener('mousedown', handleClickOutside); 18 | }; 19 | }, [ref, callback]); 20 | } 21 | export default useOutside; 22 | -------------------------------------------------------------------------------- /packages/linx-next/components/ReviewComponent/ReviewComponent.tsx: -------------------------------------------------------------------------------- 1 | import { Review } from '@prisma/client'; 2 | import Link from 'next/link'; 3 | import React from 'react'; 4 | import ReviewStars from '../ReviewStars'; 5 | import * as S from './ReviewComponent.styled'; 6 | interface Props { 7 | data: Review; 8 | } 9 | 10 | const ReviewComponent = ({ data }: Props) => { 11 | return ( 12 | 13 | 14 | {'@' + data.creatorName} 15 | 16 | 17 | {data.description} 18 | 19 | 20 | ); 21 | }; 22 | export default ReviewComponent; 23 | -------------------------------------------------------------------------------- /packages/api/src/app/routes/tag/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'hyper-express'; 2 | import { 3 | handleCreateMultipleGroupTags, 4 | handleCreateTag, 5 | handleGetTagLinkGroups, 6 | handleGetTags, 7 | } from '../../controllers/tag'; 8 | 9 | import requireUser from '../../middlewares/auth/requireUser'; 10 | 11 | const tagRouter = new Router(); 12 | 13 | tagRouter.get('/', handleGetTags); 14 | tagRouter.get('/:tag/g', handleGetTagLinkGroups); 15 | 16 | //Protected create new tag 17 | tagRouter.post('/add', requireUser, handleCreateTag); 18 | 19 | //Protected add multiple group tags 20 | tagRouter.post('/add/group/many', requireUser, handleCreateMultipleGroupTags); 21 | 22 | export default tagRouter; 23 | -------------------------------------------------------------------------------- /packages/linx-next/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@nrwl/nx/react-typescript", 4 | "../../.eslintrc.json", 5 | "next", 6 | "next/core-web-vitals" 7 | ], 8 | "ignorePatterns": ["!**/*"], 9 | "overrides": [ 10 | { 11 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 12 | "rules": { 13 | "@next/next/no-html-link-for-pages": [ 14 | "error", 15 | "packages/linx-next/pages" 16 | ] 17 | } 18 | }, 19 | { 20 | "files": ["*.ts", "*.tsx"], 21 | "rules": {} 22 | }, 23 | { 24 | "files": ["*.js", "*.jsx"], 25 | "rules": {} 26 | } 27 | ], 28 | "env": { 29 | "jest": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /tmp 5 | /out-tsc 6 | /dist/packages 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | .env 41 | .env*.* 42 | -------------------------------------------------------------------------------- /packages/api/src/app/middlewares/cors/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultRouteMiddlewareInterface } from '../../../interfaces/index'; 2 | 3 | const { FRONTEND_URL } = process.env; 4 | const env = process.env.NODE_ENV; 5 | const isProduction = env === 'production'; 6 | 7 | const corsMiddleware: defaultRouteMiddlewareInterface = (req, res, next) => { 8 | res.header( 9 | 'Access-Control-Allow-Origin', 10 | `${isProduction ? FRONTEND_URL : 'http://localhost:4200'}` 11 | ); 12 | res.header('Access-Control-Allow-Credentials', 'true'); 13 | res.header( 14 | 'Access-Control-Allow-Headers', 15 | 'Origin, X-Requested-With, Content-Type, Accept' 16 | ); 17 | next(); 18 | }; 19 | export default corsMiddleware; 20 | -------------------------------------------------------------------------------- /packages/linx-next/pages/_offline.tsx: -------------------------------------------------------------------------------- 1 | //Static page for handling app being offline or as a fallback for non WebWorker cached routes 2 | 3 | import { NextSeo } from 'next-seo'; 4 | import { ReactElement } from 'react'; 5 | import AuthLayout from '../layouts/AuthLayout/AuthLayout'; 6 | import ErrorPanel from '../components/ErrorPanel'; 7 | 8 | const Offline = () => ; 9 | 10 | Offline.getLayout = (page: ReactElement) => { 11 | return ( 12 | 13 | 17 | {page} 18 | 19 | ); 20 | }; 21 | 22 | export default Offline; 23 | -------------------------------------------------------------------------------- /packages/linx-next/containers/StatsPage/StatsPage.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as S from './StatsPage.styled'; 3 | 4 | interface StatsInterface { 5 | users: number; 6 | links: number; 7 | linkGroups: number; 8 | tags: number; 9 | review: number; 10 | } 11 | 12 | const StatsPage = ({ stats }: { stats: StatsInterface }) => { 13 | return ( 14 | 15 | 16 | Users: {stats.users} 17 | Links: {stats.links} 18 | Tags: {stats.tags} 19 | Reviews: {stats.review} 20 | Groups: {stats.linkGroups} 21 | 22 | ); 23 | }; 24 | 25 | export default StatsPage; 26 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/UserNav/UserNav.tsx: -------------------------------------------------------------------------------- 1 | import UserDropdown from '../../components/UserDropdown'; 2 | import React from 'react'; 3 | import ExpandingButton from '../../components/ExpandingButton'; 4 | import { useRouter } from 'next/router'; 5 | 6 | const UserNav = () => { 7 | const router = useRouter(); 8 | const handleClick = (e) => { 9 | router.push(process.env.FRONTEND_URL + 'new'); 10 | }; 11 | return ( 12 | <> 13 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default UserNav; 26 | -------------------------------------------------------------------------------- /packages/linx-next/api/review.ts: -------------------------------------------------------------------------------- 1 | import { Review } from '@prisma/client'; 2 | 3 | export const addReview = async (data: Omit) => { 4 | try { 5 | return await ( 6 | await fetch(`${process.env.FRONTEND_URL}/api/review/add`, { 7 | method: 'POST', 8 | body: JSON.stringify(data), 9 | }) 10 | ).json(); 11 | } catch (error) { 12 | console.log('E ' + error); 13 | } 14 | }; 15 | export const removeReview = async (id: string) => { 16 | try { 17 | return await ( 18 | await fetch(`${process.env.FRONTEND_URL}/api/review/${id}`, { 19 | method: 'DELETE', 20 | }) 21 | ).json(); 22 | } catch (error) { 23 | console.log('E ' + error); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /packages/linx-next/containers/StatsPage/StatsPage.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { LynxLogoDetail } from '../../assets/icons'; 3 | 4 | export const Wrapper = styled.div` 5 | z-index: 2; 6 | text-align: start; 7 | position: relative; 8 | padding: 5rem; 9 | border-radius: 2rem; 10 | background: ${({ theme }) => theme.backgroundSecondary}; 11 | `; 12 | 13 | export const Info = styled.p` 14 | font-size: 2.5rem; 15 | font-family: Inter, serif; 16 | font-weight: normal; 17 | line-height: 160%; 18 | text-align: start; 19 | margin: 0 1rem; 20 | `; 21 | 22 | export const Logo = styled(LynxLogoDetail)` 23 | width: 15rem; 24 | height: 15rem; 25 | position: absolute; 26 | top: -10rem; 27 | `; 28 | -------------------------------------------------------------------------------- /packages/linx-next/components/TagList/TagList.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | display: flex; 5 | flex-direction: row; 6 | flex-wrap: wrap; 7 | align-self: flex-start; 8 | `; 9 | 10 | export const TagContainer = styled.div<{ selected: boolean }>` 11 | background-color: ${({ theme, selected }) => 12 | selected ? theme.primary : theme.backgroundSecondary}; 13 | margin: 0.5rem; 14 | border-radius: 0.3rem; 15 | padding: 0.5rem; 16 | 17 | &:hover { 18 | background-color: ${({ theme }) => theme.primary}; 19 | } 20 | 21 | & div { 22 | display: inline-block; 23 | cursor: pointer; 24 | padding: 0.5rem; 25 | width: 100%; 26 | height: 100%; 27 | } 28 | `; 29 | -------------------------------------------------------------------------------- /packages/api/src/app/middlewares/cache/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultRouteMiddlewareInterface } from '../../../interfaces'; 2 | import log from '../../helpers/logger'; 3 | import redisClient from '../../lib/redis'; 4 | 5 | const cache: defaultRouteMiddlewareInterface = async (req, res) => { 6 | const key = req.originalUrl; 7 | 8 | if (req.method !== 'GET') { 9 | log.error('Cannot cache non-GET methods!'); 10 | return; 11 | } 12 | 13 | const cachedResponse = await redisClient.get(key); 14 | 15 | if (cachedResponse) { 16 | log.info(`Cache hit for ${key}`); 17 | const response = JSON.parse(cachedResponse); 18 | res.json(response); 19 | } else { 20 | log.info(`Cache miss for ${key}`); 21 | return; 22 | } 23 | }; 24 | export default cache; 25 | -------------------------------------------------------------------------------- /packages/linx-next/pages/new.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactElement } from 'react'; 2 | import { getTags } from '../api/tag'; 3 | import CreateLinkGroup from '../components/CreateLinkGroup'; 4 | import MainLayout from '../layouts/MainLayout'; 5 | 6 | //New linkgroup creation screen 7 | const New = ({ tags }) => { 8 | return ; 9 | }; 10 | export async function getStaticProps(context) { 11 | const tags = await getTags(); 12 | 13 | if (tags === null) { 14 | return { 15 | props: { tags: null }, 16 | }; 17 | } else { 18 | return { 19 | props: { tags }, 20 | }; 21 | } 22 | } 23 | New.getLayout = (page: ReactElement) => { 24 | return {page}; 25 | }; 26 | New.requireAuth = true; 27 | export default New; 28 | -------------------------------------------------------------------------------- /packages/linx-next/pages/stats.tsx: -------------------------------------------------------------------------------- 1 | import { NextSeo } from 'next-seo'; 2 | import React, { ReactElement } from 'react'; 3 | import AuthLayout from '../layouts/AuthLayout'; 4 | import StatsPage from '../containers/StatsPage'; 5 | 6 | const Stats = ({ stats }) => ; 7 | export async function getServerSideProps() { 8 | const stats = await fetch(`${process.env.FRONTEND_URL}api/stats/`).then( 9 | (res) => res.json() 10 | ); 11 | console.log(stats); 12 | return { 13 | props: { stats }, 14 | }; 15 | } 16 | Stats.getLayout = (page: ReactElement) => { 17 | return ( 18 | 19 | 20 | {page} 21 | 22 | ); 23 | }; 24 | export default Stats; 25 | -------------------------------------------------------------------------------- /packages/api/src/app/helpers/jwt.ts: -------------------------------------------------------------------------------- 1 | import { JwtPayload, sign, SignOptions, verify } from 'jsonwebtoken'; 2 | const { AUTH_CORE_SECRET } = process.env; 3 | 4 | declare module 'jsonwebtoken' { 5 | export interface JwtPayload { 6 | user: string; 7 | session: string; 8 | } 9 | } 10 | 11 | export function signJwt(object, options?: SignOptions) { 12 | return sign(object, AUTH_CORE_SECRET, options); 13 | } 14 | 15 | export function verifyJwt(token: string) { 16 | try { 17 | const decoded = verify(token, AUTH_CORE_SECRET) as JwtPayload; 18 | return { 19 | valid: true, 20 | expired: false, 21 | decoded, 22 | }; 23 | } catch (e) { 24 | return { 25 | valid: false, 26 | expired: true, 27 | decoded: null, 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/api/src/app/middlewares/rateLimit/index.ts: -------------------------------------------------------------------------------- 1 | import { defaultRouteMiddlewareInterface } from '../../../interfaces/index'; 2 | import { RateLimiterMemory } from 'rate-limiter-flexible'; 3 | import log from '../../helpers/logger'; 4 | const opts = { 5 | points: 24, // 12 points 6 | duration: 1, // Per second 7 | }; 8 | 9 | const rateLimiter = new RateLimiterMemory(opts); 10 | 11 | const rateLimiterMiddleware: defaultRouteMiddlewareInterface = ( 12 | req, 13 | res, 14 | next 15 | ) => { 16 | rateLimiter 17 | .consume(req.ip) 18 | .then(() => { 19 | next(); 20 | }) 21 | .catch(() => { 22 | log.error('Too many Requests from: ' + req.ips); 23 | res.status(429).send('Too Many Requests'); 24 | }); 25 | }; 26 | export default rateLimiterMiddleware; 27 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "nx/presets/core.json", 3 | "npmScope": "linx", 4 | "affected": { 5 | "defaultBase": "master" 6 | }, 7 | "cli": { 8 | "defaultCollection": "@nrwl/next" 9 | }, 10 | "tasksRunnerOptions": { 11 | "default": { 12 | "runner": "@nrwl/nx-cloud", 13 | "options": { 14 | "cacheableOperations": ["build", "lint", "test", "e2e"], 15 | "accessToken": "ZTk3YjdhNjEtNmJkOS00YjViLWIzM2MtOTYwNGU5NDM1OTMwfHJlYWQtd3JpdGU=" 16 | } 17 | } 18 | }, 19 | "generators": { 20 | "@nrwl/react": { 21 | "application": { 22 | "babel": true 23 | } 24 | }, 25 | "@nrwl/next": { 26 | "application": { 27 | "style": "styled-components", 28 | "linter": "eslint" 29 | } 30 | } 31 | }, 32 | "defaultProject": "linx-next" 33 | } 34 | -------------------------------------------------------------------------------- /packages/linx-next-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceRoot": "packages/linx-next-e2e/src", 3 | "projectType": "application", 4 | "targets": { 5 | "e2e": { 6 | "executor": "@nrwl/cypress:cypress", 7 | "options": { 8 | "cypressConfig": "packages/linx-next-e2e/cypress.json", 9 | "devServerTarget": "linx-next:serve:development" 10 | }, 11 | "configurations": { 12 | "production": { 13 | "devServerTarget": "linx-next:serve:production" 14 | } 15 | } 16 | }, 17 | "lint": { 18 | "executor": "@nrwl/linter:eslint", 19 | "outputs": ["{options.outputFile}"], 20 | "options": { 21 | "lintFilePatterns": ["packages/linx-next-e2e/**/*.{js,ts}"] 22 | } 23 | } 24 | }, 25 | "tags": [], 26 | "implicitDependencies": ["linx-next"] 27 | } 28 | -------------------------------------------------------------------------------- /packages/linx-next/helpers/fetcher.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { IncomingMessage, ServerResponse } from 'http'; 3 | export type FResponse = [error: string | null, data: T | null]; 4 | 5 | export const fetcher = async (url: string): Promise> => { 6 | try { 7 | const data: T = await axios.get(url, { withCredentials: true }); 8 | return [null, data]; 9 | } catch (error) { 10 | return [error, null]; 11 | } 12 | }; 13 | 14 | export const fetcherSSR = async ( 15 | req: IncomingMessage, 16 | res: ServerResponse, 17 | url: string 18 | ): Promise> => { 19 | try { 20 | const data: T = await axios.get(url, { 21 | headers: { cookie: req.headers.cookie }, 22 | }); 23 | return [null, data]; 24 | } catch (error) { 25 | return [error, null]; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /packages/api/src/app/services/review.ts: -------------------------------------------------------------------------------- 1 | import { Review } from '@prisma/client'; 2 | import { deleteFromCache, getFromCache, setExCache } from '../helpers/redis'; 3 | import db from '../lib/db'; 4 | 5 | export async function createReview(review: Omit) { 6 | const r = await db.review.create({ data: review }); 7 | setExCache(r.id, 36000, JSON.stringify(r)); 8 | return r; 9 | } 10 | export async function deleteReview(id: string, creatorName: string) { 11 | const cachedReview = await getFromCache(id); 12 | 13 | let rev; 14 | if (cachedReview) { 15 | rev = cachedReview; 16 | } else { 17 | rev = await db.review.findUnique({ where: { id } }); 18 | } 19 | 20 | if (rev && rev.creatorName === creatorName) { 21 | deleteFromCache(id); 22 | return await db.review.delete({ where: { id } }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/linx-next/components/LynxInfoPanel/LynxInfoPanel.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { LynxLogoDetail } from '../../assets/icons'; 3 | 4 | export const Wrapper = styled.div` 5 | position: relative; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | flex-direction: column; 10 | min-width: 38rem; 11 | margin: 5rem auto; 12 | padding: 0 7rem; 13 | border-radius: 3.3rem; 14 | background-color: ${({ theme }) => theme.background}; 15 | `; 16 | 17 | export const Title = styled.h1` 18 | margin: 1rem 0 2rem; 19 | padding: 0 2rem; 20 | font-family: 'Segoe UI', serif; 21 | font-size: 2.8rem; 22 | text-align: center; 23 | font-weight: bold; 24 | `; 25 | 26 | export const Logo = styled(LynxLogoDetail)` 27 | width: 10rem; 28 | height: 10rem; 29 | margin: 2rem 0 1rem; 30 | `; 31 | -------------------------------------------------------------------------------- /packages/api/src/app/services/user.types.ts: -------------------------------------------------------------------------------- 1 | export interface BasicUser { 2 | email: string; 3 | name: string; 4 | password?: string; 5 | repeat_password?: string; 6 | } 7 | export interface GoogleUser extends BasicUser { 8 | id: string; 9 | verified_email: boolean; 10 | given_name: string; 11 | family_name: string; 12 | picture: string; 13 | locale: string; 14 | } 15 | 16 | export interface GithubUser extends BasicUser { 17 | login: string; 18 | id: number; 19 | node_id: string; 20 | avatar_url: string; 21 | gravatar_id: string; 22 | name: string; 23 | company: string; 24 | blog: string; 25 | bio: string; 26 | followers: number; 27 | following: number; 28 | location: string; 29 | hireable: boolean; 30 | two_factor_authentication: boolean; 31 | } 32 | 33 | export type LynxUser = BasicUser | GoogleUser | GithubUser; 34 | -------------------------------------------------------------------------------- /packages/linx-next/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": false, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "incremental": true, 14 | "types": ["jest", "node", "@testing-library/jest-dom"], 15 | "paths": { 16 | "@/components/*": ["components/*"], 17 | "@/icons/*":["assets/icons/*"], 18 | "@/icons":["assets/icons"], 19 | "@/layouts/*":["layouts/*"], 20 | "@/styles/*":["styles/*"] 21 | } 22 | }, 23 | "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "next-env.d.ts"], 24 | "exclude": ["node_modules", "jest.config.ts"] 25 | } 26 | -------------------------------------------------------------------------------- /packages/api/src/app/routes/link/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'hyper-express'; 2 | import { LinkController } from '../../controllers'; 3 | import requireUser from '../../middlewares/auth/requireUser'; 4 | import cache from '../../middlewares/cache'; 5 | 6 | const linkRouter = new Router(); 7 | const linkController = new LinkController(); 8 | linkRouter.get('/healthcheck', (req, res) => { 9 | res.status(200).end(); 10 | }); 11 | linkRouter.post('/add', requireUser, linkController.add); 12 | linkRouter.post('/edit', requireUser, linkController.edit); 13 | linkRouter.post('/del', requireUser, linkController.delete); 14 | 15 | //For getting links no auth required 16 | linkRouter.get('/:id', cache, linkController.getSingle); 17 | linkRouter.get('/:limit/:page', linkController.getMany); 18 | linkRouter.get('/:limit/:page/:skip', linkController.getMany); 19 | 20 | export default linkRouter; 21 | -------------------------------------------------------------------------------- /packages/linx-next/components/Button/Button.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, fireEvent } from '@testing-library/react'; 3 | import Button from './Button'; 4 | 5 | describe('Button', () => { 6 | it('renders children prop', () => { 7 | const { getByText } = render(); 8 | expect(getByText('Click me')).toBeInTheDocument(); 9 | }); 10 | 11 | it('calls onClick prop when clicked', () => { 12 | const onClick = jest.fn(); 13 | const { getByRole } = render(); 14 | fireEvent.click(getByRole('button')); 15 | expect(onClick).toHaveBeenCalled(); 16 | }); 17 | 18 | it('passes type prop to button element', () => { 19 | const { getByRole } = render(); 20 | expect(getByRole('button')).toHaveAttribute('type', 'submit'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nrwl/nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nrwl/nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nrwl/nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nrwl/nx/javascript"], 32 | "rules": {} 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /packages/linx-next/components/SpecialBackground/SpecialBackground.styled.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import { LinkIcon } from '../../assets/icons'; 3 | 4 | export const SpecialBackgroundContainer = styled.div` 5 | position: fixed; 6 | display: flex; 7 | flex-direction: row; 8 | top: 15vh; 9 | width: 100%; 10 | `; 11 | 12 | export const SpecialBackgroundColumn = styled.div` 13 | display: flex; 14 | flex: 1; 15 | height: 100%; 16 | flex-direction: column; 17 | position: relative; 18 | &:nth-child(2) { 19 | align-items: flex-end; 20 | } 21 | & > svg { 22 | position: absolute; 23 | } 24 | `; 25 | 26 | export const SpecialLinkIcon = styled(LinkIcon)` 27 | width: 8rem; 28 | height: 8rem; 29 | transform: rotate(0); 30 | transition: 3s ease-in-out; 31 | &:hover { 32 | transition: 0.3s ease-in-out; 33 | transform: rotate(700deg) !important; 34 | } 35 | `; 36 | -------------------------------------------------------------------------------- /packages/linx-next/components/LinkGroupBody/LinkGroupBody.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | flex-direction: column; 8 | width: 100%; 9 | padding: 3rem 2rem; 10 | border-radius: 0 0 2rem 2rem; 11 | background: ${({ theme }) => theme.background}; 12 | `; 13 | 14 | export const Description = styled.p` 15 | width: 100%; 16 | font-size: 1.8rem; 17 | font-weight: normal; 18 | margin-bottom: 0.75rem; 19 | line-height: 160%; 20 | overflow: hidden; 21 | text-overflow: ellipsis; 22 | display: -webkit-box; 23 | -ms-box-orient: vertical; 24 | -moz-box-orient: vertical; 25 | -webkit-box-orient: vertical; 26 | line-clamp: 3; 27 | -webkit-line-clamp: 3; 28 | `; 29 | 30 | export const TagListContainer = styled.div` 31 | width: 100%; 32 | margin-bottom: 1rem; 33 | `; 34 | -------------------------------------------------------------------------------- /packages/linx-next/containers/LandingPage/LandingPage.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | z-index: 2; 5 | margin: 3rem 20vw; 6 | `; 7 | 8 | export const Header = styled.h1` 9 | font-family: 'Segoe UI', serif; 10 | font-weight: bold; 11 | text-align: center; 12 | line-height: 100%; 13 | font-size: 11rem; 14 | margin-bottom: 6rem; 15 | `; 16 | 17 | export const Info = styled.p` 18 | font-size: 2.5rem; 19 | font-family: Inter, serif; 20 | font-weight: normal; 21 | line-height: 160%; 22 | text-align: center; 23 | margin: 0 1rem; 24 | `; 25 | 26 | export const ButtonContainer = styled.div` 27 | display: flex; 28 | flex-direction: row; 29 | justify-content: space-between; 30 | margin: 4rem 22.5rem; 31 | 32 | & > button { 33 | height: 4.5rem; 34 | } 35 | 36 | & > button:nth-child(2n) { 37 | margin-left: 6rem; 38 | } 39 | `; 40 | -------------------------------------------------------------------------------- /packages/linx-next/pages/api/revalidate.ts: -------------------------------------------------------------------------------- 1 | import type { NextApiRequest, NextApiResponse } from 'next'; 2 | export default async function handler( 3 | req: NextApiRequest, 4 | res: NextApiResponse 5 | ) { 6 | const access_token = req.cookies['access_token']; 7 | const refresh_token = req.cookies['refresh_token']; 8 | 9 | if (!access_token || !refresh_token) { 10 | return res.status(401).json({ message: 'Invalid token' }); 11 | } 12 | 13 | const body = JSON.parse(req.body); 14 | const { refresh_route } = body; 15 | if (!refresh_route || typeof refresh_route !== 'string') 16 | return res.status(400).json({ message: 'Bad Request: No paths specified' }); 17 | 18 | try { 19 | await res.unstable_revalidate(refresh_route); 20 | return res.json({ revalidated: true }); 21 | } catch (err) { 22 | // Catch error and serve 500 23 | return res.status(500).send('Error revalidating'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/Header/Header.styled.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Header = styled.header` 4 | z-index: 10; 5 | display: flex; 6 | align-items: center; 7 | justify-items: center; 8 | justify-content: space-between; 9 | background-color: ${({ theme }) => theme.background}; 10 | padding: 0 5rem; 11 | height: 8rem; 12 | font-family: Inter, serif; 13 | `; 14 | 15 | export const Nav = styled.nav` 16 | display: flex; 17 | text-align: center; 18 | align-items: center; 19 | justify-items: center; 20 | justify-content: space-between; 21 | a { 22 | text-decoration: none; 23 | font-size: 1.8rem; 24 | font-family: Inter, serif; 25 | font-weight: normal; 26 | &:not(:first-child) { 27 | margin-left: 2rem; 28 | } 29 | } 30 | & button { 31 | height: 3rem; 32 | } 33 | & svg { 34 | display: flex; 35 | align-self: center; 36 | } 37 | `; 38 | -------------------------------------------------------------------------------- /packages/linx-next/api/link.ts: -------------------------------------------------------------------------------- 1 | import { Link } from '@prisma/client'; 2 | 3 | export const addLink = async ( 4 | link: string, 5 | description: string, 6 | privacyLevel = 0, 7 | groupId?: string 8 | ): Promise => { 9 | try { 10 | return await ( 11 | await fetch(`${process.env.FRONTEND_URL}/api/link/add`, { 12 | method: 'POST', 13 | body: JSON.stringify({ 14 | link, 15 | description, 16 | privacyLevel, 17 | groupId, 18 | }), 19 | }) 20 | ).json(); 21 | } catch (error) { 22 | console.log('E ' + error); 23 | } 24 | }; 25 | 26 | export const removeLink = async (id: string) => { 27 | try { 28 | return await fetch(`${process.env.FRONTEND_URL}/api/link/del`, { 29 | method: 'POST', 30 | body: JSON.stringify({ 31 | id, 32 | }), 33 | }); 34 | } catch (error) { 35 | console.log('E ' + error); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /packages/linx-next/assets/icons/CheckIcon.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { SVGProps } from 'react'; 3 | 4 | const SvgComponent = (props: SVGProps) => ( 5 | 12 | 18 | 19 | ); 20 | 21 | export default SvgComponent; 22 | -------------------------------------------------------------------------------- /packages/linx-next/layouts/AuthLayout/AuthLayout.tsx: -------------------------------------------------------------------------------- 1 | import SpecialBackground from '../../components/SpecialBackground'; 2 | import React from 'react'; 3 | import styled from 'styled-components'; 4 | import Footer from '../Footer/Footer'; 5 | import Header from '../Header'; 6 | import ServiceRouteLinks from '../../components/ServiceRouteLinks'; 7 | 8 | const Content = styled.div` 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | min-height: calc(100vh - 11rem); 13 | `; 14 | 15 | const Wrapper = styled.div` 16 | display: flex; 17 | flex-direction: column; 18 | min-height: 100vh; 19 | overflow: hidden; 20 | `; 21 | 22 | const AuthLayout = ({ children }) => { 23 | return ( 24 | 25 |
26 | {children} 27 |